blob: 34e519a72670de04679df3e66a2144d0fbaa9f10 [file] [log] [blame]
// 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 <memory>
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_timeouts.h"
#include "build/build_config.h"
#include "chrome/browser/navigation_predictor/navigation_predictor_features.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/navigation_predictor/navigation_predictor_preconnect_client.h"
#include "chrome/browser/navigation_predictor/search_engine_preconnector.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 "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 "url/origin.h"
namespace {
class NavigationPredictorPreconnectClientBrowserTest
: public subresource_filter::SubresourceFilterBrowserTest,
public predictors::PreconnectManager::Observer {
public:
NavigationPredictorPreconnectClientBrowserTest()
: subresource_filter::SubresourceFilterBrowserTest() {
feature_list_.InitFromCommandLine(
std::string(),
"NavigationPredictorPreconnectHoldback,PreconnectToSearch");
}
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());
subresource_filter::SubresourceFilterBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
subresource_filter::SubresourceFilterBrowserTest::SetUpOnMainThread();
host_resolver()->ClearRules();
NavigationPredictorPreconnectClient::EnablePreconnectsForLocalIPsForTesting(
true);
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 {
// The tests do not care about preresolves to non-test server (e.g., hard
// coded preconnects to google.com).
if (url::Origin::Create(url) !=
url::Origin::Create(https_server_->base_url())) {
return;
}
EXPECT_TRUE(success);
preresolve_done_count_++;
if (run_loop_)
run_loop_->Quit();
}
void WaitForPreresolveCount(int expected_count) {
while (preresolve_done_count_ < expected_count) {
run_loop_ = std::make_unique<base::RunLoop>();
run_loop_->Run();
run_loop_.reset();
}
}
protected:
int preresolve_done_count_ = 0;
private:
std::unique_ptr<net::EmbeddedTestServer> https_server_;
base::test::ScopedFeatureList feature_list_;
std::unique_ptr<base::RunLoop> run_loop_;
DISALLOW_COPY_AND_ASSIGN(NavigationPredictorPreconnectClientBrowserTest);
};
IN_PROC_BROWSER_TEST_F(NavigationPredictorPreconnectClientBrowserTest,
NoPreconnectSearch) {
static const char16_t kShortName[] = u"test";
static const 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());
TemplateURL* template_url = model->Add(std::make_unique<TemplateURL>(data));
ASSERT_TRUE(template_url);
model->SetUserSelectedDefaultSearchProvider(template_url);
const GURL& url = GetTestURL("/anchors_different_area.html?q=cats");
ui_test_utils::NavigateToURL(browser(), url);
// There should be preconnect from navigation, but not preconnect client.
EXPECT_EQ(1, preresolve_done_count_);
}
IN_PROC_BROWSER_TEST_F(NavigationPredictorPreconnectClientBrowserTest,
PreconnectNotSearch) {
base::HistogramTester histogram_tester;
const GURL& url = GetTestURL("/anchors_different_area.html");
ui_test_utils::NavigateToURL(browser(), url);
// There should be one preconnect from navigation and one from preconnect
// client.
WaitForPreresolveCount(2);
EXPECT_EQ(2, preresolve_done_count_);
histogram_tester.ExpectUniqueSample("NavigationPredictor.IsPubliclyRoutable",
true, 1);
}
IN_PROC_BROWSER_TEST_F(NavigationPredictorPreconnectClientBrowserTest,
PreconnectNotSearchBackgroundForeground) {
const GURL& url = GetTestURL("/anchors_different_area.html");
ui_test_utils::NavigateToURL(browser(), url);
// There should be one preconnect from navigation and one from preconnect
// client.
WaitForPreresolveCount(2);
EXPECT_EQ(2, preresolve_done_count_);
browser()->tab_strip_model()->GetActiveWebContents()->WasHidden();
browser()->tab_strip_model()->GetActiveWebContents()->WasShown();
// After showing the contents again, there should be another preconnect client
// preconnect.
WaitForPreresolveCount(3);
EXPECT_EQ(3, preresolve_done_count_);
}
class NavigationPredictorPreconnectClientBrowserTestWithUnusedIdleSocketTimeout
: public NavigationPredictorPreconnectClientBrowserTest {
public:
NavigationPredictorPreconnectClientBrowserTestWithUnusedIdleSocketTimeout()
: NavigationPredictorPreconnectClientBrowserTest() {
feature_list_.InitAndEnableFeatureWithParameters(
net::features::kNetUnusedIdleSocketTimeout,
{{"unused_idle_socket_timeout_seconds", "0"}});
}
private:
base::test::ScopedFeatureList feature_list_;
};
// Test that we preconnect after the last preconnect timed out.
IN_PROC_BROWSER_TEST_F(
NavigationPredictorPreconnectClientBrowserTestWithUnusedIdleSocketTimeout,
ActionAccuracy_timeout) {
const GURL& url = GetTestURL("/page_with_same_host_anchor_element.html");
ui_test_utils::NavigateToURL(browser(), url);
WaitForPreresolveCount(3);
EXPECT_LE(3, preresolve_done_count_);
// Expect another one.
WaitForPreresolveCount(4);
EXPECT_LE(4, preresolve_done_count_);
}
// Test that we preconnect after the last preconnect timed out.
IN_PROC_BROWSER_TEST_F(
NavigationPredictorPreconnectClientBrowserTestWithUnusedIdleSocketTimeout,
CappedAtFiveAttempts) {
const GURL& url = GetTestURL("/page_with_same_host_anchor_element.html");
ui_test_utils::NavigateToURL(browser(), url);
// Expect 1 navigation preresolve and 5 repeated onLoad calls.
WaitForPreresolveCount(6);
EXPECT_EQ(6, preresolve_done_count_);
// We should not see additional preresolves.
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
run_loop.Run();
EXPECT_EQ(6, preresolve_done_count_);
// By default, same document navigation should not trigger new preconnects.
const GURL& same_document_url =
GetTestURL("/page_with_same_host_anchor_element.html#foobar");
ui_test_utils::NavigateToURL(browser(), same_document_url);
// Expect another one.
WaitForPreresolveCount(6);
EXPECT_EQ(6, preresolve_done_count_);
}
class NavigationPredictorPreconnectClientBrowserTestWithHoldback
: public NavigationPredictorPreconnectClientBrowserTest {
public:
NavigationPredictorPreconnectClientBrowserTestWithHoldback()
: NavigationPredictorPreconnectClientBrowserTest() {
feature_list_.InitFromCommandLine("NavigationPredictorPreconnectHoldback",
"PreconnectToSearch");
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(
NavigationPredictorPreconnectClientBrowserTestWithHoldback,
NoPreconnectHoldback) {
const GURL& url = GetTestURL("/anchors_different_area.html");
ui_test_utils::NavigateToURL(browser(), url);
// There should be preconnect from navigation, but not preconnect client.
EXPECT_EQ(1, preresolve_done_count_);
ui_test_utils::NavigateToURL(browser(), url);
// There should be 2 preconnects from navigation, but not any from preconnect
// client.
EXPECT_EQ(2, preresolve_done_count_);
}
const base::Feature kPreconnectOnDidFinishNavigation{
"PreconnectOnDidFinishNavigation", base::FEATURE_DISABLED_BY_DEFAULT};
class
NavigationPredictorPreconnectClientBrowserTestPreconnectOnDidFinishNavigationSecondDelay
: public NavigationPredictorPreconnectClientBrowserTest {
public:
NavigationPredictorPreconnectClientBrowserTestPreconnectOnDidFinishNavigationSecondDelay()
: NavigationPredictorPreconnectClientBrowserTest() {
feature_list_.InitAndEnableFeatureWithParameters(
kPreconnectOnDidFinishNavigation,
{{"delay_after_commit_in_ms", "1000"}});
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(
NavigationPredictorPreconnectClientBrowserTestPreconnectOnDidFinishNavigationSecondDelay,
PreconnectNotSearch) {
const GURL& url = GetTestURL("/anchors_different_area.html");
ui_test_utils::NavigateToURL(browser(), url);
// There should be preconnect from navigation and one from OnLoad client.
WaitForPreresolveCount(2);
EXPECT_EQ(2, preresolve_done_count_);
}
class
NavigationPredictorPreconnectClientBrowserTestPreconnectOnDidFinishNavigationNoDelay
: public NavigationPredictorPreconnectClientBrowserTest {
public:
NavigationPredictorPreconnectClientBrowserTestPreconnectOnDidFinishNavigationNoDelay()
: NavigationPredictorPreconnectClientBrowserTest() {
feature_list_.InitAndEnableFeatureWithParameters(
kPreconnectOnDidFinishNavigation, {{"delay_after_commit_in_ms", "0"}});
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(
NavigationPredictorPreconnectClientBrowserTestPreconnectOnDidFinishNavigationNoDelay,
PreconnectNotSearch) {
const GURL& url = GetTestURL("/anchors_different_area.html");
ui_test_utils::NavigateToURL(browser(), url);
// There should be a navigation preconnect, a commit preconnect, and an OnLoad
// preconnect.
WaitForPreresolveCount(3);
EXPECT_EQ(3, preresolve_done_count_);
}
class NavigationPredictorSameDocumentPreconnectClientBrowserTest
: public NavigationPredictorPreconnectClientBrowserTest {
public:
NavigationPredictorSameDocumentPreconnectClientBrowserTest()
: NavigationPredictorPreconnectClientBrowserTest() {
// Configure kDelayRequestsOnMultiplexedConnections experiment params.
base::FieldTrialParams params_kNetUnusedIdleSocketTimeout;
params_kNetUnusedIdleSocketTimeout["unused_idle_socket_timeout_seconds"] =
"0";
// Configure kThrottleDelayable experiment params.
base::FieldTrialParams
params_kNavigationPredictorEnablePreconnectOnSameDocumentNavigations;
feature_list_.InitWithFeaturesAndParameters(
{{net::features::kNetUnusedIdleSocketTimeout,
params_kNetUnusedIdleSocketTimeout},
{features::
kNavigationPredictorEnablePreconnectOnSameDocumentNavigations,
params_kNavigationPredictorEnablePreconnectOnSameDocumentNavigations}},
{});
}
private:
base::test::ScopedFeatureList feature_list_;
};
// Test that we preconnect after the last preconnect timed out.
IN_PROC_BROWSER_TEST_F(
NavigationPredictorSameDocumentPreconnectClientBrowserTest,
SameDocumentNavigation) {
const GURL& url = GetTestURL("/page_with_same_host_anchor_element.html");
ui_test_utils::NavigateToURL(browser(), url);
WaitForPreresolveCount(3);
EXPECT_LE(3, preresolve_done_count_);
// Expect another one.
WaitForPreresolveCount(4);
EXPECT_LE(4, preresolve_done_count_);
const GURL& same_document_url =
GetTestURL("/page_with_same_host_anchor_element.html#foobar");
ui_test_utils::NavigateToURL(browser(), same_document_url);
// Expect another one.
WaitForPreresolveCount(8);
EXPECT_LE(8, preresolve_done_count_);
}
namespace {
// Feature to control preconnect to search.
const base::Feature kPreconnectToSearchTest{"PreconnectToSearch",
base::FEATURE_DISABLED_BY_DEFAULT};
} // namespace
class NavigationPredictorPreconnectClientBrowserTestWithSearch
: public NavigationPredictorPreconnectClientBrowserTest {
public:
NavigationPredictorPreconnectClientBrowserTestWithSearch()
: NavigationPredictorPreconnectClientBrowserTest() {
feature_list_.InitWithFeatures(
{kPreconnectToSearchTest, features::kPreconnectToSearchNonGoogle}, {});
}
private:
base::test::ScopedFeatureList feature_list_;
};
#if defined(OS_WIN) && defined(ADDRESS_SANITIZER)
#define MAYBE_PreconnectSearchWithFeature DISABLED_PreconnectSearchWithFeature
#else
#define MAYBE_PreconnectSearchWithFeature PreconnectSearchWithFeature
#endif
IN_PROC_BROWSER_TEST_F(NavigationPredictorPreconnectClientBrowserTestWithSearch,
MAYBE_PreconnectSearchWithFeature) {
static const char16_t kShortName[] = u"test";
static const 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());
TemplateURL* template_url = model->Add(std::make_unique<TemplateURL>(data));
ASSERT_TRUE(template_url);
model->SetUserSelectedDefaultSearchProvider(template_url);
const GURL& url = GetTestURL("/anchors_different_area.html?q=cats");
NavigationPredictorKeyedServiceFactory::GetForProfile(
Profile::FromBrowserContext(browser()->profile()))
->search_engine_preconnector()
->StartPreconnecting(/*with_startup_delay=*/false);
// There should be 2 DSE preconnects (2 NIKs).
WaitForPreresolveCount(2);
EXPECT_EQ(2, preresolve_done_count_);
ui_test_utils::NavigateToURL(browser(), url);
// Now there should be an onload preconnect as well as a navigation
// preconnect.
WaitForPreresolveCount(4);
EXPECT_EQ(4, preresolve_done_count_);
}
class NavigationPredictorPreconnectClientLocalURLBrowserTest
: public NavigationPredictorPreconnectClientBrowserTest {
public:
NavigationPredictorPreconnectClientLocalURLBrowserTest() = default;
private:
void SetUpOnMainThread() override {
NavigationPredictorPreconnectClientBrowserTest::SetUpOnMainThread();
NavigationPredictorPreconnectClient::EnablePreconnectsForLocalIPsForTesting(
false);
}
DISALLOW_COPY_AND_ASSIGN(
NavigationPredictorPreconnectClientLocalURLBrowserTest);
};
IN_PROC_BROWSER_TEST_F(NavigationPredictorPreconnectClientLocalURLBrowserTest,
NoPreconnectSearch) {
base::HistogramTester histogram_tester;
const GURL& url = GetTestURL("/anchors_different_area.html");
ui_test_utils::NavigateToURL(browser(), url);
// There should not be any preconnects to non-public addresses.
histogram_tester.ExpectUniqueSample("NavigationPredictor.IsPubliclyRoutable",
false, 1);
}
} // namespace