| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/loader/navigation_early_hints_manager.h" |
| |
| #include "base/memory/raw_ref.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/global_request_id.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/browser/url_loader_throttles.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/referrer.h" |
| #include "mojo/public/cpp/bindings/message.h" |
| #include "mojo/public/cpp/system/data_pipe_drainer.h" |
| #include "net/base/load_flags.h" |
| #include "net/base/schemeful_site.h" |
| #include "net/traffic_annotation/network_traffic_annotation.h" |
| #include "net/url_request/url_request_job.h" |
| #include "services/network/public/cpp/content_security_policy/content_security_policy.h" |
| #include "services/network/public/cpp/content_security_policy/csp_source_list.h" |
| #include "services/network/public/cpp/record_ontransfersizeupdate_utils.h" |
| #include "services/network/public/cpp/resource_request.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" |
| #include "services/network/public/mojom/connection_change_observer_client.mojom.h" |
| #include "services/network/public/mojom/content_security_policy.mojom-shared.h" |
| #include "services/network/public/mojom/fetch_api.mojom.h" |
| #include "services/network/public/mojom/network_context.mojom.h" |
| #include "services/network/public/mojom/url_loader.mojom.h" |
| #include "services/network/public/mojom/url_response_head.mojom.h" |
| #include "third_party/blink/public/common/loader/network_utils.h" |
| #include "third_party/blink/public/common/loader/throttling_url_loader.h" |
| #include "third_party/blink/public/common/loader/url_loader_throttle.h" |
| #include "third_party/blink/public/mojom/loader/resource_load_info.mojom-shared.h" |
| #include "url/origin.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| const net::NetworkTrafficAnnotationTag kEarlyHintsPreloadTrafficAnnotation = |
| net::DefineNetworkTrafficAnnotation("early_hints_preload", |
| R"( |
| semantics { |
| sender: "Early Hints" |
| description: |
| "This request is issued during a main frame navigation to " |
| "speculatively fetch resources that will likely be used in the frame." |
| trigger: |
| "A 103 Early Hints HTTP informational response is received during " |
| "navigation." |
| data: |
| "Arbitrary site-controlled data can be included in the URL." |
| "Requests may include cookies and site-specific credentials." |
| destination: WEBSITE |
| } |
| policy { |
| cookies_allowed: YES |
| cookies_store: "user" |
| setting: |
| "This feature cannot be disabled by Settings. This feature is not " |
| "enabled by default yet. TODO(crbug.com/40496584): Update this " |
| "description once the feature is ready." |
| chrome_policy { |
| URLBlocklist { |
| URLBlocklist: { entries: '*' } |
| } |
| } |
| chrome_policy { |
| URLAllowlist { |
| URLAllowlist { } |
| } |
| } |
| } |
| comments: |
| "Chrome uses this type of request during navigation and it cannot be " |
| "disabled. Using either URLBlocklist or URLAllowlist (or a combination " |
| "of both) limits the scope of these requests." |
| )"); |
| |
| network::mojom::CSPDirectiveName LinkAsAttributeToCSPDirective( |
| network::mojom::LinkAsAttribute attr) { |
| // https://w3c.github.io/webappsec-csp/#csp-directives |
| switch (attr) { |
| case network::mojom::LinkAsAttribute::kUnspecified: |
| return network::mojom::CSPDirectiveName::Unknown; |
| case network::mojom::LinkAsAttribute::kImage: |
| return network::mojom::CSPDirectiveName::ImgSrc; |
| case network::mojom::LinkAsAttribute::kFont: |
| return network::mojom::CSPDirectiveName::FontSrc; |
| case network::mojom::LinkAsAttribute::kScript: |
| return network::mojom::CSPDirectiveName::ScriptSrcElem; |
| case network::mojom::LinkAsAttribute::kStyleSheet: |
| return network::mojom::CSPDirectiveName::StyleSrcElem; |
| case network::mojom::LinkAsAttribute::kFetch: |
| return network::mojom::CSPDirectiveName::ConnectSrc; |
| } |
| NOTREACHED(); |
| } |
| |
| bool CheckContentSecurityPolicyForPreload( |
| const network::mojom::LinkHeaderPtr& link, |
| const std::vector<network::mojom::ContentSecurityPolicyPtr>& |
| content_security_policies) { |
| DCHECK(link->rel == network::mojom::LinkRelAttribute::kPreload || |
| link->rel == network::mojom::LinkRelAttribute::kModulePreload); |
| |
| network::mojom::CSPDirectiveName directive = |
| LinkAsAttributeToCSPDirective(link->as); |
| |
| for (network::mojom::CSPDirectiveName effective_directive = directive; |
| effective_directive != network::mojom::CSPDirectiveName::Unknown; |
| effective_directive = |
| network::CSPFallbackDirective(effective_directive, directive)) { |
| for (auto& policy : content_security_policies) { |
| const auto& it = policy->directives.find(effective_directive); |
| if (it == policy->directives.end()) { |
| continue; |
| } |
| |
| if (!network::CheckCSPSourceList(directive, *it->second, link->href, |
| *(policy->self_origin), |
| /*has_followed_redirect=*/false, |
| /*is_opaque_fenced_frame=*/false)) { |
| // TODO(crbug.com/40218207): Report CSP violation once the final |
| // response is received. |
| return false; |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| std::optional<network::mojom::RequestDestination> |
| LinkAsAttributeToRequestDestination(const network::mojom::LinkHeaderPtr& link) { |
| // https://fetch.spec.whatwg.org/#concept-potential-destination-translate |
| switch (link->as) { |
| case network::mojom::LinkAsAttribute::kUnspecified: |
| // For modulepreload, the request destination should be "script" when `as` |
| // is not specified. |
| // https://html.spec.whatwg.org/multipage/links.html#link-type-modulepreload |
| if (link->rel == network::mojom::LinkRelAttribute::kModulePreload) { |
| return network::mojom::RequestDestination::kScript; |
| } |
| return std::nullopt; |
| case network::mojom::LinkAsAttribute::kImage: |
| return network::mojom::RequestDestination::kImage; |
| case network::mojom::LinkAsAttribute::kFont: |
| return network::mojom::RequestDestination::kFont; |
| case network::mojom::LinkAsAttribute::kScript: |
| return network::mojom::RequestDestination::kScript; |
| case network::mojom::LinkAsAttribute::kStyleSheet: |
| return network::mojom::RequestDestination::kStyle; |
| case network::mojom::LinkAsAttribute::kFetch: |
| return network::mojom::RequestDestination::kEmpty; |
| } |
| } |
| |
| network::mojom::RequestMode CalculateRequestMode( |
| const network::mojom::LinkHeaderPtr& link) { |
| if (link->rel == network::mojom::LinkRelAttribute::kModulePreload) { |
| // When fetching a module script, mode is always "cors". |
| // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-single-module-script |
| return network::mojom::RequestMode::kCors; |
| } |
| |
| switch (link->cross_origin) { |
| case network::mojom::CrossOriginAttribute::kUnspecified: |
| return network::mojom::RequestMode::kNoCors; |
| case network::mojom::CrossOriginAttribute::kAnonymous: |
| case network::mojom::CrossOriginAttribute::kUseCredentials: |
| return network::mojom::RequestMode::kCors; |
| } |
| NOTREACHED(); |
| } |
| |
| network::mojom::CredentialsMode CalculateCredentialsMode( |
| const network::mojom::LinkHeaderPtr& link) { |
| switch (link->cross_origin) { |
| case network::mojom::CrossOriginAttribute::kUnspecified: |
| // For modulepreload credentials mode should be "same-origin" when |
| // `cross-origin` is not specified. |
| if (link->rel == network::mojom::LinkRelAttribute::kModulePreload) { |
| return network::mojom::CredentialsMode::kSameOrigin; |
| } else { |
| return network::mojom::CredentialsMode::kInclude; |
| } |
| case network::mojom::CrossOriginAttribute::kUseCredentials: |
| return network::mojom::CredentialsMode::kInclude; |
| case network::mojom::CrossOriginAttribute::kAnonymous: |
| return network::mojom::CredentialsMode::kSameOrigin; |
| } |
| NOTREACHED(); |
| } |
| |
| } // namespace |
| |
| NavigationEarlyHintsManagerParams::NavigationEarlyHintsManagerParams( |
| const url::Origin& origin, |
| net::IsolationInfo isolation_info, |
| mojo::Remote<network::mojom::URLLoaderFactory> loader_factory) |
| : origin(origin), |
| isolation_info(std::move(isolation_info)), |
| loader_factory(std::move(loader_factory)) {} |
| |
| NavigationEarlyHintsManagerParams::~NavigationEarlyHintsManagerParams() = |
| default; |
| |
| NavigationEarlyHintsManagerParams::NavigationEarlyHintsManagerParams( |
| NavigationEarlyHintsManagerParams&&) = default; |
| |
| NavigationEarlyHintsManagerParams& NavigationEarlyHintsManagerParams::operator=( |
| NavigationEarlyHintsManagerParams&&) = default; |
| |
| // Represents a preconnect. |
| struct NavigationEarlyHintsManager::PreconnectEntry { |
| PreconnectEntry(const url::Origin& origin, |
| network::mojom::CrossOriginAttribute cross_origin); |
| ~PreconnectEntry(); |
| PreconnectEntry(const PreconnectEntry&); |
| PreconnectEntry& operator=(const PreconnectEntry&); |
| |
| bool operator==(const PreconnectEntry&); |
| bool operator<(const PreconnectEntry&) const; |
| |
| url::Origin origin; |
| network::mojom::CrossOriginAttribute cross_origin; |
| }; |
| |
| NavigationEarlyHintsManager::PreconnectEntry::PreconnectEntry( |
| const url::Origin& origin, |
| network::mojom::CrossOriginAttribute cross_origin) |
| : origin(origin), cross_origin(cross_origin) {} |
| |
| NavigationEarlyHintsManager::PreconnectEntry::~PreconnectEntry() = default; |
| |
| NavigationEarlyHintsManager::PreconnectEntry::PreconnectEntry( |
| const PreconnectEntry&) = default; |
| |
| NavigationEarlyHintsManager::PreconnectEntry& |
| NavigationEarlyHintsManager::PreconnectEntry::operator=( |
| const PreconnectEntry&) = default; |
| |
| bool NavigationEarlyHintsManager::PreconnectEntry::operator==( |
| const PreconnectEntry& other) { |
| return origin == other.origin && cross_origin == other.cross_origin; |
| } |
| |
| bool NavigationEarlyHintsManager::PreconnectEntry::operator<( |
| const PreconnectEntry& other) const { |
| if (origin == other.origin) { |
| return cross_origin < other.cross_origin; |
| } |
| return origin < other.origin; |
| } |
| |
| NavigationEarlyHintsManager::PreloadedResource::PreloadedResource() = default; |
| |
| NavigationEarlyHintsManager::PreloadedResource::~PreloadedResource() = default; |
| |
| NavigationEarlyHintsManager::PreloadedResource::PreloadedResource( |
| const PreloadedResource&) = default; |
| |
| NavigationEarlyHintsManager::PreloadedResource& |
| NavigationEarlyHintsManager::PreloadedResource::operator=( |
| const PreloadedResource&) = default; |
| |
| NavigationEarlyHintsManager::InflightPreload::InflightPreload( |
| std::unique_ptr<blink::ThrottlingURLLoader> loader, |
| std::unique_ptr<PreloadURLLoaderClient> client) |
| : client(std::move(client)), loader(std::move(loader)) {} |
| |
| NavigationEarlyHintsManager::InflightPreload::~InflightPreload() = default; |
| |
| // A URLLoaderClient which drains the content of a request to put a |
| // response into the disk cache. If the response was already in the cache, |
| // this tries to cancel reading body to avoid further disk access. |
| class NavigationEarlyHintsManager::PreloadURLLoaderClient |
| : public network::mojom::URLLoaderClient, |
| public mojo::DataPipeDrainer::Client { |
| public: |
| PreloadURLLoaderClient(NavigationEarlyHintsManager& owner, |
| const network::ResourceRequest& request) |
| : owner_(owner), url_(request.url) {} |
| |
| ~PreloadURLLoaderClient() override = default; |
| |
| PreloadURLLoaderClient(const PreloadURLLoaderClient&) = delete; |
| PreloadURLLoaderClient& operator=(const PreloadURLLoaderClient&) = delete; |
| PreloadURLLoaderClient(PreloadURLLoaderClient&&) = delete; |
| PreloadURLLoaderClient& operator=(PreloadURLLoaderClient&&) = delete; |
| |
| private: |
| // mojom::URLLoaderClient overrides: |
| void OnReceiveEarlyHints(network::mojom::EarlyHintsPtr early_hints) override { |
| } |
| void OnReceiveResponse( |
| network::mojom::URLResponseHeadPtr head, |
| mojo::ScopedDataPipeConsumerHandle body, |
| std::optional<mojo_base::BigBuffer> cached_metadata) override { |
| if (!head->network_accessed && head->was_fetched_via_cache) { |
| // Cancel the client since the response is already stored in the cache. |
| result_.was_canceled = true; |
| MaybeCompletePreload(); |
| return; |
| } |
| |
| if (!body) { |
| return; |
| } |
| |
| if (response_body_drainer_) { |
| mojo::ReportBadMessage("NEHM_BAD_RESPONSE_BODY"); |
| return; |
| } |
| response_body_drainer_ = |
| std::make_unique<mojo::DataPipeDrainer>(this, std::move(body)); |
| } |
| void OnReceiveRedirect(const net::RedirectInfo& redirect_info, |
| network::mojom::URLResponseHeadPtr head) override {} |
| void OnUploadProgress(int64_t current_position, |
| int64_t total_size, |
| OnUploadProgressCallback callback) override { |
| NOTREACHED(); |
| } |
| void OnTransferSizeUpdated(int32_t transfer_size_diff) override { |
| network::RecordOnTransferSizeUpdatedUMA( |
| network::OnTransferSizeUpdatedFrom::kPreloadURLLoaderClient); |
| } |
| void OnComplete(const network::URLLoaderCompletionStatus& status) override { |
| if (result_.was_canceled || result_.error_code.has_value()) { |
| mojo::ReportBadMessage("NEHM_BAD_COMPLETE"); |
| return; |
| } |
| result_.error_code = status.error_code; |
| result_.cors_error_status = status.cors_error_status; |
| MaybeCompletePreload(); |
| } |
| |
| // mojo::DataPipeDrainer::Client overrides: |
| void OnDataAvailable(base::span<const uint8_t> data) override {} |
| void OnDataComplete() override { |
| DCHECK(response_body_drainer_); |
| response_body_drainer_.reset(); |
| MaybeCompletePreload(); |
| } |
| |
| bool CanCompletePreload() { |
| if (result_.was_canceled) { |
| return true; |
| } |
| if (result_.error_code.has_value() && !response_body_drainer_) { |
| return true; |
| } |
| return false; |
| } |
| |
| void MaybeCompletePreload() { |
| if (CanCompletePreload()) { |
| // Delete `this`. |
| owner_->OnPreloadComplete(url_, result_); |
| } |
| } |
| |
| const raw_ref<NavigationEarlyHintsManager> owner_; |
| const GURL url_; |
| |
| PreloadedResource result_; |
| std::unique_ptr<mojo::DataPipeDrainer> response_body_drainer_; |
| }; |
| |
| NavigationEarlyHintsManager::NavigationEarlyHintsManager( |
| BrowserContext& browser_context, |
| StoragePartition& storage_partition, |
| FrameTreeNodeId frame_tree_node_id, |
| NavigationEarlyHintsManagerParams params) |
| : browser_context_(browser_context), |
| storage_partition_(storage_partition), |
| frame_tree_node_id_(frame_tree_node_id), |
| loader_factory_(std::move(params.loader_factory)), |
| origin_(params.origin), |
| isolation_info_(std::move(params.isolation_info)) { |
| shared_loader_factory_ = |
| base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( |
| loader_factory_.get()); |
| } |
| |
| NavigationEarlyHintsManager::~NavigationEarlyHintsManager() = default; |
| |
| void NavigationEarlyHintsManager::HandleEarlyHints( |
| network::mojom::EarlyHintsPtr early_hints, |
| const network::ResourceRequest& request_for_navigation) { |
| // Ignore the second and subsequent responses to avoid situations where |
| // policies such as CSP are inconsistent among the first and following |
| // responses. This behavior is specified by the step 19.5 of |
| // https://html.spec.whatwg.org/multipage/browsing-the-web.html#create-navigation-params-by-fetching |
| if (first_early_hints_receive_time_) { |
| return; |
| } |
| |
| first_early_hints_receive_time_ = base::TimeTicks::Now(); |
| |
| net::ReferrerPolicy referrer_policy = |
| Referrer::ReferrerPolicyForUrlRequest(early_hints->referrer_policy); |
| |
| for (const auto& link : early_hints->headers->link_headers) { |
| // TODO(crbug.com/40496584): Support other `rel` attributes. |
| if (link->rel == network::mojom::LinkRelAttribute::kPreconnect) { |
| MaybePreconnect(link); |
| } else if (link->rel == network::mojom::LinkRelAttribute::kPreload || |
| link->rel == network::mojom::LinkRelAttribute::kModulePreload) { |
| MaybePreloadHintedResource(link, request_for_navigation, |
| early_hints->headers->content_security_policy, |
| referrer_policy); |
| } |
| } |
| } |
| |
| bool NavigationEarlyHintsManager::WasResourceHintsReceived() const { |
| return was_resource_hints_received_; |
| } |
| |
| std::vector<GURL> NavigationEarlyHintsManager::TakePreloadedResourceURLs() { |
| return std::move(preloaded_urls_); |
| } |
| |
| bool NavigationEarlyHintsManager::HasInflightPreloads() const { |
| return inflight_preloads_.size() > 0; |
| } |
| |
| void NavigationEarlyHintsManager::WaitForPreloadsFinishedForTesting( |
| base::OnceCallback<void(PreloadedResources)> callback) { |
| DCHECK(!preloads_completion_callback_for_testing_); |
| if (inflight_preloads_.empty()) { |
| std::move(callback).Run(preloaded_resources_); |
| } else { |
| preloads_completion_callback_for_testing_ = std::move(callback); |
| } |
| } |
| |
| void NavigationEarlyHintsManager::SetNetworkContextForTesting( |
| network::mojom::NetworkContext* network_context) { |
| DCHECK(!network_context_for_testing_); |
| DCHECK(network_context); |
| network_context_for_testing_ = network_context; |
| } |
| |
| network::mojom::NetworkContext* |
| NavigationEarlyHintsManager::GetNetworkContext() { |
| if (network_context_for_testing_) { |
| return network_context_for_testing_; |
| } |
| |
| return storage_partition_->GetNetworkContext(); |
| } |
| |
| void NavigationEarlyHintsManager::MaybePreconnect( |
| const network::mojom::LinkHeaderPtr& link) { |
| was_resource_hints_received_ = true; |
| |
| if (!ShouldHandleResourceHints(link)) { |
| return; |
| } |
| |
| PreconnectEntry entry(url::Origin::Create(link->href), link->cross_origin); |
| if (preconnect_entries_.contains(entry)) { |
| return; |
| } |
| |
| network::mojom::NetworkContext* network_context = GetNetworkContext(); |
| if (!network_context) { |
| return; |
| } |
| |
| bool allow_credentials = |
| link->cross_origin != network::mojom::CrossOriginAttribute::kAnonymous; |
| network_context->PreconnectSockets( |
| /*num_streams=*/1, link->href, |
| allow_credentials ? network::mojom::CredentialsMode::kInclude |
| : network::mojom::CredentialsMode::kOmit, |
| isolation_info_.network_anonymization_key(), |
| net::MutableNetworkTrafficAnnotationTag( |
| kEarlyHintsPreloadTrafficAnnotation), |
| /*keepalive_config=*/std::nullopt, mojo::NullRemote()); |
| preconnect_entries_.insert(std::move(entry)); |
| } |
| |
| void NavigationEarlyHintsManager::MaybePreloadHintedResource( |
| const network::mojom::LinkHeaderPtr& link, |
| const network::ResourceRequest& request_for_navigation, |
| const std::vector<network::mojom::ContentSecurityPolicyPtr>& |
| content_security_policies, |
| net::ReferrerPolicy referrer_policy) { |
| DCHECK(request_for_navigation.is_outermost_main_frame); |
| DCHECK(request_for_navigation.url.SchemeIsHTTPOrHTTPS()); |
| |
| was_resource_hints_received_ = true; |
| |
| if (!ShouldHandleResourceHints(link)) { |
| return; |
| } |
| |
| // Step 2. If options's destination is not a destination, then return null. |
| // https://html.spec.whatwg.org/multipage/semantics.html#create-a-link-request |
| std::optional<network::mojom::RequestDestination> destination = |
| LinkAsAttributeToRequestDestination(link); |
| if (!destination) { |
| return; |
| } |
| |
| if (!CheckContentSecurityPolicyForPreload(link, content_security_policies)) { |
| return; |
| } |
| |
| if (inflight_preloads_.contains(link->href) || |
| preloaded_resources_.contains(link->href)) { |
| return; |
| } |
| |
| auto preload_origin = url::Origin::Create(link->href); |
| |
| net::SiteForCookies site_for_cookies = |
| net::SiteForCookies::FromOrigin(origin_); |
| network::ResourceRequest request; |
| request.method = net::HttpRequestHeaders::kGetMethod; |
| request.priority = CalculateRequestPriority(link); |
| request.destination = *destination; |
| request.url = link->href; |
| request.site_for_cookies = site_for_cookies; |
| request.request_initiator = origin_; |
| request.referrer = net::URLRequestJob::ComputeReferrerForPolicy( |
| referrer_policy, request_for_navigation.url, request.url); |
| request.referrer_policy = referrer_policy; |
| request.load_flags = net::LOAD_NORMAL; |
| request.resource_type = |
| static_cast<int>(blink::mojom::ResourceType::kSubResource); |
| request.mode = CalculateRequestMode(link); |
| request.credentials_mode = CalculateCredentialsMode(link); |
| |
| blink::network_utils::SetAcceptHeader(request.headers, request.destination); |
| |
| std::vector<std::unique_ptr<blink::URLLoaderThrottle>> throttles = |
| CreateContentBrowserURLLoaderThrottles( |
| request, &*browser_context_, |
| base::BindRepeating(&WebContents::FromFrameTreeNodeId, |
| frame_tree_node_id_), |
| /*navigation_ui_data=*/nullptr, frame_tree_node_id_, |
| /*navigation_id=*/std::nullopt); |
| |
| auto loader_client = std::make_unique<PreloadURLLoaderClient>(*this, request); |
| auto loader = blink::ThrottlingURLLoader::CreateLoaderAndStart( |
| shared_loader_factory_, std::move(throttles), |
| content::GlobalRequestID::MakeBrowserInitiated().request_id, |
| network::mojom::kURLLoadOptionNone, &request, loader_client.get(), |
| kEarlyHintsPreloadTrafficAnnotation, |
| base::SingleThreadTaskRunner::GetCurrentDefault()); |
| |
| inflight_preloads_[request.url] = std::make_unique<InflightPreload>( |
| std::move(loader), std::move(loader_client)); |
| |
| preloaded_urls_.push_back(request.url); |
| } |
| |
| bool NavigationEarlyHintsManager::ShouldHandleResourceHints( |
| const network::mojom::LinkHeaderPtr& link) { |
| if (!link->href.SchemeIsHTTPOrHTTPS()) { |
| return false; |
| } |
| return true; |
| } |
| |
| void NavigationEarlyHintsManager::OnPreloadComplete( |
| const GURL& url, |
| const PreloadedResource& result) { |
| DCHECK(inflight_preloads_.contains(url)); |
| DCHECK(!preloaded_resources_.contains(url)); |
| preloaded_resources_[url] = result; |
| inflight_preloads_.erase(url); |
| |
| if (inflight_preloads_.empty() && preloads_completion_callback_for_testing_) { |
| std::move(preloads_completion_callback_for_testing_) |
| .Run(preloaded_resources_); |
| } |
| |
| // TODO(crbug.com/40496584): Consider to delete `this` when there is no |
| // inflight preloads. |
| } |
| |
| // Used to determine a priority for a speculative subresource request. |
| // TODO(crbug.com/40496584): This is almost the same as GetRequestPriority() in |
| // loading_predictor_tab_helper.cc and the purpose is the same. Consider merging |
| // them if the logic starts to be more mature. |
| // platform/loader/fetch/README.md in blink contains more details on |
| // prioritization as well as links to all of the relevant places in the code |
| // where priority is determined. If the priority logic is updated here, be sure |
| // to update the other code as needed. |
| net::RequestPriority NavigationEarlyHintsManager::CalculateRequestPriority( |
| const network::mojom::LinkHeaderPtr& link) { |
| // When fetchPriority is explicitly specified for preload, independent of |
| // most content types, the blink priority matches the fetchpriority value. |
| // In net priority terms that maps to MEDIUM for "high" LOWEST for "low". |
| // https://web.dev/priority-hints/#browser-priority-and-fetchpriority |
| switch (link->fetch_priority) { |
| case network::mojom::FetchPriorityAttribute::kHigh: |
| switch (link->as) { |
| case network::mojom::LinkAsAttribute::kStyleSheet: |
| return net::HIGHEST; |
| default: |
| return net::MEDIUM; |
| } |
| case network::mojom::FetchPriorityAttribute::kLow: |
| return net::LOWEST; |
| case network::mojom::FetchPriorityAttribute::kAuto: |
| switch (link->as) { |
| case network::mojom::LinkAsAttribute::kStyleSheet: |
| return net::HIGHEST; |
| case network::mojom::LinkAsAttribute::kFont: |
| case network::mojom::LinkAsAttribute::kScript: |
| return net::MEDIUM; |
| case network::mojom::LinkAsAttribute::kImage: |
| case network::mojom::LinkAsAttribute::kFetch: |
| return net::LOWEST; |
| case network::mojom::LinkAsAttribute::kUnspecified: |
| return net::IDLE; |
| } |
| } |
| NOTREACHED(); |
| } |
| |
| } // namespace content |