| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/services/auction_worklet/seller_worklet.h" |
| |
| #include <stdint.h> |
| |
| #include <cmath> |
| #include <list> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/containers/flat_map.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/strings/strcat.h" |
| #include "base/task/bind_post_task.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/time/time.h" |
| #include "base/trace_event/trace_event.h" |
| #include "content/services/auction_worklet/auction_v8_helper.h" |
| #include "content/services/auction_worklet/context_recycler.h" |
| #include "content/services/auction_worklet/direct_from_seller_signals_requester.h" |
| #include "content/services/auction_worklet/for_debugging_only_bindings.h" |
| #include "content/services/auction_worklet/private_aggregation_bindings.h" |
| #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h" |
| #include "content/services/auction_worklet/public/mojom/seller_worklet.mojom.h" |
| #include "content/services/auction_worklet/register_ad_beacon_bindings.h" |
| #include "content/services/auction_worklet/report_bindings.h" |
| #include "content/services/auction_worklet/shared_storage_bindings.h" |
| #include "content/services/auction_worklet/trusted_signals.h" |
| #include "content/services/auction_worklet/worklet_loader.h" |
| #include "gin/converter.h" |
| #include "gin/dictionary.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/remote.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/common/features.h" |
| #include "third_party/blink/public/common/interest_group/ad_auction_currencies.h" |
| #include "third_party/blink/public/common/interest_group/auction_config.h" |
| #include "third_party/blink/public/mojom/interest_group/interest_group_types.mojom.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| #include "v8/include/v8-context.h" |
| #include "v8/include/v8-forward.h" |
| #include "v8/include/v8-object.h" |
| #include "v8/include/v8-template.h" |
| |
| namespace auction_worklet { |
| |
| namespace { |
| |
| bool InsertPrioritySignals( |
| AuctionV8Helper* v8_helper, |
| base::StringPiece key, |
| const base::flat_map<std::string, double>& priority_signals, |
| v8::Local<v8::Object> object) { |
| v8::Isolate* isolate = v8_helper->isolate(); |
| v8::Local<v8::Object> v8_priority_signals = v8::Object::New(isolate); |
| for (const auto& signal : priority_signals) { |
| if (!v8_helper->InsertValue(signal.first, |
| v8::Number::New(isolate, signal.second), |
| v8_priority_signals)) { |
| return false; |
| } |
| } |
| return v8_helper->InsertValue(key, v8_priority_signals, object); |
| } |
| |
| // Attempts to create an v8 Object from `maybe_promise_buyer_timeouts`. On fatal |
| // error, returns false. Otherwise, writes the result to |
| // `out_per_buyer_timeouts`, which will be left unchanged if there are no times |
| // to write to it. |
| bool CreatePerBuyerTimeoutsObject( |
| v8::Isolate* isolate, |
| const blink::AuctionConfig::MaybePromiseBuyerTimeouts& |
| maybe_promise_buyer_timeouts, |
| v8::Local<v8::Object>& out_per_buyer_timeouts) { |
| DCHECK(!maybe_promise_buyer_timeouts.is_promise()); |
| |
| const blink::AuctionConfig::BuyerTimeouts& buyer_timeouts = |
| maybe_promise_buyer_timeouts.value(); |
| // If there are no times, leave `out_per_buyer_timeouts` empty, and indicate |
| // success. |
| if (!buyer_timeouts.per_buyer_timeouts.has_value() && |
| !buyer_timeouts.all_buyers_timeout.has_value()) { |
| return true; |
| } |
| |
| out_per_buyer_timeouts = v8::Object::New(isolate); |
| gin::Dictionary per_buyer_timeouts_dict(isolate, out_per_buyer_timeouts); |
| |
| if (buyer_timeouts.per_buyer_timeouts.has_value()) { |
| for (const auto& kv : buyer_timeouts.per_buyer_timeouts.value()) { |
| if (!per_buyer_timeouts_dict.Set(kv.first.Serialize(), |
| kv.second.InMilliseconds())) { |
| return false; |
| } |
| } |
| } |
| if (buyer_timeouts.all_buyers_timeout.has_value()) { |
| if (!per_buyer_timeouts_dict.Set( |
| "*", buyer_timeouts.all_buyers_timeout.value().InMilliseconds())) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // Attempts to create an v8 Object from `maybe_promise_buyer_currencies`. On |
| // fatal error, returns false. Otherwise, writes the result to |
| // `out_per_buyer_currencies`, which will be left unchanged if there are no |
| // currencies to write to it. |
| bool CreatePerBuyerCurrenciesObject( |
| v8::Isolate* isolate, |
| const blink::AuctionConfig::MaybePromiseBuyerCurrencies& |
| maybe_promise_buyer_currencies, |
| v8::Local<v8::Object>& out_per_buyer_currencies) { |
| DCHECK(!maybe_promise_buyer_currencies.is_promise()); |
| |
| const blink::AuctionConfig::BuyerCurrencies& buyer_currencies = |
| maybe_promise_buyer_currencies.value(); |
| // If there is nothing specified, leave `out_per_buyer_currencies` empty, and |
| // indicate success. |
| if (!buyer_currencies.per_buyer_currencies.has_value() && |
| !buyer_currencies.all_buyers_currency.has_value()) { |
| return true; |
| } |
| |
| out_per_buyer_currencies = v8::Object::New(isolate); |
| gin::Dictionary per_buyer_currencies_dict(isolate, out_per_buyer_currencies); |
| |
| if (buyer_currencies.per_buyer_currencies.has_value()) { |
| for (const auto& kv : buyer_currencies.per_buyer_currencies.value()) { |
| if (!per_buyer_currencies_dict.Set(kv.first.Serialize(), |
| kv.second.currency_code())) { |
| return false; |
| } |
| } |
| } |
| if (buyer_currencies.all_buyers_currency.has_value()) { |
| if (!per_buyer_currencies_dict.Set( |
| "*", buyer_currencies.all_buyers_currency->currency_code())) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // ### some duplication with same in interest_group_auction.cc |
| bool IsValidBid(double bid) { |
| return std::isfinite(bid) && (bid > 0.0); |
| } |
| |
| // Converts `auction_config` back to JSON format, and appends to args. |
| // Returns true if conversion succeeded. |
| // |
| // The resulting object will look something like this (based on example from |
| // explainer): |
| // |
| // { |
| // 'seller': 'https://www.example-ssp.com/', |
| // 'decisionLogicUrl': 'https://www.example-ssp.com/seller.js', |
| // 'trustedScoringSignalsUrl': ..., |
| // 'interestGroupBuyers': ['https://www.example-dsp.com', 'https://buyer2.com', |
| // ...], 'auctionSignals': {...}, 'sellerSignals': {...}, 'sellerTimeout': 100, |
| // 'perBuyerSignals': {'https://www.example-dsp.com': {...}, |
| // 'https://www.another-buyer.com': {...}, |
| // ...}, |
| // 'perBuyerTimeouts': {'https://www.example-dsp.com': 50, |
| // 'https://www.another-buyer.com': 200, |
| // '*': 150, |
| // ...}, |
| // 'perBuyerPrioritySignals': {'https://www.example-dsp.com': {...}, |
| // 'https://www.another-buyer.com': {...}, |
| // '*': {...}, |
| // ...}, |
| // } |
| bool AppendAuctionConfig(AuctionV8Helper* v8_helper, |
| v8::Local<v8::Context> context, |
| const GURL& decision_logic_url, |
| const absl::optional<GURL>& trusted_coding_signals_url, |
| const absl::optional<uint16_t> experiment_group_id, |
| const blink::AuctionConfig::NonSharedParams& |
| auction_ad_config_non_shared_params, |
| std::vector<v8::Local<v8::Value>>* args) { |
| v8::Isolate* isolate = v8_helper->isolate(); |
| v8::Local<v8::Object> auction_config_value = v8::Object::New(isolate); |
| gin::Dictionary auction_config_dict(isolate, auction_config_value); |
| if (!auction_config_dict.Set( |
| "seller", url::Origin::Create(decision_logic_url).Serialize()) || |
| !auction_config_dict.Set("decisionLogicUrl", decision_logic_url.spec()) || |
| (trusted_coding_signals_url && |
| !auction_config_dict.Set("trustedScoringSignalsUrl", |
| trusted_coding_signals_url->spec()))) { |
| return false; |
| } |
| |
| if (auction_ad_config_non_shared_params.interest_group_buyers) { |
| std::vector<v8::Local<v8::Value>> interest_group_buyers; |
| for (const url::Origin& buyer : |
| *auction_ad_config_non_shared_params.interest_group_buyers) { |
| v8::Local<v8::String> v8_buyer; |
| if (!v8_helper->CreateUtf8String(buyer.Serialize()).ToLocal(&v8_buyer)) |
| return false; |
| interest_group_buyers.push_back(v8_buyer); |
| } |
| auction_config_dict.Set("interestGroupBuyers", interest_group_buyers); |
| } |
| |
| DCHECK(!auction_ad_config_non_shared_params.auction_signals.is_promise()); |
| if (auction_ad_config_non_shared_params.auction_signals.value() && |
| !v8_helper->InsertJsonValue( |
| context, "auctionSignals", |
| *auction_ad_config_non_shared_params.auction_signals.value(), |
| auction_config_value)) { |
| return false; |
| } |
| |
| DCHECK(!auction_ad_config_non_shared_params.seller_signals.is_promise()); |
| if (auction_ad_config_non_shared_params.seller_signals.value() && |
| !v8_helper->InsertJsonValue( |
| context, "sellerSignals", |
| *auction_ad_config_non_shared_params.seller_signals.value(), |
| auction_config_value)) { |
| return false; |
| } |
| |
| if (auction_ad_config_non_shared_params.seller_timeout.has_value() && |
| !v8_helper->InsertJsonValue( |
| context, "sellerTimeout", |
| base::NumberToString( |
| auction_ad_config_non_shared_params.seller_timeout.value() |
| .InMilliseconds()), |
| auction_config_value)) { |
| return false; |
| } |
| |
| DCHECK(!auction_ad_config_non_shared_params.per_buyer_signals.is_promise()); |
| if (auction_ad_config_non_shared_params.per_buyer_signals.value() |
| .has_value()) { |
| v8::Local<v8::Object> per_buyer_value = v8::Object::New(isolate); |
| for (const auto& kv : |
| auction_ad_config_non_shared_params.per_buyer_signals.value() |
| .value()) { |
| if (!v8_helper->InsertJsonValue(context, kv.first.Serialize(), kv.second, |
| per_buyer_value)) { |
| return false; |
| } |
| } |
| auction_config_dict.Set("perBuyerSignals", per_buyer_value); |
| } |
| |
| v8::Local<v8::Object> per_buyer_timeouts; |
| if (!CreatePerBuyerTimeoutsObject( |
| isolate, auction_ad_config_non_shared_params.buyer_timeouts, |
| per_buyer_timeouts)) { |
| return false; |
| } |
| if (!per_buyer_timeouts.IsEmpty()) { |
| auction_config_dict.Set("perBuyerTimeouts", per_buyer_timeouts); |
| } |
| |
| v8::Local<v8::Object> per_buyer_cumulative_timeouts; |
| if (!CreatePerBuyerTimeoutsObject( |
| isolate, |
| auction_ad_config_non_shared_params.buyer_cumulative_timeouts, |
| per_buyer_cumulative_timeouts)) { |
| return false; |
| } |
| if (!per_buyer_cumulative_timeouts.IsEmpty()) { |
| auction_config_dict.Set("perBuyerCumulativeTimeouts", |
| per_buyer_cumulative_timeouts); |
| } |
| |
| v8::Local<v8::Object> per_buyer_currencies; |
| if (!CreatePerBuyerCurrenciesObject( |
| isolate, auction_ad_config_non_shared_params.buyer_currencies, |
| per_buyer_currencies)) { |
| return false; |
| } |
| |
| if (!per_buyer_currencies.IsEmpty()) { |
| auction_config_dict.Set("perBuyerCurrencies", per_buyer_currencies); |
| } |
| |
| if (auction_ad_config_non_shared_params.seller_currency.has_value()) { |
| auction_config_dict.Set( |
| "sellerCurrency", |
| auction_ad_config_non_shared_params.seller_currency->currency_code()); |
| } |
| |
| if (auction_ad_config_non_shared_params.per_buyer_priority_signals || |
| auction_ad_config_non_shared_params.all_buyers_priority_signals) { |
| v8::Local<v8::Object> per_buyer_priority_signals = v8::Object::New(isolate); |
| if (auction_ad_config_non_shared_params.per_buyer_priority_signals) { |
| for (const auto& kv : |
| *auction_ad_config_non_shared_params.per_buyer_priority_signals) { |
| if (!InsertPrioritySignals(v8_helper, kv.first.Serialize(), kv.second, |
| per_buyer_priority_signals)) { |
| return false; |
| } |
| } |
| } |
| if (auction_ad_config_non_shared_params.all_buyers_priority_signals) { |
| if (!InsertPrioritySignals( |
| v8_helper, "*", |
| *auction_ad_config_non_shared_params.all_buyers_priority_signals, |
| per_buyer_priority_signals)) { |
| return false; |
| } |
| } |
| auction_config_dict.Set("perBuyerPrioritySignals", |
| per_buyer_priority_signals); |
| } |
| |
| const auto& component_auctions = |
| auction_ad_config_non_shared_params.component_auctions; |
| if (!component_auctions.empty()) { |
| std::vector<v8::Local<v8::Value>> component_auction_vector; |
| for (const auto& component_auction : component_auctions) { |
| if (!AppendAuctionConfig( |
| v8_helper, context, component_auction.decision_logic_url, |
| component_auction.trusted_scoring_signals_url, |
| experiment_group_id, component_auction.non_shared_params, |
| &component_auction_vector)) { |
| return false; |
| } |
| } |
| v8::Maybe<bool> result = auction_config_value->Set( |
| context, v8_helper->CreateStringFromLiteral("componentAuctions"), |
| v8::Array::New(isolate, component_auction_vector.data(), |
| component_auction_vector.size())); |
| if (result.IsNothing() || !result.FromJust()) |
| return false; |
| } |
| |
| if (experiment_group_id.has_value()) { |
| auction_config_dict.Set("experimentGroupId", |
| static_cast<unsigned>(experiment_group_id.value())); |
| } |
| |
| args->push_back(std::move(auction_config_value)); |
| return true; |
| } |
| |
| // Adds the top-level/component seller origin from |
| // `browser_signals_other_seller` to `browser_signals_dict`. Does nothing if |
| // `browser_signals_other_seller` is null. Returns false on error. |
| bool AddOtherSeller( |
| mojom::ComponentAuctionOtherSeller* browser_signals_other_seller, |
| gin::Dictionary& browser_signals_dict) { |
| if (!browser_signals_other_seller) |
| return true; |
| if (browser_signals_other_seller->is_top_level_seller()) { |
| return browser_signals_dict.Set( |
| "topLevelSeller", |
| browser_signals_other_seller->get_top_level_seller().Serialize()); |
| } |
| DCHECK(browser_signals_other_seller->is_component_seller()); |
| return browser_signals_dict.Set( |
| "componentSeller", |
| browser_signals_other_seller->get_component_seller().Serialize()); |
| } |
| |
| // Converts reject reason string to corresponding mojom enum. |
| absl::optional<mojom::RejectReason> RejectReasonStringToEnum( |
| const std::string& reason) { |
| if (reason == "not-available") { |
| return mojom::RejectReason::kNotAvailable; |
| } else if (reason == "invalid-bid") { |
| return mojom::RejectReason::kInvalidBid; |
| } else if (reason == "bid-below-auction-floor") { |
| return mojom::RejectReason::kBidBelowAuctionFloor; |
| } else if (reason == "pending-approval-by-exchange") { |
| return mojom::RejectReason::kPendingApprovalByExchange; |
| } else if (reason == "disapproved-by-exchange") { |
| return mojom::RejectReason::kDisapprovedByExchange; |
| } else if (reason == "blocked-by-publisher") { |
| return mojom::RejectReason::kBlockedByPublisher; |
| } else if (reason == "language-exclusions") { |
| return mojom::RejectReason::kLanguageExclusions; |
| } else if (reason == "category-exclusions") { |
| return mojom::RejectReason::kCategoryExclusions; |
| } |
| // Invalid (out of range) reject reason. |
| return absl::nullopt; |
| } |
| |
| // Checks `provided_currency` against both `expected_seller_currency` and |
| // `component_expect_bid_currency`, formatting an error if needed, with |
| // `bid_label` identifying the bid being checked. |
| // Returns true on success. |
| bool VerifySellerCurrency( |
| absl::optional<blink::AdCurrency> provided_currency, |
| absl::optional<blink::AdCurrency> expected_seller_currency, |
| absl::optional<blink::AdCurrency> component_expect_bid_currency, |
| const GURL& script_url, |
| base::StringPiece bid_label, |
| std::vector<std::string>& errors_out) { |
| if (!blink::VerifyAdCurrencyCode(expected_seller_currency, |
| provided_currency)) { |
| errors_out.push_back(base::StrCat( |
| {script_url.spec(), " scoreAd() ", bid_label, |
| " mismatch vs own sellerCurrency, expected '", |
| blink::PrintableAdCurrency(expected_seller_currency), "' got '", |
| blink::PrintableAdCurrency(provided_currency), "'."})); |
| return false; |
| } |
| if (!blink::VerifyAdCurrencyCode(component_expect_bid_currency, |
| provided_currency)) { |
| errors_out.push_back(base::StrCat( |
| {script_url.spec(), " scoreAd() ", bid_label, |
| " mismatch in component auction " |
| "vs parent auction bidderCurrency, expected '", |
| blink::PrintableAdCurrency(component_expect_bid_currency), "' got '", |
| blink::PrintableAdCurrency(provided_currency), "'."})); |
| return false; |
| } |
| return true; |
| } |
| |
| } // namespace |
| |
| SellerWorklet::SellerWorklet( |
| scoped_refptr<AuctionV8Helper> v8_helper, |
| mojo::PendingRemote<mojom::AuctionSharedStorageHost> |
| shared_storage_host_remote, |
| bool pause_for_debugger_on_start, |
| mojo::PendingRemote<network::mojom::URLLoaderFactory> |
| pending_url_loader_factory, |
| const GURL& decision_logic_url, |
| const absl::optional<GURL>& trusted_scoring_signals_url, |
| const url::Origin& top_window_origin, |
| mojom::AuctionWorkletPermissionsPolicyStatePtr permissions_policy_state, |
| absl::optional<uint16_t> experiment_group_id) |
| : v8_runner_(v8_helper->v8_runner()), |
| v8_helper_(std::move(v8_helper)), |
| debug_id_( |
| base::MakeRefCounted<AuctionV8Helper::DebugId>(v8_helper_.get())), |
| url_loader_factory_(std::move(pending_url_loader_factory)), |
| script_source_url_(decision_logic_url), |
| trusted_signals_request_manager_( |
| trusted_scoring_signals_url |
| ? std::make_unique<TrustedSignalsRequestManager>( |
| TrustedSignalsRequestManager::Type::kScoringSignals, |
| url_loader_factory_.get(), |
| /*automatically_send_requests=*/true, |
| top_window_origin, |
| *trusted_scoring_signals_url, |
| /*experiment_group_id=*/experiment_group_id, |
| v8_helper_.get()) |
| : nullptr), |
| v8_state_(nullptr, base::OnTaskRunnerDeleter(v8_runner_)) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_); |
| |
| v8_state_ = std::unique_ptr<V8State, base::OnTaskRunnerDeleter>( |
| new V8State(v8_helper_, debug_id_, std::move(shared_storage_host_remote), |
| decision_logic_url, trusted_scoring_signals_url, |
| top_window_origin, std::move(permissions_policy_state), |
| experiment_group_id, weak_ptr_factory_.GetWeakPtr()), |
| base::OnTaskRunnerDeleter(v8_runner_)); |
| |
| paused_ = pause_for_debugger_on_start; |
| if (!paused_) |
| Start(); |
| } |
| |
| SellerWorklet::~SellerWorklet() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_); |
| debug_id_->AbortDebuggerPauses(); |
| } |
| |
| int SellerWorklet::context_group_id_for_testing() const { |
| return debug_id_->context_group_id(); |
| } |
| |
| void SellerWorklet::ScoreAd( |
| const std::string& ad_metadata_json, |
| double bid, |
| const absl::optional<blink::AdCurrency>& bid_currency, |
| const blink::AuctionConfig::NonSharedParams& |
| auction_ad_config_non_shared_params, |
| const absl::optional<GURL>& direct_from_seller_seller_signals, |
| const absl::optional<GURL>& direct_from_seller_auction_signals, |
| mojom::ComponentAuctionOtherSellerPtr browser_signals_other_seller, |
| const absl::optional<blink::AdCurrency>& component_expect_bid_currency, |
| const url::Origin& browser_signal_interest_group_owner, |
| const GURL& browser_signal_render_url, |
| const std::vector<GURL>& browser_signal_ad_components, |
| uint32_t browser_signal_bidding_duration_msecs, |
| const absl::optional<base::TimeDelta> seller_timeout, |
| uint64_t trace_id, |
| mojo::PendingRemote<auction_worklet::mojom::ScoreAdClient> |
| score_ad_client) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_); |
| score_ad_tasks_.emplace_front(); |
| |
| auto score_ad_task = score_ad_tasks_.begin(); |
| score_ad_task->ad_metadata_json = ad_metadata_json; |
| score_ad_task->bid = bid; |
| score_ad_task->bid_currency = bid_currency; |
| score_ad_task->auction_ad_config_non_shared_params = |
| auction_ad_config_non_shared_params; |
| score_ad_task->browser_signals_other_seller = |
| std::move(browser_signals_other_seller); |
| score_ad_task->component_expect_bid_currency = component_expect_bid_currency; |
| score_ad_task->browser_signal_interest_group_owner = |
| browser_signal_interest_group_owner; |
| score_ad_task->browser_signal_render_url = browser_signal_render_url; |
| for (const GURL& url : browser_signal_ad_components) { |
| score_ad_task->browser_signal_ad_components.emplace_back(url.spec()); |
| } |
| score_ad_task->browser_signal_bidding_duration_msecs = |
| browser_signal_bidding_duration_msecs; |
| score_ad_task->seller_timeout = seller_timeout; |
| score_ad_task->trace_id = trace_id; |
| score_ad_task->score_ad_client.Bind(std::move(score_ad_client)); |
| |
| // Deleting `score_ad_task` will destroy `score_ad_client` and thus |
| // abort this callback, so it's safe to use Unretained(this) and |
| // `score_ad_task` here. |
| score_ad_task->score_ad_client.set_disconnect_handler( |
| base::BindOnce(&SellerWorklet::OnScoreAdClientDestroyed, |
| base::Unretained(this), score_ad_task)); |
| |
| if (direct_from_seller_seller_signals) { |
| // Deleting `score_ad_task` will destroy |
| // `direct_from_seller_request_seller_signals` and thus abort this callback, |
| // so it's safe to use Unretained(this) and `score_ad_task` here. |
| score_ad_task->direct_from_seller_request_seller_signals = |
| direct_from_seller_requester_seller_signals_.LoadSignals( |
| *url_loader_factory_, *direct_from_seller_seller_signals, |
| base::BindOnce(&SellerWorklet:: |
| OnDirectFromSellerSellerSignalsDownloadedScoreAd, |
| base::Unretained(this), score_ad_task)); |
| } else { |
| score_ad_task->direct_from_seller_result_seller_signals = |
| DirectFromSellerSignalsRequester::Result(); |
| } |
| |
| if (direct_from_seller_auction_signals) { |
| // Deleting `score_ad_task` will destroy |
| // `direct_from_seller_request_auction_signals` and thus abort this |
| // callback, so it's safe to use Unretained(this) and `score_ad_task` here. |
| score_ad_task->direct_from_seller_request_auction_signals = |
| direct_from_seller_requester_auction_signals_.LoadSignals( |
| *url_loader_factory_, *direct_from_seller_auction_signals, |
| base::BindOnce( |
| &SellerWorklet:: |
| OnDirectFromSellerAuctionSignalsDownloadedScoreAd, |
| base::Unretained(this), score_ad_task)); |
| } else { |
| score_ad_task->direct_from_seller_result_auction_signals = |
| DirectFromSellerSignalsRequester::Result(); |
| } |
| |
| score_ad_task->trace_wait_deps_start = base::TimeTicks::Now(); |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("fledge", "wait_score_ad_deps", trace_id); |
| |
| // If `trusted_signals_request_manager_` exists, there's a trusted scoring |
| // signals URL which needs to be fetched before the auction can be run. |
| if (trusted_signals_request_manager_) { |
| score_ad_task->trusted_scoring_signals_request = |
| trusted_signals_request_manager_->RequestScoringSignals( |
| browser_signal_render_url, |
| score_ad_task->browser_signal_ad_components, |
| base::BindOnce(&SellerWorklet::OnTrustedScoringSignalsDownloaded, |
| base::Unretained(this), score_ad_task)); |
| return; |
| } |
| |
| ScoreAdIfReady(score_ad_task); |
| } |
| |
| void SellerWorklet::SendPendingSignalsRequests() { |
| if (trusted_signals_request_manager_) |
| trusted_signals_request_manager_->StartBatchedTrustedSignalsRequest(); |
| } |
| |
| void SellerWorklet::ReportResult( |
| const blink::AuctionConfig::NonSharedParams& |
| auction_ad_config_non_shared_params, |
| const absl::optional<GURL>& direct_from_seller_seller_signals, |
| const absl::optional<GURL>& direct_from_seller_auction_signals, |
| mojom::ComponentAuctionOtherSellerPtr browser_signals_other_seller, |
| const url::Origin& browser_signal_interest_group_owner, |
| const absl::optional<std::string>& |
| browser_signal_buyer_and_seller_reporting_id, |
| const GURL& browser_signal_render_url, |
| double browser_signal_bid, |
| const absl::optional<blink::AdCurrency>& browser_signal_bid_currency, |
| double browser_signal_desirability, |
| double browser_signal_highest_scoring_other_bid, |
| const absl::optional<blink::AdCurrency>& |
| browser_signal_highest_scoring_other_bid_currency, |
| auction_worklet::mojom::ComponentAuctionReportResultParamsPtr |
| browser_signals_component_auction_report_result_params, |
| uint32_t scoring_signals_data_version, |
| bool has_scoring_signals_data_version, |
| uint64_t trace_id, |
| ReportResultCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_); |
| // `browser_signals_component_auction_report_result_params` should only be |
| // populated for sellers in component auctions, which are the only case where |
| // `browser_signals_other_seller` is a top-level seller. |
| DCHECK_EQ(browser_signals_other_seller && |
| browser_signals_other_seller->is_top_level_seller(), |
| !browser_signals_component_auction_report_result_params.is_null()); |
| |
| report_result_tasks_.emplace_front(); |
| |
| auto report_result_task = report_result_tasks_.begin(); |
| |
| report_result_task->auction_ad_config_non_shared_params = |
| auction_ad_config_non_shared_params; |
| report_result_task->browser_signals_other_seller = |
| std::move(browser_signals_other_seller); |
| report_result_task->browser_signal_interest_group_owner = |
| browser_signal_interest_group_owner; |
| report_result_task->browser_signal_buyer_and_seller_reporting_id = |
| browser_signal_buyer_and_seller_reporting_id; |
| report_result_task->browser_signal_render_url = browser_signal_render_url; |
| report_result_task->browser_signal_bid = browser_signal_bid; |
| report_result_task->browser_signal_bid_currency = |
| std::move(browser_signal_bid_currency); |
| report_result_task->browser_signal_desirability = browser_signal_desirability; |
| report_result_task->browser_signal_highest_scoring_other_bid = |
| browser_signal_highest_scoring_other_bid; |
| report_result_task->browser_signal_highest_scoring_other_bid_currency = |
| browser_signal_highest_scoring_other_bid_currency; |
| report_result_task->browser_signals_component_auction_report_result_params = |
| std::move(browser_signals_component_auction_report_result_params); |
| report_result_task->trace_id = trace_id; |
| |
| if (has_scoring_signals_data_version) { |
| report_result_task->scoring_signals_data_version = |
| scoring_signals_data_version; |
| } |
| report_result_task->callback = std::move(callback); |
| |
| if (direct_from_seller_seller_signals) { |
| // Deleting `report_result_task` will destroy |
| // `direct_from_seller_request_seller_signals` and thus abort this callback, |
| // so it's safe to use Unretained(this) and `report_result_task` here. |
| report_result_task->direct_from_seller_request_seller_signals = |
| direct_from_seller_requester_seller_signals_.LoadSignals( |
| *url_loader_factory_, *direct_from_seller_seller_signals, |
| base::BindOnce( |
| &SellerWorklet:: |
| OnDirectFromSellerSellerSignalsDownloadedReportResult, |
| base::Unretained(this), report_result_task)); |
| } else { |
| report_result_task->direct_from_seller_result_seller_signals = |
| DirectFromSellerSignalsRequester::Result(); |
| } |
| |
| if (direct_from_seller_auction_signals) { |
| // Deleting `report_result_task` will destroy |
| // `direct_from_seller_request_auction_signals` and thus abort this |
| // callback, so it's safe to use Unretained(this) and `report_result_task` |
| // here. |
| report_result_task->direct_from_seller_request_auction_signals = |
| direct_from_seller_requester_auction_signals_.LoadSignals( |
| *url_loader_factory_, *direct_from_seller_auction_signals, |
| base::BindOnce( |
| &SellerWorklet:: |
| OnDirectFromSellerAuctionSignalsDownloadedReportResult, |
| base::Unretained(this), report_result_task)); |
| } else { |
| report_result_task->direct_from_seller_result_auction_signals = |
| DirectFromSellerSignalsRequester::Result(); |
| } |
| |
| report_result_task->trace_wait_deps_start = base::TimeTicks::Now(); |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("fledge", "wait_report_result_deps", |
| trace_id); |
| RunReportResultIfReady(report_result_task); |
| } |
| |
| void SellerWorklet::ConnectDevToolsAgent( |
| mojo::PendingAssociatedReceiver<blink::mojom::DevToolsAgent> agent) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_); |
| v8_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&V8State::ConnectDevToolsAgent, |
| base::Unretained(v8_state_.get()), std::move(agent))); |
| } |
| |
| SellerWorklet::ScoreAdTask::ScoreAdTask() = default; |
| SellerWorklet::ScoreAdTask::~ScoreAdTask() = default; |
| |
| SellerWorklet::ReportResultTask::ReportResultTask() = default; |
| SellerWorklet::ReportResultTask::~ReportResultTask() = default; |
| |
| SellerWorklet::V8State::V8State( |
| scoped_refptr<AuctionV8Helper> v8_helper, |
| scoped_refptr<AuctionV8Helper::DebugId> debug_id, |
| mojo::PendingRemote<mojom::AuctionSharedStorageHost> |
| shared_storage_host_remote, |
| const GURL& decision_logic_url, |
| const absl::optional<GURL>& trusted_scoring_signals_url, |
| const url::Origin& top_window_origin, |
| mojom::AuctionWorkletPermissionsPolicyStatePtr permissions_policy_state, |
| absl::optional<uint16_t> experiment_group_id, |
| base::WeakPtr<SellerWorklet> parent) |
| : v8_helper_(std::move(v8_helper)), |
| debug_id_(debug_id), |
| parent_(std::move(parent)), |
| user_thread_(base::SequencedTaskRunner::GetCurrentDefault()), |
| decision_logic_url_(decision_logic_url), |
| trusted_scoring_signals_url_(trusted_scoring_signals_url), |
| top_window_origin_(top_window_origin), |
| permissions_policy_state_(std::move(permissions_policy_state)), |
| experiment_group_id_(experiment_group_id) { |
| DETACH_FROM_SEQUENCE(v8_sequence_checker_); |
| v8_helper_->v8_runner()->PostTask( |
| FROM_HERE, base::BindOnce(&V8State::FinishInit, base::Unretained(this), |
| std::move(shared_storage_host_remote))); |
| } |
| |
| void SellerWorklet::V8State::SetWorkletScript( |
| WorkletLoader::Result worklet_script) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_); |
| worklet_script_ = WorkletLoader::TakeScript(std::move(worklet_script)); |
| } |
| |
| void SellerWorklet::V8State::ScoreAd( |
| const std::string& ad_metadata_json, |
| double bid, |
| const absl::optional<blink::AdCurrency>& bid_currency, |
| const blink::AuctionConfig::NonSharedParams& |
| auction_ad_config_non_shared_params, |
| DirectFromSellerSignalsRequester::Result |
| direct_from_seller_result_seller_signals, |
| DirectFromSellerSignalsRequester::Result |
| direct_from_seller_result_auction_signals, |
| scoped_refptr<TrustedSignals::Result> trusted_scoring_signals, |
| mojom::ComponentAuctionOtherSellerPtr browser_signals_other_seller, |
| const absl::optional<blink::AdCurrency>& component_expect_bid_currency, |
| const url::Origin& browser_signal_interest_group_owner, |
| const GURL& browser_signal_render_url, |
| const std::vector<std::string>& browser_signal_ad_components, |
| uint32_t browser_signal_bidding_duration_msecs, |
| const absl::optional<base::TimeDelta> seller_timeout, |
| uint64_t trace_id, |
| base::ScopedClosureRunner cleanup_score_ad_task, |
| ScoreAdCallbackInternal callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_); |
| base::TimeTicks start = base::TimeTicks::Now(); |
| |
| TRACE_EVENT_NESTABLE_ASYNC_END0("fledge", "post_v8_task", trace_id); |
| |
| // Don't need to run `cleanup_score_ad_task` if this method is invoked; |
| // it's bound to the closure to clean things up if this method got cancelled. |
| cleanup_score_ad_task.ReplaceClosure(base::OnceClosure()); |
| |
| AuctionV8Helper::FullIsolateScope isolate_scope(v8_helper_.get()); |
| v8::Isolate* isolate = v8_helper_->isolate(); |
| |
| // Short lived context, to avoid leaking data at global scope between either |
| // repeated calls to this worklet, or to calls to any other worklet. |
| ContextRecycler context_recycler(v8_helper_.get()); |
| ContextRecyclerScope context_recycler_scope(context_recycler); |
| v8::Local<v8::Context> context = context_recycler_scope.GetContext(); |
| |
| std::vector<v8::Local<v8::Value>> args; |
| if (!v8_helper_->AppendJsonValue(context, ad_metadata_json, &args)) { |
| PostScoreAdCallbackToUserThreadOnError( |
| std::move(callback), |
| /*errors=*/std::vector<std::string>()); |
| return; |
| } |
| |
| args.push_back(gin::ConvertToV8(isolate, bid)); |
| |
| if (!AppendAuctionConfig(v8_helper_.get(), context, decision_logic_url_, |
| trusted_scoring_signals_url_, experiment_group_id_, |
| auction_ad_config_non_shared_params, &args)) { |
| PostScoreAdCallbackToUserThreadOnError( |
| std::move(callback), |
| /*errors=*/std::vector<std::string>()); |
| return; |
| } |
| |
| v8::Local<v8::Value> trusted_scoring_signals_value; |
| absl::optional<uint32_t> scoring_signals_data_version; |
| if (trusted_scoring_signals) { |
| trusted_scoring_signals_value = trusted_scoring_signals->GetScoringSignals( |
| v8_helper_.get(), context, browser_signal_render_url, |
| browser_signal_ad_components); |
| scoring_signals_data_version = trusted_scoring_signals->GetDataVersion(); |
| } else { |
| trusted_scoring_signals_value = v8::Null(isolate); |
| } |
| args.push_back(trusted_scoring_signals_value); |
| |
| v8::Local<v8::Object> browser_signals = v8::Object::New(isolate); |
| gin::Dictionary browser_signals_dict(isolate, browser_signals); |
| if (!browser_signals_dict.Set("topWindowHostname", |
| top_window_origin_.host()) || |
| !AddOtherSeller(browser_signals_other_seller.get(), |
| browser_signals_dict) || |
| !browser_signals_dict.Set( |
| "interestGroupOwner", |
| browser_signal_interest_group_owner.Serialize()) || |
| !browser_signals_dict.Set("renderUrl", |
| browser_signal_render_url.spec()) || |
| !browser_signals_dict.Set("biddingDurationMsec", |
| browser_signal_bidding_duration_msecs) || |
| !browser_signals_dict.Set("bidCurrency", |
| blink::PrintableAdCurrency(bid_currency)) || |
| (scoring_signals_data_version.has_value() && |
| !browser_signals_dict.Set("dataVersion", |
| scoring_signals_data_version.value()))) { |
| PostScoreAdCallbackToUserThreadOnError( |
| std::move(callback), |
| /*errors=*/std::vector<std::string>()); |
| return; |
| } |
| if (!browser_signal_ad_components.empty()) { |
| if (!browser_signals_dict.Set("adComponents", |
| browser_signal_ad_components)) { |
| PostScoreAdCallbackToUserThreadOnError( |
| std::move(callback), |
| /*errors=*/std::vector<std::string>()); |
| return; |
| } |
| } |
| args.push_back(browser_signals); |
| |
| v8::Local<v8::Object> direct_from_seller_signals = v8::Object::New(isolate); |
| gin::Dictionary direct_from_seller_signals_dict(isolate, |
| direct_from_seller_signals); |
| std::vector<std::string> errors_out; |
| v8::Local<v8::Value> seller_signals = |
| direct_from_seller_result_seller_signals.GetSignals(*v8_helper_, context, |
| errors_out); |
| v8::Local<v8::Value> auction_signals = |
| direct_from_seller_result_auction_signals.GetSignals(*v8_helper_, context, |
| errors_out); |
| if (!direct_from_seller_signals_dict.Set("sellerSignals", seller_signals) || |
| !direct_from_seller_signals_dict.Set("auctionSignals", auction_signals)) { |
| PostScoreAdCallbackToUserThreadOnError(std::move(callback), |
| /*errors=*/std::move(errors_out)); |
| return; |
| } |
| args.push_back(direct_from_seller_signals); |
| |
| v8::Local<v8::Value> score_ad_result; |
| v8_helper_->MaybeTriggerInstrumentationBreakpoint( |
| *debug_id_, "beforeSellerWorkletScoringStart"); |
| |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("fledge", "score_ad", trace_id); |
| v8::Local<v8::UnboundScript> unbound_worklet_script = |
| worklet_script_.Get(isolate); |
| bool success = |
| v8_helper_->RunScript(context, unbound_worklet_script, debug_id_.get(), |
| seller_timeout, errors_out); |
| if (!success) { |
| TRACE_EVENT_NESTABLE_ASYNC_END0("fledge", "score_ad", trace_id); |
| // Keep debug loss reports and Private Aggregation API requests since |
| // `scoreAd()` might use them to detect script timeout or failures. |
| PostScoreAdCallbackToUserThread( |
| std::move(callback), /*score=*/0, |
| /*reject_reason=*/mojom::RejectReason::kNotAvailable, |
| /*component_auction_modified_bid_params=*/nullptr, |
| /*bid_in_seller_currency=*/absl::nullopt, |
| /*scoring_signals_data_version=*/absl::nullopt, |
| /*debug_loss_report_url=*/absl::nullopt, |
| /*debug_win_report_url=*/absl::nullopt, |
| /*pa_requests=*/{}, std::move(errors_out)); |
| return; |
| } |
| context_recycler.AddForDebuggingOnlyBindings(); |
| context_recycler.AddPrivateAggregationBindings( |
| permissions_policy_state_->private_aggregation_allowed); |
| |
| if (base::FeatureList::IsEnabled(blink::features::kSharedStorageAPI)) { |
| context_recycler.AddSharedStorageBindings( |
| shared_storage_host_remote_.is_bound() |
| ? shared_storage_host_remote_.get() |
| : nullptr, |
| permissions_policy_state_->shared_storage_allowed); |
| } |
| |
| success = |
| v8_helper_ |
| ->CallFunction(context, debug_id_.get(), |
| v8_helper_->FormatScriptName(unbound_worklet_script), |
| "scoreAd", args, std::move(seller_timeout), errors_out) |
| .ToLocal(&score_ad_result); |
| |
| TRACE_EVENT_NESTABLE_ASYNC_END0("fledge", "score_ad", trace_id); |
| base::UmaHistogramTimes("Ads.InterestGroup.Auction.ScoreAdTime", |
| base::TimeTicks::Now() - start); |
| |
| if (!success) { |
| // Keep debug loss reports and Private Aggregation API requests since |
| // `scoreAd()` might use them to detect script timeout or failures. |
| PostScoreAdCallbackToUserThread( |
| std::move(callback), /*score=*/0, |
| /*reject_reason=*/mojom::RejectReason::kNotAvailable, |
| /*component_auction_modified_bid_params=*/nullptr, |
| /*bid_in_seller_currency=*/absl::nullopt, |
| /*scoring_signals_data_version=*/absl::nullopt, |
| /*debug_loss_report_url=*/ |
| context_recycler.for_debugging_only_bindings()->TakeLossReportUrl(), |
| /*debug_win_report_url=*/absl::nullopt, |
| context_recycler.private_aggregation_bindings() |
| ->TakePrivateAggregationRequests(), |
| std::move(errors_out)); |
| return; |
| } |
| |
| double score; |
| mojom::RejectReason reject_reason = mojom::RejectReason::kNotAvailable; |
| bool allow_component_auction = false; |
| mojom::ComponentAuctionModifiedBidParamsPtr |
| component_auction_modified_bid_params; |
| absl::optional<double> bid_in_seller_currency; |
| // Try to parse the result as a number. On success, it's the desirability |
| // score. |
| if (!gin::ConvertFromV8(isolate, score_ad_result, &score)) { |
| // Otherwise, it must be an object with the desireability score, and |
| // potentially other fields as well. |
| if (!score_ad_result->IsObject()) { |
| errors_out.push_back( |
| base::StrCat({decision_logic_url_.spec(), |
| " scoreAd() did not return an object or a number."})); |
| PostScoreAdCallbackToUserThreadOnError( |
| std::move(callback), std::move(errors_out), |
| context_recycler.private_aggregation_bindings() |
| ->TakePrivateAggregationRequests()); |
| return; |
| } |
| |
| v8::Local<v8::Object> score_ad_object = score_ad_result.As<v8::Object>(); |
| gin::Dictionary result_dict(isolate, score_ad_object); |
| if (!result_dict.Get("desirability", &score)) { |
| errors_out.push_back( |
| base::StrCat({decision_logic_url_.spec(), |
| " scoreAd() return value has incorrect structure."})); |
| PostScoreAdCallbackToUserThreadOnError( |
| std::move(callback), std::move(errors_out), |
| context_recycler.private_aggregation_bindings() |
| ->TakePrivateAggregationRequests()); |
| return; |
| } |
| |
| if (!result_dict.Get("allowComponentAuction", &allow_component_auction)) |
| allow_component_auction = false; |
| |
| // If the bid is already in seller currency, forward it as |
| // incomingBidInSellerCurrency. |
| if (bid_currency.has_value() && |
| auction_ad_config_non_shared_params.seller_currency.has_value() && |
| bid_currency->currency_code() == |
| auction_ad_config_non_shared_params.seller_currency |
| ->currency_code()) { |
| bid_in_seller_currency = bid; |
| } |
| |
| v8::Local<v8::Value> incoming_bid_in_seller_currency_value; |
| if (score_ad_object |
| ->Get(context, v8_helper_->CreateStringFromLiteral( |
| "incomingBidInSellerCurrency")) |
| .ToLocal(&incoming_bid_in_seller_currency_value) && |
| !incoming_bid_in_seller_currency_value->IsUndefined()) { |
| bool ok = true; |
| double incoming_bid_in_seller_currency = 0.0; |
| |
| if (ok && |
| !auction_ad_config_non_shared_params.seller_currency.has_value()) { |
| errors_out.push_back(base::StrCat( |
| {decision_logic_url_.spec(), |
| " scoreAd() attempting to set incomingBidInSellerCurrency without " |
| "a configured sellerCurrency."})); |
| ok = false; |
| } |
| if (ok && |
| !gin::ConvertFromV8(isolate, incoming_bid_in_seller_currency_value, |
| &incoming_bid_in_seller_currency)) { |
| errors_out.push_back(base::StrCat( |
| {decision_logic_url_.spec(), |
| " scoreAd() incomingBidInSellerCurrency not a number."})); |
| ok = false; |
| } |
| if (ok && !IsValidBid(incoming_bid_in_seller_currency)) { |
| errors_out.push_back(base::StrCat( |
| {decision_logic_url_.spec(), |
| " scoreAd() incomingBidInSellerCurrency not a valid bid."})); |
| ok = false; |
| } |
| if (bid_in_seller_currency.has_value() && |
| incoming_bid_in_seller_currency != *bid_in_seller_currency) { |
| errors_out.push_back(base::StrCat( |
| {decision_logic_url_.spec(), |
| " scoreAd() attempting to set incomingBidInSellerCurrency " |
| "inconsistent with incoming bid already in seller currency."})); |
| ok = false; |
| } |
| if (!ok) { |
| PostScoreAdCallbackToUserThreadOnError( |
| std::move(callback), std::move(errors_out), |
| context_recycler.private_aggregation_bindings() |
| ->TakePrivateAggregationRequests()); |
| return; |
| } |
| bid_in_seller_currency = incoming_bid_in_seller_currency; |
| } |
| |
| v8::Local<v8::Value> reject_reason_value; |
| if (score_ad_object |
| ->Get(context, v8_helper_->CreateStringFromLiteral("rejectReason")) |
| .ToLocal(&reject_reason_value) && |
| !reject_reason_value->IsUndefined()) { |
| if (!reject_reason_value->IsString()) { |
| errors_out.push_back(base::StrCat( |
| {decision_logic_url_.spec(), |
| " rejectReason returned by scoreAd() must be a string."})); |
| } else { |
| std::string reject_reason_str; |
| result_dict.Get("rejectReason", &reject_reason_str); |
| auto reject_reason_opt = RejectReasonStringToEnum(reject_reason_str); |
| |
| if (!reject_reason_opt.has_value()) { |
| errors_out.push_back( |
| base::StrCat({decision_logic_url_.spec(), |
| " scoreAd() returned an invalid reject reason."})); |
| } else { |
| reject_reason = reject_reason_opt.value(); |
| } |
| } |
| } |
| |
| // If this is the seller in a component auction (and thus it was passed a |
| // top-level seller), need to return a |
| // mojom::ComponentAuctionModifiedBidParams. |
| if (allow_component_auction && browser_signals_other_seller && |
| browser_signals_other_seller->is_top_level_seller()) { |
| component_auction_modified_bid_params = |
| mojom::ComponentAuctionModifiedBidParams::New(); |
| |
| v8::Local<v8::Value> ad_value; |
| if (!score_ad_object |
| ->Get(context, v8_helper_->CreateStringFromLiteral("ad")) |
| .ToLocal(&ad_value) || |
| !v8_helper_->ExtractJson( |
| context, ad_value, &component_auction_modified_bid_params->ad)) { |
| component_auction_modified_bid_params->ad = "null"; |
| } |
| |
| component_auction_modified_bid_params->bid = 0; |
| component_auction_modified_bid_params->has_bid = |
| result_dict.Get("bid", &component_auction_modified_bid_params->bid); |
| if (component_auction_modified_bid_params->has_bid) { |
| bool drop_for_invalid_currency = false; |
| v8::Local<v8::Value> bid_currency_value; |
| component_auction_modified_bid_params->bid_currency = absl::nullopt; |
| std::string bid_currency_str; |
| if (score_ad_object |
| ->Get(context, |
| v8_helper_->CreateStringFromLiteral("bidCurrency")) |
| .ToLocal(&bid_currency_value) && |
| !bid_currency_value->IsUndefined()) { |
| if (!gin::ConvertFromV8(isolate, bid_currency_value, |
| &bid_currency_str) || |
| !blink::IsValidAdCurrencyCode(bid_currency_str)) { |
| errors_out.push_back( |
| base::StrCat({decision_logic_url_.spec(), |
| " scoreAd() returned an invalid bidCurrency."})); |
| drop_for_invalid_currency = true; |
| } |
| if (!drop_for_invalid_currency) { |
| component_auction_modified_bid_params->bid_currency = |
| blink::AdCurrency::From(bid_currency_str); |
| } |
| } |
| |
| if (!drop_for_invalid_currency && |
| !VerifySellerCurrency( |
| /*provided_currency=*/component_auction_modified_bid_params |
| ->bid_currency, |
| /*expected_seller_currency=*/ |
| auction_ad_config_non_shared_params.seller_currency, |
| /*component_expect_bid_currency=*/component_expect_bid_currency, |
| decision_logic_url_, "bidCurrency", errors_out)) { |
| drop_for_invalid_currency = true; |
| } |
| if (drop_for_invalid_currency) { |
| score = 0; |
| // If scoreAd() didn't already specify a reject reason, note the |
| // currency mismatch. |
| if (reject_reason == mojom::RejectReason::kNotAvailable) { |
| reject_reason = mojom::RejectReason::kWrongScoreAdCurrency; |
| } |
| } |
| } |
| } |
| } |
| |
| // Fail if `allow_component_auction` is false and this is a component seller |
| // or a top-level seller scoring a bid from a component auction - |
| // `browser_signals_other_seller` is non-null in only those two cases. |
| if (browser_signals_other_seller && !allow_component_auction) { |
| errors_out.push_back(base::StrCat( |
| {decision_logic_url_.spec(), |
| " scoreAd() return value does not have allowComponentAuction set to " |
| "true. Ad dropped from component auction."})); |
| PostScoreAdCallbackToUserThreadOnError( |
| std::move(callback), std::move(errors_out), |
| context_recycler.private_aggregation_bindings() |
| ->TakePrivateAggregationRequests()); |
| return; |
| } |
| |
| // Fail if the score is invalid. |
| if (std::isnan(score) || !std::isfinite(score)) { |
| errors_out.push_back(base::StrCat( |
| {decision_logic_url_.spec(), " scoreAd() returned an invalid score."})); |
| PostScoreAdCallbackToUserThreadOnError( |
| std::move(callback), std::move(errors_out), |
| context_recycler.private_aggregation_bindings() |
| ->TakePrivateAggregationRequests()); |
| return; |
| } |
| |
| if (score <= 0) { |
| // Keep debug report URLs because we want to send debug loss reports if |
| // seller rejected all bids. |
| PostScoreAdCallbackToUserThread( |
| std::move(callback), /*score=*/0, reject_reason, |
| /*component_auction_modified_bid_params=*/nullptr, |
| /*bid_in_seller_currency=*/absl::nullopt, scoring_signals_data_version, |
| context_recycler.for_debugging_only_bindings()->TakeLossReportUrl(), |
| context_recycler.for_debugging_only_bindings()->TakeWinReportUrl(), |
| context_recycler.private_aggregation_bindings() |
| ->TakePrivateAggregationRequests(), |
| std::move(errors_out)); |
| return; |
| } |
| |
| // This bid got accepted by scoreAd(), so clear any reject reason it may have |
| // set. |
| reject_reason = mojom::RejectReason::kNotAvailable; |
| |
| // If this is a component auction that modified the bid, validate the bid. Do |
| // this after checking the score to avoid validating modified bid values from |
| // reporting errors when desirability is <= 0. |
| if (component_auction_modified_bid_params && |
| component_auction_modified_bid_params->has_bid) { |
| // Fail if the new bid is not valid or is 0 or less. |
| if (!IsValidBid(component_auction_modified_bid_params->bid)) { |
| errors_out.push_back(base::StrCat( |
| {decision_logic_url_.spec(), " scoreAd() returned an invalid bid."})); |
| PostScoreAdCallbackToUserThreadOnError( |
| std::move(callback), std::move(errors_out), |
| context_recycler.private_aggregation_bindings() |
| ->TakePrivateAggregationRequests()); |
| return; |
| } |
| } else if (browser_signals_other_seller && |
| browser_signals_other_seller->is_top_level_seller()) { |
| // This is a component auction that did not modify the bid; e.g. it's using |
| // the bidder's bid as its own. Therefore, check it against our own |
| // currency requirements. |
| if (!VerifySellerCurrency( |
| /*provided_currency=*/bid_currency, |
| /*expected_seller_currency=*/ |
| auction_ad_config_non_shared_params.seller_currency, |
| /*component_expect_bid_currency=*/component_expect_bid_currency, |
| decision_logic_url_, "bid passthrough", errors_out)) { |
| score = 0; |
| reject_reason = mojom::RejectReason::kWrongScoreAdCurrency; |
| } |
| } |
| |
| PostScoreAdCallbackToUserThread( |
| std::move(callback), score, reject_reason, |
| std::move(component_auction_modified_bid_params), bid_in_seller_currency, |
| scoring_signals_data_version, |
| context_recycler.for_debugging_only_bindings()->TakeLossReportUrl(), |
| context_recycler.for_debugging_only_bindings()->TakeWinReportUrl(), |
| context_recycler.private_aggregation_bindings() |
| ->TakePrivateAggregationRequests(), |
| std::move(errors_out)); |
| } |
| |
| void SellerWorklet::V8State::ReportResult( |
| const blink::AuctionConfig::NonSharedParams& |
| auction_ad_config_non_shared_params, |
| DirectFromSellerSignalsRequester::Result |
| direct_from_seller_result_seller_signals, |
| DirectFromSellerSignalsRequester::Result |
| direct_from_seller_result_auction_signals, |
| mojom::ComponentAuctionOtherSellerPtr browser_signals_other_seller, |
| const url::Origin& browser_signal_interest_group_owner, |
| const absl::optional<std::string>& |
| browser_signal_buyer_and_seller_reporting_id, |
| const GURL& browser_signal_render_url, |
| double browser_signal_bid, |
| const absl::optional<blink::AdCurrency>& browser_signal_bid_currency, |
| double browser_signal_desirability, |
| double browser_signal_highest_scoring_other_bid, |
| const absl::optional<blink::AdCurrency>& |
| browser_signal_highest_scoring_other_bid_currency, |
| auction_worklet::mojom::ComponentAuctionReportResultParamsPtr |
| browser_signals_component_auction_report_result_params, |
| absl::optional<uint32_t> scoring_signals_data_version, |
| uint64_t trace_id, |
| ReportResultCallbackInternal callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_); |
| TRACE_EVENT_NESTABLE_ASYNC_END0("fledge", "post_v8_task", trace_id); |
| |
| AuctionV8Helper::FullIsolateScope isolate_scope(v8_helper_.get()); |
| v8::Isolate* isolate = v8_helper_->isolate(); |
| |
| // Short lived context, to avoid leaking data at global scope between either |
| // repeated calls to this worklet, or to calls to any other worklet. |
| ContextRecycler context_recycler(v8_helper_.get()); |
| |
| ContextRecyclerScope context_recycler_scope(context_recycler); |
| v8::Local<v8::Context> context = context_recycler_scope.GetContext(); |
| |
| std::vector<v8::Local<v8::Value>> args; |
| if (!AppendAuctionConfig(v8_helper_.get(), context, decision_logic_url_, |
| trusted_scoring_signals_url_, experiment_group_id_, |
| auction_ad_config_non_shared_params, &args)) { |
| PostReportResultCallbackToUserThread(std::move(callback), |
| /*signals_for_winner=*/absl::nullopt, |
| /*report_url=*/absl::nullopt, |
| /*ad_beacon_map=*/{}, |
| /*pa_requests=*/{}, |
| /*errors=*/std::vector<std::string>()); |
| return; |
| } |
| |
| v8::Local<v8::Object> browser_signals = v8::Object::New(isolate); |
| gin::Dictionary browser_signals_dict(isolate, browser_signals); |
| if (!browser_signals_dict.Set("topWindowHostname", |
| top_window_origin_.host()) || |
| !AddOtherSeller(browser_signals_other_seller.get(), |
| browser_signals_dict) || |
| !browser_signals_dict.Set( |
| "interestGroupOwner", |
| browser_signal_interest_group_owner.Serialize()) || |
| (browser_signal_buyer_and_seller_reporting_id.has_value() && |
| !browser_signals_dict.Set( |
| "buyerAndSellerReportingId", |
| *browser_signal_buyer_and_seller_reporting_id)) || |
| !browser_signals_dict.Set("renderUrl", |
| browser_signal_render_url.spec()) || |
| !browser_signals_dict.Set("bid", browser_signal_bid) || |
| !browser_signals_dict.Set( |
| "bidCurrency", |
| blink::PrintableAdCurrency(browser_signal_bid_currency)) || |
| !browser_signals_dict.Set("desirability", browser_signal_desirability) || |
| !browser_signals_dict.Set("highestScoringOtherBid", |
| browser_signal_highest_scoring_other_bid) || |
| !browser_signals_dict.Set( |
| "highestScoringOtherBidCurrency", |
| blink::PrintableAdCurrency( |
| browser_signal_highest_scoring_other_bid_currency)) || |
| (scoring_signals_data_version.has_value() && |
| !browser_signals_dict.Set("dataVersion", |
| scoring_signals_data_version.value()))) { |
| PostReportResultCallbackToUserThread(std::move(callback), |
| /*signals_for_winner=*/absl::nullopt, |
| /*report_url=*/absl::nullopt, |
| /*ad_beacon_map=*/{}, |
| /*pa_requests=*/{}, |
| /*errors=*/std::vector<std::string>()); |
| return; |
| } |
| if (browser_signals_component_auction_report_result_params) { |
| if (!v8_helper_->InsertJsonValue( |
| context, "topLevelSellerSignals", |
| browser_signals_component_auction_report_result_params |
| ->top_level_seller_signals, |
| browser_signals) || |
| (browser_signals_component_auction_report_result_params |
| ->has_modified_bid && |
| !browser_signals_dict.Set( |
| "modifiedBid", |
| browser_signals_component_auction_report_result_params |
| ->modified_bid))) { |
| PostReportResultCallbackToUserThread( |
| std::move(callback), |
| /*signals_for_winner=*/absl::nullopt, |
| /*report_url=*/absl::nullopt, |
| /*ad_beacon_map=*/{}, |
| /*pa_requests=*/{}, |
| /*errors=*/std::vector<std::string>()); |
| return; |
| } |
| } |
| args.push_back(browser_signals); |
| |
| std::vector<std::string> errors_out; |
| v8::Local<v8::Object> direct_from_seller_signals = v8::Object::New(isolate); |
| gin::Dictionary direct_from_seller_signals_dict(isolate, |
| direct_from_seller_signals); |
| v8::Local<v8::Value> seller_signals = |
| direct_from_seller_result_seller_signals.GetSignals(*v8_helper_, context, |
| errors_out); |
| v8::Local<v8::Value> auction_signals = |
| direct_from_seller_result_auction_signals.GetSignals(*v8_helper_, context, |
| errors_out); |
| if (!direct_from_seller_signals_dict.Set("sellerSignals", seller_signals) || |
| !direct_from_seller_signals_dict.Set("auctionSignals", auction_signals)) { |
| PostReportResultCallbackToUserThread(std::move(callback), |
| /*signals_for_winner=*/absl::nullopt, |
| /*report_url=*/absl::nullopt, |
| /*ad_beacon_map=*/{}, |
| /*pa_requests=*/{}, |
| /*errors=*/errors_out); |
| return; |
| } |
| args.push_back(direct_from_seller_signals); |
| |
| v8::Local<v8::Value> signals_for_winner_value; |
| v8_helper_->MaybeTriggerInstrumentationBreakpoint( |
| *debug_id_, "beforeSellerWorkletReportingStart"); |
| |
| v8::Local<v8::UnboundScript> unbound_worklet_script = |
| worklet_script_.Get(isolate); |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("fledge", "report_result", trace_id); |
| bool success = |
| v8_helper_->RunScript(context, unbound_worklet_script, debug_id_.get(), |
| /*script_timeout=*/absl::nullopt, errors_out); |
| |
| if (!success) { |
| TRACE_EVENT_NESTABLE_ASYNC_END0("fledge", "report_result", trace_id); |
| PostReportResultCallbackToUserThread( |
| std::move(callback), /*signals_for_winner=*/absl::nullopt, |
| /*report_url=*/absl::nullopt, /*ad_beacon_map=*/{}, |
| /*pa_requests=*/{}, std::move(errors_out)); |
| return; |
| } |
| |
| context_recycler.AddReportBindings(); |
| context_recycler.AddRegisterAdBeaconBindings(); |
| context_recycler.AddPrivateAggregationBindings( |
| permissions_policy_state_->private_aggregation_allowed); |
| |
| if (base::FeatureList::IsEnabled(blink::features::kSharedStorageAPI)) { |
| context_recycler.AddSharedStorageBindings( |
| shared_storage_host_remote_.is_bound() |
| ? shared_storage_host_remote_.get() |
| : nullptr, |
| permissions_policy_state_->shared_storage_allowed); |
| } |
| |
| success = |
| v8_helper_ |
| ->CallFunction(context, debug_id_.get(), |
| v8_helper_->FormatScriptName(unbound_worklet_script), |
| "reportResult", args, |
| /*script_timeout=*/absl::nullopt, errors_out) |
| .ToLocal(&signals_for_winner_value); |
| |
| TRACE_EVENT_NESTABLE_ASYNC_END0("fledge", "report_result", trace_id); |
| |
| if (!success) { |
| // Keep Private Aggregation API requests since `reportReport()` might use |
| // it to detect script timeout or failures. |
| PostReportResultCallbackToUserThread( |
| std::move(callback), /*signals_for_winner=*/absl::nullopt, |
| /*report_url=*/absl::nullopt, /*ad_beacon_map=*/{}, |
| context_recycler.private_aggregation_bindings() |
| ->TakePrivateAggregationRequests(), |
| std::move(errors_out)); |
| return; |
| } |
| |
| // Consider lack of error but no return value type, or a return value that |
| // can't be converted to JSON a valid result. |
| std::string signals_for_winner; |
| if (!v8_helper_->ExtractJson(context, signals_for_winner_value, |
| &signals_for_winner)) { |
| signals_for_winner = "null"; |
| } |
| |
| PostReportResultCallbackToUserThread( |
| std::move(callback), std::move(signals_for_winner), |
| context_recycler.report_bindings()->report_url(), |
| context_recycler.register_ad_beacon_bindings()->TakeAdBeaconMap(), |
| context_recycler.private_aggregation_bindings() |
| ->TakePrivateAggregationRequests(), |
| std::move(errors_out)); |
| } |
| |
| void SellerWorklet::V8State::ConnectDevToolsAgent( |
| mojo::PendingAssociatedReceiver<blink::mojom::DevToolsAgent> agent) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_); |
| v8_helper_->ConnectDevToolsAgent(std::move(agent), user_thread_, *debug_id_); |
| } |
| |
| SellerWorklet::V8State::~V8State() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_); |
| } |
| |
| void SellerWorklet::V8State::FinishInit( |
| mojo::PendingRemote<mojom::AuctionSharedStorageHost> |
| shared_storage_host_remote) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_); |
| |
| if (shared_storage_host_remote) { |
| shared_storage_host_remote_.Bind(std::move(shared_storage_host_remote)); |
| } |
| |
| debug_id_->SetResumeCallback(base::BindOnce( |
| &SellerWorklet::V8State::PostResumeToUserThread, parent_, user_thread_)); |
| } |
| |
| // static |
| void SellerWorklet::V8State::PostResumeToUserThread( |
| base::WeakPtr<SellerWorklet> parent, |
| scoped_refptr<base::SequencedTaskRunner> user_thread) { |
| // This is static since it's called from debugging, not SellerWorklet, |
| // so the usual guarantee that SellerWorklet posts things before posting |
| // V8State destruction is irrelevant. |
| user_thread->PostTask(FROM_HERE, |
| base::BindOnce(&SellerWorklet::ResumeIfPaused, parent)); |
| } |
| |
| void SellerWorklet::V8State::PostScoreAdCallbackToUserThreadOnError( |
| ScoreAdCallbackInternal callback, |
| std::vector<std::string> errors, |
| PrivateAggregationRequests pa_requests) { |
| PostScoreAdCallbackToUserThread( |
| std::move(callback), /*score=*/0, |
| /*reject_reason=*/mojom::RejectReason::kNotAvailable, |
| /*component_auction_modified_bid_params=*/nullptr, |
| /*bid_in_seller_currency=*/absl::nullopt, |
| /*scoring_signals_data_version=*/absl::nullopt, |
| /*debug_loss_report_url=*/absl::nullopt, |
| /*debug_win_report_url=*/absl::nullopt, std::move(pa_requests), |
| std::move(errors)); |
| } |
| |
| void SellerWorklet::V8State::PostScoreAdCallbackToUserThread( |
| ScoreAdCallbackInternal callback, |
| double score, |
| mojom::RejectReason reject_reason, |
| mojom::ComponentAuctionModifiedBidParamsPtr |
| component_auction_modified_bid_params, |
| absl::optional<double> bid_in_seller_currency, |
| absl::optional<uint32_t> scoring_signals_data_version, |
| absl::optional<GURL> debug_loss_report_url, |
| absl::optional<GURL> debug_win_report_url, |
| PrivateAggregationRequests pa_requests, |
| std::vector<std::string> errors) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_); |
| user_thread_->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(callback), score, reject_reason, |
| std::move(component_auction_modified_bid_params), |
| bid_in_seller_currency, scoring_signals_data_version, |
| std::move(debug_loss_report_url), |
| std::move(debug_win_report_url), std::move(pa_requests), |
| std::move(errors))); |
| } |
| |
| void SellerWorklet::V8State::PostReportResultCallbackToUserThread( |
| ReportResultCallbackInternal callback, |
| absl::optional<std::string> signals_for_winner, |
| absl::optional<GURL> report_url, |
| base::flat_map<std::string, GURL> ad_beacon_map, |
| PrivateAggregationRequests pa_requests, |
| std::vector<std::string> errors) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_); |
| user_thread_->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(callback), std::move(signals_for_winner), |
| std::move(report_url), std::move(ad_beacon_map), |
| std::move(pa_requests), std::move(errors))); |
| } |
| |
| void SellerWorklet::ResumeIfPaused() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_); |
| if (!paused_) |
| return; |
| |
| paused_ = false; |
| Start(); |
| } |
| |
| void SellerWorklet::Start() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_); |
| DCHECK(!paused_); |
| |
| base::UmaHistogramCounts100000( |
| "Ads.InterestGroup.Net.RequestUrlSizeBytes.ScoringScriptJS", |
| script_source_url_.spec().size()); |
| worklet_loader_ = std::make_unique<WorkletLoader>( |
| url_loader_factory_.get(), script_source_url_, v8_helper_, debug_id_, |
| base::BindOnce(&SellerWorklet::OnDownloadComplete, |
| base::Unretained(this))); |
| } |
| |
| void SellerWorklet::OnDownloadComplete(WorkletLoader::Result worklet_script, |
| absl::optional<std::string> error_msg) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_); |
| base::UmaHistogramCounts10M( |
| "Ads.InterestGroup.Net.ResponseSizeBytes.ScoringScriptJS", |
| worklet_script.original_size_bytes()); |
| base::UmaHistogramTimes("Ads.InterestGroup.Net.DownloadTime.ScoringScriptJS", |
| worklet_script.download_time()); |
| worklet_loader_.reset(); |
| |
| // On failure, delete `this`, as it can't do anything without a loaded script. |
| bool success = worklet_script.success(); |
| if (!success) { |
| std::move(close_pipe_callback_) |
| .Run(error_msg ? error_msg.value() : std::string()); |
| // `this` should be deleted at this point. |
| return; |
| } |
| |
| // The error message, if any, will be appended to all invoked ScoreAd() and |
| // ReportResult() callbacks. |
| load_script_error_msg_ = std::move(error_msg); |
| |
| v8_runner_->PostTask(FROM_HERE, |
| base::BindOnce(&SellerWorklet::V8State::SetWorkletScript, |
| base::Unretained(v8_state_.get()), |
| std::move(worklet_script))); |
| MaybeRecordCodeWait(); |
| |
| for (auto score_ad_task = score_ad_tasks_.begin(); |
| score_ad_task != score_ad_tasks_.end(); ++score_ad_task) { |
| ScoreAdIfReady(score_ad_task); |
| } |
| |
| for (auto report_result_task = report_result_tasks_.begin(); |
| report_result_task != report_result_tasks_.end(); ++report_result_task) { |
| RunReportResultIfReady(report_result_task); |
| } |
| } |
| |
| void SellerWorklet::MaybeRecordCodeWait() { |
| if (!IsCodeReady()) { |
| return; |
| } |
| |
| base::TimeTicks now = base::TimeTicks::Now(); |
| for (auto& task : score_ad_tasks_) { |
| task.wait_code = now - task.trace_wait_deps_start; |
| } |
| |
| for (auto& task : report_result_tasks_) { |
| task.wait_code = now - task.trace_wait_deps_start; |
| } |
| } |
| |
| void SellerWorklet::OnTrustedScoringSignalsDownloaded( |
| ScoreAdTaskList::iterator task, |
| scoped_refptr<TrustedSignals::Result> result, |
| absl::optional<std::string> error_msg) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_); |
| |
| task->trusted_scoring_signals_error_msg = std::move(error_msg); |
| task->trusted_scoring_signals_result = std::move(result); |
| // Clean up single-use object, now that it has done its job. |
| task->trusted_scoring_signals_request.reset(); |
| |
| task->wait_trusted_signals = |
| base::TimeTicks::Now() - task->trace_wait_deps_start; |
| ScoreAdIfReady(task); |
| } |
| |
| void SellerWorklet::OnScoreAdClientDestroyed(ScoreAdTaskList::iterator task) { |
| // If IsReadyToScoreAd() is false, it also hasn't posted the iterator |
| // off-thread, so we can just remove the object and have it cancel everything |
| // else. |
| if (!IsReadyToScoreAd(*task)) { |
| score_ad_tasks_.erase(task); |
| } else { |
| // Otherwise, there should be a pending V8 call. Try to cancel that, but if |
| // it already started, it will just run and throw out the results thanks to |
| // the closed client pipe. |
| DCHECK_NE(task->task_id, base::CancelableTaskTracker::kBadTaskId); |
| cancelable_task_tracker_.TryCancel(task->task_id); |
| } |
| } |
| |
| void SellerWorklet::OnDirectFromSellerSellerSignalsDownloadedScoreAd( |
| ScoreAdTaskList::iterator task, |
| DirectFromSellerSignalsRequester::Result result) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_); |
| |
| task->direct_from_seller_result_seller_signals = std::move(result); |
| task->direct_from_seller_request_seller_signals.reset(); |
| // The two direct from seller signals metrics for tracing are combined since |
| // they should be roughly the same. |
| task->wait_direct_from_seller_signals = |
| std::max(task->wait_direct_from_seller_signals, |
| base::TimeTicks::Now() - task->trace_wait_deps_start); |
| |
| ScoreAdIfReady(task); |
| } |
| |
| void SellerWorklet::OnDirectFromSellerAuctionSignalsDownloadedScoreAd( |
| ScoreAdTaskList::iterator task, |
| DirectFromSellerSignalsRequester::Result result) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_); |
| |
| task->direct_from_seller_result_auction_signals = std::move(result); |
| task->direct_from_seller_request_auction_signals.reset(); |
| // The two direct from seller signals metrics for tracing are combined since |
| // they should be roughly the same. |
| task->wait_direct_from_seller_signals = |
| std::max(task->wait_direct_from_seller_signals, |
| base::TimeTicks::Now() - task->trace_wait_deps_start); |
| |
| ScoreAdIfReady(task); |
| } |
| |
| bool SellerWorklet::IsReadyToScoreAd(const ScoreAdTask& task) const { |
| return !task.trusted_scoring_signals_request && |
| !task.direct_from_seller_request_seller_signals && |
| !task.direct_from_seller_request_auction_signals && IsCodeReady(); |
| } |
| |
| void SellerWorklet::ScoreAdIfReady(ScoreAdTaskList::iterator task) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_); |
| |
| if (!IsReadyToScoreAd(*task)) |
| return; |
| |
| TRACE_EVENT_NESTABLE_ASYNC_END1( |
| "fledge", "wait_score_ad_deps", task->trace_id, "data", |
| [&](perfetto::TracedValue trace_context) { |
| auto dict = std::move(trace_context).WriteDictionary(); |
| if (!task->wait_code.is_zero()) { |
| dict.Add("wait_code_ms", task->wait_code.InMillisecondsF()); |
| } |
| if (!task->wait_trusted_signals.is_zero()) { |
| dict.Add("wait_trusted_signals_ms", |
| task->wait_trusted_signals.InMillisecondsF()); |
| } |
| if (!task->wait_direct_from_seller_signals.is_zero()) { |
| dict.Add("wait_direct_from_seller_signals_ms", |
| task->wait_direct_from_seller_signals.InMillisecondsF()); |
| } |
| }); |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("fledge", "post_v8_task", task->trace_id); |
| |
| // Normally the PostTask below will eventually get `task` cleaned up once it |
| // posts back to DeliverScoreAdCallbackOnUserThread with its results, but that |
| // won't happen if it gets cancelled. To deal with that, a ScopedClosureRunner |
| // is passed to ask for `task` to get cleaned up in case the V8State::ScoreAd |
| // closure gets destroyed without running. |
| base::OnceClosure cleanup_score_ad_task = base::BindPostTaskToCurrentDefault( |
| base::BindOnce(&SellerWorklet::CleanUpScoreAdTaskOnUserThread, |
| weak_ptr_factory_.GetWeakPtr(), task)); |
| |
| task->task_id = cancelable_task_tracker_.PostTask( |
| v8_runner_.get(), FROM_HERE, |
| base::BindOnce( |
| &SellerWorklet::V8State::ScoreAd, base::Unretained(v8_state_.get()), |
| task->ad_metadata_json, task->bid, std::move(task->bid_currency), |
| std::move(task->auction_ad_config_non_shared_params), |
| std::move(task->direct_from_seller_result_seller_signals), |
| std::move(task->direct_from_seller_result_auction_signals), |
| std::move(task->trusted_scoring_signals_result), |
| std::move(task->browser_signals_other_seller), |
| std::move(task->component_expect_bid_currency), |
| std::move(task->browser_signal_interest_group_owner), |
| std::move(task->browser_signal_render_url), |
| std::move(task->browser_signal_ad_components), |
| task->browser_signal_bidding_duration_msecs, |
| std::move(task->seller_timeout), task->trace_id, |
| base::ScopedClosureRunner(std::move(cleanup_score_ad_task)), |
| base::BindOnce(&SellerWorklet::DeliverScoreAdCallbackOnUserThread, |
| weak_ptr_factory_.GetWeakPtr(), task))); |
| } |
| |
| void SellerWorklet::DeliverScoreAdCallbackOnUserThread( |
| ScoreAdTaskList::iterator task, |
| double score, |
| mojom::RejectReason reject_reason, |
| mojom::ComponentAuctionModifiedBidParamsPtr |
| component_auction_modified_bid_params, |
| absl::optional<double> bid_in_seller_currency, |
| absl::optional<uint32_t> scoring_signals_data_version, |
| absl::optional<GURL> debug_loss_report_url, |
| absl::optional<GURL> debug_win_report_url, |
| PrivateAggregationRequests pa_requests, |
| std::vector<std::string> errors) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_); |
| if (load_script_error_msg_) |
| errors.insert(errors.begin(), load_script_error_msg_.value()); |
| if (task->trusted_scoring_signals_error_msg) |
| errors.insert(errors.begin(), *task->trusted_scoring_signals_error_msg); |
| |
| // This is safe to do, even if the pipe was closed - the message will just be |
| // dropped. |
| // |
| // TOOD(mmenke): Consider watching for the pipe closing and aborting work if |
| // it does. Only useful if the SellerWorklet object is still in use, so |
| // unclear how useful it would be. |
| task->score_ad_client->OnScoreAdComplete( |
| score, reject_reason, std::move(component_auction_modified_bid_params), |
| std::move(bid_in_seller_currency), scoring_signals_data_version, |
| debug_loss_report_url, debug_win_report_url, std::move(pa_requests), |
| std::move(errors)); |
| score_ad_tasks_.erase(task); |
| } |
| |
| void SellerWorklet::CleanUpScoreAdTaskOnUserThread( |
| ScoreAdTaskList::iterator task) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_); |
| score_ad_tasks_.erase(task); |
| } |
| |
| void SellerWorklet::OnDirectFromSellerSellerSignalsDownloadedReportResult( |
| ReportResultTaskList::iterator task, |
| DirectFromSellerSignalsRequester::Result result) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_); |
| |
| task->direct_from_seller_result_seller_signals = std::move(result); |
| task->direct_from_seller_request_seller_signals.reset(); |
| // The two direct from seller signals metrics for tracing are combined since |
| // they should be roughly the same. |
| task->wait_direct_from_seller_signals = |
| std::max(task->wait_direct_from_seller_signals, |
| base::TimeTicks::Now() - task->trace_wait_deps_start); |
| |
| RunReportResultIfReady(task); |
| } |
| |
| void SellerWorklet::OnDirectFromSellerAuctionSignalsDownloadedReportResult( |
| ReportResultTaskList::iterator task, |
| DirectFromSellerSignalsRequester::Result result) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_); |
| |
| task->direct_from_seller_result_auction_signals = std::move(result); |
| task->direct_from_seller_request_auction_signals.reset(); |
| // The two direct from seller signals metrics for tracing are combined since |
| // they should be roughly the same. |
| task->wait_direct_from_seller_signals = |
| std::max(task->wait_direct_from_seller_signals, |
| base::TimeTicks::Now() - task->trace_wait_deps_start); |
| |
| RunReportResultIfReady(task); |
| } |
| |
| bool SellerWorklet::IsReadyToReportResult(const ReportResultTask& task) const { |
| return IsCodeReady() && !task.direct_from_seller_request_seller_signals && |
| !task.direct_from_seller_request_auction_signals; |
| } |
| |
| void SellerWorklet::RunReportResultIfReady( |
| ReportResultTaskList::iterator task) { |
| if (!IsReadyToReportResult(*task)) |
| return; |
| |
| TRACE_EVENT_NESTABLE_ASYNC_END1( |
| "fledge", "wait_report_result_deps", task->trace_id, "data", |
| [&](perfetto::TracedValue trace_context) { |
| auto dict = std::move(trace_context).WriteDictionary(); |
| if (!task->wait_code.is_zero()) { |
| dict.Add("wait_code_ms", task->wait_code.InMillisecondsF()); |
| } |
| if (!task->wait_direct_from_seller_signals.is_zero()) { |
| dict.Add("wait_direct_from_seller_signals_ms", |
| task->wait_direct_from_seller_signals.InMillisecondsF()); |
| } |
| }); |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("fledge", "post_v8_task", task->trace_id); |
| |
| cancelable_task_tracker_.PostTask( |
| v8_runner_.get(), FROM_HERE, |
| base::BindOnce( |
| &SellerWorklet::V8State::ReportResult, |
| base::Unretained(v8_state_.get()), |
| std::move(task->auction_ad_config_non_shared_params), |
| std::move(task->direct_from_seller_result_seller_signals), |
| std::move(task->direct_from_seller_result_auction_signals), |
| std::move(task->browser_signals_other_seller), |
| std::move(task->browser_signal_interest_group_owner), |
| std::move(task->browser_signal_buyer_and_seller_reporting_id), |
| std::move(task->browser_signal_render_url), task->browser_signal_bid, |
| std::move(task->browser_signal_bid_currency), |
| task->browser_signal_desirability, |
| task->browser_signal_highest_scoring_other_bid, |
| std::move(task->browser_signal_highest_scoring_other_bid_currency), |
| std::move( |
| task->browser_signals_component_auction_report_result_params), |
| task->scoring_signals_data_version, task->trace_id, |
| base::BindOnce( |
| &SellerWorklet::DeliverReportResultCallbackOnUserThread, |
| weak_ptr_factory_.GetWeakPtr(), task))); |
| } |
| |
| void SellerWorklet::DeliverReportResultCallbackOnUserThread( |
| ReportResultTaskList::iterator task, |
| const absl::optional<std::string> signals_for_winner, |
| const absl::optional<GURL> report_url, |
| base::flat_map<std::string, GURL> ad_beacon_map, |
| PrivateAggregationRequests pa_requests, |
| std::vector<std::string> errors) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_); |
| |
| if (load_script_error_msg_) |
| errors.insert(errors.begin(), load_script_error_msg_.value()); |
| |
| std::move(task->callback) |
| .Run(signals_for_winner, report_url, ad_beacon_map, |
| std::move(pa_requests), std::move(errors)); |
| report_result_tasks_.erase(task); |
| } |
| |
| bool SellerWorklet::IsCodeReady() const { |
| return (!paused_ && !worklet_loader_); |
| } |
| |
| } // namespace auction_worklet |