| // 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 <string> |
| |
| #include "base/files/file_path.h" |
| #include "base/functional/bind.h" |
| #include "base/path_service.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/timer/elapsed_timer.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/preloading/chrome_preloading.h" |
| #include "chrome/browser/preloading/preloading_prefs.h" |
| #include "chrome/browser/preloading/prerender/prerender_manager.h" |
| #include "chrome/browser/preloading/prerender/prerender_utils.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "chrome/common/webui_url_constants.h" |
| #include "chrome/test/base/chrome_test_utils.h" |
| #include "chrome/test/base/platform_browser_test.h" |
| #include "components/page_load_metrics/browser/navigation_handle_user_data.h" |
| #include "components/prefs/pref_service.h" |
| #include "content/public/browser/devtools_agent_host.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/preloading_test_util.h" |
| #include "content/public/test/prerender_test_util.h" |
| #include "content/public/test/test_navigation_observer.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "services/metrics/public/cpp/ukm_builders.h" |
| #include "services/metrics/public/cpp/ukm_recorder.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom.h" |
| |
| namespace { |
| |
| namespace { |
| |
| // Following definitions are equal to content::PrerenderFinalStatus. |
| constexpr int kFinalStatusActivated = 0; |
| constexpr int kFinalStatusInvalidSchemeNavigation = 6; |
| constexpr int kFinalStatusTriggerDestroyed = 16; |
| constexpr int kFinalStatusTabClosedWithoutUserGesture = 55; |
| constexpr int kFinalStatusCrossSiteNavigationInMainFrameNavigation = 64; |
| |
| } // namespace |
| |
| using UkmEntry = ukm::TestUkmRecorder::HumanReadableUkmEntry; |
| using ukm::builders::Preloading_Attempt; |
| |
| class PrerenderBrowserTest : public PlatformBrowserTest { |
| public: |
| PrerenderBrowserTest() |
| : prerender_helper_( |
| base::BindRepeating(&PrerenderBrowserTest::GetActiveWebContents, |
| base::Unretained(this))) {} |
| |
| void SetUp() override { |
| prerender_helper_.RegisterServerRequestMonitor(embedded_test_server()); |
| prerender_helper_.RegisterServerRequestMonitor(&ssl_server_); |
| PlatformBrowserTest::SetUp(); |
| } |
| |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| embedded_test_server()->ServeFilesFromDirectory( |
| base::PathService::CheckedGet(chrome::DIR_TEST_DATA)); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| ssl_server_.SetSSLConfig( |
| net::test_server::EmbeddedTestServer::CERT_TEST_NAMES); |
| ssl_server_.ServeFilesFromDirectory( |
| base::PathService::CheckedGet(chrome::DIR_TEST_DATA)); |
| ASSERT_TRUE(ssl_server_.Start()); |
| } |
| |
| void TearDownOnMainThread() override { |
| ASSERT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete()); |
| ASSERT_TRUE(ssl_server_.ShutdownAndWaitUntilComplete()); |
| } |
| |
| content::WebContents* GetActiveWebContents() { |
| return chrome_test_utils::GetActiveWebContents(this); |
| } |
| |
| content::test::PrerenderTestHelper& prerender_helper() { |
| return prerender_helper_; |
| } |
| |
| GURL GetUrl(const std::string& path) { |
| return ssl_server_.GetURL("a.test", path); |
| } |
| |
| GURL GetSameSiteCrossOriginUrl(const std::string& path) { |
| return ssl_server_.GetURL("b.a.test", path); |
| } |
| |
| GURL GetCrossSiteUrl(const std::string& path) { |
| return ssl_server_.GetURL("b.test", path); |
| } |
| |
| protected: |
| void TestPrerenderAndActivateInNewTab(const std::string& link_click_script, |
| bool should_be_activated); |
| |
| private: |
| content::test::PrerenderTestHelper prerender_helper_; |
| net::test_server::EmbeddedTestServer ssl_server_{ |
| net::test_server::EmbeddedTestServer::TYPE_HTTPS}; |
| }; |
| |
| // An end-to-end test of prerendering and activating. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderAndActivate) { |
| base::HistogramTester histogram_tester; |
| |
| // Navigate to an initial page. |
| GURL url = embedded_test_server()->GetURL("/empty.html"); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), url)); |
| |
| // Start a prerender. |
| GURL prerender_url = embedded_test_server()->GetURL("/simple.html"); |
| prerender_helper().AddPrerender(prerender_url); |
| |
| // Activate. |
| content::TestActivationManager activation_manager(GetActiveWebContents(), |
| prerender_url); |
| ASSERT_TRUE( |
| content::ExecJs(GetActiveWebContents()->GetPrimaryMainFrame(), |
| content::JsReplace("location = $1", prerender_url))); |
| activation_manager.WaitForNavigationFinished(); |
| EXPECT_TRUE(activation_manager.was_activated()); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| kFinalStatusActivated, 1); |
| } |
| |
| void PrerenderBrowserTest::TestPrerenderAndActivateInNewTab( |
| const std::string& link_click_script, |
| bool should_be_activated) { |
| base::HistogramTester histogram_tester; |
| |
| histogram_tester.ExpectBucketCount( |
| "Blink.UseCounter.Features", |
| blink::mojom::WebFeature::kSpeculationRulesTargetHintBlank, 0); |
| |
| // Navigate to an initial page. |
| GURL url = embedded_test_server()->GetURL("/prerender/simple_links.html"); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), url)); |
| |
| // Start a prerender. |
| GURL prerender_url = embedded_test_server()->GetURL("/prerender/empty.html"); |
| content::FrameTreeNodeId host_id = |
| prerender_helper().AddPrerender(prerender_url, |
| /*eagerness=*/std::nullopt, "_blank"); |
| EXPECT_TRUE(host_id); |
| |
| // Activate. |
| EXPECT_TRUE(ExecJs(GetActiveWebContents(), link_click_script)); |
| |
| histogram_tester.ExpectBucketCount( |
| "Blink.UseCounter.Features", |
| blink::mojom::WebFeature::kSpeculationRulesTargetHintBlank, 1); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| kFinalStatusActivated, should_be_activated ? 1 : 0); |
| } |
| |
| // An end-to-end test of prerendering in a new tab and activating. |
| // Disabled on Android due to failures: https://crbug.com/355255740. |
| #if BUILDFLAG(IS_ANDROID) |
| #define MAYBE_PrerenderAndActivate_InNewTab \ |
| DISABLED_PrerenderAndActivate_InNewTab |
| #else |
| #define MAYBE_PrerenderAndActivate_InNewTab PrerenderAndActivate_InNewTab |
| #endif |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| MAYBE_PrerenderAndActivate_InNewTab) { |
| TestPrerenderAndActivateInNewTab("clickSameSiteNewWindowLink();", true); |
| } |
| |
| // Disabled on Android due to failures: https://crbug.com/355255740. |
| #if BUILDFLAG(IS_ANDROID) |
| #define MAYBE_PrerenderAndActivate_InNewTab_Noopener \ |
| DISABLED_PrerenderAndActivate_InNewTab_Noopener |
| #else |
| #define MAYBE_PrerenderAndActivate_InNewTab_Noopener \ |
| PrerenderAndActivate_InNewTab_Noopener |
| #endif |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| MAYBE_PrerenderAndActivate_InNewTab_Noopener) { |
| TestPrerenderAndActivateInNewTab("clickSameSiteNewWindowWithNoopenerLink();", |
| true); |
| } |
| |
| // Prerendering in a new tab should not be activate for a new window with an |
| // opener. |
| // The test is flaky on android-12l-x64-dbg-tests: https://crbug.com/1490582. |
| #if BUILDFLAG(IS_ANDROID) |
| #define MAYBE_PrerenderAndActivate_InNewTab_Opener \ |
| DISABLED_PrerenderAndActivate_InNewTab_Opener |
| #else |
| #define MAYBE_PrerenderAndActivate_InNewTab_Opener \ |
| PrerenderAndActivate_InNewTab_Opener |
| #endif // #if BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_X86) |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| MAYBE_PrerenderAndActivate_InNewTab_Opener) { |
| TestPrerenderAndActivateInNewTab("clickSameSiteNewWindowWithOpenerLink();", |
| false); |
| } |
| |
| // Prerendering in a new tab should not be activate for a current tab. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| PrerenderAndActivate_InNewTab_CurrentTab) { |
| TestPrerenderAndActivateInNewTab("clickSameSiteLink();", false); |
| } |
| |
| // Tests main frame navigation on a prerendered page in a new tab. |
| // Disabled on Android due to failures: https://crbug.com/355255740. |
| #if BUILDFLAG(IS_ANDROID) |
| #define MAYBE_MainFrameNavigation_InNewTab DISABLED_MainFrameNavigation_InNewTab |
| #else |
| #define MAYBE_MainFrameNavigation_InNewTab MainFrameNavigation_InNewTab |
| #endif |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| MAYBE_MainFrameNavigation_InNewTab) { |
| base::HistogramTester histogram_tester; |
| std::string link_click_script = "clickSameSiteNewWindowLink();"; |
| |
| // Navigate to an initial page. |
| GURL url = embedded_test_server()->GetURL("/prerender/simple_links.html"); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), url)); |
| |
| // Start a prerender. |
| GURL prerender_url = embedded_test_server()->GetURL("/prerender/empty.html"); |
| content::FrameTreeNodeId host_id = prerender_helper().AddPrerender( |
| prerender_url, /*eagerness=*/std::nullopt, "_blank"); |
| EXPECT_TRUE(host_id); |
| |
| // Navigate a prerendered page to another page. |
| GURL navigation_url = |
| embedded_test_server()->GetURL("/prerender/empty.html?navigated"); |
| prerender_helper().NavigatePrerenderedPage(host_id, navigation_url); |
| |
| auto* prerender_web_contents = |
| content::WebContents::FromFrameTreeNodeId(host_id); |
| |
| // Activate. |
| content::test::PrerenderHostObserver prerender_observer( |
| *prerender_web_contents, host_id); |
| EXPECT_TRUE(ExecJs(GetActiveWebContents(), link_click_script)); |
| prerender_observer.WaitForActivation(); |
| EXPECT_EQ(prerender_web_contents->GetLastCommittedURL(), navigation_url); |
| EXPECT_EQ(prerender_web_contents->GetVisibleURL(), navigation_url); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| kFinalStatusActivated, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| PrerenderTriggeredByEmbedderAndActivate) { |
| base::HistogramTester histogram_tester; |
| |
| // Navigate to an initial page. |
| GURL url = embedded_test_server()->GetURL("/empty.html"); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), url)); |
| |
| GURL prerender_url = embedded_test_server()->GetURL("/simple.html"); |
| |
| // Start embedder triggered prerendering. |
| std::unique_ptr<content::PrerenderHandle> prerender_handle = |
| prerender_helper().AddEmbedderTriggeredPrerenderAsync( |
| prerender_url, content::PreloadingTriggerType::kEmbedder, |
| prerender_utils::kDirectUrlInputMetricSuffix, |
| ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED | |
| ui::PAGE_TRANSITION_FROM_ADDRESS_BAR)); |
| EXPECT_TRUE(prerender_handle); |
| content::test::PrerenderTestHelper::WaitForPrerenderLoadCompletion( |
| *GetActiveWebContents(), prerender_url); |
| |
| // Activate. |
| content::TestActivationManager activation_manager(GetActiveWebContents(), |
| prerender_url); |
| // Simulate a browser-initiated navigation. |
| prerender_helper().NavigatePrimaryPageAsync( |
| prerender_url, |
| ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED | |
| ui::PAGE_TRANSITION_FROM_ADDRESS_BAR)); |
| activation_manager.WaitForNavigationFinished(); |
| EXPECT_TRUE(activation_manager.was_activated()); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_DirectURLInput", |
| kFinalStatusActivated, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, EmbedderTrigger_ChromeUrl) { |
| base::HistogramTester histogram_tester; |
| |
| // Navigate to an initial page. |
| GURL url = embedded_test_server()->GetURL("/empty.html"); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), url)); |
| |
| GURL prerender_url("chrome://new-tab-page"); |
| ASSERT_FALSE(prerender_url.SchemeIsHTTPOrHTTPS()); |
| |
| // Start embedder triggered prerendering. |
| std::unique_ptr<content::PrerenderHandle> prerender_handle = |
| prerender_helper().AddEmbedderTriggeredPrerenderAsync( |
| prerender_url, content::PreloadingTriggerType::kEmbedder, |
| prerender_utils::kDirectUrlInputMetricSuffix, |
| ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED | |
| ui::PAGE_TRANSITION_FROM_ADDRESS_BAR)); |
| EXPECT_FALSE(prerender_handle); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_DirectURLInput", |
| kFinalStatusInvalidSchemeNavigation, 1); |
| } |
| |
| // Tests that UseCounter for SpeculationRules-triggered prerender is recorded. |
| // This cannot be tested in content/ as SpeculationHostImpl records the usage |
| // with ContentBrowserClient::LogWebFeatureForCurrentPage() that is not |
| // implemented in content/. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, UseCounter) { |
| base::HistogramTester histogram_tester; |
| |
| // Navigate to an initial page. |
| GURL url = embedded_test_server()->GetURL("/empty.html"); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), url)); |
| |
| histogram_tester.ExpectBucketCount( |
| "Blink.UseCounter.Features", |
| blink::mojom::WebFeature::kSpeculationRulesPrerender, 0); |
| histogram_tester.ExpectBucketCount( |
| "Blink.UseCounter.Features", |
| blink::mojom::WebFeature::kV8Document_Prerendering_AttributeGetter, 0); |
| histogram_tester.ExpectBucketCount( |
| "Blink.UseCounter.Features", |
| blink::mojom::WebFeature:: |
| kV8Document_Onprerenderingchange_AttributeSetter, |
| 0); |
| histogram_tester.ExpectBucketCount("Blink.UseCounter.Features", |
| blink::mojom::WebFeature::kPageVisits, 1); |
| |
| // Start a prerender. The API call should be recorded. |
| GURL prerender_url = embedded_test_server()->GetURL("/simple.html"); |
| prerender_helper().AddPrerender(prerender_url); |
| // kPageVisits should have been issued for kPageVisits already, but the value |
| // hasn't been updated due to the update will be delayed until the activation |
| // in the current design. The value is still expected to be one. |
| // Please refer to crrev.com/c/3856942 for implementation details. |
| histogram_tester.ExpectBucketCount("Blink.UseCounter.Features", |
| blink::mojom::WebFeature::kPageVisits, 1); |
| |
| // Accessing related attributes should also be recorded. |
| ASSERT_TRUE(content::ExecJs(GetActiveWebContents()->GetPrimaryMainFrame(), |
| "const value = document.prerendering;")); |
| ASSERT_TRUE(content::ExecJs(GetActiveWebContents()->GetPrimaryMainFrame(), |
| "document.onprerenderingchange = e => {};")); |
| |
| // Make sure the counts are stored by navigating away. |
| prerender_helper().NavigatePrimaryPage(prerender_url); |
| |
| histogram_tester.ExpectBucketCount( |
| "Blink.UseCounter.Features", |
| blink::mojom::WebFeature::kSpeculationRulesPrerender, 1); |
| histogram_tester.ExpectBucketCount( |
| "Blink.UseCounter.Features", |
| blink::mojom::WebFeature::kV8Document_Prerendering_AttributeGetter, 1); |
| histogram_tester.ExpectBucketCount( |
| "Blink.UseCounter.Features", |
| blink::mojom::WebFeature:: |
| kV8Document_Onprerenderingchange_AttributeSetter, |
| 1); |
| histogram_tester.ExpectBucketCount("Blink.UseCounter.Features", |
| blink::mojom::WebFeature::kPageVisits, 2); |
| } |
| |
| // Tests that Prerender2 cannot be triggered when preload setting is disabled. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, DisableNetworkPrediction) { |
| // Navigate to an initial page. |
| GURL url = embedded_test_server()->GetURL("/empty.html"); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), url)); |
| |
| // Disable network prediction. |
| PrefService* prefs = chrome_test_utils::GetProfile(this)->GetPrefs(); |
| prefetch::SetPreloadPagesState(prefs, |
| prefetch::PreloadPagesState::kNoPreloading); |
| ASSERT_EQ(prefetch::IsSomePreloadingEnabled(*prefs), |
| content::PreloadingEligibility::kPreloadingDisabled); |
| |
| // Attempt to trigger prerendering. |
| GURL prerender_url = embedded_test_server()->GetURL("/simple.html?1"); |
| prerender_helper().AddPrerenderAsync(prerender_url); |
| // Since preload setting is disabled, prerender shouldn't be triggered. |
| base::RunLoop().RunUntilIdle(); |
| content::FrameTreeNodeId host_id = |
| prerender_helper().GetHostForUrl(prerender_url); |
| EXPECT_TRUE(host_id.is_null()); |
| |
| // Reload the initial page to reset the speculation rules. |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), url)); |
| |
| // Re-enable the setting. |
| prefetch::SetPreloadPagesState( |
| prefs, prefetch::PreloadPagesState::kStandardPreloading); |
| ASSERT_EQ(prefetch::IsSomePreloadingEnabled(*prefs), |
| content::PreloadingEligibility::kEligible); |
| |
| // Attempt to trigger prerendering again. |
| content::test::PrerenderHostRegistryObserver registry_observer( |
| *GetActiveWebContents()); |
| prerender_helper().AddPrerenderAsync(prerender_url); |
| // Since preload setting is enabled, prerender should be triggered |
| // successfully. |
| registry_observer.WaitForTrigger(prerender_url); |
| host_id = prerender_helper().GetHostForUrl(prerender_url); |
| EXPECT_TRUE(host_id); |
| } |
| |
| // Tests that DevTools open overrides PreloadingConfig's holdback. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PreloadingHoldbackOverridden) { |
| prerender_helper().SetHoldback("Prerender", "SpeculationRules", true); |
| base::HistogramTester histogram_tester; |
| |
| // Navigate to an initial page. |
| GURL url = embedded_test_server()->GetURL("/empty.html"); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), url)); |
| PrefService* prefs = chrome_test_utils::GetProfile(this)->GetPrefs(); |
| |
| // IsSomePreloadingEnabled is *not* affected by PreloadingConfig. |
| ASSERT_EQ(prefetch::IsSomePreloadingEnabled(*prefs), |
| content::PreloadingEligibility::kEligible); |
| |
| // Emulating Devtools attached to make PreloadingHoldback overridden. Retain |
| // the returned host until the test finishes to avoid DevTools termination. |
| scoped_refptr<content::DevToolsAgentHost> dev_tools_agent_host = |
| content::DevToolsAgentHost::GetOrCreateFor(GetActiveWebContents()); |
| ASSERT_TRUE(dev_tools_agent_host); |
| |
| // Start a prerender. |
| GURL prerender_url = embedded_test_server()->GetURL("/simple.html"); |
| prerender_helper().AddPrerender(prerender_url); |
| |
| // Activate. |
| content::TestActivationManager activation_manager(GetActiveWebContents(), |
| prerender_url); |
| ASSERT_TRUE( |
| content::ExecJs(GetActiveWebContents()->GetPrimaryMainFrame(), |
| content::JsReplace("location = $1", prerender_url))); |
| activation_manager.WaitForNavigationFinished(); |
| EXPECT_TRUE(activation_manager.was_activated()); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| kFinalStatusActivated, 1); |
| } |
| |
| // Tests that Prerender2 cannot be triggered when PreloadingConfig's |
| // holdback is not overridden by DevTools. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PreloadingHoldbackNotOverridden) { |
| prerender_helper().SetHoldback("Prerender", "SpeculationRules", true); |
| |
| // Navigate to an initial page. |
| GURL url = embedded_test_server()->GetURL("/empty.html"); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), url)); |
| |
| PrefService* prefs = chrome_test_utils::GetProfile(this)->GetPrefs(); |
| |
| // IsSomePreloadingEnabled is *not* affected by PreloadingConfig. |
| ASSERT_EQ(prefetch::IsSomePreloadingEnabled(*prefs), |
| content::PreloadingEligibility::kEligible); |
| |
| content::test::PrerenderHostRegistryObserver registry_observer( |
| *GetActiveWebContents()); |
| |
| // Attempt to trigger prerendering. |
| GURL prerender_url = embedded_test_server()->GetURL("/simple.html?1"); |
| prerender_helper().AddPrerenderAsync(prerender_url); |
| // Since preload setting is disabled, prerender shouldn't be triggered. |
| registry_observer.WaitForTrigger(prerender_url); |
| content::FrameTreeNodeId host_id = |
| prerender_helper().GetHostForUrl(prerender_url); |
| EXPECT_TRUE(host_id.is_null()); |
| } |
| |
| // Tests that the same-origin main frame navigation in an embedder triggered |
| // prerendering page succeeds. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, SameOriginMainFrameNavigation) { |
| base::HistogramTester histogram_tester; |
| |
| // Navigate to an initial page. |
| GURL url = GetUrl("/empty.html"); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), url)); |
| |
| GURL prerender_url = GetUrl("/title1.html"); |
| GURL navigation_url = GetUrl("/title2.html"); |
| |
| // Start an embedder triggered prerendering. |
| std::unique_ptr<content::PrerenderHandle> prerender_handle = |
| prerender_helper().AddEmbedderTriggeredPrerenderAsync( |
| prerender_url, content::PreloadingTriggerType::kEmbedder, |
| prerender_utils::kDirectUrlInputMetricSuffix, |
| ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED | |
| ui::PAGE_TRANSITION_FROM_ADDRESS_BAR)); |
| EXPECT_TRUE(prerender_handle); |
| content::test::PrerenderTestHelper::WaitForPrerenderLoadCompletion( |
| *GetActiveWebContents(), prerender_url); |
| |
| content::FrameTreeNodeId host_id = |
| prerender_helper().GetHostForUrl(prerender_url); |
| ASSERT_TRUE(host_id); |
| |
| // Start a same-origin navigation in the prerender frame tree. It will not |
| // cancel the initiator's prerendering. |
| prerender_helper().NavigatePrerenderedPage(host_id, navigation_url); |
| |
| // Activate. |
| content::TestActivationManager activation_manager(GetActiveWebContents(), |
| prerender_url); |
| // Simulate a browser-initiated navigation. |
| prerender_helper().NavigatePrimaryPageAsync( |
| prerender_url, |
| ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED | |
| ui::PAGE_TRANSITION_FROM_ADDRESS_BAR)); |
| activation_manager.WaitForNavigationFinished(); |
| EXPECT_TRUE(activation_manager.was_activated()); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_DirectURLInput", |
| kFinalStatusActivated, 1); |
| } |
| |
| // Tests that the same-site cross-origin main frame navigation in an embedder |
| // triggered prerendering page succeeds. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| SameSiteCrossOriginMainFrameNavigation) { |
| base::HistogramTester histogram_tester; |
| |
| // Navigate to an initial page. |
| GURL url = GetUrl("/empty.html"); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), url)); |
| |
| GURL prerender_url = GetUrl("/title1.html"); |
| GURL navigation_url = |
| GetSameSiteCrossOriginUrl("/prerender_with_opt_in_header.html"); |
| |
| // Start an embedder triggered prerendering. |
| std::unique_ptr<content::PrerenderHandle> prerender_handle = |
| prerender_helper().AddEmbedderTriggeredPrerenderAsync( |
| prerender_url, content::PreloadingTriggerType::kEmbedder, |
| prerender_utils::kDirectUrlInputMetricSuffix, |
| ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED | |
| ui::PAGE_TRANSITION_FROM_ADDRESS_BAR)); |
| EXPECT_TRUE(prerender_handle); |
| content::test::PrerenderTestHelper::WaitForPrerenderLoadCompletion( |
| *GetActiveWebContents(), prerender_url); |
| |
| content::FrameTreeNodeId host_id = |
| prerender_helper().GetHostForUrl(prerender_url); |
| ASSERT_TRUE(host_id); |
| |
| content::test::PrerenderHostObserver prerender_observer( |
| *GetActiveWebContents(), host_id); |
| |
| // Start a same-site cross-origin main frame navigation in the prerender frame |
| // tree. It will not cancel the initiator's prerendering. |
| prerender_helper().NavigatePrerenderedPage(host_id, navigation_url); |
| |
| // Activate. |
| content::TestActivationManager activation_manager(GetActiveWebContents(), |
| prerender_url); |
| // Simulate a browser-initiated navigation. |
| prerender_helper().NavigatePrimaryPageAsync( |
| prerender_url, |
| ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED | |
| ui::PAGE_TRANSITION_FROM_ADDRESS_BAR)); |
| activation_manager.WaitForNavigationFinished(); |
| EXPECT_TRUE(activation_manager.was_activated()); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_DirectURLInput", |
| kFinalStatusActivated, 1); |
| } |
| |
| // Tests that the cross-site main frame navigation in an embedder triggered |
| // prerendering page cancels the prerendering. |
| IN_PROC_BROWSER_TEST_F( |
| PrerenderBrowserTest, |
| CrossSiteMainFrameNavigationCancelsEmbedderTriggeredPrerendering) { |
| base::HistogramTester histogram_tester; |
| |
| // Navigate to an initial page. |
| GURL url = GetUrl("/empty.html"); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), url)); |
| |
| GURL prerender_url = GetUrl("/title1.html"); |
| GURL navigation_url = GetCrossSiteUrl("/prerender_with_opt_in_header.html"); |
| |
| // Start an embedder triggered prerendering. |
| std::unique_ptr<content::PrerenderHandle> prerender_handle = |
| prerender_helper().AddEmbedderTriggeredPrerenderAsync( |
| prerender_url, content::PreloadingTriggerType::kEmbedder, |
| prerender_utils::kDirectUrlInputMetricSuffix, |
| ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED | |
| ui::PAGE_TRANSITION_FROM_ADDRESS_BAR)); |
| EXPECT_TRUE(prerender_handle); |
| content::test::PrerenderTestHelper::WaitForPrerenderLoadCompletion( |
| *GetActiveWebContents(), prerender_url); |
| |
| content::FrameTreeNodeId host_id = |
| prerender_helper().GetHostForUrl(prerender_url); |
| ASSERT_TRUE(host_id); |
| |
| content::test::PrerenderHostObserver prerender_observer( |
| *GetActiveWebContents(), host_id); |
| |
| // Start a cross-site main frame navigation in the prerender frame tree. It |
| // will cancel the initiator's prerendering. |
| prerender_helper().NavigatePrerenderedPage(host_id, navigation_url); |
| |
| prerender_observer.WaitForDestroyed(); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_DirectURLInput", |
| kFinalStatusCrossSiteNavigationInMainFrameNavigation, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| PrerenderWebContentsDelegate_CloseContents) { |
| base::HistogramTester histogram_tester; |
| |
| // Navigate to an initial page. |
| GURL url = embedded_test_server()->GetURL("/prerender/simple_links.html"); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), url)); |
| |
| // Start a prerender. |
| GURL prerender_url = embedded_test_server()->GetURL("/prerender/empty.html"); |
| content::FrameTreeNodeId host_id = prerender_helper().AddPrerender( |
| prerender_url, /*eagerness=*/std::nullopt, "_blank"); |
| |
| // Navigate a prerendered page to another page. |
| GURL navigation_url = |
| embedded_test_server()->GetURL("/prerender/empty.html?navigated"); |
| prerender_helper().NavigatePrerenderedPage(host_id, navigation_url); |
| |
| // WebContents::Close() should eventually call |
| // PrerenderWebContentsDelegate::CloseContents() that cancels prerendering. |
| auto* prerender_web_contents = |
| content::WebContents::FromFrameTreeNodeId(host_id); |
| ASSERT_TRUE(prerender_web_contents); |
| content::WebContentsDestroyedWatcher destroyed_watcher( |
| prerender_web_contents); |
| prerender_web_contents->Close(); |
| |
| // WebContents created for the new-tab host will eventually be destroyed after |
| // host cancellation. |
| destroyed_watcher.Wait(); |
| EXPECT_FALSE(prerender_helper().HasNewTabHandle(host_id)); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| kFinalStatusTabClosedWithoutUserGesture, 1); |
| } |
| |
| class PrerenderNewTabPageBrowserTest |
| : public PrerenderBrowserTest, |
| public testing::WithParamInterface<content::PreloadingPredictor> { |
| public: |
| PrerenderNewTabPageBrowserTest() = default; |
| |
| void SetUpOnMainThread() override { |
| PrerenderBrowserTest::SetUpOnMainThread(); |
| // Initialize PreloadingAttempt builder for the test suite. |
| test_ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>(); |
| attempt_entry_builder_ = |
| std::make_unique<content::test::PreloadingAttemptUkmEntryBuilder>( |
| chrome_preloading_predictor::kMouseHoverOrMouseDownOnNewTabPage); |
| } |
| |
| void SimulateNewTabNavigation(const GURL& url) { |
| GetActiveWebContents()->OpenURL( |
| content::OpenURLParams( |
| url, content::Referrer(), WindowOpenDisposition::CURRENT_TAB, |
| ui::PageTransitionFromInt(ui::PAGE_TRANSITION_AUTO_BOOKMARK), |
| /*is_renderer_initiated=*/false), |
| base::BindRepeating(&page_load_metrics::NavigationHandleUserData:: |
| AttachNewTabPageNavigationHandleUserData)); |
| } |
| |
| void ExpectPrerenderPageLoad( |
| const GURL& prerender_url, |
| page_load_metrics::NavigationHandleUserData::InitiatorLocation |
| initiator_location) { |
| auto entries = |
| test_ukm_recorder()->GetMergedEntriesByName("PrerenderPageLoad"); |
| for (auto& kv : entries) { |
| const ukm::mojom::UkmEntry* entry = kv.second.get(); |
| const ukm::UkmSource* source = |
| test_ukm_recorder()->GetSourceForSourceId(entry->source_id); |
| if (!source) { |
| continue; |
| } |
| EXPECT_TRUE(source->url().is_valid()); |
| if (source->url() != prerender_url) { |
| continue; |
| } |
| test_ukm_recorder()->ExpectEntryMetric( |
| entry, |
| ukm::builders::PrerenderPageLoad::kNavigation_InitiatorLocationName, |
| static_cast<int>(initiator_location)); |
| return; |
| } |
| EXPECT_TRUE(false) << "PrerenderPageLoad hasn't been recorded."; |
| } |
| |
| ukm::TestAutoSetUkmRecorder* test_ukm_recorder() { |
| return test_ukm_recorder_.get(); |
| } |
| |
| const content::test::PreloadingAttemptUkmEntryBuilder& |
| attempt_entry_builder() { |
| return *attempt_entry_builder_; |
| } |
| |
| private: |
| // This timer is for making TimeToNextNavigation in UKM consistent. |
| base::ScopedMockElapsedTimersForTest test_timer_; |
| std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_ukm_recorder_; |
| std::unique_ptr<content::test::PreloadingAttemptUkmEntryBuilder> |
| attempt_entry_builder_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderNewTabPageBrowserTest, |
| PrerenderTriggeredByNewTabPageAndActivate) { |
| base::HistogramTester histogram_tester; |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), |
| GURL(chrome::kChromeUINewTabURL))); |
| GURL prerender_url = GetUrl("/simple.html"); |
| |
| PrerenderManager::CreateForWebContents(GetActiveWebContents()); |
| auto* prerender_manager = |
| PrerenderManager::FromWebContents(GetActiveWebContents()); |
| EXPECT_TRUE(prerender_manager->StartPrerenderNewTabPage( |
| prerender_url, |
| chrome_preloading_predictor::kMouseHoverOrMouseDownOnNewTabPage)); |
| content::test::PrerenderTestHelper::WaitForPrerenderLoadCompletion( |
| *GetActiveWebContents(), prerender_url); |
| |
| // Activate. |
| content::TestActivationManager activation_manager(GetActiveWebContents(), |
| prerender_url); |
| SimulateNewTabNavigation(prerender_url); |
| activation_manager.WaitForNavigationFinished(); |
| EXPECT_TRUE(activation_manager.was_activated()); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_NewTabPage", |
| kFinalStatusActivated, 1); |
| histogram_tester.ExpectTotalCount( |
| "NewTabPage.PrerenderNavigationToActivation", 1); |
| |
| ExpectPrerenderPageLoad(prerender_url, |
| page_load_metrics::NavigationHandleUserData:: |
| InitiatorLocation::kNewTabPage); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.IsPrerenderingSRPUrl.Embedder_NewTabPage", false, 1); |
| } |
| |
| // Verify that NewTabPage prerender rejects non https url. |
| IN_PROC_BROWSER_TEST_F(PrerenderNewTabPageBrowserTest, |
| NewTabPagePrerenderNonHttps) { |
| // Navigate to an initial page. |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), |
| GURL(chrome::kChromeUINewTabURL))); |
| GURL prerender_url = embedded_test_server()->GetURL("/simple.html?prerender"); |
| |
| PrerenderManager::CreateForWebContents(GetActiveWebContents()); |
| auto* prerender_manager = |
| PrerenderManager::FromWebContents(GetActiveWebContents()); |
| EXPECT_FALSE(prerender_manager->StartPrerenderNewTabPage( |
| prerender_url, |
| chrome_preloading_predictor::kMouseHoverOrMouseDownOnNewTabPage)); |
| base::RunLoop().RunUntilIdle(); |
| content::FrameTreeNodeId host_id = |
| prerender_helper().GetHostForUrl(prerender_url); |
| EXPECT_TRUE(host_id.is_null()); |
| |
| // Navigate to a different URL other than the prerender_url to flush the |
| // metrics. |
| ASSERT_TRUE(content::NavigateToURL( |
| GetActiveWebContents(), embedded_test_server()->GetURL("/simple.html"))); |
| |
| ukm::SourceId ukm_source_id = |
| GetActiveWebContents()->GetPrimaryMainFrame()->GetPageUkmSourceId(); |
| content::test::ExpectPreloadingAttemptUkm( |
| *test_ukm_recorder(), |
| {attempt_entry_builder().BuildEntry( |
| ukm_source_id, content::PreloadingType::kPrerender, |
| content::PreloadingEligibility::kHttpsOnly, |
| content::PreloadingHoldbackStatus::kUnspecified, |
| content::PreloadingTriggeringOutcome::kUnspecified, |
| content::PreloadingFailureReason::kUnspecified, |
| /*accurate=*/false)}); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderNewTabPageBrowserTest, |
| PrerenderTriggeredCancelAndRetrigger) { |
| base::HistogramTester histogram_tester; |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), |
| GURL(chrome::kChromeUINewTabURL))); |
| GURL prerender_url = GetUrl("/simple.html"); |
| |
| PrerenderManager::CreateForWebContents(GetActiveWebContents()); |
| auto* prerender_manager = |
| PrerenderManager::FromWebContents(GetActiveWebContents()); |
| |
| base::WeakPtr<content::PrerenderHandle> prerender_handle = |
| prerender_manager->StartPrerenderNewTabPage( |
| prerender_url, |
| chrome_preloading_predictor::kMouseHoverOrMouseDownOnNewTabPage); |
| content::test::PrerenderTestHelper::WaitForPrerenderLoadCompletion( |
| *GetActiveWebContents(), prerender_url); |
| |
| prerender_manager->StopPrerenderNewTabPage(prerender_handle); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_NewTabPage", |
| kFinalStatusTriggerDestroyed, 1); |
| |
| // Retrigger after cancelation. |
| EXPECT_TRUE(prerender_manager->StartPrerenderNewTabPage( |
| prerender_url, |
| chrome_preloading_predictor::kMouseHoverOrMouseDownOnNewTabPage)); |
| content::test::PrerenderTestHelper::WaitForPrerenderLoadCompletion( |
| *GetActiveWebContents(), prerender_url); |
| |
| // Activate. |
| content::TestActivationManager activation_manager(GetActiveWebContents(), |
| prerender_url); |
| SimulateNewTabNavigation(prerender_url); |
| activation_manager.WaitForNavigationFinished(); |
| EXPECT_TRUE(activation_manager.was_activated()); |
| |
| histogram_tester.ExpectBucketCount( |
| "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_NewTabPage", |
| kFinalStatusActivated, 1); |
| histogram_tester.ExpectTotalCount( |
| "NewTabPage.PrerenderNavigationToActivation", 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderNewTabPageBrowserTest, |
| DestroyedOnNavigatedAway) { |
| base::HistogramTester histogram_tester; |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), |
| GURL(chrome::kChromeUINewTabURL))); |
| GURL prerender_url = GetUrl("/simple.html?prerender"); |
| |
| PrerenderManager::CreateForWebContents(GetActiveWebContents()); |
| auto* prerender_manager = |
| PrerenderManager::FromWebContents(GetActiveWebContents()); |
| |
| prerender_manager->StartPrerenderNewTabPage( |
| prerender_url, |
| chrome_preloading_predictor::kMouseHoverOrMouseDownOnNewTabPage); |
| content::test::PrerenderTestHelper::WaitForPrerenderLoadCompletion( |
| *GetActiveWebContents(), prerender_url); |
| |
| content::FrameTreeNodeId host_id = |
| prerender_helper().GetHostForUrl(prerender_url); |
| ASSERT_TRUE(host_id); |
| |
| // Navigate to a different page. This should cancel prerendering. |
| GURL different_url = GetUrl("/simple.html?different"); |
| content::test::PrerenderHostObserver prerender_observer( |
| *GetActiveWebContents(), host_id); |
| SimulateNewTabNavigation(different_url); |
| prerender_observer.WaitForDestroyed(); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_NewTabPage", |
| kFinalStatusTriggerDestroyed, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, TagsUseCounter) { |
| base::HistogramTester histogram_tester; |
| |
| histogram_tester.ExpectBucketCount( |
| "Blink.UseCounter.Features", |
| blink::mojom::WebFeature::kSpeculationRulesTags, 0); |
| |
| // Navigate to an initial page. |
| GURL url = |
| embedded_test_server()->GetURL("/prerender/prerender_with_tags.html"); |
| GURL prerender_url = embedded_test_server()->GetURL("/prerender/empty.html"); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), url)); |
| content::test::PrerenderTestHelper::WaitForPrerenderLoadCompletion( |
| *GetActiveWebContents(), prerender_url); |
| |
| histogram_tester.ExpectBucketCount( |
| "Blink.UseCounter.Features", |
| blink::mojom::WebFeature::kSpeculationRulesTags, 1); |
| } |
| |
| // Tests that if no tag is specified, then UseCounter will not increase. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, NoUseCountIfTagEmpty) { |
| base::HistogramTester histogram_tester; |
| |
| histogram_tester.ExpectBucketCount( |
| "Blink.UseCounter.Features", |
| blink::mojom::WebFeature::kSpeculationRulesTags, 0); |
| |
| // Navigate to an initial page. |
| GURL url = embedded_test_server()->GetURL("/prerender/empty.html"); |
| GURL prerender_url = |
| embedded_test_server()->GetURL("/prerender/empty.html?prerender"); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), url)); |
| prerender_helper().AddPrerender(prerender_url); |
| |
| histogram_tester.ExpectBucketCount( |
| "Blink.UseCounter.Features", |
| blink::mojom::WebFeature::kSpeculationRulesTags, 0); |
| } |
| |
| // TODO(crbug.com/425270853): Move the common logic of prewarm tests to |
| // PrewarmTestHelper in prerender_test_util.h |
| class PrerenderPrewarmDefaultSearchEngineTest |
| : public PrerenderBrowserTest, |
| public testing::WithParamInterface<content::PreloadingPredictor> { |
| public: |
| PrerenderPrewarmDefaultSearchEngineTest() { |
| scoped_feature_list_.InitAndEnableFeatureWithParameters( |
| features::kPrewarm, {{"url", "https://search.example.com/prewarm.html"}, |
| {"zero_suggest_trigger", "false"}}); |
| } |
| |
| void SetUpOnMainThread() override { |
| PrerenderBrowserTest::SetUpOnMainThread(); |
| PrerenderManager::CreateForWebContents(GetActiveWebContents()); |
| auto* prerender_manager = |
| PrerenderManager::FromWebContents(GetActiveWebContents()); |
| // The GetURL() function can only be called after the test server |
| // is started so we cannot override the prewarm URL feature parameter |
| // during the constructor. |
| prewarm_url_ = embedded_test_server()->GetURL("/simple.html"); |
| prerender_manager->SetPrewarmUrlForTesting(prewarm_url_); |
| } |
| |
| content::FrameTreeNodeId GetPrewarmSearchResultHost() { |
| return prerender_helper().GetPrewarmSearchResultHost(prewarm_url_); |
| } |
| |
| protected: |
| GURL prewarm_url_; |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderPrewarmDefaultSearchEngineTest, |
| PrewarmPageLoaded) { |
| base::HistogramTester histogram_tester; |
| |
| // Navigate to an initial page. |
| GURL url = embedded_test_server()->GetURL("/empty.html"); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), url)); |
| |
| // Prerender the prewarm page. |
| auto* prerender_manager = |
| PrerenderManager::FromWebContents(GetActiveWebContents()); |
| EXPECT_TRUE(prerender_manager->MaybeStartPrewarmSearchResult()); |
| auto host_id = GetPrewarmSearchResultHost(); |
| ASSERT_TRUE(host_id); |
| prerender_helper().WaitForPrerenderLoadCompletion(host_id); |
| } |
| |
| } // namespace |