| // Copyright 2019 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 "base/containers/contains.h" |
| #include "base/macros.h" |
| #include "base/run_loop.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/simple_test_tick_clock.h" |
| #include "chrome/browser/navigation_predictor/navigation_predictor_keyed_service.h" |
| #include "chrome/browser/navigation_predictor/navigation_predictor_keyed_service_factory.h" |
| #include "chrome/browser/predictors/loading_predictor.h" |
| #include "chrome/browser/predictors/loading_predictor_factory.h" |
| #include "chrome/browser/predictors/preconnect_manager.h" |
| #include "chrome/browser/search_engines/template_url_service_factory.h" |
| #include "chrome/browser/subresource_filter/subresource_filter_browser_test_harness.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/search_test_utils.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/search_engines/template_url_service.h" |
| #include "components/ukm/test_ukm_recorder.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "net/base/features.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 "url/url_constants.h" |
| |
| namespace { |
| |
| class SearchEnginePreconnectorBrowserTest |
| : public subresource_filter::SubresourceFilterBrowserTest, |
| public predictors::PreconnectManager::Observer { |
| public: |
| static constexpr char kFakeSearch[] = "https://www.fakesearch.com/"; |
| static constexpr char kGoogleSearch[] = "https://www.google.com/"; |
| |
| SearchEnginePreconnectorBrowserTest() = default; |
| ~SearchEnginePreconnectorBrowserTest() override = default; |
| |
| void SetUp() override { |
| https_server_ = std::make_unique<net::EmbeddedTestServer>( |
| net::EmbeddedTestServer::TYPE_HTTPS); |
| https_server_->ServeFilesFromSourceDirectory( |
| "chrome/test/data/navigation_predictor"); |
| ASSERT_TRUE(https_server_->Start()); |
| |
| preresolve_counts_[GetTestURL("/").GetOrigin()] = 0; |
| preresolve_counts_[GURL(kGoogleSearch)] = 0; |
| preresolve_counts_[GURL(kFakeSearch)] = 0; |
| |
| subresource_filter::SubresourceFilterBrowserTest::SetUp(); |
| } |
| |
| void SetUpOnMainThread() override { |
| subresource_filter::SubresourceFilterBrowserTest::SetUpOnMainThread(); |
| host_resolver()->ClearRules(); |
| |
| auto* loading_predictor = |
| predictors::LoadingPredictorFactory::GetForProfile( |
| browser()->profile()); |
| ASSERT_TRUE(loading_predictor); |
| loading_predictor->preconnect_manager()->SetObserverForTesting(this); |
| } |
| |
| const GURL GetTestURL(const char* file) const { |
| return https_server_->GetURL(file); |
| } |
| |
| void OnPreresolveFinished( |
| const GURL& url, |
| const net::NetworkIsolationKey& network_isolation_key, |
| bool success) override { |
| const GURL origin = url.GetOrigin(); |
| if (!base::Contains(preresolve_counts_, origin)) { |
| return; |
| } |
| |
| // Only the test URL should successfully preconnect. |
| EXPECT_EQ(origin == GetTestURL("/").GetOrigin(), success); |
| |
| ++preresolve_counts_[origin]; |
| if (run_loops_[origin]) |
| run_loops_[origin]->Quit(); |
| } |
| |
| void WaitForPreresolveCountForURL(const GURL& url, int expected_count) { |
| const GURL origin = url.GetOrigin(); |
| EXPECT_TRUE(base::Contains(preresolve_counts_, origin)); |
| while (preresolve_counts_[origin] < expected_count) { |
| run_loops_[origin] = std::make_unique<base::RunLoop>(); |
| run_loops_[origin]->Run(); |
| run_loops_[origin].reset(); |
| } |
| } |
| |
| void WaitForDelay(base::TimeDelta delay) { |
| base::RunLoop run_loop; |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, run_loop.QuitClosure(), delay); |
| run_loop.Run(); |
| } |
| |
| protected: |
| std::map<GURL, int> preresolve_counts_; |
| base::test::ScopedFeatureList feature_list_; |
| |
| private: |
| std::unique_ptr<net::EmbeddedTestServer> https_server_; |
| std::map<GURL, std::unique_ptr<base::RunLoop>> run_loops_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SearchEnginePreconnectorBrowserTest); |
| }; |
| |
| // static |
| constexpr char SearchEnginePreconnectorBrowserTest::kFakeSearch[]; |
| constexpr char SearchEnginePreconnectorBrowserTest::kGoogleSearch[]; |
| |
| class SearchEnginePreconnectorNoDelaysBrowserTest |
| : public SearchEnginePreconnectorBrowserTest { |
| public: |
| SearchEnginePreconnectorNoDelaysBrowserTest() { |
| feature_list_.InitWithFeaturesAndParameters( |
| {{features::kPreconnectToSearch, {{"startup_delay_ms", "1000000"}}}, |
| {features::kPreconnectToSearchNonGoogle, {{}}}, |
| {net::features::kNetUnusedIdleSocketTimeout, |
| {{"unused_idle_socket_timeout_seconds", "0"}}}}, |
| {}); |
| } |
| |
| ~SearchEnginePreconnectorNoDelaysBrowserTest() override = default; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(SearchEnginePreconnectorNoDelaysBrowserTest); |
| }; |
| |
| // Test routinely flakes on the Mac10.11 Tests bot (https://crbug.com/1141028). |
| IN_PROC_BROWSER_TEST_F(SearchEnginePreconnectorNoDelaysBrowserTest, |
| DISABLED_PreconnectSearch) { |
| // Put the fake search URL to be preconnected in foreground. |
| NavigationPredictorKeyedServiceFactory::GetForProfile( |
| Profile::FromBrowserContext(browser()->profile())) |
| ->search_engine_preconnector() |
| ->StartPreconnecting(/*with_startup_delay=*/false); |
| // Verifies that the default search is preconnected. |
| constexpr char16_t kShortName[] = u"test"; |
| constexpr char kSearchURL[] = "/anchors_different_area.html?q={searchTerms}"; |
| TemplateURLService* model = |
| TemplateURLServiceFactory::GetForProfile(browser()->profile()); |
| ASSERT_TRUE(model); |
| search_test_utils::WaitForTemplateURLServiceToLoad(model); |
| ASSERT_TRUE(model->loaded()); |
| |
| // Check default URL is being preconnected and test URL is not. |
| const GURL kDefaultUrl(kGoogleSearch); |
| WaitForPreresolveCountForURL(kDefaultUrl, 2); |
| EXPECT_EQ(2, preresolve_counts_[kDefaultUrl]); |
| EXPECT_EQ(0, preresolve_counts_[GetTestURL("/").GetOrigin()]); |
| |
| TemplateURLData data; |
| data.SetShortName(kShortName); |
| data.SetKeyword(data.short_name()); |
| data.SetURL(GetTestURL(kSearchURL).spec()); |
| |
| TemplateURL* template_url = model->Add(std::make_unique<TemplateURL>(data)); |
| ASSERT_TRUE(template_url); |
| model->SetUserSelectedDefaultSearchProvider(template_url); |
| |
| // Put the fake search URL to be preconnected in foreground. |
| NavigationPredictorKeyedServiceFactory::GetForProfile( |
| Profile::FromBrowserContext(browser()->profile())) |
| ->search_engine_preconnector() |
| ->StartPreconnecting(/*with_startup_delay=*/false); |
| |
| // After switching search providers, the test URL should now start being |
| // preconnected. |
| WaitForPreresolveCountForURL(GetTestURL("/"), 2); |
| // Preconnect should occur for DSE. |
| EXPECT_EQ(2, preresolve_counts_[GetTestURL("/").GetOrigin()]); |
| |
| WaitForPreresolveCountForURL(GetTestURL("/"), 4); |
| // Preconnect should occur again for DSE. |
| EXPECT_EQ(4, preresolve_counts_[GetTestURL("/").GetOrigin()]); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SearchEnginePreconnectorNoDelaysBrowserTest, |
| PreconnectOnlyInForeground) { |
| constexpr char16_t kShortName[] = u"test"; |
| constexpr char kSearchURL[] = "/anchors_different_area.html?q={searchTerms}"; |
| TemplateURLService* model = |
| TemplateURLServiceFactory::GetForProfile(browser()->profile()); |
| ASSERT_TRUE(model); |
| search_test_utils::WaitForTemplateURLServiceToLoad(model); |
| ASSERT_TRUE(model->loaded()); |
| |
| TemplateURLData data; |
| data.SetShortName(kShortName); |
| data.SetKeyword(data.short_name()); |
| data.SetURL(GetTestURL(kSearchURL).spec()); |
| |
| // Set the DSE to the test URL. |
| TemplateURL* template_url = model->Add(std::make_unique<TemplateURL>(data)); |
| ASSERT_TRUE(template_url); |
| model->SetUserSelectedDefaultSearchProvider(template_url); |
| |
| // Ensure that we wait long enough to trigger preconnects. |
| WaitForDelay(base::TimeDelta::FromMilliseconds(200)); |
| |
| TemplateURLData data_fake_search; |
| data_fake_search.SetShortName(kShortName); |
| data_fake_search.SetKeyword(data.short_name()); |
| data_fake_search.SetURL(kFakeSearch); |
| |
| template_url = model->Add(std::make_unique<TemplateURL>(data_fake_search)); |
| ASSERT_TRUE(template_url); |
| model->SetUserSelectedDefaultSearchProvider(template_url); |
| |
| // Put the fake search URL to be preconnected in foreground. |
| NavigationPredictorKeyedServiceFactory::GetForProfile( |
| Profile::FromBrowserContext(browser()->profile())) |
| ->search_engine_preconnector() |
| ->StartPreconnecting(/*with_startup_delay=*/false); |
| const GURL search_url = template_url->GenerateSearchURL({}); |
| WaitForPreresolveCountForURL(search_url, 2); |
| |
| // Preconnect should occur for fake search (2 since there are 2 NIKs). |
| EXPECT_EQ(2, preresolve_counts_[search_url]); |
| |
| // No preconnects should have been issued for the test URL. |
| EXPECT_EQ(0, preresolve_counts_[GetTestURL("/").GetOrigin()]); |
| } |
| |
| class SearchEnginePreconnectorForegroundBrowserTest |
| : public SearchEnginePreconnectorBrowserTest, |
| public testing::WithParamInterface<std::tuple<bool, bool>> { |
| public: |
| SearchEnginePreconnectorForegroundBrowserTest() { |
| { |
| if (skip_in_background()) { |
| feature_list_.InitWithFeaturesAndParameters( |
| { |
| {features::kPreconnectToSearch, |
| {{"startup_delay_ms", "1000000"}, |
| {"skip_in_background", "true"}}}, |
| {features::kPreconnectToSearchNonGoogle, {{}}}, |
| }, |
| {}); |
| } else { |
| feature_list_.InitWithFeaturesAndParameters( |
| { |
| {features::kPreconnectToSearch, |
| {{"startup_delay_ms", "1000000"}, |
| {"skip_in_background", "false"}}}, |
| {features::kPreconnectToSearchNonGoogle, {{}}}, |
| }, |
| {}); |
| } |
| } |
| } |
| |
| bool skip_in_background() const { return std::get<0>(GetParam()); } |
| |
| bool load_page() const { return std::get<1>(GetParam()); } |
| |
| ~SearchEnginePreconnectorForegroundBrowserTest() override = default; |
| |
| base::SimpleTestTickClock tick_clock_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(SearchEnginePreconnectorForegroundBrowserTest); |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| SearchEnginePreconnectorForegroundBrowserTest, |
| ::testing::Combine(::testing::Bool(), |
| ::testing::Bool())); |
| |
| // Test that search engine preconnects are done only if the browser app is |
| // likely in foreground. |
| IN_PROC_BROWSER_TEST_P(SearchEnginePreconnectorForegroundBrowserTest, |
| PreconnectOnlyInForeground) { |
| base::HistogramTester histogram_tester; |
| static const char16_t kShortName[] = u"test"; |
| static const char kSearchURL[] = |
| "/anchors_different_area.html?q={searchTerms}"; |
| static const char kSearchURLWithQuery[] = |
| "/anchors_different_area.html?q=porgs"; |
| |
| TemplateURLService* model = |
| TemplateURLServiceFactory::GetForProfile(browser()->profile()); |
| ASSERT_TRUE(model); |
| search_test_utils::WaitForTemplateURLServiceToLoad(model); |
| ASSERT_TRUE(model->loaded()); |
| |
| TemplateURLData data; |
| data.SetShortName(kShortName); |
| data.SetKeyword(data.short_name()); |
| data.SetURL(GetTestURL(kSearchURL).spec()); |
| |
| // Set the DSE to the test URL. |
| TemplateURL* template_url = model->Add(std::make_unique<TemplateURL>(data)); |
| ASSERT_TRUE(template_url); |
| model->SetUserSelectedDefaultSearchProvider(template_url); |
| |
| // Ensure that we wait long enough to trigger preconnects. |
| WaitForDelay(base::TimeDelta::FromMilliseconds(200)); |
| |
| TemplateURLData data_fake_search; |
| data_fake_search.SetShortName(kShortName); |
| data_fake_search.SetKeyword(data.short_name()); |
| const GURL fake_search_url(kFakeSearch); |
| data_fake_search.SetURL(kFakeSearch); |
| |
| template_url = model->Add(std::make_unique<TemplateURL>(data_fake_search)); |
| ASSERT_TRUE(template_url); |
| model->SetUserSelectedDefaultSearchProvider(template_url); |
| |
| tick_clock_.SetNowTicks(base::TimeTicks::Now()); |
| tick_clock_.Advance(base::TimeDelta::FromSeconds(10000)); |
| |
| NavigationPredictorKeyedServiceFactory::GetForProfile( |
| Profile::FromBrowserContext(browser()->profile())) |
| ->SetTickClockForTesting(&tick_clock_); |
| |
| if (load_page()) { |
| ui_test_utils::NavigateToURL(browser(), GetTestURL(kSearchURLWithQuery)); |
| } |
| |
| // Put the fake search URL to be preconnected in foreground. |
| NavigationPredictorKeyedServiceFactory::GetForProfile( |
| Profile::FromBrowserContext(browser()->profile())) |
| ->search_engine_preconnector() |
| ->StartPreconnecting(/*with_startup_delay=*/false); |
| |
| if (!skip_in_background() || load_page()) { |
| WaitForPreresolveCountForURL(fake_search_url, 2); |
| } |
| |
| // If preconnects are skipped in background and no web contents is in |
| // foreground, then no preconnect should happen. |
| EXPECT_EQ(skip_in_background() && !load_page() ? 0 : 2, |
| preresolve_counts_[fake_search_url]); |
| histogram_tester.ExpectUniqueSample( |
| "NavigationPredictor.SearchEnginePreconnector." |
| "IsBrowserAppLikelyInForeground", |
| load_page() ? true : false, 1); |
| |
| EXPECT_EQ(load_page() ? 1 : 0, |
| preresolve_counts_[GetTestURL("/").GetOrigin()]); |
| } |
| |
| class SearchEnginePreconnectorKeepSocketBrowserTest |
| : public SearchEnginePreconnectorBrowserTest { |
| public: |
| SearchEnginePreconnectorKeepSocketBrowserTest() { |
| feature_list_.InitWithFeaturesAndParameters( |
| {{features::kPreconnectToSearch, {{"startup_delay_ms", "1000000"}}}, |
| {features::kPreconnectToSearchNonGoogle, {{}}}, |
| {net::features::kNetUnusedIdleSocketTimeout, |
| {{"unused_idle_socket_timeout_seconds", "60"}}}}, |
| {}); |
| } |
| |
| ~SearchEnginePreconnectorKeepSocketBrowserTest() override = default; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(SearchEnginePreconnectorKeepSocketBrowserTest); |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(SearchEnginePreconnectorKeepSocketBrowserTest, |
| SocketWarmForSearch) { |
| // Verifies that a navigation to search will use a warm socket. |
| constexpr char16_t kShortName[] = u"test"; |
| constexpr char kSearchURL[] = "/anchors_different_area.html?q={searchTerms}"; |
| constexpr char kSearchURLWithQuery[] = "/anchors_different_area.html?q=porgs"; |
| |
| TemplateURLService* model = |
| TemplateURLServiceFactory::GetForProfile(browser()->profile()); |
| ASSERT_TRUE(model); |
| search_test_utils::WaitForTemplateURLServiceToLoad(model); |
| ASSERT_TRUE(model->loaded()); |
| |
| TemplateURLData data; |
| data.SetShortName(kShortName); |
| data.SetKeyword(data.short_name()); |
| data.SetURL(GetTestURL(kSearchURL).spec()); |
| |
| // Set the DSE to the test URL. |
| TemplateURL* template_url = model->Add(std::make_unique<TemplateURL>(data)); |
| ASSERT_TRUE(template_url); |
| model->SetUserSelectedDefaultSearchProvider(template_url); |
| |
| // Put the fake search URL to be preconnected in foreground. |
| NavigationPredictorKeyedServiceFactory::GetForProfile( |
| Profile::FromBrowserContext(browser()->profile())) |
| ->search_engine_preconnector() |
| ->StartPreconnecting(/*with_startup_delay=*/false); |
| |
| WaitForPreresolveCountForURL(GetTestURL(kSearchURL), 1); |
| |
| ui_test_utils::NavigateToURL(browser(), GetTestURL(kSearchURLWithQuery)); |
| |
| auto ukm_recorder = std::make_unique<ukm::TestAutoSetUkmRecorder>(); |
| |
| ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)); |
| |
| const auto& entries = |
| ukm_recorder->GetMergedEntriesByName(ukm::builders::PageLoad::kEntryName); |
| EXPECT_EQ(1u, entries.size()); |
| |
| for (const auto& kv : entries) { |
| EXPECT_TRUE(ukm_recorder->EntryHasMetric( |
| kv.second.get(), |
| ukm::builders::PageLoad::kMainFrameResource_SocketReusedName)); |
| } |
| } |
| |
| class SearchEnginePreconnectorDesktopAutoStartBrowserTest |
| : public SearchEnginePreconnectorBrowserTest { |
| public: |
| SearchEnginePreconnectorDesktopAutoStartBrowserTest() { |
| feature_list_.InitWithFeaturesAndParameters( |
| {{features::kPreconnectToSearch, {{"startup_delay_ms", "0"}}}, |
| {net::features::kNetUnusedIdleSocketTimeout, |
| {{"unused_idle_socket_timeout_seconds", "0"}}}}, |
| {}); |
| } |
| |
| ~SearchEnginePreconnectorDesktopAutoStartBrowserTest() override = default; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(SearchEnginePreconnectorDesktopAutoStartBrowserTest); |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(SearchEnginePreconnectorDesktopAutoStartBrowserTest, |
| AutoStartDesktop) { |
| // Verifies that the default search is preconnected. |
| WaitForPreresolveCountForURL(GURL(kGoogleSearch), 2); |
| } |
| |
| class SearchEnginePreconnectorGoogleOnlyBrowserTest |
| : public SearchEnginePreconnectorBrowserTest { |
| public: |
| SearchEnginePreconnectorGoogleOnlyBrowserTest() { |
| { |
| feature_list_.InitWithFeaturesAndParameters( |
| {{features::kPreconnectToSearch, {{"startup_delay_ms", "1000000"}}}, |
| {net::features::kNetUnusedIdleSocketTimeout, |
| {{"unused_idle_socket_timeout_seconds", "60"}}}}, |
| { |
| features::kPreconnectToSearchNonGoogle, |
| }); |
| } |
| } |
| |
| ~SearchEnginePreconnectorGoogleOnlyBrowserTest() override = default; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(SearchEnginePreconnectorGoogleOnlyBrowserTest); |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(SearchEnginePreconnectorGoogleOnlyBrowserTest, |
| GoogleOnly) { |
| constexpr char16_t kShortName[] = u"test"; |
| constexpr char kSearchURL[] = "/anchors_different_area.html?q={searchTerms}"; |
| TemplateURLService* model = |
| TemplateURLServiceFactory::GetForProfile(browser()->profile()); |
| ASSERT_TRUE(model); |
| search_test_utils::WaitForTemplateURLServiceToLoad(model); |
| ASSERT_TRUE(model->loaded()); |
| |
| TemplateURLData data; |
| data.SetShortName(kShortName); |
| data.SetKeyword(data.short_name()); |
| data.SetURL(GetTestURL(kSearchURL).spec()); |
| |
| // Set the DSE to the test URL. |
| TemplateURL* template_url = model->Add(std::make_unique<TemplateURL>(data)); |
| ASSERT_TRUE(template_url); |
| model->SetUserSelectedDefaultSearchProvider(template_url); |
| |
| NavigationPredictorKeyedServiceFactory::GetForProfile( |
| Profile::FromBrowserContext(browser()->profile())) |
| ->search_engine_preconnector() |
| ->StartPreconnecting(/*with_startup_delay=*/false); |
| |
| TemplateURLData data_google_search; |
| data_google_search.SetShortName(kShortName); |
| data_google_search.SetKeyword(data.short_name()); |
| data_google_search.SetURL(kGoogleSearch); |
| |
| template_url = model->Add(std::make_unique<TemplateURL>(data_google_search)); |
| ASSERT_TRUE(template_url); |
| model->SetUserSelectedDefaultSearchProvider(template_url); |
| |
| NavigationPredictorKeyedServiceFactory::GetForProfile( |
| Profile::FromBrowserContext(browser()->profile())) |
| ->search_engine_preconnector() |
| ->StartPreconnecting(/*with_startup_delay=*/false); |
| |
| const GURL search_url = template_url->GenerateSearchURL({}); |
| WaitForPreresolveCountForURL(search_url, 2); |
| |
| // Preconnect should occur for Google search (2 since there are 2 NIKs). |
| EXPECT_EQ(2, preresolve_counts_[search_url]); |
| |
| // No preconnects should have been issued for the test URL. |
| EXPECT_EQ(0, preresolve_counts_[GetTestURL("/").GetOrigin()]); |
| } |
| |
| } // namespace |