| // 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/bind.h" |
| #include "base/containers/adapters.h" |
| #include "base/files/file_path.h" |
| #include "base/path_service.h" |
| #include "base/ranges/algorithm.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier.h" |
| #include "chrome/browser/predictors/autocomplete_action_predictor.h" |
| #include "chrome/browser/predictors/autocomplete_action_predictor_factory.h" |
| #include "chrome/browser/preloading/chrome_preloading.h" |
| #include "chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_service.h" |
| #include "chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_service_factory.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/browser/safe_browsing/test_safe_browsing_navigation_observer_manager.h" |
| #include "chrome/browser/search_engines/template_url_service_factory.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_commands.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/browser/ui/location_bar/location_bar.h" |
| #include "chrome/browser/ui/omnibox/omnibox_tab_helper.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "chrome/test/base/chrome_test_utils.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/interactive_test_utils.h" |
| #include "chrome/test/base/search_test_utils.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/omnibox/browser/base_search_provider.h" |
| #include "components/omnibox/browser/omnibox_edit_model.h" |
| #include "components/safe_browsing/core/common/safe_browsing_prefs.h" |
| #include "components/search_engines/template_url_data.h" |
| #include "components/search_engines/template_url_service.h" |
| #include "content/public/browser/global_routing_id.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/web_contents_observer.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/navigation_handle_observer.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" |
| |
| namespace { |
| |
| using UkmEntry = ukm::TestUkmRecorder::HumanReadableUkmEntry; |
| using ukm::builders::Preloading_Attempt; |
| using ukm::builders::Preloading_Prediction; |
| |
| content::PreloadingFailureReason ToPreloadingFailureReason( |
| PrerenderPredictionStatus status) { |
| return static_cast<content::PreloadingFailureReason>( |
| static_cast<int>(status) + |
| static_cast<int>(content::PreloadingFailureReason:: |
| kPreloadingFailureReasonContentEnd)); |
| } |
| |
| class AutocompleteActionPredictorObserverImpl |
| : public predictors::AutocompleteActionPredictor::Observer { |
| public: |
| explicit AutocompleteActionPredictorObserverImpl( |
| predictors::AutocompleteActionPredictor* predictor) { |
| observation_.Observe(predictor); |
| } |
| |
| void WaitForInitialization() { |
| base::RunLoop loop; |
| waiting_ = loop.QuitClosure(); |
| loop.Run(); |
| } |
| |
| void OnInitialized() override { |
| DCHECK(waiting_); |
| std::move(waiting_).Run(); |
| } |
| |
| base::ScopedObservation<predictors::AutocompleteActionPredictor, |
| predictors::AutocompleteActionPredictor::Observer> |
| observation_{this}; |
| |
| base::OnceClosure waiting_; |
| }; |
| |
| // This is a browser test for Omnibox triggered prerendering. This is |
| // implemented as an interactive UI test so that it can emulate navigation |
| // initiated by URL typed on the Omnibox. |
| class PrerenderOmniboxUIBrowserTest : public InProcessBrowserTest, |
| public content::WebContentsObserver { |
| public: |
| PrerenderOmniboxUIBrowserTest() |
| : prerender_helper_(base::BindRepeating( |
| &PrerenderOmniboxUIBrowserTest::GetActiveWebContents, |
| base::Unretained(this))) { |
| scoped_feature_list_.InitAndEnableFeature( |
| features::kOmniboxTriggerForPrerender2); |
| } |
| |
| void SetUp() override { |
| prerender_helper_.SetUp(embedded_test_server()); |
| InProcessBrowserTest::SetUp(); |
| } |
| |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| embedded_test_server()->ServeFilesFromDirectory( |
| base::PathService::CheckedGet(chrome::DIR_TEST_DATA)); |
| test_ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>(); |
| ukm_entry_builder_ = |
| std::make_unique<content::test::PreloadingAttemptUkmEntryBuilder>( |
| ToPreloadingPredictor( |
| ChromePreloadingPredictor::kOmniboxDirectURLInput)); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| } |
| |
| void TearDownOnMainThread() override { |
| ASSERT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete()); |
| } |
| |
| content::WebContents* GetActiveWebContents() { |
| return browser()->tab_strip_model()->GetActiveWebContents(); |
| } |
| |
| content::test::PrerenderTestHelper& prerender_helper() { |
| return prerender_helper_; |
| } |
| |
| ukm::TestAutoSetUkmRecorder* test_ukm_recorder() { |
| return test_ukm_recorder_.get(); |
| } |
| |
| const content::test::PreloadingAttemptUkmEntryBuilder& ukm_entry_builder() { |
| return *ukm_entry_builder_; |
| } |
| |
| // Returns last committed page transition type. This value is only |
| // meaningful after calling Observe(GetActiveWebContents()) in the test case |
| // and after DidFinishNavigation. |
| ui::PageTransition GetLastPageTransitionType() { |
| return last_finished_page_transition_type_; |
| } |
| |
| // Returns last committed page is prerendered or not. This value is only |
| // meaningful after calling Observe(GetActiveWebContents()) in the test case |
| // and after DidFinishNavigation. |
| bool IsPrerenderingNavigation() { return is_prerendering_page_; } |
| |
| protected: |
| void StartOmniboxNavigationAndWaitForActivation(const GURL& url) { |
| SetOmniboxText(url.spec()); |
| PressEnterAndWaitForActivation(url); |
| } |
| |
| void SelectAutocompleteMatchAndWaitForActivation( |
| const AutocompleteMatch& match, |
| int host_id) { |
| GURL url = match.destination_url; |
| content::test::PrerenderHostObserver prerender_observer( |
| *GetActiveWebContents(), host_id); |
| omnibox()->model()->OpenMatch(match, WindowOpenDisposition::CURRENT_TAB, |
| url, std::u16string(), 0); |
| prerender_observer.WaitForActivation(); |
| } |
| |
| predictors::AutocompleteActionPredictor* GetAutocompleteActionPredictor() { |
| return predictors::AutocompleteActionPredictorFactory::GetForProfile( |
| browser()->profile()); |
| } |
| |
| void WaitForAutocompleteActionPredictorInitialization() { |
| // Initialization is already completed so it's not needed to wait for |
| // initialization. |
| if (GetAutocompleteActionPredictor()->initialized()) |
| return; |
| AutocompleteActionPredictorObserverImpl predictor_observer( |
| GetAutocompleteActionPredictor()); |
| predictor_observer.WaitForInitialization(); |
| } |
| |
| private: |
| OmniboxView* omnibox() { |
| return browser()->window()->GetLocationBar()->GetOmniboxView(); |
| } |
| |
| void FocusOmnibox() { |
| // If the omnibox already has focus, just notify OmniboxTabHelper. |
| if (omnibox()->model()->has_focus()) { |
| OmniboxTabHelper::FromWebContents(GetActiveWebContents()) |
| ->OnFocusChanged(OMNIBOX_FOCUS_VISIBLE, |
| OMNIBOX_FOCUS_CHANGE_EXPLICIT); |
| } else { |
| browser()->window()->GetLocationBar()->FocusLocation(false); |
| } |
| } |
| |
| void SetOmniboxText(const std::string& text) { |
| FocusOmnibox(); |
| // Enter user input mode to prevent spurious unelision. |
| omnibox()->model()->SetInputInProgress(true); |
| omnibox()->OnBeforePossibleChange(); |
| omnibox()->SetUserText(base::UTF8ToUTF16(text), true); |
| omnibox()->OnAfterPossibleChange(true); |
| } |
| |
| void PressEnter() { |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| [](const Browser* browser, bool ctrl_key) { |
| EXPECT_TRUE(ui_test_utils::SendKeyPressSync( |
| browser, ui::VKEY_RETURN, ctrl_key, false, false, false)); |
| }, |
| browser(), false)); |
| } |
| |
| // Presses enter and waits for Activation |
| void PressEnterAndWaitForActivation(const GURL& url) { |
| content::test::PrerenderHostObserver prerender_observer( |
| *GetActiveWebContents(), url); |
| PressEnter(); |
| prerender_observer.WaitForActivation(); |
| } |
| |
| // WebContentsObserver: |
| void DidFinishNavigation( |
| content::NavigationHandle* navigation_handle) override { |
| last_finished_page_transition_type_ = |
| navigation_handle->GetPageTransition(); |
| is_prerendering_page_ = navigation_handle->IsPrerenderedPageActivation(); |
| } |
| |
| content::test::PrerenderTestHelper prerender_helper_; |
| base::test::ScopedFeatureList scoped_feature_list_; |
| ui::PageTransition last_finished_page_transition_type_; |
| std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_ukm_recorder_; |
| std::unique_ptr<content::test::PreloadingAttemptUkmEntryBuilder> |
| ukm_entry_builder_; |
| bool is_prerendering_page_; |
| }; |
| |
| // This test covers the path from starting a omnibox triggered prerendering |
| // by AutocompleteActionPredictor, and simulate the omnibox input to check |
| // that prerendering can be activated successfully and the page transition type |
| // is correctly set as (ui::PAGE_TRANSITION_TYPED | |
| // ui::PAGE_TRANSITION_FROM_ADDRESS_BAR). |
| IN_PROC_BROWSER_TEST_F(PrerenderOmniboxUIBrowserTest, |
| PrerenderingByAutocompleteActionPredictorCanActivate) { |
| Observe(GetActiveWebContents()); |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html"); |
| ASSERT_TRUE(GetActiveWebContents()); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), kInitialUrl)); |
| |
| // Attempt to prerender a direct URL input. |
| ASSERT_TRUE(GetAutocompleteActionPredictor()); |
| WaitForAutocompleteActionPredictorInitialization(); |
| const GURL kPrerenderingUrl = |
| embedded_test_server()->GetURL("/empty.html?prerender"); |
| GetAutocompleteActionPredictor()->StartPrerendering( |
| kPrerenderingUrl, *GetActiveWebContents(), gfx::Size(50, 50)); |
| StartOmniboxNavigationAndWaitForActivation(kPrerenderingUrl); |
| EXPECT_EQ(static_cast<int>(GetLastPageTransitionType()), |
| static_cast<int>(ui::PAGE_TRANSITION_TYPED | |
| ui::PAGE_TRANSITION_FROM_ADDRESS_BAR)); |
| EXPECT_TRUE(IsPrerenderingNavigation()); |
| EXPECT_EQ(GetActiveWebContents()->GetLastCommittedURL(), kPrerenderingUrl); |
| |
| histogram_tester.ExpectUniqueSample( |
| internal::kHistogramPrerenderPredictionStatusDirectUrlInput, |
| PrerenderPredictionStatus::kHitFinished, 1); |
| // The prediction result in search suggestion is recorded with kNotStarted. |
| histogram_tester.ExpectUniqueSample( |
| internal::kHistogramPrerenderPredictionStatusDefaultSearchEngine, |
| PrerenderPredictionStatus::kNotStarted, 1); |
| } |
| |
| // This test starts two different url prerendering by |
| // AutocompleteActionPredictor, and checks that the second one is going to |
| // cancel the first one. |
| IN_PROC_BROWSER_TEST_F(PrerenderOmniboxUIBrowserTest, |
| CancelAutocompleteActionPredictorOldPrerendering) { |
| base::HistogramTester histogram_tester; |
| Observe(GetActiveWebContents()); |
| const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html"); |
| ASSERT_TRUE(GetActiveWebContents()); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), kInitialUrl)); |
| |
| // Attempt to prerender a direct URL input. |
| ASSERT_TRUE(GetAutocompleteActionPredictor()); |
| WaitForAutocompleteActionPredictorInitialization(); |
| const GURL kPrerenderingUrl = |
| embedded_test_server()->GetURL("/empty.html?prerender"); |
| content::test::PrerenderHostObserver old_prerender_observer( |
| *GetActiveWebContents(), kPrerenderingUrl); |
| const GURL kNewUrl = embedded_test_server()->GetURL("/empty.html?newUrl"); |
| GetAutocompleteActionPredictor()->StartPrerendering( |
| kPrerenderingUrl, *GetActiveWebContents(), gfx::Size(50, 50)); |
| EXPECT_NE(prerender_helper().GetHostForUrl(kPrerenderingUrl), |
| content::RenderFrameHost::kNoFrameTreeNodeId); |
| GetAutocompleteActionPredictor()->StartPrerendering( |
| kNewUrl, *GetActiveWebContents(), gfx::Size(50, 50)); |
| |
| old_prerender_observer.WaitForDestroyed(); |
| content::NavigationHandleObserver activation_observer(GetActiveWebContents(), |
| kNewUrl); |
| StartOmniboxNavigationAndWaitForActivation(kNewUrl); |
| |
| EXPECT_TRUE(IsPrerenderingNavigation()); |
| EXPECT_EQ(GetActiveWebContents()->GetLastCommittedURL(), kNewUrl); |
| |
| { |
| // Check that we store two entries for both new and old entry. |
| ukm::SourceId ukm_source_id = activation_observer.next_page_ukm_source_id(); |
| auto ukm_entries = test_ukm_recorder()->GetEntries( |
| Preloading_Attempt::kEntryName, |
| content::test::kPreloadingAttemptUkmMetrics); |
| EXPECT_EQ(ukm_entries.size(), 2u); |
| |
| std::vector<UkmEntry> expected_entries = { |
| ukm_entry_builder().BuildEntry( |
| ukm_source_id, content::PreloadingType::kPrerender, |
| content::PreloadingEligibility::kEligible, |
| content::PreloadingHoldbackStatus::kAllowed, |
| content::PreloadingTriggeringOutcome::kFailure, |
| ToPreloadingFailureReason(PrerenderPredictionStatus::kCancelled), |
| /*accurate=*/false), |
| ukm_entry_builder().BuildEntry( |
| ukm_source_id, content::PreloadingType::kPrerender, |
| content::PreloadingEligibility::kEligible, |
| content::PreloadingHoldbackStatus::kAllowed, |
| content::PreloadingTriggeringOutcome::kSuccess, |
| content::PreloadingFailureReason::kUnspecified, |
| /*accurate=*/true), |
| }; |
| EXPECT_THAT(ukm_entries, |
| testing::UnorderedElementsAreArray(expected_entries)) |
| << content::test::ActualVsExpectedUkmEntriesToString(ukm_entries, |
| expected_entries); |
| } |
| |
| // Prerender was attempted twice and the first one was cancelled. |
| histogram_tester.ExpectBucketCount( |
| internal::kHistogramPrerenderPredictionStatusDirectUrlInput, |
| PrerenderPredictionStatus::kCancelled, 1); |
| histogram_tester.ExpectBucketCount( |
| internal::kHistogramPrerenderPredictionStatusDirectUrlInput, |
| PrerenderPredictionStatus::kHitFinished, 1); |
| // The prediction result in search suggestion is recorded with kNotStarted. |
| histogram_tester.ExpectUniqueSample( |
| internal::kHistogramPrerenderPredictionStatusDefaultSearchEngine, |
| PrerenderPredictionStatus::kNotStarted, 1); |
| } |
| |
| // This test starts url prerendering by |
| // AutocompleteActionPredictor, and navigates to a different URL. |
| IN_PROC_BROWSER_TEST_F(PrerenderOmniboxUIBrowserTest, |
| AutocompleteActionPredictorWrongPrediction) { |
| base::HistogramTester histogram_tester; |
| Observe(GetActiveWebContents()); |
| const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html"); |
| ASSERT_TRUE(GetActiveWebContents()); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), kInitialUrl)); |
| |
| // Attempt to prerender a direct URL input. |
| ASSERT_TRUE(GetAutocompleteActionPredictor()); |
| WaitForAutocompleteActionPredictorInitialization(); |
| const GURL kPrerenderingUrl = |
| embedded_test_server()->GetURL("/empty.html?prerender"); |
| content::test::PrerenderHostObserver old_prerender_observer( |
| *GetActiveWebContents(), kPrerenderingUrl); |
| const GURL kNewUrl = embedded_test_server()->GetURL("/empty.html?newUrl"); |
| GetAutocompleteActionPredictor()->StartPrerendering( |
| kPrerenderingUrl, *GetActiveWebContents(), gfx::Size(50, 50)); |
| prerender_helper().WaitForPrerenderLoadCompletion(*GetActiveWebContents(), |
| kPrerenderingUrl); |
| |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), kNewUrl)); |
| old_prerender_observer.WaitForDestroyed(); |
| |
| EXPECT_FALSE(IsPrerenderingNavigation()); |
| EXPECT_EQ(GetActiveWebContents()->GetLastCommittedURL(), kNewUrl); |
| |
| { |
| ukm::SourceId ukm_source_id = |
| GetActiveWebContents()->GetPrimaryMainFrame()->GetPageUkmSourceId(); |
| auto ukm_entries = test_ukm_recorder()->GetEntries( |
| Preloading_Attempt::kEntryName, |
| content::test::kPreloadingAttemptUkmMetrics); |
| EXPECT_EQ(ukm_entries.size(), 1u); |
| |
| std::vector<UkmEntry> expected_entries = { |
| ukm_entry_builder().BuildEntry( |
| ukm_source_id, content::PreloadingType::kPrerender, |
| content::PreloadingEligibility::kEligible, |
| content::PreloadingHoldbackStatus::kAllowed, |
| content::PreloadingTriggeringOutcome::kReady, |
| content::PreloadingFailureReason::kUnspecified, |
| /*accurate=*/false), |
| }; |
| EXPECT_THAT(ukm_entries, |
| testing::UnorderedElementsAreArray(expected_entries)) |
| << content::test::ActualVsExpectedUkmEntriesToString(ukm_entries, |
| expected_entries); |
| } |
| |
| // Prerender was attempted once and was cancelled. |
| histogram_tester.ExpectBucketCount( |
| internal::kHistogramPrerenderPredictionStatusDirectUrlInput, |
| PrerenderPredictionStatus::kUnused, 1); |
| histogram_tester.ExpectTotalCount( |
| internal::kHistogramPrerenderPredictionStatusDirectUrlInput, 1); |
| } |
| |
| // This test starts same url prerendering twice by AutocompleteActionPredictor, |
| // and checks that the second one will not trigger cancellation mechanism. |
| IN_PROC_BROWSER_TEST_F(PrerenderOmniboxUIBrowserTest, |
| AutocompleteActionPredictorSameURL) { |
| base::HistogramTester histogram_tester; |
| Observe(GetActiveWebContents()); |
| const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html"); |
| ASSERT_TRUE(GetActiveWebContents()); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), kInitialUrl)); |
| |
| // Attempt to prerender a direct URL input. |
| ASSERT_TRUE(GetAutocompleteActionPredictor()); |
| WaitForAutocompleteActionPredictorInitialization(); |
| const GURL kPrerenderingUrl = |
| embedded_test_server()->GetURL("/empty.html?prerender"); |
| GetAutocompleteActionPredictor()->StartPrerendering( |
| kPrerenderingUrl, *GetActiveWebContents(), gfx::Size(50, 50)); |
| GetAutocompleteActionPredictor()->StartPrerendering( |
| kPrerenderingUrl, *GetActiveWebContents(), gfx::Size(50, 50)); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_DirectURLInput", |
| /*PrerenderFinalStatus::kTriggerDestroyed*/ 16, 0); |
| content::NavigationHandleObserver activation_observer(GetActiveWebContents(), |
| kPrerenderingUrl); |
| StartOmniboxNavigationAndWaitForActivation(kPrerenderingUrl); |
| |
| EXPECT_TRUE(IsPrerenderingNavigation()); |
| EXPECT_EQ(GetActiveWebContents()->GetLastCommittedURL(), kPrerenderingUrl); |
| |
| { |
| ukm::SourceId ukm_source_id = activation_observer.next_page_ukm_source_id(); |
| auto ukm_entries = test_ukm_recorder()->GetEntries( |
| Preloading_Attempt::kEntryName, |
| content::test::kPreloadingAttemptUkmMetrics); |
| EXPECT_EQ(ukm_entries.size(), 2u); |
| |
| std::vector<UkmEntry> expected_entries = { |
| ukm_entry_builder().BuildEntry( |
| ukm_source_id, content::PreloadingType::kPrerender, |
| content::PreloadingEligibility::kEligible, |
| content::PreloadingHoldbackStatus::kAllowed, |
| content::PreloadingTriggeringOutcome::kSuccess, |
| content::PreloadingFailureReason::kUnspecified, |
| /*accurate=*/true), |
| ukm_entry_builder().BuildEntry( |
| ukm_source_id, content::PreloadingType::kPrerender, |
| content::PreloadingEligibility::kEligible, |
| content::PreloadingHoldbackStatus::kAllowed, |
| content::PreloadingTriggeringOutcome::kDuplicate, |
| content::PreloadingFailureReason::kUnspecified, |
| /*accurate=*/true), |
| }; |
| EXPECT_THAT(ukm_entries, |
| testing::UnorderedElementsAreArray(expected_entries)) |
| << content::test::ActualVsExpectedUkmEntriesToString(ukm_entries, |
| expected_entries); |
| } |
| |
| histogram_tester.ExpectUniqueSample( |
| internal::kHistogramPrerenderPredictionStatusDirectUrlInput, |
| PrerenderPredictionStatus::kHitFinished, 1); |
| // The prediction result in search suggestion is recorded with kNotStarted. |
| histogram_tester.ExpectUniqueSample( |
| internal::kHistogramPrerenderPredictionStatusDefaultSearchEngine, |
| PrerenderPredictionStatus::kNotStarted, 1); |
| } |
| |
| class PrerenderPreloaderHoldbackBrowserTest |
| : public PrerenderOmniboxUIBrowserTest { |
| public: |
| PrerenderPreloaderHoldbackBrowserTest() { |
| feature_list_.InitWithFeatures( |
| /*enabled_features=*/{features::kOmniboxTriggerForPrerender2, |
| features::kPrerender2Holdback}, |
| /* disabled_features=*/{}); |
| } |
| ~PrerenderPreloaderHoldbackBrowserTest() override = default; |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderPreloaderHoldbackBrowserTest, |
| PrerenderHoldbackTest) { |
| Observe(GetActiveWebContents()); |
| const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html"); |
| ASSERT_TRUE(GetActiveWebContents()); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), kInitialUrl)); |
| |
| // Attempt to prerender a direct URL input with prerender holdback. |
| ASSERT_TRUE(GetAutocompleteActionPredictor()); |
| WaitForAutocompleteActionPredictorInitialization(); |
| const GURL kPrerenderingUrl = |
| embedded_test_server()->GetURL("/empty.html?prerender"); |
| GetAutocompleteActionPredictor()->StartPrerendering( |
| kPrerenderingUrl, *GetActiveWebContents(), gfx::Size(50, 50)); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), kPrerenderingUrl)); |
| |
| { |
| ukm::SourceId ukm_source_id = |
| GetActiveWebContents()->GetPrimaryMainFrame()->GetPageUkmSourceId(); |
| auto ukm_entries = test_ukm_recorder()->GetEntries( |
| Preloading_Attempt::kEntryName, |
| content::test::kPreloadingAttemptUkmMetrics); |
| |
| // PreloadingHoldbackStatus should be set to kHoldback. |
| std::vector<UkmEntry> expected_entries = { |
| ukm_entry_builder().BuildEntry( |
| ukm_source_id, content::PreloadingType::kPrerender, |
| content::PreloadingEligibility::kEligible, |
| content::PreloadingHoldbackStatus::kHoldback, |
| content::PreloadingTriggeringOutcome::kUnspecified, |
| content::PreloadingFailureReason::kUnspecified, |
| /*accurate=*/true), |
| }; |
| EXPECT_THAT(ukm_entries, |
| testing::UnorderedElementsAreArray(expected_entries)) |
| << content::test::ActualVsExpectedUkmEntriesToString(ukm_entries, |
| expected_entries); |
| } |
| } |
| |
| // Tests that NavigationHandle::IsRendererInitiated() returns RendererInitiated |
| // = false correctly. |
| IN_PROC_BROWSER_TEST_F(PrerenderOmniboxUIBrowserTest, |
| NavigationHandleIsRendererInitiatedFalse) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html"); |
| ASSERT_TRUE(GetActiveWebContents()); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), kInitialUrl)); |
| |
| ASSERT_TRUE(GetAutocompleteActionPredictor()); |
| WaitForAutocompleteActionPredictorInitialization(); |
| const GURL kPrerenderingUrl = |
| embedded_test_server()->GetURL("/empty.html?prerender"); |
| { |
| base::RunLoop run_loop; |
| content::DidFinishNavigationObserver observer( |
| GetActiveWebContents(), |
| base::BindLambdaForTesting( |
| [&run_loop](content::NavigationHandle* navigation_handle) { |
| EXPECT_TRUE(navigation_handle->IsInPrerenderedMainFrame()); |
| EXPECT_FALSE(navigation_handle->IsRendererInitiated()); |
| run_loop.Quit(); |
| })); |
| GetAutocompleteActionPredictor()->StartPrerendering( |
| kPrerenderingUrl, *GetActiveWebContents(), gfx::Size(50, 50)); |
| run_loop.Run(); |
| } |
| StartOmniboxNavigationAndWaitForActivation(kPrerenderingUrl); |
| EXPECT_EQ(GetActiveWebContents()->GetLastCommittedURL(), kPrerenderingUrl); |
| |
| histogram_tester.ExpectUniqueSample( |
| internal::kHistogramPrerenderPredictionStatusDirectUrlInput, |
| PrerenderPredictionStatus::kHitFinished, 1); |
| // The prediction result in search suggestion is recorded with kNotStarted. |
| histogram_tester.ExpectUniqueSample( |
| internal::kHistogramPrerenderPredictionStatusDefaultSearchEngine, |
| PrerenderPredictionStatus::kNotStarted, 1); |
| } |
| |
| // Verifies that same url can be prerendered after activation. |
| IN_PROC_BROWSER_TEST_F(PrerenderOmniboxUIBrowserTest, |
| SameUrlPrerenderingCanBeUsedAgainAfterActivation) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html"); |
| ASSERT_TRUE(GetActiveWebContents()); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), kInitialUrl)); |
| |
| ASSERT_TRUE(GetAutocompleteActionPredictor()); |
| WaitForAutocompleteActionPredictorInitialization(); |
| const GURL kPrerenderingUrl = |
| embedded_test_server()->GetURL("/empty.html?prerendering"); |
| |
| GetAutocompleteActionPredictor()->StartPrerendering( |
| kPrerenderingUrl, *GetActiveWebContents(), gfx::Size(50, 50)); |
| StartOmniboxNavigationAndWaitForActivation(kPrerenderingUrl); |
| |
| // Test whether same prerendering url can be started successfully again and be |
| // activated. |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), kInitialUrl)); |
| GetAutocompleteActionPredictor()->StartPrerendering( |
| kPrerenderingUrl, *GetActiveWebContents(), gfx::Size(50, 50)); |
| StartOmniboxNavigationAndWaitForActivation(kPrerenderingUrl); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_DirectURLInput", |
| /*PrerenderFinalStatus::kActivated*/ 0, 2); |
| |
| // The prediction result is recorded in each activation. |
| histogram_tester.ExpectBucketCount( |
| internal::kHistogramPrerenderPredictionStatusDirectUrlInput, |
| PrerenderPredictionStatus::kHitFinished, 2); |
| // The prediction result in search suggestion is recorded with kNotStarted. |
| histogram_tester.ExpectBucketCount( |
| internal::kHistogramPrerenderPredictionStatusDefaultSearchEngine, |
| PrerenderPredictionStatus::kNotStarted, 2); |
| } |
| |
| // TODO(https://crbug.com/1282624): Make it a Platform test after test |
| // infrastructure is ready and allows native code to manipulate omnibox on the |
| // Java side. |
| class PrerenderOmniboxSearchSuggestionUIBrowserTest |
| : public PrerenderOmniboxUIBrowserTest { |
| public: |
| PrerenderOmniboxSearchSuggestionUIBrowserTest() |
| : prerender_helper_(base::BindRepeating( |
| &PrerenderOmniboxUIBrowserTest::GetActiveWebContents, |
| base::Unretained(this))) { |
| scoped_feature_list_.InitWithFeatures( |
| {features::kSupportSearchSuggestionForPrerender2}, |
| {prerender_utils::kHidePrefetchParameter}); |
| } |
| |
| void SetUpOnMainThread() override { |
| PlatformBrowserTest::SetUpOnMainThread(); |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| |
| // Set up a generic server. |
| embedded_test_server()->ServeFilesFromDirectory( |
| base::PathService::CheckedGet(chrome::DIR_TEST_DATA)); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // Set up server for search engine. |
| search_engine_server_.SetSSLConfig( |
| net::test_server::EmbeddedTestServer::CERT_TEST_NAMES); |
| search_engine_server_.RegisterRequestHandler(base::BindRepeating( |
| &PrerenderOmniboxSearchSuggestionUIBrowserTest::HandleSearchRequest, |
| base::Unretained(this))); |
| ASSERT_TRUE(search_engine_server_.Start()); |
| |
| // Set up server for suggestion service. |
| search_suggest_server_.SetSSLConfig( |
| net::test_server::EmbeddedTestServer::CERT_TEST_NAMES); |
| search_suggest_server_.RegisterRequestHandler( |
| base::BindRepeating(&PrerenderOmniboxSearchSuggestionUIBrowserTest:: |
| HandleSearchSuggestRequest, |
| base::Unretained(this))); |
| ASSERT_TRUE(search_suggest_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, |
| "/search_page.html?q={searchTerms}&{google:prefetchSource}" |
| "{google:originalQueryForSuggestion}") |
| .spec()); |
| data.suggestions_url = |
| search_suggest_server_.GetURL(kSuggestDomain, "/?q={searchTerms}") |
| .spec(); |
| TemplateURL* template_url = model->Add(std::make_unique<TemplateURL>(data)); |
| ASSERT_TRUE(template_url); |
| model->SetUserSelectedDefaultSearchProvider(template_url); |
| test_ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>(); |
| // This test suite only tests for Default Search Engine prerendering. |
| attempt_entry_builder_ = |
| std::make_unique<content::test::PreloadingAttemptUkmEntryBuilder>( |
| ToPreloadingPredictor( |
| ChromePreloadingPredictor::kDefaultSearchEngine)); |
| prediction_entry_builder_ = |
| std::make_unique<content::test::PreloadingPredictionUkmEntryBuilder>( |
| ToPreloadingPredictor( |
| ChromePreloadingPredictor::kDefaultSearchEngine)); |
| } |
| |
| void SetUp() override { |
| prerender_helper().SetUp(&search_engine_server_); |
| InProcessBrowserTest::SetUp(); |
| } |
| |
| void TearDownOnMainThread() override { |
| ASSERT_TRUE(search_engine_server_.ShutdownAndWaitUntilComplete()); |
| ASSERT_TRUE(search_suggest_server_.ShutdownAndWaitUntilComplete()); |
| } |
| |
| std::unique_ptr<net::test_server::HttpResponse> HandleSearchSuggestRequest( |
| const net::test_server::HttpRequest& request) { |
| std::string content = ""; |
| // $1 : origin query |
| // $2 : suggested results. It should be like: "suggestion_1", |
| // "suggestion_2", ..., "suggestion_n". |
| std::string content_template = R"([ |
| "$1", |
| [$2], |
| ["", ""], |
| [], |
| { |
| "google:clientdata": { |
| "pre": 0 |
| } |
| }])"; |
| for (const auto& suggestion_rule : |
| base::Reversed(search_suggestion_rules_)) { |
| // Origin query matches a predefined rule. |
| if (request.GetURL().spec().find(suggestion_rule.origin_query) != |
| std::string::npos) { |
| // Make up suggestion list to resect the protocol of a suggestion |
| // response. |
| std::vector<std::string> formatted_suggestions( |
| suggestion_rule.suggestions.size()); |
| for (size_t i = 0; i < suggestion_rule.suggestions.size(); ++i) { |
| formatted_suggestions[i] = |
| "\"" + suggestion_rule.suggestions[i] + "\""; |
| } |
| std::string suggestions_string = |
| base::JoinString(formatted_suggestions, ","); |
| content = base::ReplaceStringPlaceholders( |
| content_template, |
| {suggestion_rule.origin_query, suggestions_string}, nullptr); |
| break; |
| } |
| } |
| |
| auto resp = std::make_unique<net::test_server::BasicHttpResponse>(); |
| resp->set_code(net::HTTP_OK); |
| resp->set_content_type("application/json"); |
| resp->set_content(content); |
| return resp; |
| } |
| |
| std::unique_ptr<net::test_server::HttpResponse> HandleSearchRequest( |
| const net::test_server::HttpRequest& request) { |
| if (request.GetURL().spec().find("favicon") != std::string::npos) |
| return nullptr; |
| |
| std::unique_ptr<net::test_server::BasicHttpResponse> resp = |
| std::make_unique<net::test_server::BasicHttpResponse>(); |
| resp->set_code(net::HTTP_OK); |
| resp->set_content_type("text/html"); |
| std::string content = R"( |
| <html><body> HI PRERENDER! |
| <script> |
| let resolveFunc; |
| const historyUpdated = new Promise((resolve, reject) => { |
| resolveFunc = resolve; |
| }); |
| function removeParam() { |
| const url = new URL(document.URL); |
| // Search parameters will contain pf=cs if the document is |
| // activated from a prerendered document. |
| if (url.searchParams.get('pf') === 'cs') { |
| url.searchParams.delete('pf'); |
| // After being activated, the page is no longer a prerendering |
| // page. If it defines any parameters for identifying the |
| // page type, it is also responsible for updating its history |
| // state by removing the parameters. |
| history.replaceState(null, "", url.toString()); |
| resolveFunc(true); |
| } else { |
| resolveFunc(false); |
| } |
| } |
| if (document.prerendering) { |
| document.addEventListener("prerenderingchange", () => { |
| removeParam(); |
| }); |
| } else { |
| // If this script is executed after the page was activated. |
| removeParam(); |
| } |
| </script> |
| </body></html> |
| )"; |
| resp->set_content(content); |
| return resp; |
| } |
| |
| GURL GetSearchUrl(const std::string& query, |
| std::string search_terms, |
| bool is_prerender) { |
| // $1: the search terms that will be retrieved. |
| // $2: flag for prefetch/prerender request. Should be &pf=cs if the url is |
| // expected to be used for a prerendering navigation. Otherwise it should be |
| // an empty string. |
| // $3: origin query. This might differ than search terms. For example, an |
| // origin query of "prerend" can have the search term of "prerender", |
| // since the suggestion service suggests to retrieve the term. |
| std::string url_template = "/search_page.html?q=$1$2&oq=$3&"; |
| return search_engine_server_.GetURL( |
| kSearchDomain, |
| base::ReplaceStringPlaceholders( |
| url_template, {search_terms, is_prerender ? "&pf=cs" : "", query}, |
| nullptr)); |
| } |
| |
| AutocompleteController* GetAutocompleteController() { |
| OmniboxView* omnibox = |
| browser()->window()->GetLocationBar()->GetOmniboxView(); |
| return omnibox->model()->autocomplete_controller(); |
| } |
| |
| protected: |
| void AddNewSuggestionRule(std::string origin_query, |
| std::vector<std::string> suggestions) { |
| search_suggestion_rules_.emplace_back( |
| SearchSuggestionTuple(origin_query, suggestions)); |
| } |
| |
| void InputSearchQuery(base::StringPiece search_query) { |
| // Trigger an omnibox suggest that has a prerender hint. |
| AutocompleteInput input(base::ASCIIToUTF16(search_query), |
| metrics::OmniboxEventProto::BLANK, |
| ChromeAutocompleteSchemeClassifier( |
| chrome_test_utils::GetProfile(this))); |
| AutocompleteController* autocomplete_controller = |
| GetAutocompleteController(); |
| // After receiving `input`, the controller should ask suggestion service for |
| // search suggestion. |
| autocomplete_controller->Start(input); |
| |
| // Wait until Autocomplete is done running. |
| ui_test_utils::WaitForAutocompleteDone(browser()); |
| EXPECT_TRUE(autocomplete_controller->done()); |
| } |
| |
| int InputSearchQueryAndWaitForTrigger(base::StringPiece search_query, |
| const GURL& expected_url) { |
| content::test::PrerenderHostRegistryObserver registry_observer( |
| *GetActiveWebContents()); |
| InputSearchQuery(search_query); |
| // The suggestion service should hint a search term which is be displayed in |
| // the page with `expected_url`. |
| registry_observer.WaitForTrigger(expected_url); |
| int host_id = prerender_helper().GetHostForUrl(expected_url); |
| return host_id; |
| } |
| |
| ukm::TestAutoSetUkmRecorder* test_ukm_recorder() { |
| return test_ukm_recorder_.get(); |
| } |
| |
| const content::test::PreloadingAttemptUkmEntryBuilder& |
| attempt_entry_builder() { |
| return *attempt_entry_builder_; |
| } |
| |
| const content::test::PreloadingPredictionUkmEntryBuilder& |
| prediction_entry_builder() { |
| return *prediction_entry_builder_; |
| } |
| |
| private: |
| struct SearchSuggestionTuple { |
| SearchSuggestionTuple(std::string origin_query, |
| std::vector<std::string> suggestions) |
| : origin_query(origin_query), suggestions(suggestions) {} |
| std::string origin_query; |
| std::vector<std::string> suggestions; |
| }; |
| |
| // Stores some hard-coded rules for testing. |
| // Tests can also call AddNewSuggestionRule to append a new rule. |
| // Note: they are order-sensitive! The last rule(the newest added rule) has |
| // the highest priority. |
| std::vector<SearchSuggestionTuple> search_suggestion_rules_{ |
| SearchSuggestionTuple("prerenderp", |
| {"prerenderprefetch", "prerenderprefetchall"}), |
| SearchSuggestionTuple("prerender2", {"prerender222", "prerender223"})}; |
| |
| constexpr static char kSearchDomain[] = "a.test"; |
| constexpr static char kSuggestDomain[] = "b.test"; |
| constexpr static char16_t kSearchDomain16[] = u"a.test"; |
| content::test::PrerenderTestHelper prerender_helper_; |
| base::test::ScopedFeatureList scoped_feature_list_; |
| net::test_server::EmbeddedTestServer search_engine_server_{ |
| net::test_server::EmbeddedTestServer::TYPE_HTTPS}; |
| net::test_server::EmbeddedTestServer search_suggest_server_{ |
| net::test_server::EmbeddedTestServer::TYPE_HTTPS}; |
| std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_ukm_recorder_; |
| std::unique_ptr<content::test::PreloadingAttemptUkmEntryBuilder> |
| attempt_entry_builder_; |
| std::unique_ptr<content::test::PreloadingPredictionUkmEntryBuilder> |
| prediction_entry_builder_; |
| }; |
| |
| // Tests the basic functionality of prerendering a search suggestion with search |
| // suggestion hints. |
| IN_PROC_BROWSER_TEST_F(PrerenderOmniboxSearchSuggestionUIBrowserTest, |
| SearchPrerenderSuggestion) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html"); |
| ASSERT_TRUE(GetActiveWebContents()); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), kInitialUrl)); |
| |
| Observe(GetActiveWebContents()); |
| std::string search_query = "prerender2"; |
| GURL expected_prerender_url = |
| GetSearchUrl(search_query, "prerender222", /*is_prerender=*/true); |
| int host_id = |
| InputSearchQueryAndWaitForTrigger(search_query, expected_prerender_url); |
| ASSERT_NE(host_id, content::RenderFrameHost::kNoFrameTreeNodeId); |
| prerender_helper().WaitForPrerenderLoadCompletion(*GetActiveWebContents(), |
| expected_prerender_url); |
| |
| // With the default setting, there should be no prefetches because the server |
| // does not respond a prefetch suggestion. |
| SearchPrefetchService* search_prefetch_service = |
| SearchPrefetchServiceFactory::GetForProfile(Profile::FromBrowserContext( |
| GetActiveWebContents()->GetBrowserContext())); |
| ASSERT_NE(search_prefetch_service, nullptr); |
| absl::optional<SearchPrefetchStatus> prefetch_status = |
| search_prefetch_service->GetSearchPrefetchStatusForTesting( |
| u"prerender222"); |
| EXPECT_FALSE(prefetch_status.has_value()); |
| histogram_tester.ExpectTotalCount( |
| "Omnibox.SearchPrefetch.PrefetchEligibilityReason2.SuggestionPrefetch", |
| 0); |
| |
| content::RenderFrameHost* prerender_rfh = |
| prerender_helper().GetPrerenderedMainFrameHost(host_id); |
| EXPECT_TRUE( |
| base::Contains(prerender_rfh->GetLastCommittedURL().spec(), "pf=cs")); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.DefaultSearchEngine." |
| "SearchTermExtractorCorrectness", |
| true, 1); |
| |
| // Ensure there is a search hint. |
| AutocompleteController* autocomplete_controller = GetAutocompleteController(); |
| auto prerender_match = base::ranges::find_if( |
| autocomplete_controller->result(), &BaseSearchProvider::ShouldPrerender); |
| ASSERT_NE(prerender_match, std::end(autocomplete_controller->result())); |
| |
| content::NavigationHandleObserver activation_observer( |
| GetActiveWebContents(), prerender_match->destination_url); |
| SelectAutocompleteMatchAndWaitForActivation(*prerender_match, host_id); |
| EXPECT_TRUE(IsPrerenderingNavigation()); |
| |
| // Wait until the history is updated. |
| ASSERT_EQ(true, content::EvalJs(GetActiveWebContents()->GetPrimaryMainFrame(), |
| "historyUpdated;")); |
| |
| EXPECT_EQ(1, prerender_helper().GetRequestCount(expected_prerender_url)); |
| // The displayed url shouldn't contain the parameter of pf=cs. |
| EXPECT_FALSE(base::Contains( |
| GetActiveWebContents()->GetLastCommittedURL().spec(), "pf=cs")); |
| EXPECT_EQ(0, prerender_helper().GetRequestCount( |
| GetActiveWebContents()->GetLastCommittedURL())); |
| |
| { |
| // Check that we store one entry corresponding to the prerender prediction |
| // and attempt. |
| ukm::SourceId ukm_source_id = activation_observer.next_page_ukm_source_id(); |
| auto attempt_ukm_entries = test_ukm_recorder()->GetEntries( |
| Preloading_Attempt::kEntryName, |
| content::test::kPreloadingAttemptUkmMetrics); |
| auto prediction_ukm_entries = test_ukm_recorder()->GetEntries( |
| Preloading_Prediction::kEntryName, |
| content::test::kPreloadingPredictionUkmMetrics); |
| EXPECT_EQ(prediction_ukm_entries.size(), 1u); |
| EXPECT_EQ(attempt_ukm_entries.size(), 1u); |
| |
| // Check that we log the correct metrics for successful prerender |
| // activation. |
| std::vector<UkmEntry> expected_prediction_entries = { |
| prediction_entry_builder().BuildEntry(ukm_source_id, |
| /*confidence=*/80, |
| /*accurate_prediction=*/true), |
| }; |
| std::vector<UkmEntry> expected_attempt_entries = { |
| attempt_entry_builder().BuildEntry( |
| ukm_source_id, content::PreloadingType::kPrerender, |
| content::PreloadingEligibility::kEligible, |
| content::PreloadingHoldbackStatus::kAllowed, |
| content::PreloadingTriggeringOutcome::kSuccess, |
| content::PreloadingFailureReason::kUnspecified, |
| /*accurate=*/true), |
| }; |
| EXPECT_THAT(attempt_ukm_entries, |
| testing::UnorderedElementsAreArray(expected_attempt_entries)) |
| << content::test::ActualVsExpectedUkmEntriesToString( |
| attempt_ukm_entries, expected_attempt_entries); |
| EXPECT_THAT(prediction_ukm_entries, |
| testing::UnorderedElementsAreArray(expected_prediction_entries)) |
| << content::test::ActualVsExpectedUkmEntriesToString( |
| prediction_ukm_entries, expected_prediction_entries); |
| } |
| |
| histogram_tester.ExpectUniqueSample( |
| internal::kHistogramPrerenderPredictionStatusDefaultSearchEngine, |
| PrerenderPredictionStatus::kHitFinished, 1); |
| |
| GURL expected_reload_url = GetActiveWebContents()->GetLastCommittedURL(); |
| |
| // Reload the page. It is supposed to send a URL request without |
| // prefetch parameters attached. |
| chrome::Reload(browser(), WindowOpenDisposition::CURRENT_TAB); |
| prerender_helper().WaitForRequest(expected_reload_url, 1); |
| } |
| |
| // Tests that prerendering the wrong URL doesn't lead to activation. |
| IN_PROC_BROWSER_TEST_F(PrerenderOmniboxSearchSuggestionUIBrowserTest, |
| WrongPrediction) { |
| base::HistogramTester histogram_tester; |
| AddNewSuggestionRule("prerender22", {"prerender222", "prerender223"}); |
| const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html"); |
| ASSERT_TRUE(GetActiveWebContents()); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), kInitialUrl)); |
| |
| Observe(GetActiveWebContents()); |
| std::string search_query_1 = "prerender2"; |
| GURL expected_prerender_url = |
| GetSearchUrl(search_query_1, "prerender222", /*is_prerender=*/true); |
| |
| // Trigger an omnibox suggest that has a prerender hint. |
| int host_id = |
| InputSearchQueryAndWaitForTrigger(search_query_1, expected_prerender_url); |
| ASSERT_NE(host_id, content::RenderFrameHost::kNoFrameTreeNodeId); |
| prerender_helper().WaitForPrerenderLoadCompletion(*GetActiveWebContents(), |
| expected_prerender_url); |
| |
| const GURL kNewUrl = embedded_test_server()->GetURL("/empty.html?newUrl"); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), kNewUrl)); |
| EXPECT_FALSE(IsPrerenderingNavigation()); |
| base::RunLoop().RunUntilIdle(); |
| |
| { |
| ukm::SourceId ukm_source_id = |
| GetActiveWebContents()->GetPrimaryMainFrame()->GetPageUkmSourceId(); |
| auto ukm_entries = test_ukm_recorder()->GetEntries( |
| Preloading_Attempt::kEntryName, |
| content::test::kPreloadingAttemptUkmMetrics); |
| EXPECT_EQ(ukm_entries.size(), 1u); |
| |
| std::vector<UkmEntry> expected_entries = { |
| attempt_entry_builder().BuildEntry( |
| ukm_source_id, content::PreloadingType::kPrerender, |
| content::PreloadingEligibility::kEligible, |
| content::PreloadingHoldbackStatus::kAllowed, |
| content::PreloadingTriggeringOutcome::kReady, |
| content::PreloadingFailureReason::kUnspecified, |
| /*accurate=*/false), |
| }; |
| EXPECT_THAT(ukm_entries, |
| testing::UnorderedElementsAreArray(expected_entries)) |
| << content::test::ActualVsExpectedUkmEntriesToString(ukm_entries, |
| expected_entries); |
| } |
| |
| histogram_tester.ExpectUniqueSample( |
| internal::kHistogramPrerenderPredictionStatusDefaultSearchEngine, |
| PrerenderPredictionStatus::kUnused, 1); |
| } |
| |
| // Tests that prerender maintain the previous prerendered page if the new |
| // prerendering aims to load a same url to the prerendered page. |
| IN_PROC_BROWSER_TEST_F(PrerenderOmniboxSearchSuggestionUIBrowserTest, |
| SameSuggestion) { |
| base::HistogramTester histogram_tester; |
| AddNewSuggestionRule("prerender22", {"prerender222", "prerender223"}); |
| const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html"); |
| ASSERT_TRUE(GetActiveWebContents()); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), kInitialUrl)); |
| |
| Observe(GetActiveWebContents()); |
| std::string search_query_1 = "prerender2"; |
| GURL expected_prerender_url = |
| GetSearchUrl(search_query_1, "prerender222", /*is_prerender=*/true); |
| |
| // Trigger an omnibox suggest that has a prerender hint. |
| int host_id = |
| InputSearchQueryAndWaitForTrigger(search_query_1, expected_prerender_url); |
| ASSERT_NE(host_id, content::RenderFrameHost::kNoFrameTreeNodeId); |
| prerender_helper().WaitForPrerenderLoadCompletion(*GetActiveWebContents(), |
| expected_prerender_url); |
| |
| std::string search_query_2 = "prerender22"; |
| InputSearchQuery(search_query_2); |
| base::RunLoop().RunUntilIdle(); |
| |
| AutocompleteController* autocomplete_controller = GetAutocompleteController(); |
| // Ensure there is a search hint. |
| auto prerender_match = base::ranges::find_if( |
| autocomplete_controller->result(), &BaseSearchProvider::ShouldPrerender); |
| ASSERT_NE(prerender_match, std::end(autocomplete_controller->result())); |
| content::NavigationHandleObserver activation_observer( |
| GetActiveWebContents(), prerender_match->destination_url); |
| SelectAutocompleteMatchAndWaitForActivation(*prerender_match, host_id); |
| EXPECT_TRUE(IsPrerenderingNavigation()); |
| base::RunLoop().RunUntilIdle(); |
| |
| { |
| // Check that we store two entries corresponding to both the prererendering |
| // attempts. |
| ukm::SourceId ukm_source_id = activation_observer.next_page_ukm_source_id(); |
| auto ukm_entries = test_ukm_recorder()->GetEntries( |
| Preloading_Attempt::kEntryName, |
| content::test::kPreloadingAttemptUkmMetrics); |
| EXPECT_EQ(ukm_entries.size(), 2u); |
| |
| // Check that we log the correct metrics for successful prerender |
| // activation and for duplicate attempt to the same prerender URL. |
| std::vector<UkmEntry> expected_entries = { |
| attempt_entry_builder().BuildEntry( |
| ukm_source_id, content::PreloadingType::kPrerender, |
| content::PreloadingEligibility::kEligible, |
| content::PreloadingHoldbackStatus::kAllowed, |
| content::PreloadingTriggeringOutcome::kSuccess, |
| content::PreloadingFailureReason::kUnspecified, |
| /*accurate=*/true), |
| attempt_entry_builder().BuildEntry( |
| ukm_source_id, content::PreloadingType::kPrerender, |
| content::PreloadingEligibility::kEligible, |
| content::PreloadingHoldbackStatus::kAllowed, |
| content::PreloadingTriggeringOutcome::kDuplicate, |
| content::PreloadingFailureReason::kUnspecified, |
| /*accurate=*/true), |
| }; |
| EXPECT_THAT(ukm_entries, |
| testing::UnorderedElementsAreArray(expected_entries)) |
| << content::test::ActualVsExpectedUkmEntriesToString(ukm_entries, |
| expected_entries); |
| } |
| |
| // Wait until the history is updated. |
| EXPECT_EQ(true, content::EvalJs(GetActiveWebContents()->GetPrimaryMainFrame(), |
| "historyUpdated;")); |
| |
| // The displayed url shouldn't contain the parameter of pf=cs. |
| EXPECT_FALSE(base::Contains( |
| GetActiveWebContents()->GetLastCommittedURL().spec(), "pf=cs")); |
| |
| histogram_tester.ExpectUniqueSample( |
| internal::kHistogramPrerenderPredictionStatusDefaultSearchEngine, |
| PrerenderPredictionStatus::kHitFinished, 1); |
| } |
| |
| // Tests that prerender is cancelled if a different prerendering starts. |
| // TODO(crbug.com/1348636): Test is flaky. |
| IN_PROC_BROWSER_TEST_F(PrerenderOmniboxSearchSuggestionUIBrowserTest, |
| DISABLED_DifferentSuggestion) { |
| base::HistogramTester histogram_tester; |
| |
| const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html"); |
| ASSERT_TRUE(GetActiveWebContents()); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), kInitialUrl)); |
| |
| Observe(GetActiveWebContents()); |
| |
| AddNewSuggestionRule("prerender22", {"prerender222", "prerender223"}); |
| std::string search_query_1 = "prerender22"; |
| GURL prerender_url = |
| GetSearchUrl(search_query_1, "prerender222", /*is_prerender=*/true); |
| int host_id = |
| InputSearchQueryAndWaitForTrigger(search_query_1, prerender_url); |
| ASSERT_NE(host_id, content::RenderFrameHost::kNoFrameTreeNodeId); |
| |
| // Start the second prerendering with the different suggestion. |
| AddNewSuggestionRule("prerender33", {"prerender333", "prerender334"}); |
| std::string search_query_2 = "prerender33"; |
| GURL prerender_url2 = |
| GetSearchUrl(search_query_2, "prerender333", /*is_prerender=*/true); |
| int host_id2 = |
| InputSearchQueryAndWaitForTrigger(search_query_2, prerender_url2); |
| ASSERT_NE(host_id, host_id2); |
| prerender_helper().WaitForPrerenderLoadCompletion(*GetActiveWebContents(), |
| prerender_url2); |
| |
| AutocompleteController* autocomplete_controller = GetAutocompleteController(); |
| // Ensure there is a search hint. |
| auto prerender_match = base::ranges::find_if( |
| autocomplete_controller->result(), &BaseSearchProvider::ShouldPrerender); |
| ASSERT_NE(prerender_match, std::end(autocomplete_controller->result())); |
| |
| content::NavigationHandleObserver activation_observer( |
| GetActiveWebContents(), prerender_match->destination_url); |
| SelectAutocompleteMatchAndWaitForActivation(*prerender_match, host_id2); |
| EXPECT_TRUE(IsPrerenderingNavigation()); |
| |
| { |
| // Check that we store two entries corresponding to both the prererendering |
| // attempts. |
| ukm::SourceId ukm_source_id = activation_observer.next_page_ukm_source_id(); |
| auto ukm_entries = test_ukm_recorder()->GetEntries( |
| Preloading_Attempt::kEntryName, |
| content::test::kPreloadingAttemptUkmMetrics); |
| EXPECT_EQ(ukm_entries.size(), 2u); |
| |
| // Check that we log the correct metrics for successful prerender |
| // activation with suggestions to the different prerender URLs. |
| std::vector<UkmEntry> expected_entries = { |
| attempt_entry_builder().BuildEntry( |
| ukm_source_id, content::PreloadingType::kPrerender, |
| content::PreloadingEligibility::kEligible, |
| content::PreloadingHoldbackStatus::kAllowed, |
| content::PreloadingTriggeringOutcome::kFailure, |
| ToPreloadingFailureReason(PrerenderPredictionStatus::kCancelled), |
| /*accurate=*/false), |
| attempt_entry_builder().BuildEntry( |
| ukm_source_id, content::PreloadingType::kPrerender, |
| content::PreloadingEligibility::kEligible, |
| content::PreloadingHoldbackStatus::kAllowed, |
| content::PreloadingTriggeringOutcome::kSuccess, |
| content::PreloadingFailureReason::kUnspecified, |
| /*accurate=*/true), |
| }; |
| EXPECT_THAT(ukm_entries, |
| testing::UnorderedElementsAreArray(expected_entries)) |
| << content::test::ActualVsExpectedUkmEntriesToString(ukm_entries, |
| expected_entries); |
| } |
| |
| // Wait until the history is updated. |
| EXPECT_EQ(true, content::EvalJs(GetActiveWebContents()->GetPrimaryMainFrame(), |
| "historyUpdated;")); |
| |
| // The displayed url shouldn't contain the parameter of pf=cs. |
| EXPECT_FALSE(base::Contains( |
| GetActiveWebContents()->GetLastCommittedURL().spec(), "pf=cs")); |
| |
| histogram_tester.ExpectBucketCount( |
| internal::kHistogramPrerenderPredictionStatusDefaultSearchEngine, |
| PrerenderPredictionStatus::kCancelled, 1); |
| histogram_tester.ExpectBucketCount( |
| internal::kHistogramPrerenderPredictionStatusDefaultSearchEngine, |
| PrerenderPredictionStatus::kHitFinished, 1); |
| } |
| |
| // Tests whether prerendering a search suggestion will have pf=cs parameter |
| // attached correctly. |
| IN_PROC_BROWSER_TEST_F(PrerenderOmniboxSearchSuggestionUIBrowserTest, |
| SearchPrerenderParameterVerification) { |
| const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html"); |
| ASSERT_TRUE(GetActiveWebContents()); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), kInitialUrl)); |
| Observe(GetActiveWebContents()); |
| std::string search_query = "prerender2"; |
| |
| GURL expected_prerender_url = |
| GetSearchUrl(search_query, "prerender222", /*is_prerender=*/true); |
| ASSERT_TRUE(base::Contains(expected_prerender_url.spec(), "pf=cs")); |
| int host_id = |
| InputSearchQueryAndWaitForTrigger(search_query, expected_prerender_url); |
| EXPECT_NE(host_id, content::RenderFrameHost::kNoFrameTreeNodeId); |
| } |
| |
| class PrerenderOmniboxReferrerChainUIBrowserTest |
| : public PrerenderOmniboxUIBrowserTest { |
| public: |
| void SetUpOnMainThread() override { |
| // Disable Safe Browsing service so we can directly control when |
| // SafeBrowsingNavigationObserverManager and SafeBrowsingNavigationObserver |
| // are instantiated. |
| browser()->profile()->GetPrefs()->SetBoolean(prefs::kSafeBrowsingEnabled, |
| false); |
| PrerenderOmniboxUIBrowserTest::SetUpOnMainThread(); |
| observer_manager_ = std::make_unique< |
| safe_browsing::TestSafeBrowsingNavigationObserverManager>(browser()); |
| observer_manager_->ObserveContents( |
| browser()->tab_strip_model()->GetActiveWebContents()); |
| } |
| |
| absl::optional<size_t> FindNavigationEventIndex( |
| const GURL& target_url, |
| content::GlobalRenderFrameHostId outermost_main_frame_id) { |
| return observer_manager_->navigation_event_list()->FindNavigationEvent( |
| base::Time::Now(), target_url, GURL(), SessionID::InvalidValue(), |
| outermost_main_frame_id, |
| (observer_manager_->navigation_event_list()->NavigationEventsSize() - |
| 1)); |
| } |
| |
| safe_browsing::NavigationEvent* GetNavigationEvent(size_t index) { |
| return observer_manager_->navigation_event_list() |
| ->navigation_events()[index] |
| .get(); |
| } |
| |
| void TearDownOnMainThread() override { observer_manager_.reset(); } |
| |
| private: |
| std::unique_ptr<safe_browsing::TestSafeBrowsingNavigationObserverManager> |
| observer_manager_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderOmniboxReferrerChainUIBrowserTest, |
| PrerenderHasNoInitiator) { |
| Observe(GetActiveWebContents()); |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html"); |
| ASSERT_TRUE(GetActiveWebContents()); |
| ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), kInitialUrl)); |
| |
| content::test::PrerenderHostRegistryObserver registry_observer( |
| *GetActiveWebContents()); |
| |
| // Attempt to prerender a direct URL input. |
| ASSERT_TRUE(GetAutocompleteActionPredictor()); |
| const GURL kPrerenderingUrl = |
| embedded_test_server()->GetURL("/empty.html?prerender"); |
| GetAutocompleteActionPredictor()->StartPrerendering( |
| kPrerenderingUrl, *GetActiveWebContents(), gfx::Size(50, 50)); |
| |
| registry_observer.WaitForTrigger(kPrerenderingUrl); |
| int host_id = prerender_helper().GetHostForUrl(kPrerenderingUrl); |
| ASSERT_NE(host_id, content::RenderFrameHost::kNoFrameTreeNodeId); |
| prerender_helper().WaitForPrerenderLoadCompletion(host_id); |
| // By using no id, we should get the most recent navigation event. |
| auto index = FindNavigationEventIndex(kPrerenderingUrl, |
| content::GlobalRenderFrameHostId()); |
| ASSERT_TRUE(index); |
| |
| // Since this was triggered by the omnibox (and hence by the user), we should |
| // have no initiator outermost main frame id. I.e., it would be incorrect to |
| // attribute this load to the document previously loaded in the outermost main |
| // frame. |
| auto* nav_event = GetNavigationEvent(*index); |
| EXPECT_FALSE(nav_event->initiator_outermost_main_frame_id); |
| } |
| |
| } // namespace |