blob: cc9d889155e8cd293f450995b9fa560e552d7e04 [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 "base/synchronization/lock.h"
#include "base/test/scoped_feature_list.h"
#include "base/thread_annotations.h"
#include "base/timer/elapsed_timer.h"
#include "content/browser/back_forward_cache_test_util.h"
#include "content/browser/preloading/prefetch/prefetch_document_manager.h"
#include "content/browser/preloading/prefetch/prefetch_features.h"
#include "content/browser/preloading/preloading.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.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/preloading_test_util.h"
#include "content/public/test/test_frame_navigation_observer.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/shell/browser/shell.h"
#include "net/cookies/canonical_cookie_test_helpers.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/controllable_http_response.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "third_party/blink/public/mojom/loader/referrer.mojom.h"
#include "third_party/blink/public/mojom/speculation_rules/speculation_rules.mojom.h"
namespace content {
namespace {
using net::test_server::ControllableHttpResponse;
class NavPrefetchBrowserTest : public ContentBrowserTest,
public BackForwardCacheMetricsTestMatcher {
public:
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
attempt_ukm_entry_builder_ =
std::make_unique<test::PreloadingAttemptUkmEntryBuilder>(
content_preloading_predictor::kSpeculationRules);
ssl_server_.RegisterRequestMonitor(
base::BindRepeating(&NavPrefetchBrowserTest::MonitorResourceRequest,
base::Unretained(this)));
ssl_server_.AddDefaultHandlers(GetTestDataFilePath());
ssl_server_.SetSSLConfig(
net::test_server::EmbeddedTestServer::CERT_TEST_NAMES);
ASSERT_TRUE(ssl_server_.Start());
}
void StartPrefetch(const GURL& url) {
auto* prefetch_document_manager =
PrefetchDocumentManager::GetOrCreateForCurrentDocument(
shell()->web_contents()->GetPrimaryMainFrame());
auto candidate = blink::mojom::SpeculationCandidate::New();
candidate->url = url;
candidate->action = blink::mojom::SpeculationAction::kPrefetch;
candidate->eagerness = blink::mojom::SpeculationEagerness::kImmediate;
candidate->referrer = Referrer::SanitizeForRequest(
url, blink::mojom::Referrer(
shell()->web_contents()->GetURL(),
network::mojom::ReferrerPolicy::kStrictOriginWhenCrossOrigin));
std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
candidates.push_back(std::move(candidate));
prefetch_document_manager->ProcessCandidates(candidates);
}
void ResetSSLConfig(
net::test_server::EmbeddedTestServer::ServerCertificate cert,
const net::SSLServerConfig& ssl_config) {
ASSERT_TRUE(ssl_server_.ResetSSLConfig(cert, ssl_config));
}
RenderFrameHostImpl& GetPrimaryMainFrameHost() {
return static_cast<WebContentsImpl*>(shell()->web_contents())
->GetPrimaryPage()
.GetMainDocument();
}
void MonitorResourceRequest(const net::test_server::HttpRequest& request) {
// This should be called on `EmbeddedTestServer::io_thread_`.
EXPECT_FALSE(BrowserThread::CurrentlyOn(content::BrowserThread::UI));
base::AutoLock auto_lock(lock_);
request_count_by_path_[request.GetURL().PathForRequest()]++;
}
int GetRequestCount(const GURL& url) {
EXPECT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::UI));
base::AutoLock auto_lock(lock_);
return request_count_by_path_[url.PathForRequest()];
}
// BackForwardCacheMetricsTestMatcher implementation.
const base::HistogramTester& histogram_tester() override {
return histogram_tester_;
}
// BackForwardCacheMetricsTestMatcher implementation.
const ukm::TestAutoSetUkmRecorder& ukm_recorder() override {
return *ukm_recorder_;
}
const test::PreloadingAttemptUkmEntryBuilder&
attempt_entry_builder() {
return *attempt_ukm_entry_builder_;
}
protected:
GURL GetUrl(const std::string& host, const std::string& path) const {
return ssl_server_.GetURL(host, path);
}
private:
base::ScopedMockElapsedTimersForTest test_timer_;
net::test_server::EmbeddedTestServer ssl_server_{
net::test_server::EmbeddedTestServer::TYPE_HTTPS};
std::map<std::string, int> request_count_by_path_ GUARDED_BY(lock_);
base::HistogramTester histogram_tester_;
std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_;
std::unique_ptr<test::PreloadingAttemptUkmEntryBuilder>
attempt_ukm_entry_builder_;
// Disable sampling for UKM preloading logs.
test::PreloadingConfigOverride preloading_config_override_;
base::test::ScopedFeatureList scoped_feature_list_;
base::Lock lock_;
};
IN_PROC_BROWSER_TEST_F(NavPrefetchBrowserTest,
DoesNotHangIfCancelledWhileWaitingForHead) {
ControllableHttpResponse response1(embedded_test_server(), "/next");
ControllableHttpResponse response2(embedded_test_server(), "/next");
ASSERT_TRUE(embedded_test_server()->Start());
GURL referrer_url = embedded_test_server()->GetURL("/empty.html");
GURL next_url = embedded_test_server()->GetURL("/next");
ASSERT_TRUE(NavigateToURL(shell(), referrer_url));
// Prefetch the next page and wait for that request to arrive.
StartPrefetch(next_url);
response1.WaitForRequest();
// Start a navigation which may block on head (since we haven't sent it).
RenderFrameHost* rfh = shell()->web_contents()->GetPrimaryMainFrame();
TestFrameNavigationObserver nav_observer(rfh);
ASSERT_TRUE(BeginNavigateToURLFromRenderer(rfh, next_url));
ASSERT_TRUE(nav_observer.navigation_started());
// Cancel the prefetch.
auto* prefetch_document_manager =
PrefetchDocumentManager::GetOrCreateForCurrentDocument(rfh);
std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
prefetch_document_manager->ProcessCandidates(candidates);
// Wait for a new request, and respond to it.
response2.WaitForRequest();
response2.Send(net::HTTP_OK);
response2.Done();
// The navigation should now succeed.
nav_observer.Wait();
EXPECT_EQ(nav_observer.last_committed_url(), next_url);
EXPECT_TRUE(nav_observer.last_navigation_succeeded());
}
// TODO(crbug.com/345352974): Make it a web platform test instead.
IN_PROC_BROWSER_TEST_F(NavPrefetchBrowserTest, ServedToRedirectionChain) {
GURL initiator_url = GetUrl("a.test", "/empty.html");
GURL des_url = GetUrl("a.test", "/title2.html");
GURL next_nav_url = GetUrl("b.test", "/server-redirect?" + des_url.spec());
ASSERT_TRUE(NavigateToURL(shell(), initiator_url));
test::TestPrefetchWatcher test_prefetch_watcher;
StartPrefetch(des_url);
test_prefetch_watcher.WaitUntilPrefetchResponseCompleted(
GetPrimaryMainFrameHost().GetDocumentToken(), des_url);
ASSERT_EQ(GetRequestCount(des_url), 1);
TestNavigationObserver nav_observer(shell()->web_contents());
nav_observer.set_wait_event(TestNavigationObserver::WaitEvent::kLoadStopped);
std::ignore = ExecJs(shell()->web_contents()->GetPrimaryMainFrame(),
JsReplace("location = $1", next_nav_url));
nav_observer.Wait();
EXPECT_TRUE(test_prefetch_watcher.PrefetchUsedInLastNavigation());
EXPECT_EQ(GetRequestCount(des_url), 1);
ukm::SourceId ukm_source_id =
shell()->web_contents()->GetPrimaryMainFrame()->GetPageUkmSourceId();
// Navigate primary page to flush the metrics.
ASSERT_TRUE(NavigateToURL(shell(), initiator_url));
test::ExpectPreloadingAttemptUkm(
ukm_recorder(),
{attempt_entry_builder().BuildEntry(
ukm_source_id, PreloadingType::kPrefetch,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kSuccess,
PreloadingFailureReason::kUnspecified,
/*accurate=*/true,
base::ScopedMockElapsedTimersForTest::kMockElapsedTime,
blink::mojom::SpeculationEagerness::kImmediate)});
}
// TODO(crbug.com/345352974): Make it a web platform test instead.
IN_PROC_BROWSER_TEST_F(NavPrefetchBrowserTest, SetCookieViaHTTPResponse) {
GURL initiator_url = GetUrl("a.test", "/empty.html");
const std::string server_cookie = "host_cookie=1";
GURL des_url = GetUrl("b.test", "/set-cookie?" + server_cookie);
ASSERT_TRUE(NavigateToURL(shell(), initiator_url));
// 1. Prefetch a resource which sets cookie.
test::TestPrefetchWatcher test_prefetch_watcher;
StartPrefetch(des_url);
test_prefetch_watcher.WaitUntilPrefetchResponseCompleted(
GetPrimaryMainFrameHost().GetDocumentToken(), des_url);
ASSERT_EQ(GetRequestCount(des_url), 1);
// 2. Activate the prefetched result.
TestNavigationObserver nav_observer(shell()->web_contents());
nav_observer.set_wait_event(TestNavigationObserver::WaitEvent::kLoadStopped);
std::ignore = ExecJs(shell()->web_contents()->GetPrimaryMainFrame(),
JsReplace("location = $1", des_url));
nav_observer.Wait();
// 3. The cookie was written into the real cookie storage.
EXPECT_TRUE(test_prefetch_watcher.PrefetchUsedInLastNavigation());
EXPECT_EQ(GetRequestCount(des_url), 1);
EXPECT_EQ(EvalJs(shell()->web_contents()->GetPrimaryMainFrame(),
"document.cookie"), server_cookie);
// 4. Navigate to another same-site page to confirm the cookie is persistent.
GURL after_prefetch_url = GetUrl("b.test", "/title2.html");
std::ignore = ExecJs(shell()->web_contents()->GetPrimaryMainFrame(),
JsReplace("location = $1", after_prefetch_url));
EXPECT_EQ(EvalJs(shell()->web_contents()->GetPrimaryMainFrame(),
"document.cookie"), server_cookie);
}
// TODO(crbug.com/345352974): Make it a web platform test instead.
IN_PROC_BROWSER_TEST_F(NavPrefetchBrowserTest,
NeverSetCookieForDiscardedPrefetch) {
GURL initiator_url = GetUrl("a.test", "/empty.html");
const std::string server_cookie = "host_cookie=1";
GURL des_url = GetUrl("b.test", "/set-cookie?" + server_cookie);
ASSERT_TRUE(NavigateToURL(shell(), initiator_url));
// 1. Prefetch a resource which sets cookie.
test::TestPrefetchWatcher test_prefetch_watcher;
StartPrefetch(des_url);
test_prefetch_watcher.WaitUntilPrefetchResponseCompleted(
GetPrimaryMainFrameHost().GetDocumentToken(), des_url);
ASSERT_EQ(GetRequestCount(des_url), 1);
// 2. Navigate to another URL to invalidate the prefetch result.
TestNavigationObserver nav_observer(shell()->web_contents());
nav_observer.set_wait_event(TestNavigationObserver::WaitEvent::kLoadStopped);
GURL after_prefetch_url = GetUrl("b.test", "/title2.html");
std::ignore = ExecJs(shell()->web_contents()->GetPrimaryMainFrame(),
JsReplace("location = $1", after_prefetch_url));
nav_observer.Wait();
// 3. Check the cookie set by discarded prefetch response cannot affect the
// real jar.
EXPECT_EQ(EvalJs(shell()->web_contents()->GetPrimaryMainFrame(),
"document.cookie").ExtractString(), "");
}
IN_PROC_BROWSER_TEST_F(
NavPrefetchBrowserTest,
CrossSitePrefetchNotServedWhenCookieChange_BeforeFirstServe) {
// Perform a cross-site prefetch which sets cookie.
const std::string server_cookie = "server_cookie=1";
GURL initiator_url = GetUrl("a.test", "/empty.html");
GURL des_url = GetUrl("b.test", "/set-cookie?" + server_cookie);
ASSERT_TRUE(NavigateToURL(shell(), initiator_url));
test::TestPrefetchWatcher test_prefetch_watcher;
StartPrefetch(des_url);
test_prefetch_watcher.WaitUntilPrefetchResponseCompleted(
GetPrimaryMainFrameHost().GetDocumentToken(), des_url);
ASSERT_EQ(GetRequestCount(des_url), 1);
// Change a cookie for the prefetched site.
ASSERT_TRUE(SetCookie(shell()->web_contents()->GetBrowserContext(), des_url,
"test=1"));
// Try to navigate a prefetched site. Prefetch is not used because default
// network context's cookie has been changed.
TestNavigationObserver nav_observer(shell()->web_contents());
nav_observer.set_wait_event(TestNavigationObserver::WaitEvent::kLoadStopped);
std::ignore = ExecJs(shell()->web_contents()->GetPrimaryMainFrame(),
JsReplace("location = $1", des_url));
nav_observer.Wait();
EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), des_url);
EXPECT_FALSE(test_prefetch_watcher.PrefetchUsedInLastNavigation());
EXPECT_EQ(GetRequestCount(des_url), 2);
EXPECT_THAT(GetCanonicalCookies(shell()->web_contents()->GetBrowserContext(),
des_url),
testing::UnorderedElementsAre(
net::MatchesCookieNameValue("server_cookie", "1"),
net::MatchesCookieNameValue("test", "1")));
}
IN_PROC_BROWSER_TEST_F(
NavPrefetchBrowserTest,
CrossSitePrefetchNotServedWhenCookieChange_AfterFirstServe) {
if (!BackForwardCache::IsBackForwardCacheFeatureEnabled()) {
GTEST_SKIP()
<< "This test assumes that BFCache is used when back navigation";
}
// Perform a cross-site prefetch which sets cookie.
const std::string server_cookie = "server_cookie=1";
GURL initiator_url = GetUrl("a.test", "/empty.html");
GURL des_url = GetUrl("b.test", "/set-cookie?" + server_cookie);
ASSERT_TRUE(NavigateToURL(shell(), initiator_url));
test::TestPrefetchWatcher test_prefetch_watcher;
StartPrefetch(des_url);
test_prefetch_watcher.WaitUntilPrefetchResponseCompleted(
GetPrimaryMainFrameHost().GetDocumentToken(), des_url);
ASSERT_EQ(GetRequestCount(des_url), 1);
// Activate a prefetch. Cookie is successfully copied to default network
// context.
{
TestNavigationObserver nav_observer(shell()->web_contents());
nav_observer.set_wait_event(
TestNavigationObserver::WaitEvent::kLoadStopped);
std::ignore = ExecJs(shell()->web_contents()->GetPrimaryMainFrame(),
JsReplace("location = $1", des_url));
nav_observer.Wait();
EXPECT_TRUE(test_prefetch_watcher.PrefetchUsedInLastNavigation());
EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), des_url);
EXPECT_THAT(GetCanonicalCookies(
shell()->web_contents()->GetBrowserContext(), des_url),
testing::UnorderedElementsAre(
net::MatchesCookieNameValue("server_cookie", "1")));
}
// Back to the initial site. Since the document that initiated prefetch has
// been restored from BFCache, `PrefetchDocumentManager` and the prefetch
// should still be alive in this timing.
{
TestNavigationObserver observer1(shell()->web_contents());
shell()->GoBackOrForward(-1);
observer1.Wait();
ASSERT_EQ(shell()->web_contents()->GetLastCommittedURL(), initiator_url);
ExpectRestored(FROM_HERE);
}
// Activate a prefetch again. Prefetch is served since it can be used multiple
// times.
{
TestNavigationObserver nav_observer(shell()->web_contents());
nav_observer.set_wait_event(
TestNavigationObserver::WaitEvent::kLoadStopped);
std::ignore = ExecJs(shell()->web_contents()->GetPrimaryMainFrame(),
JsReplace("location = $1", des_url));
nav_observer.Wait();
EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), des_url);
EXPECT_TRUE(test_prefetch_watcher.PrefetchUsedInLastNavigation());
EXPECT_EQ(GetRequestCount(des_url), 1);
}
// Set a cookie to a prefetched site.
ASSERT_TRUE(SetCookie(shell()->web_contents()->GetBrowserContext(), des_url,
"test=1"));
// Back to the initial site and try to activate prefetch again. Prefetch is
// not served because the cookie has been changed.
{
TestNavigationObserver nav_observer(shell()->web_contents());
shell()->GoBackOrForward(-1);
nav_observer.Wait();
ASSERT_EQ(shell()->web_contents()->GetLastCommittedURL(), initiator_url);
ExpectRestored(FROM_HERE);
}
{
TestNavigationObserver nav_observer(shell()->web_contents());
nav_observer.set_wait_event(
TestNavigationObserver::WaitEvent::kLoadStopped);
std::ignore = ExecJs(shell()->web_contents()->GetPrimaryMainFrame(),
JsReplace("location = $1", des_url));
nav_observer.Wait();
EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), des_url);
EXPECT_FALSE(test_prefetch_watcher.PrefetchUsedInLastNavigation());
EXPECT_EQ(GetRequestCount(des_url), 2);
EXPECT_THAT(GetCanonicalCookies(
shell()->web_contents()->GetBrowserContext(), des_url),
testing::UnorderedElementsAre(
net::MatchesCookieNameValue("server_cookie", "1"),
net::MatchesCookieNameValue("test", "1")));
}
}
// In the tests below about auth/cert errors, we just check that the prefetches
// should fail. We expect no dialogs etc. are presented to users on such
// failures, because we don't pass URLLoaderNetworkServiceObserver for prefetch
// requests in the first place. If we should pass
// URLLoaderNetworkServiceObserver in the future (which is unlikely though), we
// would need more test coverage here (for prefetches to
// ServiceWorker-controlled URLs, check dialogs like in
// content/browser/service_worker/service_worker_auth_browsertest.cc, etc.).
// Tests that prefetch fails when auth is requested.
IN_PROC_BROWSER_TEST_F(NavPrefetchBrowserTest, AuthRequested) {
GURL initiator_url = GetUrl("a.test", "/empty.html");
GURL destination_url = GetUrl("a.test", "/auth-basic");
ASSERT_TRUE(NavigateToURL(shell(), initiator_url));
test::TestPrefetchWatcher test_prefetch_watcher;
StartPrefetch(destination_url);
test_prefetch_watcher.WaitUntilPrefetchResponseCompleted(
GetPrimaryMainFrameHost().GetDocumentToken(), destination_url);
ASSERT_EQ(GetRequestCount(destination_url), 1);
TestNavigationObserver nav_observer(shell()->web_contents());
nav_observer.set_wait_event(TestNavigationObserver::WaitEvent::kLoadStopped);
std::ignore = ExecJs(shell()->web_contents()->GetPrimaryMainFrame(),
JsReplace("location = $1", destination_url));
nav_observer.Wait();
EXPECT_FALSE(test_prefetch_watcher.PrefetchUsedInLastNavigation());
EXPECT_EQ(GetRequestCount(destination_url), 2);
ukm::SourceId ukm_source_id =
shell()->web_contents()->GetPrimaryMainFrame()->GetPageUkmSourceId();
// Navigate primary page to flush the metrics.
ASSERT_TRUE(NavigateToURL(shell(), initiator_url));
test::ExpectPreloadingAttemptUkm(
ukm_recorder(),
{attempt_entry_builder().BuildEntry(
ukm_source_id, PreloadingType::kPrefetch,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kFailure,
ToPreloadingFailureReason(PrefetchStatus::kPrefetchFailedNon2XX),
/*accurate=*/true,
/*ready_time=*/std::nullopt,
blink::mojom::SpeculationEagerness::kImmediate)});
}
// Tests that prefetch fails when client cert is requested.
IN_PROC_BROWSER_TEST_F(NavPrefetchBrowserTest, ClientCertRequested) {
GURL initiator_url = GetUrl("a.test", "/empty.html");
GURL destination_url = GetUrl("a.test", "/title2.html");
ASSERT_TRUE(NavigateToURL(shell(), initiator_url));
// Reset the server's config to cause a client cert request.
net::SSLServerConfig ssl_config;
ssl_config.client_cert_type =
net::SSLServerConfig::ClientCertType::REQUIRE_CLIENT_CERT;
ResetSSLConfig(net::test_server::EmbeddedTestServer::CERT_TEST_NAMES,
ssl_config);
test::TestPrefetchWatcher test_prefetch_watcher;
StartPrefetch(destination_url);
test_prefetch_watcher.WaitUntilPrefetchResponseCompleted(
GetPrimaryMainFrameHost().GetDocumentToken(), destination_url);
// Reset the server's config to normal.
ResetSSLConfig(net::test_server::EmbeddedTestServer::CERT_TEST_NAMES,
net::SSLServerConfig());
TestNavigationObserver nav_observer(shell()->web_contents());
nav_observer.set_wait_event(TestNavigationObserver::WaitEvent::kLoadStopped);
std::ignore = ExecJs(shell()->web_contents()->GetPrimaryMainFrame(),
JsReplace("location = $1", destination_url));
nav_observer.Wait();
EXPECT_FALSE(test_prefetch_watcher.PrefetchUsedInLastNavigation());
// Prefetch request should not be counted.
EXPECT_EQ(GetRequestCount(destination_url), 1);
ukm::SourceId ukm_source_id =
shell()->web_contents()->GetPrimaryMainFrame()->GetPageUkmSourceId();
// Navigate primary page to flush the metrics.
ASSERT_TRUE(NavigateToURL(shell(), initiator_url));
test::ExpectPreloadingAttemptUkm(
ukm_recorder(),
{attempt_entry_builder().BuildEntry(
ukm_source_id, PreloadingType::kPrefetch,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kFailure,
ToPreloadingFailureReason(PrefetchStatus::kPrefetchFailedNetError),
/*accurate=*/true,
/*ready_time=*/std::nullopt,
blink::mojom::SpeculationEagerness::kImmediate)});
}
// Tests that prefetch fails when cert is expired.
IN_PROC_BROWSER_TEST_F(NavPrefetchBrowserTest, CertExpired) {
GURL initiator_url = GetUrl("a.test", "/empty.html");
GURL destination_url = GetUrl("a.test", "/title2.html");
ASSERT_TRUE(NavigateToURL(shell(), initiator_url));
// Reset the server's config to cause a cert error.
ResetSSLConfig(net::test_server::EmbeddedTestServer::CERT_EXPIRED,
net::SSLServerConfig());
test::TestPrefetchWatcher test_prefetch_watcher;
StartPrefetch(destination_url);
test_prefetch_watcher.WaitUntilPrefetchResponseCompleted(
GetPrimaryMainFrameHost().GetDocumentToken(), destination_url);
// Reset the server's config to normal.
ResetSSLConfig(net::test_server::EmbeddedTestServer::CERT_TEST_NAMES,
net::SSLServerConfig());
TestNavigationObserver nav_observer(shell()->web_contents());
nav_observer.set_wait_event(TestNavigationObserver::WaitEvent::kLoadStopped);
std::ignore = ExecJs(shell()->web_contents()->GetPrimaryMainFrame(),
JsReplace("location = $1", destination_url));
nav_observer.Wait();
EXPECT_FALSE(test_prefetch_watcher.PrefetchUsedInLastNavigation());
// Prefetch request should not be counted.
EXPECT_EQ(GetRequestCount(destination_url), 1);
ukm::SourceId ukm_source_id =
shell()->web_contents()->GetPrimaryMainFrame()->GetPageUkmSourceId();
// Navigate primary page to flush the metrics.
ASSERT_TRUE(NavigateToURL(shell(), initiator_url));
test::ExpectPreloadingAttemptUkm(
ukm_recorder(),
{attempt_entry_builder().BuildEntry(
ukm_source_id, PreloadingType::kPrefetch,
PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
PreloadingTriggeringOutcome::kFailure,
ToPreloadingFailureReason(PrefetchStatus::kPrefetchFailedNetError),
/*accurate=*/true,
/*ready_time=*/std::nullopt,
blink::mojom::SpeculationEagerness::kImmediate)});
}
} // namespace
} // namespace content