| // Copyright 2020 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/preloading/prerender/prerender_host_registry.h" |
| |
| #include <cstdint> |
| |
| #include "base/memory_coordinator/memory_coordinator_features.h" |
| #include "base/memory_coordinator/memory_limit.h" |
| #include "base/memory_coordinator/test_memory_consumer_registry.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/test/bind.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "components/variations/scoped_variations_ids_provider.h" |
| #include "content/browser/preloading/preload_pipeline_info_impl.h" |
| #include "content/browser/preloading/preloading.h" |
| #include "content/browser/preloading/preloading_confidence.h" |
| #include "content/browser/preloading/preloading_config.h" |
| #include "content/browser/preloading/prerender/prerender_features.h" |
| #include "content/browser/preloading/prerender/prerender_final_status.h" |
| #include "content/browser/preloading/prerender/prerender_host.h" |
| #include "content/browser/preloading/prerender/prerender_metrics.h" |
| #include "content/browser/preloading/speculation_rules/speculation_host_impl.h" |
| #include "content/browser/renderer_host/render_frame_host_impl.h" |
| #include "content/browser/site_instance_impl.h" |
| #include "content/public/browser/preload_pipeline_info.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/test/preloading_test_util.h" |
| #include "content/public/test/prerender_test_util.h" |
| #include "content/public/test/test_browser_context.h" |
| #include "content/test/mock_commit_deferring_condition.h" |
| #include "content/test/navigation_simulator_impl.h" |
| #include "content/test/test_render_view_host.h" |
| #include "content/test/test_web_contents.h" |
| #include "net/base/load_flags.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/mojom/loader/mixed_content.mojom.h" |
| #include "third_party/blink/public/mojom/speculation_rules/speculation_rules.mojom.h" |
| |
| namespace content { |
| namespace { |
| |
| blink::mojom::SpeculationCandidatePtr CreatePrerenderCandidate( |
| const GURL& url) { |
| auto candidate = blink::mojom::SpeculationCandidate::New(); |
| candidate->action = blink::mojom::SpeculationAction::kPrerender; |
| candidate->url = url; |
| candidate->referrer = blink::mojom::Referrer::New(); |
| candidate->eagerness = blink::mojom::SpeculationEagerness::kImmediate; |
| candidate->tags = {std::nullopt}; |
| return candidate; |
| } |
| |
| void SendCandidates(const std::vector<GURL>& urls, |
| mojo::Remote<blink::mojom::SpeculationHost>& remote) { |
| std::vector<blink::mojom::SpeculationCandidatePtr> candidates; |
| candidates.resize(urls.size()); |
| std::ranges::transform(urls, candidates.begin(), &CreatePrerenderCandidate); |
| remote->UpdateSpeculationCandidates(std::move(candidates), false); |
| remote.FlushForTesting(); |
| } |
| |
| void SendCandidate(const GURL& url, |
| mojo::Remote<blink::mojom::SpeculationHost>& remote) { |
| SendCandidates({url}, remote); |
| } |
| |
| std::unique_ptr<NavigationSimulatorImpl> CreateActivation( |
| const GURL& prerendering_url, |
| WebContentsImpl& web_contents) { |
| std::unique_ptr<NavigationSimulatorImpl> navigation = |
| NavigationSimulatorImpl::CreateRendererInitiated( |
| prerendering_url, web_contents.GetPrimaryMainFrame()); |
| navigation->SetReferrer(blink::mojom::Referrer::New( |
| web_contents.GetPrimaryMainFrame()->GetLastCommittedURL(), |
| network::mojom::ReferrerPolicy::kStrictOriginWhenCrossOrigin)); |
| return navigation; |
| } |
| |
| // Finish a prerendering navigation that was already started with |
| // CreateAndStartHost(). |
| void CommitPrerenderNavigation(PrerenderHost& host) { |
| // Normally we could use EmbeddedTestServer to provide a response, but these |
| // tests use RenderViewHostImplTestHarness so the load goes through a |
| // TestNavigationURLLoader which we don't have access to in order to |
| // complete. Use NavigationSimulator to finish the navigation. |
| FrameTreeNode* ftn = FrameTreeNode::From(host.GetPrerenderedMainFrameHost()); |
| std::unique_ptr<NavigationSimulator> sim = |
| NavigationSimulatorImpl::CreateFromPendingInFrame(ftn); |
| sim->Commit(); |
| EXPECT_TRUE(host.is_ready_for_activation()); |
| } |
| |
| class PrerenderHostRegistryTest : public RenderViewHostImplTestHarness { |
| public: |
| PrerenderHostRegistryTest() |
| : scoped_variations_ids_provider_( |
| variations::test::ScopedVariationsIdsProvider( |
| variations::VariationsIdsProvider::Mode::kUseSignedInState)) { |
| scoped_feature_list_prerender2_fallback_.InitWithFeaturesAndParameters( |
| { |
| { |
| features::kPrerender2FallbackPrefetchSpecRules, |
| { |
| { |
| features:: |
| kPrerender2FallbackPrefetchUseBlockUntilHeadTimetout |
| .name, |
| "false", |
| }, |
| { |
| features::kPrerender2FallbackPrefetchSchedulerPolicy |
| .name, |
| "NotUse", |
| }, |
| }, |
| }, |
| }, |
| {}); |
| } |
| ~PrerenderHostRegistryTest() override = default; |
| |
| void SetUp() override { |
| RenderViewHostImplTestHarness::SetUp(); |
| |
| web_contents_delegate_ = |
| std::make_unique<test::ScopedPrerenderWebContentsDelegate>(*contents()); |
| contents()->NavigateAndCommit(GURL("https://example.com/")); |
| } |
| |
| RenderFrameHostImpl* NavigatePrimaryPage(TestWebContents* web_contents, |
| const GURL& dest_url) { |
| std::unique_ptr<NavigationSimulatorImpl> navigation = |
| NavigationSimulatorImpl::CreateRendererInitiated( |
| dest_url, web_contents->GetPrimaryMainFrame()); |
| navigation->SetTransition(ui::PAGE_TRANSITION_LINK); |
| navigation->Start(); |
| navigation->Commit(); |
| RenderFrameHostImpl* render_frame_host = |
| web_contents->GetPrimaryMainFrame(); |
| EXPECT_EQ(render_frame_host->GetLastCommittedURL(), dest_url); |
| return render_frame_host; |
| } |
| |
| // Helper method to test the navigation param matching logic which allows a |
| // prerender host to be used in a potential activation navigation only if its |
| // params match the potential activation navigation params. Use setup_callback |
| // to set the parameters. Returns true if the host was selected as a |
| // potential candidate for activation, and false otherwise. |
| [[nodiscard]] bool CheckIsActivatedForParams( |
| base::OnceCallback<void(NavigationSimulatorImpl*)> setup_callback) { |
| RenderFrameHostImpl* render_frame_host = contents()->GetPrimaryMainFrame(); |
| |
| const GURL kPrerenderingUrl("https://example.com/next"); |
| registry().CreateAndStartHost(GeneratePrerenderAttributes( |
| kPrerenderingUrl, PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kImmediate, render_frame_host)); |
| PrerenderHost* prerender_host = |
| registry().FindHostByUrlForTesting(kPrerenderingUrl); |
| CommitPrerenderNavigation(*prerender_host); |
| |
| std::unique_ptr<NavigationSimulatorImpl> navigation = |
| NavigationSimulatorImpl::CreateRendererInitiated(kPrerenderingUrl, |
| render_frame_host); |
| // Set a default referrer policy that matches the initial prerender |
| // navigation. |
| // TODO(falken): Fix NavigationSimulatorImpl to do this itself. |
| navigation->SetReferrer(blink::mojom::Referrer::New( |
| contents()->GetPrimaryMainFrame()->GetLastCommittedURL(), |
| network::mojom::ReferrerPolicy::kStrictOriginWhenCrossOrigin)); |
| |
| // Change a parameter to differentiate the activation request from the |
| // prerendering request. |
| std::move(setup_callback).Run(navigation.get()); |
| navigation->Start(); |
| NavigationRequest* navigation_request = navigation->GetNavigationHandle(); |
| // Use is_running_potential_prerender_activation_checks() instead of |
| // IsPrerenderedPageActivation() because the NavigationSimulator does not |
| // proceed past CommitDeferringConditions on potential activations, |
| // so IsPrerenderedPageActivation() will fail with a CHECK because |
| // prerender_frame_tree_node_id_ is not populated. |
| // TODO(crbug.com/40784651): Fix NavigationSimulator to wait for |
| // commit deferring conditions as it does throttles. |
| return navigation_request |
| ->is_running_potential_prerender_activation_checks(); |
| } |
| |
| // Helper method to perform a prerender activation that includes specialized |
| // handling or setup on the initial prerender navigation via the |
| // setup_callback parameter. |
| void SetupPrerenderAndCommit( |
| base::OnceCallback<void(NavigationSimulatorImpl*)> setup_callback) { |
| const GURL kPrerenderingUrl("https://example.com/next"); |
| const PrerenderHostId prerender_host_id = |
| registry().CreateAndStartHost(GeneratePrerenderAttributes( |
| kPrerenderingUrl, PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kImmediate, |
| contents()->GetPrimaryMainFrame())); |
| ASSERT_TRUE(prerender_host_id); |
| PrerenderHost* prerender_host = |
| registry().FindNonReservedHostById(prerender_host_id); |
| |
| // Complete the initial prerender navigation. |
| FrameTreeNode* ftn = |
| FrameTreeNode::From(prerender_host->GetPrerenderedMainFrameHost()); |
| std::unique_ptr<NavigationSimulatorImpl> sim = |
| NavigationSimulatorImpl::CreateFromPendingInFrame(ftn); |
| std::move(setup_callback).Run(sim.get()); |
| sim->Commit(); |
| EXPECT_TRUE(prerender_host->is_ready_for_activation()); |
| |
| // Activate the prerendered page. |
| contents()->ActivatePrerenderedPage(kPrerenderingUrl); |
| } |
| |
| PrerenderAttributes GeneratePrerenderAttributes( |
| const GURL& url, |
| PreloadingTriggerType trigger_type, |
| const std::string& histogram_suffix, |
| std::optional<blink::mojom::SpeculationEagerness> eagerness, |
| RenderFrameHostImpl* rfh) { |
| switch (trigger_type) { |
| case PreloadingTriggerType::kSpeculationRule: |
| case PreloadingTriggerType::kSpeculationRuleFromIsolatedWorld: |
| case PreloadingTriggerType::kSpeculationRuleFromAutoSpeculationRules: |
| return PrerenderAttributes( |
| url, trigger_type, histogram_suffix, |
| std::make_optional(SpeculationRulesParams( |
| blink::mojom::SpeculationTargetHint::kNoHint, |
| eagerness.value_or( |
| blink::mojom::SpeculationEagerness::kImmediate), |
| SpeculationRulesTags())), |
| Referrer(), |
| /*no_vary_search_hint=*/std::nullopt, rfh, contents()->GetWeakPtr(), |
| ui::PAGE_TRANSITION_LINK, |
| /*should_warm_up_compositor=*/false, |
| /*should_prepare_paint_tree=*/false, |
| blink::mojom::SpeculationAction::kPrerender, |
| /*url_match_predicate=*/{}, |
| /*prerender_navigation_handle_callback=*/{}, |
| PreloadPipelineInfoImpl::Create( |
| /*planned_max_preloading_type=*/PreloadingType::kPrerender), |
| /*allow_reuse=*/false, |
| /*form_submission=*/false); |
| case PreloadingTriggerType::kEmbedder: |
| return PrerenderAttributes( |
| url, trigger_type, histogram_suffix, |
| /*speculation_rules_params=*/std::nullopt, Referrer(), |
| /*no_vary_search_hint=*/std::nullopt, |
| /*initiator_render_frame_host=*/nullptr, contents()->GetWeakPtr(), |
| ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED | |
| ui::PAGE_TRANSITION_FROM_ADDRESS_BAR), |
| /*should_warm_up_compositor=*/false, |
| /*should_prepare_paint_tree=*/false, |
| blink::mojom::SpeculationAction::kPrerender, |
| /*url_match_predicate=*/{}, |
| /*prerender_navigation_handle_callback=*/{}, |
| PreloadPipelineInfoImpl::Create( |
| /*planned_max_preloading_type=*/PreloadingType::kPrerender), |
| /*allow_reuse=*/false, |
| /*form_submission=*/false); |
| } |
| } |
| |
| void ExpectUniqueSampleOfSpeculationRuleFinalStatus( |
| PrerenderFinalStatus status, |
| base::HistogramBase::Count32 count = 1) { |
| histogram_tester_.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| status, count); |
| } |
| |
| void ExpectBucketCountOfSpeculationRuleFinalStatus( |
| PrerenderFinalStatus status, |
| base::HistogramBase::Count32 count = 1) { |
| histogram_tester_.ExpectBucketCount( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| status, count); |
| } |
| |
| void ExpectUniqueSampleOfEmbedderFinalStatus( |
| PrerenderFinalStatus status, |
| const std::string& histogram_suffix, |
| base::HistogramBase::Count32 count = 1) { |
| histogram_tester_.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_" + |
| histogram_suffix, |
| status, count); |
| } |
| |
| void ExpectBucketCountOfEmbedderFinalStatus( |
| PrerenderFinalStatus status, |
| const std::string& histogram_suffix, |
| base::HistogramBase::Count32 count = 1) { |
| histogram_tester_.ExpectBucketCount( |
| "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_" + |
| histogram_suffix, |
| status, count); |
| } |
| |
| void ExpectUniqueSampleOfActivationNavigationParamsMatch( |
| PrerenderHost::ActivationNavigationParamsMatch result, |
| base::HistogramBase::Count32 count = 1) { |
| histogram_tester_.ExpectUniqueSample( |
| "Prerender.Experimental.ActivationNavigationParamsMatch." |
| "SpeculationRule", |
| result, count); |
| } |
| |
| void ExpectBucketCountOfActivationNavigationParamsMatch( |
| PrerenderHost::ActivationNavigationParamsMatch result, |
| base::HistogramBase::Count32 count = 1) { |
| histogram_tester_.ExpectBucketCount( |
| "Prerender.Experimental.ActivationNavigationParamsMatch." |
| "SpeculationRule", |
| result, count); |
| } |
| |
| PrerenderHostRegistry& registry() { |
| return *contents()->GetPrerenderHostRegistry(); |
| } |
| |
| base::HistogramTester& histogram_tester() { return histogram_tester_; } |
| |
| private: |
| test::ScopedPrerenderFeatureList scoped_feature_list_; |
| base::test::ScopedFeatureList scoped_feature_list_prerender2_fallback_; |
| base::HistogramTester histogram_tester_; |
| |
| protected: |
| base::TestMemoryConsumerRegistry test_registry_; |
| std::unique_ptr<test::ScopedPrerenderWebContentsDelegate> |
| web_contents_delegate_; |
| // Prevent `DCHECK(g_instance)` failure in |
| // `VariationsIdsProvider::GetInstance()` via |
| // `PrefetchContainer::MakeInitialResourceRequest()`. |
| variations::test::ScopedVariationsIdsProvider scoped_variations_ids_provider_; |
| }; |
| |
| TEST_F(PrerenderHostRegistryTest, CreateAndStartHost_SpeculationRule) { |
| const GURL kPrerenderingUrl("https://example.com/next"); |
| const PrerenderHostId prerender_host_id = |
| registry().CreateAndStartHost(GeneratePrerenderAttributes( |
| kPrerenderingUrl, PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kImmediate, |
| contents()->GetPrimaryMainFrame())); |
| ASSERT_TRUE(prerender_host_id); |
| PrerenderHost* prerender_host = |
| registry().FindNonReservedHostById(prerender_host_id); |
| CommitPrerenderNavigation(*prerender_host); |
| |
| contents()->ActivatePrerenderedPage(kPrerenderingUrl); |
| |
| // "Navigation.TimeToActivatePrerender.SpeculationRule" histogram should be |
| // recorded on every prerender activation. |
| histogram_tester().ExpectTotalCount( |
| "Navigation.TimeToActivatePrerender.SpeculationRule", 1u); |
| } |
| |
| // Helper observer to trigger a prerender request and cancel the current |
| // prerender while `is_starting_prerendering_` is true. |
| class PrerenderStarterObserver : public WebContentsObserver { |
| public: |
| explicit PrerenderStarterObserver(WebContents* web_contents, |
| PrerenderHostRegistry* registry, |
| PrerenderAttributes attributes) |
| : WebContentsObserver(web_contents), |
| registry_(registry), |
| attributes_(std::move(attributes)) {} |
| |
| void DidStartNavigation(NavigationHandle* navigation_handle) override { |
| if (triggered_) { |
| return; |
| } |
| triggered_ = true; |
| // 1. Trigger a new prerender (host1) while `is_starting_prerendering_` is |
| // true. |
| registry_->CreateAndStartHost(attributes_); |
| |
| // 2. Cancel the starting prerender (host0) while |
| // `is_starting_prerendering_` is true. Because `is_starting_prerendering_` |
| // is true, CancelHosts's internal StartPrerendering is skipped, leaving |
| // host1 in `pending_prerenders_` with running_prerender_host_id_ null. |
| PrerenderHostId host_id0 = registry_->GetPrerenderHostIdForNavigation( |
| static_cast<NavigationRequest*>(navigation_handle)); |
| if (host_id0) { |
| registry_->CancelHost(host_id0, PrerenderFinalStatus::kDestroyed); |
| } |
| } |
| |
| private: |
| raw_ptr<PrerenderHostRegistry> registry_; |
| PrerenderAttributes attributes_; |
| bool triggered_ = false; |
| }; |
| |
| // Repro of https://crbug.com/553069087 |
| // |
| // Tests that creating a new prerender host when another prerender is already |
| // queued in `pending_prerenders_` triggers a CHECK failure due to host ID |
| // mismatch. |
| // |
| // TODO(crbug.com/553069087): Fix. |
| TEST_F(PrerenderHostRegistryTest, |
| DISABLED_CreateAndStartHost_WhenPendingPrerendersExist) { |
| const GURL kUrl0("https://example.com/page0"); |
| const GURL kUrl1("https://example.com/page1"); |
| const GURL kUrl2("https://example.com/page2"); |
| |
| // 1. Put tab in background. |
| contents()->WasHidden(); |
| |
| PrerenderStarterObserver observer( |
| contents(), ®istry(), |
| GeneratePrerenderAttributes( |
| kUrl1, PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kImmediate, |
| contents()->GetPrimaryMainFrame())); |
| |
| // 2. Create host0. While host0 is starting, DidStartNavigation creates host1 |
| // and cancels host0. This leaves host1 in `pending_prerenders_` while |
| // `running_prerender_host_id_` is null and |
| // `PrerenderCanBeStartedWhenInitiatorIsInBackground()` is true. |
| const PrerenderHostId host_id0 = |
| registry().CreateAndStartHost(GeneratePrerenderAttributes( |
| kUrl0, PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kImmediate, |
| contents()->GetPrimaryMainFrame())); |
| EXPECT_FALSE(host_id0); |
| EXPECT_EQ(registry().GetPendingPrerendersCountForTesting(), 1u); |
| |
| // 3. Create host2 while still in background. |
| // Because `running_prerender_host_id_` is null and background starting is |
| // allowed, CreateAndStartHost calls `StartPrerendering(PrerenderHostId())`, |
| // which dequeues and starts host1 from the front of `pending_prerenders_`. |
| // Prior to the fix, this triggers CHECK(started_prerender_host_id == |
| // prerender_host_id) because started_prerender_host_id (host1) != |
| // prerender_host_id (host2). |
| const PrerenderHostId host_id2 = |
| registry().CreateAndStartHost(GeneratePrerenderAttributes( |
| kUrl2, PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kImmediate, |
| contents()->GetPrimaryMainFrame())); |
| EXPECT_TRUE(host_id2); |
| } |
| |
| TEST_F(PrerenderHostRegistryTest, CreateAndStartHost_Embedder_DirectURLInput) { |
| const GURL kPrerenderingUrl("https://example.com/next"); |
| const PrerenderHostId prerender_host_id = |
| registry().CreateAndStartHost(GeneratePrerenderAttributes( |
| kPrerenderingUrl, PreloadingTriggerType::kEmbedder, "DirectURLInput", |
| std::nullopt, contents()->GetPrimaryMainFrame())); |
| ASSERT_TRUE(prerender_host_id); |
| PrerenderHost* prerender_host = |
| registry().FindNonReservedHostById(prerender_host_id); |
| CommitPrerenderNavigation(*prerender_host); |
| |
| contents()->ActivatePrerenderedPageFromAddressBar(kPrerenderingUrl); |
| |
| // "Navigation.TimeToActivatePrerender.Embedder_DirectURLInput" histogram |
| // should be recorded on every prerender activation. |
| histogram_tester().ExpectTotalCount( |
| "Navigation.TimeToActivatePrerender.Embedder_DirectURLInput", 1u); |
| } |
| |
| TEST_F(PrerenderHostRegistryTest, CreateAndStartHost_PreloadingConfigHoldback) { |
| content::test::PreloadingConfigOverride preloading_config_override; |
| preloading_config_override.SetHoldback( |
| PreloadingType::kPrerender, |
| content_preloading_predictor::kSpeculationRules, true); |
| const GURL kPrerenderingUrl("https://example.com/next"); |
| auto* preloading_data = PreloadingData::GetOrCreateForWebContents(contents()); |
| PreloadingURLMatchCallback same_url_matcher = |
| PreloadingData::GetSameURLMatcher(kPrerenderingUrl); |
| PreloadingAttempt* preloading_attempt = preloading_data->AddPreloadingAttempt( |
| content_preloading_predictor::kSpeculationRules, |
| PreloadingType::kPrerender, std::move(same_url_matcher), |
| contents()->GetPrimaryMainFrame()->GetPageUkmSourceId()); |
| const PrerenderHostId prerender_host_id = registry().CreateAndStartHost( |
| GeneratePrerenderAttributes( |
| kPrerenderingUrl, PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kImmediate, |
| contents()->GetPrimaryMainFrame()), |
| preloading_attempt); |
| EXPECT_FALSE(prerender_host_id); |
| } |
| |
| TEST_F(PrerenderHostRegistryTest, |
| CreateAndStartHost_HoldbackOverride_Holdback) { |
| const GURL kPrerenderingUrl("https://example.com/next"); |
| auto* preloading_data = PreloadingData::GetOrCreateForWebContents(contents()); |
| PreloadingURLMatchCallback same_url_matcher = |
| PreloadingData::GetSameURLMatcher(kPrerenderingUrl); |
| PreloadingAttempt* preloading_attempt = preloading_data->AddPreloadingAttempt( |
| content_preloading_predictor::kSpeculationRules, |
| PreloadingType::kPrerender, std::move(same_url_matcher), |
| contents()->GetPrimaryMainFrame()->GetPageUkmSourceId()); |
| |
| auto attributes = GeneratePrerenderAttributes( |
| kPrerenderingUrl, PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kImmediate, |
| contents()->GetPrimaryMainFrame()); |
| attributes.holdback_status_override = PreloadingHoldbackStatus::kHoldback; |
| |
| const PrerenderHostId prerender_host_id = |
| registry().CreateAndStartHost(attributes, preloading_attempt); |
| |
| EXPECT_FALSE(prerender_host_id); |
| } |
| |
| TEST_F(PrerenderHostRegistryTest, CreateAndStartHost_HoldbackOverride_Allowed) { |
| content::test::PreloadingConfigOverride preloading_config_override; |
| preloading_config_override.SetHoldback( |
| PreloadingType::kPrerender, |
| content_preloading_predictor::kSpeculationRules, true); |
| const GURL kPrerenderingUrl("https://example.com/next"); |
| auto* preloading_data = PreloadingData::GetOrCreateForWebContents(contents()); |
| PreloadingURLMatchCallback same_url_matcher = |
| PreloadingData::GetSameURLMatcher(kPrerenderingUrl); |
| PreloadingAttempt* preloading_attempt = preloading_data->AddPreloadingAttempt( |
| content_preloading_predictor::kSpeculationRules, |
| PreloadingType::kPrerender, std::move(same_url_matcher), |
| contents()->GetPrimaryMainFrame()->GetPageUkmSourceId()); |
| |
| auto attributes = GeneratePrerenderAttributes( |
| kPrerenderingUrl, PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kImmediate, |
| contents()->GetPrimaryMainFrame()); |
| attributes.holdback_status_override = PreloadingHoldbackStatus::kAllowed; |
| |
| const PrerenderHostId prerender_host_id = |
| registry().CreateAndStartHost(attributes, preloading_attempt); |
| ASSERT_TRUE(prerender_host_id); |
| |
| PrerenderHost* prerender_host = |
| registry().FindNonReservedHostById(prerender_host_id); |
| CommitPrerenderNavigation(*prerender_host); |
| |
| contents()->ActivatePrerenderedPage(kPrerenderingUrl); |
| |
| // "Navigation.TimeToActivatePrerender.SpeculationRule" histogram should be |
| // recorded on every prerender activation. |
| histogram_tester().ExpectTotalCount( |
| "Navigation.TimeToActivatePrerender.SpeculationRule", 1u); |
| } |
| |
| TEST_F(PrerenderHostRegistryTest, CreateAndStartHostForSameURL) { |
| const GURL kPrerenderingUrl("https://example.com/next"); |
| |
| const PrerenderHostId prerender_host_id1 = |
| registry().CreateAndStartHost(GeneratePrerenderAttributes( |
| kPrerenderingUrl, PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kImmediate, |
| contents()->GetPrimaryMainFrame())); |
| EXPECT_TRUE(prerender_host_id1); |
| PrerenderHost* prerender_host1 = |
| registry().FindNonReservedHostById(prerender_host_id1); |
| |
| // Start the prerender host for the same URL. This second host should be |
| // ignored, and the first host should still be findable. |
| const PrerenderHostId prerender_host_id2 = |
| registry().CreateAndStartHost(GeneratePrerenderAttributes( |
| kPrerenderingUrl, PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kImmediate, |
| contents()->GetPrimaryMainFrame())); |
| EXPECT_FALSE(prerender_host_id2); |
| EXPECT_EQ(registry().FindHostByUrlForTesting(kPrerenderingUrl), |
| prerender_host1); |
| CommitPrerenderNavigation(*prerender_host1); |
| |
| contents()->ActivatePrerenderedPage(kPrerenderingUrl); |
| } |
| |
| // Tests that PrerenderHostRegistry limits the number of started prerenders |
| // to a specific number, and after once the prerender page was activated, |
| // PrerenderHostRegistry can start prerendering a new one. |
| TEST_F(PrerenderHostRegistryTest, NumberLimit_Activation) { |
| std::vector<PrerenderHostId> prerender_host_ids; |
| std::vector<GURL> prerendering_urls; |
| for (int i = 0; |
| i < |
| PrerenderHostRegistry::kMaxRunningSpeculationRulesImmediatePrerenders + |
| 1; |
| i++) { |
| const GURL prerendering_url("https://example.com/next" + |
| base::NumberToString(i)); |
| PrerenderHostId prerender_host_id = |
| registry().CreateAndStartHost(GeneratePrerenderAttributes( |
| prerendering_url, PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kImmediate, |
| contents()->GetPrimaryMainFrame())); |
| |
| prerender_host_ids.push_back(prerender_host_id); |
| prerendering_urls.push_back(prerendering_url); |
| } |
| |
| // PrerenderHostRegistry should only start prerendering within the limit. |
| for (int i = 0; |
| i < |
| PrerenderHostRegistry::kMaxRunningSpeculationRulesImmediatePrerenders; |
| i++) { |
| EXPECT_TRUE(prerender_host_ids[i]); |
| } |
| EXPECT_FALSE( |
| prerender_host_ids[PrerenderHostRegistry:: |
| kMaxRunningSpeculationRulesImmediatePrerenders]); |
| ExpectUniqueSampleOfSpeculationRuleFinalStatus( |
| PrerenderFinalStatus::kMaxNumOfRunningImmediatePrerendersExceeded); |
| |
| // Activate the first prerender. |
| PrerenderHost* prerender_host = |
| registry().FindHostByUrlForTesting(prerendering_urls[0]); |
| CommitPrerenderNavigation(*prerender_host); |
| contents()->ActivatePrerenderedPage(prerendering_urls[0]); |
| |
| // After the first prerender page was activated, PrerenderHostRegistry can |
| // start prerendering a new one. |
| PrerenderHostId prerender_host_id = |
| registry().CreateAndStartHost(GeneratePrerenderAttributes( |
| prerendering_urls[PrerenderHostRegistry:: |
| kMaxRunningSpeculationRulesImmediatePrerenders], |
| PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kImmediate, |
| contents()->GetPrimaryMainFrame())); |
| EXPECT_TRUE(prerender_host_id); |
| ExpectBucketCountOfSpeculationRuleFinalStatus( |
| PrerenderFinalStatus::kMaxNumOfRunningImmediatePrerendersExceeded); |
| } |
| |
| // Tests that PrerenderHostRegistry limits the number of started prerenders |
| // to a specific number, and new candidates can be processed after the initiator |
| // page navigates to a new same-origin page. |
| TEST_F(PrerenderHostRegistryTest, NumberLimit_SameOriginNavigateAway) { |
| RenderFrameHostImpl* render_frame_host = contents()->GetPrimaryMainFrame(); |
| ASSERT_TRUE(render_frame_host); |
| |
| mojo::Remote<blink::mojom::SpeculationHost> remote1; |
| SpeculationHostImpl::Bind(render_frame_host, |
| remote1.BindNewPipeAndPassReceiver()); |
| ASSERT_TRUE(remote1.is_connected()); |
| |
| std::vector<GURL> prerendering_urls; |
| for (int i = 0; |
| i < |
| PrerenderHostRegistry::kMaxRunningSpeculationRulesImmediatePrerenders + |
| 1; |
| i++) { |
| prerendering_urls.emplace_back("https://example.com/next" + |
| base::NumberToString(i)); |
| } |
| SendCandidates(prerendering_urls, remote1); |
| |
| // PrerenderHostRegistry should only start prerenderings within the limit. |
| for (int i = 0; |
| i < |
| PrerenderHostRegistry::kMaxRunningSpeculationRulesImmediatePrerenders; |
| i++) { |
| ASSERT_NE(registry().FindHostByUrlForTesting(prerendering_urls[i]), |
| nullptr); |
| } |
| ASSERT_EQ(registry().FindHostByUrlForTesting( |
| prerendering_urls |
| [PrerenderHostRegistry:: |
| kMaxRunningSpeculationRulesImmediatePrerenders]), |
| nullptr); |
| ExpectUniqueSampleOfSpeculationRuleFinalStatus( |
| PrerenderFinalStatus::kMaxNumOfRunningImmediatePrerendersExceeded); |
| |
| // The initiator document navigates away. |
| render_frame_host = |
| NavigatePrimaryPage(contents(), GURL("https://example.com/elsewhere")); |
| |
| // After the initiator page navigates away, the started prerendering should be |
| // cancelled, and PrerenderHostRegistry can start prerendering a new one. |
| for (int i = 0; |
| i < |
| PrerenderHostRegistry::kMaxRunningSpeculationRulesImmediatePrerenders + |
| 1; |
| i++) { |
| EXPECT_EQ(registry().FindHostByUrlForTesting(prerendering_urls[i]), |
| nullptr); |
| } |
| mojo::Remote<blink::mojom::SpeculationHost> remote2; |
| SpeculationHostImpl::Bind(render_frame_host, |
| remote2.BindNewPipeAndPassReceiver()); |
| SendCandidate( |
| prerendering_urls[PrerenderHostRegistry:: |
| kMaxRunningSpeculationRulesImmediatePrerenders], |
| remote2); |
| |
| EXPECT_NE(registry().FindHostByUrlForTesting( |
| prerendering_urls |
| [PrerenderHostRegistry:: |
| kMaxRunningSpeculationRulesImmediatePrerenders]), |
| nullptr); |
| ExpectBucketCountOfSpeculationRuleFinalStatus( |
| PrerenderFinalStatus::kMaxNumOfRunningImmediatePrerendersExceeded); |
| } |
| |
| // Tests that PrerenderHostRegistry limits the number of started prerenders |
| // to a specific number, and new candidates can be processed after the initiator |
| // page navigates to a new cross-origin page. |
| TEST_F(PrerenderHostRegistryTest, NumberLimit_CrossOriginNavigateAway) { |
| RenderFrameHostImpl* render_frame_host = contents()->GetPrimaryMainFrame(); |
| ASSERT_TRUE(render_frame_host); |
| |
| mojo::Remote<blink::mojom::SpeculationHost> remote1; |
| SpeculationHostImpl::Bind(render_frame_host, |
| remote1.BindNewPipeAndPassReceiver()); |
| ASSERT_TRUE(remote1.is_connected()); |
| |
| std::vector<GURL> prerendering_urls; |
| for (int i = 0; |
| i < |
| PrerenderHostRegistry::kMaxRunningSpeculationRulesImmediatePrerenders + |
| 1; |
| i++) { |
| prerendering_urls.emplace_back("https://example.com/next" + |
| base::NumberToString(i)); |
| } |
| SendCandidates(prerendering_urls, remote1); |
| |
| // PrerenderHostRegistry should only start prerenderings within the limit. |
| for (int i = 0; |
| i < |
| PrerenderHostRegistry::kMaxRunningSpeculationRulesImmediatePrerenders; |
| i++) { |
| ASSERT_NE(registry().FindHostByUrlForTesting(prerendering_urls[i]), |
| nullptr); |
| } |
| ASSERT_EQ(registry().FindHostByUrlForTesting( |
| prerendering_urls |
| [PrerenderHostRegistry:: |
| kMaxRunningSpeculationRulesImmediatePrerenders]), |
| nullptr); |
| ExpectUniqueSampleOfSpeculationRuleFinalStatus( |
| PrerenderFinalStatus::kMaxNumOfRunningImmediatePrerendersExceeded); |
| |
| // The initiator document navigates away to a cross-origin page. |
| render_frame_host = |
| NavigatePrimaryPage(contents(), GURL("https://example.org/")); |
| |
| // After the initiator page navigates away, the started prerendering should be |
| // cancelled, and PrerenderHostRegistry can start prerendering a new one. |
| for (int i = 0; |
| i < |
| PrerenderHostRegistry::kMaxRunningSpeculationRulesImmediatePrerenders + |
| 1; |
| i++) { |
| EXPECT_EQ(registry().FindHostByUrlForTesting(prerendering_urls[i]), |
| nullptr); |
| } |
| mojo::Remote<blink::mojom::SpeculationHost> remote2; |
| SpeculationHostImpl::Bind(render_frame_host, |
| remote2.BindNewPipeAndPassReceiver()); |
| const GURL prerendering_url("https://example.org/next"); |
| SendCandidate(prerendering_url, remote2); |
| EXPECT_NE(registry().FindHostByUrlForTesting(prerendering_url), nullptr); |
| ExpectBucketCountOfSpeculationRuleFinalStatus( |
| PrerenderFinalStatus::kMaxNumOfRunningImmediatePrerendersExceeded); |
| } |
| |
| class PrerenderHostRegistryLimitGroupTest |
| : public PrerenderHostRegistryTest, |
| public testing::WithParamInterface<bool> { |
| public: |
| using PrerenderLimitGroup = PrerenderHostRegistry::PrerenderLimitGroup; |
| |
| void SetUp() override { |
| feature_list_.InitWithFeatures({base::kStatefulMemoryPressure, |
| blink::features::kPrerender2MemoryControls}, |
| {}); |
| PrerenderHostRegistryTest::SetUp(); |
| } |
| |
| const std::string histogram_suffix = "EmbedderSuffixForTest"; |
| |
| bool IsNewTabTrigger(PrerenderLimitGroup limit_group) { |
| return GetParam() && limit_group != PrerenderLimitGroup::kEmbedder; |
| } |
| |
| PrerenderHostId CreateAndStartHostByLimitGroup( |
| PrerenderLimitGroup limit_group) { |
| static int unique_id = 0; |
| const GURL prerendering_url("https://example.com/next_" + |
| base::NumberToString(unique_id)); |
| unique_id++; |
| auto prerender_attributes = [&] { |
| switch (limit_group) { |
| case PrerenderLimitGroup::kSpeculationRulesImmediate: |
| return GeneratePrerenderAttributes( |
| prerendering_url, PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kImmediate, |
| contents()->GetPrimaryMainFrame()); |
| case PrerenderLimitGroup::kSpeculationRulesNonImmediate: |
| return GeneratePrerenderAttributes( |
| prerendering_url, PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kModerate, |
| contents()->GetPrimaryMainFrame()); |
| case PrerenderLimitGroup::kEmbedder: |
| return GeneratePrerenderAttributes( |
| prerendering_url, PreloadingTriggerType::kEmbedder, |
| histogram_suffix, std::nullopt, nullptr); |
| } |
| }(); |
| |
| PreloadingPredictor embedder_predictor(100, "Embedder"); |
| |
| PreloadingPredictor creating_predictor = [&] { |
| switch (limit_group) { |
| case PrerenderLimitGroup::kSpeculationRulesImmediate: |
| case PrerenderLimitGroup::kSpeculationRulesNonImmediate: |
| return content_preloading_predictor::kSpeculationRules; |
| case PrerenderLimitGroup::kEmbedder: |
| return embedder_predictor; |
| } |
| }(); |
| PreloadingPredictor enacting_predictor = [&] { |
| switch (limit_group) { |
| case PrerenderLimitGroup::kSpeculationRulesImmediate: |
| return content_preloading_predictor::kSpeculationRules; |
| case PrerenderLimitGroup::kSpeculationRulesNonImmediate: |
| // Arbitrarily chosen non-immediate predictor. |
| return preloading_predictor::kUrlPointerDownOnAnchor; |
| case PrerenderLimitGroup::kEmbedder: |
| return embedder_predictor; |
| } |
| }(); |
| |
| return IsNewTabTrigger(limit_group) |
| ? registry().CreateAndStartHostForNewTab( |
| prerender_attributes, creating_predictor, |
| enacting_predictor, PreloadingConfidence{100}) |
| : registry().CreateAndStartHost(prerender_attributes); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| PrerenderHostRegistryLimitGroupTest, |
| testing::Bool()); |
| |
| TEST_P(PrerenderHostRegistryLimitGroupTest, Immediate) { |
| // Starts the immediate prerenders as many times as the specific limit. |
| for (int i = 0; |
| i < |
| PrerenderHostRegistry::kMaxRunningSpeculationRulesImmediatePrerenders; |
| i++) { |
| PrerenderHostId prerender_host_id = CreateAndStartHostByLimitGroup( |
| PrerenderLimitGroup::kSpeculationRulesImmediate); |
| EXPECT_TRUE(prerender_host_id); |
| } |
| |
| // If we try to start immediate prerenders after reaching the limit, that |
| // should be canceled with kMaxNumOfRunningImmediatePrerendersExceeded. |
| PrerenderHostId prerender_host_id_immediate_exceeded = |
| CreateAndStartHostByLimitGroup( |
| PrerenderLimitGroup::kSpeculationRulesImmediate); |
| EXPECT_FALSE(prerender_host_id_immediate_exceeded); |
| ExpectUniqueSampleOfSpeculationRuleFinalStatus( |
| PrerenderFinalStatus::kMaxNumOfRunningImmediatePrerendersExceeded, 1); |
| |
| // On the other hand, prerenders belonging to different limit |
| // group(non-immediate, embedder) can still be started. |
| PrerenderHostId prerender_host_id_non_immediate = |
| CreateAndStartHostByLimitGroup( |
| PrerenderLimitGroup::kSpeculationRulesNonImmediate); |
| PrerenderHostId prerender_host_id_embedder = |
| CreateAndStartHostByLimitGroup(PrerenderLimitGroup::kEmbedder); |
| EXPECT_TRUE(prerender_host_id_non_immediate); |
| EXPECT_TRUE(prerender_host_id_embedder); |
| ExpectUniqueSampleOfSpeculationRuleFinalStatus( |
| PrerenderFinalStatus::kMaxNumOfRunningImmediatePrerendersExceeded, 1); |
| ExpectUniqueSampleOfEmbedderFinalStatus( |
| PrerenderFinalStatus::kMaxNumOfRunningEmbedderPrerendersExceeded, |
| histogram_suffix, 0); |
| } |
| |
| TEST_P(PrerenderHostRegistryLimitGroupTest, NonImmediate) { |
| std::vector<PrerenderHostId> started_prerender_ids; |
| |
| // Starts the non-immediate prerenders as many times as the specific limit. |
| for (int i = 0; |
| i < |
| PrerenderHostRegistry::kMaxRunningSpeculationRulesNonImmediatePrerenders; |
| i++) { |
| PrerenderHostId prerender_host_id = CreateAndStartHostByLimitGroup( |
| PrerenderLimitGroup::kSpeculationRulesNonImmediate); |
| started_prerender_ids.push_back(prerender_host_id); |
| EXPECT_TRUE(prerender_host_id); |
| } |
| |
| // Even after the limit of non-immediate speculation rules is reached, it is |
| // permissible to start a new prerender. Instead, the oldest prerender will be |
| // canceled with kMaxNumOfRunningNonImmediatePrerendersExceeded to make room |
| // for a new one. |
| PrerenderHostId prerender_host_id_non_immediate_exceeded = |
| CreateAndStartHostByLimitGroup( |
| PrerenderLimitGroup::kSpeculationRulesNonImmediate); |
| ASSERT_TRUE(prerender_host_id_non_immediate_exceeded); |
| ExpectUniqueSampleOfSpeculationRuleFinalStatus( |
| PrerenderFinalStatus::kMaxNumOfRunningNonImmediatePrerendersExceeded, 1); |
| |
| for (auto id : started_prerender_ids) { |
| FrameTreeNodeId prerender_frame_tree_node_id = |
| PrerenderHost::GetFrameTreeNodeIdForId(id); |
| auto* web_contents_impl = static_cast<WebContentsImpl*>( |
| WebContents::FromFrameTreeNodeId(prerender_frame_tree_node_id)); |
| PrerenderHost* prerender_host = nullptr; |
| if (web_contents_impl) { |
| prerender_host = web_contents_impl->GetPrerenderHostRegistry() |
| ->FindNonReservedHostById(id); |
| } |
| if (id == started_prerender_ids[0]) { |
| // The oldest prerender has been canceled. |
| EXPECT_EQ(prerender_host, nullptr); |
| } else { |
| EXPECT_NE(prerender_host, nullptr); |
| } |
| } |
| |
| // On the other hand, prerenders belonging to different limit group(immediate, |
| // embedder) can still be started and not invoke cancellation, as these limits |
| // are separated. |
| PrerenderHostId prerender_host_id_immediate = CreateAndStartHostByLimitGroup( |
| PrerenderLimitGroup::kSpeculationRulesImmediate); |
| PrerenderHostId prerender_host_id_embedder = |
| CreateAndStartHostByLimitGroup(PrerenderLimitGroup::kEmbedder); |
| EXPECT_TRUE(prerender_host_id_immediate); |
| EXPECT_TRUE(prerender_host_id_embedder); |
| ExpectUniqueSampleOfSpeculationRuleFinalStatus( |
| PrerenderFinalStatus::kMaxNumOfRunningNonImmediatePrerendersExceeded, 1); |
| ExpectUniqueSampleOfEmbedderFinalStatus( |
| PrerenderFinalStatus::kMaxNumOfRunningEmbedderPrerendersExceeded, |
| histogram_suffix, 0); |
| } |
| |
| TEST_P(PrerenderHostRegistryLimitGroupTest, Embedder) { |
| // Starts the embedder prerenders as many times as the specific limit. |
| const int max_embedder_prerenders = |
| web_contents()->GetDelegate()->AllowedPrerenderingCount(*web_contents()); |
| for (int i = 0; i < max_embedder_prerenders; i++) { |
| PrerenderHostId prerender_host_id = |
| CreateAndStartHostByLimitGroup(PrerenderLimitGroup::kEmbedder); |
| EXPECT_TRUE(prerender_host_id); |
| } |
| |
| // If we try to start embedder prerenders after reaching the limit, that |
| // should be canceled with kMaxNumOfRunningEmbedderPrerendersExceeded. |
| PrerenderHostId prerender_host_id_embedder_exceeded = |
| CreateAndStartHostByLimitGroup(PrerenderLimitGroup::kEmbedder); |
| EXPECT_FALSE(prerender_host_id_embedder_exceeded); |
| ExpectUniqueSampleOfEmbedderFinalStatus( |
| PrerenderFinalStatus::kMaxNumOfRunningEmbedderPrerendersExceeded, |
| histogram_suffix, 1); |
| |
| // On the other hand, prerenders belonging to different limit group(immediate, |
| // non-egaer) can still be started. |
| PrerenderHostId prerender_host_id_immediate = CreateAndStartHostByLimitGroup( |
| PrerenderLimitGroup::kSpeculationRulesImmediate); |
| PrerenderHostId prerender_host_id_non_immediate = |
| CreateAndStartHostByLimitGroup( |
| PrerenderLimitGroup::kSpeculationRulesNonImmediate); |
| EXPECT_TRUE(prerender_host_id_immediate); |
| EXPECT_TRUE(prerender_host_id_non_immediate); |
| ExpectBucketCountOfSpeculationRuleFinalStatus( |
| PrerenderFinalStatus::kMaxNumOfRunningImmediatePrerendersExceeded, 0); |
| ExpectBucketCountOfSpeculationRuleFinalStatus( |
| PrerenderFinalStatus::kMaxNumOfRunningNonImmediatePrerendersExceeded, 0); |
| ExpectUniqueSampleOfEmbedderFinalStatus( |
| PrerenderFinalStatus::kMaxNumOfRunningEmbedderPrerendersExceeded, |
| histogram_suffix, 1); |
| } |
| |
| TEST_P(PrerenderHostRegistryLimitGroupTest, StatefulNumberLimit) { |
| // Set memory limit to 50%. |
| { |
| base::RunLoop run_loop; |
| test_registry_.NotifyUpdateMemoryLimitAsync(50, run_loop.QuitClosure()); |
| run_loop.Run(); |
| } |
| |
| const int default_limit = |
| PrerenderHostRegistry::kMaxRunningSpeculationRulesImmediatePrerenders; |
| const int expected_limit = default_limit / 2; |
| |
| std::vector<PrerenderHostId> prerender_host_ids; |
| |
| for (int i = 0; i < expected_limit + 1; i++) { |
| PrerenderHostId prerender_host_id = CreateAndStartHostByLimitGroup( |
| PrerenderLimitGroup::kSpeculationRulesImmediate); |
| prerender_host_ids.push_back(prerender_host_id); |
| } |
| |
| for (int i = 0; i < expected_limit; i++) { |
| EXPECT_TRUE(prerender_host_ids[i]); |
| if (IsNewTabTrigger(PrerenderLimitGroup::kSpeculationRulesImmediate)) { |
| EXPECT_TRUE( |
| registry().HasNewTabHandleByIdForTesting(prerender_host_ids[i])); |
| } else { |
| EXPECT_TRUE(registry().FindNonReservedHostById(prerender_host_ids[i]) != |
| nullptr); |
| } |
| } |
| EXPECT_FALSE(prerender_host_ids[expected_limit]); |
| EXPECT_EQ(expected_limit, |
| registry().GetHostCountByLimitGroupForTesting( |
| PrerenderLimitGroup::kSpeculationRulesImmediate)); |
| } |
| |
| class PrerenderHostRegistryLegacyMemoryControlsTest |
| : public PrerenderHostRegistryTest { |
| public: |
| void SetUp() override { |
| feature_list_.InitWithFeatures({blink::features::kPrerender2MemoryControls}, |
| {base::kStatefulMemoryPressure}); |
| PrerenderHostRegistryTest::SetUp(); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| TEST_F(PrerenderHostRegistryLegacyMemoryControlsTest, |
| StatefulNumberLimit_Disabled_NonCritical) { |
| const GURL prerendering_url("https://example.com/next"); |
| PrerenderHostId prerender_host_id = |
| registry().CreateAndStartHost(GeneratePrerenderAttributes( |
| prerendering_url, PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kImmediate, |
| contents()->GetPrimaryMainFrame())); |
| EXPECT_TRUE(prerender_host_id); |
| |
| { |
| base::RunLoop run_loop; |
| test_registry_.NotifyUpdateMemoryLimitAsync(80, base::DoNothing()); |
| test_registry_.NotifyReleaseMemoryAsync(run_loop.QuitClosure()); |
| run_loop.Run(); |
| } |
| |
| EXPECT_TRUE(registry().FindNonReservedHostById(prerender_host_id)); |
| } |
| |
| TEST_F(PrerenderHostRegistryLegacyMemoryControlsTest, |
| StatefulNumberLimit_Disabled_Critical) { |
| const GURL prerendering_url("https://example.com/next"); |
| PrerenderHostId prerender_host_id = |
| registry().CreateAndStartHost(GeneratePrerenderAttributes( |
| prerendering_url, PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kImmediate, |
| contents()->GetPrimaryMainFrame())); |
| EXPECT_TRUE(prerender_host_id); |
| |
| // Under the legacy memory pressure system (when kStatefulMemoryPressure is |
| // disabled), critical memory pressure notifications are dispatched with a |
| // simulated memory limit of 0% (<= CriticalPressureThreshold()). |
| { |
| base::RunLoop run_loop; |
| test_registry_.NotifyUpdateMemoryLimitAsync( |
| base::MemoryLimit::CriticalPressureThreshold(), base::DoNothing()); |
| test_registry_.NotifyReleaseMemoryAsync(run_loop.QuitClosure()); |
| run_loop.Run(); |
| } |
| |
| EXPECT_FALSE(registry().FindNonReservedHostById(prerender_host_id)); |
| |
| // Verify that after critical memory pressure cancellation in legacy mode, |
| // future prerenders are not prevented from being created. |
| const GURL prerendering_url2("https://example.com/next2"); |
| PrerenderHostId new_host_id = |
| registry().CreateAndStartHost(GeneratePrerenderAttributes( |
| prerendering_url2, PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kImmediate, |
| contents()->GetPrimaryMainFrame())); |
| EXPECT_TRUE(new_host_id); |
| } |
| |
| TEST_P(PrerenderHostRegistryLimitGroupTest, StatefulNumberLimit_Critical) { |
| const int initial_count = 3; |
| std::vector<PrerenderHostId> prerender_host_ids; |
| for (int i = 0; i < initial_count; i++) { |
| PrerenderHostId prerender_host_id = CreateAndStartHostByLimitGroup( |
| PrerenderLimitGroup::kSpeculationRulesImmediate); |
| prerender_host_ids.push_back(prerender_host_id); |
| } |
| EXPECT_EQ(initial_count, |
| registry().GetHostCountByLimitGroupForTesting( |
| PrerenderLimitGroup::kSpeculationRulesImmediate)); |
| |
| // Under stateful memory pressure (when kStatefulMemoryPressure is enabled), |
| // a critical memory pressure notification (0%) actively evicts all running |
| // hosts AND statefully updates the current memory limit to 0%, preventing |
| // any new prerenders from starting. |
| { |
| base::RunLoop run_loop; |
| test_registry_.NotifyUpdateMemoryLimitAsync(0, base::DoNothing()); |
| test_registry_.NotifyReleaseMemoryAsync(run_loop.QuitClosure()); |
| run_loop.Run(); |
| } |
| |
| EXPECT_EQ(0, registry().GetHostCountByLimitGroupForTesting( |
| PrerenderLimitGroup::kSpeculationRulesImmediate)); |
| |
| // Verify that under stateful memory controls, future prerenders are indeed |
| // prevented from starting while critical memory pressure persists. |
| PrerenderHostId new_host_id = CreateAndStartHostByLimitGroup( |
| PrerenderLimitGroup::kSpeculationRulesImmediate); |
| EXPECT_EQ(PrerenderHostId(), new_host_id); |
| EXPECT_EQ(0, registry().GetHostCountByLimitGroupForTesting( |
| PrerenderLimitGroup::kSpeculationRulesImmediate)); |
| |
| // Reset memory limit back to 100% for subsequent tests. |
| { |
| base::RunLoop run_loop; |
| test_registry_.NotifyUpdateMemoryLimitAsync(100, base::DoNothing()); |
| test_registry_.NotifyReleaseMemoryAsync(run_loop.QuitClosure()); |
| run_loop.Run(); |
| } |
| } |
| |
| TEST_F(PrerenderHostRegistryTest, MemoryControlsDisabled_NoOp) { |
| const GURL prerendering_url("https://example.com/next"); |
| PrerenderHostId prerender_host_id = |
| registry().CreateAndStartHost(GeneratePrerenderAttributes( |
| prerendering_url, PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kImmediate, |
| contents()->GetPrimaryMainFrame())); |
| EXPECT_TRUE(prerender_host_id); |
| |
| // When kPrerender2MemoryControls is disabled, memory pressure notifications |
| // must be completely ignored as a no-op, avoiding CancelAllHosts. |
| { |
| base::RunLoop run_loop; |
| test_registry_.NotifyUpdateMemoryLimitAsync(0, base::DoNothing()); |
| test_registry_.NotifyReleaseMemoryAsync(run_loop.QuitClosure()); |
| run_loop.Run(); |
| } |
| |
| EXPECT_TRUE(registry().FindNonReservedHostById(prerender_host_id)); |
| } |
| |
| TEST_P(PrerenderHostRegistryLimitGroupTest, StatefulActiveEviction) { |
| const int initial_count = 10; |
| std::vector<PrerenderHostId> prerender_host_ids; |
| for (int i = 0; i < initial_count; i++) { |
| PrerenderHostId host_id = CreateAndStartHostByLimitGroup( |
| PrerenderLimitGroup::kSpeculationRulesImmediate); |
| ASSERT_TRUE(host_id); |
| prerender_host_ids.push_back(host_id); |
| } |
| |
| EXPECT_EQ(initial_count, |
| registry().GetHostCountByLimitGroupForTesting( |
| PrerenderLimitGroup::kSpeculationRulesImmediate)); |
| |
| { |
| base::RunLoop run_loop; |
| test_registry_.NotifyUpdateMemoryLimitAsync(50, base::DoNothing()); |
| test_registry_.NotifyReleaseMemoryAsync(run_loop.QuitClosure()); |
| run_loop.Run(); |
| } |
| |
| const int expected_remaining = initial_count / 2; |
| EXPECT_EQ(expected_remaining, |
| registry().GetHostCountByLimitGroupForTesting( |
| PrerenderLimitGroup::kSpeculationRulesImmediate)); |
| |
| // The oldest 5 hosts should remain running. |
| for (int i = 0; i < expected_remaining; i++) { |
| if (IsNewTabTrigger(PrerenderLimitGroup::kSpeculationRulesImmediate)) { |
| EXPECT_TRUE( |
| registry().HasNewTabHandleByIdForTesting(prerender_host_ids[i])); |
| } else { |
| EXPECT_TRUE(registry().FindNonReservedHostById(prerender_host_ids[i])); |
| } |
| } |
| |
| // The newest 5 hosts should be evicted. |
| for (int i = expected_remaining; i < initial_count; i++) { |
| if (IsNewTabTrigger(PrerenderLimitGroup::kSpeculationRulesImmediate)) { |
| EXPECT_FALSE( |
| registry().HasNewTabHandleByIdForTesting(prerender_host_ids[i])); |
| } else { |
| EXPECT_FALSE(registry().FindNonReservedHostById(prerender_host_ids[i])); |
| } |
| } |
| } |
| |
| TEST_P(PrerenderHostRegistryLimitGroupTest, |
| StatefulActiveEviction_MixedTriggers) { |
| if (GetParam()) { |
| // Pending queue logic is tested for standard in-tab prerendering. |
| return; |
| } |
| |
| const int initial_count = 10; |
| std::vector<PrerenderHostId> prerender_host_ids; |
| for (int i = 0; i < initial_count; i++) { |
| const GURL prerendering_url("https://example.com/next_mixed_" + |
| base::NumberToString(i)); |
| auto attributes = GeneratePrerenderAttributes( |
| prerendering_url, PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kImmediate, |
| contents()->GetPrimaryMainFrame()); |
| |
| PrerenderHostId host_id; |
| if (i % 2 == 0) { |
| host_id = registry().CreateAndStartHost(attributes); |
| } else { |
| host_id = registry().CreateAndStartHostForNewTab( |
| attributes, content_preloading_predictor::kSpeculationRules, |
| content_preloading_predictor::kSpeculationRules, |
| PreloadingConfidence{100}); |
| } |
| ASSERT_TRUE(host_id); |
| prerender_host_ids.push_back(host_id); |
| } |
| |
| EXPECT_EQ(initial_count, registry().GetHostCountByLimitGroupForTesting( |
| PrerenderHostRegistry::PrerenderLimitGroup:: |
| kSpeculationRulesImmediate)); |
| |
| { |
| base::RunLoop run_loop; |
| test_registry_.NotifyUpdateMemoryLimitAsync(50, base::DoNothing()); |
| test_registry_.NotifyReleaseMemoryAsync(run_loop.QuitClosure()); |
| run_loop.Run(); |
| } |
| |
| const int expected_remaining = initial_count / 2; |
| EXPECT_EQ(expected_remaining, registry().GetHostCountByLimitGroupForTesting( |
| PrerenderHostRegistry::PrerenderLimitGroup:: |
| kSpeculationRulesImmediate)); |
| |
| // The oldest 5 hosts (indices 0 to 4) should remain running. |
| for (int i = 0; i < expected_remaining; i++) { |
| if (i % 2 == 0) { |
| EXPECT_TRUE(registry().FindNonReservedHostById(prerender_host_ids[i])); |
| } else { |
| EXPECT_TRUE( |
| registry().HasNewTabHandleByIdForTesting(prerender_host_ids[i])); |
| } |
| } |
| |
| // The newest 5 hosts (indices 5 to 9) should be evicted. |
| for (int i = expected_remaining; i < initial_count; i++) { |
| if (i % 2 == 0) { |
| EXPECT_FALSE(registry().FindNonReservedHostById(prerender_host_ids[i])); |
| } else { |
| EXPECT_FALSE( |
| registry().HasNewTabHandleByIdForTesting(prerender_host_ids[i])); |
| } |
| } |
| } |
| |
| TEST_P(PrerenderHostRegistryLimitGroupTest, |
| StatefulActiveEviction_NonImmediate) { |
| PrerenderHostId host1 = CreateAndStartHostByLimitGroup( |
| PrerenderLimitGroup::kSpeculationRulesNonImmediate); |
| ASSERT_TRUE(host1); |
| |
| PrerenderHostId host2 = CreateAndStartHostByLimitGroup( |
| PrerenderLimitGroup::kSpeculationRulesNonImmediate); |
| ASSERT_TRUE(host2); |
| |
| EXPECT_EQ(2, registry().GetHostCountByLimitGroupForTesting( |
| PrerenderLimitGroup::kSpeculationRulesNonImmediate)); |
| |
| // Set memory limit to 50% (scaled limit becomes 2 * 50% = 1). |
| { |
| base::RunLoop run_loop; |
| test_registry_.NotifyUpdateMemoryLimitAsync(50, base::DoNothing()); |
| test_registry_.NotifyReleaseMemoryAsync(run_loop.QuitClosure()); |
| run_loop.Run(); |
| } |
| |
| // Under FIFO eviction for non-immediate prerenders, host1 (oldest) should be |
| // evicted and host2 (newest) should remain running. |
| EXPECT_EQ(1, registry().GetHostCountByLimitGroupForTesting( |
| PrerenderLimitGroup::kSpeculationRulesNonImmediate)); |
| if (IsNewTabTrigger(PrerenderLimitGroup::kSpeculationRulesNonImmediate)) { |
| EXPECT_FALSE(registry().HasNewTabHandleByIdForTesting(host1)); |
| EXPECT_TRUE(registry().HasNewTabHandleByIdForTesting(host2)); |
| } else { |
| EXPECT_FALSE(registry().FindNonReservedHostById(host1)); |
| EXPECT_TRUE(registry().FindNonReservedHostById(host2)); |
| } |
| } |
| |
| struct StatefulPerGroupScalingParams { |
| bool scale_immediate; |
| bool scale_non_immediate; |
| bool scale_embedder; |
| }; |
| |
| class PrerenderHostRegistryStatefulPerGroupTest |
| : public PrerenderHostRegistryTest, |
| public ::testing::WithParamInterface<StatefulPerGroupScalingParams> { |
| public: |
| void SetUp() override { |
| const auto& params = GetParam(); |
| feature_list_.InitWithFeaturesAndParameters( |
| {{base::kStatefulMemoryPressure, |
| {{"PrerenderScaleImmediate", |
| params.scale_immediate ? "true" : "false"}, |
| {"PrerenderScaleNonImmediate", |
| params.scale_non_immediate ? "true" : "false"}, |
| {"PrerenderScaleEmbedder", |
| params.scale_embedder ? "true" : "false"}}}, |
| {blink::features::kPrerender2MemoryControls, {}}}, |
| {}); |
| PrerenderHostRegistryTest::SetUp(); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| TEST_P(PrerenderHostRegistryStatefulPerGroupTest, ScalingBehavior_AllGroups) { |
| const auto& params = GetParam(); |
| |
| // Set memory limit to 50%. |
| { |
| base::RunLoop run_loop; |
| test_registry_.NotifyUpdateMemoryLimitAsync(50, run_loop.QuitClosure()); |
| run_loop.Run(); |
| } |
| |
| // Immediate Group |
| const int expected_immediate = |
| params.scale_immediate |
| ? PrerenderHostRegistry:: |
| kMaxRunningSpeculationRulesImmediatePrerenders / |
| 2 |
| : PrerenderHostRegistry:: |
| kMaxRunningSpeculationRulesImmediatePrerenders; |
| |
| // Non-Immediate Group |
| const int expected_non_immediate = |
| params.scale_non_immediate |
| ? PrerenderHostRegistry:: |
| kMaxRunningSpeculationRulesNonImmediatePrerenders / |
| 2 |
| : PrerenderHostRegistry:: |
| kMaxRunningSpeculationRulesNonImmediatePrerenders; |
| |
| // Embedder Group |
| const int max_embedder = |
| web_contents()->GetDelegate()->AllowedPrerenderingCount(*web_contents()); |
| const int expected_embedder = |
| params.scale_embedder ? max_embedder / 2 : max_embedder; |
| |
| EXPECT_EQ(expected_immediate, registry().GetScaledLimitForTesting( |
| PrerenderHostRegistry::PrerenderLimitGroup:: |
| kSpeculationRulesImmediate)); |
| |
| EXPECT_EQ(expected_non_immediate, |
| registry().GetScaledLimitForTesting( |
| PrerenderHostRegistry::PrerenderLimitGroup:: |
| kSpeculationRulesNonImmediate)); |
| |
| EXPECT_EQ(expected_embedder, |
| registry().GetScaledLimitForTesting( |
| PrerenderHostRegistry::PrerenderLimitGroup::kEmbedder)); |
| } |
| |
| TEST_P(PrerenderHostRegistryStatefulPerGroupTest, |
| CriticalMemoryPressure_SafetyNet) { |
| // Start 1 host of immediate group |
| PrerenderHostId imm_id = |
| registry().CreateAndStartHost(GeneratePrerenderAttributes( |
| GURL("https://example.com/imm"), |
| PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kImmediate, |
| contents()->GetPrimaryMainFrame())); |
| ASSERT_TRUE(imm_id); |
| |
| // Trigger critical memory pressure (0%) |
| { |
| base::RunLoop run_loop; |
| test_registry_.NotifyUpdateMemoryLimitAsync(0, base::DoNothing()); |
| test_registry_.NotifyReleaseMemoryAsync(run_loop.QuitClosure()); |
| run_loop.Run(); |
| } |
| |
| // Verify all hosts are cancelled under critical memory pressure safety net |
| EXPECT_EQ(0, registry().GetHostCountByLimitGroupForTesting( |
| PrerenderHostRegistry::PrerenderLimitGroup:: |
| kSpeculationRulesImmediate)); |
| EXPECT_EQ(0, registry().GetHostCountByLimitGroupForTesting( |
| PrerenderHostRegistry::PrerenderLimitGroup:: |
| kSpeculationRulesNonImmediate)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| AllGroupScalingCombinations, |
| PrerenderHostRegistryStatefulPerGroupTest, |
| ::testing::Values(StatefulPerGroupScalingParams{false, false, false}, |
| StatefulPerGroupScalingParams{true, false, false}, |
| StatefulPerGroupScalingParams{false, true, false}, |
| StatefulPerGroupScalingParams{false, false, true}, |
| StatefulPerGroupScalingParams{true, true, true})); |
| |
| TEST_F(PrerenderHostRegistryTest, |
| ReserveHostToActivateBeforeReadyForActivation) { |
| const GURL original_url = contents()->GetLastCommittedURL(); |
| const GURL kPrerenderingUrl("https://example.com/next"); |
| |
| const PrerenderHostId prerender_host_id = |
| registry().CreateAndStartHost(GeneratePrerenderAttributes( |
| kPrerenderingUrl, PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kImmediate, |
| contents()->GetPrimaryMainFrame())); |
| ASSERT_TRUE(prerender_host_id); |
| PrerenderHost* prerender_host = |
| registry().FindHostByUrlForTesting(kPrerenderingUrl); |
| FrameTreeNode* ftn = |
| FrameTreeNode::From(prerender_host->GetPrerenderedMainFrameHost()); |
| std::unique_ptr<NavigationSimulatorImpl> sim = |
| NavigationSimulatorImpl::CreateFromPendingInFrame(ftn); |
| // Ensure that navigation in prerendering frame tree does not commit and |
| // PrerenderHost doesn't become ready for activation. |
| sim->SetAutoAdvance(false); |
| |
| EXPECT_FALSE(prerender_host->is_ready_for_activation()); |
| |
| test::PrerenderHostObserver prerender_host_observer(*contents(), |
| kPrerenderingUrl); |
| |
| // Start activation. |
| std::unique_ptr<NavigationSimulatorImpl> navigation = |
| CreateActivation(kPrerenderingUrl, *contents()); |
| navigation->Start(); |
| |
| // Wait until PrerenderCommitDeferringCondition runs. |
| // TODO(nhiroki): Avoid using base::RunUntilIdle() and instead use some |
| // explicit signal of the running condition. |
| base::RunLoop().RunUntilIdle(); |
| |
| // The activation should be deferred by PrerenderCommitDeferringCondition |
| // until the main frame navigation in the prerendering frame tree finishes. |
| NavigationRequest* navigation_request = navigation->GetNavigationHandle(); |
| EXPECT_TRUE( |
| navigation_request->IsCommitDeferringConditionDeferredForTesting()); |
| EXPECT_FALSE(prerender_host_observer.was_activated()); |
| EXPECT_EQ(contents()->GetPrimaryMainFrame()->GetLastCommittedURL(), |
| original_url); |
| |
| // Finish the main frame navigation. |
| sim->Commit(); |
| |
| // Finish the activation. |
| prerender_host_observer.WaitForDestroyed(); |
| EXPECT_TRUE(prerender_host_observer.was_activated()); |
| EXPECT_EQ(registry().FindHostByUrlForTesting(kPrerenderingUrl), nullptr); |
| EXPECT_EQ(contents()->GetPrimaryMainFrame()->GetLastCommittedURL(), |
| kPrerenderingUrl); |
| } |
| |
| TEST_F(PrerenderHostRegistryTest, CancelHost) { |
| const GURL kPrerenderingUrl("https://example.com/next"); |
| const PrerenderHostId prerender_host_id = |
| registry().CreateAndStartHost(GeneratePrerenderAttributes( |
| kPrerenderingUrl, PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kImmediate, |
| contents()->GetPrimaryMainFrame())); |
| ASSERT_TRUE(prerender_host_id); |
| EXPECT_NE(registry().FindHostByUrlForTesting(kPrerenderingUrl), nullptr); |
| |
| registry().CancelHost(prerender_host_id, PrerenderFinalStatus::kDestroyed); |
| EXPECT_EQ(registry().FindHostByUrlForTesting(kPrerenderingUrl), nullptr); |
| } |
| |
| // Test cancelling a prerender while a CommitDeferringCondition is running. |
| // This activation should fall back to a regular navigation. |
| TEST_F(PrerenderHostRegistryTest, |
| CancelHostWhileCommitDeferringConditionIsRunning) { |
| const GURL original_url = contents()->GetLastCommittedURL(); |
| |
| // Start prerendering. |
| const GURL kPrerenderingUrl("https://example.com/next"); |
| const PrerenderHostId prerender_host_id = |
| registry().CreateAndStartHost(GeneratePrerenderAttributes( |
| kPrerenderingUrl, PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kImmediate, |
| contents()->GetPrimaryMainFrame())); |
| ASSERT_TRUE(prerender_host_id); |
| PrerenderHost* prerender_host = |
| registry().FindHostByUrlForTesting(kPrerenderingUrl); |
| CommitPrerenderNavigation(*prerender_host); |
| |
| test::PrerenderHostObserver prerender_host_observer(*contents(), |
| kPrerenderingUrl); |
| |
| // Now navigate the primary page to the prerendered URL so that we activate |
| // the prerender. Use a CommitDeferringCondition to pause activation |
| // before it completes. |
| std::unique_ptr<NavigationSimulatorImpl> navigation; |
| |
| { |
| MockCommitDeferringConditionInstaller installer( |
| kPrerenderingUrl, CommitDeferringCondition::Result::kDefer); |
| // Start trying to activate the prerendered page. |
| navigation = CreateActivation(kPrerenderingUrl, *contents()); |
| navigation->Start(); |
| |
| // Wait for the condition to pause the activation. |
| installer.WaitUntilInstalled(); |
| installer.condition().WaitUntilInvoked(); |
| |
| // The request should be deferred by the condition. |
| auto* navigation_request = |
| static_cast<NavigationRequest*>(navigation->GetNavigationHandle()); |
| EXPECT_TRUE( |
| navigation_request->IsCommitDeferringConditionDeferredForTesting()); |
| |
| // The primary page should still be the original page. |
| EXPECT_EQ(contents()->GetLastCommittedURL(), original_url); |
| |
| // Cancel the prerender while the CommitDeferringCondition is running. |
| registry().CancelHost(prerender_host_id, PrerenderFinalStatus::kDestroyed); |
| prerender_host_observer.WaitForDestroyed(); |
| EXPECT_FALSE(prerender_host_observer.was_activated()); |
| EXPECT_EQ(registry().FindHostByUrlForTesting(kPrerenderingUrl), nullptr); |
| |
| // Resume the activation. This should fall back to a regular navigation. |
| installer.condition().CallResumeClosure(); |
| } |
| |
| navigation->Commit(); |
| EXPECT_EQ(contents()->GetPrimaryMainFrame()->GetLastCommittedURL(), |
| kPrerenderingUrl); |
| } |
| |
| // Test cancelling a prerender and then starting a new prerender for the same |
| // URL while a CommitDeferringCondition is running. This activation should not |
| // reserve the second prerender and should fall back to a regular navigation. |
| TEST_F(PrerenderHostRegistryTest, |
| CancelAndStartHostWhileCommitDeferringConditionIsRunning) { |
| const GURL original_url = contents()->GetLastCommittedURL(); |
| const GURL kPrerenderingUrl("https://example.com/next"); |
| |
| const PrerenderHostId prerender_host_id = |
| registry().CreateAndStartHost(GeneratePrerenderAttributes( |
| kPrerenderingUrl, PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kImmediate, |
| contents()->GetPrimaryMainFrame())); |
| ASSERT_TRUE(prerender_host_id); |
| PrerenderHost* prerender_host = |
| registry().FindHostByUrlForTesting(kPrerenderingUrl); |
| CommitPrerenderNavigation(*prerender_host); |
| |
| test::PrerenderHostObserver prerender_host_observer(*contents(), |
| kPrerenderingUrl); |
| |
| // Now navigate the primary page to the prerendered URL so that we activate |
| // the prerender. Use a CommitDeferringCondition to pause activation |
| // before it completes. |
| std::unique_ptr<NavigationSimulatorImpl> navigation; |
| base::OnceClosure resume_navigation; |
| |
| { |
| MockCommitDeferringConditionInstaller installer( |
| kPrerenderingUrl, CommitDeferringCondition::Result::kDefer); |
| // Start trying to activate the prerendered page. |
| navigation = CreateActivation(kPrerenderingUrl, *contents()); |
| navigation->Start(); |
| |
| // Wait for the condition to pause the activation. |
| installer.WaitUntilInstalled(); |
| installer.condition().WaitUntilInvoked(); |
| resume_navigation = installer.condition().TakeResumeClosure(); |
| |
| // The request should be deferred by the condition. |
| auto* navigation_request = |
| static_cast<NavigationRequest*>(navigation->GetNavigationHandle()); |
| EXPECT_TRUE( |
| navigation_request->IsCommitDeferringConditionDeferredForTesting()); |
| |
| // The primary page should still be the original page. |
| EXPECT_EQ(contents()->GetLastCommittedURL(), original_url); |
| |
| // Cancel the prerender while the CommitDeferringCondition is running. |
| registry().CancelHost(prerender_host_id, PrerenderFinalStatus::kDestroyed); |
| prerender_host_observer.WaitForDestroyed(); |
| EXPECT_FALSE(prerender_host_observer.was_activated()); |
| EXPECT_EQ(registry().FindHostByUrlForTesting(kPrerenderingUrl), nullptr); |
| } |
| |
| { |
| // Start the second prerender for the same URL. |
| const PrerenderHostId prerender_host_id2 = |
| registry().CreateAndStartHost(GeneratePrerenderAttributes( |
| kPrerenderingUrl, PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kImmediate, |
| contents()->GetPrimaryMainFrame())); |
| ASSERT_TRUE(prerender_host_id2); |
| PrerenderHost* prerender_host2 = |
| registry().FindHostByUrlForTesting(kPrerenderingUrl); |
| CommitPrerenderNavigation(*prerender_host2); |
| |
| EXPECT_NE(prerender_host_id, prerender_host_id2); |
| } |
| |
| // Resume the initial activation. This should not reserve the second |
| // prerender and should fall back to a regular navigation. |
| std::move(resume_navigation).Run(); |
| navigation->Commit(); |
| EXPECT_EQ(contents()->GetPrimaryMainFrame()->GetLastCommittedURL(), |
| kPrerenderingUrl); |
| |
| // The second prerender should still exist. |
| EXPECT_NE(registry().FindHostByUrlForTesting(kPrerenderingUrl), nullptr); |
| } |
| |
| // Tests that prerendering should be canceled if the trigger is in the |
| // background and its type is kEmbedder. |
| // For the case where the trigger type is speculation rules, |
| // browsertests `TestSequentialPrerenderingInBackground` covers it. |
| TEST_F(PrerenderHostRegistryTest, |
| DontStartPrerenderWhenEmbedderTriggerIsAlreadyHidden) { |
| // The visibility state to be HIDDEN will cause prerendering not started when |
| // trigger type is kEmbedder. |
| contents()->WasHidden(); |
| |
| const GURL kPrerenderingUrl = GURL("https://example.com/empty.html"); |
| RenderFrameHostImpl* initiator_rfh = contents()->GetPrimaryMainFrame(); |
| PrerenderHostId prerender_host_id = |
| registry().CreateAndStartHost(GeneratePrerenderAttributes( |
| kPrerenderingUrl, PreloadingTriggerType::kEmbedder, "DirectURLInput", |
| std::nullopt, initiator_rfh)); |
| EXPECT_FALSE(prerender_host_id); |
| PrerenderHost* prerender_host = |
| registry().FindNonReservedHostById(prerender_host_id); |
| EXPECT_EQ(prerender_host, nullptr); |
| histogram_tester().ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_DirectURLInput", |
| PrerenderFinalStatus::kTriggerBackgrounded, 1u); |
| } |
| |
| // ------------------------------------------------- |
| // Activation navigation parameter matching unit tests. |
| // These tests change a parameter to differentiate the activation request from |
| // the prerendering request. |
| |
| // A positive test to show that if the navigation params are equal then the |
| // prerender host is selected for activation. |
| TEST_F(PrerenderHostRegistryTest, SameInitialAndActivationParams) { |
| EXPECT_TRUE(CheckIsActivatedForParams( |
| base::BindLambdaForTesting([](NavigationSimulatorImpl* navigation) { |
| // Do not change any params, so activation happens. |
| }))); |
| ExpectUniqueSampleOfActivationNavigationParamsMatch( |
| PrerenderHost::ActivationNavigationParamsMatch::kOk); |
| } |
| |
| TEST_F(PrerenderHostRegistryTest, |
| CompareInitialAndActivationBeginParams_InitiatorFrameToken) { |
| EXPECT_FALSE(CheckIsActivatedForParams( |
| base::BindLambdaForTesting([](NavigationSimulatorImpl* navigation) { |
| const GURL kOriginalUrl("https://example.com/"); |
| navigation->SetInitiatorFrame(nullptr); |
| navigation->set_initiator_origin(url::Origin::Create(kOriginalUrl)); |
| }))); |
| ExpectUniqueSampleOfActivationNavigationParamsMatch( |
| PrerenderHost::ActivationNavigationParamsMatch::kInitiatorFrameToken); |
| } |
| |
| TEST_F(PrerenderHostRegistryTest, |
| CompareInitialAndActivationBeginParams_Headers) { |
| EXPECT_FALSE(CheckIsActivatedForParams( |
| base::BindLambdaForTesting([](NavigationSimulatorImpl* navigation) { |
| navigation->set_request_headers("User-Agent: Test"); |
| }))); |
| ExpectUniqueSampleOfActivationNavigationParamsMatch( |
| PrerenderHost::ActivationNavigationParamsMatch::kHttpRequestHeader); |
| } |
| |
| // Tests that the Purpose header is ignored when comparing request headers. |
| TEST_F(PrerenderHostRegistryTest, PurposeHeaderIsIgnoredForParamMatching) { |
| EXPECT_TRUE(CheckIsActivatedForParams( |
| base::BindLambdaForTesting([](NavigationSimulatorImpl* navigation) { |
| navigation->set_request_headers("Sec-Purpose: Test"); |
| }))); |
| ExpectUniqueSampleOfActivationNavigationParamsMatch( |
| PrerenderHost::ActivationNavigationParamsMatch::kOk); |
| } |
| |
| TEST_F(PrerenderHostRegistryTest, |
| CompareInitialAndActivationBeginParams_LoadFlags) { |
| EXPECT_FALSE(CheckIsActivatedForParams( |
| base::BindLambdaForTesting([](NavigationSimulatorImpl* navigation) { |
| navigation->set_load_flags(net::LOAD_ONLY_FROM_CACHE); |
| }))); |
| ExpectUniqueSampleOfActivationNavigationParamsMatch( |
| PrerenderHost::ActivationNavigationParamsMatch::kLoadFlags); |
| |
| // If the potential activation request requires validation or bypass of the |
| // browser cache, the prerendered page should not be activated. |
| EXPECT_FALSE(CheckIsActivatedForParams( |
| base::BindLambdaForTesting([](NavigationSimulatorImpl* navigation) { |
| navigation->set_load_flags(net::LOAD_VALIDATE_CACHE); |
| }))); |
| EXPECT_FALSE(CheckIsActivatedForParams( |
| base::BindLambdaForTesting([](NavigationSimulatorImpl* navigation) { |
| navigation->set_load_flags(net::LOAD_BYPASS_CACHE); |
| }))); |
| EXPECT_FALSE(CheckIsActivatedForParams( |
| base::BindLambdaForTesting([](NavigationSimulatorImpl* navigation) { |
| navigation->set_load_flags(net::LOAD_DISABLE_CACHE); |
| }))); |
| ExpectBucketCountOfActivationNavigationParamsMatch( |
| PrerenderHost::ActivationNavigationParamsMatch::kCacheLoadFlags, 3); |
| } |
| |
| TEST_F(PrerenderHostRegistryTest, |
| CompareInitialAndActivationBeginParams_SkipServiceWorker) { |
| EXPECT_FALSE(CheckIsActivatedForParams( |
| base::BindLambdaForTesting([](NavigationSimulatorImpl* navigation) { |
| navigation->set_skip_service_worker(true); |
| }))); |
| ExpectUniqueSampleOfActivationNavigationParamsMatch( |
| PrerenderHost::ActivationNavigationParamsMatch::kSkipServiceWorker); |
| } |
| |
| TEST_F(PrerenderHostRegistryTest, |
| CompareInitialAndActivationBeginParams_MixedContentContextType) { |
| EXPECT_FALSE(CheckIsActivatedForParams( |
| base::BindLambdaForTesting([](NavigationSimulatorImpl* navigation) { |
| navigation->set_mixed_content_context_type( |
| blink::mojom::MixedContentContextType::kNotMixedContent); |
| }))); |
| ExpectUniqueSampleOfActivationNavigationParamsMatch( |
| PrerenderHost::ActivationNavigationParamsMatch::kMixedContentContextType); |
| } |
| |
| TEST_F(PrerenderHostRegistryTest, |
| CompareInitialAndActivationBeginParams_IsFormSubmission) { |
| EXPECT_FALSE(CheckIsActivatedForParams( |
| base::BindLambdaForTesting([](NavigationSimulatorImpl* navigation) { |
| navigation->SetIsFormSubmission(true); |
| }))); |
| ExpectUniqueSampleOfActivationNavigationParamsMatch( |
| PrerenderHost::ActivationNavigationParamsMatch::kIsFormSubmission); |
| } |
| |
| TEST_F(PrerenderHostRegistryTest, |
| CompareInitialAndActivationBeginParams_SearchableFormUrl) { |
| EXPECT_FALSE(CheckIsActivatedForParams( |
| base::BindLambdaForTesting([](NavigationSimulatorImpl* navigation) { |
| const GURL kOriginalUrl("https://example.com/"); |
| navigation->set_searchable_form_url(kOriginalUrl); |
| }))); |
| ExpectUniqueSampleOfActivationNavigationParamsMatch( |
| PrerenderHost::ActivationNavigationParamsMatch::kSearchableFormUrl); |
| } |
| |
| TEST_F(PrerenderHostRegistryTest, |
| CompareInitialAndActivationBeginParams_SearchableFormEncoding) { |
| EXPECT_FALSE(CheckIsActivatedForParams( |
| base::BindLambdaForTesting([](NavigationSimulatorImpl* navigation) { |
| navigation->set_searchable_form_encoding("Test encoding"); |
| }))); |
| ExpectUniqueSampleOfActivationNavigationParamsMatch( |
| PrerenderHost::ActivationNavigationParamsMatch::kSearchableFormEncoding); |
| } |
| |
| TEST_F(PrerenderHostRegistryTest, |
| CompareInitialAndActivationCommonParams_InitiatorOrigin) { |
| EXPECT_FALSE(CheckIsActivatedForParams( |
| base::BindLambdaForTesting([](NavigationSimulatorImpl* navigation) { |
| navigation->set_initiator_origin(url::Origin()); |
| }))); |
| ExpectUniqueSampleOfActivationNavigationParamsMatch( |
| PrerenderHost::ActivationNavigationParamsMatch::kInitiatorOrigin); |
| } |
| |
| TEST_F(PrerenderHostRegistryTest, |
| CompareInitialAndActivationCommonParams_ShouldNotCheckMainWorldCSP) { |
| // Initial navigation blocked by the main world CSP cancels prerendering. |
| // So, it's safe to match the page for CSP bypassing requests from isolated |
| // worlds (e.g., extensions). |
| EXPECT_TRUE(CheckIsActivatedForParams( |
| base::BindLambdaForTesting([](NavigationSimulatorImpl* navigation) { |
| navigation->set_should_check_main_world_csp( |
| network::mojom::CSPDisposition::DO_NOT_CHECK); |
| }))); |
| ExpectUniqueSampleOfActivationNavigationParamsMatch( |
| PrerenderHost::ActivationNavigationParamsMatch::kOk); |
| } |
| |
| TEST_F(PrerenderHostRegistryTest, |
| CompareInitialAndActivationCommonParams_Method) { |
| EXPECT_FALSE(CheckIsActivatedForParams( |
| base::BindLambdaForTesting([](NavigationSimulatorImpl* navigation) { |
| navigation->SetMethod("POST"); |
| }))); |
| // The method parameter change is detected as a HTTP request header change. |
| ExpectUniqueSampleOfActivationNavigationParamsMatch( |
| PrerenderHost::ActivationNavigationParamsMatch::kHttpRequestHeader); |
| } |
| |
| TEST_F(PrerenderHostRegistryTest, |
| CompareInitialAndActivationCommonParams_HrefTranslate) { |
| EXPECT_FALSE(CheckIsActivatedForParams( |
| base::BindLambdaForTesting([](NavigationSimulatorImpl* navigation) { |
| navigation->set_href_translate("test"); |
| }))); |
| ExpectUniqueSampleOfActivationNavigationParamsMatch( |
| PrerenderHost::ActivationNavigationParamsMatch::kHrefTranslate); |
| } |
| |
| TEST_F(PrerenderHostRegistryTest, |
| CompareInitialAndActivationCommonParams_Transition) { |
| EXPECT_FALSE(CheckIsActivatedForParams( |
| base::BindLambdaForTesting([](NavigationSimulatorImpl* navigation) { |
| navigation->SetTransition(ui::PAGE_TRANSITION_FORM_SUBMIT); |
| }))); |
| ExpectUniqueSampleOfActivationNavigationParamsMatch( |
| PrerenderHost::ActivationNavigationParamsMatch::kTransition); |
| |
| histogram_tester().ExpectUniqueSample( |
| "Prerender.Experimental.ActivationTransitionMismatch.SpeculationRule", |
| ui::PAGE_TRANSITION_FORM_SUBMIT, 1); |
| } |
| |
| TEST_F(PrerenderHostRegistryTest, |
| CompareInitialAndActivationCommonParams_RequestContextType) { |
| EXPECT_FALSE(CheckIsActivatedForParams( |
| base::BindLambdaForTesting([](NavigationSimulatorImpl* navigation) { |
| navigation->set_request_context_type( |
| blink::mojom::RequestContextType::AUDIO); |
| }))); |
| ExpectUniqueSampleOfActivationNavigationParamsMatch( |
| PrerenderHost::ActivationNavigationParamsMatch::kRequestContextType); |
| } |
| |
| TEST_F(PrerenderHostRegistryTest, |
| CompareInitialAndActivationCommonParams_ReferrerPolicy) { |
| EXPECT_TRUE(CheckIsActivatedForParams( |
| base::BindLambdaForTesting([&](NavigationSimulatorImpl* navigation) { |
| navigation->SetReferrer(blink::mojom::Referrer::New( |
| contents()->GetPrimaryMainFrame()->GetLastCommittedURL(), |
| network::mojom::ReferrerPolicy::kAlways)); |
| }))); |
| ExpectUniqueSampleOfActivationNavigationParamsMatch( |
| PrerenderHost::ActivationNavigationParamsMatch::kOk); |
| } |
| |
| // End navigation parameter matching tests --------- |
| |
| // Begin replication state matching tests ---------- |
| |
| TEST_F(PrerenderHostRegistryTest, InsecureRequestPolicyIsSetWhilePrerendering) { |
| SetupPrerenderAndCommit( |
| base::BindLambdaForTesting([](NavigationSimulatorImpl* navigation) { |
| navigation->set_insecure_request_policy( |
| blink::mojom::InsecureRequestPolicy::kBlockAllMixedContent); |
| })); |
| EXPECT_EQ(contents() |
| ->GetPrimaryMainFrame() |
| ->frame_tree_node() |
| ->current_replication_state() |
| .insecure_request_policy, |
| blink::mojom::InsecureRequestPolicy::kBlockAllMixedContent); |
| } |
| |
| TEST_F(PrerenderHostRegistryTest, |
| InsecureNavigationsSetIsSetWhilePrerendering) { |
| SetupPrerenderAndCommit( |
| base::BindLambdaForTesting([](NavigationSimulatorImpl* navigation) { |
| const std::vector<uint32_t> insecure_navigations = {1, 2}; |
| navigation->set_insecure_navigations_set(insecure_navigations); |
| })); |
| const std::vector<uint32_t> insecure_navigations = {1, 2}; |
| EXPECT_EQ(contents() |
| ->GetPrimaryMainFrame() |
| ->frame_tree_node() |
| ->current_replication_state() |
| .insecure_navigations_set, |
| insecure_navigations); |
| } |
| |
| // End replication state matching tests ------------ |
| |
| TEST_F(PrerenderHostRegistryTest, OneTaskToDeleteAllHosts) { |
| std::vector<PrerenderHostId> prerender_host_ids; |
| std::vector<std::unique_ptr<test::PrerenderHostObserver>> |
| prerender_host_observers; |
| |
| for (int i = 0; i < 2; i++) { |
| const GURL prerendering_url("https://example.com/next" + |
| base::NumberToString(i)); |
| PrerenderHostId prerender_host_id = |
| registry().CreateAndStartHost(GeneratePrerenderAttributes( |
| prerendering_url, PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kImmediate, |
| contents()->GetPrimaryMainFrame())); |
| |
| prerender_host_observers.emplace_back( |
| std::make_unique<test::PrerenderHostObserver>(*contents(), |
| prerender_host_id)); |
| prerender_host_ids.push_back(prerender_host_id); |
| } |
| int pending_task_before_posting_abandon_task = |
| task_environment()->GetPendingMainThreadTaskCount(); |
| registry().CancelHosts( |
| prerender_host_ids, |
| PrerenderCancellationReason(PrerenderFinalStatus::kDestroyed)); |
| int pending_task_after_posting_abandon_task = |
| task_environment()->GetPendingMainThreadTaskCount(); |
| // Only one task was posted. |
| EXPECT_EQ(pending_task_before_posting_abandon_task + 1, |
| pending_task_after_posting_abandon_task); |
| for (auto& observer : prerender_host_observers) { |
| // All PrerenderHosts were deleted, so it should not timeout. |
| observer->WaitForDestroyed(); |
| } |
| } |
| |
| TEST_F(PrerenderHostRegistryTest, DisallowPageHavingEffectiveUrl_TriggerUrl) { |
| const GURL original_url = contents()->GetLastCommittedURL(); |
| const GURL kModifiedSiteUrl("custom-scheme://custom"); |
| |
| // Let the trigger's URL have the effective URL. |
| EffectiveURLContentBrowserClient modified_client( |
| original_url, kModifiedSiteUrl, |
| /*requires_dedicated_process=*/false); |
| ContentBrowserClient* old_client = |
| SetBrowserClientForTesting(&modified_client); |
| |
| // Start prerendering. This should fail as the initiator's URL has the |
| // effective URL. |
| const GURL kPrerenderingUrl("https://example.com/empty.html"); |
| const PrerenderHostId prerender_host_id = |
| registry().CreateAndStartHost(GeneratePrerenderAttributes( |
| kPrerenderingUrl, PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kImmediate, |
| contents()->GetPrimaryMainFrame())); |
| EXPECT_FALSE(prerender_host_id); |
| PrerenderHost* prerender_host = |
| registry().FindNonReservedHostById(prerender_host_id); |
| EXPECT_EQ(prerender_host, nullptr); |
| ExpectUniqueSampleOfSpeculationRuleFinalStatus( |
| PrerenderFinalStatus::kTriggerUrlHasEffectiveUrl); |
| |
| SetBrowserClientForTesting(old_client); |
| } |
| |
| TEST_F(PrerenderHostRegistryTest, |
| DisallowPageHavingEffectiveUrl_PrerenderingUrl) { |
| const GURL original_url = contents()->GetLastCommittedURL(); |
| const GURL kPrerenderingUrl("https://example.com/empty.html"); |
| const GURL kModifiedSiteUrl("custom-scheme://custom"); |
| |
| // Let the prerendering URL have the effective URL. |
| EffectiveURLContentBrowserClient modified_client( |
| kPrerenderingUrl, kModifiedSiteUrl, |
| /*requires_dedicated_process=*/false); |
| ContentBrowserClient* old_client = |
| SetBrowserClientForTesting(&modified_client); |
| |
| // Start prerendering. This should fail as the prerendering URL has the |
| // effective URL. |
| const PrerenderHostId prerender_host_id = |
| registry().CreateAndStartHost(GeneratePrerenderAttributes( |
| kPrerenderingUrl, PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kImmediate, |
| contents()->GetPrimaryMainFrame())); |
| EXPECT_FALSE(prerender_host_id); |
| PrerenderHost* prerender_host = |
| registry().FindNonReservedHostById(prerender_host_id); |
| EXPECT_EQ(prerender_host, nullptr); |
| ExpectUniqueSampleOfSpeculationRuleFinalStatus( |
| PrerenderFinalStatus::kPrerenderingUrlHasEffectiveUrl); |
| |
| SetBrowserClientForTesting(old_client); |
| } |
| |
| TEST_F(PrerenderHostRegistryTest, |
| DisallowPageHavingEffectiveUrl_ActivationUrl) { |
| const GURL original_url = contents()->GetLastCommittedURL(); |
| const GURL kPrerenderingUrl("https://example.com/empty.html"); |
| const GURL kModifiedSiteUrl("custom-scheme://custom"); |
| |
| // Start prerendering. |
| const PrerenderHostId prerender_host_id = |
| registry().CreateAndStartHost(GeneratePrerenderAttributes( |
| kPrerenderingUrl, PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kImmediate, |
| contents()->GetPrimaryMainFrame())); |
| ASSERT_TRUE(prerender_host_id); |
| PrerenderHost* prerender_host = |
| registry().FindHostByUrlForTesting(kPrerenderingUrl); |
| CommitPrerenderNavigation(*prerender_host); |
| |
| // Let the prerendering URL have the effective URL after prerendering. |
| EffectiveURLContentBrowserClient modified_client( |
| kPrerenderingUrl, kModifiedSiteUrl, |
| /*requires_dedicated_process=*/false); |
| ContentBrowserClient* old_client = |
| SetBrowserClientForTesting(&modified_client); |
| |
| // Navigate the primary page to the prerendering URL that has the effective |
| // URL. This should fail to activate the prerendered page. |
| contents()->NavigateAndCommit(kPrerenderingUrl); |
| |
| ExpectUniqueSampleOfSpeculationRuleFinalStatus( |
| PrerenderFinalStatus::kActivationUrlHasEffectiveUrl); |
| |
| SetBrowserClientForTesting(old_client); |
| } |
| |
| TEST_F(PrerenderHostRegistryTest, PotentialPrerenderProcessReuseUMA) { |
| const GURL kPrerenderingUrl("https://example.com/next"); |
| // Start prerendering. |
| const PrerenderHostId prerender_host_id = |
| registry().CreateAndStartHost(GeneratePrerenderAttributes( |
| kPrerenderingUrl, PreloadingTriggerType::kSpeculationRule, "", |
| blink::mojom::SpeculationEagerness::kImmediate, |
| contents()->GetPrimaryMainFrame())); |
| ASSERT_TRUE(prerender_host_id); |
| PrerenderHost* prerender_host = |
| registry().FindHostByUrlForTesting(kPrerenderingUrl); |
| CommitPrerenderNavigation(*prerender_host); |
| |
| { |
| base::HistogramTester histogram_tester; |
| auto navigation = CreateActivation(kPrerenderingUrl, *contents()); |
| navigation->Start(); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderProcessReuseAvailability", |
| PrerenderHostRegistry::PrerenderProcessReuseAvailability:: |
| kHasMatchableHosts, |
| 1); |
| } |
| { |
| base::HistogramTester histogram_tester; |
| const GURL kSameSiteUrl("https://example.com/other"); |
| auto navigation = CreateActivation(kSameSiteUrl, *contents()); |
| navigation->Start(); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderProcessReuseAvailability", |
| PrerenderHostRegistry::PrerenderProcessReuseAvailability:: |
| kHasSameOriginHosts, |
| 1); |
| } |
| { |
| base::HistogramTester histogram_tester; |
| const GURL kSameSiteUrl("https://www.example.com:8000/other"); |
| auto navigation = CreateActivation(kSameSiteUrl, *contents()); |
| navigation->Start(); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderProcessReuseAvailability", |
| PrerenderHostRegistry::PrerenderProcessReuseAvailability:: |
| kHasSameSiteHosts, |
| 1); |
| } |
| { |
| base::HistogramTester histogram_tester; |
| const GURL kNotSameSiteUrl("https://another.com/other"); |
| auto navigation = CreateActivation(kNotSameSiteUrl, *contents()); |
| navigation->Start(); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderProcessReuseAvailability", |
| PrerenderHostRegistry::PrerenderProcessReuseAvailability:: |
| kNoSameOriginOrSiteHosts, |
| 1); |
| } |
| } |
| |
| } // namespace |
| } // namespace content |