blob: 8f0ec0811213d82d5e5700e744774063e6dfa089 [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_process_manager.h"
#include <cstddef>
#include <list>
#include <memory>
#include <optional>
#include <string>
#include <tuple>
#include <vector>
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/notreached.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/run_until.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "base/types/expected.h"
#include "content/browser/interest_group/bidding_and_auction_server_key_fetcher.h"
#include "content/browser/interest_group/data_decoder_manager.h"
#include "content/browser/interest_group/interest_group_features.h"
#include "content/browser/interest_group/trusted_signals_cache_impl.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/browser/service_worker/service_worker_process_manager.h"
#include "content/common/features.h"
#include "content/public/browser/frame_tree_node_id.h"
#include "content/public/browser/site_instance.h"
#include "content/public/browser/site_isolation_mode.h"
#include "content/public/browser/site_isolation_policy.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/mock_render_process_host.h"
#include "content/public/test/test_browser_context.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 "content/test/test_content_browser_client.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/system/message_pipe.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/mojom/ip_address_space.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace content {
namespace {
using RequestWorkletServiceOutcome =
AuctionProcessManager::RequestWorkletServiceOutcome;
// Alias constants to improve readability.
const size_t kMaxSellerProcesses = AuctionProcessManager::kMaxSellerProcesses;
const size_t kMaxBidderProcesses = AuctionProcessManager::kMaxBidderProcesses;
// For tests that make sure the TrustedSignalsCache is wired up correctly, the
// cache is configured to fail with this error, if it successfully receives a
// request.
constexpr std::string_view kCacheMessage =
"The cache failed with the right error.";
base::OnceClosure NeverInvokedClosure() {
return base::BindOnce([]() { ADD_FAILURE() << "This should not be called"; });
}
template <class AuctionManagerBaseType>
class TestAuctionProcessManager
: public AuctionManagerBaseType,
public auction_worklet::mojom::AuctionWorkletService {
public:
// Per-AuctionWorkletService receiver pipe information. Public only so inlined
// public methods can use it.
struct ReceiverContext {
explicit ReceiverContext(
base::WeakPtr<AuctionProcessManager::WorkletProcess> worklet_process)
: worklet_process(std::move(worklet_process)) {}
// The associated worklet process, which may have been destroyed.
base::WeakPtr<AuctionProcessManager::WorkletProcess> worklet_process;
// The TrustedSignalsCache Mojo pipe, received from
// SetTrustedSignalsCache(). There should only be a single call to that
// method, so should only be one such pipe.
mojo::Remote<auction_worklet::mojom::TrustedSignalsCache> cache_remote;
// If non-null, its Quit() method will be invoked when `cache_remote` is
// populated.
raw_ptr<base::RunLoop> wait_for_cache_remote_run_loop;
};
explicit TestAuctionProcessManager(
TrustedSignalsCacheImpl* trusted_signals_cache)
: AuctionManagerBaseType(trusted_signals_cache) {}
TestAuctionProcessManager(const TestAuctionProcessManager&) = delete;
const TestAuctionProcessManager& operator=(const TestAuctionProcessManager&) =
delete;
~TestAuctionProcessManager() override = default;
void SetTrustedSignalsCache(
mojo::PendingRemote<auction_worklet::mojom::TrustedSignalsCache>
trusted_signals_cache) override {
ReceiverContext& context = receiver_set_.current_context();
// This should only be called once per pipe.
ASSERT_FALSE(context.cache_remote);
context.cache_remote.Bind(std::move(trusted_signals_cache));
if (context.wait_for_cache_remote_run_loop) {
context.wait_for_cache_remote_run_loop->Quit();
context.wait_for_cache_remote_run_loop = nullptr;
}
}
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_id,
auction_worklet::mojom::TrustedSignalsPublicKeyPtr public_key) override {
NOTREACHED();
}
void LoadSellerWorklet(
mojo::PendingReceiver<auction_worklet::mojom::SellerWorklet>
seller_worklet,
std::vector<
mojo::PendingRemote<auction_worklet::mojom::AuctionSharedStorageHost>>
shared_storage_hosts,
bool should_pause_on_start,
mojo::PendingRemote<network::mojom::URLLoaderFactory> 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_id,
std::optional<bool> send_creative_scanning_metadata,
auction_worklet::mojom::TrustedSignalsPublicKeyPtr public_key,
mojo::PendingRemote<auction_worklet::mojom::LoadSellerWorkletClient>
trusted_signals_url_allowed) override {
NOTREACHED();
}
void ClosePipes() {
receiver_set_.Clear();
// No wait to flush a closed pipe from the end that was closed. Run until
// the other side has noticed the pipe was closed instead.
base::RunLoop().RunUntilIdle();
}
// Get the index that this process was created (e.g. if it was the first
// created process, return 0). Returns -1 and has an EXPECT failure if the
// process wasn't found or has since been destroyed. `handle` must have been
// assigned a WorkletProcess.
size_t ProcessCreationOrder(
const AuctionProcessManager::ProcessHandle& handle) {
EXPECT_TRUE(handle.worklet_process_for_testing());
for (size_t i = 0u; i < launched_processes_.size(); ++i) {
if (handle.worklet_process_for_testing() ==
launched_processes_[i].get()) {
return i;
}
}
ADD_FAILURE() << "Worklet process not found";
return -1;
}
// Simulate readiness of the creation_index'th launched process.
// This function will return fail if there is no ith process.
// Do not call this function after any process in the test has been
// destroyed.
//
// Only works for the dedicated process case. In the non-dedicated case, these
// should go through the MockRenderProcessHost.
void SimulateReadyProcess(size_t creation_index) {
if constexpr (std::is_same<AuctionManagerBaseType,
InRendererAuctionProcessManager>::value) {
// This should not be used in the InRendererAuctionProcessManager case.
NOTREACHED();
return;
}
if (launched_processes_.size() <= creation_index) {
ADD_FAILURE() << "Process unexpectedly doesn't exist: " << creation_index;
return;
}
launched_processes_[creation_index]->OnLaunchedWithProcess(
base::Process::Current());
return;
}
// Checks that `handle` has no cache remote. Calls RunUntilIdle() to make sure
// there are no pending calls to pass in a cache remote.
void ExpectNoCacheRemote(const AuctionProcessManager::ProcessHandle& handle) {
// `handle` must be assigned a process.
ASSERT_TRUE(handle.worklet_process_for_testing());
base::RunLoop().RunUntilIdle();
// `handle` must still be assigned a process.
ASSERT_TRUE(handle.worklet_process_for_testing());
ReceiverContext* context = FindContextForProcess(handle);
ASSERT_TRUE(context);
EXPECT_FALSE(context->cache_remote);
}
// Waits until a non-zero number of TrustedSignalsCache PendingRemotes have
// been received, and then returns them all. Because of the complexity of
// figuring out which remote come from which worklet pipe, they aren't tracked
// by which WorkletProcess's receiver they were received by.
auction_worklet::mojom::TrustedSignalsCache* WaitForCacheRemote(
const AuctionProcessManager::ProcessHandle& handle) {
// `handle` must be assigned a process.
CHECK(handle.worklet_process_for_testing());
ReceiverContext* context = FindContextForProcess(handle);
if (!context) {
return nullptr;
}
if (!context->cache_remote) {
base::RunLoop run_loop;
context->wait_for_cache_remote_run_loop = &run_loop;
// Null out context, since the pointer may be invalidated while spinning
// the message loop.
context = nullptr;
run_loop.Run();
context = FindContextForProcess(handle);
if (!context) {
return nullptr;
}
}
EXPECT_TRUE(context->cache_remote.is_bound());
EXPECT_TRUE(context->cache_remote.is_connected());
return context->cache_remote.get();
}
private:
AuctionProcessManager::WorkletProcess::ProcessContext CreateProcessInternal(
AuctionProcessManager::WorkletProcess& worklet_process) override {
launched_processes_.emplace_back(worklet_process.GetWeakPtrForTesting());
if constexpr (std::is_same<AuctionManagerBaseType,
InRendererAuctionProcessManager>::value) {
// The MockRenderProcessHost drops Mojo pipes on the floor by default, so
// need to set a binder so `this` can intercept them and bind them to
// `receiver_set_`, while still exercising the production
// CreateProcessInternal() call.
//
// The `worklet_process` weak pointer is passed to store in
// `receiver_set_` when BindInterface() is invoke. This assumes that
// BindInterface() will only be invoked once, synchronously, for each
// AuctionManagerBaseType::CreateProcessInternal() invocation.
static_cast<MockRenderProcessHost*>(
worklet_process.site_instance()->GetOrCreateProcessForTesting())
->OverrideBinderForTesting(
auction_worklet::mojom::AuctionWorkletService::Name_,
base::BindRepeating(&TestAuctionProcessManager<
AuctionManagerBaseType>::BindInterface,
weak_ptr_factory_.GetWeakPtr(),
worklet_process.GetWeakPtrForTesting()));
// Defer to the RendererProcessHost mocks when using the InRenderer path.
return AuctionManagerBaseType::CreateProcessInternal(worklet_process);
} else {
mojo::PendingRemote<auction_worklet::mojom::AuctionWorkletService>
service;
receiver_set_.Add(
this, service.InitWithNewPipeAndPassReceiver(),
ReceiverContext(worklet_process.GetWeakPtrForTesting()));
return AuctionProcessManager::WorkletProcess::ProcessContext(
std::move(service));
}
}
// Callback when trying to bind a pipe through the MockRenderProcessHost.
void BindInterface(
base::WeakPtr<AuctionProcessManager::WorkletProcess> worklet_process,
mojo::ScopedMessagePipeHandle pipe) {
receiver_set_.Add(
this,
mojo::PendingReceiver<auction_worklet::mojom::AuctionWorkletService>(
std::move(pipe)),
ReceiverContext(worklet_process));
}
// Finds the ReceiverContext associated with `handle`. `handle` must have a
// process. It's considered and error for no such ReceiverContext to exist.
ReceiverContext* FindContextForProcess(
const AuctionProcessManager::ProcessHandle& handle) {
// `handle` must be assigned a process.
CHECK(handle.worklet_process_for_testing());
for (const auto& receiver : receiver_set_.GetAllContexts()) {
if (receiver.second->worklet_process.get() ==
handle.worklet_process_for_testing()) {
return receiver.second;
}
}
ADD_FAILURE() << "Context associated with process not found.";
return nullptr;
}
std::vector<base::WeakPtr<AuctionProcessManager::WorkletProcess>>
launched_processes_;
mojo::ReceiverSet<auction_worklet::mojom::AuctionWorkletService,
ReceiverContext>
receiver_set_;
base::WeakPtrFactory<TestAuctionProcessManager<AuctionManagerBaseType>>
weak_ptr_factory_{this};
};
class TestCacheClient
: public auction_worklet::mojom::TrustedSignalsCacheClient {
public:
explicit TestCacheClient(auction_worklet::mojom::TrustedSignalsCache* cache)
: cache_(cache) {}
~TestCacheClient() override = default;
void RequestSignalsExpectingSuccess(
base::UnguessableToken compression_group_token) {
run_loop_ = std::make_unique<base::RunLoop>();
cache_->GetTrustedSignals(compression_group_token,
receiver_.BindNewPipeAndPassRemote());
run_loop_->Run();
run_loop_.reset();
}
private:
// TrustedSignalsCacheClient implementation:
void OnSuccess(auction_worklet::mojom::TrustedSignalsCompressionScheme
compression_scheme,
mojo_base::BigBuffer foo) override {
ADD_FAILURE() << "Valid signals should never be received in these tests";
run_loop_->Quit();
}
void OnError(const std::string& error_message) override {
EXPECT_EQ(error_message, kCacheMessage);
run_loop_->Quit();
}
std::unique_ptr<base::RunLoop> run_loop_;
raw_ptr<auction_worklet::mojom::TrustedSignalsCache> cache_;
mojo::Receiver<auction_worklet::mojom::TrustedSignalsCacheClient> receiver_{
this};
};
// ContentBrowserClient to disable strict site isolation.
class PartialSiteIsolationContentBrowserClient
: public TestContentBrowserClient {
public:
bool ShouldEnableStrictSiteIsolation() override { return false; }
bool ShouldDisableSiteIsolation(
SiteIsolationMode site_isolation_mode) override {
switch (site_isolation_mode) {
case SiteIsolationMode::kStrictSiteIsolation:
return true;
case SiteIsolationMode::kPartialSiteIsolation:
return false;
}
}
};
// The three ways the base test fixture can be configured.
enum class ProcessMode {
// Use DedicatedAuctionProcessManager.
kDedicated,
// Use InRendererProcessManager and kSitePerProcess.
kInRendererSitePerProcess,
// Use InRendererProcessManager and disabled kSitePerProcess.
kInRendererSharedProcess,
};
class AuctionProcessManagerTest
: public testing::TestWithParam<
std::tuple<AuctionProcessManager::WorkletType, ProcessMode>> {
protected:
AuctionProcessManagerTest() {
SiteIsolationPolicy::DisableFlagCachingForTesting();
std::vector<base::test::FeatureRefAndParams> enabled_features{
{features::kFledgeStartAnticipatoryProcesses,
{{"AnticipatoryProcessHoldTime", "10s"}}}};
std::vector<base::test::FeatureRef> disabled_features;
switch (GetProcessMode()) {
case ProcessMode::kDedicated:
break;
case ProcessMode::kInRendererSitePerProcess:
scoped_command_line_.GetProcessCommandLine()->AppendSwitch(
switches::kSitePerProcess);
break;
case ProcessMode::kInRendererSharedProcess:
disabled_features.emplace_back(
features::kOriginKeyedProcessesByDefault);
scoped_command_line_.GetProcessCommandLine()->RemoveSwitch(
switches::kSitePerProcess);
original_browser_client_ =
content::SetBrowserClientForTesting(&browser_client_);
break;
}
RenderProcessHostImpl::set_render_process_host_factory_for_testing(
&rph_factory_);
// Note: if we're going to disable kOriginKeyedProcessesByDefault, as is
// done in the kInRendererSharedProcess case, it's important to do it here
// before we create any SiteInstances, since that will create
// BrowsingInstances, and each BrowsingInstance will create a default
// isolation state based on kOriginKeyedProcessesByDefault.
feature_list_.InitWithFeaturesAndParameters(enabled_features,
disabled_features);
// This StartIsolatingSite() call should be done before any SiteInstances
// are created, so that it applies to them.
SiteInstance::StartIsolatingSite(
&test_browser_context_, kIsolatedOrigin.GetURL(),
ChildProcessSecurityPolicy::IsolatedOriginSource::TEST);
site_instance1_ = SiteInstance::Create(&test_browser_context_);
site_instance2_ = SiteInstance::Create(&test_browser_context_);
CreateAuctionProcessManager(&trusted_signals_cache_);
}
virtual ~AuctionProcessManagerTest() {
if (original_browser_client_) {
content::SetBrowserClientForTesting(original_browser_client_);
}
RenderProcessHostImpl::set_render_process_host_factory_for_testing(nullptr);
}
void CreateAuctionProcessManager(
TrustedSignalsCacheImpl* trusted_signals_cache) {
// Need to clear the raw ptr first, in case there's already an existing
// process.
auction_process_manager_ = nullptr;
switch (GetProcessMode()) {
case ProcessMode::kDedicated:
dedicated_process_manager_.emplace(trusted_signals_cache);
auction_process_manager_ = &dedicated_process_manager_.value();
break;
case ProcessMode::kInRendererSitePerProcess:
case ProcessMode::kInRendererSharedProcess:
in_renderer_process_manager_.emplace(trusted_signals_cache);
auction_process_manager_ = &in_renderer_process_manager_.value();
break;
}
}
// Closes all worklet pipes, much like a crash.
void ClosePipes() {
if (dedicated_process_manager_) {
dedicated_process_manager_->ClosePipes();
} else {
in_renderer_process_manager_->ClosePipes();
}
}
// Wraps calling ProcessCreationOrder() on the correct
// TestAuctionProcessManager.
size_t ProcessCreationOrder(
const AuctionProcessManager::ProcessHandle& handle) {
if (dedicated_process_manager_) {
return dedicated_process_manager_->ProcessCreationOrder(handle);
} else {
return in_renderer_process_manager_->ProcessCreationOrder(handle);
}
}
// Calls WaitForCacheRemote() on the correct TestAuctionProcessManager.
auction_worklet::mojom::TrustedSignalsCache* WaitForCacheRemote(
const AuctionProcessManager::ProcessHandle& handle) {
if (dedicated_process_manager_) {
return dedicated_process_manager_->WaitForCacheRemote(handle);
} else {
return in_renderer_process_manager_->WaitForCacheRemote(handle);
}
}
// Checks that `handle` has no cache remote. Calls RunUntilIdle() to make sure
// there are no pending calls to pass in a cache remote.
void ExpectNoCacheRemote(const AuctionProcessManager::ProcessHandle& handle) {
if (dedicated_process_manager_) {
dedicated_process_manager_->ExpectNoCacheRemote(handle);
} else {
in_renderer_process_manager_->ExpectNoCacheRemote(handle);
}
}
// Validates `handle` has received a cache remote that works for the provided
// origin.
void ValidateCacheRemote(const AuctionProcessManager::ProcessHandle& handle,
const url::Origin& origin) {
auto* cache_remote = WaitForCacheRemote(handle);
ASSERT_TRUE(cache_remote);
std::unique_ptr<TrustedSignalsCacheImpl::Handle> trusted_signals_handle;
int partition_id_ignored = 0;
// Request signals of the corresponding worklet type, on behalf of `origin`.
// None of the other parameters matter.
switch (GetWorkletType()) {
case AuctionProcessManager::WorkletType::kBidder:
trusted_signals_handle =
trusted_signals_cache_.RequestTrustedBiddingSignals(
/*url_loader_factory=*/nullptr, FrameTreeNodeId(1),
{"devtools_auction_id"},
url::Origin::Create(GURL("https://main-frame-origin.test")),
network::mojom::IPAddressSpace::kPublic, origin,
"Interest Group Name",
blink::InterestGroup::ExecutionMode::kCompatibilityMode,
url::Origin::Create(GURL("https://joinin-origin.test")),
GURL("https://trusted-signals-url/"),
url::Origin::Create(GURL("https://coordinator.test")),
/*trusted_bidding_signals_keys=*/{}, /*additional_params=*/{},
/*buyer_tkv_signals=*/std::nullopt, partition_id_ignored);
break;
case AuctionProcessManager::WorkletType::kSeller:
trusted_signals_handle =
trusted_signals_cache_.RequestTrustedScoringSignals(
/*url_loader_factory=*/nullptr, FrameTreeNodeId(1),
{"devtools_auction_id"},
url::Origin::Create(GURL("https://main-frame-origin.test")),
network::mojom::IPAddressSpace::kPublic, origin,
GURL("https://trusted-signals-url/"),
url::Origin::Create(GURL("https://coordinator.test")),
url::Origin::Create(GURL("https://bidder.test")),
url::Origin::Create(GURL("https://joining-origin.test")),
GURL("https://render-url.test"), /*component_render_urls=*/{},
/*additional_params=*/{}, /*seller_tkv_signals=*/std::nullopt,
partition_id_ignored);
break;
}
TestCacheClient cache_client(cache_remote);
cache_client.RequestSignalsExpectingSuccess(
trusted_signals_handle->compression_group_token());
}
// Currently only works when testing the dedicated path.
void SimulateReadyProcess(size_t creation_index) {
CHECK(dedicated_process_manager_);
dedicated_process_manager_->SimulateReadyProcess(creation_index);
}
void MaybeStartAnticipatoryProcess(
const url::Origin& origin,
std::optional<AuctionProcessManager::WorkletType> worklet_type =
std::nullopt) {
auction_process_manager_->MaybeStartAnticipatoryProcess(
origin, site_instance1_.get(), worklet_type.value_or(GetWorkletType()));
}
std::string RequestWorkletServiceOutcomeUmaName(
std::optional<AuctionProcessManager::WorkletType> worklet_type =
std::nullopt) {
return base::StrCat({"Ads.InterestGroup.Auction.",
worklet_type.value_or(GetWorkletType()) ==
AuctionProcessManager::WorkletType::kSeller
? "Seller."
: "Buyer.",
"RequestWorkletServiceOutcome"});
}
void RequestWorkletService(
AuctionProcessManager::ProcessHandle* process_handle,
const url::Origin& origin,
AuctionProcessManager::WorkletType worklet_type,
bool expect_success,
RequestWorkletServiceOutcome expected_outcome) {
base::HistogramTester histogram_tester;
bool success = auction_process_manager_->RequestWorkletService(
worklet_type, origin, site_instance1_.get(), process_handle,
base::DoNothing());
EXPECT_EQ(expect_success, success);
histogram_tester.ExpectUniqueSample(
RequestWorkletServiceOutcomeUmaName(worklet_type), expected_outcome,
1u);
}
// Request a worklet service and expect the request to complete synchronously.
// There's no async version, since async calls are only triggered by deleting
// another handle. Uses `site_instance1_` if no `site_instance` is provided.
std::unique_ptr<AuctionProcessManager::ProcessHandle>
GetServiceOfTypeExpectSuccess(
AuctionProcessManager::WorkletType worklet_type,
const url::Origin& origin,
scoped_refptr<SiteInstance> site_instance = nullptr) {
if (!site_instance) {
site_instance = site_instance1_;
}
auto process_handle =
std::make_unique<AuctionProcessManager::ProcessHandle>();
EXPECT_TRUE(auction_process_manager_->RequestWorkletService(
worklet_type, origin, std::move(site_instance), process_handle.get(),
NeverInvokedClosure()));
EXPECT_TRUE(process_handle->GetService());
return process_handle;
}
// Requests a process of type GetWorkletType().
std::unique_ptr<AuctionProcessManager::ProcessHandle> GetServiceExpectSuccess(
const url::Origin& origin) {
return GetServiceOfTypeExpectSuccess(GetWorkletType(), origin);
}
// Returns the maximum number of processes of type GetWorkletType().
size_t GetMaxProcesses() const {
switch (GetWorkletType()) {
case AuctionProcessManager::WorkletType::kSeller:
return kMaxSellerProcesses;
case AuctionProcessManager::WorkletType::kBidder:
return kMaxBidderProcesses;
}
}
ProcessMode GetProcessMode() const {
return std::get<ProcessMode>(GetParam());
}
AuctionProcessManager::WorkletType GetWorkletType() const {
return std::get<AuctionProcessManager::WorkletType>(GetParam());
}
AuctionProcessManager::WorkletType GetOtherWorkletType() const {
switch (GetWorkletType()) {
case AuctionProcessManager::WorkletType::kSeller:
return AuctionProcessManager::WorkletType::kBidder;
case AuctionProcessManager::WorkletType::kBidder:
return AuctionProcessManager::WorkletType::kSeller;
}
}
// Returns the number of pending requests of GetWorkletType() type.
size_t GetPendingRequestsOfWorkletType() {
switch (GetWorkletType()) {
case AuctionProcessManager::WorkletType::kSeller:
return auction_process_manager_->GetPendingSellerRequestsForTesting();
case AuctionProcessManager::WorkletType::kBidder:
return auction_process_manager_->GetPendingBidderRequestsForTesting();
}
}
// Returns active processes of GetWorkletType() by default.
size_t GetActiveProcessesOfWorkletType(
std::optional<AuctionProcessManager::WorkletType> type = std::nullopt) {
switch (type.value_or(GetWorkletType())) {
case AuctionProcessManager::WorkletType::kSeller:
return auction_process_manager_->GetSellerProcessCountForTesting();
case AuctionProcessManager::WorkletType::kBidder:
return auction_process_manager_->GetBidderProcessCountForTesting();
}
}
void CheckOnlyIdleProcessesWithCount(size_t expected_idle_process_count) {
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(),
expected_idle_process_count);
EXPECT_EQ(auction_process_manager_->GetBidderProcessCountForTesting(), 0u);
EXPECT_EQ(auction_process_manager_->GetSellerProcessCountForTesting(), 0u);
}
// Isolated by StartIsolatingSite() call in the constructor.
const url::Origin kIsolatedOrigin =
url::Origin::Create(GURL("https://bank.test"));
const url::Origin kOriginA = url::Origin::Create(GURL("https://a.test"));
const url::Origin kOriginB = url::Origin::Create(GURL("https://b.test"));
const url::Origin kOriginC = url::Origin::Create(GURL("https://c.test"));
BrowserTaskEnvironment task_environment_{
content::BrowserTaskEnvironment::TimeSource::MOCK_TIME};
base::test::ScopedFeatureList feature_list_;
// `scoped_command_line_` must be destroyeded after any WorkletProcessManager.
// Otherwise idle worklet processes may try to read it after destruction.
base::test::ScopedCommandLine scoped_command_line_;
MockRenderProcessHostFactory rph_factory_;
TestBrowserContext test_browser_context_;
// Used by the kInRendererSharedProcess case to disable strict site isolation.
PartialSiteIsolationContentBrowserClient browser_client_;
raw_ptr<ContentBrowserClient> original_browser_client_;
// `site_instance1_` and `site_instance2_` are in different browsing
// instances.
scoped_refptr<SiteInstance> site_instance1_;
scoped_refptr<SiteInstance> site_instance2_;
DataDecoderManager data_decoder_manager_;
TrustedSignalsCacheImpl trusted_signals_cache_{
&data_decoder_manager_,
base::BindRepeating(
[](const url::Origin& scope_origin,
const std::optional<url::Origin>& coordinator,
base::OnceCallback<void(base::expected<BiddingAndAuctionServerKey,
std::string>)> callback) {
std::move(callback).Run(
base::unexpected(std::string(kCacheMessage)));
})};
// Only one of these two is populated, based on the ProcessMode.
std::optional<TestAuctionProcessManager<DedicatedAuctionProcessManager>>
dedicated_process_manager_;
std::optional<TestAuctionProcessManager<InRendererAuctionProcessManager>>
in_renderer_process_manager_;
// Points to whichever of the above is non-null.
raw_ptr<AuctionProcessManager> auction_process_manager_;
};
// Run most tests in both kDedicated and kInRendererSitePerProcess ProcessModes,
// as their behavior should be very similar in most cases.
using SitePerProcessAuctionProcessManagerTest = AuctionProcessManagerTest;
INSTANTIATE_TEST_SUITE_P(
All,
SitePerProcessAuctionProcessManagerTest,
testing::Combine(
testing::Values(AuctionProcessManager::WorkletType::kSeller,
AuctionProcessManager::WorkletType::kBidder),
testing::Values(ProcessMode::kDedicated,
ProcessMode::kInRendererSitePerProcess)));
// Tests for the kInRendererSharedProcess ProcessMode only. These are different
// enough for the SameSite test, that no tests are currently run in all three
// modes.
using SharedRendererInRendererAuctionProcessManagerTest =
AuctionProcessManagerTest;
INSTANTIATE_TEST_SUITE_P(
All,
SharedRendererInRendererAuctionProcessManagerTest,
testing::Combine(
testing::Values(AuctionProcessManager::WorkletType::kSeller,
AuctionProcessManager::WorkletType::kBidder),
testing::Values(ProcessMode::kInRendererSharedProcess)));
TEST_P(SitePerProcessAuctionProcessManagerTest, Basic) {
auto worklet = GetServiceExpectSuccess(kOriginA);
EXPECT_TRUE(worklet->GetService());
EXPECT_EQ(1u, GetActiveProcessesOfWorkletType());
EXPECT_EQ(0u, GetActiveProcessesOfWorkletType(GetOtherWorkletType()));
EXPECT_EQ(0u, auction_process_manager_->GetIdleProcessCountForTesting());
}
// Make sure requests for different origins don't share processes, nor do
// sellers and bidders.
TEST_P(SitePerProcessAuctionProcessManagerTest,
MultipleRequestsForDifferentProcesses) {
auto worlket_a = GetServiceOfTypeExpectSuccess(GetWorkletType(), kOriginA);
auto worklet_b = GetServiceOfTypeExpectSuccess(GetWorkletType(), kOriginB);
auto worklet_of_other_type_a =
GetServiceOfTypeExpectSuccess(GetOtherWorkletType(), kOriginA);
auto worklet_of_other_type_b =
GetServiceOfTypeExpectSuccess(GetOtherWorkletType(), kOriginB);
EXPECT_EQ(2u, GetActiveProcessesOfWorkletType(
AuctionProcessManager::WorkletType::kBidder));
EXPECT_EQ(2u, GetActiveProcessesOfWorkletType(
AuctionProcessManager::WorkletType::kSeller));
EXPECT_EQ(0u, auction_process_manager_->GetIdleProcessCountForTesting());
EXPECT_NE(worlket_a->GetService(), worklet_b->GetService());
EXPECT_NE(worlket_a->GetService(), worklet_of_other_type_a->GetService());
EXPECT_NE(worlket_a->GetService(), worklet_of_other_type_b->GetService());
EXPECT_NE(worklet_b->GetService(), worklet_of_other_type_a->GetService());
EXPECT_NE(worklet_b->GetService(), worklet_of_other_type_b->GetService());
EXPECT_NE(worklet_of_other_type_a->GetService(),
worklet_of_other_type_b->GetService());
}
TEST_P(SitePerProcessAuctionProcessManagerTest,
MultipleRequestsForSameProcess) {
// Request 3 processes of the same type for the same origin. All requests
// should get the same process.
auto process_a1 = GetServiceExpectSuccess(kOriginA);
EXPECT_TRUE(process_a1->GetService());
auto process_a2 = GetServiceExpectSuccess(kOriginA);
EXPECT_EQ(process_a1->GetService(), process_a2->GetService());
EXPECT_EQ(1u, GetActiveProcessesOfWorkletType());
auto process_a3 = GetServiceExpectSuccess(kOriginA);
EXPECT_EQ(process_a1->GetService(), process_a3->GetService());
EXPECT_EQ(1u, GetActiveProcessesOfWorkletType());
// Request process of the other type with the same origin. It should get a
// different process.
std::unique_ptr<AuctionProcessManager::ProcessHandle> other_process_a1 =
GetServiceOfTypeExpectSuccess(GetOtherWorkletType(), kOriginA);
EXPECT_EQ(1u, GetActiveProcessesOfWorkletType());
EXPECT_EQ(1u, GetActiveProcessesOfWorkletType(GetOtherWorkletType()));
EXPECT_NE(process_a1->GetService(), other_process_a1->GetService());
}
// Test requesting and releasing worklet processes, exceeding the limit. This
// test does not cover the case of multiple requests sharing the same process,
// which is covered by the next test.
TEST_P(SitePerProcessAuctionProcessManagerTest, LimitExceeded) {
// The list of operations below assumes at least 3 processes are allowed at
// once.
CHECK_GE(GetMaxProcesses(), 3u);
// Operations applied to the process manager. All requests use unique origins,
// so no need to specify that.
struct Operation {
enum class Op {
// Request the specified number of handle. If there are less than
// GetMaxProcesses() handles already, expects a process to be immediately
// assigned. All requests use different origins from every other request.
kRequestHandles,
// Destroy a handle with the given index. If the index is less than
// GetMaxProcesses(), then expect a ProcessHandle to have its callback
// invoked, if there are more than GetMaxProcesses() already.
kDestroyHandle,
// Same as destroy handle, but additionally destroys the next handle that
// would have been assigned the next available process slot, and makes
// sure the handle after that one gets a process instead.
kDestroyHandleAndNextInQueue,
};
Op op;
// Number of handles to request for kRequestHandles operations.
std::optional<size_t> num_handles;
// Used for kDestroyHandle and kDestroyHandleAndNextInQueue operations.
std::optional<size_t> index;
// The number of total handles expected after this operation. This can be
// inferred by sum of requested handles requests less handles destroyed
// handles, but having it explcitly in the struct makes sure the test cases
// are testing what they're expected to.
size_t expected_total_handles;
// If `num_handles` is set, this represents whether each request caused us
// to hit the limit for the number of processes.
bool hit_limit_after_requesting_handles;
};
const Operation kOperationList[] = {
{Operation::Op::kRequestHandles,
/*num_handles=*/GetMaxProcesses(),
/*index=*/std::nullopt,
/*expected_total_handles=*/GetMaxProcesses(),
/*hit_limit_after_requesting_handles=*/false},
// Check destroying intermediate, last, and first handle when there are no
// queued requests. Keep exactly GetMaxProcesses() requests, to ensure
// there are in fact first, last, and intermediate requests (as long as
// GetMaxProcesses() is at least 3).
{Operation::Op::kDestroyHandle, /*num_handles=*/std::nullopt,
/*index=*/1u, /*expected_total_handles=*/GetMaxProcesses() - 1},
{Operation::Op::kRequestHandles,
/*num_handles=*/1,
/*index=*/std::nullopt,
/*expected_total_handles=*/GetMaxProcesses(),
/*hit_limit_after_requesting_handles=*/false},
{Operation::Op::kDestroyHandle, /*num_handles=*/std::nullopt,
/*index=*/0u, /*expected_total_handles=*/GetMaxProcesses() - 1},
{Operation::Op::kRequestHandles,
/*num_handles=*/1,
/*index=*/std::nullopt,
/*expected_total_handles=*/GetMaxProcesses(),
/*hit_limit_after_requesting_handles=*/false},
{Operation::Op::kDestroyHandle, /*num_handles=*/std::nullopt,
/*index=*/GetMaxProcesses() - 1,
/*expected_total_handles=*/GetMaxProcesses() - 1},
{Operation::Op::kRequestHandles,
/*num_handles=*/1,
/*index=*/std::nullopt,
/*expected_total_handles=*/GetMaxProcesses(),
/*hit_limit_after_requesting_handles=*/false},
// Queue 3 more requests, but delete the last and first of them, to test
// deleting queued requests.
{Operation::Op::kRequestHandles,
/*num_handles=*/3,
/*index=*/std::nullopt,
/*expected_total_handles=*/GetMaxProcesses() + 3,
/*hit_limit_after_requesting_handles=*/true},
{Operation::Op::kDestroyHandle, /*num_handles=*/std::nullopt,
/*index=*/GetMaxProcesses(),
/*expected_total_handles=*/GetMaxProcesses() + 2},
{Operation::Op::kDestroyHandle, /*num_handles=*/std::nullopt,
/*index=*/GetMaxProcesses() + 1,
/*expected_total_handles=*/GetMaxProcesses() + 1},
// Request 4 more processes.
{Operation::Op::kRequestHandles,
/*num_handles=*/4,
/*index=*/std::nullopt,
/*expected_total_handles=*/GetMaxProcesses() + 5,
/*hit_limit_after_requesting_handles=*/true},
// Destroy the first handle and the first pending in the queue immediately
// afterwards. The next pending request should get a process.
{Operation::Op::kDestroyHandleAndNextInQueue,
/*num_handles=*/std::nullopt, /*index=*/0u,
/*expected_total_handles=*/GetMaxProcesses() + 3},
// Destroy three more requests that have been asssigned processes, being
// sure to destroy the first, last, and some request request with nether,
// amongst requests with assigned processes.
{Operation::Op::kDestroyHandle, /*num_handles=*/std::nullopt,
/*index=*/GetMaxProcesses() - 1,
/*expected_total_handles=*/GetMaxProcesses() + 2},
{Operation::Op::kDestroyHandle, /*num_handles=*/std::nullopt,
/*index=*/0u, /*expected_total_handles=*/GetMaxProcesses() + 1},
{Operation::Op::kDestroyHandle, /*num_handles=*/std::nullopt,
/*index=*/1u, /*expected_total_handles=*/GetMaxProcesses()},
};
struct ProcessHandleData {
std::unique_ptr<AuctionProcessManager::ProcessHandle> process_handle =
std::make_unique<AuctionProcessManager::ProcessHandle>();
std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
};
std::vector<ProcessHandleData> data;
// Used to create distinct origins for each handle
int num_origins = 0;
for (const auto& operation : kOperationList) {
switch (operation.op) {
case Operation::Op::kRequestHandles:
for (size_t i = 0; i < *operation.num_handles; ++i) {
size_t original_size = data.size();
data.emplace_back(ProcessHandleData());
url::Origin distinct_origin = url::Origin::Create(
GURL(base::StringPrintf("https://%i.test", ++num_origins)));
base::HistogramTester histogram_tester;
ASSERT_EQ(original_size < GetMaxProcesses(),
auction_process_manager_->RequestWorkletService(
GetWorkletType(), distinct_origin, site_instance1_,
data.back().process_handle.get(),
data.back().run_loop->QuitClosure()));
RequestWorkletServiceOutcome expected_result =
operation.hit_limit_after_requesting_handles
? RequestWorkletServiceOutcome::kHitProcessLimit
: RequestWorkletServiceOutcome::kCreatedNewDedicatedProcess;
histogram_tester.ExpectUniqueSample(
RequestWorkletServiceOutcomeUmaName(), expected_result,
/*expected_bucket_count=*/1);
}
break;
case Operation::Op::kDestroyHandle: {
size_t original_size = data.size();
ASSERT_GT(data.size(), *operation.index);
data.erase(data.begin() + *operation.index);
// If destroying one of the first GetMaxProcesses() handles, and
// there were more than GetMaxProcesses() handles before, the
// first of the handles waiting on a process should get a process.
if (*operation.index < GetMaxProcesses() &&
original_size > GetMaxProcesses()) {
data[GetMaxProcesses() - 1].run_loop->Run();
EXPECT_TRUE(data[GetMaxProcesses() - 1].process_handle->GetService());
}
break;
}
case Operation::Op::kDestroyHandleAndNextInQueue: {
ASSERT_GT(data.size(), *operation.index);
ASSERT_GT(data.size(), GetMaxProcesses() + 1);
data.erase(data.begin() + *operation.index);
data.erase(data.begin() + GetMaxProcesses());
data[GetMaxProcesses() - 1].run_loop->Run();
EXPECT_TRUE(data[GetMaxProcesses() - 1].process_handle->GetService());
break;
}
}
EXPECT_EQ(operation.expected_total_handles, data.size());
// The first GetMaxProcesses() ProcessHandles should all have
// assigned processes, which should all be distinct.
for (size_t i = 0; i < data.size() && i < GetMaxProcesses(); ++i) {
EXPECT_TRUE(data[i].process_handle->GetService());
for (size_t j = 0; j < i; ++j) {
EXPECT_NE(data[i].process_handle->GetService(),
data[j].process_handle->GetService());
}
}
// Make sure all pending tasks have been run.
base::RunLoop().RunUntilIdle();
// All other requests should not have been assigned processes yet.
for (size_t i = GetMaxProcesses(); i < data.size(); ++i) {
EXPECT_FALSE(data[i].run_loop->AnyQuitCalled());
EXPECT_FALSE(data[i].process_handle->GetService());
}
}
}
// Check the process sharing logic - specifically, that requests share processes
// when origins match, and that handles that share a process only count once
// towrads the process limit the process limit.
TEST_P(SitePerProcessAuctionProcessManagerTest, ProcessSharing) {
// This test assumes GetMaxProcesses() is greater than 1.
DCHECK_GT(GetMaxProcesses(), 1u);
// Make 2*GetMaxProcesses() requests for each of GetMaxProcesses() different
// origins. All requests should succeed immediately.
std::vector<std::list<std::unique_ptr<AuctionProcessManager::ProcessHandle>>>
processes(GetMaxProcesses());
for (size_t origin_index = 0; origin_index < GetMaxProcesses();
++origin_index) {
url::Origin origin = url::Origin::Create(
GURL(base::StringPrintf("https://%zu.test", origin_index)));
base::HistogramTester histogram_tester;
for (size_t i = 0; i < 2 * GetMaxProcesses(); ++i) {
processes[origin_index].emplace_back(GetServiceExpectSuccess(origin));
// All requests for the same origin share a process.
EXPECT_EQ(processes[origin_index].back()->GetService(),
processes[origin_index].front()->GetService());
EXPECT_EQ(origin_index + 1, GetActiveProcessesOfWorkletType());
}
histogram_tester.ExpectBucketCount(
RequestWorkletServiceOutcomeUmaName(),
RequestWorkletServiceOutcome::kCreatedNewDedicatedProcess, 1);
histogram_tester.ExpectBucketCount(
RequestWorkletServiceOutcomeUmaName(),
RequestWorkletServiceOutcome::kUsedExistingDedicatedProcess,
2 * GetMaxProcesses() - 1);
// Each origin should have a different process.
for (size_t origin_index2 = 0; origin_index2 < origin_index;
++origin_index2) {
EXPECT_NE(processes[origin_index].front()->GetService(),
processes[origin_index2].front()->GetService());
}
}
// Make two process requests for kOriginA and one one for kOriginB, which
// should all be blocked due to the process limit being reached.
base::RunLoop run_loop_delayed_a1;
auto process_delayed_a1 =
std::make_unique<AuctionProcessManager::ProcessHandle>();
ASSERT_FALSE(auction_process_manager_->RequestWorkletService(
GetWorkletType(), kOriginA, site_instance1_, process_delayed_a1.get(),
run_loop_delayed_a1.QuitClosure()));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(run_loop_delayed_a1.AnyQuitCalled());
EXPECT_FALSE(process_delayed_a1->GetService());
EXPECT_EQ(GetMaxProcesses(), GetActiveProcessesOfWorkletType());
base::RunLoop run_loop_delayed_a2;
auto process_delayed_a2 =
std::make_unique<AuctionProcessManager::ProcessHandle>();
ASSERT_FALSE(auction_process_manager_->RequestWorkletService(
GetWorkletType(), kOriginA, site_instance1_, process_delayed_a2.get(),
run_loop_delayed_a2.QuitClosure()));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(run_loop_delayed_a2.AnyQuitCalled());
EXPECT_FALSE(process_delayed_a2->GetService());
EXPECT_EQ(GetMaxProcesses(), GetActiveProcessesOfWorkletType());
base::RunLoop run_loop_delayed_b;
auto process_delayed_b =
std::make_unique<AuctionProcessManager::ProcessHandle>();
ASSERT_FALSE(auction_process_manager_->RequestWorkletService(
GetWorkletType(), kOriginB, site_instance1_, process_delayed_b.get(),
run_loop_delayed_b.QuitClosure()));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(run_loop_delayed_b.AnyQuitCalled());
EXPECT_FALSE(process_delayed_b->GetService());
EXPECT_EQ(GetMaxProcesses(), GetActiveProcessesOfWorkletType());
// Release processes for first origin one at a time, until only one is left.
// The pending requests for kOriginA and kOriginB should remain stalled.
while (processes[0].size() > 1u) {
processes[0].pop_front();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(run_loop_delayed_a1.AnyQuitCalled());
EXPECT_FALSE(process_delayed_a1->GetService());
EXPECT_FALSE(run_loop_delayed_a2.AnyQuitCalled());
EXPECT_FALSE(process_delayed_a2->GetService());
EXPECT_FALSE(run_loop_delayed_b.AnyQuitCalled());
EXPECT_FALSE(process_delayed_b->GetService());
EXPECT_EQ(GetMaxProcesses(), GetActiveProcessesOfWorkletType());
}
// Remove the final process for the first origin. It should queue a callback
// to resume the kOriginA requests (prioritized alphabetically), but nothing
// should happen until the callbacks are invoked.
processes[0].pop_front();
EXPECT_FALSE(run_loop_delayed_a1.AnyQuitCalled());
EXPECT_FALSE(process_delayed_a1->GetService());
EXPECT_FALSE(run_loop_delayed_a2.AnyQuitCalled());
EXPECT_FALSE(process_delayed_a2->GetService());
EXPECT_FALSE(run_loop_delayed_b.AnyQuitCalled());
EXPECT_FALSE(process_delayed_b->GetService());
// The two kOriginA callbacks should be invoked when the message loop next
// spins. The two kOriginA requests should now have been assigned the same
// service, while the kOriginB request is still pending.
run_loop_delayed_a1.Run();
run_loop_delayed_a2.Run();
EXPECT_TRUE(process_delayed_a1->GetService());
EXPECT_TRUE(process_delayed_a2->GetService());
EXPECT_EQ(process_delayed_a1->GetService(), process_delayed_a2->GetService());
EXPECT_FALSE(run_loop_delayed_b.AnyQuitCalled());
EXPECT_FALSE(process_delayed_b->GetService());
EXPECT_EQ(GetMaxProcesses(), GetActiveProcessesOfWorkletType());
// Freeing one of the two kOriginA processes should have no effect.
process_delayed_a2.reset();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(run_loop_delayed_b.AnyQuitCalled());
EXPECT_FALSE(process_delayed_b->GetService());
// Freeing the other one should queue a task to give the kOriginB requests a
// process.
process_delayed_a1.reset();
EXPECT_FALSE(run_loop_delayed_b.AnyQuitCalled());
EXPECT_FALSE(process_delayed_b->GetService());
run_loop_delayed_b.Run();
EXPECT_TRUE(process_delayed_b->GetService());
EXPECT_EQ(GetMaxProcesses(), GetActiveProcessesOfWorkletType());
}
TEST_P(SitePerProcessAuctionProcessManagerTest,
DestroyHandlesWithPendingRequests) {
// Make GetMaxProcesses() requests for worklets with different origins.
std::list<std::unique_ptr<AuctionProcessManager::ProcessHandle>> processes;
for (size_t i = 0; i < GetMaxProcesses(); ++i) {
url::Origin origin =
url::Origin::Create(GURL(base::StringPrintf("https://%zu.test", i)));
processes.emplace_back(GetServiceExpectSuccess(origin));
}
// Make a pending request.
auto pending_process1 =
std::make_unique<AuctionProcessManager::ProcessHandle>();
ASSERT_FALSE(auction_process_manager_->RequestWorkletService(
GetWorkletType(), kOriginA, site_instance1_, pending_process1.get(),
NeverInvokedClosure()));
EXPECT_EQ(1u, GetPendingRequestsOfWorkletType());
// Destroy the pending request. Its callback should not be invoked.
pending_process1.reset();
EXPECT_EQ(0u, GetPendingRequestsOfWorkletType());
base::RunLoop().RunUntilIdle();
// Make two more pending process requests.
auto pending_process2 =
std::make_unique<AuctionProcessManager::ProcessHandle>();
ASSERT_FALSE(auction_process_manager_->RequestWorkletService(
GetWorkletType(), kOriginA, site_instance1_, pending_process2.get(),
NeverInvokedClosure()));
auto pending_process3 =
std::make_unique<AuctionProcessManager::ProcessHandle>();
base::RunLoop pending_process3_run_loop;
ASSERT_FALSE(auction_process_manager_->RequestWorkletService(
GetWorkletType(), kOriginB, site_instance1_, pending_process3.get(),
pending_process3_run_loop.QuitClosure()));
EXPECT_EQ(2u, GetPendingRequestsOfWorkletType());
// Delete a process. This should result in a posted task to give
// `pending_process2` a process.
processes.pop_front();
EXPECT_EQ(1u, GetPendingRequestsOfWorkletType());
// Destroy `pending_process2` before it gets passed a process.
pending_process2.reset();
// `pending_process3` should get a process instead.
pending_process3_run_loop.Run();
EXPECT_TRUE(pending_process3->GetService());
EXPECT_EQ(0u, auction_process_manager_->GetPendingSellerRequestsForTesting());
}
// Check that process crash is handled properly, by creating a new process.
TEST_P(SitePerProcessAuctionProcessManagerTest, ProcessCrash) {
auto process = GetServiceExpectSuccess(kOriginA);
auction_worklet::mojom::AuctionWorkletService* service =
process->GetService();
EXPECT_TRUE(service);
EXPECT_EQ(1u, GetActiveProcessesOfWorkletType());
// Close pipes. No new pipe should be created.
ClosePipes();
EXPECT_EQ(0u, GetActiveProcessesOfWorkletType());
// Requesting a new process will create a new pipe.
auto process2 = GetServiceExpectSuccess(kOriginA);
auction_worklet::mojom::AuctionWorkletService* service2 =
process2->GetService();
EXPECT_TRUE(service2);
EXPECT_NE(service, service2);
EXPECT_NE(process, process2);
EXPECT_EQ(1u, GetActiveProcessesOfWorkletType());
}
TEST_P(SitePerProcessAuctionProcessManagerTest, DisconnectBeforeDelete) {
// Exercise the codepath where the mojo pipe to a service is broken when
// a handle to its process is still alive, to make sure this is handled
// correctly (rather than hitting a DCHECK on incorrect refcounting).
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_a1 =
GetServiceExpectSuccess(kOriginA);
ClosePipes();
task_environment_.RunUntilIdle();
handle_a1.reset();
task_environment_.RunUntilIdle();
}
TEST_P(SitePerProcessAuctionProcessManagerTest,
DoesNotStartAnticipatoryProcessIfFeatureDisabled) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndDisableFeature(
features::kFledgeStartAnticipatoryProcesses);
MaybeStartAnticipatoryProcess(kOriginA, GetWorkletType());
CheckOnlyIdleProcessesWithCount(0);
}
TEST_P(SitePerProcessAuctionProcessManagerTest,
ProcessLimitIsRespected_AnticipatoryProcessesOnly) {
// Create the maximum possible # of anticipatory processes.
for (size_t i = 0; i < GetMaxProcesses(); ++i) {
url::Origin origin =
url::Origin::Create(GURL(base::StringPrintf("https://%i.test", i)));
MaybeStartAnticipatoryProcess(origin, GetWorkletType());
CheckOnlyIdleProcessesWithCount(1 + i);
}
// We can't make more.
MaybeStartAnticipatoryProcess(kOriginA, GetWorkletType());
CheckOnlyIdleProcessesWithCount(GetMaxProcesses());
}
// Make sure the process limit is respected when we have a combination of
// anticipatory and active processes. Make sure we can make processes of
// the other type (active and idle) even if we've hit the limit of one type.
TEST_P(SitePerProcessAuctionProcessManagerTest,
ProcessLimitIsRespected_ActiveAndAnticipatoryProcesses) {
// Alternate creating anticipatory and active processes. Each active processes
// will consume 1 anticipatory process. After the for loop, we end up with 1
// anticipatory process and GetMaxProcesses() - 1 active processes.
MaybeStartAnticipatoryProcess(url::Origin::Create(GURL("https://0.test")),
GetWorkletType());
CheckOnlyIdleProcessesWithCount(1);
std::vector<std::unique_ptr<AuctionProcessManager::ProcessHandle>> handles;
for (size_t i = 0; i < GetMaxProcesses() - 1; ++i) {
url::Origin origin =
url::Origin::Create(GURL(base::StringPrintf("https://%i.test", i)));
handles.emplace_back(
std::make_unique<AuctionProcessManager::ProcessHandle>());
RequestWorkletService(handles.back().get(), origin, GetWorkletType(),
/*expect_success=*/true,
RequestWorkletServiceOutcome::kUsedIdleProcess);
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(), 0u);
EXPECT_EQ(GetActiveProcessesOfWorkletType(), handles.size());
origin =
url::Origin::Create(GURL(base::StringPrintf("https://%i.test", i + 1)));
MaybeStartAnticipatoryProcess(origin, GetWorkletType());
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(), 1u);
EXPECT_EQ(GetActiveProcessesOfWorkletType(), handles.size());
}
// Can't make more anticipatory processes of this type.
MaybeStartAnticipatoryProcess(kOriginA, GetWorkletType());
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(), 1u);
EXPECT_EQ(GetActiveProcessesOfWorkletType(), GetMaxProcesses() - 1);
// Can make an anticipatory process of the other type.
MaybeStartAnticipatoryProcess(kOriginB, GetOtherWorkletType());
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(), 2u);
EXPECT_EQ(GetActiveProcessesOfWorkletType(), GetMaxProcesses() - 1);
// We should still be able to create another worklet with the
// anticipatory process we made of this type.
handles.emplace_back(
std::make_unique<AuctionProcessManager::ProcessHandle>());
RequestWorkletService(
handles.back().get(),
url::Origin::Create(
GURL(base::StringPrintf("https://%i.test", GetMaxProcesses() - 1))),
GetWorkletType(),
/*expect_success=*/true, RequestWorkletServiceOutcome::kUsedIdleProcess);
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(), 1u);
EXPECT_EQ(GetActiveProcessesOfWorkletType(), GetMaxProcesses());
// Can't make more processes of this type.
handles.emplace_back(
std::make_unique<AuctionProcessManager::ProcessHandle>());
RequestWorkletService(handles.back().get(), kOriginB, GetWorkletType(),
/*expect_success=*/false,
RequestWorkletServiceOutcome::kHitProcessLimit);
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(), 1u);
EXPECT_EQ(GetActiveProcessesOfWorkletType(), GetMaxProcesses());
// Create a handle that should be backed by the anticipatory process of the
// other type for kOriginB, created three code blocks back.
handles.emplace_back(
std::make_unique<AuctionProcessManager::ProcessHandle>());
if (GetProcessMode() == ProcessMode::kDedicated) {
// If using the dedicated process manager, the origin doesn't need to match
// that of the idle process to reuse it, so can request a service for
// kOriginC to get the WorkletProcess for kOriginB.
RequestWorkletService(handles.back().get(), kOriginC, GetOtherWorkletType(),
/*expect_success=*/true,
RequestWorkletServiceOutcome::kUsedIdleProcess);
} else {
// In the in-renderer case, have to request the same origin to use the
// anticipatory process.
RequestWorkletService(handles.back().get(), kOriginB, GetOtherWorkletType(),
/*expect_success=*/true,
RequestWorkletServiceOutcome::kUsedIdleProcess);
}
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(), 0u);
EXPECT_EQ(GetActiveProcessesOfWorkletType(), GetMaxProcesses());
handles.clear();
EXPECT_EQ(GetActiveProcessesOfWorkletType(), 0u);
// Now we should no longer be at the process limit. We can make more
// processes.
handles.emplace_back(
std::make_unique<AuctionProcessManager::ProcessHandle>());
RequestWorkletService(
handles.back().get(), url::Origin::Create(GURL("https://worklet2.test")),
GetWorkletType(),
/*expect_success=*/true,
RequestWorkletServiceOutcome::kCreatedNewDedicatedProcess);
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(), 0u);
EXPECT_EQ(GetActiveProcessesOfWorkletType(), 1u);
MaybeStartAnticipatoryProcess(
url::Origin::Create(GURL("https://worklet3.test")), GetWorkletType());
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(), 1u);
EXPECT_EQ(GetActiveProcessesOfWorkletType(), 1u);
}
TEST_P(SitePerProcessAuctionProcessManagerTest,
DoNotStartMultipleProcessesSameOriginAndType) {
MaybeStartAnticipatoryProcess(kOriginA, GetWorkletType());
CheckOnlyIdleProcessesWithCount(1);
MaybeStartAnticipatoryProcess(kOriginA, GetWorkletType());
CheckOnlyIdleProcessesWithCount(1);
}
TEST_P(SitePerProcessAuctionProcessManagerTest,
CanStartProcessesWithSameOriginIfOneIsSellerAndOneIsBuyer) {
MaybeStartAnticipatoryProcess(kOriginA, GetWorkletType());
CheckOnlyIdleProcessesWithCount(1);
MaybeStartAnticipatoryProcess(kOriginA, GetOtherWorkletType());
CheckOnlyIdleProcessesWithCount(2);
}
TEST_P(SitePerProcessAuctionProcessManagerTest,
DoNotStartProcessWithSameOriginAndTypeAsExistingProcess) {
AuctionProcessManager::ProcessHandle process_handle;
RequestWorkletService(
&process_handle, kOriginA, GetWorkletType(), /*expect_success=*/true,
RequestWorkletServiceOutcome::kCreatedNewDedicatedProcess);
EXPECT_EQ(GetActiveProcessesOfWorkletType(), 1u);
MaybeStartAnticipatoryProcess(kOriginA, GetWorkletType());
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(), 0u);
EXPECT_EQ(GetActiveProcessesOfWorkletType(), 1u);
}
// This test covers the different behavior when there's an unused anticipatory
// process created with varying parameters.
TEST_P(SitePerProcessAuctionProcessManagerTest,
TryToUseAnticipatoryProcessOfSameOrDifferentOriginAndType) {
for (const auto& origin_to_request : {kOriginA, kOriginB}) {
SCOPED_TRACE(origin_to_request);
for (AuctionProcessManager::WorkletType worklet_type_to_request :
{GetWorkletType(), GetOtherWorkletType()}) {
SCOPED_TRACE(static_cast<int>(worklet_type_to_request));
MaybeStartAnticipatoryProcess(kOriginA, GetWorkletType());
CheckOnlyIdleProcessesWithCount(1);
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(), 1u);
if (GetProcessMode() == ProcessMode::kDedicated ||
(origin_to_request == kOriginA &&
worklet_type_to_request == GetWorkletType())) {
// In the dedicated case, or the in-renderer case where the origin and
// worklet types both match, requesting a process should result in using
// the idle process.
AuctionProcessManager::ProcessHandle handle;
RequestWorkletService(&handle, origin_to_request,
worklet_type_to_request,
/*expect_success=*/true,
RequestWorkletServiceOutcome::kUsedIdleProcess);
EXPECT_EQ(GetActiveProcessesOfWorkletType(worklet_type_to_request), 1u);
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(),
0u);
} else {
// In the other case, a new process should be created, leaving the
// anticipatory process idle.
AuctionProcessManager::ProcessHandle handle;
RequestWorkletService(
&handle, origin_to_request, worklet_type_to_request,
/*expect_success=*/true,
RequestWorkletServiceOutcome::kCreatedNewDedicatedProcess);
EXPECT_EQ(GetActiveProcessesOfWorkletType(worklet_type_to_request), 1u);
// The antipatory process should still exist and be idle.
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(),
1u);
// Make a request matching the parameters of the anticipatory process.
AuctionProcessManager::ProcessHandle handle2;
RequestWorkletService(&handle2, kOriginA, GetWorkletType(),
/*expect_success=*/true,
RequestWorkletServiceOutcome::kUsedIdleProcess);
// There should be no more idle processes.
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(),
0u);
}
// There should be no processes, idle or otherwise, at this point, so the
// manager is in a clean state for the next loop iteration.
CheckOnlyIdleProcessesWithCount(0);
}
}
}
TEST_P(SitePerProcessAuctionProcessManagerTest,
ReassignsOrDestroysIdleProcessOfSameTypeOnlyAfterReachingLimit) {
// Make an anticipatory process of the other type. This will not be
// convertible to a process of our type after we hit the limit.
MaybeStartAnticipatoryProcess(kOriginA, GetOtherWorkletType());
CheckOnlyIdleProcessesWithCount(1);
for (size_t i = 0; i < GetMaxProcesses(); ++i) {
url::Origin origin =
url::Origin::Create(GURL(base::StringPrintf("https://%i.test", i)));
MaybeStartAnticipatoryProcess(origin, GetWorkletType());
CheckOnlyIdleProcessesWithCount(2 + i);
}
// Try assigning these to different origins.
std::vector<std::unique_ptr<AuctionProcessManager::ProcessHandle>> handles;
for (size_t i = 0; i < GetMaxProcesses(); ++i) {
url::Origin origin =
url::Origin::Create(GURL(base::StringPrintf("https://%i_2.test", i)));
handles.emplace_back(
std::make_unique<AuctionProcessManager::ProcessHandle>());
if (GetProcessMode() == ProcessMode::kDedicated) {
// We should assign the oldest anticipatory process of the same type
// because we've hit the process limit -- we'd prefer to assign a newer
// anticipatory process than to use the older process & have to remove one
// of our anticipatory processes. All anticipatory processes were of type
// GetWorkletType() except the first one.
RequestWorkletService(handles.back().get(), origin, GetWorkletType(),
/*expect_success=*/true,
RequestWorkletServiceOutcome::kUsedIdleProcess);
EXPECT_EQ(ProcessCreationOrder(*handles.back()), i + 1u);
} else {
// In the renderer case, processes could not be used for another origin,
// so a new process was created, and an old idle process destroyed.
RequestWorkletService(
handles.back().get(), origin, GetWorkletType(),
/*expect_success=*/true,
RequestWorkletServiceOutcome::kCreatedNewDedicatedProcess);
EXPECT_EQ(ProcessCreationOrder(*handles.back()),
GetMaxProcesses() + i + 1u);
}
EXPECT_EQ(GetActiveProcessesOfWorkletType(), i + 1u);
}
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(), 1u);
}
TEST_P(SitePerProcessAuctionProcessManagerTest,
ProcessesCanBeAssignedInDifferentOrderFromHowTheyWereMade) {
std::vector<std::tuple<url::Origin, AuctionProcessManager::WorkletType>>
origins_and_types;
for (url::Origin origin : {kOriginA, kOriginB, kOriginC}) {
for (AuctionProcessManager::WorkletType type :
{GetWorkletType(), GetOtherWorkletType()}) {
origins_and_types.emplace_back(origin, type);
MaybeStartAnticipatoryProcess(origin, type);
}
}
std::vector<std::unique_ptr<AuctionProcessManager::ProcessHandle>> handles;
for (size_t i = 0; i < origins_and_types.size(); i++) {
size_t inverse_index = origins_and_types.size() - 1 - i;
const auto& origin_and_type = origins_and_types[inverse_index];
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle =
std::make_unique<AuctionProcessManager::ProcessHandle>();
RequestWorkletService(
handle.get(), std::get<url::Origin>(origin_and_type),
std::get<AuctionProcessManager::WorkletType>(origin_and_type),
/*expect_success=*/true,
RequestWorkletServiceOutcome::kUsedIdleProcess);
if (GetProcessMode() == ProcessMode::kDedicated) {
// We assigned the oldest available idle process.
EXPECT_EQ(ProcessCreationOrder(*handle), i);
} else {
// We assigned process created with the same origin.
EXPECT_EQ(ProcessCreationOrder(*handle), inverse_index);
}
EXPECT_EQ(GetActiveProcessesOfWorkletType(
AuctionProcessManager::WorkletType::kBidder) +
GetActiveProcessesOfWorkletType(
AuctionProcessManager::WorkletType::kSeller),
i + 1u);
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(),
inverse_index);
handles.push_back(std::move(handle));
}
}
// Make sure we're not creating duplicate processes for
// an origin, even if we've assigned one of our anticipatory
// processes to a worklet.
TEST_P(SitePerProcessAuctionProcessManagerTest,
DoesNotRecreateAnticipatoryProcessForOriginAfterAssigned) {
url::Origin origins[] = {kOriginA, kOriginB, kOriginC};
for (const url::Origin& origin_to_request_service : origins) {
CheckOnlyIdleProcessesWithCount(0);
for (const url::Origin& origin_for_anticipatory_process : origins) {
MaybeStartAnticipatoryProcess(origin_for_anticipatory_process,
GetWorkletType());
}
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(), 3u);
AuctionProcessManager::ProcessHandle handle;
RequestWorkletService(&handle, origin_to_request_service, GetWorkletType(),
/*expect_success=*/true,
RequestWorkletServiceOutcome::kUsedIdleProcess);
EXPECT_EQ(GetActiveProcessesOfWorkletType(), 1u);
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(), 2u);
for (const url::Origin& origin_for_anticipatory_process : origins) {
MaybeStartAnticipatoryProcess(origin_for_anticipatory_process,
GetWorkletType());
}
EXPECT_EQ(GetActiveProcessesOfWorkletType(), 1u);
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(), 2u);
// Reset the number of processes for the next loop by letting the idle
// processes expire. The active process will go out of scope.
task_environment_.FastForwardBy(
features::kFledgeStartAnticipatoryProcessExpirationTime.Get());
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(), 0u);
}
}
TEST_P(SitePerProcessAuctionProcessManagerTest,
RemovesProcessAfterExpirationTime) {
base::HistogramTester histogram_tester;
MaybeStartAnticipatoryProcess(kOriginA, GetWorkletType());
CheckOnlyIdleProcessesWithCount(1);
task_environment_.FastForwardBy(
features::kFledgeStartAnticipatoryProcessExpirationTime.Get() -
base::Milliseconds(1));
CheckOnlyIdleProcessesWithCount(1);
task_environment_.FastForwardBy(base::Milliseconds(1));
CheckOnlyIdleProcessesWithCount(0);
histogram_tester.ExpectUniqueSample(
"Ads.InterestGroup.Auction.IdleProcessExpired", true, 1);
}
TEST_P(SitePerProcessAuctionProcessManagerTest,
CorrectProcessGetsDeletedAfterExpiration) {
MaybeStartAnticipatoryProcess(kOriginA, GetWorkletType());
CheckOnlyIdleProcessesWithCount(1);
task_environment_.FastForwardBy(base::Milliseconds(1));
MaybeStartAnticipatoryProcess(kOriginB, GetWorkletType());
CheckOnlyIdleProcessesWithCount(2);
// One processes should be deleted after the first
// features::kFledgeStartAnticipatoryProcessExpirationTime
// passes.
task_environment_.FastForwardBy(
features::kFledgeStartAnticipatoryProcessExpirationTime.Get() -
base::Milliseconds(2));
CheckOnlyIdleProcessesWithCount(2);
task_environment_.FastForwardBy(base::Milliseconds(1));
CheckOnlyIdleProcessesWithCount(1);
// Should not add a new idle process of kOriginB. We shouldn't
// have deleted that process.
MaybeStartAnticipatoryProcess(kOriginB, GetWorkletType());
CheckOnlyIdleProcessesWithCount(1);
// Should add a new idle process of kOriginA.
MaybeStartAnticipatoryProcess(kOriginA, GetWorkletType());
CheckOnlyIdleProcessesWithCount(2);
}
TEST_P(SitePerProcessAuctionProcessManagerTest,
DoesNotRemoveActiveProcessAfterExpirationTime) {
MaybeStartAnticipatoryProcess(kOriginA, GetWorkletType());
CheckOnlyIdleProcessesWithCount(1);
AuctionProcessManager::ProcessHandle handle;
base::HistogramTester histogram_tester;
RequestWorkletService(&handle, kOriginA, GetWorkletType(),
/*expect_success=*/true,
RequestWorkletServiceOutcome::kUsedIdleProcess);
EXPECT_EQ(GetActiveProcessesOfWorkletType(), 1u);
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(), 0u);
task_environment_.FastForwardBy(
features::kFledgeStartAnticipatoryProcessExpirationTime.Get());
EXPECT_EQ(GetActiveProcessesOfWorkletType(), 1u);
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(), 0u);
histogram_tester.ExpectUniqueSample(
"Ads.InterestGroup.Auction.IdleProcessExpired", false, 1);
}
TEST_P(SitePerProcessAuctionProcessManagerTest,
PrioritizesReadyIdleUnboundProcess) {
// Unbound processes are only created in the dedicated process case.
if (GetProcessMode() != ProcessMode::kDedicated) {
return;
}
MaybeStartAnticipatoryProcess(kOriginA, GetWorkletType());
MaybeStartAnticipatoryProcess(kOriginB, GetOtherWorkletType());
MaybeStartAnticipatoryProcess(kOriginC, GetWorkletType());
CheckOnlyIdleProcessesWithCount(3);
const size_t kLastCreatedProcessIndex = 2;
SimulateReadyProcess(kLastCreatedProcessIndex);
AuctionProcessManager::ProcessHandle handle1, handle2;
RequestWorkletService(&handle1, kOriginA, GetWorkletType(),
/*expect_success=*/true,
RequestWorkletServiceOutcome::kUsedIdleProcess);
EXPECT_EQ(GetActiveProcessesOfWorkletType(), 1u);
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(),
kLastCreatedProcessIndex);
EXPECT_EQ(ProcessCreationOrder(handle1), kLastCreatedProcessIndex);
// The next best process is the first created one.
RequestWorkletService(&handle2, kOriginB, GetWorkletType(),
/*expect_success=*/true,
RequestWorkletServiceOutcome::kUsedIdleProcess);
EXPECT_EQ(GetActiveProcessesOfWorkletType(), 2u);
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(), 1u);
EXPECT_EQ(ProcessCreationOrder(handle2), 0u);
}
TEST_P(SitePerProcessAuctionProcessManagerTest,
PrioritizesEarliestReadyUnboundIdleProcess) {
// Unbound processes are only created in the dedicated process case.
if (GetProcessMode() != ProcessMode::kDedicated) {
return;
}
std::vector<url::Origin> origins = {kOriginA, kOriginB, kOriginC};
for (const auto& origin : origins) {
MaybeStartAnticipatoryProcess(origin, GetWorkletType());
}
CheckOnlyIdleProcessesWithCount(3);
for (size_t i = 0; i < origins.size(); ++i) {
SimulateReadyProcess(i);
}
// Because the processes are all ready, they should be allocated in order.
std::vector<std::unique_ptr<AuctionProcessManager::ProcessHandle>> handles;
for (size_t i = 0; i < origins.size(); ++i) {
handles.emplace_back(
std::make_unique<AuctionProcessManager::ProcessHandle>());
RequestWorkletService(handles.back().get(), origins[i], GetWorkletType(),
/*expect_success=*/true,
RequestWorkletServiceOutcome::kUsedIdleProcess);
EXPECT_EQ(GetActiveProcessesOfWorkletType(), i + 1);
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(), 2 - i);
EXPECT_EQ(ProcessCreationOrder(*handles.back()), i);
}
}
TEST_P(SitePerProcessAuctionProcessManagerTest,
PrioritizesReadyUnboundIdleProcessOfSameTypeIfOverLimit) {
// Unbound processes are only created in the dedicated process case.
if (GetProcessMode() != ProcessMode::kDedicated) {
return;
}
MaybeStartAnticipatoryProcess(kOriginA, GetOtherWorkletType());
CheckOnlyIdleProcessesWithCount(1);
for (size_t i = 0; i < GetMaxProcesses(); ++i) {
url::Origin origin =
url::Origin::Create(GURL(base::StringPrintf("https://%i.test", i)));
MaybeStartAnticipatoryProcess(origin, GetWorkletType());
CheckOnlyIdleProcessesWithCount(2 + i);
}
// Both the process of the other type and the last process
// of the same type are ready.
SimulateReadyProcess(GetMaxProcesses());
SimulateReadyProcess(0);
// We are at the limit so we should use that last process.
AuctionProcessManager::ProcessHandle handle1, handle2, handle3;
RequestWorkletService(&handle1, kOriginA, GetWorkletType(),
/*expect_success=*/true,
RequestWorkletServiceOutcome::kUsedIdleProcess);
EXPECT_EQ(GetActiveProcessesOfWorkletType(), 1u);
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(),
GetMaxProcesses());
EXPECT_EQ(ProcessCreationOrder(handle1), GetMaxProcesses());
// Even though the first process is ready we have to use the same type because
// we're at the limit.
RequestWorkletService(&handle2, kOriginB, GetWorkletType(),
/*expect_success=*/true,
RequestWorkletServiceOutcome::kUsedIdleProcess);
EXPECT_EQ(GetActiveProcessesOfWorkletType(), 2u);
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(),
GetMaxProcesses() - 1);
EXPECT_EQ(ProcessCreationOrder(handle2), 1u);
// We can use the first process when we request a process for
// GetOtherWorkletType().
RequestWorkletService(&handle3, kOriginC, GetOtherWorkletType(),
/*expect_success=*/true,
RequestWorkletServiceOutcome::kUsedIdleProcess);
EXPECT_EQ(GetActiveProcessesOfWorkletType(), 2u);
EXPECT_EQ(GetActiveProcessesOfWorkletType(GetOtherWorkletType()), 1u);
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(),
GetMaxProcesses() - 2);
EXPECT_EQ(ProcessCreationOrder(handle3), 0u);
}
// Exercise the codepath where a RenderProcessHostDestroyed is received, to
// make sure it doesn't crash.
TEST_P(SitePerProcessAuctionProcessManagerTest, ProcessDeleteBeforeHandle) {
// The process crashing case in the dedicated process world is covered by the
// ProcessCrash test, rather than this one.
if (GetProcessMode() == ProcessMode::kDedicated) {
return;
}
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_a1 =
GetServiceExpectSuccess(kOriginA);
ASSERT_FALSE(rph_factory_.GetProcesses()->empty());
for (std::unique_ptr<MockRenderProcessHost>& proc :
*rph_factory_.GetProcesses()) {
proc.reset();
}
task_environment_.RunUntilIdle();
handle_a1.reset();
task_environment_.RunUntilIdle();
}
TEST_P(SitePerProcessAuctionProcessManagerTest, PidLookup) {
auto handle = GetServiceExpectSuccess(kOriginA);
base::ProcessId expected_pid = base::Process::Current().Pid();
// Request PID twice. Should happen asynchronously, but only use one RPC.
base::RunLoop run_loop0, run_loop1;
bool got_pid0 = false, got_pid1 = false;
std::optional<base::ProcessId> pid0 =
handle->GetPid(base::BindLambdaForTesting(
[&run_loop0, &got_pid0, expected_pid](base::ProcessId pid) {
EXPECT_EQ(expected_pid, pid);
got_pid0 = true;
run_loop0.Quit();
}));
EXPECT_FALSE(pid0.has_value());
std::optional<base::ProcessId> pid1 =
handle->GetPid(base::BindLambdaForTesting(
[&run_loop1, &got_pid1, expected_pid](base::ProcessId pid) {
EXPECT_EQ(expected_pid, pid);
got_pid1 = true;
run_loop1.Quit();
}));
EXPECT_FALSE(pid1.has_value());
if (dedicated_process_manager_) {
SimulateReadyProcess(/*creation_index=*/0);
} else {
for (std::unique_ptr<MockRenderProcessHost>& proc :
*rph_factory_.GetProcesses()) {
proc->SimulateReady();
}
}
run_loop0.Run();
EXPECT_TRUE(got_pid0);
run_loop1.Run();
EXPECT_TRUE(got_pid1);
// Next attempt should be synchronous.
std::optional<base::ProcessId> pid2 =
handle->GetPid(base::BindOnce([](base::ProcessId pid) {
ADD_FAILURE() << "Should not get to callback in pid2 case";
}));
EXPECT_EQ(expected_pid, pid2);
// Reusing the process with another handle should also result in synchronous
// PID lookups.
auto handle2 = GetServiceExpectSuccess(kOriginA);
std::optional<base::ProcessId> pid3 =
handle2->GetPid(base::BindOnce([](base::ProcessId pid) {
ADD_FAILURE() << "Should not get to callback in pid2 case";
}));
EXPECT_EQ(expected_pid, pid3);
}
TEST_P(SitePerProcessAuctionProcessManagerTest,
PidLookupRendererProcessAlreadyRunning) {
// There's no analog to a renderer process already existing in the dedicated
// process world.
if (GetProcessMode() == ProcessMode::kDedicated) {
return;
}
// "Launch" the appropriate process before we even ask for it, and mark its
// launch as completed. |frame_site_instance| will help keep it alive.
scoped_refptr<SiteInstance> frame_site_instance =
site_instance1_->GetRelatedSiteInstance(kOriginA.GetURL());
frame_site_instance->GetOrCreateProcessForTesting()->Init();
for (std::unique_ptr<MockRenderProcessHost>& proc :
*rph_factory_.GetProcesses()) {
proc->SimulateReady();
}
auto handle = GetServiceOfTypeExpectSuccess(GetWorkletType(), kOriginA,
frame_site_instance);
base::ProcessId expected_pid = base::Process::Current().Pid();
// Request PID twice. Should happen asynchronously, but only use one RPC.
std::optional<base::ProcessId> pid0 =
handle->GetPid(base::BindOnce([](base::ProcessId pid) {
ADD_FAILURE() << "Should not get to callback in pid0 case";
}));
ASSERT_TRUE(pid0.has_value());
EXPECT_EQ(expected_pid, pid0.value());
std::optional<base::ProcessId> pid1 =
handle->GetPid(base::BindOnce([](base::ProcessId pid) {
ADD_FAILURE() << "Should not get to callback in pid1 case";
}));
ASSERT_TRUE(pid1.has_value());
EXPECT_EQ(expected_pid, pid1.value());
}
TEST_P(SharedRendererInRendererAuctionProcessManagerTest,
MultipleSiteInstances) {
base::HistogramTester histogram_tester;
// Launch some services in different origins and browsing instances.
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_a1 =
GetServiceOfTypeExpectSuccess(GetWorkletType(), kOriginA,
site_instance1_);
int id_a1 = handle_a1->GetRenderProcessHostForTesting()->GetDeprecatedID();
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_a2 =
GetServiceOfTypeExpectSuccess(GetWorkletType(), kOriginA,
site_instance2_);
int id_a2 = handle_a2->GetRenderProcessHostForTesting()->GetDeprecatedID();
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_b1 =
GetServiceOfTypeExpectSuccess(GetWorkletType(), kOriginB,
site_instance1_);
int id_b1 = handle_b1->GetRenderProcessHostForTesting()->GetDeprecatedID();
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_b2 =
GetServiceOfTypeExpectSuccess(GetWorkletType(), kOriginB,
site_instance2_);
int id_b2 = handle_b2->GetRenderProcessHostForTesting()->GetDeprecatedID();
// Non-site-isolation requiring origins can share processes, but not across
// different browsing instances.
EXPECT_NE(id_a1, id_a2);
EXPECT_EQ(id_a1, id_b1);
EXPECT_NE(id_a1, id_b2);
EXPECT_NE(id_a2, id_b1);
EXPECT_EQ(id_a2, id_b2);
EXPECT_NE(id_b1, id_b2);
histogram_tester.ExpectUniqueSample(
RequestWorkletServiceOutcomeUmaName(),
RequestWorkletServiceOutcome::kUsedSharedProcess, 4);
// Site-isolation requiring origins are distinct from non-isolated ones, but
// can share across browsing instances.
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_i1 =
GetServiceOfTypeExpectSuccess(GetWorkletType(), kIsolatedOrigin,
site_instance1_);
int id_i1 = handle_i1->GetRenderProcessHostForTesting()->GetDeprecatedID();
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_i2 =
GetServiceOfTypeExpectSuccess(GetWorkletType(), kIsolatedOrigin,
site_instance2_);
int id_i2 = handle_i2->GetRenderProcessHostForTesting()->GetDeprecatedID();
EXPECT_EQ(id_i1, id_i2);
EXPECT_NE(id_i1, id_a1);
EXPECT_NE(id_i1, id_a2);
EXPECT_NE(id_i1, id_b1);
EXPECT_NE(id_i1, id_b2);
histogram_tester.ExpectBucketCount(
RequestWorkletServiceOutcomeUmaName(),
RequestWorkletServiceOutcome::kCreatedNewDedicatedProcess, 1);
histogram_tester.ExpectBucketCount(
RequestWorkletServiceOutcomeUmaName(),
RequestWorkletServiceOutcome::kUsedExistingDedicatedProcess, 1);
}
// Test that anticipatory processes are not created for origins that can use the
// shared renderer process.
TEST_P(SharedRendererInRendererAuctionProcessManagerTest,
MaybeStartAnticipatoryProcess_DoesNotStartIfSharedProcessPossible) {
MaybeStartAnticipatoryProcess(kOriginA, GetWorkletType());
CheckOnlyIdleProcessesWithCount(0);
}
// Test that anticipatory processes can be created for isolated origins.
TEST_P(SharedRendererInRendererAuctionProcessManagerTest,
MaybeStartAnticipatoryProcess_StartsProcessForIsolatedOrigin) {
MaybeStartAnticipatoryProcess(kIsolatedOrigin);
CheckOnlyIdleProcessesWithCount(1);
// Don't use this process for an origin that can use a shared process.
AuctionProcessManager::ProcessHandle handle1, handle2;
RequestWorkletService(&handle1, kOriginA, GetWorkletType(),
/*expect_success=*/true,
RequestWorkletServiceOutcome::kUsedSharedProcess);
CheckOnlyIdleProcessesWithCount(1);
// Can use this process for the same isolated origin.
RequestWorkletService(&handle2, kIsolatedOrigin, GetWorkletType(),
/*expect_success=*/true,
RequestWorkletServiceOutcome::kUsedIdleProcess);
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(), 0u);
EXPECT_EQ(GetActiveProcessesOfWorkletType(GetWorkletType()), 1u);
}
// Tests the site-per-process sharing model, focusing on the multiple
// SiteInstances case, which should not affect process sharing.
TEST_P(SitePerProcessAuctionProcessManagerTest, MultipleSiteInstances) {
base::HistogramTester histogram_tester;
// Launch some services in different origins and browsing instances.
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_a1 =
GetServiceOfTypeExpectSuccess(GetWorkletType(), kOriginA,
site_instance1_);
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_a2 =
GetServiceOfTypeExpectSuccess(GetWorkletType(), kOriginA,
site_instance2_);
// Despite having different SiteInstances, `handle_a1` and `handle_a2` should
// share the same process and service, since they share an origin.
EXPECT_EQ(handle_a1->worklet_process_for_testing(),
handle_a2->worklet_process_for_testing());
EXPECT_EQ(handle_a1->GetService(), handle_a2->GetService());
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_b1 =
GetServiceOfTypeExpectSuccess(GetWorkletType(), kOriginB,
site_instance1_);
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_b2 =
GetServiceOfTypeExpectSuccess(GetWorkletType(), kOriginB,
site_instance2_);
// Similarly, `handle_b1` and `handle_b2` should share a process and service.
EXPECT_EQ(handle_b1->worklet_process_for_testing(),
handle_b2->worklet_process_for_testing());
EXPECT_EQ(handle_b1->GetService(), handle_b2->GetService());
// Since sites are partitioned by origin, the `a` handles and `b` handles
// should use different processes and services.
EXPECT_NE(handle_a1->worklet_process_for_testing(),
handle_b1->worklet_process_for_testing());
EXPECT_NE(handle_a1->GetService(), handle_b1->GetService());
// If using InRendererMode, they should also use different RenderProcessHosts.
if (GetProcessMode() != ProcessMode::kDedicated) {
EXPECT_NE(handle_a1->GetRenderProcessHostForTesting()->GetDeprecatedID(),
handle_b1->GetRenderProcessHostForTesting()->GetDeprecatedID());
}
histogram_tester.ExpectBucketCount(
RequestWorkletServiceOutcomeUmaName(),
RequestWorkletServiceOutcome::kCreatedNewDedicatedProcess, 2);
histogram_tester.ExpectBucketCount(
RequestWorkletServiceOutcomeUmaName(),
RequestWorkletServiceOutcome::kUsedExistingDedicatedProcess, 2);
// Stuff that's also isolated by explicit requests gets the same treatment.
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_i1 =
GetServiceOfTypeExpectSuccess(GetWorkletType(), kIsolatedOrigin,
site_instance1_);
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_i2 =
GetServiceOfTypeExpectSuccess(GetWorkletType(), kIsolatedOrigin,
site_instance2_);
EXPECT_EQ(handle_i1->worklet_process_for_testing(),
handle_i2->worklet_process_for_testing());
EXPECT_EQ(handle_i1->GetService(), handle_i2->GetService());
// If using InRendererMode, they should also use different RenderProcessHosts.
if (GetProcessMode() != ProcessMode::kDedicated) {
EXPECT_NE(handle_i1->GetRenderProcessHostForTesting()->GetDeprecatedID(),
handle_a1->GetRenderProcessHostForTesting()->GetDeprecatedID());
EXPECT_NE(handle_i1->GetRenderProcessHostForTesting()->GetDeprecatedID(),
handle_b1->GetRenderProcessHostForTesting()->GetDeprecatedID());
}
histogram_tester.ExpectBucketCount(
RequestWorkletServiceOutcomeUmaName(),
RequestWorkletServiceOutcome::kCreatedNewDedicatedProcess, 3);
histogram_tester.ExpectBucketCount(
RequestWorkletServiceOutcomeUmaName(),
RequestWorkletServiceOutcome::kUsedExistingDedicatedProcess, 3);
}
TEST_P(SharedRendererInRendererAuctionProcessManagerTest, PolicyChange) {
// Launch site in default instance.
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_a1 =
GetServiceOfTypeExpectSuccess(GetWorkletType(), kOriginA,
site_instance1_);
EXPECT_FALSE(
handle_a1->site_instance_for_testing()->RequiresDedicatedProcess());
RenderProcessHost* shared_process =
handle_a1->GetRenderProcessHostForTesting();
// Change policy so that A can no longer use shared instances.
SiteInstance::StartIsolatingSite(
&test_browser_context_, kOriginA.GetURL(),
ChildProcessSecurityPolicy::IsolatedOriginSource::TEST);
site_instance1_ = SiteInstance::Create(&test_browser_context_);
// Launch another A-origin worklet, this should get a different process.
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_a2 =
GetServiceOfTypeExpectSuccess(GetWorkletType(), kOriginA,
site_instance1_);
EXPECT_TRUE(
handle_a2->site_instance_for_testing()->RequiresDedicatedProcess());
EXPECT_NE(handle_a2->GetRenderProcessHostForTesting(), shared_process);
// Destroy shared process and try to get another A one --- should reuse the
// same non-shared process.
handle_a1.reset();
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_a3 =
GetServiceOfTypeExpectSuccess(GetWorkletType(), kOriginA,
site_instance1_);
EXPECT_TRUE(
handle_a3->site_instance_for_testing()->RequiresDedicatedProcess());
EXPECT_EQ(handle_a2->GetRenderProcessHostForTesting(),
handle_a3->GetRenderProcessHostForTesting());
// Checking GetRenderProcessHostForTesting isn't enough since SiteInstance
// can share it, too.
EXPECT_EQ(handle_a2->worklet_process_for_testing(),
handle_a3->worklet_process_for_testing());
}
TEST_P(SitePerProcessAuctionProcessManagerTest, TrustedSignalsCache) {
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_a1 =
GetServiceExpectSuccess(kOriginA);
ValidateCacheRemote(*handle_a1, kOriginA);
// Creating another handle to the same process should not result in another
// cache pipe being passed to the AuctionWorkletService.
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_a2 =
GetServiceExpectSuccess(kOriginA);
EXPECT_EQ(handle_a1->GetService(), handle_a2->GetService());
// Cache remote should still be live.
ValidateCacheRemote(*handle_a2, kOriginA);
// Requesting a service from a different origin, however, results in a cache
// pipe being passed to the new AuctionWorkletService.
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_b =
GetServiceExpectSuccess(kOriginB);
EXPECT_NE(handle_a1->GetService(), handle_b->GetService());
ValidateCacheRemote(*handle_b, kOriginB);
}
TEST_P(SitePerProcessAuctionProcessManagerTest,
TrustedSignalsCacheSentToAnticipatoryProcess) {
// Creating an anticipatory process and then getting a handle to it should
// result in a cache pipe being sent to the AuctionWorkletService.
MaybeStartAnticipatoryProcess(kOriginA);
// This should not send a second cache pipe to the process.
MaybeStartAnticipatoryProcess(kOriginA);
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(), 1u);
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_a1 =
GetServiceExpectSuccess(kOriginA);
ValidateCacheRemote(*handle_a1, kOriginA);
// Make sure the anticipatory process was actually used.
EXPECT_EQ(auction_process_manager_->GetIdleProcessCountForTesting(), 0u);
// Creating another handle to the same process should not result in another
// cache pipe being passed to the AuctionWorkletService.
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_a2 =
GetServiceExpectSuccess(kOriginA);
EXPECT_EQ(handle_a1->GetService(), handle_a2->GetService());
ValidateCacheRemote(*handle_a2, kOriginA);
// Trying to create an anticipatory process matching the existing process
// should do nothing, including not sending a new pipe to the process.
MaybeStartAnticipatoryProcess(kOriginA);
ValidateCacheRemote(*handle_a1, kOriginA);
}
// Test that no cache pipe is sent, and there is no crash, when the trusted
// signals cache is disabled.
TEST_P(SitePerProcessAuctionProcessManagerTest, TrustedSignalsCacheDisabled) {
// Create a new AuctionProcessManager without a TrustedSignalsCache.
CreateAuctionProcessManager(/*trusted_signals_cache=*/nullptr);
// Check there's no trusted signals cache in the base case.
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_a =
GetServiceExpectSuccess(kOriginA);
ExpectNoCacheRemote(*handle_a);
// Check there's no trusted siganls cache when creating anticipatory
// processes.
MaybeStartAnticipatoryProcess(kOriginB);
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_b =
GetServiceExpectSuccess(kOriginB);
ExpectNoCacheRemote(*handle_b);
}
// Test the single shared renderer process case. Since anticipatory processes
// aren't created in that case, don't bother testing that case.
TEST_P(SharedRendererInRendererAuctionProcessManagerTest, TrustedSignalsCache) {
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_a1 =
GetServiceExpectSuccess(kOriginA);
ValidateCacheRemote(*handle_a1, kOriginA);
// Creating another handle with the same origin results in a pipe, instead of
// reusing the old one, and a new cache remote should be passed to it.
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_a2 =
GetServiceExpectSuccess(kOriginA);
EXPECT_NE(handle_a1->GetService(), handle_a2->GetService());
ValidateCacheRemote(*handle_a2, kOriginA);
// Requesting a service from a different origin, however, results in a cache
// pipe being passed to the new AuctionWorkletService.
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_b =
GetServiceExpectSuccess(kOriginB);
ValidateCacheRemote(*handle_b, kOriginB);
}
// Test that no cache pipe is sent, and there is no crash, when the trusted
// signals cache is disabled.
TEST_P(SharedRendererInRendererAuctionProcessManagerTest,
TrustedSignalsCacheDisabled) {
// Create a new AuctionProcessManager without a TrustedSignalsCache.
CreateAuctionProcessManager(/*trusted_signals_cache=*/nullptr);
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle =
GetServiceExpectSuccess(kOriginA);
ExpectNoCacheRemote(*handle);
}
} // namespace
} // namespace content