blob: 86774bb8540e619f9471c5138b7b9b922ce6b54c [file] [log] [blame]
Avi Drissman4e1b7bc32022-09-15 14:03:501// Copyright 2021 The Chromium Authors
Kenichi Ishibashi7eb8cf62021-04-07 12:35:052// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Kenichi Ishibashi3d2627be2021-09-02 12:23:175#include "base/memory/weak_ptr.h"
Kenichi Ishibashia3138052021-04-07 12:54:596#include "base/run_loop.h"
Kenichi Ishibashi523272d2021-07-30 10:05:157#include "base/strings/strcat.h"
Kenichi Ishibashi5ca5a1772023-06-13 06:32:118#include "base/strings/string_split.h"
Lei Zhange02299a2021-04-26 23:12:249#include "base/strings/stringprintf.h"
Kenichi Ishibashi7eb8cf62021-04-07 12:35:0510#include "base/strings/utf_string_conversions.h"
Sean Mahere672a662023-01-09 21:42:2811#include "base/task/sequenced_task_runner.h"
12#include "base/task/single_thread_task_runner.h"
Kenichi Ishibashia3138052021-04-07 12:54:5913#include "base/test/bind.h"
Kenichi Ishibashi598c75572023-09-28 06:20:1614#include "base/test/metrics/histogram_tester.h"
Kenichi Ishibashi7eb8cf62021-04-07 12:35:0515#include "base/test/scoped_feature_list.h"
16#include "base/threading/thread_restrictions.h"
17#include "components/network_session_configurator/common/network_switches.h"
Jiacheng Guoaaad33d42024-03-29 01:23:2018#include "content/browser/devtools/protocol/devtools_protocol_test_support.h"
Kenichi Ishibashia3138052021-04-07 12:54:5919#include "content/browser/loader/navigation_early_hints_manager.h"
Kenichi Ishibashi7eb8cf62021-04-07 12:35:0520#include "content/browser/renderer_host/render_frame_host_impl.h"
Kenichi Ishibashif3f80522021-07-16 02:37:0721#include "content/browser/web_contents/web_contents_impl.h"
Kenichi Ishibashi7eb8cf62021-04-07 12:35:0522#include "content/public/test/browser_test.h"
23#include "content/public/test/content_browser_test.h"
24#include "content/public/test/content_browser_test_utils.h"
25#include "content/public/test/content_mock_cert_verifier.h"
Miyoung Shin164d9e22022-02-26 06:10:1326#include "content/public/test/fenced_frame_test_util.h"
27#include "content/public/test/prerender_test_util.h"
28#include "content/public/test/test_navigation_observer.h"
Kenichi Ishibashif3f80522021-07-16 02:37:0729#include "content/public/test/url_loader_interceptor.h"
Kenichi Ishibashi7eb8cf62021-04-07 12:35:0530#include "content/shell/browser/shell.h"
Kenichi Ishibashif3f80522021-07-16 02:37:0731#include "content/test/content_browser_test_utils_internal.h"
32#include "net/base/features.h"
Kenichi Ishibashi7eb8cf62021-04-07 12:35:0533#include "net/dns/mock_host_resolver.h"
34#include "net/http/http_status_code.h"
35#include "net/test/cert_test_util.h"
Kenichi Ishibashifc2dd873d2021-06-10 23:46:4236#include "net/test/embedded_test_server/embedded_test_server.h"
Kenichi Ishibashi3d2627be2021-09-02 12:23:1737#include "net/test/embedded_test_server/embedded_test_server_connection_listener.h"
Kenichi Ishibashifc2dd873d2021-06-10 23:46:4238#include "net/test/embedded_test_server/http_request.h"
39#include "net/test/embedded_test_server/http_response.h"
Kenichi Ishibashi7eb8cf62021-04-07 12:35:0540#include "net/test/quic_simple_test_server.h"
41#include "net/test/test_data_directory.h"
Adam Ricec23e7f6f2024-07-18 05:44:5042#include "net/third_party/quiche/src/quiche/common/http/http_header_block.h"
Kenichi Ishibashif3f80522021-07-16 02:37:0743#include "services/network/public/cpp/network_switches.h"
Kenichi Ishibashi523272d2021-07-30 10:05:1544#include "services/network/public/mojom/early_hints.mojom.h"
45#include "services/network/public/mojom/link_header.mojom.h"
Kenichi Ishibashi7eb8cf62021-04-07 12:35:0546
47namespace content {
48
Kenichi Ishibashia3138052021-04-07 12:54:5949using PreloadedResources = NavigationEarlyHintsManager::PreloadedResources;
50
Kenichi Ishibashi7eb8cf62021-04-07 12:35:0551namespace {
52
53struct HeaderField {
54 HeaderField(const std::string& name, const std::string& value)
55 : name(name), value(value) {}
56
57 std::string name;
58 std::string value;
59};
60
61struct ResponseEntry {
62 ResponseEntry(const std::string& path, net::HttpStatusCode status_code)
63 : path(path) {
64 headers[":path"] = path;
65 headers[":status"] = base::StringPrintf("%d", status_code);
66 }
67
Peter Kastingeb8c3ce2021-08-20 04:39:3568 void AddEarlyHints(const std::vector<HeaderField>& header_fields) {
Adam Ricec23e7f6f2024-07-18 05:44:5069 quiche::HttpHeaderBlock hints_headers;
Peter Kastingeb8c3ce2021-08-20 04:39:3570 for (const auto& header : header_fields)
Kenichi Ishibashi7eb8cf62021-04-07 12:35:0571 hints_headers.AppendValueOrAddHeader(header.name, header.value);
72 early_hints.push_back(std::move(hints_headers));
73 }
74
75 std::string path;
Adam Ricec23e7f6f2024-07-18 05:44:5076 quiche::HttpHeaderBlock headers;
Kenichi Ishibashi7eb8cf62021-04-07 12:35:0577 std::string body;
Adam Ricec23e7f6f2024-07-18 05:44:5078 std::vector<quiche::HttpHeaderBlock> early_hints;
Kenichi Ishibashi7eb8cf62021-04-07 12:35:0579};
80
81const char kPageWithHintedScriptPath[] = "/page_with_hinted_js.html";
82const char kPageWithHintedScriptBody[] = "<script src=\"/hinted.js\"></script>";
Kenichi Ishibashi9b772bc2021-06-01 06:05:0383
84const char kPageWithHintedCorsScriptPath[] = "/page_with_hinted_cors_js.html";
85const char kPageWithHintedCorsScriptBody[] =
86 "<script src=\"/hinted.js\" crossorigin></script>";
87
Kenichi Ishibashi75a97ae2021-07-12 23:07:3188const char kPageWithIframePath[] = "/page_with_iframe.html";
89const char kPageWithIframeBody[] =
90 "<iframe src=\"page_with_hinted_js.html\"></iframe>";
91
Kenichi Ishibashid3e17ca2021-05-17 04:38:5292const char kPageWithHintedModuleScriptPath[] =
93 "/page_with_hinted_module_js.html";
94const char kPageWithHintedModuleScriptBody[] =
95 "<script src=\"/hinted.js\" type=\"module\"></script>";
Kenichi Ishibashi9b772bc2021-06-01 06:05:0396
Kenichi Ishibashi7eb8cf62021-04-07 12:35:0597const char kHintedScriptPath[] = "/hinted.js";
98const char kHintedScriptBody[] = "document.title = 'Done';";
Kenichi Ishibashi9b772bc2021-06-01 06:05:0399
Kenichi Ishibashia3138052021-04-07 12:54:59100const char kHintedStylesheetPath[] = "/hinted.css";
101const char kHintedStylesheetBody[] = "/*empty*/";
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05102
Kenichi Ishibashif3f80522021-07-16 02:37:07103const char kEmptyPagePath[] = "/empty.html";
104const char kEmptyPageBody[] = "<html></html>";
105
Kenichi Ishibashiaf6ea52a2022-03-15 22:17:10106const char kRedirectedPagePath[] = "/redirected.html";
107const char kRedirectedPageBody[] = "<script src=\"/hinted.js\"></script>";
108
Kenichi Ishibashi3d2627be2021-09-02 12:23:17109// Listens to sockets on an EmbeddedTestServer for preconnect tests. Created
110// on the UI thread. EmbeddedTestServerConnectionListener methods are called
111// from a different thread than the UI thread.
112class PreconnectListener
113 : public net::test_server::EmbeddedTestServerConnectionListener {
114 public:
115 PreconnectListener()
Sean Maher5b9af51f2022-11-21 15:32:47116 : task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()),
Kenichi Ishibashi3d2627be2021-09-02 12:23:17117 weak_ptr_factory_(this) {
118 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
119 }
120 ~PreconnectListener() override = default;
121
122 // net::test_server::EmbeddedTestServerConnectionListener implementation:
123 std::unique_ptr<net::StreamSocket> AcceptedSocket(
124 std::unique_ptr<net::StreamSocket> connection) override {
125 task_runner_->PostTask(
126 FROM_HERE, base::BindOnce(&PreconnectListener::AcceptedSocketOnUIThread,
127 weak_ptr_factory_.GetWeakPtr()));
128 return connection;
129 }
130 void ReadFromSocket(const net::StreamSocket& connection, int rv) override {}
131
132 size_t num_accepted_sockets() {
133 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
134 return num_accepted_sockets_;
135 }
136
137 private:
138 void AcceptedSocketOnUIThread() {
139 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
140 ++num_accepted_sockets_;
141 }
142
143 scoped_refptr<base::SequencedTaskRunner> task_runner_;
144 size_t num_accepted_sockets_ = 0;
145
146 base::WeakPtrFactory<PreconnectListener> weak_ptr_factory_;
147};
148
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05149} // namespace
150
151// Most tests use EmbeddedTestServer but this uses QuicSimpleTestServer because
152// Early Hints are only plumbed over HTTP/2 or HTTP/3 (QUIC).
Jiacheng Guoaaad33d42024-03-29 01:23:20153class NavigationEarlyHintsTest : public DevToolsProtocolTest {
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05154 public:
Miyoung Shin164d9e22022-02-26 06:10:13155 NavigationEarlyHintsTest() {
Mohannad Farrag8bc303d62023-10-05 12:30:30156 feature_list_.InitWithFeatures(
157 std::vector<base::test::FeatureRef>{
158 net::features::kSplitCacheByNetworkIsolationKey},
159 std::vector<base::test::FeatureRef>{
160 net::features::kMigrateSessionsOnNetworkChangeV2});
Miyoung Shin164d9e22022-02-26 06:10:13161 }
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05162 ~NavigationEarlyHintsTest() override = default;
163
164 void SetUpOnMainThread() override {
Jiacheng Guoaaad33d42024-03-29 01:23:20165 DevToolsProtocolTest::SetUpOnMainThread();
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05166 ConfigureMockCertVerifier();
Kenichi Ishibashif3f80522021-07-16 02:37:07167 host_resolver()->AddRule("*", "127.0.0.1");
Kenichi Ishibashi3d2627be2021-09-02 12:23:17168
169 cross_origin_server_.RegisterRequestHandler(
170 base::BindRepeating(&NavigationEarlyHintsTest::HandleCrossOriginRequest,
171 base::Unretained(this)));
172 preconnect_listener_ = std::make_unique<PreconnectListener>();
173 cross_origin_server().SetConnectionListener(preconnect_listener_.get());
174 ASSERT_TRUE(cross_origin_server_.Start());
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05175 }
176
177 void SetUpCommandLine(base::CommandLine* command_line) override {
178 command_line->AppendSwitchASCII(switches::kOriginToForceQuicOn, "*");
179 mock_cert_verifier_.SetUpCommandLine(command_line);
Kenichi Ishibashif3f80522021-07-16 02:37:07180
Kenichi Ishibashif3f80522021-07-16 02:37:07181 ASSERT_TRUE(net::QuicSimpleTestServer::Start());
182
Jiacheng Guoaaad33d42024-03-29 01:23:20183 DevToolsProtocolTest::SetUpCommandLine(command_line);
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05184 }
185
186 void TearDown() override {
187 base::ScopedAllowBaseSyncPrimitivesForTesting allow_wait;
188 net::QuicSimpleTestServer::Shutdown();
Jiacheng Guoaaad33d42024-03-29 01:23:20189 DevToolsProtocolTest::TearDown();
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05190 }
191
Kenichi Ishibashifc2dd873d2021-06-10 23:46:42192 net::test_server::EmbeddedTestServer& cross_origin_server() {
193 return cross_origin_server_;
194 }
195
Jiacheng Guoaaad33d42024-03-29 01:23:20196 std::string WaitForHintedScriptDevtoolsRequestId() {
197 base::Value::Dict result;
198 while (true) {
199 result = WaitForNotification("Network.requestWillBeSent", true);
200 const base::Value* request_url = result.FindByDottedPath("request.url");
201 if (request_url->GetString() ==
202 net::QuicSimpleTestServer::GetFileURL(kHintedScriptPath).spec()) {
203 return *result.FindString("requestId");
204 }
205 }
206 }
207
Jiacheng Guo52965902024-04-02 09:28:09208 base::Value::Dict WaitForDevtoolsEarlyHints() {
209 base::Value::Dict result;
210 while (true) {
211 result = WaitForNotification("Network.responseReceivedEarlyHints", true);
212 return result;
213 }
214 }
215
Jiacheng Guoaaad33d42024-03-29 01:23:20216 base::Value::Dict WaitForResponseReceived(const std::string& request_id) {
217 base::Value::Dict result;
218 while (true) {
219 result = WaitForNotification("Network.responseReceived", true);
220 const std::string* received_id = result.FindString("requestId");
221 if (received_id && *received_id == request_id) {
222 return result;
223 }
224 }
225 }
226
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05227 protected:
Kenichi Ishibashi523272d2021-07-30 10:05:15228 base::test::ScopedFeatureList& feature_list() { return feature_list_; }
229
Kenichi Ishibashi3d2627be2021-09-02 12:23:17230 PreconnectListener& preconnect_listener() { return *preconnect_listener_; }
231
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05232 void SetUpInProcessBrowserTestFixture() override {
233 mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
234 }
235
236 void TearDownInProcessBrowserTestFixture() override {
237 mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
238 }
239
240 void ConfigureMockCertVerifier() {
241 auto test_cert =
242 net::ImportCertFromFile(net::GetTestCertsDirectory(), "quic-chain.pem");
243 net::CertVerifyResult verify_result;
244 verify_result.verified_cert = test_cert;
245 mock_cert_verifier_.mock_cert_verifier()->AddResultForCert(
246 test_cert, verify_result, net::OK);
247 mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
248 }
249
Kenichi Ishibashia3138052021-04-07 12:54:59250 HeaderField CreatePreloadLinkForScript() {
251 return HeaderField(
252 "link",
253 base::StringPrintf("<%s>; rel=preload; as=script", kHintedScriptPath));
254 }
255
Kenichi Ishibashi9b772bc2021-06-01 06:05:03256 HeaderField CreatePreloadLinkForCorsScript() {
257 return HeaderField(
258 "link", base::StringPrintf("<%s>; rel=preload; as=script; crossorigin",
259 kHintedScriptPath));
260 }
261
Kenichi Ishibashid3e17ca2021-05-17 04:38:52262 HeaderField CreateModulePreloadLink() {
263 return HeaderField("link", base::StringPrintf("<%s>; rel=modulepreload",
264 kHintedScriptPath));
265 }
266
Kenichi Ishibashia3138052021-04-07 12:54:59267 HeaderField CreatePreloadLinkForStylesheet() {
Kenichi Ishibashi3c7d4192021-08-16 04:55:26268 return HeaderField("link", base::StringPrintf("<%s>; rel=preload; as=style",
269 kHintedStylesheetPath));
Kenichi Ishibashia3138052021-04-07 12:54:59270 }
271
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05272 void RegisterResponse(const ResponseEntry& entry) {
273 net::QuicSimpleTestServer::AddResponseWithEarlyHints(
274 entry.path, entry.headers, entry.body, entry.early_hints);
275 }
276
Kenichi Ishibashia3138052021-04-07 12:54:59277 void RegisterHintedScriptResource() {
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05278 ResponseEntry hinted_script_entry(kHintedScriptPath, net::HTTP_OK);
279 hinted_script_entry.headers["content-type"] = "application/javascript";
Kenichi Ishibashif3f80522021-07-16 02:37:07280 hinted_script_entry.headers["cache-control"] = "max-age=3600";
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05281 hinted_script_entry.body = kHintedScriptBody;
282 RegisterResponse(hinted_script_entry);
Kenichi Ishibashia3138052021-04-07 12:54:59283 }
284
285 void RegisterHintedStylesheetResource() {
286 ResponseEntry hinted_script_entry(kHintedStylesheetPath, net::HTTP_OK);
287 hinted_script_entry.headers["content-type"] = "text/css";
288 hinted_script_entry.body = kHintedStylesheetBody;
289 RegisterResponse(hinted_script_entry);
290 }
291
Kenichi Ishibashiaf6ea52a2022-03-15 22:17:10292 void RegisterRedirectedPage() {
293 ResponseEntry entry(kRedirectedPagePath, net::HTTP_OK);
294 entry.body = kRedirectedPageBody;
295 RegisterResponse(entry);
296 }
297
Kenichi Ishibashia3138052021-04-07 12:54:59298 ResponseEntry CreatePageEntryWithHintedScript(
299 net::HttpStatusCode status_code) {
300 RegisterHintedScriptResource();
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05301
302 ResponseEntry entry(kPageWithHintedScriptPath, status_code);
303 entry.body = kPageWithHintedScriptBody;
Kenichi Ishibashia3138052021-04-07 12:54:59304 HeaderField link_header = CreatePreloadLinkForScript();
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05305 entry.AddEarlyHints({std::move(link_header)});
306
307 return entry;
308 }
309
Kenichi Ishibashif3f80522021-07-16 02:37:07310 ResponseEntry CreateEmptyPageEntryWithHintedScript() {
311 RegisterHintedScriptResource();
312
313 ResponseEntry entry(kEmptyPagePath, net::HTTP_OK);
314 entry.body = kEmptyPageBody;
315 HeaderField link_header = CreatePreloadLinkForScript();
316 entry.AddEarlyHints({std::move(link_header)});
317
318 return entry;
319 }
320
Kenichi Ishibashi9b772bc2021-06-01 06:05:03321 ResponseEntry CreatePageEntryWithHintedCorsScript(
322 net::HttpStatusCode status_code) {
323 RegisterHintedScriptResource();
324
325 ResponseEntry entry(kPageWithHintedCorsScriptPath, status_code);
326 entry.body = kPageWithHintedCorsScriptBody;
327 HeaderField link_header = CreatePreloadLinkForCorsScript();
328 entry.AddEarlyHints({std::move(link_header)});
329
330 return entry;
331 }
332
Kenichi Ishibashid3e17ca2021-05-17 04:38:52333 ResponseEntry CreatePageEntryWithHintedModuleScript(
334 net::HttpStatusCode status_code) {
335 RegisterHintedScriptResource();
336
337 ResponseEntry entry(kPageWithHintedModuleScriptPath, status_code);
338 entry.body = kPageWithHintedModuleScriptBody;
339 HeaderField link_header = CreateModulePreloadLink();
340 entry.AddEarlyHints({std::move(link_header)});
341
342 return entry;
343 }
344
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05345 bool NavigateToURLAndWaitTitle(const GURL& url, const std::string& title) {
Kenichi Ishibashiaf6ea52a2022-03-15 22:17:10346 return NavigateToURLAndWaitTitleWithCommitURL(url, url, title);
347 }
348
349 bool NavigateToURLAndWaitTitleWithCommitURL(const GURL& url,
350 const GURL& expected_commit_url,
351 const std::string& title) {
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05352 std::u16string title16 = base::ASCIIToUTF16(title);
353 TitleWatcher title_watcher(shell()->web_contents(), title16);
Solomon Kinardab293bae2024-09-19 17:13:51354 if (!NavigateToURL(shell(), url, expected_commit_url)) {
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05355 return false;
Solomon Kinardab293bae2024-09-19 17:13:51356 }
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05357 return title16 == title_watcher.WaitAndGetTitle();
358 }
359
Miyoung Shin164d9e22022-02-26 06:10:13360 NavigationEarlyHintsManager* GetEarlyHintsManager(RenderFrameHostImpl* rfh) {
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05361 return rfh->early_hints_manager();
362 }
363
Kenichi Ishibashia3138052021-04-07 12:54:59364 PreloadedResources WaitForPreloadedResources() {
Miyoung Shin164d9e22022-02-26 06:10:13365 return WaitForPreloadedResources(static_cast<RenderFrameHostImpl*>(
Dave Tapuska327c06c92022-06-13 20:31:51366 shell()->web_contents()->GetPrimaryMainFrame()));
Miyoung Shin164d9e22022-02-26 06:10:13367 }
368
369 PreloadedResources WaitForPreloadedResources(RenderFrameHostImpl* rfh) {
Kenichi Ishibashia3138052021-04-07 12:54:59370 base::RunLoop loop;
371 PreloadedResources result;
Solomon Kinardab293bae2024-09-19 17:13:51372 if (!GetEarlyHintsManager(rfh)) {
Kenichi Ishibashia3138052021-04-07 12:54:59373 return result;
Solomon Kinardab293bae2024-09-19 17:13:51374 }
Kenichi Ishibashia3138052021-04-07 12:54:59375
Miyoung Shin164d9e22022-02-26 06:10:13376 GetEarlyHintsManager(rfh)->WaitForPreloadsFinishedForTesting(
Kenichi Ishibashia3138052021-04-07 12:54:59377 base::BindLambdaForTesting([&](PreloadedResources preloaded_resources) {
378 result = preloaded_resources;
379 loop.Quit();
380 }));
381 loop.Run();
382 return result;
383 }
384
Kenichi Ishibashifc2dd873d2021-06-10 23:46:42385 enum class FetchResult {
386 kFetched,
387 kBlocked,
388 };
Kenichi Ishibashif3f80522021-07-16 02:37:07389 FetchResult FetchScriptOnDocument(ToRenderFrameHost target, GURL src) {
390 EvalJsResult result = EvalJs(target, JsReplace(R"(
Kenichi Ishibashifc2dd873d2021-06-10 23:46:42391 new Promise(resolve => {
392 const script = document.createElement("script");
393 script.src = $1;
394 script.onerror = () => resolve("blocked");
395 script.onload = () => resolve("fetched");
396 document.body.appendChild(script);
397 });
398 )",
Kenichi Ishibashif3f80522021-07-16 02:37:07399 src));
Kenichi Ishibashifc2dd873d2021-06-10 23:46:42400 return result.ExtractString() == "fetched" ? FetchResult::kFetched
401 : FetchResult::kBlocked;
402 }
403
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05404 private:
Kenichi Ishibashifc2dd873d2021-06-10 23:46:42405 std::unique_ptr<net::test_server::HttpResponse> HandleCrossOriginRequest(
406 const net::test_server::HttpRequest& request) {
407 GURL relative_url = request.base_url.Resolve(request.relative_url);
Kenichi Ishibashif3f80522021-07-16 02:37:07408
Kenichi Ishibashiaf6ea52a2022-03-15 22:17:10409 if (relative_url.path() == kEmptyPagePath) {
Kenichi Ishibashif3f80522021-07-16 02:37:07410 auto response = std::make_unique<net::test_server::BasicHttpResponse>();
411 response->set_code(net::HTTP_OK);
412 response->set_content_type("text/html");
413 response->set_content("");
414 return std::move(response);
415 }
416
Kenichi Ishibashiaf6ea52a2022-03-15 22:17:10417 if (relative_url.path() == kRedirectedPagePath) {
418 auto response = std::make_unique<net::test_server::BasicHttpResponse>();
419 response->set_code(net::HTTP_OK);
420 response->set_content_type("text/html");
421 response->set_content(kRedirectedPageBody);
422 return std::move(response);
423 }
424
Solomon Kinardab293bae2024-09-19 17:13:51425 if (relative_url.path() != kHintedScriptPath) {
Kenichi Ishibashifc2dd873d2021-06-10 23:46:42426 return nullptr;
Solomon Kinardab293bae2024-09-19 17:13:51427 }
Kenichi Ishibashifc2dd873d2021-06-10 23:46:42428
429 auto response = std::make_unique<net::test_server::BasicHttpResponse>();
430 response->set_code(net::HTTP_OK);
431 response->set_content_type("application/javascript");
Kenichi Ishibashiaf6ea52a2022-03-15 22:17:10432 response->set_content(kHintedScriptBody);
Kenichi Ishibashifc2dd873d2021-06-10 23:46:42433
434 std::string query = relative_url.query();
435 if (query == "corp-cross-origin") {
436 response->AddCustomHeader("Cross-Origin-Resource-Policy", "cross-origin");
437 } else if (query == "corp-same-origin") {
438 response->AddCustomHeader("Cross-Origin-Resource-Policy", "same-origin");
439 }
440
441 return std::move(response);
442 }
443
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05444 base::test::ScopedFeatureList feature_list_;
445
446 ContentMockCertVerifier mock_cert_verifier_;
Kenichi Ishibashifc2dd873d2021-06-10 23:46:42447
Kenichi Ishibashif3f80522021-07-16 02:37:07448 // For tests that fetch resources from a cross origin server.
Kenichi Ishibashifc2dd873d2021-06-10 23:46:42449 net::EmbeddedTestServer cross_origin_server_;
Kenichi Ishibashi3d2627be2021-09-02 12:23:17450 std::unique_ptr<PreconnectListener> preconnect_listener_;
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05451};
452
453IN_PROC_BROWSER_TEST_F(NavigationEarlyHintsTest, Basic) {
Kenichi Ishibashi598c75572023-09-28 06:20:16454 base::HistogramTester histograms;
455
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05456 ResponseEntry entry = CreatePageEntryWithHintedScript(net::HTTP_OK);
457 RegisterResponse(entry);
458
459 EXPECT_TRUE(NavigateToURLAndWaitTitle(
460 net::QuicSimpleTestServer::GetFileURL(kPageWithHintedScriptPath),
461 "Done"));
Kenichi Ishibashia3138052021-04-07 12:54:59462 PreloadedResources preloads = WaitForPreloadedResources();
463 EXPECT_EQ(preloads.size(), 1UL);
464
465 GURL preloaded_url = net::QuicSimpleTestServer::GetFileURL(kHintedScriptPath);
466 auto it = preloads.find(preloaded_url);
467 ASSERT_TRUE(it != preloads.end());
Kenichi Ishibashid379dea2021-04-08 11:21:27468 ASSERT_FALSE(it->second.was_canceled);
469 ASSERT_TRUE(it->second.error_code.has_value());
470 EXPECT_EQ(it->second.error_code.value(), net::OK);
Kenichi Ishibashi598c75572023-09-28 06:20:16471
472 histograms.ExpectTotalCount(
473 "Navigation.EarlyHints.WillStartRequestToEarlyHintsTime", 1);
474 histograms.ExpectTotalCount(
475 "Navigation.EarlyHints.EarlyHintsToResponseStartTime", 1);
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05476}
477
Kenichi Ishibashi9b772bc2021-06-01 06:05:03478IN_PROC_BROWSER_TEST_F(NavigationEarlyHintsTest, CorsAttribute) {
479 ResponseEntry entry = CreatePageEntryWithHintedCorsScript(net::HTTP_OK);
480 RegisterResponse(entry);
481
482 EXPECT_TRUE(NavigateToURLAndWaitTitle(
483 net::QuicSimpleTestServer::GetFileURL(kPageWithHintedCorsScriptPath),
484 "Done"));
485 PreloadedResources preloads = WaitForPreloadedResources();
486 EXPECT_EQ(preloads.size(), 1UL);
487
488 GURL preloaded_url = net::QuicSimpleTestServer::GetFileURL(kHintedScriptPath);
489 auto it = preloads.find(preloaded_url);
490 ASSERT_TRUE(it != preloads.end());
491 ASSERT_FALSE(it->second.was_canceled);
492 ASSERT_TRUE(it->second.error_code.has_value());
493 EXPECT_EQ(it->second.error_code.value(), net::OK);
494}
495
Kenichi Ishibashid3e17ca2021-05-17 04:38:52496IN_PROC_BROWSER_TEST_F(NavigationEarlyHintsTest, ModulePreload) {
497 ResponseEntry entry = CreatePageEntryWithHintedModuleScript(net::HTTP_OK);
498 RegisterResponse(entry);
499
500 EXPECT_TRUE(NavigateToURLAndWaitTitle(
501 net::QuicSimpleTestServer::GetFileURL(kPageWithHintedModuleScriptPath),
502 "Done"));
503 PreloadedResources preloads = WaitForPreloadedResources();
504 EXPECT_EQ(preloads.size(), 1UL);
505
506 GURL preloaded_url = net::QuicSimpleTestServer::GetFileURL(kHintedScriptPath);
507 auto it = preloads.find(preloaded_url);
508 ASSERT_TRUE(it != preloads.end());
509 ASSERT_FALSE(it->second.was_canceled);
510 ASSERT_TRUE(it->second.error_code.has_value());
511 EXPECT_EQ(it->second.error_code.value(), net::OK);
512}
513
Kenichi Ishibashi75a97ae2021-07-12 23:07:31514IN_PROC_BROWSER_TEST_F(NavigationEarlyHintsTest, DisallowPreloadFromIframe) {
515 ResponseEntry page_entry(kPageWithIframePath, net::HTTP_OK);
516 page_entry.body = kPageWithIframeBody;
517 RegisterResponse(page_entry);
518
519 ResponseEntry iframe_entry = CreatePageEntryWithHintedScript(net::HTTP_OK);
520 RegisterResponse(iframe_entry);
521
522 EXPECT_TRUE(NavigateToURL(
523 shell(), net::QuicSimpleTestServer::GetFileURL(kPageWithIframePath)));
524
525 // Find RenderFrameHost for the iframe.
526 std::vector<RenderFrameHost*> all_frames =
527 CollectAllRenderFrameHosts(shell()->web_contents());
528 ASSERT_EQ(all_frames.size(), 2UL);
Dave Tapuska800bc542021-09-14 13:42:50529 ASSERT_EQ(all_frames[0], all_frames[1]->GetParent());
Kenichi Ishibashi75a97ae2021-07-12 23:07:31530 RenderFrameHostImpl* iframe_host =
531 static_cast<RenderFrameHostImpl*>(all_frames[1]);
532
533 EXPECT_TRUE(WaitForLoadStop(WebContents::FromRenderFrameHost(iframe_host)));
534 ASSERT_EQ(iframe_host->GetLastCommittedURL(),
535 net::QuicSimpleTestServer::GetFileURL(kPageWithHintedScriptPath));
536
537 // NavigationEarlyHintsManager should not be created for subframes. If it were
538 // created it should have been created before navigation commit.
539 EXPECT_EQ(iframe_host->early_hints_manager(), nullptr);
540}
541
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05542IN_PROC_BROWSER_TEST_F(NavigationEarlyHintsTest, NavigationServerError) {
543 ResponseEntry entry =
544 CreatePageEntryWithHintedScript(net::HTTP_INTERNAL_SERVER_ERROR);
545 entry.body = "Internal Server Error";
546 RegisterResponse(entry);
547
548 EXPECT_TRUE(NavigateToURL(shell(), net::QuicSimpleTestServer::GetFileURL(
549 kPageWithHintedScriptPath)));
Kenichi Ishibashia3138052021-04-07 12:54:59550 PreloadedResources preloads = WaitForPreloadedResources();
Kenichi Ishibashi0b8c8652022-03-25 02:46:02551 EXPECT_EQ(preloads.size(), 1UL);
552
553 GURL preloaded_url = net::QuicSimpleTestServer::GetFileURL(kHintedScriptPath);
554 auto it = preloads.find(preloaded_url);
555 ASSERT_NE(it, preloads.end());
556 ASSERT_FALSE(it->second.was_canceled);
557 ASSERT_TRUE(it->second.error_code.has_value());
558 EXPECT_EQ(it->second.error_code.value(), net::OK);
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05559}
560
Kenichi Ishibashiaf6ea52a2022-03-15 22:17:10561IN_PROC_BROWSER_TEST_F(NavigationEarlyHintsTest, RedirectSameOrigin) {
562 RegisterRedirectedPage();
563
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05564 ResponseEntry entry = CreatePageEntryWithHintedScript(net::HTTP_FOUND);
Kenichi Ishibashiaf6ea52a2022-03-15 22:17:10565 entry.headers["location"] = kRedirectedPagePath;
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05566 entry.body = "";
567 RegisterResponse(entry);
568
Kenichi Ishibashiaf6ea52a2022-03-15 22:17:10569 EXPECT_TRUE(NavigateToURLAndWaitTitleWithCommitURL(
570 net::QuicSimpleTestServer::GetFileURL(kPageWithHintedScriptPath),
571 net::QuicSimpleTestServer::GetFileURL(kRedirectedPagePath), "Done"));
572
573 PreloadedResources preloads = WaitForPreloadedResources();
574 EXPECT_EQ(preloads.size(), 1UL);
575
576 GURL preloaded_url = net::QuicSimpleTestServer::GetFileURL(kHintedScriptPath);
577 auto it = preloads.find(preloaded_url);
578 ASSERT_TRUE(it != preloads.end());
579 ASSERT_FALSE(it->second.was_canceled);
580 ASSERT_TRUE(it->second.error_code.has_value());
581 EXPECT_EQ(it->second.error_code.value(), net::OK);
582}
583
584IN_PROC_BROWSER_TEST_F(NavigationEarlyHintsTest, RedirectCrossOrigin) {
585 const GURL kRedirectedUrl = cross_origin_server().GetURL(kRedirectedPagePath);
586
587 ResponseEntry entry = CreatePageEntryWithHintedScript(net::HTTP_FOUND);
588 entry.headers["location"] = kRedirectedUrl.spec();
589 entry.body = "";
590 RegisterResponse(entry);
591
592 EXPECT_TRUE(NavigateToURLAndWaitTitleWithCommitURL(
593 net::QuicSimpleTestServer::GetFileURL(kPageWithHintedScriptPath),
594 kRedirectedUrl, "Done"));
595
Kenichi Ishibashia3138052021-04-07 12:54:59596 PreloadedResources preloads = WaitForPreloadedResources();
597 EXPECT_TRUE(preloads.empty());
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05598}
599
Kenichi Ishibashia3138052021-04-07 12:54:59600IN_PROC_BROWSER_TEST_F(NavigationEarlyHintsTest, InvalidPreloadLink) {
601 const std::string kPath = "/hinted.html";
602
603 RegisterHintedScriptResource();
604
605 ResponseEntry entry(kPath, net::HTTP_OK);
606 entry.body = "body";
607 entry.AddEarlyHints(
608 {HeaderField("link", base::StringPrintf("<%s>; rel=preload; as=invalid",
609 kHintedScriptPath))});
610 RegisterResponse(entry);
611
612 EXPECT_TRUE(
613 NavigateToURL(shell(), net::QuicSimpleTestServer::GetFileURL(kPath)));
614 PreloadedResources preloads = WaitForPreloadedResources();
615 EXPECT_TRUE(preloads.empty());
616}
617
Kenichi Ishibashi50840fb92022-03-21 13:50:16618IN_PROC_BROWSER_TEST_F(NavigationEarlyHintsTest, MultipleEarlyHints) {
Kenichi Ishibashia3138052021-04-07 12:54:59619 RegisterHintedScriptResource();
620 RegisterHintedStylesheetResource();
621
622 ResponseEntry entry(kPageWithHintedScriptPath, net::HTTP_OK);
623 entry.body = kPageWithHintedScriptBody;
624
625 // Set two Early Hints responses which contain duplicate preload link headers.
Kenichi Ishibashi50840fb92022-03-21 13:50:16626 // The second response should be ignored.
Kenichi Ishibashia3138052021-04-07 12:54:59627 HeaderField script_link_header = CreatePreloadLinkForScript();
628 HeaderField stylesheet_link_header = CreatePreloadLinkForStylesheet();
629 entry.AddEarlyHints({script_link_header});
630 entry.AddEarlyHints({script_link_header, stylesheet_link_header});
631 RegisterResponse(entry);
632
633 EXPECT_TRUE(NavigateToURLAndWaitTitle(
634 net::QuicSimpleTestServer::GetFileURL(kPageWithHintedScriptPath),
635 "Done"));
636 PreloadedResources preloads = WaitForPreloadedResources();
Kenichi Ishibashi50840fb92022-03-21 13:50:16637 EXPECT_EQ(preloads.size(), 1UL);
Kenichi Ishibashia3138052021-04-07 12:54:59638
639 GURL script_url = net::QuicSimpleTestServer::GetFileURL(kHintedScriptPath);
640 GURL stylesheet_url =
641 net::QuicSimpleTestServer::GetFileURL(kHintedStylesheetPath);
642 EXPECT_TRUE(preloads.contains(script_url));
Kenichi Ishibashi50840fb92022-03-21 13:50:16643 EXPECT_FALSE(preloads.contains(stylesheet_url));
Kenichi Ishibashia3138052021-04-07 12:54:59644}
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05645
Kenichi Ishibashifc2dd873d2021-06-10 23:46:42646const char kPageWithCrossOriginScriptPage[] =
647 "/page_with_cross_origin_script.html";
648
649IN_PROC_BROWSER_TEST_F(NavigationEarlyHintsTest, CORP_Pass) {
650 // The server response's is a script with
651 // `Cross-Origin-Resource-Policy: cross-origin`.
652 const GURL kCrossOriginScriptUrl =
653 cross_origin_server().GetURL("/hinted.js?corp-cross-origin");
654
655 ResponseEntry page_entry(kPageWithCrossOriginScriptPage, net::HTTP_OK);
656 HeaderField link_header = HeaderField(
657 "link", base::StringPrintf("<%s>; rel=preload; as=script",
658 kCrossOriginScriptUrl.spec().c_str()));
659 page_entry.AddEarlyHints({std::move(link_header)});
660 RegisterResponse(page_entry);
661
662 EXPECT_TRUE(NavigateToURL(shell(), net::QuicSimpleTestServer::GetFileURL(
663 kPageWithCrossOriginScriptPage)));
664 EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
665
Kenichi Ishibashif3f80522021-07-16 02:37:07666 EXPECT_EQ(FetchScriptOnDocument(shell(), kCrossOriginScriptUrl),
Kenichi Ishibashifc2dd873d2021-06-10 23:46:42667 FetchResult::kFetched);
668
669 PreloadedResources preloads = WaitForPreloadedResources();
670 EXPECT_EQ(preloads.size(), 1UL);
671
672 auto it = preloads.find(kCrossOriginScriptUrl);
673 ASSERT_TRUE(it != preloads.end());
674 ASSERT_FALSE(it->second.was_canceled);
675 ASSERT_TRUE(it->second.error_code.has_value());
676 EXPECT_EQ(it->second.error_code.value(), net::OK);
677}
678
679IN_PROC_BROWSER_TEST_F(NavigationEarlyHintsTest, CORP_Blocked) {
680 // The server response's is a script with
681 // `Cross-Origin-Resource-Policy: same-origin`.
682 const GURL kCrossOriginScriptUrl =
683 cross_origin_server().GetURL("/hinted.js?corp-same-origin");
684
685 ResponseEntry page_entry(kPageWithCrossOriginScriptPage, net::HTTP_OK);
686 HeaderField link_header = HeaderField(
687 "link", base::StringPrintf("<%s>; rel=preload; as=script",
688 kCrossOriginScriptUrl.spec().c_str()));
689 page_entry.AddEarlyHints({std::move(link_header)});
690 RegisterResponse(page_entry);
691
692 EXPECT_TRUE(NavigateToURL(shell(), net::QuicSimpleTestServer::GetFileURL(
693 kPageWithCrossOriginScriptPage)));
694 EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
695
696 // The script fetch should be blocked.
Kenichi Ishibashif3f80522021-07-16 02:37:07697 EXPECT_EQ(FetchScriptOnDocument(shell(), kCrossOriginScriptUrl),
Kenichi Ishibashifc2dd873d2021-06-10 23:46:42698 FetchResult::kBlocked);
699}
700
Kenichi Ishibashif3f80522021-07-16 02:37:07701IN_PROC_BROWSER_TEST_F(NavigationEarlyHintsTest, COEP_Pass) {
702 // The server sends `Cross-Origin-Resource-Policy: cross-origin`.
703 const GURL kCrossOriginScriptUrl =
704 cross_origin_server().GetURL("/hinted.js?corp-cross-origin");
705 ResponseEntry page_entry(kPageWithCrossOriginScriptPage, net::HTTP_OK);
706 page_entry.headers["cross-origin-embedder-policy"] = "require-corp";
707 HeaderField link_header = HeaderField(
708 "link", base::StringPrintf("<%s>; rel=preload; as=script",
709 kCrossOriginScriptUrl.spec().c_str()));
710 HeaderField coep =
711 HeaderField("cross-origin-embedder-policy", "require-corp");
712 page_entry.AddEarlyHints({std::move(link_header), std::move(coep)});
713 RegisterResponse(page_entry);
714
715 EXPECT_TRUE(NavigateToURL(shell(), net::QuicSimpleTestServer::GetFileURL(
716 kPageWithCrossOriginScriptPage)));
717 EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
718
719 EXPECT_EQ(FetchScriptOnDocument(shell(), kCrossOriginScriptUrl),
720 FetchResult::kFetched);
721}
722
723IN_PROC_BROWSER_TEST_F(NavigationEarlyHintsTest, COEP_Block) {
724 // The server does not send `Cross-Origin-Resource-Policy` header.
725 const GURL kCrossOriginScriptUrl = cross_origin_server().GetURL("/hinted.js");
726 ResponseEntry page_entry(kPageWithCrossOriginScriptPage, net::HTTP_OK);
727 page_entry.headers["cross-origin-embedder-policy"] = "require-corp";
728 HeaderField link_header = HeaderField(
729 "link", base::StringPrintf("<%s>; rel=preload; as=script",
730 kCrossOriginScriptUrl.spec().c_str()));
731 HeaderField coep =
732 HeaderField("cross-origin-embedder-policy", "require-corp");
733 page_entry.AddEarlyHints({std::move(link_header), std::move(coep)});
734 RegisterResponse(page_entry);
735
736 EXPECT_TRUE(NavigateToURL(shell(), net::QuicSimpleTestServer::GetFileURL(
737 kPageWithCrossOriginScriptPage)));
738 EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
739
740 EXPECT_EQ(FetchScriptOnDocument(shell(), kCrossOriginScriptUrl),
741 FetchResult::kBlocked);
742}
743
744// Test that network isolation key is set correctly for Early Hints preload.
Brianna Goldsteind22b0642022-10-11 16:30:50745IN_PROC_BROWSER_TEST_F(NavigationEarlyHintsTest, NetworkAnonymizationKey) {
Kenichi Ishibashif3f80522021-07-16 02:37:07746 const GURL kHintedScriptUrl =
747 net::QuicSimpleTestServer::GetFileURL(kHintedScriptPath);
748
749 ResponseEntry entry = CreateEmptyPageEntryWithHintedScript();
750 RegisterResponse(entry);
751
Arthur Sonzognic686e8f2024-01-11 08:36:37752 std::optional<bool> is_cached;
Kenichi Ishibashif3f80522021-07-16 02:37:07753 URLLoaderInterceptor interceptor(
754 base::BindLambdaForTesting(
755 [&](URLLoaderInterceptor::RequestParams* params) { return false; }),
756 base::BindLambdaForTesting(
757 [&](const GURL& request_url,
758 const network::URLLoaderCompletionStatus& status) {
Solomon Kinardab293bae2024-09-19 17:13:51759 if (request_url != kHintedScriptUrl) {
Kenichi Ishibashif3f80522021-07-16 02:37:07760 return;
Solomon Kinardab293bae2024-09-19 17:13:51761 }
Kenichi Ishibashif3f80522021-07-16 02:37:07762 is_cached = status.exists_in_cache;
763 }),
764 base::NullCallback());
765
766 ASSERT_TRUE(NavigateToURL(
767 shell(), net::QuicSimpleTestServer::GetFileURL(kEmptyPagePath)));
768
769 // Make sure the hinted resource is preloaded.
770 PreloadedResources preloads = WaitForPreloadedResources();
771 auto it = preloads.find(kHintedScriptUrl);
772 ASSERT_TRUE(it != preloads.end());
773 ASSERT_FALSE(it->second.was_canceled);
774 ASSERT_EQ(it->second.error_code.value(), net::OK);
775
776 ASSERT_FALSE(is_cached.value());
Arthur Sonzognic686e8f2024-01-11 08:36:37777 is_cached = std::nullopt;
Kenichi Ishibashif3f80522021-07-16 02:37:07778
779 // Fetch the hinted resource from the main frame. It should come from the
780 // cache.
781 FetchScriptOnDocument(shell(), kHintedScriptUrl);
782 ASSERT_TRUE(is_cached.value());
783
784 // Reset `is_cached` to make sure it is set true or false.
Arthur Sonzognic686e8f2024-01-11 08:36:37785 is_cached = std::nullopt;
Kenichi Ishibashif3f80522021-07-16 02:37:07786
787 // Create an iframe with a different origin and fetch the hinted resource from
788 // the iframe. It should not come from the cache.
789 auto* web_contents = static_cast<WebContentsImpl*>(shell()->web_contents());
790 RenderFrameHost* iframe =
791 CreateSubframe(web_contents, /*frame_id=*/"",
792 cross_origin_server().GetURL("/empty.html"),
793 /*wait_for_navigation=*/true);
794 FetchScriptOnDocument(iframe, kHintedScriptUrl);
795 ASSERT_FALSE(is_cached.value());
796}
797
Kenichi Ishibashi57abd54c2023-01-05 23:39:06798IN_PROC_BROWSER_TEST_F(NavigationEarlyHintsTest, SimplePreconnect) {
Kenichi Ishibashi3d2627be2021-09-02 12:23:17799 const char kPageWithPreconnect[] = "/page_with_preconnect.html";
800 const GURL kPreconnectUrl = cross_origin_server().GetURL("/");
801 ResponseEntry page_entry(kPageWithPreconnect, net::HTTP_OK);
802 HeaderField link_header =
803 HeaderField("link", base::StringPrintf("<%s>; rel=preconnect",
804 kPreconnectUrl.spec().c_str()));
805 page_entry.AddEarlyHints({std::move(link_header)});
806 RegisterResponse(page_entry);
807
808 ASSERT_TRUE(NavigateToURL(
809 shell(), net::QuicSimpleTestServer::GetFileURL(kPageWithPreconnect)));
810 ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
811
Kenichi Ishibashi57abd54c2023-01-05 23:39:06812 EXPECT_EQ(preconnect_listener().num_accepted_sockets(), 1UL);
Dave Tapuska327c06c92022-06-13 20:31:51813 EXPECT_TRUE(
814 GetEarlyHintsManager(static_cast<RenderFrameHostImpl*>(
815 shell()->web_contents()->GetPrimaryMainFrame()))
816 ->WasResourceHintsReceived());
Kenichi Ishibashi3d2627be2021-09-02 12:23:17817}
818
Kenichi Ishibashi1e123462022-03-21 13:30:46819IN_PROC_BROWSER_TEST_F(NavigationEarlyHintsTest, InvalidHeader_NewLine) {
820 const std::string kPath = "/header-contains-newline.html";
821 ResponseEntry entry(kPath, net::HTTP_OK);
822 entry.AddEarlyHints({HeaderField("invalid-header", "foo\r\nbar")});
823 RegisterResponse(entry);
824 EXPECT_FALSE(
825 NavigateToURL(shell(), net::QuicSimpleTestServer::GetFileURL(kPath)));
826}
827
Jiacheng Guoaaad33d42024-03-29 01:23:20828IN_PROC_BROWSER_TEST_F(NavigationEarlyHintsTest, DevtoolsEventsForEarlyHint) {
829 ResponseEntry entry = CreatePageEntryWithHintedScript(net::HTTP_OK);
830 RegisterResponse(entry);
831 shell()->LoadURL(GURL("about:blank"));
832 EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
833
834 Attach();
Rakina Zata Amni8590ad572024-08-05 23:30:02835 // Send this synchronously, otherwise it might not be sent yet when the
836 // navigation start and the message sending gets suspended.
837 SendCommandSync("Network.enable");
Jiacheng Guoaaad33d42024-03-29 01:23:20838 GURL target_url =
839 net::QuicSimpleTestServer::GetFileURL(kPageWithHintedScriptPath);
840 EXPECT_TRUE(NavigateToURL(shell(), target_url, target_url));
841
842 std::string hinted_id = WaitForHintedScriptDevtoolsRequestId();
Jiacheng Guo52965902024-04-02 09:28:09843
844 {
845 base::Value::Dict early_hints_event = WaitForDevtoolsEarlyHints();
846 base::Value::Dict* early_hints_headers =
847 early_hints_event.FindDict("headers");
848 ASSERT_TRUE(early_hints_headers);
849 HeaderField link_header = CreatePreloadLinkForScript();
850 EXPECT_EQ(*early_hints_headers->FindString(link_header.name),
851 link_header.value);
852 }
853
854 {
855 base::Value::Dict result = WaitForResponseReceived(hinted_id);
856 base::Value* from_early_hints_value =
857 result.FindByDottedPath("response.fromEarlyHints");
858 ASSERT_TRUE(from_early_hints_value);
859 EXPECT_TRUE(from_early_hints_value->is_bool());
860 EXPECT_TRUE(from_early_hints_value->GetBool());
861 }
Jiacheng Guoaaad33d42024-03-29 01:23:20862}
863
Kenichi Ishibashif3f80522021-07-16 02:37:07864class NavigationEarlyHintsAddressSpaceTest : public NavigationEarlyHintsTest {
865 public:
866 NavigationEarlyHintsAddressSpaceTest() = default;
867 ~NavigationEarlyHintsAddressSpaceTest() override = default;
868
869 void SetUpCommandLine(base::CommandLine* command_line) override {
870 NavigationEarlyHintsTest::SetUpCommandLine(command_line);
871
872 private_server_.AddDefaultHandlers(GetTestDataFilePath());
873 ASSERT_TRUE(private_server_.Start());
874
875 // Treat the main test server as public for IPAddressSpace tests.
876 command_line->AppendSwitchASCII(
877 network::switches::kIpAddressSpaceOverrides,
878 base::StringPrintf("127.0.0.1:%d=public",
879 net::QuicSimpleTestServer::GetPort()));
880 }
881
882 net::test_server::EmbeddedTestServer& private_server() {
883 return private_server_;
884 }
885
886 private:
887 // For tests that trigger private network requests.
888 net::EmbeddedTestServer private_server_;
889};
890
891// Tests that Early Hints preload is blocked when hints comes from the public
892// network but a hinted resource is located in a private network.
893IN_PROC_BROWSER_TEST_F(NavigationEarlyHintsAddressSpaceTest,
894 PublicToPrivateRequestBlocked) {
895 const GURL kPrivateResourceUrl = private_server().GetURL("/blank.jpg");
896 ResponseEntry page_entry(kEmptyPagePath, net::HTTP_OK);
897 HeaderField link_header = HeaderField(
898 "link", base::StringPrintf("<%s>; rel=preload; as=image",
899 kPrivateResourceUrl.spec().c_str()));
900 page_entry.AddEarlyHints({std::move(link_header)});
901 RegisterResponse(page_entry);
902
903 EXPECT_TRUE(NavigateToURL(
904 shell(), net::QuicSimpleTestServer::GetFileURL(kEmptyPagePath)));
905 EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
906
907 PreloadedResources preloads = WaitForPreloadedResources();
908 EXPECT_EQ(preloads.size(), 1UL);
909
910 auto it = preloads.find(kPrivateResourceUrl);
911 ASSERT_TRUE(it != preloads.end());
912 ASSERT_FALSE(it->second.was_canceled);
913 ASSERT_TRUE(it->second.error_code.has_value());
Jonathan Haoced487522023-12-01 17:01:33914 EXPECT_EQ(it->second.error_code.value(),
915 net::ERR_BLOCKED_BY_PRIVATE_NETWORK_ACCESS_CHECKS);
Kenichi Ishibashif3f80522021-07-16 02:37:07916 EXPECT_EQ(it->second.cors_error_status->cors_error,
917 network::mojom::CorsError::kInsecurePrivateNetwork);
918}
919
Miyoung Shin164d9e22022-02-26 06:10:13920class NavigationEarlyHintsPrerenderTest : public NavigationEarlyHintsTest {
921 public:
922 NavigationEarlyHintsPrerenderTest()
923 : prerender_helper_(base::BindRepeating(
924 &NavigationEarlyHintsPrerenderTest::web_contents,
925 base::Unretained(this))) {}
926 ~NavigationEarlyHintsPrerenderTest() override = default;
927
928 test::PrerenderTestHelper* prerender_helper() { return &prerender_helper_; }
929
930 WebContents* web_contents() { return shell()->web_contents(); }
931
932 private:
933 test::PrerenderTestHelper prerender_helper_;
934};
935
936IN_PROC_BROWSER_TEST_F(NavigationEarlyHintsPrerenderTest,
937 AllowPreloadInPrerendering) {
938 EXPECT_TRUE(NavigateToURL(
939 shell(), net::QuicSimpleTestServer::GetFileURL("/title1.html")));
940 ResponseEntry entry = CreatePageEntryWithHintedScript(net::HTTP_OK);
941 RegisterResponse(entry);
942
943 // Loads a page in the prerender.
Avi Drissman580a3da62024-09-04 16:16:56944 FrameTreeNodeId host_id = prerender_helper()->AddPrerender(
Miyoung Shin164d9e22022-02-26 06:10:13945 net::QuicSimpleTestServer::GetFileURL(kPageWithHintedScriptPath));
946 RenderFrameHostImpl* prerender_rfh = static_cast<RenderFrameHostImpl*>(
947 prerender_helper()->GetPrerenderedMainFrameHost(host_id));
948 EXPECT_NE(prerender_rfh, nullptr);
949 EXPECT_NE(prerender_rfh->early_hints_manager(), nullptr);
950
951 PreloadedResources preloads = WaitForPreloadedResources(prerender_rfh);
952 EXPECT_EQ(preloads.size(), 1UL);
953
954 GURL script_url = net::QuicSimpleTestServer::GetFileURL(kHintedScriptPath);
955 EXPECT_TRUE(preloads.contains(script_url));
956}
957
958class NavigationEarlyHintsFencedFrameTest : public NavigationEarlyHintsTest {
959 public:
960 NavigationEarlyHintsFencedFrameTest() = default;
961
962 test::FencedFrameTestHelper& fenced_frame_test_helper() {
963 return fenced_frame_test_helper_;
964 }
965
966 ResponseEntry CreatePageEntryWithHintedScriptInFencedFrame(
967 net::HttpStatusCode status_code) {
968 RegisterHintedScriptResource();
969
970 ResponseEntry entry(kPageWithHintedScriptPath, status_code);
971 entry.headers["supports-loading-mode"] = "fenced-frame";
972 entry.body = kPageWithHintedScriptBody;
973 HeaderField link_header = CreatePreloadLinkForScript();
974 HeaderField fenced_frame_header =
975 HeaderField("supports-loading-mode", "fenced-frame");
976 entry.AddEarlyHints(
977 {std::move(link_header), std::move(fenced_frame_header)});
978 return entry;
979 }
980
981 private:
982 test::FencedFrameTestHelper fenced_frame_test_helper_;
983};
984
985IN_PROC_BROWSER_TEST_F(NavigationEarlyHintsFencedFrameTest,
986 DisallowPreloadInFencedFrame) {
987 EXPECT_TRUE(NavigateToURL(
988 shell(), net::QuicSimpleTestServer::GetFileURL("/title1.html")));
989
990 ResponseEntry entry =
991 CreatePageEntryWithHintedScriptInFencedFrame(net::HTTP_OK);
992 RegisterResponse(entry);
993
994 // Create a fenced frame.
995 RenderFrameHostImpl* fenced_frame_host = static_cast<RenderFrameHostImpl*>(
996 fenced_frame_test_helper().CreateFencedFrame(
Dave Tapuska327c06c92022-06-13 20:31:51997 shell()->web_contents()->GetPrimaryMainFrame(),
Miyoung Shin164d9e22022-02-26 06:10:13998 net::QuicSimpleTestServer::GetFileURL(kPageWithHintedScriptPath)));
999 EXPECT_NE(fenced_frame_host, nullptr);
1000 EXPECT_EQ(fenced_frame_host->early_hints_manager(), nullptr);
1001}
1002
Kenichi Ishibashi5ca5a1772023-06-13 06:32:111003namespace {
1004
1005const char kHttp1EarlyHintsPath[] = "/early-hints";
1006
1007class Http1EarlyHintsResponse : public net::test_server::HttpResponse {
1008 public:
1009 Http1EarlyHintsResponse() = default;
1010 ~Http1EarlyHintsResponse() override = default;
1011
1012 void SendResponse(
1013 base::WeakPtr<net::test_server::HttpResponseDelegate> delegate) override {
1014 base::StringPairs early_hints_headers = {
1015 {"Link", "</cacheable.js>; rel=preload; as=script"}};
1016 delegate->SendResponseHeaders(net::HTTP_EARLY_HINTS, "Early Hints",
1017 early_hints_headers);
1018
1019 base::StringPairs final_response_headers = {
1020 {"Content-Type", "text/html"},
1021 {"Link", "</cacheable.js>; rel=preload; as=script"}};
1022 delegate->SendResponseHeaders(net::HTTP_OK, "OK", final_response_headers);
1023
1024 delegate->SendContentsAndFinish("<script src=\"cacheable.js\"></script>");
1025 }
1026};
1027
1028std::unique_ptr<net::test_server::HttpResponse> HandleHttpEarlyHintsRequest(
1029 const net::test_server::HttpRequest& request) {
1030 const GURL relative_url = request.base_url.Resolve(request.relative_url);
1031 if (relative_url.path() == kHttp1EarlyHintsPath) {
1032 return std::make_unique<Http1EarlyHintsResponse>();
1033 }
1034 return nullptr;
1035}
1036
1037} // namespace
1038
1039class NavigationEarlyHintsHttp1Test : public ContentBrowserTest,
1040 public testing::WithParamInterface<bool> {
1041 public:
1042 NavigationEarlyHintsHttp1Test() {
1043 if (EnableEarlyHintsForHttp1()) {
1044 scoped_feature_list_.InitAndEnableFeature(
1045 net::features::kEnableEarlyHintsOnHttp11);
1046 } else {
1047 scoped_feature_list_.InitAndDisableFeature(
1048 net::features::kEnableEarlyHintsOnHttp11);
1049 }
1050 }
1051
1052 void SetUpOnMainThread() override {
1053 ContentBrowserTest::SetUpOnMainThread();
1054 host_resolver()->AddRule("*", "127.0.0.1");
1055 embedded_test_server()->AddDefaultHandlers();
1056 embedded_test_server()->RegisterRequestHandler(
1057 base::BindRepeating(&HandleHttpEarlyHintsRequest));
1058 ASSERT_TRUE(embedded_test_server()->Start());
1059 }
1060
1061 bool EnableEarlyHintsForHttp1() { return GetParam(); }
1062
1063 private:
1064 base::test::ScopedFeatureList scoped_feature_list_;
1065};
1066
1067INSTANTIATE_TEST_SUITE_P(All, NavigationEarlyHintsHttp1Test, testing::Bool());
1068
1069// Tests that Early Hints are allowed or disallowed on HTTP/1.1 based on a
1070// feature flag.
1071IN_PROC_BROWSER_TEST_P(NavigationEarlyHintsHttp1Test, AllowEarlyHints) {
1072 const GURL url = embedded_test_server()->GetURL(kHttp1EarlyHintsPath);
1073 ASSERT_TRUE(NavigateToURL(shell(), url));
1074
1075 NavigationEarlyHintsManager* early_hints_manager =
1076 static_cast<RenderFrameHostImpl*>(
1077 shell()->web_contents()->GetPrimaryMainFrame())
1078 ->early_hints_manager();
1079 if (EnableEarlyHintsForHttp1()) {
1080 ASSERT_TRUE(early_hints_manager->WasResourceHintsReceived());
1081 } else {
1082 ASSERT_TRUE(early_hints_manager == nullptr);
1083 }
1084}
1085
Kenichi Ishibashi7eb8cf62021-04-07 12:35:051086} // namespace content