| // Copyright 2021 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <string> |
| |
| #include "base/bind.h" |
| #include "base/files/file_path.h" |
| #include "base/path_service.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/predictors/autocomplete_action_predictor.h" |
| #include "chrome/browser/predictors/autocomplete_action_predictor_factory.h" |
| #include "chrome/browser/prefetch/prefetch_prefs.h" |
| #include "chrome/browser/prefetch/search_prefetch/field_trial_settings.h" |
| #include "chrome/browser/prerender/prerender_manager.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/search_engines/template_url_service_factory.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/test/base/chrome_test_utils.h" |
| #include "chrome/test/base/search_test_utils.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "components/omnibox/browser/autocomplete_match.h" |
| #include "components/policy/policy_constants.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/search_engines/template_url_data.h" |
| #include "components/search_engines/template_url_service.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 "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 "testing/gtest/include/gtest/gtest.h" |
| #include "ui/base/page_transition_types.h" |
| |
| #if BUILDFLAG(IS_ANDROID) |
| #include "chrome/test/base/android/android_browser_test.h" |
| #else |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #endif |
| |
| namespace { |
| |
| class OmniboxPrerenderBrowserTest : public PlatformBrowserTest { |
| public: |
| OmniboxPrerenderBrowserTest() |
| : prerender_helper_(base::BindRepeating( |
| &OmniboxPrerenderBrowserTest::GetActiveWebContents, |
| base::Unretained(this))) { |
| scoped_feature_list_.InitAndEnableFeature( |
| features::kOmniboxTriggerForPrerender2); |
| } |
| |
| 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_; |
| } |
| |
| Profile* GetProfile() { |
| #if BUILDFLAG(IS_ANDROID) |
| return chrome_test_utils::GetProfile(this); |
| #else |
| return browser()->profile(); |
| #endif |
| } |
| |
| predictors::AutocompleteActionPredictor* GetAutocompleteActionPredictor() { |
| Profile* profile = GetProfile(); |
| return predictors::AutocompleteActionPredictorFactory::GetForProfile( |
| profile); |
| } |
| |
| private: |
| content::test::PrerenderTestHelper prerender_helper_; |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| // This test class uses the kPrerender2 default value, which is true for Android |
| // and false for others. In contrast, OmniboxPrerenderBrowserTest enables |
| // kPrerender2 by PrerenderTestHelper. |
| class OmniboxPrerenderDefaultPrerender2BrowserTest |
| : public PlatformBrowserTest { |
| public: |
| OmniboxPrerenderDefaultPrerender2BrowserTest() { |
| scoped_feature_list_.InitAndEnableFeature( |
| features::kOmniboxTriggerForPrerender2); |
| } |
| |
| void SetUp() override { 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); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| // Tests that Prerender2 cannot be triggered when preload setting is disabled. |
| IN_PROC_BROWSER_TEST_F(OmniboxPrerenderBrowserTest, DisableNetworkPrediction) { |
| // Disable network prediction. |
| PrefService* prefs = GetProfile()->GetPrefs(); |
| prefetch::SetPreloadPagesState(prefs, |
| prefetch::PreloadPagesState::kNoPreloading); |
| ASSERT_FALSE(prefetch::IsSomePreloadingEnabled(*prefs)); |
| |
| // Attempt to prerender a direct URL input. |
| auto* predictor = GetAutocompleteActionPredictor(); |
| ASSERT_TRUE(predictor); |
| content::WebContents* web_contents = GetActiveWebContents(); |
| GURL prerender_url = embedded_test_server()->GetURL("/simple.html"); |
| predictor->StartPrerendering(prerender_url, *web_contents, gfx::Size(50, 50)); |
| |
| // 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); |
| |
| // Re-enable the setting. |
| prefetch::SetPreloadPagesState( |
| prefs, prefetch::PreloadPagesState::kStandardPreloading); |
| ASSERT_TRUE(prefetch::IsSomePreloadingEnabled(*prefs)); |
| |
| content::test::PrerenderHostRegistryObserver registry_observer(*web_contents); |
| // Attempt to trigger prerendering again. |
| predictor->StartPrerendering(prerender_url, *web_contents, gfx::Size(50, 50)); |
| |
| // 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); |
| } |
| |
| // Verifies that prerendering functions in document are properly exposed. |
| // TODO(https://crbug.com/1286374): test is flaky. |
| IN_PROC_BROWSER_TEST_F( |
| OmniboxPrerenderBrowserTest, |
| DISABLED_PrerenderFunctionsProperlyExportedWhenInitiatedByOmnibox) { |
| const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html"); |
| ASSERT_TRUE(GetActiveWebContents()); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), kInitialUrl)); |
| EXPECT_EQ(true, |
| EvalJs(GetActiveWebContents(), "document.prerendering === false")); |
| EXPECT_EQ( |
| 0, |
| EvalJs(GetActiveWebContents(), |
| "performance.getEntriesByType('navigation')[0].activationStart")); |
| |
| const GURL kPrerenderingUrl = |
| embedded_test_server()->GetURL("/prerender/onprerendering_check.html"); |
| |
| GetAutocompleteActionPredictor()->StartPrerendering( |
| kPrerenderingUrl, *GetActiveWebContents(), gfx::Size(50, 50)); |
| |
| int host_id = prerender_helper().GetHostForUrl(kPrerenderingUrl); |
| content::RenderFrameHost* prerender_frame_host = |
| prerender_helper().GetPrerenderedMainFrameHost(host_id); |
| EXPECT_EQ(true, |
| EvalJs(prerender_frame_host, "document.prerendering === true")); |
| |
| // Simulate a browser-initiated navigation. |
| content::test::PrerenderHostObserver prerender_observer( |
| *GetActiveWebContents(), kPrerenderingUrl); |
| |
| GetActiveWebContents()->OpenURL(content::OpenURLParams( |
| kPrerenderingUrl, content::Referrer(), WindowOpenDisposition::CURRENT_TAB, |
| ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED | |
| ui::PAGE_TRANSITION_FROM_ADDRESS_BAR), |
| /*is_renderer_initiated=*/false)); |
| prerender_observer.WaitForActivation(); |
| |
| EXPECT_EQ(true, EvalJs(prerender_frame_host, |
| "onprerenderingchange_observed_promise")); |
| EXPECT_LT( |
| 0.0, |
| EvalJs(prerender_frame_host, |
| "performance.getEntriesByType('navigation')[0].activationStart") |
| .ExtractDouble()); |
| } |
| |
| // Verifies that the exportation of prerendering functions in the document is |
| // handled properly when Prerender2 is set to be the default value. For android, |
| // on which Prerender2 is enabled, those functions are expected to be exported, |
| // while the functions are not supposed to be exported on other platforms. |
| IN_PROC_BROWSER_TEST_F(OmniboxPrerenderDefaultPrerender2BrowserTest, |
| PrerenderFunctionsCheckWithDefaultFlag) { |
| const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html"); |
| ASSERT_TRUE(GetActiveWebContents()); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), kInitialUrl)); |
| |
| #if BUILDFLAG(IS_ANDROID) |
| EXPECT_EQ(true, |
| EvalJs(GetActiveWebContents(), "document.prerendering === false")); |
| EXPECT_EQ(0, EvalJs(GetActiveWebContents(), |
| "performance.getEntriesByType('navigation')[0]." |
| "activationStart")); |
| EXPECT_EQ(true, EvalJs(GetActiveWebContents(), |
| "'onprerenderingchange' in document")); |
| #else |
| EXPECT_EQ(true, EvalJs(GetActiveWebContents(), |
| "document.prerendering === undefined")); |
| EXPECT_EQ(true, EvalJs(GetActiveWebContents(), |
| "performance.getEntriesByType('navigation')[0]." |
| "activationStart === undefined")); |
| EXPECT_EQ(true, EvalJs(GetActiveWebContents(), |
| "document.onprerenderingchange === undefined")); |
| #endif |
| } |
| |
| class PrerenderOmniboxSearchSuggestionExpiryBrowserTest |
| : public OmniboxPrerenderBrowserTest { |
| public: |
| PrerenderOmniboxSearchSuggestionExpiryBrowserTest() { |
| feature_list_.InitWithFeaturesAndParameters( |
| {{features::kSupportSearchSuggestionForPrerender2, {}}, |
| {kSearchPrefetchServicePrefetching, |
| { |
| {"prefetch_caching_limit_ms", "10"}, |
| }}}, |
| {}); |
| } |
| |
| void SetUpOnMainThread() override { |
| OmniboxPrerenderBrowserTest::SetUpOnMainThread(); |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| |
| search_engine_server_.SetSSLConfig( |
| net::test_server::EmbeddedTestServer::CERT_TEST_NAMES); |
| search_engine_server_.ServeFilesFromDirectory( |
| base::PathService::CheckedGet(chrome::DIR_TEST_DATA)); |
| ASSERT_TRUE(search_engine_server_.Start()); |
| |
| TemplateURLService* model = TemplateURLServiceFactory::GetForProfile( |
| chrome_test_utils::GetProfile(this)); |
| ASSERT_TRUE(model); |
| search_test_utils::WaitForTemplateURLServiceToLoad(model); |
| ASSERT_TRUE(model->loaded()); |
| TemplateURLData data; |
| data.SetShortName(kSearchDomain16); |
| data.SetKeyword(data.short_name()); |
| data.SetURL(search_engine_server_ |
| .GetURL(kSearchDomain, "/title1.html?q={searchTerms}") |
| .spec()); |
| TemplateURL* template_url = model->Add(std::make_unique<TemplateURL>(data)); |
| ASSERT_TRUE(template_url); |
| model->SetUserSelectedDefaultSearchProvider(template_url); |
| } |
| |
| protected: |
| int PrerenderQuery(const std::string& search_terms, |
| const GURL& expected_prerender_url) { |
| AutocompleteMatch match = CreateSearchSuggestionMatch(search_terms); |
| prerender_manager_->StartPrerenderSearchSuggestion(match); |
| int host_id = prerender_helper().GetHostForUrl(expected_prerender_url); |
| EXPECT_NE(host_id, content::RenderFrameHost::kNoFrameTreeNodeId); |
| return host_id; |
| } |
| |
| void PrerenderQueryAndWaitForExpiring(const std::string& search_terms, |
| const GURL& expected_prerender_url) { |
| int host_id = PrerenderQuery(search_terms, expected_prerender_url); |
| |
| content::test::PrerenderHostObserver prerender_observer( |
| *GetActiveWebContents(), host_id); |
| |
| // The prerender will be destroyed automatically soon, since the duration is |
| // set to 10ms. |
| prerender_observer.WaitForDestroyed(); |
| } |
| |
| GURL GetSearchSuggestionUrl(const std::string& search_terms) { |
| return search_engine_server_.GetURL(kSearchDomain, |
| "/title1.html?q=" + search_terms); |
| } |
| |
| void InitializePrerenderManager() { |
| PrerenderManager::CreateForWebContents(GetActiveWebContents()); |
| |
| prerender_manager_ = |
| PrerenderManager::FromWebContents(GetActiveWebContents()); |
| ASSERT_TRUE(prerender_manager_); |
| } |
| |
| PrerenderManager* prerender_manager() { return prerender_manager_; } |
| |
| private: |
| AutocompleteMatch CreateSearchSuggestionMatch( |
| const std::string& search_terms) { |
| AutocompleteMatch match; |
| match.search_terms_args = std::make_unique<TemplateURLRef::SearchTermsArgs>( |
| base::UTF8ToUTF16(search_terms)); |
| match.search_terms_args->original_query = base::UTF8ToUTF16(search_terms); |
| match.destination_url = GetSearchSuggestionUrl(search_terms); |
| match.keyword = base::UTF8ToUTF16(search_terms); |
| match.RecordAdditionalInfo("should_prerender", "true"); |
| return match; |
| } |
| |
| constexpr static char kSearchDomain[] = "a.test"; |
| constexpr static char16_t kSearchDomain16[] = u"a.test"; |
| base::test::ScopedFeatureList feature_list_; |
| PrerenderManager* prerender_manager_; |
| net::test_server::EmbeddedTestServer search_engine_server_{ |
| net::test_server::EmbeddedTestServer::TYPE_HTTPS}; |
| }; |
| |
| // Tests that an ongoing prerender which loads an SRP should be canceled |
| // automatically after the expiry duration. |
| IN_PROC_BROWSER_TEST_F(PrerenderOmniboxSearchSuggestionExpiryBrowserTest, |
| SearchPrerenderExpiry) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html"); |
| ASSERT_TRUE(GetActiveWebContents()); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), kInitialUrl)); |
| InitializePrerenderManager(); |
| |
| std::string search_query = "prerender2"; |
| GURL expected_prerender_url = GetSearchSuggestionUrl("prerender222"); |
| PrerenderQueryAndWaitForExpiring("prerender222", expected_prerender_url); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_" |
| "DefaultSearchEngine", |
| /*PrerenderHost::FinalStatus::kEmbedderTriggeredAndDestroyed*/ 35, 1); |
| |
| // Select the prerender hint. The prerendered result has been deleted, so |
| // browser loads the search result over again. |
| content::TestNavigationObserver observer(GetActiveWebContents()); |
| GetActiveWebContents()->OpenURL(content::OpenURLParams( |
| expected_prerender_url, content::Referrer(), |
| WindowOpenDisposition::CURRENT_TAB, |
| ui::PageTransitionFromInt(ui::PAGE_TRANSITION_GENERATED | |
| ui::PAGE_TRANSITION_FROM_ADDRESS_BAR), |
| /*is_renderer_initiated=*/false)); |
| observer.Wait(); |
| |
| // The prediction is correct, so kHitFinished should be recorded. |
| histogram_tester.ExpectUniqueSample( |
| internal::kHistogramPrerenderPredictionStatusDefaultSearchEngine, |
| PrerenderPredictionStatus::kHitFinished, 1); |
| // Since the prerendered page ran out of time, the timing metric should |
| // record `prefetch_caching_limit_ms`. |
| histogram_tester.ExpectUniqueTimeSample( |
| "Prerender.Experimental.Search." |
| "FirstCorrectPrerenderHintReceivedToRealSearchNavigationStartedDuration", |
| base::Milliseconds(10), 1); |
| } |
| |
| // Tests that kCanceled is correctly recorded in the case that PrerenderManager |
| // receives a new suggestion. Note: kCancel should only recorded when |
| // PrerenderManager receives a new suggestion or on primary-page changed. |
| // Otherwise one prediction might be recorded twice. |
| IN_PROC_BROWSER_TEST_F(PrerenderOmniboxSearchSuggestionExpiryBrowserTest, |
| DifferentSuggestionAfterPrerenderExpired) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html"); |
| ASSERT_TRUE(GetActiveWebContents()); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), kInitialUrl)); |
| InitializePrerenderManager(); |
| |
| GURL expected_prerender_url = GetSearchSuggestionUrl("prerender222"); |
| // Prerender the first query, and wait for it to be deleted. |
| PrerenderQueryAndWaitForExpiring("prerender222", expected_prerender_url); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_" |
| "DefaultSearchEngine", |
| /*PrerenderHost::FinalStatus::kEmbedderTriggeredAndDestroyed*/ 35, 1); |
| |
| // Nothing should be recorded. Because there is no new navigation nor new |
| // search suggestion. |
| histogram_tester.ExpectTotalCount( |
| internal::kHistogramPrerenderPredictionStatusDefaultSearchEngine, 0); |
| |
| // Suggest to prerender another term. |
| GURL prerender_url_2 = GetSearchSuggestionUrl("prerender233"); |
| PrerenderQuery("prerender233", prerender_url_2); |
| |
| // PrerenderPredictionStatus::kCancelled should be recorded for the prediction |
| // of "prerender222". |
| histogram_tester.ExpectUniqueSample( |
| internal::kHistogramPrerenderPredictionStatusDefaultSearchEngine, |
| PrerenderPredictionStatus::kCancelled, 1); |
| |
| content::TestNavigationObserver observer(GetActiveWebContents()); |
| GetActiveWebContents()->OpenURL(content::OpenURLParams( |
| prerender_url_2, content::Referrer(), WindowOpenDisposition::CURRENT_TAB, |
| ui::PageTransitionFromInt(ui::PAGE_TRANSITION_GENERATED | |
| ui::PAGE_TRANSITION_FROM_ADDRESS_BAR), |
| /*is_renderer_initiated=*/false)); |
| observer.Wait(); |
| |
| // The prediction is correct, so kHitFinished should be recorded. |
| histogram_tester.ExpectBucketCount( |
| internal::kHistogramPrerenderPredictionStatusDefaultSearchEngine, |
| PrerenderPredictionStatus::kHitFinished, 1); |
| |
| // Two predictions, two samples. |
| histogram_tester.ExpectTotalCount( |
| internal::kHistogramPrerenderPredictionStatusDefaultSearchEngine, 2); |
| } |
| |
| } // namespace |