blob: 3a6b3d5af6f08d5e6c1f94a8449cc798f93c8029 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/preloading/prerender/prerender_manager.h"
#include <string>
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/preloading/chrome_preloading.h"
#include "chrome/browser/preloading/prerender/prerender_utils.h"
#include "chrome/browser/preloading/scoped_prewarm_feature_list.h"
#include "chrome/browser/search_engines/template_url_service_factory_test_util.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "components/search_engines/template_url_service.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "content/public/test/preloading_test_util.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/web_contents_tester.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
class PrerenderManagerTest : public ChromeRenderViewHostTestHarness {
public:
PrerenderManagerTest(const PrerenderManagerTest&) = delete;
PrerenderManagerTest& operator=(const PrerenderManagerTest&) = delete;
PrerenderManagerTest()
: ChromeRenderViewHostTestHarness(
content::BrowserTaskEnvironment::REAL_IO_THREAD),
prerender_helper_(
base::BindRepeating(&PrerenderManagerTest::GetActiveWebContents,
base::Unretained(this))) {
reuse_feature_list_.InitAndEnableFeatureWithParameters(
features::kPrerender2ReuseHost, {{"reuse_search_host", "true"}});
}
void SetUp() override {
prerender_helper_.RegisterServerRequestMonitor(&test_server_);
ASSERT_TRUE(test_server_.Start());
ChromeRenderViewHostTestHarness::SetUp();
// Set up TemplateURLService for search prerendering.
TemplateURLServiceFactoryTestUtil factory_util(profile());
factory_util.VerifyLoad();
TemplateURLData template_url_data;
template_url_data.SetURL(GetUrl(search_site() + "?q={searchTerms}").spec());
factory_util.model()->SetUserSelectedDefaultSearchProvider(
factory_util.model()->Add(
std::make_unique<TemplateURL>(template_url_data)));
PrerenderManager::CreateForWebContents(GetActiveWebContents());
ASSERT_TRUE(PrerenderManager::FromWebContents(web_contents()));
web_contents_delegate_ =
std::make_unique<content::test::ScopedPrerenderWebContentsDelegate>(
*web_contents());
}
content::WebContents* GetActiveWebContents() { return web_contents(); }
GURL GetSearchSuggestionUrl(const std::string& original_query,
const std::string& search_terms) {
return GetUrl(search_site() + "?q=" + search_terms +
"&oq=" + original_query);
}
GURL GetCanonicalSearchUrl(const GURL& search_suggestion_url) {
GURL canonical_search_url;
EXPECT_TRUE(HasCanonicalPreloadingOmniboxSearchURL(
search_suggestion_url, profile(), &canonical_search_url));
return canonical_search_url;
}
content::WebContentsTester* web_contents_tester() {
return content::WebContentsTester::For(web_contents());
}
protected:
GURL GetUrl(const std::string& path) { return test_server_.GetURL(path); }
PrerenderManager* prerender_manager() {
return PrerenderManager::FromWebContents(web_contents());
}
content::test::PrerenderTestHelper& prerender_helper() {
return prerender_helper_;
}
content::PreloadingFailureReason ToPreloadingFailureReason(
PrerenderPredictionStatus status) {
return static_cast<content::PreloadingFailureReason>(
static_cast<int>(status) +
static_cast<int>(content::PreloadingFailureReason::
kPreloadingFailureReasonContentEnd));
}
content::PreloadingAttempt* TriggerDirectUrlInputPrerender(
const GURL& prerendering_url) {
auto* preloading_data = content::PreloadingData::GetOrCreateForWebContents(
GetActiveWebContents());
content::PreloadingURLMatchCallback same_url_matcher =
content::PreloadingData::GetSameURLMatcher(prerendering_url);
content::PreloadingAttempt* preloading_attempt =
preloading_data->AddPreloadingAttempt(
chrome_preloading_predictor::kOmniboxDirectURLInput,
content::PreloadingType::kPrerender, same_url_matcher,
GetActiveWebContents()
->GetPrimaryMainFrame()
->GetPageUkmSourceId());
prerender_manager()->StartPrerenderDirectUrlInput(prerendering_url,
*preloading_attempt);
return preloading_attempt;
}
private:
static std::string search_site() { return "/title1.html"; }
content::test::PrerenderTestHelper prerender_helper_;
test::ScopedPrewarmFeatureList scoped_prewarm_feature_list_{
test::ScopedPrewarmFeatureList::PrewarmState::kDisabled};
base::test::ScopedFeatureList reuse_feature_list_;
std::unique_ptr<content::test::ScopedPrerenderWebContentsDelegate>
web_contents_delegate_;
net::EmbeddedTestServer test_server_;
};
TEST_F(PrerenderManagerTest, StartCleanSearchSuggestionPrerender) {
GURL prerendering_url = GetSearchSuggestionUrl("pre", "prerender");
GURL canonical_url = GetCanonicalSearchUrl(prerendering_url);
content::test::PrerenderHostRegistryObserver registry_observer(
*GetActiveWebContents());
prerender_manager()->StartPrerenderSearchResult(
canonical_url, prerendering_url, /*attempt=*/nullptr);
registry_observer.WaitForTrigger(prerendering_url);
content::FrameTreeNodeId prerender_host_id =
prerender_helper().GetHostForUrl(prerendering_url);
EXPECT_TRUE(prerender_host_id);
}
// Tests that the old prerender will be destroyed when starting prerendering a
// different search result.
TEST_F(PrerenderManagerTest, StartNewSuggestionPrerender) {
GURL prerendering_url1 = GetSearchSuggestionUrl("pre", "prefetch");
GURL canonical_url1 = GetCanonicalSearchUrl(prerendering_url1);
content::test::PrerenderHostRegistryObserver registry_observer(
*GetActiveWebContents());
prerender_manager()->StartPrerenderSearchResult(
canonical_url1, prerendering_url1, /*attempt=*/nullptr);
registry_observer.WaitForTrigger(prerendering_url1);
content::FrameTreeNodeId prerender_host_id1 =
prerender_helper().GetHostForUrl(prerendering_url1);
ASSERT_TRUE(prerender_host_id1);
content::test::PrerenderHostObserver host_observer(*GetActiveWebContents(),
prerender_host_id1);
GURL prerendering_url2 = GetSearchSuggestionUrl("prer", "prerender");
GURL canonical_url2 = GetCanonicalSearchUrl(prerendering_url2);
prerender_manager()->StartPrerenderSearchResult(
canonical_url2, prerendering_url2, /*attempt=*/nullptr);
host_observer.WaitForDestroyed();
registry_observer.WaitForTrigger(prerendering_url2);
EXPECT_TRUE(prerender_manager()->HasSearchResultPagePrerendered());
EXPECT_EQ(canonical_url2,
prerender_manager()->GetPrerenderCanonicalSearchURLForTesting());
content::FrameTreeNodeId prerender_host_id2 =
prerender_helper().GetHostForUrl(prerendering_url2);
EXPECT_EQ(prerender_host_id1, prerender_host_id2);
}
// Tests that the old prerender is not destroyed when starting prerendering the
// same search suggestion.
TEST_F(PrerenderManagerTest, StartSameSuggestionPrerender) {
GURL prerendering_url1 = GetSearchSuggestionUrl("pre", "prerender");
GURL canonical_url = GetCanonicalSearchUrl(prerendering_url1);
content::test::PrerenderHostRegistryObserver registry_observer(
*GetActiveWebContents());
prerender_manager()->StartPrerenderSearchResult(
canonical_url, prerendering_url1, /*attempt=*/nullptr);
registry_observer.WaitForTrigger(prerendering_url1);
content::FrameTreeNodeId prerender_host_id1 =
prerender_helper().GetHostForUrl(prerendering_url1);
EXPECT_TRUE(prerender_host_id1);
GURL prerendering_url2 = GetSearchSuggestionUrl("prer", "prerender");
prerender_manager()->StartPrerenderSearchResult(
canonical_url, prerendering_url2, /*attempt=*/nullptr);
EXPECT_TRUE(prerender_manager()->HasSearchResultPagePrerendered());
// The created prerender for `prerendering_url1` still exists, so the
// prerender_host_id should be the same.
content::FrameTreeNodeId prerender_host_id2 =
prerender_helper().GetHostForUrl(prerendering_url2);
EXPECT_EQ(prerender_host_id2, prerender_host_id1);
}
TEST_F(PrerenderManagerTest, StartCleanPrerenderDirectUrlInput) {
GURL prerendering_url = GetUrl("/foo");
content::test::PrerenderHostRegistryObserver registry_observer(
*GetActiveWebContents());
TriggerDirectUrlInputPrerender(prerendering_url);
registry_observer.WaitForTrigger(prerendering_url);
content::FrameTreeNodeId prerender_host_id =
prerender_helper().GetHostForUrl(prerendering_url);
EXPECT_TRUE(prerender_host_id);
}
// Test that the PreloadingTriggeringOutcome is set to kFailure when the DUI
// predictor suggests a different URL.
TEST_F(PrerenderManagerTest, StartNewPrerenderDirectUrlInput) {
GURL prerendering_url = GetUrl("/foo");
content::test::PrerenderHostRegistryObserver registry_observer(
*GetActiveWebContents());
content::PreloadingAttempt* preloading_attempt =
TriggerDirectUrlInputPrerender(prerendering_url);
registry_observer.WaitForTrigger(prerendering_url);
content::FrameTreeNodeId prerender_host_id =
prerender_helper().GetHostForUrl(prerendering_url);
EXPECT_TRUE(prerender_host_id);
content::test::PrerenderHostObserver host_observer(*GetActiveWebContents(),
prerender_host_id);
GURL prerendering_url2 = GetUrl("/bar");
TriggerDirectUrlInputPrerender(prerendering_url2);
host_observer.WaitForDestroyed();
registry_observer.WaitForTrigger(prerendering_url2);
EXPECT_EQ(ToPreloadingFailureReason(PrerenderPredictionStatus::kCancelled),
content::test::PreloadingAttemptAccessor(preloading_attempt)
.GetFailureReason());
}
// TODO(https://crbug.com/334988071): Add all embedder triggers and make it
// mandatory when adding new triggers to PrerenderManager.
enum TriggerType {
kDirectUrlInput = 0,
kSearchSuggestion = 1,
};
class PrerenderManagerBasicRequirementTest
: public testing::WithParamInterface<TriggerType>,
public PrerenderManagerTest {
public:
static std::string DescribeParams(
const testing::TestParamInfo<ParamType>& info) {
switch (info.param) {
case kDirectUrlInput:
return "DirectUrlInput";
case kSearchSuggestion:
return "SearchSuggestion";
}
}
content::FrameTreeNodeId StartPrerender() {
content::test::PrerenderHostRegistryObserver registry_observer(
*GetActiveWebContents());
GURL prerendering_url;
switch (GetParam()) {
case kDirectUrlInput:
prerendering_url = GetUrl("/foo");
TriggerDirectUrlInputPrerender(prerendering_url);
break;
case kSearchSuggestion:
prerendering_url = GetSearchSuggestionUrl("pre", "prerender");
GURL canonical_url = GetCanonicalSearchUrl(prerendering_url);
prerender_manager()->StartPrerenderSearchResult(
canonical_url, prerendering_url, /*attempt=*/nullptr);
break;
}
registry_observer.WaitForTrigger(prerendering_url);
return prerender_helper().GetHostForUrl(prerendering_url);
}
std::string GetMetricSuffix() {
switch (GetParam()) {
case kDirectUrlInput:
return prerender_utils::kDirectUrlInputMetricSuffix;
case kSearchSuggestion:
return prerender_utils::kDefaultSearchEngineMetricSuffix;
}
NOTREACHED();
}
// Navigates to another page that cannot be prerendered.
void NavigateAway() {
web_contents_tester()->NavigateAndCommit(GetUrl("/empty.html"));
}
};
INSTANTIATE_TEST_SUITE_P(
/* no prefix */,
PrerenderManagerBasicRequirementTest,
testing::Values(TriggerType::kDirectUrlInput,
TriggerType::kSearchSuggestion),
PrerenderManagerBasicRequirementTest::DescribeParams);
// Tests that the PrerenderHandle is destroyed when the primary page changed.
TEST_P(PrerenderManagerBasicRequirementTest, NavigateAway) {
base::HistogramTester histogram_tester;
content::FrameTreeNodeId prerender_host_id = StartPrerender();
ASSERT_TRUE(prerender_host_id);
content::test::PrerenderHostObserver host_observer(*GetActiveWebContents(),
prerender_host_id);
NavigateAway();
host_observer.WaitForDestroyed();
histogram_tester.ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.Embedder_" +
GetMetricSuffix(),
/*kTriggerDestroyed*/ 16, 1);
switch (GetParam()) {
case kSearchSuggestion:
EXPECT_FALSE(prerender_manager()->HasSearchResultPagePrerendered());
break;
default:
return;
}
}
class PrerenderManagerPrewarmTest : public PrerenderManagerTest {
public:
PrerenderManagerPrewarmTest() = default;
~PrerenderManagerPrewarmTest() override = default;
private:
test::ScopedPrewarmFeatureList scoped_prewarm_feature_list_{
test::ScopedPrewarmFeatureList::PrewarmState::kEnabledWithNoTrigger};
};
TEST_F(PrerenderManagerPrewarmTest, StartPrewarmSearchResult) {
const GURL prewarm_url(features::kPrewarmUrl.Get());
ASSERT_TRUE(prewarm_url.is_valid());
// Prerender the prewarm page.
content::test::PrerenderHostRegistryObserver registry_observer(
*GetActiveWebContents());
ASSERT_TRUE(prerender_manager()->MaybeStartPrewarmSearchResult());
registry_observer.WaitForTrigger(prewarm_url);
// Prewarm page should not be found here as it's matcher was set as not
// matching to any URL.
content::FrameTreeNodeId prerender_host_id =
prerender_helper().GetHostForUrl(prewarm_url);
EXPECT_EQ(prerender_host_id, content::FrameTreeNodeId());
}
} // namespace