blob: e766c0be1fbd08c50867b0be4a9ec04ac2e29044 [file] [log] [blame]
// 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/bidder_worklet.h"
#include <stdint.h>
#include <algorithm>
#include <cmath>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#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 "base/types/optional_util.h"
#include "content/services/auction_worklet/auction_v8_helper.h"
#include "content/services/auction_worklet/bidder_lazy_filler.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/bidder_worklet.mojom-shared.h"
#include "content/services/auction_worklet/public/mojom/private_aggregation_request.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/set_bid_bindings.h"
#include "content/services/auction_worklet/set_priority_bindings.h"
#include "content/services/auction_worklet/set_priority_signals_override_bindings.h"
#include "content/services/auction_worklet/shared_storage_bindings.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 "context_recycler.h"
#include "gin/converter.h"
#include "gin/dictionary.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.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/common/features.h"
#include "third_party/blink/public/common/interest_group/ad_auction_constants.h"
#include "third_party/blink/public/common/interest_group/ad_auction_currencies.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-container.h"
#include "v8/include/v8-context.h"
#include "v8/include/v8-exception.h"
#include "v8/include/v8-forward.h"
#include "v8/include/v8-object.h"
#include "v8/include/v8-primitive.h"
#include "v8/include/v8-template.h"
#include "v8/include/v8-wasm.h"
namespace auction_worklet {
namespace {
class DeepFreezeAllowAll : public v8::Context::DeepFreezeDelegate {
public:
bool FreezeEmbedderObjectAndGetChildren(
v8::Local<v8::Object> obj,
std::vector<v8::Local<v8::Object>>& children_out) override {
return true;
}
};
bool AppendJsonValueOrNull(AuctionV8Helper* const v8_helper,
v8::Local<v8::Context> context,
const std::string* maybe_json,
std::vector<v8::Local<v8::Value>>* args) {
v8::Isolate* isolate = v8_helper->isolate();
if (maybe_json) {
if (!v8_helper->AppendJsonValue(context, *maybe_json, args)) {
return false;
}
} else {
args->push_back(v8::Null(isolate));
}
return true;
}
// Converts a vector of blink::InterestGroup::Ads into a v8 object.
bool CreateAdVector(AuctionV8Helper* v8_helper,
v8::Local<v8::Context> context,
base::RepeatingCallback<bool(const GURL&)> is_ad_excluded,
const std::vector<blink::InterestGroup::Ad>& ads,
v8::Local<v8::Value>& out_value) {
v8::Isolate* isolate = v8_helper->isolate();
std::vector<v8::Local<v8::Value>> ads_vector;
for (const auto& ad : ads) {
if (is_ad_excluded.Run(ad.render_url)) {
continue;
}
v8::Local<v8::Object> ad_object = v8::Object::New(isolate);
gin::Dictionary ad_dict(isolate, ad_object);
if (!ad_dict.Set("renderUrl", ad.render_url.spec()) ||
(ad.metadata && !v8_helper->InsertJsonValue(context, "metadata",
*ad.metadata, ad_object))) {
return false;
}
ads_vector.emplace_back(std::move(ad_object));
}
out_value = v8::Array::New(isolate, ads_vector.data(), ads_vector.size());
return true;
}
bool HasKAnonFailureComponent(
const auction_worklet::mojom::PrivateAggregationRequestPtr& request) {
if (request->contribution->is_histogram_contribution()) {
return false;
}
const auction_worklet::mojom::AggregatableReportForEventContributionPtr&
event_contribution = request->contribution->get_for_event_contribution();
if (event_contribution->bucket->is_signal_bucket() &&
event_contribution->bucket->get_signal_bucket()->base_value ==
auction_worklet::mojom::BaseValue::kBidRejectReason) {
return true;
}
if (event_contribution->value->is_signal_value() &&
event_contribution->value->get_signal_value()->base_value ==
auction_worklet::mojom::BaseValue::kBidRejectReason) {
return true;
}
return false;
}
absl::optional<base::TimeDelta> NullOptIfZero(base::TimeDelta delta) {
if (delta.is_zero()) {
return absl::nullopt;
}
return delta;
}
} // namespace
BidderWorklet::BidderWorklet(
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& script_source_url,
const absl::optional<GURL>& wasm_helper_url,
const absl::optional<GURL>& trusted_bidding_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_(v8_helper),
debug_id_(
base::MakeRefCounted<AuctionV8Helper::DebugId>(v8_helper.get())),
url_loader_factory_(std::move(pending_url_loader_factory)),
script_source_url_(script_source_url),
wasm_helper_url_(wasm_helper_url),
trusted_signals_request_manager_(
trusted_bidding_signals_url
? std::make_unique<TrustedSignalsRequestManager>(
TrustedSignalsRequestManager::Type::kBiddingSignals,
url_loader_factory_.get(),
/*automatically_send_requests=*/false,
top_window_origin,
*trusted_bidding_signals_url,
experiment_group_id,
v8_helper_.get())
: nullptr),
top_window_origin_(top_window_origin),
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),
script_source_url_, top_window_origin_,
std::move(permissions_policy_state), wasm_helper_url_,
trusted_bidding_signals_url, weak_ptr_factory_.GetWeakPtr()),
base::OnTaskRunnerDeleter(v8_runner_));
paused_ = pause_for_debugger_on_start;
if (!paused_) {
Start();
}
}
BidderWorklet::~BidderWorklet() {
DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
debug_id_->AbortDebuggerPauses();
}
int BidderWorklet::context_group_id_for_testing() const {
return debug_id_->context_group_id();
}
// static
bool BidderWorklet::IsKAnon(
const mojom::BidderWorkletNonSharedParams* bidder_worklet_non_shared_params,
const std::string& key) {
auto it = bidder_worklet_non_shared_params->kanon_keys.find(
mojom::KAnonKey::New(key));
return it != bidder_worklet_non_shared_params->kanon_keys.end() && it->second;
}
// static
bool BidderWorklet::IsKAnon(
const mojom::BidderWorkletNonSharedParams* bidder_worklet_non_shared_params,
const GURL& script_source_url,
const mojom::BidderWorkletBidPtr& bid) {
if (!bid) {
return true;
}
if (!BidderWorklet::IsKAnon(
bidder_worklet_non_shared_params,
blink::KAnonKeyForAdBid(url::Origin::Create(script_source_url),
script_source_url, bid->ad_descriptor))) {
return false;
}
if (bid->ad_component_descriptors.has_value()) {
for (const auto& ad_component_descriptor :
bid->ad_component_descriptors.value()) {
if (!BidderWorklet::IsKAnon(
bidder_worklet_non_shared_params,
blink::KAnonKeyForAdComponentBid(ad_component_descriptor))) {
return false;
}
}
}
return true;
}
void BidderWorklet::BeginGenerateBid(
mojom::BidderWorkletNonSharedParamsPtr bidder_worklet_non_shared_params,
mojom::KAnonymityBidMode kanon_mode,
const url::Origin& interest_group_join_origin,
const absl::optional<GURL>& direct_from_seller_per_buyer_signals,
const absl::optional<GURL>& direct_from_seller_auction_signals,
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,
uint64_t trace_id,
mojo::PendingAssociatedRemote<mojom::GenerateBidClient> generate_bid_client,
mojo::PendingAssociatedReceiver<mojom::GenerateBidFinalizer>
bid_finalizer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
generate_bid_tasks_.emplace_front();
auto generate_bid_task = generate_bid_tasks_.begin();
generate_bid_task->bidder_worklet_non_shared_params =
std::move(bidder_worklet_non_shared_params);
generate_bid_task->kanon_mode = kanon_mode;
generate_bid_task->interest_group_join_origin = interest_group_join_origin;
generate_bid_task->browser_signal_seller_origin =
browser_signal_seller_origin;
generate_bid_task->browser_signal_top_level_seller_origin =
browser_signal_top_level_seller_origin;
generate_bid_task->bidding_browser_signals =
std::move(bidding_browser_signals);
generate_bid_task->auction_start_time = auction_start_time;
generate_bid_task->trace_id = trace_id;
generate_bid_task->generate_bid_client.Bind(std::move(generate_bid_client));
// Deleting `generate_bid_task` will destroy `generate_bid_client` and thus
// abort this callback, so it's safe to use Unretained(this) and
// `generate_bid_task` here.
generate_bid_task->generate_bid_client.set_disconnect_handler(
base::BindOnce(&BidderWorklet::OnGenerateBidClientDestroyed,
base::Unretained(this), generate_bid_task));
// Listen to call to FinalizeGenerateBid completing arguments.
generate_bid_task->finalize_generate_bid_receiver_id =
finalize_receiver_set_.Add(this, std::move(bid_finalizer),
generate_bid_task);
HandleDirectFromSellerForGenerateBid(direct_from_seller_per_buyer_signals,
direct_from_seller_auction_signals,
generate_bid_task);
const auto& trusted_bidding_signals_keys =
generate_bid_task->bidder_worklet_non_shared_params
->trusted_bidding_signals_keys;
generate_bid_task->trace_wait_deps_start = base::TimeTicks::Now();
TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("fledge", "wait_generate_bid_deps",
trace_id);
if (trusted_signals_request_manager_) {
generate_bid_task->trusted_bidding_signals_request =
trusted_signals_request_manager_->RequestBiddingSignals(
generate_bid_task->bidder_worklet_non_shared_params->name,
trusted_bidding_signals_keys,
base::BindOnce(&BidderWorklet::OnTrustedBiddingSignalsDownloaded,
base::Unretained(this), generate_bid_task));
return;
}
// Deleting `generate_bid_task` will destroy `generate_bid_client` and thus
// abort this callback, so it's safe to use Unretained(this) and
// `generate_bid_task` here.
generate_bid_task->generate_bid_client->OnBiddingSignalsReceived(
/*priority_vector=*/{},
/*trusted_signals_fetch_latency=*/base::TimeDelta(),
base::BindOnce(&BidderWorklet::SignalsReceivedCallback,
base::Unretained(this), generate_bid_task));
}
void BidderWorklet::SendPendingSignalsRequests() {
if (trusted_signals_request_manager_) {
trusted_signals_request_manager_->StartBatchedTrustedSignalsRequest();
}
}
void BidderWorklet::ReportWin(
mojom::ReportingIdField reporting_id_field,
const std::string& reporting_id,
const absl::optional<std::string>& auction_signals_json,
const absl::optional<std::string>& per_buyer_signals_json,
const absl::optional<GURL>& direct_from_seller_per_buyer_signals,
const absl::optional<GURL>& direct_from_seller_auction_signals,
const std::string& seller_signals_json,
const GURL& browser_signal_render_url,
double browser_signal_bid,
const absl::optional<blink::AdCurrency>& browser_signal_bid_currency,
double browser_signal_highest_scoring_other_bid,
const absl::optional<blink::AdCurrency>&
browser_signal_highest_scoring_other_bid_currency,
bool browser_signal_made_highest_scoring_other_bid,
absl::optional<double> browser_signal_ad_cost,
absl::optional<uint16_t> browser_signal_modeling_signals,
uint8_t browser_signal_join_count,
uint8_t browser_signal_recency,
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,
uint64_t trace_id,
ReportWinCallback report_win_callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
report_win_tasks_.emplace_front();
auto report_win_task = report_win_tasks_.begin();
report_win_task->reporting_id_field = reporting_id_field,
report_win_task->reporting_id = reporting_id;
report_win_task->auction_signals_json = auction_signals_json;
report_win_task->per_buyer_signals_json = per_buyer_signals_json;
report_win_task->seller_signals_json = seller_signals_json;
report_win_task->browser_signal_render_url = browser_signal_render_url;
report_win_task->browser_signal_bid = browser_signal_bid;
report_win_task->browser_signal_bid_currency = browser_signal_bid_currency;
report_win_task->browser_signal_highest_scoring_other_bid =
browser_signal_highest_scoring_other_bid;
report_win_task->browser_signal_highest_scoring_other_bid_currency =
browser_signal_highest_scoring_other_bid_currency;
report_win_task->browser_signal_made_highest_scoring_other_bid =
browser_signal_made_highest_scoring_other_bid;
report_win_task->browser_signal_ad_cost = browser_signal_ad_cost;
report_win_task->browser_signal_modeling_signals =
browser_signal_modeling_signals;
report_win_task->browser_signal_join_count = browser_signal_join_count;
report_win_task->browser_signal_recency = browser_signal_recency;
report_win_task->browser_signal_seller_origin = browser_signal_seller_origin;
report_win_task->browser_signal_top_level_seller_origin =
browser_signal_top_level_seller_origin;
if (has_bidding_signals_data_version) {
report_win_task->bidding_signals_data_version =
bidding_signals_data_version;
}
report_win_task->callback = std::move(report_win_callback);
report_win_task->trace_id = trace_id;
if (direct_from_seller_per_buyer_signals) {
// Deleting `report_win_task` will destroy
// `direct_from_seller_request_seller_signals` and thus abort this
// callback, so it's safe to use Unretained(this) and `report_win_task`
// here.
report_win_task->direct_from_seller_request_per_buyer_signals =
direct_from_seller_requester_per_buyer_signals_.LoadSignals(
*url_loader_factory_, *direct_from_seller_per_buyer_signals,
base::BindOnce(
&BidderWorklet::
OnDirectFromSellerPerBuyerSignalsDownloadedReportWin,
base::Unretained(this), report_win_task));
} else {
report_win_task->direct_from_seller_result_per_buyer_signals =
DirectFromSellerSignalsRequester::Result();
}
if (direct_from_seller_auction_signals) {
// Deleting `report_win_task` will destroy
// `direct_from_seller_request_auction_signals` and thus abort this
// callback, so it's safe to use Unretained(this) and `report_win_task`
// here.
report_win_task->direct_from_seller_request_auction_signals =
direct_from_seller_requester_auction_signals_.LoadSignals(
*url_loader_factory_, *direct_from_seller_auction_signals,
base::BindOnce(
&BidderWorklet::
OnDirectFromSellerAuctionSignalsDownloadedReportWin,
base::Unretained(this), report_win_task));
} else {
report_win_task->direct_from_seller_result_auction_signals =
DirectFromSellerSignalsRequester::Result();
}
report_win_task->trace_wait_deps_start = base::TimeTicks::Now();
TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("fledge", "wait_report_win_deps", trace_id);
RunReportWinIfReady(report_win_task);
}
void BidderWorklet::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)));
}
void BidderWorklet::FinishGenerateBid(
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 absl::optional<blink::AdCurrency>& expected_buyer_currency,
const absl::optional<GURL>& direct_from_seller_per_buyer_signals,
const absl::optional<GURL>& direct_from_seller_auction_signals) {
GenerateBidTaskList::iterator task = finalize_receiver_set_.current_context();
task->auction_signals_json = auction_signals_json;
task->per_buyer_signals_json = per_buyer_signals_json;
task->per_buyer_timeout = per_buyer_timeout;
task->expected_buyer_currency = expected_buyer_currency;
task->finalize_generate_bid_called = true;
HandleDirectFromSellerForGenerateBid(direct_from_seller_per_buyer_signals,
direct_from_seller_auction_signals,
task);
finalize_receiver_set_.Remove(*task->finalize_generate_bid_receiver_id);
task->finalize_generate_bid_receiver_id = absl::nullopt;
task->wait_promises = base::TimeTicks::Now() - task->trace_wait_deps_start;
GenerateBidIfReady(task);
}
BidderWorklet::GenerateBidTask::GenerateBidTask() = default;
BidderWorklet::GenerateBidTask::~GenerateBidTask() = default;
BidderWorklet::ReportWinTask::ReportWinTask() = default;
BidderWorklet::ReportWinTask::~ReportWinTask() = default;
BidderWorklet::V8State::V8State(
scoped_refptr<AuctionV8Helper> v8_helper,
scoped_refptr<AuctionV8Helper::DebugId> debug_id,
mojo::PendingRemote<mojom::AuctionSharedStorageHost>
shared_storage_host_remote,
const GURL& script_source_url,
const url::Origin& top_window_origin,
mojom::AuctionWorkletPermissionsPolicyStatePtr permissions_policy_state,
const absl::optional<GURL>& wasm_helper_url,
const absl::optional<GURL>& trusted_bidding_signals_url,
base::WeakPtr<BidderWorklet> parent)
: v8_helper_(std::move(v8_helper)),
debug_id_(std::move(debug_id)),
parent_(std::move(parent)),
user_thread_(base::SequencedTaskRunner::GetCurrentDefault()),
owner_(url::Origin::Create(script_source_url)),
script_source_url_(script_source_url),
top_window_origin_(top_window_origin),
permissions_policy_state_(std::move(permissions_policy_state)),
wasm_helper_url_(wasm_helper_url),
trusted_bidding_signals_url_(trusted_bidding_signals_url) {
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 BidderWorklet::V8State::SetWorkletScript(
WorkletLoader::Result worklet_script) {
DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);
worklet_script_ = WorkletLoader::TakeScript(std::move(worklet_script));
}
void BidderWorklet::V8State::SetWasmHelper(
WorkletWasmLoader::Result wasm_helper) {
DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);
wasm_helper_ = std::move(wasm_helper);
}
BidderWorklet::V8State::SingleGenerateBidResult::SingleGenerateBidResult() =
default;
BidderWorklet::V8State::SingleGenerateBidResult::SingleGenerateBidResult(
std::unique_ptr<ContextRecycler> context_recycler_for_rerun,
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,
absl::optional<double> set_priority,
base::flat_map<std::string, mojom::PrioritySignalsDoublePtr>
update_priority_signals_overrides,
PrivateAggregationRequests pa_requests,
std::vector<std::string> error_msgs)
: context_recycler_for_rerun(std::move(context_recycler_for_rerun)),
bid(std::move(bid)),
bidding_signals_data_version(std::move(bidding_signals_data_version)),
debug_loss_report_url(std::move(debug_loss_report_url)),
debug_win_report_url(std::move(debug_win_report_url)),
set_priority(std::move(set_priority)),
update_priority_signals_overrides(
std::move(update_priority_signals_overrides)),
pa_requests(std::move(pa_requests)),
error_msgs(std::move(error_msgs)) {}
BidderWorklet::V8State::SingleGenerateBidResult::SingleGenerateBidResult(
SingleGenerateBidResult&&) = default;
BidderWorklet::V8State::SingleGenerateBidResult::~SingleGenerateBidResult() =
default;
BidderWorklet::V8State::SingleGenerateBidResult&
BidderWorklet::V8State::SingleGenerateBidResult::operator=(
SingleGenerateBidResult&&) = default;
void BidderWorklet::V8State::ReportWin(
mojom::ReportingIdField reporting_id_field,
const std::string& reporting_id,
const absl::optional<std::string>& auction_signals_json,
const absl::optional<std::string>& per_buyer_signals_json,
DirectFromSellerSignalsRequester::Result
direct_from_seller_result_per_buyer_signals,
DirectFromSellerSignalsRequester::Result
direct_from_seller_result_auction_signals,
const std::string& seller_signals_json,
const GURL& browser_signal_render_url,
double browser_signal_bid,
const absl::optional<blink::AdCurrency>& browser_signal_bid_currency,
double browser_signal_highest_scoring_other_bid,
const absl::optional<blink::AdCurrency>&
browser_signal_highest_scoring_other_bid_currency,
bool browser_signal_made_highest_scoring_other_bid,
const absl::optional<double>& browser_signal_ad_cost,
const absl::optional<uint16_t>& browser_signal_modeling_signals,
uint8_t browser_signal_join_count,
uint8_t browser_signal_recency,
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,
uint64_t trace_id,
ReportWinCallbackInternal 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 (!AppendJsonValueOrNull(v8_helper_.get(), context,
base::OptionalToPtr(auction_signals_json),
&args) ||
!AppendJsonValueOrNull(v8_helper_.get(), context,
base::OptionalToPtr(per_buyer_signals_json),
&args) ||
!v8_helper_->AppendJsonValue(context, seller_signals_json, &args)) {
PostReportWinCallbackToUserThread(std::move(callback),
/*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);
const char* reporting_id_field_name = nullptr;
switch (reporting_id_field) {
case mojom::ReportingIdField::kInterestGroupName:
reporting_id_field_name = "interestGroupName";
break;
case mojom::ReportingIdField::kBuyerReportingId:
reporting_id_field_name = "buyerReportingId";
break;
case mojom::ReportingIdField::kBuyerAndSellerReportingId:
reporting_id_field_name = "buyerAndSellerReportingId";
break;
}
if (!browser_signals_dict.Set("topWindowHostname",
top_window_origin_.host()) ||
!browser_signals_dict.Set(
"interestGroupOwner",
url::Origin::Create(script_source_url_).Serialize()) ||
!browser_signals_dict.Set(reporting_id_field_name, 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_signal_ad_cost.has_value() &&
!browser_signals_dict.Set("adCost", *browser_signal_ad_cost)) ||
(browser_signal_modeling_signals.has_value() &&
!browser_signals_dict.Set(
"modelingSignals",
static_cast<double>(*browser_signal_modeling_signals))) ||
!browser_signals_dict.Set(
"joinCount", static_cast<double>(browser_signal_join_count)) ||
!browser_signals_dict.Set("recency",
static_cast<double>(browser_signal_recency)) ||
!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)) ||
!browser_signals_dict.Set(
"madeHighestScoringOtherBid",
browser_signal_made_highest_scoring_other_bid) ||
!browser_signals_dict.Set("seller",
browser_signal_seller_origin.Serialize()) ||
(browser_signal_top_level_seller_origin &&
!browser_signals_dict.Set(
"topLevelSeller",
browser_signal_top_level_seller_origin->Serialize())) ||
(bidding_signals_data_version.has_value() &&
!browser_signals_dict.Set("dataVersion",
bidding_signals_data_version.value()))) {
PostReportWinCallbackToUserThread(std::move(callback),
/*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> per_buyer_signals =
direct_from_seller_result_per_buyer_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("perBuyerSignals",
per_buyer_signals) ||
!direct_from_seller_signals_dict.Set("auctionSignals", auction_signals)) {
PostReportWinCallbackToUserThread(std::move(callback),
/*report_url=*/absl::nullopt,
/*ad_beacon_map=*/{}, /*pa_requests=*/{},
/*errors=*/std::move(errors_out));
return;
}
args.push_back(direct_from_seller_signals);
// An empty return value indicates an exception was thrown. Any other return
// value indicates no exception.
v8_helper_->MaybeTriggerInstrumentationBreakpoint(
*debug_id_, "beforeBidderWorkletReportingStart");
v8::Local<v8::UnboundScript> unbound_worklet_script =
worklet_script_.Get(isolate);
TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("fledge", "report_win", trace_id);
bool script_failed =
!v8_helper_->RunScript(context, unbound_worklet_script, debug_id_.get(),
/*script_timeout=*/absl::nullopt, errors_out);
if (script_failed) {
TRACE_EVENT_NESTABLE_ASYNC_END0("fledge", "report_win", trace_id);
PostReportWinCallbackToUserThread(
std::move(callback), /*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);
}
script_failed =
v8_helper_
->CallFunction(context, debug_id_.get(),
v8_helper_->FormatScriptName(unbound_worklet_script),
"reportWin", args, /*script_timeout=*/absl::nullopt,
errors_out)
.IsEmpty();
TRACE_EVENT_NESTABLE_ASYNC_END0("fledge", "report_win", trace_id);
if (script_failed) {
// Keep Private Aggregation API requests since `reportWin()` might use it to
// detect script timeout or failures.
PostReportWinCallbackToUserThread(
std::move(callback), /*report_url=*/absl::nullopt,
/*ad_beacon_map=*/{},
context_recycler.private_aggregation_bindings()
->TakePrivateAggregationRequests(),
std::move(errors_out));
return;
}
// This covers both the case where a report URL was provided, and the case one
// was not.
PostReportWinCallbackToUserThread(
std::move(callback), context_recycler.report_bindings()->report_url(),
context_recycler.register_ad_beacon_bindings()->TakeAdBeaconMap(),
context_recycler.private_aggregation_bindings()
->TakePrivateAggregationRequests(),
std::move(errors_out));
}
void BidderWorklet::V8State::GenerateBid(
mojom::BidderWorkletNonSharedParamsPtr bidder_worklet_non_shared_params,
mojom::KAnonymityBidMode kanon_mode,
const url::Origin& interest_group_join_origin,
const absl::optional<std::string>& auction_signals_json,
const absl::optional<std::string>& per_buyer_signals_json,
DirectFromSellerSignalsRequester::Result
direct_from_seller_result_per_buyer_signals,
DirectFromSellerSignalsRequester::Result
direct_from_seller_result_auction_signals,
const absl::optional<base::TimeDelta> per_buyer_timeout,
const absl::optional<blink::AdCurrency>& expected_buyer_currency,
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,
uint64_t trace_id,
base::ScopedClosureRunner cleanup_generate_bid_task,
GenerateBidCallbackInternal callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);
TRACE_EVENT_NESTABLE_ASYNC_END0("fledge", "post_v8_task", trace_id);
// Don't need to run `cleanup_generate_bid_task` if this method is invoked;
// it's bound to the closure to clean things up if this method got cancelled.
cleanup_generate_bid_task.ReplaceClosure(base::OnceClosure());
base::TimeTicks bidding_start = base::TimeTicks::Now();
absl::optional<SingleGenerateBidResult> result = GenerateSingleBid(
*bidder_worklet_non_shared_params, interest_group_join_origin,
base::OptionalToPtr(auction_signals_json),
base::OptionalToPtr(per_buyer_signals_json),
direct_from_seller_result_per_buyer_signals,
direct_from_seller_result_auction_signals, per_buyer_timeout,
expected_buyer_currency, browser_signal_seller_origin,
base::OptionalToPtr(browser_signal_top_level_seller_origin),
bidding_browser_signals, auction_start_time,
trusted_bidding_signals_result, trace_id,
/*context_recycler_for_rerun=*/nullptr,
/*restrict_to_kanon_ads=*/false);
if (!result.has_value()) {
PostErrorBidCallbackToUserThread(
std::move(callback),
/*bidding_latency=*/base::TimeTicks::Now() - bidding_start);
return;
}
mojom::BidderWorkletBidPtr bid = std::move(result->bid);
mojom::BidderWorkletKAnonEnforcedBidPtr kanon_bid;
// No need for `kanon_bid` if not doing anything with k-anon, or if bidding
// fails w/o the restriction. This assumes it follows it won't succeed with
// k-anon restriction, but if we don't we will have to re-run every rejected
// bid, which is unreasonable.
if (kanon_mode != mojom::KAnonymityBidMode::kNone && bid) {
if (IsKAnon(bidder_worklet_non_shared_params.get(), script_source_url_,
bid)) {
// Result is already k-anon so it's the same for both runs.
kanon_bid =
mojom::BidderWorkletKAnonEnforcedBid::NewSameAsNonEnforced(nullptr);
} else {
// Main run got a non-k-anon result, and we care about k-anonymity. Re-run
// the bidder with non-k-anon ads hidden.
absl::optional<SingleGenerateBidResult> restricted_result =
GenerateSingleBid(
*bidder_worklet_non_shared_params.get(),
interest_group_join_origin,
base::OptionalToPtr(auction_signals_json),
base::OptionalToPtr(per_buyer_signals_json),
direct_from_seller_result_per_buyer_signals,
direct_from_seller_result_auction_signals, per_buyer_timeout,
expected_buyer_currency, browser_signal_seller_origin,
base::OptionalToPtr(browser_signal_top_level_seller_origin),
bidding_browser_signals, auction_start_time,
trusted_bidding_signals_result, trace_id,
std::move(result->context_recycler_for_rerun),
/*restrict_to_kanon_ads=*/true);
if (restricted_result.has_value() && restricted_result->bid) {
kanon_bid = mojom::BidderWorkletKAnonEnforcedBid::NewBid(
std::move(restricted_result->bid));
}
if (kanon_mode == mojom::KAnonymityBidMode::kEnforce) {
PrivateAggregationRequests non_kanon_pa_requests =
std::move(result->pa_requests);
base::EraseIf(
non_kanon_pa_requests,
[](const auction_worklet::mojom::PrivateAggregationRequestPtr&
request) { return !HasKAnonFailureComponent(request); });
// We are enforcing the k-anonymity, so the restricted result is the one
// to use for reporting, etc., and needs to succeed.
if (!restricted_result.has_value()) {
PostErrorBidCallbackToUserThread(
std::move(callback),
/*bidding_latency=*/base::TimeTicks::Now() - bidding_start,
std::move(non_kanon_pa_requests));
return;
}
result = std::move(restricted_result);
result->non_kanon_pa_requests = std::move(non_kanon_pa_requests);
} else {
DCHECK_EQ(kanon_mode, mojom::KAnonymityBidMode::kSimulate);
// Here, `result` is already what we want for reporting, etc., so
// nothing actually to do in this case.
}
}
}
user_thread_->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback), std::move(bid), std::move(kanon_bid),
std::move(result->bidding_signals_data_version),
std::move(result->debug_loss_report_url),
std::move(result->debug_win_report_url),
std::move(result->set_priority),
std::move(result->update_priority_signals_overrides),
std::move(result->pa_requests),
std::move(result->non_kanon_pa_requests),
/*bidding_latency=*/base::TimeTicks::Now() - bidding_start,
std::move(result->error_msgs)));
}
absl::optional<BidderWorklet::V8State::SingleGenerateBidResult>
BidderWorklet::V8State::GenerateSingleBid(
const mojom::BidderWorkletNonSharedParams& bidder_worklet_non_shared_params,
const url::Origin& interest_group_join_origin,
const std::string* auction_signals_json,
const std::string* per_buyer_signals_json,
const DirectFromSellerSignalsRequester::Result&
direct_from_seller_result_per_buyer_signals,
const DirectFromSellerSignalsRequester::Result&
direct_from_seller_result_auction_signals,
const absl::optional<base::TimeDelta> per_buyer_timeout,
const absl::optional<blink::AdCurrency>& expected_buyer_currency,
const url::Origin& browser_signal_seller_origin,
const url::Origin* browser_signal_top_level_seller_origin,
const mojom::BiddingBrowserSignalsPtr& bidding_browser_signals,
base::Time auction_start_time,
const scoped_refptr<TrustedSignals::Result>& trusted_bidding_signals_result,
uint64_t trace_id,
std::unique_ptr<ContextRecycler> context_recycler_for_rerun,
bool restrict_to_kanon_ads) {
// Can't make a bid without any ads, or if we aren't permitted to spend any
// time on it.
if (!bidder_worklet_non_shared_params.ads ||
(per_buyer_timeout.has_value() && per_buyer_timeout.value().is_zero())) {
return absl::nullopt;
}
if (context_recycler_for_rerun) {
DCHECK(restrict_to_kanon_ads);
}
base::TimeTicks start = base::TimeTicks::Now();
std::vector<std::string> errors_out;
AuctionV8Helper::FullIsolateScope isolate_scope(v8_helper_.get());
v8::Isolate* isolate = v8_helper_->isolate();
ContextRecycler* context_recycler = nullptr;
std::unique_ptr<ContextRecycler> fresh_context_recycler;
bool reused_context = false;
bool should_deep_freeze = false;
// See if we can reuse an existing context.
switch (bidder_worklet_non_shared_params.execution_mode) {
case blink::mojom::InterestGroup::ExecutionMode::kGroupedByOriginMode:
if (context_recycler_for_origin_group_mode_ &&
join_origin_for_origin_group_mode_ == interest_group_join_origin) {
context_recycler = context_recycler_for_origin_group_mode_.get();
reused_context = true;
}
break;
case blink::mojom::InterestGroup::ExecutionMode::kFrozenContext:
should_deep_freeze = true;
if (context_recycler_for_frozen_context_) {
context_recycler = context_recycler_for_frozen_context_.get();
reused_context = true;
}
break;
case blink::mojom::InterestGroup::ExecutionMode::kCompatibilityMode:
break;
}
base::UmaHistogramBoolean("Ads.InterestGroup.Auction.ContextReused",
reused_context);
// See if we can reuse a context for k-anon re-run. The group-by-origin and
// frozen context mode would do that, too, so this is only a fallback for
// when that's not on.
if (!context_recycler && context_recycler_for_rerun) {
context_recycler = context_recycler_for_rerun.get();
reused_context = true;
}
v8_helper_->MaybeTriggerInstrumentationBreakpoint(
*debug_id_, "beforeBidderWorkletBiddingStart");
// No recycled context, make a fresh one.
if (!context_recycler) {
fresh_context_recycler = CreateContextRecyclerAndRunTopLevelForGenerateBid(
trace_id, per_buyer_timeout, should_deep_freeze, errors_out);
if (!fresh_context_recycler) {
return absl::make_optional(SingleGenerateBidResult(
std::unique_ptr<ContextRecycler>(), mojom::BidderWorkletBidPtr(),
/*bidding_signals_data_version=*/absl::nullopt,
/*debug_loss_report_url=*/absl::nullopt,
/*debug_win_report_url=*/absl::nullopt,
/*set_priority=*/absl::nullopt,
/*update_priority_signals_overrides=*/{},
/*pa_requests=*/{}, std::move(errors_out)));
}
context_recycler = fresh_context_recycler.get();
// Save the context for next time (if applicable).
switch (bidder_worklet_non_shared_params.execution_mode) {
case blink::mojom::InterestGroup::ExecutionMode::kGroupedByOriginMode:
context_recycler_for_origin_group_mode_ =
std::move(fresh_context_recycler);
join_origin_for_origin_group_mode_ = interest_group_join_origin;
break;
case blink::mojom::InterestGroup::ExecutionMode::kFrozenContext:
context_recycler_for_frozen_context_ =
std::move(fresh_context_recycler);
break;
case blink::mojom::InterestGroup::ExecutionMode::kCompatibilityMode:
break;
}
}
base::RepeatingCallback<bool(const GURL&)> should_exclude_ad_due_to_kanon =
base::BindRepeating(
[](bool restrict_to_kanon_ads,
const mojom::BidderWorkletNonSharedParams* params,
const url::Origin* owner, const GURL* bidding_url,
const GURL& ad_url) {
return restrict_to_kanon_ads &&
!BidderWorklet::IsKAnon(
params,
blink::KAnonKeyForAdBid(*owner, *bidding_url, ad_url));
},
restrict_to_kanon_ads, &bidder_worklet_non_shared_params, &owner_,
&script_source_url_);
base::RepeatingCallback<bool(const GURL&)>
should_exclude_component_ad_due_to_kanon = base::BindRepeating(
[](bool restrict_to_kanon_ads,
const mojom::BidderWorkletNonSharedParams* params,
const GURL& ad_url) {
return restrict_to_kanon_ads &&
!BidderWorklet::IsKAnon(
params, blink::KAnonKeyForAdComponentBid(ad_url));
},
restrict_to_kanon_ads, &bidder_worklet_non_shared_params);
ContextRecyclerScope context_recycler_scope(*context_recycler);
v8::Local<v8::Context> context = context_recycler_scope.GetContext();
context_recycler->set_bid_bindings()->ReInitialize(
start, browser_signal_top_level_seller_origin != nullptr,
&bidder_worklet_non_shared_params, expected_buyer_currency,
should_exclude_ad_due_to_kanon, should_exclude_component_ad_due_to_kanon);
std::vector<v8::Local<v8::Value>> args;
v8::Local<v8::Object> interest_group_object = v8::Object::New(isolate);
gin::Dictionary interest_group_dict(isolate, interest_group_object);
if (!interest_group_dict.Set("owner", owner_.Serialize()) ||
!interest_group_dict.Set("name", bidder_worklet_non_shared_params.name) ||
!interest_group_dict.Set("useBiddingSignalsPrioritization",
bidder_worklet_non_shared_params
.enable_bidding_signals_prioritization) ||
!interest_group_dict.Set("biddingLogicUrl", script_source_url_.spec()) ||
(wasm_helper_url_ &&
!interest_group_dict.Set("biddingWasmHelperUrl",
wasm_helper_url_->spec())) ||
(bidder_worklet_non_shared_params.update_url &&
(!interest_group_dict.Set(
"updateUrl", bidder_worklet_non_shared_params.update_url->spec()) ||
// TODO(https://crbug.com/1420080) Remove deprecated `dailyUpdateUrl`
// alias.
!interest_group_dict.Set(
"dailyUpdateUrl",
bidder_worklet_non_shared_params.update_url->spec()))) ||
(trusted_bidding_signals_url_ &&
!interest_group_dict.Set("trustedBiddingSignalsUrl",
trusted_bidding_signals_url_->spec()))) {
return absl::nullopt;
}
context_recycler->interest_group_lazy_filler()->ReInitialize(
&bidder_worklet_non_shared_params);
if (!context_recycler->interest_group_lazy_filler()->FillInObject(
interest_group_object)) {
return absl::nullopt;
}
v8::Local<v8::Value> ads;
if (!CreateAdVector(v8_helper_.get(), context, should_exclude_ad_due_to_kanon,
*bidder_worklet_non_shared_params.ads, ads) ||
!v8_helper_->InsertValue("ads", std::move(ads), interest_group_object)) {
return absl::nullopt;
}
if (bidder_worklet_non_shared_params.ad_components) {
v8::Local<v8::Value> ad_components;
if (!CreateAdVector(
v8_helper_.get(), context, should_exclude_component_ad_due_to_kanon,
*bidder_worklet_non_shared_params.ad_components, ad_components) ||
!v8_helper_->InsertValue("adComponents", std::move(ad_components),
interest_group_object)) {
return absl::nullopt;
}
}
args.push_back(std::move(interest_group_object));
if (!AppendJsonValueOrNull(v8_helper_.get(), context, auction_signals_json,
&args) ||
!AppendJsonValueOrNull(v8_helper_.get(), context, per_buyer_signals_json,
&args)) {
return absl::nullopt;
}
v8::Local<v8::Value> trusted_signals;
if (!trusted_bidding_signals_result ||
!bidder_worklet_non_shared_params.trusted_bidding_signals_keys ||
bidder_worklet_non_shared_params.trusted_bidding_signals_keys->empty()) {
trusted_signals = v8::Null(isolate);
} else {
trusted_signals = trusted_bidding_signals_result->GetBiddingSignals(
v8_helper_.get(), context,
*bidder_worklet_non_shared_params.trusted_bidding_signals_keys);
}
args.push_back(trusted_signals);
absl::optional<uint32_t> bidding_signals_data_version;
if (trusted_bidding_signals_result) {
bidding_signals_data_version =
trusted_bidding_signals_result->GetDataVersion();
}
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()) ||
!browser_signals_dict.Set("seller",
browser_signal_seller_origin.Serialize()) ||
(browser_signal_top_level_seller_origin &&
!browser_signals_dict.Set(
"topLevelSeller",
browser_signal_top_level_seller_origin->Serialize())) ||
!browser_signals_dict.Set("joinCount",
bidding_browser_signals->join_count) ||
!browser_signals_dict.Set("bidCount",
bidding_browser_signals->bid_count) ||
(bidding_signals_data_version.has_value() &&
!browser_signals_dict.Set("dataVersion",
bidding_signals_data_version.value()))) {
return absl::nullopt;
}
if (wasm_helper_.success()) {
v8::Local<v8::WasmModuleObject> module;
v8::Maybe<bool> result = v8::Nothing<bool>();
if (WorkletWasmLoader::MakeModule(wasm_helper_).ToLocal(&module)) {
result = browser_signals->Set(
context, gin::StringToV8(isolate, "wasmHelper"), module);
}
if (result.IsNothing() || !result.FromJust()) {
return absl::nullopt;
}
}
context_recycler->bidding_browser_signals_lazy_filler()->ReInitialize(
bidding_browser_signals.get(), auction_start_time);
if (!context_recycler->bidding_browser_signals_lazy_filler()->FillInObject(
browser_signals)) {
return absl::nullopt;
}
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);
v8::Local<v8::Value> per_buyer_signals =
direct_from_seller_result_per_buyer_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("perBuyerSignals",
per_buyer_signals) ||
!direct_from_seller_signals_dict.Set("auctionSignals", auction_signals)) {
return absl::nullopt;
}
args.push_back(direct_from_seller_signals);
v8::Local<v8::Value> generate_bid_result;
TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("fledge", "generate_bid", trace_id);
bool got_return_value =
v8_helper_
->CallFunction(
context, debug_id_.get(),
v8_helper_->FormatScriptName(worklet_script_.Get(isolate)),
"generateBid", args, std::move(per_buyer_timeout), errors_out)
.ToLocal(&generate_bid_result);
TRACE_EVENT_NESTABLE_ASYNC_END0("fledge", "generate_bid", trace_id);
base::UmaHistogramTimes("Ads.InterestGroup.Auction.GenerateBidTime",
base::TimeTicks::Now() - start);
if (got_return_value) {
context_recycler->set_bid_bindings()->SetBid(
generate_bid_result,
base::StrCat({script_source_url_.spec(), " generateBid() "}),
errors_out);
}
if (!context_recycler->set_bid_bindings()->has_bid()) {
// If no bid was returned (due to an error or just not choosing to bid), or
// the method timed out and no intermediate result was given through
// `setBid()`, return an error. Keep debug loss reports and Private
// Aggregation API requests since `generateBid()` might use them to detect
// script timeout or failures. Keep any set priority and set priority
// overrides because an interest group may want to update them even when not
// bidding. No need to return a ContextRecycler since this will not be
// re-run.
return absl::make_optional(SingleGenerateBidResult(
std::unique_ptr<ContextRecycler>(), mojom::BidderWorkletBidPtr(),
/*bidding_signals_data_version=*/absl::nullopt,
context_recycler->for_debugging_only_bindings()->TakeLossReportUrl(),
/*debug_win_report_url=*/absl::nullopt,
context_recycler->set_priority_bindings()->set_priority(),
context_recycler->set_priority_signals_override_bindings()
->TakeSetPrioritySignalsOverrides(),
context_recycler->private_aggregation_bindings()
->TakePrivateAggregationRequests(),
std::move(errors_out)));
}
// If the context recycler wasn't saved based on `execution_mode`,
// `fresh_context_recycler` is non-null here, and it will be provided to the
// caller for potential re-use for k-anon re-run.
return absl::make_optional(SingleGenerateBidResult(
std::move(fresh_context_recycler),
context_recycler->set_bid_bindings()->TakeBid(),
bidding_signals_data_version,
context_recycler->for_debugging_only_bindings()->TakeLossReportUrl(),
context_recycler->for_debugging_only_bindings()->TakeWinReportUrl(),
context_recycler->set_priority_bindings()->set_priority(),
context_recycler->set_priority_signals_override_bindings()
->TakeSetPrioritySignalsOverrides(),
context_recycler->private_aggregation_bindings()
->TakePrivateAggregationRequests(),
std::move(errors_out)));
}
std::unique_ptr<ContextRecycler>
BidderWorklet::V8State::CreateContextRecyclerAndRunTopLevelForGenerateBid(
uint64_t trace_id,
const absl::optional<base::TimeDelta> per_buyer_timeout,
bool should_deep_freeze,
std::vector<std::string>& errors_out) {
base::TimeTicks start = base::TimeTicks::Now();
std::unique_ptr<ContextRecycler> context_recycler =
std::make_unique<ContextRecycler>(v8_helper_.get());
ContextRecyclerScope context_recycler_scope(*context_recycler);
v8::Local<v8::Context> context = context_recycler_scope.GetContext();
v8::Local<v8::UnboundScript> unbound_worklet_script =
worklet_script_.Get(v8_helper_->isolate());
TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("fledge", "biddingScript", trace_id);
bool success =
v8_helper_->RunScript(context, unbound_worklet_script, debug_id_.get(),
per_buyer_timeout, errors_out);
TRACE_EVENT_NESTABLE_ASYNC_END0("fledge", "biddingScript", trace_id);
base::UmaHistogramTimes("Ads.InterestGroup.Auction.BidScriptTime",
base::TimeTicks::Now() - start);
if (!success) {
return nullptr;
}
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);
}
context_recycler->AddSetBidBindings();
context_recycler->AddSetPriorityBindings();
context_recycler->AddSetPrioritySignalsOverrideBindings();
context_recycler->AddInterestGroupLazyFiller();
context_recycler->AddBiddingBrowserSignalsLazyFiller();
if (should_deep_freeze) {
v8::TryCatch try_catch(v8_helper_->isolate());
DeepFreezeAllowAll allow_jsapiobject;
context->DeepFreeze(&allow_jsapiobject);
if (try_catch.HasCaught()) {
errors_out.push_back(AuctionV8Helper::FormatExceptionMessage(
context, try_catch.Message()));
return nullptr;
}
}
return context_recycler;
}
void BidderWorklet::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_);
}
BidderWorklet::V8State::~V8State() {
DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);
}
void BidderWorklet::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(
&BidderWorklet::V8State::PostResumeToUserThread, parent_, user_thread_));
}
// static
void BidderWorklet::V8State::PostResumeToUserThread(
base::WeakPtr<BidderWorklet> parent,
scoped_refptr<base::SequencedTaskRunner> user_thread) {
// This is static since it's called from debugging, not BidderWorklet,
// so the usual guarantee that BidderWorklet posts things before posting
// V8State destruction is irrelevant.
user_thread->PostTask(FROM_HERE,
base::BindOnce(&BidderWorklet::ResumeIfPaused, parent));
}
void BidderWorklet::V8State::PostReportWinCallbackToUserThread(
ReportWinCallbackInternal callback,
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(v8_sequence_checker_);
user_thread_->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(report_url),
std::move(ad_beacon_map),
std::move(pa_requests), std::move(errors)));
}
void BidderWorklet::V8State::PostErrorBidCallbackToUserThread(
GenerateBidCallbackInternal callback,
base::TimeDelta bidding_latency,
PrivateAggregationRequests non_kanon_pa_requests,
std::vector<std::string> error_msgs) {
DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);
user_thread_->PostTask(
FROM_HERE,
base::BindOnce(
std::move(callback), mojom::BidderWorkletBidPtr(),
mojom::BidderWorkletKAnonEnforcedBidPtr(),
/*bidding_signals_data_version=*/absl::nullopt,
/*debug_loss_report_url=*/absl::nullopt,
/*debug_win_report_url=*/absl::nullopt,
/*set_priority=*/absl::nullopt,
/*update_priority_signals_overrides=*/
base::flat_map<std::string, mojom::PrioritySignalsDoublePtr>(),
/*pa_requests=*/
PrivateAggregationRequests(), std::move(non_kanon_pa_requests),
bidding_latency, std::move(error_msgs)));
}
void BidderWorklet::ResumeIfPaused() {
DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
if (!paused_) {
return;
}
paused_ = false;
Start();
}
void BidderWorklet::Start() {
DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
DCHECK(!paused_);
base::UmaHistogramCounts100000(
"Ads.InterestGroup.Net.RequestUrlSizeBytes.BiddingScriptJS",
script_source_url_.spec().size());
worklet_loader_ = std::make_unique<WorkletLoader>(
url_loader_factory_.get(), script_source_url_, v8_helper_, debug_id_,
base::BindOnce(&BidderWorklet::OnScriptDownloaded,
base::Unretained(this)));
if (wasm_helper_url_.has_value()) {
base::UmaHistogramCounts100000(
"Ads.InterestGroup.Net.RequestUrlSizeBytes.BiddingScriptWasm",
wasm_helper_url_->spec().size());
wasm_loader_ = std::make_unique<WorkletWasmLoader>(
url_loader_factory_.get(), wasm_helper_url_.value(), v8_helper_,
debug_id_,
base::BindOnce(&BidderWorklet::OnWasmDownloaded,
base::Unretained(this)));
}
}
void BidderWorklet::OnScriptDownloaded(WorkletLoader::Result worklet_script,
absl::optional<std::string> error_msg) {
DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
base::UmaHistogramCounts10M(
"Ads.InterestGroup.Net.ResponseSizeBytes.BiddingScriptJS",
worklet_script.original_size_bytes());
base::UmaHistogramTimes("Ads.InterestGroup.Net.DownloadTime.BiddingScriptJS",
worklet_script.download_time());
worklet_loader_.reset();
// On failure, close pipe and delete `this`, as it can't do anything without a
// loaded script.
if (!worklet_script.success()) {
std::move(close_pipe_callback_)
.Run(error_msg ? error_msg.value() : std::string());
// `this` should be deleted at this point.
return;
}
if (error_msg.has_value()) {
load_code_error_msgs_.push_back(std::move(error_msg.value()));
}
v8_runner_->PostTask(FROM_HERE,
base::BindOnce(&BidderWorklet::V8State::SetWorkletScript,
base::Unretained(v8_state_.get()),
std::move(worklet_script)));
MaybeRecordCodeWait();
RunReadyTasks();
}
void BidderWorklet::OnWasmDownloaded(WorkletWasmLoader::Result wasm_helper,
absl::optional<std::string> error_msg) {
DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
base::UmaHistogramCounts10M(
"Ads.InterestGroup.Net.ResponseSizeBytes.BiddingScriptWasm",
wasm_helper.original_size_bytes());
base::UmaHistogramTimes(
"Ads.InterestGroup.Net.DownloadTime.BiddingScriptWasm",
wasm_helper.download_time());
wasm_loader_.reset();
// If the WASM helper is actually requested, delete `this` and inform the
// browser process of the failure. ReportWin() calls would theoretically still
// be allowed, but that adds a lot more complexity around BidderWorklet reuse.
if (!wasm_helper.success()) {
std::move(close_pipe_callback_)
.Run(error_msg ? error_msg.value() : std::string());
// `this` should be deleted at this point.
return;
}
if (error_msg.has_value()) {
load_code_error_msgs_.push_back(std::move(error_msg.value()));
}
v8_runner_->PostTask(FROM_HERE,
base::BindOnce(&BidderWorklet::V8State::SetWasmHelper,
base::Unretained(v8_state_.get()),
std::move(wasm_helper)));
MaybeRecordCodeWait();
RunReadyTasks();
}
void BidderWorklet::MaybeRecordCodeWait() {
if (!IsCodeReady()) {
return;
}
base::TimeTicks now = base::TimeTicks::Now();
for (auto& task : generate_bid_tasks_) {
task.wait_code = now - task.trace_wait_deps_start;
}
for (auto& task : report_win_tasks_) {
task.wait_code = now - task.trace_wait_deps_start;
}
}
void BidderWorklet::RunReadyTasks() {
// Run all GenerateBid() tasks that are ready. GenerateBidIfReady() does *not*
// modify `generate_bid_tasks_` when invoked, so this is safe.
for (auto generate_bid_task = generate_bid_tasks_.begin();
generate_bid_task != generate_bid_tasks_.end(); ++generate_bid_task) {
GenerateBidIfReady(generate_bid_task);
}
// While reportWin() doesn't use WASM, since we do load it, we wait for it in
// order to ensure determinism if the load fails.
if (!IsCodeReady()) {
return;
}
// Run all ReportWin() tasks that are ready. RunReportWinIfReady() does *not*
// modify `report_win_tasks_` when invoked, so this is safe.
for (auto report_win_task = report_win_tasks_.begin();
report_win_task != report_win_tasks_.end(); ++report_win_task) {
RunReportWinIfReady(report_win_task);
}
}
void BidderWorklet::OnTrustedBiddingSignalsDownloaded(
GenerateBidTaskList::iterator task,
scoped_refptr<TrustedSignals::Result> result,
absl::optional<std::string> error_msg) {
DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
const TrustedSignals::Result::PriorityVector* priority_vector = nullptr;
if (result) {
priority_vector =
result->GetPriorityVector(task->bidder_worklet_non_shared_params->name);
}
task->trusted_bidding_signals_error_msg = std::move(error_msg);
task->trusted_bidding_signals_result = std::move(result);
task->trusted_bidding_signals_request.reset();
// Deleting `generate_bid_task` will destroy `generate_bid_client` and thus
// abort this callback, so it's safe to use Unretained(this) and
// `generate_bid_task` here.
task->generate_bid_client->OnBiddingSignalsReceived(
priority_vector ? *priority_vector
: TrustedSignals::Result::PriorityVector(),
/*trusted_signals_fetch_latency=*/base::TimeTicks::Now() -
task->trace_wait_deps_start,
base::BindOnce(&BidderWorklet::SignalsReceivedCallback,
base::Unretained(this), task));
}
void BidderWorklet::OnGenerateBidClientDestroyed(
GenerateBidTaskList::iterator task) {
// If the task hasn't received the signals called callback or the code hasn't
// loaded, it hasn't posted a task to run off-thread, so can be safely
// deleted, as everything else, including fetching trusted bidding signals,
// can be safely cancelled.
if (!IsReadyToGenerateBid(*task)) {
CleanUpBidTaskOnUserThread(task);
} else {
// Otherwise, there should be a pending V8 call. Try to cancel that, but if
// it already started, it will just run and invoke the GenerateBidClient's
// OnGenerateBidComplete() method, which will safely do nothing since the
// pipe is now closed.
DCHECK_NE(task->task_id, base::CancelableTaskTracker::kBadTaskId);
cancelable_task_tracker_.TryCancel(task->task_id);
}
}
void BidderWorklet::SignalsReceivedCallback(
GenerateBidTaskList::iterator task) {
DCHECK(!task->signals_received_callback_invoked);
task->signals_received_callback_invoked = true;
task->wait_trusted_signals =
base::TimeTicks::Now() - task->trace_wait_deps_start;
GenerateBidIfReady(task);
}
void BidderWorklet::HandleDirectFromSellerForGenerateBid(
const absl::optional<GURL>& direct_from_seller_per_buyer_signals,
const absl::optional<GURL>& direct_from_seller_auction_signals,
GenerateBidTaskList::iterator task) {
if (direct_from_seller_per_buyer_signals) {
// We expect each parameter to be provided at most once between
// BeginGenerateBid/FinishGenerateBid. If we are already fetching this
// kind of signals this is clearly the second time it was specified.
DCHECK(!task->direct_from_seller_request_per_buyer_signals);
// Deleting `task` will destroy
// `direct_from_seller_request_per_buyer_signals` and thus abort this
// callback, so it's safe to use Unretained(this) and `task`
// here.
task->direct_from_seller_request_per_buyer_signals =
direct_from_seller_requester_per_buyer_signals_.LoadSignals(
*url_loader_factory_, *direct_from_seller_per_buyer_signals,
base::BindOnce(
&BidderWorklet::
OnDirectFromSellerPerBuyerSignalsDownloadedGenerateBid,
base::Unretained(this), task));
}
if (direct_from_seller_auction_signals) {
// We expect each parameter to be provided at most once between
// BeginGenerateBid/FinishGenerateBid. If we are already fetching this
// kind of signals this is clearly the second time it was specified.
DCHECK(!task->direct_from_seller_request_auction_signals);
// Deleting `task` will destroy
// `direct_from_seller_request_auction_signals` and thus abort this
// callback, so it's safe to use Unretained(this) and `task`
// here.
task->direct_from_seller_request_auction_signals =
direct_from_seller_requester_auction_signals_.LoadSignals(
*url_loader_factory_, *direct_from_seller_auction_signals,
base::BindOnce(
&BidderWorklet::
OnDirectFromSellerAuctionSignalsDownloadedGenerateBid,
base::Unretained(this), task));
}
}
void BidderWorklet::OnDirectFromSellerPerBuyerSignalsDownloadedGenerateBid(
GenerateBidTaskList::iterator task,
DirectFromSellerSignalsRequester::Result result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
task->direct_from_seller_result_per_buyer_signals = std::move(result);
task->direct_from_seller_request_per_buyer_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);
GenerateBidIfReady(task);
}
void BidderWorklet::OnDirectFromSellerAuctionSignalsDownloadedGenerateBid(
GenerateBidTaskList::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);
GenerateBidIfReady(task);
}
bool BidderWorklet::IsReadyToGenerateBid(const GenerateBidTask& task) const {
return task.signals_received_callback_invoked &&
task.finalize_generate_bid_called &&
!task.direct_from_seller_request_per_buyer_signals &&
!task.direct_from_seller_request_auction_signals && IsCodeReady();
}
void BidderWorklet::GenerateBidIfReady(GenerateBidTaskList::iterator task) {
DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
if (!IsReadyToGenerateBid(*task)) {
return;
}
// If there was a trusted signals request, it should have already completed
// and been cleaned up before `signals_received_callback_invoked` was set to
// true.
DCHECK(!task->trusted_bidding_signals_request);
TRACE_EVENT_NESTABLE_ASYNC_END1(
"fledge", "wait_generate_bid_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());
}
if (!task->wait_promises.is_zero()) {
dict.Add("wait_promises_ms", task->wait_promises.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 DeliverBidCallbackOnUserThread 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::GenerateBid closure gets destroyed without running.
base::OnceClosure cleanup_generate_bid_task =
base::BindPostTaskToCurrentDefault(
base::BindOnce(&BidderWorklet::CleanUpBidTaskOnUserThread,
weak_ptr_factory_.GetWeakPtr(), task));
// Other than the `generate_bid_client` and `task_id` fields, no fields of
// `task` are needed after this point, so can consume them instead of copying
// them.
//
// Since IsReadyToGenerateBid() is true, the GenerateBidTask won't be deleted
// on the main thread during this call, even if the GenerateBidClient pipe is
// deleted by the caller (unless the BidderWorklet itself is deleted).
// Therefore, it's safe to post a callback with the `task` iterator the v8
// thread.
task->task_id = cancelable_task_tracker_.PostTask(
v8_runner_.get(), FROM_HERE,
base::BindOnce(
&BidderWorklet::V8State::GenerateBid,
base::Unretained(v8_state_.get()),
std::move(task->bidder_worklet_non_shared_params), task->kanon_mode,
std::move(task->interest_group_join_origin),
std::move(task->auction_signals_json),
std::move(task->per_buyer_signals_json),
std::move(task->direct_from_seller_result_per_buyer_signals),
std::move(task->direct_from_seller_result_auction_signals),
std::move(task->per_buyer_timeout),
std::move(task->expected_buyer_currency),
std::move(task->browser_signal_seller_origin),
std::move(task->browser_signal_top_level_seller_origin),
std::move(task->bidding_browser_signals), task->auction_start_time,
std::move(task->trusted_bidding_signals_result), task->trace_id,
base::ScopedClosureRunner(std::move(cleanup_generate_bid_task)),
base::BindOnce(&BidderWorklet::DeliverBidCallbackOnUserThread,
weak_ptr_factory_.GetWeakPtr(), task)));
}
void BidderWorklet::OnDirectFromSellerPerBuyerSignalsDownloadedReportWin(
ReportWinTaskList::iterator task,
DirectFromSellerSignalsRequester::Result result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
task->direct_from_seller_result_per_buyer_signals = std::move(result);
task->direct_from_seller_request_per_buyer_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);
RunReportWinIfReady(task);
}
void BidderWorklet::OnDirectFromSellerAuctionSignalsDownloadedReportWin(
ReportWinTaskList::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);
RunReportWinIfReady(task);
}
bool BidderWorklet::IsReadyToReportWin(const ReportWinTask& task) const {
return IsCodeReady() && !task.direct_from_seller_request_per_buyer_signals &&
!task.direct_from_seller_request_auction_signals;
}
void BidderWorklet::RunReportWinIfReady(ReportWinTaskList::iterator task) {
DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
if (!IsReadyToReportWin(*task)) {
return;
}
TRACE_EVENT_NESTABLE_ASYNC_END1(
"fledge", "wait_report_win_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);
// Other than the callback field, no fields of `task` are needed after this
// point, so can consume them instead of copying them.
cancelable_task_tracker_.PostTask(
v8_runner_.get(), FROM_HERE,
base::BindOnce(
&BidderWorklet::V8State::ReportWin, base::Unretained(v8_state_.get()),
std::move(task->reporting_id_field), std::move(task->reporting_id),
std::move(task->auction_signals_json),
std::move(task->per_buyer_signals_json),
std::move(task->direct_from_seller_result_per_buyer_signals),
std::move(task->direct_from_seller_result_auction_signals),
std::move(task->seller_signals_json),
std::move(task->browser_signal_render_url),
std::move(task->browser_signal_bid),
std::move(task->browser_signal_bid_currency),
std::move(task->browser_signal_highest_scoring_other_bid),
std::move(task->browser_signal_highest_scoring_other_bid_currency),
std::move(task->browser_signal_made_highest_scoring_other_bid),
std::move(task->browser_signal_ad_cost),
std::move(task->browser_signal_modeling_signals),
std::move(task->browser_signal_join_count),
std::move(task->browser_signal_recency),
std::move(task->browser_signal_seller_origin),
std::move(task->browser_signal_top_level_seller_origin),
std::move(task->bidding_signals_data_version), task->trace_id,
base::BindOnce(&BidderWorklet::DeliverReportWinOnUserThread,
weak_ptr_factory_.GetWeakPtr(), task)));
}
void BidderWorklet::DeliverBidCallbackOnUserThread(
GenerateBidTaskList::iterator task,
mojom::BidderWorkletBidPtr bid,
mojom::BidderWorkletKAnonEnforcedBidPtr kanon_bid,
absl::optional<uint32_t> bidding_signals_data_version,
absl::optional<GURL> debug_loss_report_url,
absl::optional<GURL> debug_win_report_url,
absl::optional<double> set_priority,
base::flat_map<std::string, mojom::PrioritySignalsDoublePtr>
update_priority_signals_overrides,
PrivateAggregationRequests pa_requests,
PrivateAggregationRequests non_kanon_pa_requests,
base::TimeDelta bidding_latency,
std::vector<std::string> error_msgs) {
DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
error_msgs.insert(error_msgs.end(), load_code_error_msgs_.begin(),
load_code_error_msgs_.end());
if (task->trusted_bidding_signals_error_msg) {
error_msgs.emplace_back(
std::move(task->trusted_bidding_signals_error_msg).value());
}
task->generate_bid_client->OnGenerateBidComplete(
std::move(bid), std::move(kanon_bid),
bidding_signals_data_version.value_or(0),
bidding_signals_data_version.has_value(), debug_loss_report_url,
debug_win_report_url, set_priority.value_or(0), set_priority.has_value(),
std::move(update_priority_signals_overrides), std::move(pa_requests),
std::move(non_kanon_pa_requests), bidding_latency,
mojom::GenerateBidDependencyLatencies::New(
/*code_ready_latency=*/NullOptIfZero(task->wait_code),
/*config_promises_latency=*/NullOptIfZero(task->wait_promises),
/*direct_from_seller_signals_latency=*/
NullOptIfZero(task->wait_direct_from_seller_signals),
/*trusted_bidding_signals_latency=*/
NullOptIfZero(task->wait_trusted_signals)),
error_msgs);
CleanUpBidTaskOnUserThread(task);
}
void BidderWorklet::CleanUpBidTaskOnUserThread(
GenerateBidTaskList::iterator task) {
DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
// Disconnect the FinalizeGenerateBid pipe, if any, since that refers
// to `task` (it generally will be closed already, but may not be if
// GenerateBidClient disconnected before FinalizeGenerateBid was called).
if (task->finalize_generate_bid_receiver_id.has_value()) {
finalize_receiver_set_.Remove(*task->finalize_generate_bid_receiver_id);
}
generate_bid_tasks_.erase(task);
}
void BidderWorklet::DeliverReportWinOnUserThread(
ReportWinTaskList::iterator task,
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_);
errors.insert(errors.end(), load_code_error_msgs_.begin(),
load_code_error_msgs_.end());
std::move(task->callback)
.Run(std::move(report_url), std::move(ad_beacon_map),
std::move(pa_requests), std::move(errors));
report_win_tasks_.erase(task);
}
bool BidderWorklet::IsCodeReady() const {
// If `paused_`, loading hasn't started yet. Otherwise, null loaders indicate
// the worklet script has loaded successfully, and there's no WASM helper, or
// it has also loaded successfully.
return !paused_ && !worklet_loader_ && !wasm_loader_;
}
} // namespace auction_worklet