| // Copyright 2021 The Chromium Authors. All rights reserved. |
| // 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_BIDDER_WORKLET_H_ |
| #define CONTENT_SERVICES_AUCTION_WORKLET_BIDDER_WORKLET_H_ |
| |
| #include <cmath> |
| #include <list> |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include "base/callback.h" |
| #include "base/containers/flat_map.h" |
| #include "base/containers/unique_ptr_adapters.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/time/time.h" |
| #include "content/services/auction_worklet/auction_v8_helper.h" |
| #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h" |
| #include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h" |
| #include "content/services/auction_worklet/trusted_signals.h" |
| #include "content/services/auction_worklet/trusted_signals_request_manager.h" |
| #include "content/services/auction_worklet/worklet_loader.h" |
| #include "mojo/public/cpp/bindings/pending_associated_receiver.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "mojo/public/cpp/bindings/struct_ptr.h" |
| #include "services/network/public/mojom/url_loader_factory.mojom.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "third_party/blink/public/mojom/interest_group/interest_group_types.mojom-forward.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| #include "v8/include/v8-persistent-handle.h" |
| |
| namespace v8 { |
| class UnboundScript; |
| } // namespace v8 |
| |
| namespace auction_worklet { |
| |
| // Represents a bidder worklet for FLEDGE |
| // (https://github.com/WICG/turtledove/blob/main/FLEDGE.md). Loads and runs the |
| // bidder worklet's Javascript. |
| // |
| // Each worklet object can only be used to load and run a single script's |
| // generateBid() and (if the bid is won) reportWin() once. |
| // |
| // The BidderWorklet is non-threadsafe, and lives entirely on the main / user |
| // sequence. It has an internal V8State object that runs scripts, and is only |
| // used on the V8 sequence. |
| // |
| // TODO(mmenke): Make worklets reuseable. Allow a single BidderWorklet instance |
| // to both be used for two generateBid() calls for different interest groups |
| // with the same owner in the same auction, and to be used to bid for the same |
| // interest group in different auctions. |
| class BidderWorklet : public mojom::BidderWorklet { |
| public: |
| // Deletes the worklet immediately and resets the BidderWorklet's Mojo pipe |
| // with the provided description. See mojo::Receiver::ResetWithReason(). |
| using ClosePipeCallback = |
| base::OnceCallback<void(const std::string& description)>; |
| |
| // Starts loading the worklet script on construction, as well as the trusted |
| // bidding data, if necessary. Will then call the script's generateBid() |
| // function and invoke the callback with the results. Callback will always be |
| // invoked asynchronously, once a bid has been generated or a fatal error has |
| // occurred. |
| // |
| // Data is cached and will be reused by ReportWin(). |
| BidderWorklet(scoped_refptr<AuctionV8Helper> v8_helper, |
| bool pause_for_debugger_on_start, |
| mojo::PendingRemote<network::mojom::URLLoaderFactory> |
| pending_url_loader_factory, |
| const GURL& script_source_url, |
| const absl::optional<GURL>& bidding_wasm_helper_url, |
| const absl::optional<GURL>& trusted_bidding_signals_url, |
| const url::Origin& top_window_origin); |
| explicit BidderWorklet(const BidderWorklet&) = delete; |
| ~BidderWorklet() override; |
| BidderWorklet& operator=(const BidderWorklet&) = delete; |
| |
| // Sets the callback to be invoked on errors which require closing the pipe. |
| // Callback will also immediately delete `this`. Not an argument to |
| // constructor because the Mojo ReceiverId needs to be bound to the callback, |
| // but can only get that after creating the worklet. Must be called |
| // immediately after creating a BidderWorklet. |
| void set_close_pipe_callback(ClosePipeCallback close_pipe_callback) { |
| close_pipe_callback_ = std::move(close_pipe_callback); |
| } |
| |
| int context_group_id_for_testing() const; |
| |
| // mojom::BidderWorklet implementation: |
| void GenerateBid( |
| mojom::BidderWorkletNonSharedParamsPtr bidder_worklet_non_shared_params, |
| const absl::optional<std::string>& auction_signals_json, |
| const absl::optional<std::string>& per_buyer_signals_json, |
| const absl::optional<base::TimeDelta> per_buyer_timeout, |
| const url::Origin& browser_signal_seller_origin, |
| const absl::optional<url::Origin>& browser_signal_top_level_seller_origin, |
| mojom::BiddingBrowserSignalsPtr bidding_browser_signals, |
| base::Time auction_start_time, |
| GenerateBidCallback generate_bid_callback) override; |
| void SendPendingSignalsRequests() override; |
| void ReportWin( |
| const std::string& interest_group_name, |
| const absl::optional<std::string>& auction_signals_json, |
| const absl::optional<std::string>& per_buyer_signals_json, |
| const std::string& seller_signals_json, |
| const GURL& browser_signal_render_url, |
| double browser_signal_bid, |
| double browser_signal_highest_scoring_other_bid, |
| bool browser_signal_made_highest_scoring_other_bid, |
| const url::Origin& browser_signal_seller_origin, |
| const absl::optional<url::Origin>& browser_signal_top_level_seller_origin, |
| uint32_t bidding_signals_data_version, |
| bool has_bidding_signals_data_version, |
| ReportWinCallback report_win_callback) override; |
| void ConnectDevToolsAgent( |
| mojo::PendingAssociatedReceiver<blink::mojom::DevToolsAgent> agent) |
| override; |
| |
| private: |
| struct GenerateBidTask { |
| GenerateBidTask(); |
| ~GenerateBidTask(); |
| |
| mojom::BidderWorkletNonSharedParamsPtr bidder_worklet_non_shared_params; |
| absl::optional<std::string> auction_signals_json; |
| absl::optional<std::string> per_buyer_signals_json; |
| absl::optional<base::TimeDelta> per_buyer_timeout; |
| url::Origin browser_signal_seller_origin; |
| absl::optional<url::Origin> browser_signal_top_level_seller_origin; |
| mojom::BiddingBrowserSignalsPtr bidding_browser_signals; |
| base::Time auction_start_time; |
| |
| // Set while loading is in progress. |
| std::unique_ptr<TrustedSignalsRequestManager::Request> |
| trusted_bidding_signals_request; |
| // Results of loading trusted bidding signals. |
| scoped_refptr<TrustedSignals::Result> trusted_bidding_signals_result; |
| // Error message returned by attempt to load `trusted_bidding_signals_`. |
| // Errors loading it are not fatal, so such errors are cached here and only |
| // reported on bid completion. |
| absl::optional<std::string> trusted_bidding_signals_error_msg; |
| |
| GenerateBidCallback callback; |
| }; |
| |
| using GenerateBidTaskList = std::list<GenerateBidTask>; |
| |
| struct ReportWinTask { |
| ReportWinTask(); |
| ~ReportWinTask(); |
| |
| std::string interest_group_name; |
| absl::optional<std::string> auction_signals_json; |
| absl::optional<std::string> per_buyer_signals_json; |
| std::string seller_signals_json; |
| GURL browser_signal_render_url; |
| double browser_signal_bid; |
| double browser_signal_highest_scoring_other_bid; |
| bool browser_signal_made_highest_scoring_other_bid; |
| url::Origin browser_signal_seller_origin; |
| absl::optional<url::Origin> browser_signal_top_level_seller_origin; |
| absl::optional<uint32_t> bidding_signals_data_version; |
| |
| ReportWinCallback callback; |
| }; |
| |
| using ReportWinTaskList = std::list<ReportWinTask>; |
| |
| // Portion of BidderWorklet that deals with V8 execution, and therefore lives |
| // on the v8 thread --- everything except the constructor must be run there. |
| class V8State { |
| public: |
| V8State(scoped_refptr<AuctionV8Helper> v8_helper, |
| scoped_refptr<AuctionV8Helper::DebugId> debug_id, |
| const GURL& script_source_url, |
| const url::Origin& top_window_origin, |
| const absl::optional<GURL>& wasm_helper_url, |
| const absl::optional<GURL>& trusted_bidding_signals_url, |
| base::WeakPtr<BidderWorklet> parent); |
| |
| void SetWorkletScript(WorkletLoader::Result worklet_script); |
| void SetWasmHelper(WorkletWasmLoader::Result wasm_helper); |
| |
| // These match the mojom GenerateBidCallback / ReportWinCallback functions, |
| // except the errors vectors are passed by value. They're callbacks that |
| // must be invoked on the main sequence, and passed to the V8State. |
| using GenerateBidCallbackInternal = base::OnceCallback<void( |
| mojom::BidderWorkletBidPtr bid, |
| absl::optional<uint32_t> bidding_signals_data_version, |
| absl::optional<GURL> debug_loss_report_url, |
| absl::optional<GURL> debug_win_report_url, |
| std::vector<std::string> error_msgs)>; |
| using ReportWinCallbackInternal = |
| base::OnceCallback<void(absl::optional<GURL> report_url, |
| base::flat_map<std::string, GURL> ad_beacon_map, |
| std::vector<std::string> errors)>; |
| |
| void ReportWin(const std::string& interest_group_name, |
| const absl::optional<std::string>& auction_signals_json, |
| const absl::optional<std::string>& per_buyer_signals_json, |
| const std::string& seller_signals_json, |
| const GURL& browser_signal_render_url, |
| double browser_signal_bid, |
| double browser_signal_highest_scoring_other_bid, |
| bool browser_signal_made_highest_scoring_other_bid, |
| const url::Origin& browser_signal_seller_origin, |
| const absl::optional<url::Origin>& |
| browser_signal_top_level_seller_origin, |
| const absl::optional<uint32_t>& bidding_signals_data_version, |
| ReportWinCallbackInternal callback); |
| |
| void GenerateBid( |
| mojom::BidderWorkletNonSharedParamsPtr bidder_worklet_non_shared_params, |
| const absl::optional<std::string>& auction_signals_json, |
| const absl::optional<std::string>& per_buyer_signals_json, |
| const absl::optional<base::TimeDelta> per_buyer_timeout, |
| const url::Origin& browser_signal_seller_origin, |
| const absl::optional<url::Origin>& |
| browser_signal_top_level_seller_origin, |
| mojom::BiddingBrowserSignalsPtr bidding_browser_signals, |
| base::Time auction_start_time, |
| scoped_refptr<TrustedSignals::Result> trusted_bidding_signals_result, |
| GenerateBidCallbackInternal callback); |
| |
| void ConnectDevToolsAgent( |
| mojo::PendingAssociatedReceiver<blink::mojom::DevToolsAgent> agent); |
| |
| private: |
| friend class base::DeleteHelper<V8State>; |
| ~V8State(); |
| |
| void FinishInit(); |
| |
| void PostReportWinCallbackToUserThread( |
| ReportWinCallbackInternal callback, |
| const absl::optional<GURL>& report_url, |
| base::flat_map<std::string, GURL> ad_beacon_map, |
| std::vector<std::string> errors); |
| |
| void PostErrorBidCallbackToUserThread( |
| GenerateBidCallbackInternal callback, |
| std::vector<std::string> error_msgs = std::vector<std::string>(), |
| absl::optional<GURL> debug_loss_report_url = absl::nullopt); |
| |
| static void PostResumeToUserThread( |
| base::WeakPtr<BidderWorklet> parent, |
| scoped_refptr<base::SequencedTaskRunner> user_thread); |
| |
| const scoped_refptr<AuctionV8Helper> v8_helper_; |
| const scoped_refptr<AuctionV8Helper::DebugId> debug_id_; |
| const base::WeakPtr<BidderWorklet> parent_; |
| const scoped_refptr<base::SequencedTaskRunner> user_thread_; |
| |
| const url::Origin owner_; |
| |
| // Compiled script, not bound to any context. Can be repeatedly bound to |
| // different context and executed, without persisting any state. |
| v8::Global<v8::UnboundScript> worklet_script_; |
| |
| // Loaded WASM module. Can be used to create instances for each context. |
| WorkletWasmLoader::Result wasm_helper_; |
| |
| const GURL script_source_url_; |
| const url::Origin top_window_origin_; |
| const absl::optional<GURL> wasm_helper_url_; |
| const absl::optional<GURL> trusted_bidding_signals_url_; |
| |
| SEQUENCE_CHECKER(v8_sequence_checker_); |
| }; |
| |
| void ResumeIfPaused(); |
| void Start(); |
| |
| void OnScriptDownloaded(WorkletLoader::Result worklet_script, |
| absl::optional<std::string> error_msg); |
| void OnWasmDownloaded(WorkletWasmLoader::Result worklet_script, |
| absl::optional<std::string> error_msg); |
| void RunReadyGenerateBidTasks(); |
| void RunReportWinTasks(); |
| |
| void OnTrustedBiddingSignalsDownloaded( |
| GenerateBidTaskList::iterator task, |
| scoped_refptr<TrustedSignals::Result> result, |
| absl::optional<std::string> error_msg); |
| |
| // Checks if the script has been loaded successfully, and the |
| // TrustedSignals load has finished (successfully or not). If so, calls |
| // generateBid(), and invokes `load_script_and_generate_bid_callback_` with |
| // the resulting bid, if any. May only be called once BidderWorklet has |
| // successfully loaded. |
| void GenerateBidIfReady(GenerateBidTaskList::iterator task); |
| |
| void RunReportWin(ReportWinTaskList::iterator task); |
| |
| // Invokes the `callback` of `task` with the provided values, and removes |
| // `task` from `generate_bid_tasks_`. |
| void DeliverBidCallbackOnUserThread( |
| GenerateBidTaskList::iterator task, |
| mojom::BidderWorkletBidPtr bid, |
| absl::optional<uint32_t> bidding_signals_data_version, |
| absl::optional<GURL> debug_loss_report_url, |
| absl::optional<GURL> debug_win_report_url, |
| std::vector<std::string> error_msgs); |
| |
| // Invokes the `callback` of `task` with the provided values, and removes |
| // `task` from `report_win_tasks_`. |
| void DeliverReportWinOnUserThread( |
| ReportWinTaskList::iterator task, |
| absl::optional<GURL> report_url, |
| base::flat_map<std::string, GURL> ad_beacon_map, |
| std::vector<std::string> errors); |
| |
| // Returns true if unpaused and the script and WASM helper (if needed) have |
| // loaded. |
| bool IsCodeReady() const; |
| |
| scoped_refptr<base::SequencedTaskRunner> v8_runner_; |
| |
| const scoped_refptr<AuctionV8Helper> v8_helper_; |
| const scoped_refptr<AuctionV8Helper::DebugId> debug_id_; |
| mojo::Remote<network::mojom::URLLoaderFactory> url_loader_factory_; |
| |
| bool paused_; |
| |
| // Values shared by all interest groups that the BidderWorklet can be used |
| // for. |
| const GURL script_source_url_; |
| absl::optional<GURL> wasm_helper_url_; |
| |
| // Populated only if `this` was created with a non-null |
| // `trusted_scoring_signals_url`. |
| std::unique_ptr<TrustedSignalsRequestManager> |
| trusted_signals_request_manager_; |
| |
| // Top window origin for the auctions sharing this BidderWorklet. |
| const url::Origin top_window_origin_; |
| |
| // These are deleted once each resource is loaded. |
| std::unique_ptr<WorkletLoader> worklet_loader_; |
| std::unique_ptr<WorkletWasmLoader> wasm_loader_; |
| |
| // Lives on `v8_runner_`. Since it's deleted there via DeleteSoon, tasks can |
| // be safely posted from main thread to it with an Unretained pointer. |
| std::unique_ptr<V8State, base::OnTaskRunnerDeleter> v8_state_; |
| |
| // Pending calls to the corresponding Javascript methods. Only accessed on |
| // main thread, but iterators to their elements are bound to callbacks passed |
| // to the v8 thread, so these need to be std::lists rather than std::vectors. |
| GenerateBidTaskList generate_bid_tasks_; |
| ReportWinTaskList report_win_tasks_; |
| |
| ClosePipeCallback close_pipe_callback_; |
| |
| // Errors that occurred while loading the code, if any. |
| std::vector<std::string> load_code_error_msgs_; |
| |
| SEQUENCE_CHECKER(user_sequence_checker_); |
| |
| // Used when posting callbacks back from V8State. |
| base::WeakPtrFactory<BidderWorklet> weak_ptr_factory_{this}; |
| }; |
| |
| } // namespace auction_worklet |
| |
| #endif // CONTENT_SERVICES_AUCTION_WORKLET_BIDDER_WORKLET_H_ |