| // 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 |