blob: 1676a64f864b7e3d6fa078aec6524149cb5a3b04 [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 <list>
#include <memory>
#include <string>
#include <vector>
#include "base/callback.h"
#include "base/check.h"
#include "base/notreached.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_feature_list.h"
#include "content/browser/renderer_host/render_process_host_impl.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_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 "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 "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 {
// Alias constants to improve readability.
const size_t kMaxSellerProcesses = AuctionProcessManager::kMaxSellerProcesses;
const size_t kMaxBidderProcesses = AuctionProcessManager::kMaxBidderProcesses;
class TestAuctionProcessManager
: public AuctionProcessManager,
public auction_worklet::mojom::AuctionWorkletService {
public:
TestAuctionProcessManager() = default;
TestAuctionProcessManager(const TestAuctionProcessManager&) = delete;
const TestAuctionProcessManager& operator=(const TestAuctionProcessManager&) =
delete;
~TestAuctionProcessManager() override = default;
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,
bool has_experiment_nonce,
uint16_t experiment_nonce) override {
NOTREACHED();
}
void LoadSellerWorklet(
mojo::PendingReceiver<auction_worklet::mojom::SellerWorklet>
seller_worklet,
bool should_pause_on_start,
mojo::PendingRemote<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,
bool has_experiment_nonce,
uint16_t experiment_nonce) override {
NOTREACHED();
}
size_t NumReceivers() {
// Flush so that any closed pipes are removed. No need to worry about
// pending creation requests, since this class is called into directly,
// rather than over a Mojo pipe.
receiver_set_.FlushForTesting();
return receiver_set_.size();
}
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();
}
private:
RenderProcessHost* LaunchProcess(
mojo::PendingReceiver<auction_worklet::mojom::AuctionWorkletService>
auction_worklet_service_receiver,
const ProcessHandle* handle,
const std::string& display_name) override {
receiver_set_.Add(this, std::move(auction_worklet_service_receiver));
return handle->site_instance_for_testing()->GetProcess();
}
scoped_refptr<SiteInstance> MaybeComputeSiteInstance(
SiteInstance* frame_site_instance,
const url::Origin& worklet_origin) override {
return frame_site_instance->GetRelatedSiteInstance(worklet_origin.GetURL());
}
bool TryUseSharedProcess(ProcessHandle* process_handle) override {
return false;
}
mojo::ReceiverSet<auction_worklet::mojom::AuctionWorkletService>
receiver_set_;
};
class AuctionProcessManagerTest
: public testing::TestWithParam<AuctionProcessManager::WorkletType> {
protected:
AuctionProcessManagerTest()
: site_instance_(SiteInstance::Create(&test_browser_context_)) {}
void SetUp() override {
RenderProcessHostImpl::set_render_process_host_factory_for_testing(
&rph_factory_);
SiteIsolationPolicy::DisableFlagCachingForTesting();
}
void TearDown() override {
RenderProcessHostImpl::set_render_process_host_factory_for_testing(nullptr);
}
// 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.
std::unique_ptr<AuctionProcessManager::ProcessHandle>
GetServiceOfTypeExpectSuccess(AuctionProcessManager::WorkletType worklet_type,
const url::Origin& origin) {
auto process_handle =
std::make_unique<AuctionProcessManager::ProcessHandle>();
EXPECT_TRUE(auction_process_manager_.RequestWorkletService(
worklet_type, origin, site_instance_, process_handle.get(),
NeverInvokedClosure()));
EXPECT_TRUE(process_handle->GetService());
return process_handle;
}
// Requests a process of type GetParam().
std::unique_ptr<AuctionProcessManager::ProcessHandle> GetServiceExpectSuccess(
const url::Origin& origin) {
return GetServiceOfTypeExpectSuccess(GetParam(), origin);
}
// Returns the maximum number of processes of type GetParam().
size_t GetMaxProcesses() const {
switch (GetParam()) {
case AuctionProcessManager::WorkletType::kSeller:
return kMaxSellerProcesses;
case AuctionProcessManager::WorkletType::kBidder:
return kMaxBidderProcesses;
}
}
// Returns the number of pending requests of GetParam() type.
size_t GetPendingRequestsOfParamType() const {
switch (GetParam()) {
case AuctionProcessManager::WorkletType::kSeller:
return auction_process_manager_.GetPendingSellerRequestsForTesting();
case AuctionProcessManager::WorkletType::kBidder:
return auction_process_manager_.GetPendingBidderRequestsForTesting();
}
}
base::OnceClosure NeverInvokedClosure() {
return base::BindOnce(
[]() { ADD_FAILURE() << "This should not be called"; });
}
BrowserTaskEnvironment task_environment_;
TestBrowserContext test_browser_context_;
MockRenderProcessHostFactory rph_factory_;
scoped_refptr<SiteInstance> site_instance_;
TestAuctionProcessManager auction_process_manager_;
const url::Origin kOriginA = url::Origin::Create(GURL("https://a.test"));
const url::Origin kOriginB = url::Origin::Create(GURL("https://b.test"));
};
INSTANTIATE_TEST_SUITE_P(
All,
AuctionProcessManagerTest,
testing::Values(AuctionProcessManager::WorkletType::kSeller,
AuctionProcessManager::WorkletType::kBidder));
TEST_P(AuctionProcessManagerTest, Basic) {
auto seller = GetServiceExpectSuccess(kOriginA);
EXPECT_TRUE(seller->GetService());
EXPECT_EQ(1u, auction_process_manager_.NumReceivers());
}
// Make sure requests for different origins don't share processes, nor do
// sellers and bidders.
//
// This test doesn't use the parameterization, but using TEST_F() for a single
// test would require another test fixture, and so would add more complexity
// than it's worth, for only a single unit test.
TEST_P(AuctionProcessManagerTest, MultipleRequestsForDifferentProcesses) {
auto seller_a = GetServiceOfTypeExpectSuccess(
AuctionProcessManager::WorkletType::kSeller, kOriginA);
auto seller_b = GetServiceOfTypeExpectSuccess(
AuctionProcessManager::WorkletType::kSeller, kOriginB);
auto buyer_a = GetServiceOfTypeExpectSuccess(
AuctionProcessManager::WorkletType::kBidder, kOriginA);
auto buyer_b = GetServiceOfTypeExpectSuccess(
AuctionProcessManager::WorkletType::kBidder, kOriginB);
EXPECT_EQ(4u, auction_process_manager_.NumReceivers());
EXPECT_NE(seller_a->GetService(), seller_b->GetService());
EXPECT_NE(seller_a->GetService(), buyer_a->GetService());
EXPECT_NE(seller_a->GetService(), buyer_b->GetService());
EXPECT_NE(seller_b->GetService(), buyer_a->GetService());
EXPECT_NE(seller_b->GetService(), buyer_b->GetService());
EXPECT_NE(buyer_a->GetService(), buyer_b->GetService());
}
TEST_P(AuctionProcessManagerTest, 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, auction_process_manager_.NumReceivers());
auto process_a3 = GetServiceExpectSuccess(kOriginA);
EXPECT_EQ(process_a1->GetService(), process_a3->GetService());
EXPECT_EQ(1u, auction_process_manager_.NumReceivers());
// Request process of the other type with the same origin. It should get a
// different process.
std::unique_ptr<AuctionProcessManager::ProcessHandle> other_process_a1;
switch (GetParam()) {
case AuctionProcessManager::WorkletType::kSeller:
other_process_a1 = GetServiceOfTypeExpectSuccess(
AuctionProcessManager::WorkletType::kBidder, kOriginA);
break;
case AuctionProcessManager::WorkletType::kBidder:
other_process_a1 = GetServiceOfTypeExpectSuccess(
AuctionProcessManager::WorkletType::kSeller, kOriginA);
break;
}
EXPECT_EQ(2u, auction_process_manager_.NumReceivers());
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(AuctionProcessManagerTest, 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.
absl::optional<size_t> num_handles;
// Used for kDestroyHandle and kDestroyHandleAndNextInQueue operations.
absl::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;
};
const Operation kOperationList[] = {
{Operation::Op::kRequestHandles, /*num_handles=*/GetMaxProcesses(),
/*index=*/absl::nullopt,
/*expected_total_handles=*/GetMaxProcesses()},
// 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=*/absl::nullopt,
/*index=*/1u, /*expected_total_handles=*/GetMaxProcesses() - 1},
{Operation::Op::kRequestHandles, /*num_handles=*/1,
/*index=*/absl::nullopt,
/*expected_total_handles=*/GetMaxProcesses()},
{Operation::Op::kDestroyHandle, /*num_handles=*/absl::nullopt,
/*index=*/0u, /*expected_total_handles=*/GetMaxProcesses() - 1},
{Operation::Op::kRequestHandles, /*num_handles=*/1,
/*index=*/absl::nullopt,
/*expected_total_handles=*/GetMaxProcesses()},
{Operation::Op::kDestroyHandle, /*num_handles=*/absl::nullopt,
/*index=*/GetMaxProcesses() - 1,
/*expected_total_handles=*/GetMaxProcesses() - 1},
{Operation::Op::kRequestHandles, /*num_handles=*/1,
/*index=*/absl::nullopt,
/*expected_total_handles=*/GetMaxProcesses()},
// Queue 3 more requests, but delete the last and first of them, to test
// deleting queued requests.
{Operation::Op::kRequestHandles, /*num_handles=*/3,
/*index=*/absl::nullopt,
/*expected_total_handles=*/GetMaxProcesses() + 3},
{Operation::Op::kDestroyHandle, /*num_handles=*/absl::nullopt,
/*index=*/GetMaxProcesses(),
/*expected_total_handles=*/GetMaxProcesses() + 2},
{Operation::Op::kDestroyHandle, /*num_handles=*/absl::nullopt,
/*index=*/GetMaxProcesses() + 1,
/*expected_total_handles=*/GetMaxProcesses() + 1},
// Request 4 more processes.
{Operation::Op::kRequestHandles, /*num_handles=*/4,
/*index=*/absl::nullopt,
/*expected_total_handles=*/GetMaxProcesses() + 5},
// 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=*/absl::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=*/absl::nullopt,
/*index=*/GetMaxProcesses() - 1,
/*expected_total_handles=*/GetMaxProcesses() + 2},
{Operation::Op::kDestroyHandle, /*num_handles=*/absl::nullopt,
/*index=*/0u, /*expected_total_handles=*/GetMaxProcesses() + 1},
{Operation::Op::kDestroyHandle, /*num_handles=*/absl::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)));
ASSERT_EQ(original_size < GetMaxProcesses(),
auction_process_manager_.RequestWorkletService(
GetParam(), distinct_origin, site_instance_,
data.back().process_handle.get(),
data.back().run_loop->QuitClosure()));
}
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(AuctionProcessManagerTest, 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)));
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, auction_process_manager_.NumReceivers());
}
// 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(
GetParam(), kOriginA, site_instance_, 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(), auction_process_manager_.NumReceivers());
base::RunLoop run_loop_delayed_a2;
auto process_delayed_a2 =
std::make_unique<AuctionProcessManager::ProcessHandle>();
ASSERT_FALSE(auction_process_manager_.RequestWorkletService(
GetParam(), kOriginA, site_instance_, 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(), auction_process_manager_.NumReceivers());
base::RunLoop run_loop_delayed_b;
auto process_delayed_b =
std::make_unique<AuctionProcessManager::ProcessHandle>();
ASSERT_FALSE(auction_process_manager_.RequestWorkletService(
GetParam(), kOriginB, site_instance_, 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(), auction_process_manager_.NumReceivers());
// 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(), auction_process_manager_.NumReceivers());
}
// 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(), auction_process_manager_.NumReceivers());
// 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(), auction_process_manager_.NumReceivers());
}
TEST_P(AuctionProcessManagerTest, 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(
GetParam(), kOriginA, site_instance_, pending_process1.get(),
NeverInvokedClosure()));
EXPECT_EQ(1u, GetPendingRequestsOfParamType());
// Destroy the pending request. Its callback should not be invoked.
pending_process1.reset();
EXPECT_EQ(0u, GetPendingRequestsOfParamType());
base::RunLoop().RunUntilIdle();
// Make two more pending process requests.
auto pending_process2 =
std::make_unique<AuctionProcessManager::ProcessHandle>();
ASSERT_FALSE(auction_process_manager_.RequestWorkletService(
GetParam(), kOriginA, site_instance_, pending_process2.get(),
NeverInvokedClosure()));
auto pending_process3 =
std::make_unique<AuctionProcessManager::ProcessHandle>();
base::RunLoop pending_process3_run_loop;
ASSERT_FALSE(auction_process_manager_.RequestWorkletService(
GetParam(), kOriginB, site_instance_, pending_process3.get(),
pending_process3_run_loop.QuitClosure()));
EXPECT_EQ(2u, GetPendingRequestsOfParamType());
// Delete a process. This should result in a posted task to give
// `pending_process2` a process.
processes.pop_front();
EXPECT_EQ(1u, GetPendingRequestsOfParamType());
// 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(AuctionProcessManagerTest, ProcessCrash) {
auto process = GetServiceExpectSuccess(kOriginA);
auction_worklet::mojom::AuctionWorkletService* service =
process->GetService();
EXPECT_TRUE(service);
EXPECT_EQ(1u, auction_process_manager_.NumReceivers());
// Close pipes. No new pipe should be created.
auction_process_manager_.ClosePipes();
EXPECT_EQ(0u, auction_process_manager_.NumReceivers());
// 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, auction_process_manager_.NumReceivers());
}
TEST_P(AuctionProcessManagerTest, 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);
auction_process_manager_.ClosePipes();
task_environment_.RunUntilIdle();
handle_a1.reset();
task_environment_.RunUntilIdle();
}
TEST_P(AuctionProcessManagerTest, ProcessDeleteBeforeHandle) {
// Exercise the codepath where a RenderProcessHostDestroyed is received, to
// make sure it doesn't crash.
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_a1 =
GetServiceExpectSuccess(kOriginA);
for (std::unique_ptr<MockRenderProcessHost>& proc :
*rph_factory_.GetProcesses()) {
proc.reset();
}
task_environment_.RunUntilIdle();
handle_a1.reset();
task_environment_.RunUntilIdle();
}
TEST_F(AuctionProcessManagerTest, PidLookup) {
auto handle = GetServiceOfTypeExpectSuccess(
AuctionProcessManager::WorkletType::kSeller, 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;
absl::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());
absl::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());
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.
absl::optional<base::ProcessId> pid2 =
handle->GetPid(base::BindOnce([](base::ProcessId pid) {
ADD_FAILURE() << "Should not get to callback in pid2 case";
}));
ASSERT_TRUE(pid2.has_value());
EXPECT_EQ(expected_pid, pid2.value());
}
TEST_F(AuctionProcessManagerTest, PidLookupAlreadyRunning) {
// "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_instance_->GetRelatedSiteInstance(kOriginA.GetURL());
frame_site_instance->GetProcess()->Init();
for (std::unique_ptr<MockRenderProcessHost>& proc :
*rph_factory_.GetProcesses()) {
proc->SimulateReady();
}
auto handle = GetServiceOfTypeExpectSuccess(
AuctionProcessManager::WorkletType::kSeller, kOriginA);
base::ProcessId expected_pid = base::Process::Current().Pid();
// Request PID twice. Should happen asynchronously, but only use one RPC.
absl::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());
absl::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());
}
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;
}
}
};
class InRendererAuctionProcessManagerTest : public ::testing::Test {
protected:
InRendererAuctionProcessManagerTest() {
SiteInstance::StartIsolatingSite(
&test_browser_context_, kIsolatedOrigin.GetURL(),
ChildProcessSecurityPolicy::IsolatedOriginSource::TEST);
// Created these after StartIsolatingSite so they are affected by it.
site_instance1_ = SiteInstance::Create(&test_browser_context_);
site_instance2_ = SiteInstance::Create(&test_browser_context_);
}
void SetUp() override {
RenderProcessHostImpl::set_render_process_host_factory_for_testing(
&rph_factory_);
SiteIsolationPolicy::DisableFlagCachingForTesting();
}
void TearDown() override {
RenderProcessHostImpl::set_render_process_host_factory_for_testing(nullptr);
}
std::unique_ptr<AuctionProcessManager::ProcessHandle>
GetServiceOfTypeExpectSuccess(AuctionProcessManager::WorkletType worklet_type,
scoped_refptr<SiteInstance> site_instance,
const url::Origin& origin) {
auto process_handle =
std::make_unique<AuctionProcessManager::ProcessHandle>();
EXPECT_TRUE(auction_process_manager_.RequestWorkletService(
worklet_type, origin, site_instance, process_handle.get(),
NeverInvokedClosure()));
EXPECT_TRUE(process_handle->GetService());
return process_handle;
}
base::OnceClosure NeverInvokedClosure() {
return base::BindOnce(
[]() { ADD_FAILURE() << "This should not be called"; });
}
BrowserTaskEnvironment task_environment_;
TestBrowserContext test_browser_context_;
MockRenderProcessHostFactory rph_factory_;
// `site_instance1_` and `site_instance2_` are in different browsing
// instances.
scoped_refptr<SiteInstance> site_instance1_, site_instance2_;
InRendererAuctionProcessManager auction_process_manager_;
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 kIsolatedOrigin =
url::Origin::Create(GURL("https://bank.test"));
};
TEST_F(InRendererAuctionProcessManagerTest, AndroidLike) {
PartialSiteIsolationContentBrowserClient browser_client;
ContentBrowserClient* orig_browser_client =
content::SetBrowserClientForTesting(&browser_client);
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeatures(
/*enabled_features=*/{features::kProcessSharingWithDefaultSiteInstances},
/*disabled_features=*/{features::kProcessSharingWithStrictSiteInstances});
base::test::ScopedCommandLine scoped_command_line;
scoped_command_line.GetProcessCommandLine()->RemoveSwitch(
switches::kSitePerProcess);
// Launch some services in different origins and browsing instances.
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_a1 =
GetServiceOfTypeExpectSuccess(AuctionProcessManager::WorkletType::kSeller,
site_instance1_, kOriginA);
int id_a1 = handle_a1->GetRenderProcessHostForTesting()->GetID();
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_a2 =
GetServiceOfTypeExpectSuccess(AuctionProcessManager::WorkletType::kSeller,
site_instance2_, kOriginA);
int id_a2 = handle_a2->GetRenderProcessHostForTesting()->GetID();
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_b1 =
GetServiceOfTypeExpectSuccess(AuctionProcessManager::WorkletType::kSeller,
site_instance1_, kOriginB);
int id_b1 = handle_b1->GetRenderProcessHostForTesting()->GetID();
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_b2 =
GetServiceOfTypeExpectSuccess(AuctionProcessManager::WorkletType::kSeller,
site_instance2_, kOriginB);
int id_b2 = handle_b2->GetRenderProcessHostForTesting()->GetID();
// 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);
// Site-isolation requiring origins are distinct from non-isolated ones, but
// can share across browsing instances.
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_i1 =
GetServiceOfTypeExpectSuccess(AuctionProcessManager::WorkletType::kSeller,
site_instance1_, kIsolatedOrigin);
int id_i1 = handle_i1->GetRenderProcessHostForTesting()->GetID();
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_i2 =
GetServiceOfTypeExpectSuccess(AuctionProcessManager::WorkletType::kSeller,
site_instance2_, kIsolatedOrigin);
int id_i2 = handle_i2->GetRenderProcessHostForTesting()->GetID();
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);
content::SetBrowserClientForTesting(orig_browser_client);
}
TEST_F(InRendererAuctionProcessManagerTest, DesktopLike) {
// Use a site-per-process mode.
base::test::ScopedCommandLine scoped_command_line;
scoped_command_line.GetProcessCommandLine()->AppendSwitch(
switches::kSitePerProcess);
// Launch some services in different origins and browsing instances.
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_a1 =
GetServiceOfTypeExpectSuccess(AuctionProcessManager::WorkletType::kSeller,
site_instance1_, kOriginA);
int id_a1 = handle_a1->GetRenderProcessHostForTesting()->GetID();
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_a2 =
GetServiceOfTypeExpectSuccess(AuctionProcessManager::WorkletType::kSeller,
site_instance2_, kOriginA);
int id_a2 = handle_a2->GetRenderProcessHostForTesting()->GetID();
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_b1 =
GetServiceOfTypeExpectSuccess(AuctionProcessManager::WorkletType::kSeller,
site_instance1_, kOriginB);
int id_b1 = handle_b1->GetRenderProcessHostForTesting()->GetID();
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_b2 =
GetServiceOfTypeExpectSuccess(AuctionProcessManager::WorkletType::kSeller,
site_instance2_, kOriginB);
int id_b2 = handle_b2->GetRenderProcessHostForTesting()->GetID();
// Since we are site-per-process, things should be grouped by origin.
EXPECT_EQ(id_a1, id_a2);
EXPECT_NE(id_a1, id_b1);
EXPECT_NE(id_a1, id_b2);
EXPECT_NE(id_a2, id_b1);
EXPECT_NE(id_a2, id_b2);
EXPECT_EQ(id_b1, id_b2);
// Stuff that's also isolated by explicit requests gets the same treatment.
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_i1 =
GetServiceOfTypeExpectSuccess(AuctionProcessManager::WorkletType::kSeller,
site_instance1_, kIsolatedOrigin);
int id_i1 = handle_i1->GetRenderProcessHostForTesting()->GetID();
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_i2 =
GetServiceOfTypeExpectSuccess(AuctionProcessManager::WorkletType::kSeller,
site_instance2_, kIsolatedOrigin);
int id_i2 = handle_i2->GetRenderProcessHostForTesting()->GetID();
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);
}
TEST_F(InRendererAuctionProcessManagerTest, PolicyChange) {
PartialSiteIsolationContentBrowserClient browser_client;
ContentBrowserClient* orig_browser_client =
content::SetBrowserClientForTesting(&browser_client);
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeatures(
/*enabled_features=*/{features::kProcessSharingWithDefaultSiteInstances},
/*disabled_features=*/{features::kProcessSharingWithStrictSiteInstances});
base::test::ScopedCommandLine scoped_command_line;
scoped_command_line.GetProcessCommandLine()->RemoveSwitch(
switches::kSitePerProcess);
// Launch site in default instance.
std::unique_ptr<AuctionProcessManager::ProcessHandle> handle_a1 =
GetServiceOfTypeExpectSuccess(AuctionProcessManager::WorkletType::kSeller,
site_instance1_, kOriginA);
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(AuctionProcessManager::WorkletType::kSeller,
site_instance1_, kOriginA);
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(AuctionProcessManager::WorkletType::kSeller,
site_instance1_, kOriginA);
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());
content::SetBrowserClientForTesting(orig_browser_client);
}
} // namespace
} // namespace content