blob: 9c04bb413b5a38bf9e9090effa008491d58f287c [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/browser/interest_group/auction_worklet_manager.h"
#include <stdint.h>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/hash/hash.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/not_fatal_until.h"
#include "base/strings/strcat.h"
#include "content/browser/interest_group/auction_metrics_recorder.h"
#include "content/browser/interest_group/auction_process_manager.h"
#include "content/browser/interest_group/auction_shared_storage_host.h"
#include "content/browser/interest_group/auction_url_loader_factory_proxy.h"
#include "content/browser/interest_group/debuggable_auction_worklet.h"
#include "content/browser/interest_group/subresource_url_authorizations.h"
#include "content/browser/interest_group/subresource_url_builder.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/common/content_export.h"
#include "content/services/auction_worklet/public/mojom/auction_network_events_handler.mojom-forward.h"
#include "content/services/auction_worklet/public/mojom/auction_shared_storage_host.mojom.h"
#include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
#include "content/services/auction_worklet/public/mojom/seller_worklet.mojom.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/network/public/mojom/client_security_state.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace content {
namespace {
// If on, worklet assignment/failure callbacks will be executed in chunks rather
// than all at once.
BASE_FEATURE(kFledgeSplitUpWorkletAssignment,
"FledgeSplitUpWorkletAssignment",
base::FEATURE_ENABLED_BY_DEFAULT);
// We use sequence numbers with handles to make sure they are assigned in FIFO
// order.
using HandleKey = std::pair<uint64_t, AuctionWorkletManager::WorkletHandle*>;
auction_worklet::mojom::AuctionWorkletPermissionsPolicyStatePtr
GetAuctionWorkletPermissionsPolicyState(RenderFrameHostImpl* auction_runner_rfh,
const GURL& worklet_script_url) {
const blink::PermissionsPolicy* permissions_policy =
auction_runner_rfh->permissions_policy();
url::Origin worklet_origin = url::Origin::Create(worklet_script_url);
return auction_worklet::mojom::AuctionWorkletPermissionsPolicyState::New(
permissions_policy->IsFeatureEnabledForOrigin(
blink::mojom::PermissionsPolicyFeature::kPrivateAggregation,
worklet_origin),
permissions_policy->IsFeatureEnabledForOrigin(
blink::mojom::PermissionsPolicyFeature::kSharedStorage,
worklet_origin));
}
} // namespace
const size_t AuctionWorkletManager::kBatchSize;
int AuctionWorkletManager::GetFrameTreeNodeID() {
return delegate_->GetFrame()->frame_tree_node()->frame_tree_node_id();
}
class AuctionWorkletManager::WorkletOwner
: public base::RefCounted<AuctionWorkletManager::WorkletOwner> {
public:
// Attempts to immediately create a worklet. If that fails, the WorkletOwner
// will immediately start waiting for a process to be available, and once one
// is, create a worklet, informing all associated WorkletHandles.
//
// If this is for a bidder workelt, `number_of_bidder_threads` specifies
// the number of threads to allocate to the bidder.
WorkletOwner(AuctionWorkletManager* worklet_manager,
WorkletKey worklet_info,
size_t number_of_bidder_threads);
// Registers/unregisters a WorkletHandle for the worklet `this` owns.
void RegisterHandle(HandleKey handle);
void UnregisterHandle(HandleKey handle);
uint64_t GetNextSeqNum() { return next_handle_seq_num_++; }
auction_worklet::mojom::BidderWorklet* bidder_worklet() {
DCHECK(bidder_worklet_);
return bidder_worklet_.get();
}
auction_worklet::mojom::SellerWorklet* seller_worklet() {
DCHECK(seller_worklet_);
return seller_worklet_.get();
}
const WorkletKey& worklet_info() const { return worklet_info_; }
// Whether or not a worklet has been created. Once a worklet has been created,
// always returns true, even after disconnect or error.
bool worklet_created() const {
return bidder_worklet_.is_bound() || seller_worklet_.is_bound();
}
SubresourceUrlAuthorizations* subresource_url_authorizations() {
if (!url_loader_factory_proxy_) {
return nullptr;
}
return &url_loader_factory_proxy_->subresource_url_authorizations();
}
std::vector<std::string> ComputeDevtoolsAuctionIds();
// Adds `auction_metrics_recorder` to the list of AuctionMetricsRecorders
// on which this will call OnWorkletReady when this worklet is ready.
// If this worklet is already ready when this is called, it'll call it
// immediately instead.
void NotifyAuctionMetricsRecorderWhenReady(
AuctionMetricsRecorder* auction_metrics_recorder);
private:
friend class base::RefCounted<WorkletOwner>;
~WorkletOwner();
void MaybeQueueNotifications();
void DispatchSomeNotifications();
void DispatchSomeSuccessNotifications();
void DispatchSomeFailureNotifications();
// Called if the worklet becomes unusable. This happens on destruction (once
// all refs have been released) or when the Mojo pipe is closed. Removes
// `this` from `worklet_manager_`, so that future requests using the same
// `worklet_info` will trigger creation of a new worklet, rather than trying
// to use the unusable one.
void WorkletNoLongerUsable();
// Called once the AuctionProcessManager provides a process to load a worklet
// in. Immediately loads the worklet and informs WorkletHandles. If this is
// for a bidder worklet, `number_of_bidder_threads` specifies
// the number of threads to allocate to the bidder.
void OnProcessAssigned(size_t number_of_bidder_threads);
// Called when a PID for a worklet process thread has a PID assigned, a proxy
// for its readiness to begin processing requests. This is used to record
// the OnWorkletReady event to the AuctionMetricsRecorder for phase start/end
// metrics. For worklets that have multiple threads, this will be called once
// for each of those threads.
void OnThreadReady(base::ProcessId pid);
// Mojo disconnect with reason handler. If there's a description, it's a load
// error. Otherwise, it's a crash. Passes error information on to all
// associated WorkletHandles.
void OnWorkletDisconnected(uint32_t /* custom_reason */,
const std::string& description);
static std::vector<std::string> GetDevtoolsAuctionIds(
base::WeakPtr<WorkletOwner> self);
// Set to null once `this` is removed from AuctionWorkletManager's
// WorkletOwner list, which happens on destruction or on Mojo pipe closure.
// The latter allows a handle to still exist and refer to a WorkletOwner with
// a broken Worklet pipe, while new requests for the same worklet will result
// in creating a fresh Mojo worklet.
raw_ptr<AuctionWorkletManager> worklet_manager_;
const WorkletKey worklet_info_;
AuctionProcessManager::ProcessHandle process_handle_;
// These are handles that have not yet been notified of having a process,
// either because the process isn't available yet or because we haven't
// gotten around to dispatching the notification. If the process fails before
// handle assignment, it's removed from this set, too.
std::set<HandleKey> handles_waiting_for_process_;
// These are handles that have been notified of having a process, and not of
// a process failure.
std::set<raw_ptr<WorkletHandle, SetExperimental>> handles_with_process_;
std::unique_ptr<AuctionURLLoaderFactoryProxy> url_loader_factory_proxy_;
mojo::Remote<auction_worklet::mojom::BidderWorklet> bidder_worklet_;
mojo::Remote<auction_worklet::mojom::SellerWorklet> seller_worklet_;
// This must be destroyed before the worklet it's passed, since it hangs on to
// a raw pointer to it.
std::vector<std::unique_ptr<DebuggableAuctionWorklet>> worklet_debugs_;
// If true, we will split callback notifications into small batches.
bool split_up_notifications_;
bool notifications_pending_ = false;
std::optional<FatalErrorType> notify_error_type_;
std::vector<std::string> notify_errors_;
uint64_t next_handle_seq_num_ = 0;
// Map from devtools auction ID to number of handles from that auction.
std::map<std::string, int> registered_devtools_auction_ids_;
// When a worklet is requested before it's ready, we store the
// AuctionMetricsRecorder here so that it can be notified when the worklet
// is ready. There's an AuctionMetricsRecorder for each auction, and since
// multiple auctions can reuse the same worklet, there may in fact be multiple
// auctions waiting for the same worklet, which is why this is a list.
std::vector<raw_ptr<AuctionMetricsRecorder>>
auction_metrics_recorders_to_notify_;
// When the requested worklet is ready, we can immediately record this to the
// AuctionMetricsRecorder instead of adding the AuctionMetricsRecorder to
// `auction_metrics_recorders_to_notify_`, defined above.
bool is_worklet_ready_ = false;
base::WeakPtrFactory<WorkletOwner> weak_ptr_factory_{this};
};
AuctionWorkletManager::WorkletOwner::WorkletOwner(
AuctionWorkletManager* worklet_manager,
WorkletKey worklet_info,
size_t number_of_bidder_threads)
: worklet_manager_(worklet_manager),
worklet_info_(std::move(worklet_info)),
split_up_notifications_(
base::FeatureList::IsEnabled(kFledgeSplitUpWorkletAssignment)) {
if (worklet_manager_->auction_process_manager()->RequestWorkletService(
worklet_info_.type, url::Origin::Create(worklet_info_.script_url),
worklet_manager_->delegate()->GetFrameSiteInstance(),
&process_handle_,
base::BindOnce(
&AuctionWorkletManager::WorkletOwner::OnProcessAssigned,
base::Unretained(this), number_of_bidder_threads))) {
OnProcessAssigned(number_of_bidder_threads);
}
}
void AuctionWorkletManager::WorkletOwner::RegisterHandle(HandleKey handle) {
handles_waiting_for_process_.insert(handle);
++registered_devtools_auction_ids_[handle.second->devtools_auction_id_];
if (worklet_created()) {
MaybeQueueNotifications();
}
}
void AuctionWorkletManager::WorkletOwner::UnregisterHandle(HandleKey handle) {
auto it = registered_devtools_auction_ids_.find(
handle.second->devtools_auction_id_);
CHECK(it != registered_devtools_auction_ids_.end(),
base::NotFatalUntil::M130);
--it->second;
if (it->second == 0) {
registered_devtools_auction_ids_.erase(it);
}
if (!handles_waiting_for_process_.erase(handle)) {
// The handle should only be in one of the sets, so only need to search
// `handles_with_process_` if it wasn't in `handles_waiting_for_process_`.
handles_with_process_.erase(handle.second);
}
DCHECK_EQ(handles_waiting_for_process_.count(handle), 0u);
}
std::vector<std::string>
AuctionWorkletManager::WorkletOwner::ComputeDevtoolsAuctionIds() {
std::vector<std::string> result;
for (const auto& [id, count] : registered_devtools_auction_ids_) {
result.push_back(id);
}
return result;
}
void AuctionWorkletManager::WorkletOwner::NotifyAuctionMetricsRecorderWhenReady(
AuctionMetricsRecorder* auction_metrics_recorder) {
if (is_worklet_ready_) {
auction_metrics_recorder->OnWorkletReady();
} else {
auction_metrics_recorders_to_notify_.push_back(auction_metrics_recorder);
}
}
AuctionWorkletManager::WorkletOwner::~WorkletOwner() {
DCHECK(handles_waiting_for_process_.empty());
DCHECK(handles_with_process_.empty());
WorkletNoLongerUsable();
}
void AuctionWorkletManager::WorkletOwner::MaybeQueueNotifications() {
if (notifications_pending_) {
return;
}
notifications_pending_ = true;
// This uses a weak pointer and not a ref-count holding one to avoid extending
// lifetime of `this` beyond the handles.
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&WorkletOwner::DispatchSomeNotifications,
weak_ptr_factory_.GetWeakPtr()));
}
void AuctionWorkletManager::WorkletOwner::DispatchSomeNotifications() {
// Make sure `this` isn't released in the middle of dispatch loops if they
// drop all the handles.
scoped_refptr<WorkletOwner> guard(this);
notifications_pending_ = false;
// Failure/success is checked here and not at queuing time since things may
// change by the time this method is invoked.
if (notify_error_type_.has_value()) {
DispatchSomeFailureNotifications();
} else {
DispatchSomeSuccessNotifications();
}
}
void AuctionWorkletManager::WorkletOwner::DispatchSomeFailureNotifications() {
// In case of an error, we notify both of `handles_with_process_` and
// `handles_waiting_for_process_`. It's OK to remove things from these
// sets here since we won't do anything else with them anyway, and it
// protects us from re-entrancy weirdness.
size_t num_notified = 0;
size_t to_notify =
handles_with_process_.size() + handles_waiting_for_process_.size();
if (split_up_notifications_) {
to_notify = std::min(to_notify, kBatchSize);
}
while (num_notified < to_notify && !handles_with_process_.empty()) {
auto node = handles_with_process_.extract(handles_with_process_.begin());
node.value()->OnFatalError(*notify_error_type_, notify_errors_);
++num_notified;
}
while (num_notified < to_notify && !handles_waiting_for_process_.empty()) {
auto node = handles_waiting_for_process_.extract(
handles_waiting_for_process_.begin());
node.value().second->OnFatalError(*notify_error_type_, notify_errors_);
++num_notified;
}
if (!handles_with_process_.empty() || !handles_waiting_for_process_.empty()) {
MaybeQueueNotifications();
}
}
void AuctionWorkletManager::WorkletOwner::DispatchSomeSuccessNotifications() {
// In case of success, we only notify `handles_waiting_for_process_`, and
// also add them to `handles_with_process_`; if the next time we run
// DispatchSomeNotifications() we're in failure state we want them to get
// the failure notification.
size_t num_notified = 0;
size_t to_notify = handles_waiting_for_process_.size();
if (split_up_notifications_) {
to_notify = std::min(to_notify, kBatchSize);
}
// This loops needs to check `handles_waiting_for_process_.empty()` for the
// case where items are removed in response to callbacks.
while (num_notified < to_notify && !handles_waiting_for_process_.empty()) {
auto node = handles_waiting_for_process_.extract(
handles_waiting_for_process_.begin());
// Must do the insert before the callback in case the callback deletes
// the handle.
handles_with_process_.insert(node.value().second);
node.value().second->OnWorkletAvailable();
++num_notified;
}
if (!handles_waiting_for_process_.empty()) {
MaybeQueueNotifications();
}
}
void AuctionWorkletManager::WorkletOwner::WorkletNoLongerUsable() {
if (worklet_manager_) {
worklet_manager_->OnWorkletNoLongerUsable(this);
worklet_manager_ = nullptr;
}
}
void AuctionWorkletManager::WorkletOwner::OnProcessAssigned(
size_t number_of_bidder_threads) {
DCHECK(!bidder_worklet_.is_bound());
DCHECK(!seller_worklet_.is_bound());
Delegate* delegate = worklet_manager_->delegate();
mojo::PendingRemote<network::mojom::URLLoaderFactory> url_loader_factory;
mojo::PendingRemote<auction_worklet::mojom::AuctionNetworkEventsHandler>
auction_network_events_handler;
RenderFrameHostImpl* const rfh = delegate->GetFrame();
worklet_manager_->auction_network_events_proxy_->Clone(
auction_network_events_handler.InitWithNewPipeAndPassReceiver());
url_loader_factory_proxy_ = std::make_unique<AuctionURLLoaderFactoryProxy>(
url_loader_factory.InitWithNewPipeAndPassReceiver(),
base::BindRepeating(&Delegate::GetFrameURLLoaderFactory,
base::Unretained(delegate)),
base::BindRepeating(&Delegate::GetTrustedURLLoaderFactory,
base::Unretained(delegate)),
base::BindOnce(&Delegate::PreconnectSocket, base::Unretained(delegate)),
base::BindRepeating(&Delegate::GetCookieDeprecationLabel,
base::Unretained(delegate)),
base::BindRepeating(&WorkletOwner::GetDevtoolsAuctionIds,
weak_ptr_factory_.GetWeakPtr()),
/*force_reload=*/rfh->reload_type() == ReloadType::BYPASSING_CACHE,
worklet_manager_->top_window_origin(), worklet_manager_->frame_origin(),
// NOTE: `rfh` can be null in tests.
/*renderer_process_id=*/
rfh ? std::optional<int>(rfh->GetProcess()->GetID()) : std::nullopt,
/*is_for_seller_=*/worklet_info_.type == WorkletType::kSeller,
delegate->GetClientSecurityState(), worklet_info_.script_url,
worklet_info_.wasm_url, worklet_info_.signals_url,
worklet_info_.needs_cors_for_additional_bid,
rfh->frame_tree_node()->frame_tree_node_id());
CHECK(worklet_debugs_.empty());
switch (worklet_info_.type) {
case WorkletType::kBidder: {
mojo::PendingReceiver<auction_worklet::mojom::BidderWorklet>
worklet_receiver = bidder_worklet_.BindNewPipeAndPassReceiver();
std::vector<
mojo::PendingRemote<auction_worklet::mojom::AuctionSharedStorageHost>>
shared_storage_hosts;
for (size_t i = 0; i < number_of_bidder_threads; ++i) {
worklet_debugs_.emplace_back(new DebuggableAuctionWorklet(
delegate->GetFrame(), process_handle_, worklet_info_.script_url,
bidder_worklet_.get(),
/*thread_index=*/i));
if (std::optional<base::ProcessId> maybe_pid =
worklet_debugs_.back()->GetPid(
base::BindOnce(&WorkletOwner::OnThreadReady,
weak_ptr_factory_.GetWeakPtr()))) {
OnThreadReady(*maybe_pid);
}
// For `DebuggableAuctionWorklet` created synchronously for the same
// frame, they should have the same `should_pause_on_start()` state.
CHECK_EQ(worklet_debugs_[i]->should_pause_on_start(),
worklet_debugs_[0]->should_pause_on_start());
shared_storage_hosts.push_back(
worklet_manager_->MaybeBindAuctionSharedStorageHost(
delegate->GetFrame(),
url::Origin::Create(worklet_info_.script_url)));
}
process_handle_.GetService()->LoadBidderWorklet(
std::move(worklet_receiver), std::move(shared_storage_hosts),
worklet_debugs_[0]->should_pause_on_start(),
std::move(url_loader_factory),
std::move(auction_network_events_handler), worklet_info_.script_url,
worklet_info_.wasm_url, worklet_info_.signals_url,
worklet_info_.trusted_bidding_signals_slot_size_param,
worklet_manager_->top_window_origin(),
GetAuctionWorkletPermissionsPolicyState(delegate->GetFrame(),
worklet_info_.script_url),
worklet_info_.experiment_group_id, /*public_key=*/nullptr);
bidder_worklet_.set_disconnect_with_reason_handler(base::BindOnce(
&WorkletOwner::OnWorkletDisconnected, base::Unretained(this)));
break;
}
case WorkletType::kSeller: {
mojo::PendingReceiver<auction_worklet::mojom::SellerWorklet>
worklet_receiver = seller_worklet_.BindNewPipeAndPassReceiver();
std::vector<
mojo::PendingRemote<auction_worklet::mojom::AuctionSharedStorageHost>>
shared_storage_hosts;
for (size_t i = 0;
i < static_cast<size_t>(
features::kFledgeSellerWorkletThreadPoolSize.Get());
++i) {
worklet_debugs_.emplace_back(new DebuggableAuctionWorklet(
delegate->GetFrame(), process_handle_, worklet_info_.script_url,
seller_worklet_.get(),
/*thread_index=*/i));
if (std::optional<base::ProcessId> maybe_pid =
worklet_debugs_.back()->GetPid(
base::BindOnce(&WorkletOwner::OnThreadReady,
weak_ptr_factory_.GetWeakPtr()))) {
OnThreadReady(*maybe_pid);
}
// For `DebuggableAuctionWorklet` created synchronously for the same
// frame, they should have the same `should_pause_on_start()` state.
CHECK_EQ(worklet_debugs_[i]->should_pause_on_start(),
worklet_debugs_[0]->should_pause_on_start());
shared_storage_hosts.push_back(
worklet_manager_->MaybeBindAuctionSharedStorageHost(
delegate->GetFrame(),
url::Origin::Create(worklet_info_.script_url)));
}
process_handle_.GetService()->LoadSellerWorklet(
std::move(worklet_receiver), std::move(shared_storage_hosts),
worklet_debugs_[0]->should_pause_on_start(),
std::move(url_loader_factory),
std::move(auction_network_events_handler), worklet_info_.script_url,
worklet_info_.signals_url, worklet_manager_->top_window_origin(),
GetAuctionWorkletPermissionsPolicyState(delegate->GetFrame(),
worklet_info_.script_url),
worklet_info_.experiment_group_id);
seller_worklet_.set_disconnect_with_reason_handler(base::BindOnce(
&WorkletOwner::OnWorkletDisconnected, base::Unretained(this)));
break;
}
}
MaybeQueueNotifications();
}
void AuctionWorkletManager::WorkletOwner::OnThreadReady(
base::ProcessId unused_pid) {
// OnThreadReady may be called multiple times, since there may be multiple
// threads for this worklet. We consider the *first* thread ready to be the
// point at which the worklet is ready, since that's the point at which the
// worklet can begin processing requests.
if (is_worklet_ready_) {
return;
}
for (AuctionMetricsRecorder* auction_metrics_recorder :
auction_metrics_recorders_to_notify_) {
auction_metrics_recorder->OnWorkletReady();
}
auction_metrics_recorders_to_notify_.clear();
is_worklet_ready_ = true;
}
void AuctionWorkletManager::WorkletOwner::OnWorkletDisconnected(
uint32_t /* custom_reason */,
const std::string& description) {
// This may only be invoked once per worklet, and `worklet_manager_` is only
// cleared by this method and on destruction, so it should not be null.
DCHECK(worklet_manager_);
WorkletNoLongerUsable();
// If there's a description, it's a load failure. Otherwise, it's a crash.
if (!description.empty()) {
notify_error_type_ = FatalErrorType::kScriptLoadFailed;
notify_errors_.push_back(description);
} else {
notify_error_type_ = FatalErrorType::kWorkletCrash;
notify_errors_.emplace_back(
base::StrCat({worklet_info_.script_url.spec(), " crashed."}));
}
MaybeQueueNotifications();
}
// static
std::vector<std::string>
AuctionWorkletManager::WorkletOwner::GetDevtoolsAuctionIds(
base::WeakPtr<WorkletOwner> self) {
if (self) {
return self->ComputeDevtoolsAuctionIds();
}
return std::vector<std::string>();
}
AuctionWorkletManager::WorkletKey::WorkletKey(
WorkletType type,
const GURL& script_url,
const std::optional<GURL>& wasm_url,
const std::optional<GURL>& signals_url,
bool needs_cors_for_additional_bid,
std::optional<uint16_t> experiment_group_id,
const std::string& trusted_bidding_signals_slot_size_param,
const std::optional<url::Origin>& trusted_signals_coordinator)
: type(type),
script_url(script_url),
wasm_url(wasm_url),
signals_url(signals_url),
needs_cors_for_additional_bid(needs_cors_for_additional_bid),
experiment_group_id(experiment_group_id),
trusted_bidding_signals_slot_size_param(
trusted_bidding_signals_slot_size_param),
trusted_signals_coordinator(trusted_signals_coordinator) {}
AuctionWorkletManager::WorkletKey::WorkletKey(const WorkletKey&) = default;
AuctionWorkletManager::WorkletKey::WorkletKey(WorkletKey&&) = default;
AuctionWorkletManager::WorkletKey::~WorkletKey() = default;
namespace {
size_t CombineHash(size_t hash, size_t new_value) {
static constexpr size_t kMagic =
sizeof(size_t) > 4 ? 0x9e3779b97f4a7c15 : 0x9e3779b9;
hash ^= new_value + kMagic + (hash << 6) + (hash >> 2);
return hash;
}
} // namespace
size_t AuctionWorkletManager::WorkletKey::GetHash() const {
using base::FastHash;
size_t hash = (type == WorkletType::kBidder ? 0x1ee4cafc : 0x8dfe2bb7);
hash = CombineHash(hash, FastHash(script_url.spec()));
hash = CombineHash(hash, wasm_url ? FastHash(wasm_url->spec()) : 0xaf57570a);
hash = CombineHash(hash,
signals_url ? FastHash(signals_url->spec()) : 0xbee1271e);
hash = CombineHash(hash,
needs_cors_for_additional_bid ? 0x6748ee16 : 0xc2a13cd1);
hash = CombineHash(hash,
experiment_group_id ? *experiment_group_id : 0xd60fc235);
hash = CombineHash(hash, FastHash(trusted_bidding_signals_slot_size_param));
hash = CombineHash(
hash, trusted_signals_coordinator
? FastHash(trusted_signals_coordinator->GetURL().spec())
: 0xf3a287b1);
return hash;
}
bool AuctionWorkletManager::WorkletKey::WorkletKey::operator<(
const WorkletKey& other) const {
return std::tie(type, script_url, wasm_url, signals_url,
needs_cors_for_additional_bid, experiment_group_id,
trusted_bidding_signals_slot_size_param,
trusted_signals_coordinator) <
std::tie(other.type, other.script_url, other.wasm_url,
other.signals_url, other.needs_cors_for_additional_bid,
other.experiment_group_id,
other.trusted_bidding_signals_slot_size_param,
other.trusted_signals_coordinator);
}
AuctionWorkletManager::WorkletHandle::~WorkletHandle() {
// We register with subresource_url_authorizations() only if
// AuthorizeSubresourceUrls() was called, so deregister if that's the case.
//
// This also must imply that the object should exist --- the proxy that owns
// the SubresourceUrlAuthorizations gets created when a process is assigned,
// and that's a precondition for calling AuthorizeSubresourceUrls().
if (authorized_subresources_) {
DCHECK(worklet_owner_->subresource_url_authorizations());
worklet_owner_->subresource_url_authorizations()
->OnWorkletHandleDestruction(this);
}
worklet_owner_->UnregisterHandle(HandleKey(seq_num_, this));
}
auction_worklet::mojom::BidderWorklet*
AuctionWorkletManager::WorkletHandle::GetBidderWorklet() {
DCHECK_EQ(WorkletType::kBidder, worklet_owner_->worklet_info().type);
DCHECK(worklet_owner_->bidder_worklet());
return worklet_owner_->bidder_worklet();
}
auction_worklet::mojom::SellerWorklet*
AuctionWorkletManager::WorkletHandle::GetSellerWorklet() {
DCHECK_EQ(WorkletType::kSeller, worklet_owner_->worklet_info().type);
DCHECK(worklet_owner_->seller_worklet());
return worklet_owner_->seller_worklet();
}
const SubresourceUrlAuthorizations& AuctionWorkletManager::WorkletHandle::
GetSubresourceUrlAuthorizationsForTesting() {
DCHECK(authorized_subresources_);
DCHECK(worklet_owner_->subresource_url_authorizations());
return *worklet_owner_->subresource_url_authorizations();
}
std::vector<std::string>
AuctionWorkletManager::WorkletHandle::GetDevtoolsAuctionIdsForTesting() {
return worklet_owner_->ComputeDevtoolsAuctionIds();
}
AuctionWorkletManager::WorkletHandle::WorkletHandle(
std::string devtools_auction_id,
scoped_refptr<WorkletOwner> worklet_owner,
base::OnceClosure worklet_available_callback,
FatalErrorCallback fatal_error_callback)
: worklet_owner_(std::move(worklet_owner)),
devtools_auction_id_(std::move(devtools_auction_id)),
worklet_available_callback_(std::move(worklet_available_callback)),
fatal_error_callback_(std::move(fatal_error_callback)),
seq_num_(worklet_owner_->GetNextSeqNum()) {
DCHECK(worklet_available_callback_);
DCHECK(fatal_error_callback_);
worklet_owner_->RegisterHandle(HandleKey(seq_num_, this));
}
void AuctionWorkletManager::WorkletHandle::OnWorkletAvailable() {
DCHECK(worklet_available_callback_);
std::move(worklet_available_callback_).Run();
}
void AuctionWorkletManager::WorkletHandle::OnFatalError(
FatalErrorType type,
const std::vector<std::string>& errors) {
DCHECK(fatal_error_callback_);
std::move(fatal_error_callback_).Run(type, errors);
}
void AuctionWorkletManager::WorkletHandle::AuthorizeSubresourceUrls(
const SubresourceUrlBuilder& subresource_url_builder) {
if (authorized_subresources_) {
return;
}
authorized_subresources_ = true;
DCHECK(worklet_owner_->subresource_url_authorizations());
std::vector<SubresourceUrlBuilder::BundleSubresourceInfo>
authorized_subresource_urls;
if (subresource_url_builder.auction_signals()) {
authorized_subresource_urls.push_back(
*subresource_url_builder.auction_signals());
}
switch (worklet_owner_->worklet_info().type) {
case WorkletType::kBidder: {
const url::Origin bidder_origin =
url::Origin::Create(worklet_owner_->worklet_info().script_url);
auto it = subresource_url_builder.per_buyer_signals().find(bidder_origin);
if (it != subresource_url_builder.per_buyer_signals().end()) {
authorized_subresource_urls.push_back(it->second);
}
break;
}
case WorkletType::kSeller: {
if (subresource_url_builder.seller_signals()) {
authorized_subresource_urls.push_back(
*subresource_url_builder.seller_signals());
}
break;
}
}
worklet_owner_->subresource_url_authorizations()->AuthorizeSubresourceUrls(
this, std::move(authorized_subresource_urls));
}
bool AuctionWorkletManager::WorkletHandle::worklet_created() const {
return worklet_owner_->worklet_created();
}
AuctionWorkletManager::AuctionWorkletManager(
AuctionProcessManager* auction_process_manager,
url::Origin top_window_origin,
url::Origin frame_origin,
Delegate* delegate)
: auction_process_manager_(auction_process_manager),
top_window_origin_(std::move(top_window_origin)),
frame_origin_(std::move(frame_origin)),
delegate_(delegate),
auction_network_events_proxy_(
std::make_unique<AuctionNetworkEventsProxy>(GetFrameTreeNodeID())) {
if (base::FeatureList::IsEnabled(blink::features::kSharedStorageAPI)) {
auction_shared_storage_host_ = std::make_unique<AuctionSharedStorageHost>(
static_cast<StoragePartitionImpl*>(
delegate_->GetFrame()->GetProcess()->GetStoragePartition())
->GetSharedStorageManager());
}
}
AuctionWorkletManager::~AuctionWorkletManager() = default;
// static
AuctionWorkletManager::WorkletKey AuctionWorkletManager::BidderWorkletKey(
const GURL& bidding_logic_url,
const std::optional<GURL>& wasm_url,
const std::optional<GURL>& trusted_bidding_signals_url,
bool needs_cors_for_additional_bid,
std::optional<uint16_t> experiment_group_id,
const std::string& trusted_bidding_signals_slot_size_param,
const std::optional<url::Origin>& trusted_bidding_signals_coordinator) {
return WorkletKey(WorkletType::kBidder,
/*script_url=*/bidding_logic_url, wasm_url,
/*signals_url=*/trusted_bidding_signals_url,
needs_cors_for_additional_bid,
trusted_bidding_signals_url.has_value()
? experiment_group_id
: std::nullopt,
trusted_bidding_signals_url.has_value()
? trusted_bidding_signals_slot_size_param
: "",
trusted_bidding_signals_url.has_value()
? trusted_bidding_signals_coordinator
: std::nullopt);
}
void AuctionWorkletManager::RequestBidderWorklet(
std::string devtools_auction_id,
const GURL& bidding_logic_url,
const std::optional<GURL>& wasm_url,
const std::optional<GURL>& trusted_bidding_signals_url,
bool needs_cors_for_additional_bid,
std::optional<uint16_t> experiment_group_id,
const std::string& trusted_bidding_signals_slot_size_param,
const std::optional<url::Origin>& trusted_bidding_signals_coordinator,
base::OnceClosure worklet_available_callback,
FatalErrorCallback fatal_error_callback,
std::unique_ptr<WorkletHandle>& out_worklet_handle,
AuctionMetricsRecorder* auction_metrics_recorder) {
RequestWorkletByKey(
BidderWorkletKey(bidding_logic_url, wasm_url, trusted_bidding_signals_url,
needs_cors_for_additional_bid, experiment_group_id,
trusted_bidding_signals_slot_size_param,
trusted_bidding_signals_coordinator),
std::move(devtools_auction_id), std::move(worklet_available_callback),
std::move(fatal_error_callback), out_worklet_handle,
/*number_of_bidder_threads=*/1, auction_metrics_recorder);
}
void AuctionWorkletManager::RequestSellerWorklet(
std::string devtools_auction_id,
const GURL& decision_logic_url,
const std::optional<GURL>& trusted_scoring_signals_url,
std::optional<uint16_t> experiment_group_id,
base::OnceClosure worklet_available_callback,
FatalErrorCallback fatal_error_callback,
std::unique_ptr<WorkletHandle>& out_worklet_handle,
AuctionMetricsRecorder* auction_metrics_recorder) {
WorkletKey worklet_info(WorkletType::kSeller,
/*script_url=*/decision_logic_url,
/*wasm_url=*/std::nullopt,
/*signals_url=*/trusted_scoring_signals_url,
/*needs_cors_for_additional_bid=*/false,
experiment_group_id,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/std::nullopt);
RequestWorkletByKey(std::move(worklet_info), std::move(devtools_auction_id),
std::move(worklet_available_callback),
std::move(fatal_error_callback), out_worklet_handle,
/*number_of_bidder_threads=*/0, auction_metrics_recorder);
}
void AuctionWorkletManager::RequestWorkletByKey(
WorkletKey worklet_info,
std::string devtools_auction_id,
base::OnceClosure worklet_available_callback,
FatalErrorCallback fatal_error_callback,
std::unique_ptr<WorkletHandle>& out_worklet_handle,
size_t number_of_bidder_threads,
AuctionMetricsRecorder* auction_metrics_recorder) {
DCHECK(!out_worklet_handle);
auto worklet_it = worklets_.find(worklet_info);
scoped_refptr<WorkletOwner> worklet;
if (worklet_it != worklets_.end()) {
worklet = worklet_it->second;
} else {
// Can't just insert in the map and put a reference in `worklet_it`, since
// need to keep a live reference.
worklet = base::MakeRefCounted<WorkletOwner>(this, worklet_info,
number_of_bidder_threads);
worklets_.emplace(std::pair(std::move(worklet_info), worklet.get()));
}
if (auction_metrics_recorder) {
auction_metrics_recorder->OnWorkletRequested();
worklet->NotifyAuctionMetricsRecorderWhenReady(auction_metrics_recorder);
}
out_worklet_handle.reset(new WorkletHandle(
std::move(devtools_auction_id), std::move(worklet),
std::move(worklet_available_callback), std::move(fatal_error_callback)));
}
void AuctionWorkletManager::OnWorkletNoLongerUsable(WorkletOwner* worklet) {
DCHECK(worklets_.count(worklet->worklet_info()));
DCHECK_EQ(worklet, worklets_[worklet->worklet_info()]);
worklets_.erase(worklet->worklet_info());
}
mojo::PendingRemote<auction_worklet::mojom::AuctionSharedStorageHost>
AuctionWorkletManager::MaybeBindAuctionSharedStorageHost(
RenderFrameHostImpl* auction_runner_rfh,
const url::Origin& worklet_origin) {
mojo::PendingRemote<auction_worklet::mojom::AuctionSharedStorageHost> remote;
const blink::PermissionsPolicy* permissions_policy =
auction_runner_rfh->permissions_policy();
if (auction_shared_storage_host_ &&
permissions_policy->IsFeatureEnabledForOrigin(
blink::mojom::PermissionsPolicyFeature::kSharedStorage,
worklet_origin)) {
auction_shared_storage_host_->BindNewReceiver(
auction_runner_rfh, worklet_origin,
remote.InitWithNewPipeAndPassReceiver());
}
return remote;
}
} // namespace content