blob: 71794602f0898b34be109bc0802d947eafaeb6f8 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/preloading/prerenderer_impl.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "content/browser/preloading/prefetch/prefetch_features.h"
#include "content/browser/preloading/prefetch/prefetch_service.h"
#include "content/browser/preloading/prefetch/prefetch_status.h"
#include "content/browser/preloading/preloading.h"
#include "content/browser/preloading/preloading_confidence.h"
#include "content/browser/preloading/preloading_decider.h"
#include "content/browser/preloading/prerender/prerender_features.h"
#include "content/browser/preloading/prerender/prerender_host_registry.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/common/content_client.h"
#include "content/public/common/isolated_world_ids.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/prefetch_test_util.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_renderer_host.h"
#include "content/shell/browser/shell.h"
#include "content/test/test_content_browser_client.h"
#include "content/test/test_web_contents.h"
#include "net/dns/mock_host_resolver.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/navigation/preloading_headers.h"
#include "third_party/blink/public/mojom/speculation_rules/speculation_rules.mojom.h"
namespace content {
namespace {
struct RequestPathAndSecPurposeHeader {
std::string path;
std::string sec_purpose_header_value;
bool operator==(const RequestPathAndSecPurposeHeader& other) const = default;
};
std::ostream& operator<<(std::ostream& ostream,
const RequestPathAndSecPurposeHeader& x) {
return ostream << "RequestPathAndSecPurposeHeader {.path = \"" << x.path
<< "\", .sec_purpose_header_value = \""
<< x.sec_purpose_header_value << "\"}";
}
class PrerendererImplBrowserTestBase : public ContentBrowserTest {
public:
PrerendererImplBrowserTestBase() = default;
~PrerendererImplBrowserTestBase() override = default;
void SetUp() override {
prerender_helper_ = std::make_unique<test::PrerenderTestHelper>(
base::BindRepeating(
[](PrerendererImplBrowserTestBase* that) {
return &that->web_contents();
},
base::Unretained(this)),
/*force_disable_prerender2fallback=*/false,
/*force_enable_prerender2innewtab==*/false);
ContentBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
https_server_ = std::make_unique<net::EmbeddedTestServer>(
net::EmbeddedTestServer::TYPE_HTTPS);
histogram_tester_ = std::make_unique<base::HistogramTester>();
host_resolver()->AddRule("*", "127.0.0.1");
https_server_->SetSSLConfig(
net::test_server::EmbeddedTestServer::CERT_TEST_NAMES);
https_server_->RegisterRequestMonitor(
base::BindRepeating(&PrerendererImplBrowserTestBase::OnResourceRequest,
base::Unretained(this)));
https_server_->AddDefaultHandlers(GetTestDataFilePath());
ASSERT_TRUE(https_server_->Start());
embedded_test_server()->RegisterRequestMonitor(
base::BindRepeating(&PrerendererImplBrowserTestBase::OnResourceRequest,
base::Unretained(this)));
embedded_test_server()->AddDefaultHandlers(GetTestDataFilePath());
ASSERT_TRUE(embedded_test_server()->Start());
}
void TearDownOnMainThread() override {
ASSERT_TRUE(https_server_->ShutdownAndWaitUntilComplete());
ASSERT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
}
GURL GetUrl(const std::string& path) {
return https_server_->GetURL("a.test", path);
}
GURL GetCrossSiteUrl(const std::string& path) {
return https_server_->GetURL("b.test", path);
}
GURL GetUrlHttp(const std::string& path) {
return embedded_test_server()->GetURL("a.test", path);
}
Prefetcher& GetPrefetcher() {
return PreloadingDecider::GetOrCreateForCurrentDocument(
web_contents_impl().GetPrimaryMainFrame())
->GetPrefetcherForTesting();
}
PrerendererImpl& GetPrerendererImpl() {
return static_cast<PrerendererImpl&>(
PreloadingDecider::GetOrCreateForCurrentDocument(
web_contents_impl().GetPrimaryMainFrame())
->GetPrerendererForTesting());
}
blink::mojom::SpeculationCandidatePtr CreateSpeculationCandidate(
const GURL& url) {
return blink::mojom::SpeculationCandidate::New(
/*url=*/url,
/*action=*/blink::mojom::SpeculationAction::kPrerender,
/*referrer=*/blink::mojom::Referrer::New(),
/*requires_anonymous_client_ip_when_cross_origin=*/false,
/*target_browsing_context_name_hint=*/
blink::mojom::SpeculationTargetHint::kNoHint,
/*eagerness=*/blink::mojom::SpeculationEagerness::kEager,
/*no_vary_search_hint=*/nullptr,
/*injection_type=*/blink::mojom::SpeculationInjectionType::kNone,
/*tags=*/std::vector<std::optional<std::string>>{std::nullopt});
}
std::vector<RequestPathAndSecPurposeHeader> GetObservedRequests() {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
base::AutoLock auto_lock(lock_);
std::vector<RequestPathAndSecPurposeHeader> ret;
for (auto request : requests_) {
ret.push_back(RequestPathAndSecPurposeHeader{
.path = request.GetURL().PathForRequest(),
.sec_purpose_header_value =
request.headers[blink::kSecPurposeHeaderName],
});
}
return ret;
}
void SetResponseDelay(base::TimeDelta duration) {
response_delay_ = duration;
}
net::EmbeddedTestServer& https_server() { return *https_server_.get(); }
base::HistogramTester& histogram_tester() { return *histogram_tester_.get(); }
test::PrerenderTestHelper& prerender_helper() {
return *prerender_helper_.get();
}
WebContents& web_contents() { return *shell()->web_contents(); }
WebContentsImpl& web_contents_impl() {
return static_cast<WebContentsImpl&>(web_contents());
}
protected:
base::test::ScopedFeatureList feature_list_;
private:
void OnResourceRequest(const net::test_server::HttpRequest& request) {
// Called from a thread for EmbeddedTestServer.
CHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI) &&
!BrowserThread::CurrentlyOn(BrowserThread::IO));
base::PlatformThread::Sleep(response_delay_);
// So, we guard the field with lock.
base::AutoLock auto_lock(lock_);
requests_.push_back(request);
}
std::unique_ptr<net::EmbeddedTestServer> https_server_;
std::unique_ptr<base::HistogramTester> histogram_tester_;
std::unique_ptr<test::PrerenderTestHelper> prerender_helper_;
base::TimeDelta response_delay_ = base::Seconds(0);
base::Lock lock_;
std::vector<net::test_server::HttpRequest> requests_ GUARDED_BY(lock_);
};
class PrerendererImplBrowserTestNoPrefetchAhead
: public PrerendererImplBrowserTestBase {
public:
PrerendererImplBrowserTestNoPrefetchAhead() {
feature_list_.InitWithFeatures(
{features::kPrefetchReusable},
{features::kPrerender2FallbackPrefetchSpecRules,
blink::features::kLCPTimingPredictorPrerender2});
}
};
class PrerendererImplBrowserTestPrefetchAhead
: public PrerendererImplBrowserTestBase,
public ::testing::WithParamInterface<
features::Prerender2FallbackPrefetchSchedulerPolicy> {
public:
PrerendererImplBrowserTestPrefetchAhead() {
const char* prefetch_scheduler_policy = [&]() {
switch (GetParam()) {
case features::Prerender2FallbackPrefetchSchedulerPolicy::kNotUse:
return "NotUse";
case features::Prerender2FallbackPrefetchSchedulerPolicy::kPrioritize:
return "Prioritize";
case features::Prerender2FallbackPrefetchSchedulerPolicy::kBurst:
return "Burst";
}
}();
feature_list_.InitWithFeaturesAndParameters(
{{features::kPrefetchReusable, {}},
{features::kPrerender2FallbackPrefetchSpecRules,
{
{"kPrerender2FallbackPrefetchSchedulerPolicy",
prefetch_scheduler_policy},
}},
{features::kPrefetchUseContentRefactor,
{
{"prefetch_timeout_ms", "1500"},
{"block_until_head_timeout_moderate_prefetch", "500"},
}}},
{blink::features::kLCPTimingPredictorPrerender2,
// `kPrefetchServiceWorker` is disabled to make the prefetch fail due
// to ServiceWorker-related ineligibility.
features::kPrefetchServiceWorker});
}
};
INSTANTIATE_TEST_SUITE_P(
ParametrizedTests,
PrerendererImplBrowserTestPrefetchAhead,
testing::Values(
features::Prerender2FallbackPrefetchSchedulerPolicy::kNotUse,
features::Prerender2FallbackPrefetchSchedulerPolicy::kPrioritize,
features::Prerender2FallbackPrefetchSchedulerPolicy::kBurst));
IN_PROC_BROWSER_TEST_F(PrerendererImplBrowserTestNoPrefetchAhead,
PrefetchNotTriggeredPrerenderSuccess) {
ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));
const GURL prerender_url = GetUrl("/title1.html");
blink::mojom::SpeculationCandidatePtr candidate =
CreateSpeculationCandidate(prerender_url);
PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
PreloadingTriggerType::kSpeculationRule);
GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
PreloadingConfidence{100});
prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);
prerender_helper().NavigatePrimaryPage(prerender_url);
histogram_tester().ExpectTotalCount(
"Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome", 0);
histogram_tester().ExpectUniqueSample(
"Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
PreloadingTriggeringOutcome::kSuccess, 1);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kActivated, 1);
std::vector<RequestPathAndSecPurposeHeader> expected{
{.path = "/empty.html", .sec_purpose_header_value = ""},
{.path = "/title1.html",
.sec_purpose_header_value =
blink::kSecPurposePrefetchPrerenderHeaderValue}};
ASSERT_EQ(expected, GetObservedRequests());
}
IN_PROC_BROWSER_TEST_F(PrerendererImplBrowserTestNoPrefetchAhead,
PrefetchNotTriggeredPrerenderFailure) {
ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));
const GURL prerender_url = GetUrl("/title1.html");
blink::mojom::SpeculationCandidatePtr candidate =
CreateSpeculationCandidate(prerender_url);
PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
PreloadingTriggerType::kSpeculationRule);
GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
PreloadingConfidence{100});
prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);
// Make prerender failure by calling a forbidden API.
test::PrerenderHostObserver observer(web_contents(), prerender_url);
RenderFrameHost* rfh =
prerender_helper().GetPrerenderedMainFrameHost(prerender_url);
ASSERT_TRUE(rfh);
const char* script = "navigator.getGamepads();";
rfh->ExecuteJavaScriptForTests(base::UTF8ToUTF16(script),
base::NullCallback(),
ISOLATED_WORLD_ID_GLOBAL);
observer.WaitForDestroyed();
prerender_helper().NavigatePrimaryPage(prerender_url);
histogram_tester().ExpectTotalCount(
"Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome", 0);
histogram_tester().ExpectUniqueSample(
"Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
PreloadingTriggeringOutcome::kFailure, 1);
std::vector<RequestPathAndSecPurposeHeader> expected{
{.path = "/empty.html", .sec_purpose_header_value = ""},
{.path = "/title1.html",
.sec_purpose_header_value =
blink::kSecPurposePrefetchPrerenderHeaderValue},
{.path = "/title1.html", .sec_purpose_header_value = ""},
};
ASSERT_EQ(expected, GetObservedRequests());
}
IN_PROC_BROWSER_TEST_P(PrerendererImplBrowserTestPrefetchAhead,
PrefetchSuccessPrerenderSuccess) {
ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));
const GURL prerender_url = GetUrl("/title1.html");
blink::mojom::SpeculationCandidatePtr candidate =
CreateSpeculationCandidate(prerender_url);
PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
PreloadingTriggerType::kSpeculationRule);
GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
PreloadingConfidence{100});
prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);
prerender_helper().NavigatePrimaryPage(prerender_url);
// TODO(https://crbug.com/342089066): Record prefetch as
// kTriggeredButUpgradedToPrerender.
histogram_tester().ExpectUniqueSample(
"Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome",
PreloadingTriggeringOutcome::kSuccess, 1);
histogram_tester().ExpectUniqueSample(
"Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
PreloadingTriggeringOutcome::kSuccess, 1);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kActivated, 1);
std::vector<RequestPathAndSecPurposeHeader> expected{
{.path = "/empty.html", .sec_purpose_header_value = ""},
{.path = "/title1.html",
.sec_purpose_header_value =
blink::kSecPurposePrefetchPrerenderHeaderValue}};
ASSERT_EQ(expected, GetObservedRequests());
}
IN_PROC_BROWSER_TEST_P(PrerendererImplBrowserTestPrefetchAhead,
PrefetchSuccessPrerenderNotEligible) {
ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));
test::TestPrefetchWatcher watcher;
const GURL prerender_url = GetCrossSiteUrl("/title1.html");
blink::mojom::SpeculationCandidatePtr candidate =
CreateSpeculationCandidate(prerender_url);
PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
PreloadingTriggerType::kSpeculationRule);
GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
PreloadingConfidence{100});
watcher.WaitUntilPrefetchResponseCompleted(
web_contents_impl().GetPrimaryMainFrame()->GetDocumentToken(),
prerender_url);
prerender_helper().NavigatePrimaryPage(prerender_url);
histogram_tester().ExpectUniqueSample(
"Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome",
PreloadingTriggeringOutcome::kSuccess, 1);
histogram_tester().ExpectUniqueSample(
"Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
PreloadingTriggeringOutcome::kUnspecified, 1);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kCrossSiteNavigationInInitialNavigation, 1);
std::vector<RequestPathAndSecPurposeHeader> expected{
{.path = "/empty.html", .sec_purpose_header_value = ""},
{.path = "/title1.html",
.sec_purpose_header_value =
blink::kSecPurposePrefetchPrerenderHeaderValue},
};
ASSERT_EQ(expected, GetObservedRequests());
}
IN_PROC_BROWSER_TEST_P(PrerendererImplBrowserTestPrefetchAhead,
PrefetchSuccessPrerenderFailure) {
ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));
const GURL prerender_url = GetUrl("/title1.html");
blink::mojom::SpeculationCandidatePtr candidate =
CreateSpeculationCandidate(prerender_url);
PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
PreloadingTriggerType::kSpeculationRule);
GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
PreloadingConfidence{100});
prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);
// Make prerender failure by calling a forbidden API.
test::PrerenderHostObserver observer(web_contents(), prerender_url);
RenderFrameHost* rfh =
prerender_helper().GetPrerenderedMainFrameHost(prerender_url);
ASSERT_TRUE(rfh);
const char* script = "navigator.getGamepads();";
rfh->ExecuteJavaScriptForTests(base::UTF8ToUTF16(script),
base::NullCallback(),
ISOLATED_WORLD_ID_GLOBAL);
observer.WaitForDestroyed();
prerender_helper().NavigatePrimaryPage(prerender_url);
// TODO(https://crbug.com/342089066): Record prefetch as
// kTriggeredButUpgradedToPrerender.
histogram_tester().ExpectUniqueSample(
"Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome",
PreloadingTriggeringOutcome::kSuccess, 1);
histogram_tester().ExpectUniqueSample(
"Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
PreloadingTriggeringOutcome::kFailure, 1);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kMojoBinderPolicy, 1);
std::vector<RequestPathAndSecPurposeHeader> expected{
{.path = "/empty.html", .sec_purpose_header_value = ""},
{.path = "/title1.html",
.sec_purpose_header_value =
blink::kSecPurposePrefetchPrerenderHeaderValue},
};
ASSERT_EQ(expected, GetObservedRequests());
}
IN_PROC_BROWSER_TEST_P(PrerendererImplBrowserTestPrefetchAhead,
PrefetchNotEligiblePrerenderFailure) {
PrefetchService::SetForceIneligibilityForTesting(
PreloadingEligibility::kHostIsNonUnique);
ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));
const GURL prerender_url = GetUrl("/title1.html");
blink::mojom::SpeculationCandidatePtr candidate =
CreateSpeculationCandidate(prerender_url);
PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
PreloadingTriggerType::kSpeculationRule);
GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
PreloadingConfidence{100});
// Here we shouldn't call
// `prerender_helper().WaitForPrerenderLoadCompletion(prerender_url)` since
// this eligibility check of prefetch synchronously fails and the call first
// tries to get `PrerenderHost`, which has been already destructed.
ASSERT_TRUE(NavigateToURL(shell(), prerender_url));
histogram_tester().ExpectUniqueSample(
"Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome",
PreloadingTriggeringOutcome::kUnspecified, 1);
histogram_tester().ExpectUniqueSample(
"Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
PreloadingTriggeringOutcome::kFailure, 1);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kPrerenderFailedDuringPrefetch, 1);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrefetchAheadOfPrerenderFailed.PrefetchStatus."
"SpeculationRule",
PrefetchStatus::kPrefetchIneligibleHostIsNonUnique, 1);
std::vector<RequestPathAndSecPurposeHeader> expected{
{.path = "/empty.html", .sec_purpose_header_value = ""},
{.path = "/title1.html", .sec_purpose_header_value = ""},
};
ASSERT_EQ(expected, GetObservedRequests());
}
IN_PROC_BROWSER_TEST_P(PrerendererImplBrowserTestPrefetchAhead,
PrefetchNotEligibleNonHttpsPrerenderSuccess) {
ASSERT_TRUE(NavigateToURL(shell(), GetUrlHttp("/empty.html")));
const GURL prerender_url = GetUrlHttp("/title1.html");
blink::mojom::SpeculationCandidatePtr candidate =
CreateSpeculationCandidate(prerender_url);
PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
PreloadingTriggerType::kSpeculationRule);
GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
PreloadingConfidence{100});
prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);
prerender_helper().NavigatePrimaryPage(prerender_url);
histogram_tester().ExpectUniqueSample(
"Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome",
PreloadingTriggeringOutcome::kUnspecified, 1);
histogram_tester().ExpectUniqueSample(
"Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
PreloadingTriggeringOutcome::kSuccess, 1);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kActivated, 1);
std::vector<RequestPathAndSecPurposeHeader> expected{
{.path = "/empty.html", .sec_purpose_header_value = ""},
{.path = "/title1.html",
.sec_purpose_header_value =
blink::kSecPurposePrefetchPrerenderHeaderValue},
};
ASSERT_EQ(expected, GetObservedRequests());
}
IN_PROC_BROWSER_TEST_P(PrerendererImplBrowserTestPrefetchAhead,
PrefetchNotEligibleNonHttpsPrerenderSuccessWithDelay) {
ASSERT_TRUE(NavigateToURL(shell(), GetUrlHttp("/empty.html")));
base::test::TestFuture<base::OnceClosure> eligibility_check_callback_future;
auto& prefetch_service = *PrefetchService::GetFromFrameTreeNodeId(
web_contents().GetPrimaryMainFrame()->GetFrameTreeNodeId());
prefetch_service.SetDelayEligibilityCheckForTesting(base::BindRepeating(
[](base::test::TestFuture<base::OnceClosure>*
eligibility_check_callback_future,
base::OnceClosure callback) {
eligibility_check_callback_future->SetValue(std::move(callback));
},
base::Unretained(&eligibility_check_callback_future)));
const GURL prerender_url = GetUrlHttp("/title1.html");
blink::mojom::SpeculationCandidatePtr candidate =
CreateSpeculationCandidate(prerender_url);
PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
PreloadingTriggerType::kSpeculationRule);
GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
PreloadingConfidence{100});
base::PlatformThread::Sleep(base::Milliseconds(101));
// Proceed to the eligibility check.
eligibility_check_callback_future.Take().Run();
prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);
prerender_helper().NavigatePrimaryPage(prerender_url);
histogram_tester().ExpectUniqueSample(
"Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome",
PreloadingTriggeringOutcome::kUnspecified, 1);
histogram_tester().ExpectUniqueSample(
"Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
PreloadingTriggeringOutcome::kSuccess, 1);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kActivated, 1);
std::vector<RequestPathAndSecPurposeHeader> expected{
{.path = "/empty.html", .sec_purpose_header_value = ""},
{.path = "/title1.html",
.sec_purpose_header_value =
blink::kSecPurposePrefetchPrerenderHeaderValue},
};
ASSERT_EQ(expected, GetObservedRequests());
}
// Scenario:
//
// - URL U has a service worker.
// - Trigger prefetch ahead of prerender A for URL U.
// - A failed in eligibility check due to SW.
// - Trigger prerender A' for URL U.
// - A' falls back to non-prefetch navigation.
// - Navigation is started. A' is used.
IN_PROC_BROWSER_TEST_P(PrerendererImplBrowserTestPrefetchAhead,
PrefetchNotEligibleServiceWorkerPrerenderSuccess) {
ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/prerender/empty.html")));
std::string script = R"(
(async () => {
await navigator.serviceWorker.register('./sw_fallback.js');
await navigator.serviceWorker.ready;
return true;
})();
)";
EXPECT_TRUE(ExecJs(web_contents().GetPrimaryMainFrame(), script));
const GURL prerender_url = GetUrl("/prerender/empty.html?2");
blink::mojom::SpeculationCandidatePtr candidate =
CreateSpeculationCandidate(prerender_url);
PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
PreloadingTriggerType::kSpeculationRule);
GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
PreloadingConfidence{100});
prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);
prerender_helper().NavigatePrimaryPage(prerender_url);
histogram_tester().ExpectUniqueSample(
"Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome",
PreloadingTriggeringOutcome::kUnspecified, 1);
histogram_tester().ExpectUniqueSample(
"Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
PreloadingTriggeringOutcome::kSuccess, 1);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kActivated, 1);
std::vector<RequestPathAndSecPurposeHeader> expected{
{.path = "/prerender/empty.html", .sec_purpose_header_value = ""},
{.path = "/prerender/sw_fallback.js", .sec_purpose_header_value = ""},
{.path = "/prerender/empty.html?2",
.sec_purpose_header_value =
blink::kSecPurposePrefetchPrerenderHeaderValue},
};
ASSERT_EQ(expected, GetObservedRequests());
}
// Variant of PrefetchNotEligibleServiceWorkerPrerenderSuccess.
// Eligibility check with delay.
//
// Scenario:
//
// - URL U has a service worker.
// - Trigger prefetch ahead of prerender A for URL U.
// - Trigger prerender A' for URL U.
// - A blocks A'.
// - A failed in eligibility check due to SW.
// - A' falls back to non-prefetch navigation.
// - Navigation is started. A' is used.
IN_PROC_BROWSER_TEST_P(
PrerendererImplBrowserTestPrefetchAhead,
PrefetchNotEligibleServiceWorkerPrerenderSuccessWithDelay) {
ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/prerender/empty.html")));
std::string script = R"(
(async () => {
await navigator.serviceWorker.register('./sw_fallback.js');
await navigator.serviceWorker.ready;
return true;
})();
)";
EXPECT_TRUE(ExecJs(web_contents().GetPrimaryMainFrame(), script));
base::test::TestFuture<base::OnceClosure> eligibility_check_callback_future;
auto& prefetch_service = *PrefetchService::GetFromFrameTreeNodeId(
web_contents().GetPrimaryMainFrame()->GetFrameTreeNodeId());
prefetch_service.SetDelayEligibilityCheckForTesting(base::BindRepeating(
[](base::test::TestFuture<base::OnceClosure>*
eligibility_check_callback_future,
base::OnceClosure callback) {
eligibility_check_callback_future->SetValue(std::move(callback));
},
base::Unretained(&eligibility_check_callback_future)));
const GURL prerender_url = GetUrl("/prerender/empty.html?2");
blink::mojom::SpeculationCandidatePtr candidate =
CreateSpeculationCandidate(prerender_url);
PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
PreloadingTriggerType::kSpeculationRule);
GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
PreloadingConfidence{100});
base::PlatformThread::Sleep(base::Milliseconds(101));
// Proceed to the eligibility check.
eligibility_check_callback_future.Take().Run();
prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);
prerender_helper().NavigatePrimaryPage(prerender_url);
histogram_tester().ExpectUniqueSample(
"Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome",
PreloadingTriggeringOutcome::kUnspecified, 1);
histogram_tester().ExpectUniqueSample(
"Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
PreloadingTriggeringOutcome::kSuccess, 1);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kActivated, 1);
std::vector<RequestPathAndSecPurposeHeader> expected{
{.path = "/prerender/empty.html", .sec_purpose_header_value = ""},
{.path = "/prerender/sw_fallback.js", .sec_purpose_header_value = ""},
{.path = "/prerender/empty.html?2",
.sec_purpose_header_value =
blink::kSecPurposePrefetchPrerenderHeaderValue},
};
ASSERT_EQ(expected, GetObservedRequests());
}
// Scenario:
//
// - Trigger prefetch ahead of prerender A for URL U.
// - Trigger prerender A' for URL U.
// - A blocks A'.
// - A failed due to timeout.
// - The failure is propagated to A'.
// - Navigation is started. No preloads are used.
IN_PROC_BROWSER_TEST_P(PrerendererImplBrowserTestPrefetchAhead,
PrefetchTimeoutPrerenderFailure) {
// Prefetch will fail as
// `prefetch_timeout_ms = 1500 < response_delay_ = 1500 + 1000`.
SetResponseDelay(base::Milliseconds(1500 + 1000));
ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));
const GURL prerender_url = GetUrl("/title1.html");
blink::mojom::SpeculationCandidatePtr candidate =
CreateSpeculationCandidate(prerender_url);
PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
PreloadingTriggerType::kSpeculationRule);
GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
PreloadingConfidence{100});
prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);
ASSERT_TRUE(NavigateToURL(shell(), prerender_url));
histogram_tester().ExpectUniqueSample(
"Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome",
PreloadingTriggeringOutcome::kFailure, 1);
histogram_tester().ExpectUniqueSample(
"Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
PreloadingTriggeringOutcome::kFailure, 1);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kPrerenderFailedDuringPrefetch, 1);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrefetchAheadOfPrerenderFailed.PrefetchStatus."
"SpeculationRule",
PrefetchStatus::kPrefetchNotFinishedInTime, 1);
std::vector<RequestPathAndSecPurposeHeader> expected{
{.path = "/empty.html", .sec_purpose_header_value = ""},
// Prefetch and prerender, timed out and aborted.
{.path = "/title1.html",
.sec_purpose_header_value =
blink::kSecPurposePrefetchPrerenderHeaderValue},
// Normal navigation.
{.path = "/title1.html", .sec_purpose_header_value = ""}};
ASSERT_EQ(expected, GetObservedRequests());
}
// Consider a case that a site uses a SpecRules containing prefetch and
// prerender for a URL U.
//
// Scenario:
//
// - Trigger prefetch A a URL U.
// - Trigger prefetch ahead of prerender B for URL U.
// - B is migrated into A. A inherits `PreloadPipelineInfo` and is considered
// as `IsLikelyAheadOfPrerender`.
// - Trigger prerender B' for URL U.
// - A blocks B'.
// - Navigation is started. B' is used.
IN_PROC_BROWSER_TEST_P(PrerendererImplBrowserTestPrefetchAhead,
PrefetchMigratedPrefetchSuccessPrerenderSuccess) {
ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));
const GURL prerender_url = GetUrl("/title1.html");
PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
PreloadingTriggerType::kSpeculationRule);
{
blink::mojom::SpeculationCandidatePtr candidate =
CreateSpeculationCandidate(prerender_url);
candidate->action = blink::mojom::SpeculationAction::kPrefetch;
GetPrefetcher().MaybePrefetch(std::move(candidate), enacting_predictor);
}
{
blink::mojom::SpeculationCandidatePtr candidate =
CreateSpeculationCandidate(prerender_url);
GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
PreloadingConfidence{100});
}
prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);
prerender_helper().NavigatePrimaryPage(prerender_url);
EXPECT_THAT(
histogram_tester().GetAllSamples(
"Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome"),
testing::ElementsAre(
base::Bucket(PreloadingTriggeringOutcome::kUnspecified, 1),
base::Bucket(PreloadingTriggeringOutcome::kSuccess, 1)));
histogram_tester().ExpectUniqueSample(
"Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
PreloadingTriggeringOutcome::kSuccess, 1);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kActivated, 1);
std::vector<RequestPathAndSecPurposeHeader> expected{
{.path = "/empty.html", .sec_purpose_header_value = ""},
{.path = "/title1.html",
.sec_purpose_header_value = blink::kSecPurposePrefetchHeaderValue}};
ASSERT_EQ(expected, GetObservedRequests());
}
// Variant of PrefetchMigratedPrefetchSuccessPrerenderSuccess.
// The order of migration and prefetch completion is reversed.
IN_PROC_BROWSER_TEST_P(PrerendererImplBrowserTestPrefetchAhead,
PrefetchSuccessPrefetchMigratedPrerenderSuccess) {
ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));
const GURL prerender_url = GetUrl("/title1.html");
PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
PreloadingTriggerType::kSpeculationRule);
{
test::TestPrefetchWatcher watcher;
blink::mojom::SpeculationCandidatePtr candidate =
CreateSpeculationCandidate(prerender_url);
candidate->action = blink::mojom::SpeculationAction::kPrefetch;
GetPrefetcher().MaybePrefetch(std::move(candidate), enacting_predictor);
watcher.WaitUntilPrefetchResponseCompleted(
web_contents_impl().GetPrimaryMainFrame()->GetDocumentToken(),
prerender_url);
}
{
blink::mojom::SpeculationCandidatePtr candidate =
CreateSpeculationCandidate(prerender_url);
GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
PreloadingConfidence{100});
}
prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);
prerender_helper().NavigatePrimaryPage(prerender_url);
EXPECT_THAT(
histogram_tester().GetAllSamples(
"Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome"),
testing::ElementsAre(
base::Bucket(PreloadingTriggeringOutcome::kUnspecified, 1),
base::Bucket(PreloadingTriggeringOutcome::kSuccess, 1)));
histogram_tester().ExpectUniqueSample(
"Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
PreloadingTriggeringOutcome::kSuccess, 1);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kActivated, 1);
std::vector<RequestPathAndSecPurposeHeader> expected{
{.path = "/empty.html", .sec_purpose_header_value = ""},
{.path = "/title1.html",
.sec_purpose_header_value = blink::kSecPurposePrefetchHeaderValue}};
ASSERT_EQ(expected, GetObservedRequests());
}
// Scenario:
//
// - Trigger prefetch A a URL U.
// - Eligibility check for A is done.
// - Trigger prefetch ahead of prerender B for URL U.
// - B is migrated into A. A inherits `PreloadPipelineInfo` and is considered
// as `IsLikelyAheadOfPrerender`. Eligibility of A is propagated to the
// `PreloadPipelineInfo`.
// - Trigger prerender B' for URL U.
// - A blocks B'.
// - A fails by timeout.
// - Navigation is started. No preloads are used.
//
// This shows the necessity of eligibility propagation in
// `PrefetchContainer::MigrateNewlyAdded()`.
IN_PROC_BROWSER_TEST_P(PrerendererImplBrowserTestPrefetchAhead,
PrefetchMigratedPrefetchFailurePrerenderFailure) {
// Prefetch will fail as
// `prefetch_timeout_ms = 1500 < response_delay_ = 1500 + 1000`.
SetResponseDelay(base::Milliseconds(1500 + 1000));
ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));
const GURL prerender_url = GetUrl("/title1.html");
PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
PreloadingTriggerType::kSpeculationRule);
{
blink::mojom::SpeculationCandidatePtr candidate =
CreateSpeculationCandidate(prerender_url);
candidate->action = blink::mojom::SpeculationAction::kPrefetch;
GetPrefetcher().MaybePrefetch(std::move(candidate), enacting_predictor);
}
// Ensure that eligibility check is done.
base::PlatformThread::Sleep(base::Milliseconds(101));
{
blink::mojom::SpeculationCandidatePtr candidate =
CreateSpeculationCandidate(prerender_url);
GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
PreloadingConfidence{100});
}
prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);
ASSERT_TRUE(NavigateToURL(shell(), prerender_url));
EXPECT_THAT(
histogram_tester().GetAllSamples(
"Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome"),
testing::ElementsAre(
base::Bucket(PreloadingTriggeringOutcome::kUnspecified, 1),
base::Bucket(PreloadingTriggeringOutcome::kFailure, 1)));
histogram_tester().ExpectUniqueSample(
"Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
PreloadingTriggeringOutcome::kFailure, 1);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kPrerenderFailedDuringPrefetch, 1);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrefetchAheadOfPrerenderFailed.PrefetchStatus."
"SpeculationRule",
PrefetchStatus::kPrefetchNotFinishedInTime, 1);
std::vector<RequestPathAndSecPurposeHeader> expected{
{.path = "/empty.html", .sec_purpose_header_value = ""},
{.path = "/title1.html",
.sec_purpose_header_value = blink::kSecPurposePrefetchHeaderValue},
{.path = "/title1.html", .sec_purpose_header_value = ""}};
ASSERT_EQ(expected, GetObservedRequests());
}
// Scenario:
//
// - Trigger prefetch A.
// - Trigger prefetch ahead of prerender B for URL U.
// - B is migrated into A. A inherits `PreloadPipelineInfo` and is considered
// as `IsLikelyAheadOfPrerender`.
// - Trigger prerender B' for URL U.
// - A fails in eligibility check.
// - B' fails as the ineligibility is not admissible.
// - Navigation is started. No preloads are used.
IN_PROC_BROWSER_TEST_P(PrerendererImplBrowserTestPrefetchAhead,
PrefetchMigratedPrefetchNotEligiblePrerenderFailure) {
PrefetchService::SetForceIneligibilityForTesting(
PreloadingEligibility::kHostIsNonUnique);
base::test::TestFuture<base::OnceClosure> eligibility_check_callback_future;
auto& prefetch_service = *PrefetchService::GetFromFrameTreeNodeId(
web_contents().GetPrimaryMainFrame()->GetFrameTreeNodeId());
prefetch_service.SetDelayEligibilityCheckForTesting(base::BindRepeating(
[](base::test::TestFuture<base::OnceClosure>*
eligibility_check_callback_future,
base::OnceClosure callback) {
eligibility_check_callback_future->SetValue(std::move(callback));
},
base::Unretained(&eligibility_check_callback_future)));
ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));
const GURL prerender_url = GetUrl("/title1.html");
PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
PreloadingTriggerType::kSpeculationRule);
{
blink::mojom::SpeculationCandidatePtr candidate =
CreateSpeculationCandidate(prerender_url);
candidate->action = blink::mojom::SpeculationAction::kPrefetch;
GetPrefetcher().MaybePrefetch(std::move(candidate), enacting_predictor);
}
{
blink::mojom::SpeculationCandidatePtr candidate =
CreateSpeculationCandidate(prerender_url);
GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
PreloadingConfidence{100});
}
// Proceed to the eligibility check of the first prefetch.
eligibility_check_callback_future.Take().Run();
// Here we shouldn't call
// `prerender_helper().WaitForPrerenderLoadCompletion(prerender_url)` since
// the call first tries to get `PrerenderHost`, which has been already
// destructed.
prerender_helper().NavigatePrimaryPage(prerender_url);
histogram_tester().ExpectUniqueSample(
"Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome",
PreloadingTriggeringOutcome::kUnspecified, 2);
histogram_tester().ExpectUniqueSample(
"Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
PreloadingTriggeringOutcome::kFailure, 1);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kPrerenderFailedDuringPrefetch, 1);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrefetchAheadOfPrerenderFailed.PrefetchStatus."
"SpeculationRule",
PrefetchStatus::kPrefetchIneligibleHostIsNonUnique, 1);
std::vector<RequestPathAndSecPurposeHeader> expected{
{.path = "/empty.html", .sec_purpose_header_value = ""},
{.path = "/title1.html", .sec_purpose_header_value = ""},
};
ASSERT_EQ(expected, GetObservedRequests());
}
// Consider a case that a site uses a SpecRules containing prefetch and
// prerender for a URL U.
//
// Scenario:
//
// - Trigger prefetch A a URL U.
// - Trigger prefetch ahead of prerender B for URL U.
// - B is migrated into A. A inherits `PreloadPipelineInfo` and is considered
// as `IsLikelyAheadOfPrerender`.
// - Trigger prerender B' for URL U.
// - A blocks B'.
// - A failed due to timeout.
// - The failure is propagated to B'.
// - Navigation is started. No preloads are used.
IN_PROC_BROWSER_TEST_P(PrerendererImplBrowserTestPrefetchAhead,
PrefetchMigratedPrefetchTimeoutPrerenderFailure) {
// Prefetch will fail as
// `prefetch_timeout_ms = 1500 < response_delay_ = 1500 + 1000`.
SetResponseDelay(base::Milliseconds(1500 + 1000));
ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));
const GURL prerender_url = GetUrl("/title1.html");
PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
PreloadingTriggerType::kSpeculationRule);
{
blink::mojom::SpeculationCandidatePtr candidate =
CreateSpeculationCandidate(prerender_url);
candidate->action = blink::mojom::SpeculationAction::kPrefetch;
GetPrefetcher().MaybePrefetch(std::move(candidate), enacting_predictor);
}
{
blink::mojom::SpeculationCandidatePtr candidate =
CreateSpeculationCandidate(prerender_url);
GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
PreloadingConfidence{100});
}
prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);
ASSERT_TRUE(NavigateToURL(shell(), prerender_url));
EXPECT_THAT(
histogram_tester().GetAllSamples(
"Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome"),
testing::ElementsAre(
base::Bucket(PreloadingTriggeringOutcome::kUnspecified, 1),
base::Bucket(PreloadingTriggeringOutcome::kFailure, 1)));
histogram_tester().ExpectUniqueSample(
"Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
PreloadingTriggeringOutcome::kFailure, 1);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kPrerenderFailedDuringPrefetch, 1);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrefetchAheadOfPrerenderFailed.PrefetchStatus."
"SpeculationRule",
PrefetchStatus::kPrefetchNotFinishedInTime, 1);
std::vector<RequestPathAndSecPurposeHeader> expected{
{.path = "/empty.html", .sec_purpose_header_value = ""},
// Prefetch and prerender, timed out and aborted.
{.path = "/title1.html",
.sec_purpose_header_value = blink::kSecPurposePrefetchHeaderValue},
// Normal navigation.
{.path = "/title1.html", .sec_purpose_header_value = ""}};
ASSERT_EQ(expected, GetObservedRequests());
}
// Scenario:
//
// - Trigger prefetch ahead of prerender A for URL U.
// - Trigger prerender A' for URL U.
// - A blocks A'.
// - Prefetch matching process ended due to timeout. A' is aborted.
// - A received response and succeeded.
// - Navigation is started. A is used.
//
// TODO(crbug.com/372851198): The scenario described does not seem to work as
// desired. A second request is made to /title1.html, and
// `PrefetchContainer::Reader::OnPrefetchProbeResult()` is not called. Before
// https://chromium-review.googlesource.com/c/chromium/src/+/6056830, the second
// request was avoided due to the HTTP cache, but after that change the overall
// brokenness of the scenario is revealed. It is likely that the /title1.html
// response is not making it into the speculation rules prefetch cache.
IN_PROC_BROWSER_TEST_P(
PrerendererImplBrowserTestPrefetchAhead,
DISABLED_PrefetchSuccessPrefetchMatchResolverTimeoutPrerenderFailure) {
SetResponseDelay(base::Milliseconds(1000));
ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));
const GURL prerender_url = GetUrl("/title1.html");
{
test::TestPrefetchWatcher watcher;
blink::mojom::SpeculationCandidatePtr candidate =
CreateSpeculationCandidate(prerender_url);
// Use `kModerate` to trigger `PrefetchMatchResolver::OnTimeout()`.
// Note that `block_until_head_timeout_moderate_prefetch = 500 <
// response_delay_ = 1000 < prefetch_timeout_ms = 1500`.
candidate->eagerness = blink::mojom::SpeculationEagerness::kModerate;
PreloadingPredictor enacting_predictor =
GetPredictorForPreloadingTriggerType(
PreloadingTriggerType::kSpeculationRule);
GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
PreloadingConfidence{100});
prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);
watcher.WaitUntilPrefetchResponseCompleted(
web_contents_impl().GetPrimaryMainFrame()->GetDocumentToken(),
prerender_url);
}
ASSERT_TRUE(NavigateToURL(shell(), prerender_url));
histogram_tester().ExpectUniqueSample(
"Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome",
PreloadingTriggeringOutcome::kReady, 1);
histogram_tester().ExpectUniqueSample(
"Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
PreloadingTriggeringOutcome::kFailure, 1);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
PrerenderFinalStatus::kPrerenderFailedDuringPrefetch, 1);
histogram_tester().ExpectUniqueSample(
"Prerender.Experimental.PrefetchAheadOfPrerenderFailed.PrefetchStatus."
"SpeculationRule",
PrefetchStatus::kPrefetchNotFinishedInTime, 1);
std::vector<RequestPathAndSecPurposeHeader> expected{
{.path = "/empty.html", .sec_purpose_header_value = ""},
// Prerender is aborted, but prefetch is success and used.
{.path = "/title1.html",
.sec_purpose_header_value =
blink::kSecPurposePrefetchPrerenderHeaderValue}};
ASSERT_EQ(expected, GetObservedRequests());
}
// Consider a case that a site inserted a SpecRules for prerender, removed it,
// and inserted again.
//
// Scenario:
//
// - Trigger prefetch ahead of prerender A for URL U.
// - Trigger prerender A' for URL U.
// - A' uses A.
// - A' is cancelled.
// - Trigger prefetch ahead of prerender B for URL U.
// - B is migrated into A. A inherits `PreloadPipelineInfo`.
// - Trigger prerender B' for URL U.
// - B' uses A.
// - Navigation is started. B' is used.
IN_PROC_BROWSER_TEST_P(PrerendererImplBrowserTestPrefetchAhead,
PrerenderSuccessCancelledAnotherPrerenderSuccess) {
ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));
const GURL prerender_url = GetUrl("/title1.html");
PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
PreloadingTriggerType::kSpeculationRule);
{
blink::mojom::SpeculationCandidatePtr candidate =
CreateSpeculationCandidate(prerender_url);
GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
PreloadingConfidence{100});
prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);
GetPrerendererImpl().CancelStartedPrerendersForTesting();
}
{
blink::mojom::SpeculationCandidatePtr candidate =
CreateSpeculationCandidate(prerender_url);
GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
PreloadingConfidence{100});
prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);
}
prerender_helper().NavigatePrimaryPage(prerender_url);
EXPECT_THAT(
histogram_tester().GetAllSamples(
"Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome"),
testing::ElementsAre(
base::Bucket(PreloadingTriggeringOutcome::kSuccess, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(
"Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome"),
testing::ElementsAre(
base::Bucket(PreloadingTriggeringOutcome::kReady, 1),
base::Bucket(PreloadingTriggeringOutcome::kSuccess, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(
"Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule"),
testing::ElementsAre(
base::Bucket(PrerenderFinalStatus::kActivated, 1),
base::Bucket(PrerenderFinalStatus::kTriggerDestroyed, 1)));
std::vector<RequestPathAndSecPurposeHeader> expected{
{.path = "/empty.html", .sec_purpose_header_value = ""},
{.path = "/title1.html",
.sec_purpose_header_value =
blink::kSecPurposePrefetchPrerenderHeaderValue}};
ASSERT_EQ(expected, GetObservedRequests());
}
} // namespace
} // namespace content