blob: e26fdef123a7ac33bd8566a33ab6e157c108cc26 [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 "build/build_config.h"
#include "chrome/browser/prefetch/prefetch_prefs.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/test/base/chrome_test_utils.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/prerender_test_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.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"
#if BUILDFLAG(IS_ANDROID)
#include "chrome/test/base/android/android_browser_test.h"
#else
#include "chrome/test/base/in_process_browser_test.h"
#endif
namespace {
namespace {
// Following definitions are equal to content::PrerenderFinalStatus.
constexpr int kFinalStatusActivated = 0;
constexpr int kFinalStatusCrossSiteNavigationInMainFrameNavigation = 64;
} // namespace
class PrerenderBrowserTest : public PlatformBrowserTest {
public:
PrerenderBrowserTest()
: prerender_helper_(
base::BindRepeating(&PrerenderBrowserTest::GetActiveWebContents,
base::Unretained(this))) {}
void SetUp() override {
prerender_helper_.SetUp(embedded_test_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());
}
void TearDownOnMainThread() override {
ASSERT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
}
content::WebContents* GetActiveWebContents() {
return chrome_test_utils::GetActiveWebContents(this);
}
content::test::PrerenderTestHelper& prerender_helper() {
return prerender_helper_;
}
private:
content::test::PrerenderTestHelper prerender_helper_;
};
class PrerenderHoldbackBrowserTest : public PrerenderBrowserTest {
public:
PrerenderHoldbackBrowserTest() {
feature_list_.InitAndEnableFeature(features::kPreloadingHoldback);
}
private:
base::test::ScopedFeatureList feature_list_;
};
// 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);
}
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 =
GetActiveWebContents()->StartPrerendering(
prerender_url, content::PrerenderTriggerType::kEmbedder,
prerender_utils::kDirectUrlInputMetricSuffix,
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
content::PreloadingHoldbackStatus::kUnspecified, nullptr);
EXPECT_TRUE(prerender_handle);
content::test::PrerenderTestHelper::WaitForPrerenderLoadCompletion(
*GetActiveWebContents(), prerender_url);
// Activate.
content::TestActivationManager activation_manager(GetActiveWebContents(),
prerender_url);
// Simulate a browser-initiated navigation.
GetActiveWebContents()->OpenURL(content::OpenURLParams(
prerender_url, content::Referrer(), WindowOpenDisposition::CURRENT_TAB,
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
/*is_renderer_initiated=*/false));
activation_manager.WaitForNavigationFinished();
EXPECT_TRUE(activation_manager.was_activated());
histogram_tester.ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.Embedder_DirectURLInput",
kFinalStatusActivated, 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();
int host_id = prerender_helper().GetHostForUrl(prerender_url);
EXPECT_EQ(host_id, content::RenderFrameHost::kNoFrameTreeNodeId);
// 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_NE(host_id, content::RenderFrameHost::kNoFrameTreeNodeId);
}
// Tests that Devtools open overrides PreloadingHoldback.
IN_PROC_BROWSER_TEST_F(PrerenderHoldbackBrowserTest,
PreloadingHoldbackOverridden) {
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();
ASSERT_EQ(prefetch::IsSomePreloadingEnabled(*prefs),
content::PreloadingEligibility::kPreloadingDisabled);
// 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 PreloadingHoldback is not
// overridden by Devtools.
IN_PROC_BROWSER_TEST_F(PrerenderHoldbackBrowserTest,
PreloadingHoldbackNotOverridden) {
// 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();
ASSERT_EQ(prefetch::IsSomePreloadingEnabled(*prefs),
content::PreloadingEligibility::kPreloadingDisabled);
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);
int host_id = prerender_helper().GetHostForUrl(prerender_url);
EXPECT_EQ(host_id, content::RenderFrameHost::kNoFrameTreeNodeId);
}
// TODO(crbug.com/1239281): Merge PrerenderMainFrameNavigationBrowserTest into
// PrerenderBrowserTest.
class PrerenderMainFrameNavigationBrowserTest : public PrerenderBrowserTest {
public:
PrerenderMainFrameNavigationBrowserTest() {
// TODO(crbug.com/1394910): Use HTTPS URLs in tests to avoid having to
// disable this feature.
feature_list_.InitAndDisableFeature(features::kHttpsUpgrades);
}
private:
base::test::ScopedFeatureList feature_list_;
};
// Tests that the same-origin main frame navigation in an embedder triggered
// prerendering page succeeds.
IN_PROC_BROWSER_TEST_F(PrerenderMainFrameNavigationBrowserTest,
SameOriginMainFrameNavigation) {
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("/title1.html");
GURL navigation_url = embedded_test_server()->GetURL("/title2.html");
// Start an embedder triggered prerendering.
std::unique_ptr<content::PrerenderHandle> prerender_handle =
GetActiveWebContents()->StartPrerendering(
prerender_url, content::PrerenderTriggerType::kEmbedder,
prerender_utils::kDirectUrlInputMetricSuffix,
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
content::PreloadingHoldbackStatus::kUnspecified, nullptr);
EXPECT_TRUE(prerender_handle);
content::test::PrerenderTestHelper::WaitForPrerenderLoadCompletion(
*GetActiveWebContents(), prerender_url);
int host_id = prerender_helper().GetHostForUrl(prerender_url);
ASSERT_NE(host_id, content::RenderFrameHost::kNoFrameTreeNodeId);
// 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.
GetActiveWebContents()->OpenURL(content::OpenURLParams(
prerender_url, content::Referrer(), WindowOpenDisposition::CURRENT_TAB,
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
/*is_renderer_initiated=*/false));
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(PrerenderMainFrameNavigationBrowserTest,
SameSiteCrossOriginMainFrameNavigation) {
base::HistogramTester histogram_tester;
// Navigate to an initial page.
GURL url = embedded_test_server()->GetURL("a.test", "/empty.html");
ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), url));
GURL prerender_url = embedded_test_server()->GetURL("a.test", "/title1.html");
GURL navigation_url = embedded_test_server()->GetURL(
"b.a.test", "/prerender_with_opt_in_header.html");
// Start an embedder triggered prerendering.
std::unique_ptr<content::PrerenderHandle> prerender_handle =
GetActiveWebContents()->StartPrerendering(
prerender_url, content::PrerenderTriggerType::kEmbedder,
prerender_utils::kDirectUrlInputMetricSuffix,
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
content::PreloadingHoldbackStatus::kUnspecified, nullptr);
EXPECT_TRUE(prerender_handle);
content::test::PrerenderTestHelper::WaitForPrerenderLoadCompletion(
*GetActiveWebContents(), prerender_url);
int host_id = prerender_helper().GetHostForUrl(prerender_url);
ASSERT_NE(host_id, content::RenderFrameHost::kNoFrameTreeNodeId);
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.
GetActiveWebContents()->OpenURL(content::OpenURLParams(
prerender_url, content::Referrer(), WindowOpenDisposition::CURRENT_TAB,
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
/*is_renderer_initiated=*/false));
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(
PrerenderMainFrameNavigationBrowserTest,
CrossSiteMainFrameNavigationCancelsEmbedderTriggeredPrerendering) {
base::HistogramTester histogram_tester;
// Navigate to an initial page.
GURL url = embedded_test_server()->GetURL("a.test", "/empty.html");
ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), url));
GURL prerender_url = embedded_test_server()->GetURL("a.test", "/title1.html");
GURL navigation_url = embedded_test_server()->GetURL(
"b.test", "/prerender_with_opt_in_header.html");
// Start an embedder triggered prerendering.
std::unique_ptr<content::PrerenderHandle> prerender_handle =
GetActiveWebContents()->StartPrerendering(
prerender_url, content::PrerenderTriggerType::kEmbedder,
prerender_utils::kDirectUrlInputMetricSuffix,
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
content::PreloadingHoldbackStatus::kUnspecified, nullptr);
EXPECT_TRUE(prerender_handle);
content::test::PrerenderTestHelper::WaitForPrerenderLoadCompletion(
*GetActiveWebContents(), prerender_url);
int host_id = prerender_helper().GetHostForUrl(prerender_url);
ASSERT_NE(host_id, content::RenderFrameHost::kNoFrameTreeNodeId);
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);
}
} // namespace