blob: ad0021983dca78e174b81d7a79b3df8f0827d170 [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/services/auction_worklet/bidder_worklet.h"
#include <algorithm>
#include <cmath>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "content/services/auction_worklet/auction_v8_helper.h"
#include "content/services/auction_worklet/for_debugging_only_bindings.h"
#include "content/services/auction_worklet/public/mojom/auction_worklet_service.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/trusted_signals.h"
#include "content/services/auction_worklet/trusted_signals_request_manager.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 "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/interest_group/ad_auction_constants.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-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 {
bool AppendJsonValueOrNull(AuctionV8Helper* const v8_helper,
v8::Local<v8::Context> context,
const absl::optional<std::string>& maybe_json,
std::vector<v8::Local<v8::Value>>* args) {
v8::Isolate* isolate = v8_helper->isolate();
if (maybe_json.has_value()) {
if (!v8_helper->AppendJsonValue(context, maybe_json.value(), args))
return false;
} else {
args->push_back(v8::Null(isolate));
}
return true;
}
// Creates a V8 array containing information about the passed in previous wins.
// Array is sorted by time, earliest wins first. Modifies order of `prev_wins`
// input vector. This should should be harmless, since each list of previous
// wins is only used for a single bid in a single auction, and its order is
// unspecified, anyways.
v8::MaybeLocal<v8::Value> CreatePrevWinsArray(
AuctionV8Helper* v8_helper,
v8::Local<v8::Context> context,
base::Time auction_start_time,
std::vector<mojom::PreviousWinPtr>& prev_wins) {
std::sort(prev_wins.begin(), prev_wins.end(),
[](const mojom::PreviousWinPtr& prev_win1,
const mojom::PreviousWinPtr& prev_win2) {
return prev_win1->time < prev_win2->time;
});
std::vector<v8::Local<v8::Value>> prev_wins_v8;
v8::Isolate* isolate = v8_helper->isolate();
for (const auto& prev_win : prev_wins) {
int64_t time_delta = (auction_start_time - prev_win->time).InSeconds();
// Don't give negative times if clock has changed since last auction win.
if (time_delta < 0)
time_delta = 0;
v8::Local<v8::Value> win_values[2];
win_values[0] = v8::Number::New(isolate, time_delta);
if (!v8_helper->CreateValueFromJson(context, prev_win->ad_json)
.ToLocal(&win_values[1])) {
return v8::MaybeLocal<v8::Value>();
}
prev_wins_v8.push_back(
v8::Array::New(isolate, win_values, std::size(win_values)));
}
return v8::Array::New(isolate, prev_wins_v8.data(), prev_wins_v8.size());
}
// Converts a vector of blink::InterestGroup::Ads into a v8 object.
bool CreateAdVector(AuctionV8Helper* v8_helper,
v8::Local<v8::Context> context,
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) {
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;
}
} // namespace
BidderWorklet::BidderWorklet(
scoped_refptr<AuctionV8Helper> v8_helper,
bool pause_for_debugger_on_start,
mojo::PendingRemote<network::mojom::URLLoaderFactory>
pending_url_loader_factory,
const GURL& script_source_url,
const absl::optional<GURL>& wasm_helper_url,
const absl::optional<GURL>& trusted_bidding_signals_url,
const url::Origin& top_window_origin)
: 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,
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_, script_source_url_, top_window_origin_,
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();
}
void BidderWorklet::GenerateBid(
mojom::BidderWorkletNonSharedParamsPtr bidder_worklet_non_shared_params,
const absl::optional<std::string>& auction_signals_json,
const absl::optional<std::string>& per_buyer_signals_json,
const absl::optional<base::TimeDelta> per_buyer_timeout,
const url::Origin& browser_signal_seller_origin,
const absl::optional<url::Origin>& browser_signal_top_level_seller_origin,
mojom::BiddingBrowserSignalsPtr bidding_browser_signals,
base::Time auction_start_time,
GenerateBidCallback generate_bid_callback) {
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->auction_signals_json = auction_signals_json;
generate_bid_task->per_buyer_signals_json = per_buyer_signals_json;
generate_bid_task->per_buyer_timeout = per_buyer_timeout;
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->callback = std::move(generate_bid_callback);
const auto& trusted_bidding_signals_keys =
generate_bid_task->bidder_worklet_non_shared_params
->trusted_bidding_signals_keys;
if (trusted_signals_request_manager_ &&
trusted_bidding_signals_keys.has_value() &&
!trusted_bidding_signals_keys->empty()) {
generate_bid_task->trusted_bidding_signals_request =
trusted_signals_request_manager_->RequestBiddingSignals(
*trusted_bidding_signals_keys,
base::BindOnce(&BidderWorklet::OnTrustedBiddingSignalsDownloaded,
base::Unretained(this), generate_bid_task));
return;
}
GenerateBidIfReady(generate_bid_task);
}
void BidderWorklet::SendPendingSignalsRequests() {
if (trusted_signals_request_manager_)
trusted_signals_request_manager_->StartBatchedTrustedSignalsRequest();
}
void BidderWorklet::ReportWin(
const std::string& interest_group_name,
const absl::optional<std::string>& auction_signals_json,
const absl::optional<std::string>& per_buyer_signals_json,
const std::string& seller_signals_json,
const GURL& browser_signal_render_url,
double browser_signal_bid,
double browser_signal_highest_scoring_other_bid,
bool browser_signal_made_highest_scoring_other_bid,
const url::Origin& browser_signal_seller_origin,
const absl::optional<url::Origin>& browser_signal_top_level_seller_origin,
uint32_t bidding_signals_data_version,
bool has_bidding_signals_data_version,
ReportWinCallback report_win_callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
report_win_tasks_.emplace_front();
auto report_win_task = report_win_tasks_.begin();
report_win_task->interest_group_name = interest_group_name;
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_highest_scoring_other_bid =
browser_signal_highest_scoring_other_bid;
report_win_task->browser_signal_made_highest_scoring_other_bid =
browser_signal_made_highest_scoring_other_bid;
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);
// If not yet ready, need to wait for load to complete.
if (!IsCodeReady())
return;
RunReportWin(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)));
}
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,
const GURL& script_source_url,
const url::Origin& top_window_origin,
const absl::optional<GURL>& wasm_helper_url,
const absl::optional<GURL>& trusted_bidding_signals_url,
base::WeakPtr<BidderWorklet> parent)
: v8_helper_(std::move(v8_helper)),
debug_id_(std::move(debug_id)),
parent_(std::move(parent)),
user_thread_(base::SequencedTaskRunnerHandle::Get()),
script_source_url_(script_source_url),
top_window_origin_(top_window_origin),
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)));
}
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);
}
void BidderWorklet::V8State::ReportWin(
const std::string& interest_group_name,
const absl::optional<std::string>& auction_signals_json,
const absl::optional<std::string>& per_buyer_signals_json,
const std::string& seller_signals_json,
const GURL& browser_signal_render_url,
double browser_signal_bid,
double browser_signal_highest_scoring_other_bid,
bool browser_signal_made_highest_scoring_other_bid,
const url::Origin& browser_signal_seller_origin,
const absl::optional<url::Origin>& browser_signal_top_level_seller_origin,
const absl::optional<uint32_t>& bidding_signals_data_version,
ReportWinCallbackInternal callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);
AuctionV8Helper::FullIsolateScope isolate_scope(v8_helper_.get());
v8::Isolate* isolate = v8_helper_->isolate();
v8::Local<v8::ObjectTemplate> global_template =
v8::ObjectTemplate::New(isolate);
ReportBindings report_bindings(v8_helper_.get(), global_template);
RegisterAdBeaconBindings register_ad_beacon_bindings(v8_helper_.get(),
global_template);
// Short lived context, to avoid leaking data at global scope between either
// repeated calls to this worklet, or to calls to any other worklet.
v8::Local<v8::Context> context = v8_helper_->CreateContext(global_template);
v8::Context::Scope context_scope(context);
std::vector<v8::Local<v8::Value>> args;
if (!AppendJsonValueOrNull(v8_helper_.get(), context, auction_signals_json,
&args) ||
!AppendJsonValueOrNull(v8_helper_.get(), context, per_buyer_signals_json,
&args) ||
!v8_helper_->AppendJsonValue(context, seller_signals_json, &args)) {
PostReportWinCallbackToUserThread(
std::move(callback), absl::nullopt /* report_url */,
/*ad_beacon_map=*/{}, std::vector<std::string>() /* errors */);
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()) ||
!browser_signals_dict.Set(
"interestGroupOwner",
url::Origin::Create(script_source_url_).Serialize()) ||
!browser_signals_dict.Set("interestGroupName", interest_group_name) ||
!browser_signals_dict.Set("renderUrl",
browser_signal_render_url.spec()) ||
!browser_signals_dict.Set("bid", browser_signal_bid) ||
!browser_signals_dict.Set("highestScoringOtherBid",
browser_signal_highest_scoring_other_bid) ||
!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), absl::nullopt /* report_url */,
/*ad_beacon_map=*/{}, std::vector<std::string>() /* errors */);
return;
}
args.push_back(browser_signals);
// An empty return value indicates an exception was thrown. Any other return
// value indicates no exception.
std::vector<std::string> errors_out;
v8_helper_->MaybeTriggerInstrumentationBreakpoint(
*debug_id_, "beforeBidderWorkletReportingStart");
if (v8_helper_
->RunScript(context, worklet_script_.Get(isolate), debug_id_.get(),
"reportWin", args, /*script_timeout=*/absl::nullopt,
errors_out)
.IsEmpty()) {
PostReportWinCallbackToUserThread(
std::move(callback), absl::nullopt /* report_url */,
/*ad_beacon_map=*/{}, 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), report_bindings.report_url(),
register_ad_beacon_bindings.TakeAdBeaconMap(), std::move(errors_out));
}
void BidderWorklet::V8State::GenerateBid(
mojom::BidderWorkletNonSharedParamsPtr bidder_worklet_non_shared_params,
const absl::optional<std::string>& auction_signals_json,
const absl::optional<std::string>& per_buyer_signals_json,
const absl::optional<base::TimeDelta> per_buyer_timeout,
const url::Origin& browser_signal_seller_origin,
const absl::optional<url::Origin>& browser_signal_top_level_seller_origin,
mojom::BiddingBrowserSignalsPtr bidding_browser_signals,
base::Time auction_start_time,
scoped_refptr<TrustedSignals::Result> trusted_bidding_signals_result,
GenerateBidCallbackInternal callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);
// Can't make a bid without any ads.
if (!bidder_worklet_non_shared_params->ads) {
PostErrorBidCallbackToUserThread(std::move(callback));
return;
}
base::TimeTicks start = base::TimeTicks::Now();
AuctionV8Helper::FullIsolateScope isolate_scope(v8_helper_.get());
v8::Isolate* isolate = v8_helper_->isolate();
v8::Local<v8::ObjectTemplate> global_template =
v8::ObjectTemplate::New(isolate);
ForDebuggingOnlyBindings for_debugging_only_bindings(v8_helper_.get(),
global_template);
SetBidBindings set_bid_bindings(
v8_helper_.get(), global_template, start,
browser_signal_top_level_seller_origin.has_value(),
bidder_worklet_non_shared_params->ads,
bidder_worklet_non_shared_params->ad_components);
// Short lived context, to avoid leaking data at global scope between either
// repeated calls to this worklet, or to calls to any other worklet.
v8::Local<v8::Context> context = v8_helper_->CreateContext(global_template);
v8::Context::Scope context_scope(context);
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", url::Origin::Create(script_source_url_).Serialize()) ||
!interest_group_dict.Set("name",
bidder_worklet_non_shared_params->name) ||
!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->daily_update_url &&
!interest_group_dict.Set(
"dailyUpdateUrl",
bidder_worklet_non_shared_params->daily_update_url->spec())) ||
(trusted_bidding_signals_url_ &&
!interest_group_dict.Set("trustedBiddingSignalsUrl",
trusted_bidding_signals_url_->spec())) ||
(bidder_worklet_non_shared_params->user_bidding_signals &&
!v8_helper_->InsertJsonValue(
context, "userBiddingSignals",
*bidder_worklet_non_shared_params->user_bidding_signals,
interest_group_object))) {
PostErrorBidCallbackToUserThread(std::move(callback));
return;
}
if (bidder_worklet_non_shared_params->trusted_bidding_signals_keys) {
std::vector<v8::Local<v8::Value>> trusted_bidding_signals_keys;
for (const auto& key :
*bidder_worklet_non_shared_params->trusted_bidding_signals_keys) {
v8::Local<v8::Value> key_value;
if (!v8_helper_->CreateUtf8String(key).ToLocal(&key_value)) {
PostErrorBidCallbackToUserThread(std::move(callback));
return;
}
trusted_bidding_signals_keys.emplace_back(std::move(key_value));
}
if (!v8_helper_->InsertValue(
"trustedBiddingSignalsKeys",
v8::Array::New(isolate, trusted_bidding_signals_keys.data(),
trusted_bidding_signals_keys.size()),
interest_group_object)) {
PostErrorBidCallbackToUserThread(std::move(callback));
return;
}
}
v8::Local<v8::Value> ads;
if (!CreateAdVector(v8_helper_.get(), context,
*bidder_worklet_non_shared_params->ads, ads) ||
!v8_helper_->InsertValue("ads", std::move(ads), interest_group_object)) {
PostErrorBidCallbackToUserThread(std::move(callback));
return;
}
if (bidder_worklet_non_shared_params->ad_components) {
v8::Local<v8::Value> ad_components;
if (!CreateAdVector(v8_helper_.get(), context,
*bidder_worklet_non_shared_params->ad_components,
ad_components) ||
!v8_helper_->InsertValue("adComponents", std::move(ad_components),
interest_group_object)) {
PostErrorBidCallbackToUserThread(std::move(callback));
return;
}
}
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)) {
PostErrorBidCallbackToUserThread(std::move(callback));
return;
}
v8::Local<v8::Value> trusted_signals;
absl::optional<uint32_t> bidding_signals_data_version;
if (!trusted_bidding_signals_result) {
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);
bidding_signals_data_version =
trusted_bidding_signals_result->GetDataVersion();
}
args.push_back(trusted_signals);
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()))) {
PostErrorBidCallbackToUserThread(std::move(callback));
return;
}
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()) {
PostErrorBidCallbackToUserThread(std::move(callback));
return;
}
}
v8::Local<v8::Value> prev_wins;
if (!CreatePrevWinsArray(v8_helper_.get(), context, auction_start_time,
bidding_browser_signals->prev_wins)
.ToLocal(&prev_wins)) {
PostErrorBidCallbackToUserThread(std::move(callback));
return;
}
v8::Maybe<bool> result = browser_signals->Set(
context, gin::StringToV8(isolate, "prevWins"), prev_wins);
if (result.IsNothing() || !result.FromJust()) {
PostErrorBidCallbackToUserThread(std::move(callback));
return;
}
args.push_back(browser_signals);
v8::Local<v8::Value> generate_bid_result;
std::vector<std::string> errors_out;
v8_helper_->MaybeTriggerInstrumentationBreakpoint(
*debug_id_, "beforeBidderWorkletBiddingStart");
bool got_return_value =
v8_helper_
->RunScript(context, worklet_script_.Get(isolate), debug_id_.get(),
"generateBid", args, std::move(per_buyer_timeout),
errors_out)
.ToLocal(&generate_bid_result);
if (got_return_value) {
set_bid_bindings.SetBid(
generate_bid_result,
base::StrCat({script_source_url_.spec(), " generateBid() "}),
errors_out);
}
if (!set_bid_bindings.has_bid()) {
// If we either don't have a valid return value, or we have no return value
// and no intermediate result was given through setBid, return an error.
// Keep debug loss reports since `generateBid()` might use it to detect
// script timeout or failures.
PostErrorBidCallbackToUserThread(
std::move(callback), std::move(errors_out),
for_debugging_only_bindings.TakeLossReportUrl());
return;
}
user_thread_->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), set_bid_bindings.TakeBid(),
bidding_signals_data_version,
for_debugging_only_bindings.TakeLossReportUrl(),
for_debugging_only_bindings.TakeWinReportUrl(),
std::move(errors_out)));
}
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() {
DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);
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,
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(errors)));
}
void BidderWorklet::V8State::PostErrorBidCallbackToUserThread(
GenerateBidCallbackInternal callback,
std::vector<std::string> error_msgs,
absl::optional<GURL> debug_loss_report_url) {
DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);
user_thread_->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback), mojom::BidderWorkletBidPtr(),
/*bidding_signals_data_version=*/absl::nullopt,
/*debug_loss_report_url=*/std::move(debug_loss_report_url),
/*debug_win_report_url=*/absl::nullopt,
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());
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)));
RunReadyGenerateBidTasks();
RunReportWinTasks(); // These only depends on JS, so they can be run.
}
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());
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)));
RunReadyGenerateBidTasks();
// ReportWin() tasks currently don't have access to WASM.
}
void BidderWorklet::RunReadyGenerateBidTasks() {
// 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);
}
}
void BidderWorklet::RunReportWinTasks() {
// Run all ReportWin() tasks. RunReportWin() 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) {
RunReportWin(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_);
task->trusted_bidding_signals_error_msg = std::move(error_msg);
task->trusted_bidding_signals_result = std::move(result);
task->trusted_bidding_signals_request.reset();
GenerateBidIfReady(task);
}
void BidderWorklet::GenerateBidIfReady(GenerateBidTaskList::iterator task) {
DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
if (task->trusted_bidding_signals_request || !IsCodeReady())
return;
// Other than the callback field, no fields of `task` are needed after this
// point, so can consume them instead of copying them.
v8_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&BidderWorklet::V8State::GenerateBid,
base::Unretained(v8_state_.get()),
std::move(task->bidder_worklet_non_shared_params),
std::move(task->auction_signals_json),
std::move(task->per_buyer_signals_json),
std::move(task->per_buyer_timeout),
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),
base::BindOnce(&BidderWorklet::DeliverBidCallbackOnUserThread,
weak_ptr_factory_.GetWeakPtr(), task)));
}
void BidderWorklet::RunReportWin(ReportWinTaskList::iterator task) {
DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
// Other than the callback field, no fields of `task` are needed after this
// point, so can consume them instead of copying them.
v8_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&BidderWorklet::V8State::ReportWin, base::Unretained(v8_state_.get()),
std::move(task->interest_group_name),
std::move(task->auction_signals_json),
std::move(task->per_buyer_signals_json),
std::move(task->seller_signals_json),
std::move(task->browser_signal_render_url),
std::move(task->browser_signal_bid),
std::move(task->browser_signal_highest_scoring_other_bid),
std::move(task->browser_signal_made_highest_scoring_other_bid),
std::move(task->browser_signal_seller_origin),
std::move(task->browser_signal_top_level_seller_origin),
std::move(task->bidding_signals_data_version),
base::BindOnce(&BidderWorklet::DeliverReportWinOnUserThread,
weak_ptr_factory_.GetWeakPtr(), task)));
}
void BidderWorklet::DeliverBidCallbackOnUserThread(
GenerateBidTaskList::iterator task,
mojom::BidderWorkletBidPtr bid,
absl::optional<uint32_t> bidding_signals_data_version,
absl::optional<GURL> debug_loss_report_url,
absl::optional<GURL> debug_win_report_url,
std::vector<std::string> error_msgs) {
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());
}
std::move(task->callback)
.Run(std::move(bid), bidding_signals_data_version.value_or(0),
bidding_signals_data_version.has_value(), debug_loss_report_url,
debug_win_report_url, error_msgs);
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,
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), 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