1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203
  204
  205
  206
  207
  208
  209
  210
  211
  212
  213
  214
  215
  216
  217
  218
  219
  220
  221
  222
  223
  224
  225
  226
  227
  228
  229
  230
  231
  232
  233
  234
  235
  236
  237
  238
  239
  240
  241
  242
  243
  244
  245
  246
  247
  248
  249
  250
  251
  252
  253
  254

content / services / auction_worklet / direct_from_seller_signals_requester.h [blame]

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CONTENT_SERVICES_AUCTION_WORKLET_DIRECT_FROM_SELLER_SIGNALS_REQUESTER_H_
#define CONTENT_SERVICES_AUCTION_WORKLET_DIRECT_FROM_SELLER_SIGNALS_REQUESTER_H_

#include <list>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <vector>

#include "base/functional/callback.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "base/types/strong_alias.h"
#include "content/common/content_export.h"
#include "services/network/public/mojom/url_loader_factory.mojom-forward.h"
#include "third_party/abseil-cpp/absl/types/variant.h"
#include "url/gurl.h"
#include "v8/include/v8-forward.h"

namespace net {

class HttpResponseHeaders;

}  // namespace net

namespace auction_worklet {

class AuctionDownloader;
class AuctionV8Helper;

// Requests DirectFromSellerSignals from subresource bundles, caching a *single*
// recently-fetched value, such as one of per-buyer, seller, or auction signals.
//
// If multiple incoming requests are made at the same time for the same uncached
// URL, the requests will be coalesced so that only one request is made.
class CONTENT_EXPORT DirectFromSellerSignalsRequester {
 public:
  // Contains the signals loaded from the subresource bundle, a null value and
  // error if fetching or parsing signals failed, or the default-constructed
  // value of null signals with no error.
  //
  // This can be created and destroyed on any sequence, but GetSignals() can
  // only be used on the V8 sequence.
  class CONTENT_EXPORT Result {
   public:
    // Constructs a Result with a null value with no error.
    Result();

    Result(Result&&);
    Result& operator=(Result&&);

    ~Result();

    // Parses the internal JSON string into a v8::Value.
    //
    // If there was an error requesting / parsing the signals, an error string
    // will be appended to `errors`, and a V8 null value will be returned. If
    // the Result was default-constructed, a V8 null value will be returned
    // without updating `errors`.
    v8::Local<v8::Value> GetSignals(AuctionV8Helper& v8_helper,
                                    v8::Local<v8::Context> context,
                                    std::vector<std::string>& errors) const;

    // Returns true if this Result is a null value, and false otherwise. Returns
    // false if Result is an error.
    bool IsNull() const;

   private:
    // Private methods are called by DirectFromSellerSignalsRequester.
    friend DirectFromSellerSignalsRequester;

    // Response JSON strings use thread-safe refcounting to allow multiple
    // Result objects to share the same response without copies.
    class ResponseString : public base::RefCountedThreadSafe<ResponseString> {
     public:
      explicit ResponseString(std::string&& other);

      explicit ResponseString(const ResponseString&) = delete;
      ResponseString& operator=(const ResponseString&) = delete;

      const std::string& value() const { return value_; }

     private:
      friend class base::RefCountedThreadSafe<ResponseString>;
      ~ResponseString();

      const std::string value_;
    };

    // Error strings are small, and are stored directly.
    using ErrorString = base::StrongAlias<class ErrorStringTag, std::string>;

    // A network request always results in a response, or an error.
    //
    // Default-constructed, this will be a null scoped_refptr<ResponseString>.
    using ResponseOrError =
        absl::variant<scoped_refptr<ResponseString>, ErrorString>;

    // Constructs a Result based on the result of the network download.
    Result(GURL signals_url,
           std::unique_ptr<std::string> response_body,
           scoped_refptr<net::HttpResponseHeaders> headers,
           std::optional<std::string> error);

    // The copy constructor is used for internal caching, and for passing
    // results to every pending caller when a coalesced download completes.
    Result(const Result&);
    Result& operator=(const Result&);

    const GURL& signals_url() const { return signals_url_; }

    GURL signals_url_;
    ResponseOrError response_or_error_;
  };

  using DirectFromSellerSignalsRequesterCallback =
      base::OnceCallback<void(Result)>;

  // Represents a single pending request for DirectFromSellerSignals from a
  // consumer. Destroying it cancels the request. All live Requests must be
  // destroyed before the DirectFromSellerSignalsRequester used to create them.
  //
  // It is illegal to destroy other pending Requests when a Request's callback
  // is invoked.
  //
  // Requests must be destroyed on the sequence that called
  // DirectFromSellerSignalsRequester::LoadSignals().
  class CONTENT_EXPORT Request {
   public:
    ~Request();

    Request(const Request&) = delete;
    Request& operator=(const Request&) = delete;

   private:
    friend DirectFromSellerSignalsRequester;

    explicit Request(DirectFromSellerSignalsRequesterCallback callback,
                     DirectFromSellerSignalsRequester& requester,
                     const GURL& signals_url);

    // Methods to run the callback synchronously and asynchronously (by posting
    // to the SequencedTaskRunner::CurrentDefaultHandle).
    //
    // The async version uses WeakPtr, so it will be cancelled if this Request
    // object is destroyed. The sync version should only be used after a
    // download has completed, as these are subject to cancellation if the
    // corresponding AuctionDownloader (owned by this class) is destroyed.
    //
    // Note that the callback may destroy this Request.
    void RunCallbackSync(Result result);
    void RunCallbackAsync(Result result);

    void set_coalesce_iterator(std::list<raw_ptr<Request>>::iterator it) {
      DCHECK_EQ(*it, this);
      maybe_coalesce_iterator_ = it;
    }

    DirectFromSellerSignalsRequesterCallback callback_;

    // Never null.
    raw_ptr<DirectFromSellerSignalsRequester> requester_;

    // For looking up the list used with `maybe_coalesce_iterator_`.
    GURL signals_url_;

    // The iterator to the list of coalesced requests in `coalesced_downloads_`,
    // used for cancellation of callbacks and downloads.
    //
    // NOTE: This can be nullopt if serving from cache, or if the download
    // already completed -- it will have a value when there is still an
    // outstanding request for `signals_url_`.
    std::optional<std::list<raw_ptr<Request>>::iterator>
        maybe_coalesce_iterator_;

    // Must appear after all other members.
    base::WeakPtrFactory<Request> weak_factory_{this};
  };

  DirectFromSellerSignalsRequester();
  ~DirectFromSellerSignalsRequester();

  DirectFromSellerSignalsRequester(const DirectFromSellerSignalsRequester&) =
      delete;
  DirectFromSellerSignalsRequester& operator=(
      const DirectFromSellerSignalsRequester&) = delete;

  // Loads the signals from `signals_url`, providing the results to `callback`.
  //
  // `callback` will always be invoked, and invoked asynchronously, unless the
  // Request is deleted first, cancelling the request.
  std::unique_ptr<Request> LoadSignals(
      network::mojom::URLLoaderFactory& url_loader_factory,
      const GURL& signals_url,
      DirectFromSellerSignalsRequesterCallback callback);

 private:
  // Stores a single active download, along with all callbacks waiting on that
  // download.
  struct CoalescedDownload {
    explicit CoalescedDownload(std::unique_ptr<AuctionDownloader> downloader);
    ~CoalescedDownload();

    CoalescedDownload(const CoalescedDownload&) = delete;
    CoalescedDownload& operator=(const CoalescedDownload&) = delete;
    CoalescedDownload(CoalescedDownload&&);
    CoalescedDownload& operator=(CoalescedDownload&&);

    // The downloader downloading the current `coalesced_downloads_` GURL.
    std::unique_ptr<AuctionDownloader> downloader;

    // A list of all requests whose callbacks should be called when the download
    // completes.
    //
    // Result instances should remove themselves from this list upon their
    // destruction, and if the list is empty, remove the `cached_result_` pair
    // for their `signals_url_`.
    //
    // This guarantees that none of these raw pointers ever point to destroyed
    // Requests.
    std::list<raw_ptr<Request>> requests;
  };

  // Called only when the AuctionDownloader loads new signals.
  //
  // Validates headers, caches the results, and calls all callbacks held in
  // Result objects in `coalesced_downloads_` that are waiting on the URL.
  void OnSignalsDownloaded(GURL signals_url,
                           base::TimeTicks start_time,
                           std::unique_ptr<std::string> response_body,
                           scoped_refptr<net::HttpResponseHeaders> headers,
                           std::optional<std::string> error);

  void OnRequestDestroyed(Request& request);

  // The most recently-downloaded result is cached, along with its URL.
  // Initially, nothing is cached, and the URL is null.
  Result cached_result_;

  // For each URL that is actively downloading, stores its downloader, and the
  // list of all callbacks to be called when the download completes.
  std::map<GURL, CoalescedDownload> coalesced_downloads_;
};

}  // namespace auction_worklet

#endif  // CONTENT_SERVICES_AUCTION_WORKLET_DIRECT_FROM_SELLER_SIGNALS_REQUESTER_H_