blob: 2ccd3ab7fd1a1f085b282241bd67fa5c88f94e76 [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 <array>
#include <list>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "base/types/expected.h"
#include "content/browser/interest_group/auction_metrics_recorder.h"
#include "content/browser/interest_group/auction_process_manager.h"
#include "content/browser/interest_group/interest_group_features.h"
#include "content/browser/interest_group/subresource_url_authorizations.h"
#include "content/browser/interest_group/subresource_url_builder.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/public/browser/site_instance.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/test_renderer_host.h"
#include "content/services/auction_worklet/public/mojom/auction_network_events_handler.mojom.h"
#include "content/services/auction_worklet/public/mojom/auction_shared_storage_host.mojom.h"
#include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h"
#include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
#include "content/services/auction_worklet/public/mojom/in_progress_auction_download.mojom.h"
#include "content/services/auction_worklet/public/mojom/seller_worklet.mojom.h"
#include "content/services/auction_worklet/public/mojom/trusted_signals_cache.mojom.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/system/functions.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "services/network/public/mojom/client_security_state.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/interest_group/ad_display_size.h"
#include "third_party/blink/public/common/interest_group/auction_config.h"
#include "url/gurl.h"
#include "url/origin.h"
using testing::UnorderedElementsAre;
namespace content {
namespace {
const char kAuction1[] = "a";
const char kAuction2[] = "b";
const char kAuction3[] = "c";
const char kAuction4[] = "d";
const char kAuction5[] = "e";
using BundleSubresourceInfo = SubresourceUrlBuilder::BundleSubresourceInfo;
constexpr char kBuyer1OriginStr[] = "https://origin.test";
constexpr char kBuyer2OriginStr[] = "https://origin2.test";
AuctionWorkletManager::FatalErrorCallback NeverInvokedFatalErrorCallback() {
return base::BindOnce(
[](AuctionWorkletManager::FatalErrorType fatal_error_type,
const std::vector<std::string>& errors) {
ADD_FAILURE() << "This should not be called";
});
}
blink::DirectFromSellerSignalsSubresource CreateSubresource(
const GURL& bundle_url) {
blink::DirectFromSellerSignalsSubresource subresource;
subresource.bundle_url = bundle_url;
return subresource;
}
blink::DirectFromSellerSignals PopulateSubresources() {
blink::DirectFromSellerSignals result;
const url::Origin kBuyer1Origin = url::Origin::Create(GURL(kBuyer1OriginStr));
const url::Origin kBuyer2Origin = url::Origin::Create(GURL(kBuyer2OriginStr));
const GURL bundle_url1 = GURL("https://seller.test/bundle1");
const GURL bundle_url2 = GURL("https://seller.test/bundle2");
result.prefix = GURL("https://seller.test/signals");
result.per_buyer_signals[kBuyer1Origin] = CreateSubresource(bundle_url1);
result.per_buyer_signals[kBuyer2Origin] = CreateSubresource(bundle_url2);
blink::DirectFromSellerSignalsSubresource seller_signals =
CreateSubresource(bundle_url1);
result.seller_signals = seller_signals;
blink::DirectFromSellerSignalsSubresource auction_signals =
CreateSubresource(bundle_url2);
result.auction_signals = auction_signals;
return result;
}
bool PublicKeyEvaluateHelper(
const auction_worklet::mojom::TrustedSignalsPublicKey* public_key,
base::expected<BiddingAndAuctionServerKey, std::string> expected_key) {
if (expected_key.has_value() && public_key) {
uint32_t key_id = 0;
EXPECT_TRUE(base::HexStringToUInt(
std::string_view(expected_key->id).substr(0, 2), &key_id));
return key_id == public_key->id && expected_key->key == public_key->key;
} else if (!expected_key.has_value() && !public_key) {
return true;
} else {
return false;
}
}
// Single-use helper for waiting for a load error and inspecting its data.
class FatalLoadErrorHelper {
public:
FatalLoadErrorHelper() = default;
~FatalLoadErrorHelper() = default;
AuctionWorkletManager::FatalErrorCallback Callback() {
return base::BindOnce(&FatalLoadErrorHelper::OnFatalError,
base::Unretained(this));
}
void WaitForResult() { run_loop_.Run(); }
AuctionWorkletManager::FatalErrorType fatal_error_type() const {
return fatal_error_type_;
}
const std::vector<std::string>& errors() const { return errors_; }
private:
void OnFatalError(AuctionWorkletManager::FatalErrorType fatal_error_type,
const std::vector<std::string>& errors) {
EXPECT_FALSE(run_loop_.AnyQuitCalled());
fatal_error_type_ = fatal_error_type;
errors_ = std::move(errors);
run_loop_.Quit();
}
AuctionWorkletManager::FatalErrorType fatal_error_type_;
std::vector<std::string> errors_;
// For use by FatalErrorCallback only.
base::RunLoop run_loop_;
};
// Single-use helper for testing seller worklet callbacks. Checks their
// invocation order, checks that there's an accessible handle when appropriate,
// and provides the ability to wait for each call, and tracks error parameters.
// Also owns the WorkletHandle.
class SellerWorkletHelper {
public:
SellerWorkletHelper() = default;
~SellerWorkletHelper() = default;
base::OnceClosure ProcessAssignedCallback() {
// Only expect the process assignment callback to be invoked if the callback
// is created in the first place.
expect_process_assigned_ = true;
return base::BindOnce(&SellerWorkletHelper::OnProcessAssigned,
base::Unretained(this));
}
base::OnceClosure WorkletAvailableCallback() {
return base::BindOnce(&SellerWorkletHelper::OnWorkletAvailable,
base::Unretained(this));
}
AuctionWorkletManager::FatalErrorCallback FatalErrorCallback() {
return base::BindOnce(&SellerWorkletHelper::OnFatalError,
base::Unretained(this));
}
// Returns whether the corresponding callback has been invoked.
bool process_assigned() { return process_run_loop_.AnyQuitCalled(); }
bool worklet_available() { return worklet_run_loop_.AnyQuitCalled(); }
// Values pass to the fatal error callback, if it's been invoked.
std::optional<AuctionWorkletManager::FatalErrorType> fatal_error_type() {
return fatal_error_type_;
}
const std::optional<std::vector<std::string>>& errors() const {
return errors_;
}
// Waits for the corresponding callback.
void WaitForProcess() { process_run_loop_.Run(); }
void WaitForWorklet() { worklet_run_loop_.Run(); }
AuctionWorkletManager::FatalErrorType WaitForFatalError() {
fatal_error_run_loop_.Run();
CHECK(fatal_error_type_);
return *fatal_error_type_;
}
std::unique_ptr<AuctionWorkletManager::WorkletHandle>& handle() {
return handle_;
}
private:
void OnProcessAssigned() {
CHECK(expect_process_assigned_);
EXPECT_FALSE(process_run_loop_.AnyQuitCalled());
EXPECT_FALSE(worklet_run_loop_.AnyQuitCalled());
EXPECT_FALSE(fatal_error_run_loop_.AnyQuitCalled());
process_run_loop_.Quit();
}
void OnWorkletAvailable() {
if (expect_process_assigned_) {
EXPECT_TRUE(process_run_loop_.AnyQuitCalled());
}
EXPECT_FALSE(worklet_run_loop_.AnyQuitCalled());
EXPECT_FALSE(fatal_error_run_loop_.AnyQuitCalled());
EXPECT_TRUE(handle_->GetSellerWorklet());
worklet_run_loop_.Quit();
}
void OnFatalError(AuctionWorkletManager::FatalErrorType fatal_error_type,
const std::vector<std::string>& errors) {
if (expect_process_assigned_) {
EXPECT_TRUE(process_run_loop_.AnyQuitCalled());
}
EXPECT_FALSE(fatal_error_run_loop_.AnyQuitCalled());
fatal_error_type_ = fatal_error_type;
errors_ = errors;
fatal_error_run_loop_.Quit();
}
bool expect_process_assigned_ = false;
base::RunLoop process_run_loop_;
base::RunLoop worklet_run_loop_;
base::RunLoop fatal_error_run_loop_;
std::optional<AuctionWorkletManager::FatalErrorType> fatal_error_type_;
std::optional<std::vector<std::string>> errors_;
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle_;
};
// BidderWorklet that holds onto passed in callbacks, to let the test fixture
// invoke them.
class MockBidderWorklet : public auction_worklet::mojom::BidderWorklet {
public:
explicit MockBidderWorklet(
mojo::PendingReceiver<auction_worklet::mojom::BidderWorklet>
pending_receiver,
mojo::PendingRemote<network::mojom::URLLoaderFactory>
pending_url_loader_factory,
const GURL& script_source_url,
const std::optional<GURL>& wasm_url,
const std::optional<GURL>& trusted_bidding_signals_url,
const url::Origin& top_window_origin,
auction_worklet::mojom::TrustedSignalsPublicKeyPtr public_key,
bool enable_dtor_pending_signals_check)
: url_loader_factory_(std::move(pending_url_loader_factory)),
script_source_url_(script_source_url),
wasm_url_(wasm_url),
public_key_(std::move(public_key)),
trusted_bidding_signals_url_(trusted_bidding_signals_url),
top_window_origin_(top_window_origin),
enable_dtor_pending_signals_check_(enable_dtor_pending_signals_check),
receiver_(this, std::move(pending_receiver)) {}
MockBidderWorklet(const MockBidderWorklet&) = delete;
const MockBidderWorklet& operator=(const MockBidderWorklet&) = delete;
~MockBidderWorklet() override {
if (enable_dtor_pending_signals_check_) {
// Process any pending SendPendingSignalsRequests() calls.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(expected_num_send_pending_signals_requests_calls_,
num_send_pending_signals_requests_calls_);
}
}
// auction_worklet::mojom::BidderWorklet implementation:
void BeginGenerateBid(
auction_worklet::mojom::BidderWorkletNonSharedParamsPtr
bidder_worklet_non_shared_params,
auction_worklet::mojom::TrustedSignalsCacheKeyPtr
trusted_signals_cache_key,
auction_worklet::mojom::KAnonymityBidMode kanon_mode,
const url::Origin& interest_group_join_origin,
const std::optional<GURL>& direct_from_seller_per_buyer_signals,
const std::optional<GURL>& direct_from_seller_auction_signals,
const url::Origin& browser_signal_seller_origin,
const std::optional<url::Origin>& browser_signal_top_level_seller_origin,
const base::TimeDelta browser_signal_recency,
bool browser_signal_for_debugging_only_sampling,
blink::mojom::BiddingBrowserSignalsPtr bidding_browser_signals,
base::Time auction_start_time,
const std::optional<blink::AdSize>& requested_ad_size,
uint16_t multi_bid_limit,
uint64_t group_by_origin_id,
uint64_t trace_id,
mojo::PendingAssociatedRemote<auction_worklet::mojom::GenerateBidClient>
generate_bid_client,
mojo::PendingAssociatedReceiver<
auction_worklet::mojom::GenerateBidFinalizer> bid_finalizer)
override {
NOTREACHED();
}
void SendPendingSignalsRequests() override {
++num_send_pending_signals_requests_calls_;
if (num_send_pending_signals_requests_calls_ ==
expected_num_send_pending_signals_requests_calls_) {
send_pending_signals_requests_called_loop_->Quit();
}
}
void ReportWin(
bool is_for_additional_bid,
const std::optional<std::string>& interest_group_name_reporting_id,
const std::optional<std::string>& buyer_reporting_id,
const std::optional<std::string>& buyer_and_seller_reporting_id,
const std::optional<std::string>& selected_buyer_and_seller_reporting_id,
const std::optional<std::string>& auction_signals_json,
const std::optional<std::string>& per_buyer_signals_json,
const std::optional<GURL>& direct_from_seller_per_buyer_signals,
const std::optional<std::string>&
direct_from_seller_per_buyer_signals_header_ad_slot,
const std::optional<GURL>& direct_from_seller_auction_signals,
const std::optional<std::string>&
direct_from_seller_auction_signals_header_ad_slot,
const std::string& seller_signals_json,
auction_worklet::mojom::KAnonymityStatus kanon_status,
const GURL& browser_signal_render_url,
double browser_signal_bid,
const std::optional<blink::AdCurrency>& browser_signal_bid_currency,
double browser_signal_highest_scoring_other_bid,
const std::optional<blink::AdCurrency>&
browser_signal_highest_scoring_other_bid_currency,
bool browser_signal_made_highest_scoring_other_bid,
std::optional<double> browser_signal_ad_cost,
std::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 std::optional<url::Origin>& browser_signal_top_level_seller_origin,
const std::optional<base::TimeDelta> browser_signal_reporting_timeout,
std::optional<uint32_t> bidding_signals_data_version,
const std::optional<std::string>& aggregate_win_signals,
uint64_t trace_id,
ReportWinCallback report_win_callback) override {
NOTREACHED();
}
void ConnectDevToolsAgent(
mojo::PendingAssociatedReceiver<blink::mojom::DevToolsAgent> agent,
uint32_t thread_index) override {
ADD_FAILURE()
<< "ConnectDevToolsAgent should not be called on MockBidderWorklet";
}
void ClosePipe(const char* error) {
DCHECK(error);
receiver_.ResetWithReason(/*custom_reason_code=*/0, error);
}
void WaitForSendPendingSignalsRequests(
int expected_num_send_pending_signals_requests_calls) {
DCHECK_LT(expected_num_send_pending_signals_requests_calls_,
expected_num_send_pending_signals_requests_calls);
expected_num_send_pending_signals_requests_calls_ =
expected_num_send_pending_signals_requests_calls;
if (num_send_pending_signals_requests_calls_ <
expected_num_send_pending_signals_requests_calls_) {
send_pending_signals_requests_called_loop_ =
std::make_unique<base::RunLoop>();
send_pending_signals_requests_called_loop_->Run();
}
EXPECT_EQ(expected_num_send_pending_signals_requests_calls_,
num_send_pending_signals_requests_calls_);
}
mojo::Remote<network::mojom::URLLoaderFactory>& url_loader_factory() {
return url_loader_factory_;
}
const GURL& script_source_url() const { return script_source_url_; }
const std::optional<GURL>& wasm_url() const { return wasm_url_; }
const auction_worklet::mojom::TrustedSignalsPublicKey* public_key() const {
return public_key_.get();
}
const std::optional<GURL>& trusted_bidding_signals_url() const {
return trusted_bidding_signals_url_;
}
const url::Origin& top_window_origin() const { return top_window_origin_; }
int num_send_pending_signals_requests_calls() const {
return num_send_pending_signals_requests_calls_;
}
private:
mojo::Remote<network::mojom::URLLoaderFactory> url_loader_factory_;
const GURL script_source_url_;
const std::optional<GURL> wasm_url_;
const auction_worklet::mojom::TrustedSignalsPublicKeyPtr public_key_;
const std::optional<GURL> trusted_bidding_signals_url_;
const url::Origin top_window_origin_;
const bool enable_dtor_pending_signals_check_;
// Number of times SendPendingSignalsRequests() has been invoked. Used to
// check that calls through Mojo BidderWorklet interfaces make it to the
// correct MockBidderWorklet.
int num_send_pending_signals_requests_calls_ = 0;
// Number of SendPendingSignalsRequests() to wait for. Once this is hit,
// `send_pending_signals_requests_called_loop_` is invoked. Must match
// num_send_pending_signals_requests_calls_ on destruction (which catches
// unexpected extra calls).
int expected_num_send_pending_signals_requests_calls_ = 0;
std::unique_ptr<base::RunLoop> send_pending_signals_requests_called_loop_;
// Receiver is last so that destroying `this` while there's a pending callback
// over the pipe will not DCHECK.
mojo::Receiver<auction_worklet::mojom::BidderWorklet> receiver_;
};
// SellerWorklet that holds onto passed in callbacks, to let the test fixture
// invoke them.
class MockSellerWorklet : public auction_worklet::mojom::SellerWorklet {
public:
explicit MockSellerWorklet(
mojo::PendingReceiver<auction_worklet::mojom::SellerWorklet>
pending_receiver,
mojo::PendingRemote<network::mojom::URLLoaderFactory>
pending_url_loader_factory,
const GURL& script_source_url,
const std::optional<GURL>& trusted_scoring_signals_url,
const url::Origin& top_window_origin,
auction_worklet::mojom::TrustedSignalsPublicKeyPtr public_key)
: url_loader_factory_(std::move(pending_url_loader_factory)),
script_source_url_(script_source_url),
trusted_scoring_signals_url_(trusted_scoring_signals_url),
top_window_origin_(top_window_origin),
public_key_(std::move(public_key)),
receiver_(this, std::move(pending_receiver)) {}
MockSellerWorklet(const MockSellerWorklet&) = delete;
const MockSellerWorklet& operator=(const MockSellerWorklet&) = delete;
~MockSellerWorklet() override {
// Process any pending SendPendingSignalsRequests() calls.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(expected_num_send_pending_signals_requests_calls_,
num_send_pending_signals_requests_calls_);
}
// auction_worklet::mojom::SellerWorklet implementation:
void ScoreAd(
const std::string& ad_metadata_json,
double bid,
const std::optional<blink::AdCurrency>& bid_currency,
const blink::AuctionConfig::NonSharedParams&
auction_ad_config_non_shared_params,
auction_worklet::mojom::TrustedSignalsCacheKeyPtr
trusted_signals_cache_key,
auction_worklet::mojom::CreativeInfoWithoutOwnerPtr ad,
std::vector<auction_worklet::mojom::CreativeInfoWithoutOwnerPtr>
ad_components,
const std::optional<GURL>& direct_from_seller_seller_signals,
const std::optional<std::string>&
direct_from_seller_seller_signals_header_ad_slot,
const std::optional<GURL>& direct_from_seller_auction_signals,
const std::optional<std::string>&
direct_from_seller_auction_signals_header_ad_slot,
auction_worklet::mojom::ComponentAuctionOtherSellerPtr
browser_signals_other_seller,
const std::optional<blink::AdCurrency>& component_expect_bid_currency,
const url::Origin& browser_signal_interest_group_owner,
const std::optional<std::string>&
browser_signal_selected_buyer_and_seller_reporting_id,
const std::optional<std::string>&
browser_signal_buyer_and_seller_reporting_id,
uint32_t browser_signal_bidding_duration_msecs,
bool browser_signal_for_debugging_only_in_cooldown_or_lockout,
bool browser_signal_for_debugging_only_sampling,
const std::optional<base::TimeDelta> seller_timeout,
uint64_t group_by_origin_id,
bool allow_group_by_origin_mode,
uint64_t trace_id,
const url::Origin& bidder_joining_origin,
mojo::PendingRemote<auction_worklet::mojom::ScoreAdClient>
score_ad_client) override {
NOTREACHED();
}
void SendPendingSignalsRequests() override {
++num_send_pending_signals_requests_calls_;
if (num_send_pending_signals_requests_calls_ ==
expected_num_send_pending_signals_requests_calls_) {
send_pending_signals_requests_called_loop_->Quit();
}
}
void ReportResult(
const blink::AuctionConfig::NonSharedParams&
auction_ad_config_non_shared_params,
const std::optional<GURL>& direct_from_seller_seller_signals,
const std::optional<std::string>&
direct_from_seller_seller_signals_header_ad_slot,
const std::optional<GURL>& direct_from_seller_auction_signals,
const std::optional<std::string>&
direct_from_seller_auction_signals_header_ad_slot,
auction_worklet::mojom::ComponentAuctionOtherSellerPtr
browser_signals_other_seller,
const url::Origin& browser_signal_interest_group_owner,
const std::optional<std::string>&
browser_signal_buyer_and_seller_reporting_id,
const std::optional<std::string>&
browser_signal_selected_buyer_and_seller_reporting_id,
const GURL& browser_signal_render_url,
double browser_signal_bid,
const std::optional<blink::AdCurrency>& browser_signal_bid_currency,
double browser_signal_desirability,
double browser_signal_highest_scoring_other_bid,
const std::optional<blink::AdCurrency>&
browser_signal_highest_scoring_other_bid_currency,
auction_worklet::mojom::ComponentAuctionReportResultParamsPtr
browser_signals_component_auction_report_result_params,
std::optional<uint32_t> browser_signal_data_version,
uint64_t trace_id,
ReportResultCallback report_result_callback) override {
NOTREACHED();
}
void ConnectDevToolsAgent(
mojo::PendingAssociatedReceiver<blink::mojom::DevToolsAgent> agent,
uint32_t thread_index) override {
ADD_FAILURE()
<< "ConnectDevToolsAgent should not be called on MockSellerWorklet";
}
void ClosePipe(const char* error) {
DCHECK(error);
receiver_.ResetWithReason(/*custom_reason_code=*/0, error);
}
void WaitForSendPendingSignalsRequests(
int expected_num_send_pending_signals_requests_calls) {
DCHECK_LT(expected_num_send_pending_signals_requests_calls_,
expected_num_send_pending_signals_requests_calls);
expected_num_send_pending_signals_requests_calls_ =
expected_num_send_pending_signals_requests_calls;
if (num_send_pending_signals_requests_calls_ <
expected_num_send_pending_signals_requests_calls_) {
send_pending_signals_requests_called_loop_ =
std::make_unique<base::RunLoop>();
send_pending_signals_requests_called_loop_->Run();
}
EXPECT_EQ(expected_num_send_pending_signals_requests_calls_,
num_send_pending_signals_requests_calls_);
}
mojo::Remote<network::mojom::URLLoaderFactory>& url_loader_factory() {
return url_loader_factory_;
}
const GURL& script_source_url() const { return script_source_url_; }
const std::optional<GURL>& trusted_scoring_signals_url() const {
return trusted_scoring_signals_url_;
}
const url::Origin& top_window_origin() const { return top_window_origin_; }
const auction_worklet::mojom::TrustedSignalsPublicKey* public_key() const {
return public_key_.get();
}
int num_send_pending_signals_requests_calls() const {
return num_send_pending_signals_requests_calls_;
}
private:
mojo::Remote<network::mojom::URLLoaderFactory> url_loader_factory_;
const GURL script_source_url_;
const std::optional<GURL> trusted_scoring_signals_url_;
const url::Origin top_window_origin_;
const auction_worklet::mojom::TrustedSignalsPublicKeyPtr public_key_;
// Number of times SendPendingSignalsRequests() has been invoked. Used to
// check that calls through Mojo SellerWorklet interfaces make it to the
// correct MockSellerWorklet.
int num_send_pending_signals_requests_calls_ = 0;
// Number of SendPendingSignalsRequests() to wait for. Once this is hit,
// `send_pending_signals_requests_called_loop_` is invoked. Must match
// num_send_pending_signals_requests_calls_ on destruction (which catches
// unexpected extra calls).
int expected_num_send_pending_signals_requests_calls_ = 0;
std::unique_ptr<base::RunLoop> send_pending_signals_requests_called_loop_;
// Receiver is last so that destroying `this` while there's a pending callback
// over the pipe will not DCHECK.
mojo::Receiver<auction_worklet::mojom::SellerWorklet> receiver_;
};
// AuctionProcessManager and AuctionWorkletService - combining the two with a
// mojo::ReceiverSet makes it easier to track which call came over which
// receiver than using separate classes.
class MockAuctionProcessManager
: public DedicatedAuctionProcessManager,
public auction_worklet::mojom::AuctionWorkletService {
public:
MockAuctionProcessManager()
: DedicatedAuctionProcessManager(/*trusted_signals_cache=*/nullptr) {}
~MockAuctionProcessManager() override = default;
struct WorkletInfo {
bool operator==(const WorkletInfo& other) const = default;
WorkletType worklet_type;
url::Origin origin;
};
// DedicatedAuctionProcessManager implementation:
WorkletProcess::ProcessContext CreateProcessInternal(
WorkletProcess& worklet_process) override {
mojo::PendingRemote<auction_worklet::mojom::AuctionWorkletService> service;
receiver_set_.Add(
this, service.InitWithNewPipeAndPassReceiver(),
WorkletInfo(worklet_process.worklet_type(), worklet_process.origin()));
return WorkletProcess::ProcessContext(std::move(service));
}
void OnNewProcessAssigned(const ProcessHandle* handle) override {
if (defer_on_launched_for_handles_) {
auto new_handle =
std::make_unique<AuctionProcessManager::ProcessHandle>();
base::test::TestFuture<void> process_available;
if (!RequestWorkletService(handle->worklet_type(), handle->origin(),
/*frame_site_instance=*/nullptr,
new_handle.get(),
process_available.GetCallback())) {
CHECK(process_available.Wait());
}
deferred_on_launch_call_handles_.push_back(std::move(new_handle));
} else {
handle->OnBaseProcessLaunchedForTesting(base::Process::Current());
}
}
void DeferOnLaunchedForHandles() { defer_on_launched_for_handles_ = true; }
void CallOnLaunchedWithPidForAllHandles() {
for (auto& handle : deferred_on_launch_call_handles_) {
handle->OnBaseProcessLaunchedForTesting(base::Process::Current());
}
deferred_on_launch_call_handles_.clear();
}
void DisableBidderWorkletDtorPendingSignalsCheck() {
enable_bidder_worklet_dtor_pending_signals_check_ = false;
}
// auction_worklet::mojom::AuctionWorkletService implementation:
void SetTrustedSignalsCache(
mojo::PendingRemote<auction_worklet::mojom::TrustedSignalsCache>
trusted_signals_cache) override {}
void LoadBidderWorklet(
mojo::PendingReceiver<auction_worklet::mojom::BidderWorklet>
bidder_worklet_receiver,
std::vector<
mojo::PendingRemote<auction_worklet::mojom::AuctionSharedStorageHost>>
shared_storage_hosts,
bool pause_for_debugger_on_start,
mojo::PendingRemote<network::mojom::URLLoaderFactory>
pending_url_loader_factory,
mojo::PendingRemote<auction_worklet::mojom::AuctionNetworkEventsHandler>
auction_network_events_handler,
auction_worklet::mojom::InProgressAuctionDownloadPtr script_load,
auction_worklet::mojom::InProgressAuctionDownloadPtr wasm_load,
const std::optional<GURL>& trusted_bidding_signals_url,
const std::string& trusted_bidding_signals_slot_size_param,
const url::Origin& top_window_origin,
auction_worklet::mojom::AuctionWorkletPermissionsPolicyStatePtr
permissions_policy_state,
std::optional<uint16_t> experiment_group_id,
auction_worklet::mojom::TrustedSignalsPublicKeyPtr public_key) override {
DCHECK(!bidder_worklet_);
// Make sure this request came over the right pipe.
EXPECT_EQ(receiver_set_.current_context().worklet_type,
AuctionProcessManager::WorkletType::kBidder);
EXPECT_EQ(receiver_set_.current_context().origin,
url::Origin::Create(script_load->url));
bidder_worklet_ = std::make_unique<MockBidderWorklet>(
std::move(bidder_worklet_receiver),
std::move(pending_url_loader_factory), std::move(script_load->url),
(wasm_load ? std::move(wasm_load->url) : std::optional<GURL>()),
trusted_bidding_signals_url, top_window_origin, std::move(public_key),
enable_bidder_worklet_dtor_pending_signals_check_);
if (bidder_worklet_run_loop_) {
bidder_worklet_run_loop_->Quit();
}
}
void LoadSellerWorklet(
mojo::PendingReceiver<auction_worklet::mojom::SellerWorklet>
seller_worklet_receiver,
std::vector<
mojo::PendingRemote<auction_worklet::mojom::AuctionSharedStorageHost>>
shared_storage_hosts,
bool should_pause_on_start,
mojo::PendingRemote<network::mojom::URLLoaderFactory>
pending_url_loader_factory,
mojo::PendingRemote<auction_worklet::mojom::AuctionNetworkEventsHandler>
auction_network_events_handler,
auction_worklet::mojom::InProgressAuctionDownloadPtr script_load,
const std::optional<GURL>& trusted_scoring_signals_url,
const url::Origin& top_window_origin,
auction_worklet::mojom::AuctionWorkletPermissionsPolicyStatePtr
permissions_policy_state,
std::optional<uint16_t> experiment_group_id,
std::optional<bool> send_creative_scanning_metadata,
auction_worklet::mojom::TrustedSignalsPublicKeyPtr public_key,
mojo::PendingRemote<auction_worklet::mojom::LoadSellerWorkletClient>
load_seller_worklet_client) override {
DCHECK(!seller_worklet_);
if (load_seller_worklet_client) {
load_seller_worklet_clients_.emplace_back(
std::move(load_seller_worklet_client));
if (load_seller_worklet_loop_) {
load_seller_worklet_loop_->Quit();
}
}
// Make sure this request came over the right pipe.
EXPECT_EQ(receiver_set_.current_context().worklet_type,
AuctionProcessManager::WorkletType::kSeller);
EXPECT_EQ(receiver_set_.current_context().origin,
url::Origin::Create(script_load->url));
seller_worklet_ = std::make_unique<MockSellerWorklet>(
std::move(seller_worklet_receiver),
std::move(pending_url_loader_factory), std::move(script_load->url),
trusted_scoring_signals_url, top_window_origin, std::move(public_key));
if (seller_worklet_run_loop_) {
seller_worklet_run_loop_->Quit();
}
}
std::unique_ptr<MockBidderWorklet> WaitForBidderWorklet() {
if (!bidder_worklet_) {
bidder_worklet_run_loop_ = std::make_unique<base::RunLoop>();
bidder_worklet_run_loop_->Run();
}
DCHECK(bidder_worklet_);
return std::move(bidder_worklet_);
}
bool HasBidderWorkletRequest() {
base::RunLoop().RunUntilIdle();
return bidder_worklet_.get() != nullptr;
}
std::unique_ptr<MockSellerWorklet> WaitForSellerWorklet() {
if (!seller_worklet_) {
seller_worklet_run_loop_ = std::make_unique<base::RunLoop>();
seller_worklet_run_loop_->Run();
}
DCHECK(seller_worklet_);
return std::move(seller_worklet_);
}
bool HasSellerWorkletRequest() const {
base::RunLoop().RunUntilIdle();
return seller_worklet_.get() != nullptr;
}
// Waits until `load_seller_worklet_clients_` is non-empty (if needed) and
// invokes the SellerWorkletLoaded() method of the client with
// `trusted_signals_url_allowed`. Expects there to only be a single entry in
// `load_seller_worklet_clients_`.
void SellerWorkletLoaded(bool trusted_signals_url_allowed) {
if (load_seller_worklet_clients_.empty()) {
ASSERT_FALSE(load_seller_worklet_loop_);
load_seller_worklet_loop_ = std::make_unique<base::RunLoop>();
load_seller_worklet_loop_->Run();
load_seller_worklet_loop_.reset();
}
ASSERT_EQ(load_seller_worklet_clients_.size(), 1u);
mojo::Remote<auction_worklet::mojom::LoadSellerWorkletClient>(
std::move(load_seller_worklet_clients_.front()))
->SellerWorkletLoaded(trusted_signals_url_allowed);
load_seller_worklet_clients_.clear();
}
private:
// The most recently created unclaimed bidder worklet.
std::unique_ptr<MockBidderWorklet> bidder_worklet_;
std::unique_ptr<base::RunLoop> bidder_worklet_run_loop_;
// The most recently created unclaimed seller worklet.
std::unique_ptr<MockSellerWorklet> seller_worklet_;
std::unique_ptr<base::RunLoop> seller_worklet_run_loop_;
// If true, MockBidderWorklet destructor will check to make sure the
// proper number of trusted signals requests were received.
bool enable_bidder_worklet_dtor_pending_signals_check_ = true;
// Map from ReceiverSet IDs to display name when the process was launched.
// Used to verify that worklets are created in the right process.
std::map<mojo::ReceiverId, std::string> receiver_display_name_map_;
// ReceiverSet is last (except for ProcessHandles) so that destroying `this`
// while there's a pending callback over the pipe will not DCHECK. Each
// context is the WorkletProcess associated with the pipe, to make sure
// worklets are requested over the correct pipe.
mojo::ReceiverSet<auction_worklet::mojom::AuctionWorkletService, WorkletInfo>
receiver_set_;
// Pipes to send seller worklet load completion messages. These pipes are only
// provided when using the KVv2 cache for cross-origin seller signals
// requests.
std::vector<
mojo::PendingRemote<auction_worklet::mojom::LoadSellerWorkletClient>>
load_seller_worklet_clients_;
// Run loop used to wait until `load_seller_worklet_clients_` has at least one
// member.
std::unique_ptr<base::RunLoop> load_seller_worklet_loop_;
bool defer_on_launched_for_handles_ = false;
std::vector<std::unique_ptr<AuctionProcessManager::ProcessHandle>>
deferred_on_launch_call_handles_;
};
// Helper to check that value of calling TrustedScoringSignalsUrlAllowed() on a
// WorkletHandle during the invocation of a callback, to verify that the value
// is correctly set before the callback is invoked.
class QuerySignalsUrlAllowedHelper {
public:
QuerySignalsUrlAllowedHelper() = default;
~QuerySignalsUrlAllowedHelper() = default;
base::OnceClosure GetCallback() {
return base::BindOnce(&QuerySignalsUrlAllowedHelper::OnCallback,
base::Unretained(this));
}
std::unique_ptr<AuctionWorkletManager::WorkletHandle>& handle() {
return handle_;
}
bool ScoringSignalsUrlAllowed() {
run_loop_.Run();
return signals_url_allowed_;
}
private:
void OnCallback() {
signals_url_allowed_ = handle_->TrustedScoringSignalsUrlAllowed();
run_loop_.Quit();
}
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle_;
bool signals_url_allowed_ = false;
base::RunLoop run_loop_;
};
class AuctionWorkletManagerTest : public RenderViewHostTestHarness,
public AuctionWorkletManager::Delegate {
public:
AuctionWorkletManagerTest()
: RenderViewHostTestHarness(
base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
mojo::SetDefaultProcessErrorHandler(base::BindRepeating(
&AuctionWorkletManagerTest::OnBadMessage, base::Unretained(this)));
}
~AuctionWorkletManagerTest() override {
// Any bad message should have been inspected and cleared before the end of
// the test.
EXPECT_EQ(std::string(), bad_message_);
mojo::SetDefaultProcessErrorHandler(base::NullCallback());
}
void SetUp() override {
RenderViewHostTestHarness::SetUp();
auction_process_manager_ = std::make_unique<MockAuctionProcessManager>();
auction_metrics_recorder_manager_ =
std::make_unique<AuctionMetricsRecorderManager>(
ukm::AssignNewSourceId());
auction_worklet_manager_ = std::make_unique<AuctionWorkletManager>(
auction_process_manager_.get(), kTopWindowOrigin, kFrameOrigin, this);
}
void TearDown() override {
auction_worklet_manager_.reset();
auction_process_manager_.reset();
RenderViewHostTestHarness::TearDown();
}
// AuctionWorkletManager::Delegate implementation:
network::mojom::URLLoaderFactory* GetFrameURLLoaderFactory() override {
return &url_loader_factory_;
}
network::mojom::URLLoaderFactory* GetTrustedURLLoaderFactory() override {
return &url_loader_factory_;
}
void PreconnectSocket(
const GURL& url,
const net::NetworkAnonymizationKey& network_anonymization_key) override {}
RenderFrameHostImpl* GetFrame() override {
return static_cast<RenderFrameHostImpl*>(
web_contents()->GetPrimaryMainFrame());
}
scoped_refptr<SiteInstance> GetFrameSiteInstance() override {
return scoped_refptr<SiteInstance>();
}
network::mojom::ClientSecurityStatePtr GetClientSecurityState() override {
return network::mojom::ClientSecurityState::New();
}
std::optional<std::string> GetCookieDeprecationLabel() override {
return std::nullopt;
}
void GetTrustedKeyValueServerKey(
const url::Origin& scope_origin,
const std::optional<url::Origin>& coordinator,
base::OnceCallback<void(base::expected<BiddingAndAuctionServerKey,
std::string>)> callback) override {
// Not implemented because this method is not called in this test.
NOTREACHED();
}
protected:
void OnBadMessage(const std::string& reason) {
// No test expects multiple bad messages at a time
EXPECT_EQ(std::string(), bad_message_);
// Empty bad messages aren't expected. This check allows an empty
// `bad_message_` field to mean no bad message, avoiding using an optional,
// which has less helpful output on EXPECT failures.
EXPECT_FALSE(reason.empty());
bad_message_ = reason;
}
// Gets and clear most recent bad Mojo message.
std::string TakeBadMessage() { return std::move(bad_message_); }
const url::Origin kTopWindowOrigin =
url::Origin::Create(GURL("https://top.window.origin.test/"));
// Frame origin is passed in buth otherwise ignored by these tests - it's only
// used by the DevTools hooks, which only have integration tests.
const url::Origin kFrameOrigin =
url::Origin::Create(GURL("https://frame.origin.test/"));
// Defaults used by most tests.
const GURL kDecisionLogicUrl = GURL("https://origin.test/script");
const GURL kWasmUrl = GURL("https://origin.test/wasm");
const GURL kTrustedSignalsUrl = GURL("https://origin.test/trusted_signals");
const SubresourceUrlBuilder kEmptySubresourceBuilder{std::nullopt};
const SubresourceUrlBuilder kPopulatedSubresourceBuilder{
PopulateSubresources()};
std::string bad_message_;
network::TestURLLoaderFactory url_loader_factory_;
std::unique_ptr<MockAuctionProcessManager> auction_process_manager_;
std::unique_ptr<AuctionMetricsRecorderManager>
auction_metrics_recorder_manager_;
std::unique_ptr<AuctionWorkletManager> auction_worklet_manager_;
};
TEST_F(AuctionWorkletManagerTest, SingleBidderWorklet) {
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle;
base::test::TestFuture<void> worklet_available;
auction_worklet_manager_->RequestBidderWorklet(
kAuction1, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/std::nullopt,
/*contextual_data=*/std::nullopt, worklet_available.GetCallback(),
NeverInvokedFatalErrorCallback(), handle,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available.Wait());
EXPECT_TRUE(handle->GetBidderWorklet());
EXPECT_THAT(handle->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction1));
handle->AuthorizeSubresourceUrls(kPopulatedSubresourceBuilder);
std::unique_ptr<MockBidderWorklet> bidder_worklet =
auction_process_manager_->WaitForBidderWorklet();
EXPECT_EQ(kDecisionLogicUrl, bidder_worklet->script_source_url());
EXPECT_EQ(kWasmUrl, bidder_worklet->wasm_url());
EXPECT_EQ(kTrustedSignalsUrl, bidder_worklet->trusted_bidding_signals_url());
EXPECT_EQ(kTopWindowOrigin, bidder_worklet->top_window_origin());
EXPECT_EQ(0, bidder_worklet->num_send_pending_signals_requests_calls());
handle->GetBidderWorklet()->SendPendingSignalsRequests();
bidder_worklet->WaitForSendPendingSignalsRequests(1);
const url::Origin kBuyer1Origin = url::Origin::Create(GURL(kBuyer1OriginStr));
const url::Origin kBuyer2Origin = url::Origin::Create(GURL(kBuyer2OriginStr));
const SubresourceUrlBuilder::BundleSubresourceInfo expected_buyer1_full_info =
kPopulatedSubresourceBuilder.per_buyer_signals().at(kBuyer1Origin);
const SubresourceUrlBuilder::BundleSubresourceInfo expected_buyer2_full_info =
kPopulatedSubresourceBuilder.per_buyer_signals().at(kBuyer2Origin);
EXPECT_EQ(
nullptr,
handle->GetSubresourceUrlAuthorizationsForTesting().GetAuthorizationInfo(
kPopulatedSubresourceBuilder.seller_signals()->subresource_url));
EXPECT_EQ(
kPopulatedSubresourceBuilder.auction_signals(),
*handle->GetSubresourceUrlAuthorizationsForTesting().GetAuthorizationInfo(
kPopulatedSubresourceBuilder.auction_signals()->subresource_url));
EXPECT_EQ(
expected_buyer1_full_info,
*handle->GetSubresourceUrlAuthorizationsForTesting().GetAuthorizationInfo(
expected_buyer1_full_info.subresource_url));
EXPECT_EQ(
nullptr,
handle->GetSubresourceUrlAuthorizationsForTesting().GetAuthorizationInfo(
expected_buyer2_full_info.subresource_url));
}
TEST_F(AuctionWorkletManagerTest, SingleSellerWorklet) {
SellerWorkletHelper seller_helper;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/std::nullopt,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper.ProcessAssignedCallback(),
seller_helper.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper.WaitForWorklet();
EXPECT_TRUE(seller_helper.handle()->GetSellerWorklet());
EXPECT_THAT(seller_helper.handle()->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction1));
seller_helper.handle()->AuthorizeSubresourceUrls(
kPopulatedSubresourceBuilder);
std::unique_ptr<MockSellerWorklet> seller_worklet =
auction_process_manager_->WaitForSellerWorklet();
EXPECT_EQ(kDecisionLogicUrl, seller_worklet->script_source_url());
EXPECT_EQ(kTrustedSignalsUrl, seller_worklet->trusted_scoring_signals_url());
EXPECT_EQ(kTopWindowOrigin, seller_worklet->top_window_origin());
EXPECT_EQ(0, seller_worklet->num_send_pending_signals_requests_calls());
seller_helper.handle()->GetSellerWorklet()->SendPendingSignalsRequests();
seller_worklet->WaitForSendPendingSignalsRequests(1);
const url::Origin kBuyer1Origin = url::Origin::Create(GURL(kBuyer1OriginStr));
const url::Origin kBuyer2Origin = url::Origin::Create(GURL(kBuyer2OriginStr));
const SubresourceUrlBuilder::BundleSubresourceInfo expected_buyer1_full_info =
kPopulatedSubresourceBuilder.per_buyer_signals().at(kBuyer1Origin);
const SubresourceUrlBuilder::BundleSubresourceInfo expected_buyer2_full_info =
kPopulatedSubresourceBuilder.per_buyer_signals().at(kBuyer2Origin);
EXPECT_EQ(
kPopulatedSubresourceBuilder.seller_signals(),
*seller_helper.handle()
->GetSubresourceUrlAuthorizationsForTesting()
.GetAuthorizationInfo(
kPopulatedSubresourceBuilder.seller_signals()->subresource_url));
EXPECT_EQ(
kPopulatedSubresourceBuilder.auction_signals(),
*seller_helper.handle()
->GetSubresourceUrlAuthorizationsForTesting()
.GetAuthorizationInfo(kPopulatedSubresourceBuilder.auction_signals()
->subresource_url));
EXPECT_EQ(nullptr, seller_helper.handle()
->GetSubresourceUrlAuthorizationsForTesting()
.GetAuthorizationInfo(
expected_buyer1_full_info.subresource_url));
EXPECT_EQ(nullptr, seller_helper.handle()
->GetSubresourceUrlAuthorizationsForTesting()
.GetAuthorizationInfo(
expected_buyer2_full_info.subresource_url));
}
TEST_F(AuctionWorkletManagerTest,
SingleSellerWorkletNoProcessAssignedCallback) {
SellerWorkletHelper seller_helper;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/std::nullopt,
/*send_creative_scanning_metadata=*/std::nullopt,
/*process_assigned_callback=*/base::OnceClosure(),
seller_helper.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper.WaitForWorklet();
EXPECT_TRUE(seller_helper.handle()->GetSellerWorklet());
EXPECT_THAT(seller_helper.handle()->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction1));
seller_helper.handle()->AuthorizeSubresourceUrls(
kPopulatedSubresourceBuilder);
std::unique_ptr<MockSellerWorklet> seller_worklet =
auction_process_manager_->WaitForSellerWorklet();
EXPECT_EQ(kDecisionLogicUrl, seller_worklet->script_source_url());
EXPECT_EQ(kTrustedSignalsUrl, seller_worklet->trusted_scoring_signals_url());
EXPECT_EQ(kTopWindowOrigin, seller_worklet->top_window_origin());
EXPECT_EQ(0, seller_worklet->num_send_pending_signals_requests_calls());
seller_helper.handle()->GetSellerWorklet()->SendPendingSignalsRequests();
seller_worklet->WaitForSendPendingSignalsRequests(1);
const url::Origin kBuyer1Origin = url::Origin::Create(GURL(kBuyer1OriginStr));
const url::Origin kBuyer2Origin = url::Origin::Create(GURL(kBuyer2OriginStr));
const SubresourceUrlBuilder::BundleSubresourceInfo expected_buyer1_full_info =
kPopulatedSubresourceBuilder.per_buyer_signals().at(kBuyer1Origin);
const SubresourceUrlBuilder::BundleSubresourceInfo expected_buyer2_full_info =
kPopulatedSubresourceBuilder.per_buyer_signals().at(kBuyer2Origin);
EXPECT_EQ(
kPopulatedSubresourceBuilder.seller_signals(),
*seller_helper.handle()
->GetSubresourceUrlAuthorizationsForTesting()
.GetAuthorizationInfo(
kPopulatedSubresourceBuilder.seller_signals()->subresource_url));
EXPECT_EQ(
kPopulatedSubresourceBuilder.auction_signals(),
*seller_helper.handle()
->GetSubresourceUrlAuthorizationsForTesting()
.GetAuthorizationInfo(kPopulatedSubresourceBuilder.auction_signals()
->subresource_url));
EXPECT_EQ(nullptr, seller_helper.handle()
->GetSubresourceUrlAuthorizationsForTesting()
.GetAuthorizationInfo(
expected_buyer1_full_info.subresource_url));
EXPECT_EQ(nullptr, seller_helper.handle()
->GetSubresourceUrlAuthorizationsForTesting()
.GetAuthorizationInfo(
expected_buyer2_full_info.subresource_url));
}
TEST_F(AuctionWorkletManagerTest,
SingleBidderWorkletEmptyDirectFromSellerSignals) {
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle;
base::test::TestFuture<void> worklet_available;
auction_worklet_manager_->RequestBidderWorklet(
kAuction1, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/std::nullopt,
/*contextual_data=*/std::nullopt, worklet_available.GetCallback(),
NeverInvokedFatalErrorCallback(), handle,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available.Wait());
EXPECT_TRUE(handle->GetBidderWorklet());
handle->AuthorizeSubresourceUrls(kEmptySubresourceBuilder);
std::unique_ptr<MockBidderWorklet> bidder_worklet =
auction_process_manager_->WaitForBidderWorklet();
EXPECT_TRUE(
handle->GetSubresourceUrlAuthorizationsForTesting().IsEmptyForTesting());
}
TEST_F(AuctionWorkletManagerTest,
SingleSellerWorkletEmptyDirectFromSellerSignals) {
SellerWorkletHelper seller_helper;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/std::nullopt,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper.ProcessAssignedCallback(),
seller_helper.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper.WaitForWorklet();
EXPECT_TRUE(seller_helper.handle()->GetSellerWorklet());
seller_helper.handle()->AuthorizeSubresourceUrls(kEmptySubresourceBuilder);
std::unique_ptr<MockSellerWorklet> seller_worklet =
auction_process_manager_->WaitForSellerWorklet();
EXPECT_TRUE(seller_helper.handle()
->GetSubresourceUrlAuthorizationsForTesting()
.IsEmptyForTesting());
}
// Test the case where a process assignment completes asynchronously. This
// only happens when the BidderWorklet process limit has been reached. This test
// also serves to make sure that different bidder origins result in different
// processes.
TEST_F(AuctionWorkletManagerTest, BidderWorkletAsync) {
// Create `kMaxBidderProcesses` for origins other than https://origin.test.
//
// For proper destruction ordering, `handles` should be after
// `bidder_worklets`. Otherwise, worklet destruction will result in invoking
// the `handles` fatal error callback, as if they had crashed.
std::list<std::unique_ptr<MockBidderWorklet>> bidder_worklets;
std::list<std::unique_ptr<AuctionWorkletManager::WorkletHandle>> handles;
for (size_t i = 0; i < AuctionProcessManager::kMaxBidderProcesses; ++i) {
EXPECT_EQ(i, auction_process_manager_->GetBidderProcessCountForTesting());
GURL decision_logic_url =
GURL(base::StringPrintf("https://origin%zu.test", i));
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle;
base::test::TestFuture<void> worklet_available;
auction_worklet_manager_->RequestBidderWorklet(
kAuction1, decision_logic_url, /*wasm_url=*/std::nullopt,
/*trusted_bidding_signals_url=*/std::nullopt,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/std::nullopt,
/*contextual_data=*/std::nullopt, worklet_available.GetCallback(),
NeverInvokedFatalErrorCallback(), handle,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available.Wait());
EXPECT_TRUE(handle->GetBidderWorklet());
EXPECT_EQ(i + 1,
auction_process_manager_->GetBidderProcessCountForTesting());
std::unique_ptr<MockBidderWorklet> bidder_worklet =
auction_process_manager_->WaitForBidderWorklet();
EXPECT_EQ(decision_logic_url, bidder_worklet->script_source_url());
EXPECT_EQ(std::nullopt, bidder_worklet->wasm_url());
EXPECT_EQ(std::nullopt, bidder_worklet->trusted_bidding_signals_url());
EXPECT_EQ(kTopWindowOrigin, bidder_worklet->top_window_origin());
EXPECT_EQ(0, bidder_worklet->num_send_pending_signals_requests_calls());
handle->GetBidderWorklet()->SendPendingSignalsRequests();
bidder_worklet->WaitForSendPendingSignalsRequests(1);
handles.emplace_back(std::move(handle));
bidder_worklets.emplace_back(std::move(bidder_worklet));
}
// Should be at the bidder process limit.
EXPECT_EQ(AuctionProcessManager::kMaxBidderProcesses,
auction_process_manager_->GetBidderProcessCountForTesting());
// The next request for a distinct bidder worklet should not be able to
// complete for now, since there's no available process quota.
base::test::TestFuture<void> worklet_available2;
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle;
auction_worklet_manager_->RequestBidderWorklet(
kAuction1, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/std::nullopt,
/*contextual_data=*/std::nullopt, worklet_available2.GetCallback(),
NeverInvokedFatalErrorCallback(), handle,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
EXPECT_EQ(AuctionProcessManager::kMaxBidderProcesses,
auction_process_manager_->GetBidderProcessCountForTesting());
task_environment()->RunUntilIdle();
EXPECT_FALSE(worklet_available2.IsReady());
// Freeing a WorkletHandle should result in a new process being
// available, and the most recent request getting a new worklet.
handles.pop_front();
ASSERT_TRUE(worklet_available2.Wait());
EXPECT_TRUE(handle->GetBidderWorklet());
std::unique_ptr<MockBidderWorklet> bidder_worklet =
auction_process_manager_->WaitForBidderWorklet();
EXPECT_EQ(kDecisionLogicUrl, bidder_worklet->script_source_url());
EXPECT_EQ(kWasmUrl, bidder_worklet->wasm_url());
EXPECT_EQ(kTrustedSignalsUrl, bidder_worklet->trusted_bidding_signals_url());
EXPECT_EQ(kTopWindowOrigin, bidder_worklet->top_window_origin());
EXPECT_EQ(0, bidder_worklet->num_send_pending_signals_requests_calls());
handle->GetBidderWorklet()->SendPendingSignalsRequests();
bidder_worklet->WaitForSendPendingSignalsRequests(1);
// Should still be at the process limit.
EXPECT_EQ(AuctionProcessManager::kMaxBidderProcesses,
auction_process_manager_->GetBidderProcessCountForTesting());
}
// Test the case where a process assignment completes asynchronously. This
// only happens when the SellerWorklet process limit has been reached. This test
// also serves to make sure that different seller origins result in different
// processes.
TEST_F(AuctionWorkletManagerTest, SellerWorkletAsync) {
// Create `kMaxSellerProcesses` for origins other than https://origin.test.
//
// For proper destruction ordering, `handles` should be after
// `seller_worklets`. Otherwise, worklet destruction will result in invoking
// the `handles` fatal error callback, as if they had crashed.
std::list<std::unique_ptr<MockSellerWorklet>> seller_worklets;
std::list<std::unique_ptr<AuctionWorkletManager::WorkletHandle>> handles;
for (size_t i = 0; i < AuctionProcessManager::kMaxSellerProcesses; ++i) {
EXPECT_EQ(i, auction_process_manager_->GetSellerProcessCountForTesting());
GURL decision_logic_url =
GURL(base::StringPrintf("https://origin%zu.test", i));
SellerWorkletHelper seller_helper;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, decision_logic_url,
/*trusted_scoring_signals_url=*/std::nullopt,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/std::nullopt,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper.ProcessAssignedCallback(),
seller_helper.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper.WaitForWorklet();
EXPECT_TRUE(seller_helper.handle()->GetSellerWorklet());
EXPECT_EQ(i + 1,
auction_process_manager_->GetSellerProcessCountForTesting());
std::unique_ptr<MockSellerWorklet> seller_worklet =
auction_process_manager_->WaitForSellerWorklet();
EXPECT_EQ(decision_logic_url, seller_worklet->script_source_url());
EXPECT_EQ(std::nullopt, seller_worklet->trusted_scoring_signals_url());
EXPECT_EQ(kTopWindowOrigin, seller_worklet->top_window_origin());
EXPECT_EQ(0, seller_worklet->num_send_pending_signals_requests_calls());
seller_helper.handle()->GetSellerWorklet()->SendPendingSignalsRequests();
seller_worklet->WaitForSendPendingSignalsRequests(1);
handles.emplace_back(std::move(seller_helper.handle()));
seller_worklets.emplace_back(std::move(seller_worklet));
}
// Should be at the seller process limit.
EXPECT_EQ(AuctionProcessManager::kMaxSellerProcesses,
auction_process_manager_->GetSellerProcessCountForTesting());
// The next request for a distinct seller worklet should not be able to
// complete for now, since there's no available process quota.
SellerWorkletHelper seller_helper;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/std::nullopt,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper.ProcessAssignedCallback(),
seller_helper.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
EXPECT_EQ(AuctionProcessManager::kMaxSellerProcesses,
auction_process_manager_->GetSellerProcessCountForTesting());
task_environment()->RunUntilIdle();
EXPECT_FALSE(seller_helper.process_assigned());
EXPECT_FALSE(seller_helper.worklet_available());
// Freeing a WorkletHandle should result in a new process being
// available, and the most recent request getting a new worklet.
handles.pop_front();
seller_helper.WaitForWorklet();
EXPECT_TRUE(seller_helper.handle()->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet =
auction_process_manager_->WaitForSellerWorklet();
EXPECT_EQ(kDecisionLogicUrl, seller_worklet->script_source_url());
EXPECT_EQ(kTrustedSignalsUrl, seller_worklet->trusted_scoring_signals_url());
EXPECT_EQ(kTopWindowOrigin, seller_worklet->top_window_origin());
EXPECT_EQ(0, seller_worklet->num_send_pending_signals_requests_calls());
seller_helper.handle()->GetSellerWorklet()->SendPendingSignalsRequests();
seller_worklet->WaitForSendPendingSignalsRequests(1);
// Should still be at the process limit.
EXPECT_EQ(AuctionProcessManager::kMaxSellerProcesses,
auction_process_manager_->GetSellerProcessCountForTesting());
}
// Test that requests with the same parameters reuse bidder worklets.
TEST_F(AuctionWorkletManagerTest, ReuseBidderWorklet) {
// Load a bidder worklet.
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle1;
base::test::TestFuture<void> worklet_available1;
auction_worklet_manager_->RequestBidderWorklet(
kAuction1, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/std::nullopt,
/*contextual_data=*/std::nullopt, worklet_available1.GetCallback(),
NeverInvokedFatalErrorCallback(), handle1,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available1.Wait());
EXPECT_TRUE(handle1->GetBidderWorklet());
std::unique_ptr<MockBidderWorklet> bidder_worklet1 =
auction_process_manager_->WaitForBidderWorklet();
EXPECT_EQ(kDecisionLogicUrl, bidder_worklet1->script_source_url());
EXPECT_EQ(kWasmUrl, bidder_worklet1->wasm_url());
EXPECT_EQ(kTrustedSignalsUrl, bidder_worklet1->trusted_bidding_signals_url());
EXPECT_EQ(kTopWindowOrigin, bidder_worklet1->top_window_origin());
handle1->GetBidderWorklet()->SendPendingSignalsRequests();
bidder_worklet1->WaitForSendPendingSignalsRequests(1);
// Should only be one process.
EXPECT_EQ(1u, auction_process_manager_->GetBidderProcessCountForTesting());
EXPECT_THAT(handle1->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction1));
// Load a bidder worklet with the same parameters. The worklet should be
// reused.
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle2;
base::test::TestFuture<void> worklet_available2;
auction_worklet_manager_->RequestBidderWorklet(
kAuction2, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/std::nullopt,
/*contextual_data=*/std::nullopt, worklet_available2.GetCallback(),
NeverInvokedFatalErrorCallback(), handle2,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available2.Wait());
EXPECT_EQ(handle1->GetBidderWorklet(), handle2->GetBidderWorklet());
EXPECT_FALSE(auction_process_manager_->HasBidderWorkletRequest());
handle2->GetBidderWorklet()->SendPendingSignalsRequests();
bidder_worklet1->WaitForSendPendingSignalsRequests(2);
// Should still only be one process.
EXPECT_EQ(1u, auction_process_manager_->GetBidderProcessCountForTesting());
// ... but used by both auctions.
EXPECT_THAT(handle2->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction1, kAuction2));
// Close original handle. Worklet should still be alive, and so should its
// process.
handle1.reset();
EXPECT_EQ(1u, auction_process_manager_->GetBidderProcessCountForTesting());
// We should no longer attribute its work to the first auction, however.
EXPECT_THAT(handle2->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction2));
// Load a bidder worklet with the same parameters. The worklet should still be
// reused again.
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle3;
base::test::TestFuture<void> worklet_available3;
auction_worklet_manager_->RequestBidderWorklet(
kAuction3, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/std::nullopt,
/*contextual_data=*/std::nullopt, worklet_available3.GetCallback(),
NeverInvokedFatalErrorCallback(), handle3,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available3.Wait());
EXPECT_EQ(handle2->GetBidderWorklet(), handle3->GetBidderWorklet());
EXPECT_FALSE(auction_process_manager_->HasBidderWorkletRequest());
handle3->GetBidderWorklet()->SendPendingSignalsRequests();
bidder_worklet1->WaitForSendPendingSignalsRequests(3);
// Should still only be one process.
EXPECT_EQ(1u, auction_process_manager_->GetBidderProcessCountForTesting());
EXPECT_THAT(handle3->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction2, kAuction3));
// Close both remaining handles.
handle2.reset();
handle3.reset();
// Process should be destroyed.
EXPECT_EQ(0u, auction_process_manager_->GetBidderProcessCountForTesting());
// Request another bidder worklet. A new BidderWorklet in a new process should
// be created.
base::test::TestFuture<void> worklet_available4;
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle4;
auction_worklet_manager_->RequestBidderWorklet(
kAuction4, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/std::nullopt,
/*contextual_data=*/std::nullopt, worklet_available4.GetCallback(),
NeverInvokedFatalErrorCallback(), handle4,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available4.Wait());
EXPECT_TRUE(handle4->GetBidderWorklet());
std::unique_ptr<MockBidderWorklet> bidder_worklet2 =
auction_process_manager_->WaitForBidderWorklet();
EXPECT_EQ(kDecisionLogicUrl, bidder_worklet2->script_source_url());
EXPECT_EQ(kWasmUrl, bidder_worklet2->wasm_url());
EXPECT_EQ(kTrustedSignalsUrl, bidder_worklet2->trusted_bidding_signals_url());
EXPECT_EQ(kTopWindowOrigin, bidder_worklet2->top_window_origin());
handle4->GetBidderWorklet()->SendPendingSignalsRequests();
bidder_worklet2->WaitForSendPendingSignalsRequests(1);
EXPECT_EQ(1u, auction_process_manager_->GetBidderProcessCountForTesting());
EXPECT_THAT(handle4->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction4));
}
// Test that requests with the same parameters reuse seller worklets.
TEST_F(AuctionWorkletManagerTest, ReuseSellerWorklet) {
// Load a seller worklet.
SellerWorkletHelper seller_helper1;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/std::nullopt,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper1.ProcessAssignedCallback(),
seller_helper1.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper1.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper1.WaitForWorklet();
EXPECT_TRUE(seller_helper1.handle()->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet1 =
auction_process_manager_->WaitForSellerWorklet();
EXPECT_EQ(kDecisionLogicUrl, seller_worklet1->script_source_url());
EXPECT_EQ(kTrustedSignalsUrl, seller_worklet1->trusted_scoring_signals_url());
EXPECT_EQ(kTopWindowOrigin, seller_worklet1->top_window_origin());
seller_helper1.handle()->GetSellerWorklet()->SendPendingSignalsRequests();
seller_worklet1->WaitForSendPendingSignalsRequests(1);
// Should only be one process.
EXPECT_EQ(1u, auction_process_manager_->GetSellerProcessCountForTesting());
EXPECT_THAT(seller_helper1.handle()->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction1));
// Load a seller worklet with the same parameters. The worklet should be
// reused.
SellerWorkletHelper seller_helper2;
auction_worklet_manager_->RequestSellerWorklet(
kAuction2, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/std::nullopt,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper2.ProcessAssignedCallback(),
seller_helper2.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper2.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper2.WaitForWorklet();
EXPECT_EQ(seller_helper1.handle()->GetSellerWorklet(),
seller_helper2.handle()->GetSellerWorklet());
EXPECT_FALSE(auction_process_manager_->HasSellerWorkletRequest());
seller_helper2.handle()->GetSellerWorklet()->SendPendingSignalsRequests();
seller_worklet1->WaitForSendPendingSignalsRequests(2);
// Should still only be one process.
EXPECT_EQ(1u, auction_process_manager_->GetSellerProcessCountForTesting());
// ... but used by both auctions.
EXPECT_THAT(seller_helper2.handle()->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction1, kAuction2));
// Close original handle. Worklet should still be alive, and so should its
// process.
seller_helper1.handle().reset();
EXPECT_EQ(1u, auction_process_manager_->GetSellerProcessCountForTesting());
// We should no longer attribute its work to the first auction, however.
EXPECT_THAT(seller_helper2.handle()->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction2));
// Load a seller worklet with the same parameters. The worklet should still be
// reused again.
SellerWorkletHelper seller_helper3;
auction_worklet_manager_->RequestSellerWorklet(
kAuction3, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/std::nullopt,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper3.ProcessAssignedCallback(),
seller_helper3.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper3.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper3.WaitForWorklet();
EXPECT_EQ(seller_helper2.handle()->GetSellerWorklet(),
seller_helper3.handle()->GetSellerWorklet());
EXPECT_FALSE(auction_process_manager_->HasSellerWorkletRequest());
seller_helper3.handle()->GetSellerWorklet()->SendPendingSignalsRequests();
seller_worklet1->WaitForSendPendingSignalsRequests(3);
// Should still only be one process.
EXPECT_EQ(1u, auction_process_manager_->GetSellerProcessCountForTesting());
EXPECT_THAT(seller_helper2.handle()->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction2, kAuction3));
// Close both remaining handles.
seller_helper2.handle().reset();
seller_helper3.handle().reset();
// Process should be destroyed.
EXPECT_EQ(0u, auction_process_manager_->GetSellerProcessCountForTesting());
// Request another seller worklet. A new SellerWorklet in a new process should
// be created.
SellerWorkletHelper seller_helper4;
auction_worklet_manager_->RequestSellerWorklet(
kAuction4, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/std::nullopt,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper4.ProcessAssignedCallback(),
seller_helper4.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper4.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper4.WaitForWorklet();
EXPECT_TRUE(seller_helper4.handle()->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet2 =
auction_process_manager_->WaitForSellerWorklet();
EXPECT_EQ(kDecisionLogicUrl, seller_worklet2->script_source_url());
EXPECT_EQ(kTrustedSignalsUrl, seller_worklet2->trusted_scoring_signals_url());
EXPECT_EQ(kTopWindowOrigin, seller_worklet2->top_window_origin());
EXPECT_EQ(0, seller_worklet2->num_send_pending_signals_requests_calls());
seller_helper4.handle()->GetSellerWorklet()->SendPendingSignalsRequests();
seller_worklet2->WaitForSendPendingSignalsRequests(1);
EXPECT_EQ(1u, auction_process_manager_->GetSellerProcessCountForTesting());
EXPECT_THAT(seller_helper4.handle()->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction4));
}
// Make sure that worklets are not reused when parameters don't match.
TEST_F(AuctionWorkletManagerTest, DifferentBidderWorklets) {
// Load a bidder worklet.
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle1;
base::test::TestFuture<void> worklet_available1;
auction_worklet_manager_->RequestBidderWorklet(
kAuction1, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/std::nullopt,
/*contextual_data=*/std::nullopt, worklet_available1.GetCallback(),
NeverInvokedFatalErrorCallback(), handle1,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available1.Wait());
EXPECT_TRUE(handle1->GetBidderWorklet());
std::unique_ptr<MockBidderWorklet> bidder_worklet1 =
auction_process_manager_->WaitForBidderWorklet();
EXPECT_EQ(kDecisionLogicUrl, bidder_worklet1->script_source_url());
EXPECT_EQ(kWasmUrl, bidder_worklet1->wasm_url());
EXPECT_EQ(kTrustedSignalsUrl, bidder_worklet1->trusted_bidding_signals_url());
EXPECT_EQ(kTopWindowOrigin, bidder_worklet1->top_window_origin());
// Should only be one process.
EXPECT_EQ(1u, auction_process_manager_->GetBidderProcessCountForTesting());
EXPECT_THAT(handle1->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction1));
// Load a bidder worklet with a different decision logic URL. A new worklet
// should be created, using the same process.
const GURL kDifferentDecisionLogicUrl =
GURL("https://origin.test/different_script");
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle2;
base::test::TestFuture<void> worklet_available2;
auction_worklet_manager_->RequestBidderWorklet(
kAuction2, kDifferentDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/std::nullopt,
/*contextual_data=*/std::nullopt, worklet_available2.GetCallback(),
NeverInvokedFatalErrorCallback(), handle2,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available2.Wait());
EXPECT_TRUE(handle1->GetBidderWorklet());
EXPECT_NE(handle1->GetBidderWorklet(), handle2->GetBidderWorklet());
std::unique_ptr<MockBidderWorklet> bidder_worklet2 =
auction_process_manager_->WaitForBidderWorklet();
EXPECT_EQ(kDifferentDecisionLogicUrl, bidder_worklet2->script_source_url());
EXPECT_EQ(kWasmUrl, bidder_worklet2->wasm_url());
EXPECT_EQ(kTrustedSignalsUrl, bidder_worklet2->trusted_bidding_signals_url());
EXPECT_EQ(kTopWindowOrigin, bidder_worklet2->top_window_origin());
// Should still only be one process.
EXPECT_EQ(1u, auction_process_manager_->GetBidderProcessCountForTesting());
EXPECT_THAT(handle2->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction2));
// Load a bidder worklet with a different (null) trusted signals URL. A new
// worklet should be created, using the same process.
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle3;
base::test::TestFuture<void> worklet_available3;
auction_worklet_manager_->RequestBidderWorklet(
kAuction3, kDecisionLogicUrl, kWasmUrl,
/*trusted_bidding_signals_url=*/std::nullopt,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/std::nullopt,
/*contextual_data=*/std::nullopt, worklet_available3.GetCallback(),
NeverInvokedFatalErrorCallback(), handle3,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available3.Wait());
EXPECT_TRUE(handle3->GetBidderWorklet());
EXPECT_NE(handle1->GetBidderWorklet(), handle3->GetBidderWorklet());
EXPECT_NE(handle2->GetBidderWorklet(), handle3->GetBidderWorklet());
std::unique_ptr<MockBidderWorklet> bidder_worklet3 =
auction_process_manager_->WaitForBidderWorklet();
EXPECT_EQ(kDecisionLogicUrl, bidder_worklet3->script_source_url());
EXPECT_EQ(kWasmUrl, bidder_worklet3->wasm_url());
EXPECT_EQ(std::nullopt, bidder_worklet3->trusted_bidding_signals_url());
EXPECT_EQ(kTopWindowOrigin, bidder_worklet3->top_window_origin());
// Should still only be one process.
EXPECT_EQ(1u, auction_process_manager_->GetBidderProcessCountForTesting());
EXPECT_THAT(handle3->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction3));
// Load a bidder worklet with a different (null) wasm helper URL. A new
// worklet should be created, using the same process.
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle4;
base::test::TestFuture<void> worklet_available4;
auction_worklet_manager_->RequestBidderWorklet(
kAuction4, kDecisionLogicUrl, /*wasm_url=*/std::nullopt,
kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/std::nullopt,
/*contextual_data=*/std::nullopt, worklet_available4.GetCallback(),
NeverInvokedFatalErrorCallback(), handle4,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available4.Wait());
EXPECT_TRUE(handle4->GetBidderWorklet());
EXPECT_NE(handle1->GetBidderWorklet(), handle4->GetBidderWorklet());
EXPECT_NE(handle2->GetBidderWorklet(), handle4->GetBidderWorklet());
EXPECT_NE(handle3->GetBidderWorklet(), handle4->GetBidderWorklet());
std::unique_ptr<MockBidderWorklet> bidder_worklet4 =
auction_process_manager_->WaitForBidderWorklet();
EXPECT_EQ(kDecisionLogicUrl, bidder_worklet4->script_source_url());
EXPECT_EQ(std::nullopt, bidder_worklet4->wasm_url());
EXPECT_EQ(kTrustedSignalsUrl, bidder_worklet4->trusted_bidding_signals_url());
EXPECT_EQ(kTopWindowOrigin, bidder_worklet4->top_window_origin());
// Should still only be one process.
EXPECT_EQ(1u, auction_process_manager_->GetBidderProcessCountForTesting());
EXPECT_THAT(handle4->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction4));
}
// Test bidder worklet matching with different experiment IDs.
TEST_F(AuctionWorkletManagerTest, BidderWorkletExperimentIDs) {
const unsigned short kExperiment1 = 123u;
const unsigned short kExperiment2 = 234u;
base::test::TestFuture<void> worklet_available1;
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle1;
auction_worklet_manager_->RequestBidderWorklet(
kAuction1, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false, kExperiment1,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/std::nullopt,
/*contextual_data=*/std::nullopt, worklet_available1.GetCallback(),
NeverInvokedFatalErrorCallback(), handle1,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available1.Wait());
EXPECT_TRUE(handle1->GetBidderWorklet());
std::unique_ptr<MockBidderWorklet> bidder_worklet1 =
auction_process_manager_->WaitForBidderWorklet();
// Request one with a different experiment ID. Should result in a different
// worklet.
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle2;
base::test::TestFuture<void> worklet_available2;
auction_worklet_manager_->RequestBidderWorklet(
kAuction2, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false, kExperiment2,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/std::nullopt,
/*contextual_data=*/std::nullopt, worklet_available2.GetCallback(),
NeverInvokedFatalErrorCallback(), handle2,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available2.Wait());
EXPECT_TRUE(handle2->GetBidderWorklet());
std::unique_ptr<MockBidderWorklet> bidder_worklet2 =
auction_process_manager_->WaitForBidderWorklet();
EXPECT_NE(handle1->GetBidderWorklet(), handle2->GetBidderWorklet());
// Now try with different trusted signals URL (using WASM url instead).
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle3;
base::test::TestFuture<void> worklet_available3;
auction_worklet_manager_->RequestBidderWorklet(
kAuction3, kDecisionLogicUrl, kWasmUrl, kWasmUrl,
/*needs_cors_for_additional_bid=*/false, kExperiment1,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/std::nullopt,
/*contextual_data=*/std::nullopt, worklet_available3.GetCallback(),
NeverInvokedFatalErrorCallback(), handle3,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available3.Wait());
EXPECT_TRUE(handle3->GetBidderWorklet());
std::unique_ptr<MockBidderWorklet> bidder_worklet3 =
auction_process_manager_->WaitForBidderWorklet();
EXPECT_NE(handle1->GetBidderWorklet(), handle3->GetBidderWorklet());
EXPECT_NE(handle2->GetBidderWorklet(), handle3->GetBidderWorklet());
// Now test with null trusted signals URL. For bidder worklets this should be
// as if no experiment was given, since that's the only way they see it.
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle4;
base::test::TestFuture<void> worklet_available4;
auction_worklet_manager_->RequestBidderWorklet(
kAuction4, kDecisionLogicUrl, kWasmUrl,
/*trusted_bidding_signals_url=*/std::nullopt,
/*needs_cors_for_additional_bid=*/false, kExperiment1,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/std::nullopt,
/*contextual_data=*/std::nullopt, worklet_available4.GetCallback(),
NeverInvokedFatalErrorCallback(), handle4,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available4.Wait());
EXPECT_TRUE(handle4->GetBidderWorklet());
std::unique_ptr<MockBidderWorklet> bidder_worklet4 =
auction_process_manager_->WaitForBidderWorklet();
EXPECT_NE(handle1->GetBidderWorklet(), handle4->GetBidderWorklet());
EXPECT_NE(handle2->GetBidderWorklet(), handle4->GetBidderWorklet());
EXPECT_NE(handle3->GetBidderWorklet(), handle4->GetBidderWorklet());
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle5;
base::test::TestFuture<void> worklet_available5;
auction_worklet_manager_->RequestBidderWorklet(
kAuction5, kDecisionLogicUrl, kWasmUrl,
/*trusted_bidding_signals_url=*/std::nullopt,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/std::nullopt,
/*contextual_data=*/std::nullopt, worklet_available5.GetCallback(),
NeverInvokedFatalErrorCallback(), handle5,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available5.Wait());
EXPECT_TRUE(handle5->GetBidderWorklet());
EXPECT_EQ(handle5->GetBidderWorklet(), handle4->GetBidderWorklet());
EXPECT_THAT(handle4->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction4, kAuction5));
}
// Test bidder worklet matching with different CORS mode.
TEST_F(AuctionWorkletManagerTest, BidderWorkletCORSForAdditionalBid) {
base::test::TestFuture<void> worklet_available1;
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle1;
auction_worklet_manager_->RequestBidderWorklet(
kAuction1, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/std::nullopt,
/*contextual_data=*/std::nullopt, worklet_available1.GetCallback(),
NeverInvokedFatalErrorCallback(), handle1,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available1.Wait());
EXPECT_TRUE(handle1->GetBidderWorklet());
std::unique_ptr<MockBidderWorklet> bidder_worklet1 =
auction_process_manager_->WaitForBidderWorklet();
// Request one with a different CORS setting. Should result in a different
// worklet.
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle2;
base::test::TestFuture<void> worklet_available2;
auction_worklet_manager_->RequestBidderWorklet(
kAuction1, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/true,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/std::nullopt,
/*contextual_data=*/std::nullopt, worklet_available2.GetCallback(),
NeverInvokedFatalErrorCallback(), handle2,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available2.Wait());
EXPECT_TRUE(handle2->GetBidderWorklet());
std::unique_ptr<MockBidderWorklet> bidder_worklet2 =
auction_process_manager_->WaitForBidderWorklet();
EXPECT_NE(handle1->GetBidderWorklet(), handle2->GetBidderWorklet());
}
// Make sure that worklets are not reused when parameters don't match.
TEST_F(AuctionWorkletManagerTest, DifferentSellerWorklets) {
// Load a seller worklet.
SellerWorkletHelper seller_helper1;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/std::nullopt,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper1.ProcessAssignedCallback(),
seller_helper1.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper1.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper1.WaitForWorklet();
EXPECT_TRUE(seller_helper1.handle()->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet1 =
auction_process_manager_->WaitForSellerWorklet();
EXPECT_EQ(kDecisionLogicUrl, seller_worklet1->script_source_url());
EXPECT_EQ(kTrustedSignalsUrl, seller_worklet1->trusted_scoring_signals_url());
EXPECT_EQ(kTopWindowOrigin, seller_worklet1->top_window_origin());
// Should only be one process.
EXPECT_EQ(1u, auction_process_manager_->GetSellerProcessCountForTesting());
// Load a seller worklet with a different decision logic URL. A new worklet
// should be created, using the same process.
const GURL kDifferentDecisionLogicUrl =
GURL("https://origin.test/different_script");
SellerWorkletHelper seller_helper2;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDifferentDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/std::nullopt,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper2.ProcessAssignedCallback(),
seller_helper2.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper2.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper2.WaitForWorklet();
EXPECT_TRUE(seller_helper1.handle()->GetSellerWorklet());
EXPECT_NE(seller_helper1.handle()->GetSellerWorklet(),
seller_helper2.handle()->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet2 =
auction_process_manager_->WaitForSellerWorklet();
EXPECT_EQ(kDifferentDecisionLogicUrl, seller_worklet2->script_source_url());
EXPECT_EQ(kTrustedSignalsUrl, seller_worklet2->trusted_scoring_signals_url());
EXPECT_EQ(kTopWindowOrigin, seller_worklet2->top_window_origin());
// Should still only be one process.
EXPECT_EQ(1u, auction_process_manager_->GetSellerProcessCountForTesting());
// Load a seller worklet with a different (null) trusted signals URL. A new
// worklet should be created, using the same process.
SellerWorkletHelper seller_helper3;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl,
/*trusted_scoring_signals_url=*/std::nullopt,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/std::nullopt,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper3.ProcessAssignedCallback(),
seller_helper3.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper3.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper3.WaitForWorklet();
EXPECT_TRUE(seller_helper3.handle()->GetSellerWorklet());
EXPECT_NE(seller_helper1.handle()->GetSellerWorklet(),
seller_helper3.handle()->GetSellerWorklet());
EXPECT_NE(seller_helper2.handle()->GetSellerWorklet(),
seller_helper3.handle()->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet3 =
auction_process_manager_->WaitForSellerWorklet();
EXPECT_EQ(kDecisionLogicUrl, seller_worklet3->script_source_url());
EXPECT_EQ(std::nullopt, seller_worklet3->trusted_scoring_signals_url());
EXPECT_EQ(kTopWindowOrigin, seller_worklet3->top_window_origin());
// Should still only be one process.
EXPECT_EQ(1u, auction_process_manager_->GetSellerProcessCountForTesting());
}
// Test seller worklet matching with different experiment IDs.
TEST_F(AuctionWorkletManagerTest, SellerWorkletExperimentIDs) {
const unsigned short kExperiment1 = 123u;
const unsigned short kExperiment2 = 234u;
SellerWorkletHelper seller_helper1;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kTrustedSignalsUrl, kExperiment1,
/*trusted_scoring_signals_coordinator=*/std::nullopt,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper1.ProcessAssignedCallback(),
seller_helper1.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper1.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper1.WaitForWorklet();
EXPECT_TRUE(seller_helper1.handle()->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet1 =
auction_process_manager_->WaitForSellerWorklet();
// Request one with a different experiment ID. Should result in a different
// worklet.
SellerWorkletHelper seller_helper2;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kTrustedSignalsUrl, kExperiment2,
/*trusted_scoring_signals_coordinator=*/std::nullopt,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper2.ProcessAssignedCallback(),
seller_helper2.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper2.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper2.WaitForWorklet();
EXPECT_TRUE(seller_helper2.handle()->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet2 =
auction_process_manager_->WaitForSellerWorklet();
EXPECT_NE(seller_helper1.handle()->GetSellerWorklet(),
seller_helper2.handle()->GetSellerWorklet());
// Now try with different trusted signals URL (using WASM url instead).
SellerWorkletHelper seller_helper3;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kWasmUrl, kExperiment1,
/*trusted_scoring_signals_coordinator=*/std::nullopt,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper3.ProcessAssignedCallback(),
seller_helper3.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper3.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper3.WaitForWorklet();
EXPECT_TRUE(seller_helper3.handle()->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet3 =
auction_process_manager_->WaitForSellerWorklet();
EXPECT_NE(seller_helper1.handle()->GetSellerWorklet(),
seller_helper3.handle()->GetSellerWorklet());
EXPECT_NE(seller_helper2.handle()->GetSellerWorklet(),
seller_helper3.handle()->GetSellerWorklet());
// Now test with null trusted signals URL. For seller worklet, we should still
// distinguish different experiment IDs since the ID shows up in
// AuctionConfig.
SellerWorkletHelper seller_helper4;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl,
/*trusted_scoring_signals_url=*/std::nullopt, kExperiment1,
/*trusted_scoring_signals_coordinator=*/std::nullopt,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper4.ProcessAssignedCallback(),
seller_helper4.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper4.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper4.WaitForWorklet();
EXPECT_TRUE(seller_helper4.handle()->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet4 =
auction_process_manager_->WaitForSellerWorklet();
EXPECT_NE(seller_helper1.handle()->GetSellerWorklet(),
seller_helper4.handle()->GetSellerWorklet());
EXPECT_NE(seller_helper2.handle()->GetSellerWorklet(),
seller_helper4.handle()->GetSellerWorklet());
EXPECT_NE(seller_helper3.handle()->GetSellerWorklet(),
seller_helper4.handle()->GetSellerWorklet());
SellerWorkletHelper seller_helper5;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl,
/*trusted_scoring_signals_url=*/std::nullopt,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/std::nullopt,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper5.ProcessAssignedCallback(),
seller_helper5.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper5.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper5.WaitForWorklet();
EXPECT_TRUE(seller_helper5.handle()->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet5 =
auction_process_manager_->WaitForSellerWorklet();
EXPECT_NE(seller_helper1.handle()->GetSellerWorklet(),
seller_helper5.handle()->GetSellerWorklet());
EXPECT_NE(seller_helper2.handle()->GetSellerWorklet(),
seller_helper5.handle()->GetSellerWorklet());
EXPECT_NE(seller_helper3.handle()->GetSellerWorklet(),
seller_helper5.handle()->GetSellerWorklet());
EXPECT_NE(seller_helper4.handle()->GetSellerWorklet(),
seller_helper5.handle()->GetSellerWorklet());
}
// Test seller worklet matching with different setting for sending creative
// scanning metadata.
TEST_F(AuctionWorkletManagerTest, SellerWorkletSendCreativeScanningMetadata) {
SellerWorkletHelper seller_helper1;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/std::nullopt,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper1.ProcessAssignedCallback(),
seller_helper1.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper1.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper1.WaitForWorklet();
EXPECT_TRUE(seller_helper1.handle()->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet1 =
auction_process_manager_->WaitForSellerWorklet();
// Request one with a different `send_creative_scanning_metadata`. Should
// result in a different worklet.
SellerWorkletHelper seller_helper2;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/std::nullopt,
/*send_creative_scanning_metadata=*/true,
seller_helper2.ProcessAssignedCallback(),
seller_helper2.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper2.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper2.WaitForWorklet();
EXPECT_TRUE(seller_helper2.handle()->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet2 =
auction_process_manager_->WaitForSellerWorklet();
EXPECT_NE(seller_helper1.handle()->GetSellerWorklet(),
seller_helper2.handle()->GetSellerWorklet());
// "false" is different from nullopt because of AuctionConfig serialization.
SellerWorkletHelper seller_helper3;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/std::nullopt,
/*send_creative_scanning_metadata=*/false,
seller_helper3.ProcessAssignedCallback(),
seller_helper3.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper3.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper3.WaitForWorklet();
EXPECT_TRUE(seller_helper3.handle()->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet3 =
auction_process_manager_->WaitForSellerWorklet();
EXPECT_NE(seller_helper3.handle()->GetSellerWorklet(),
seller_helper1.handle()->GetSellerWorklet());
EXPECT_NE(seller_helper3.handle()->GetSellerWorklet(),
seller_helper2.handle()->GetSellerWorklet());
}
TEST_F(AuctionWorkletManagerTest, BidderWorkletLoadError) {
const char kErrorText[] = "Goat teleportation error";
// Load a bidder worklet.
FatalLoadErrorHelper load_error_helper;
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle;
base::test::TestFuture<void> worklet_available;
auction_worklet_manager_->RequestBidderWorklet(
kAuction1, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/std::nullopt,
/*contextual_data=*/std::nullopt, worklet_available.GetCallback(),
load_error_helper.Callback(), handle,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available.Wait());
EXPECT_TRUE(handle->GetBidderWorklet());
// Return a load error.
std::unique_ptr<MockBidderWorklet> bidder_worklet =
auction_process_manager_->WaitForBidderWorklet();
bidder_worklet->ClosePipe(kErrorText);
// Wait for the load error, check the parameters.
load_error_helper.WaitForResult();
EXPECT_THAT(load_error_helper.errors(), testing::ElementsAre(kErrorText));
EXPECT_EQ(AuctionWorkletManager::FatalErrorType::kScriptLoadFailed,
load_error_helper.fatal_error_type());
// Should be safe to call into the worklet, even after the error. This allows
// errors to be handled asynchronously.
handle->GetBidderWorklet()->SendPendingSignalsRequests();
task_environment()->RunUntilIdle();
// Another request for the same worklet should trigger creation of a new
// worklet, even though the old handle for the worklet hasn't been deleted
// yet.
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle2;
base::test::TestFuture<void> worklet_available2;
auction_worklet_manager_->RequestBidderWorklet(
kAuction2, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/std::nullopt,
/*contextual_data=*/std::nullopt, worklet_available2.GetCallback(),
NeverInvokedFatalErrorCallback(), handle2,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available2.Wait());
EXPECT_TRUE(handle2->GetBidderWorklet());
EXPECT_NE(handle->GetBidderWorklet(), handle2->GetBidderWorklet());
std::unique_ptr<MockBidderWorklet> bidder_worklet2 =
auction_process_manager_->WaitForBidderWorklet();
EXPECT_THAT(handle2->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction2));
}
// Make sure that errors that occur when some worklets haven't gotten
// their success callbacks yet work right.
TEST_F(AuctionWorkletManagerTest, LoadErrorWithoutProcessAssigned) {
const size_t kNumWorklets = AuctionWorkletManager::kBatchSize * 3;
// Normally, ~MockBidderWorklet() spins an event loop in order to wait for all
// incoming pending signals requests. This causes trouble for this test since
// it makes a bunch of extra notifications get dispatched from the nested
// event loop, making it hard to precisely inject a failure. Since this test
// doesn't care about that, just turn that functionality off.
auction_process_manager_->DisableBidderWorkletDtorPendingSignalsCheck();
size_t success_callbacks = 0;
size_t error_callbacks = 0;
std::vector<std::unique_ptr<AuctionWorkletManager::WorkletHandle>> handles;
for (size_t i = 0; i < kNumWorklets; ++i) {
handles.emplace_back();
auction_worklet_manager_->RequestBidderWorklet(
kAuction1, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/std::nullopt,
/*contextual_data=*/std::nullopt,
base::BindOnce(
[](size_t* success_callbacks_ptr, size_t worklet_index) {
// Successes must be invoked in order, starting from the one for
// 0th handle.
EXPECT_EQ(worklet_index, *success_callbacks_ptr);
++*success_callbacks_ptr;
},
&success_callbacks, i),
base::BindLambdaForTesting(
[&](AuctionWorkletManager::FatalErrorType fatal_error_type,
const std::vector<std::string>& errors) {
++error_callbacks;
EXPECT_EQ(fatal_error_type,
AuctionWorkletManager::FatalErrorType::kWorkletCrash);
EXPECT_THAT(errors, ::testing::ElementsAre(
"https://origin.test/script crashed."));
}),
handles.back(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
}
// Grab the first worklet to inject a simulated crash.
std::unique_ptr<MockBidderWorklet> bidder_worklet =
auction_process_manager_->WaitForBidderWorklet();
bidder_worklet.reset();
task_environment()->RunUntilIdle();
// We expect the notification batch size # of successes, and as many failures
// as there were requests.
EXPECT_EQ(AuctionWorkletManager::kBatchSize, success_callbacks);
EXPECT_EQ(kNumWorklets, error_callbacks);
}
// Make sure that success callbacks are ordered properly.
TEST_F(AuctionWorkletManagerTest, LoadSuccessOrder) {
const size_t kNumWorklets = AuctionWorkletManager::kBatchSize * 3 + 1;
size_t success_callbacks = 0;
std::vector<std::unique_ptr<AuctionWorkletManager::WorkletHandle>> handles;
base::RunLoop run_loop;
for (size_t i = 0; i < kNumWorklets; ++i) {
handles.emplace_back();
auction_worklet_manager_->RequestBidderWorklet(
kAuction1, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/std::nullopt,
/*contextual_data=*/std::nullopt,
base::BindOnce(
[](size_t* success_callbacks_ptr, size_t limit,
base::RunLoop* run_loop, size_t worklet_index) {
// Successes must be invoked in order, starting from the one for
// 0th handle.
EXPECT_EQ(worklet_index, *success_callbacks_ptr);
++*success_callbacks_ptr;
if (*success_callbacks_ptr == limit) {
run_loop->Quit();
}
},
&success_callbacks, kNumWorklets, &run_loop, i),
NeverInvokedFatalErrorCallback(), handles.back(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
}
run_loop.Run();
EXPECT_EQ(kNumWorklets, success_callbacks);
std::unique_ptr<MockBidderWorklet> bidder_worklet =
auction_process_manager_->WaitForBidderWorklet();
}
TEST_F(AuctionWorkletManagerTest, SellerWorkletLoadError) {
const char kErrorText[] = "Goat teleportation error";
// Load a seller worklet.
SellerWorkletHelper seller_helper1;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/std::nullopt,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper1.ProcessAssignedCallback(),
seller_helper1.WorkletAvailableCallback(),
seller_helper1.FatalErrorCallback(), seller_helper1.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper1.WaitForWorklet();
EXPECT_TRUE(seller_helper1.handle()->GetSellerWorklet());
// Return a load error.
std::unique_ptr<MockSellerWorklet> seller_worklet =
auction_process_manager_->WaitForSellerWorklet();
seller_worklet->ClosePipe(kErrorText);
// Wait for the load error, check the parameters.
seller_helper1.WaitForFatalError();
EXPECT_THAT(*seller_helper1.errors(), testing::ElementsAre(kErrorText));
EXPECT_EQ(AuctionWorkletManager::FatalErrorType::kScriptLoadFailed,
seller_helper1.fatal_error_type());
// Should be safe to call into the worklet, even after the error. This allows
// errors to be handled asynchronously.
seller_helper1.handle()->GetSellerWorklet()->SendPendingSignalsRequests();
task_environment()->RunUntilIdle();
// Another request for the same worklet should trigger creation of a new
// worklet, even though the old handle for the worklet hasn't been deleted
// yet.
SellerWorkletHelper seller_helper2;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/std::nullopt,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper2.ProcessAssignedCallback(),
seller_helper2.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper2.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper2.WaitForWorklet();
EXPECT_TRUE(seller_helper2.handle()->GetSellerWorklet());
EXPECT_NE(seller_helper1.handle()->GetSellerWorklet(),
seller_helper2.handle()->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet2 =
auction_process_manager_->WaitForSellerWorklet();
}
TEST_F(AuctionWorkletManagerTest,
SellerWorkletLoadErrorNoProcessAssignedCallback) {
const char kErrorText[] = "Goat teleportation error";
// Load a seller worklet.
SellerWorkletHelper seller_helper1;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/std::nullopt,
/*send_creative_scanning_metadata=*/std::nullopt,
/*process_assigned_callback=*/base::OnceClosure(),
seller_helper1.WorkletAvailableCallback(),
seller_helper1.FatalErrorCallback(), seller_helper1.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper1.WaitForWorklet();
EXPECT_TRUE(seller_helper1.handle()->GetSellerWorklet());
// Return a load error.
std::unique_ptr<MockSellerWorklet> seller_worklet =
auction_process_manager_->WaitForSellerWorklet();
seller_worklet->ClosePipe(kErrorText);
// Wait for the load error, check the parameters.
seller_helper1.WaitForFatalError();
EXPECT_THAT(*seller_helper1.errors(), testing::ElementsAre(kErrorText));
EXPECT_EQ(AuctionWorkletManager::FatalErrorType::kScriptLoadFailed,
seller_helper1.fatal_error_type());
// Should be safe to call into the worklet, even after the error. This allows
// errors to be handled asynchronously.
seller_helper1.handle()->GetSellerWorklet()->SendPendingSignalsRequests();
task_environment()->RunUntilIdle();
// Another request for the same worklet should trigger creation of a new
// worklet, even though the old handle for the worklet hasn't been deleted
// yet.
SellerWorkletHelper seller_helper2;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/std::nullopt,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper2.ProcessAssignedCallback(),
seller_helper2.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper2.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper2.WaitForWorklet();
EXPECT_TRUE(seller_helper2.handle()->GetSellerWorklet());
EXPECT_NE(seller_helper1.handle()->GetSellerWorklet(),
seller_helper2.handle()->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet2 =
auction_process_manager_->WaitForSellerWorklet();
}
TEST_F(AuctionWorkletManagerTest, BidderWorkletCrash) {
// Load a bidder worklet.
FatalLoadErrorHelper load_error_helper;
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle;
base::test::TestFuture<void> worklet_available;
auction_worklet_manager_->RequestBidderWorklet(
kAuction1, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/std::nullopt,
/*contextual_data=*/std::nullopt, worklet_available.GetCallback(),
load_error_helper.Callback(), handle,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available.Wait());
EXPECT_TRUE(handle->GetBidderWorklet());
// Close the worklet pipe, simulating a worklet crash.
std::unique_ptr<MockBidderWorklet> bidder_worklet =
auction_process_manager_->WaitForBidderWorklet();
bidder_worklet.reset();
// Wait for the error, check the parameters.
load_error_helper.WaitForResult();
EXPECT_THAT(load_error_helper.errors(),
testing::ElementsAre("https://origin.test/script crashed."));
EXPECT_EQ(AuctionWorkletManager::FatalErrorType::kWorkletCrash,
load_error_helper.fatal_error_type());
// Should be safe to call into the worklet, even after the error. This allows
// errors to be handled asynchronously.
handle->GetBidderWorklet()->SendPendingSignalsRequests();
task_environment()->RunUntilIdle();
handle->GetBidderWorklet()->SendPendingSignalsRequests();
// Another request for the same worklet should trigger creation of a new
// worklet, even though the old handle for the worklet hasn't been deleted
// yet.
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle2;
base::test::TestFuture<void> worklet_available2;
auction_worklet_manager_->RequestBidderWorklet(
kAuction1, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/std::nullopt,
/*contextual_data=*/std::nullopt, worklet_available2.GetCallback(),
NeverInvokedFatalErrorCallback(), handle2,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available2.Wait());
EXPECT_TRUE(handle2->GetBidderWorklet());
EXPECT_NE(handle->GetBidderWorklet(), handle2->GetBidderWorklet());
std::unique_ptr<MockBidderWorklet> bidder_worklet2 =
auction_process_manager_->WaitForBidderWorklet();
}
TEST_F(AuctionWorkletManagerTest, SellerWorkletCrash) {
// Load a seller worklet.
SellerWorkletHelper seller_helper1;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/std::nullopt,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper1.ProcessAssignedCallback(),
seller_helper1.WorkletAvailableCallback(),
seller_helper1.FatalErrorCallback(), seller_helper1.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper1.WaitForWorklet();
EXPECT_TRUE(seller_helper1.handle()->GetSellerWorklet());
// Close the worklet pipe, simulating a worklet crash.
std::unique_ptr<MockSellerWorklet> seller_worklet =
auction_process_manager_->WaitForSellerWorklet();
seller_worklet.reset();
// Wait for the error, check the parameters.
seller_helper1.WaitForFatalError();
EXPECT_THAT(*seller_helper1.errors(),
testing::ElementsAre("https://origin.test/script crashed."));
EXPECT_EQ(AuctionWorkletManager::FatalErrorType::kWorkletCrash,
seller_helper1.fatal_error_type());
// Should be safe to call into the worklet, even after the error. This allows
// errors to be handled asynchronously.
seller_helper1.handle()->GetSellerWorklet()->SendPendingSignalsRequests();
task_environment()->RunUntilIdle();
seller_helper1.handle()->GetSellerWorklet()->SendPendingSignalsRequests();
// Another request for the same worklet should trigger creation of a new
// worklet, even though the old handle for the worklet hasn't been deleted
// yet.
SellerWorkletHelper seller_helper2;
auction_worklet_manager_->RequestSellerWorklet(
kAuction2, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/std::nullopt,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper2.ProcessAssignedCallback(),
seller_helper2.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper2.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper2.WaitForWorklet();
EXPECT_TRUE(seller_helper2.handle()->GetSellerWorklet());
EXPECT_NE(seller_helper1.handle()->GetSellerWorklet(),
seller_helper2.handle()->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet2 =
auction_process_manager_->WaitForSellerWorklet();
EXPECT_THAT(seller_helper2.handle()->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction2));
}
// Test reentrant deletion of a WorkletHandle on error.
TEST_F(AuctionWorkletManagerTest, BidderWorkletDeleteOnError) {
const char kErrorText[] = "Goat teleporation error";
// Load a bidder worklet.
base::RunLoop run_loop;
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle;
base::test::TestFuture<void> worklet_available;
auction_worklet_manager_->RequestBidderWorklet(
kAuction1, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/std::nullopt,
/*contextual_data=*/std::nullopt, worklet_available.GetCallback(),
base::BindLambdaForTesting(
[&](AuctionWorkletManager::FatalErrorType fatal_error_type,
const std::vector<std::string>& errors) {
handle.reset();
run_loop.Quit();
}),
handle,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available.Wait());
EXPECT_TRUE(handle->GetBidderWorklet());
// Return a load error.
std::unique_ptr<MockBidderWorklet> bidder_worklet =
auction_process_manager_->WaitForBidderWorklet();
bidder_worklet->ClosePipe(kErrorText);
run_loop.Run();
// The process should have been deleted, and there should be no crashes.
EXPECT_EQ(0u, auction_process_manager_->GetBidderProcessCountForTesting());
}
// Test re-entrant deletion of a WorkletHandle on success, and following
// failure on the same worklet.
TEST_F(AuctionWorkletManagerTest, BidderWorkletDeleteOnSuccess) {
const char kErrorText[] = "Ox undercapacity error";
int worklets_received = 0;
int failures_received = 0;
std::vector<std::unique_ptr<AuctionWorkletManager::WorkletHandle>> handles;
// This test assumes that the 10 requests it handles all fit within the same
// notification batch; otherwise it would still pass but not actually exercise
// what it's meant to exercise.
ASSERT_LE(10u, AuctionWorkletManager::kBatchSize);
// Request 10 worklets; on receipt of first one, delete first 4 handles.
for (int i = 0; i < 10; ++i) {
handles.emplace_back();
auction_worklet_manager_->RequestBidderWorklet(
kAuction1, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/std::nullopt,
/*contextual_data=*/std::nullopt,
base::BindOnce(
[](int worklet_index, int* worklets_received_ptr,
std::vector<std::unique_ptr<
AuctionWorkletManager::WorkletHandle>>* handles_ptr) {
if (*worklets_received_ptr == 0) {
EXPECT_EQ(0, worklet_index);
for (int j = 0; j < 4; ++j) {
handles_ptr->erase(handles_ptr->begin());
}
} else {
// After receiving 0, we deleted 0, 1, 2, 3, so we expect 4 and
// on..
EXPECT_EQ(3 + *worklets_received_ptr, worklet_index);
}
++*worklets_received_ptr;
},
i, &worklets_received, &handles),
base::BindLambdaForTesting(
[&](AuctionWorkletManager::FatalErrorType fatal_error_type,
const std::vector<std::string>& errors) {
++failures_received;
EXPECT_EQ(
fatal_error_type,
AuctionWorkletManager::FatalErrorType::kScriptLoadFailed);
EXPECT_THAT(errors, ::testing::ElementsAre(kErrorText));
}),
handles.back(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
}
task_environment()->RunUntilIdle();
// We expect the first worklet + 6 that weren't cancelled.
EXPECT_EQ(7, worklets_received);
// Return a load error.
std::unique_ptr<MockBidderWorklet> bidder_worklet =
auction_process_manager_->WaitForBidderWorklet();
bidder_worklet->ClosePipe(kErrorText);
// Only that 6 that weren't cancelled should receive it.
task_environment()->RunUntilIdle();
EXPECT_EQ(6, failures_received);
}
// Test reentrant deletion of a WorkletHandle on error.
TEST_F(AuctionWorkletManagerTest, SellerWorkletDeleteOnError) {
const char kErrorText[] = "Goat teleporation error";
// Load a seller worklet.
base::RunLoop run_loop;
SellerWorkletHelper seller_helper;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/std::nullopt,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper.ProcessAssignedCallback(),
seller_helper.WorkletAvailableCallback(),
base::BindLambdaForTesting(
[&](AuctionWorkletManager::FatalErrorType fatal_error_type,
const std::vector<std::string>& errors) {
seller_helper.handle().reset();
run_loop.Quit();
}),
seller_helper.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper.WaitForWorklet();
EXPECT_TRUE(seller_helper.handle()->GetSellerWorklet());
// Return a load error.
std::unique_ptr<MockSellerWorklet> seller_worklet =
auction_process_manager_->WaitForSellerWorklet();
seller_worklet->ClosePipe(kErrorText);
run_loop.Run();
// The process should have been deleted, and there should be no crashes.
EXPECT_EQ(0u, auction_process_manager_->GetSellerProcessCountForTesting());
}
// Minimal test that bidder worklets' AuctionURLLoaderFactoryProxies are
// correctly configured.
TEST_F(AuctionWorkletManagerTest, BidderWorkletUrlRequestProtection) {
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle;
base::test::TestFuture<void> worklet_available;
auction_worklet_manager_->RequestBidderWorklet(
kAuction1, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/std::nullopt,
/*contextual_data=*/std::nullopt, worklet_available.GetCallback(),
NeverInvokedFatalErrorCallback(), handle,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available.Wait());
EXPECT_TRUE(handle->GetBidderWorklet());
std::unique_ptr<MockBidderWorklet> bidder_worklet =
auction_process_manager_->WaitForBidderWorklet();
struct AllowedUrls {
GURL url;
const char* mime_type;
};
const auto kAllowedUrls = std::to_array<AllowedUrls>({
{kDecisionLogicUrl, "application/javascript"},
{kWasmUrl, "application/wasm"},
{GURL("https://origin.test/"
"trusted_signals?hostname=top.window.origin.test&render_urls=not_"
"validated"),
"application/json"},
});
// kDecisionLogicUrl and kWasmURL were already requested in
// RequestBidderWorklet.
size_t kRequestsStartedWithWorklet = 2;
ASSERT_EQ(kRequestsStartedWithWorklet,
url_loader_factory_.pending_requests()->size());
for (size_t i = 0; i < std::size(kAllowedUrls); ++i) {
network::ResourceRequest request;
request.url = kAllowedUrls[i].url;
request.headers.SetHeader(net::HttpRequestHeaders::kAccept,
kAllowedUrls[i].mime_type);
mojo::PendingRemote<network::mojom::URLLoader> receiver;
mojo::PendingReceiver<network::mojom::URLLoaderClient> client;
bidder_worklet->url_loader_factory()->CreateLoaderAndStart(
receiver.InitWithNewPipeAndPassReceiver(), /*request_id=*/0,
/*options=*/0, request, client.InitWithNewPipeAndPassRemote(),
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
bidder_worklet->url_loader_factory().FlushForTesting();
EXPECT_TRUE(bidder_worklet->url_loader_factory().is_connected());
ASSERT_EQ(i + kRequestsStartedWithWorklet + 1,
url_loader_factory_.pending_requests()->size());
EXPECT_EQ(kAllowedUrls[i].url,
(*url_loader_factory_
.pending_requests())[i + kRequestsStartedWithWorklet]
.request.url);
}
// Other URLs should be rejected.
network::ResourceRequest request;
request.url = GURL("https://origin.test/");
request.headers.SetHeader(net::HttpRequestHeaders::kAccept,
kAllowedUrls[0].mime_type);
mojo::PendingRemote<network::mojom::URLLoader> receiver;
mojo::PendingReceiver<network::mojom::URLLoaderClient> client;
bidder_worklet->url_loader_factory()->CreateLoaderAndStart(
receiver.InitWithNewPipeAndPassReceiver(), /*request_id=*/0,
/*options=*/0, request, client.InitWithNewPipeAndPassRemote(),
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
bidder_worklet->url_loader_factory().FlushForTesting();
EXPECT_FALSE(bidder_worklet->url_loader_factory().is_connected());
EXPECT_EQ(std::size(kAllowedUrls) + kRequestsStartedWithWorklet,
url_loader_factory_.pending_requests()->size());
EXPECT_EQ("Unexpected request", TakeBadMessage());
}
// Minimal test that seller worklets' AuctionURLLoaderFactoryProxies are
// correctly configured.
TEST_F(AuctionWorkletManagerTest, SellerWorkletUrlRequestProtection) {
SellerWorkletHelper seller_helper;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/std::nullopt,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper.ProcessAssignedCallback(),
seller_helper.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper.WaitForWorklet();
EXPECT_TRUE(seller_helper.handle()->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet =
auction_process_manager_->WaitForSellerWorklet();
struct AllowedUrlMapping {
GURL url;
const char* mime_type;
};
const auto kAllowedUrls = std::to_array<AllowedUrlMapping>({
{
kDecisionLogicUrl,
"application/javascript",
},
{GURL("https://origin.test/"
"trusted_signals?hostname=top.window.origin.test&render_urls=not_"
"validated"),
"application/json"},
});
// kDecisionLogicUrl was already requested in RequestBidderWorklet.
size_t kRequestsStartedWithWorklet = 1;
ASSERT_EQ(kRequestsStartedWithWorklet,
url_loader_factory_.pending_requests()->size());
for (size_t i = 0; i < std::size(kAllowedUrls); ++i) {
network::ResourceRequest request;
request.url = kAllowedUrls[i].url;
request.headers.SetHeader(net::HttpRequestHeaders::kAccept,
kAllowedUrls[i].mime_type);
mojo::PendingRemote<network::mojom::URLLoader> receiver;
mojo::PendingReceiver<network::mojom::URLLoaderClient> client;
seller_worklet->url_loader_factory()->CreateLoaderAndStart(
receiver.InitWithNewPipeAndPassReceiver(), /*request_id=*/0,
/*options=*/0, request, client.InitWithNewPipeAndPassRemote(),
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
seller_worklet->url_loader_factory().FlushForTesting();
EXPECT_TRUE(seller_worklet->url_loader_factory().is_connected());
ASSERT_EQ(i + kRequestsStartedWithWorklet + 1,
url_loader_factory_.pending_requests()->size());
EXPECT_EQ(kAllowedUrls[i].url,
(*url_loader_factory_
.pending_requests())[i + kRequestsStartedWithWorklet]
.request.url);
}
// Other URLs should be rejected.
network::ResourceRequest request;
request.url = GURL("https://origin.test/");
request.headers.SetHeader(net::HttpRequestHeaders::kAccept,
kAllowedUrls[0].mime_type);
mojo::PendingRemote<network::mojom::URLLoader> receiver;
mojo::PendingReceiver<network::mojom::URLLoaderClient> client;
seller_worklet->url_loader_factory()->CreateLoaderAndStart(
receiver.InitWithNewPipeAndPassReceiver(), /*request_id=*/0,
/*options=*/0, request, client.InitWithNewPipeAndPassRemote(),
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
seller_worklet->url_loader_factory().FlushForTesting();
EXPECT_FALSE(seller_worklet->url_loader_factory().is_connected());
EXPECT_EQ(std::size(kAllowedUrls) + 1,
url_loader_factory_.pending_requests()->size());
EXPECT_EQ("Unexpected request", TakeBadMessage());
}
TEST(WorkletKeyTest, HashConsistentForEqualKeys) {
AuctionWorkletManager::WorkletKey key1(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/
false, /*send_creative_scanning_metadata=*/std::nullopt, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"tkv_signals");
AuctionWorkletManager::WorkletKey key2(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/std::nullopt, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"tkv_signals");
EXPECT_FALSE(key1 < key2);
EXPECT_FALSE(key2 < key1);
EXPECT_EQ(key1.GetHash(), key2.GetHash());
}
TEST(WorkletKeyTest, HashIsDifferentForKeysWithDifferentType) {
AuctionWorkletManager::WorkletKey key1(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/std::nullopt, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"tkv_signals");
AuctionWorkletManager::WorkletKey key2(
AuctionWorkletManager::WorkletType::kSeller,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/std::nullopt, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"tkv_signals");
EXPECT_TRUE(key1 < key2 || key2 < key1);
EXPECT_NE(key1.GetHash(), key2.GetHash());
}
TEST(WorkletKeyTest, HashIsDifferentForKeysWithDifferentScriptUrl) {
AuctionWorkletManager::WorkletKey key1(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/std::nullopt, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"tkv_signals");
AuctionWorkletManager::WorkletKey key2(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://different.example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/std::nullopt, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"tkv_signals");
EXPECT_TRUE(key1 < key2 || key2 < key1);
EXPECT_NE(key1.GetHash(), key2.GetHash());
}
TEST(WorkletKeyTest, HashIsDifferentForKeysWithDifferentWasmUrl) {
AuctionWorkletManager::WorkletKey key1(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/std::nullopt, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"tkv_signals");
AuctionWorkletManager::WorkletKey key2(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://different.example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/std::nullopt, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"tkv_signals");
EXPECT_TRUE(key1 < key2 || key2 < key1);
EXPECT_NE(key1.GetHash(), key2.GetHash());
}
TEST(WorkletKeyTest, HashIsDifferentWhenGivenNullOptWasmUrl) {
AuctionWorkletManager::WorkletKey key1(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/std::nullopt, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"tkv_signals");
AuctionWorkletManager::WorkletKey key2(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"), std::nullopt,
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/std::nullopt, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"tkv_signals");
EXPECT_TRUE(key1 < key2 || key2 < key1);
EXPECT_NE(key1.GetHash(), key2.GetHash());
}
TEST(WorkletKeyTest, HashIsDifferentForKeysWithDifferentSignalsUrl) {
AuctionWorkletManager::WorkletKey key1(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/std::nullopt, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"tkv_signals");
AuctionWorkletManager::WorkletKey key2(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://different.example.test/signals_url"),
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/std::nullopt, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"tkv_signals");
EXPECT_TRUE(key1 < key2 || key2 < key1);
EXPECT_NE(key1.GetHash(), key2.GetHash());
}
TEST(WorkletKeyTest, HashIsDifferentWhenGivenNullOptSignalsUrl) {
AuctionWorkletManager::WorkletKey key1(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/std::nullopt, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"tkv_signals");
AuctionWorkletManager::WorkletKey key2(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"), std::nullopt,
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/std::nullopt, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"tkv_signals");
EXPECT_TRUE(key1 < key2 || key2 < key1);
EXPECT_NE(key1.GetHash(), key2.GetHash());
}
TEST(WorkletKeyTest, HashIsDifferentForKeysWithDifferentExperiment) {
AuctionWorkletManager::WorkletKey key1(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/std::nullopt, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"tkv_signals");
AuctionWorkletManager::WorkletKey key2(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/std::nullopt, 0x48u,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"tkv_signals");
EXPECT_TRUE(key1 < key2 || key2 < key1);
EXPECT_NE(key1.GetHash(), key2.GetHash());
}
TEST(WorkletKeyTest, HashIsDifferentWhenGivenNullOptExperiment) {
AuctionWorkletManager::WorkletKey key1(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/std::nullopt, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"tkv_signals");
AuctionWorkletManager::WorkletKey key2(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/std::nullopt, std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"tkv_signals");
EXPECT_TRUE(key1 < key2 || key2 < key1);
EXPECT_NE(key1.GetHash(), key2.GetHash());
}
TEST(WorkletKeyTest, HashIsDifferentForKeysWithDifferentCORSForAdditionalBid) {
AuctionWorkletManager::WorkletKey key1(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/std::nullopt, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"tkv_signals");
AuctionWorkletManager::WorkletKey key2(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/true,
/*send_creative_scanning_metadata=*/std::nullopt, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"tkv_signals");
EXPECT_TRUE(key1 < key2 || key2 < key1);
EXPECT_NE(key1.GetHash(), key2.GetHash());
}
TEST(WorkletKeyTest, HashIsSameForDifferentSlotSizeParamWhenNoSignalsUrl) {
AuctionWorkletManager::WorkletKey key1(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"), /*signals_url=*/std::nullopt,
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/std::nullopt, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"tkv_signals");
AuctionWorkletManager::WorkletKey key2(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"), /*signals_url=*/std::nullopt,
/*needs_cors_for_additional_bid=*/true,
/*send_creative_scanning_metadata=*/std::nullopt, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"foo=bar",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"tkv_signals");
EXPECT_TRUE(key1 < key2 || key2 < key1);
EXPECT_NE(key1.GetHash(), key2.GetHash());
}
TEST(WorkletKeyTest, HashIsDifferentForDifferentSlotSizeParamWithSignalsUrl) {
AuctionWorkletManager::WorkletKey key1(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/std::nullopt, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"tkv_signals");
AuctionWorkletManager::WorkletKey key2(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/true,
/*send_creative_scanning_metadata=*/std::nullopt, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"foo=bar",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"tkv_signals");
EXPECT_TRUE(key1 < key2 || key2 < key1);
EXPECT_NE(key1.GetHash(), key2.GetHash());
}
TEST(WorkletKeyTest, HashIsDifferentForKeysWithDifferentCoordinator) {
AuctionWorkletManager::WorkletKey key1(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/std::nullopt, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
url::Origin::Create(GURL("https://foo.test")),
/*contextual_data=*/"tkv_signals");
AuctionWorkletManager::WorkletKey key2(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/std::nullopt, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
url::Origin::Create(GURL("https://bar.test")),
/*contextual_data=*/"tkv_signals");
EXPECT_TRUE(key1 < key2 || key2 < key1);
EXPECT_NE(key1.GetHash(), key2.GetHash());
}
TEST(WorkletKeyTest, HashIsDifferentWhenGivenNullOptCoordinator) {
AuctionWorkletManager::WorkletKey key1(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/std::nullopt, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
url::Origin::Create(GURL("https://foo.test")),
/*contextual_data=*/"tkv_signals");
AuctionWorkletManager::WorkletKey key2(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/std::nullopt, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/std::nullopt,
/*contextual_data=*/"tkv_signals");
EXPECT_TRUE(key1 < key2 || key2 < key1);
EXPECT_NE(key1.GetHash(), key2.GetHash());
}
TEST(WorkletKeyTest, HashIsDifferentForKeysWithCreativeScanning) {
AuctionWorkletManager::WorkletKey key1(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/true, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"tkv_signals");
AuctionWorkletManager::WorkletKey key2(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/false, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"tkv_signals");
EXPECT_TRUE(key1 < key2 || key2 < key1);
EXPECT_NE(key1.GetHash(), key2.GetHash());
}
TEST(WorkletKeyTest, HashIsDifferentWhenGivenNullOptCreativeScanning) {
AuctionWorkletManager::WorkletKey key1(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/true, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"tkv_signals");
AuctionWorkletManager::WorkletKey key2(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/std::nullopt, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"tkv_signals");
EXPECT_TRUE(key1 < key2 || key2 < key1);
EXPECT_NE(key1.GetHash(), key2.GetHash());
}
TEST(WorkletKeyTest, HashIsDifferentForKeysWithContexualData) {
AuctionWorkletManager::WorkletKey key1(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/false, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"buyer_signals");
AuctionWorkletManager::WorkletKey key2(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/false, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"seller_signals");
EXPECT_TRUE(key1 < key2 || key2 < key1);
EXPECT_NE(key1.GetHash(), key2.GetHash());
}
TEST(WorkletKeyTest, HashIsDifferentWhenGivenNullOptContextualData) {
AuctionWorkletManager::WorkletKey key1(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/true, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/std::nullopt);
AuctionWorkletManager::WorkletKey key2(
AuctionWorkletManager::WorkletType::kBidder,
GURL("https://example.test/script_url"),
GURL("https://example.test/wasm_url"),
GURL("https://example.test/signals_url"),
/*needs_cors_for_additional_bid=*/false,
/*send_creative_scanning_metadata=*/std::nullopt, 0x85u,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_signals_coordinator=*/
url::Origin::Create(GURL("https://example.test/coordinator")),
/*contextual_data=*/"tkv_signals");
EXPECT_TRUE(key1 < key2 || key2 < key1);
EXPECT_NE(key1.GetHash(), key2.GetHash());
}
TEST_F(AuctionWorkletManagerTest,
DoesNotCrashWhenProcessReadyAfterWorkletDestroyed) {
auction_process_manager_->DeferOnLaunchedForHandles();
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle;
base::test::TestFuture<void> worklet_available;
auction_worklet_manager_->RequestBidderWorklet(
kAuction1, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/std::nullopt,
/*contextual_data=*/std::nullopt, worklet_available.GetCallback(),
NeverInvokedFatalErrorCallback(), handle,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available.Wait());
EXPECT_TRUE(handle->GetBidderWorklet());
std::unique_ptr<MockBidderWorklet> bidder_worklet =
auction_process_manager_->WaitForBidderWorklet();
bidder_worklet.reset();
handle.reset();
auction_process_manager_->CallOnLaunchedWithPidForAllHandles();
}
class AuctionWorkletManagerKVv2Test : public AuctionWorkletManagerTest {
public:
AuctionWorkletManagerKVv2Test() {
feature_list_.InitWithFeatures(
/*enabled_features=*/
{blink::features::kFledgeTrustedSignalsKVv2Support},
/*disabled_features=*/
{features::kFledgeUseKVv2SignalsCache});
}
~AuctionWorkletManagerKVv2Test() override { DCHECK(!fetch_key_callback_); }
void GetTrustedKeyValueServerKey(
const url::Origin& scope_origin,
const std::optional<url::Origin>& coordinator,
base::OnceCallback<void(base::expected<BiddingAndAuctionServerKey,
std::string>)> callback) override {
DCHECK(!fetch_key_callback_);
if (synchronous_fetch_) {
std::move(callback).Run(key_);
} else {
fetch_key_callback_ = std::move(callback);
}
}
protected:
base::test::ScopedFeatureList feature_list_;
url::Origin coordinator_{
url::Origin::Create(GURL("https://coordinator.test/"))};
base::OnceCallback<void(
base::expected<BiddingAndAuctionServerKey, std::string>)>
fetch_key_callback_;
bool synchronous_fetch_ = true;
base::expected<BiddingAndAuctionServerKey, std::string> key_{
BiddingAndAuctionServerKey("public-key", /*id=*/"00")};
};
TEST_F(AuctionWorkletManagerKVv2Test,
SingleBidderWorkletSyncFetchedKeyBeforeProcessAssigned) {
std::vector<base::expected<BiddingAndAuctionServerKey, std::string>>
expected_keys = {BiddingAndAuctionServerKey("public-key", /*id=*/"00"),
base::unexpected("Failed to fetch public key.")};
for (const auto& key : expected_keys) {
key_ = key;
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle;
base::test::TestFuture<void> worklet_available;
auction_worklet_manager_->RequestBidderWorklet(
kAuction1, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"", coordinator_,
/*contextual_data=*/std::nullopt, worklet_available.GetCallback(),
NeverInvokedFatalErrorCallback(), handle,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available.Wait());
EXPECT_TRUE(handle->GetBidderWorklet());
std::unique_ptr<MockBidderWorklet> bidder_worklet =
auction_process_manager_->WaitForBidderWorklet();
EXPECT_EQ(kDecisionLogicUrl, bidder_worklet->script_source_url());
EXPECT_EQ(kTrustedSignalsUrl,
bidder_worklet->trusted_bidding_signals_url());
EXPECT_TRUE(PublicKeyEvaluateHelper(bidder_worklet->public_key(), key_));
}
}
TEST_F(AuctionWorkletManagerKVv2Test,
SingleBidderWorkletAsyncFetchedKeyBeforeProcessAssigned) {
std::vector<base::expected<BiddingAndAuctionServerKey, std::string>>
expected_keys = {BiddingAndAuctionServerKey("public-key", /*id=*/"00"),
base::unexpected("Failed to fetch public key.")};
auction_process_manager_->DeferOnLaunchedForHandles();
synchronous_fetch_ = false;
for (const auto& key : expected_keys) {
key_ = key;
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle;
base::test::TestFuture<void> worklet_available;
auction_worklet_manager_->RequestBidderWorklet(
kAuction1, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"", coordinator_,
/*contextual_data=*/std::nullopt, worklet_available.GetCallback(),
NeverInvokedFatalErrorCallback(), handle,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
std::move(fetch_key_callback_).Run(key_);
auction_process_manager_->CallOnLaunchedWithPidForAllHandles();
ASSERT_TRUE(worklet_available.Wait());
EXPECT_TRUE(handle->GetBidderWorklet());
std::unique_ptr<MockBidderWorklet> bidder_worklet =
auction_process_manager_->WaitForBidderWorklet();
EXPECT_EQ(kDecisionLogicUrl, bidder_worklet->script_source_url());
EXPECT_EQ(kTrustedSignalsUrl,
bidder_worklet->trusted_bidding_signals_url());
EXPECT_TRUE(PublicKeyEvaluateHelper(bidder_worklet->public_key(), key_));
}
}
TEST_F(AuctionWorkletManagerKVv2Test,
SingleBidderWorkletAsyncFetchedKeyAfterProcessAssigned) {
std::vector<base::expected<BiddingAndAuctionServerKey, std::string>>
expected_keys = {BiddingAndAuctionServerKey("public-key", /*id=*/"00"),
base::unexpected("Failed to fetch public key.")};
auction_process_manager_->DeferOnLaunchedForHandles();
synchronous_fetch_ = false;
for (const auto& key : expected_keys) {
key_ = key;
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle;
base::test::TestFuture<void> worklet_available;
auction_worklet_manager_->RequestBidderWorklet(
kAuction1, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"", coordinator_,
/*contextual_data=*/std::nullopt, worklet_available.GetCallback(),
NeverInvokedFatalErrorCallback(), handle,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
auction_process_manager_->CallOnLaunchedWithPidForAllHandles();
std::move(fetch_key_callback_).Run(key_);
ASSERT_TRUE(worklet_available.Wait());
EXPECT_TRUE(handle->GetBidderWorklet());
std::unique_ptr<MockBidderWorklet> bidder_worklet =
auction_process_manager_->WaitForBidderWorklet();
EXPECT_EQ(kDecisionLogicUrl, bidder_worklet->script_source_url());
EXPECT_EQ(kTrustedSignalsUrl,
bidder_worklet->trusted_bidding_signals_url());
EXPECT_TRUE(PublicKeyEvaluateHelper(bidder_worklet->public_key(), key_));
}
}
// Make sure that worklets are not reused when parameters don't match.
TEST_F(AuctionWorkletManagerKVv2Test,
DifferentBidderWorkletsWithDifferentCoordinators) {
// Load a KVv2 bidder worklet.
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle1;
base::test::TestFuture<void> worklet_available1;
auction_worklet_manager_->RequestBidderWorklet(
kAuction1, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/
url::Origin::Create(GURL("https://a.test/")),
/*contextual_data=*/std::nullopt, worklet_available1.GetCallback(),
NeverInvokedFatalErrorCallback(), handle1,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available1.Wait());
EXPECT_TRUE(handle1->GetBidderWorklet());
std::unique_ptr<MockBidderWorklet> bidder_worklet1 =
auction_process_manager_->WaitForBidderWorklet();
EXPECT_EQ(kDecisionLogicUrl, bidder_worklet1->script_source_url());
EXPECT_EQ(kTrustedSignalsUrl, bidder_worklet1->trusted_bidding_signals_url());
EXPECT_TRUE(PublicKeyEvaluateHelper(bidder_worklet1->public_key(), key_));
// Should only be one process.
EXPECT_EQ(1u, auction_process_manager_->GetBidderProcessCountForTesting());
EXPECT_THAT(handle1->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction1));
// Load a KVv2 bidder worklet with a different coorinator. A new worklet
// should be created, using the same process.
const GURL kDifferentDecisionLogicUrl =
GURL("https://origin.test/different_script");
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle2;
base::test::TestFuture<void> worklet_available2;
auction_worklet_manager_->RequestBidderWorklet(
kAuction2, kDifferentDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"",
url::Origin::Create(GURL("https://b.test/")),
/*contextual_data=*/std::nullopt, worklet_available2.GetCallback(),
NeverInvokedFatalErrorCallback(), handle2,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available2.Wait());
EXPECT_TRUE(handle1->GetBidderWorklet());
EXPECT_NE(handle1->GetBidderWorklet(), handle2->GetBidderWorklet());
std::unique_ptr<MockBidderWorklet> bidder_worklet2 =
auction_process_manager_->WaitForBidderWorklet();
EXPECT_EQ(kDifferentDecisionLogicUrl, bidder_worklet2->script_source_url());
EXPECT_EQ(kTrustedSignalsUrl, bidder_worklet2->trusted_bidding_signals_url());
EXPECT_TRUE(PublicKeyEvaluateHelper(bidder_worklet2->public_key(), key_));
// Should still only be one process.
EXPECT_EQ(1u, auction_process_manager_->GetBidderProcessCountForTesting());
EXPECT_THAT(handle2->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction2));
}
// Test that requests with the same parameters reuse bidder worklets.
TEST_F(AuctionWorkletManagerKVv2Test, ReuseBidderWorklet) {
// Load a KVv2 bidder worklet.
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle1;
base::test::TestFuture<void> worklet_available1;
auction_worklet_manager_->RequestBidderWorklet(
kAuction1, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"", coordinator_,
/*contextual_data=*/std::nullopt, worklet_available1.GetCallback(),
NeverInvokedFatalErrorCallback(), handle1,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available1.Wait());
EXPECT_TRUE(handle1->GetBidderWorklet());
std::unique_ptr<MockBidderWorklet> bidder_worklet1 =
auction_process_manager_->WaitForBidderWorklet();
EXPECT_EQ(kDecisionLogicUrl, bidder_worklet1->script_source_url());
EXPECT_EQ(kTrustedSignalsUrl, bidder_worklet1->trusted_bidding_signals_url());
EXPECT_TRUE(PublicKeyEvaluateHelper(bidder_worklet1->public_key(), key_));
handle1->GetBidderWorklet()->SendPendingSignalsRequests();
bidder_worklet1->WaitForSendPendingSignalsRequests(1);
// Should only be one process.
EXPECT_EQ(1u, auction_process_manager_->GetBidderProcessCountForTesting());
EXPECT_THAT(handle1->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction1));
// Load a KVv2 bidder worklet with the same parameters. The worklet should be
// reused.
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle2;
base::test::TestFuture<void> worklet_available2;
auction_worklet_manager_->RequestBidderWorklet(
kAuction2, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"", coordinator_,
/*contextual_data=*/std::nullopt, worklet_available2.GetCallback(),
NeverInvokedFatalErrorCallback(), handle2,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available2.Wait());
EXPECT_EQ(handle1->GetBidderWorklet(), handle2->GetBidderWorklet());
EXPECT_FALSE(auction_process_manager_->HasBidderWorkletRequest());
handle2->GetBidderWorklet()->SendPendingSignalsRequests();
bidder_worklet1->WaitForSendPendingSignalsRequests(2);
// Should still only be one process.
EXPECT_EQ(1u, auction_process_manager_->GetBidderProcessCountForTesting());
// ... but used by both auctions.
EXPECT_THAT(handle2->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction1, kAuction2));
// Close original handle. Worklet should still be alive, and so should its
// process.
handle1.reset();
EXPECT_EQ(1u, auction_process_manager_->GetBidderProcessCountForTesting());
// We should no longer attribute its work to the first auction, however.
EXPECT_THAT(handle2->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction2));
// Load a KVv2 bidder worklet with the same parameters. The worklet should
// still be reused again.
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle3;
base::test::TestFuture<void> worklet_available3;
auction_worklet_manager_->RequestBidderWorklet(
kAuction3, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"", coordinator_,
/*contextual_data=*/std::nullopt, worklet_available3.GetCallback(),
NeverInvokedFatalErrorCallback(), handle3,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available3.Wait());
EXPECT_EQ(handle2->GetBidderWorklet(), handle3->GetBidderWorklet());
EXPECT_FALSE(auction_process_manager_->HasBidderWorkletRequest());
handle3->GetBidderWorklet()->SendPendingSignalsRequests();
bidder_worklet1->WaitForSendPendingSignalsRequests(3);
// Should still only be one process.
EXPECT_EQ(1u, auction_process_manager_->GetBidderProcessCountForTesting());
EXPECT_THAT(handle3->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction2, kAuction3));
// Close both remaining handles.
handle2.reset();
handle3.reset();
// Process should be destroyed.
EXPECT_EQ(0u, auction_process_manager_->GetBidderProcessCountForTesting());
// Request another KVv2 bidder worklet. A new BidderWorklet in a new process
// should be created.
base::test::TestFuture<void> worklet_available4;
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle4;
auction_worklet_manager_->RequestBidderWorklet(
kAuction4, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"", coordinator_,
/*contextual_data=*/std::nullopt, worklet_available4.GetCallback(),
NeverInvokedFatalErrorCallback(), handle4,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available4.Wait());
EXPECT_TRUE(handle4->GetBidderWorklet());
std::unique_ptr<MockBidderWorklet> bidder_worklet2 =
auction_process_manager_->WaitForBidderWorklet();
EXPECT_EQ(kDecisionLogicUrl, bidder_worklet2->script_source_url());
EXPECT_EQ(kTrustedSignalsUrl, bidder_worklet2->trusted_bidding_signals_url());
EXPECT_TRUE(PublicKeyEvaluateHelper(bidder_worklet2->public_key(), key_));
handle4->GetBidderWorklet()->SendPendingSignalsRequests();
bidder_worklet2->WaitForSendPendingSignalsRequests(1);
EXPECT_EQ(1u, auction_process_manager_->GetBidderProcessCountForTesting());
EXPECT_THAT(handle4->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction4));
}
// Test a bidder worklet can be correctly requested when coordinator is empty
// and `kFledgeTrustedSignalsKVv2Support` is enabled.
TEST_F(AuctionWorkletManagerKVv2Test, BidderWorkletWithoutCoordinator) {
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle;
base::test::TestFuture<void> worklet_available;
auction_worklet_manager_->RequestBidderWorklet(
kAuction1, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/std::nullopt,
/*contextual_data=*/std::nullopt, worklet_available.GetCallback(),
NeverInvokedFatalErrorCallback(), handle,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available.Wait());
EXPECT_TRUE(handle->GetBidderWorklet());
std::unique_ptr<MockBidderWorklet> bidder_worklet =
auction_process_manager_->WaitForBidderWorklet();
EXPECT_EQ(kDecisionLogicUrl, bidder_worklet->script_source_url());
EXPECT_EQ(kTrustedSignalsUrl, bidder_worklet->trusted_bidding_signals_url());
EXPECT_TRUE(!bidder_worklet->public_key());
}
TEST_F(AuctionWorkletManagerKVv2Test,
SingleSellerWorkletSyncFetchedKeyBeforeProcessAssigned) {
std::vector<base::expected<BiddingAndAuctionServerKey, std::string>>
expected_keys = {BiddingAndAuctionServerKey("public-key", /*id=*/"00"),
base::unexpected("Failed to fetch public key.")};
for (const auto& key : expected_keys) {
key_ = key;
SellerWorkletHelper seller_helper;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/
url::Origin::Create(GURL("https://origin.test/")),
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper.ProcessAssignedCallback(),
seller_helper.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper.WaitForWorklet();
EXPECT_TRUE(seller_helper.handle()->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet =
auction_process_manager_->WaitForSellerWorklet();
EXPECT_EQ(kDecisionLogicUrl, seller_worklet->script_source_url());
EXPECT_EQ(kTrustedSignalsUrl,
seller_worklet->trusted_scoring_signals_url());
EXPECT_TRUE(PublicKeyEvaluateHelper(seller_worklet->public_key(), key_));
}
}
TEST_F(AuctionWorkletManagerKVv2Test,
SingleSellerWorkletAsyncFetchedKeyBeforeProcessAssigned) {
std::vector<base::expected<BiddingAndAuctionServerKey, std::string>>
expected_keys = {BiddingAndAuctionServerKey("public-key", /*id=*/"00"),
base::unexpected("Failed to fetch public key.")};
auction_process_manager_->DeferOnLaunchedForHandles();
synchronous_fetch_ = false;
for (const auto& key : expected_keys) {
key_ = key;
SellerWorkletHelper seller_helper;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/
url::Origin::Create(GURL("https://origin.test/")),
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper.ProcessAssignedCallback(),
seller_helper.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
std::move(fetch_key_callback_).Run(key_);
auction_process_manager_->CallOnLaunchedWithPidForAllHandles();
seller_helper.WaitForWorklet();
EXPECT_TRUE(seller_helper.handle()->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet =
auction_process_manager_->WaitForSellerWorklet();
EXPECT_EQ(kDecisionLogicUrl, seller_worklet->script_source_url());
EXPECT_EQ(kTrustedSignalsUrl,
seller_worklet->trusted_scoring_signals_url());
EXPECT_TRUE(PublicKeyEvaluateHelper(seller_worklet->public_key(), key_));
}
}
TEST_F(AuctionWorkletManagerKVv2Test,
SingleSellerWorkletAsyncFetchedKeyAfterProcessAssigned) {
std::vector<base::expected<BiddingAndAuctionServerKey, std::string>>
expected_keys = {BiddingAndAuctionServerKey("public-key", /*id=*/"00"),
base::unexpected("Failed to fetch public key.")};
auction_process_manager_->DeferOnLaunchedForHandles();
synchronous_fetch_ = false;
for (const auto& key : expected_keys) {
key_ = key;
SellerWorkletHelper seller_helper;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/
url::Origin::Create(GURL("https://origin.test/")),
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper.ProcessAssignedCallback(),
seller_helper.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
auction_process_manager_->CallOnLaunchedWithPidForAllHandles();
std::move(fetch_key_callback_).Run(key_);
seller_helper.WaitForWorklet();
EXPECT_TRUE(seller_helper.handle()->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet =
auction_process_manager_->WaitForSellerWorklet();
EXPECT_EQ(kDecisionLogicUrl, seller_worklet->script_source_url());
EXPECT_EQ(kTrustedSignalsUrl,
seller_worklet->trusted_scoring_signals_url());
EXPECT_TRUE(PublicKeyEvaluateHelper(seller_worklet->public_key(), key_));
}
}
// Make sure that worklets are not reused when parameters don't match.
TEST_F(AuctionWorkletManagerKVv2Test,
DifferentSellerWorkletsWithDifferentCoordinators) {
// Load a KVv2 bidder worklet.
SellerWorkletHelper seller_helper1;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/
url::Origin::Create(GURL("https://a.test/")),
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper1.ProcessAssignedCallback(),
seller_helper1.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper1.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper1.WaitForWorklet();
EXPECT_TRUE(seller_helper1.handle()->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet1 =
auction_process_manager_->WaitForSellerWorklet();
EXPECT_EQ(kDecisionLogicUrl, seller_worklet1->script_source_url());
EXPECT_EQ(kTrustedSignalsUrl, seller_worklet1->trusted_scoring_signals_url());
EXPECT_TRUE(PublicKeyEvaluateHelper(seller_worklet1->public_key(), key_));
// Should only be one process.
EXPECT_EQ(1u, auction_process_manager_->GetSellerProcessCountForTesting());
EXPECT_THAT(seller_helper1.handle()->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction1));
// Load a KVv2 seller worklet with a different coorinator. A new worklet
// should be created, using the same process.
const GURL kDifferentDecisionLogicUrl =
GURL("https://origin.test/different_script");
SellerWorkletHelper seller_helper2;
auction_worklet_manager_->RequestSellerWorklet(
kAuction2, kDifferentDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/
url::Origin::Create(GURL("https://b.test/")),
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper2.ProcessAssignedCallback(),
seller_helper2.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper2.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper2.WaitForWorklet();
EXPECT_TRUE(seller_helper2.handle()->GetSellerWorklet());
EXPECT_NE(seller_helper1.handle()->GetSellerWorklet(),
seller_helper2.handle()->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet2 =
auction_process_manager_->WaitForSellerWorklet();
EXPECT_EQ(kDifferentDecisionLogicUrl, seller_worklet2->script_source_url());
EXPECT_EQ(kTrustedSignalsUrl, seller_worklet2->trusted_scoring_signals_url());
EXPECT_TRUE(PublicKeyEvaluateHelper(seller_worklet2->public_key(), key_));
// Should still only be one process.
EXPECT_EQ(1u, auction_process_manager_->GetSellerProcessCountForTesting());
EXPECT_THAT(seller_helper2.handle()->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction2));
}
// Test that requests with the same parameters reuse seller worklets.
TEST_F(AuctionWorkletManagerKVv2Test, ReuseSellerWorklet) {
// Load a KVv2 seller worklet.
SellerWorkletHelper seller_helper1;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt, coordinator_,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper1.ProcessAssignedCallback(),
seller_helper1.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper1.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper1.WaitForWorklet();
EXPECT_TRUE(seller_helper1.handle()->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet1 =
auction_process_manager_->WaitForSellerWorklet();
EXPECT_EQ(kDecisionLogicUrl, seller_worklet1->script_source_url());
EXPECT_EQ(kTrustedSignalsUrl, seller_worklet1->trusted_scoring_signals_url());
EXPECT_TRUE(PublicKeyEvaluateHelper(seller_worklet1->public_key(), key_));
seller_helper1.handle()->GetSellerWorklet()->SendPendingSignalsRequests();
seller_worklet1->WaitForSendPendingSignalsRequests(1);
// Should only be one process.
EXPECT_EQ(1u, auction_process_manager_->GetSellerProcessCountForTesting());
EXPECT_THAT(seller_helper1.handle()->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction1));
// Load a KVv2 seller worklet with the same parameters. The worklet should be
// reused.
SellerWorkletHelper seller_helper2;
auction_worklet_manager_->RequestSellerWorklet(
kAuction2, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt, coordinator_,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper2.ProcessAssignedCallback(),
seller_helper2.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper2.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper2.WaitForWorklet();
EXPECT_EQ(seller_helper1.handle()->GetSellerWorklet(),
seller_helper2.handle()->GetSellerWorklet());
EXPECT_FALSE(auction_process_manager_->HasSellerWorkletRequest());
seller_helper2.handle()->GetSellerWorklet()->SendPendingSignalsRequests();
seller_worklet1->WaitForSendPendingSignalsRequests(2);
// Should still only be one process.
EXPECT_EQ(1u, auction_process_manager_->GetSellerProcessCountForTesting());
// ... but used by both auctions.
EXPECT_THAT(seller_helper2.handle()->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction1, kAuction2));
// Close original handle. Worklet should still be alive, and so should its
// process.
seller_helper1.handle().reset();
EXPECT_EQ(1u, auction_process_manager_->GetSellerProcessCountForTesting());
// We should no longer attribute its work to the first auction, however.
EXPECT_THAT(seller_helper2.handle()->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction2));
// Load a KVv2 seller worklet with the same parameters. The worklet should
// still be reused again.
SellerWorkletHelper seller_helper3;
auction_worklet_manager_->RequestSellerWorklet(
kAuction3, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt, coordinator_,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper3.ProcessAssignedCallback(),
seller_helper3.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper3.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper3.WaitForWorklet();
EXPECT_EQ(seller_helper2.handle()->GetSellerWorklet(),
seller_helper3.handle()->GetSellerWorklet());
EXPECT_FALSE(auction_process_manager_->HasSellerWorkletRequest());
seller_helper3.handle()->GetSellerWorklet()->SendPendingSignalsRequests();
seller_worklet1->WaitForSendPendingSignalsRequests(3);
// Should still only be one process.
EXPECT_EQ(1u, auction_process_manager_->GetSellerProcessCountForTesting());
EXPECT_THAT(seller_helper3.handle()->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction2, kAuction3));
// Close both remaining handles.
seller_helper2.handle().reset();
seller_helper3.handle().reset();
// Process should be destroyed.
EXPECT_EQ(0u, auction_process_manager_->GetSellerProcessCountForTesting());
// Request another KVv2 seller worklet. A new SellerWorklet in a new process
// should be created.
SellerWorkletHelper seller_helper4;
auction_worklet_manager_->RequestSellerWorklet(
kAuction4, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt, coordinator_,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper4.ProcessAssignedCallback(),
seller_helper4.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper4.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper4.WaitForWorklet();
EXPECT_TRUE(seller_helper4.handle()->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet2 =
auction_process_manager_->WaitForSellerWorklet();
EXPECT_EQ(kDecisionLogicUrl, seller_worklet2->script_source_url());
EXPECT_EQ(kTrustedSignalsUrl, seller_worklet2->trusted_scoring_signals_url());
EXPECT_TRUE(PublicKeyEvaluateHelper(seller_worklet2->public_key(), key_));
seller_helper4.handle()->GetSellerWorklet()->SendPendingSignalsRequests();
seller_worklet2->WaitForSendPendingSignalsRequests(1);
EXPECT_EQ(1u, auction_process_manager_->GetSellerProcessCountForTesting());
EXPECT_THAT(seller_helper4.handle()->GetDevtoolsAuctionIdsForTesting(),
UnorderedElementsAre(kAuction4));
}
// Test a seller worklet can be correctly requested when coordinator is empty
// and `kFledgeTrustedSignalsKVv2Support` is enabled.
TEST_F(AuctionWorkletManagerKVv2Test, SellerWorkletWithoutCoordinator) {
SellerWorkletHelper seller_helper;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/std::nullopt,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper.ProcessAssignedCallback(),
seller_helper.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper.WaitForWorklet();
EXPECT_TRUE(seller_helper.handle()->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet =
auction_process_manager_->WaitForSellerWorklet();
EXPECT_EQ(kDecisionLogicUrl, seller_worklet->script_source_url());
EXPECT_EQ(kTrustedSignalsUrl, seller_worklet->trusted_scoring_signals_url());
EXPECT_TRUE(!seller_worklet->public_key());
}
// Test that when both kFledgeTrustedSignalsKVv2Support and
// kFledgeUseKVv2SignalsCache are enabled, worklets don't get
// TrustedSignalsPublicKeys.
TEST_F(AuctionWorkletManagerKVv2Test, KVv2SignalsCacheEnabled) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(features::kFledgeUseKVv2SignalsCache);
std::unique_ptr<AuctionWorkletManager::WorkletHandle> bidder_handle;
base::test::TestFuture<void> bidder_worklet_available;
auction_worklet_manager_->RequestBidderWorklet(
kAuction1, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"", coordinator_,
/*contextual_data=*/std::nullopt, bidder_worklet_available.GetCallback(),
NeverInvokedFatalErrorCallback(), bidder_handle,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(bidder_worklet_available.Wait());
EXPECT_TRUE(bidder_handle->GetBidderWorklet());
std::unique_ptr<MockBidderWorklet> bidder_worklet =
auction_process_manager_->WaitForBidderWorklet();
EXPECT_EQ(kDecisionLogicUrl, bidder_worklet->script_source_url());
EXPECT_EQ(kTrustedSignalsUrl, bidder_worklet->trusted_bidding_signals_url());
EXPECT_FALSE(bidder_worklet->public_key());
SellerWorkletHelper seller_helper;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt, coordinator_,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper.ProcessAssignedCallback(),
seller_helper.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper.WaitForWorklet();
EXPECT_TRUE(seller_helper.handle()->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet =
auction_process_manager_->WaitForSellerWorklet();
EXPECT_EQ(kDecisionLogicUrl, seller_worklet->script_source_url());
EXPECT_EQ(kTrustedSignalsUrl, seller_worklet->trusted_scoring_signals_url());
EXPECT_FALSE(seller_worklet->public_key());
}
TEST_F(AuctionWorkletManagerKVv2Test,
SellerWorkletTrustedScoringSignalsUrlAllowed) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(features::kFledgeUseKVv2SignalsCache);
const GURL kCrossOriginTrustedSignalsUrl =
GURL("https://other.origin.test/trusted_signals");
QuerySignalsUrlAllowedHelper helper1;
base::test::TestFuture<void> process_assigned1;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kCrossOriginTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/
url::Origin::Create(GURL("https://origin.test/")),
/*send_creative_scanning_metadata=*/std::nullopt,
process_assigned1.GetCallback(), helper1.GetCallback(),
NeverInvokedFatalErrorCallback(), helper1.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
// The process assigned callback should be invoked even before the seller
// worklet callback is run.
process_assigned1.Get();
auction_process_manager_->SellerWorkletLoaded(false);
EXPECT_FALSE(helper1.ScoringSignalsUrlAllowed());
// Destroy the process to prevent reuse.
helper1.handle().reset();
// Wait for the SellerWorklet request to be observed, and then destroy it, by
// throwing away the returned MockSellerWorklet.
auction_process_manager_->WaitForSellerWorklet();
QuerySignalsUrlAllowedHelper helper2;
base::test::TestFuture<void> process_assigned2;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kCrossOriginTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/
url::Origin::Create(GURL("https://origin.test/")),
/*send_creative_scanning_metadata=*/std::nullopt,
process_assigned2.GetCallback(), helper2.GetCallback(),
NeverInvokedFatalErrorCallback(), helper2.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
// The process assigned callback should be invoked even before the seller
// worklet callback is run.
process_assigned2.Get();
auction_process_manager_->SellerWorkletLoaded(true);
EXPECT_TRUE(helper2.ScoringSignalsUrlAllowed());
}
// Much like above, but process creation is async. This is accomplished by
// creating the max number of seller worklets before making the request with a
// cross-origin scoring signals request. The main purpose of this test is to
// test that the process assigned callback is correctly invoked in that case.
TEST_F(AuctionWorkletManagerKVv2Test,
SellerWorkletTrustedScoringSignalsUrlAllowedAsync) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(features::kFledgeUseKVv2SignalsCache);
const GURL kCrossOriginTrustedSignalsUrl =
GURL("https://other.origin.test/trusted_signals");
// Create `kMaxSellerProcesses` for origins other than https://origin.test.
//
// For proper destruction ordering, `handles` should be after
// `seller_worklets`. Otherwise, worklet destruction will result in invoking
// the `handles` fatal error callback, as if they had crashed.
std::list<std::unique_ptr<MockSellerWorklet>> seller_worklets;
std::list<std::unique_ptr<AuctionWorkletManager::WorkletHandle>> handles;
for (size_t i = 0; i < AuctionProcessManager::kMaxSellerProcesses; ++i) {
EXPECT_EQ(i, auction_process_manager_->GetSellerProcessCountForTesting());
GURL decision_logic_url =
GURL(base::StringPrintf("https://origin%zu.test", i));
SellerWorkletHelper seller_helper;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, decision_logic_url,
/*trusted_scoring_signals_url=*/std::nullopt,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/std::nullopt,
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper.ProcessAssignedCallback(),
seller_helper.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper.WaitForWorklet();
EXPECT_TRUE(seller_helper.handle()->GetSellerWorklet());
EXPECT_EQ(i + 1,
auction_process_manager_->GetSellerProcessCountForTesting());
std::unique_ptr<MockSellerWorklet> seller_worklet =
auction_process_manager_->WaitForSellerWorklet();
EXPECT_EQ(decision_logic_url, seller_worklet->script_source_url());
EXPECT_EQ(std::nullopt, seller_worklet->trusted_scoring_signals_url());
EXPECT_EQ(kTopWindowOrigin, seller_worklet->top_window_origin());
EXPECT_EQ(0, seller_worklet->num_send_pending_signals_requests_calls());
seller_helper.handle()->GetSellerWorklet()->SendPendingSignalsRequests();
seller_worklet->WaitForSendPendingSignalsRequests(1);
handles.emplace_back(std::move(seller_helper.handle()));
seller_worklets.emplace_back(std::move(seller_worklet));
}
QuerySignalsUrlAllowedHelper helper;
base::test::TestFuture<void> process_assigned;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kCrossOriginTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/
url::Origin::Create(GURL("https://origin.test/")),
/*send_creative_scanning_metadata=*/std::nullopt,
process_assigned.GetCallback(), helper.GetCallback(),
NeverInvokedFatalErrorCallback(), helper.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
// Destroy a single handle to unwedge the worklet service and trigger the
// process assignment callback.
handles.begin()->reset();
// The process assigned callback should be invoked even before the seller
// worklet callback is run.
process_assigned.Get();
auction_process_manager_->SellerWorkletLoaded(true);
EXPECT_TRUE(helper.ScoringSignalsUrlAllowed());
}
// Check the case that the process crashes while waiting for the
// ScoringSignalsUrlAllowed callback.
TEST_F(AuctionWorkletManagerKVv2Test,
ClosePipeWhileWaitingForSellerWorkletTrustedScoringSignalsUrlAllowed) {
const char kErrorText[] = "Goat teleportation error";
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(features::kFledgeUseKVv2SignalsCache);
const GURL kCrossOriginTrustedSignalsUrl =
GURL("https://other.origin.test/trusted_signals");
SellerWorkletHelper seller_helper1;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kCrossOriginTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/
url::Origin::Create(GURL("https://origin.test/")),
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper1.ProcessAssignedCallback(),
base::MakeExpectedNotRunClosure(FROM_HERE),
seller_helper1.FatalErrorCallback(), seller_helper1.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
// Wait for the SellerWorklet request to be observed, and then close it with
// an error.
auction_process_manager_->WaitForSellerWorklet()->ClosePipe(kErrorText);
seller_helper1.WaitForFatalError();
EXPECT_THAT(*seller_helper1.errors(), testing::ElementsAre(kErrorText));
EXPECT_EQ(AuctionWorkletManager::FatalErrorType::kScriptLoadFailed,
seller_helper1.fatal_error_type());
// Try to load the worklet again, and simulate a process crash. This should
// create a new worklet pipe instead of reusing the one from above.
SellerWorkletHelper seller_helper2;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kCrossOriginTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/
url::Origin::Create(GURL("https://origin.test/")),
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper2.ProcessAssignedCallback(),
base::MakeExpectedNotRunClosure(FROM_HERE),
seller_helper2.FatalErrorCallback(), seller_helper2.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
// Wait for the SellerWorklet request to be observed, and then destroy it, by
// throwing away the returned MockSellerWorklet.
auction_process_manager_->WaitForSellerWorklet();
seller_helper2.WaitForFatalError();
EXPECT_THAT(*seller_helper2.errors(),
testing::ElementsAre("https://origin.test/script crashed."));
EXPECT_EQ(AuctionWorkletManager::FatalErrorType::kWorkletCrash,
seller_helper2.fatal_error_type());
}
// Test the case where the same worklet is requested during a process
// assignment callback.
TEST_F(AuctionWorkletManagerKVv2Test, ProcessAssignedReentrancy) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(features::kFledgeUseKVv2SignalsCache);
const GURL kCrossOriginTrustedSignalsUrl =
GURL("https://other.origin.test/trusted_signals");
// Using cross-origin separates the process assignment callback from the
// worklet available one. This test covers both cases.
for (bool use_cross_origin_signals : {false, true}) {
SCOPED_TRACE(use_cross_origin_signals);
GURL trusted_signals_url = use_cross_origin_signals
? kCrossOriginTrustedSignalsUrl
: kTrustedSignalsUrl;
SellerWorkletHelper seller_helper1;
SellerWorkletHelper seller_helper2;
base::OnceClosure request_seller2_callback =
base::BindLambdaForTesting([&]() {
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, trusted_signals_url,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/
url::Origin::Create(GURL("https://origin.test/")),
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper2.ProcessAssignedCallback(),
seller_helper2.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper2.handle(),
auction_metrics_recorder_manager_
->CreateAuctionMetricsRecorder());
});
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, trusted_signals_url,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/
url::Origin::Create(GURL("https://origin.test/")),
/*send_creative_scanning_metadata=*/std::nullopt,
std::move(request_seller2_callback),
seller_helper1.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper1.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
if (use_cross_origin_signals) {
auction_process_manager_->SellerWorkletLoaded(true);
}
seller_helper1.WaitForWorklet();
seller_helper2.WaitForWorklet();
// Destroy the seller worklet.
auction_process_manager_->WaitForSellerWorklet();
}
}
class AuctionWorkletManagerKVv2DisableTest : public AuctionWorkletManagerTest {
public:
AuctionWorkletManagerKVv2DisableTest() {
feature_list_.InitWithFeatures(
/*enabled_features=*/{},
/*disabled_features=*/
{blink::features::kFledgeTrustedSignalsKVv2Support});
}
protected:
base::test::ScopedFeatureList feature_list_;
};
// Test a bidder worklet can be correctly requested with a valid coordinator
// when `kFledgeTrustedSignalsKVv2Support` is disabled. It depends on
// `GetBiddingAndAuctionServerKey()` to cause a crash if it is called
// unexpectedly.
TEST_F(AuctionWorkletManagerKVv2DisableTest,
BidderWorkletWithKVv2FeatureDisabled) {
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle;
base::test::TestFuture<void> worklet_available;
auction_worklet_manager_->RequestBidderWorklet(
kAuction1, kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
/*needs_cors_for_additional_bid=*/false,
/*experiment_group_id=*/std::nullopt,
/*trusted_bidding_signals_slot_size_param=*/"",
/*trusted_bidding_signals_coordinator=*/
url::Origin::Create(GURL("https://origin.test/")),
/*contextual_data=*/std::nullopt, worklet_available.GetCallback(),
NeverInvokedFatalErrorCallback(), handle,
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
ASSERT_TRUE(worklet_available.Wait());
EXPECT_TRUE(handle->GetBidderWorklet());
std::unique_ptr<MockBidderWorklet> bidder_worklet =
auction_process_manager_->WaitForBidderWorklet();
EXPECT_EQ(kDecisionLogicUrl, bidder_worklet->script_source_url());
EXPECT_EQ(kTrustedSignalsUrl, bidder_worklet->trusted_bidding_signals_url());
EXPECT_TRUE(!bidder_worklet->public_key());
}
// Test a seller worklet can be correctly requested with a valid coordinator
// when `kFledgeTrustedSignalsKVv2Support` is disabled. It depends on
// `GetBiddingAndAuctionServerKey()` to cause a crash if it is called
// unexpectedly.
TEST_F(AuctionWorkletManagerKVv2DisableTest,
SellerWorkletWithKVv2FeatureDisabled) {
SellerWorkletHelper seller_helper;
auction_worklet_manager_->RequestSellerWorklet(
kAuction1, kDecisionLogicUrl, kTrustedSignalsUrl,
/*experiment_group_id=*/std::nullopt,
/*trusted_scoring_signals_coordinator=*/
url::Origin::Create(GURL("https://origin.test/")),
/*send_creative_scanning_metadata=*/std::nullopt,
seller_helper.ProcessAssignedCallback(),
seller_helper.WorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), seller_helper.handle(),
auction_metrics_recorder_manager_->CreateAuctionMetricsRecorder());
seller_helper.WaitForWorklet();
EXPECT_TRUE(seller_helper.handle()->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet =
auction_process_manager_->WaitForSellerWorklet();
EXPECT_EQ(kDecisionLogicUrl, seller_worklet->script_source_url());
EXPECT_EQ(kTrustedSignalsUrl, seller_worklet->trusted_scoring_signals_url());
}
} // namespace
} // namespace content