blob: 35365f41352514696966e19542e06d03d16f9091 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <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