blob: 9bf28c467b37bd2bb87c58edb92db8f327b5d18d [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/interest_group/auction_worklet_manager.h"
#include <list>
#include <memory>
#include <string>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/check.h"
#include "base/cxx17_backports.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "content/browser/interest_group/auction_process_manager.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/seller_worklet.mojom.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/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/abseil-cpp/absl/types/optional.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace content {
namespace {
base::OnceClosure NeverInvokedWorkletAvailableCallback() {
return base::BindOnce([]() { ADD_FAILURE() << "This should not be called"; });
}
AuctionWorkletManager::FatalErrorCallback NeverInvokedFatalErrorCallback() {
return base::BindOnce(
[](AuctionWorkletManager::FatalErrorType fatal_error_type,
const std::vector<std::string>& errors) {
ADD_FAILURE() << "This should not be called";
});
}
// 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_;
};
// 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 absl::optional<GURL>& wasm_url,
const absl::optional<GURL>& trusted_bidding_signals_url,
const url::Origin& top_window_origin)
: url_loader_factory_(std::move(pending_url_loader_factory)),
script_source_url_(script_source_url),
wasm_url_(wasm_url),
trusted_bidding_signals_url_(trusted_bidding_signals_url),
top_window_origin_(top_window_origin),
receiver_(this, std::move(pending_receiver)) {}
MockBidderWorklet(const MockBidderWorklet&) = delete;
const MockBidderWorklet& operator=(const MockBidderWorklet&) = delete;
~MockBidderWorklet() 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 GenerateBid(
auction_worklet::mojom::BidderWorkletNonSharedParamsPtr
bidder_worklet_non_shared_params,
const absl::optional<std::string>& auction_signals_json,
const absl::optional<std::string>& per_buyer_signals_json,
const url::Origin& seller_origin,
auction_worklet::mojom::BiddingBrowserSignalsPtr bidding_browser_signals,
base::Time auction_start_time,
GenerateBidCallback generate_bid_callback) 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(const std::string& interest_group_name,
const absl::optional<std::string>& auction_signals_json,
const absl::optional<std::string>& per_buyer_signals_json,
const std::string& seller_signals_json,
const GURL& browser_signal_render_url,
double browser_signal_bid,
const url::Origin& browser_signal_seller_origin,
ReportWinCallback report_win_callback) override {
NOTREACHED();
}
void ConnectDevToolsAgent(
mojo::PendingReceiver<blink::mojom::DevToolsAgent> agent) 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 absl::optional<GURL>& wasm_url() const { return wasm_url_; }
const absl::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 absl::optional<GURL> wasm_url_;
const absl::optional<GURL> trusted_bidding_signals_url_;
const url::Origin top_window_origin_;
// 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 absl::optional<GURL>& trusted_scoring_signals_url,
const url::Origin& top_window_origin)
: 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),
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,
blink::mojom::AuctionAdConfigNonSharedParamsPtr
auction_ad_config_non_shared_params,
const url::Origin& browser_signal_interest_group_owner,
const GURL& browser_signal_render_url,
const std::vector<GURL>& browser_signal_ad_components,
uint32_t browser_signal_bidding_duration_msecs,
ScoreAdCallback score_ad_callback) 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(blink::mojom::AuctionAdConfigNonSharedParamsPtr
auction_ad_config_non_shared_params,
const url::Origin& browser_signal_interest_group_owner,
const GURL& browser_signal_render_url,
double browser_signal_bid,
double browser_signal_desirability,
ReportResultCallback report_result_callback) override {
NOTREACHED();
}
void ConnectDevToolsAgent(
mojo::PendingReceiver<blink::mojom::DevToolsAgent> agent) 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 absl::optional<GURL>& trusted_scoring_signals_url() const {
return trusted_scoring_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 absl::optional<GURL> trusted_scoring_signals_url_;
const url::Origin top_window_origin_;
// 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 AuctionProcessManager,
public auction_worklet::mojom::AuctionWorkletService {
public:
MockAuctionProcessManager() = default;
~MockAuctionProcessManager() override = default;
// AuctionProcessManager implementation:
void LaunchProcess(
mojo::PendingReceiver<auction_worklet::mojom::AuctionWorkletService>
auction_worklet_service_receiver,
const std::string& display_name) override {
mojo::ReceiverId receiver_id =
receiver_set_.Add(this, std::move(auction_worklet_service_receiver));
// Have to flush the receiver set, so that any closed receivers are removed,
// before searching for duplicate process names.
receiver_set_.FlushForTesting();
// Each receiver should get a unique display name. This check serves to help
// ensure that processes are correctly reused.
EXPECT_EQ(0u, receiver_display_name_map_.count(receiver_id));
for (auto receiver : receiver_display_name_map_) {
// Ignore closed receivers. ReportWin() will result in re-loading a
// worklet, after closing the original worklet, which may require
// re-creating the AuctionWorkletService.
if (receiver_set_.HasReceiver(receiver.first))
EXPECT_NE(receiver.second, display_name);
}
receiver_display_name_map_[receiver_id] = display_name;
}
// auction_worklet::mojom::AuctionWorkletService implementation:
void LoadBidderWorklet(
mojo::PendingReceiver<auction_worklet::mojom::BidderWorklet>
bidder_worklet_receiver,
bool pause_for_debugger_on_start,
mojo::PendingRemote<network::mojom::URLLoaderFactory>
pending_url_loader_factory,
const GURL& script_source_url,
const absl::optional<GURL>& bidding_wasm_helper_url,
const absl::optional<GURL>& trusted_bidding_signals_url,
const url::Origin& top_window_origin) override {
DCHECK(!bidder_worklet_);
// Make sure this request came over the right pipe.
EXPECT_EQ(receiver_display_name_map_[receiver_set_.current_receiver()],
ComputeDisplayName(AuctionProcessManager::WorkletType::kBidder,
url::Origin::Create(script_source_url)));
bidder_worklet_ = std::make_unique<MockBidderWorklet>(
std::move(bidder_worklet_receiver),
std::move(pending_url_loader_factory), script_source_url,
bidding_wasm_helper_url, trusted_bidding_signals_url,
top_window_origin);
if (bidder_worklet_run_loop_)
bidder_worklet_run_loop_->Quit();
}
void LoadSellerWorklet(
mojo::PendingReceiver<auction_worklet::mojom::SellerWorklet>
seller_worklet_receiver,
bool should_pause_on_start,
mojo::PendingRemote<network::mojom::URLLoaderFactory>
pending_url_loader_factory,
const GURL& script_source_url,
const absl::optional<GURL>& trusted_scoring_signals_url,
const url::Origin& top_window_origin) override {
DCHECK(!seller_worklet_);
// Make sure this request came over the right pipe.
EXPECT_EQ(receiver_display_name_map_[receiver_set_.current_receiver()],
ComputeDisplayName(AuctionProcessManager::WorkletType::kSeller,
url::Origin::Create(script_source_url)));
seller_worklet_ = std::make_unique<MockSellerWorklet>(
std::move(seller_worklet_receiver),
std::move(pending_url_loader_factory), script_source_url,
trusted_scoring_signals_url, top_window_origin);
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;
}
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_;
// 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 so that destroying `this` while there's a pending
// callback over the pipe will not DCHECK.
mojo::ReceiverSet<auction_worklet::mojom::AuctionWorkletService>
receiver_set_;
};
class AuctionWorkletManagerTest : public testing::Test,
public AuctionWorkletManager::Delegate {
public:
AuctionWorkletManagerTest()
: auction_worklet_manager_(&auction_process_manager_,
kTopWindowOrigin,
kFrameOrigin,
this) {
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());
}
// AuctionWorkletManager::Delegate implementation:
network::mojom::URLLoaderFactory* GetFrameURLLoaderFactory() override {
return &url_loader_factory_;
}
network::mojom::URLLoaderFactory* GetTrustedURLLoaderFactory() override {
return &url_loader_factory_;
}
RenderFrameHostImpl* GetFrame() override { return nullptr; }
network::mojom::ClientSecurityStatePtr GetClientSecurityState() override {
return network::mojom::ClientSecurityState::New();
}
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");
base::test::TaskEnvironment task_environment_;
std::string bad_message_;
network::TestURLLoaderFactory url_loader_factory_;
MockAuctionProcessManager auction_process_manager_;
AuctionWorkletManager auction_worklet_manager_;
};
TEST_F(AuctionWorkletManagerTest, SingleBidderWorklet) {
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle;
ASSERT_TRUE(auction_worklet_manager_.RequestBidderWorklet(
kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
NeverInvokedWorkletAvailableCallback(), NeverInvokedFatalErrorCallback(),
handle));
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);
}
TEST_F(AuctionWorkletManagerTest, SingleSellerWorklet) {
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle;
ASSERT_TRUE(auction_worklet_manager_.RequestSellerWorklet(
kDecisionLogicUrl, kTrustedSignalsUrl,
NeverInvokedWorkletAvailableCallback(), NeverInvokedFatalErrorCallback(),
handle));
EXPECT_TRUE(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());
handle->GetSellerWorklet()->SendPendingSignalsRequests();
seller_worklet->WaitForSendPendingSignalsRequests(1);
}
// Test the case where a bidder worklet request 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;
ASSERT_TRUE(auction_worklet_manager_.RequestBidderWorklet(
decision_logic_url, /*wasm_url=*/absl::nullopt,
/*trusted_bidding_signals_url=*/absl::nullopt,
NeverInvokedWorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), handle));
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(absl::nullopt, bidder_worklet->wasm_url());
EXPECT_EQ(absl::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 complete
// synchronously, since there's no available process quota.
base::RunLoop worklet_available_loop;
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle;
ASSERT_FALSE(auction_worklet_manager_.RequestBidderWorklet(
kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
worklet_available_loop.QuitClosure(), NeverInvokedFatalErrorCallback(),
handle));
EXPECT_EQ(AuctionProcessManager::kMaxBidderProcesses,
auction_process_manager_.GetBidderProcessCountForTesting());
EXPECT_FALSE(worklet_available_loop.AnyQuitCalled());
// Freeing a WorkletHandle should result in a new process being
// available, and the most recent request getting a new worklet.
handles.pop_front();
worklet_available_loop.Run();
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 seller worklet request 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));
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle;
ASSERT_TRUE(auction_worklet_manager_.RequestSellerWorklet(
decision_logic_url, /*trusted_scoring_signals_url=*/absl::nullopt,
NeverInvokedWorkletAvailableCallback(),
NeverInvokedFatalErrorCallback(), handle));
EXPECT_TRUE(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(absl::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());
handle->GetSellerWorklet()->SendPendingSignalsRequests();
seller_worklet->WaitForSendPendingSignalsRequests(1);
handles.emplace_back(std::move(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 complete
// synchronously, since there's no available process quota.
base::RunLoop worklet_available_loop;
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle;
ASSERT_FALSE(auction_worklet_manager_.RequestSellerWorklet(
kDecisionLogicUrl, kTrustedSignalsUrl,
worklet_available_loop.QuitClosure(), NeverInvokedFatalErrorCallback(),
handle));
EXPECT_EQ(AuctionProcessManager::kMaxSellerProcesses,
auction_process_manager_.GetSellerProcessCountForTesting());
EXPECT_FALSE(worklet_available_loop.AnyQuitCalled());
// Freeing a WorkletHandle should result in a new process being
// available, and the most recent request getting a new worklet.
handles.pop_front();
worklet_available_loop.Run();
EXPECT_TRUE(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());
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;
ASSERT_TRUE(auction_worklet_manager_.RequestBidderWorklet(
kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
NeverInvokedWorkletAvailableCallback(), NeverInvokedFatalErrorCallback(),
handle1));
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());
// Load a bidder worklet with the same parameters. The worklet should be
// reused.
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle2;
ASSERT_TRUE(auction_worklet_manager_.RequestBidderWorklet(
kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
NeverInvokedWorkletAvailableCallback(), NeverInvokedFatalErrorCallback(),
handle2));
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());
// Close original handle. Worklet should still be alive, and so should its
// process.
handle1.reset();
EXPECT_EQ(1u, auction_process_manager_.GetBidderProcessCountForTesting());
// Load a bidder worklet with the same parameters. The worklet should still be
// reused again.
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle3;
ASSERT_TRUE(auction_worklet_manager_.RequestBidderWorklet(
kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
NeverInvokedWorkletAvailableCallback(), NeverInvokedFatalErrorCallback(),
handle3));
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());
// 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.
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle4;
ASSERT_TRUE(auction_worklet_manager_.RequestBidderWorklet(
kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
NeverInvokedWorkletAvailableCallback(), NeverInvokedFatalErrorCallback(),
handle4));
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());
}
// Test that requests with the same parameters reuse seller worklets.
TEST_F(AuctionWorkletManagerTest, ReuseSellerWorklet) {
// Load a seller worklet.
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle1;
ASSERT_TRUE(auction_worklet_manager_.RequestSellerWorklet(
kDecisionLogicUrl, kTrustedSignalsUrl,
NeverInvokedWorkletAvailableCallback(), NeverInvokedFatalErrorCallback(),
handle1));
EXPECT_TRUE(handle1->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());
handle1->GetSellerWorklet()->SendPendingSignalsRequests();
seller_worklet1->WaitForSendPendingSignalsRequests(1);
// Should only be one process.
EXPECT_EQ(1u, auction_process_manager_.GetSellerProcessCountForTesting());
// Load a seller worklet with the same parameters. The worklet should be
// reused.
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle2;
ASSERT_TRUE(auction_worklet_manager_.RequestSellerWorklet(
kDecisionLogicUrl, kTrustedSignalsUrl,
NeverInvokedWorkletAvailableCallback(), NeverInvokedFatalErrorCallback(),
handle2));
EXPECT_EQ(handle1->GetSellerWorklet(), handle2->GetSellerWorklet());
EXPECT_FALSE(auction_process_manager_.HasSellerWorkletRequest());
handle2->GetSellerWorklet()->SendPendingSignalsRequests();
seller_worklet1->WaitForSendPendingSignalsRequests(2);
// Should still only be one process.
EXPECT_EQ(1u, auction_process_manager_.GetSellerProcessCountForTesting());
// Close original handle. Worklet should still be alive, and so should its
// process.
handle1.reset();
EXPECT_EQ(1u, auction_process_manager_.GetSellerProcessCountForTesting());
// Load a seller worklet with the same parameters. The worklet should still be
// reused again.
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle3;
ASSERT_TRUE(auction_worklet_manager_.RequestSellerWorklet(
kDecisionLogicUrl, kTrustedSignalsUrl,
NeverInvokedWorkletAvailableCallback(), NeverInvokedFatalErrorCallback(),
handle3));
EXPECT_EQ(handle2->GetSellerWorklet(), handle3->GetSellerWorklet());
EXPECT_FALSE(auction_process_manager_.HasSellerWorkletRequest());
handle3->GetSellerWorklet()->SendPendingSignalsRequests();
seller_worklet1->WaitForSendPendingSignalsRequests(3);
// Should still only be one process.
EXPECT_EQ(1u, auction_process_manager_.GetSellerProcessCountForTesting());
// Close both remaining handles.
handle2.reset();
handle3.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.
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle4;
ASSERT_TRUE(auction_worklet_manager_.RequestSellerWorklet(
kDecisionLogicUrl, kTrustedSignalsUrl,
NeverInvokedWorkletAvailableCallback(), NeverInvokedFatalErrorCallback(),
handle4));
EXPECT_TRUE(handle4->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());
handle4->GetSellerWorklet()->SendPendingSignalsRequests();
seller_worklet2->WaitForSendPendingSignalsRequests(1);
EXPECT_EQ(1u, auction_process_manager_.GetSellerProcessCountForTesting());
}
// 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;
ASSERT_TRUE(auction_worklet_manager_.RequestBidderWorklet(
kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
NeverInvokedWorkletAvailableCallback(), NeverInvokedFatalErrorCallback(),
handle1));
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());
// 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;
ASSERT_TRUE(auction_worklet_manager_.RequestBidderWorklet(
kDifferentDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
NeverInvokedWorkletAvailableCallback(), NeverInvokedFatalErrorCallback(),
handle2));
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());
// 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;
ASSERT_TRUE(auction_worklet_manager_.RequestBidderWorklet(
kDecisionLogicUrl, kWasmUrl,
/*trusted_bidding_signals_url=*/absl::nullopt,
NeverInvokedWorkletAvailableCallback(), NeverInvokedFatalErrorCallback(),
handle3));
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(absl::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());
// 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;
ASSERT_TRUE(auction_worklet_manager_.RequestBidderWorklet(
kDecisionLogicUrl, /*wasm_url=*/absl::nullopt, kTrustedSignalsUrl,
NeverInvokedWorkletAvailableCallback(), NeverInvokedFatalErrorCallback(),
handle4));
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(absl::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());
}
// Make sure that worklets are not reused when parameters don't match.
TEST_F(AuctionWorkletManagerTest, DifferentSellerWorklets) {
// Load a seller worklet.
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle1;
ASSERT_TRUE(auction_worklet_manager_.RequestSellerWorklet(
kDecisionLogicUrl, kTrustedSignalsUrl,
NeverInvokedWorkletAvailableCallback(), NeverInvokedFatalErrorCallback(),
handle1));
EXPECT_TRUE(handle1->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");
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle2;
ASSERT_TRUE(auction_worklet_manager_.RequestSellerWorklet(
kDifferentDecisionLogicUrl, kTrustedSignalsUrl,
NeverInvokedWorkletAvailableCallback(), NeverInvokedFatalErrorCallback(),
handle2));
EXPECT_TRUE(handle1->GetSellerWorklet());
EXPECT_NE(handle1->GetSellerWorklet(), handle2->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.
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle3;
ASSERT_TRUE(auction_worklet_manager_.RequestSellerWorklet(
kDecisionLogicUrl, /*trusted_scoring_signals_url=*/absl::nullopt,
NeverInvokedWorkletAvailableCallback(), NeverInvokedFatalErrorCallback(),
handle3));
EXPECT_TRUE(handle3->GetSellerWorklet());
EXPECT_NE(handle1->GetSellerWorklet(), handle3->GetSellerWorklet());
EXPECT_NE(handle2->GetSellerWorklet(), handle3->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet3 =
auction_process_manager_.WaitForSellerWorklet();
EXPECT_EQ(kDecisionLogicUrl, seller_worklet3->script_source_url());
EXPECT_EQ(absl::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_F(AuctionWorkletManagerTest, BidderWorkletLoadError) {
const char kErrorText[] = "Goat teleportation error";
// Load a bidder worklet.
FatalLoadErrorHelper load_error_helper;
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle;
ASSERT_TRUE(auction_worklet_manager_.RequestBidderWorklet(
kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
NeverInvokedWorkletAvailableCallback(), load_error_helper.Callback(),
handle));
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;
ASSERT_TRUE(auction_worklet_manager_.RequestBidderWorklet(
kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
NeverInvokedWorkletAvailableCallback(), load_error_helper.Callback(),
handle2));
EXPECT_TRUE(handle2->GetBidderWorklet());
EXPECT_NE(handle->GetBidderWorklet(), handle2->GetBidderWorklet());
std::unique_ptr<MockBidderWorklet> bidder_worklet2 =
auction_process_manager_.WaitForBidderWorklet();
}
TEST_F(AuctionWorkletManagerTest, SellerWorkletLoadError) {
const char kErrorText[] = "Goat teleportation error";
// Load a seller worklet.
FatalLoadErrorHelper load_error_helper;
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle;
ASSERT_TRUE(auction_worklet_manager_.RequestSellerWorklet(
kDecisionLogicUrl, kTrustedSignalsUrl,
NeverInvokedWorkletAvailableCallback(), load_error_helper.Callback(),
handle));
EXPECT_TRUE(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.
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->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.
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle2;
ASSERT_TRUE(auction_worklet_manager_.RequestSellerWorklet(
kDecisionLogicUrl, kTrustedSignalsUrl,
NeverInvokedWorkletAvailableCallback(), load_error_helper.Callback(),
handle2));
EXPECT_TRUE(handle2->GetSellerWorklet());
EXPECT_NE(handle->GetSellerWorklet(), handle2->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;
ASSERT_TRUE(auction_worklet_manager_.RequestBidderWorklet(
kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
NeverInvokedWorkletAvailableCallback(), load_error_helper.Callback(),
handle));
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;
ASSERT_TRUE(auction_worklet_manager_.RequestBidderWorklet(
kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
NeverInvokedWorkletAvailableCallback(), load_error_helper.Callback(),
handle2));
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.
FatalLoadErrorHelper load_error_helper;
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle;
ASSERT_TRUE(auction_worklet_manager_.RequestSellerWorklet(
kDecisionLogicUrl, kTrustedSignalsUrl,
NeverInvokedWorkletAvailableCallback(), load_error_helper.Callback(),
handle));
EXPECT_TRUE(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.
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->GetSellerWorklet()->SendPendingSignalsRequests();
task_environment_.RunUntilIdle();
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.
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle2;
ASSERT_TRUE(auction_worklet_manager_.RequestSellerWorklet(
kDecisionLogicUrl, kTrustedSignalsUrl,
NeverInvokedWorkletAvailableCallback(), load_error_helper.Callback(),
handle2));
EXPECT_TRUE(handle2->GetSellerWorklet());
EXPECT_NE(handle->GetSellerWorklet(), handle2->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet2 =
auction_process_manager_.WaitForSellerWorklet();
}
// 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;
ASSERT_TRUE(auction_worklet_manager_.RequestBidderWorklet(
kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
NeverInvokedWorkletAvailableCallback(),
base::BindLambdaForTesting(
[&](AuctionWorkletManager::FatalErrorType fatal_error_type,
const std::vector<std::string>& errors) {
handle.reset();
run_loop.Quit();
}),
handle));
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 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;
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle;
ASSERT_TRUE(auction_worklet_manager_.RequestSellerWorklet(
kDecisionLogicUrl, kTrustedSignalsUrl,
NeverInvokedWorkletAvailableCallback(),
base::BindLambdaForTesting(
[&](AuctionWorkletManager::FatalErrorType fatal_error_type,
const std::vector<std::string>& errors) {
handle.reset();
run_loop.Quit();
}),
handle));
EXPECT_TRUE(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;
ASSERT_TRUE(auction_worklet_manager_.RequestBidderWorklet(
kDecisionLogicUrl, kWasmUrl, kTrustedSignalsUrl,
NeverInvokedWorkletAvailableCallback(), NeverInvokedFatalErrorCallback(),
handle));
EXPECT_TRUE(handle->GetBidderWorklet());
std::unique_ptr<MockBidderWorklet> bidder_worklet =
auction_process_manager_.WaitForBidderWorklet();
const struct {
GURL url;
const char* mime_type;
} kAllowedUrls[] = {
{kDecisionLogicUrl, "application/javascript"},
{kWasmUrl, "application/wasm"},
{GURL("https://origin.test/"
"trusted_signals?hostname=top.window.origin.test&render_urls=not_"
"validated"),
"application/json"},
};
for (size_t i = 0; i < base::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(), 0 /*request_id=*/,
/*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 + 1, url_loader_factory_.pending_requests()->size());
EXPECT_EQ(kAllowedUrls[i].url,
(*url_loader_factory_.pending_requests())[i].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(base::size(kAllowedUrls),
url_loader_factory_.pending_requests()->size());
EXPECT_EQ("Unexpected request", TakeBadMessage());
}
// Minimal test that seller worklets' AuctionURLLoaderFactoryProxies are
// correctly configured.
TEST_F(AuctionWorkletManagerTest, SellerWorkletUrlRequestProtection) {
std::unique_ptr<AuctionWorkletManager::WorkletHandle> handle;
ASSERT_TRUE(auction_worklet_manager_.RequestSellerWorklet(
kDecisionLogicUrl, kTrustedSignalsUrl,
NeverInvokedWorkletAvailableCallback(), NeverInvokedFatalErrorCallback(),
handle));
EXPECT_TRUE(handle->GetSellerWorklet());
std::unique_ptr<MockSellerWorklet> seller_worklet =
auction_process_manager_.WaitForSellerWorklet();
const struct {
GURL url;
const char* mime_type;
} kAllowedUrls[] = {
{kDecisionLogicUrl, "application/javascript"},
{GURL("https://origin.test/"
"trusted_signals?hostname=top.window.origin.test&render_urls=not_"
"validated"),
"application/json"},
};
for (size_t i = 0; i < base::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(), 0 /*request_id=*/,
/*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 + 1, url_loader_factory_.pending_requests()->size());
EXPECT_EQ(kAllowedUrls[i].url,
(*url_loader_factory_.pending_requests())[i].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(base::size(kAllowedUrls),
url_loader_factory_.pending_requests()->size());
EXPECT_EQ("Unexpected request", TakeBadMessage());
}
} // namespace
} // namespace content