blob: 4368a5485f3b3ef8b130ebed37468af0e6a1a24a [file] [log] [blame]
// Copyright 2020 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 <stdint.h>
#include <memory>
#include <set>
#include <string>
#include "base/base_switches.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/containers/unique_ptr_adapters.h"
#include "base/run_loop.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.h"
#include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings_factory.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/history/history_test_utils.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/net/prediction_options.h"
#include "chrome/browser/net/profile_network_context_service.h"
#include "chrome/browser/net/profile_network_context_service_factory.h"
#include "chrome/browser/policy/policy_test_utils.h"
#include "chrome/browser/prefetch/no_state_prefetch/no_state_prefetch_manager_factory.h"
#include "chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_features.h"
#include "chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_origin_prober.h"
#include "chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_params.h"
#include "chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_prefetch_status.h"
#include "chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_proxy_configurator.h"
#include "chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_service.h"
#include "chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_service_factory.h"
#include "chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_subresource_manager.h"
#include "chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_tab_helper.h"
#include "chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_test_utils.h"
#include "chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_url_loader_interceptor.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ssl/certificate_reporting_test_utils.h"
#include "chrome/browser/ssl/ssl_browsertest_util.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/data_reduction_proxy/core/browser/data_reduction_proxy_settings.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_features.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_switches.h"
#include "components/language/core/browser/pref_names.h"
#include "components/no_state_prefetch/browser/no_state_prefetch_handle.h"
#include "components/no_state_prefetch/browser/no_state_prefetch_manager.h"
#include "components/no_state_prefetch/common/no_state_prefetch_final_status.h"
#include "components/policy/policy_constants.h"
#include "components/prefs/pref_service.h"
#include "components/security_interstitials/content/security_interstitial_tab_helper.h"
#include "components/security_interstitials/content/ssl_blocking_page.h"
#include "components/security_interstitials/content/ssl_blocking_page_base.h"
#include "components/security_interstitials/content/ssl_cert_reporter.h"
#include "components/ukm/test_ukm_recorder.h"
#include "components/variations/variations_params_manager.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/service_worker_context.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/network_service_util.h"
#include "content/public/common/page_type.h"
#include "content/public/common/user_agent.h"
#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_base.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_utils.h"
#include "google_apis/google_api_keys.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/sync_call_restrictions.h"
#include "net/base/proxy_string_util.h"
#include "net/cert/cert_database.h"
#include "net/cert/cert_status_flags.h"
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/http/http_util.h"
#include "net/ssl/client_cert_identity_test_util.h"
#include "net/ssl/client_cert_store.h"
#include "net/ssl/ssl_config.h"
#include "net/ssl/ssl_info.h"
#include "net/ssl/ssl_server_config.h"
#include "net/test/cert_test_util.h"
#include "net/test/embedded_test_server/default_handlers.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/embedded_test_server_connection_listener.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "net/test/test_certificate_data.h"
#include "net/test/test_data_directory.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_source.h"
#include "services/network/public/cpp/client_hints.h"
#include "services/network/public/cpp/network_quality_tracker.h"
#include "services/network/public/mojom/network_service_test.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "services/network/test/test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/common/client_hints/client_hints.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace {
constexpr gfx::Size kSize(640, 480);
const char kAllowedUAClientHint[] = "sec-ch-ua";
const char kAllowedUAMobileClientHint[] = "sec-ch-ua-mobile";
const char kAllowedUAPlatformClientHint[] = "sec-ch-ua-platform";
class TestCustomProxyConfigClient
: public network::mojom::CustomProxyConfigClient {
public:
explicit TestCustomProxyConfigClient(
mojo::PendingReceiver<network::mojom::CustomProxyConfigClient>
pending_receiver,
base::OnceClosure update_closure)
: receiver_(this, std::move(pending_receiver)),
update_closure_(std::move(update_closure)) {}
// network::mojom::CustomProxyConfigClient:
void OnCustomProxyConfigUpdated(
network::mojom::CustomProxyConfigPtr proxy_config) override {
config_ = std::move(proxy_config);
if (update_closure_) {
std::move(update_closure_).Run();
}
}
void MarkProxiesAsBad(base::TimeDelta bypass_duration,
const net::ProxyList& bad_proxies,
MarkProxiesAsBadCallback callback) override {}
void ClearBadProxiesCache() override {}
network::mojom::CustomProxyConfigPtr config_;
private:
mojo::Receiver<network::mojom::CustomProxyConfigClient> receiver_;
base::OnceClosure update_closure_;
};
class AuthChallengeObserver : public content::NotificationObserver {
public:
explicit AuthChallengeObserver(content::WebContents* web_contents) {
registrar_.Add(this, chrome::NOTIFICATION_AUTH_NEEDED,
content::Source<content::NavigationController>(
&web_contents->GetController()));
}
~AuthChallengeObserver() override = default;
bool GotAuthChallenge() const { return got_auth_challenge_; }
void Reset() { got_auth_challenge_ = false; }
// content::NotificationObserver:
void Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) override {
got_auth_challenge_ |= type == chrome::NOTIFICATION_AUTH_NEEDED;
}
private:
content::NotificationRegistrar registrar_;
bool got_auth_challenge_ = false;
};
// Runs a closure when all expected URLs have been fetched successfully.
class TestTabHelperObserver : public PrefetchProxyTabHelper::Observer {
public:
explicit TestTabHelperObserver(PrefetchProxyTabHelper* tab_helper)
: tab_helper_(tab_helper) {
tab_helper_->AddObserverForTesting(this);
}
~TestTabHelperObserver() { tab_helper_->RemoveObserverForTesting(this); }
void SetDecoyPrefetchClosure(base::OnceClosure closure) {
on_decoy_prefetch_closure_ = std::move(closure);
}
void SetOnPrefetchSuccessfulClosure(base::OnceClosure closure) {
on_successful_prefetch_closure_ = std::move(closure);
}
void SetOnPrefetchErrorClosure(base::OnceClosure closure) {
on_prefetch_error_closure_ = std::move(closure);
}
void SetExpectedSuccessfulURLs(const std::set<GURL>& expected_urls) {
expected_successful_prefetch_urls_ = expected_urls;
}
void SetExpectedPrefetchErrors(
const std::set<std::pair<GURL, int>> expected_prefetch_errors) {
expected_prefetch_errors_ = expected_prefetch_errors;
}
void SetOnNSPFinishedClosure(base::OnceClosure closure) {
on_nsp_finished_closure_ = std::move(closure);
}
// PrefetchProxyTabHelper::Observer:
void OnDecoyPrefetchCompleted(const GURL& url) override {
if (on_decoy_prefetch_closure_) {
std::move(on_decoy_prefetch_closure_).Run();
}
}
void OnPrefetchCompletedSuccessfully(const GURL& url) override {
auto it = expected_successful_prefetch_urls_.find(url);
if (it != expected_successful_prefetch_urls_.end()) {
expected_successful_prefetch_urls_.erase(it);
}
if (!expected_successful_prefetch_urls_.empty())
return;
if (!on_successful_prefetch_closure_)
return;
std::move(on_successful_prefetch_closure_).Run();
}
void OnPrefetchCompletedWithError(const GURL& url, int error_code) override {
std::pair<GURL, int> error_pair = {url, error_code};
auto it = expected_prefetch_errors_.find(error_pair);
if (it != expected_prefetch_errors_.end()) {
expected_prefetch_errors_.erase(it);
}
if (!expected_prefetch_errors_.empty())
return;
if (!on_prefetch_error_closure_)
return;
std::move(on_prefetch_error_closure_).Run();
}
void OnNoStatePrefetchFinished() override {
if (on_nsp_finished_closure_) {
std::move(on_nsp_finished_closure_).Run();
}
}
private:
PrefetchProxyTabHelper* tab_helper_;
base::OnceClosure on_decoy_prefetch_closure_;
base::OnceClosure on_successful_prefetch_closure_;
std::set<GURL> expected_successful_prefetch_urls_;
base::OnceClosure on_prefetch_error_closure_;
std::set<std::pair<GURL, int>> expected_prefetch_errors_;
base::OnceClosure on_nsp_finished_closure_;
};
// A stub ClientCertStore that returns a FakeClientCertIdentity.
class ClientCertStoreStub : public net::ClientCertStore {
public:
explicit ClientCertStoreStub(net::ClientCertIdentityList list)
: list_(std::move(list)) {}
~ClientCertStoreStub() override = default;
// net::ClientCertStore:
void GetClientCerts(const net::SSLCertRequestInfo& cert_request_info,
ClientCertListCallback callback) override {
std::move(callback).Run(std::move(list_));
}
private:
net::ClientCertIdentityList list_;
};
std::unique_ptr<net::ClientCertStore> CreateCertStore() {
base::FilePath certs_dir = net::GetTestCertsDirectory();
net::ClientCertIdentityList cert_identity_list;
{
base::ScopedAllowBlockingForTesting allow_blocking;
std::unique_ptr<net::FakeClientCertIdentity> cert_identity =
net::FakeClientCertIdentity::CreateFromCertAndKeyFiles(
certs_dir, "client_1.pem", "client_1.pk8");
EXPECT_TRUE(cert_identity.get());
if (cert_identity)
cert_identity_list.push_back(std::move(cert_identity));
}
return std::unique_ptr<net::ClientCertStore>(
new ClientCertStoreStub(std::move(cert_identity_list)));
}
class CustomProbeOverrideDelegate
: public PrefetchProxyOriginProber::ProbeURLOverrideDelegate {
public:
explicit CustomProbeOverrideDelegate(const GURL& override_url)
: url_(override_url) {}
~CustomProbeOverrideDelegate() = default;
GURL OverrideProbeURL(const GURL& url) override { return url_; }
private:
GURL url_;
};
class TestServerConnectionCounter
: public net::test_server::EmbeddedTestServerConnectionListener {
public:
TestServerConnectionCounter() = default;
~TestServerConnectionCounter() override = default;
size_t count() const { return count_; }
private:
void ReadFromSocket(const net::StreamSocket& connection, int rv) override {}
std::unique_ptr<net::StreamSocket> AcceptedSocket(
std::unique_ptr<net::StreamSocket> socket) override {
count_++;
return socket;
}
size_t count_ = 0;
};
// Reading the output of |testing::UnorderedElementsAreArray| is impossible.
std::string ActualHumanReadableMetricsToDebugString(
std::vector<ukm::TestUkmRecorder::HumanReadableUkmEntry> entries) {
std::string result = "Actual Entries:\n";
if (entries.empty()) {
result = "<empty>";
}
for (size_t i = 0; i < entries.size(); ++i) {
const auto& entry = entries[i];
result += base::StringPrintf("=== Entry #%zu\n", i);
result += base::StringPrintf("Source ID: %d\n",
static_cast<int>(entry.source_id));
for (const auto& metric : entry.metrics) {
result += base::StringPrintf("Metric '%s' = %d\n", metric.first.c_str(),
static_cast<int>(metric.second));
}
result += "\n";
}
result += "\n";
return result;
}
std::vector<testing::Matcher<ukm::TestUkmRecorder::HumanReadableUkmEntry>>
BuildPrefetchResourceMatchers(
const std::vector<ukm::TestUkmRecorder::HumanReadableUkmEntry>& entries) {
using UkmEntry = ukm::TestUkmRecorder::HumanReadableUkmEntry;
auto matchers = std::vector<testing::Matcher<UkmEntry>>{};
for (const auto& entry : entries) {
auto source_id_matcher =
testing::Field(&ukm::TestUkmRecorder::HumanReadableUkmEntry::source_id,
entry.source_id);
auto metrics_pairs =
std::vector<testing::Matcher<std::pair<std::string, int64_t>>>{};
for (const auto& metric : entry.metrics) {
std::string name = metric.first;
int64_t value = metric.second;
if (name == "DataLength" || name == "FetchDurationMS" ||
name == "NavigationStartToFetchStartMS") {
// This matcher only needs to check for a positive value since checking
// the exact value will be flaky.
metrics_pairs.push_back(testing::Pair(name, testing::Gt(0L)));
} else if (name == "ISPFilteringStatus") {
// Treat TLS Success and DNS Success as the same since the exact check
// done is flaky in tests. No probe should always match.
if (value == 0) {
metrics_pairs.push_back(testing::Pair(name, 0));
} else if (value == 2 || value == 4) {
metrics_pairs.push_back(testing::Pair(name, testing::AnyOf(2, 4)));
} else if (value == 1 || value == 3) {
metrics_pairs.push_back(testing::Pair(name, testing::AnyOf(1, 3)));
} else {
NOTREACHED();
}
} else {
metrics_pairs.push_back(testing::Pair(name, value));
}
}
matchers.push_back(testing::AllOf(
source_id_matcher,
testing::Field(
&UkmEntry::metrics,
testing::WhenSorted(testing::ElementsAreArray(metrics_pairs)))));
}
return matchers;
}
} // namespace
// Occasional flakes on Windows (https://crbug.com/1045971).
#if defined(OS_WIN) || defined(OS_MAC) || BUILDFLAG(IS_CHROMEOS_ASH)
#define DISABLE_ON_WIN_MAC_CHROMEOS(x) DISABLED_##x
#else
#define DISABLE_ON_WIN_MAC_CHROMEOS(x) x
#endif
class PrefetchProxyBrowserTest
: public InProcessBrowserTest,
public prerender::NoStatePrefetchHandle::Observer,
public net::test_server::EmbeddedTestServerConnectionListener {
public:
PrefetchProxyBrowserTest() {
origin_server_ = std::make_unique<net::EmbeddedTestServer>(
net::EmbeddedTestServer::TYPE_HTTPS);
origin_server_->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
origin_server_->ServeFilesFromSourceDirectory("chrome/test/data");
origin_server_->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
origin_server_->RegisterRequestHandler(
base::BindRepeating(&PrefetchProxyBrowserTest::HandleOriginRequest,
base::Unretained(this)));
EXPECT_TRUE(origin_server_->Start());
referring_page_server_ = std::make_unique<net::EmbeddedTestServer>(
net::EmbeddedTestServer::TYPE_HTTPS);
referring_page_server_->SetSSLConfig(
net::EmbeddedTestServer::CERT_TEST_NAMES);
referring_page_server_->ServeFilesFromSourceDirectory("chrome/test/data");
referring_page_server_->SetSSLConfig(
net::EmbeddedTestServer::CERT_TEST_NAMES);
EXPECT_TRUE(referring_page_server_->Start());
proxy_server_ = std::make_unique<net::EmbeddedTestServer>(
net::EmbeddedTestServer::TYPE_HTTPS);
proxy_server_->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
proxy_server_->ServeFilesFromSourceDirectory("chrome/test/data");
proxy_server_->RegisterRequestHandler(base::BindRepeating(
&PrefetchProxyBrowserTest::HandleProxyRequest, base::Unretained(this)));
proxy_server_->SetConnectionListener(this);
EXPECT_TRUE(proxy_server_->Start());
http_server_ = std::make_unique<net::EmbeddedTestServer>(
net::EmbeddedTestServer::TYPE_HTTP);
http_server_->ServeFilesFromSourceDirectory("chrome/test/data");
EXPECT_TRUE(http_server_->Start());
canary_server_ = std::make_unique<net::EmbeddedTestServer>(
net::EmbeddedTestServer::TYPE_HTTP);
canary_server_->RegisterRequestHandler(
base::BindRepeating(&PrefetchProxyBrowserTest::HandleCanaryRequest,
base::Unretained(this)));
EXPECT_TRUE(canary_server_->Start());
}
void SetUp() override {
SetFeatures();
InProcessBrowserTest::SetUp();
}
// This browsertest uses a separate method to handle enabling/disabling
// features since order is tricky when doing different feature lists between
// base and derived classes.
virtual void SetFeatures() {
// Important: Features with parameters can't be used here, because it will
// cause a failed DCHECK in the SSL reporting test.
scoped_feature_list_.InitAndEnableFeature(features::kIsolatePrerenders);
}
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
// So that we can test for client hints.
g_browser_process->network_quality_tracker()
->ReportEffectiveConnectionTypeForTesting(
net::EFFECTIVE_CONNECTION_TYPE_2G);
ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
// Ensure the service gets created before the tests start.
PrefetchProxyServiceFactory::GetForProfile(browser()->profile());
host_resolver()->AddRule("a.test", "127.0.0.1");
host_resolver()->AddRule("proxy.a.test", "127.0.0.1");
host_resolver()->AddRule("insecure.com", "127.0.0.1");
host_resolver()->AddRule("a.test", "127.0.0.1");
host_resolver()->AddRule("b.test", "127.0.0.1");
host_resolver()->AddSimulatedFailure("baddnsprobe.a.test");
}
void SetUpCommandLine(base::CommandLine* cmd) override {
cmd->AppendSwitch("prefetch-proxy-never-send-decoy-requests-for-testing");
// For the proxy.
cmd->AppendSwitch("ignore-certificate-errors");
cmd->AppendSwitchASCII("isolated-prerender-tunnel-proxy",
GetProxyURL().spec());
cmd->AppendSwitchASCII(switches::kEnableBlinkFeatures,
"SpeculationRulesPrefetchProxy");
}
void SetDataSaverEnabled(bool enabled) {
data_reduction_proxy::DataReductionProxySettings::
SetDataSaverEnabledForTesting(browser()->profile()->GetPrefs(),
enabled);
}
void ResetFeatureList() { scoped_feature_list_.Reset(); }
content::WebContents* GetWebContents() const {
return browser()->tab_strip_model()->GetActiveWebContents();
}
void MakeNavigationPrediction(const GURL& doc_url,
const std::vector<GURL>& predicted_urls) {
NavigationPredictorKeyedServiceFactory::GetForProfile(browser()->profile())
->OnPredictionUpdated(
GetWebContents(), doc_url,
NavigationPredictorKeyedService::PredictionSource::
kAnchorElementsParsedFromWebPage,
predicted_urls);
}
void InsertSpeculation(bool subresources,
const std::vector<GURL>& prefetch_urls) {
std::string speculation_script = R"(
var script = document.createElement('script');
script.type = 'speculationrules';
script.text = `{)";
if (subresources)
speculation_script.append(R"("prefetch_with_subresources": [{)");
else
speculation_script.append(R"("prefetch": [{)");
speculation_script.append(R"("source": "list",
"urls": [)");
bool first = true;
for (const GURL& url : prefetch_urls) {
if (!first)
speculation_script.append(",");
first = false;
speculation_script.append("\"").append(url.spec()).append("\"");
}
speculation_script.append(R"(],
"requires": ["anonymous-client-ip-when-cross-origin"]
}]
}`;
document.head.appendChild(script);)");
EXPECT_TRUE(ExecuteScript(GetWebContents(), speculation_script));
}
std::unique_ptr<prerender::NoStatePrefetchHandle> StartPrerender(
const GURL& url) {
prerender::NoStatePrefetchManager* no_state_prefetch_manager =
prerender::NoStatePrefetchManagerFactory::GetForBrowserContext(
browser()->profile());
return no_state_prefetch_manager->AddPrerenderFromNavigationPredictor(
url,
GetWebContents()->GetController().GetDefaultSessionStorageNamespace(),
kSize);
}
network::mojom::CustomProxyConfigPtr WaitForUpdatedCustomProxyConfig() {
PrefetchProxyService* prefetch_proxy_service =
PrefetchProxyServiceFactory::GetForProfile(browser()->profile());
base::RunLoop run_loop;
mojo::Remote<network::mojom::CustomProxyConfigClient> client_remote;
TestCustomProxyConfigClient config_client(
client_remote.BindNewPipeAndPassReceiver(), run_loop.QuitClosure());
prefetch_proxy_service->proxy_configurator()->AddCustomProxyConfigClient(
std::move(client_remote));
run_loop.Run();
return std::move(config_client.config_);
}
void WaitForTLSCanaryCheck() {
PrefetchProxyService* service =
PrefetchProxyServiceFactory::GetForProfile(browser()->profile());
while (!service->origin_prober()->IsTLSCanaryCheckCompleteForTesting()) {
base::RunLoop().RunUntilIdle();
}
}
void WaitForDNSCanaryCheck() {
PrefetchProxyService* service =
PrefetchProxyServiceFactory::GetForProfile(browser()->profile());
while (service->origin_prober()->IsDNSCanaryCheckActiveForTesting()) {
base::RunLoop().RunUntilIdle();
}
}
bool RequestHasClientHints(const net::test_server::HttpRequest& request) {
for (const auto& elem : network::GetClientHintToNameMap()) {
const auto& header = elem.second;
// The UA {mobile} Client Hint is whitelisted so we don't check it.
if (header == std::string(kAllowedUAClientHint)) {
continue;
}
if (header == std::string(kAllowedUAMobileClientHint)) {
continue;
}
if (header == std::string(kAllowedUAPlatformClientHint)) {
continue;
}
if (base::Contains(request.headers, header)) {
LOG(WARNING) << "request has " << header;
return true;
}
}
return false;
}
void VerifyProxyConfig(network::mojom::CustomProxyConfigPtr config,
bool want_empty = false) {
ASSERT_TRUE(config);
EXPECT_EQ(config->rules.type,
net::ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME);
EXPECT_FALSE(config->should_override_existing_config);
EXPECT_FALSE(config->allow_non_idempotent_methods);
if (want_empty) {
EXPECT_EQ(config->rules.proxies_for_https.size(), 0U);
} else {
ASSERT_EQ(config->rules.proxies_for_https.size(), 1U);
EXPECT_EQ(GURL(net::ProxyServerToProxyUri(
config->rules.proxies_for_https.Get())),
GetProxyURL());
}
}
bool CheckForResourceInIsolatedCache(const GURL& url) {
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
DCHECK(tab_helper);
DCHECK(tab_helper->GetIsolatedContextForTesting());
return net::OK ==
content::LoadBasicRequest(tab_helper->GetIsolatedContextForTesting(),
url, net::LOAD_ONLY_FROM_CACHE);
}
absl::optional<int64_t> GetUKMMetric(const GURL& url,
const std::string& event_name,
const std::string& metric_name) {
SCOPED_TRACE(metric_name);
auto entries = ukm_recorder_->GetEntriesByName(event_name);
DCHECK_EQ(1U, entries.size());
const auto* entry = entries.front();
ukm_recorder_->ExpectEntrySourceHasUrl(entry, url);
const int64_t* value =
ukm::TestUkmRecorder::GetEntryMetric(entry, metric_name);
if (value == nullptr) {
return absl::nullopt;
}
return absl::optional<int64_t>(*value);
}
void VerifyNoUKMEvent(const std::string& event_name) {
SCOPED_TRACE(event_name);
auto entries = ukm_recorder_->GetEntriesByName(event_name);
EXPECT_TRUE(entries.empty());
}
void VerifyUKMOnSRP(const GURL& url,
const std::string& metric_name,
absl::optional<int64_t> expected) {
SCOPED_TRACE(metric_name);
auto actual = GetUKMMetric(url, ukm::builders::PrefetchProxy::kEntryName,
metric_name);
EXPECT_EQ(actual, expected);
}
void VerifyUKMAfterSRP(const GURL& url,
const std::string& metric_name,
absl::optional<int64_t> expected) {
SCOPED_TRACE(metric_name);
auto actual = GetUKMMetric(
url, ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
metric_name);
EXPECT_EQ(actual, expected);
}
// Verifies that the entries for |ukm_source_id| match |url| and then returns
// all the prefetched resource metrics.
std::vector<ukm::TestUkmRecorder::HumanReadableUkmEntry>
GetAndVerifyPrefetchedResourceUKM(const GURL& url,
ukm::SourceId ukm_source_id) {
const ukm::UkmSource* source =
ukm_recorder_->GetSourceForSourceId(ukm_source_id);
DCHECK(source);
EXPECT_TRUE(base::Contains(source->urls(), url));
return ukm_recorder_->GetEntries("PrefetchProxy.PrefetchedResource",
{
"DataLength",
"FetchDurationMS",
"ISPFilteringStatus",
"LinkClicked",
"LinkPosition",
"NavigationStartToFetchStartMS",
"ResourceType",
"Status",
});
}
// Uses the url's path to match requests since the host from
// EmbeddedTestServer is always 127.0.0.1.
void VerifyOriginRequestsAreIsolated(const std::set<std::string>& paths) {
size_t verified_url_count = 0;
for (const auto& request : origin_server_requests()) {
const GURL& url = request.GetURL();
if (paths.find(url.path()) == paths.end()) {
continue;
}
SCOPED_TRACE(request.GetURL().spec());
EXPECT_EQ(request.headers.find("user-agent")->second,
content::GetReducedUserAgent(
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kUseMobileUserAgent),
version_info::GetMajorVersionNumber()));
EXPECT_EQ(request.headers.find("purpose")->second, "prefetch");
verified_url_count++;
}
EXPECT_EQ(paths.size(), verified_url_count);
}
size_t OriginServerRequestCount() const {
base::RunLoop().RunUntilIdle();
return origin_server_request_count_;
}
const std::vector<net::test_server::HttpRequest>& proxy_server_requests()
const {
return proxy_server_requests_;
}
const std::vector<net::test_server::HttpRequest>& origin_server_requests()
const {
return origin_server_requests_;
}
GURL GetProxyURL() const {
return proxy_server_->GetURL("proxy.a.test", "/");
}
GURL GetInsecureURL(const std::string& path) {
return http_server_->GetURL("insecure.com", path);
}
GURL GetOriginServerURL(const std::string& path) const {
return origin_server_->GetURL("a.test", path);
}
GURL GetReferringPageServerURL(const std::string& path) const {
return referring_page_server_->GetURL("www.google.com", path);
}
GURL GetCanaryServerURL() const { return canary_server_->GetURL("/"); }
private:
std::unique_ptr<net::test_server::HttpResponse> HandleOriginRequest(
const net::test_server::HttpRequest& request) {
if (request.GetURL().spec().find("favicon") != std::string::npos)
return nullptr;
SCOPED_TRACE(request.GetURL().spec());
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(
&PrefetchProxyBrowserTest::MonitorOriginResourceRequestOnUIThread,
base::Unretained(this), request));
if (request.relative_url == "/auth_challenge") {
std::unique_ptr<net::test_server::BasicHttpResponse> resp =
std::make_unique<net::test_server::BasicHttpResponse>();
resp->set_code(net::HTTP_UNAUTHORIZED);
resp->AddCustomHeader("www-authenticate", "Basic realm=\"test\"");
return resp;
}
bool is_prefetch =
request.headers.find("Purpose") != request.headers.end() &&
request.headers.find("Purpose")->second == "prefetch";
if (request.relative_url == "/404_on_prefetch") {
std::unique_ptr<net::test_server::BasicHttpResponse> resp =
std::make_unique<net::test_server::BasicHttpResponse>();
resp->set_code(is_prefetch ? net::HTTP_NOT_FOUND : net::HTTP_OK);
resp->set_content_type("text/html");
resp->set_content("<html><body>Test</body></html>");
return resp;
}
return nullptr;
}
void OnProxyTunnelDone(TestProxyTunnelConnection* tunnel) {
auto iter = tunnels_.find(tunnel);
if (iter != tunnels_.end()) {
tunnels_.erase(iter);
}
}
std::unique_ptr<net::test_server::HttpResponse> HandleProxyRequest(
const net::test_server::HttpRequest& request) {
if (request.all_headers.find("CONNECT auth_challenge.com:443") !=
std::string::npos) {
std::unique_ptr<net::test_server::BasicHttpResponse> resp =
std::make_unique<net::test_server::BasicHttpResponse>();
resp->set_code(net::HTTP_UNAUTHORIZED);
resp->AddCustomHeader("www-authenticate", "Basic realm=\"test\"");
return resp;
}
if (request.all_headers.find("CONNECT error.com:443") !=
std::string::npos) {
std::unique_ptr<net::test_server::BasicHttpResponse> resp =
std::make_unique<net::test_server::BasicHttpResponse>();
resp->set_code(net::HTTP_BAD_REQUEST);
return resp;
}
std::vector<std::string> request_lines =
base::SplitString(request.all_headers, "\r\n", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
DCHECK(!request_lines.empty());
std::vector<std::string> request_line =
base::SplitString(request_lines[0], " ", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
DCHECK_EQ(3U, request_line.size());
EXPECT_EQ("CONNECT", request_line[0]);
EXPECT_EQ("HTTP/1.1", request_line[2]);
GURL request_origin("https://" + request_line[1]);
EXPECT_TRUE("a.test" == request_origin.host() ||
"b.test" == request_origin.host());
bool found_chrome_tunnel_header = false;
for (const std::string& header : request_lines) {
if (base::Contains(header, "chrome-tunnel") &&
base::Contains(header, "key=" + google_apis::GetAPIKey())) {
found_chrome_tunnel_header = true;
break;
}
}
EXPECT_TRUE(found_chrome_tunnel_header);
auto new_tunnel = std::make_unique<TestProxyTunnelConnection>();
new_tunnel->SetOnDoneCallback(
base::BindOnce(&PrefetchProxyBrowserTest::OnProxyTunnelDone,
base::Unretained(this), new_tunnel.get()));
EXPECT_TRUE(new_tunnel->ConnectToPeerOnLocalhost(
request_origin.EffectiveIntPort()));
tunnels_.insert(std::move(new_tunnel));
// This method is called on embedded test server thread. Post the
// information on UI thread.
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(
&PrefetchProxyBrowserTest::MonitorProxyResourceRequestOnUIThread,
base::Unretained(this), request));
std::unique_ptr<net::test_server::BasicHttpResponse> resp =
std::make_unique<net::test_server::BasicHttpResponse>();
resp->set_code(net::HTTP_OK);
return resp;
}
std::unique_ptr<net::test_server::HttpResponse> HandleCanaryRequest(
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);
// Make sure whitespace is ok, especially trailing newline.
resp->set_content(" OK\n");
return resp;
}
void MonitorProxyResourceRequestOnUIThread(
const net::test_server::HttpRequest& request) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
proxy_server_requests_.push_back(request);
}
void MonitorOriginResourceRequestOnUIThread(
const net::test_server::HttpRequest& request) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
origin_server_request_count_++;
origin_server_requests_.push_back(request);
EXPECT_TRUE(request.headers.find("Accept-Language") !=
request.headers.end());
EXPECT_EQ(request.headers.find("Accept-Language")->second,
net::HttpUtil::GenerateAcceptLanguageHeader(
browser()->profile()->GetPrefs()->GetString(
language::prefs::kAcceptLanguages)));
}
// prerender::NoStatePrefetchHandle::Observer:
void OnPrefetchNetworkBytesChanged(
prerender::NoStatePrefetchHandle* handle) override {}
void OnPrefetchStop(prerender::NoStatePrefetchHandle* handle) override {}
// net::test_server::EmbeddedTestServerConnectionListener:
void ReadFromSocket(const net::StreamSocket& socket, int rv) override {}
std::unique_ptr<net::StreamSocket> AcceptedSocket(
std::unique_ptr<net::StreamSocket> socket) override {
return socket;
}
void OnResponseCompletedSuccessfully(
std::unique_ptr<net::StreamSocket> socket) override {
DCHECK(socket->IsConnected());
// Find a tunnel that isn't being used already.
for (const auto& tunnel : tunnels_) {
if (tunnel->IsReadyForIncomingSocket()) {
tunnel->StartProxy(std::move(socket));
return;
}
}
}
base::test::ScopedFeatureList scoped_feature_list_;
std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_;
std::unique_ptr<net::EmbeddedTestServer> proxy_server_;
std::unique_ptr<net::EmbeddedTestServer> origin_server_;
std::unique_ptr<net::EmbeddedTestServer> http_server_;
std::unique_ptr<net::EmbeddedTestServer> canary_server_;
std::unique_ptr<net::EmbeddedTestServer> referring_page_server_;
std::vector<net::test_server::HttpRequest> origin_server_requests_;
std::vector<net::test_server::HttpRequest> proxy_server_requests_;
// These all live on |proxy_server_|'s IO Thread.
std::set<std::unique_ptr<TestProxyTunnelConnection>,
base::UniquePtrComparator>
tunnels_;
size_t origin_server_request_count_ = 0;
};
IN_PROC_BROWSER_TEST_F(
PrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(ServiceWorkerRegistrationIsNotEligible)) {
SetDataSaverEnabled(true);
// Load a page that registers a service worker.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
GetOriginServerURL("/service_worker/create_service_worker.html")));
EXPECT_EQ("DONE", EvalJs(GetWebContents(),
"register('network_fallback_worker.js');"));
content::ServiceWorkerContext* service_worker_context_ =
browser()
->profile()
->GetDefaultStoragePartition()
->GetServiceWorkerContext();
EXPECT_EQ(true, service_worker_context_->MaybeHasRegistrationForOrigin(
url::Origin::Create(GetOriginServerURL("/"))));
EXPECT_EQ(false, service_worker_context_->MaybeHasRegistrationForOrigin(
url::Origin::Create(GURL("https://unregistered.com"))));
GURL prefetch_url = GetOriginServerURL("/title2.html");
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {prefetch_url});
// No run loop is needed here since the service worker check is synchronous.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), prefetch_url));
// Navigate again to trigger UKM recording.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
// 6 = |kPrefetchNotEligibleUserHasServiceWorker|
EXPECT_EQ(absl::optional<int64_t>(6),
GetUKMMetric(prefetch_url,
ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::
kSRPClickPrefetchStatusName));
}
IN_PROC_BROWSER_TEST_F(PrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(DRPClientConfigPlumbing)) {
SetDataSaverEnabled(true);
auto client_config = WaitForUpdatedCustomProxyConfig();
VerifyProxyConfig(std::move(client_config));
}
IN_PROC_BROWSER_TEST_F(
PrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(NoAuthChallenges_FromProxy)) {
SetDataSaverEnabled(true);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
WaitForUpdatedCustomProxyConfig();
std::unique_ptr<AuthChallengeObserver> auth_observer =
std::make_unique<AuthChallengeObserver>(GetWebContents());
// Do a positive test first to make sure we get an auth challenge under these
// circumstances.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), GetOriginServerURL("/auth_challenge")));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(auth_observer->GotAuthChallenge());
// Test that a proxy auth challenge does not show a dialog.
auth_observer->Reset();
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {GURL("https://auth_challenge.com/")});
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(auth_observer->GotAuthChallenge());
}
IN_PROC_BROWSER_TEST_F(PrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(ProxyServerBackOff)) {
SetDataSaverEnabled(true);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
WaitForUpdatedCustomProxyConfig();
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
TestTabHelperObserver tab_helper_observer(tab_helper);
GURL error_url("https://error.com/");
base::RunLoop run_loop;
tab_helper_observer.SetOnPrefetchErrorClosure(run_loop.QuitClosure());
tab_helper_observer.SetExpectedPrefetchErrors(
{{error_url, net::ERR_TUNNEL_CONNECTION_FAILED}});
base::HistogramTester histogram_tester;
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {error_url});
run_loop.Run();
histogram_tester.ExpectUniqueSample("PrefetchProxy.Proxy.RespCode", 400, 1);
EXPECT_EQ(1U, tab_helper->srp_metrics().predicted_urls_count_);
EXPECT_EQ(1U, tab_helper->srp_metrics().prefetch_attempted_count_);
EXPECT_EQ(0U, tab_helper->srp_metrics().prefetch_successful_count_);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), error_url));
ASSERT_TRUE(tab_helper->after_srp_metrics());
EXPECT_EQ(
absl::make_optional(PrefetchProxyPrefetchStatus::kPrefetchFailedNetError),
tab_helper->after_srp_metrics()->prefetch_status_);
// Doing this prefetch again is immediately skipped because the proxy is not
// available.
MakeNavigationPrediction(doc_url, {error_url});
EXPECT_EQ(1U, tab_helper->srp_metrics().predicted_urls_count_);
EXPECT_EQ(0U, tab_helper->srp_metrics().prefetch_attempted_count_);
EXPECT_EQ(0U, tab_helper->srp_metrics().prefetch_successful_count_);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), error_url));
ASSERT_TRUE(tab_helper->after_srp_metrics());
EXPECT_EQ(absl::make_optional(
PrefetchProxyPrefetchStatus::kPrefetchProxyNotAvailable),
tab_helper->after_srp_metrics()->prefetch_status_);
}
IN_PROC_BROWSER_TEST_F(PrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(CookieOnHigherLevelDomain)) {
SetDataSaverEnabled(true);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
WaitForUpdatedCustomProxyConfig();
ASSERT_TRUE(content::SetCookie(browser()->profile(), GURL("https://foo.com"),
"type=PeanutButter"));
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL prefetch_url("https://m.foo.com");
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {prefetch_url});
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1U, tab_helper->srp_metrics().predicted_urls_count_);
EXPECT_EQ(0U, tab_helper->srp_metrics().prefetch_eligible_count_);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), prefetch_url));
ASSERT_TRUE(tab_helper->after_srp_metrics());
EXPECT_EQ(
absl::make_optional(
PrefetchProxyPrefetchStatus::kPrefetchNotEligibleUserHasCookies),
tab_helper->after_srp_metrics()->prefetch_status_);
}
IN_PROC_BROWSER_TEST_F(PrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(CookieOnOtherPath)) {
SetDataSaverEnabled(true);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
WaitForUpdatedCustomProxyConfig();
ASSERT_TRUE(content::SetCookie(browser()->profile(), GURL("https://foo.com"),
"cookietype=PeanutButter;path=/cookiecookie"));
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL prefetch_url("https://foo.com/no-cookies-here");
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {prefetch_url});
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1U, tab_helper->srp_metrics().predicted_urls_count_);
EXPECT_EQ(0U, tab_helper->srp_metrics().prefetch_eligible_count_);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), prefetch_url));
ASSERT_TRUE(tab_helper->after_srp_metrics());
EXPECT_EQ(
absl::make_optional(
PrefetchProxyPrefetchStatus::kPrefetchNotEligibleUserHasCookies),
tab_helper->after_srp_metrics()->prefetch_status_);
}
IN_PROC_BROWSER_TEST_F(PrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(ExpiredCookie)) {
SetDataSaverEnabled(true);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
WaitForUpdatedCustomProxyConfig();
ASSERT_TRUE(content::SetCookie(
browser()->profile(), GetOriginServerURL("/"),
"cookietype=Stale;Expires=Sat, 1 Jan 2000 00:00:00 GMT"));
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL prefetch_url = GetOriginServerURL("/simple.html");
TestTabHelperObserver tab_helper_observer(tab_helper);
tab_helper_observer.SetExpectedSuccessfulURLs({prefetch_url});
base::RunLoop run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(run_loop.QuitClosure());
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {prefetch_url});
run_loop.Run();
EXPECT_EQ(1U, tab_helper->srp_metrics().predicted_urls_count_);
EXPECT_EQ(1U, tab_helper->srp_metrics().prefetch_eligible_count_);
EXPECT_EQ(1U, tab_helper->srp_metrics().prefetch_successful_count_);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), prefetch_url));
ASSERT_TRUE(tab_helper->after_srp_metrics());
EXPECT_EQ(
absl::make_optional(PrefetchProxyPrefetchStatus::kPrefetchUsedNoProbe),
tab_helper->after_srp_metrics()->prefetch_status_);
}
IN_PROC_BROWSER_TEST_F(
PrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(CookieOnNonApplicableDomain)) {
SetDataSaverEnabled(true);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
WaitForUpdatedCustomProxyConfig();
ASSERT_TRUE(content::SetCookie(browser()->profile(), GURL("https://foo.com"),
"cookietype=Oatmeal"));
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL prefetch_url = GetOriginServerURL("/simple.html");
TestTabHelperObserver tab_helper_observer(tab_helper);
tab_helper_observer.SetExpectedSuccessfulURLs({prefetch_url});
base::RunLoop run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(run_loop.QuitClosure());
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {prefetch_url});
run_loop.Run();
EXPECT_EQ(1U, tab_helper->srp_metrics().predicted_urls_count_);
EXPECT_EQ(1U, tab_helper->srp_metrics().prefetch_eligible_count_);
EXPECT_EQ(1U, tab_helper->srp_metrics().prefetch_successful_count_);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), prefetch_url));
ASSERT_TRUE(tab_helper->after_srp_metrics());
EXPECT_EQ(
absl::make_optional(PrefetchProxyPrefetchStatus::kPrefetchUsedNoProbe),
tab_helper->after_srp_metrics()->prefetch_status_);
}
IN_PROC_BROWSER_TEST_F(
PrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(NoAuthChallenges_FromOrigin)) {
SetDataSaverEnabled(true);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
WaitForUpdatedCustomProxyConfig();
GURL auth_challenge_url = GetOriginServerURL("/auth_challenge");
std::unique_ptr<AuthChallengeObserver> auth_observer =
std::make_unique<AuthChallengeObserver>(GetWebContents());
// Do a positive test first to make sure we get an auth challenge under these
// circumstances.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), auth_challenge_url));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(auth_observer->GotAuthChallenge());
// Test that an origin auth challenge does not show a dialog.
auth_observer->Reset();
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
TestTabHelperObserver tab_helper_observer(tab_helper);
base::RunLoop run_loop;
tab_helper_observer.SetOnPrefetchErrorClosure(run_loop.QuitClosure());
tab_helper_observer.SetExpectedPrefetchErrors(
{{auth_challenge_url, net::HTTP_UNAUTHORIZED}});
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {auth_challenge_url});
run_loop.Run();
EXPECT_FALSE(auth_observer->GotAuthChallenge());
}
IN_PROC_BROWSER_TEST_F(PrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(ConnectProxyEndtoEnd)) {
// The test assumes the previous page gets deleted after navigation. Disable
// back/forward cache to ensure that it doesn't get preserved in the cache.
content::DisableBackForwardCacheForTesting(
GetWebContents(), content::BackForwardCache::TEST_ASSUMES_NO_CACHING);
SetDataSaverEnabled(true);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(),
GetOriginServerURL("/simple.html")));
WaitForUpdatedCustomProxyConfig();
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
TestTabHelperObserver tab_helper_observer(tab_helper);
GURL prefetch_url = GetOriginServerURL("/title2.html");
base::RunLoop run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(run_loop.QuitClosure());
tab_helper_observer.SetExpectedSuccessfulURLs({prefetch_url});
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {prefetch_url});
// This run loop will quit when the prefetch response has been successfully
// done and processed.
run_loop.Run();
EXPECT_EQ(tab_helper->srp_metrics().prefetch_attempted_count_, 1U);
EXPECT_EQ(tab_helper->srp_metrics().prefetch_successful_count_, 1U);
size_t starting_origin_request_count = OriginServerRequestCount();
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), prefetch_url));
EXPECT_EQ(u"Title Of Awesomeness", GetWebContents()->GetTitle());
VerifyOriginRequestsAreIsolated({prefetch_url.path()});
// The origin server should not have served this request.
EXPECT_EQ(starting_origin_request_count, OriginServerRequestCount());
}
IN_PROC_BROWSER_TEST_F(PrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(PrefetchingUKM_Success)) {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
"isolated-prerender-unlimited-prefetches");
GURL starting_page = GetOriginServerURL("/simple.html");
SetDataSaverEnabled(true);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), starting_page));
WaitForUpdatedCustomProxyConfig();
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL eligible_link_1 = GetOriginServerURL("/title1.html");
GURL eligible_link_2 = GetOriginServerURL("/title2.html");
GURL eligible_link_3 = GetOriginServerURL("/title3.html");
TestTabHelperObserver tab_helper_observer(tab_helper);
tab_helper_observer.SetExpectedSuccessfulURLs(
{eligible_link_1, eligible_link_2, eligible_link_3});
base::RunLoop run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(run_loop.QuitClosure());
ukm::SourceId srp_source_id =
GetWebContents()->GetMainFrame()->GetPageUkmSourceId();
base::HistogramTester histogram_tester;
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {
eligible_link_1,
eligible_link_2,
GURL("http://not-eligible.com/1"),
GURL("http://not-eligible.com/2"),
GURL("http://not-eligible.com/3"),
eligible_link_3,
});
// This run loop will quit when all the prefetch responses have been
// successfully done and processed.
run_loop.Run();
histogram_tester.ExpectTotalCount("PrefetchProxy.Prefetch.Mainframe.RespCode",
3);
histogram_tester.ExpectTotalCount(
"PrefetchProxy.Prefetch.Mainframe.BodyLength", 3);
histogram_tester.ExpectTotalCount(
"PrefetchProxy.Prefetch.Mainframe.TotalTime", 3);
histogram_tester.ExpectTotalCount(
"PrefetchProxy.Prefetch.Mainframe.ConnectTime", 3);
// Navigate to a prefetched page to trigger UKM recording.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), eligible_link_2));
base::RunLoop().RunUntilIdle();
VerifyOriginRequestsAreIsolated({
eligible_link_1.path(),
eligible_link_2.path(),
eligible_link_3.path(),
});
using UkmEntry = ukm::TestUkmRecorder::HumanReadableUkmEntry;
auto expected_entries = std::vector<UkmEntry>{
// eligible_link_1
UkmEntry{
srp_source_id,
{
{"DataLength", 0}, /* only checked for > 0 */
{"FetchDurationMS", 0}, /* only checked for > 0 */
{"NavigationStartToFetchStartMS", 0}, /* only checked for > 0 */
{"LinkClicked", 0},
{"LinkPosition", 0},
{"ResourceType", 1},
{"Status", 14},
}},
// eligible_link_2
UkmEntry{
srp_source_id,
{
{"DataLength", 0}, /* only checked for > 0 */
{"FetchDurationMS", 0}, /* only checked for > 0 */
{"NavigationStartToFetchStartMS", 0}, /* only checked for > 0 */
{"ISPFilteringStatus", 0},
{"LinkClicked", 1},
{"LinkPosition", 1},
{"ResourceType", 1},
{"Status", 0},
}},
// not eligible url #1
UkmEntry{srp_source_id,
{
{"LinkClicked", 0},
{"LinkPosition", 2},
{"ResourceType", 1},
{"Status", 7},
}},
// not eligible url #2
UkmEntry{srp_source_id,
{
{"LinkClicked", 0},
{"LinkPosition", 3},
{"ResourceType", 1},
{"Status", 7},
}},
// not eligible url #3
UkmEntry{srp_source_id,
{
{"LinkClicked", 0},
{"LinkPosition", 4},
{"ResourceType", 1},
{"Status", 7},
}},
// eligible_link_3
UkmEntry{
srp_source_id,
{
{"DataLength", 0}, /* only checked for > 0 */
{"FetchDurationMS", 0}, /* only checked for > 0 */
{"NavigationStartToFetchStartMS", 0}, /* only checked for > 0 */
{"LinkClicked", 0},
{"LinkPosition", 5},
{"ResourceType", 1},
{"Status", 14},
}},
};
auto actual_entries =
GetAndVerifyPrefetchedResourceUKM(starting_page, srp_source_id);
EXPECT_THAT(actual_entries,
testing::UnorderedElementsAreArray(
BuildPrefetchResourceMatchers(expected_entries)))
<< ActualHumanReadableMetricsToDebugString(actual_entries);
// This bit mask records which links were eligible for prefetching with
// respect to their order in the navigation prediction. The LSB corresponds to
// the first index in the prediction, and is set if that url was eligible.
// Given the above URLs, they map to each bit accordingly:
//
// Note: The only difference between eligible and non-eligible urls is the
// scheme.
//
// (eligible) https://a.test/1
// (eligible) https://a.test/2 |
// (not eligible) http://not-eligible.com/1 | |
// (not eligible) http://not-eligible.com/2 | | |
// (not eligible) http://not-eligible.com/3 | | | |
// (eligible) https://a.test/3 | | | | |
// | | | | | |
// V V V V V V
// int64_t expected_bitmask = 0b 1 0 0 0 1 1;
constexpr int64_t expected_bitmask = 0b100011;
VerifyUKMOnSRP(
starting_page,
ukm::builders::PrefetchProxy::kordered_eligible_pages_bitmaskName,
expected_bitmask);
VerifyUKMOnSRP(starting_page,
ukm::builders::PrefetchProxy::kprefetch_eligible_countName, 3);
VerifyUKMOnSRP(starting_page,
ukm::builders::PrefetchProxy::kprefetch_attempted_countName,
3);
VerifyUKMOnSRP(starting_page,
ukm::builders::PrefetchProxy::kprefetch_successful_countName,
3);
VerifyNoUKMEvent(ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName);
// Navigate to trigger UKM recording.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
base::RunLoop().RunUntilIdle();
VerifyUKMAfterSRP(
eligible_link_2,
ukm::builders::PrefetchProxy_AfterSRPClick::kClickedLinkSRPPositionName,
1);
VerifyUKMAfterSRP(
eligible_link_2,
ukm::builders::PrefetchProxy_AfterSRPClick::kSRPPrefetchEligibleCountName,
3);
// 0 is the value of |kPrefetchUsedNoProbe|. The enum is not used here
// intentionally because its value should never change.
VerifyUKMAfterSRP(
eligible_link_2,
ukm::builders::PrefetchProxy_AfterSRPClick::kSRPClickPrefetchStatusName,
0);
EXPECT_EQ(
absl::nullopt,
GetUKMMetric(
eligible_link_2,
ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::kProbeLatencyMsName));
}
IN_PROC_BROWSER_TEST_F(PrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(Origin503RetryAfter)) {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
"isolated-prerender-unlimited-prefetches");
GURL starting_page = GetOriginServerURL("/simple.html");
SetDataSaverEnabled(true);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), starting_page));
WaitForUpdatedCustomProxyConfig();
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL eligible_link_ok = GetOriginServerURL("/title1.html");
GURL eligible_link_503 =
GetOriginServerURL("/prefetch/prefetch_proxy/page503.html");
// Do a prefetch with 503 Service Unavailable, Retry After 10s.
{
base::HistogramTester histogram_tester;
TestTabHelperObserver tab_helper_observer(tab_helper);
tab_helper_observer.SetExpectedPrefetchErrors(
{{eligible_link_503, net::HTTP_SERVICE_UNAVAILABLE}});
base::RunLoop run_loop;
tab_helper_observer.SetOnPrefetchErrorClosure(run_loop.QuitClosure());
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {eligible_link_503});
run_loop.Run();
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.RespCode",
net::HTTP_SERVICE_UNAVAILABLE, 1);
EXPECT_EQ(1U, tab_helper->srp_metrics().predicted_urls_count_);
EXPECT_EQ(1U, tab_helper->srp_metrics().prefetch_attempted_count_);
EXPECT_EQ(0U, tab_helper->srp_metrics().prefetch_successful_count_);
}
// Expect that another request is not sent to the origin.
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {eligible_link_ok});
base::RunLoop().RunUntilIdle();
EXPECT_EQ(2U, tab_helper->srp_metrics().predicted_urls_count_);
EXPECT_EQ(1U, tab_helper->srp_metrics().prefetch_attempted_count_);
EXPECT_EQ(0U, tab_helper->srp_metrics().prefetch_successful_count_);
// Navigate to the ineligible prefetch page to verify the status.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), eligible_link_ok));
EXPECT_EQ(PrefetchProxyPrefetchStatus::kPrefetchIneligibleRetryAfter,
*tab_helper->after_srp_metrics()->prefetch_status_);
// Wait 10s and verify we do prefetch afterwards.
{
base::RunLoop run_loop;
content::GetUIThreadTaskRunner({})->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), base::Seconds(10));
run_loop.Run();
}
{
base::RunLoop run_loop;
TestTabHelperObserver tab_helper_observer(tab_helper);
tab_helper_observer.SetExpectedSuccessfulURLs({eligible_link_ok});
tab_helper_observer.SetOnPrefetchSuccessfulClosure(run_loop.QuitClosure());
MakeNavigationPrediction(doc_url, {eligible_link_ok});
run_loop.Run();
}
EXPECT_EQ(1U, tab_helper->srp_metrics().predicted_urls_count_);
EXPECT_EQ(1U, tab_helper->srp_metrics().prefetch_attempted_count_);
EXPECT_EQ(1U, tab_helper->srp_metrics().prefetch_successful_count_);
}
// 204's don't commit so this is used to test that the AfterSRPMetrics UKM event
// is recorded if the page does not commit. In the wild, we expect this to
// normally occur due to aborted navigations but the end result is the same.
IN_PROC_BROWSER_TEST_F(PrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(PrefetchingUKM_NoCommit)) {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
"isolated-prerender-unlimited-prefetches");
GURL starting_page = GetOriginServerURL("/simple.html");
SetDataSaverEnabled(true);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), starting_page));
WaitForUpdatedCustomProxyConfig();
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL eligible_link_204 =
GetOriginServerURL("/prefetch/prefetch_proxy/page204.html");
TestTabHelperObserver tab_helper_observer(tab_helper);
tab_helper_observer.SetExpectedSuccessfulURLs({eligible_link_204});
base::RunLoop run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(run_loop.QuitClosure());
base::HistogramTester histogram_tester;
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {eligible_link_204});
// This run loop will quit when all the prefetch responses have been
// successfully done and processed.
run_loop.Run();
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.RespCode", 204, 1);
// Navigate to a prefetched page to trigger UKM recording. Note that because
// the navigation is never committed, the UKM recording happens immediately.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), eligible_link_204));
base::RunLoop().RunUntilIdle();
VerifyUKMAfterSRP(
eligible_link_204,
ukm::builders::PrefetchProxy_AfterSRPClick::kClickedLinkSRPPositionName,
0);
VerifyUKMAfterSRP(
eligible_link_204,
ukm::builders::PrefetchProxy_AfterSRPClick::kSRPPrefetchEligibleCountName,
1);
// 0 is the value of |kPrefetchUsedNoProbe|. The enum is not used here
// intentionally because its value should never change.
VerifyUKMAfterSRP(
eligible_link_204,
ukm::builders::PrefetchProxy_AfterSRPClick::kSRPClickPrefetchStatusName,
0);
EXPECT_EQ(
absl::nullopt,
GetUKMMetric(
eligible_link_204,
ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::kProbeLatencyMsName));
}
IN_PROC_BROWSER_TEST_F(
PrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(PrefetchingUKM_PrefetchError)) {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
"isolated-prerender-unlimited-prefetches");
GURL starting_page = GetOriginServerURL("/simple.html");
SetDataSaverEnabled(true);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), starting_page));
WaitForUpdatedCustomProxyConfig();
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL prefetch_404_url = GetOriginServerURL("/404_on_prefetch");
TestTabHelperObserver tab_helper_observer(tab_helper);
tab_helper_observer.SetExpectedPrefetchErrors(
{{prefetch_404_url, net::HTTP_NOT_FOUND}});
base::RunLoop run_loop;
tab_helper_observer.SetOnPrefetchErrorClosure(run_loop.QuitClosure());
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {prefetch_404_url});
// This run loop will quit when all the prefetch responses have been
// done and processed.
run_loop.Run();
// Navigate to the predicted page to trigger UKM recording.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), prefetch_404_url));
base::RunLoop().RunUntilIdle();
VerifyUKMOnSRP(
starting_page,
ukm::builders::PrefetchProxy::kordered_eligible_pages_bitmaskName, 0b01);
VerifyUKMOnSRP(starting_page,
ukm::builders::PrefetchProxy::kprefetch_eligible_countName, 1);
VerifyUKMOnSRP(starting_page,
ukm::builders::PrefetchProxy::kprefetch_attempted_countName,
1);
VerifyUKMOnSRP(starting_page,
ukm::builders::PrefetchProxy::kprefetch_successful_countName,
0);
VerifyNoUKMEvent(ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName);
// Navigate to trigger UKM recording.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
base::RunLoop().RunUntilIdle();
VerifyUKMAfterSRP(
prefetch_404_url,
ukm::builders::PrefetchProxy_AfterSRPClick::kClickedLinkSRPPositionName,
0);
VerifyUKMAfterSRP(
prefetch_404_url,
ukm::builders::PrefetchProxy_AfterSRPClick::kSRPPrefetchEligibleCountName,
1);
// 12 is the value of |kPrefetchFailedNon2XX|. The enum is not
// used here intentionally because its value should never change.
VerifyUKMAfterSRP(
prefetch_404_url,
ukm::builders::PrefetchProxy_AfterSRPClick::kSRPClickPrefetchStatusName,
12);
EXPECT_EQ(
absl::nullopt,
GetUKMMetric(
prefetch_404_url,
ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::kProbeLatencyMsName));
}
IN_PROC_BROWSER_TEST_F(
PrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(PrefetchingUKM_LinkNotOnSRP)) {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
"isolated-prerender-unlimited-prefetches");
GURL starting_page = GetOriginServerURL("/simple.html");
SetDataSaverEnabled(true);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), starting_page));
WaitForUpdatedCustomProxyConfig();
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL eligible_link = GetOriginServerURL("/title1.html");
TestTabHelperObserver tab_helper_observer(tab_helper);
tab_helper_observer.SetExpectedSuccessfulURLs({eligible_link});
base::RunLoop run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(run_loop.QuitClosure());
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {eligible_link});
// This run loop will quit when all the prefetch responses have been
// successfully done and processed.
run_loop.Run();
GURL link_not_on_srp = GetOriginServerURL("/title2.html");
// Navigate to the page to trigger UKM recording.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), link_not_on_srp));
base::RunLoop().RunUntilIdle();
VerifyUKMOnSRP(
starting_page,
ukm::builders::PrefetchProxy::kordered_eligible_pages_bitmaskName, 0b01);
VerifyUKMOnSRP(starting_page,
ukm::builders::PrefetchProxy::kprefetch_eligible_countName, 1);
VerifyUKMOnSRP(starting_page,
ukm::builders::PrefetchProxy::kprefetch_attempted_countName,
1);
VerifyUKMOnSRP(starting_page,
ukm::builders::PrefetchProxy::kprefetch_successful_countName,
1);
VerifyNoUKMEvent(ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName);
// Navigate to trigger UKM recording.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
base::RunLoop().RunUntilIdle();
VerifyUKMAfterSRP(
link_not_on_srp,
ukm::builders::PrefetchProxy_AfterSRPClick::kClickedLinkSRPPositionName,
absl::nullopt);
VerifyUKMAfterSRP(
link_not_on_srp,
ukm::builders::PrefetchProxy_AfterSRPClick::kSRPPrefetchEligibleCountName,
1);
// 15 is the value of |kNavigatedToLinkNotOnSRP|. The enum is not used here
// intentionally because its value should never change.
VerifyUKMAfterSRP(
link_not_on_srp,
ukm::builders::PrefetchProxy_AfterSRPClick::kSRPClickPrefetchStatusName,
15);
EXPECT_EQ(
absl::nullopt,
GetUKMMetric(
link_not_on_srp,
ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::kProbeLatencyMsName));
}
IN_PROC_BROWSER_TEST_F(
PrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(PrefetchingUKM_LinkNotEligible)) {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
"isolated-prerender-unlimited-prefetches");
GURL starting_page = GetOriginServerURL("/simple.html");
SetDataSaverEnabled(true);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), starting_page));
WaitForUpdatedCustomProxyConfig();
GURL ineligible_link = GetInsecureURL("/title1.html");
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {ineligible_link});
// No run loop is needed here since the eligibility check won't run a cookie
// check or prefetch, so everything will be synchronous.
// Navigate to the page to trigger UKM recording.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), ineligible_link));
base::RunLoop().RunUntilIdle();
VerifyUKMOnSRP(
starting_page,
ukm::builders::PrefetchProxy::kordered_eligible_pages_bitmaskName, 0b00);
VerifyUKMOnSRP(starting_page,
ukm::builders::PrefetchProxy::kprefetch_eligible_countName, 0);
VerifyUKMOnSRP(starting_page,
ukm::builders::PrefetchProxy::kprefetch_attempted_countName,
0);
VerifyUKMOnSRP(starting_page,
ukm::builders::PrefetchProxy::kprefetch_successful_countName,
0);
VerifyNoUKMEvent(ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName);
// Navigate to trigger UKM recording.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
base::RunLoop().RunUntilIdle();
VerifyUKMAfterSRP(
ineligible_link,
ukm::builders::PrefetchProxy_AfterSRPClick::kClickedLinkSRPPositionName,
0);
VerifyUKMAfterSRP(
ineligible_link,
ukm::builders::PrefetchProxy_AfterSRPClick::kSRPPrefetchEligibleCountName,
0);
// 7 is the value of |kPrefetchNotEligibleSchemeIsNotHttps|. The enum is not
// used here intentionally because its value should never change.
VerifyUKMAfterSRP(
ineligible_link,
ukm::builders::PrefetchProxy_AfterSRPClick::kSRPClickPrefetchStatusName,
7);
EXPECT_EQ(
absl::nullopt,
GetUKMMetric(
ineligible_link,
ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::kProbeLatencyMsName));
}
IN_PROC_BROWSER_TEST_F(
PrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(PrefetchingUKM_PrefetchNotStarted)) {
GURL starting_page = GetOriginServerURL("/simple.html");
SetDataSaverEnabled(true);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), starting_page));
WaitForUpdatedCustomProxyConfig();
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
// By default, only 1 link will be prefetched.
GURL eligible_link_1 = GetOriginServerURL("/title1.html");
GURL eligible_link_2 = GetOriginServerURL("/title2.html");
TestTabHelperObserver tab_helper_observer(tab_helper);
tab_helper_observer.SetExpectedSuccessfulURLs({eligible_link_1});
base::RunLoop run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(run_loop.QuitClosure());
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {
eligible_link_1,
eligible_link_2,
GURL("http://not-eligible.com/1"),
GURL("http://not-eligible.com/2"),
GURL("http://not-eligible.com/3"),
});
// This run loop will quit when all the prefetch responses have been
// successfully done and processed.
run_loop.Run();
// Navigate to a prefetched page to trigger UKM recording.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), eligible_link_2));
base::RunLoop().RunUntilIdle();
VerifyUKMOnSRP(
starting_page,
ukm::builders::PrefetchProxy::kordered_eligible_pages_bitmaskName, 0b11);
VerifyUKMOnSRP(starting_page,
ukm::builders::PrefetchProxy::kprefetch_eligible_countName, 2);
VerifyUKMOnSRP(starting_page,
ukm::builders::PrefetchProxy::kprefetch_attempted_countName,
1);
VerifyUKMOnSRP(starting_page,
ukm::builders::PrefetchProxy::kprefetch_successful_countName,
1);
VerifyNoUKMEvent(ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName);
// Navigate to trigger UKM recording.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
base::RunLoop().RunUntilIdle();
VerifyUKMAfterSRP(
eligible_link_2,
ukm::builders::PrefetchProxy_AfterSRPClick::kClickedLinkSRPPositionName,
1);
VerifyUKMAfterSRP(
eligible_link_2,
ukm::builders::PrefetchProxy_AfterSRPClick::kSRPPrefetchEligibleCountName,
2);
// 3 is the value of |kPrefetchNotStarted|. The enum is not used here
// intentionally because its value should never change.
VerifyUKMAfterSRP(
eligible_link_2,
ukm::builders::PrefetchProxy_AfterSRPClick::kSRPClickPrefetchStatusName,
3);
EXPECT_EQ(
absl::nullopt,
GetUKMMetric(
eligible_link_2,
ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::kProbeLatencyMsName));
}
IN_PROC_BROWSER_TEST_F(PrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(CookiesUsedAndCopied)) {
// The test assumes the previous page gets deleted after navigation. Disable
// back/forward cache to ensure that it doesn't get preserved in the cache.
content::DisableBackForwardCacheForTesting(
GetWebContents(), content::BackForwardCache::TEST_ASSUMES_NO_CACHING);
GURL starting_page = GetOriginServerURL("/simple.html");
SetDataSaverEnabled(true);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), starting_page));
WaitForUpdatedCustomProxyConfig();
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL eligible_link =
GetOriginServerURL("/prefetch/prefetch_proxy/prefetch_page.html");
TestTabHelperObserver tab_helper_observer(tab_helper);
tab_helper_observer.SetExpectedSuccessfulURLs({eligible_link});
base::RunLoop prefetch_run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(
prefetch_run_loop.QuitClosure());
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {eligible_link});
// This run loop will quit when all the prefetch responses have been
// successfully done and processed.
prefetch_run_loop.Run();
std::vector<net::test_server::HttpRequest> origin_requests_after_prefetch =
origin_server_requests();
base::HistogramTester histogram_tester;
// Navigate to the predicted site.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), eligible_link));
std::vector<net::test_server::HttpRequest> origin_requests_after_click =
origin_server_requests();
// We expect that the image and possibly other resources (NSP not tested here)
// were loaded.
EXPECT_GT(origin_requests_after_click.size(),
origin_requests_after_prefetch.size());
bool inspected_image_request = false;
for (size_t i = origin_requests_after_prefetch.size();
i < origin_requests_after_click.size(); ++i) {
net::test_server::HttpRequest request = origin_requests_after_click[i];
if (request.GetURL().path() != "/prefetch/prefetch_proxy/image.png") {
// Other requests are nice and all, but we're just going to check the
// image since it won't have been prefetched.
continue;
}
inspected_image_request = true;
// The prefetched cookie should be present.
auto cookie_iter = request.headers.find("Cookie");
ASSERT_FALSE(cookie_iter == request.headers.end());
EXPECT_EQ(cookie_iter->second, "type=ChocolateChip");
}
EXPECT_TRUE(inspected_image_request);
histogram_tester.ExpectTotalCount(
"PrefetchProxy.AfterClick.Mainframe.CookieWaitTime", 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.CookiesToCopy", 1, 1);
// The cookie from prefetch should also be present in the CookieManager API.
EXPECT_EQ("type=ChocolateChip",
content::GetCookies(
browser()->profile(), eligible_link,
net::CookieOptions::SameSiteCookieContext::MakeInclusive()));
}
IN_PROC_BROWSER_TEST_F(PrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(ClientCertDenied)) {
// Make the browser use the ClientCertStoreStub instead of the regular one.
ProfileNetworkContextServiceFactory::GetForContext(browser()->profile())
->set_client_cert_store_factory_for_testing(
base::BindRepeating(&CreateCertStore));
SetDataSaverEnabled(true);
WaitForUpdatedCustomProxyConfig();
// Setup a test server that requires a client cert.
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
net::SSLServerConfig ssl_config;
ssl_config.client_cert_type =
net::SSLServerConfig::ClientCertType::REQUIRE_CLIENT_CERT;
https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES,
ssl_config);
https_server.ServeFilesFromSourceDirectory("chrome/test/data");
ASSERT_TRUE(https_server.Start());
GURL client_cert_needed_page = https_server.GetURL("b.test", "/simple.html");
// Configure the normal profile to automatically satisfy the client cert
// request.
std::unique_ptr<base::DictionaryValue> setting =
std::make_unique<base::DictionaryValue>();
base::Value* filters = setting->SetKey("filters", base::ListValue());
filters->Append(base::DictionaryValue());
HostContentSettingsMapFactory::GetForProfile(browser()->profile())
->SetWebsiteSettingDefaultScope(
client_cert_needed_page, GURL(),
ContentSettingsType::AUTO_SELECT_CERTIFICATE, std::move(setting));
// Navigating to the page should work just fine in the normal profile.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), client_cert_needed_page));
content::NavigationEntry* entry =
GetWebContents()->GetController().GetLastCommittedEntry();
EXPECT_EQ(entry->GetPageType(), content::PAGE_TYPE_NORMAL);
// Prefetching the page should fail.
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
TestTabHelperObserver tab_helper_observer(tab_helper);
tab_helper_observer.SetExpectedPrefetchErrors(
{{client_cert_needed_page, net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED}});
base::RunLoop run_loop;
tab_helper_observer.SetOnPrefetchErrorClosure(run_loop.QuitClosure());
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {client_cert_needed_page});
// This run loop will quit when the prefetch response have been
// successfully done and processed with the expected error.
run_loop.Run();
}
class PrefetchProxyWithDecoyRequestsBrowserTest
: public PrefetchProxyBrowserTest {
public:
PrefetchProxyWithDecoyRequestsBrowserTest() = default;
~PrefetchProxyWithDecoyRequestsBrowserTest() override = default;
// PrefetchProxyBrowserTest:
void SetUpCommandLine(base::CommandLine* cmd) override {
PrefetchProxyBrowserTest::SetUpCommandLine(cmd);
cmd->RemoveSwitch("prefetch-proxy-never-send-decoy-requests-for-testing");
cmd->AppendSwitch("prefetch-proxy-always-send-decoy-requests-for-testing");
}
};
IN_PROC_BROWSER_TEST_F(PrefetchProxyWithDecoyRequestsBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(ServiceWorker)) {
SetDataSaverEnabled(true);
GURL starting_page =
GetOriginServerURL("/service_worker/create_service_worker.html");
// Load a page that registers a service worker.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), starting_page));
EXPECT_EQ("DONE", EvalJs(GetWebContents(),
"register('network_fallback_worker.js');"));
content::ServiceWorkerContext* service_worker_context_ =
browser()
->profile()
->GetDefaultStoragePartition()
->GetServiceWorkerContext();
ASSERT_TRUE(service_worker_context_->MaybeHasRegistrationForOrigin(
url::Origin::Create(starting_page)));
ukm::SourceId srp_source_id =
GetWebContents()->GetMainFrame()->GetPageUkmSourceId();
base::RunLoop run_loop;
TestTabHelperObserver tab_helper_observer(
PrefetchProxyTabHelper::FromWebContents(GetWebContents()));
tab_helper_observer.SetDecoyPrefetchClosure(run_loop.QuitClosure());
size_t starting_origin_request_count = origin_server_requests().size();
GURL prefetch_url = GetOriginServerURL("/title2.html");
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {prefetch_url});
run_loop.Run();
size_t after_prefetch_origin_request_count = origin_server_requests().size();
EXPECT_EQ(starting_origin_request_count + 1,
after_prefetch_origin_request_count);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), prefetch_url));
// The prefetch should not have been used, so the webpage should have been
// requested again.
EXPECT_GT(origin_server_requests().size(),
after_prefetch_origin_request_count);
// Navigate again to trigger UKM recording.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
using UkmEntry = ukm::TestUkmRecorder::HumanReadableUkmEntry;
auto expected_entries = std::vector<UkmEntry>{
// prefetch_url
UkmEntry{
srp_source_id,
{
{"DataLength", 0}, /* only checked for > 0 */
{"FetchDurationMS", 0}, /* only checked for > 0 */
{"NavigationStartToFetchStartMS", 0}, /* only checked for > 0 */
{"LinkClicked", 1},
{"LinkPosition", 0},
{"ResourceType", 1},
{"Status", 29},
}},
};
auto actual_entries =
GetAndVerifyPrefetchedResourceUKM(starting_page, srp_source_id);
EXPECT_THAT(actual_entries,
testing::UnorderedElementsAreArray(
BuildPrefetchResourceMatchers(expected_entries)))
<< ActualHumanReadableMetricsToDebugString(actual_entries);
// 29 = |kPrefetchIsPrivacyDecoy|
EXPECT_EQ(absl::optional<int64_t>(29),
GetUKMMetric(prefetch_url,
ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::
kSRPClickPrefetchStatusName));
VerifyUKMAfterSRP(
prefetch_url,
ukm::builders::PrefetchProxy_AfterSRPClick::kClickedLinkSRPPositionName,
0);
VerifyUKMAfterSRP(
prefetch_url,
ukm::builders::PrefetchProxy_AfterSRPClick::kSRPPrefetchEligibleCountName,
0);
EXPECT_EQ(
absl::nullopt,
GetUKMMetric(
prefetch_url, ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::kProbeLatencyMsName));
}
IN_PROC_BROWSER_TEST_F(PrefetchProxyWithDecoyRequestsBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(Cookie)) {
GURL starting_page = GetOriginServerURL("/simple.html");
SetDataSaverEnabled(true);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), starting_page));
WaitForUpdatedCustomProxyConfig();
ASSERT_TRUE(content::SetCookie(browser()->profile(), GetOriginServerURL("/"),
"cookietype=ChocolateChip"));
ukm::SourceId srp_source_id =
GetWebContents()->GetMainFrame()->GetPageUkmSourceId();
base::RunLoop run_loop;
TestTabHelperObserver tab_helper_observer(
PrefetchProxyTabHelper::FromWebContents(GetWebContents()));
tab_helper_observer.SetDecoyPrefetchClosure(run_loop.QuitClosure());
size_t starting_origin_request_count = origin_server_requests().size();
GURL prefetch_url = GetOriginServerURL("/title2.html");
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {prefetch_url});
run_loop.Run();
size_t after_prefetch_origin_request_count = origin_server_requests().size();
EXPECT_EQ(starting_origin_request_count + 1,
after_prefetch_origin_request_count);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), prefetch_url));
// The prefetch should not have been used, so the webpage should have been
// requested again.
EXPECT_GT(origin_server_requests().size(),
after_prefetch_origin_request_count);
// Navigate again to trigger UKM recording.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
using UkmEntry = ukm::TestUkmRecorder::HumanReadableUkmEntry;
auto expected_entries = std::vector<UkmEntry>{
// prefetch_url
UkmEntry{
srp_source_id,
{
{"DataLength", 0}, /* only checked for > 0 */
{"FetchDurationMS", 0}, /* only checked for > 0 */
{"NavigationStartToFetchStartMS", 0}, /* only checked for > 0 */
{"LinkClicked", 1},
{"LinkPosition", 0},
{"ResourceType", 1},
{"Status", 29},
}},
};
auto actual_entries =
GetAndVerifyPrefetchedResourceUKM(starting_page, srp_source_id);
EXPECT_THAT(actual_entries,
testing::UnorderedElementsAreArray(
BuildPrefetchResourceMatchers(expected_entries)))
<< ActualHumanReadableMetricsToDebugString(actual_entries);
// 29 = |kPrefetchIsPrivacyDecoy|
EXPECT_EQ(absl::optional<int64_t>(29),
GetUKMMetric(prefetch_url,
ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::
kSRPClickPrefetchStatusName));
VerifyUKMAfterSRP(
prefetch_url,
ukm::builders::PrefetchProxy_AfterSRPClick::kClickedLinkSRPPositionName,
0);
VerifyUKMAfterSRP(
prefetch_url,
ukm::builders::PrefetchProxy_AfterSRPClick::kSRPPrefetchEligibleCountName,
0);
EXPECT_EQ(
absl::nullopt,
GetUKMMetric(
prefetch_url, ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::kProbeLatencyMsName));
}
class PolicyTestPrefetchProxyBrowserTest : public policy::PolicyTest {
public:
void SetUp() override {
scoped_feature_list_.InitAndEnableFeature(features::kIsolatePrerenders);
policy::PolicyTest::SetUp();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
PolicyTest::SetUpCommandLine(command_line);
command_line->AppendSwitch("enable-spdy-proxy-auth");
}
content::WebContents* GetWebContents() const {
return browser()->tab_strip_model()->GetActiveWebContents();
}
void MakeNavigationPrediction(const GURL& doc_url,
const std::vector<GURL>& predicted_urls) {
NavigationPredictorKeyedServiceFactory::GetForProfile(browser()->profile())
->OnPredictionUpdated(
GetWebContents(), doc_url,
NavigationPredictorKeyedService::PredictionSource::
kAnchorElementsParsedFromWebPage,
predicted_urls);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// Predictions should be ignored when the preload setting is disabled by policy.
IN_PROC_BROWSER_TEST_F(PolicyTestPrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(NoPrefetching)) {
policy::PolicyMap policies;
policies.Set(
policy::key::kNetworkPredictionOptions, policy::POLICY_LEVEL_MANDATORY,
policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_CLOUD,
base::Value(chrome_browser_net::NETWORK_PREDICTION_NEVER), nullptr);
UpdateProviderPolicy(policies);
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {GURL("https://test.com/")});
base::RunLoop().RunUntilIdle();
EXPECT_EQ(tab_helper->srp_metrics().predicted_urls_count_, 0U);
}
// A negative test where the only thing missing is the policy change from
// default, ensure that predictions are getting used.
IN_PROC_BROWSER_TEST_F(PolicyTestPrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(PrefetchingWithDefault)) {
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {GURL("https://test.com/")});
base::RunLoop().RunUntilIdle();
EXPECT_EQ(tab_helper->srp_metrics().predicted_urls_count_, 1U);
}
class SSLReportingPrefetchProxyBrowserTest : public PrefetchProxyBrowserTest {
public:
SSLReportingPrefetchProxyBrowserTest() {
// Certificate reports are only sent from official builds, unless this has
// been called.
CertReportHelper::SetFakeOfficialBuildForTesting();
}
void SetUpCommandLine(base::CommandLine* cmd) override {
PrefetchProxyBrowserTest::SetUpCommandLine(cmd);
cmd->RemoveSwitch("ignore-certificate-errors");
// CertReportHelper::ShouldReportCertificateError checks the value of this
// variation. Ensure reporting is enabled.
variations::testing::VariationParamsManager::AppendVariationParams(
"ReportCertificateErrors", "ShowAndPossiblySend",
{{"sendingThreshold", "1.0"}}, cmd);
}
security_interstitials::SecurityInterstitialPage* GetInterstitialPage(
content::WebContents* tab) {
security_interstitials::SecurityInterstitialTabHelper* helper =
security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
tab);
if (!helper)
return nullptr;
return helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting();
}
};
IN_PROC_BROWSER_TEST_F(
SSLReportingPrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(NoIntersitialSSLErrorReporting)) {
SetDataSaverEnabled(true);
WaitForUpdatedCustomProxyConfig();
// Setup a test server that requires a client cert.
net::EmbeddedTestServer https_expired_server(
net::EmbeddedTestServer::TYPE_HTTPS);
https_expired_server.SetSSLConfig(net::EmbeddedTestServer::CERT_EXPIRED);
https_expired_server.ServeFilesFromSourceDirectory("chrome/test/data");
ASSERT_TRUE(https_expired_server.Start());
GURL safe_page = GetOriginServerURL("/simple.html");
// Opt in to sending reports for invalid certificate chains.
certificate_reporting_test_utils::SetCertReportingOptIn(
browser(), certificate_reporting_test_utils::EXTENDED_REPORTING_OPT_IN);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), safe_page));
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL eligible_link = https_expired_server.GetURL("b.test", "/simple.html");
TestTabHelperObserver tab_helper_observer(tab_helper);
// |ERR_INSECURE_RESPONSE| is set by the URLLoader.
tab_helper_observer.SetExpectedPrefetchErrors(
{{eligible_link, net::ERR_INSECURE_RESPONSE}});
base::RunLoop prefetch_run_loop;
tab_helper_observer.SetOnPrefetchErrorClosure(
prefetch_run_loop.QuitClosure());
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {eligible_link});
// This run loop stops when the prefetches completes with its error.
prefetch_run_loop.Run();
// No interstitial should be shown and so no report will be made.
EXPECT_FALSE(GetInterstitialPage(GetWebContents()));
}
class DomainReliabilityPrefetchProxyBrowserTest
: public PrefetchProxyBrowserTest {
public:
DomainReliabilityPrefetchProxyBrowserTest() = default;
void SetUp() override {
ProfileNetworkContextService::SetDiscardDomainReliabilityUploadsForTesting(
false);
PrefetchProxyBrowserTest::SetUp();
}
void SetUpCommandLine(base::CommandLine* cmd) override {
PrefetchProxyBrowserTest::SetUpCommandLine(cmd);
cmd->AppendSwitch(switches::kEnableDomainReliability);
}
network::mojom::NetworkContext* GetNormalNetworkContext() {
return browser()
->profile()
->GetDefaultStoragePartition()
->GetNetworkContext();
}
void RequestMonitor(const net::test_server::HttpRequest& request) {
requests_.push_back(request);
if (request.GetURL().path() == "/domainreliabilty-upload" &&
on_got_reliability_report_) {
std::move(on_got_reliability_report_).Run();
}
}
protected:
base::OnceClosure on_got_reliability_report_;
std::vector<net::test_server::HttpRequest> requests_;
};
IN_PROC_BROWSER_TEST_F(
DomainReliabilityPrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(NoDomainReliabilityUploads)) {
SetDataSaverEnabled(true);
WaitForUpdatedCustomProxyConfig();
net::EmbeddedTestServer https_report_server(
net::EmbeddedTestServer::TYPE_HTTPS);
https_report_server.RegisterRequestMonitor(base::BindRepeating(
&DomainReliabilityPrefetchProxyBrowserTest::RequestMonitor,
base::Unretained(this)));
net::test_server::RegisterDefaultHandlers(&https_report_server);
ASSERT_TRUE(https_report_server.Start());
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
{
mojo::ScopedAllowSyncCallForTesting allow_sync_call;
GetNormalNetworkContext()->AddDomainReliabilityContextForTesting(
https_report_server.GetURL("a.test", "/").GetOrigin(),
https_report_server.GetURL("a.test", "/domainreliabilty-upload"));
}
// Do a prefetch which will fail.
// This url will cause the server to close the socket, resulting in a net
// error.
GURL error_url = https_report_server.GetURL("a.test", "/close-socket");
TestTabHelperObserver tab_helper_observer(tab_helper);
tab_helper_observer.SetExpectedPrefetchErrors(
{{error_url, net::ERR_EMPTY_RESPONSE}});
base::RunLoop prefetch_run_loop;
tab_helper_observer.SetOnPrefetchErrorClosure(
prefetch_run_loop.QuitClosure());
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {error_url});
// This run loop will quit when all the prefetch responses have errored.
prefetch_run_loop.Run();
base::RunLoop report_run_loop;
on_got_reliability_report_ = report_run_loop.QuitClosure();
// Now navigate to the same page and expect that there will be a single domain
// reliability report, i.e.: this navigation and not one from the prefetch.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), error_url));
{
mojo::ScopedAllowSyncCallForTesting allow_sync_call;
GetNormalNetworkContext()->ForceDomainReliabilityUploadsForTesting();
}
// This run loop will quit when the most recent navigation send its
// reliability report. By this time we expect that if the prefetch would have
// sent a report, it would have already done so.
report_run_loop.Run();
size_t found_reports = 0;
for (const net::test_server::HttpRequest& request : requests_) {
if (request.GetURL().path() == "/domainreliabilty-upload") {
found_reports++;
}
}
EXPECT_EQ(1U, found_reports);
}
class ProbingEnabled_CanaryOff_HTTPHead_PrefetchProxyBrowserTest
: public PrefetchProxyBrowserTest {
public:
void SetFeatures() override {
PrefetchProxyBrowserTest::SetFeatures();
scoped_feature_list_.InitAndEnableFeatureWithParameters(
features::kIsolatePrerendersMustProbeOrigin,
{
{"do_canary", "false"},
{"replace_tls_with_http", "true"},
{"ineligible_decoy_request_probability", "0"},
{"ineligible_decoy_request_probability", "0"},
});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(
ProbingEnabled_CanaryOff_HTTPHead_PrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(ProbeGood)) {
SetDataSaverEnabled(true);
GURL starting_page = GetOriginServerURL("/simple.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), starting_page));
WaitForUpdatedCustomProxyConfig();
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL eligible_link = GetOriginServerURL("/title2.html");
TestTabHelperObserver tab_helper_observer(tab_helper);
tab_helper_observer.SetExpectedSuccessfulURLs({eligible_link});
base::RunLoop run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(run_loop.QuitClosure());
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {eligible_link});
// This run loop will quit when all the prefetch responses have been
// successfully done and processed.
run_loop.Run();
// Navigate to the prefetched page, this also triggers UKM recording.
size_t starting_origin_request_count = OriginServerRequestCount();
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), eligible_link));
// Only the probe should have hit the origin server.
EXPECT_EQ(starting_origin_request_count + 1, OriginServerRequestCount());
EXPECT_EQ(u"Title Of Awesomeness", GetWebContents()->GetTitle());
ASSERT_TRUE(tab_helper->after_srp_metrics());
ASSERT_TRUE(tab_helper->after_srp_metrics()->prefetch_status_.has_value());
// 1 is the value of "prefetch used, probe success". The test does not
// reference the enum directly to ensure that casting the enum to an int went
// cleanly, and to provide an extra review point if the value should ever
// accidentally change in the future, which it never should.
EXPECT_EQ(1, static_cast<int>(
tab_helper->after_srp_metrics()->prefetch_status_.value()));
absl::optional<base::TimeDelta> probe_latency =
tab_helper->after_srp_metrics()->probe_latency_;
ASSERT_TRUE(probe_latency.has_value());
EXPECT_GT(probe_latency.value(), base::TimeDelta());
// Navigate again to trigger UKM recording.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
base::RunLoop().RunUntilIdle();
// 1 = |kPrefetchUsedProbeSuccess|.
EXPECT_EQ(absl::optional<int64_t>(1),
GetUKMMetric(eligible_link,
ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::
kSRPClickPrefetchStatusName));
// The actual probe latency is hard to deterministically test for. Just make
// sure it is set within reasonable bounds.
absl::optional<int64_t> probe_latency_ms = GetUKMMetric(
eligible_link, ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::kProbeLatencyMsName);
EXPECT_NE(absl::nullopt, probe_latency_ms);
EXPECT_GT(probe_latency_ms.value(), 0);
EXPECT_LT(probe_latency_ms.value(), 1000);
}
IN_PROC_BROWSER_TEST_F(
ProbingEnabled_CanaryOff_HTTPHead_PrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(ProbeBad)) {
SetDataSaverEnabled(true);
GURL starting_page = GetOriginServerURL("/simple.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), starting_page));
WaitForUpdatedCustomProxyConfig();
// Override the probing URL.
PrefetchProxyService* service =
PrefetchProxyServiceFactory::GetForProfile(browser()->profile());
CustomProbeOverrideDelegate delegate(GURL("http://invalid.com"));
service->origin_prober()->SetProbeURLOverrideDelegateOverrideForTesting(
&delegate);
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL eligible_link = GetOriginServerURL("/title2.html");
TestTabHelperObserver tab_helper_observer(tab_helper);
tab_helper_observer.SetExpectedSuccessfulURLs({eligible_link});
base::RunLoop run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(run_loop.QuitClosure());
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {eligible_link});
// This run loop will quit when all the prefetch responses have been
// successfully done and processed.
run_loop.Run();
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), eligible_link));
EXPECT_EQ(u"Title Of Awesomeness", GetWebContents()->GetTitle());
ASSERT_TRUE(tab_helper->after_srp_metrics());
ASSERT_TRUE(tab_helper->after_srp_metrics()->prefetch_status_.has_value());
// 2 is the value of "prefetch not used, probe failed". The test does not
// reference the enum directly to ensure that casting the enum to an int went
// cleanly, and to provide an extra review point if the value should ever
// accidentally change in the future, which it never should.
EXPECT_EQ(2, static_cast<int>(
tab_helper->after_srp_metrics()->prefetch_status_.value()));
absl::optional<base::TimeDelta> probe_latency =
tab_helper->after_srp_metrics()->probe_latency_;
ASSERT_TRUE(probe_latency.has_value());
EXPECT_GT(probe_latency.value(), base::TimeDelta());
// Navigate again to trigger UKM recording.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
base::RunLoop().RunUntilIdle();
// 1 = |kPrefetchNotUsedProbeFailed|.
EXPECT_EQ(absl::optional<int64_t>(2),
GetUKMMetric(eligible_link,
ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::
kSRPClickPrefetchStatusName));
// The actual probe latency is hard to deterministically test for. Just make
// sure it is set within reasonable bounds.
absl::optional<int64_t> probe_latency_ms = GetUKMMetric(
eligible_link, ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::kProbeLatencyMsName);
EXPECT_NE(absl::nullopt, probe_latency_ms);
}
class PrefetchProxyBaseProbingBrowserTest : public PrefetchProxyBrowserTest {
public:
const base::HistogramTester& histogram_tester() const {
return histogram_tester_;
}
void RunProbeTest(bool probe_success,
bool expect_successful_tls_probe,
int64_t expected_status,
bool expect_probe) {
WaitForTLSCanaryCheck();
WaitForDNSCanaryCheck();
// Setup a local probing server so we can watch its accepted socket count.
TestServerConnectionCounter probe_counter;
net::EmbeddedTestServer probing_server(net::EmbeddedTestServer::TYPE_HTTPS);
probing_server.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
probing_server.SetConnectionListener(&probe_counter);
ASSERT_TRUE(probing_server.Start());
SetDataSaverEnabled(true);
GURL starting_page = GetOriginServerURL("/simple.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), starting_page));
WaitForUpdatedCustomProxyConfig();
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL eligible_link = GetOriginServerURL("/title2.html");
TestTabHelperObserver tab_helper_observer(tab_helper);
tab_helper_observer.SetExpectedSuccessfulURLs({eligible_link});
base::RunLoop run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(run_loop.QuitClosure());
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {eligible_link});
// This run loop will quit when all the prefetch responses have been
// successfully done and processed.
run_loop.Run();
CustomProbeOverrideDelegate probe_delegate(
probe_success ? probing_server.GetURL("a.test", "/")
: GURL("http://invalid.com"));
PrefetchProxyService* service =
PrefetchProxyServiceFactory::GetForProfile(browser()->profile());
service->origin_prober()->SetProbeURLOverrideDelegateOverrideForTesting(
&probe_delegate);
// Navigate to the prefetched page, this also triggers UKM recording.
ASSERT_EQ(0U, probe_counter.count());
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), eligible_link));
EXPECT_EQ(expect_successful_tls_probe, 1U == probe_counter.count());
EXPECT_EQ(u"Title Of Awesomeness", GetWebContents()->GetTitle());
ASSERT_TRUE(tab_helper->after_srp_metrics());
ASSERT_TRUE(tab_helper->after_srp_metrics()->prefetch_status_.has_value());
EXPECT_EQ(expected_status,
static_cast<int>(
tab_helper->after_srp_metrics()->prefetch_status_.value()));
absl::optional<base::TimeDelta> probe_latency =
tab_helper->after_srp_metrics()->probe_latency_;
if (expect_probe) {
ASSERT_TRUE(probe_latency.has_value());
EXPECT_GT(probe_latency.value(), base::TimeDelta());
} else {
EXPECT_FALSE(probe_latency.has_value());
}
// Navigate again to trigger UKM recording.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(
absl::optional<int64_t>(expected_status),
GetUKMMetric(eligible_link,
ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::
kSRPClickPrefetchStatusName));
absl::optional<int64_t> probe_latency_ms = GetUKMMetric(
eligible_link, ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::kProbeLatencyMsName);
if (expect_probe) {
EXPECT_NE(absl::nullopt, probe_latency_ms);
} else {
EXPECT_EQ(absl::nullopt, probe_latency_ms);
}
}
private:
base::HistogramTester histogram_tester_;
};
class ProbingEnabled_CanaryOn_BothCanaryGood_PrefetchProxyBrowserTest
: public PrefetchProxyBaseProbingBrowserTest {
public:
void SetFeatures() override {
PrefetchProxyBaseProbingBrowserTest::SetFeatures();
scoped_feature_list_.InitAndEnableFeatureWithParameters(
features::kIsolatePrerendersMustProbeOrigin,
{
{"do_canary", "true"},
{"tls_canary_url", GetCanaryServerURL().spec()},
{"dns_canary_url", GetCanaryServerURL().spec()},
{"ineligible_decoy_request_probability", "0"},
});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
class ProbingEnabled_CanaryOn_TLSCanaryBad_DNSCanaryBad_PrefetchProxyBrowserTest
: public PrefetchProxyBaseProbingBrowserTest {
public:
void SetFeatures() override {
PrefetchProxyBaseProbingBrowserTest::SetFeatures();
scoped_feature_list_.InitAndEnableFeatureWithParameters(
features::kIsolatePrerendersMustProbeOrigin,
{
{"do_canary", "true"},
{"tls_canary_url", "http://invalid.com"},
{"dns_canary_url", "http://invalid.com"},
{"ineligible_decoy_request_probability", "0"},
});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
class
ProbingEnabled_CanaryOn_TLSCanaryBad_DNSCanaryGood_PrefetchProxyBrowserTest
: public PrefetchProxyBaseProbingBrowserTest {
public:
void SetFeatures() override {
PrefetchProxyBaseProbingBrowserTest::SetFeatures();
scoped_feature_list_.InitAndEnableFeatureWithParameters(
features::kIsolatePrerendersMustProbeOrigin,
{
{"do_canary", "true"},
{"tls_canary_url", "http://invalid.com"},
{"dns_canary_url", GetCanaryServerURL().spec()},
{"ineligible_decoy_request_probability", "0"},
});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
class
ProbingEnabled_CanaryOn_TLSCanaryGood_DNSCanaryBad_PrefetchProxyBrowserTest
: public PrefetchProxyBaseProbingBrowserTest {
public:
void SetFeatures() override {
PrefetchProxyBaseProbingBrowserTest::SetFeatures();
scoped_feature_list_.InitAndEnableFeatureWithParameters(
features::kIsolatePrerendersMustProbeOrigin,
{
{"do_canary", "true"},
{"tls_canary_url", GetCanaryServerURL().spec()},
{"dns_canary_url", "http://invalid.com"},
{"ineligible_decoy_request_probability", "0"},
});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
class ProbingEnabled_CanaryOn_CanaryBad_PrefetchProxyBrowserTest
: public PrefetchProxyBaseProbingBrowserTest {
public:
void SetFeatures() override {
PrefetchProxyBaseProbingBrowserTest::SetFeatures();
scoped_feature_list_.InitAndEnableFeatureWithParameters(
features::kIsolatePrerendersMustProbeOrigin,
{
{"do_canary", "true"},
{"canary_url", "http://invalid.com"},
{"ineligible_decoy_request_probability", "0"},
});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
class ProbingDisabledPrefetchProxyBrowserTest
: public PrefetchProxyBaseProbingBrowserTest {
public:
void SetFeatures() override {
PrefetchProxyBaseProbingBrowserTest::SetFeatures();
scoped_feature_list_.InitAndDisableFeature(
features::kIsolatePrerendersMustProbeOrigin);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(
ProbingEnabled_CanaryOn_BothCanaryGood_PrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(NoProbe)) {
RunProbeTest(/*probe_success=*/false,
/*expect_successful_tls_probe=*/false,
/*expected_status=*/0,
/*expect_probe=*/false);
}
IN_PROC_BROWSER_TEST_F(
ProbingEnabled_CanaryOn_TLSCanaryGood_DNSCanaryBad_PrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(DNSProbeOK)) {
RunProbeTest(/*probe_success=*/true,
/*expect_successful_tls_probe=*/false,
/*expected_status=*/1,
/*expect_probe=*/true);
histogram_tester().ExpectTotalCount(
"Availability.Prober.FinalState.IsolatedPrerenderDNSCanaryCheck", 1);
histogram_tester().ExpectTotalCount(
"Availability.Prober.FinalState.IsolatedPrerenderTLSCanaryCheck", 1);
}
IN_PROC_BROWSER_TEST_F(
ProbingEnabled_CanaryOn_TLSCanaryGood_DNSCanaryBad_PrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(DNSProbeBad)) {
RunProbeTest(/*probe_success=*/false,
/*expect_successful_tls_probe=*/false,
/*expected_status=*/2,
/*expect_probe=*/true);
}
IN_PROC_BROWSER_TEST_F(
ProbingEnabled_CanaryOn_TLSCanaryBad_DNSCanaryBad_PrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(TLSProbeOK)) {
RunProbeTest(/*probe_success=*/true,
/*expect_successful_tls_probe=*/true,
/*expected_status=*/1,
/*expect_probe=*/true);
}
IN_PROC_BROWSER_TEST_F(
ProbingEnabled_CanaryOn_TLSCanaryBad_DNSCanaryBad_PrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(TLSProbeBad)) {
RunProbeTest(/*probe_success=*/false,
/*expect_successful_tls_probe=*/false,
/*expected_status=*/2,
/*expect_probe=*/true);
}
IN_PROC_BROWSER_TEST_F(
ProbingEnabled_CanaryOn_TLSCanaryBad_DNSCanaryGood_PrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(TLSProbeOK)) {
RunProbeTest(/*probe_success=*/true,
/*expect_successful_tls_probe=*/true,
/*expected_status=*/1,
/*expect_probe=*/true);
}
IN_PROC_BROWSER_TEST_F(
ProbingEnabled_CanaryOn_TLSCanaryBad_DNSCanaryGood_PrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(TLSProbeBad)) {
RunProbeTest(/*probe_success=*/false,
/*expect_successful_tls_probe=*/false,
/*expected_status=*/2,
/*expect_probe=*/true);
}
class PrefetchProxyWithNSPBrowserTest : public PrefetchProxyBrowserTest {
public:
void SetUpCommandLine(base::CommandLine* cmd) override {
PrefetchProxyBrowserTest::SetUpCommandLine(cmd);
cmd->AppendSwitch("isolated-prerender-nsp-enabled");
}
void SetFeatures() override {
PrefetchProxyBrowserTest::SetFeatures();
scoped_feature_list_.InitAndEnableFeature(
blink::features::kLightweightNoStatePrefetch);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(PrefetchProxyWithNSPBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(SuccessfulNSPEndToEnd)) {
// The test assumes the previous page gets deleted after navigation. Disable
// back/forward cache to ensure that it doesn't get preserved in the cache.
content::DisableBackForwardCacheForTesting(
GetWebContents(), content::BackForwardCache::TEST_ASSUMES_NO_CACHING);
base::HistogramTester histogram_tester;
SetDataSaverEnabled(true);
GURL starting_page = GetOriginServerURL("/simple.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), starting_page));
WaitForUpdatedCustomProxyConfig();
ui_test_utils::WaitForHistoryToLoad(HistoryServiceFactory::GetForProfile(
browser()->profile(), ServiceAccessType::EXPLICIT_ACCESS));
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL eligible_link =
GetOriginServerURL("/prefetch/prefetch_proxy/prefetch_page.html");
TestTabHelperObserver tab_helper_observer(tab_helper);
tab_helper_observer.SetExpectedSuccessfulURLs({eligible_link});
base::RunLoop prefetch_run_loop;
base::RunLoop nsp_run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(
prefetch_run_loop.QuitClosure());
tab_helper_observer.SetOnNSPFinishedClosure(nsp_run_loop.QuitClosure());
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {eligible_link});
// This run loop will quit when all the prefetch responses have been
// successfully done and processed.
prefetch_run_loop.Run();
std::vector<net::test_server::HttpRequest> origin_requests_before_prerender =
origin_server_requests();
std::vector<net::test_server::HttpRequest> proxy_requests_before_prerender =
proxy_server_requests();
// This run loop will quit when a NSP finishes.
nsp_run_loop.Run();
// Regression test for crbug/1131712.
WaitForHistoryBackendToRun(browser()->profile());
ui_test_utils::HistoryEnumerator enumerator(browser()->profile());
EXPECT_FALSE(base::Contains(enumerator.urls(), eligible_link));
std::vector<net::test_server::HttpRequest> origin_requests_after_prerender =
origin_server_requests();
std::vector<net::test_server::HttpRequest> proxy_requests_after_prerender =
proxy_server_requests();
EXPECT_GT(proxy_requests_after_prerender.size(),
proxy_requests_before_prerender.size());
for (const net::test_server::HttpRequest& request :
origin_requests_after_prerender) {
EXPECT_FALSE(RequestHasClientHints(request));
}
// Check that the page's Javascript was NSP'd, but not the mainframe.
bool found_nsp_javascript = false;
bool found_nsp_mainframe = false;
bool found_image = false;
for (size_t i = origin_requests_before_prerender.size();
i < origin_requests_after_prerender.size(); ++i) {
net::test_server::HttpRequest request = origin_requests_after_prerender[i];
// prefetch_page.html sets a cookie on its response and we should see it
// here.
auto cookie_iter = request.headers.find("Cookie");
ASSERT_FALSE(cookie_iter == request.headers.end());
EXPECT_EQ(cookie_iter->second, "type=ChocolateChip");
GURL nsp_url = request.GetURL();
found_nsp_javascript |=
nsp_url.path() == "/prefetch/prefetch_proxy/prefetch.js";
found_nsp_mainframe |= nsp_url.path() == eligible_link.path();
found_image |= nsp_url.path() == "/prefetch/prefetch_proxy/image.png";
}
EXPECT_TRUE(found_nsp_javascript);
EXPECT_FALSE(found_nsp_mainframe);
EXPECT_FALSE(found_image);
VerifyOriginRequestsAreIsolated({
"/prefetch/prefetch_proxy/prefetch.js",
eligible_link.path(),
});
// Verify the resource load was reported to the subresource manager.
PrefetchProxyService* service =
PrefetchProxyServiceFactory::GetForProfile(browser()->profile());
PrefetchProxySubresourceManager* manager =
service->GetSubresourceManagerForURL(eligible_link);
ASSERT_TRUE(manager);
base::RunLoop().RunUntilIdle();
std::set<GURL> expected_subresources = {
GetOriginServerURL("/prefetch/prefetch_proxy/prefetch.js"),
GetOriginServerURL("/prefetch/prefetch_proxy/prefetch-redirect-start.js"),
GetOriginServerURL(
"/prefetch/prefetch_proxy/prefetch-redirect-middle.js"),
GetOriginServerURL("/prefetch/prefetch_proxy/prefetch-redirect-end.js"),
};
EXPECT_EQ(expected_subresources, manager->successfully_loaded_subresources());
EXPECT_TRUE(CheckForResourceInIsolatedCache(
GetOriginServerURL("/prefetch/prefetch_proxy/prefetch.js")));
EXPECT_TRUE(CheckForResourceInIsolatedCache(
GetOriginServerURL("/prefetch/prefetch_proxy/prefetch-redirect-end.js")));
// Navigate to the predicted site. We expect:
// * The mainframe HTML will not be requested from the origin server.
// * The JavaScript will not be requested from the origin server.
// * The prefetched JavaScript will be executed.
// * The image will be fetched.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), eligible_link));
std::vector<net::test_server::HttpRequest> proxy_requests_after_click =
proxy_server_requests();
// Nothing should have gone through the proxy.
EXPECT_EQ(proxy_requests_after_prerender.size(),
proxy_requests_after_click.size());
std::vector<net::test_server::HttpRequest> origin_requests_after_click =
origin_server_requests();
// Only one request for the image is expected, and it should have cookies.
ASSERT_EQ(origin_requests_after_prerender.size() + 1,
origin_requests_after_click.size());
net::test_server::HttpRequest request =
origin_requests_after_click[origin_requests_after_click.size() - 1];
EXPECT_EQ(request.GetURL().path(), "/prefetch/prefetch_proxy/image.png");
auto cookie_iter = request.headers.find("Cookie");
ASSERT_FALSE(cookie_iter == request.headers.end());
EXPECT_EQ(cookie_iter->second, "type=ChocolateChip");
// The cookie from prefetch should also be present in the CookieManager API.
EXPECT_EQ("type=ChocolateChip",
content::GetCookies(
browser()->profile(), eligible_link,
net::CookieOptions::SameSiteCookieContext::MakeInclusive()));
histogram_tester.ExpectTotalCount(
"PrefetchProxy.AfterClick.Mainframe.CookieWaitTime", 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.CookiesToCopy", 1, 1);
// Check that the JavaScript ran.
EXPECT_EQ(u"JavaScript Executed", GetWebContents()->GetTitle());
// Navigate one more time to destroy the SubresourceManager so that its UMA is
// recorded and to trigger UKM recording.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
// 16 = |kPrefetchUsedNoProbeWithNSP|.
EXPECT_EQ(absl::optional<int64_t>(16),
GetUKMMetric(eligible_link,
ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::
kSRPClickPrefetchStatusName));
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Subresources.NetError", net::OK, 2);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Subresources.Quantity", 4, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Subresources.RespCode", 200, 2);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.AfterClick.Subresources.UsedCache", true, 2);
}
IN_PROC_BROWSER_TEST_F(PrefetchProxyWithNSPBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(StartsSpareRenderer)) {
// Enable low-end device mode to turn off automatic spare renderers. Note that
// this will also prevent NSPs from triggering, but the logic under test
// happens before that anyways.
base::CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kEnableLowEndDeviceMode);
base::CommandLine::ForCurrentProcess()->AppendSwitch(
"isolated-prerender-start-spare-renderer");
SetDataSaverEnabled(true);
GURL starting_page = GetOriginServerURL("/simple.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), starting_page));
WaitForUpdatedCustomProxyConfig();
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL eligible_link =
GetOriginServerURL("/prefetch/prefetch_proxy/prefetch_page.html");
TestTabHelperObserver tab_helper_observer(tab_helper);
tab_helper_observer.SetExpectedSuccessfulURLs({eligible_link});
base::RunLoop prefetch_run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(
prefetch_run_loop.QuitClosure());
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {eligible_link});
base::HistogramTester histogram_tester;
// This run loop will quit when all the prefetch responses have been
// successfully done and processed.
prefetch_run_loop.Run();
// Navigate to trigger the histogram recording.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.SpareRenderer.CountStartedOnSRP", 1, 1);
}
namespace {
std::unique_ptr<net::test_server::HttpResponse> HandleNonEligibleOrigin(
const net::test_server::HttpRequest& request) {
if (request.GetURL().path() == "/script.js") {
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("application/javascript");
resp->set_content("console.log(0);");
return resp;
}
return nullptr;
}
std::unique_ptr<net::test_server::HttpResponse>
HandleOriginWithIneligibleSubresources(
net::EmbeddedTestServer* non_eligible_server,
const net::test_server::HttpRequest& request) {
GURL url = request.GetURL();
if (url.path() == "/page.html") {
GURL same_origin_resource =
non_eligible_server->GetURL("a.test", "/script.js");
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");
resp->set_content(base::StringPrintf(R"(
<html>
<head>
<script src="%s">
</head>
<body>Test</body>
</html>)",
same_origin_resource.spec().c_str()));
return resp;
}
return nullptr;
}
std::unique_ptr<net::test_server::HttpResponse> HandleEligibleOrigin(
net::EmbeddedTestServer* eligible_server,
net::EmbeddedTestServer* non_eligible_server,
const net::test_server::HttpRequest& request) {
GURL url = request.GetURL();
if (url.path() == "/page.html") {
GURL same_origin_resource = eligible_server->GetURL("a.test", "/script.js");
GURL redirect_resource = eligible_server->GetURL("a.test", "/redirect.js");
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");
resp->set_content(base::StringPrintf(R"(
<html>
<head>
<script src="%s">
<script src="%s">
</head>
<body>Test</body>
</html>)",
same_origin_resource.spec().c_str(),
redirect_resource.spec().c_str()));
return resp;
}
if (url.path() == "/script.js") {
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("application/javascript");
resp->set_content("console.log(0);");
return resp;
}
if (url.path() == "/redirect.js") {
std::unique_ptr<net::test_server::BasicHttpResponse> resp =
std::make_unique<net::test_server::BasicHttpResponse>();
resp->set_code(net::HTTP_TEMPORARY_REDIRECT);
resp->AddCustomHeader(
"location", non_eligible_server->GetURL("b.test", "/script.js").spec());
return resp;
}
return nullptr;
}
} // namespace
IN_PROC_BROWSER_TEST_F(
PrefetchProxyWithNSPBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(NSPWithIneligibleSubresourceRedirect)) {
net::EmbeddedTestServer non_eligible_origin(
net::EmbeddedTestServer::TYPE_HTTPS);
non_eligible_origin.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
non_eligible_origin.RegisterRequestHandler(
base::BindRepeating(&HandleNonEligibleOrigin));
ASSERT_TRUE(non_eligible_origin.Start());
net::EmbeddedTestServer eligible_origin(net::EmbeddedTestServer::TYPE_HTTPS);
eligible_origin.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
eligible_origin.RegisterRequestHandler(base::BindRepeating(
&HandleEligibleOrigin, &eligible_origin, &non_eligible_origin));
ASSERT_TRUE(eligible_origin.Start());
content::SetCookie(browser()->profile(),
non_eligible_origin.GetURL("b.test", "/"), "cookie=yes");
SetDataSaverEnabled(true);
WaitForUpdatedCustomProxyConfig();
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL eligible_link = eligible_origin.GetURL("a.test", "/page.html");
TestTabHelperObserver tab_helper_observer(tab_helper);
tab_helper_observer.SetExpectedSuccessfulURLs({eligible_link});
base::RunLoop prefetch_run_loop;
base::RunLoop nsp_run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(
prefetch_run_loop.QuitClosure());
tab_helper_observer.SetOnNSPFinishedClosure(nsp_run_loop.QuitClosure());
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {eligible_link});
// This run loop will quit when all the prefetch responses have been
// successfully done and processed.
prefetch_run_loop.Run();
// This run loop will quit when a NSP finishes.
nsp_run_loop.Run();
// Verify the resource load was reported to the subresource manager.
PrefetchProxyService* service =
PrefetchProxyServiceFactory::GetForProfile(browser()->profile());
PrefetchProxySubresourceManager* manager =
service->GetSubresourceManagerForURL(eligible_link);
ASSERT_TRUE(manager);
base::RunLoop().RunUntilIdle();
std::set<GURL> expected_subresources = {
eligible_origin.GetURL("a.test", "/script.js"),
};
EXPECT_EQ(expected_subresources, manager->successfully_loaded_subresources());
}
IN_PROC_BROWSER_TEST_F(
PrefetchProxyWithNSPBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(NSPWithIneligibleSubresources)) {
TestServerConnectionCounter http_counter;
net::EmbeddedTestServer non_eligible_origin(
net::EmbeddedTestServer::TYPE_HTTP);
non_eligible_origin.SetConnectionListener(&http_counter);
ASSERT_TRUE(non_eligible_origin.Start());
net::EmbeddedTestServer eligible_origin(net::EmbeddedTestServer::TYPE_HTTPS);
eligible_origin.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
eligible_origin.RegisterRequestHandler(base::BindRepeating(
&HandleOriginWithIneligibleSubresources, &non_eligible_origin));
ASSERT_TRUE(eligible_origin.Start());
SetDataSaverEnabled(true);
WaitForUpdatedCustomProxyConfig();
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL eligible_link = eligible_origin.GetURL("a.test", "/page.html");
TestTabHelperObserver tab_helper_observer(tab_helper);
tab_helper_observer.SetExpectedSuccessfulURLs({eligible_link});
base::RunLoop prefetch_run_loop;
base::RunLoop nsp_run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(
prefetch_run_loop.QuitClosure());
tab_helper_observer.SetOnNSPFinishedClosure(nsp_run_loop.QuitClosure());
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {eligible_link});
// This run loop will quit when all the prefetch responses have been
// successfully done and processed.
prefetch_run_loop.Run();
// This run loop will quit when a NSP finishes.
nsp_run_loop.Run();
EXPECT_EQ(0U, http_counter.count());
// Verify the resource load was reported to the subresource manager.
PrefetchProxyService* service =
PrefetchProxyServiceFactory::GetForProfile(browser()->profile());
PrefetchProxySubresourceManager* manager =
service->GetSubresourceManagerForURL(eligible_link);
ASSERT_TRUE(manager);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(manager->successfully_loaded_subresources().empty());
}
IN_PROC_BROWSER_TEST_F(PrefetchProxyWithNSPBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(PrefetchButNSPDenied)) {
// NSP is disabled on low-end devices.
base::CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kEnableLowEndDeviceMode);
SetDataSaverEnabled(true);
GURL starting_page = GetOriginServerURL("/simple.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), starting_page));
WaitForUpdatedCustomProxyConfig();
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL eligible_link =
GetOriginServerURL("/prefetch/prefetch_proxy/prefetch_page.html");
TestTabHelperObserver tab_helper_observer(tab_helper);
tab_helper_observer.SetExpectedSuccessfulURLs({eligible_link});
base::RunLoop prefetch_run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(
prefetch_run_loop.QuitClosure());
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {eligible_link});
// This run loop will quit when all the prefetch responses have been
// successfully done and processed.
prefetch_run_loop.Run();
// Navigate to the predicted site.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), eligible_link));
// Navigate again to trigger UKM recording.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
// 19 = |kPrefetchUsedNoProbeNSPAttemptDenied|.
EXPECT_EQ(absl::optional<int64_t>(19),
GetUKMMetric(eligible_link,
ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::
kSRPClickPrefetchStatusName));
}
IN_PROC_BROWSER_TEST_F(PrefetchProxyWithNSPBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(OnlyOneNSP)) {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
"isolated-prerender-unlimited-prefetches");
SetDataSaverEnabled(true);
GURL starting_page = GetOriginServerURL("/simple.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), starting_page));
WaitForUpdatedCustomProxyConfig();
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL eligible_link_1 =
GetOriginServerURL("/prefetch/prefetch_proxy/prefetch_page.html");
GURL eligible_link_2 =
GetOriginServerURL("/prefetch/prefetch_proxy/prefetch_page.html?page=2");
TestTabHelperObserver tab_helper_observer(tab_helper);
// Do the prefetches separately so that we know only the first link will ever
// get prerendered.
tab_helper_observer.SetExpectedSuccessfulURLs({eligible_link_1});
base::RunLoop nsp_run_loop;
base::RunLoop prefetch_1_run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(
prefetch_1_run_loop.QuitClosure());
tab_helper_observer.SetOnNSPFinishedClosure(nsp_run_loop.QuitClosure());
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {eligible_link_1});
// This run loop will quit when the first prefetch response has been
// successfully done and processed.
prefetch_1_run_loop.Run();
nsp_run_loop.Run();
tab_helper_observer.SetExpectedSuccessfulURLs({eligible_link_2});
base::RunLoop prefetch_2_run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(
prefetch_2_run_loop.QuitClosure());
MakeNavigationPrediction(doc_url, {eligible_link_2});
// This run loop will quit when the second prefetch response has been
// successfully done and processed.
prefetch_2_run_loop.Run();
// Navigate to the second predicted site.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), eligible_link_2));
// Navigate again to trigger UKM recording.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
// 22 = |kPrefetchUsedNoProbeNSPNotStarted|.
EXPECT_EQ(absl::optional<int64_t>(22),
GetUKMMetric(eligible_link_2,
ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::
kSRPClickPrefetchStatusName));
}
IN_PROC_BROWSER_TEST_F(PrefetchProxyWithNSPBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(NoAppCache)) {
SetDataSaverEnabled(true);
GURL starting_page = GetOriginServerURL("/simple.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), starting_page));
WaitForUpdatedCustomProxyConfig();
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL eligible_link =
GetOriginServerURL("/prefetch/prefetch_proxy/app_cache.html");
TestTabHelperObserver tab_helper_observer(tab_helper);
tab_helper_observer.SetExpectedSuccessfulURLs({eligible_link});
base::RunLoop prefetch_run_loop;
base::RunLoop nsp_run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(
prefetch_run_loop.QuitClosure());
tab_helper_observer.SetOnNSPFinishedClosure(nsp_run_loop.QuitClosure());
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {eligible_link});
// This run loop will quit when all the prefetch responses have been
// successfully done and processed.
prefetch_run_loop.Run();
std::vector<net::test_server::HttpRequest> origin_requests_before_prerender =
origin_server_requests();
// This run loop will quit when a NSP finishes.
nsp_run_loop.Run();
std::vector<net::test_server::HttpRequest> origin_requests_after_prerender =
origin_server_requests();
// There should not have been any additional requests.
EXPECT_EQ(origin_requests_before_prerender.size(),
origin_requests_after_prerender.size());
}
IN_PROC_BROWSER_TEST_F(PrefetchProxyWithNSPBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(NoLinkRelSearch)) {
SetDataSaverEnabled(true);
GURL starting_page = GetOriginServerURL("/simple.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), starting_page));
WaitForUpdatedCustomProxyConfig();
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL eligible_link =
GetOriginServerURL("/prefetch/prefetch_proxy/link-rel-search-tag.html");
TestTabHelperObserver tab_helper_observer(tab_helper);
tab_helper_observer.SetExpectedSuccessfulURLs({eligible_link});
base::RunLoop prefetch_run_loop;
base::RunLoop nsp_run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(
prefetch_run_loop.QuitClosure());
tab_helper_observer.SetOnNSPFinishedClosure(nsp_run_loop.QuitClosure());
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {eligible_link});
// This run loop will quit when all the prefetch responses have been
// successfully done and processed.
prefetch_run_loop.Run();
std::vector<net::test_server::HttpRequest> origin_requests_before_prerender =
origin_server_requests();
// This run loop will quit when a NSP finishes.
nsp_run_loop.Run();
std::vector<net::test_server::HttpRequest> origin_requests_after_prerender =
origin_server_requests();
// There should not have been any additional requests.
EXPECT_EQ(origin_requests_before_prerender.size(),
origin_requests_after_prerender.size());
}
IN_PROC_BROWSER_TEST_F(PrefetchProxyWithNSPBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(LimitSubresourceCount)) {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
"isolated-prerender-unlimited-prefetches");
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
kIsolatedPrerenderLimitNSPSubresourcesCmdLineFlag, "1");
SetDataSaverEnabled(true);
GURL starting_page = GetOriginServerURL("/simple.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), starting_page));
WaitForUpdatedCustomProxyConfig();
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL eligible_link =
GetOriginServerURL("/prefetch/prefetch_proxy/prefetch_page.html");
TestTabHelperObserver tab_helper_observer(tab_helper);
tab_helper_observer.SetExpectedSuccessfulURLs({eligible_link});
base::RunLoop prefetch_run_loop;
base::RunLoop nsp_run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(
prefetch_run_loop.QuitClosure());
tab_helper_observer.SetOnNSPFinishedClosure(nsp_run_loop.QuitClosure());
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {eligible_link});
// This run loop will quit when a NSP finishes.
nsp_run_loop.Run();
base::HistogramTester histogram_tester;
// Navigate to the predicted site.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), eligible_link));
// Checks that only one resource was used from cache.
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.AfterClick.Subresources.UsedCache", true, 1);
// Navigate again to trigger UKM recording.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
// 16 = |kPrefetchUsedNoProbeWithNSP|.
EXPECT_EQ(absl::optional<int64_t>(16),
GetUKMMetric(eligible_link,
ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::
kSRPClickPrefetchStatusName));
}
class ProbingAndNSPEnabledPrefetchProxyBrowserTest
: public PrefetchProxyBrowserTest {
public:
void SetUpCommandLine(base::CommandLine* cmd) override {
PrefetchProxyBrowserTest::SetUpCommandLine(cmd);
cmd->AppendSwitch("isolated-prerender-nsp-enabled");
}
void SetFeatures() override {
PrefetchProxyBrowserTest::SetFeatures();
scoped_feature_list_.InitAndEnableFeature(
blink::features::kLightweightNoStatePrefetch);
probing_scoped_feature_list_.InitAndEnableFeatureWithParameters(
features::kIsolatePrerendersMustProbeOrigin,
{
{"do_canary", "false"},
{"ineligible_decoy_request_probability", "0"},
});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
base::test::ScopedFeatureList probing_scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(ProbingAndNSPEnabledPrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(ProbeGood_NSPSuccess)) {
SetDataSaverEnabled(true);
GURL starting_page = GetOriginServerURL("/simple.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), starting_page));
WaitForUpdatedCustomProxyConfig();
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL eligible_link =
GetOriginServerURL("/prefetch/prefetch_proxy/prefetch_page.html");
TestTabHelperObserver tab_helper_observer(tab_helper);
tab_helper_observer.SetExpectedSuccessfulURLs({eligible_link});
base::RunLoop prefetch_run_loop;
base::RunLoop nsp_run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(
prefetch_run_loop.QuitClosure());
tab_helper_observer.SetOnNSPFinishedClosure(nsp_run_loop.QuitClosure());
ukm::SourceId srp_source_id =
GetWebContents()->GetMainFrame()->GetPageUkmSourceId();
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {eligible_link});
// This run loop will quit when a NSP finishes.
nsp_run_loop.Run();
// This event should not be recorded until after the prefetched page is done.
VerifyNoUKMEvent(ukm::builders::PrefetchProxy_PrefetchedResource::kEntryName);
// Navigate to the predicted site.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), eligible_link));
// This event should not be recorded until after the prefetched page is done.
VerifyNoUKMEvent(ukm::builders::PrefetchProxy_PrefetchedResource::kEntryName);
// Navigate again to trigger UKM recording.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
// 17 = |kPrefetchUsedProbeSuccessWithNSP|.
EXPECT_EQ(absl::optional<int64_t>(17),
GetUKMMetric(eligible_link,
ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::
kSRPClickPrefetchStatusName));
using UkmEntry = ukm::TestUkmRecorder::HumanReadableUkmEntry;
auto expected_entries = std::vector<UkmEntry>{
// eligible_link
UkmEntry{
srp_source_id,
{
{"DataLength", 0}, /* only checked for > 0 */
{"FetchDurationMS", 0}, /* only checked for > 0 */
{"NavigationStartToFetchStartMS", 0}, /* only checked for > 0 */
{"ISPFilteringStatus", 1}, /* matches either 1 or 3 */
{"LinkClicked", 1},
{"LinkPosition", 0},
{"ResourceType", 1},
{"Status", 1},
}},
// and two subresources
UkmEntry{
srp_source_id,
{
{"DataLength", 0}, /* only checked for > 0 */
{"FetchDurationMS", 0}, /* only checked for > 0 */
{"NavigationStartToFetchStartMS", 0}, /* only checked for > 0 */
{"ISPFilteringStatus", 1}, /* matches either 1 or 3 */
{"LinkClicked", 1},
{"LinkPosition", 0},
{"ResourceType", 2},
{"Status", 1},
}},
UkmEntry{
srp_source_id,
{
{"DataLength", 0}, /* only checked for > 0 */
{"FetchDurationMS", 0}, /* only checked for > 0 */
{"NavigationStartToFetchStartMS", 0}, /* only checked for > 0 */
{"ISPFilteringStatus", 1}, /* matches either 1 or 3 */
{"LinkClicked", 1},
{"LinkPosition", 0},
{"ResourceType", 2},
{"Status", 1},
}},
};
auto actual_entries =
GetAndVerifyPrefetchedResourceUKM(starting_page, srp_source_id);
EXPECT_THAT(actual_entries,
testing::UnorderedElementsAreArray(
BuildPrefetchResourceMatchers(expected_entries)))
<< ActualHumanReadableMetricsToDebugString(actual_entries);
}
IN_PROC_BROWSER_TEST_F(ProbingAndNSPEnabledPrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(ProbeGood_NSPDenied)) {
// NSP is disabled on low-end devices.
base::CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kEnableLowEndDeviceMode);
SetDataSaverEnabled(true);
GURL starting_page = GetOriginServerURL("/simple.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), starting_page));
WaitForUpdatedCustomProxyConfig();
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL eligible_link =
GetOriginServerURL("/prefetch/prefetch_proxy/prefetch_page.html");
TestTabHelperObserver tab_helper_observer(tab_helper);
tab_helper_observer.SetExpectedSuccessfulURLs({eligible_link});
base::RunLoop prefetch_run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(
prefetch_run_loop.QuitClosure());
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {eligible_link});
// This run loop will quit when all the prefetch responses have been
// successfully done and processed.
prefetch_run_loop.Run();
// Navigate to the predicted site.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), eligible_link));
// Navigate again to trigger UKM recording.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
// 20 = |kPrefetchUsedProbeSuccessNSPAttemptDenied|.
EXPECT_EQ(absl::optional<int64_t>(20),
GetUKMMetric(eligible_link,
ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::
kSRPClickPrefetchStatusName));
}
IN_PROC_BROWSER_TEST_F(ProbingAndNSPEnabledPrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(ProbeGood_NSPNotStarted)) {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
"isolated-prerender-unlimited-prefetches");
SetDataSaverEnabled(true);
GURL starting_page = GetOriginServerURL("/simple.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), starting_page));
WaitForUpdatedCustomProxyConfig();
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL eligible_link_1 =
GetOriginServerURL("/prefetch/prefetch_proxy/prefetch_page.html");
GURL eligible_link_2 =
GetOriginServerURL("/prefetch/prefetch_proxy/prefetch_page.html?page=2");
TestTabHelperObserver tab_helper_observer(tab_helper);
// Do the prefetches separately so that we know only the first link will ever
// get prerendered.
tab_helper_observer.SetExpectedSuccessfulURLs({eligible_link_1});
base::RunLoop nsp_run_loop;
base::RunLoop prefetch_1_run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(
prefetch_1_run_loop.QuitClosure());
tab_helper_observer.SetOnNSPFinishedClosure(nsp_run_loop.QuitClosure());
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {eligible_link_1});
// This run loop will quit when the first prefetch response has been
// successfully done and processed.
prefetch_1_run_loop.Run();
nsp_run_loop.Run();
tab_helper_observer.SetExpectedSuccessfulURLs({eligible_link_2});
base::RunLoop prefetch_2_run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(
prefetch_2_run_loop.QuitClosure());
MakeNavigationPrediction(doc_url, {eligible_link_2});
// This run loop will quit when the second prefetch response has been
// successfully done and processed.
prefetch_2_run_loop.Run();
// Navigate to the second predicted site.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), eligible_link_2));
// Navigate again to trigger UKM recording.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
// 23 = |kPrefetchUsedProbeSuccessNSPNotStarted|.
EXPECT_EQ(absl::optional<int64_t>(23),
GetUKMMetric(eligible_link_2,
ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::
kSRPClickPrefetchStatusName));
}
IN_PROC_BROWSER_TEST_F(ProbingAndNSPEnabledPrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(ProbeBad_NSPSuccess)) {
// The test assumes the previous page gets deleted after navigation. Disable
// back/forward cache to ensure that it doesn't get preserved in the cache.
content::DisableBackForwardCacheForTesting(
GetWebContents(), content::BackForwardCache::TEST_ASSUMES_NO_CACHING);
SetDataSaverEnabled(true);
GURL starting_page = GetOriginServerURL("/simple.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), starting_page));
WaitForUpdatedCustomProxyConfig();
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL eligible_link =
GetOriginServerURL("/prefetch/prefetch_proxy/prefetch_page.html");
TestTabHelperObserver tab_helper_observer(tab_helper);
tab_helper_observer.SetExpectedSuccessfulURLs({eligible_link});
base::RunLoop prefetch_run_loop;
base::RunLoop nsp_run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(
prefetch_run_loop.QuitClosure());
tab_helper_observer.SetOnNSPFinishedClosure(nsp_run_loop.QuitClosure());
ukm::SourceId srp_source_id =
GetWebContents()->GetMainFrame()->GetPageUkmSourceId();
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {eligible_link});
// This run loop will quit when a NSP finishes.
nsp_run_loop.Run();
std::vector<net::test_server::HttpRequest> origin_requests_after_prerender =
origin_server_requests();
std::vector<net::test_server::HttpRequest> proxy_requests_after_prerender =
proxy_server_requests();
// Override the probing URL.
PrefetchProxyService* service =
PrefetchProxyServiceFactory::GetForProfile(browser()->profile());
CustomProbeOverrideDelegate delegate(GURL("http://invalid.com"));
service->origin_prober()->SetProbeURLOverrideDelegateOverrideForTesting(
&delegate);
// This event should not be recorded until after the prefetched page is done.
VerifyNoUKMEvent(ukm::builders::PrefetchProxy_PrefetchedResource::kEntryName);
// Navigate to the predicted site.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), eligible_link));
std::vector<net::test_server::HttpRequest> origin_requests_after_click =
origin_server_requests();
std::vector<net::test_server::HttpRequest> proxy_requests_after_click =
proxy_server_requests();
// All the resources should be loaded from the server since nothing was
// eligible to be reused from the prefetch on a bad probe.
EXPECT_EQ(origin_requests_after_prerender.size() + 6,
origin_requests_after_click.size());
// The proxy should not be used any further.
EXPECT_EQ(proxy_requests_after_prerender.size(),
proxy_requests_after_click.size());
// This event should not be recorded until after the prefetched page is done.
VerifyNoUKMEvent(ukm::builders::PrefetchProxy_PrefetchedResource::kEntryName);
// Navigate again to trigger UKM recording.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
// 18 = |kPrefetchNotUsedProbeFailedWithNSP|.
EXPECT_EQ(absl::optional<int64_t>(18),
GetUKMMetric(eligible_link,
ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::
kSRPClickPrefetchStatusName));
using UkmEntry = ukm::TestUkmRecorder::HumanReadableUkmEntry;
auto expected_entries = std::vector<UkmEntry>{
// eligible_link
UkmEntry{
srp_source_id,
{
{"DataLength", 0}, /* only checked for > 0 */
{"FetchDurationMS", 0}, /* only checked for > 0 */
{"NavigationStartToFetchStartMS", 0}, /* only checked for > 0 */
{"ISPFilteringStatus", 4}, /* matches either 2 or 4 */
{"LinkClicked", 1},
{"LinkPosition", 0},
{"ResourceType", 1},
{"Status", 2},
}},
// and two subresources
UkmEntry{
srp_source_id,
{
{"DataLength", 0}, /* only checked for > 0 */
{"FetchDurationMS", 0}, /* only checked for > 0 */
{"NavigationStartToFetchStartMS", 0}, /* only checked for > 0 */
{"ISPFilteringStatus", 4}, /* matches either 2 or 4 */
{"LinkClicked", 1},
{"LinkPosition", 0},
{"ResourceType", 2},
{"Status", 14},
}},
UkmEntry{
srp_source_id,
{
{"DataLength", 0}, /* only checked for > 0 */
{"FetchDurationMS", 0}, /* only checked for > 0 */
{"NavigationStartToFetchStartMS", 0}, /* only checked for > 0 */
{"ISPFilteringStatus", 4}, /* matches either 2 or 4 */
{"LinkClicked", 1},
{"LinkPosition", 0},
{"ResourceType", 2},
{"Status", 14},
}},
};
auto actual_entries =
GetAndVerifyPrefetchedResourceUKM(starting_page, srp_source_id);
EXPECT_THAT(actual_entries,
testing::UnorderedElementsAreArray(
BuildPrefetchResourceMatchers(expected_entries)))
<< ActualHumanReadableMetricsToDebugString(actual_entries);
}
IN_PROC_BROWSER_TEST_F(ProbingAndNSPEnabledPrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(ProbeBad_NSPDenied)) {
// NSP is disabled on low-end devices.
base::CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kEnableLowEndDeviceMode);
SetDataSaverEnabled(true);
GURL starting_page = GetOriginServerURL("/simple.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), starting_page));
WaitForUpdatedCustomProxyConfig();
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL eligible_link =
GetOriginServerURL("/prefetch/prefetch_proxy/prefetch_page.html");
TestTabHelperObserver tab_helper_observer(tab_helper);
tab_helper_observer.SetExpectedSuccessfulURLs({eligible_link});
base::RunLoop prefetch_run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(
prefetch_run_loop.QuitClosure());
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {eligible_link});
// This run loop will quit when all the prefetch responses have been
// successfully done and processed.
prefetch_run_loop.Run();
// Override the probing URL.
PrefetchProxyService* service =
PrefetchProxyServiceFactory::GetForProfile(browser()->profile());
CustomProbeOverrideDelegate delegate(GURL("http://invalid.com"));
service->origin_prober()->SetProbeURLOverrideDelegateOverrideForTesting(
&delegate);
// Navigate to the predicted site.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), eligible_link));
// Navigate again to trigger UKM recording.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
// 21 = |kPrefetchNotUsedProbeFailedNSPAttemptDenied|.
EXPECT_EQ(absl::optional<int64_t>(21),
GetUKMMetric(eligible_link,
ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::
kSRPClickPrefetchStatusName));
}
IN_PROC_BROWSER_TEST_F(ProbingAndNSPEnabledPrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(ProbeBad_NSPNotStarted)) {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
"isolated-prerender-unlimited-prefetches");
SetDataSaverEnabled(true);
GURL starting_page = GetOriginServerURL("/simple.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), starting_page));
WaitForUpdatedCustomProxyConfig();
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL eligible_link_1 =
GetOriginServerURL("/prefetch/prefetch_proxy/prefetch_page.html");
GURL eligible_link_2 =
GetOriginServerURL("/prefetch/prefetch_proxy/prefetch_page.html?page=2");
TestTabHelperObserver tab_helper_observer(tab_helper);
// Do the prefetches separately so that we know only the first link will ever
// get prerendered.
tab_helper_observer.SetExpectedSuccessfulURLs({eligible_link_1});
base::RunLoop nsp_run_loop;
base::RunLoop prefetch_1_run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(
prefetch_1_run_loop.QuitClosure());
tab_helper_observer.SetOnNSPFinishedClosure(nsp_run_loop.QuitClosure());
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {eligible_link_1});
// This run loop will quit when the first prefetch response has been
// successfully done and processed.
prefetch_1_run_loop.Run();
nsp_run_loop.Run();
tab_helper_observer.SetExpectedSuccessfulURLs({eligible_link_2});
base::RunLoop prefetch_2_run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(
prefetch_2_run_loop.QuitClosure());
MakeNavigationPrediction(doc_url, {eligible_link_2});
// This run loop will quit when the second prefetch response has been
// successfully done and processed.
prefetch_2_run_loop.Run();
// Override the probing URL.
PrefetchProxyService* service =
PrefetchProxyServiceFactory::GetForProfile(browser()->profile());
CustomProbeOverrideDelegate delegate(GURL("http://invalid.com"));
service->origin_prober()->SetProbeURLOverrideDelegateOverrideForTesting(
&delegate);
// Navigate to the second predicted site.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), eligible_link_2));
// Navigate again to trigger UKM recording.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
// 24 = |kPrefetchNotUsedProbeFailedNSPNotStarted|.
EXPECT_EQ(absl::optional<int64_t>(24),
GetUKMMetric(eligible_link_2,
ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::
kSRPClickPrefetchStatusName));
}
class SpeculationPrefetchProxyTest : public PrefetchProxyBrowserTest {
public:
void SetUpCommandLine(base::CommandLine* cmd) override {
PrefetchProxyBrowserTest::SetUpCommandLine(cmd);
cmd->AppendSwitch("isolated-prerender-nsp-enabled");
}
void SetFeatures() override {
scoped_feature_list_.InitWithFeaturesAndParameters(
{{features::kIsolatePrerenders,
{{"use_speculation_rules", "true"}, {"max_srp_prefetches", "3"}}},
{blink::features::kLightweightNoStatePrefetch, {}},
{blink::features::kSpeculationRulesPrefetchProxy, {}}},
{{features::kLazyImageLoading}});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(SpeculationPrefetchProxyTest,
DISABLE_ON_WIN_MAC_CHROMEOS(SuccessfulNSPEndToEnd)) {
// The test assumes the previous page gets deleted after navigation. Disable
// back/forward cache to ensure that it doesn't get preserved in the cache.
content::DisableBackForwardCacheForTesting(
GetWebContents(), content::BackForwardCache::TEST_ASSUMES_NO_CACHING);
base::HistogramTester histogram_tester;
SetDataSaverEnabled(true);
WaitForUpdatedCustomProxyConfig();
ui_test_utils::WaitForHistoryToLoad(HistoryServiceFactory::GetForProfile(
browser()->profile(), ServiceAccessType::EXPLICIT_ACCESS));
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL eligible_link =
GetOriginServerURL("/prefetch/prefetch_proxy/prefetch_page.html");
TestTabHelperObserver tab_helper_observer(tab_helper);
tab_helper_observer.SetExpectedSuccessfulURLs({eligible_link});
base::RunLoop prefetch_run_loop;
base::RunLoop nsp_run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(
prefetch_run_loop.QuitClosure());
tab_helper_observer.SetOnNSPFinishedClosure(nsp_run_loop.QuitClosure());
// Make sure we are on a valid referring page.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), GetReferringPageServerURL("/search/q=blah")));
InsertSpeculation(true, {eligible_link});
// This run loop will quit when all the prefetch responses have been
// successfully done and processed.
prefetch_run_loop.Run();
std::vector<net::test_server::HttpRequest> origin_requests_before_prerender =
origin_server_requests();
std::vector<net::test_server::HttpRequest> proxy_requests_before_prerender =
proxy_server_requests();
// This run loop will quit when a NSP finishes.
nsp_run_loop.Run();
// Regression test for crbug/1131712.
WaitForHistoryBackendToRun(browser()->profile());
ui_test_utils::HistoryEnumerator enumerator(browser()->profile());
EXPECT_FALSE(base::Contains(enumerator.urls(), eligible_link));
std::vector<net::test_server::HttpRequest> origin_requests_after_prerender =
origin_server_requests();
std::vector<net::test_server::HttpRequest> proxy_requests_after_prerender =
proxy_server_requests();
EXPECT_GT(proxy_requests_after_prerender.size(),
proxy_requests_before_prerender.size());
for (const net::test_server::HttpRequest& request :
origin_requests_after_prerender) {
EXPECT_FALSE(RequestHasClientHints(request));
}
// Check that the page's Javascript was NSP'd, but not the mainframe.
bool found_nsp_javascript = false;
bool found_nsp_mainframe = false;
bool found_image = false;
for (size_t i = origin_requests_before_prerender.size();
i < origin_requests_after_prerender.size(); ++i) {
net::test_server::HttpRequest request = origin_requests_after_prerender[i];
// prefetch_page.html sets a cookie on its response and we should see it
// here.
auto cookie_iter = request.headers.find("Cookie");
ASSERT_FALSE(cookie_iter == request.headers.end());
EXPECT_EQ(cookie_iter->second, "type=ChocolateChip");
GURL nsp_url = request.GetURL();
found_nsp_javascript |=
nsp_url.path() == "/prefetch/prefetch_proxy/prefetch.js";
found_nsp_mainframe |= nsp_url.path() == eligible_link.path();
found_image |= nsp_url.path() == "/prefetch/prefetch_proxy/image.png";
}
EXPECT_TRUE(found_nsp_javascript);
EXPECT_FALSE(found_nsp_mainframe);
EXPECT_FALSE(found_image);
VerifyOriginRequestsAreIsolated({
"/prefetch/prefetch_proxy/prefetch.js",
eligible_link.path(),
});
// Verify the resource load was reported to the subresource manager.
PrefetchProxyService* service =
PrefetchProxyServiceFactory::GetForProfile(browser()->profile());
PrefetchProxySubresourceManager* manager =
service->GetSubresourceManagerForURL(eligible_link);
ASSERT_TRUE(manager);
base::RunLoop().RunUntilIdle();
std::set<GURL> expected_subresources = {
GetOriginServerURL("/prefetch/prefetch_proxy/prefetch.js"),
GetOriginServerURL("/prefetch/prefetch_proxy/prefetch-redirect-start.js"),
GetOriginServerURL(
"/prefetch/prefetch_proxy/prefetch-redirect-middle.js"),
GetOriginServerURL("/prefetch/prefetch_proxy/prefetch-redirect-end.js"),
};
EXPECT_EQ(expected_subresources, manager->successfully_loaded_subresources());
EXPECT_TRUE(CheckForResourceInIsolatedCache(
GetOriginServerURL("/prefetch/prefetch_proxy/prefetch.js")));
EXPECT_TRUE(CheckForResourceInIsolatedCache(
GetOriginServerURL("/prefetch/prefetch_proxy/prefetch-redirect-end.js")));
// Navigate to the predicted site. We expect:
// * The mainframe HTML will not be requested from the origin server.
// * The JavaScript will not be requested from the origin server.
// * The prefetched JavaScript will be executed.
// * The image will be fetched.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), eligible_link));
std::vector<net::test_server::HttpRequest> proxy_requests_after_click =
proxy_server_requests();
// Nothing should have gone through the proxy.
EXPECT_EQ(proxy_requests_after_prerender.size(),
proxy_requests_after_click.size());
std::vector<net::test_server::HttpRequest> origin_requests_after_click =
origin_server_requests();
// Only one request for the image is expected, and it should have cookies.
EXPECT_EQ(origin_requests_after_prerender.size() + 1,
origin_requests_after_click.size());
net::test_server::HttpRequest request =
origin_requests_after_click[origin_requests_after_click.size() - 1];
EXPECT_EQ(request.GetURL().path(), "/prefetch/prefetch_proxy/image.png");
auto cookie_iter = request.headers.find("Cookie");
ASSERT_FALSE(cookie_iter == request.headers.end());
EXPECT_EQ(cookie_iter->second, "type=ChocolateChip");
// The cookie from prefetch should also be present in the CookieManager API.
EXPECT_EQ("type=ChocolateChip",
content::GetCookies(
browser()->profile(), eligible_link,
net::CookieOptions::SameSiteCookieContext::MakeInclusive()));
histogram_tester.ExpectTotalCount(
"PrefetchProxy.AfterClick.Mainframe.CookieWaitTime", 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.CookiesToCopy", 1, 1);
// Check that the JavaScript ran.
EXPECT_EQ(u"JavaScript Executed", GetWebContents()->GetTitle());
// Navigate one more time to destroy the SubresourceManager so that its UMA is
// recorded and to trigger UKM recording.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
// 16 = |kPrefetchUsedNoProbeWithNSP|.
EXPECT_EQ(absl::optional<int64_t>(16),
GetUKMMetric(eligible_link,
ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::
kSRPClickPrefetchStatusName));
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Subresources.NetError", net::OK, 2);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Subresources.Quantity", 4, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Subresources.RespCode", 200, 2);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.AfterClick.Subresources.UsedCache", true, 2);
}
IN_PROC_BROWSER_TEST_F(SpeculationPrefetchProxyTest,
DISABLE_ON_WIN_MAC_CHROMEOS(ConnectProxyEndtoEnd)) {
SetDataSaverEnabled(true);
WaitForUpdatedCustomProxyConfig();
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
TestTabHelperObserver tab_helper_observer(tab_helper);
GURL prefetch_url = GetOriginServerURL("/title2.html");
base::RunLoop run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(run_loop.QuitClosure());
tab_helper_observer.SetExpectedSuccessfulURLs({prefetch_url});
// Make sure we are on a valid referring page.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), GetReferringPageServerURL("/search/q=blah")));
InsertSpeculation(false, {prefetch_url});
// This run loop will quit when the prefetch response has been successfully
// done and processed.
run_loop.Run();
EXPECT_EQ(tab_helper->srp_metrics().prefetch_attempted_count_, 1U);
EXPECT_EQ(tab_helper->srp_metrics().prefetch_successful_count_, 1U);
size_t starting_origin_request_count = OriginServerRequestCount();
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), prefetch_url));
EXPECT_EQ(u"Title Of Awesomeness", GetWebContents()->GetTitle());
VerifyOriginRequestsAreIsolated({prefetch_url.path()});
// The origin server should not have served this request.
EXPECT_EQ(starting_origin_request_count, OriginServerRequestCount());
}
IN_PROC_BROWSER_TEST_F(SpeculationPrefetchProxyTest,
DISABLE_ON_WIN_MAC_CHROMEOS(TwoSpeculations)) {
SetDataSaverEnabled(true);
WaitForUpdatedCustomProxyConfig();
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
TestTabHelperObserver tab_helper_observer(tab_helper);
GURL prefetch_url = GetOriginServerURL("/title2.html");
base::RunLoop run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(run_loop.QuitClosure());
tab_helper_observer.SetExpectedSuccessfulURLs({prefetch_url});
// Make sure we are on a valid referring page.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), GetReferringPageServerURL("/search/q=blah")));
InsertSpeculation(false, {prefetch_url});
// This run loop will quit when the prefetch response has been successfully
// done and processed.
run_loop.Run();
EXPECT_EQ(tab_helper->srp_metrics().prefetch_attempted_count_, 1U);
EXPECT_EQ(tab_helper->srp_metrics().prefetch_successful_count_, 1U);
base::RunLoop run_loop_2;
GURL prefetch_url_2 = GetOriginServerURL("/title1.html");
tab_helper_observer.SetOnPrefetchSuccessfulClosure(run_loop_2.QuitClosure());
tab_helper_observer.SetExpectedSuccessfulURLs({prefetch_url_2});
InsertSpeculation(false, {prefetch_url_2});
// This run loop will quit when the prefetch response has been successfully
// done and processed.
run_loop_2.Run();
// Verify that we de-dupe and only fetch one new URL.
EXPECT_EQ(tab_helper->srp_metrics().prefetch_attempted_count_, 2U);
EXPECT_EQ(tab_helper->srp_metrics().prefetch_successful_count_, 2U);
}
class PrefetchProxyPrerenderBrowserTest : public PrefetchProxyBrowserTest {
public:
PrefetchProxyPrerenderBrowserTest()
: prerender_test_helper_(base::BindRepeating(
&PrefetchProxyPrerenderBrowserTest::GetWebContents,
base::Unretained(this))) {}
~PrefetchProxyPrerenderBrowserTest() override = default;
PrefetchProxyPrerenderBrowserTest(const PrefetchProxyPrerenderBrowserTest&) =
delete;
PrefetchProxyPrerenderBrowserTest& operator=(
const PrefetchProxyPrerenderBrowserTest&) = delete;
void TearDown() override { PrefetchProxyBrowserTest::ResetFeatureList(); }
void SetUp() override {
prerender_test_helper_.SetUp(embedded_test_server());
PrefetchProxyBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(embedded_test_server()->Start());
PrefetchProxyBrowserTest::SetUpOnMainThread();
}
content::test::PrerenderTestHelper& prerender_test_helper() {
return prerender_test_helper_;
}
content::WebContents* GetWebContents() {
return browser()->tab_strip_model()->GetActiveWebContents();
}
private:
content::test::PrerenderTestHelper prerender_test_helper_;
};
IN_PROC_BROWSER_TEST_F(PrefetchProxyPrerenderBrowserTest,
ShouldNotAffectPrefetchProxyTabHelperOnPrerendering) {
SetDataSaverEnabled(true);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
WaitForUpdatedCustomProxyConfig();
ASSERT_TRUE(content::SetCookie(browser()->profile(), GURL("https://foo.com"),
"type=PeanutButter"));
GURL initial_url = embedded_test_server()->GetURL("/empty.html");
GURL prerender_url = embedded_test_server()->GetURL("/title1.html");
ASSERT_NE(ui_test_utils::NavigateToURL(browser(), initial_url), nullptr);
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
// Start prerendering to check if it affects on the prefetch proxy.
int host_id = prerender_test_helper().AddPrerender(prerender_url);
content::RenderFrameHost* prerender_render_frame_host =
prerender_test_helper().GetPrerenderedMainFrameHost(host_id);
EXPECT_NE(prerender_render_frame_host, nullptr);
GURL prerender_render_frame_url =
prerender_render_frame_host->GetLastCommittedURL();
ASSERT_FALSE(tab_helper->after_srp_metrics());
EXPECT_EQ(0U, tab_helper->srp_metrics().predicted_urls_count_);
GURL prefetch_url("https://m.foo.com");
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {prefetch_url});
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1U, tab_helper->srp_metrics().predicted_urls_count_);
EXPECT_EQ(0U, tab_helper->srp_metrics().prefetch_eligible_count_);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), prefetch_url));
ASSERT_TRUE(tab_helper->after_srp_metrics());
EXPECT_EQ(
absl::make_optional(
PrefetchProxyPrefetchStatus::kPrefetchNotEligibleUserHasCookies),
tab_helper->after_srp_metrics()->prefetch_status_);
// Check if the prefetched URL is different from the prerendered frame.
EXPECT_NE(tab_helper->after_srp_metrics()->url_, prerender_render_frame_url);
}
class ZeroCacheTimePrefetchProxyBrowserTest : public PrefetchProxyBrowserTest {
public:
void SetUpCommandLine(base::CommandLine* cmd) override {
PrefetchProxyBrowserTest::SetUpCommandLine(cmd);
}
void SetFeatures() override {
scoped_feature_list_.InitAndEnableFeatureWithParameters(
features::kIsolatePrerenders, {
{"cacheable_duration", "0"},
});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(
ZeroCacheTimePrefetchProxyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(PrefetchAfterCacheExpiration)) {
SetDataSaverEnabled(true);
GURL starting_page = GetOriginServerURL("/simple.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), starting_page));
WaitForUpdatedCustomProxyConfig();
PrefetchProxyTabHelper* tab_helper =
PrefetchProxyTabHelper::FromWebContents(GetWebContents());
GURL eligible_link =
GetOriginServerURL("/prefetch/prefetch_proxy/prefetch_page.html");
TestTabHelperObserver tab_helper_observer(tab_helper);
tab_helper_observer.SetExpectedSuccessfulURLs({eligible_link});
base::RunLoop prefetch_run_loop;
tab_helper_observer.SetOnPrefetchSuccessfulClosure(
prefetch_run_loop.QuitClosure());
base::HistogramTester histogram_tester;
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {eligible_link});
// This run loop will quit when the prefetch response has been
// successfully done and processed.
prefetch_run_loop.Run();
// Navigate to the predicted site.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), eligible_link));
// Navigate again to trigger UKM recording.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
// 30 = |kPrefetchIsStale|.
EXPECT_EQ(absl::optional<int64_t>(30),
GetUKMMetric(eligible_link,
ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::
kSRPClickPrefetchStatusName));
histogram_tester.ExpectTotalCount(
"PrefetchProxy.Prefetch.Mainframe.CookiesToCopy", 0);
}