#include "content/browser/preloading/prefetch/prefetch_service.h"
#include <optional>
#include <string_view>
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "base/timer/elapsed_timer.h"
#include "components/variations/scoped_variations_ids_provider.h"
#include "content/browser/browser_context_impl.h"
#include "content/browser/preloading/prefetch/mock_prefetch_service_delegate.h"
#include "content/browser/preloading/prefetch/prefetch_container.h"
#include "content/browser/preloading/prefetch/prefetch_document_manager.h"
#include "content/browser/preloading/prefetch/prefetch_features.h"
#include "content/browser/preloading/prefetch/prefetch_match_resolver.h"
#include "content/browser/preloading/prefetch/prefetch_params.h"
#include "content/browser/preloading/prefetch/prefetch_request.h"
#include "content/browser/preloading/prefetch/prefetch_scheduler.h"
#include "content/browser/preloading/prefetch/prefetch_servable_state.h"
#include "content/browser/preloading/prefetch/prefetch_serving_handle.h"
#include "content/browser/preloading/prefetch/prefetch_serving_page_metrics_container.h"
#include "content/browser/preloading/prefetch/prefetch_streaming_url_loader.h"
#include "content/browser/preloading/prefetch/prefetch_test_util_internal.h"
#include "content/browser/preloading/prefetch/prefetch_type.h"
#include "content/browser/preloading/preloading.h"
#include "content/browser/preloading/preloading_attempt_impl.h"
#include "content/browser/preloading/preloading_data_impl.h"
#include "content/browser/preloading/prerender/prerender_features.h"
#include "content/browser/preloading/speculation_rules/speculation_rules_tags.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/frame_accept_header.h"
#include "content/public/browser/prefetch_request_status_listener.h"
#include "content/public/browser/preload_pipeline_info.h"
#include "content/public/browser/preloading.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/test/fake_service_worker_context.h"
#include "content/public/test/mock_client_hints_controller_delegate.h"
#include "content/public/test/mock_navigation_handle.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/preloading_test_util.h"
#include "content/public/test/test_browser_context.h"
#include "content/test/test_content_browser_client.h"
#include "net/base/load_flags.h"
#include "net/base/load_timing_internal_info.h"
#include "net/base/proxy_chain.h"
#include "net/base/proxy_server.h"
#include "net/base/request_priority.h"
#include "net/http/http_no_vary_search_data.h"
#include "net/http/http_request_headers.h"
#include "services/metrics/public/cpp/metrics_utils.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/network/public/cpp/parsed_headers.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/public/mojom/cookie_manager.mojom.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/public/mojom/proxy_lookup_client.mojom.h"
#include "services/network/test/test_network_context.h"
#include "services/network/test/test_url_loader_factory.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/blink/public/common/navigation/preloading_headers.h"
#include "url/gurl.h"
namespace content {
namespace {
#if BUILDFLAG(IS_CHROMEOS)
#define DISABLED_CHROMEOS(x) DISABLED_##x
#else
#define DISABLED_CHROMEOS(x) x
#endif
#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_CASTOS)
#define DISABLED_CHROMEOS_AND_CASTOS(x) DISABLED_##x
#else
#define DISABLED_CHROMEOS_AND_CASTOS(x) x
#endif
constexpr int kAddedToURLRequestStartLatency = 123;
constexpr int kHeaderLatency = 456;
const char kHTMLMimeType[] = "text/html";
const char kHTMLBody[] = R"(
<!DOCTYPE HTML>
<html>
<head></head>
<body></body>
</html>)";
const char kHTMLBodyServerError[] = R"(
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>500 Internal Server Error</title>
</head>
<body>
</body>
</html>
)";
class ScopedPrefetchServiceContentBrowserClient
: public TestContentBrowserClient {
public:
explicit ScopedPrefetchServiceContentBrowserClient(
std::unique_ptr<MockPrefetchServiceDelegate>
mock_prefetch_service_delegate)
: mock_prefetch_service_delegate_(
std::move(mock_prefetch_service_delegate)) {
old_browser_client_ = SetBrowserClientForTesting(this);
off_the_record_context_ = std::make_unique<TestBrowserContext>();
off_the_record_context_->set_is_off_the_record(true);
}
~ScopedPrefetchServiceContentBrowserClient() override {
EXPECT_EQ(this, SetBrowserClientForTesting(old_browser_client_));
}
std::unique_ptr<PrefetchServiceDelegate> CreatePrefetchServiceDelegate(
BrowserContext*) override {
return std::move(mock_prefetch_service_delegate_);
}
void UseOffTheRecordContextForStoragePartition(bool use) {
use_off_the_record_context_for_storage_paritition_ = use;
}
StoragePartitionConfig GetStoragePartitionConfigForSite(
BrowserContext* browser_context,
const GURL& site) override {
if (use_off_the_record_context_for_storage_paritition_) {
return StoragePartitionConfig::CreateDefault(
off_the_record_context_.get());
}
return TestContentBrowserClient::GetStoragePartitionConfigForSite(
browser_context, site);
}
private:
raw_ptr<ContentBrowserClient> old_browser_client_;
std::unique_ptr<MockPrefetchServiceDelegate> mock_prefetch_service_delegate_;
std::unique_ptr<TestBrowserContext> off_the_record_context_;
bool use_off_the_record_context_for_storage_paritition_{false};
};
class TestNetworkContext : public network::TestNetworkContext {
public:
explicit TestNetworkContext(std::optional<net::ProxyInfo> proxy_info)
: proxy_info_(proxy_info) {}
void LookUpProxyForURL(
const GURL& url,
const net::NetworkAnonymizationKey& network_anonymization_key,
mojo::PendingRemote<network::mojom::ProxyLookupClient>
pending_proxy_lookup_client) override {
mojo::Remote<network::mojom::ProxyLookupClient> proxy_lookup_client(
std::move(pending_proxy_lookup_client));
proxy_lookup_client->OnProxyLookupComplete(net::OK, proxy_info_);
}
private:
std::optional<net::ProxyInfo> proxy_info_;
};
net::RequestPriority ExpectedPriorityForEagerness(
blink::mojom::SpeculationEagerness eagerness) {
switch (eagerness) {
case blink::mojom::SpeculationEagerness::kConservative:
return net::RequestPriority::MEDIUM;
case blink::mojom::SpeculationEagerness::kModerate:
return net::RequestPriority::LOW;
default:
return net::RequestPriority::IDLE;
}
}
class PrefetchFakeServiceWorkerContext : public FakeServiceWorkerContext {
public:
explicit PrefetchFakeServiceWorkerContext(
BrowserTaskEnvironment& task_environment)
: task_environment_(task_environment) {}
PrefetchFakeServiceWorkerContext(const PrefetchFakeServiceWorkerContext&) =
delete;
PrefetchFakeServiceWorkerContext& operator=(
const PrefetchFakeServiceWorkerContext&) = delete;
~PrefetchFakeServiceWorkerContext() override = default;
void CheckHasServiceWorker(const GURL& url,
const blink::StorageKey& key,
CheckHasServiceWorkerCallback callback) override {
if (long_service_worker_check_duration_.is_positive()) {
task_environment()->FastForwardBy(long_service_worker_check_duration_);
}
if (!MaybeHasRegistrationForStorageKey(key)) {
std::move(callback).Run(ServiceWorkerCapability::NO_SERVICE_WORKER);
return;
}
auto service_worker_info = std::ranges::find_if(
service_worker_scopes_,
[url](const std::pair<GURL, ServiceWorkerCapability>&
service_worker_info) {
return base::StartsWith(url.spec(), service_worker_info.first.spec());
});
if (service_worker_info != service_worker_scopes_.end()) {
std::move(callback).Run(service_worker_info->second);
return;
}
std::move(callback).Run(ServiceWorkerCapability::NO_SERVICE_WORKER);
}
void AddServiceWorkerScope(const GURL& scope,
ServiceWorkerCapability capability) {
ASSERT_NE(capability, ServiceWorkerCapability::NO_SERVICE_WORKER);
service_worker_scopes_[scope] = capability;
}
void SetServiceWorkerCheckDuration(base::TimeDelta duration) {
long_service_worker_check_duration_ = duration;
}
private:
BrowserTaskEnvironment* task_environment() {
return &task_environment_.get();
}
base::TimeDelta long_service_worker_check_duration_;
std::map<GURL, ServiceWorkerCapability> service_worker_scopes_;
const base::raw_ref<BrowserTaskEnvironment> task_environment_;
};
struct NavigationResult {
std::unique_ptr<testing::NiceMock<MockNavigationHandle>> navigation_handle;
base::test::TestFuture<PrefetchServingHandle> serving_handle_future;
};
class ProbePrefetchRequestStatusListener
: public content::PrefetchRequestStatusListener {
public:
~ProbePrefetchRequestStatusListener() override = default;
void OnPrefetchStartFailedGeneric() override {
prefetch_start_failed_called_ = true;
}
void OnPrefetchStartFailedDuplicate() override {
prefetch_start_failed_duplicate_called_ = true;
}
void OnPrefetchResponseCompleted() override {
prefetch_response_completed_called_ = true;
}
void OnPrefetchResponseError() override {
prefetch_response_error_called_ = true;
}
void OnPrefetchResponseServerError(int response_code) override {
prefetch_response_server_error_called_ = true;
server_response_code_ = response_code;
}
bool GetPrefetchStartFailedCalled() { return prefetch_start_failed_called_; }
bool GetPrefetchResponseCompletedCalled() {
return prefetch_response_completed_called_;
}
bool GetPrefetchResponseErrorCalled() {
return prefetch_response_error_called_;
}
bool GetPrefetchResponseServerErrorCalled() {
return prefetch_response_server_error_called_;
}
base::WeakPtr<ProbePrefetchRequestStatusListener> GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
private:
bool prefetch_start_failed_called_ = false;
bool prefetch_start_failed_duplicate_called_ = false;
bool prefetch_response_completed_called_ = false;
bool prefetch_response_error_called_ = false;
bool prefetch_response_server_error_called_ = false;
std::optional<int> server_response_code_;
base::WeakPtrFactory<ProbePrefetchRequestStatusListener> weak_factory_{this};
};
class TestablePrefetchRequestStatusListener
: public content::PrefetchRequestStatusListener {
public:
explicit TestablePrefetchRequestStatusListener(
base::WeakPtr<ProbePrefetchRequestStatusListener> probe_listener)
: probe_listener_(probe_listener) {}
~TestablePrefetchRequestStatusListener() override = default;
void OnPrefetchStartFailedGeneric() override {
probe_listener_->OnPrefetchStartFailedGeneric();
}
void OnPrefetchStartFailedDuplicate() override {
probe_listener_->OnPrefetchStartFailedDuplicate();
}
void OnPrefetchResponseCompleted() override {
probe_listener_->OnPrefetchResponseCompleted();
}
void OnPrefetchResponseError() override {
probe_listener_->OnPrefetchResponseError();
}
void OnPrefetchResponseServerError(int response_code) override {
probe_listener_->OnPrefetchResponseServerError(response_code);
}
private:
base::WeakPtr<ProbePrefetchRequestStatusListener> probe_listener_ = nullptr;
};
class PrefetchServiceTestBase : public PrefetchingMetricsTestBase {
public:
const int kServiceWorkerCheckDuration = 1000;
PrefetchServiceTestBase()
: test_url_loader_factory_(true),
test_shared_url_loader_factory_(
base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
&test_url_loader_factory_)) {
service_worker_context_ =
std::make_unique<PrefetchFakeServiceWorkerContext>(*task_environment());
}
void SetUp() override {
PrefetchingMetricsTestBase::SetUp();
browser_context()
->GetDefaultStoragePartition()
->GetNetworkContext()
->GetCookieManager(cookie_manager_.BindNewPipeAndPassReceiver());
InitScopedFeatureList();
PrefetchService::SetURLLoaderFactoryForTesting(
test_shared_url_loader_factory_.get());
PrefetchService::SetHostNonUniqueFilterForTesting(
[](std::string_view) { return false; });
PrefetchService::SetServiceWorkerContextForTesting(
service_worker_context_.get());
}
void TearDown() override {
if (PrefetchDocumentManager::GetForCurrentDocument(main_rfh()))
PrefetchDocumentManager::DeleteForCurrentDocument(main_rfh());
PrefetchDocumentManager::SetPrefetchServiceForTesting(nullptr);
mock_navigation_handle_.reset();
PrefetchService::SetURLLoaderFactoryForTesting(nullptr);
PrefetchService::SetHostNonUniqueFilterForTesting(nullptr);
PrefetchService::SetServiceWorkerContextForTesting(nullptr);
PrefetchService::SetURLLoaderFactoryForTesting(nullptr);
test_content_browser_client_.reset();
request_handler_keep_alive_.clear();
service_worker_context_.reset();
PrefetchingMetricsTestBase::TearDown();
}
virtual void InitScopedFeatureList() = 0;
void InitBaseParams() {
scoped_feature_list_base_params_.InitWithFeaturesAndParameters(
{{features::kPrefetchUseContentRefactor,
{{"ineligible_decoy_request_probability", "0"},
{"prefetch_container_lifetime_s", "-1"}}}},
{blink::features::kRemovePurposeHeaderForPrefetch});
}
void MakePrefetchService(std::unique_ptr<MockPrefetchServiceDelegate>
mock_prefetch_service_delegate) {
test_content_browser_client_ =
std::make_unique<ScopedPrefetchServiceContentBrowserClient>(
std::move(mock_prefetch_service_delegate));
BrowserContextImpl::From(browser_context())
->SetPrefetchServiceForTesting(
std::make_unique<PrefetchService>(browser_context()));
PrefetchDocumentManager::SetPrefetchServiceForTesting(&prefetch_service());
}
PrefetchService& prefetch_service() {
return *BrowserContextImpl::From(browser_context())->GetPrefetchService();
}
void MakePrefetchOnMainFrame(
const GURL& prefetch_url,
const PrefetchType& prefetch_type =
PrefetchType(PreloadingTriggerType::kSpeculationRule,
true,
blink::mojom::SpeculationEagerness::kImmediate),
const blink::mojom::Referrer& referrer = blink::mojom::Referrer(),
network::mojom::NoVarySearchPtr&& no_vary_search_hint =
network::mojom::NoVarySearchPtr(),
PreloadingType planned_max_preloading_type = PreloadingType::kPrefetch) {
CHECK(prefetch_type.IsRendererInitiated());
PrefetchDocumentManager* prefetch_document_manager =
PrefetchDocumentManager::GetOrCreateForCurrentDocument(main_rfh());
prefetch_document_manager->PrefetchUrl(
prefetch_url, prefetch_type,
GetPredictorForPreloadingTriggerType(prefetch_type.trigger_type()),
referrer, SpeculationRulesTags(), no_vary_search_hint,
PreloadPipelineInfo::Create(planned_max_preloading_type));
task_environment()->RunUntilIdle();
}
[[nodiscard]] std::unique_ptr<PrefetchHandle> MakePrefetchFromEmbedder(
const GURL& prefetch_url,
const PrefetchType& prefetch_type,
const blink::mojom::Referrer& referrer = blink::mojom::Referrer(),
const std::optional<url::Origin> referring_origin = std::nullopt) {
CHECK(!prefetch_type.IsRendererInitiated());
auto prefetch_request = PrefetchRequest::CreateBrowserInitiated(
*web_contents(), prefetch_url, prefetch_type,
test::kPreloadingEmbedderHistgramSuffixForTesting, referrer,
std::move(referring_origin),
std::nullopt,
std::nullopt,
PreloadPipelineInfo::Create(
PreloadingType::kPrefetch),
nullptr);
return prefetch_service().AddPrefetchRequestWithHandle(
std::move(prefetch_request));
}
[[nodiscard]] std::unique_ptr<content::PrefetchHandle>
MakePrefetchFromBrowserContext(
const GURL& url,
std::optional<net::HttpNoVarySearchData> no_vary_search_data,
const net::HttpRequestHeaders& additional_headers,
std::unique_ptr<PrefetchRequestStatusListener> request_status_listener,
base::TimeDelta ttl = base::Seconds( 60 * 10),
bool should_disable_block_until_head_timeout = false) {
return browser_context()->StartBrowserPrefetchRequest(
url, test::kPreloadingEmbedderHistgramSuffixForTesting, true,
no_vary_search_data, PrefetchPriority::kHighest, additional_headers,
std::move(request_status_listener), ttl,
true,
should_disable_block_until_head_timeout,
false);
}
int RequestCount() { return test_url_loader_factory_.NumPending(); }
void ClearCompletedRequests() {
std::vector<network::TestURLLoaderFactory::PendingRequest>* requests =
test_url_loader_factory_.pending_requests();
std::erase_if(
*requests,
[](const network::TestURLLoaderFactory::PendingRequest& request) {
return !request.client.is_connected();
});
}
struct VerifyCommonRequestStateOptions {
bool use_prefetch_proxy = false;
net::RequestPriority expected_priority = net::RequestPriority::IDLE;
std::optional<std::string> sec_purpose_header_value = std::nullopt;
net::HttpRequestHeaders additional_headers;
};
void VerifyCommonRequestState(const GURL& url) {
VerifyCommonRequestState(url, {});
}
void VerifyCommonRequestStateForWebContentsPrefetch(
const GURL& url,
const VerifyCommonRequestStateOptions& options) {
VerifyCommonRequestStateOptions options_override = options;
options_override.expected_priority =
base::FeatureList::IsEnabled(
features::kPrefetchNetworkPriorityForEmbedders)
? net::RequestPriority::MEDIUM
: net::RequestPriority::IDLE;
VerifyCommonRequestState(url, options_override);
}
void VerifyCommonRequestStateForBrowserContextPrefetch(
const GURL& url,
const VerifyCommonRequestStateOptions& options) {
VerifyCommonRequestStateOptions options_override = options;
options_override.expected_priority = net::RequestPriority::HIGHEST;
VerifyCommonRequestState(url, options_override);
}
void VerifyCommonRequestState(
const GURL& url,
const VerifyCommonRequestStateOptions& options) {
SCOPED_TRACE(url.spec());
EXPECT_EQ(RequestCount(), 1);
network::TestURLLoaderFactory::PendingRequest* request =
test_url_loader_factory_.GetPendingRequest(0);
VerifyCommonRequestState(url, options, request);
}
void VerifyCommonRequestStateByUrl(const GURL& url) {
VerifyCommonRequestStateByUrl(url, {});
}
void VerifyCommonRequestStateByUrl(
const GURL& url,
const VerifyCommonRequestStateOptions& options) {
SCOPED_TRACE(url.spec());
auto* pending_request = GetPendingRequestByUrl(url);
ASSERT_TRUE(pending_request);
VerifyCommonRequestState(url, options, pending_request);
}
void VerifyPrefetchAttemptIsPending(const GURL& url) {
PrefetchKey prefetch_key(MainDocumentToken(), url);
base::WeakPtr<PrefetchContainer> prefetch_container =
prefetch_service().MatchUrl(prefetch_key);
ASSERT_TRUE(prefetch_container);
ASSERT_FALSE(prefetch_container->GetResourceRequest());
ASSERT_EQ(prefetch_container->GetLoadState(),
PrefetchContainer::LoadState::kEligible);
}
void VerifyIsolationInfo(const net::IsolationInfo& isolation_info) {
EXPECT_FALSE(isolation_info.IsEmpty());
EXPECT_TRUE(isolation_info.network_isolation_key().IsFullyPopulated());
EXPECT_FALSE(isolation_info.network_isolation_key().IsTransient());
EXPECT_FALSE(isolation_info.site_for_cookies().IsNull());
}
network::mojom::URLResponseHeadPtr CreateURLResponseHeadForPrefetch(
net::HttpStatusCode http_status,
const std::string mime_type,
bool use_prefetch_proxy,
const std::vector<std::pair<std::string, std::string>>& headers,
const GURL& request_url) {
auto head = network::CreateURLResponseHead(http_status);
head->response_time = base::Time::Now();
head->request_time =
head->response_time - base::Milliseconds(kTotalTimeDuration);
head->load_timing.connect_timing.connect_end =
base::TimeTicks::Now() - base::Minutes(2);
head->load_timing.connect_timing.connect_start =
head->load_timing.connect_timing.connect_end -
base::Milliseconds(kConnectTimeDuration);
head->load_timing.receive_headers_end = base::TimeTicks::Now();
head->load_timing.request_start = head->load_timing.receive_headers_end -
base::Milliseconds(kHeaderLatency);
head->load_timing_internal_info = net::LoadTimingInternalInfo();
head->proxy_chain =
use_prefetch_proxy
? net::ProxyChain::FromSchemeHostAndPort(
net::ProxyServer::Scheme::SCHEME_HTTPS,
PrefetchProxyHost(
GURL(MockPrefetchServiceDelegate::kPrefetchProxyAddress))
.spec(),
std::nullopt)
: net::ProxyChain::Direct();
head->mime_type = mime_type;
for (const auto& header : headers) {
head->headers->AddHeader(header.first, header.second);
}
if (!head->parsed_headers) {
head->parsed_headers =
network::PopulateParsedHeaders(head->headers.get(), request_url);
}
return head;
}
void MakeSingleRedirectAndWait(
const GURL& url,
net::HttpStatusCode http_status = net::HTTP_PERMANENT_REDIRECT,
net::ReferrerPolicy referrer_policy =
net::ReferrerPolicy::REDUCE_GRANULARITY_ON_TRANSITION_CROSS_ORIGIN) {
network::TestURLLoaderFactory::PendingRequest* request =
test_url_loader_factory_.GetPendingRequest(0);
ASSERT_TRUE(request);
ASSERT_TRUE(request->client);
net::RedirectInfo redirect_info;
redirect_info.new_method = "GET";
redirect_info.new_referrer_policy = referrer_policy;
redirect_info.new_url = url;
request->client->OnReceiveRedirect(
redirect_info,
CreateURLResponseHeadForPrefetch(http_status, kHTMLMimeType,
true, {}, url));
task_environment()->RunUntilIdle();
}
void Disconnect() {
network::TestURLLoaderFactory::PendingRequest* request =
test_url_loader_factory_.GetPendingRequest(0);
ASSERT_TRUE(request);
ASSERT_TRUE(request->client);
request->client.reset();
}
void VerifyFollowRedirectParams(size_t expected_follow_redirect_params_size) {
network::TestURLLoaderFactory::PendingRequest* request =
test_url_loader_factory_.GetPendingRequest(0);
ASSERT_TRUE(request);
ASSERT_TRUE(request->test_url_loader);
const auto& follow_redirect_params =
request->test_url_loader->follow_redirect_params();
EXPECT_EQ(follow_redirect_params.size(),
expected_follow_redirect_params_size);
for (const auto& follow_redirect_param : follow_redirect_params) {
EXPECT_EQ(follow_redirect_param.removed_headers.size(), 0U);
EXPECT_TRUE(follow_redirect_param.modified_headers.IsEmpty());
EXPECT_TRUE(follow_redirect_param.modified_cors_exempt_headers.IsEmpty());
EXPECT_FALSE(follow_redirect_param.new_url);
}
}
void MakeResponseAndWait(
net::HttpStatusCode http_status,
net::Error net_error,
const std::string mime_type,
bool use_prefetch_proxy,
std::vector<std::pair<std::string, std::string>> headers,
const std::string& body) {
network::TestURLLoaderFactory::PendingRequest* request =
test_url_loader_factory_.GetPendingRequest(0);
ASSERT_TRUE(request);
auto head = CreateURLResponseHeadForPrefetch(http_status, mime_type,
use_prefetch_proxy, headers,
request->request.url);
MakeResponseAndWait(request->request.url, net_error, std::move(head), body);
}
void MakeResponseAndWait(const GURL& url,
net::Error net_error,
network::mojom::URLResponseHeadPtr head,
const std::string& body) {
network::URLLoaderCompletionStatus status(net_error);
test_url_loader_factory_.AddResponse(url, std::move(head), body, status);
task_environment()->RunUntilIdle();
test_url_loader_factory_.ClearResponses();
}
void SendHeadOfResponseAndWait(
net::HttpStatusCode http_status,
const std::string mime_type,
bool use_prefetch_proxy,
std::vector<std::pair<std::string, std::string>> headers,
uint32_t expected_total_body_size) {
network::TestURLLoaderFactory::PendingRequest* request =
test_url_loader_factory_.GetPendingRequest(0);
ASSERT_FALSE(producer_handle_for_gurl_.count(request->request.url));
ASSERT_TRUE(request);
SendHeadOfResponseAndWait(http_status, mime_type, use_prefetch_proxy,
headers, expected_total_body_size, request);
ASSERT_TRUE(producer_handle_for_gurl_.count(request->request.url));
}
void SendHeadOfResponseForUrlAndWait(
const GURL& request_url,
net::HttpStatusCode http_status,
const std::string mime_type,
bool use_prefetch_proxy,
std::vector<std::pair<std::string, std::string>> headers,
uint32_t expected_total_body_size) {
auto* request = GetPendingRequestByUrl(request_url);
ASSERT_TRUE(request);
ASSERT_TRUE(request->client);
ASSERT_FALSE(producer_handle_for_gurl_.count(request->request.url));
SendHeadOfResponseAndWait(http_status, mime_type, use_prefetch_proxy,
headers, expected_total_body_size, request);
ASSERT_TRUE(producer_handle_for_gurl_.count(request->request.url));
}
void SendBodyContentOfResponseAndWait(const std::string& body) {
network::TestURLLoaderFactory::PendingRequest* request =
test_url_loader_factory_.GetPendingRequest(0);
ASSERT_TRUE(request);
SendBodyContentOfResponseAndWait(body, request);
}
void SendBodyContentOfResponseForUrlAndWait(const GURL& url,
const std::string& body) {
auto* request = GetPendingRequestByUrl(url);
ASSERT_TRUE(request);
SendBodyContentOfResponseAndWait(body, request);
}
void CompleteResponseAndWait(net::Error net_error,
uint32_t expected_total_body_size) {
network::TestURLLoaderFactory::PendingRequest* request =
test_url_loader_factory_.GetPendingRequest(0);
ASSERT_TRUE(request);
CompleteResponseAndWait(net_error, expected_total_body_size, request);
}
void CompleteResponseForUrlAndWait(const GURL& url,
net::Error net_error,
uint32_t expected_total_body_size) {
auto* request = GetPendingRequestByUrl(url);
ASSERT_TRUE(request);
CompleteResponseAndWait(net_error, expected_total_body_size, request);
}
bool SetCookie(const GURL& url, const std::string& value) {
std::unique_ptr<net::CanonicalCookie> cookie(
net::CanonicalCookie::CreateForTesting(url, value, base::Time::Now()));
EXPECT_TRUE(cookie.get());
bool result = false;
base::RunLoop run_loop;
net::CookieOptions options;
options.set_include_httponly();
options.set_same_site_cookie_context(
net::CookieOptions::SameSiteCookieContext::MakeInclusive());
cookie_manager_->SetCanonicalCookie(
*cookie.get(), url, options,
base::BindOnce(
[](bool* result, base::RunLoop* run_loop,
net::CookieAccessResult set_cookie_access_result) {
*result = set_cookie_access_result.status.IsInclude();
run_loop->Quit();
},
&result, &run_loop));
run_loop.Run();
return result;
}
void Navigate(
const GURL& url,
int initiator_process_id,
const std::optional<blink::LocalFrameToken>& initiator_local_frame_token,
const std::optional<blink::DocumentToken>& initiator_document_token) {
mock_navigation_handle_ =
std::make_unique<testing::NiceMock<MockNavigationHandle>>(
web_contents());
mock_navigation_handle_->set_url(url);
mock_navigation_handle_->set_initiator_process_id(initiator_process_id);
mock_navigation_handle_->set_initiator_frame_token(
base::OptionalToPtr(initiator_local_frame_token));
if (initiator_document_token &&
PrefetchDocumentManager::FromDocumentToken(initiator_process_id,
*initiator_document_token)) {
PrefetchServingPageMetricsContainer::GetOrCreateForNavigationHandle(
*mock_navigation_handle_);
}
}
void NavigateInitiatedByRenderer(const GURL& url) {
Navigate(url, main_rfh()->GetProcess()->GetDeprecatedID(),
main_rfh()->GetFrameToken(), MainDocumentToken());
}
void NavigateInitiatedByBrowser(const GURL& url) {
Navigate(url, ChildProcessHost::kInvalidUniqueID, std::nullopt,
std::nullopt);
}
std::optional<PrefetchServingPageMetrics>
GetMetricsForMostRecentNavigation() {
if (!mock_navigation_handle_)
return std::nullopt;
return PrefetchServingPageMetrics::GetForNavigationHandle(
*mock_navigation_handle_);
}
base::WeakPtr<PrefetchServingPageMetricsContainer>
GetServingPageMetricsContainerForMostRecentNavigation() {
if (!mock_navigation_handle_) {
return nullptr;
}
auto* serving_page_metrics_container =
PrefetchServingPageMetricsContainer::GetForNavigationHandle(
*mock_navigation_handle_);
if (!serving_page_metrics_container) {
return nullptr;
}
return serving_page_metrics_container->GetWeakPtr();
}
blink::DocumentToken MainDocumentToken() {
return static_cast<RenderFrameHostImpl*>(main_rfh())->GetDocumentToken();
}
void GetPrefetchToServe(
base::test::TestFuture<PrefetchServingHandle>& future,
const GURL& url,
std::optional<blink::DocumentToken> initiator_document_token) {
auto callback = base::BindOnce(
[](base::test::TestFuture<PrefetchServingHandle>* future,
std::vector<PrefetchRequestHandler>* request_handler_keep_alive,
PrefetchServingHandle prefetch_to_serve) {
if (prefetch_to_serve) {
auto request_handler =
prefetch_to_serve.CreateRequestHandler().first;
CHECK(request_handler);
request_handler_keep_alive->push_back(std::move(request_handler));
}
future->SetValue(std::move(prefetch_to_serve));
},
base::Unretained(&future),
base::Unretained(&request_handler_keep_alive_));
auto key = PrefetchKey(initiator_document_token, url);
PrefetchMatchResolver::FindPrefetchForTesting(
prefetch_service(), std::move(key),
PrefetchServiceWorkerState::kDisallowed,
false,
GetServingPageMetricsContainerForMostRecentNavigation(),
std::move(callback));
}
PrefetchServingHandle GetPrefetchToServe(
const GURL& url,
std::optional<blink::DocumentToken> initiator_document_token) {
base::test::TestFuture<PrefetchServingHandle> future;
GetPrefetchToServe(future, url, std::move(initiator_document_token));
return future.Take();
}
PrefetchServingHandle GetPrefetchToServe(const GURL& url) {
return GetPrefetchToServe(url, MainDocumentToken());
}
std::unique_ptr<NavigationResult> SimulatePartOfNavigation(
const GURL& url,
bool is_renderer_initiated,
bool is_nav_prerender) {
return is_renderer_initiated
? SimulatePartOfNavigation(
url, is_nav_prerender,
main_rfh()->GetProcess()->GetDeprecatedID(),
main_rfh()->GetFrameToken(), MainDocumentToken())
: SimulatePartOfNavigation(url, is_nav_prerender,
ChildProcessHost::kInvalidUniqueID,
std::nullopt, std::nullopt);
}
std::unique_ptr<NavigationResult> SimulatePartOfNavigation(
const GURL& url,
bool is_nav_prerender,
int initiator_process_id,
const std::optional<blink::LocalFrameToken>& initiator_local_frame_token,
const std::optional<blink::DocumentToken>& initiator_document_token) {
auto res = std::make_unique<NavigationResult>();
res->navigation_handle =
std::make_unique<testing::NiceMock<MockNavigationHandle>>(
web_contents());
res->navigation_handle->set_url(url);
res->navigation_handle->set_initiator_process_id(initiator_process_id);
res->navigation_handle->set_initiator_frame_token(
base::OptionalToPtr(initiator_local_frame_token));
if (initiator_document_token &&
PrefetchDocumentManager::FromDocumentToken(initiator_process_id,
*initiator_document_token)) {
PrefetchServingPageMetricsContainer::GetOrCreateForNavigationHandle(
*res->navigation_handle);
}
auto callback = base::BindOnce(
[](base::test::TestFuture<PrefetchServingHandle>& serving_handle_future,
PrefetchServingHandle serving_handle) {
serving_handle_future.SetValue(std::move(serving_handle));
},
std::ref(res->serving_handle_future));
auto serving_page_metrics_container =
[&res]() -> base::WeakPtr<PrefetchServingPageMetricsContainer> {
auto* serving_page_metrics_container =
PrefetchServingPageMetricsContainer::GetForNavigationHandle(
*res->navigation_handle);
if (!serving_page_metrics_container) {
return nullptr;
}
return serving_page_metrics_container->GetWeakPtr();
}();
auto key = PrefetchKey(initiator_document_token, url);
PrefetchMatchResolver::FindPrefetchForTesting(
prefetch_service(), std::move(key),
PrefetchServiceWorkerState::kDisallowed, is_nav_prerender,
std::move(serving_page_metrics_container), std::move(callback));
return res;
}
ScopedPrefetchServiceContentBrowserClient* test_content_browser_client() {
return test_content_browser_client_.get();
}
static void ExpectServingMetrics(
const base::Location& location,
std::optional<PrefetchServingPageMetrics> serving_page_metrics,
PrefetchStatus expected_prefetch_status,
std::optional<base::TimeDelta> prefetch_header_latency,
bool required_private_prefetch_proxy) {
SCOPED_TRACE(::testing::Message() << "callsite: " << location.ToString());
ASSERT_TRUE(serving_page_metrics);
ASSERT_TRUE(serving_page_metrics->prefetch_status);
EXPECT_EQ(serving_page_metrics->prefetch_status.value(),
static_cast<int>(expected_prefetch_status));
EXPECT_EQ(serving_page_metrics->prefetch_header_latency,
prefetch_header_latency);
EXPECT_EQ(serving_page_metrics->required_private_prefetch_proxy,
required_private_prefetch_proxy);
EXPECT_TRUE(serving_page_metrics->same_tab_as_prefetching_tab);
}
void ExpectServingMetrics(PrefetchStatus expected_prefetch_status,
bool prefetch_header_latency = false,
bool required_private_prefetch_proxy = true) {
std::optional<base::TimeDelta> prefetch_header_latency_value;
if (prefetch_header_latency) {
prefetch_header_latency_value = base::Milliseconds(kHeaderLatency);
}
ExpectServingMetrics(FROM_HERE, GetMetricsForMostRecentNavigation(),
expected_prefetch_status,
std::move(prefetch_header_latency_value),
required_private_prefetch_proxy);
}
void ExpectServingMetricsSuccess(
bool required_private_prefetch_proxy = true) {
ExpectServingMetrics(PrefetchStatus::kPrefetchSuccessful,
true,
required_private_prefetch_proxy);
}
struct ExpectServingMetricsArgs {
PrefetchStatus prefetch_status;
std::optional<base::TimeDelta> prefetch_header_latency;
bool required_private_prefetch_proxy;
};
static void ExpectServingMetrics(
const base::Location& location,
const std::unique_ptr<NavigationResult>& nav_res,
ExpectServingMetricsArgs args) {
ExpectServingMetrics(location,
PrefetchServingPageMetrics::GetForNavigationHandle(
*nav_res->navigation_handle),
args.prefetch_status, args.prefetch_header_latency,
args.required_private_prefetch_proxy);
}
static void ExpectServingReaderSuccess(
const PrefetchServingHandle& serving_handle) {
ExpectServingReaderSuccess(FROM_HERE, serving_handle);
}
static void ExpectServingReaderSuccess(
const base::Location& location,
const PrefetchServingHandle& serving_handle) {
SCOPED_TRACE(::testing::Message() << "callsite: " << location.ToString());
ASSERT_TRUE(serving_handle);
EXPECT_TRUE(serving_handle.HasPrefetchStatus());
EXPECT_EQ(serving_handle.GetPrefetchStatus(),
PrefetchStatus::kPrefetchSuccessful);
EXPECT_EQ(serving_handle.GetServableState(base::TimeDelta::Max()),
PrefetchServableState::kServable);
ASSERT_TRUE(serving_handle.GetPrefetchContainer()->GetNonRedirectHead());
EXPECT_TRUE(serving_handle.GetPrefetchContainer()
->GetNonRedirectHead()
->was_in_prefetch_cache);
}
protected:
network::TestURLLoaderFactory::PendingRequest* GetPendingRequestByUrl(
const GURL& url) {
auto& pending_requests = *test_url_loader_factory_.pending_requests();
auto it = std::ranges::find(pending_requests, url,
[](const auto& pending_request) {
return pending_request.request.url;
});
if (it == pending_requests.end()) {
return nullptr;
}
network::TestURLLoaderFactory::PendingRequest* request = &*it;
return request;
}
void VerifyCommonRequestState(
const GURL& url,
const VerifyCommonRequestStateOptions& options,
const network::TestURLLoaderFactory::PendingRequest* request) {
ASSERT_TRUE(request);
EXPECT_EQ(request->request.url, url);
EXPECT_EQ(request->request.method, "GET");
EXPECT_TRUE(request->request.enable_load_timing);
EXPECT_EQ(request->request.load_flags, net::LOAD_PREFETCH);
EXPECT_EQ(request->request.credentials_mode,
network::mojom::CredentialsMode::kInclude);
EXPECT_THAT(
request->request.headers.GetHeader(blink::kPurposeHeaderName),
testing::Optional(std::string(blink::kSecPurposePrefetchHeaderValue)));
std::string sec_purpose_header_value;
if (options.sec_purpose_header_value) {
sec_purpose_header_value = options.sec_purpose_header_value.value();
} else {
sec_purpose_header_value =
options.use_prefetch_proxy
? blink::kSecPurposePrefetchAnonymousClientIpHeaderValue
: blink::kSecPurposePrefetchHeaderValue;
}
EXPECT_THAT(
request->request.headers.GetHeader(blink::kSecPurposeHeaderName),
testing::Optional(sec_purpose_header_value));
EXPECT_THAT(request->request.headers.GetHeader("Accept"),
testing::Optional(FrameAcceptHeaderValue(
true, browser_context())));
EXPECT_THAT(request->request.headers.GetHeader("Upgrade-Insecure-Requests"),
testing::Optional(std::string("1")));
ASSERT_TRUE(request->request.trusted_params.has_value());
VerifyIsolationInfo(request->request.trusted_params->isolation_info);
EXPECT_EQ(request->request.priority, options.expected_priority);
net::HttpRequestHeaders::Iterator header_it(options.additional_headers);
while (header_it.GetNext()) {
EXPECT_THAT(request->request.headers.GetHeader(header_it.name()),
testing::Optional(header_it.value()));
}
}
void SendHeadOfResponseAndWait(
net::HttpStatusCode http_status,
const std::string mime_type,
bool use_prefetch_proxy,
std::vector<std::pair<std::string, std::string>> headers,
uint32_t expected_total_body_size,
network::TestURLLoaderFactory::PendingRequest* request) {
ASSERT_FALSE(producer_handle_for_gurl_.count(request->request.url));
ASSERT_TRUE(request);
ASSERT_TRUE(request->client);
auto head = CreateURLResponseHeadForPrefetch(http_status, mime_type,
use_prefetch_proxy, headers,
request->request.url);
mojo::ScopedDataPipeConsumerHandle body;
EXPECT_EQ(mojo::CreateDataPipe(
expected_total_body_size,
producer_handle_for_gurl_[request->request.url], body),
MOJO_RESULT_OK);
request->client->OnReceiveResponse(std::move(head), std::move(body),
std::nullopt);
task_environment()->RunUntilIdle();
}
void SendBodyContentOfResponseAndWait(
const std::string& body,
network::TestURLLoaderFactory::PendingRequest* request) {
ASSERT_TRUE(producer_handle_for_gurl_.count(request->request.url));
ASSERT_TRUE(producer_handle_for_gurl_[request->request.url]);
EXPECT_EQ(producer_handle_for_gurl_[request->request.url]->WriteAllData(
base::as_byte_span(body)),
MOJO_RESULT_OK);
task_environment()->RunUntilIdle();
}
void CompleteResponseAndWait(
net::Error net_error,
uint32_t expected_total_body_size,
network::TestURLLoaderFactory::PendingRequest* request) {
ASSERT_TRUE(request);
ASSERT_TRUE(request->client);
if (producer_handle_for_gurl_.count(request->request.url)) {
producer_handle_for_gurl_[request->request.url].reset();
producer_handle_for_gurl_.erase(request->request.url);
}
network::URLLoaderCompletionStatus completion_status(net_error);
completion_status.decoded_body_length = expected_total_body_size;
request->client->OnComplete(completion_status);
task_environment()->RunUntilIdle();
test_url_loader_factory_.ClearResponses();
}
base::ScopedMockElapsedTimersForTest scoped_test_timer_;
std::unique_ptr<PrefetchFakeServiceWorkerContext> service_worker_context_;
mojo::Remote<network::mojom::CookieManager> cookie_manager_;
network::TestURLLoaderFactory test_url_loader_factory_;
scoped_refptr<network::SharedURLLoaderFactory>
test_shared_url_loader_factory_;
base::test::ScopedFeatureList scoped_feature_list_base_params_;
content::test::PreloadingConfigOverride preloading_config_override_;
std::unique_ptr<testing::NiceMock<MockNavigationHandle>>
mock_navigation_handle_;
std::unique_ptr<ScopedPrefetchServiceContentBrowserClient>
test_content_browser_client_;
std::map<GURL, mojo::ScopedDataPipeProducerHandle> producer_handle_for_gurl_;
std::vector<PrefetchRequestHandler> request_handler_keep_alive_;
variations::test::ScopedVariationsIdsProvider scoped_variations_ids_provider_{
variations::VariationsIdsProvider::Mode::kIgnoreSignedInState};
};
class PrefetchServiceTest
: public PrefetchServiceTestBase,
public WithPrefetchRearchParam,
public ::testing::WithParamInterface<PrefetchRearchParam> {
public:
PrefetchServiceTest() : WithPrefetchRearchParam(GetParam()) {}
void InitScopedFeatureList() override {
InitBaseParams();
InitRearchFeatures();
}
};
INSTANTIATE_TEST_SUITE_P(ParametrizedTests,
PrefetchServiceTest,
testing::ValuesIn(PrefetchRearchParam::Params()));
TEST_P(PrefetchServiceTest, SuccessCase) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
const PrefetchType prefetch_type =
PrefetchType(PreloadingTriggerType::kSpeculationRule,
true,
blink::mojom::SpeculationEagerness::kImmediate);
MakePrefetchOnMainFrame(GURL("https://example.com"), prefetch_type);
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = true});
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}}, kHTMLBody);
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody));
NavigateInitiatedByRenderer(GURL("https://example.com"));
ExpectServingReaderSuccess(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetricsSuccess();
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.AfterClick.RedirectChainSize", 1, 1);
histogram_tester.ExpectUniqueSample(
base::StringPrintf(
"Prefetch.PrefetchMatchingBlockedNavigation.PerMatchingCandidate.%s",
GetMetricsSuffixTriggerTypeAndEagerness(
prefetch_type, std::nullopt)),
false, 1);
histogram_tester.ExpectUniqueSample(
base::StrCat(
{"Prefetch.PrefetchPotentialCandidateServingResult."
"PerMatchingCandidate.",
GetMetricsSuffixTriggerTypeAndEagerness(
prefetch_type, std::nullopt)}),
PrefetchPotentialCandidateServingResult::kServed, 1);
}
TEST_P(PrefetchServiceTest, SuccessCase_Browser) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
std::nullopt));
net::HttpRequestHeaders request_additional_headers = {};
request_additional_headers.SetHeader("foo", "bar");
request_additional_headers.SetHeader("foo1", "bar1");
std::unique_ptr<ProbePrefetchRequestStatusListener> probe_listener =
std::make_unique<ProbePrefetchRequestStatusListener>();
std::unique_ptr<content::PrefetchRequestStatusListener>
request_status_listener =
std::make_unique<TestablePrefetchRequestStatusListener>(
probe_listener->GetWeakPtr());
std::unique_ptr<content::PrefetchHandle> handle =
MakePrefetchFromBrowserContext(GURL("https://example.com?b=1"),
std::nullopt, request_additional_headers,
std::move(request_status_listener));
task_environment()->RunUntilIdle();
VerifyCommonRequestStateForBrowserContextPrefetch(
GURL("https://example.com?b=1"),
{.use_prefetch_proxy = false,
.additional_headers = request_additional_headers});
EXPECT_FALSE(probe_listener->GetPrefetchStartFailedCalled());
EXPECT_FALSE(probe_listener->GetPrefetchResponseCompletedCalled());
EXPECT_FALSE(probe_listener->GetPrefetchResponseErrorCalled());
EXPECT_FALSE(probe_listener->GetPrefetchResponseServerErrorCalled());
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
false,
{{"X-Testing", "Hello World"}}, kHTMLBody);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.RespCode", net::HTTP_OK, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.NetError", net::OK, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.BodyLength", std::size(kHTMLBody), 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.TotalTime", kTotalTimeDuration, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.ConnectTime", kConnectTimeDuration, 1);
EXPECT_FALSE(probe_listener->GetPrefetchStartFailedCalled());
EXPECT_TRUE(probe_listener->GetPrefetchResponseCompletedCalled());
EXPECT_FALSE(probe_listener->GetPrefetchResponseErrorCalled());
EXPECT_FALSE(probe_listener->GetPrefetchResponseServerErrorCalled());
NavigateInitiatedByBrowser(GURL("https://example.com?b=1"));
PrefetchServingHandle serving_handle =
GetPrefetchToServe(GURL("https://example.com?b=1"), std::nullopt);
ExpectServingReaderSuccess(serving_handle);
EXPECT_EQ(serving_handle.GetPrefetchContainer()->GetURL(),
GURL("https://example.com/?b=1"));
histogram_tester.ExpectUniqueSample(
base::StringPrintf(
"Prefetch.PrefetchMatchingBlockedNavigation.PerMatchingCandidate.%s",
GetMetricsSuffixTriggerTypeAndEagerness(
PrefetchType(PreloadingTriggerType::kEmbedder,
false),
test::kPreloadingEmbedderHistgramSuffixForTesting)),
false, 1);
histogram_tester.ExpectUniqueSample(
base::StrCat({"Prefetch.PrefetchPotentialCandidateServingResult."
"PerMatchingCandidate.",
GetMetricsSuffixTriggerTypeAndEagerness(
PrefetchType(PreloadingTriggerType::kEmbedder,
false),
test::kPreloadingEmbedderHistgramSuffixForTesting)}),
PrefetchPotentialCandidateServingResult::kServed, 1);
}
TEST_P(PrefetchServiceTest, SuccessCase_Browser_NoVarySearch) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
std::nullopt));
net::HttpRequestHeaders request_additional_headers = {};
request_additional_headers.SetHeader("foo", "bar");
request_additional_headers.SetHeader("foo1", "bar1");
std::unique_ptr<ProbePrefetchRequestStatusListener> probe_listener =
std::make_unique<ProbePrefetchRequestStatusListener>();
std::unique_ptr<content::PrefetchRequestStatusListener>
request_status_listener =
std::make_unique<TestablePrefetchRequestStatusListener>(
probe_listener->GetWeakPtr());
net::HttpNoVarySearchData nvs_data =
net::HttpNoVarySearchData::CreateFromNoVaryParams({"a"}, false);
std::unique_ptr<content::PrefetchHandle> handle =
MakePrefetchFromBrowserContext(GURL("https://example.com?a=1"), nvs_data,
request_additional_headers,
std::move(request_status_listener));
task_environment()->RunUntilIdle();
VerifyCommonRequestStateForBrowserContextPrefetch(
GURL("https://example.com?a=1"),
{.use_prefetch_proxy = false,
.additional_headers = request_additional_headers});
EXPECT_FALSE(probe_listener->GetPrefetchStartFailedCalled());
EXPECT_FALSE(probe_listener->GetPrefetchResponseCompletedCalled());
EXPECT_FALSE(probe_listener->GetPrefetchResponseErrorCalled());
EXPECT_FALSE(probe_listener->GetPrefetchResponseServerErrorCalled());
MakeResponseAndWait(
net::HTTP_OK, net::OK, kHTMLMimeType,
false,
{{"X-Testing", "Hello World"}, {"No-Vary-Search", R"(params=("a"))"}},
kHTMLBody);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.RespCode", net::HTTP_OK, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.NetError", net::OK, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.BodyLength", std::size(kHTMLBody), 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.TotalTime", kTotalTimeDuration, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.ConnectTime", kConnectTimeDuration, 1);
EXPECT_FALSE(probe_listener->GetPrefetchStartFailedCalled());
EXPECT_TRUE(probe_listener->GetPrefetchResponseCompletedCalled());
EXPECT_FALSE(probe_listener->GetPrefetchResponseErrorCalled());
EXPECT_FALSE(probe_listener->GetPrefetchResponseServerErrorCalled());
NavigateInitiatedByBrowser(GURL("https://example.com"));
PrefetchServingHandle serving_handle =
GetPrefetchToServe(GURL("https://example.com"), std::nullopt);
ExpectServingReaderSuccess(serving_handle);
EXPECT_EQ(serving_handle.GetPrefetchContainer()->GetURL(),
GURL("https://example.com/?a=1"));
}
TEST_P(PrefetchServiceTest, FailureCase_Browser_ServerErrorResponseCode) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
std::nullopt));
std::unique_ptr<ProbePrefetchRequestStatusListener> probe_listener =
std::make_unique<ProbePrefetchRequestStatusListener>();
std::unique_ptr<content::PrefetchRequestStatusListener>
request_status_listener =
std::make_unique<TestablePrefetchRequestStatusListener>(
probe_listener->GetWeakPtr());
std::unique_ptr<content::PrefetchHandle> handle =
MakePrefetchFromBrowserContext(GURL("https://example.com?b=1"),
std::nullopt, {},
std::move(request_status_listener));
task_environment()->RunUntilIdle();
VerifyCommonRequestStateForBrowserContextPrefetch(
GURL("https://example.com?b=1"), {.use_prefetch_proxy = false});
EXPECT_FALSE(probe_listener->GetPrefetchStartFailedCalled());
EXPECT_FALSE(probe_listener->GetPrefetchResponseCompletedCalled());
EXPECT_FALSE(probe_listener->GetPrefetchResponseErrorCalled());
EXPECT_FALSE(probe_listener->GetPrefetchResponseServerErrorCalled());
MakeResponseAndWait(net::HTTP_INTERNAL_SERVER_ERROR, net::OK, kHTMLMimeType,
false, {}, kHTMLBodyServerError);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.RespCode",
net::HTTP_INTERNAL_SERVER_ERROR, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.NetError", net::OK, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.BodyLength",
std::size(kHTMLBodyServerError), 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.TotalTime", kTotalTimeDuration, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.ConnectTime", kConnectTimeDuration, 1);
EXPECT_FALSE(probe_listener->GetPrefetchStartFailedCalled());
EXPECT_FALSE(probe_listener->GetPrefetchResponseCompletedCalled());
EXPECT_FALSE(probe_listener->GetPrefetchResponseErrorCalled());
EXPECT_TRUE(probe_listener->GetPrefetchResponseServerErrorCalled());
NavigateInitiatedByBrowser(GURL("https://example.com?b=1"));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com?b=1")));
}
TEST_P(PrefetchServiceTest, FailureCase_Browser_NetError) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
std::nullopt));
std::unique_ptr<ProbePrefetchRequestStatusListener> probe_listener =
std::make_unique<ProbePrefetchRequestStatusListener>();
std::unique_ptr<content::PrefetchRequestStatusListener>
request_status_listener =
std::make_unique<TestablePrefetchRequestStatusListener>(
probe_listener->GetWeakPtr());
EXPECT_FALSE(probe_listener->GetPrefetchStartFailedCalled());
EXPECT_FALSE(probe_listener->GetPrefetchResponseCompletedCalled());
EXPECT_FALSE(probe_listener->GetPrefetchResponseErrorCalled());
EXPECT_FALSE(probe_listener->GetPrefetchResponseServerErrorCalled());
std::unique_ptr<content::PrefetchHandle> handle =
MakePrefetchFromBrowserContext(GURL("https://example.com?c=1"),
std::nullopt, {},
std::move(request_status_listener));
task_environment()->RunUntilIdle();
VerifyCommonRequestStateForBrowserContextPrefetch(
GURL("https://example.com?c=1"), {.use_prefetch_proxy = false});
MakeResponseAndWait(net::HTTP_OK, net::ERR_FAILED, kHTMLMimeType,
false,
{{"X-Testing", "Hello World"}}, kHTMLBody);
EXPECT_FALSE(probe_listener->GetPrefetchStartFailedCalled());
EXPECT_FALSE(probe_listener->GetPrefetchResponseCompletedCalled());
EXPECT_TRUE(probe_listener->GetPrefetchResponseErrorCalled());
EXPECT_FALSE(probe_listener->GetPrefetchResponseServerErrorCalled());
ExpectPrefetchFailedNetError(histogram_tester, net::ERR_FAILED,
blink::mojom::SpeculationEagerness::kImmediate,
false,
true);
NavigateInitiatedByBrowser(GURL("https://example.com?c=1"));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com?c=1")));
}
TEST_P(PrefetchServiceTest, FailureCase_Browser_NotEligibleNonHttps) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
std::nullopt));
std::unique_ptr<ProbePrefetchRequestStatusListener> probe_listener =
std::make_unique<ProbePrefetchRequestStatusListener>();
std::unique_ptr<content::PrefetchRequestStatusListener>
request_status_listener =
std::make_unique<TestablePrefetchRequestStatusListener>(
probe_listener->GetWeakPtr());
EXPECT_FALSE(probe_listener->GetPrefetchStartFailedCalled());
EXPECT_FALSE(probe_listener->GetPrefetchResponseCompletedCalled());
EXPECT_FALSE(probe_listener->GetPrefetchResponseErrorCalled());
EXPECT_FALSE(probe_listener->GetPrefetchResponseServerErrorCalled());
std::unique_ptr<content::PrefetchHandle> handle =
MakePrefetchFromBrowserContext(GURL("http://example.com"), std::nullopt,
{}, std::move(request_status_listener));
task_environment()->RunUntilIdle();
EXPECT_TRUE(probe_listener->GetPrefetchStartFailedCalled());
EXPECT_FALSE(probe_listener->GetPrefetchResponseCompletedCalled());
EXPECT_FALSE(probe_listener->GetPrefetchResponseErrorCalled());
EXPECT_FALSE(probe_listener->GetPrefetchResponseServerErrorCalled());
EXPECT_EQ(RequestCount(), 0);
histogram_tester.ExpectUniqueSample(
"Preloading.Prefetch.PrefetchStatus",
PrefetchStatus::kPrefetchIneligibleSchemeIsNotHttps, 1);
ExpectPrefetchNotEligible(
histogram_tester, PreloadingEligibility::kSchemeIsNotHttps,
false, true);
NavigateInitiatedByBrowser(GURL("http://example.com"));
EXPECT_FALSE(GetPrefetchToServe(GURL("http://example.com")));
}
TEST_P(PrefetchServiceTest, BrowserContextPrefetchRespectsTTL) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
std::nullopt));
net::HttpRequestHeaders request_additional_headers = {};
request_additional_headers.SetHeader("foo", "bar");
request_additional_headers.SetHeader("foo1", "bar1");
std::unique_ptr<ProbePrefetchRequestStatusListener> probe_listener =
std::make_unique<ProbePrefetchRequestStatusListener>();
std::unique_ptr<content::PrefetchRequestStatusListener>
request_status_listener =
std::make_unique<TestablePrefetchRequestStatusListener>(
probe_listener->GetWeakPtr());
std::unique_ptr<content::PrefetchHandle> handle =
MakePrefetchFromBrowserContext(GURL("https://example.com?b=1"),
std::nullopt, request_additional_headers,
std::move(request_status_listener),
base::Minutes(5));
task_environment()->RunUntilIdle();
VerifyCommonRequestStateForBrowserContextPrefetch(
GURL("https://example.com?b=1"),
{.use_prefetch_proxy = false,
.additional_headers = request_additional_headers});
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
false,
{{"X-Testing", "Hello World"}}, kHTMLBody);
NavigateInitiatedByBrowser(GURL("https://example.com?b=1"));
PrefetchServingHandle serving_handle =
GetPrefetchToServe(GURL("https://example.com?b=1"), std::nullopt);
EXPECT_EQ(serving_handle.GetPrefetchContainer()->GetURL(),
GURL("https://example.com?b=1"));
task_environment()->FastForwardBy(base::Minutes(5));
EXPECT_FALSE(serving_handle);
}
TEST_P(PrefetchServiceTest, PrefetchDoesNotMatchIfDocumentTokenDoesNotMatch) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
MakePrefetchOnMainFrame(GURL("https://example.com"));
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = true});
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}}, kHTMLBody);
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody));
NavigateInitiatedByRenderer(GURL("https://example.com"));
blink::DocumentToken different_document_token;
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com"),
different_document_token));
}
TEST_P(PrefetchServiceTest, SuccessCase_Embedder) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
std::nullopt));
const PrefetchType prefetch_type = PrefetchType(
PreloadingTriggerType::kEmbedder, false);
auto handle =
MakePrefetchFromEmbedder(GURL("https://example.com"), prefetch_type);
task_environment()->RunUntilIdle();
VerifyCommonRequestStateForWebContentsPrefetch(GURL("https://example.com"),
{.use_prefetch_proxy = false});
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
false,
{{"X-Testing", "Hello World"}}, kHTMLBody);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.RespCode", net::HTTP_OK, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.NetError", net::OK, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.BodyLength", std::size(kHTMLBody), 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.TotalTime", kTotalTimeDuration, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.ConnectTime", kConnectTimeDuration, 1);
NavigateInitiatedByBrowser(GURL("https://example.com"));
ExpectServingReaderSuccess(
GetPrefetchToServe(GURL("https://example.com"), std::nullopt));
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.AfterClick.RedirectChainSize", 1, 1);
histogram_tester.ExpectUniqueSample(
base::StringPrintf(
"Prefetch.PrefetchMatchingBlockedNavigation.PerMatchingCandidate.%s",
GetMetricsSuffixTriggerTypeAndEagerness(
prefetch_type,
test::kPreloadingEmbedderHistgramSuffixForTesting)),
false, 1);
histogram_tester.ExpectUniqueSample(
base::StrCat({"Prefetch.PrefetchPotentialCandidateServingResult."
"PerMatchingCandidate.",
GetMetricsSuffixTriggerTypeAndEagerness(
prefetch_type,
test::kPreloadingEmbedderHistgramSuffixForTesting)}),
PrefetchPotentialCandidateServingResult::kServed, 1);
}
TEST_P(PrefetchServiceTest,
PrefetchDoesNotMatchIfDocumentTokenDoesNotMatch_Embedder) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
std::nullopt));
auto handle =
MakePrefetchFromEmbedder(GURL("https://example.com"),
PrefetchType(PreloadingTriggerType::kEmbedder,
false));
task_environment()->RunUntilIdle();
VerifyCommonRequestStateForWebContentsPrefetch(GURL("https://example.com"),
{.use_prefetch_proxy = false});
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
false,
{{"X-Testing", "Hello World"}}, kHTMLBody);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.RespCode", net::HTTP_OK, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.NetError", net::OK, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.BodyLength", std::size(kHTMLBody), 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.TotalTime", kTotalTimeDuration, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.ConnectTime", kConnectTimeDuration, 1);
NavigateInitiatedByBrowser(GURL("https://example.com"));
EXPECT_FALSE(
GetPrefetchToServe(GURL("https://example.com"), MainDocumentToken()));
}
TEST_P(PrefetchServiceTest, NoPrefetchingPreloadingDisabled) {
base::HistogramTester histogram_tester;
std::unique_ptr<MockPrefetchServiceDelegate> mock_prefetch_service_delegate =
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
0);
EXPECT_CALL(*mock_prefetch_service_delegate, IsSomePreloadingEnabled)
.Times(1)
.WillOnce(testing::Return(PreloadingEligibility::kPreloadingDisabled));
MakePrefetchService(std::move(mock_prefetch_service_delegate));
MakePrefetchOnMainFrame(GURL("https://example.com"));
EXPECT_EQ(RequestCount(), 0);
histogram_tester.ExpectUniqueSample(
"Preloading.Prefetch.PrefetchStatus",
PrefetchStatus::kPrefetchIneligiblePreloadingDisabled, 1);
ExpectPrefetchNotEligible(histogram_tester,
PreloadingEligibility::kPreloadingDisabled);
NavigateInitiatedByRenderer(GURL("https://example.com"));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetrics(PrefetchStatus::kPrefetchIneligiblePreloadingDisabled);
}
TEST_P(PrefetchServiceTest, NoPrefetchingDomainNotInAllowList) {
base::HistogramTester histogram_tester;
std::unique_ptr<MockPrefetchServiceDelegate> mock_prefetch_service_delegate =
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
0);
EXPECT_CALL(*mock_prefetch_service_delegate,
IsDomainInPrefetchAllowList(testing::_))
.Times(1)
.WillOnce(testing::Return(false));
MakePrefetchService(std::move(mock_prefetch_service_delegate));
MakePrefetchOnMainFrame(GURL("https://example.com"));
EXPECT_EQ(RequestCount(), 0);
ExpectPrefetchNotEligible(histogram_tester,
PreloadingEligibility::kUnspecified);
NavigateInitiatedByRenderer(GURL("https://example.com"));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com")));
std::optional<PrefetchServingPageMetrics> serving_page_metrics =
GetMetricsForMostRecentNavigation();
ASSERT_TRUE(serving_page_metrics);
EXPECT_FALSE(serving_page_metrics->prefetch_status);
}
class PrefetchServiceAllowAllDomainsTest
: public PrefetchServiceTestBase,
public WithPrefetchRearchParam,
public ::testing::WithParamInterface<PrefetchRearchParam> {
public:
PrefetchServiceAllowAllDomainsTest() : WithPrefetchRearchParam(GetParam()) {}
void InitScopedFeatureList() override {
InitBaseParams();
InitRearchFeatures();
scoped_feature_list_.InitWithFeaturesAndParameters(
{{features::kPrefetchUseContentRefactor,
{{"ineligible_decoy_request_probability", "0"},
{"prefetch_container_lifetime_s", "-1"},
{"allow_all_domains", "true"}}}},
{});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
INSTANTIATE_TEST_SUITE_P(ParametrizedTests,
PrefetchServiceAllowAllDomainsTest,
testing::ValuesIn(PrefetchRearchParam::Params()));
TEST_P(PrefetchServiceAllowAllDomainsTest, AllowAllDomains) {
base::HistogramTester histogram_tester;
std::unique_ptr<MockPrefetchServiceDelegate> mock_prefetch_service_delegate =
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>();
EXPECT_CALL(*mock_prefetch_service_delegate,
IsDomainInPrefetchAllowList(testing::_))
.Times(0);
MakePrefetchService(std::move(mock_prefetch_service_delegate));
MakePrefetchOnMainFrame(GURL("https://example.com"));
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = true});
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}}, kHTMLBody);
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody));
NavigateInitiatedByRenderer(GURL("https://example.com"));
ExpectServingReaderSuccess(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetricsSuccess();
}
class PrefetchServiceAllowAllDomainsForExtendedPreloadingTest
: public PrefetchServiceTestBase,
public WithPrefetchRearchParam,
public ::testing::WithParamInterface<PrefetchRearchParam> {
public:
PrefetchServiceAllowAllDomainsForExtendedPreloadingTest()
: WithPrefetchRearchParam(GetParam()) {}
void InitScopedFeatureList() override {
InitBaseParams();
InitRearchFeatures();
scoped_feature_list_.InitWithFeaturesAndParameters(
{{features::kPrefetchUseContentRefactor,
{{"ineligible_decoy_request_probability", "0"},
{"prefetch_container_lifetime_s", "-1"},
{"allow_all_domains_for_extended_preloading", "true"}}}},
{});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
INSTANTIATE_TEST_SUITE_P(
ParametrizedTests,
PrefetchServiceAllowAllDomainsForExtendedPreloadingTest,
testing::ValuesIn(PrefetchRearchParam::Params()));
TEST_P(PrefetchServiceAllowAllDomainsForExtendedPreloadingTest,
ExtendedPreloadingEnabled) {
base::HistogramTester histogram_tester;
std::unique_ptr<MockPrefetchServiceDelegate> mock_prefetch_service_delegate =
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>();
EXPECT_CALL(*mock_prefetch_service_delegate, IsExtendedPreloadingEnabled)
.Times(1)
.WillOnce(testing::Return(true));
EXPECT_CALL(*mock_prefetch_service_delegate,
IsDomainInPrefetchAllowList(testing::_))
.Times(0);
MakePrefetchService(std::move(mock_prefetch_service_delegate));
MakePrefetchOnMainFrame(GURL("https://example.com"));
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = true});
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}}, kHTMLBody);
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody));
NavigateInitiatedByRenderer(GURL("https://example.com"));
ExpectServingReaderSuccess(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetricsSuccess();
}
TEST_P(PrefetchServiceAllowAllDomainsForExtendedPreloadingTest,
ExtendedPreloadingDisabled) {
base::HistogramTester histogram_tester;
std::unique_ptr<MockPrefetchServiceDelegate> mock_prefetch_service_delegate =
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
0);
EXPECT_CALL(*mock_prefetch_service_delegate, IsExtendedPreloadingEnabled)
.Times(1)
.WillOnce(testing::Return(false));
EXPECT_CALL(*mock_prefetch_service_delegate,
IsDomainInPrefetchAllowList(testing::_))
.Times(1)
.WillOnce(testing::Return(false));
MakePrefetchService(std::move(mock_prefetch_service_delegate));
MakePrefetchOnMainFrame(GURL("https://example.com"));
EXPECT_EQ(RequestCount(), 0);
ExpectPrefetchNotEligible(histogram_tester,
PreloadingEligibility::kUnspecified);
NavigateInitiatedByRenderer(GURL("https://example.com"));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com")));
std::optional<PrefetchServingPageMetrics> serving_page_metrics =
GetMetricsForMostRecentNavigation();
ASSERT_TRUE(serving_page_metrics);
EXPECT_FALSE(serving_page_metrics->prefetch_status);
}
TEST_P(PrefetchServiceTest, NonProxiedPrefetchDoesNotRequireAllowList) {
NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL("https://example.com/referrer"));
base::HistogramTester histogram_tester;
std::unique_ptr<MockPrefetchServiceDelegate> mock_prefetch_service_delegate =
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>();
ON_CALL(*mock_prefetch_service_delegate, IsExtendedPreloadingEnabled)
.WillByDefault(testing::Return(false));
ON_CALL(*mock_prefetch_service_delegate,
IsDomainInPrefetchAllowList(testing::_))
.WillByDefault(testing::Return(false));
MakePrefetchService(std::move(mock_prefetch_service_delegate));
MakePrefetchOnMainFrame(
GURL("https://example.com"),
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false,
blink::mojom::SpeculationEagerness::kImmediate));
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = false});
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
false,
{{"X-Testing", "Hello World"}}, kHTMLBody);
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody));
NavigateInitiatedByRenderer(GURL("https://example.com"));
ExpectServingReaderSuccess(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetricsSuccess(false);
}
TEST_P(PrefetchServiceTest, NotEligibleHostnameNonUnique) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
PrefetchService::SetHostNonUniqueFilterForTesting(
[](std::string_view) { return true; });
MakePrefetchOnMainFrame(GURL("https://example.com"));
EXPECT_EQ(RequestCount(), 0);
histogram_tester.ExpectUniqueSample(
"Preloading.Prefetch.PrefetchStatus",
PrefetchStatus::kPrefetchIneligibleHostIsNonUnique, 1);
ExpectPrefetchNotEligible(histogram_tester,
PreloadingEligibility::kHostIsNonUnique);
NavigateInitiatedByRenderer(GURL("https://example.com"));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetrics(PrefetchStatus::kPrefetchIneligibleHostIsNonUnique);
}
TEST_P(PrefetchServiceTest, NotEligibleDataSaverEnabled) {
base::HistogramTester histogram_tester;
std::unique_ptr<MockPrefetchServiceDelegate> mock_prefetch_service_delegate =
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
0);
EXPECT_CALL(*mock_prefetch_service_delegate, IsSomePreloadingEnabled)
.Times(1)
.WillOnce(testing::Return(PreloadingEligibility::kDataSaverEnabled));
MakePrefetchService(std::move(mock_prefetch_service_delegate));
MakePrefetchOnMainFrame(GURL("https://example.com"));
EXPECT_EQ(RequestCount(), 0);
histogram_tester.ExpectUniqueSample(
"Preloading.Prefetch.PrefetchStatus",
PrefetchStatus::kPrefetchIneligibleDataSaverEnabled, 1);
ExpectPrefetchNotEligible(histogram_tester,
PreloadingEligibility::kDataSaverEnabled);
NavigateInitiatedByRenderer(GURL("https://example.com"));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetrics(PrefetchStatus::kPrefetchIneligibleDataSaverEnabled);
}
TEST_P(PrefetchServiceTest, NotEligibleNonHttps) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
MakePrefetchOnMainFrame(GURL("http://example.com"));
EXPECT_EQ(RequestCount(), 0);
histogram_tester.ExpectUniqueSample(
"Preloading.Prefetch.PrefetchStatus",
PrefetchStatus::kPrefetchIneligibleSchemeIsNotHttps, 1);
ExpectPrefetchNotEligible(histogram_tester,
PreloadingEligibility::kSchemeIsNotHttps);
NavigateInitiatedByRenderer(GURL("http://example.com"));
EXPECT_FALSE(GetPrefetchToServe(GURL("http://example.com")));
ExpectServingMetrics(PrefetchStatus::kPrefetchIneligibleSchemeIsNotHttps);
}
TEST_P(PrefetchServiceTest, NotEligiblePrefetchProxyNotAvailable) {
base::HistogramTester histogram_tester;
std::unique_ptr<MockPrefetchServiceDelegate> mock_prefetch_service_delegate =
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>();
EXPECT_CALL(*mock_prefetch_service_delegate, GetDefaultPrefetchProxyHost)
.Times(1)
.WillOnce(testing::Return(GURL("")));
MakePrefetchService(std::move(mock_prefetch_service_delegate));
MakePrefetchOnMainFrame(GURL("https://example.com"));
EXPECT_EQ(RequestCount(), 0);
histogram_tester.ExpectUniqueSample(
"Preloading.Prefetch.PrefetchStatus",
PrefetchStatus::kPrefetchIneligiblePrefetchProxyNotAvailable, 1);
ExpectPrefetchNotEligible(histogram_tester,
PreloadingEligibility::kPrefetchProxyNotAvailable);
NavigateInitiatedByRenderer(GURL("https://example.com"));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetrics(
PrefetchStatus::kPrefetchIneligiblePrefetchProxyNotAvailable);
}
TEST_P(PrefetchServiceTest,
EligiblePrefetchProxyNotAvailableNonProxiedPrefetch) {
base::HistogramTester histogram_tester;
std::unique_ptr<MockPrefetchServiceDelegate> mock_prefetch_service_delegate =
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>();
EXPECT_CALL(*mock_prefetch_service_delegate, GetDefaultPrefetchProxyHost)
.Times(1)
.WillOnce(testing::Return(GURL("")));
MakePrefetchService(std::move(mock_prefetch_service_delegate));
MakePrefetchOnMainFrame(
GURL("https://example.com"),
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false,
blink::mojom::SpeculationEagerness::kImmediate));
VerifyCommonRequestState(GURL("https://example.com"));
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
false,
{{"X-Testing", "Hello World"}}, kHTMLBody);
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody));
NavigateInitiatedByRenderer(GURL("https://example.com"));
ExpectServingReaderSuccess(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetricsSuccess(false);
}
TEST_P(PrefetchServiceTest, NotEligibleOriginWithinRetryAfterWindow) {
base::HistogramTester histogram_tester;
std::unique_ptr<MockPrefetchServiceDelegate> mock_prefetch_service_delegate =
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>();
EXPECT_CALL(*mock_prefetch_service_delegate,
IsOriginOutsideRetryAfterWindow(GURL("https://example.com")))
.Times(1)
.WillOnce(testing::Return(false));
MakePrefetchService(std::move(mock_prefetch_service_delegate));
MakePrefetchOnMainFrame(GURL("https://example.com"));
EXPECT_EQ(RequestCount(), 0);
histogram_tester.ExpectUniqueSample(
"Preloading.Prefetch.PrefetchStatus",
PrefetchStatus::kPrefetchIneligibleRetryAfter, 1);
ExpectPrefetchNotEligible(histogram_tester,
PreloadingEligibility::kRetryAfter);
NavigateInitiatedByRenderer(GURL("https://example.com"));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetrics(PrefetchStatus::kPrefetchIneligibleRetryAfter);
}
TEST_P(PrefetchServiceTest, EligibleNonHttpsNonProxiedPotentiallyTrustworthy) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
MakePrefetchOnMainFrame(
GURL("https://localhost"),
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false,
blink::mojom::SpeculationEagerness::kImmediate));
VerifyCommonRequestState(GURL("https://localhost"));
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
false,
{{"X-Testing", "Hello World"}}, kHTMLBody);
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody));
NavigateInitiatedByRenderer(GURL("https://localhost"));
ExpectServingReaderSuccess(GetPrefetchToServe(GURL("https://localhost")));
ExpectServingMetricsSuccess(false);
}
TEST_P(PrefetchServiceTest, NotEligibleServiceWorkerRegistered) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
service_worker_context_->AddRegistrationToRegisteredStorageKeys(
blink::StorageKey::CreateFromStringForTesting("https://example.com"));
service_worker_context_->AddServiceWorkerScope(
GURL("https://example.com"),
ServiceWorkerCapability::SERVICE_WORKER_WITH_FETCH_HANDLER);
MakePrefetchOnMainFrame(GURL("https://example.com"));
EXPECT_EQ(RequestCount(), 0);
histogram_tester.ExpectUniqueSample(
"Preloading.Prefetch.PrefetchStatus",
PrefetchStatus::kPrefetchIneligibleUserHasServiceWorker, 1);
ExpectPrefetchNotEligible(histogram_tester,
PreloadingEligibility::kUserHasServiceWorker);
NavigateInitiatedByRenderer(GURL("https://example.com"));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetrics(PrefetchStatus::kPrefetchIneligibleUserHasServiceWorker);
}
TEST_P(PrefetchServiceTest,
NotEligibleServiceWorkerRegisteredServiceWorkerCheckUKM) {
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
service_worker_context_->AddRegistrationToRegisteredStorageKeys(
blink::StorageKey::CreateFromStringForTesting("https://example.com"));
service_worker_context_->AddServiceWorkerScope(
GURL("https://example.com"),
ServiceWorkerCapability::SERVICE_WORKER_WITH_FETCH_HANDLER);
service_worker_context_->SetServiceWorkerCheckDuration(
base::Microseconds(kServiceWorkerCheckDuration));
MakePrefetchOnMainFrame(
GURL("https://example.com"),
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false,
blink::mojom::SpeculationEagerness::kImmediate));
EXPECT_EQ(RequestCount(), 0);
NavigateInitiatedByRenderer(GURL("https://example.com"));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com")));
ForceLogsUploadAndGetUkmId();
using UkmEntry = ukm::builders::Preloading_Attempt;
auto actual_attempts = test_ukm_recorder()->GetEntries(
UkmEntry::kEntryName,
{
UkmEntry::kPrefetchServiceWorkerRegisteredCheckName,
UkmEntry::kPrefetchServiceWorkerRegisteredForURLCheckDurationName,
});
EXPECT_EQ(actual_attempts.size(), 1u);
ASSERT_TRUE(actual_attempts[0].metrics.count(
UkmEntry::kPrefetchServiceWorkerRegisteredCheckName));
EXPECT_EQ(actual_attempts[0]
.metrics[UkmEntry::kPrefetchServiceWorkerRegisteredCheckName],
static_cast<int64_t>(
PreloadingAttemptImpl::ServiceWorkerRegisteredCheck::kPath));
ASSERT_TRUE(actual_attempts[0].metrics.count(
UkmEntry::kPrefetchServiceWorkerRegisteredForURLCheckDurationName));
EXPECT_EQ(
actual_attempts[0].metrics
[UkmEntry::kPrefetchServiceWorkerRegisteredForURLCheckDurationName],
ukm::GetExponentialBucketMin(
kServiceWorkerCheckDuration,
PreloadingAttemptImpl::
kServiceWorkerRegisteredCheckDurationBucketSpacing));
}
TEST_P(PrefetchServiceTest, EligibleServiceWorkerNotRegistered) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
service_worker_context_->AddRegistrationToRegisteredStorageKeys(
blink::StorageKey::CreateFromStringForTesting("https://other.com"));
service_worker_context_->AddServiceWorkerScope(
GURL("https://other.com"),
ServiceWorkerCapability::SERVICE_WORKER_WITH_FETCH_HANDLER);
MakePrefetchOnMainFrame(GURL("https://example.com"));
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = true});
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}}, kHTMLBody);
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody));
NavigateInitiatedByRenderer(GURL("https://example.com"));
ExpectServingReaderSuccess(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetricsSuccess();
}
TEST_P(PrefetchServiceTest,
EligibleServiceWorkerNotRegisteredServiceWorkerCheckUKM) {
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
service_worker_context_->AddRegistrationToRegisteredStorageKeys(
blink::StorageKey::CreateFromStringForTesting("https://other.com"));
service_worker_context_->AddServiceWorkerScope(
GURL("https://other.com"),
ServiceWorkerCapability::SERVICE_WORKER_WITH_FETCH_HANDLER);
MakePrefetchOnMainFrame(GURL("https://example.com"));
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = true});
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}}, kHTMLBody);
NavigateInitiatedByRenderer(GURL("https://example.com"));
ExpectServingReaderSuccess(GetPrefetchToServe(GURL("https://example.com")));
ForceLogsUploadAndGetUkmId();
using UkmEntry = ukm::builders::Preloading_Attempt;
auto actual_attempts = test_ukm_recorder()->GetEntries(
UkmEntry::kEntryName,
{
UkmEntry::kPrefetchServiceWorkerRegisteredCheckName,
UkmEntry::kPrefetchServiceWorkerRegisteredForURLCheckDurationName,
});
EXPECT_EQ(actual_attempts.size(), 1u);
ASSERT_TRUE(actual_attempts[0].metrics.count(
UkmEntry::kPrefetchServiceWorkerRegisteredCheckName));
EXPECT_EQ(
actual_attempts[0]
.metrics[UkmEntry::kPrefetchServiceWorkerRegisteredCheckName],
static_cast<int64_t>(
PreloadingAttemptImpl::ServiceWorkerRegisteredCheck::kOriginOnly));
ASSERT_TRUE(actual_attempts[0].metrics.count(
UkmEntry::kPrefetchServiceWorkerRegisteredForURLCheckDurationName));
EXPECT_EQ(
actual_attempts[0].metrics
[UkmEntry::kPrefetchServiceWorkerRegisteredForURLCheckDurationName],
ukm::GetExponentialBucketMin(
0, PreloadingAttemptImpl::
kServiceWorkerRegisteredCheckDurationBucketSpacing));
}
TEST_P(PrefetchServiceTest, NotEligibleServiceWorkerNoFetchHandlerRegistered) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
service_worker_context_->AddRegistrationToRegisteredStorageKeys(
blink::StorageKey::CreateFromStringForTesting("https://example.com"));
service_worker_context_->AddServiceWorkerScope(
GURL("https://example.com"),
ServiceWorkerCapability::SERVICE_WORKER_NO_FETCH_HANDLER);
MakePrefetchOnMainFrame(GURL("https://example.com"));
EXPECT_EQ(RequestCount(), 0);
histogram_tester.ExpectUniqueSample(
"Preloading.Prefetch.PrefetchStatus",
PrefetchStatus::kPrefetchIneligibleUserHasServiceWorkerNoFetchHandler, 1);
ExpectPrefetchNotEligible(
histogram_tester,
PreloadingEligibility::kUserHasServiceWorkerNoFetchHandler);
NavigateInitiatedByRenderer(GURL("https://example.com"));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetrics(
PrefetchStatus::kPrefetchIneligibleUserHasServiceWorkerNoFetchHandler);
}
TEST_P(PrefetchServiceTest,
NotEligibleServiceWorkerNoFetchHandlerRegisteredServiceWorkerCheckUKM) {
ukm::TestAutoSetUkmRecorder ukm_recorder;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
service_worker_context_->AddRegistrationToRegisteredStorageKeys(
blink::StorageKey::CreateFromStringForTesting("https://example.com"));
service_worker_context_->AddServiceWorkerScope(
GURL("https://example.com"),
ServiceWorkerCapability::SERVICE_WORKER_NO_FETCH_HANDLER);
service_worker_context_->SetServiceWorkerCheckDuration(
base::Microseconds(kServiceWorkerCheckDuration));
MakePrefetchOnMainFrame(
GURL("https://example.com"),
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false,
blink::mojom::SpeculationEagerness::kImmediate));
EXPECT_EQ(RequestCount(), 0);
NavigateInitiatedByRenderer(GURL("https://example.com"));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com")));
ForceLogsUploadAndGetUkmId();
using UkmEntry = ukm::builders::Preloading_Attempt;
auto actual_attempts = test_ukm_recorder()->GetEntries(
UkmEntry::kEntryName,
{
UkmEntry::kPrefetchServiceWorkerRegisteredCheckName,
UkmEntry::kPrefetchServiceWorkerRegisteredForURLCheckDurationName,
});
EXPECT_EQ(actual_attempts.size(), 1u);
ASSERT_TRUE(actual_attempts[0].metrics.count(
UkmEntry::kPrefetchServiceWorkerRegisteredCheckName));
EXPECT_EQ(actual_attempts[0]
.metrics[UkmEntry::kPrefetchServiceWorkerRegisteredCheckName],
static_cast<int64_t>(
PreloadingAttemptImpl::ServiceWorkerRegisteredCheck::kPath));
ASSERT_TRUE(actual_attempts[0].metrics.count(
UkmEntry::kPrefetchServiceWorkerRegisteredForURLCheckDurationName));
EXPECT_EQ(
actual_attempts[0].metrics
[UkmEntry::kPrefetchServiceWorkerRegisteredForURLCheckDurationName],
ukm::GetExponentialBucketMin(
kServiceWorkerCheckDuration,
PreloadingAttemptImpl::
kServiceWorkerRegisteredCheckDurationBucketSpacing));
}
TEST_P(PrefetchServiceTest, EligibleServiceWorkerNotRegisteredAtThisPath) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
service_worker_context_->AddRegistrationToRegisteredStorageKeys(
blink::StorageKey::CreateFromStringForTesting("https://example.com"));
service_worker_context_->AddServiceWorkerScope(
GURL("https://example.com/sw"),
ServiceWorkerCapability::SERVICE_WORKER_WITH_FETCH_HANDLER);
MakePrefetchOnMainFrame(GURL("https://example.com/non_sw/index.html"));
VerifyCommonRequestState(GURL("https://example.com/non_sw/index.html"),
{.use_prefetch_proxy = true});
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}}, kHTMLBody);
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody));
NavigateInitiatedByRenderer(GURL("https://example.com/non_sw/index.html"));
ExpectServingReaderSuccess(
GetPrefetchToServe(GURL("https://example.com/non_sw/index.html")));
ExpectServingMetricsSuccess();
}
TEST_P(PrefetchServiceTest, NotEligibleUserHasCookies) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
ASSERT_TRUE(SetCookie(GURL("https://example.com"), "testing"));
MakePrefetchOnMainFrame(GURL("https://example.com"));
EXPECT_EQ(RequestCount(), 0);
histogram_tester.ExpectUniqueSample(
"Preloading.Prefetch.PrefetchStatus",
PrefetchStatus::kPrefetchIneligibleUserHasCookies, 1);
ExpectPrefetchNotEligible(histogram_tester,
PreloadingEligibility::kUserHasCookies);
NavigateInitiatedByRenderer(GURL("https://example.com"));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetrics(PrefetchStatus::kPrefetchIneligibleUserHasCookies);
}
TEST_P(PrefetchServiceTest, EligibleUserHasCookiesForDifferentUrl) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
ASSERT_TRUE(SetCookie(GURL("https://other.com"), "testing"));
MakePrefetchOnMainFrame(GURL("https://example.com"));
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = true});
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}}, kHTMLBody);
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody));
NavigateInitiatedByRenderer(GURL("https://example.com"));
ExpectServingReaderSuccess(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetricsSuccess();
}
TEST_P(PrefetchServiceTest, EligibleSameOriginPrefetchCanHaveExistingCookies) {
NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL("https://example.com/referrer"));
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
ASSERT_TRUE(SetCookie(GURL("https://example.com"), "testing"));
MakePrefetchOnMainFrame(
GURL("https://example.com"),
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false,
blink::mojom::SpeculationEagerness::kImmediate));
VerifyCommonRequestState(GURL("https://example.com"));
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
false,
{{"X-Testing", "Hello World"}}, kHTMLBody);
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody));
NavigateInitiatedByRenderer(GURL("https://example.com"));
ExpectServingReaderSuccess(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetricsSuccess(false);
}
TEST_P(PrefetchServiceTest,
DISABLED_CHROMEOS(FailedCookiesChangedAfterPrefetchStarted)) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
MakePrefetchOnMainFrame(GURL("https://example.com"));
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = true});
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}}, kHTMLBody);
ASSERT_TRUE(SetCookie(GURL("https://example.com"), "testing"));
task_environment()->RunUntilIdle();
NavigateInitiatedByRenderer(GURL("https://example.com"));
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.RespCode", net::HTTP_OK, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.NetError", net::OK, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.BodyLength", std::size(kHTMLBody), 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.TotalTime", kTotalTimeDuration, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.ConnectTime", kConnectTimeDuration, 1);
std::optional<PrefetchReferringPageMetrics> referring_page_metrics =
PrefetchReferringPageMetrics::GetForCurrentDocument(main_rfh());
EXPECT_EQ(referring_page_metrics->prefetch_attempted_count, 1);
EXPECT_EQ(referring_page_metrics->prefetch_eligible_count, 1);
EXPECT_EQ(referring_page_metrics->prefetch_successful_count, 1);
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetrics(PrefetchStatus::kPrefetchNotUsedCookiesChanged,
true);
ExpectCorrectUkmLogs({.outcome = PreloadingTriggeringOutcome::kFailure,
.failure = ToPreloadingFailureReason(
PrefetchStatus::kPrefetchNotUsedCookiesChanged),
.is_accurate = true,
.expect_ready_time = true});
histogram_tester.ExpectUniqueSample(
"Preloading.Prefetch.PrefetchStatus",
PrefetchStatus::kPrefetchNotUsedCookiesChanged, 1);
}
TEST_P(PrefetchServiceTest,
DISABLED_CHROMEOS(SameOriginPrefetchIgnoresProxyRequirement)) {
NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL("https://example.com/referrer"));
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
MakePrefetchOnMainFrame(GURL("https://example.com"));
VerifyCommonRequestState(GURL("https://example.com"));
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
false,
{{"X-Testing", "Hello World"}}, kHTMLBody);
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody));
NavigateInitiatedByRenderer(GURL("https://example.com"));
ExpectServingReaderSuccess(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetricsSuccess();
}
TEST_P(PrefetchServiceTest,
DISABLED_CHROMEOS(NotEligibleSameSiteCrossOriginPrefetchRequiresProxy)) {
NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL("https://example.com/referrer"));
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
MakePrefetchOnMainFrame(GURL("https://other.example.com"));
EXPECT_EQ(RequestCount(), 0);
histogram_tester.ExpectUniqueSample(
"Preloading.Prefetch.PrefetchStatus",
PrefetchStatus::
kPrefetchIneligibleSameSiteCrossOriginPrefetchRequiredProxy,
1);
ExpectPrefetchNotEligible(
histogram_tester,
PreloadingEligibility::kSameSiteCrossOriginPrefetchRequiredProxy);
NavigateInitiatedByRenderer(GURL("https://other.example.com"));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://other.example.com")));
ExpectServingMetrics(
PrefetchStatus::
kPrefetchIneligibleSameSiteCrossOriginPrefetchRequiredProxy);
}
TEST_P(PrefetchServiceTest, NotEligibleExistingConnectProxy) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
net::ProxyInfo proxy_info;
proxy_info.UseNamedProxy("proxy.com");
TestNetworkContext network_context_for_proxy_lookup(proxy_info);
PrefetchService::SetNetworkContextForProxyLookupForTesting(
&network_context_for_proxy_lookup);
MakePrefetchOnMainFrame(GURL("https://example.com"));
EXPECT_EQ(RequestCount(), 0);
histogram_tester.ExpectUniqueSample(
"Preloading.Prefetch.PrefetchStatus",
PrefetchStatus::kPrefetchIneligibleExistingProxy, 1);
ExpectPrefetchNotEligible(histogram_tester,
PreloadingEligibility::kExistingProxy);
NavigateInitiatedByRenderer(GURL("https://example.com"));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetrics(PrefetchStatus::kPrefetchIneligibleExistingProxy);
PrefetchService::SetNetworkContextForProxyLookupForTesting(nullptr);
}
TEST_P(PrefetchServiceTest, EligibleExistingConnectProxyButSameOriginPrefetch) {
NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL("https://example.com/referrer"));
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
net::ProxyInfo proxy_info;
proxy_info.UseNamedProxy("proxy.com");
TestNetworkContext network_context_for_proxy_lookup(proxy_info);
PrefetchService::SetNetworkContextForProxyLookupForTesting(
&network_context_for_proxy_lookup);
MakePrefetchOnMainFrame(
GURL("https://example.com"),
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false,
blink::mojom::SpeculationEagerness::kImmediate));
VerifyCommonRequestState(GURL("https://example.com"));
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
false,
{{"X-Testing", "Hello World"}}, kHTMLBody);
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody));
NavigateInitiatedByRenderer(GURL("https://example.com"));
ExpectServingReaderSuccess(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetricsSuccess(false);
PrefetchService::SetNetworkContextForProxyLookupForTesting(nullptr);
}
TEST_P(PrefetchServiceTest, FailedNon2XXResponseCode) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
MakePrefetchOnMainFrame(GURL("https://example.com"));
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = true});
MakeResponseAndWait(net::HTTP_NOT_FOUND, net::OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}}, kHTMLBody);
ExpectPrefetchFailedAfterResponseReceived(
histogram_tester, net::HTTP_NOT_FOUND, std::size(kHTMLBody),
PrefetchStatus::kPrefetchFailedNon2XX);
NavigateInitiatedByRenderer(GURL("https://example.com"));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetrics(PrefetchStatus::kPrefetchFailedNon2XX,
true);
}
TEST_P(PrefetchServiceTest, FailedNetError) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
MakePrefetchOnMainFrame(GURL("https://example.com"));
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = true});
MakeResponseAndWait(net::HTTP_OK, net::ERR_FAILED, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}}, kHTMLBody);
ExpectPrefetchFailedNetError(histogram_tester, net::ERR_FAILED);
NavigateInitiatedByRenderer(GURL("https://example.com"));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetrics(PrefetchStatus::kPrefetchFailedNetError);
}
TEST_P(PrefetchServiceTest, HandleRetryAfterResponse) {
base::HistogramTester histogram_tester;
std::unique_ptr<MockPrefetchServiceDelegate> mock_prefetch_service_delegate =
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>();
EXPECT_CALL(
*mock_prefetch_service_delegate,
ReportOriginRetryAfter(GURL("https://example.com"), base::Seconds(1234)))
.Times(1);
MakePrefetchService(std::move(mock_prefetch_service_delegate));
MakePrefetchOnMainFrame(GURL("https://example.com"));
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = true});
MakeResponseAndWait(net::HTTP_SERVICE_UNAVAILABLE, net::OK, kHTMLMimeType,
true,
{{"Retry-After", "1234"}, {"X-Testing", "Hello World"}},
"");
ExpectPrefetchFailedAfterResponseReceived(
histogram_tester, net::HTTP_SERVICE_UNAVAILABLE, 0,
PrefetchStatus::kPrefetchFailedNon2XX);
NavigateInitiatedByRenderer(GURL("https://example.com"));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetrics(PrefetchStatus::kPrefetchFailedNon2XX,
true);
}
TEST_P(PrefetchServiceTest, SuccessNonHTML) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
MakePrefetchOnMainFrame(GURL("https://example.com"));
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = true});
std::string body = "fake PDF";
MakeResponseAndWait(net::HTTP_OK, net::OK, "application/pdf",
true,
{{"X-Testing", "Hello World"}}, body);
ExpectPrefetchSuccess(histogram_tester, body.size());
NavigateInitiatedByRenderer(GURL("https://example.com"));
ExpectServingReaderSuccess(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetricsSuccess();
}
TEST_P(PrefetchServiceTest,
MultipleNavigationRequestsCallGetPrefetchAfterCookieChange) {
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
MakePrefetchOnMainFrame(GURL("https://example.com"));
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = true});
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}}, kHTMLBody);
ASSERT_TRUE(SetCookie(GURL("https://example.com"), "testing"));
task_environment()->RunUntilIdle();
NavigateInitiatedByRenderer(GURL("https://example.com"));
base::test::TestFuture<PrefetchServingHandle> future_1;
GetPrefetchToServe(future_1, GURL("https://example.com"),
MainDocumentToken());
EXPECT_TRUE(future_1.IsReady());
EXPECT_FALSE(future_1.Get().GetPrefetchContainer());
NavigateInitiatedByRenderer(GURL("https://example.com"));
base::test::TestFuture<PrefetchServingHandle> future_2;
GetPrefetchToServe(future_2, GURL("https://example.com"),
MainDocumentToken());
EXPECT_TRUE(future_2.IsReady());
EXPECT_FALSE(future_2.Get().GetPrefetchContainer());
}
TEST_P(PrefetchServiceTest, NotServeableNavigationInDifferentRenderFrameHost) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
MakePrefetchOnMainFrame(GURL("https://example.com"));
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = true});
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}}, kHTMLBody);
blink::LocalFrameToken other_token(base::UnguessableToken::Create());
ASSERT_NE(other_token, main_rfh()->GetFrameToken());
blink::DocumentToken different_document_token;
ASSERT_NE(different_document_token, MainDocumentToken());
Navigate(GURL("https://example.com"),
main_rfh()->GetProcess()->GetDeprecatedID(), other_token,
different_document_token);
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com"),
different_document_token));
std::optional<PrefetchServingPageMetrics> serving_page_metrics =
GetMetricsForMostRecentNavigation();
EXPECT_FALSE(serving_page_metrics);
}
class PrefetchServiceWithHTMLOnlyTest
: public PrefetchServiceTestBase,
public WithPrefetchRearchParam,
public ::testing::WithParamInterface<PrefetchRearchParam> {
public:
PrefetchServiceWithHTMLOnlyTest() : WithPrefetchRearchParam(GetParam()) {}
void InitScopedFeatureList() override {
InitBaseParams();
InitRearchFeatures();
scoped_feature_list_.InitWithFeaturesAndParameters(
{{features::kPrefetchUseContentRefactor,
{{"ineligible_decoy_request_probability", "0"},
{"prefetch_container_lifetime_s", "-1"},
{"html_only", "true"}}}},
{});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
INSTANTIATE_TEST_SUITE_P(ParametrizedTests,
PrefetchServiceWithHTMLOnlyTest,
testing::ValuesIn(PrefetchRearchParam::Params()));
TEST_P(PrefetchServiceWithHTMLOnlyTest, FailedNonHTMLWithHTMLOnly) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
MakePrefetchOnMainFrame(GURL("https://example.com"));
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = true});
std::string body = "fake PDF";
MakeResponseAndWait(net::HTTP_OK, net::OK, "application/pdf",
true,
{{"X-Testing", "Hello World"}}, body);
ExpectPrefetchFailedAfterResponseReceived(
histogram_tester, net::HTTP_OK, body.size(),
PrefetchStatus::kPrefetchFailedMIMENotSupported);
NavigateInitiatedByRenderer(GURL("https://example.com"));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetrics(PrefetchStatus::kPrefetchFailedMIMENotSupported,
true);
}
class PrefetchServiceAlwaysMakeDecoyRequestTest
: public PrefetchServiceTestBase,
public WithPrefetchRearchParam,
public ::testing::WithParamInterface<PrefetchRearchParam> {
public:
PrefetchServiceAlwaysMakeDecoyRequestTest()
: WithPrefetchRearchParam(GetParam()) {}
void InitScopedFeatureList() override {
InitBaseParams();
InitRearchFeatures();
scoped_feature_list_.InitWithFeaturesAndParameters(
{{features::kPrefetchUseContentRefactor,
{{"ineligible_decoy_request_probability", "1"},
{"prefetch_container_lifetime_s", "-1"}}}},
{});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
INSTANTIATE_TEST_SUITE_P(ParametrizedTests,
PrefetchServiceAlwaysMakeDecoyRequestTest,
testing::ValuesIn(PrefetchRearchParam::Params()));
TEST_P(PrefetchServiceAlwaysMakeDecoyRequestTest, DecoyRequest) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
ASSERT_TRUE(SetCookie(GURL("https://example.com"), "testing"));
MakePrefetchOnMainFrame(GURL("https://example.com"));
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = true});
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}}, kHTMLBody);
ExpectPrefetchFailedBeforeResponseReceived(
histogram_tester, PrefetchStatus::kPrefetchIsPrivacyDecoy);
NavigateInitiatedByRenderer(GURL("https://example.com"));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetrics(PrefetchStatus::kPrefetchIsPrivacyDecoy,
true);
}
TEST_P(PrefetchServiceAlwaysMakeDecoyRequestTest,
NavigateBeforeDecoyResponseReceived) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
ASSERT_TRUE(SetCookie(GURL("https://example.com"), "testing"));
MakePrefetchOnMainFrame(GURL("https://example.com"));
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = true});
NavigateInitiatedByRenderer(GURL("https://example.com"));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com")));
ExpectCorrectUkmLogs({.outcome = PreloadingTriggeringOutcome::kUnspecified});
}
TEST_P(PrefetchServiceAlwaysMakeDecoyRequestTest,
NoDecoyRequestDisableDecoysBasedOnUserSettings) {
base::HistogramTester histogram_tester;
std::unique_ptr<MockPrefetchServiceDelegate> mock_prefetch_service_delegate =
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>();
EXPECT_CALL(*mock_prefetch_service_delegate, DisableDecoysBasedOnUserSettings)
.Times(1)
.WillOnce(testing::Return(true));
MakePrefetchService(std::move(mock_prefetch_service_delegate));
ASSERT_TRUE(SetCookie(GURL("https://example.com"), "testing"));
MakePrefetchOnMainFrame(GURL("https://example.com"));
EXPECT_EQ(RequestCount(), 0);
ExpectPrefetchNotEligible(histogram_tester,
PreloadingEligibility::kUserHasCookies);
NavigateInitiatedByRenderer(GURL("https://example.com"));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetrics(PrefetchStatus::kPrefetchIneligibleUserHasCookies);
}
TEST_P(PrefetchServiceAlwaysMakeDecoyRequestTest,
DISABLED_CHROMEOS(RedirectDecoyRequest)) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
service_worker_context_->AddRegistrationToRegisteredStorageKeys(
blink::StorageKey::CreateFromStringForTesting("https://redirect.com"));
service_worker_context_->AddServiceWorkerScope(
GURL("https://redirect.com"),
ServiceWorkerCapability::SERVICE_WORKER_WITH_FETCH_HANDLER);
MakePrefetchOnMainFrame(GURL("https://example.com"));
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = true});
VerifyFollowRedirectParams(0);
MakeSingleRedirectAndWait(GURL("https://redirect.com"));
VerifyFollowRedirectParams(1);
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}}, kHTMLBody);
ExpectPrefetchFailedBeforeResponseReceived(
histogram_tester, PrefetchStatus::kPrefetchIsPrivacyDecoy);
NavigateInitiatedByRenderer(GURL("https://example.com"));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetrics(PrefetchStatus::kPrefetchIsPrivacyDecoy,
true);
}
class PrefetchServiceIncognitoTest
: public PrefetchServiceTestBase,
public WithPrefetchRearchParam,
public ::testing::WithParamInterface<PrefetchRearchParam> {
public:
PrefetchServiceIncognitoTest() : WithPrefetchRearchParam(GetParam()) {}
void InitScopedFeatureList() override {
InitBaseParams();
InitRearchFeatures();
}
protected:
std::unique_ptr<BrowserContext> CreateBrowserContext() override {
auto browser_context = std::make_unique<TestBrowserContext>();
browser_context->set_is_off_the_record(true);
return browser_context;
}
};
INSTANTIATE_TEST_SUITE_P(ParametrizedTests,
PrefetchServiceIncognitoTest,
testing::ValuesIn(PrefetchRearchParam::Params()));
TEST_P(PrefetchServiceIncognitoTest, OffTheRecordEligible) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
MakePrefetchOnMainFrame(
GURL("https://example.com/"),
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false,
blink::mojom::SpeculationEagerness::kImmediate));
VerifyCommonRequestState(GURL("https://example.com/"));
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
false, {}, kHTMLBody);
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody));
}
TEST_P(PrefetchServiceTest, NonDefaultStoragePartition) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
test_content_browser_client_->UseOffTheRecordContextForStoragePartition(true);
MakePrefetchOnMainFrame(
GURL("https://example.com"),
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false,
blink::mojom::SpeculationEagerness::kImmediate));
EXPECT_EQ(RequestCount(), 0);
histogram_tester.ExpectUniqueSample(
"Preloading.Prefetch.PrefetchStatus",
PrefetchStatus::kPrefetchIneligibleNonDefaultStoragePartition, 1);
ExpectPrefetchNotEligible(histogram_tester,
PreloadingEligibility::kNonDefaultStoragePartition);
NavigateInitiatedByRenderer(GURL("https://example.com"));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetrics(
PrefetchStatus::kPrefetchIneligibleNonDefaultStoragePartition,
false,
false);
}
TEST_P(PrefetchServiceTest, DISABLED_CHROMEOS(StreamingURLLoaderSuccessCase)) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
MakePrefetchOnMainFrame(GURL("https://example.com"));
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = true});
SendHeadOfResponseAndWait(net::HTTP_OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}},
std::size(kHTMLBody));
NavigateInitiatedByRenderer(GURL("https://example.com"));
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.RespCode", net::HTTP_OK, 1);
histogram_tester.ExpectTotalCount("PrefetchProxy.Prefetch.Mainframe.NetError",
0);
histogram_tester.ExpectTotalCount(
"PrefetchProxy.Prefetch.Mainframe.BodyLength", 0);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.TotalTime", kTotalTimeDuration, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.ConnectTime", kConnectTimeDuration, 1);
std::optional<PrefetchReferringPageMetrics> referring_page_metrics =
PrefetchReferringPageMetrics::GetForCurrentDocument(main_rfh());
EXPECT_EQ(referring_page_metrics->prefetch_attempted_count, 1);
EXPECT_EQ(referring_page_metrics->prefetch_eligible_count, 1);
EXPECT_EQ(referring_page_metrics->prefetch_successful_count, 0);
PrefetchServingHandle serving_handle =
GetPrefetchToServe(GURL("https://example.com"));
ASSERT_TRUE(serving_handle);
EXPECT_TRUE(serving_handle.HasPrefetchStatus());
EXPECT_EQ(serving_handle.GetPrefetchStatus(),
PrefetchStatus::kPrefetchNotFinishedInTime);
EXPECT_EQ(serving_handle.GetServableState(base::TimeDelta::Max()),
PrefetchServableState::kServable);
EXPECT_TRUE(serving_handle.GetPrefetchContainer()->GetNonRedirectHead());
EXPECT_TRUE(serving_handle.GetPrefetchContainer()
->GetNonRedirectHead()
->was_in_prefetch_cache);
ExpectServingMetrics(PrefetchStatus::kPrefetchNotFinishedInTime);
SendBodyContentOfResponseAndWait(kHTMLBody);
CompleteResponseAndWait(net::OK, std::size(kHTMLBody));
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody),
blink::mojom::SpeculationEagerness::kImmediate,
true);
ExpectServingReaderSuccess(serving_handle);
ExpectServingMetricsSuccess();
}
TEST_P(PrefetchServiceTest, DISABLED_CHROMEOS(NoVarySearchSuccessCase)) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
MakePrefetchOnMainFrame(GURL("https://example.com/?a=1"));
VerifyCommonRequestState(GURL("https://example.com/?a=1"),
{.use_prefetch_proxy = true});
MakeResponseAndWait(
net::HTTP_OK, net::OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}, {"No-Vary-Search", R"(params=("a"))"}},
kHTMLBody);
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody));
NavigateInitiatedByRenderer(GURL("https://example.com"));
PrefetchServingHandle serving_handle =
GetPrefetchToServe(GURL("https://example.com"));
ExpectServingReaderSuccess(serving_handle);
EXPECT_EQ(serving_handle.GetPrefetchContainer()->GetURL(),
GURL("https://example.com/?a=1"));
ExpectServingMetricsSuccess();
}
TEST_P(PrefetchServiceTest, NoVarySearchSuccessCase_Embedder) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
std::nullopt));
auto handle =
MakePrefetchFromEmbedder(GURL("https://example.com?a=1"),
PrefetchType(PreloadingTriggerType::kEmbedder,
false));
task_environment()->RunUntilIdle();
VerifyCommonRequestStateForWebContentsPrefetch(
GURL("https://example.com?a=1"), {.use_prefetch_proxy = false});
MakeResponseAndWait(
net::HTTP_OK, net::OK, kHTMLMimeType,
false,
{{"X-Testing", "Hello World"}, {"No-Vary-Search", R"(params=("a"))"}},
kHTMLBody);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.RespCode", net::HTTP_OK, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.NetError", net::OK, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.BodyLength", std::size(kHTMLBody), 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.TotalTime", kTotalTimeDuration, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.ConnectTime", kConnectTimeDuration, 1);
NavigateInitiatedByBrowser(GURL("https://example.com"));
PrefetchServingHandle serving_handle =
GetPrefetchToServe(GURL("https://example.com"), std::nullopt);
ExpectServingReaderSuccess(serving_handle);
EXPECT_EQ(serving_handle.GetPrefetchContainer()->GetURL(),
GURL("https://example.com/?a=1"));
}
TEST_P(PrefetchServiceTest, DISABLED_CHROMEOS(PrefetchEligibleRedirect)) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
MakePrefetchOnMainFrame(GURL("https://example.com"));
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = true});
VerifyFollowRedirectParams(0);
MakeSingleRedirectAndWait(GURL("https://redirect.com"));
VerifyFollowRedirectParams(1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Redirect.Result",
PrefetchRedirectResult::kSuccessRedirectFollowed, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Redirect.NetworkContextStateTransition",
PrefetchRedirectNetworkContextTransition::kIsolatedToIsolated, 1);
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}}, kHTMLBody);
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody));
NavigateInitiatedByRenderer(GURL("https://example.com"));
ExpectServingReaderSuccess(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetricsSuccess();
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.AfterClick.RedirectChainSize", 2, 1);
}
TEST_P(PrefetchServiceTest, DISABLED_CHROMEOS(IneligibleRedirectCookies)) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
ASSERT_TRUE(SetCookie(GURL("https://redirect.com"), "testing"));
MakePrefetchOnMainFrame(GURL("https://example.com"));
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = true});
VerifyFollowRedirectParams(0);
NavigateInitiatedByRenderer(GURL("https://example.com"));
base::test::TestFuture<PrefetchServingHandle> future;
GetPrefetchToServe(future, GURL("https://example.com"), MainDocumentToken());
MakeSingleRedirectAndWait(GURL("https://redirect.com"));
VerifyFollowRedirectParams(0);
EXPECT_FALSE(future.Take());
histogram_tester.ExpectUniqueSample("PrefetchProxy.Redirect.Result",
PrefetchRedirectResult::kFailedIneligible,
1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Redirect.NetworkContextStateTransition",
PrefetchRedirectNetworkContextTransition::kIsolatedToIsolated, 1);
ExpectPrefetchFailedBeforeResponseReceived(
histogram_tester, PrefetchStatus::kPrefetchFailedIneligibleRedirect,
true);
NavigateInitiatedByRenderer(GURL("https://example.com"));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetrics(PrefetchStatus::kPrefetchFailedIneligibleRedirect);
histogram_tester.ExpectTotalCount(
"PrefetchProxy.AfterClick.RedirectChainSize", 0);
}
TEST_P(PrefetchServiceTest,
DISABLED_CHROMEOS(IneligibleRedirectServiceWorker)) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
service_worker_context_->AddRegistrationToRegisteredStorageKeys(
blink::StorageKey::CreateFromStringForTesting("https://redirect.com"));
service_worker_context_->AddServiceWorkerScope(
GURL("https://redirect.com"),
ServiceWorkerCapability::SERVICE_WORKER_WITH_FETCH_HANDLER);
MakePrefetchOnMainFrame(GURL("https://example.com"));
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = true});
VerifyFollowRedirectParams(0);
MakeSingleRedirectAndWait(GURL("https://redirect.com"));
VerifyFollowRedirectParams(0);
histogram_tester.ExpectUniqueSample("PrefetchProxy.Redirect.Result",
PrefetchRedirectResult::kFailedIneligible,
1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Redirect.NetworkContextStateTransition",
PrefetchRedirectNetworkContextTransition::kIsolatedToIsolated, 1);
ExpectPrefetchFailedBeforeResponseReceived(
histogram_tester, PrefetchStatus::kPrefetchFailedIneligibleRedirect);
NavigateInitiatedByRenderer(GURL("https://example.com"));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetrics(PrefetchStatus::kPrefetchFailedIneligibleRedirect);
histogram_tester.ExpectTotalCount(
"PrefetchProxy.AfterClick.RedirectChainSize", 0);
}
TEST_P(PrefetchServiceTest, DISABLED_CHROMEOS(InvalidRedirect)) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
MakePrefetchOnMainFrame(GURL("https://example.com"));
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = true});
VerifyFollowRedirectParams(0);
MakeSingleRedirectAndWait(GURL("https://redirect.com"), net::HTTP_OK);
VerifyFollowRedirectParams(0);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Redirect.Result",
PrefetchRedirectResult::kFailedInvalidResponseCode, 1);
histogram_tester.ExpectTotalCount(
"PrefetchProxy.Redirect.NetworkContextStateTransition", 0);
ExpectPrefetchFailedBeforeResponseReceived(
histogram_tester, PrefetchStatus::kPrefetchFailedInvalidRedirect);
NavigateInitiatedByRenderer(GURL("https://example.com"));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetrics(PrefetchStatus::kPrefetchFailedInvalidRedirect);
histogram_tester.ExpectTotalCount(
"PrefetchProxy.AfterClick.RedirectChainSize", 0);
}
TEST_P(PrefetchServiceTest,
DISABLED_CHROMEOS(PrefetchSameOriginEligibleRedirect)) {
NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL("https://example.com/referrer"));
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
MakePrefetchOnMainFrame(
GURL("https://example.com"),
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false,
blink::mojom::SpeculationEagerness::kImmediate));
VerifyCommonRequestState(GURL("https://example.com"));
VerifyFollowRedirectParams(0);
MakeSingleRedirectAndWait(GURL("https://example.com/redirect"));
VerifyFollowRedirectParams(1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Redirect.Result",
PrefetchRedirectResult::kSuccessRedirectFollowed, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Redirect.NetworkContextStateTransition",
PrefetchRedirectNetworkContextTransition::kDefaultToDefault, 1);
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
false,
{{"X-Testing", "Hello World"}}, kHTMLBody);
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody));
NavigateInitiatedByRenderer(GURL("https://example.com"));
ExpectServingReaderSuccess(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetricsSuccess(false);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.AfterClick.RedirectChainSize", 2, 1);
}
TEST_P(PrefetchServiceTest,
DISABLED_CHROMEOS(IneligibleSameSiteCrossOriginRequiresProxyRedirect)) {
NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL("https://example.com/referrer"));
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
MakePrefetchOnMainFrame(GURL("https://example.com"));
VerifyCommonRequestState(GURL("https://example.com"));
VerifyFollowRedirectParams(0);
MakeSingleRedirectAndWait(GURL("https://other.example.com/redirect"));
VerifyFollowRedirectParams(0);
histogram_tester.ExpectUniqueSample("PrefetchProxy.Redirect.Result",
PrefetchRedirectResult::kFailedIneligible,
1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Redirect.NetworkContextStateTransition",
PrefetchRedirectNetworkContextTransition::kDefaultToDefault, 1);
ExpectPrefetchFailedBeforeResponseReceived(
histogram_tester, PrefetchStatus::kPrefetchFailedIneligibleRedirect);
NavigateInitiatedByRenderer(GURL("https://example.com"));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetrics(PrefetchStatus::kPrefetchFailedIneligibleRedirect);
histogram_tester.ExpectTotalCount(
"PrefetchProxy.AfterClick.RedirectChainSize", 0);
}
TEST_P(PrefetchServiceTest,
DISABLED_CHROMEOS(RedirectDefaultToIsolatedNetworkContextTransition)) {
NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL("https://example.com/referrer"));
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
MakePrefetchOnMainFrame(
GURL("https://example.com"),
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false,
blink::mojom::SpeculationEagerness::kImmediate));
VerifyCommonRequestState(GURL("https://example.com"));
VerifyFollowRedirectParams(0);
MakeSingleRedirectAndWait(GURL("https://redirect.com"));
VerifyFollowRedirectParams(0);
ClearCompletedRequests();
VerifyCommonRequestState(GURL("https://redirect.com"));
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Redirect.Result",
PrefetchRedirectResult::kSuccessRedirectFollowed, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Redirect.NetworkContextStateTransition",
PrefetchRedirectNetworkContextTransition::kDefaultToIsolated, 1);
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
false,
{{"X-Testing", "Hello World"}}, kHTMLBody);
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody));
NavigateInitiatedByRenderer(GURL("https://example.com"));
ExpectServingReaderSuccess(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetricsSuccess(false);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.AfterClick.RedirectChainSize", 2, 1);
}
TEST_P(PrefetchServiceTest,
DISABLED_CHROMEOS(
RedirectDefaultToIsolatedNetworkContextTransitionWithProxy)) {
NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL("https://example.com/referrer"));
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
MakePrefetchOnMainFrame(GURL("https://example.com"));
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = false});
VerifyFollowRedirectParams(0);
MakeSingleRedirectAndWait(GURL("https://redirect.com"));
VerifyFollowRedirectParams(0);
ClearCompletedRequests();
VerifyCommonRequestState(GURL("https://redirect.com"),
{.use_prefetch_proxy = true});
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Redirect.Result",
PrefetchRedirectResult::kSuccessRedirectFollowed, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Redirect.NetworkContextStateTransition",
PrefetchRedirectNetworkContextTransition::kDefaultToIsolated, 1);
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
false,
{{"X-Testing", "Hello World"}}, kHTMLBody);
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody));
NavigateInitiatedByRenderer(GURL("https://example.com"));
ExpectServingReaderSuccess(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetricsSuccess();
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.AfterClick.RedirectChainSize", 2, 1);
}
TEST_P(PrefetchServiceTest,
DISABLED_CHROMEOS(RedirectIsolatedToDefaultNetworkContextTransition)) {
NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL("https://example.com/referrer"));
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
MakePrefetchOnMainFrame(
GURL("https://other.com"),
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false,
blink::mojom::SpeculationEagerness::kImmediate));
VerifyCommonRequestState(GURL("https://other.com"),
{.use_prefetch_proxy = false});
VerifyFollowRedirectParams(0);
MakeSingleRedirectAndWait(GURL("https://example.com/redirect"));
VerifyFollowRedirectParams(0);
ClearCompletedRequests();
VerifyCommonRequestState(GURL("https://example.com/redirect"),
{.use_prefetch_proxy = false});
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Redirect.Result",
PrefetchRedirectResult::kSuccessRedirectFollowed, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Redirect.NetworkContextStateTransition",
PrefetchRedirectNetworkContextTransition::kIsolatedToDefault, 1);
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
false,
{{"X-Testing", "Hello World"}}, kHTMLBody);
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody));
NavigateInitiatedByRenderer(GURL("https://other.com"));
ExpectServingReaderSuccess(GetPrefetchToServe(GURL("https://other.com")));
ExpectServingMetricsSuccess(false);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.AfterClick.RedirectChainSize", 2, 1);
}
TEST_P(PrefetchServiceTest,
DISABLED_CHROMEOS(RedirectNetworkContextTransitionBlockUntilHead)) {
NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL("https://example.com/referrer"));
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
MakePrefetchOnMainFrame(
GURL("https://example.com"),
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false,
blink::mojom::SpeculationEagerness::kImmediate));
VerifyCommonRequestState(GURL("https://example.com"));
VerifyFollowRedirectParams(0);
NavigateInitiatedByRenderer(GURL("https://example.com"));
base::test::TestFuture<PrefetchServingHandle> future;
GetPrefetchToServe(future, GURL("https://example.com"), MainDocumentToken());
EXPECT_FALSE(future.IsReady());
MakeSingleRedirectAndWait(GURL("https://redirect.com"));
VerifyFollowRedirectParams(0);
ClearCompletedRequests();
VerifyCommonRequestState(GURL("https://redirect.com"));
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Redirect.Result",
PrefetchRedirectResult::kSuccessRedirectFollowed, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Redirect.NetworkContextStateTransition",
PrefetchRedirectNetworkContextTransition::kDefaultToIsolated, 1);
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
false,
{{"X-Testing", "Hello World"}}, kHTMLBody);
PrefetchServingHandle serving_handle = future.Take();
ASSERT_TRUE(serving_handle);
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody),
blink::mojom::SpeculationEagerness::kImmediate,
true);
ExpectServingReaderSuccess(serving_handle);
ExpectServingMetricsSuccess(false);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.AfterClick.RedirectChainSize", 2, 1);
}
TEST_P(PrefetchServiceTest,
DISABLED_CHROMEOS(RedirectInsufficientReferrerPolicy)) {
NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL("https://referrer.com"));
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
blink::mojom::Referrer referrer;
referrer.url = GURL("https://referrer.com");
referrer.policy = network::mojom::ReferrerPolicy::kDefault;
MakePrefetchOnMainFrame(
GURL("https://example.com"),
PrefetchType(PreloadingTriggerType::kSpeculationRule,
true,
blink::mojom::SpeculationEagerness::kImmediate),
referrer);
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = true});
VerifyFollowRedirectParams(0);
MakeSingleRedirectAndWait(GURL("https://redirect.com"),
net::HTTP_PERMANENT_REDIRECT,
net::ReferrerPolicy::NEVER_CLEAR);
VerifyFollowRedirectParams(0);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Redirect.Result",
PrefetchRedirectResult::kFailedInsufficientReferrerPolicy, 1);
histogram_tester.ExpectTotalCount(
"PrefetchProxy.Redirect.NetworkContextStateTransition", 0);
ExpectPrefetchFailedBeforeResponseReceived(
histogram_tester, PrefetchStatus::kPrefetchFailedInvalidRedirect);
NavigateInitiatedByRenderer(GURL("https://example.com"));
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com")));
ExpectServingMetrics(PrefetchStatus::kPrefetchFailedInvalidRedirect);
histogram_tester.ExpectTotalCount(
"PrefetchProxy.AfterClick.RedirectChainSize", 0);
}
class PrefetchServiceAlwaysBlockUntilHeadTest
: public PrefetchServiceTestBase,
public WithPrefetchRearchParam,
public ::testing::WithParamInterface<
std::tuple<PrefetchRearchParam, blink::mojom::SpeculationEagerness>> {
public:
PrefetchServiceAlwaysBlockUntilHeadTest()
: WithPrefetchRearchParam(std::get<0>(GetParam())) {}
const int kPrefetchTimeout = 10000;
const int kBlockUntilHeadTimeout = 1000;
void InitScopedFeatureList() override {
InitBaseParams();
InitRearchFeatures();
scoped_feature_list_.InitWithFeaturesAndParameters(
{{features::kPrefetchUseContentRefactor,
{
{"ineligible_decoy_request_probability", "0"},
{"prefetch_container_lifetime_s", "-1"},
{"prefetch_timeout_ms", "10000"},
{"block_until_head_timeout_immediate_prefetch", "1000"},
{"block_until_head_timeout_eager_prefetch", "1000"},
{"block_until_head_timeout_moderate_prefetch", "1000"},
{"block_until_head_timeout_conservative_prefetch", "1000"},
}}},
{});
}
blink::mojom::SpeculationEagerness GetEagernessParam() {
return std::get<1>(GetParam());
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
INSTANTIATE_TEST_SUITE_P(
ParametrizedTests,
PrefetchServiceAlwaysBlockUntilHeadTest,
testing::Combine(
testing::ValuesIn(PrefetchRearchParam::Params()),
testing::Values(blink::mojom::SpeculationEagerness::kModerate,
blink::mojom::SpeculationEagerness::kConservative)));
TEST_P(PrefetchServiceAlwaysBlockUntilHeadTest,
DISABLED_CHROMEOS(BlockUntilHeadReceived)) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
const PrefetchType prefetch_type =
PrefetchType(PreloadingTriggerType::kSpeculationRule,
true, GetEagernessParam());
MakePrefetchOnMainFrame(GURL("https://example.com"), prefetch_type);
VerifyCommonRequestState(
GURL("https://example.com"),
{.use_prefetch_proxy = true,
.expected_priority = ExpectedPriorityForEagerness(GetEagernessParam())});
NavigateInitiatedByRenderer(GURL("https://example.com"));
base::test::TestFuture<PrefetchServingHandle> future;
GetPrefetchToServe(future, GURL("https://example.com"), MainDocumentToken());
EXPECT_FALSE(future.IsReady());
task_environment()->FastForwardBy(base::Milliseconds(500));
SendHeadOfResponseAndWait(net::HTTP_OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}},
std::size(kHTMLBody));
PrefetchServingHandle serving_handle = future.Take();
ASSERT_TRUE(serving_handle);
SendBodyContentOfResponseAndWait(kHTMLBody);
CompleteResponseAndWait(net::OK, std::size(kHTMLBody));
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody),
GetEagernessParam(),
true);
ExpectServingReaderSuccess(serving_handle);
ExpectServingMetricsSuccess();
std::string histogram_suffix =
GetMetricsSuffixTriggerTypeAndEagerness(prefetch_type, std::nullopt);
histogram_tester.ExpectUniqueTimeSample(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.Served.%s",
histogram_suffix),
base::Milliseconds(500), 1);
histogram_tester.ExpectTotalCount(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.NotServed.%s",
histogram_suffix),
0);
histogram_tester.ExpectUniqueSample(
base::StringPrintf(
"Prefetch.PrefetchMatchingBlockedNavigation.PerMatchingCandidate.%s",
histogram_suffix),
true, 1);
}
TEST_P(PrefetchServiceAlwaysBlockUntilHeadTest,
DISABLED_CHROMEOS(NVSBlockUntilHeadReceived)) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
network::mojom::NoVarySearchPtr no_vary_search_hint =
network::mojom::NoVarySearch::New();
no_vary_search_hint->vary_on_key_order = true;
no_vary_search_hint->search_variance =
network::mojom::SearchParamsVariance::NewNoVaryParams(
std::vector<std::string>({"a"}));
const PrefetchType prefetch_type =
PrefetchType(PreloadingTriggerType::kSpeculationRule,
true, GetEagernessParam());
MakePrefetchOnMainFrame(
GURL("https://example.com/index.html?a=5"), prefetch_type,
blink::mojom::Referrer(), std::move(no_vary_search_hint));
VerifyCommonRequestState(
GURL("https://example.com/index.html?a=5"),
{.use_prefetch_proxy = true,
.expected_priority = ExpectedPriorityForEagerness(GetEagernessParam())});
NavigateInitiatedByRenderer(GURL("https://example.com/index.html"));
base::test::TestFuture<PrefetchServingHandle> future;
GetPrefetchToServe(future, GURL("https://example.com/index.html"),
MainDocumentToken());
EXPECT_FALSE(future.IsReady());
task_environment()->FastForwardBy(base::Milliseconds(600));
SendHeadOfResponseAndWait(
net::HTTP_OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}, {"No-Vary-Search", "params=(\"a\")"}},
std::size(kHTMLBody));
PrefetchServingHandle serving_handle = future.Take();
ASSERT_TRUE(serving_handle);
SendBodyContentOfResponseAndWait(kHTMLBody);
CompleteResponseAndWait(net::OK, std::size(kHTMLBody));
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody),
GetEagernessParam(),
true);
ExpectServingReaderSuccess(serving_handle);
ExpectServingMetricsSuccess();
std::string histogram_suffix =
GetMetricsSuffixTriggerTypeAndEagerness(prefetch_type, std::nullopt);
histogram_tester.ExpectUniqueTimeSample(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.Served.%s",
histogram_suffix),
base::Milliseconds(600), 1);
histogram_tester.ExpectTotalCount(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.NotServed.%s",
histogram_suffix),
0);
histogram_tester.ExpectTotalCount(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.NotServed.%s",
histogram_suffix),
0);
histogram_tester.ExpectUniqueSample(
base::StringPrintf(
"Prefetch.PrefetchMatchingBlockedNavigation.PerMatchingCandidate.%s",
histogram_suffix),
true, 1);
histogram_tester.ExpectUniqueSample(
base::StrCat({"Prefetch.PrefetchPotentialCandidateServingResult."
"PerMatchingCandidate.",
histogram_suffix}),
PrefetchPotentialCandidateServingResult::kServed, 1);
}
TEST_P(PrefetchServiceAlwaysBlockUntilHeadTest,
DISABLED_CHROMEOS(NVSBlockUntilHeadReceivedNoMatchNoNVSHeader)) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
network::mojom::NoVarySearchPtr no_vary_search_hint =
network::mojom::NoVarySearch::New();
no_vary_search_hint->vary_on_key_order = true;
no_vary_search_hint->search_variance =
network::mojom::SearchParamsVariance::NewNoVaryParams(
std::vector<std::string>({"a"}));
const PrefetchType prefetch_type =
PrefetchType(PreloadingTriggerType::kSpeculationRule,
true, GetEagernessParam());
MakePrefetchOnMainFrame(
GURL("https://example.com/index.html?a=5"), prefetch_type,
blink::mojom::Referrer(),
std::move(no_vary_search_hint));
VerifyCommonRequestState(
GURL("https://example.com/index.html?a=5"),
{.use_prefetch_proxy = true,
.expected_priority = ExpectedPriorityForEagerness(GetEagernessParam())});
NavigateInitiatedByRenderer(GURL("https://example.com/index.html"));
base::test::TestFuture<PrefetchServingHandle> future;
GetPrefetchToServe(future, GURL("https://example.com/index.html"),
MainDocumentToken());
EXPECT_FALSE(future.IsReady());
task_environment()->FastForwardBy(base::Milliseconds(700));
SendHeadOfResponseAndWait(net::HTTP_OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}},
std::size(kHTMLBody));
PrefetchServingHandle serving_handle = future.Take();
ASSERT_FALSE(serving_handle);
SendBodyContentOfResponseAndWait(kHTMLBody);
CompleteResponseAndWait(net::OK, std::size(kHTMLBody));
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody),
GetEagernessParam());
ExpectServingMetricsSuccess();
std::string histogram_suffix =
GetMetricsSuffixTriggerTypeAndEagerness(prefetch_type, std::nullopt);
histogram_tester.ExpectTotalCount(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.Served.%s",
histogram_suffix),
0);
histogram_tester.ExpectUniqueTimeSample(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.NotServed.%s",
histogram_suffix),
base::Milliseconds(700), 1);
histogram_tester.ExpectUniqueSample(
base::StringPrintf(
"Prefetch.PrefetchMatchingBlockedNavigation.PerMatchingCandidate.%s",
histogram_suffix),
true, 1);
histogram_tester.ExpectUniqueSample(
base::StrCat({"Prefetch.PrefetchPotentialCandidateServingResult."
"PerMatchingCandidate.",
histogram_suffix}),
PrefetchPotentialCandidateServingResult::
kNotServedDeterminedNVSHeaderMismatch,
1);
}
TEST_P(PrefetchServiceAlwaysBlockUntilHeadTest,
DISABLED_CHROMEOS(NVSBlockUntilHeadReceivedNoMatchByNVSHeader)) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
network::mojom::NoVarySearchPtr no_vary_search_hint =
network::mojom::NoVarySearch::New();
no_vary_search_hint->vary_on_key_order = true;
no_vary_search_hint->search_variance =
network::mojom::SearchParamsVariance::NewNoVaryParams(
std::vector<std::string>({"a"}));
const PrefetchType prefetch_type =
PrefetchType(PreloadingTriggerType::kSpeculationRule,
true, GetEagernessParam());
MakePrefetchOnMainFrame(
GURL("https://example.com/index.html?a=5"), prefetch_type,
blink::mojom::Referrer(),
std::move(no_vary_search_hint));
VerifyCommonRequestState(
GURL("https://example.com/index.html?a=5"),
{.use_prefetch_proxy = true,
.expected_priority = ExpectedPriorityForEagerness(GetEagernessParam())});
NavigateInitiatedByRenderer(GURL("https://example.com/index.html"));
base::test::TestFuture<PrefetchServingHandle> future;
GetPrefetchToServe(future, GURL("https://example.com/index.html"),
MainDocumentToken());
EXPECT_FALSE(future.IsReady());
task_environment()->FastForwardBy(
base::Milliseconds(kAddedToURLRequestStartLatency + kHeaderLatency));
SendHeadOfResponseAndWait(
net::HTTP_OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}, {"No-Vary-Search", "params=(\"b\")"}},
std::size(kHTMLBody));
PrefetchServingHandle serving_handle = future.Take();
ASSERT_FALSE(serving_handle);
SendBodyContentOfResponseAndWait(kHTMLBody);
CompleteResponseAndWait(net::OK, std::size(kHTMLBody));
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody),
GetEagernessParam());
ExpectServingMetricsSuccess();
std::string histogram_suffix =
GetMetricsSuffixTriggerTypeAndEagerness(prefetch_type, std::nullopt);
histogram_tester.ExpectTotalCount(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.Served.%s",
histogram_suffix),
0);
histogram_tester.ExpectUniqueTimeSample(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.NotServed.%s",
histogram_suffix),
base::Milliseconds(kAddedToURLRequestStartLatency + kHeaderLatency), 1);
histogram_tester.ExpectUniqueSample(
base::StringPrintf(
"Prefetch.PrefetchMatchingBlockedNavigation.PerMatchingCandidate.%s",
histogram_suffix),
true, 1);
histogram_tester.ExpectUniqueSample(
base::StrCat({"Prefetch.PrefetchPotentialCandidateServingResult."
"PerMatchingCandidate.",
histogram_suffix}),
PrefetchPotentialCandidateServingResult::
kNotServedDeterminedNVSHeaderMismatch,
1);
}
TEST_P(PrefetchServiceAlwaysBlockUntilHeadTest,
DISABLED_CHROMEOS(FailedCookiesChangedWhileBlockUntilHead)) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
const PrefetchType prefetch_type =
PrefetchType(PreloadingTriggerType::kSpeculationRule,
true, GetEagernessParam());
MakePrefetchOnMainFrame(GURL("https://example.com"), prefetch_type);
VerifyCommonRequestState(
GURL("https://example.com"),
{.use_prefetch_proxy = true,
.expected_priority = ExpectedPriorityForEagerness(GetEagernessParam())});
NavigateInitiatedByRenderer(GURL("https://example.com"));
base::test::TestFuture<PrefetchServingHandle> future;
GetPrefetchToServe(future, GURL("https://example.com"), MainDocumentToken());
EXPECT_FALSE(future.IsReady());
task_environment()->FastForwardBy(base::Milliseconds(800));
ASSERT_TRUE(SetCookie(GURL("https://example.com"), "testing"));
task_environment()->RunUntilIdle();
SendHeadOfResponseAndWait(net::HTTP_OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}},
std::size(kHTMLBody));
PrefetchServingHandle serving_handle = future.Take();
EXPECT_FALSE(serving_handle);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.RespCode", net::HTTP_OK, 1);
histogram_tester.ExpectTotalCount("PrefetchProxy.Prefetch.Mainframe.NetError",
0);
histogram_tester.ExpectTotalCount(
"PrefetchProxy.Prefetch.Mainframe.BodyLength", 0);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.TotalTime", kTotalTimeDuration, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.ConnectTime", kConnectTimeDuration, 1);
std::optional<PrefetchReferringPageMetrics> referring_page_metrics =
PrefetchReferringPageMetrics::GetForCurrentDocument(main_rfh());
EXPECT_EQ(referring_page_metrics->prefetch_attempted_count, 1);
EXPECT_EQ(referring_page_metrics->prefetch_eligible_count, 1);
EXPECT_EQ(referring_page_metrics->prefetch_successful_count, 0);
ExpectServingMetrics(PrefetchStatus::kPrefetchNotUsedCookiesChanged);
ExpectCorrectUkmLogs({.outcome = PreloadingTriggeringOutcome::kFailure,
.failure = ToPreloadingFailureReason(
PrefetchStatus::kPrefetchNotUsedCookiesChanged),
.is_accurate = true,
.eagerness = GetEagernessParam()});
std::string histogram_suffix =
GetMetricsSuffixTriggerTypeAndEagerness(prefetch_type, std::nullopt);
histogram_tester.ExpectTotalCount(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.Served.%s",
histogram_suffix),
0);
histogram_tester.ExpectUniqueTimeSample(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.NotServed.%s",
histogram_suffix),
base::Milliseconds(800), 1);
histogram_tester.ExpectUniqueSample(
base::StringPrintf(
"Prefetch.PrefetchMatchingBlockedNavigation.PerMatchingCandidate.%s",
histogram_suffix),
true, 1);
histogram_tester.ExpectUniqueSample(
base::StrCat({"Prefetch.PrefetchPotentialCandidateServingResult."
"PerMatchingCandidate.",
histogram_suffix}),
PrefetchPotentialCandidateServingResult::kNotServedCookiesChanged, 1);
}
TEST_P(PrefetchServiceAlwaysBlockUntilHeadTest,
DISABLED_CHROMEOS(FailedTimeoutWhileBlockUntilHead)) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
PrefetchType prefetch_type(PreloadingTriggerType::kSpeculationRule,
true, GetEagernessParam());
MakePrefetchOnMainFrame(GURL("https://example.com"), prefetch_type);
VerifyCommonRequestState(
GURL("https://example.com"),
{.use_prefetch_proxy = true,
.expected_priority = ExpectedPriorityForEagerness(GetEagernessParam())});
NavigateInitiatedByRenderer(GURL("https://example.com"));
base::test::TestFuture<PrefetchServingHandle> future;
GetPrefetchToServe(future, GURL("https://example.com"), MainDocumentToken());
EXPECT_FALSE(future.IsReady());
task_environment()->FastForwardBy(base::Milliseconds(kPrefetchTimeout));
PrefetchServingHandle serving_handle = future.Take();
EXPECT_FALSE(serving_handle);
ExpectPrefetchFailedNetError(histogram_tester, net::ERR_TIMED_OUT,
GetEagernessParam(),
true);
ExpectServingMetrics(PrefetchStatus::kPrefetchFailedNetError);
std::string histogram_suffix =
GetMetricsSuffixTriggerTypeAndEagerness(prefetch_type, std::nullopt);
base::TimeDelta block_until_head_timeout = PrefetchBlockUntilHeadTimeout(
prefetch_type, false,
false);
histogram_tester.ExpectTotalCount(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.Served.%s",
histogram_suffix),
0);
histogram_tester.ExpectUniqueTimeSample(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.NotServed.%s",
histogram_suffix),
block_until_head_timeout, 1);
histogram_tester.ExpectUniqueSample(
base::StringPrintf(
"Prefetch.PrefetchMatchingBlockedNavigation.PerMatchingCandidate.%s",
histogram_suffix),
true, 1);
histogram_tester.ExpectUniqueSample(
base::StrCat({"Prefetch.PrefetchPotentialCandidateServingResult."
"PerMatchingCandidate.",
histogram_suffix}),
PrefetchPotentialCandidateServingResult::kNotServedBlockUntilHeadTimeout,
1);
}
TEST_P(PrefetchServiceAlwaysBlockUntilHeadTest,
DISABLED_CHROMEOS(FailedTimeoutWhileBlockUntilHeadForOlderNavigation)) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
const PrefetchType prefetch_type =
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false, GetEagernessParam());
MakePrefetchOnMainFrame(GURL("https://example.com"), prefetch_type);
VerifyCommonRequestState(
GURL("https://example.com"),
{.expected_priority = ExpectedPriorityForEagerness(GetEagernessParam())});
NavigateInitiatedByRenderer(GURL("https://example.com"));
base::test::TestFuture<PrefetchServingHandle> first_future;
GetPrefetchToServe(first_future, GURL("https://example.com"),
MainDocumentToken());
EXPECT_FALSE(first_future.IsReady());
NavigateInitiatedByRenderer(GURL("https://example.com"));
base::test::TestFuture<PrefetchServingHandle> second_future;
GetPrefetchToServe(second_future, GURL("https://example.com"),
MainDocumentToken());
EXPECT_FALSE(second_future.IsReady());
task_environment()->FastForwardBy(base::Milliseconds(kPrefetchTimeout));
EXPECT_TRUE(first_future.IsReady());
EXPECT_TRUE(second_future.IsReady());
PrefetchServingHandle serving_handle = second_future.Take();
EXPECT_FALSE(serving_handle);
ExpectPrefetchFailedNetError(histogram_tester, net::ERR_TIMED_OUT,
GetEagernessParam(),
true);
ExpectServingMetrics(PrefetchStatus::kPrefetchFailedNetError,
false,
false);
std::string histogram_suffix =
GetMetricsSuffixTriggerTypeAndEagerness(prefetch_type, std::nullopt);
histogram_tester.ExpectTotalCount(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.Served.%s",
histogram_suffix),
0);
histogram_tester.ExpectUniqueTimeSample(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.NotServed.%s",
histogram_suffix),
base::Milliseconds(kBlockUntilHeadTimeout), 2);
histogram_tester.ExpectUniqueSample(
base::StringPrintf(
"Prefetch.PrefetchMatchingBlockedNavigation.PerMatchingCandidate.%s",
histogram_suffix),
true, 2);
NavigateInitiatedByRenderer(GURL("https://example.com"));
base::test::TestFuture<PrefetchServingHandle> third_future;
GetPrefetchToServe(third_future, GURL("https://example.com"),
MainDocumentToken());
serving_handle = third_future.Take();
EXPECT_FALSE(serving_handle);
histogram_tester.ExpectTotalCount(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.Served.%s",
histogram_suffix),
0);
histogram_tester.ExpectUniqueTimeSample(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.NotServed.%s",
histogram_suffix),
base::Milliseconds(kBlockUntilHeadTimeout), 2);
histogram_tester.ExpectUniqueSample(
base::StringPrintf(
"Prefetch.PrefetchMatchingBlockedNavigation.PerMatchingCandidate.%s",
histogram_suffix),
true, 2);
}
TEST_P(PrefetchServiceAlwaysBlockUntilHeadTest,
DISABLED_CHROMEOS(FailedNetErrorWhileBlockUntilHead)) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
const PrefetchType prefetch_type =
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false, GetEagernessParam());
MakePrefetchOnMainFrame(GURL("https://example.com"), prefetch_type);
VerifyCommonRequestState(
GURL("https://example.com"),
{.expected_priority = ExpectedPriorityForEagerness(GetEagernessParam())});
NavigateInitiatedByRenderer(GURL("https://example.com"));
base::test::TestFuture<PrefetchServingHandle> future;
GetPrefetchToServe(future, GURL("https://example.com"), MainDocumentToken());
EXPECT_FALSE(future.IsReady());
task_environment()->FastForwardBy(base::Milliseconds(300));
CompleteResponseAndWait(net::ERR_ACCESS_DENIED, 0);
PrefetchServingHandle serving_handle = future.Take();
EXPECT_FALSE(serving_handle);
ExpectPrefetchFailedNetError(histogram_tester, net::ERR_ACCESS_DENIED,
GetEagernessParam(),
true);
ExpectServingMetrics(PrefetchStatus::kPrefetchFailedNetError,
false,
false);
std::string histogram_suffix =
GetMetricsSuffixTriggerTypeAndEagerness(prefetch_type, std::nullopt);
histogram_tester.ExpectTotalCount(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.Served.%s",
histogram_suffix),
0);
histogram_tester.ExpectUniqueTimeSample(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.NotServed.%s",
histogram_suffix),
base::Milliseconds(300), 1);
histogram_tester.ExpectUniqueSample(
base::StringPrintf(
"Prefetch.PrefetchMatchingBlockedNavigation.PerMatchingCandidate.%s",
histogram_suffix),
true, 1);
histogram_tester.ExpectUniqueSample(
base::StrCat({"Prefetch.PrefetchPotentialCandidateServingResult."
"PerMatchingCandidate.",
histogram_suffix}),
PrefetchPotentialCandidateServingResult::kNotServedLoadFailed, 1);
}
TEST_P(
PrefetchServiceAlwaysBlockUntilHeadTest,
DISABLED_CHROMEOS_AND_CASTOS(NVSBlockUntilHeadReceivedOneMatchOneTimeout)) {
const std::string kTestUrl = "https://example.com/index.html";
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(2));
const PrefetchType prefetch_type =
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false, GetEagernessParam());
{
network::mojom::NoVarySearchPtr no_vary_search_hint =
network::mojom::NoVarySearch::New();
no_vary_search_hint->vary_on_key_order = true;
no_vary_search_hint->search_variance =
network::mojom::SearchParamsVariance::NewNoVaryParams(
std::vector<std::string>({"a"}));
MakePrefetchOnMainFrame(
GURL(kTestUrl + "?a=5"), prefetch_type,
blink::mojom::Referrer(),
std::move(no_vary_search_hint));
VerifyCommonRequestState(GURL(kTestUrl + "?a=5"),
{.expected_priority = ExpectedPriorityForEagerness(
GetEagernessParam())});
}
{
network::mojom::NoVarySearchPtr no_vary_search_hint =
network::mojom::NoVarySearch::New();
no_vary_search_hint->vary_on_key_order = true;
no_vary_search_hint->search_variance =
network::mojom::SearchParamsVariance::NewNoVaryParams(
std::vector<std::string>({"b"}));
MakePrefetchOnMainFrame(
GURL(kTestUrl + "?b=3"), prefetch_type,
blink::mojom::Referrer(),
std::move(no_vary_search_hint));
VerifyPrefetchAttemptIsPending(GURL(kTestUrl + "?b=3"));
}
NavigateInitiatedByRenderer(GURL(kTestUrl));
base::test::TestFuture<PrefetchServingHandle> future;
GetPrefetchToServe(future, GURL(kTestUrl), MainDocumentToken());
task_environment()->FastForwardBy(
base::Milliseconds(kAddedToURLRequestStartLatency + kHeaderLatency));
EXPECT_FALSE(future.IsReady());
SendHeadOfResponseAndWait(
net::HTTP_OK, kHTMLMimeType,
false,
{{"X-Testing", "Hello World"}, {"No-Vary-Search", "params=(\"a\")"}},
std::size(kHTMLBody));
PrefetchServingHandle serving_handle = future.Take();
ASSERT_TRUE(serving_handle);
EXPECT_EQ(serving_handle.GetPrefetchContainer()->GetURL(),
GURL(kTestUrl + "?a=5"));
SendBodyContentOfResponseAndWait(kHTMLBody);
CompleteResponseAndWait(net::OK, std::size(kHTMLBody));
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.RespCode", net::HTTP_OK, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.NetError", net::OK, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.BodyLength", std::size(kHTMLBody), 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.TotalTime", kTotalTimeDuration, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.ConnectTime", kConnectTimeDuration, 1);
std::optional<PrefetchReferringPageMetrics> referring_page_metrics =
PrefetchReferringPageMetrics::GetForCurrentDocument(main_rfh());
EXPECT_EQ(referring_page_metrics->prefetch_attempted_count, 2);
EXPECT_EQ(referring_page_metrics->prefetch_eligible_count, 2);
EXPECT_EQ(referring_page_metrics->prefetch_successful_count, 1);
ExpectServingMetricsSuccess(false);
std::string histogram_suffix =
GetMetricsSuffixTriggerTypeAndEagerness(prefetch_type, std::nullopt);
histogram_tester.ExpectUniqueTimeSample(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.Served.%s",
histogram_suffix),
base::Milliseconds(kAddedToURLRequestStartLatency + kHeaderLatency), 1);
histogram_tester.ExpectTotalCount(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.NotServed.%s",
histogram_suffix),
0);
histogram_tester.ExpectUniqueSample(
base::StringPrintf(
"Prefetch.PrefetchMatchingBlockedNavigation.PerMatchingCandidate.%s",
histogram_suffix),
true, 1);
}
TEST_P(PrefetchServiceAlwaysBlockUntilHeadTest,
DISABLED_CHROMEOS_AND_CASTOS(
FailedCookiesChangedAfterPrefetchStartedTimedoutNVSHintPrefetch)) {
const std::string kTestUrl = "https://example.com/index.html";
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(2));
MakePrefetchOnMainFrame(
GURL(kTestUrl),
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false, GetEagernessParam()));
VerifyCommonRequestState(
GURL(kTestUrl),
{.expected_priority = ExpectedPriorityForEagerness(GetEagernessParam())});
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
false,
{{"X-Testing", "Hello World"}}, kHTMLBody);
{
network::mojom::NoVarySearchPtr no_vary_search_hint =
network::mojom::NoVarySearch::New();
no_vary_search_hint->vary_on_key_order = true;
no_vary_search_hint->search_variance =
network::mojom::SearchParamsVariance::NewNoVaryParams(
std::vector<std::string>({"a"}));
MakePrefetchOnMainFrame(
GURL(kTestUrl + "?a=1"),
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false, GetEagernessParam()),
blink::mojom::Referrer(),
std::move(no_vary_search_hint));
VerifyCommonRequestStateByUrl(
GURL(kTestUrl + "?a=1"),
{.expected_priority =
ExpectedPriorityForEagerness(GetEagernessParam())});
}
ASSERT_TRUE(SetCookie(GURL("https://example.com"), "testing"));
task_environment()->RunUntilIdle();
NavigateInitiatedByRenderer(GURL(kTestUrl));
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.RespCode", net::HTTP_OK, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.NetError", net::OK, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.BodyLength", std::size(kHTMLBody), 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.TotalTime", kTotalTimeDuration, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.ConnectTime", kConnectTimeDuration, 1);
std::optional<PrefetchReferringPageMetrics> referring_page_metrics =
PrefetchReferringPageMetrics::GetForCurrentDocument(main_rfh());
EXPECT_EQ(referring_page_metrics->prefetch_attempted_count, 2);
EXPECT_EQ(referring_page_metrics->prefetch_eligible_count, 2);
EXPECT_EQ(referring_page_metrics->prefetch_successful_count, 1);
EXPECT_FALSE(GetPrefetchToServe(GURL("https://example.com/index.html")));
}
TEST_P(PrefetchServiceAlwaysBlockUntilHeadTest,
DISABLED_CHROMEOS_AND_CASTOS(
FailedCookiesChangedAfterPrefetchStartedNVSHintPrefetch)) {
const std::string kTestUrl = "https://example.com/index.html";
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(2));
MakePrefetchOnMainFrame(
GURL(kTestUrl),
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false, GetEagernessParam()));
VerifyCommonRequestState(
GURL(kTestUrl),
{.expected_priority = ExpectedPriorityForEagerness(GetEagernessParam())});
{
network::mojom::NoVarySearchPtr no_vary_search_hint =
network::mojom::NoVarySearch::New();
no_vary_search_hint->vary_on_key_order = true;
no_vary_search_hint->search_variance =
network::mojom::SearchParamsVariance::NewNoVaryParams(
std::vector<std::string>({"a"}));
GURL url_2{kTestUrl + "?a=1"};
MakePrefetchOnMainFrame(
url_2,
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false, GetEagernessParam()),
blink::mojom::Referrer(),
std::move(no_vary_search_hint));
VerifyPrefetchAttemptIsPending(url_2);
}
ASSERT_TRUE(SetCookie(GURL("https://example.com"), "testing"));
task_environment()->RunUntilIdle();
NavigateInitiatedByRenderer(GURL(kTestUrl));
base::test::TestFuture<PrefetchServingHandle> future;
GetPrefetchToServe(future, GURL(kTestUrl), MainDocumentToken());
EXPECT_FALSE(future.IsReady());
task_environment()->RunUntilIdle();
EXPECT_FALSE(future.IsReady());
SendHeadOfResponseAndWait(
net::HTTP_OK, kHTMLMimeType,
false,
{{"X-Testing", "Hello World"}, {"No-Vary-Search", "params=(\"e\")"}},
std::size(kHTMLBody));
EXPECT_TRUE(future.IsReady());
PrefetchServingHandle serving_handle = future.Take();
EXPECT_FALSE(serving_handle);
SendBodyContentOfResponseAndWait(kHTMLBody);
CompleteResponseAndWait(net::OK, std::size(kHTMLBody));
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.RespCode", net::HTTP_OK, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.NetError", net::OK, 0);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.BodyLength", std::size(kHTMLBody), 0);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.TotalTime", kTotalTimeDuration, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.ConnectTime", kConnectTimeDuration, 1);
std::optional<PrefetchReferringPageMetrics> referring_page_metrics =
PrefetchReferringPageMetrics::GetForCurrentDocument(main_rfh());
EXPECT_EQ(referring_page_metrics->prefetch_attempted_count, 2);
EXPECT_EQ(referring_page_metrics->prefetch_eligible_count, 2);
EXPECT_EQ(referring_page_metrics->prefetch_successful_count, 0);
ExpectServingMetrics(PrefetchStatus::kPrefetchNotUsedCookiesChanged,
false,
false);
}
TEST_P(PrefetchServiceAlwaysBlockUntilHeadTest,
DISABLED_CHROMEOS_AND_CASTOS(
NVSBlockUntilHeadReceivedMultipleMatchesByNVSHint)) {
const std::string kTestUrl = "https://example.com/index.html";
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(2));
const PrefetchType prefetch_type =
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false, GetEagernessParam());
{
network::mojom::NoVarySearchPtr no_vary_search_hint =
network::mojom::NoVarySearch::New();
no_vary_search_hint->vary_on_key_order = true;
no_vary_search_hint->search_variance =
network::mojom::SearchParamsVariance::NewNoVaryParams(
std::vector<std::string>({"a"}));
GURL not_matched_url = GURL(kTestUrl + "?a=5");
MakePrefetchOnMainFrame(
not_matched_url, prefetch_type,
blink::mojom::Referrer(),
std::move(no_vary_search_hint));
VerifyCommonRequestState(not_matched_url,
{.expected_priority = ExpectedPriorityForEagerness(
GetEagernessParam())});
}
{
network::mojom::NoVarySearchPtr no_vary_search_hint =
network::mojom::NoVarySearch::New();
no_vary_search_hint->vary_on_key_order = true;
no_vary_search_hint->search_variance =
network::mojom::SearchParamsVariance::NewNoVaryParams(
std::vector<std::string>({"b"}));
GURL matched_url = GURL(kTestUrl + "?b=3");
MakePrefetchOnMainFrame(
matched_url, prefetch_type,
blink::mojom::Referrer(),
std::move(no_vary_search_hint));
VerifyPrefetchAttemptIsPending(matched_url);
}
NavigateInitiatedByRenderer(GURL(kTestUrl));
base::test::TestFuture<PrefetchServingHandle> future;
GetPrefetchToServe(future, GURL(kTestUrl), MainDocumentToken());
EXPECT_FALSE(future.IsReady());
task_environment()->RunUntilIdle();
SendHeadOfResponseAndWait(
net::HTTP_OK, kHTMLMimeType,
false,
{{"X-Testing", "Hello World"}, {"No-Vary-Search", "params=(\"e\")"}},
std::size(kHTMLBody));
EXPECT_TRUE(future.IsReady());
PrefetchServingHandle serving_handle = future.Take();
ASSERT_FALSE(serving_handle);
SendBodyContentOfResponseAndWait(kHTMLBody);
CompleteResponseAndWait(net::OK, std::size(kHTMLBody));
SendHeadOfResponseForUrlAndWait(
GURL(kTestUrl + "?b=3"), net::HTTP_OK, kHTMLMimeType,
false,
{{"X-Testing", "Hello World"}, {"No-Vary-Search", "params=(\"b\")"}},
std::size(kHTMLBody));
std::optional<PrefetchReferringPageMetrics> referring_page_metrics =
PrefetchReferringPageMetrics::GetForCurrentDocument(main_rfh());
EXPECT_EQ(referring_page_metrics->prefetch_attempted_count, 2);
EXPECT_EQ(referring_page_metrics->prefetch_eligible_count, 2);
std::string histogram_suffix =
GetMetricsSuffixTriggerTypeAndEagerness(prefetch_type, std::nullopt);
histogram_tester.ExpectTotalCount(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.Served.%s",
histogram_suffix),
0);
histogram_tester.ExpectUniqueTimeSample(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.NotServed.%s",
histogram_suffix),
base::Milliseconds(0), 1);
histogram_tester.ExpectUniqueSample(
base::StringPrintf(
"Prefetch.PrefetchMatchingBlockedNavigation.PerMatchingCandidate.%s",
histogram_suffix),
true, 1);
}
TEST_P(PrefetchServiceAlwaysBlockUntilHeadTest,
DISABLED_CHROMEOS(BlockUntilHeadTimedout)) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
const PrefetchType prefetch_type =
PrefetchType(PreloadingTriggerType::kSpeculationRule,
true, GetEagernessParam());
MakePrefetchOnMainFrame(GURL("https://example.com"), prefetch_type);
VerifyCommonRequestState(
GURL("https://example.com"),
{.use_prefetch_proxy = true,
.expected_priority = ExpectedPriorityForEagerness(GetEagernessParam())});
NavigateInitiatedByRenderer(GURL("https://example.com"));
base::test::TestFuture<PrefetchServingHandle> future;
GetPrefetchToServe(future, GURL("https://example.com"), MainDocumentToken());
EXPECT_FALSE(future.IsReady());
task_environment()->FastForwardBy(base::Milliseconds(1000));
PrefetchServingHandle serving_handle = future.Take();
EXPECT_FALSE(serving_handle);
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}}, kHTMLBody);
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody),
GetEagernessParam(),
true);
ExpectServingMetricsSuccess();
EXPECT_FALSE(serving_handle);
std::string histogram_suffix =
GetMetricsSuffixTriggerTypeAndEagerness(prefetch_type, std::nullopt);
histogram_tester.ExpectTotalCount(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.Served.%s",
histogram_suffix),
0);
histogram_tester.ExpectUniqueTimeSample(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.NotServed.%s",
histogram_suffix),
base::Milliseconds(1000), 1);
histogram_tester.ExpectUniqueSample(
base::StringPrintf(
"Prefetch.PrefetchMatchingBlockedNavigation.PerMatchingCandidate.%s",
histogram_suffix),
true, 1);
histogram_tester.ExpectUniqueSample(
base::StrCat({"Prefetch.PrefetchPotentialCandidateServingResult."
"PerMatchingCandidate.",
histogram_suffix}),
PrefetchPotentialCandidateServingResult::kNotServedBlockUntilHeadTimeout,
1);
}
TEST_P(PrefetchServiceAlwaysBlockUntilHeadTest,
DISABLED_CHROMEOS(HeadReceivedBeforeTimeout)) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
const PrefetchType prefetch_type =
PrefetchType(PreloadingTriggerType::kSpeculationRule,
true, GetEagernessParam());
MakePrefetchOnMainFrame(GURL("https://example.com"), prefetch_type);
VerifyCommonRequestState(
GURL("https://example.com"),
{.use_prefetch_proxy = true,
.expected_priority = ExpectedPriorityForEagerness(GetEagernessParam())});
NavigateInitiatedByRenderer(GURL("https://example.com"));
base::test::TestFuture<PrefetchServingHandle> future;
GetPrefetchToServe(future, GURL("https://example.com"), MainDocumentToken());
EXPECT_FALSE(future.IsReady());
task_environment()->FastForwardBy(base::Milliseconds(1000));
PrefetchServingHandle serving_handle = future.Take();
EXPECT_FALSE(serving_handle);
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}}, kHTMLBody);
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody),
GetEagernessParam(),
true);
ExpectServingMetricsSuccess();
EXPECT_FALSE(serving_handle);
std::string histogram_suffix =
GetMetricsSuffixTriggerTypeAndEagerness(prefetch_type, std::nullopt);
histogram_tester.ExpectTotalCount(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.Served.%s",
histogram_suffix),
0);
histogram_tester.ExpectUniqueTimeSample(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.NotServed.%s",
histogram_suffix),
base::Milliseconds(1000), 1);
histogram_tester.ExpectUniqueSample(
base::StringPrintf(
"Prefetch.PrefetchMatchingBlockedNavigation.PerMatchingCandidate.%s",
histogram_suffix),
true, 1);
histogram_tester.ExpectUniqueSample(
base::StrCat({"Prefetch.PrefetchPotentialCandidateServingResult."
"PerMatchingCandidate.",
histogram_suffix}),
PrefetchPotentialCandidateServingResult::kNotServedBlockUntilHeadTimeout,
1);
}
TEST_P(PrefetchServiceAlwaysBlockUntilHeadTest,
DISABLED_CHROMEOS(MultipleGetPrefetchToServe)) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
const PrefetchType prefetch_type =
PrefetchType(PreloadingTriggerType::kSpeculationRule,
true, GetEagernessParam());
MakePrefetchOnMainFrame(GURL("https://example.com"), prefetch_type);
VerifyCommonRequestState(
GURL("https://example.com"),
{.use_prefetch_proxy = true,
.expected_priority = ExpectedPriorityForEagerness(GetEagernessParam())});
NavigateInitiatedByRenderer(GURL("https://example.com"));
base::test::TestFuture<PrefetchServingHandle> first_future;
GetPrefetchToServe(first_future, GURL("https://example.com"),
MainDocumentToken());
NavigateInitiatedByRenderer(GURL("https://example.com"));
base::test::TestFuture<PrefetchServingHandle> second_future;
GetPrefetchToServe(second_future, GURL("https://example.com"),
MainDocumentToken());
EXPECT_FALSE(second_future.IsReady());
task_environment()->FastForwardBy(base::Milliseconds(1000));
EXPECT_TRUE(first_future.IsReady());
EXPECT_TRUE(second_future.IsReady());
PrefetchServingHandle serving_handle = second_future.Take();
EXPECT_FALSE(serving_handle);
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}}, kHTMLBody);
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody),
GetEagernessParam(),
true);
ExpectServingMetricsSuccess();
std::string histogram_suffix =
GetMetricsSuffixTriggerTypeAndEagerness(prefetch_type, std::nullopt);
histogram_tester.ExpectTotalCount(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.Served.%s",
histogram_suffix),
0);
histogram_tester.ExpectUniqueTimeSample(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.NotServed.%s",
histogram_suffix),
base::Milliseconds(1000), 2);
histogram_tester.ExpectUniqueSample(
base::StringPrintf(
"Prefetch.PrefetchMatchingBlockedNavigation.PerMatchingCandidate.%s",
histogram_suffix),
true, 2);
}
class PrefetchServiceDisableBlockUntilHeadTimeoutTest
: public PrefetchServiceTestBase,
public WithPrefetchRearchParam,
public ::testing::WithParamInterface<PrefetchRearchParam> {
public:
PrefetchServiceDisableBlockUntilHeadTimeoutTest()
: WithPrefetchRearchParam(GetParam()) {}
static constexpr int kBlockUntilHeadTimeout = 1000;
void InitScopedFeatureList() override {
InitBaseParams();
InitRearchFeatures();
scoped_feature_list_.InitWithFeaturesAndParameters(
{{features::kPrefetchUseContentRefactor,
{
{"ineligible_decoy_request_probability", "0"},
{"prefetch_container_lifetime_s", "-1"},
{"prefetch_timeout_ms", "10000"},
{"block_until_head_timeout_embedder_prefetch",
base::NumberToString(kBlockUntilHeadTimeout)},
}}},
{});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
INSTANTIATE_TEST_SUITE_P(ParametrizedTests,
PrefetchServiceDisableBlockUntilHeadTimeoutTest,
testing::ValuesIn(PrefetchRearchParam::Params()));
TEST_P(PrefetchServiceDisableBlockUntilHeadTimeoutTest,
DISABLED_CHROMEOS(DisableBlockUntilHeadTimeoutFalse)) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
std::nullopt));
std::unique_ptr<content::PrefetchHandle> handle =
MakePrefetchFromBrowserContext(
GURL("https://example.com"),
std::nullopt, {},
nullptr,
base::Seconds( 60 * 10),
false);
task_environment()->RunUntilIdle();
VerifyCommonRequestStateForBrowserContextPrefetch(
GURL("https://example.com"), {.use_prefetch_proxy = false});
std::unique_ptr<NavigationResult> navigation_result =
SimulatePartOfNavigation(GURL("https://example.com"),
false,
false);
task_environment()->RunUntilIdle();
ASSERT_FALSE(navigation_result->serving_handle_future.IsReady());
task_environment()->FastForwardBy(base::Milliseconds(kBlockUntilHeadTimeout));
EXPECT_TRUE(navigation_result->serving_handle_future.IsReady());
EXPECT_FALSE(navigation_result->serving_handle_future.Take());
auto metrics_suffix = GetMetricsSuffixTriggerTypeAndEagerness(
PrefetchType(PreloadingTriggerType::kEmbedder,
false),
test::kPreloadingEmbedderHistgramSuffixForTesting);
histogram_tester.ExpectUniqueSample(
base::StringPrintf(
"Prefetch.PrefetchMatchingBlockedNavigation.PerMatchingCandidate.%s",
metrics_suffix),
true, 1);
histogram_tester.ExpectUniqueTimeSample(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.NotServed.%s",
metrics_suffix),
base::Milliseconds(kBlockUntilHeadTimeout), 1);
histogram_tester.ExpectTotalCount(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.Served.%s",
metrics_suffix),
0);
}
TEST_P(PrefetchServiceDisableBlockUntilHeadTimeoutTest,
DISABLED_CHROMEOS(DisableBlockUntilHeadTimeoutTrue)) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
std::nullopt));
std::unique_ptr<content::PrefetchHandle> handle =
MakePrefetchFromBrowserContext(
GURL("https://example.com"),
std::nullopt, {},
nullptr,
base::Seconds( 60 * 10),
true);
task_environment()->RunUntilIdle();
VerifyCommonRequestStateForBrowserContextPrefetch(
GURL("https://example.com"), {.use_prefetch_proxy = false});
std::unique_ptr<NavigationResult> navigation_result =
SimulatePartOfNavigation(GURL("https://example.com"),
false,
false);
ASSERT_FALSE(navigation_result->serving_handle_future.IsReady());
task_environment()->FastForwardBy(base::Milliseconds(kBlockUntilHeadTimeout));
EXPECT_FALSE(navigation_result->serving_handle_future.IsReady());
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}}, kHTMLBody);
EXPECT_TRUE(navigation_result->serving_handle_future.IsReady());
EXPECT_TRUE(navigation_result->serving_handle_future.Take());
auto metrics_suffix = GetMetricsSuffixTriggerTypeAndEagerness(
PrefetchType(PreloadingTriggerType::kEmbedder,
false),
test::kPreloadingEmbedderHistgramSuffixForTesting);
histogram_tester.ExpectUniqueSample(
base::StringPrintf(
"Prefetch.PrefetchMatchingBlockedNavigation.PerMatchingCandidate.%s",
metrics_suffix),
true, 1);
histogram_tester.ExpectUniqueTimeSample(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.Served.%s",
metrics_suffix),
base::Milliseconds(kBlockUntilHeadTimeout), 1);
histogram_tester.ExpectTotalCount(
base::StringPrintf(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.NotServed.%s",
metrics_suffix),
0);
}
TEST_P(PrefetchServiceTest, PrefetchEviction) {
base::HistogramTester histogram_tester;
struct TestCase {
const std::optional<url::Origin> referring_origin;
const GURL prefetch_url;
};
const std::vector<TestCase> test_cases = {
{url::Origin::Create(GURL("https://a.test")), GURL("https://a.test/0")},
{url::Origin::Create(GURL("https://a.test")), GURL("https://b.test/1")},
{url::Origin::Create(GURL("https://b.test")), GURL("https://a.test/2")},
{url::Origin::Create(GURL("https://b.test")), GURL("https://b.test/3")},
{std::nullopt, GURL("https://a.test/4")},
{std::nullopt, GURL("https://b.test/5")},
};
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
std::nullopt));
std::vector<std::unique_ptr<PrefetchHandle>> handles;
for (const auto& test_case : test_cases) {
handles.push_back(
MakePrefetchFromEmbedder(test_case.prefetch_url,
PrefetchType(PreloadingTriggerType::kEmbedder,
false),
{}, test_case.referring_origin));
}
task_environment()->RunUntilIdle();
auto filter_builder = BrowsingDataFilterBuilder::Create(
BrowsingDataFilterBuilder::Mode::kDelete);
filter_builder->AddOrigin(url::Origin::Create(GURL("https://a.test")));
auto filter = filter_builder->BuildStorageKeyFilter();
prefetch_service().EvictPrefetchesForBrowsingDataRemoval(
filter, PrefetchStatus::kPrefetchEvictedAfterBrowsingDataRemoved);
task_environment()->RunUntilIdle();
EXPECT_FALSE(handles[0]->IsAlive());
EXPECT_FALSE(handles[1]->IsAlive());
EXPECT_FALSE(handles[4]->IsAlive());
EXPECT_TRUE(handles[2]->IsAlive());
EXPECT_TRUE(handles[3]->IsAlive());
EXPECT_TRUE(handles[5]->IsAlive());
histogram_tester.ExpectUniqueSample(
"Preloading.Prefetch.PrefetchStatus",
PrefetchStatus::kPrefetchEvictedAfterBrowsingDataRemoved, 3);
prefetch_service().EvictPrefetchesForBrowsingDataRemoval(
BrowsingDataFilterBuilder::Create(
BrowsingDataFilterBuilder::Mode::kPreserve)
->BuildStorageKeyFilter(),
PrefetchStatus::kPrefetchEvictedAfterBrowsingDataRemoved);
task_environment()->RunUntilIdle();
EXPECT_FALSE(handles[2]->IsAlive());
EXPECT_FALSE(handles[3]->IsAlive());
EXPECT_FALSE(handles[5]->IsAlive());
histogram_tester.ExpectUniqueSample(
"Preloading.Prefetch.PrefetchStatus",
PrefetchStatus::kPrefetchEvictedAfterBrowsingDataRemoved, 6);
}
TEST_P(PrefetchServiceTest, PrefetchEvictionForEligibleButNotStartedPrefetch) {
NavigateAndCommit(GURL("https://example.com"));
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
2));
const auto url_1 = GURL("https://example.com/one");
const auto url_2 = GURL("https://example.com/two");
auto candidate_1 = blink::mojom::SpeculationCandidate::New();
candidate_1->url = url_1;
candidate_1->action = blink::mojom::SpeculationAction::kPrefetch;
candidate_1->eagerness = blink::mojom::SpeculationEagerness::kImmediate;
candidate_1->referrer = blink::mojom::Referrer::New();
auto candidate_2 = candidate_1.Clone();
candidate_2->url = url_2;
std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
candidates.push_back(candidate_1.Clone());
candidates.push_back(candidate_2.Clone());
auto* prefetch_document_manager =
PrefetchDocumentManager::GetOrCreateForCurrentDocument(main_rfh());
prefetch_document_manager->ProcessCandidates(candidates);
task_environment()->RunUntilIdle();
base::WeakPtr<PrefetchContainer> prefetch_container1, prefetch_container2;
std::tie(std::ignore, prefetch_container1) =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(MainDocumentToken(), url_1))[0];
std::tie(std::ignore, prefetch_container2) =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(MainDocumentToken(), url_2))[0];
ASSERT_EQ(prefetch_container1->GetLoadState(),
PrefetchContainer::LoadState::kStarted);
ASSERT_EQ(prefetch_container2->GetLoadState(),
PrefetchContainer::LoadState::kEligible);
prefetch_service().EvictPrefetchesForBrowsingDataRemoval(
BrowsingDataFilterBuilder::Create(
BrowsingDataFilterBuilder::Mode::kPreserve)
->BuildStorageKeyFilter(),
PrefetchStatus::kPrefetchEvictedAfterBrowsingDataRemoved);
{
const auto source_id = ForceLogsUploadAndGetUkmId();
auto actual_attempts = test_ukm_recorder()->GetEntries(
ukm::builders::Preloading_Attempt::kEntryName,
test::kPreloadingAttemptUkmMetrics);
ASSERT_EQ(actual_attempts.size(), 2u);
std::vector<ukm::TestUkmRecorder::HumanReadableUkmEntry> expected_attempts =
{attempt_entry_builder()->BuildEntry(
source_id, PreloadingType::kPrefetch,
PreloadingEligibility::kEligible,
PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kFailure,
ToPreloadingFailureReason(
PrefetchStatus::kPrefetchEvictedAfterBrowsingDataRemoved),
false,
std::nullopt,
blink::mojom::SpeculationEagerness::kImmediate),
attempt_entry_builder()->BuildEntry(
source_id, PreloadingType::kPrefetch,
PreloadingEligibility::kEligible,
PreloadingHoldbackStatus::kUnspecified,
PreloadingTriggeringOutcome::kUnspecified,
PreloadingFailureReason::kUnspecified,
false,
std::nullopt,
blink::mojom::SpeculationEagerness::kImmediate)};
ASSERT_THAT(actual_attempts,
testing::UnorderedElementsAreArray(expected_attempts))
<< test::ActualVsExpectedUkmEntriesToString(actual_attempts,
expected_attempts);
}
}
TEST_P(PrefetchServiceTest, PrefetchEvictionDuringEligiblityCheck) {
NavigateAndCommit(GURL("https://example.com"));
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
1));
PrefetchServiceInjectedEligibilityCheckFuture
eligibility_check_callback_future(prefetch_service());
const auto url_1 = GURL("https://example.com/one");
auto candidate_1 = blink::mojom::SpeculationCandidate::New();
candidate_1->url = url_1;
candidate_1->action = blink::mojom::SpeculationAction::kPrefetch;
candidate_1->eagerness = blink::mojom::SpeculationEagerness::kImmediate;
candidate_1->referrer = blink::mojom::Referrer::New();
std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
candidates.push_back(candidate_1.Clone());
auto* prefetch_document_manager =
PrefetchDocumentManager::GetOrCreateForCurrentDocument(main_rfh());
prefetch_document_manager->ProcessCandidates(candidates);
task_environment()->RunUntilIdle();
base::WeakPtr<PrefetchContainer> prefetch_container1;
std::tie(std::ignore, prefetch_container1) =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(MainDocumentToken(), url_1))[0];
ASSERT_EQ(prefetch_container1->GetLoadState(),
PrefetchContainer::LoadState::kNotStarted);
prefetch_service().EvictPrefetchesForBrowsingDataRemoval(
BrowsingDataFilterBuilder::Create(
BrowsingDataFilterBuilder::Mode::kPreserve)
->BuildStorageKeyFilter(),
PrefetchStatus::kPrefetchEvictedAfterBrowsingDataRemoved);
{
const auto source_id = ForceLogsUploadAndGetUkmId();
auto actual_attempts = test_ukm_recorder()->GetEntries(
ukm::builders::Preloading_Attempt::kEntryName,
test::kPreloadingAttemptUkmMetrics);
ASSERT_EQ(actual_attempts.size(), 1u);
std::vector<ukm::TestUkmRecorder::HumanReadableUkmEntry> expected_attempts =
{attempt_entry_builder()->BuildEntry(
source_id, PreloadingType::kPrefetch,
PreloadingEligibility::kUnspecified,
PreloadingHoldbackStatus::kUnspecified,
PreloadingTriggeringOutcome::kUnspecified,
PreloadingFailureReason::kUnspecified,
false,
std::nullopt,
blink::mojom::SpeculationEagerness::kImmediate)};
ASSERT_THAT(actual_attempts,
testing::UnorderedElementsAreArray(expected_attempts))
<< test::ActualVsExpectedUkmEntriesToString(actual_attempts,
expected_attempts);
}
eligibility_check_callback_future->Take().Run(
PreloadingEligibility::kEligible);
}
TEST_P(PrefetchServiceTest, PrefetchEvictionWhenHoldback) {
content::test::PreloadingConfigOverride preloading_config_override;
preloading_config_override.SetHoldback(
PreloadingType::kPrefetch,
content_preloading_predictor::kSpeculationRules, true);
NavigateAndCommit(GURL("https://example.com"));
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
1));
const auto url_1 = GURL("https://example.com/one");
auto candidate_1 = blink::mojom::SpeculationCandidate::New();
candidate_1->url = url_1;
candidate_1->action = blink::mojom::SpeculationAction::kPrefetch;
candidate_1->eagerness = blink::mojom::SpeculationEagerness::kImmediate;
candidate_1->referrer = blink::mojom::Referrer::New();
std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
candidates.push_back(candidate_1.Clone());
auto* prefetch_document_manager =
PrefetchDocumentManager::GetOrCreateForCurrentDocument(main_rfh());
prefetch_document_manager->ProcessCandidates(candidates);
task_environment()->RunUntilIdle();
base::WeakPtr<PrefetchContainer> prefetch_container1;
std::tie(std::ignore, prefetch_container1) =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(MainDocumentToken(), url_1))[0];
task_environment()->RunUntilIdle();
ASSERT_EQ(prefetch_container1->GetLoadState(),
PrefetchContainer::LoadState::kFailedHeldback);
prefetch_service().EvictPrefetchesForBrowsingDataRemoval(
BrowsingDataFilterBuilder::Create(
BrowsingDataFilterBuilder::Mode::kPreserve)
->BuildStorageKeyFilter(),
PrefetchStatus::kPrefetchEvictedAfterBrowsingDataRemoved);
{
const auto source_id = ForceLogsUploadAndGetUkmId();
auto actual_attempts = test_ukm_recorder()->GetEntries(
ukm::builders::Preloading_Attempt::kEntryName,
test::kPreloadingAttemptUkmMetrics);
ASSERT_EQ(actual_attempts.size(), 1u);
std::vector<ukm::TestUkmRecorder::HumanReadableUkmEntry> expected_attempts =
{attempt_entry_builder()->BuildEntry(
source_id, PreloadingType::kPrefetch,
PreloadingEligibility::kEligible,
PreloadingHoldbackStatus::kHoldback,
PreloadingTriggeringOutcome::kUnspecified,
PreloadingFailureReason::kUnspecified,
false,
std::nullopt,
blink::mojom::SpeculationEagerness::kImmediate)};
ASSERT_THAT(actual_attempts,
testing::UnorderedElementsAreArray(expected_attempts))
<< test::ActualVsExpectedUkmEntriesToString(actual_attempts,
expected_attempts);
}
}
class PrefetchServiceLimitsTest
: public PrefetchServiceTestBase,
public WithPrefetchRearchParam,
public ::testing::WithParamInterface<PrefetchRearchParam> {
public:
PrefetchServiceLimitsTest() : WithPrefetchRearchParam(GetParam()) {}
void InitScopedFeatureList() override {
InitBaseParams();
InitRearchFeatures();
}
PrefetchServingHandle CompletePrefetch(
GURL url,
blink::mojom::SpeculationEagerness eagerness) {
MakePrefetchOnMainFrame(
url, PrefetchType(PreloadingTriggerType::kSpeculationRule,
false, eagerness));
return CompleteExistingPrefetch(
url, {.expected_priority = ExpectedPriorityForEagerness(eagerness)});
}
PrefetchServingHandle CompleteExistingPrefetch(
GURL url,
const VerifyCommonRequestStateOptions& common_options = {}) {
VerifyCommonRequestState(url, common_options);
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
false,
{{"X-Testing", "Hello World"}}, kHTMLBody);
NavigateInitiatedByRenderer(url);
return GetPrefetchToServe(url);
}
};
INSTANTIATE_TEST_SUITE_P(ParametrizedTests,
PrefetchServiceLimitsTest,
testing::ValuesIn(PrefetchRearchParam::Params()));
TEST_P(PrefetchServiceLimitsTest,
NonImmediatePrefetchAllowedWhenImmediateLimitIsReached) {
const GURL url_over_limit = GURL("https://example.com/over_limit");
const GURL url_conservative = GURL("https://example.com/conservative");
NavigateAndCommit(GURL("https://example.com"));
MakePrefetchService(std::make_unique<
testing::NiceMock<MockPrefetchServiceDelegate>>(
kMaxNumberOfImmediatePrefetchesPerPage +
2));
for (int i = 0; i < kMaxNumberOfImmediatePrefetchesPerPage; ++i) {
const GURL url("https://example.com/" + base::NumberToString(i));
ASSERT_TRUE(
CompletePrefetch(url, blink::mojom::SpeculationEagerness::kImmediate));
}
MakePrefetchOnMainFrame(
url_over_limit,
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false,
blink::mojom::SpeculationEagerness::kImmediate));
EXPECT_EQ(RequestCount(), 0);
NavigateInitiatedByRenderer(url_over_limit);
ASSERT_FALSE(GetPrefetchToServe(url_over_limit));
auto non_immediate_prefetch = CompletePrefetch(
url_conservative, blink::mojom::SpeculationEagerness::kConservative);
ASSERT_TRUE(non_immediate_prefetch);
EXPECT_EQ(non_immediate_prefetch.GetPrefetchStatus(),
PrefetchStatus::kPrefetchSuccessful);
std::optional<PrefetchReferringPageMetrics> referring_page_metrics =
PrefetchReferringPageMetrics::GetForCurrentDocument(main_rfh());
EXPECT_EQ(referring_page_metrics->prefetch_attempted_count,
kMaxNumberOfImmediatePrefetchesPerPage + 2);
EXPECT_EQ(referring_page_metrics->prefetch_eligible_count,
kMaxNumberOfImmediatePrefetchesPerPage + 2);
EXPECT_EQ(referring_page_metrics->prefetch_successful_count,
kMaxNumberOfImmediatePrefetchesPerPage + 1);
}
TEST_P(PrefetchServiceLimitsTest, NonImmediatePrefetchEvictedAtLimit) {
const GURL url_1 = GURL("https://example.com/one");
const GURL url_2 = GURL("https://example.com/two");
const GURL url_3 = GURL("https://example.com/three");
const GURL url_4 = GURL("https://example.com/four");
NavigateAndCommit(GURL("https://example.com"));
ASSERT_EQ(kMaxNumberOfNonImmediatePrefetchesPerPage, 2);
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
4));
base::MockRepeatingCallback<void(const GURL& url)> mock_destruction_callback;
EXPECT_CALL(mock_destruction_callback, Run(url_1)).Times(1);
EXPECT_CALL(mock_destruction_callback, Run(url_2)).Times(1);
PrefetchDocumentManager::GetOrCreateForCurrentDocument(main_rfh())
->SetPrefetchDestructionCallback(mock_destruction_callback.Get());
auto prefetch_1 =
CompletePrefetch(url_1, blink::mojom::SpeculationEagerness::kModerate);
ASSERT_TRUE(prefetch_1);
EXPECT_EQ(prefetch_1.GetPrefetchStatus(),
PrefetchStatus::kPrefetchSuccessful);
auto prefetch_2 =
CompletePrefetch(url_2, blink::mojom::SpeculationEagerness::kModerate);
ASSERT_TRUE(prefetch_2);
EXPECT_EQ(prefetch_2.GetPrefetchStatus(),
PrefetchStatus::kPrefetchSuccessful);
ASSERT_TRUE(prefetch_1);
auto prefetch_3 =
CompletePrefetch(url_3, blink::mojom::SpeculationEagerness::kModerate);
ASSERT_TRUE(prefetch_3);
EXPECT_EQ(prefetch_3.GetPrefetchStatus(),
PrefetchStatus::kPrefetchSuccessful);
ASSERT_FALSE(prefetch_1);
ASSERT_TRUE(prefetch_2);
auto prefetch_4 =
CompletePrefetch(url_4, blink::mojom::SpeculationEagerness::kModerate);
ASSERT_TRUE(prefetch_4);
EXPECT_EQ(prefetch_4.GetPrefetchStatus(),
PrefetchStatus::kPrefetchSuccessful);
ASSERT_FALSE(prefetch_2);
ASSERT_TRUE(prefetch_3);
{
const auto source_id = ForceLogsUploadAndGetUkmId();
auto actual_attempts = test_ukm_recorder()->GetEntries(
ukm::builders::Preloading_Attempt::kEntryName,
test::kPreloadingAttemptUkmMetrics);
EXPECT_EQ(actual_attempts.size(), 4u);
std::vector<ukm::TestUkmRecorder::HumanReadableUkmEntry> expected_attempts =
{attempt_entry_builder()->BuildEntry(
source_id, PreloadingType::kPrefetch,
PreloadingEligibility::kEligible,
PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kFailure,
ToPreloadingFailureReason(
content::PrefetchStatus::kPrefetchEvictedForNewerPrefetch),
true,
base::ScopedMockElapsedTimersForTest::kMockElapsedTime,
blink::mojom::SpeculationEagerness::kModerate),
attempt_entry_builder()->BuildEntry(
source_id, PreloadingType::kPrefetch,
PreloadingEligibility::kEligible,
PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kFailure,
ToPreloadingFailureReason(
content::PrefetchStatus::kPrefetchEvictedForNewerPrefetch),
true,
base::ScopedMockElapsedTimersForTest::kMockElapsedTime,
blink::mojom::SpeculationEagerness::kModerate),
attempt_entry_builder()->BuildEntry(
source_id, PreloadingType::kPrefetch,
PreloadingEligibility::kEligible,
PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kReady,
PreloadingFailureReason::kUnspecified,
true,
base::ScopedMockElapsedTimersForTest::kMockElapsedTime,
blink::mojom::SpeculationEagerness::kModerate),
attempt_entry_builder()->BuildEntry(
source_id, PreloadingType::kPrefetch,
PreloadingEligibility::kEligible,
PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kReady,
PreloadingFailureReason::kUnspecified,
true,
base::ScopedMockElapsedTimersForTest::kMockElapsedTime,
blink::mojom::SpeculationEagerness::kModerate)};
EXPECT_THAT(actual_attempts,
testing::UnorderedElementsAreArray(expected_attempts))
<< test::ActualVsExpectedUkmEntriesToString(actual_attempts,
expected_attempts);
}
}
TEST_P(PrefetchServiceLimitsTest, PrefetchWithNoCandidateIsNotStarted) {
const GURL url_1 = GURL("https://example.com/one");
const GURL url_2 = GURL("https://example.com/two");
const GURL url_3 = GURL("https://example.com/three");
NavigateAndCommit(GURL("https://example.com"));
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
3));
auto candidate_1 = blink::mojom::SpeculationCandidate::New();
candidate_1->url = url_1;
candidate_1->action = blink::mojom::SpeculationAction::kPrefetch;
candidate_1->eagerness = blink::mojom::SpeculationEagerness::kImmediate;
candidate_1->referrer = blink::mojom::Referrer::New();
auto candidate_2 = candidate_1.Clone();
candidate_2->url = url_2;
auto candidate_3 = candidate_1.Clone();
candidate_3->url = url_3;
auto* prefetch_document_manager =
PrefetchDocumentManager::GetOrCreateForCurrentDocument(main_rfh());
ASSERT_TRUE(prefetch_document_manager);
base::MockRepeatingCallback<void(const GURL& url)> mock_destruction_callback;
EXPECT_CALL(mock_destruction_callback, Run(url_2)).Times(1);
prefetch_document_manager->SetPrefetchDestructionCallback(
mock_destruction_callback.Get());
std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
candidates.push_back(candidate_1.Clone());
candidates.push_back(candidate_2.Clone());
candidates.push_back(candidate_3.Clone());
prefetch_document_manager->ProcessCandidates(candidates);
task_environment()->RunUntilIdle();
VerifyCommonRequestState(url_1);
candidates.clear();
candidates.push_back(candidate_1.Clone());
candidates.push_back(candidate_3.Clone());
prefetch_document_manager->ProcessCandidates(candidates);
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
false,
{{"X-Testing", "Hello World"}}, kHTMLBody);
VerifyCommonRequestState(url_3);
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
false,
{{"X-Testing", "Hello World"}}, kHTMLBody);
EXPECT_EQ(RequestCount(), 0);
}
TEST_P(PrefetchServiceLimitsTest,
InProgressPrefetchWithNoCandidateIsCancelled) {
const GURL url_1 = GURL("https://example.com/one");
const GURL url_2 = GURL("https://example.com/two");
NavigateAndCommit(GURL("https://example.com"));
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
2));
auto candidate_1 = blink::mojom::SpeculationCandidate::New();
candidate_1->url = url_1;
candidate_1->action = blink::mojom::SpeculationAction::kPrefetch;
candidate_1->eagerness = blink::mojom::SpeculationEagerness::kImmediate;
candidate_1->referrer = blink::mojom::Referrer::New();
auto candidate_2 = candidate_1.Clone();
candidate_2->url = url_2;
auto* prefetch_document_manager =
PrefetchDocumentManager::GetOrCreateForCurrentDocument(main_rfh());
ASSERT_TRUE(prefetch_document_manager);
base::MockRepeatingCallback<void(const GURL& url)> mock_destruction_callback;
EXPECT_CALL(mock_destruction_callback, Run(url_1)).Times(1);
prefetch_document_manager->SetPrefetchDestructionCallback(
mock_destruction_callback.Get());
std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
candidates.push_back(candidate_1.Clone());
candidates.push_back(candidate_2.Clone());
prefetch_document_manager->ProcessCandidates(candidates);
task_environment()->RunUntilIdle();
VerifyCommonRequestState(url_1);
candidates.clear();
candidates.push_back(candidate_2.Clone());
prefetch_document_manager->ProcessCandidates(candidates);
task_environment()->RunUntilIdle();
EXPECT_EQ(test_url_loader_factory_.pending_requests()->size(), 2u);
EXPECT_FALSE(
test_url_loader_factory_.GetPendingRequest(0)->client.is_connected());
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
false,
{{"X-Testing", "Hello World"}}, kHTMLBody);
VerifyCommonRequestState(url_2);
NavigateInitiatedByRenderer(url_1);
PrefetchServingHandle serving_handle = GetPrefetchToServe(url_1);
EXPECT_FALSE(serving_handle);
}
TEST_P(PrefetchServiceLimitsTest, CompletedPrefetchWithNoCandidateIsEvicted) {
const GURL url_1 = GURL("https://example.com/one");
const GURL url_2 = GURL("https://example.com/two");
const GURL url_3 = GURL("https://example.com/three");
NavigateAndCommit(GURL("https://example.com"));
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
2));
auto candidate_1 = blink::mojom::SpeculationCandidate::New();
candidate_1->url = url_1;
candidate_1->action = blink::mojom::SpeculationAction::kPrefetch;
candidate_1->eagerness = blink::mojom::SpeculationEagerness::kImmediate;
candidate_1->referrer = blink::mojom::Referrer::New();
auto candidate_2 = candidate_1.Clone();
candidate_2->url = url_2;
auto* prefetch_document_manager =
PrefetchDocumentManager::GetOrCreateForCurrentDocument(main_rfh());
ASSERT_TRUE(prefetch_document_manager);
base::MockRepeatingCallback<void(const GURL& url)> mock_destruction_callback;
EXPECT_CALL(mock_destruction_callback, Run(url_1)).Times(1);
prefetch_document_manager->SetPrefetchDestructionCallback(
mock_destruction_callback.Get());
std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
candidates.push_back(candidate_1.Clone());
candidates.push_back(candidate_2.Clone());
prefetch_document_manager->ProcessCandidates(candidates);
task_environment()->RunUntilIdle();
auto prefetch_1 = CompleteExistingPrefetch(url_1);
ASSERT_TRUE(prefetch_1);
auto prefetch_2 = CompleteExistingPrefetch(url_2);
ASSERT_TRUE(prefetch_2);
candidates.clear();
candidates.push_back(candidate_2.Clone());
prefetch_document_manager->ProcessCandidates(candidates);
task_environment()->RunUntilIdle();
EXPECT_FALSE(prefetch_1);
EXPECT_TRUE(prefetch_2);
}
TEST_P(PrefetchServiceLimitsTest, PrefetchReset) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeaturesAndParameters(
{{features::kPrefetchUseContentRefactor,
{{"ineligible_decoy_request_probability", "0"},
{"prefetch_container_lifetime_s", "1"}}}},
{});
NavigateAndCommit(GURL("https://example.com"));
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
2));
auto* prefetch_document_manager =
PrefetchDocumentManager::GetOrCreateForCurrentDocument(main_rfh());
const auto url = GURL("https://example.com/one");
base::MockRepeatingCallback<void(const GURL& url)> mock_destruction_callback;
EXPECT_CALL(mock_destruction_callback, Run(url)).Times(1);
prefetch_document_manager->SetPrefetchDestructionCallback(
mock_destruction_callback.Get());
auto candidate = blink::mojom::SpeculationCandidate::New();
candidate->url = url;
candidate->action = blink::mojom::SpeculationAction::kPrefetch;
candidate->eagerness = blink::mojom::SpeculationEagerness::kImmediate;
candidate->referrer = blink::mojom::Referrer::New();
std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
candidates.push_back(candidate.Clone());
prefetch_document_manager->ProcessCandidates(candidates);
auto prefetch = CompleteExistingPrefetch(url);
ASSERT_TRUE(prefetch);
EXPECT_EQ(prefetch.GetPrefetchStatus(), PrefetchStatus::kPrefetchSuccessful);
task_environment()->FastForwardBy(base::Seconds(1));
EXPECT_FALSE(prefetch);
candidates.clear();
candidates.push_back(candidate.Clone());
prefetch_document_manager->ProcessCandidates(candidates);
VerifyCommonRequestState(url);
{
const auto source_id = ForceLogsUploadAndGetUkmId();
auto actual_attempts = test_ukm_recorder()->GetEntries(
ukm::builders::Preloading_Attempt::kEntryName,
test::kPreloadingAttemptUkmMetrics);
EXPECT_EQ(actual_attempts.size(), 2u);
std::vector<ukm::TestUkmRecorder::HumanReadableUkmEntry> expected_attempts =
{attempt_entry_builder()->BuildEntry(
source_id, PreloadingType::kPrefetch,
PreloadingEligibility::kEligible,
PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kFailure,
ToPreloadingFailureReason(PrefetchStatus::kPrefetchIsStale),
true,
base::ScopedMockElapsedTimersForTest::kMockElapsedTime,
blink::mojom::SpeculationEagerness::kImmediate),
attempt_entry_builder()->BuildEntry(
source_id, PreloadingType::kPrefetch,
PreloadingEligibility::kEligible,
PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kRunning,
PreloadingFailureReason::kUnspecified,
false,
std::nullopt,
blink::mojom::SpeculationEagerness::kImmediate)};
EXPECT_THAT(actual_attempts,
testing::UnorderedElementsAreArray(expected_attempts))
<< test::ActualVsExpectedUkmEntriesToString(actual_attempts,
expected_attempts);
}
}
TEST_P(PrefetchServiceLimitsTest, NextPrefetchQueuedImmediatelyAfterReset) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeaturesAndParameters(
{
{features::kPrefetchUseContentRefactor,
{{"ineligible_decoy_request_probability", "0"},
{"prefetch_container_lifetime_s", "1"}}},
},
{});
NavigateAndCommit(GURL("https://example.com"));
MakePrefetchService(std::make_unique<
testing::NiceMock<MockPrefetchServiceDelegate>>(
kMaxNumberOfImmediatePrefetchesPerPage +
1));
auto* prefetch_document_manager =
PrefetchDocumentManager::GetOrCreateForCurrentDocument(main_rfh());
std::vector<GURL> urls;
for (size_t i = 0; i < kMaxNumberOfImmediatePrefetchesPerPage + 1; ++i) {
urls.emplace_back("https://example.com/" + base::NumberToString(i));
}
base::MockRepeatingCallback<void(const GURL& url)> mock_destruction_callback;
for (size_t i = 0; i < kMaxNumberOfImmediatePrefetchesPerPage; ++i) {
EXPECT_CALL(mock_destruction_callback, Run(urls[i])).Times(1);
}
prefetch_document_manager->SetPrefetchDestructionCallback(
mock_destruction_callback.Get());
auto base_candidate = blink::mojom::SpeculationCandidate::New();
base_candidate->action = blink::mojom::SpeculationAction::kPrefetch;
base_candidate->eagerness = blink::mojom::SpeculationEagerness::kImmediate;
base_candidate->referrer = blink::mojom::Referrer::New();
std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
for (const auto& url : urls) {
auto candidate = base_candidate.Clone();
candidate->url = url;
candidates.push_back(std::move(candidate));
}
prefetch_document_manager->ProcessCandidates(candidates);
task_environment()->RunUntilIdle();
std::vector<PrefetchServingHandle> prefetches;
for (size_t i = 0; i < kMaxNumberOfImmediatePrefetchesPerPage; ++i) {
auto prefetch = CompleteExistingPrefetch(urls[i]);
ASSERT_TRUE(prefetch);
EXPECT_EQ(prefetch.GetPrefetchStatus(),
PrefetchStatus::kPrefetchSuccessful);
prefetches.push_back(std::move(prefetch));
}
EXPECT_EQ(RequestCount(), 0);
task_environment()->FastForwardBy(base::Seconds(1));
for (auto& prefetch : prefetches) {
EXPECT_FALSE(prefetch);
}
VerifyCommonRequestState(urls[kMaxNumberOfImmediatePrefetchesPerPage]);
}
TEST_P(PrefetchServiceTest, PrefetchQueueNotStuckWhenResettingRunningPrefetch) {
NavigateAndCommit(GURL("https://example.com"));
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
0));
const auto url_1 = GURL("https://example.com/one");
const auto url_2 = GURL("https://example.com/two");
auto handle_1 =
MakePrefetchFromBrowserContext(url_1, std::nullopt, {}, nullptr);
auto handle_2 =
MakePrefetchFromBrowserContext(url_2, std::nullopt, {}, nullptr);
task_environment()->RunUntilIdle();
base::WeakPtr<PrefetchContainer> prefetch_container1, prefetch_container2;
std::tie(std::ignore, prefetch_container1) =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(std::nullopt, url_1))[0];
std::tie(std::ignore, prefetch_container2) =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(std::nullopt, url_2))[0];
ASSERT_EQ(prefetch_container1->GetLoadState(),
PrefetchContainer::LoadState::kStarted);
ASSERT_EQ(prefetch_container2->GetLoadState(),
PrefetchContainer::LoadState::kEligible);
handle_1.reset();
task_environment()->RunUntilIdle();
EXPECT_FALSE(prefetch_container1);
EXPECT_EQ(prefetch_container2->GetLoadState(),
PrefetchContainer::LoadState::kStarted);
}
TEST_P(PrefetchServiceLimitsTest, PrefetchFailsAndIsReset) {
base::HistogramTester histogram_tester;
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeaturesAndParameters(
{{features::kPrefetchUseContentRefactor,
{{"ineligible_decoy_request_probability", "0"},
{"prefetch_container_lifetime_s", "1"}}}},
{});
NavigateAndCommit(GURL("https://example.com"));
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
1));
auto* prefetch_document_manager =
PrefetchDocumentManager::GetOrCreateForCurrentDocument(main_rfh());
const auto url = GURL("https://example.com/one");
base::MockRepeatingCallback<void(const GURL& url)> mock_destruction_callback;
EXPECT_CALL(mock_destruction_callback, Run(url)).Times(1);
prefetch_document_manager->SetPrefetchDestructionCallback(
mock_destruction_callback.Get());
auto candidate = blink::mojom::SpeculationCandidate::New();
candidate->url = url;
candidate->action = blink::mojom::SpeculationAction::kPrefetch;
candidate->eagerness = blink::mojom::SpeculationEagerness::kImmediate;
candidate->referrer = blink::mojom::Referrer::New();
std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
candidates.push_back(candidate.Clone());
prefetch_document_manager->ProcessCandidates(candidates);
base::RunLoop().RunUntilIdle();
VerifyCommonRequestState(url);
MakeResponseAndWait(net::HTTP_OK, net::ERR_FAILED, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}}, kHTMLBody);
task_environment()->FastForwardBy(base::Seconds(1));
ExpectCorrectUkmLogs({.outcome = PreloadingTriggeringOutcome::kFailure,
.failure = ToPreloadingFailureReason(
PrefetchStatus::kPrefetchFailedNetError)});
histogram_tester.ExpectUniqueSample("Preloading.Prefetch.PrefetchStatus",
PrefetchStatus::kPrefetchFailedNetError,
1);
}
TEST_P(PrefetchServiceLimitsTest, ImmediatePrefetchLimitIsDynamic) {
ASSERT_GE(kMaxNumberOfImmediatePrefetchesPerPage, 2u);
std::vector<GURL> urls;
for (size_t i = 0; i < kMaxNumberOfImmediatePrefetchesPerPage + 1; ++i) {
urls.emplace_back("https://example.com/" + base::NumberToString(i));
}
NavigateAndCommit(GURL("https://example.com"));
MakePrefetchService(std::make_unique<
testing::NiceMock<MockPrefetchServiceDelegate>>(
kMaxNumberOfImmediatePrefetchesPerPage +
2));
auto* prefetch_document_manager =
PrefetchDocumentManager::GetOrCreateForCurrentDocument(main_rfh());
ASSERT_TRUE(prefetch_document_manager);
base::MockRepeatingCallback<void(const GURL&)> destruction_cb;
EXPECT_CALL(destruction_cb, Run(urls[0])).Times(1);
EXPECT_CALL(destruction_cb, Run(urls[1])).Times(1);
prefetch_document_manager->SetPrefetchDestructionCallback(
destruction_cb.Get());
auto base_candidate = blink::mojom::SpeculationCandidate::New();
base_candidate->action = blink::mojom::SpeculationAction::kPrefetch;
base_candidate->eagerness = blink::mojom::SpeculationEagerness::kImmediate;
base_candidate->referrer = blink::mojom::Referrer::New();
std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
for (size_t i = 0; i < kMaxNumberOfImmediatePrefetchesPerPage; ++i) {
auto candidate = base_candidate.Clone();
candidate->url = urls[i];
candidates.push_back(std::move(candidate));
}
prefetch_document_manager->ProcessCandidates(candidates);
task_environment()->RunUntilIdle();
std::vector<PrefetchServingHandle> prefetches;
for (size_t i = 0; i < kMaxNumberOfImmediatePrefetchesPerPage; ++i) {
auto prefetch = CompleteExistingPrefetch(urls[i]);
ASSERT_TRUE(prefetch);
EXPECT_EQ(prefetch.GetPrefetchStatus(),
PrefetchStatus::kPrefetchSuccessful);
prefetches.push_back(std::move(prefetch));
}
candidates.clear();
for (size_t i = 1; i < kMaxNumberOfImmediatePrefetchesPerPage + 1; ++i) {
auto candidate = base_candidate.Clone();
candidate->url = urls[i];
candidates.push_back(std::move(candidate));
}
prefetch_document_manager->ProcessCandidates(candidates);
task_environment()->RunUntilIdle();
auto prefetch_extra =
CompleteExistingPrefetch(urls[kMaxNumberOfImmediatePrefetchesPerPage]);
ASSERT_TRUE(prefetch_extra);
EXPECT_EQ(prefetch_extra.GetPrefetchStatus(),
PrefetchStatus::kPrefetchSuccessful);
EXPECT_FALSE(prefetches[0]);
for (size_t i = 1; i < kMaxNumberOfImmediatePrefetchesPerPage; ++i) {
EXPECT_TRUE(prefetches[i]);
}
candidates.clear();
for (size_t i = 0; i < kMaxNumberOfImmediatePrefetchesPerPage + 1; ++i) {
auto candidate = base_candidate.Clone();
candidate->url = urls[i];
candidates.push_back(std::move(candidate));
}
prefetch_document_manager->ProcessCandidates(candidates);
task_environment()->RunUntilIdle();
EXPECT_EQ(RequestCount(), 0);
candidates.clear();
for (size_t i = 0; i < kMaxNumberOfImmediatePrefetchesPerPage + 1; ++i) {
if (i != 1) {
auto candidate = base_candidate.Clone();
candidate->url = urls[i];
candidates.push_back(std::move(candidate));
}
}
prefetch_document_manager->ProcessCandidates(candidates);
task_environment()->RunUntilIdle();
auto prefetch_0_new = CompleteExistingPrefetch(urls[0]);
ASSERT_TRUE(prefetch_0_new);
EXPECT_EQ(prefetch_0_new.GetPrefetchStatus(),
PrefetchStatus::kPrefetchSuccessful);
EXPECT_FALSE(prefetches[1]);
for (size_t i = 2; i < kMaxNumberOfImmediatePrefetchesPerPage; ++i) {
EXPECT_TRUE(prefetches[i]);
}
{
const auto source_id = ForceLogsUploadAndGetUkmId();
auto actual_attempts = test_ukm_recorder()->GetEntries(
ukm::builders::Preloading_Attempt::kEntryName,
test::kPreloadingAttemptUkmMetrics);
EXPECT_EQ(actual_attempts.size(),
kMaxNumberOfImmediatePrefetchesPerPage + 2);
std::vector<ukm::TestUkmRecorder::HumanReadableUkmEntry> expected_attempts;
expected_attempts.push_back(attempt_entry_builder()->BuildEntry(
source_id, PreloadingType::kPrefetch, PreloadingEligibility::kEligible,
PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kFailure,
ToPreloadingFailureReason(
content::PrefetchStatus::kPrefetchEvictedAfterCandidateRemoved),
true,
base::ScopedMockElapsedTimersForTest::kMockElapsedTime,
blink::mojom::SpeculationEagerness::kImmediate));
expected_attempts.push_back(attempt_entry_builder()->BuildEntry(
source_id, PreloadingType::kPrefetch, PreloadingEligibility::kEligible,
PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kFailure,
ToPreloadingFailureReason(
content::PrefetchStatus::kPrefetchEvictedAfterCandidateRemoved),
true,
base::ScopedMockElapsedTimersForTest::kMockElapsedTime,
blink::mojom::SpeculationEagerness::kImmediate));
for (size_t i = 2; i < kMaxNumberOfImmediatePrefetchesPerPage; ++i) {
expected_attempts.push_back(attempt_entry_builder()->BuildEntry(
source_id, PreloadingType::kPrefetch,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kReady,
PreloadingFailureReason::kUnspecified,
true,
base::ScopedMockElapsedTimersForTest::kMockElapsedTime,
blink::mojom::SpeculationEagerness::kImmediate));
}
expected_attempts.push_back(attempt_entry_builder()->BuildEntry(
source_id, PreloadingType::kPrefetch, PreloadingEligibility::kEligible,
PreloadingHoldbackStatus::kAllowed, PreloadingTriggeringOutcome::kReady,
PreloadingFailureReason::kUnspecified,
true,
base::ScopedMockElapsedTimersForTest::kMockElapsedTime,
blink::mojom::SpeculationEagerness::kImmediate));
expected_attempts.push_back(attempt_entry_builder()->BuildEntry(
source_id, PreloadingType::kPrefetch, PreloadingEligibility::kEligible,
PreloadingHoldbackStatus::kAllowed, PreloadingTriggeringOutcome::kReady,
PreloadingFailureReason::kUnspecified,
true,
base::ScopedMockElapsedTimersForTest::kMockElapsedTime,
blink::mojom::SpeculationEagerness::kImmediate));
EXPECT_THAT(actual_attempts,
testing::UnorderedElementsAreArray(expected_attempts))
<< test::ActualVsExpectedUkmEntriesToString(actual_attempts,
expected_attempts);
}
}
TEST_P(PrefetchServiceLimitsTest, RemoveCandidateForFailedPrefetch) {
const GURL url = GURL("https://example.com/one");
NavigateAndCommit(GURL("https://example.com"));
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
1));
auto candidate = blink::mojom::SpeculationCandidate::New();
candidate->url = url;
candidate->action = blink::mojom::SpeculationAction::kPrefetch;
candidate->eagerness = blink::mojom::SpeculationEagerness::kImmediate;
candidate->referrer = blink::mojom::Referrer::New();
auto* prefetch_document_manager =
PrefetchDocumentManager::GetOrCreateForCurrentDocument(main_rfh());
ASSERT_TRUE(prefetch_document_manager);
base::MockRepeatingCallback<void(const GURL& url)> mock_destruction_callback;
EXPECT_CALL(mock_destruction_callback, Run(url)).Times(1);
prefetch_document_manager->SetPrefetchDestructionCallback(
mock_destruction_callback.Get());
std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
candidates.push_back(candidate.Clone());
prefetch_document_manager->ProcessCandidates(candidates);
task_environment()->RunUntilIdle();
VerifyCommonRequestState(url);
MakeResponseAndWait(net::HTTP_OK, net::ERR_FAILED, kHTMLMimeType,
false,
{{"X-Testing", "Hello World"}}, kHTMLBody);
candidates.clear();
prefetch_document_manager->ProcessCandidates(candidates);
task_environment()->RunUntilIdle();
ExpectCorrectUkmLogs({.outcome = PreloadingTriggeringOutcome::kFailure,
.failure = ToPreloadingFailureReason(
PrefetchStatus::kPrefetchFailedNetError)});
}
blink::UserAgentMetadata GetFakeUserAgentMetadata() {
blink::UserAgentMetadata metadata;
metadata.brand_version_list.emplace_back("fake", "42");
metadata.brand_full_version_list.emplace_back("fake", "42.0.1701.99");
metadata.full_version = "42.0.1701.99";
return metadata;
}
class PrefetchServiceClientHintsTest
: public PrefetchServiceTestBase,
public WithPrefetchRearchParam,
public ::testing::WithParamInterface<PrefetchRearchParam> {
public:
PrefetchServiceClientHintsTest() : WithPrefetchRearchParam(GetParam()) {}
void InitScopedFeatureList() override {
InitBaseParams();
InitRearchFeatures();
}
protected:
std::unique_ptr<BrowserContext> CreateBrowserContext() override {
auto browser_context = PrefetchServiceTestBase::CreateBrowserContext();
TestBrowserContext::FromBrowserContext(browser_context.get())
->SetClientHintsControllerDelegate(&client_hints_controller_delegate_);
return browser_context;
}
MockClientHintsControllerDelegate& client_hints_controller_delegate() {
return client_hints_controller_delegate_;
}
private:
MockClientHintsControllerDelegate client_hints_controller_delegate_{
GetFakeUserAgentMetadata()};
};
INSTANTIATE_TEST_SUITE_P(ParametrizedTests,
PrefetchServiceClientHintsTest,
testing::ValuesIn(PrefetchRearchParam::Params()));
TEST_P(PrefetchServiceClientHintsTest, NoClientHintsWhenDisabled) {
base::test::ScopedFeatureList disable_prefetch_ch;
disable_prefetch_ch.InitAndDisableFeature(features::kPrefetchClientHints);
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
NavigateAndCommit(GURL("https://example.com/"));
MakePrefetchOnMainFrame(
GURL("https://example.com/two"),
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false,
blink::mojom::SpeculationEagerness::kImmediate),
blink::mojom::Referrer(GURL("https://example.com/"),
network::mojom::ReferrerPolicy::kStrictOrigin));
auto* pending = test_url_loader_factory_.GetPendingRequest(0);
ASSERT_TRUE(pending);
EXPECT_FALSE(pending->request.headers.HasHeader("Sec-CH-UA"));
}
TEST_P(PrefetchServiceClientHintsTest, LowEntropyClientHints) {
base::test::ScopedFeatureList enable_prefetch_ch;
enable_prefetch_ch.InitAndEnableFeature(features::kPrefetchClientHints);
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
NavigateAndCommit(GURL("https://example.com/"));
MakePrefetchOnMainFrame(
GURL("https://example.com/two"),
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false,
blink::mojom::SpeculationEagerness::kImmediate),
blink::mojom::Referrer(GURL("https://example.com/"),
network::mojom::ReferrerPolicy::kStrictOrigin));
auto* pending = test_url_loader_factory_.GetPendingRequest(0);
ASSERT_TRUE(pending);
EXPECT_TRUE(pending->request.headers.HasHeader("Sec-CH-UA"));
}
TEST_P(PrefetchServiceClientHintsTest, HighEntropyClientHints) {
base::test::ScopedFeatureList enable_prefetch_ch;
enable_prefetch_ch.InitAndEnableFeature(features::kPrefetchClientHints);
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
NavigateAndCommit(GURL("https://example.com/"));
client_hints_controller_delegate().SetMostRecentMainFrameViewportSize(
gfx::Size(800, 600));
client_hints_controller_delegate().PersistClientHints(
url::Origin::Create(GURL("https://example.com/")),
nullptr,
{network::mojom::WebClientHintsType::kViewportWidth});
MakePrefetchOnMainFrame(
GURL("https://example.com/two"),
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false,
blink::mojom::SpeculationEagerness::kImmediate),
blink::mojom::Referrer(GURL("https://example.com/"),
network::mojom::ReferrerPolicy::kStrictOrigin));
auto* pending = test_url_loader_factory_.GetPendingRequest(0);
ASSERT_TRUE(pending);
EXPECT_TRUE(pending->request.headers.HasHeader("Sec-CH-UA"));
std::optional<std::string> viewport_width =
pending->request.headers.GetHeader("Sec-CH-Viewport-Width");
EXPECT_TRUE(viewport_width.has_value());
int viewport_width_int;
EXPECT_TRUE(base::StringToInt(*viewport_width, &viewport_width_int));
EXPECT_GT(viewport_width_int, 0);
}
TEST_P(PrefetchServiceClientHintsTest, CrossSiteNone) {
base::test::ScopedFeatureList enable_prefetch_ch;
enable_prefetch_ch.InitAndEnableFeatureWithParameters(
features::kPrefetchClientHints, {{"cross_site_behavior", "none"}});
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
NavigateAndCommit(GURL("https://a.test/"));
client_hints_controller_delegate().SetMostRecentMainFrameViewportSize(
gfx::Size(800, 600));
client_hints_controller_delegate().PersistClientHints(
url::Origin::Create(GURL("https://b.test/")),
nullptr,
{network::mojom::WebClientHintsType::kViewportWidth});
MakePrefetchOnMainFrame(
GURL("https://b.test/"),
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false,
blink::mojom::SpeculationEagerness::kImmediate),
blink::mojom::Referrer(GURL("https://a.test/"),
network::mojom::ReferrerPolicy::kStrictOrigin));
auto* pending = test_url_loader_factory_.GetPendingRequest(0);
ASSERT_TRUE(pending);
EXPECT_FALSE(pending->request.headers.HasHeader("Sec-CH-UA"));
EXPECT_FALSE(pending->request.headers.HasHeader("Sec-CH-Viewport-Width"));
}
TEST_P(PrefetchServiceClientHintsTest, CrossSiteLowEntropy) {
base::test::ScopedFeatureList enable_prefetch_ch;
enable_prefetch_ch.InitAndEnableFeatureWithParameters(
features::kPrefetchClientHints, {{"cross_site_behavior", "low_entropy"}});
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
NavigateAndCommit(GURL("https://a.test/"));
client_hints_controller_delegate().SetMostRecentMainFrameViewportSize(
gfx::Size(800, 600));
client_hints_controller_delegate().PersistClientHints(
url::Origin::Create(GURL("https://b.test/")),
nullptr,
{network::mojom::WebClientHintsType::kViewportWidth});
MakePrefetchOnMainFrame(
GURL("https://b.test/"),
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false,
blink::mojom::SpeculationEagerness::kImmediate),
blink::mojom::Referrer(GURL("https://a.test/"),
network::mojom::ReferrerPolicy::kStrictOrigin));
auto* pending = test_url_loader_factory_.GetPendingRequest(0);
ASSERT_TRUE(pending);
EXPECT_TRUE(pending->request.headers.HasHeader("Sec-CH-UA"));
EXPECT_FALSE(pending->request.headers.HasHeader("Sec-CH-Viewport-Width"));
}
TEST_P(PrefetchServiceClientHintsTest, CrossSiteAll) {
base::test::ScopedFeatureList enable_prefetch_ch;
enable_prefetch_ch.InitAndEnableFeatureWithParameters(
features::kPrefetchClientHints, {{"cross_site_behavior", "all"}});
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
NavigateAndCommit(GURL("https://a.test/"));
client_hints_controller_delegate().SetMostRecentMainFrameViewportSize(
gfx::Size(800, 600));
client_hints_controller_delegate().PersistClientHints(
url::Origin::Create(GURL("https://b.test/")),
nullptr,
{network::mojom::WebClientHintsType::kViewportWidth});
MakePrefetchOnMainFrame(
GURL("https://b.test/"),
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false,
blink::mojom::SpeculationEagerness::kImmediate),
blink::mojom::Referrer(GURL("https://a.test/"),
network::mojom::ReferrerPolicy::kStrictOrigin));
auto* pending = test_url_loader_factory_.GetPendingRequest(0);
ASSERT_TRUE(pending);
EXPECT_TRUE(pending->request.headers.HasHeader("Sec-CH-UA"));
EXPECT_TRUE(pending->request.headers.HasHeader("Sec-CH-Viewport-Width"));
}
TEST_P(PrefetchServiceTest, CancelWhileBlockedOnHead) {
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
NavigateAndCommit(GURL("https://example.com/"));
GURL next_url("https://example.com/two");
MakePrefetchOnMainFrame(
next_url,
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false,
blink::mojom::SpeculationEagerness::kImmediate),
blink::mojom::Referrer(GURL("https://example.com/"),
network::mojom::ReferrerPolicy::kStrictOrigin));
NavigateInitiatedByRenderer(next_url);
base::test::TestFuture<PrefetchServingHandle> future;
GetPrefetchToServe(future, next_url, MainDocumentToken());
EXPECT_FALSE(future.IsReady());
auto* prefetch_document_manager =
PrefetchDocumentManager::GetOrCreateForCurrentDocument(main_rfh());
std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
prefetch_document_manager->ProcessCandidates(candidates);
task_environment()->RunUntilIdle();
ASSERT_TRUE(future.IsReady());
EXPECT_FALSE(future.Get());
}
enum class Event {
kInitialEligibilityCheckCompleteSuccess,
kInitialEligibilityCheckCompleteFailure,
kRedirectEligibilityCheckCompleteSuccess,
kRedirectEligibilityCheckCompleteFailure,
kPrefetchURLLoaderOnReceiveRedirect,
kPrefetchURLLoaderOnReceiveRedirectCrossSite,
kPrefetchURLLoaderOnReceiveResponseSuccess,
kPrefetchURLLoaderOnReceiveResponseFailure,
kPrefetchURLLoaderOnCompleteSuccess,
kPrefetchURLLoaderOnCompleteFailure,
kPrefetchURLLoaderDisconnect,
kStartServing,
kResetPrefetchContainer,
kObserverOnWillBeDestroyed,
kObserverOnGotInitialEligibility,
kObserverOnDeterminedHead,
kObserverOnPrefetchCompletedOrFailed,
};
std::string GetEventName(Event event) {
switch (event) {
case Event::kInitialEligibilityCheckCompleteSuccess:
return "Event::kInitialEligibilityCheckCompleteSuccess";
case Event::kInitialEligibilityCheckCompleteFailure:
return "Event::kInitialEligibilityCheckCompleteFailure";
case Event::kRedirectEligibilityCheckCompleteSuccess:
return "Event::kRedirectEligibilityCheckCompleteSuccess";
case Event::kRedirectEligibilityCheckCompleteFailure:
return "Event::kRedirectEligibilityCheckCompleteFailure";
case Event::kPrefetchURLLoaderOnReceiveRedirect:
return "Event::kPrefetchURLLoaderOnReceiveRedirect";
case Event::kPrefetchURLLoaderOnReceiveRedirectCrossSite:
return "Event::kPrefetchURLLoaderOnReceiveRedirectCrossSite";
case Event::kPrefetchURLLoaderOnReceiveResponseSuccess:
return "Event::kPrefetchURLLoaderOnReceiveResponseSuccess";
case Event::kPrefetchURLLoaderOnReceiveResponseFailure:
return "Event::kPrefetchURLLoaderOnReceiveResponseFailure";
case Event::kPrefetchURLLoaderOnCompleteSuccess:
return "Event::kPrefetchURLLoaderOnCompleteSuccess";
case Event::kPrefetchURLLoaderOnCompleteFailure:
return "Event::kPrefetchURLLoaderOnCompleteFailure";
case Event::kPrefetchURLLoaderDisconnect:
return "Event::kPrefetchURLLoaderDisconnect";
case Event::kStartServing:
return "Event::kStartServing";
case Event::kResetPrefetchContainer:
return "Event::kResetPrefetchContainer";
case Event::kObserverOnWillBeDestroyed:
return "Event::kObserverOnWillBeDestroyed";
case Event::kObserverOnGotInitialEligibility:
return "Event::kObserverOnGotInitialEligibility";
case Event::kObserverOnDeterminedHead:
return "Event::kObserverOnDeterminedHead";
case Event::kObserverOnPrefetchCompletedOrFailed:
return "Event::kObserverOnPrefetchCompletedOrFailed";
}
}
std::ostream& operator<<(std::ostream& os, Event event) {
return os << GetEventName(event);
}
std::vector<Event> CalculateOutputEventSequence(
const std::vector<Event>& input_event_sequence) {
std::vector<Event> output_event_sequence;
bool prefetch_completed = false;
bool determined_head_issued = false;
bool prefetch_container_destroyed = false;
bool during_redirect_eligibility_check = false;
for (const Event event : input_event_sequence) {
output_event_sequence.push_back(event);
if (prefetch_container_destroyed) {
continue;
}
switch (event) {
case Event::kInitialEligibilityCheckCompleteSuccess:
case Event::kInitialEligibilityCheckCompleteFailure:
output_event_sequence.push_back(
Event::kObserverOnGotInitialEligibility);
break;
case Event::kRedirectEligibilityCheckCompleteSuccess:
case Event::kRedirectEligibilityCheckCompleteFailure:
if (prefetch_completed) {
break;
}
CHECK(during_redirect_eligibility_check);
during_redirect_eligibility_check = false;
if (event == Event::kRedirectEligibilityCheckCompleteFailure) {
CHECK(!determined_head_issued);
determined_head_issued = true;
output_event_sequence.push_back(Event::kObserverOnDeterminedHead);
prefetch_completed = true;
}
break;
case Event::kPrefetchURLLoaderOnReceiveRedirect:
case Event::kPrefetchURLLoaderOnReceiveRedirectCrossSite:
CHECK(!prefetch_completed);
CHECK(!during_redirect_eligibility_check);
during_redirect_eligibility_check = true;
break;
case Event::kPrefetchURLLoaderOnReceiveResponseSuccess:
case Event::kPrefetchURLLoaderOnReceiveResponseFailure:
CHECK(!prefetch_completed);
CHECK(!determined_head_issued);
determined_head_issued = true;
output_event_sequence.push_back(Event::kObserverOnDeterminedHead);
break;
case Event::kPrefetchURLLoaderOnCompleteSuccess:
CHECK(!prefetch_completed);
CHECK(determined_head_issued);
prefetch_completed = true;
output_event_sequence.push_back(
Event::kObserverOnPrefetchCompletedOrFailed);
break;
case Event::kPrefetchURLLoaderDisconnect:
if (prefetch_completed) {
break;
}
if (!base::FeatureList::IsEnabled(
features::kPrefetchGracefulNotification)) {
prefetch_completed = true;
if (!during_redirect_eligibility_check) {
break;
}
if (!determined_head_issued) {
determined_head_issued = true;
output_event_sequence.push_back(Event::kObserverOnDeterminedHead);
}
break;
}
[[fallthrough]];
case Event::kPrefetchURLLoaderOnCompleteFailure:
CHECK(!prefetch_completed);
prefetch_completed = true;
during_redirect_eligibility_check = false;
if (!determined_head_issued) {
determined_head_issued = true;
output_event_sequence.push_back(Event::kObserverOnDeterminedHead);
}
output_event_sequence.push_back(
Event::kObserverOnPrefetchCompletedOrFailed);
break;
case Event::kStartServing:
break;
case Event::kResetPrefetchContainer:
prefetch_container_destroyed = true;
output_event_sequence.push_back(Event::kObserverOnWillBeDestroyed);
if (!base::FeatureList::IsEnabled(
features::kPrefetchGracefulNotification) &&
!prefetch_completed && during_redirect_eligibility_check) {
CHECK(!determined_head_issued);
determined_head_issued = true;
output_event_sequence.push_back(Event::kObserverOnDeterminedHead);
}
break;
case Event::kObserverOnWillBeDestroyed:
case Event::kObserverOnGotInitialEligibility:
case Event::kObserverOnDeterminedHead:
case Event::kObserverOnPrefetchCompletedOrFailed:
NOTREACHED();
}
}
return output_event_sequence;
}
std::vector<Event> TrimEventSequence(std::vector<Event> event_sequence) {
std::vector<Event> trimmed_event_sequence;
bool serving_started = false;
bool prefetch_completed = false;
bool redirect_received_before_terminated = false;
for (size_t index = 0; index < event_sequence.size(); ++index) {
switch (event_sequence[index]) {
case Event::kResetPrefetchContainer:
trimmed_event_sequence.push_back(event_sequence[index]);
if (serving_started && !prefetch_completed &&
index + 1 < event_sequence.size() &&
(event_sequence[index + 1] ==
Event::kPrefetchURLLoaderOnCompleteSuccess ||
event_sequence[index + 1] ==
Event::kPrefetchURLLoaderOnCompleteFailure)) {
trimmed_event_sequence.push_back(event_sequence[index + 1]);
}
return trimmed_event_sequence;
case Event::kPrefetchURLLoaderDisconnect:
trimmed_event_sequence.push_back(event_sequence[index]);
break;
case Event::kRedirectEligibilityCheckCompleteSuccess:
case Event::kRedirectEligibilityCheckCompleteFailure:
if (redirect_received_before_terminated) {
trimmed_event_sequence.push_back(event_sequence[index]);
}
break;
default:
if (!prefetch_completed) {
trimmed_event_sequence.push_back(event_sequence[index]);
}
break;
}
switch (event_sequence[index]) {
case Event::kPrefetchURLLoaderOnReceiveRedirect:
case Event::kPrefetchURLLoaderOnReceiveRedirectCrossSite:
CHECK(!redirect_received_before_terminated);
if (!prefetch_completed) {
redirect_received_before_terminated = true;
}
break;
case Event::kPrefetchURLLoaderOnCompleteSuccess:
case Event::kPrefetchURLLoaderOnCompleteFailure:
case Event::kPrefetchURLLoaderDisconnect:
case Event::kRedirectEligibilityCheckCompleteFailure:
prefetch_completed = true;
break;
case Event::kStartServing:
CHECK(!serving_started);
serving_started = true;
break;
default:
break;
}
}
return trimmed_event_sequence;
}
void InsertToEventSequence(std::set<std::vector<Event>>& new_event_sequences,
std::vector<Event> event_sequence,
int index,
Event event) {
if (std::find(event_sequence.begin(), event_sequence.end(), event) !=
event_sequence.end()) {
return;
}
event_sequence.insert(event_sequence.begin() + (index + 1), event);
new_event_sequences.insert(TrimEventSequence(std::move(event_sequence)));
}
std::set<std::vector<Event>> ExpandEventSequence(
std::vector<Event> event_sequence) {
CHECK_EQ(event_sequence[0], Event::kInitialEligibilityCheckCompleteSuccess);
std::set<std::vector<Event>> new_event_sequences;
CHECK_EQ(event_sequence[0], Event::kInitialEligibilityCheckCompleteSuccess);
for (size_t index = 0; index < event_sequence.size(); ++index) {
InsertToEventSequence(new_event_sequences, event_sequence, index,
Event::kPrefetchURLLoaderDisconnect);
InsertToEventSequence(new_event_sequences, event_sequence, index,
Event::kResetPrefetchContainer);
InsertToEventSequence(new_event_sequences, event_sequence, index,
Event::kPrefetchURLLoaderOnCompleteFailure);
if (event_sequence[index] ==
Event::kPrefetchURLLoaderOnReceiveResponseSuccess) {
InsertToEventSequence(new_event_sequences, event_sequence, index,
Event::kStartServing);
}
}
new_event_sequences.insert(event_sequence);
return new_event_sequences;
}
std::set<std::vector<Event>> ValidInputEventSequences() {
std::set<std::vector<Event>> event_sequences = {
{
Event::kInitialEligibilityCheckCompleteSuccess,
Event::kPrefetchURLLoaderOnReceiveRedirect,
Event::kRedirectEligibilityCheckCompleteFailure,
},
{
Event::kInitialEligibilityCheckCompleteSuccess,
Event::kPrefetchURLLoaderOnReceiveRedirectCrossSite,
Event::kRedirectEligibilityCheckCompleteFailure,
},
{
Event::kInitialEligibilityCheckCompleteSuccess,
Event::kPrefetchURLLoaderOnReceiveResponseFailure,
Event::kPrefetchURLLoaderOnCompleteFailure,
},
{
Event::kInitialEligibilityCheckCompleteSuccess,
Event::kPrefetchURLLoaderOnReceiveResponseSuccess,
Event::kPrefetchURLLoaderOnCompleteFailure,
},
{
Event::kInitialEligibilityCheckCompleteSuccess,
Event::kPrefetchURLLoaderOnReceiveResponseSuccess,
Event::kPrefetchURLLoaderOnCompleteSuccess,
},
{
Event::kInitialEligibilityCheckCompleteSuccess,
Event::kPrefetchURLLoaderOnReceiveRedirect,
Event::kRedirectEligibilityCheckCompleteSuccess,
Event::kPrefetchURLLoaderOnReceiveResponseSuccess,
Event::kPrefetchURLLoaderOnCompleteSuccess,
},
{
Event::kInitialEligibilityCheckCompleteSuccess,
Event::kPrefetchURLLoaderOnReceiveRedirectCrossSite,
Event::kRedirectEligibilityCheckCompleteSuccess,
Event::kPrefetchURLLoaderOnReceiveResponseSuccess,
Event::kPrefetchURLLoaderOnCompleteSuccess,
}};
while (true) {
std::set<std::vector<Event>> new_event_sequences;
for (const std::vector<Event>& event_sequence : event_sequences) {
new_event_sequences.merge(ExpandEventSequence(event_sequence));
}
if (new_event_sequences == event_sequences) {
break;
}
event_sequences.swap(new_event_sequences);
}
std::erase_if(event_sequences, [](const std::vector<Event>& event_sequence) {
return std::find(event_sequence.begin(), event_sequence.end(),
Event::kResetPrefetchContainer) == event_sequence.end();
});
event_sequences.insert(
std::vector<Event>({Event::kInitialEligibilityCheckCompleteFailure,
Event::kResetPrefetchContainer}));
event_sequences.insert(std::vector<Event>({Event::kResetPrefetchContainer}));
return event_sequences;
}
class PrefetchServiceEventTest
: public PrefetchServiceTestBase,
public WithPrefetchRearchParam,
public ::testing::WithParamInterface<
std::tuple<PrefetchRearchParam, std::vector<Event>>> {
public:
PrefetchServiceEventTest()
: WithPrefetchRearchParam(std::get<0>(GetParam())) {}
void InitScopedFeatureList() override {
InitBaseParams();
InitRearchFeatures();
}
};
class RecordingPrefetchContainerObserver final
: public PrefetchContainer::Observer {
public:
explicit RecordingPrefetchContainerObserver(
PrefetchContainer& prefetch_container)
: prefetch_container_(prefetch_container.GetWeakPtr()) {
prefetch_container_->AddObserver(this);
}
~RecordingPrefetchContainerObserver() override {
if (prefetch_container_) {
prefetch_container_->RemoveObserver(this);
}
}
const std::vector<Event>& actual_output_event_sequence() const {
return actual_output_event_sequence_;
}
void AddEvent(Event event) { actual_output_event_sequence_.push_back(event); }
private:
void OnWillBeDestroyed(PrefetchContainer& prefetch_container) override {
AddEvent(Event::kObserverOnWillBeDestroyed);
}
void OnGotInitialEligibility(PrefetchContainer& prefetch_container,
PreloadingEligibility eligibility) override {
AddEvent(Event::kObserverOnGotInitialEligibility);
}
void OnDeterminedHead(PrefetchContainer& prefetch_container) override {
switch (prefetch_container.GetLoadState()) {
case PrefetchContainer::LoadState::kNotStarted:
case PrefetchContainer::LoadState::kEligible:
case PrefetchContainer::LoadState::kFailedIneligible:
case PrefetchContainer::LoadState::kStarted:
case PrefetchContainer::LoadState::kFailedHeldback:
NOTREACHED();
case PrefetchContainer::LoadState::kDeterminedHead:
case PrefetchContainer::LoadState::kFailedDeterminedHead:
case PrefetchContainer::LoadState::kCompleted:
case PrefetchContainer::LoadState::kFailed:
break;
}
AddEvent(Event::kObserverOnDeterminedHead);
}
void OnPrefetchCompletedOrFailed(
PrefetchContainer& prefetch_container,
const network::URLLoaderCompletionStatus& completion_status,
const std::optional<int>& response_code) override {
CHECK(prefetch_container.GetLoadState() ==
PrefetchContainer::LoadState::kCompleted ||
prefetch_container.GetLoadState() ==
PrefetchContainer::LoadState::kFailed);
AddEvent(Event::kObserverOnPrefetchCompletedOrFailed);
}
base::WeakPtr<PrefetchContainer> prefetch_container_;
std::vector<Event> actual_output_event_sequence_;
};
TEST_P(PrefetchServiceEventTest, ActualObserverCallbacks) {
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
GURL initial_url("https://example.com");
PrefetchServiceInjectedEligibilityCheckFuture
eligibility_check_callback_future(prefetch_service());
MakePrefetchOnMainFrame(initial_url);
auto prefetches = prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(MainDocumentToken(), initial_url));
ASSERT_EQ(1u, prefetches.size());
base::WeakPtr<PrefetchContainer> prefetch_container = prefetches[0].second;
ASSERT_TRUE(prefetch_container);
ASSERT_EQ(prefetch_container->GetLoadState(),
PrefetchContainer::LoadState::kNotStarted);
RecordingPrefetchContainerObserver observer(*prefetch_container);
const bool use_prefetch_proxy = true;
const auto& input_event_sequence = std::get<1>(GetParam());
bool on_complete_failure_called = false;
for (const Event event : input_event_sequence) {
observer.AddEvent(event);
switch (event) {
case Event::kInitialEligibilityCheckCompleteSuccess:
case Event::kInitialEligibilityCheckCompleteFailure:
case Event::kRedirectEligibilityCheckCompleteSuccess:
case Event::kRedirectEligibilityCheckCompleteFailure: {
ASSERT_TRUE(eligibility_check_callback_future->IsReady());
const bool is_eligible =
event == Event::kInitialEligibilityCheckCompleteSuccess ||
event == Event::kRedirectEligibilityCheckCompleteSuccess;
auto eligibility = is_eligible
? PreloadingEligibility::kEligible
: PreloadingEligibility::kHostIsNonUnique;
if (!base::FeatureList::IsEnabled(
features::kPrefetchGracefulNotification) &&
!is_eligible && on_complete_failure_called) {
EXPECT_DEATH_IF_SUPPORTED(
eligibility_check_callback_future->Take().Run(eligibility), "");
} else {
eligibility_check_callback_future->Take().Run(eligibility);
}
task_environment()->RunUntilIdle();
break;
}
case Event::kPrefetchURLLoaderOnReceiveRedirect:
case Event::kPrefetchURLLoaderOnReceiveRedirectCrossSite:
ASSERT_FALSE(eligibility_check_callback_future->IsReady());
MakeSingleRedirectAndWait(
event == Event::kPrefetchURLLoaderOnReceiveRedirect
? GURL("https://example.com/redirected")
: GURL("https://cross-site.example.org/redirected"));
ASSERT_TRUE(eligibility_check_callback_future->Wait());
break;
case Event::kPrefetchURLLoaderOnReceiveResponseSuccess:
SendHeadOfResponseAndWait(net::HTTP_OK, kHTMLMimeType,
use_prefetch_proxy, {}, std::size(kHTMLBody));
SendBodyContentOfResponseAndWait(kHTMLBody);
break;
case Event::kPrefetchURLLoaderOnReceiveResponseFailure:
SendHeadOfResponseAndWait(net::HTTP_NOT_FOUND, kHTMLMimeType,
use_prefetch_proxy, {}, std::size(kHTMLBody));
break;
case Event::kPrefetchURLLoaderOnCompleteSuccess:
CompleteResponseAndWait(net::OK, std::size(kHTMLBody));
break;
case Event::kPrefetchURLLoaderOnCompleteFailure:
on_complete_failure_called = true;
CompleteResponseAndWait(net::ERR_FAILED, std::size(kHTMLBody));
break;
case Event::kStartServing:
ASSERT_TRUE(prefetch_container);
ASSERT_TRUE(prefetch_container->GetStreamingURLLoader());
prefetch_container->GetStreamingURLLoader()->OnStartServing();
break;
case Event::kPrefetchURLLoaderDisconnect:
ASSERT_TRUE(prefetch_container);
if (auto loader = prefetch_container->GetStreamingURLLoader()) {
base::test::TestFuture<void> disconnect_future;
loader->SetOnDeletionScheduledForTests(
disconnect_future.GetCallback());
Disconnect();
ASSERT_TRUE(disconnect_future.Wait());
} else {
Disconnect();
}
break;
case Event::kResetPrefetchContainer:
ASSERT_TRUE(prefetch_container);
prefetch_service().MayReleasePrefetch(prefetch_container);
break;
case Event::kObserverOnWillBeDestroyed:
case Event::kObserverOnGotInitialEligibility:
case Event::kObserverOnDeterminedHead:
case Event::kObserverOnPrefetchCompletedOrFailed:
NOTREACHED();
}
}
EXPECT_EQ(observer.actual_output_event_sequence(),
CalculateOutputEventSequence(input_event_sequence));
}
INSTANTIATE_TEST_SUITE_P(
,
PrefetchServiceEventTest,
testing::Combine(testing::ValuesIn(PrefetchRearchParam::Params()),
testing::ValuesIn(ValidInputEventSequences())));
TEST_P(
PrefetchServiceTest,
DISABLED_CHROMEOS(URLLoaderDisconnectedWhileHandlingRedirectEligibilty)) {
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
MakePrefetchOnMainFrame(GURL("https://example.com"));
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = true});
VerifyFollowRedirectParams(0);
PrefetchServiceInjectedEligibilityCheckFuture
redirect_eligibility_check_callback_future(prefetch_service());
MakeSingleRedirectAndWait(GURL("https://redirect.com"));
base::test::TestFuture<void> disconnect_future;
std::vector<std::pair<GURL, base::WeakPtr<PrefetchContainer>>> prefetches =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(MainDocumentToken(), GURL("https://example.com")));
ASSERT_EQ(1u, prefetches.size());
base::WeakPtr<PrefetchContainer> prefetch_container = prefetches[0].second;
prefetch_container->GetStreamingURLLoader()->SetOnDeletionScheduledForTests(
disconnect_future.GetCallback());
Disconnect();
ASSERT_TRUE(disconnect_future.Wait());
redirect_eligibility_check_callback_future->Take().Run(
PreloadingEligibility::kEligible);
task_environment()->RunUntilIdle();
EXPECT_EQ(prefetch_container->GetServableState(base::TimeDelta::Max()),
PrefetchServableState::kNotServable);
std::unique_ptr<NavigationResult> navigation_result =
SimulatePartOfNavigation(GURL("https://example.com"),
true,
true);
ASSERT_TRUE(navigation_result->serving_handle_future.IsReady());
EXPECT_FALSE(navigation_result->serving_handle_future.Take());
}
TEST_P(
PrefetchServiceTest,
DISABLED_CHROMEOS(
URLLoaderDisconnectedWhileHandlingRedirectEligibilty_BlockUntilHead)) {
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
MakePrefetchOnMainFrame(GURL("https://example.com"));
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = true});
VerifyFollowRedirectParams(0);
std::unique_ptr<NavigationResult> navigation_result =
SimulatePartOfNavigation(GURL("https://example.com"),
true,
true);
task_environment()->RunUntilIdle();
ASSERT_FALSE(navigation_result->serving_handle_future.IsReady());
PrefetchServiceInjectedEligibilityCheckFuture
redirect_eligibility_check_callback_future(prefetch_service());
MakeSingleRedirectAndWait(GURL("https://redirect.com"));
base::test::TestFuture<void> disconnect_future;
std::vector<std::pair<GURL, base::WeakPtr<PrefetchContainer>>> prefetches =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(MainDocumentToken(), GURL("https://example.com")));
ASSERT_EQ(1u, prefetches.size());
base::WeakPtr<PrefetchContainer> prefetch_container = prefetches[0].second;
prefetch_container->GetStreamingURLLoader()->SetOnDeletionScheduledForTests(
disconnect_future.GetCallback());
Disconnect();
ASSERT_TRUE(disconnect_future.Wait());
redirect_eligibility_check_callback_future->Take().Run(
PreloadingEligibility::kEligible);
task_environment()->RunUntilIdle();
EXPECT_FALSE(navigation_result->serving_handle_future.Take());
}
TEST_P(
PrefetchServiceTest,
DISABLED_CHROMEOS(MultipleConcurrentNavigationSuccessBeforeNavigations)) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
MakePrefetchOnMainFrame(GURL("https://example.com"));
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = true});
MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}}, kHTMLBody);
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody));
std::unique_ptr<NavigationResult> nav_res1 = SimulatePartOfNavigation(
GURL("https://example.com"), true,
false);
std::unique_ptr<NavigationResult> nav_res2 = SimulatePartOfNavigation(
GURL("https://example.com"), true,
false);
task_environment()->RunUntilIdle();
ExpectServingReaderSuccess(FROM_HERE, nav_res1->serving_handle_future.Take());
ExpectServingMetrics(
FROM_HERE, nav_res1,
{.prefetch_status = PrefetchStatus::kPrefetchSuccessful,
.prefetch_header_latency = base::Milliseconds(kHeaderLatency),
.required_private_prefetch_proxy = true});
ExpectServingReaderSuccess(FROM_HERE, nav_res2->serving_handle_future.Take());
ExpectServingMetrics(
FROM_HERE, nav_res2,
{.prefetch_status = PrefetchStatus::kPrefetchSuccessful,
.prefetch_header_latency = base::Milliseconds(kHeaderLatency),
.required_private_prefetch_proxy = true});
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.AfterClick.RedirectChainSize", 1, 2);
histogram_tester.ExpectUniqueSample(
"Prefetch.PrefetchMatchingBlockedNavigation.PerMatchingCandidate."
"SpeculationRule_"
"Immediate2",
false, 2);
PrefetchDocumentManager::DeleteForCurrentDocument(main_rfh());
histogram_tester.ExpectUniqueSample(
"Prefetch.PrefetchContainer.ServedCount.SpeculationRule_Immediate2", 2,
1);
}
TEST_P(
PrefetchServiceTest,
DISABLED_CHROMEOS(MultipleConcurrentNavigationBlockUntilHeadThenSuccess)) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
MakePrefetchOnMainFrame(GURL("https://example.com"));
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = true});
std::unique_ptr<NavigationResult> nav_res1 = SimulatePartOfNavigation(
GURL("https://example.com"), true,
false);
std::unique_ptr<NavigationResult> nav_res2 = SimulatePartOfNavigation(
GURL("https://example.com"), true,
false);
task_environment()->RunUntilIdle();
SendHeadOfResponseAndWait(net::HTTP_OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}},
std::size(kHTMLBody));
SendBodyContentOfResponseAndWait(kHTMLBody);
CompleteResponseAndWait(net::OK, std::size(kHTMLBody));
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody),
blink::mojom::SpeculationEagerness::kImmediate,
true);
ExpectServingReaderSuccess(FROM_HERE, nav_res1->serving_handle_future.Take());
ExpectServingMetrics(
FROM_HERE, nav_res1,
{.prefetch_status = PrefetchStatus::kPrefetchNotFinishedInTime,
.prefetch_header_latency = std::nullopt,
.required_private_prefetch_proxy = true});
ExpectServingReaderSuccess(FROM_HERE, nav_res2->serving_handle_future.Take());
ExpectServingMetrics(
FROM_HERE, nav_res2,
{.prefetch_status = PrefetchStatus::kPrefetchSuccessful,
.prefetch_header_latency = base::Milliseconds(kHeaderLatency),
.required_private_prefetch_proxy = true});
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.AfterClick.RedirectChainSize", 1, 2);
histogram_tester.ExpectUniqueSample(
"Prefetch.PrefetchMatchingBlockedNavigation.PerMatchingCandidate."
"SpeculationRule_"
"Immediate2",
true, 2);
PrefetchDocumentManager::DeleteForCurrentDocument(main_rfh());
histogram_tester.ExpectUniqueSample(
"Prefetch.PrefetchContainer.ServedCount.SpeculationRule_Immediate2", 2,
1);
}
TEST_P(PrefetchServiceTest,
DISABLED_CHROMEOS(
MultipleConcurrentNavigationBlockUntilHeadThenSuccessFail)) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
network::mojom::NoVarySearchPtr no_vary_search_hint =
network::mojom::NoVarySearch::New();
no_vary_search_hint->vary_on_key_order = true;
no_vary_search_hint->search_variance =
network::mojom::SearchParamsVariance::NewNoVaryParams(
std::vector<std::string>({"match", "notEventuallyMatch"}));
MakePrefetchOnMainFrame(
GURL("https://example.com/?match=0"),
PrefetchType(PreloadingTriggerType::kSpeculationRule,
true,
blink::mojom::SpeculationEagerness::kImmediate),
blink::mojom::Referrer(), std::move(no_vary_search_hint));
VerifyCommonRequestState(GURL("https://example.com/?match=0"),
{.use_prefetch_proxy = true});
std::unique_ptr<NavigationResult> nav_res1 = SimulatePartOfNavigation(
GURL("https://example.com/?match=1"), true,
false);
std::unique_ptr<NavigationResult> nav_res2 = SimulatePartOfNavigation(
GURL("https://example.com/?notEventuallyMatch=1"),
true, false);
task_environment()->RunUntilIdle();
SendHeadOfResponseAndWait(
net::HTTP_OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}, {"No-Vary-Search", "params=(\"match\")"}},
std::size(kHTMLBody));
SendBodyContentOfResponseAndWait(kHTMLBody);
CompleteResponseAndWait(net::OK, std::size(kHTMLBody));
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody),
blink::mojom::SpeculationEagerness::kImmediate,
true);
ExpectServingReaderSuccess(FROM_HERE, nav_res1->serving_handle_future.Take());
ExpectServingMetrics(
FROM_HERE, nav_res1,
{.prefetch_status = PrefetchStatus::kPrefetchNotFinishedInTime,
.prefetch_header_latency = std::nullopt,
.required_private_prefetch_proxy = true});
EXPECT_FALSE(nav_res2->serving_handle_future.Take());
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.AfterClick.RedirectChainSize", 1, 1);
histogram_tester.ExpectUniqueSample(
"Prefetch.PrefetchMatchingBlockedNavigation.PerMatchingCandidate."
"SpeculationRule_"
"Immediate2",
true, 2);
PrefetchDocumentManager::DeleteForCurrentDocument(main_rfh());
histogram_tester.ExpectUniqueSample(
"Prefetch.PrefetchContainer.ServedCount.SpeculationRule_Immediate2", 1,
1);
}
TEST_P(PrefetchServiceTest,
DISABLED_CHROMEOS(
MultipleConcurrentNavigationBlockUntilHeadThenCookiesChanged)) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
MakePrefetchOnMainFrame(GURL("https://example.com"));
VerifyCommonRequestState(GURL("https://example.com"),
{.use_prefetch_proxy = true});
std::unique_ptr<NavigationResult> nav_res1 = SimulatePartOfNavigation(
GURL("https://example.com"), true,
false);
std::unique_ptr<NavigationResult> nav_res2 = SimulatePartOfNavigation(
GURL("https://example.com"), true,
false);
task_environment()->RunUntilIdle();
ASSERT_TRUE(SetCookie(GURL("https://example.com"), "testing"));
SendHeadOfResponseAndWait(net::HTTP_OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}},
std::size(kHTMLBody));
SendBodyContentOfResponseAndWait(kHTMLBody);
CompleteResponseAndWait(net::OK, std::size(kHTMLBody));
EXPECT_FALSE(nav_res1->serving_handle_future.Take());
EXPECT_FALSE(nav_res2->serving_handle_future.Take());
histogram_tester.ExpectTotalCount(
"PrefetchProxy.AfterClick.RedirectChainSize", 0);
histogram_tester.ExpectUniqueSample(
"Prefetch.PrefetchMatchingBlockedNavigation.PerMatchingCandidate."
"SpeculationRule_"
"Immediate2",
true, 2);
PrefetchDocumentManager::DeleteForCurrentDocument(main_rfh());
histogram_tester.ExpectUniqueSample(
"Prefetch.PrefetchContainer.ServedCount.SpeculationRule_Immediate2", 0,
1);
}
TEST_P(PrefetchServiceTest,
DISABLED_CHROMEOS(PrefetchAheadOfPrerenderSuccess)) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures(
{features::kPrerender2FallbackPrefetchSpecRules}, {});
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
PrefetchServiceInjectedEligibilityCheckFuture
eligibility_check_callback_future(prefetch_service());
MakePrefetchOnMainFrame(
GURL("https://example.com"),
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false,
blink::mojom::SpeculationEagerness::kImmediate),
blink::mojom::Referrer(), network::mojom::NoVarySearchPtr(),
PreloadingType::kPrerender);
std::unique_ptr<NavigationResult> nav_res = SimulatePartOfNavigation(
GURL("https://example.com"), true,
true);
task_environment()->RunUntilIdle();
ASSERT_FALSE(nav_res->serving_handle_future.IsReady());
eligibility_check_callback_future->Take().Run(
PreloadingEligibility::kEligible);
VerifyCommonRequestState(
GURL("https://example.com"),
{
.use_prefetch_proxy = false,
.sec_purpose_header_value =
blink::kSecPurposePrefetchPrerenderHeaderValue,
});
SendHeadOfResponseAndWait(net::HTTP_OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}},
std::size(kHTMLBody));
SendBodyContentOfResponseAndWait(kHTMLBody);
CompleteResponseAndWait(net::OK, std::size(kHTMLBody));
ExpectPrefetchSuccess(histogram_tester, std::size(kHTMLBody),
blink::mojom::SpeculationEagerness::kImmediate,
true);
ExpectServingReaderSuccess(FROM_HERE, nav_res->serving_handle_future.Take());
ExpectServingMetrics(
FROM_HERE, nav_res,
{.prefetch_status = PrefetchStatus::kPrefetchSuccessful,
.prefetch_header_latency = base::Milliseconds(kHeaderLatency),
.required_private_prefetch_proxy = false});
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.AfterClick.RedirectChainSize", 1, 1);
histogram_tester.ExpectUniqueSample(
"Prefetch.PrefetchMatchingBlockedNavigation.PerMatchingCandidate."
"SpeculationRule_"
"Immediate2",
true, 1);
}
TEST_P(PrefetchServiceTest,
DISABLED_CHROMEOS(PrefetchAheadOfPrerenderIneligible)) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures(
{features::kPrerender2FallbackPrefetchSpecRules}, {});
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
PrefetchServiceInjectedEligibilityCheckFuture
eligibility_check_callback_future(prefetch_service());
MakePrefetchOnMainFrame(
GURL("http://example.com"),
PrefetchType(PreloadingTriggerType::kSpeculationRule,
false,
blink::mojom::SpeculationEagerness::kImmediate),
blink::mojom::Referrer(), network::mojom::NoVarySearchPtr(),
PreloadingType::kPrerender);
std::unique_ptr<NavigationResult> nav_res = SimulatePartOfNavigation(
GURL("http://example.com"), true,
true);
task_environment()->RunUntilIdle();
ASSERT_FALSE(nav_res->serving_handle_future.IsReady());
eligibility_check_callback_future->Take().Run(
PreloadingEligibility::kEligible);
ASSERT_FALSE(nav_res->serving_handle_future.Take());
EXPECT_EQ(RequestCount(), 0);
ExpectPrefetchNotEligible(histogram_tester,
PreloadingEligibility::kSchemeIsNotHttps,
true);
}
TEST_P(PrefetchServiceTest,
DISABLED_CHROMEOS(IsPrefetchDuplicateSameNoVarySearchHint)) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
std::nullopt));
std::unique_ptr<ProbePrefetchRequestStatusListener> probe_listener =
std::make_unique<ProbePrefetchRequestStatusListener>();
std::unique_ptr<content::PrefetchRequestStatusListener>
request_status_listener =
std::make_unique<TestablePrefetchRequestStatusListener>(
probe_listener->GetWeakPtr());
std::vector<std::string> no_vary_params = {"ts"};
net::HttpNoVarySearchData nvs_hint =
net::HttpNoVarySearchData::CreateFromNoVaryParams(no_vary_params, false);
GURL pf_one_url("https://example.com/search?q=ai&ts=1000");
std::unique_ptr<content::PrefetchHandle> handle =
MakePrefetchFromBrowserContext(pf_one_url, nvs_hint, {},
std::move(request_status_listener));
task_environment()->RunUntilIdle();
GURL pf_two_url("https://example.com/search?q=ai&ts=1001");
EXPECT_TRUE(browser_context()->IsPrefetchDuplicate(pf_two_url, nvs_hint));
GURL pf_three_url("https://example.com/search?q=ai&ts=1000&qsubts=1000");
EXPECT_FALSE(browser_context()->IsPrefetchDuplicate(pf_three_url, nvs_hint));
GURL pf_four_url("https://example.com/search?q=dogs&ts=1002");
EXPECT_FALSE(browser_context()->IsPrefetchDuplicate(pf_four_url, nvs_hint));
}
class PrefetchServiceAddPrefetchContainerTest
: public PrefetchServiceTestBase,
public WithPrefetchRearchParam,
public ::testing::WithParamInterface<PrefetchRearchParam> {
public:
PrefetchServiceAddPrefetchContainerTest()
: WithPrefetchRearchParam(GetParam()) {}
void InitScopedFeatureList() override {
InitBaseParams();
InitRearchFeatures();
scoped_feature_list_for_prerender2_fallback_.InitWithFeatures(
{features::kPrerender2FallbackPrefetchSpecRules}, {});
}
base::WeakPtr<PrefetchContainer> CreateSpeculationRulesPrefetchContainer(
const blink::DocumentToken& document_token,
const GURL& prefetch_url,
PreloadingType planned_max_preloading_type) {
auto prefetch_type =
PrefetchType(PreloadingTriggerType::kSpeculationRule,
true,
blink::mojom::SpeculationEagerness::kImmediate);
auto* preloading_data =
PreloadingData::GetOrCreateForWebContents(web_contents());
PreloadingURLMatchCallback matcher =
PreloadingDataImpl::GetPrefetchServiceMatcher(
prefetch_service(), PrefetchKey(document_token, prefetch_url));
auto* attempt = static_cast<PreloadingAttemptImpl*>(
preloading_data->AddPreloadingAttempt(
GetPredictorForPreloadingTriggerType(prefetch_type.trigger_type()),
PreloadingType::kPrefetch, std::move(matcher),
web_contents()->GetPrimaryMainFrame()->GetPageUkmSourceId()));
attempt->SetSpeculationEagerness(prefetch_type.GetEagerness());
auto prefetch_request = PrefetchRequest::CreateRendererInitiated(
static_cast<content::RenderFrameHostImpl&>(*main_rfh()), document_token,
prefetch_url, std::move(prefetch_type), blink::mojom::Referrer(),
std::make_optional(SpeculationRulesTags()),
std::nullopt,
std::nullopt,
nullptr,
PreloadPipelineInfo::Create(planned_max_preloading_type),
attempt->GetWeakPtr());
return prefetch_service()
.AddPrefetchRequestWithoutStartingPrefetchForTesting(
std::move(prefetch_request));
}
private:
base::test::ScopedFeatureList scoped_feature_list_for_prerender2_fallback_;
};
INSTANTIATE_TEST_SUITE_P(ParametrizedTests,
PrefetchServiceAddPrefetchContainerTest,
testing::ValuesIn(PrefetchRearchParam::Params()));
TEST_P(PrefetchServiceAddPrefetchContainerTest,
PreserveOldIfOldIsAliveIfUsePrefetchPrerenderIntegrationEnabled) {
blink::DocumentToken document_token;
auto prefetch_container1 = CreateSpeculationRulesPrefetchContainer(
document_token, GURL("https://example.com"), PreloadingType::kPrefetch);
PreloadingAttempt* attempt1 = prefetch_container1->request().attempt();
auto prefetch_container2 = CreateSpeculationRulesPrefetchContainer(
document_token, GURL("https://example.com"), PreloadingType::kPrefetch);
std::vector<std::pair<GURL, base::WeakPtr<PrefetchContainer>>> prefetches =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(document_token, GURL("https://example.com")));
ASSERT_EQ(1u, prefetches.size());
base::WeakPtr<PrefetchContainer> prefetch_container = prefetches[0].second;
ASSERT_EQ(attempt1, prefetch_container->request().attempt());
}
TEST_P(PrefetchServiceAddPrefetchContainerTest, PreserveOldIfOldIsStarted) {
blink::DocumentToken document_token;
auto prefetch_container1 = CreateSpeculationRulesPrefetchContainer(
document_token, GURL("https://example.com"), PreloadingType::kPrefetch);
PreloadingAttempt* attempt1 = prefetch_container1->request().attempt();
prefetch_container1->SimulatePrefetchEligibleForTest();
prefetch_service().StartSinglePrefetchForTesting(prefetch_container1);
auto prefetch_container2 = CreateSpeculationRulesPrefetchContainer(
document_token, GURL("https://example.com"), PreloadingType::kPrefetch);
std::vector<std::pair<GURL, base::WeakPtr<PrefetchContainer>>> prefetches =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(document_token, GURL("https://example.com")));
ASSERT_EQ(1u, prefetches.size());
base::WeakPtr<PrefetchContainer> prefetch_container = prefetches[0].second;
ASSERT_EQ(attempt1, prefetch_container->request().attempt());
}
TEST_P(PrefetchServiceAddPrefetchContainerTest,
PreservesOldIfOldIsAheadOfPrerender) {
blink::DocumentToken document_token;
auto prefetch_container1 = CreateSpeculationRulesPrefetchContainer(
document_token, GURL("https://example.com"), PreloadingType::kPrerender);
PreloadingAttempt* attempt1 = prefetch_container1->request().attempt();
auto prefetch_container2 = CreateSpeculationRulesPrefetchContainer(
document_token, GURL("https://example.com"), PreloadingType::kPrefetch);
std::vector<std::pair<GURL, base::WeakPtr<PrefetchContainer>>> prefetches =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(document_token, GURL("https://example.com")));
ASSERT_EQ(1u, prefetches.size());
base::WeakPtr<PrefetchContainer> prefetch_container = prefetches[0].second;
ASSERT_EQ(attempt1, prefetch_container->request().attempt());
}
TEST_P(PrefetchServiceAddPrefetchContainerTest,
ReplacesOldWithNewIfOldIsAheadOfPrerenderAndNotServable) {
blink::DocumentToken document_token;
auto prefetch_container1 = CreateSpeculationRulesPrefetchContainer(
document_token, GURL("https://example.com"), PreloadingType::kPrerender);
prefetch_container1->SimulatePrefetchFailedIneligibleForTest(
PreloadingEligibility::kDataSaverEnabled);
auto prefetch_container2 = CreateSpeculationRulesPrefetchContainer(
document_token, GURL("https://example.com"), PreloadingType::kPrefetch);
PreloadingAttempt* attempt2 = prefetch_container2->request().attempt();
std::vector<std::pair<GURL, base::WeakPtr<PrefetchContainer>>> prefetches =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(document_token, GURL("https://example.com")));
ASSERT_EQ(1u, prefetches.size());
base::WeakPtr<PrefetchContainer> prefetch_container = prefetches[0].second;
ASSERT_EQ(attempt2, prefetch_container->request().attempt());
}
TEST_P(PrefetchServiceAddPrefetchContainerTest,
TakesOldWithAttributeMigrationIfNewIsAheadOfPrerender) {
blink::DocumentToken document_token;
auto prefetch_container1 = CreateSpeculationRulesPrefetchContainer(
document_token, GURL("https://example.com"), PreloadingType::kPrefetch);
PreloadingAttempt* attempt1 = prefetch_container1->request().attempt();
auto prefetch_container2 = CreateSpeculationRulesPrefetchContainer(
document_token, GURL("https://example.com"), PreloadingType::kPrerender);
{
std::vector<std::pair<GURL, base::WeakPtr<PrefetchContainer>>> prefetches =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(document_token, GURL("https://example.com")));
ASSERT_EQ(1u, prefetches.size());
base::WeakPtr<PrefetchContainer> prefetch_container = prefetches[0].second;
ASSERT_EQ(attempt1, prefetch_container->request().attempt());
ASSERT_TRUE(prefetch_container->IsLikelyAheadOfPrerender());
}
auto prefetch_container3 = CreateSpeculationRulesPrefetchContainer(
document_token, GURL("https://example.com"), PreloadingType::kPrefetch);
{
std::vector<std::pair<GURL, base::WeakPtr<PrefetchContainer>>> prefetches =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(document_token, GURL("https://example.com")));
ASSERT_EQ(1u, prefetches.size());
base::WeakPtr<PrefetchContainer> prefetch_container = prefetches[0].second;
ASSERT_EQ(attempt1, prefetch_container->request().attempt());
}
}
TEST_P(PrefetchServiceTest, PrefetchScheduler_RunsTwoConcurrentPrefetches) {
if (!UsePrefetchScheduler()) {
GTEST_SKIP() << "Assume PrefetchScheduler";
}
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeaturesAndParameters(
{{features::kPrefetchSchedulerTesting,
{{"kPrefetchSchedulerTestingActiveSetSizeLimitForBase", "2"},
{"kPrefetchSchedulerTestingActiveSetSizeLimitForBurst", "2"}}}},
{});
NavigateAndCommit(GURL("https://example.com"));
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
0));
const auto url_1 = GURL("https://example.com/one");
const auto url_2 = GURL("https://example.com/two");
const auto url_3 = GURL("https://example.com/three");
auto handle_1 =
MakePrefetchFromBrowserContext(url_1, std::nullopt, {}, nullptr);
auto handle_2 =
MakePrefetchFromBrowserContext(url_2, std::nullopt, {}, nullptr);
auto handle_3 =
MakePrefetchFromBrowserContext(url_3, std::nullopt, {}, nullptr);
task_environment()->RunUntilIdle();
base::WeakPtr<PrefetchContainer> prefetch_container1, prefetch_container2,
prefetch_container3;
std::tie(std::ignore, prefetch_container1) =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(std::nullopt, url_1))[0];
std::tie(std::ignore, prefetch_container2) =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(std::nullopt, url_2))[0];
std::tie(std::ignore, prefetch_container3) =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(std::nullopt, url_3))[0];
ASSERT_EQ(prefetch_container1->GetLoadState(),
PrefetchContainer::LoadState::kStarted);
ASSERT_EQ(prefetch_container2->GetLoadState(),
PrefetchContainer::LoadState::kStarted);
ASSERT_EQ(prefetch_container3->GetLoadState(),
PrefetchContainer::LoadState::kEligible);
handle_2.reset();
EXPECT_FALSE(prefetch_container2);
task_environment()->RunUntilIdle();
ASSERT_EQ(prefetch_container1->GetLoadState(),
PrefetchContainer::LoadState::kStarted);
ASSERT_EQ(prefetch_container3->GetLoadState(),
PrefetchContainer::LoadState::kStarted);
}
TEST_P(PrefetchServiceTest, PrefetchScheduler_Prioritize) {
if (!(UsePrefetchScheduler() &&
features::kPrefetchSchedulerProgressSyncBestEffort.Get())) {
GTEST_SKIP() << "Assume PrefetchScheduler and "
"PrefetchSchedulerProgressSyncBestEffort";
}
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeaturesAndParameters(
{{features::kPrefetchSchedulerTesting,
{{"kPrefetchSchedulerTestingActiveSetSizeLimitForBase", "1"},
{"kPrefetchSchedulerTestingActiveSetSizeLimitForBurst", "1"}}}},
{});
NavigateAndCommit(GURL("https://example.com"));
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
0));
prefetch_service()
.GetPrefetchSchedulerForTesting()
.SetCalculatePriorityForTesting(
base::BindRepeating([](const PrefetchContainer& prefetch_container) {
if (prefetch_container.GetURL().possibly_invalid_spec().ends_with(
"?prioritize=1")) {
return PrefetchSchedulerPriority::kHighTest;
}
return PrefetchSchedulerPriority::kBase;
}));
const auto url_1 = GURL("https://example.com/one");
const auto url_2 = GURL("https://example.com/two");
const auto url_3 = GURL("https://example.com/two?prioritize=1");
auto handle_1 =
MakePrefetchFromBrowserContext(url_1, std::nullopt, {}, nullptr);
auto handle_2 =
MakePrefetchFromBrowserContext(url_2, std::nullopt, {}, nullptr);
auto handle_3 =
MakePrefetchFromBrowserContext(url_3, std::nullopt, {}, nullptr);
task_environment()->RunUntilIdle();
base::WeakPtr<PrefetchContainer> prefetch_container1, prefetch_container2,
prefetch_container3;
std::tie(std::ignore, prefetch_container1) =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(std::nullopt, url_1))[0];
std::tie(std::ignore, prefetch_container2) =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(std::nullopt, url_2))[0];
std::tie(std::ignore, prefetch_container3) =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(std::nullopt, url_3))[0];
ASSERT_EQ(prefetch_container1->GetLoadState(),
PrefetchContainer::LoadState::kStarted);
ASSERT_EQ(prefetch_container2->GetLoadState(),
PrefetchContainer::LoadState::kEligible);
ASSERT_EQ(prefetch_container3->GetLoadState(),
PrefetchContainer::LoadState::kEligible);
handle_1.reset();
EXPECT_FALSE(prefetch_container1);
task_environment()->RunUntilIdle();
ASSERT_EQ(prefetch_container2->GetLoadState(),
PrefetchContainer::LoadState::kEligible);
ASSERT_EQ(prefetch_container3->GetLoadState(),
PrefetchContainer::LoadState::kStarted);
}
TEST_P(PrefetchServiceTest, PrefetchScheduler_Prioritize_Async) {
if (!(UsePrefetchScheduler() &&
!features::kPrefetchSchedulerProgressSyncBestEffort.Get())) {
GTEST_SKIP() << "Assume PrefetchScheduler and not "
"PrefetchSchedulerProgressSyncBestEffort";
}
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeaturesAndParameters(
{{features::kPrefetchSchedulerTesting,
{{"kPrefetchSchedulerTestingActiveSetSizeLimitForBase", "1"},
{"kPrefetchSchedulerTestingActiveSetSizeLimitForBurst", "1"}}}},
{});
NavigateAndCommit(GURL("https://example.com"));
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
0));
prefetch_service()
.GetPrefetchSchedulerForTesting()
.SetCalculatePriorityForTesting(
base::BindRepeating([](const PrefetchContainer& prefetch_container) {
if (prefetch_container.GetURL().possibly_invalid_spec().ends_with(
"?prioritize=1")) {
return PrefetchSchedulerPriority::kHighTest;
}
return PrefetchSchedulerPriority::kBase;
}));
const auto url_1 = GURL("https://example.com/one");
const auto url_2 = GURL("https://example.com/two?prioritize=1");
auto handle_1 =
MakePrefetchFromBrowserContext(url_1, std::nullopt, {}, nullptr);
auto handle_2 =
MakePrefetchFromBrowserContext(url_2, std::nullopt, {}, nullptr);
task_environment()->RunUntilIdle();
base::WeakPtr<PrefetchContainer> prefetch_container1, prefetch_container2;
std::tie(std::ignore, prefetch_container1) =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(std::nullopt, url_1))[0];
std::tie(std::ignore, prefetch_container2) =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(std::nullopt, url_2))[0];
ASSERT_EQ(prefetch_container1->GetLoadState(),
PrefetchContainer::LoadState::kEligible);
ASSERT_EQ(prefetch_container2->GetLoadState(),
PrefetchContainer::LoadState::kStarted);
}
TEST_P(PrefetchServiceTest, PrefetchScheduler_Burst) {
if (!UsePrefetchScheduler()) {
GTEST_SKIP() << "Assume PrefetchScheduler";
}
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeaturesAndParameters(
{
{features::kPrefetchSchedulerTesting,
{{"kPrefetchSchedulerTestingActiveSetSizeLimitForBase", "1"},
{"kPrefetchSchedulerTestingActiveSetSizeLimitForBurst", "2"}}},
},
{});
NavigateAndCommit(GURL("https://example.com"));
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
0));
prefetch_service()
.GetPrefetchSchedulerForTesting()
.SetCalculatePriorityForTesting(
base::BindRepeating([](const PrefetchContainer& prefetch_container) {
if (prefetch_container.GetURL().possibly_invalid_spec().ends_with(
"?burst=1")) {
return PrefetchSchedulerPriority::kBurstTest;
}
return PrefetchSchedulerPriority::kBase;
}));
const auto url_1 = GURL("https://example.com/one");
const auto url_2 = GURL("https://example.com/two");
const auto url_3 = GURL("https://example.com/three?burst=1");
const auto url_4 = GURL("https://example.com/four?burst=1");
auto handle_1 =
MakePrefetchFromBrowserContext(url_1, std::nullopt, {}, nullptr);
auto handle_2 =
MakePrefetchFromBrowserContext(url_2, std::nullopt, {}, nullptr);
task_environment()->RunUntilIdle();
base::WeakPtr<PrefetchContainer> prefetch_container1, prefetch_container2,
prefetch_container3, prefetch_container4;
std::tie(std::ignore, prefetch_container1) =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(std::nullopt, url_1))[0];
std::tie(std::ignore, prefetch_container2) =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(std::nullopt, url_2))[0];
ASSERT_EQ(prefetch_container1->GetLoadState(),
PrefetchContainer::LoadState::kStarted);
ASSERT_EQ(prefetch_container2->GetLoadState(),
PrefetchContainer::LoadState::kEligible);
auto handle_3 =
MakePrefetchFromBrowserContext(url_3, std::nullopt, {}, nullptr);
auto handle_4 =
MakePrefetchFromBrowserContext(url_4, std::nullopt, {}, nullptr);
task_environment()->RunUntilIdle();
std::tie(std::ignore, prefetch_container3) =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(std::nullopt, url_3))[0];
std::tie(std::ignore, prefetch_container4) =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(std::nullopt, url_4))[0];
ASSERT_EQ(prefetch_container1->GetLoadState(),
PrefetchContainer::LoadState::kStarted);
ASSERT_EQ(prefetch_container2->GetLoadState(),
PrefetchContainer::LoadState::kEligible);
ASSERT_EQ(prefetch_container3->GetLoadState(),
PrefetchContainer::LoadState::kStarted);
ASSERT_EQ(prefetch_container4->GetLoadState(),
PrefetchContainer::LoadState::kEligible);
handle_3.reset();
EXPECT_FALSE(prefetch_container3);
task_environment()->RunUntilIdle();
ASSERT_EQ(prefetch_container1->GetLoadState(),
PrefetchContainer::LoadState::kStarted);
ASSERT_EQ(prefetch_container2->GetLoadState(),
PrefetchContainer::LoadState::kEligible);
ASSERT_EQ(prefetch_container4->GetLoadState(),
PrefetchContainer::LoadState::kStarted);
}
TEST_P(PrefetchServiceTest, PrefetchScheduler_BurstTakesPriority) {
if (!(UsePrefetchScheduler() &&
features::kPrefetchSchedulerProgressSyncBestEffort.Get())) {
GTEST_SKIP() << "Assume PrefetchScheduler and "
"PrefetchSchedulerProgressSyncBestEffort";
}
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeaturesAndParameters(
{
{features::kPrefetchSchedulerTesting,
{{"kPrefetchSchedulerTestingActiveSetSizeLimitForBase", "1"},
{"kPrefetchSchedulerTestingActiveSetSizeLimitForBurst", "2"}}},
},
{});
NavigateAndCommit(GURL("https://example.com"));
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
0));
prefetch_service()
.GetPrefetchSchedulerForTesting()
.SetCalculatePriorityForTesting(
base::BindRepeating([](const PrefetchContainer& prefetch_container) {
if (prefetch_container.GetURL().possibly_invalid_spec().ends_with(
"?burst=1")) {
return PrefetchSchedulerPriority::kBurstTest;
}
return PrefetchSchedulerPriority::kBase;
}));
const auto url_1 = GURL("https://example.com/one");
const auto url_2 = GURL("https://example.com/two");
const auto url_3 = GURL("https://example.com/three?burst=1");
const auto url_4 = GURL("https://example.com/four?burst=1");
auto handle_1 =
MakePrefetchFromBrowserContext(url_1, std::nullopt, {}, nullptr);
auto handle_2 =
MakePrefetchFromBrowserContext(url_2, std::nullopt, {}, nullptr);
auto handle_3 =
MakePrefetchFromBrowserContext(url_3, std::nullopt, {}, nullptr);
auto handle_4 =
MakePrefetchFromBrowserContext(url_4, std::nullopt, {}, nullptr);
task_environment()->RunUntilIdle();
base::WeakPtr<PrefetchContainer> prefetch_container1, prefetch_container2,
prefetch_container3, prefetch_container4;
std::tie(std::ignore, prefetch_container1) =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(std::nullopt, url_1))[0];
std::tie(std::ignore, prefetch_container2) =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(std::nullopt, url_2))[0];
std::tie(std::ignore, prefetch_container3) =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(std::nullopt, url_3))[0];
std::tie(std::ignore, prefetch_container4) =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(std::nullopt, url_4))[0];
ASSERT_EQ(prefetch_container1->GetLoadState(),
PrefetchContainer::LoadState::kStarted);
ASSERT_EQ(prefetch_container2->GetLoadState(),
PrefetchContainer::LoadState::kEligible);
ASSERT_EQ(prefetch_container3->GetLoadState(),
PrefetchContainer::LoadState::kStarted);
ASSERT_EQ(prefetch_container4->GetLoadState(),
PrefetchContainer::LoadState::kEligible);
handle_3.reset();
EXPECT_FALSE(prefetch_container3);
task_environment()->RunUntilIdle();
ASSERT_EQ(prefetch_container1->GetLoadState(),
PrefetchContainer::LoadState::kStarted);
ASSERT_EQ(prefetch_container2->GetLoadState(),
PrefetchContainer::LoadState::kEligible);
ASSERT_EQ(prefetch_container4->GetLoadState(),
PrefetchContainer::LoadState::kStarted);
handle_1.reset();
EXPECT_FALSE(prefetch_container1);
task_environment()->RunUntilIdle();
ASSERT_EQ(prefetch_container2->GetLoadState(),
PrefetchContainer::LoadState::kEligible);
ASSERT_EQ(prefetch_container4->GetLoadState(),
PrefetchContainer::LoadState::kStarted);
}
TEST_P(PrefetchServiceTest, PrefetchScheduler_BurstTakesPriority_Async) {
if (!(UsePrefetchScheduler() &&
!features::kPrefetchSchedulerProgressSyncBestEffort.Get())) {
GTEST_SKIP() << "Assume PrefetchScheduler and not "
"PrefetchSchedulerProgressSyncBestEffort";
}
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeaturesAndParameters(
{
{features::kPrefetchSchedulerTesting,
{{"kPrefetchSchedulerTestingActiveSetSizeLimitForBase", "1"},
{"kPrefetchSchedulerTestingActiveSetSizeLimitForBurst", "2"}}},
},
{});
NavigateAndCommit(GURL("https://example.com"));
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
0));
prefetch_service()
.GetPrefetchSchedulerForTesting()
.SetCalculatePriorityForTesting(
base::BindRepeating([](const PrefetchContainer& prefetch_container) {
if (prefetch_container.GetURL().possibly_invalid_spec().ends_with(
"?burst=1")) {
return PrefetchSchedulerPriority::kBurstTest;
}
return PrefetchSchedulerPriority::kBase;
}));
const auto url_1 = GURL("https://example.com/one");
const auto url_2 = GURL("https://example.com/two");
const auto url_3 = GURL("https://example.com/three?burst=1");
const auto url_4 = GURL("https://example.com/four?burst=1");
auto handle_1 =
MakePrefetchFromBrowserContext(url_1, std::nullopt, {}, nullptr);
auto handle_2 =
MakePrefetchFromBrowserContext(url_2, std::nullopt, {}, nullptr);
auto handle_3 =
MakePrefetchFromBrowserContext(url_3, std::nullopt, {}, nullptr);
auto handle_4 =
MakePrefetchFromBrowserContext(url_4, std::nullopt, {}, nullptr);
task_environment()->RunUntilIdle();
base::WeakPtr<PrefetchContainer> prefetch_container1, prefetch_container2,
prefetch_container3, prefetch_container4;
std::tie(std::ignore, prefetch_container1) =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(std::nullopt, url_1))[0];
std::tie(std::ignore, prefetch_container2) =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(std::nullopt, url_2))[0];
std::tie(std::ignore, prefetch_container3) =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(std::nullopt, url_3))[0];
std::tie(std::ignore, prefetch_container4) =
prefetch_service().GetAllForUrlWithoutRefAndQueryForTesting(
PrefetchKey(std::nullopt, url_4))[0];
ASSERT_EQ(prefetch_container1->GetLoadState(),
PrefetchContainer::LoadState::kEligible);
ASSERT_EQ(prefetch_container2->GetLoadState(),
PrefetchContainer::LoadState::kEligible);
ASSERT_EQ(prefetch_container3->GetLoadState(),
PrefetchContainer::LoadState::kStarted);
ASSERT_EQ(prefetch_container4->GetLoadState(),
PrefetchContainer::LoadState::kStarted);
handle_3.reset();
EXPECT_FALSE(prefetch_container3);
task_environment()->RunUntilIdle();
ASSERT_EQ(prefetch_container1->GetLoadState(),
PrefetchContainer::LoadState::kEligible);
ASSERT_EQ(prefetch_container2->GetLoadState(),
PrefetchContainer::LoadState::kEligible);
ASSERT_EQ(prefetch_container4->GetLoadState(),
PrefetchContainer::LoadState::kStarted);
}
TEST_P(PrefetchServiceTest,
UMA_Prefetch_PrefetchContainer_AddedTo_Embedder_Success) {
base::HistogramTester histogram_tester;
NavigateAndCommit(GURL("https://example.com"));
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
0));
const auto url = GURL("https://example.com/prefetched");
auto handle = MakePrefetchFromBrowserContext(url, std::nullopt, {}, nullptr);
task_environment()->RunUntilIdle();
task_environment()->FastForwardBy(
base::Milliseconds(kAddedToURLRequestStartLatency + kHeaderLatency));
auto head = CreateURLResponseHeadForPrefetch(
net::HTTP_OK, kHTMLMimeType,
false, {{"X-Testing", "Hello World"}}, url);
constexpr base::TimeDelta url_request_to_domain_lookup =
base::Milliseconds(10);
head->load_timing.connect_timing.domain_lookup_start =
head->load_timing.request_start + url_request_to_domain_lookup;
constexpr base::TimeDelta create_stream_delay = base::Milliseconds(11);
head->load_timing_internal_info->create_stream_delay = create_stream_delay;
constexpr base::TimeDelta connected_callback_delay = base::Milliseconds(12);
head->load_timing_internal_info->connected_callback_delay =
connected_callback_delay;
constexpr base::TimeDelta initialize_stream_delay = base::Milliseconds(13);
head->load_timing_internal_info->initialize_stream_delay =
initialize_stream_delay;
MakeResponseAndWait(url, net::OK, std::move(head), kHTMLBody);
handle.reset();
histogram_tester.ExpectUniqueSample(
base::StrCat(
{"Prefetch.PrefetchContainer.AddedToInitialEligibility.Embedder_",
test::kPreloadingEmbedderHistgramSuffixForTesting}),
0, 1);
histogram_tester.ExpectUniqueSample(
base::StrCat(
{"Prefetch.PrefetchContainer.AddedToPrefetchStarted.Embedder_",
test::kPreloadingEmbedderHistgramSuffixForTesting}),
0, 1);
histogram_tester.ExpectUniqueSample(
base::StrCat(
{"Prefetch.PrefetchContainer.AddedToURLRequestStarted.Embedder_",
test::kPreloadingEmbedderHistgramSuffixForTesting}),
kAddedToURLRequestStartLatency, 1);
histogram_tester.ExpectUniqueSample(
base::StrCat(
{"Prefetch.PrefetchContainer.AddedToDomainLookupStarted.Embedder_",
test::kPreloadingEmbedderHistgramSuffixForTesting}),
kAddedToURLRequestStartLatency +
url_request_to_domain_lookup.InMilliseconds(),
1);
histogram_tester.ExpectUniqueSample(
base::StrCat({"Prefetch.PrefetchContainer.CreateStreamDelay.Embedder_",
test::kPreloadingEmbedderHistgramSuffixForTesting}),
create_stream_delay.InMilliseconds(), 1);
histogram_tester.ExpectUniqueSample(
base::StrCat(
{"Prefetch.Prefetchcontainer.ConnectedCallbackDelay.Embedder_",
test::kPreloadingEmbedderHistgramSuffixForTesting}),
connected_callback_delay.InMilliseconds(), 1);
histogram_tester.ExpectUniqueSample(
base::StrCat(
{"Prefetch.Prefetchcontainer.InitializeStreamDelay.Embedder_",
test::kPreloadingEmbedderHistgramSuffixForTesting}),
initialize_stream_delay.InMilliseconds(), 1);
histogram_tester.ExpectUniqueSample(
base::StrCat({"Prefetch.PrefetchContainer."
"AddedToHeaderDeterminedSuccessfully.Embedder_",
test::kPreloadingEmbedderHistgramSuffixForTesting}),
kAddedToURLRequestStartLatency + kHeaderLatency, 1);
histogram_tester.ExpectUniqueSample(
base::StrCat({"Prefetch.PrefetchContainer."
"AddedToPrefetchCompletedSuccessfully.Embedder_",
test::kPreloadingEmbedderHistgramSuffixForTesting}),
kAddedToURLRequestStartLatency + kHeaderLatency, 1);
}
TEST_P(PrefetchServiceTest,
UMA_Prefetch_PrefetchContainer_AddedTo_Embedder_Fail) {
base::HistogramTester histogram_tester;
NavigateAndCommit(GURL("https://example.com"));
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
0));
const auto url = GURL("https://example.com/prefetched");
auto handle = MakePrefetchFromBrowserContext(url, std::nullopt, {}, nullptr);
task_environment()->RunUntilIdle();
task_environment()->FastForwardBy(
base::Milliseconds(kAddedToURLRequestStartLatency + kHeaderLatency));
handle.reset();
histogram_tester.ExpectUniqueSample(
base::StrCat(
{"Prefetch.PrefetchContainer.AddedToInitialEligibility.Embedder_",
test::kPreloadingEmbedderHistgramSuffixForTesting}),
0, 1);
histogram_tester.ExpectUniqueSample(
base::StrCat(
{"Prefetch.PrefetchContainer.AddedToPrefetchStarted.Embedder_",
test::kPreloadingEmbedderHistgramSuffixForTesting}),
0, 1);
histogram_tester.ExpectTotalCount(
base::StrCat(
{"Prefetch.PrefetchContainer.AddedToURLRequestStarted.Embedder_",
test::kPreloadingEmbedderHistgramSuffixForTesting}),
0);
histogram_tester.ExpectTotalCount(
base::StrCat({"Prefetch.PrefetchContainer."
"AddedToHeaderDeterminedSuccesfully.Embedder_",
test::kPreloadingEmbedderHistgramSuffixForTesting}),
0);
histogram_tester.ExpectTotalCount(
base::StrCat({"Prefetch.PrefetchContainer."
"AddedToPrefetchCompletedSuccessfully.Embedder_",
test::kPreloadingEmbedderHistgramSuffixForTesting}),
0);
}
TEST_P(
PrefetchServiceTest,
UMA_Prefetch_PrefetchMatchingBlockedNavigation_And_BlockUntilHeadDuration_PrerenderOrNonPrerender) {
base::HistogramTester histogram_tester;
MakePrefetchService(
std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
MakePrefetchOnMainFrame(GURL("https://example.com"));
base::test::TestFuture<PrefetchServingHandle> future;
GetPrefetchToServe(future, GURL("https://example.com"), MainDocumentToken());
EXPECT_FALSE(future.IsReady());
task_environment()->FastForwardBy(base::Milliseconds(kHeaderLatency));
SendHeadOfResponseAndWait(net::HTTP_OK, kHTMLMimeType,
true,
{{"X-Testing", "Hello World"}},
std::size(kHTMLBody));
PrefetchServingHandle serving_handle = future.Take();
ASSERT_TRUE(serving_handle);
SendBodyContentOfResponseAndWait(kHTMLBody);
CompleteResponseAndWait(net::OK, std::size(kHTMLBody));
histogram_tester.ExpectUniqueSample(
"Prefetch.PrefetchMatchingBlockedNavigation.PerMatchingCandidate."
"SpeculationRule_Immediate2",
true, 1);
histogram_tester.ExpectUniqueSample(
"Prefetch.PrefetchMatchingBlockedNavigation.PerMatchingCandidate."
"NonPrerender.SpeculationRule_Immediate2",
true, 1);
histogram_tester.ExpectTotalCount(
"Prefetch.PrefetchMatchingBlockedNavigation.PerMatchingCandidate."
"Prerender.SpeculationRule_Immediate2",
0);
histogram_tester.ExpectUniqueTimeSample(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.Served."
"SpeculationRule_Immediate2",
base::Milliseconds(kHeaderLatency), 1);
histogram_tester.ExpectTotalCount(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.NotServed."
"SpeculationRule_Immediate2",
0);
histogram_tester.ExpectUniqueTimeSample(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.NonPrerender."
"Served.SpeculationRule_Immediate2",
base::Milliseconds(kHeaderLatency), 1);
histogram_tester.ExpectTotalCount(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.NonPrerender."
"NotServed.SpeculationRule_Immediate2",
0);
histogram_tester.ExpectTotalCount(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.Prerender.Served."
"SpeculationRule_Immediate2",
0);
histogram_tester.ExpectTotalCount(
"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.Prerender."
"NotServed.SpeculationRule_Immediate2",
0);
}
}
}