blob: 07ea9571f8c59d6429a31db7e317f55635c7492c [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
5#include "content/browser/loader/navigation_early_hints_manager.h"
6
Ali Hijazid87307d2022-11-07 20:15:037#include "base/memory/raw_ref.h"
Kenichi Ishibashi11fc3a82021-12-12 08:32:158#include "base/metrics/histogram_functions.h"
Sean Maher5b9af51f2022-11-21 15:32:479#include "base/task/single_thread_task_runner.h"
Kenichi Ishibashi7eb8cf62021-04-07 12:35:0510#include "content/public/browser/browser_context.h"
11#include "content/public/browser/global_request_id.h"
Kenichi Ishibashi3d2627be2021-09-02 12:23:1712#include "content/public/browser/storage_partition.h"
Kenichi Ishibashi7eb8cf62021-04-07 12:35:0513#include "content/public/browser/url_loader_throttles.h"
14#include "content/public/browser/web_contents.h"
Kenichi Ishibashi0b56b612022-03-09 01:16:5415#include "content/public/common/referrer.h"
Kenichi Ishibashid379dea2021-04-08 11:21:2716#include "mojo/public/cpp/bindings/message.h"
17#include "mojo/public/cpp/system/data_pipe_drainer.h"
Kenichi Ishibashi7eb8cf62021-04-07 12:35:0518#include "net/base/load_flags.h"
19#include "net/base/schemeful_site.h"
20#include "net/traffic_annotation/network_traffic_annotation.h"
Kenichi Ishibashifc2dd873d2021-06-10 23:46:4221#include "net/url_request/url_request_job.h"
Kenichi Ishibashi2f5317b2022-03-17 10:49:1022#include "services/network/public/cpp/content_security_policy/content_security_policy.h"
23#include "services/network/public/cpp/content_security_policy/csp_source_list.h"
Aman Verma5637b1cd2022-12-09 21:05:5524#include "services/network/public/cpp/record_ontransfersizeupdate_utils.h"
Kenichi Ishibashi7eb8cf62021-04-07 12:35:0525#include "services/network/public/cpp/resource_request.h"
26#include "services/network/public/cpp/shared_url_loader_factory.h"
Kenichi Ishibashif3f80522021-07-16 02:37:0727#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
Keita Suzuki8aaa3032025-05-20 16:46:5528#include "services/network/public/mojom/connection_change_observer_client.mojom.h"
Kenichi Ishibashi2f5317b2022-03-17 10:49:1029#include "services/network/public/mojom/content_security_policy.mojom-shared.h"
Kenichi Ishibashi7eb8cf62021-04-07 12:35:0530#include "services/network/public/mojom/fetch_api.mojom.h"
Kenichi Ishibashi3d2627be2021-09-02 12:23:1731#include "services/network/public/mojom/network_context.mojom.h"
Kenichi Ishibashid379dea2021-04-08 11:21:2732#include "services/network/public/mojom/url_loader.mojom.h"
Christian Dullweber5e66727b2021-07-02 13:23:1533#include "services/network/public/mojom/url_response_head.mojom.h"
Kenichi Ishibashia9247992022-07-23 01:09:1734#include "third_party/blink/public/common/loader/network_utils.h"
Kenichi Ishibashi7eb8cf62021-04-07 12:35:0535#include "third_party/blink/public/common/loader/throttling_url_loader.h"
36#include "third_party/blink/public/common/loader/url_loader_throttle.h"
37#include "third_party/blink/public/mojom/loader/resource_load_info.mojom-shared.h"
38#include "url/origin.h"
39
40namespace content {
41
42namespace {
43
44const net::NetworkTrafficAnnotationTag kEarlyHintsPreloadTrafficAnnotation =
45 net::DefineNetworkTrafficAnnotation("early_hints_preload",
46 R"(
47 semantics {
48 sender: "Early Hints"
49 description:
50 "This request is issued during a main frame navigation to "
51 "speculatively fetch resources that will likely be used in the frame."
52 trigger:
53 "A 103 Early Hints HTTP informational response is received during "
54 "navigation."
55 data:
56 "Arbitrary site-controlled data can be included in the URL."
57 "Requests may include cookies and site-specific credentials."
58 destination: WEBSITE
59 }
60 policy {
61 cookies_allowed: YES
62 cookies_store: "user"
63 setting:
64 "This feature cannot be disabled by Settings. This feature is not "
Alison Gale3a3959162024-04-29 18:52:4465 "enabled by default yet. TODO(crbug.com/40496584): Update this "
Kenichi Ishibashi7eb8cf62021-04-07 12:35:0566 "description once the feature is ready."
67 chrome_policy {
68 URLBlocklist {
69 URLBlocklist: { entries: '*' }
70 }
71 }
72 chrome_policy {
73 URLAllowlist {
74 URLAllowlist { }
75 }
76 }
77 }
78 comments:
79 "Chrome uses this type of request during navigation and it cannot be "
80 "disabled. Using either URLBlocklist or URLAllowlist (or a combination "
81 "of both) limits the scope of these requests."
82)");
83
Kenichi Ishibashi2f5317b2022-03-17 10:49:1084network::mojom::CSPDirectiveName LinkAsAttributeToCSPDirective(
85 network::mojom::LinkAsAttribute attr) {
Kenichi Ishibashid18285c2023-01-30 04:11:2686 // https://w3c.github.io/webappsec-csp/#csp-directives
Kenichi Ishibashi2f5317b2022-03-17 10:49:1087 switch (attr) {
88 case network::mojom::LinkAsAttribute::kUnspecified:
89 return network::mojom::CSPDirectiveName::Unknown;
90 case network::mojom::LinkAsAttribute::kImage:
91 return network::mojom::CSPDirectiveName::ImgSrc;
92 case network::mojom::LinkAsAttribute::kFont:
93 return network::mojom::CSPDirectiveName::FontSrc;
94 case network::mojom::LinkAsAttribute::kScript:
95 return network::mojom::CSPDirectiveName::ScriptSrcElem;
96 case network::mojom::LinkAsAttribute::kStyleSheet:
97 return network::mojom::CSPDirectiveName::StyleSrcElem;
Kenichi Ishibashid18285c2023-01-30 04:11:2698 case network::mojom::LinkAsAttribute::kFetch:
99 return network::mojom::CSPDirectiveName::ConnectSrc;
Kenichi Ishibashi2f5317b2022-03-17 10:49:10100 }
Peter Boströmfc7ddc182024-10-31 19:37:21101 NOTREACHED();
Kenichi Ishibashi2f5317b2022-03-17 10:49:10102}
103
104bool CheckContentSecurityPolicyForPreload(
105 const network::mojom::LinkHeaderPtr& link,
106 const std::vector<network::mojom::ContentSecurityPolicyPtr>&
107 content_security_policies) {
108 DCHECK(link->rel == network::mojom::LinkRelAttribute::kPreload ||
109 link->rel == network::mojom::LinkRelAttribute::kModulePreload);
110
111 network::mojom::CSPDirectiveName directive =
112 LinkAsAttributeToCSPDirective(link->as);
113
114 for (network::mojom::CSPDirectiveName effective_directive = directive;
115 effective_directive != network::mojom::CSPDirectiveName::Unknown;
116 effective_directive =
117 network::CSPFallbackDirective(effective_directive, directive)) {
118 for (auto& policy : content_security_policies) {
119 const auto& it = policy->directives.find(effective_directive);
Solomon Kinardab293bae2024-09-19 17:13:51120 if (it == policy->directives.end()) {
Kenichi Ishibashi2f5317b2022-03-17 10:49:10121 continue;
Solomon Kinardab293bae2024-09-19 17:13:51122 }
Kenichi Ishibashi2f5317b2022-03-17 10:49:10123
Antonio Sartori60acdfb2024-09-12 09:39:04124 if (!network::CheckCSPSourceList(directive, *it->second, link->href,
125 *(policy->self_origin),
126 /*has_followed_redirect=*/false,
127 /*is_opaque_fenced_frame=*/false)) {
Alison Gale3a3959162024-04-29 18:52:44128 // TODO(crbug.com/40218207): Report CSP violation once the final
Kenichi Ishibashi2f5317b2022-03-17 10:49:10129 // response is received.
130 return false;
131 }
132 }
133 }
134
135 return true;
136}
137
Arthur Sonzognic686e8f2024-01-11 08:36:37138std::optional<network::mojom::RequestDestination>
Kenichi Ishibashid18285c2023-01-30 04:11:26139LinkAsAttributeToRequestDestination(const network::mojom::LinkHeaderPtr& link) {
140 // https://fetch.spec.whatwg.org/#concept-potential-destination-translate
Kenichi Ishibashid3e17ca2021-05-17 04:38:52141 switch (link->as) {
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05142 case network::mojom::LinkAsAttribute::kUnspecified:
Kenichi Ishibashi461474e2022-11-22 09:56:18143 // For modulepreload, the request destination should be "script" when `as`
144 // is not specified.
145 // https://html.spec.whatwg.org/multipage/links.html#link-type-modulepreload
Kenichi Ishibashid3e17ca2021-05-17 04:38:52146 if (link->rel == network::mojom::LinkRelAttribute::kModulePreload) {
147 return network::mojom::RequestDestination::kScript;
Kenichi Ishibashid3e17ca2021-05-17 04:38:52148 }
Arthur Sonzognic686e8f2024-01-11 08:36:37149 return std::nullopt;
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05150 case network::mojom::LinkAsAttribute::kImage:
151 return network::mojom::RequestDestination::kImage;
152 case network::mojom::LinkAsAttribute::kFont:
153 return network::mojom::RequestDestination::kFont;
154 case network::mojom::LinkAsAttribute::kScript:
155 return network::mojom::RequestDestination::kScript;
156 case network::mojom::LinkAsAttribute::kStyleSheet:
157 return network::mojom::RequestDestination::kStyle;
Kenichi Ishibashid18285c2023-01-30 04:11:26158 case network::mojom::LinkAsAttribute::kFetch:
159 return network::mojom::RequestDestination::kEmpty;
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05160 }
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05161}
162
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05163network::mojom::RequestMode CalculateRequestMode(
Kenichi Ishibashid3e17ca2021-05-17 04:38:52164 const network::mojom::LinkHeaderPtr& link) {
165 if (link->rel == network::mojom::LinkRelAttribute::kModulePreload) {
166 // When fetching a module script, mode is always "cors".
Kenichi Ishibashif3f80522021-07-16 02:37:07167 // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-single-module-script
Kenichi Ishibashid3e17ca2021-05-17 04:38:52168 return network::mojom::RequestMode::kCors;
169 }
170
171 switch (link->cross_origin) {
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05172 case network::mojom::CrossOriginAttribute::kUnspecified:
173 return network::mojom::RequestMode::kNoCors;
174 case network::mojom::CrossOriginAttribute::kAnonymous:
175 case network::mojom::CrossOriginAttribute::kUseCredentials:
176 return network::mojom::RequestMode::kCors;
177 }
Peter Boströmfc7ddc182024-10-31 19:37:21178 NOTREACHED();
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05179}
180
Kenichi Ishibashid3e17ca2021-05-17 04:38:52181network::mojom::CredentialsMode CalculateCredentialsMode(
182 const network::mojom::LinkHeaderPtr& link) {
183 switch (link->cross_origin) {
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05184 case network::mojom::CrossOriginAttribute::kUnspecified:
Kenichi Ishibashid3e17ca2021-05-17 04:38:52185 // For modulepreload credentials mode should be "same-origin" when
186 // `cross-origin` is not specified.
187 if (link->rel == network::mojom::LinkRelAttribute::kModulePreload) {
188 return network::mojom::CredentialsMode::kSameOrigin;
189 } else {
190 return network::mojom::CredentialsMode::kInclude;
191 }
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05192 case network::mojom::CrossOriginAttribute::kUseCredentials:
193 return network::mojom::CredentialsMode::kInclude;
194 case network::mojom::CrossOriginAttribute::kAnonymous:
195 return network::mojom::CredentialsMode::kSameOrigin;
196 }
Peter Boströmfc7ddc182024-10-31 19:37:21197 NOTREACHED();
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05198}
199
200} // namespace
201
Kenichi Ishibashi1f67a2d2021-09-02 01:36:48202NavigationEarlyHintsManagerParams::NavigationEarlyHintsManagerParams(
203 const url::Origin& origin,
204 net::IsolationInfo isolation_info,
205 mojo::Remote<network::mojom::URLLoaderFactory> loader_factory)
206 : origin(origin),
207 isolation_info(std::move(isolation_info)),
208 loader_factory(std::move(loader_factory)) {}
209
210NavigationEarlyHintsManagerParams::~NavigationEarlyHintsManagerParams() =
211 default;
212
213NavigationEarlyHintsManagerParams::NavigationEarlyHintsManagerParams(
214 NavigationEarlyHintsManagerParams&&) = default;
215
216NavigationEarlyHintsManagerParams& NavigationEarlyHintsManagerParams::operator=(
217 NavigationEarlyHintsManagerParams&&) = default;
218
Kenichi Ishibashi3d2627be2021-09-02 12:23:17219// Represents a preconnect.
220struct NavigationEarlyHintsManager::PreconnectEntry {
221 PreconnectEntry(const url::Origin& origin,
222 network::mojom::CrossOriginAttribute cross_origin);
223 ~PreconnectEntry();
224 PreconnectEntry(const PreconnectEntry&);
225 PreconnectEntry& operator=(const PreconnectEntry&);
226
227 bool operator==(const PreconnectEntry&);
228 bool operator<(const PreconnectEntry&) const;
229
230 url::Origin origin;
231 network::mojom::CrossOriginAttribute cross_origin;
232};
233
234NavigationEarlyHintsManager::PreconnectEntry::PreconnectEntry(
235 const url::Origin& origin,
236 network::mojom::CrossOriginAttribute cross_origin)
237 : origin(origin), cross_origin(cross_origin) {}
238
239NavigationEarlyHintsManager::PreconnectEntry::~PreconnectEntry() = default;
240
241NavigationEarlyHintsManager::PreconnectEntry::PreconnectEntry(
242 const PreconnectEntry&) = default;
243
244NavigationEarlyHintsManager::PreconnectEntry&
245NavigationEarlyHintsManager::PreconnectEntry::operator=(
246 const PreconnectEntry&) = default;
247
248bool NavigationEarlyHintsManager::PreconnectEntry::operator==(
249 const PreconnectEntry& other) {
250 return origin == other.origin && cross_origin == other.cross_origin;
251}
252
253bool NavigationEarlyHintsManager::PreconnectEntry::operator<(
254 const PreconnectEntry& other) const {
Solomon Kinardab293bae2024-09-19 17:13:51255 if (origin == other.origin) {
Kenichi Ishibashi3d2627be2021-09-02 12:23:17256 return cross_origin < other.cross_origin;
Solomon Kinardab293bae2024-09-19 17:13:51257 }
Kenichi Ishibashi3d2627be2021-09-02 12:23:17258 return origin < other.origin;
259}
260
Kenichi Ishibashid379dea2021-04-08 11:21:27261NavigationEarlyHintsManager::PreloadedResource::PreloadedResource() = default;
262
263NavigationEarlyHintsManager::PreloadedResource::~PreloadedResource() = default;
264
265NavigationEarlyHintsManager::PreloadedResource::PreloadedResource(
266 const PreloadedResource&) = default;
267
268NavigationEarlyHintsManager::PreloadedResource&
269NavigationEarlyHintsManager::PreloadedResource::operator=(
270 const PreloadedResource&) = default;
271
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05272NavigationEarlyHintsManager::InflightPreload::InflightPreload(
273 std::unique_ptr<blink::ThrottlingURLLoader> loader,
Kenichi Ishibashid379dea2021-04-08 11:21:27274 std::unique_ptr<PreloadURLLoaderClient> client)
Paul Semel60f61862022-08-24 08:59:12275 : client(std::move(client)), loader(std::move(loader)) {}
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05276
277NavigationEarlyHintsManager::InflightPreload::~InflightPreload() = default;
278
Kenichi Ishibashid379dea2021-04-08 11:21:27279// A URLLoaderClient which drains the content of a request to put a
280// response into the disk cache. If the response was already in the cache,
281// this tries to cancel reading body to avoid further disk access.
282class NavigationEarlyHintsManager::PreloadURLLoaderClient
283 : public network::mojom::URLLoaderClient,
284 public mojo::DataPipeDrainer::Client {
285 public:
Kenichi Ishibashi11fc3a82021-12-12 08:32:15286 PreloadURLLoaderClient(NavigationEarlyHintsManager& owner,
287 const network::ResourceRequest& request)
Kenichi Ishibashi56ba3e72023-03-07 10:54:12288 : owner_(owner), url_(request.url) {}
Kenichi Ishibashid379dea2021-04-08 11:21:27289
290 ~PreloadURLLoaderClient() override = default;
291
292 PreloadURLLoaderClient(const PreloadURLLoaderClient&) = delete;
293 PreloadURLLoaderClient& operator=(const PreloadURLLoaderClient&) = delete;
294 PreloadURLLoaderClient(PreloadURLLoaderClient&&) = delete;
295 PreloadURLLoaderClient& operator=(PreloadURLLoaderClient&&) = delete;
296
297 private:
298 // mojom::URLLoaderClient overrides:
299 void OnReceiveEarlyHints(network::mojom::EarlyHintsPtr early_hints) override {
300 }
Leszek Swirski154cddb2022-08-29 17:57:19301 void OnReceiveResponse(
302 network::mojom::URLResponseHeadPtr head,
303 mojo::ScopedDataPipeConsumerHandle body,
Arthur Sonzognic686e8f2024-01-11 08:36:37304 std::optional<mojo_base::BigBuffer> cached_metadata) override {
Kenichi Ishibashi50beef12022-02-04 17:05:51305 if (!head->network_accessed && head->was_fetched_via_cache) {
306 // Cancel the client since the response is already stored in the cache.
307 result_.was_canceled = true;
308 MaybeCompletePreload();
Kenichi Ishibashid379dea2021-04-08 11:21:27309 return;
Kenichi Ishibashi50beef12022-02-04 17:05:51310 }
311
Solomon Kinardab293bae2024-09-19 17:13:51312 if (!body) {
John Abd-El-Malek0e47fed2022-05-16 16:29:00313 return;
Solomon Kinardab293bae2024-09-19 17:13:51314 }
John Abd-El-Malek0e47fed2022-05-16 16:29:00315
316 if (response_body_drainer_) {
317 mojo::ReportBadMessage("NEHM_BAD_RESPONSE_BODY");
318 return;
319 }
320 response_body_drainer_ =
321 std::make_unique<mojo::DataPipeDrainer>(this, std::move(body));
Kenichi Ishibashid379dea2021-04-08 11:21:27322 }
323 void OnReceiveRedirect(const net::RedirectInfo& redirect_info,
324 network::mojom::URLResponseHeadPtr head) override {}
325 void OnUploadProgress(int64_t current_position,
326 int64_t total_size,
327 OnUploadProgressCallback callback) override {
Peter Boströmfc7ddc182024-10-31 19:37:21328 NOTREACHED();
Kenichi Ishibashid379dea2021-04-08 11:21:27329 }
Aman Verma5637b1cd2022-12-09 21:05:55330 void OnTransferSizeUpdated(int32_t transfer_size_diff) override {
331 network::RecordOnTransferSizeUpdatedUMA(
332 network::OnTransferSizeUpdatedFrom::kPreloadURLLoaderClient);
333 }
Kenichi Ishibashid379dea2021-04-08 11:21:27334 void OnComplete(const network::URLLoaderCompletionStatus& status) override {
335 if (result_.was_canceled || result_.error_code.has_value()) {
336 mojo::ReportBadMessage("NEHM_BAD_COMPLETE");
337 return;
338 }
339 result_.error_code = status.error_code;
Kenichi Ishibashif3f80522021-07-16 02:37:07340 result_.cors_error_status = status.cors_error_status;
Kenichi Ishibashid379dea2021-04-08 11:21:27341 MaybeCompletePreload();
342 }
343
344 // mojo::DataPipeDrainer::Client overrides:
Lukasz Anforowicz2e81e8ae2024-06-13 15:34:40345 void OnDataAvailable(base::span<const uint8_t> data) override {}
Kenichi Ishibashid379dea2021-04-08 11:21:27346 void OnDataComplete() override {
347 DCHECK(response_body_drainer_);
348 response_body_drainer_.reset();
349 MaybeCompletePreload();
350 }
351
352 bool CanCompletePreload() {
Solomon Kinardab293bae2024-09-19 17:13:51353 if (result_.was_canceled) {
Kenichi Ishibashid379dea2021-04-08 11:21:27354 return true;
Solomon Kinardab293bae2024-09-19 17:13:51355 }
356 if (result_.error_code.has_value() && !response_body_drainer_) {
Kenichi Ishibashid379dea2021-04-08 11:21:27357 return true;
Solomon Kinardab293bae2024-09-19 17:13:51358 }
Kenichi Ishibashid379dea2021-04-08 11:21:27359 return false;
360 }
361
362 void MaybeCompletePreload() {
363 if (CanCompletePreload()) {
364 // Delete `this`.
Ali Hijazid87307d2022-11-07 20:15:03365 owner_->OnPreloadComplete(url_, result_);
Kenichi Ishibashid379dea2021-04-08 11:21:27366 }
367 }
368
Ali Hijazid87307d2022-11-07 20:15:03369 const raw_ref<NavigationEarlyHintsManager> owner_;
Kenichi Ishibashid379dea2021-04-08 11:21:27370 const GURL url_;
371
372 PreloadedResource result_;
373 std::unique_ptr<mojo::DataPipeDrainer> response_body_drainer_;
374};
375
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05376NavigationEarlyHintsManager::NavigationEarlyHintsManager(
377 BrowserContext& browser_context,
Kenichi Ishibashi3d2627be2021-09-02 12:23:17378 StoragePartition& storage_partition,
Avi Drissman580a3da62024-09-04 16:16:56379 FrameTreeNodeId frame_tree_node_id,
Kenichi Ishibashi1f67a2d2021-09-02 01:36:48380 NavigationEarlyHintsManagerParams params)
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05381 : browser_context_(browser_context),
Kenichi Ishibashi3d2627be2021-09-02 12:23:17382 storage_partition_(storage_partition),
Kenichi Ishibashi1f67a2d2021-09-02 01:36:48383 frame_tree_node_id_(frame_tree_node_id),
384 loader_factory_(std::move(params.loader_factory)),
385 origin_(params.origin),
386 isolation_info_(std::move(params.isolation_info)) {
Kenichi Ishibashif3f80522021-07-16 02:37:07387 shared_loader_factory_ =
388 base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
389 loader_factory_.get());
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05390}
391
392NavigationEarlyHintsManager::~NavigationEarlyHintsManager() = default;
393
394void NavigationEarlyHintsManager::HandleEarlyHints(
395 network::mojom::EarlyHintsPtr early_hints,
Kenichi Ishibashic4db0692022-03-07 06:04:11396 const network::ResourceRequest& request_for_navigation) {
Kenichi Ishibashi50840fb92022-03-21 13:50:16397 // Ignore the second and subsequent responses to avoid situations where
398 // policies such as CSP are inconsistent among the first and following
Kenichi Ishibashiaf9a4252022-11-22 07:27:52399 // responses. This behavior is specified by the step 19.5 of
400 // https://html.spec.whatwg.org/multipage/browsing-the-web.html#create-navigation-params-by-fetching
Kenichi Ishibashi598c75572023-09-28 06:20:16401 if (first_early_hints_receive_time_) {
Kenichi Ishibashi50840fb92022-03-21 13:50:16402 return;
Kenichi Ishibashi598c75572023-09-28 06:20:16403 }
Kenichi Ishibashi50840fb92022-03-21 13:50:16404
Kenichi Ishibashi598c75572023-09-28 06:20:16405 first_early_hints_receive_time_ = base::TimeTicks::Now();
Kenichi Ishibashi50840fb92022-03-21 13:50:16406
Kenichi Ishibashi0b56b612022-03-09 01:16:54407 net::ReferrerPolicy referrer_policy =
408 Referrer::ReferrerPolicyForUrlRequest(early_hints->referrer_policy);
Kenichi Ishibashi523272d2021-07-30 10:05:15409
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05410 for (const auto& link : early_hints->headers->link_headers) {
Alison Gale3a3959162024-04-29 18:52:44411 // TODO(crbug.com/40496584): Support other `rel` attributes.
Kenichi Ishibashi3d2627be2021-09-02 12:23:17412 if (link->rel == network::mojom::LinkRelAttribute::kPreconnect) {
Kenichi Ishibashi97b5f4b02022-06-20 07:55:07413 MaybePreconnect(link);
Kenichi Ishibashi3d2627be2021-09-02 12:23:17414 } else if (link->rel == network::mojom::LinkRelAttribute::kPreload ||
415 link->rel == network::mojom::LinkRelAttribute::kModulePreload) {
Kenichi Ishibashi2f5317b2022-03-17 10:49:10416 MaybePreloadHintedResource(link, request_for_navigation,
417 early_hints->headers->content_security_policy,
Kenichi Ishibashi97b5f4b02022-06-20 07:55:07418 referrer_policy);
Kenichi Ishibashid3e17ca2021-05-17 04:38:52419 }
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05420 }
421}
422
Kenichi Ishibashi784dd402021-10-15 00:19:24423bool NavigationEarlyHintsManager::WasResourceHintsReceived() const {
Kenichi Ishibashi97b5f4b02022-06-20 07:55:07424 return was_resource_hints_received_;
Kenichi Ishibashicf248fb2021-04-07 22:29:35425}
426
Kenichi Ishibashi17ecfb62021-06-21 03:31:15427std::vector<GURL> NavigationEarlyHintsManager::TakePreloadedResourceURLs() {
428 return std::move(preloaded_urls_);
429}
430
Kenichi Ishibashi03cb6bf2021-05-18 08:16:57431bool NavigationEarlyHintsManager::HasInflightPreloads() const {
432 return inflight_preloads_.size() > 0;
433}
434
Kenichi Ishibashia3138052021-04-07 12:54:59435void NavigationEarlyHintsManager::WaitForPreloadsFinishedForTesting(
436 base::OnceCallback<void(PreloadedResources)> callback) {
437 DCHECK(!preloads_completion_callback_for_testing_);
Solomon Kinardab293bae2024-09-19 17:13:51438 if (inflight_preloads_.empty()) {
Kenichi Ishibashia3138052021-04-07 12:54:59439 std::move(callback).Run(preloaded_resources_);
Solomon Kinardab293bae2024-09-19 17:13:51440 } else {
Kenichi Ishibashia3138052021-04-07 12:54:59441 preloads_completion_callback_for_testing_ = std::move(callback);
Solomon Kinardab293bae2024-09-19 17:13:51442 }
Kenichi Ishibashia3138052021-04-07 12:54:59443}
444
Kenichi Ishibashi3d2627be2021-09-02 12:23:17445void NavigationEarlyHintsManager::SetNetworkContextForTesting(
446 network::mojom::NetworkContext* network_context) {
447 DCHECK(!network_context_for_testing_);
448 DCHECK(network_context);
449 network_context_for_testing_ = network_context;
450}
451
452network::mojom::NetworkContext*
453NavigationEarlyHintsManager::GetNetworkContext() {
Solomon Kinardab293bae2024-09-19 17:13:51454 if (network_context_for_testing_) {
Kenichi Ishibashi3d2627be2021-09-02 12:23:17455 return network_context_for_testing_;
Solomon Kinardab293bae2024-09-19 17:13:51456 }
Kenichi Ishibashi3d2627be2021-09-02 12:23:17457
Ali Hijazid87307d2022-11-07 20:15:03458 return storage_partition_->GetNetworkContext();
Kenichi Ishibashi3d2627be2021-09-02 12:23:17459}
460
Kenichi Ishibashi3d2627be2021-09-02 12:23:17461void NavigationEarlyHintsManager::MaybePreconnect(
Kenichi Ishibashi97b5f4b02022-06-20 07:55:07462 const network::mojom::LinkHeaderPtr& link) {
Kenichi Ishibashi85479ea2021-10-07 06:34:37463 was_resource_hints_received_ = true;
464
Solomon Kinardab293bae2024-09-19 17:13:51465 if (!ShouldHandleResourceHints(link)) {
Kenichi Ishibashi3d2627be2021-09-02 12:23:17466 return;
Solomon Kinardab293bae2024-09-19 17:13:51467 }
Kenichi Ishibashi3d2627be2021-09-02 12:23:17468
469 PreconnectEntry entry(url::Origin::Create(link->href), link->cross_origin);
Solomon Kinardab293bae2024-09-19 17:13:51470 if (preconnect_entries_.contains(entry)) {
Kenichi Ishibashi3d2627be2021-09-02 12:23:17471 return;
Solomon Kinardab293bae2024-09-19 17:13:51472 }
Kenichi Ishibashi3d2627be2021-09-02 12:23:17473
474 network::mojom::NetworkContext* network_context = GetNetworkContext();
Solomon Kinardab293bae2024-09-19 17:13:51475 if (!network_context) {
Kenichi Ishibashi3d2627be2021-09-02 12:23:17476 return;
Solomon Kinardab293bae2024-09-19 17:13:51477 }
Kenichi Ishibashi3d2627be2021-09-02 12:23:17478
479 bool allow_credentials =
480 link->cross_origin != network::mojom::CrossOriginAttribute::kAnonymous;
Brianna Goldsteind22b0642022-10-11 16:30:50481 network_context->PreconnectSockets(
James Lee806fe91e2024-02-13 13:54:21482 /*num_streams=*/1, link->href,
483 allow_credentials ? network::mojom::CredentialsMode::kInclude
484 : network::mojom::CredentialsMode::kOmit,
Keita Suzuki84c59522024-11-09 01:17:35485 isolation_info_.network_anonymization_key(),
486 net::MutableNetworkTrafficAnnotationTag(
Keita Suzuki2c837712025-04-16 06:09:17487 kEarlyHintsPreloadTrafficAnnotation),
Keita Suzuki71387af2025-04-16 13:03:25488 /*keepalive_config=*/std::nullopt, mojo::NullRemote());
Kenichi Ishibashi3d2627be2021-09-02 12:23:17489 preconnect_entries_.insert(std::move(entry));
490}
491
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05492void NavigationEarlyHintsManager::MaybePreloadHintedResource(
493 const network::mojom::LinkHeaderPtr& link,
Kenichi Ishibashic4db0692022-03-07 06:04:11494 const network::ResourceRequest& request_for_navigation,
Kenichi Ishibashi2f5317b2022-03-17 10:49:10495 const std::vector<network::mojom::ContentSecurityPolicyPtr>&
496 content_security_policies,
Kenichi Ishibashi97b5f4b02022-06-20 07:55:07497 net::ReferrerPolicy referrer_policy) {
David Bokan98aabfe92022-04-14 02:10:12498 DCHECK(request_for_navigation.is_outermost_main_frame);
Kenichi Ishibashic4db0692022-03-07 06:04:11499 DCHECK(request_for_navigation.url.SchemeIsHTTPOrHTTPS());
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05500
Kenichi Ishibashi85479ea2021-10-07 06:34:37501 was_resource_hints_received_ = true;
Kenichi Ishibashicf248fb2021-04-07 22:29:35502
Solomon Kinardab293bae2024-09-19 17:13:51503 if (!ShouldHandleResourceHints(link)) {
Kenichi Ishibashicf248fb2021-04-07 22:29:35504 return;
Solomon Kinardab293bae2024-09-19 17:13:51505 }
Kenichi Ishibashicf248fb2021-04-07 22:29:35506
Kenichi Ishibashi461474e2022-11-22 09:56:18507 // Step 2. If options's destination is not a destination, then return null.
508 // https://html.spec.whatwg.org/multipage/semantics.html#create-a-link-request
Arthur Sonzognic686e8f2024-01-11 08:36:37509 std::optional<network::mojom::RequestDestination> destination =
Kenichi Ishibashid18285c2023-01-30 04:11:26510 LinkAsAttributeToRequestDestination(link);
511 if (!destination) {
Kenichi Ishibashi461474e2022-11-22 09:56:18512 return;
Kenichi Ishibashid18285c2023-01-30 04:11:26513 }
Kenichi Ishibashi461474e2022-11-22 09:56:18514
Solomon Kinardab293bae2024-09-19 17:13:51515 if (!CheckContentSecurityPolicyForPreload(link, content_security_policies)) {
Kenichi Ishibashi2f5317b2022-03-17 10:49:10516 return;
Solomon Kinardab293bae2024-09-19 17:13:51517 }
Kenichi Ishibashi2f5317b2022-03-17 10:49:10518
Kenichi Ishibashi3d2627be2021-09-02 12:23:17519 if (inflight_preloads_.contains(link->href) ||
520 preloaded_resources_.contains(link->href)) {
521 return;
522 }
523
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05524 auto preload_origin = url::Origin::Create(link->href);
525
526 net::SiteForCookies site_for_cookies =
Kenichi Ishibashif3f80522021-07-16 02:37:07527 net::SiteForCookies::FromOrigin(origin_);
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05528 network::ResourceRequest request;
529 request.method = net::HttpRequestHeaders::kGetMethod;
530 request.priority = CalculateRequestPriority(link);
Kenichi Ishibashid18285c2023-01-30 04:11:26531 request.destination = *destination;
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05532 request.url = link->href;
533 request.site_for_cookies = site_for_cookies;
Kenichi Ishibashif3f80522021-07-16 02:37:07534 request.request_initiator = origin_;
Kenichi Ishibashifc2dd873d2021-06-10 23:46:42535 request.referrer = net::URLRequestJob::ComputeReferrerForPolicy(
Kenichi Ishibashi0b56b612022-03-09 01:16:54536 referrer_policy, request_for_navigation.url, request.url);
537 request.referrer_policy = referrer_policy;
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05538 request.load_flags = net::LOAD_NORMAL;
539 request.resource_type =
540 static_cast<int>(blink::mojom::ResourceType::kSubResource);
Kenichi Ishibashid3e17ca2021-05-17 04:38:52541 request.mode = CalculateRequestMode(link);
542 request.credentials_mode = CalculateCredentialsMode(link);
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05543
Kenichi Ishibashia9247992022-07-23 01:09:17544 blink::network_utils::SetAcceptHeader(request.headers, request.destination);
545
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05546 std::vector<std::unique_ptr<blink::URLLoaderThrottle>> throttles =
547 CreateContentBrowserURLLoaderThrottles(
Ali Hijazid87307d2022-11-07 20:15:03548 request, &*browser_context_,
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05549 base::BindRepeating(&WebContents::FromFrameTreeNodeId,
550 frame_tree_node_id_),
Xinghui Lueddfce32024-01-11 19:32:16551 /*navigation_ui_data=*/nullptr, frame_tree_node_id_,
Arthur Sonzognie5fff99c2024-02-21 15:58:24552 /*navigation_id=*/std::nullopt);
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05553
Kenichi Ishibashi11fc3a82021-12-12 08:32:15554 auto loader_client = std::make_unique<PreloadURLLoaderClient>(*this, request);
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05555 auto loader = blink::ThrottlingURLLoader::CreateLoaderAndStart(
Kenichi Ishibashif3f80522021-07-16 02:37:07556 shared_loader_factory_, std::move(throttles),
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05557 content::GlobalRequestID::MakeBrowserInitiated().request_id,
Kenichi Ishibashid379dea2021-04-08 11:21:27558 network::mojom::kURLLoadOptionNone, &request, loader_client.get(),
Sean Maher5b9af51f2022-11-21 15:32:47559 kEarlyHintsPreloadTrafficAnnotation,
560 base::SingleThreadTaskRunner::GetCurrentDefault());
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05561
562 inflight_preloads_[request.url] = std::make_unique<InflightPreload>(
563 std::move(loader), std::move(loader_client));
Kenichi Ishibashi17ecfb62021-06-21 03:31:15564
565 preloaded_urls_.push_back(request.url);
Kenichi Ishibashi523272d2021-07-30 10:05:15566}
567
Kenichi Ishibashi3d2627be2021-09-02 12:23:17568bool NavigationEarlyHintsManager::ShouldHandleResourceHints(
Kenichi Ishibashi97b5f4b02022-06-20 07:55:07569 const network::mojom::LinkHeaderPtr& link) {
Solomon Kinardab293bae2024-09-19 17:13:51570 if (!link->href.SchemeIsHTTPOrHTTPS()) {
Kenichi Ishibashi523272d2021-07-30 10:05:15571 return false;
Solomon Kinardab293bae2024-09-19 17:13:51572 }
Kenichi Ishibashi523272d2021-07-30 10:05:15573 return true;
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05574}
575
576void NavigationEarlyHintsManager::OnPreloadComplete(
577 const GURL& url,
Kenichi Ishibashid379dea2021-04-08 11:21:27578 const PreloadedResource& result) {
579 DCHECK(inflight_preloads_.contains(url));
580 DCHECK(!preloaded_resources_.contains(url));
581 preloaded_resources_[url] = result;
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05582 inflight_preloads_.erase(url);
Kenichi Ishibashia3138052021-04-07 12:54:59583
584 if (inflight_preloads_.empty() && preloads_completion_callback_for_testing_) {
585 std::move(preloads_completion_callback_for_testing_)
586 .Run(preloaded_resources_);
587 }
588
Alison Gale3a3959162024-04-29 18:52:44589 // TODO(crbug.com/40496584): Consider to delete `this` when there is no
590 // inflight preloads.
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05591}
592
Patrick Meenancbaedf62023-02-09 21:16:39593// Used to determine a priority for a speculative subresource request.
Alison Gale3a3959162024-04-29 18:52:44594// TODO(crbug.com/40496584): This is almost the same as GetRequestPriority() in
Patrick Meenancbaedf62023-02-09 21:16:39595// loading_predictor_tab_helper.cc and the purpose is the same. Consider merging
596// them if the logic starts to be more mature.
Patrick Meenan71113c22023-02-15 15:30:47597// platform/loader/fetch/README.md in blink contains more details on
598// prioritization as well as links to all of the relevant places in the code
599// where priority is determined. If the priority logic is updated here, be sure
600// to update the other code as needed.
Patrick Meenancbaedf62023-02-09 21:16:39601net::RequestPriority NavigationEarlyHintsManager::CalculateRequestPriority(
602 const network::mojom::LinkHeaderPtr& link) {
603 // When fetchPriority is explicitly specified for preload, independent of
604 // most content types, the blink priority matches the fetchpriority value.
605 // In net priority terms that maps to MEDIUM for "high" LOWEST for "low".
606 // https://web.dev/priority-hints/#browser-priority-and-fetchpriority
607 switch (link->fetch_priority) {
608 case network::mojom::FetchPriorityAttribute::kHigh:
609 switch (link->as) {
Patrick Meenancbaedf62023-02-09 21:16:39610 case network::mojom::LinkAsAttribute::kStyleSheet:
611 return net::HIGHEST;
612 default:
613 return net::MEDIUM;
614 }
615 case network::mojom::FetchPriorityAttribute::kLow:
616 return net::LOWEST;
617 case network::mojom::FetchPriorityAttribute::kAuto:
618 switch (link->as) {
Patrick Meenancbaedf62023-02-09 21:16:39619 case network::mojom::LinkAsAttribute::kStyleSheet:
620 return net::HIGHEST;
Patrick Meenan71113c22023-02-15 15:30:47621 case network::mojom::LinkAsAttribute::kFont:
Patrick Meenancbaedf62023-02-09 21:16:39622 case network::mojom::LinkAsAttribute::kScript:
623 return net::MEDIUM;
624 case network::mojom::LinkAsAttribute::kImage:
625 case network::mojom::LinkAsAttribute::kFetch:
626 return net::LOWEST;
627 case network::mojom::LinkAsAttribute::kUnspecified:
628 return net::IDLE;
629 }
630 }
Peter Boström01ab59a2024-08-15 02:39:49631 NOTREACHED();
Patrick Meenancbaedf62023-02-09 21:16:39632}
633
Kenichi Ishibashi7eb8cf62021-04-07 12:35:05634} // namespace content