Avi Drissman | 4e1b7bc3 | 2022-09-15 14:03:50 | [diff] [blame] | 1 | // Copyright 2021 The Chromium Authors |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
Kenichi Ishibashi | 3d2627be | 2021-09-02 12:23:17 | [diff] [blame] | 5 | #include "base/memory/weak_ptr.h" |
Kenichi Ishibashi | a313805 | 2021-04-07 12:54:59 | [diff] [blame] | 6 | #include "base/run_loop.h" |
Kenichi Ishibashi | 523272d | 2021-07-30 10:05:15 | [diff] [blame] | 7 | #include "base/strings/strcat.h" |
Kenichi Ishibashi | 5ca5a177 | 2023-06-13 06:32:11 | [diff] [blame] | 8 | #include "base/strings/string_split.h" |
Lei Zhang | e02299a | 2021-04-26 23:12:24 | [diff] [blame] | 9 | #include "base/strings/stringprintf.h" |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 10 | #include "base/strings/utf_string_conversions.h" |
Sean Maher | e672a66 | 2023-01-09 21:42:28 | [diff] [blame] | 11 | #include "base/task/sequenced_task_runner.h" |
| 12 | #include "base/task/single_thread_task_runner.h" |
Kenichi Ishibashi | a313805 | 2021-04-07 12:54:59 | [diff] [blame] | 13 | #include "base/test/bind.h" |
Kenichi Ishibashi | 598c7557 | 2023-09-28 06:20:16 | [diff] [blame] | 14 | #include "base/test/metrics/histogram_tester.h" |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 15 | #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 Guo | aaad33d4 | 2024-03-29 01:23:20 | [diff] [blame] | 18 | #include "content/browser/devtools/protocol/devtools_protocol_test_support.h" |
Kenichi Ishibashi | a313805 | 2021-04-07 12:54:59 | [diff] [blame] | 19 | #include "content/browser/loader/navigation_early_hints_manager.h" |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 20 | #include "content/browser/renderer_host/render_frame_host_impl.h" |
Kenichi Ishibashi | f3f8052 | 2021-07-16 02:37:07 | [diff] [blame] | 21 | #include "content/browser/web_contents/web_contents_impl.h" |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 22 | #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 Shin | 164d9e2 | 2022-02-26 06:10:13 | [diff] [blame] | 26 | #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 Ishibashi | f3f8052 | 2021-07-16 02:37:07 | [diff] [blame] | 29 | #include "content/public/test/url_loader_interceptor.h" |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 30 | #include "content/shell/browser/shell.h" |
Kenichi Ishibashi | f3f8052 | 2021-07-16 02:37:07 | [diff] [blame] | 31 | #include "content/test/content_browser_test_utils_internal.h" |
| 32 | #include "net/base/features.h" |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 33 | #include "net/dns/mock_host_resolver.h" |
| 34 | #include "net/http/http_status_code.h" |
| 35 | #include "net/test/cert_test_util.h" |
Kenichi Ishibashi | fc2dd873d | 2021-06-10 23:46:42 | [diff] [blame] | 36 | #include "net/test/embedded_test_server/embedded_test_server.h" |
Kenichi Ishibashi | 3d2627be | 2021-09-02 12:23:17 | [diff] [blame] | 37 | #include "net/test/embedded_test_server/embedded_test_server_connection_listener.h" |
Kenichi Ishibashi | fc2dd873d | 2021-06-10 23:46:42 | [diff] [blame] | 38 | #include "net/test/embedded_test_server/http_request.h" |
| 39 | #include "net/test/embedded_test_server/http_response.h" |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 40 | #include "net/test/quic_simple_test_server.h" |
| 41 | #include "net/test/test_data_directory.h" |
Adam Rice | c23e7f6f | 2024-07-18 05:44:50 | [diff] [blame] | 42 | #include "net/third_party/quiche/src/quiche/common/http/http_header_block.h" |
Kenichi Ishibashi | f3f8052 | 2021-07-16 02:37:07 | [diff] [blame] | 43 | #include "services/network/public/cpp/network_switches.h" |
Kenichi Ishibashi | 523272d | 2021-07-30 10:05:15 | [diff] [blame] | 44 | #include "services/network/public/mojom/early_hints.mojom.h" |
| 45 | #include "services/network/public/mojom/link_header.mojom.h" |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 46 | |
| 47 | namespace content { |
| 48 | |
Kenichi Ishibashi | a313805 | 2021-04-07 12:54:59 | [diff] [blame] | 49 | using PreloadedResources = NavigationEarlyHintsManager::PreloadedResources; |
| 50 | |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 51 | namespace { |
| 52 | |
| 53 | struct 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 | |
| 61 | struct 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 Kasting | eb8c3ce | 2021-08-20 04:39:35 | [diff] [blame] | 68 | void AddEarlyHints(const std::vector<HeaderField>& header_fields) { |
Adam Rice | c23e7f6f | 2024-07-18 05:44:50 | [diff] [blame] | 69 | quiche::HttpHeaderBlock hints_headers; |
Peter Kasting | eb8c3ce | 2021-08-20 04:39:35 | [diff] [blame] | 70 | for (const auto& header : header_fields) |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 71 | hints_headers.AppendValueOrAddHeader(header.name, header.value); |
| 72 | early_hints.push_back(std::move(hints_headers)); |
| 73 | } |
| 74 | |
| 75 | std::string path; |
Adam Rice | c23e7f6f | 2024-07-18 05:44:50 | [diff] [blame] | 76 | quiche::HttpHeaderBlock headers; |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 77 | std::string body; |
Adam Rice | c23e7f6f | 2024-07-18 05:44:50 | [diff] [blame] | 78 | std::vector<quiche::HttpHeaderBlock> early_hints; |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 79 | }; |
| 80 | |
| 81 | const char kPageWithHintedScriptPath[] = "/page_with_hinted_js.html"; |
| 82 | const char kPageWithHintedScriptBody[] = "<script src=\"/hinted.js\"></script>"; |
Kenichi Ishibashi | 9b772bc | 2021-06-01 06:05:03 | [diff] [blame] | 83 | |
| 84 | const char kPageWithHintedCorsScriptPath[] = "/page_with_hinted_cors_js.html"; |
| 85 | const char kPageWithHintedCorsScriptBody[] = |
| 86 | "<script src=\"/hinted.js\" crossorigin></script>"; |
| 87 | |
Kenichi Ishibashi | 75a97ae | 2021-07-12 23:07:31 | [diff] [blame] | 88 | const char kPageWithIframePath[] = "/page_with_iframe.html"; |
| 89 | const char kPageWithIframeBody[] = |
| 90 | "<iframe src=\"page_with_hinted_js.html\"></iframe>"; |
| 91 | |
Kenichi Ishibashi | d3e17ca | 2021-05-17 04:38:52 | [diff] [blame] | 92 | const char kPageWithHintedModuleScriptPath[] = |
| 93 | "/page_with_hinted_module_js.html"; |
| 94 | const char kPageWithHintedModuleScriptBody[] = |
| 95 | "<script src=\"/hinted.js\" type=\"module\"></script>"; |
Kenichi Ishibashi | 9b772bc | 2021-06-01 06:05:03 | [diff] [blame] | 96 | |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 97 | const char kHintedScriptPath[] = "/hinted.js"; |
| 98 | const char kHintedScriptBody[] = "document.title = 'Done';"; |
Kenichi Ishibashi | 9b772bc | 2021-06-01 06:05:03 | [diff] [blame] | 99 | |
Kenichi Ishibashi | a313805 | 2021-04-07 12:54:59 | [diff] [blame] | 100 | const char kHintedStylesheetPath[] = "/hinted.css"; |
| 101 | const char kHintedStylesheetBody[] = "/*empty*/"; |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 102 | |
Kenichi Ishibashi | f3f8052 | 2021-07-16 02:37:07 | [diff] [blame] | 103 | const char kEmptyPagePath[] = "/empty.html"; |
| 104 | const char kEmptyPageBody[] = "<html></html>"; |
| 105 | |
Kenichi Ishibashi | af6ea52a | 2022-03-15 22:17:10 | [diff] [blame] | 106 | const char kRedirectedPagePath[] = "/redirected.html"; |
| 107 | const char kRedirectedPageBody[] = "<script src=\"/hinted.js\"></script>"; |
| 108 | |
Kenichi Ishibashi | 3d2627be | 2021-09-02 12:23:17 | [diff] [blame] | 109 | // 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. |
| 112 | class PreconnectListener |
| 113 | : public net::test_server::EmbeddedTestServerConnectionListener { |
| 114 | public: |
| 115 | PreconnectListener() |
Sean Maher | 5b9af51f | 2022-11-21 15:32:47 | [diff] [blame] | 116 | : task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()), |
Kenichi Ishibashi | 3d2627be | 2021-09-02 12:23:17 | [diff] [blame] | 117 | 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 Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 149 | } // 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 Guo | aaad33d4 | 2024-03-29 01:23:20 | [diff] [blame] | 153 | class NavigationEarlyHintsTest : public DevToolsProtocolTest { |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 154 | public: |
Miyoung Shin | 164d9e2 | 2022-02-26 06:10:13 | [diff] [blame] | 155 | NavigationEarlyHintsTest() { |
Mohannad Farrag | 8bc303d6 | 2023-10-05 12:30:30 | [diff] [blame] | 156 | feature_list_.InitWithFeatures( |
| 157 | std::vector<base::test::FeatureRef>{ |
| 158 | net::features::kSplitCacheByNetworkIsolationKey}, |
| 159 | std::vector<base::test::FeatureRef>{ |
| 160 | net::features::kMigrateSessionsOnNetworkChangeV2}); |
Miyoung Shin | 164d9e2 | 2022-02-26 06:10:13 | [diff] [blame] | 161 | } |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 162 | ~NavigationEarlyHintsTest() override = default; |
| 163 | |
| 164 | void SetUpOnMainThread() override { |
Jiacheng Guo | aaad33d4 | 2024-03-29 01:23:20 | [diff] [blame] | 165 | DevToolsProtocolTest::SetUpOnMainThread(); |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 166 | ConfigureMockCertVerifier(); |
Kenichi Ishibashi | f3f8052 | 2021-07-16 02:37:07 | [diff] [blame] | 167 | host_resolver()->AddRule("*", "127.0.0.1"); |
Kenichi Ishibashi | 3d2627be | 2021-09-02 12:23:17 | [diff] [blame] | 168 | |
| 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 Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 175 | } |
| 176 | |
| 177 | void SetUpCommandLine(base::CommandLine* command_line) override { |
| 178 | command_line->AppendSwitchASCII(switches::kOriginToForceQuicOn, "*"); |
| 179 | mock_cert_verifier_.SetUpCommandLine(command_line); |
Kenichi Ishibashi | f3f8052 | 2021-07-16 02:37:07 | [diff] [blame] | 180 | |
Kenichi Ishibashi | f3f8052 | 2021-07-16 02:37:07 | [diff] [blame] | 181 | ASSERT_TRUE(net::QuicSimpleTestServer::Start()); |
| 182 | |
Jiacheng Guo | aaad33d4 | 2024-03-29 01:23:20 | [diff] [blame] | 183 | DevToolsProtocolTest::SetUpCommandLine(command_line); |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 184 | } |
| 185 | |
| 186 | void TearDown() override { |
| 187 | base::ScopedAllowBaseSyncPrimitivesForTesting allow_wait; |
| 188 | net::QuicSimpleTestServer::Shutdown(); |
Jiacheng Guo | aaad33d4 | 2024-03-29 01:23:20 | [diff] [blame] | 189 | DevToolsProtocolTest::TearDown(); |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 190 | } |
| 191 | |
Kenichi Ishibashi | fc2dd873d | 2021-06-10 23:46:42 | [diff] [blame] | 192 | net::test_server::EmbeddedTestServer& cross_origin_server() { |
| 193 | return cross_origin_server_; |
| 194 | } |
| 195 | |
Jiacheng Guo | aaad33d4 | 2024-03-29 01:23:20 | [diff] [blame] | 196 | 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 Guo | 5296590 | 2024-04-02 09:28:09 | [diff] [blame] | 208 | 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 Guo | aaad33d4 | 2024-03-29 01:23:20 | [diff] [blame] | 216 | 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 Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 227 | protected: |
Kenichi Ishibashi | 523272d | 2021-07-30 10:05:15 | [diff] [blame] | 228 | base::test::ScopedFeatureList& feature_list() { return feature_list_; } |
| 229 | |
Kenichi Ishibashi | 3d2627be | 2021-09-02 12:23:17 | [diff] [blame] | 230 | PreconnectListener& preconnect_listener() { return *preconnect_listener_; } |
| 231 | |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 232 | 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 Ishibashi | a313805 | 2021-04-07 12:54:59 | [diff] [blame] | 250 | HeaderField CreatePreloadLinkForScript() { |
| 251 | return HeaderField( |
| 252 | "link", |
| 253 | base::StringPrintf("<%s>; rel=preload; as=script", kHintedScriptPath)); |
| 254 | } |
| 255 | |
Kenichi Ishibashi | 9b772bc | 2021-06-01 06:05:03 | [diff] [blame] | 256 | HeaderField CreatePreloadLinkForCorsScript() { |
| 257 | return HeaderField( |
| 258 | "link", base::StringPrintf("<%s>; rel=preload; as=script; crossorigin", |
| 259 | kHintedScriptPath)); |
| 260 | } |
| 261 | |
Kenichi Ishibashi | d3e17ca | 2021-05-17 04:38:52 | [diff] [blame] | 262 | HeaderField CreateModulePreloadLink() { |
| 263 | return HeaderField("link", base::StringPrintf("<%s>; rel=modulepreload", |
| 264 | kHintedScriptPath)); |
| 265 | } |
| 266 | |
Kenichi Ishibashi | a313805 | 2021-04-07 12:54:59 | [diff] [blame] | 267 | HeaderField CreatePreloadLinkForStylesheet() { |
Kenichi Ishibashi | 3c7d419 | 2021-08-16 04:55:26 | [diff] [blame] | 268 | return HeaderField("link", base::StringPrintf("<%s>; rel=preload; as=style", |
| 269 | kHintedStylesheetPath)); |
Kenichi Ishibashi | a313805 | 2021-04-07 12:54:59 | [diff] [blame] | 270 | } |
| 271 | |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 272 | void RegisterResponse(const ResponseEntry& entry) { |
| 273 | net::QuicSimpleTestServer::AddResponseWithEarlyHints( |
| 274 | entry.path, entry.headers, entry.body, entry.early_hints); |
| 275 | } |
| 276 | |
Kenichi Ishibashi | a313805 | 2021-04-07 12:54:59 | [diff] [blame] | 277 | void RegisterHintedScriptResource() { |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 278 | ResponseEntry hinted_script_entry(kHintedScriptPath, net::HTTP_OK); |
| 279 | hinted_script_entry.headers["content-type"] = "application/javascript"; |
Kenichi Ishibashi | f3f8052 | 2021-07-16 02:37:07 | [diff] [blame] | 280 | hinted_script_entry.headers["cache-control"] = "max-age=3600"; |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 281 | hinted_script_entry.body = kHintedScriptBody; |
| 282 | RegisterResponse(hinted_script_entry); |
Kenichi Ishibashi | a313805 | 2021-04-07 12:54:59 | [diff] [blame] | 283 | } |
| 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 Ishibashi | af6ea52a | 2022-03-15 22:17:10 | [diff] [blame] | 292 | void RegisterRedirectedPage() { |
| 293 | ResponseEntry entry(kRedirectedPagePath, net::HTTP_OK); |
| 294 | entry.body = kRedirectedPageBody; |
| 295 | RegisterResponse(entry); |
| 296 | } |
| 297 | |
Kenichi Ishibashi | a313805 | 2021-04-07 12:54:59 | [diff] [blame] | 298 | ResponseEntry CreatePageEntryWithHintedScript( |
| 299 | net::HttpStatusCode status_code) { |
| 300 | RegisterHintedScriptResource(); |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 301 | |
| 302 | ResponseEntry entry(kPageWithHintedScriptPath, status_code); |
| 303 | entry.body = kPageWithHintedScriptBody; |
Kenichi Ishibashi | a313805 | 2021-04-07 12:54:59 | [diff] [blame] | 304 | HeaderField link_header = CreatePreloadLinkForScript(); |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 305 | entry.AddEarlyHints({std::move(link_header)}); |
| 306 | |
| 307 | return entry; |
| 308 | } |
| 309 | |
Kenichi Ishibashi | f3f8052 | 2021-07-16 02:37:07 | [diff] [blame] | 310 | 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 Ishibashi | 9b772bc | 2021-06-01 06:05:03 | [diff] [blame] | 321 | 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 Ishibashi | d3e17ca | 2021-05-17 04:38:52 | [diff] [blame] | 333 | 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 Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 345 | bool NavigateToURLAndWaitTitle(const GURL& url, const std::string& title) { |
Kenichi Ishibashi | af6ea52a | 2022-03-15 22:17:10 | [diff] [blame] | 346 | 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 Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 352 | std::u16string title16 = base::ASCIIToUTF16(title); |
| 353 | TitleWatcher title_watcher(shell()->web_contents(), title16); |
Solomon Kinard | ab293bae | 2024-09-19 17:13:51 | [diff] [blame] | 354 | if (!NavigateToURL(shell(), url, expected_commit_url)) { |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 355 | return false; |
Solomon Kinard | ab293bae | 2024-09-19 17:13:51 | [diff] [blame] | 356 | } |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 357 | return title16 == title_watcher.WaitAndGetTitle(); |
| 358 | } |
| 359 | |
Miyoung Shin | 164d9e2 | 2022-02-26 06:10:13 | [diff] [blame] | 360 | NavigationEarlyHintsManager* GetEarlyHintsManager(RenderFrameHostImpl* rfh) { |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 361 | return rfh->early_hints_manager(); |
| 362 | } |
| 363 | |
Kenichi Ishibashi | a313805 | 2021-04-07 12:54:59 | [diff] [blame] | 364 | PreloadedResources WaitForPreloadedResources() { |
Miyoung Shin | 164d9e2 | 2022-02-26 06:10:13 | [diff] [blame] | 365 | return WaitForPreloadedResources(static_cast<RenderFrameHostImpl*>( |
Dave Tapuska | 327c06c9 | 2022-06-13 20:31:51 | [diff] [blame] | 366 | shell()->web_contents()->GetPrimaryMainFrame())); |
Miyoung Shin | 164d9e2 | 2022-02-26 06:10:13 | [diff] [blame] | 367 | } |
| 368 | |
| 369 | PreloadedResources WaitForPreloadedResources(RenderFrameHostImpl* rfh) { |
Kenichi Ishibashi | a313805 | 2021-04-07 12:54:59 | [diff] [blame] | 370 | base::RunLoop loop; |
| 371 | PreloadedResources result; |
Solomon Kinard | ab293bae | 2024-09-19 17:13:51 | [diff] [blame] | 372 | if (!GetEarlyHintsManager(rfh)) { |
Kenichi Ishibashi | a313805 | 2021-04-07 12:54:59 | [diff] [blame] | 373 | return result; |
Solomon Kinard | ab293bae | 2024-09-19 17:13:51 | [diff] [blame] | 374 | } |
Kenichi Ishibashi | a313805 | 2021-04-07 12:54:59 | [diff] [blame] | 375 | |
Miyoung Shin | 164d9e2 | 2022-02-26 06:10:13 | [diff] [blame] | 376 | GetEarlyHintsManager(rfh)->WaitForPreloadsFinishedForTesting( |
Kenichi Ishibashi | a313805 | 2021-04-07 12:54:59 | [diff] [blame] | 377 | base::BindLambdaForTesting([&](PreloadedResources preloaded_resources) { |
| 378 | result = preloaded_resources; |
| 379 | loop.Quit(); |
| 380 | })); |
| 381 | loop.Run(); |
| 382 | return result; |
| 383 | } |
| 384 | |
Kenichi Ishibashi | fc2dd873d | 2021-06-10 23:46:42 | [diff] [blame] | 385 | enum class FetchResult { |
| 386 | kFetched, |
| 387 | kBlocked, |
| 388 | }; |
Kenichi Ishibashi | f3f8052 | 2021-07-16 02:37:07 | [diff] [blame] | 389 | FetchResult FetchScriptOnDocument(ToRenderFrameHost target, GURL src) { |
| 390 | EvalJsResult result = EvalJs(target, JsReplace(R"( |
Kenichi Ishibashi | fc2dd873d | 2021-06-10 23:46:42 | [diff] [blame] | 391 | 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 Ishibashi | f3f8052 | 2021-07-16 02:37:07 | [diff] [blame] | 399 | src)); |
Kenichi Ishibashi | fc2dd873d | 2021-06-10 23:46:42 | [diff] [blame] | 400 | return result.ExtractString() == "fetched" ? FetchResult::kFetched |
| 401 | : FetchResult::kBlocked; |
| 402 | } |
| 403 | |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 404 | private: |
Kenichi Ishibashi | fc2dd873d | 2021-06-10 23:46:42 | [diff] [blame] | 405 | 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 Ishibashi | f3f8052 | 2021-07-16 02:37:07 | [diff] [blame] | 408 | |
Kenichi Ishibashi | af6ea52a | 2022-03-15 22:17:10 | [diff] [blame] | 409 | if (relative_url.path() == kEmptyPagePath) { |
Kenichi Ishibashi | f3f8052 | 2021-07-16 02:37:07 | [diff] [blame] | 410 | 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 Ishibashi | af6ea52a | 2022-03-15 22:17:10 | [diff] [blame] | 417 | 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 Kinard | ab293bae | 2024-09-19 17:13:51 | [diff] [blame] | 425 | if (relative_url.path() != kHintedScriptPath) { |
Kenichi Ishibashi | fc2dd873d | 2021-06-10 23:46:42 | [diff] [blame] | 426 | return nullptr; |
Solomon Kinard | ab293bae | 2024-09-19 17:13:51 | [diff] [blame] | 427 | } |
Kenichi Ishibashi | fc2dd873d | 2021-06-10 23:46:42 | [diff] [blame] | 428 | |
| 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 Ishibashi | af6ea52a | 2022-03-15 22:17:10 | [diff] [blame] | 432 | response->set_content(kHintedScriptBody); |
Kenichi Ishibashi | fc2dd873d | 2021-06-10 23:46:42 | [diff] [blame] | 433 | |
| 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 Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 444 | base::test::ScopedFeatureList feature_list_; |
| 445 | |
| 446 | ContentMockCertVerifier mock_cert_verifier_; |
Kenichi Ishibashi | fc2dd873d | 2021-06-10 23:46:42 | [diff] [blame] | 447 | |
Kenichi Ishibashi | f3f8052 | 2021-07-16 02:37:07 | [diff] [blame] | 448 | // For tests that fetch resources from a cross origin server. |
Kenichi Ishibashi | fc2dd873d | 2021-06-10 23:46:42 | [diff] [blame] | 449 | net::EmbeddedTestServer cross_origin_server_; |
Kenichi Ishibashi | 3d2627be | 2021-09-02 12:23:17 | [diff] [blame] | 450 | std::unique_ptr<PreconnectListener> preconnect_listener_; |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 451 | }; |
| 452 | |
| 453 | IN_PROC_BROWSER_TEST_F(NavigationEarlyHintsTest, Basic) { |
Kenichi Ishibashi | 598c7557 | 2023-09-28 06:20:16 | [diff] [blame] | 454 | base::HistogramTester histograms; |
| 455 | |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 456 | ResponseEntry entry = CreatePageEntryWithHintedScript(net::HTTP_OK); |
| 457 | RegisterResponse(entry); |
| 458 | |
| 459 | EXPECT_TRUE(NavigateToURLAndWaitTitle( |
| 460 | net::QuicSimpleTestServer::GetFileURL(kPageWithHintedScriptPath), |
| 461 | "Done")); |
Kenichi Ishibashi | a313805 | 2021-04-07 12:54:59 | [diff] [blame] | 462 | 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 Ishibashi | d379dea | 2021-04-08 11:21:27 | [diff] [blame] | 468 | 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 Ishibashi | 598c7557 | 2023-09-28 06:20:16 | [diff] [blame] | 471 | |
| 472 | histograms.ExpectTotalCount( |
| 473 | "Navigation.EarlyHints.WillStartRequestToEarlyHintsTime", 1); |
| 474 | histograms.ExpectTotalCount( |
| 475 | "Navigation.EarlyHints.EarlyHintsToResponseStartTime", 1); |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 476 | } |
| 477 | |
Kenichi Ishibashi | 9b772bc | 2021-06-01 06:05:03 | [diff] [blame] | 478 | IN_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 Ishibashi | d3e17ca | 2021-05-17 04:38:52 | [diff] [blame] | 496 | IN_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 Ishibashi | 75a97ae | 2021-07-12 23:07:31 | [diff] [blame] | 514 | IN_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 Tapuska | 800bc54 | 2021-09-14 13:42:50 | [diff] [blame] | 529 | ASSERT_EQ(all_frames[0], all_frames[1]->GetParent()); |
Kenichi Ishibashi | 75a97ae | 2021-07-12 23:07:31 | [diff] [blame] | 530 | 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 Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 542 | IN_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 Ishibashi | a313805 | 2021-04-07 12:54:59 | [diff] [blame] | 550 | PreloadedResources preloads = WaitForPreloadedResources(); |
Kenichi Ishibashi | 0b8c865 | 2022-03-25 02:46:02 | [diff] [blame] | 551 | 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 Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 559 | } |
| 560 | |
Kenichi Ishibashi | af6ea52a | 2022-03-15 22:17:10 | [diff] [blame] | 561 | IN_PROC_BROWSER_TEST_F(NavigationEarlyHintsTest, RedirectSameOrigin) { |
| 562 | RegisterRedirectedPage(); |
| 563 | |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 564 | ResponseEntry entry = CreatePageEntryWithHintedScript(net::HTTP_FOUND); |
Kenichi Ishibashi | af6ea52a | 2022-03-15 22:17:10 | [diff] [blame] | 565 | entry.headers["location"] = kRedirectedPagePath; |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 566 | entry.body = ""; |
| 567 | RegisterResponse(entry); |
| 568 | |
Kenichi Ishibashi | af6ea52a | 2022-03-15 22:17:10 | [diff] [blame] | 569 | 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 | |
| 584 | IN_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 Ishibashi | a313805 | 2021-04-07 12:54:59 | [diff] [blame] | 596 | PreloadedResources preloads = WaitForPreloadedResources(); |
| 597 | EXPECT_TRUE(preloads.empty()); |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 598 | } |
| 599 | |
Kenichi Ishibashi | a313805 | 2021-04-07 12:54:59 | [diff] [blame] | 600 | IN_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 Ishibashi | 50840fb9 | 2022-03-21 13:50:16 | [diff] [blame] | 618 | IN_PROC_BROWSER_TEST_F(NavigationEarlyHintsTest, MultipleEarlyHints) { |
Kenichi Ishibashi | a313805 | 2021-04-07 12:54:59 | [diff] [blame] | 619 | 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 Ishibashi | 50840fb9 | 2022-03-21 13:50:16 | [diff] [blame] | 626 | // The second response should be ignored. |
Kenichi Ishibashi | a313805 | 2021-04-07 12:54:59 | [diff] [blame] | 627 | 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 Ishibashi | 50840fb9 | 2022-03-21 13:50:16 | [diff] [blame] | 637 | EXPECT_EQ(preloads.size(), 1UL); |
Kenichi Ishibashi | a313805 | 2021-04-07 12:54:59 | [diff] [blame] | 638 | |
| 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 Ishibashi | 50840fb9 | 2022-03-21 13:50:16 | [diff] [blame] | 643 | EXPECT_FALSE(preloads.contains(stylesheet_url)); |
Kenichi Ishibashi | a313805 | 2021-04-07 12:54:59 | [diff] [blame] | 644 | } |
Kenichi Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 645 | |
Kenichi Ishibashi | fc2dd873d | 2021-06-10 23:46:42 | [diff] [blame] | 646 | const char kPageWithCrossOriginScriptPage[] = |
| 647 | "/page_with_cross_origin_script.html"; |
| 648 | |
| 649 | IN_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 Ishibashi | f3f8052 | 2021-07-16 02:37:07 | [diff] [blame] | 666 | EXPECT_EQ(FetchScriptOnDocument(shell(), kCrossOriginScriptUrl), |
Kenichi Ishibashi | fc2dd873d | 2021-06-10 23:46:42 | [diff] [blame] | 667 | 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 | |
| 679 | IN_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 Ishibashi | f3f8052 | 2021-07-16 02:37:07 | [diff] [blame] | 697 | EXPECT_EQ(FetchScriptOnDocument(shell(), kCrossOriginScriptUrl), |
Kenichi Ishibashi | fc2dd873d | 2021-06-10 23:46:42 | [diff] [blame] | 698 | FetchResult::kBlocked); |
| 699 | } |
| 700 | |
Kenichi Ishibashi | f3f8052 | 2021-07-16 02:37:07 | [diff] [blame] | 701 | IN_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 | |
| 723 | IN_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 Goldstein | d22b064 | 2022-10-11 16:30:50 | [diff] [blame] | 745 | IN_PROC_BROWSER_TEST_F(NavigationEarlyHintsTest, NetworkAnonymizationKey) { |
Kenichi Ishibashi | f3f8052 | 2021-07-16 02:37:07 | [diff] [blame] | 746 | const GURL kHintedScriptUrl = |
| 747 | net::QuicSimpleTestServer::GetFileURL(kHintedScriptPath); |
| 748 | |
| 749 | ResponseEntry entry = CreateEmptyPageEntryWithHintedScript(); |
| 750 | RegisterResponse(entry); |
| 751 | |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 752 | std::optional<bool> is_cached; |
Kenichi Ishibashi | f3f8052 | 2021-07-16 02:37:07 | [diff] [blame] | 753 | 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 Kinard | ab293bae | 2024-09-19 17:13:51 | [diff] [blame] | 759 | if (request_url != kHintedScriptUrl) { |
Kenichi Ishibashi | f3f8052 | 2021-07-16 02:37:07 | [diff] [blame] | 760 | return; |
Solomon Kinard | ab293bae | 2024-09-19 17:13:51 | [diff] [blame] | 761 | } |
Kenichi Ishibashi | f3f8052 | 2021-07-16 02:37:07 | [diff] [blame] | 762 | 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 Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 777 | is_cached = std::nullopt; |
Kenichi Ishibashi | f3f8052 | 2021-07-16 02:37:07 | [diff] [blame] | 778 | |
| 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 Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 785 | is_cached = std::nullopt; |
Kenichi Ishibashi | f3f8052 | 2021-07-16 02:37:07 | [diff] [blame] | 786 | |
| 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 Ishibashi | 57abd54c | 2023-01-05 23:39:06 | [diff] [blame] | 798 | IN_PROC_BROWSER_TEST_F(NavigationEarlyHintsTest, SimplePreconnect) { |
Kenichi Ishibashi | 3d2627be | 2021-09-02 12:23:17 | [diff] [blame] | 799 | 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 Ishibashi | 57abd54c | 2023-01-05 23:39:06 | [diff] [blame] | 812 | EXPECT_EQ(preconnect_listener().num_accepted_sockets(), 1UL); |
Dave Tapuska | 327c06c9 | 2022-06-13 20:31:51 | [diff] [blame] | 813 | EXPECT_TRUE( |
| 814 | GetEarlyHintsManager(static_cast<RenderFrameHostImpl*>( |
| 815 | shell()->web_contents()->GetPrimaryMainFrame())) |
| 816 | ->WasResourceHintsReceived()); |
Kenichi Ishibashi | 3d2627be | 2021-09-02 12:23:17 | [diff] [blame] | 817 | } |
| 818 | |
Kenichi Ishibashi | 1e12346 | 2022-03-21 13:30:46 | [diff] [blame] | 819 | IN_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 Guo | aaad33d4 | 2024-03-29 01:23:20 | [diff] [blame] | 828 | IN_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 Amni | 8590ad57 | 2024-08-05 23:30:02 | [diff] [blame] | 835 | // 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 Guo | aaad33d4 | 2024-03-29 01:23:20 | [diff] [blame] | 838 | 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 Guo | 5296590 | 2024-04-02 09:28:09 | [diff] [blame] | 843 | |
| 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 Guo | aaad33d4 | 2024-03-29 01:23:20 | [diff] [blame] | 862 | } |
| 863 | |
Kenichi Ishibashi | f3f8052 | 2021-07-16 02:37:07 | [diff] [blame] | 864 | class 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. |
| 893 | IN_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 Hao | ced48752 | 2023-12-01 17:01:33 | [diff] [blame] | 914 | EXPECT_EQ(it->second.error_code.value(), |
| 915 | net::ERR_BLOCKED_BY_PRIVATE_NETWORK_ACCESS_CHECKS); |
Kenichi Ishibashi | f3f8052 | 2021-07-16 02:37:07 | [diff] [blame] | 916 | EXPECT_EQ(it->second.cors_error_status->cors_error, |
| 917 | network::mojom::CorsError::kInsecurePrivateNetwork); |
| 918 | } |
| 919 | |
Miyoung Shin | 164d9e2 | 2022-02-26 06:10:13 | [diff] [blame] | 920 | class 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 | |
| 936 | IN_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 Drissman | 580a3da6 | 2024-09-04 16:16:56 | [diff] [blame] | 944 | FrameTreeNodeId host_id = prerender_helper()->AddPrerender( |
Miyoung Shin | 164d9e2 | 2022-02-26 06:10:13 | [diff] [blame] | 945 | 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 | |
| 958 | class 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 | |
| 985 | IN_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 Tapuska | 327c06c9 | 2022-06-13 20:31:51 | [diff] [blame] | 997 | shell()->web_contents()->GetPrimaryMainFrame(), |
Miyoung Shin | 164d9e2 | 2022-02-26 06:10:13 | [diff] [blame] | 998 | net::QuicSimpleTestServer::GetFileURL(kPageWithHintedScriptPath))); |
| 999 | EXPECT_NE(fenced_frame_host, nullptr); |
| 1000 | EXPECT_EQ(fenced_frame_host->early_hints_manager(), nullptr); |
| 1001 | } |
| 1002 | |
Kenichi Ishibashi | 5ca5a177 | 2023-06-13 06:32:11 | [diff] [blame] | 1003 | namespace { |
| 1004 | |
| 1005 | const char kHttp1EarlyHintsPath[] = "/early-hints"; |
| 1006 | |
| 1007 | class 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 | |
| 1028 | std::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 | |
| 1039 | class 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 | |
| 1067 | INSTANTIATE_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. |
| 1071 | IN_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 Ishibashi | 7eb8cf6 | 2021-04-07 12:35:05 | [diff] [blame] | 1086 | } // namespace content |