| // 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 |