| // Copyright 2022 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/preloading/prefetch/prefetch_service.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <optional> |
| #include <string_view> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/auto_reset.h" |
| #include "base/barrier_closure.h" |
| #include "base/containers/fixed_flat_set.h" |
| #include "base/feature_list.h" |
| #include "base/location.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/timer/timer.h" |
| #include "content/browser/browser_context_impl.h" |
| #include "content/browser/devtools/render_frame_devtools_agent_host.h" |
| #include "content/browser/preloading/prefetch/no_vary_search_helper.h" |
| #include "content/browser/preloading/prefetch/prefetch_container.h" |
| #include "content/browser/preloading/prefetch/prefetch_document_manager.h" |
| #include "content/browser/preloading/prefetch/prefetch_features.h" |
| #include "content/browser/preloading/prefetch/prefetch_handle_impl.h" |
| #include "content/browser/preloading/prefetch/prefetch_match_resolver.h" |
| #include "content/browser/preloading/prefetch/prefetch_network_context.h" |
| #include "content/browser/preloading/prefetch/prefetch_origin_prober.h" |
| #include "content/browser/preloading/prefetch/prefetch_params.h" |
| #include "content/browser/preloading/prefetch/prefetch_proxy_configurator.h" |
| #include "content/browser/preloading/prefetch/prefetch_response_reader.h" |
| #include "content/browser/preloading/prefetch/prefetch_status.h" |
| #include "content/browser/preloading/prefetch/prefetch_streaming_url_loader.h" |
| #include "content/browser/preloading/prefetch/proxy_lookup_client_impl.h" |
| #include "content/browser/preloading/preloading_attempt_impl.h" |
| #include "content/browser/preloading/prerender/prerender_features.h" |
| #include "content/browser/renderer_host/frame_tree_node.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/frame_accept_header.h" |
| #include "content/public/browser/prefetch_service_delegate.h" |
| #include "content/public/browser/preloading.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/service_worker_context.h" |
| #include "content/public/browser/spare_render_process_host_manager.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/browser/url_loader_request_interceptor.h" |
| #include "content/public/browser/visibility.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/content_constants.h" |
| #include "net/base/load_flags.h" |
| #include "net/base/url_util.h" |
| #include "net/cookies/canonical_cookie.h" |
| #include "net/cookies/cookie_partition_key_collection.h" |
| #include "net/cookies/site_for_cookies.h" |
| #include "net/http/http_no_vary_search_data.h" |
| #include "net/http/http_request_headers.h" |
| #include "net/http/http_status_code.h" |
| #include "net/http/http_util.h" |
| #include "net/traffic_annotation/network_traffic_annotation.h" |
| #include "services/network/public/cpp/devtools_observer_util.h" |
| #include "services/network/public/cpp/is_potentially_trustworthy.h" |
| #include "services/network/public/cpp/resource_request.h" |
| #include "services/network/public/mojom/cookie_manager.mojom.h" |
| #include "services/network/public/mojom/devtools_observer.mojom.h" |
| #include "services/network/public/mojom/url_response_head.mojom-shared.h" |
| #include "services/network/public/mojom/url_response_head.mojom.h" |
| #include "third_party/blink/public/common/loader/referrer_utils.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| #include "url/url_constants.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| static ServiceWorkerContext* g_service_worker_context_for_testing = nullptr; |
| |
| bool (*g_host_non_unique_filter)(std::string_view) = nullptr; |
| |
| static network::SharedURLLoaderFactory* g_url_loader_factory_for_testing = |
| nullptr; |
| |
| static network::mojom::NetworkContext* |
| g_network_context_for_proxy_lookup_for_testing = nullptr; |
| |
| PrefetchService::DelayEligibilityCheckForTesting& |
| GetDelayEligibilityCheckForTesting() { |
| static base::NoDestructor<PrefetchService::DelayEligibilityCheckForTesting> |
| prefetch_delay_eligibility_check_for_testing; |
| return *prefetch_delay_eligibility_check_for_testing; |
| } |
| |
| std::optional<PreloadingEligibility>& GetForceIneligibilityForTesting() { |
| static std::optional<PreloadingEligibility> |
| prefetch_force_ineligibility_for_testing; |
| return prefetch_force_ineligibility_for_testing; |
| } |
| |
| bool ShouldConsiderDecoyRequestForStatus(PreloadingEligibility eligibility) { |
| switch (eligibility) { |
| case PreloadingEligibility::kUserHasCookies: |
| case PreloadingEligibility::kUserHasServiceWorker: |
| case PreloadingEligibility::kUserHasServiceWorkerNoFetchHandler: |
| case PreloadingEligibility::kRedirectFromServiceWorker: |
| case PreloadingEligibility::kRedirectToServiceWorker: |
| // If the prefetch is not eligible because of cookie or a service worker, |
| // then maybe send a decoy. |
| return true; |
| case PreloadingEligibility::kBatterySaverEnabled: |
| case PreloadingEligibility::kDataSaverEnabled: |
| case PreloadingEligibility::kExistingProxy: |
| case PreloadingEligibility::kHostIsNonUnique: |
| case PreloadingEligibility::kNonDefaultStoragePartition: |
| case PreloadingEligibility::kPrefetchProxyNotAvailable: |
| case PreloadingEligibility::kPreloadingDisabled: |
| case PreloadingEligibility::kRetryAfter: |
| case PreloadingEligibility::kSameSiteCrossOriginPrefetchRequiredProxy: |
| case PreloadingEligibility::kSchemeIsNotHttps: |
| // These statuses don't relate to any user state, so don't send a decoy |
| // request. |
| return false; |
| case PreloadingEligibility::kEligible: |
| default: |
| // Other ineligible cases are not used in `PrefetchService`. |
| NOTREACHED(); |
| } |
| } |
| |
| bool ShouldStartSpareRenderer() { |
| if (!PrefetchStartsSpareRenderer()) { |
| return false; |
| } |
| |
| for (RenderProcessHost::iterator iter(RenderProcessHost::AllHostsIterator()); |
| !iter.IsAtEnd(); iter.Advance()) { |
| if (iter.GetCurrentValue()->IsUnused()) { |
| // There is already a spare renderer. |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| void RecordPrefetchProxyPrefetchMainframeTotalTime( |
| network::mojom::URLResponseHead* head) { |
| DCHECK(head); |
| |
| base::Time start = head->request_time; |
| base::Time end = head->response_time; |
| |
| if (start.is_null() || end.is_null()) { |
| return; |
| } |
| |
| UMA_HISTOGRAM_CUSTOM_TIMES("PrefetchProxy.Prefetch.Mainframe.TotalTime", |
| end - start, base::Milliseconds(10), |
| base::Seconds(30), 100); |
| } |
| |
| void RecordPrefetchProxyPrefetchMainframeConnectTime( |
| network::mojom::URLResponseHead* head) { |
| DCHECK(head); |
| |
| base::TimeTicks start = head->load_timing.connect_timing.connect_start; |
| base::TimeTicks end = head->load_timing.connect_timing.connect_end; |
| |
| if (start.is_null() || end.is_null()) { |
| return; |
| } |
| |
| UMA_HISTOGRAM_TIMES("PrefetchProxy.Prefetch.Mainframe.ConnectTime", |
| end - start); |
| } |
| |
| void RecordPrefetchProxyPrefetchMainframeRespCode(int response_code) { |
| base::UmaHistogramSparse("PrefetchProxy.Prefetch.Mainframe.RespCode", |
| response_code); |
| } |
| |
| void RecordPrefetchProxyPrefetchMainframeCookiesToCopy( |
| size_t cookie_list_size) { |
| UMA_HISTOGRAM_COUNTS_100("PrefetchProxy.Prefetch.Mainframe.CookiesToCopy", |
| cookie_list_size); |
| } |
| |
| void CookieSetHelper(base::RepeatingClosure closure, |
| net::CookieAccessResult access_result) { |
| closure.Run(); |
| } |
| |
| // Returns true if the prefetch is heldback, and set the holdback status |
| // correspondingly. |
| bool CheckAndSetPrefetchHoldbackStatus( |
| base::WeakPtr<PrefetchContainer> prefetch_container) { |
| if (!prefetch_container->HasPreloadingAttempt()) { |
| return false; |
| } |
| |
| bool devtools_client_exist = [&] { |
| // Currently DevTools only supports when the prefetch is initiated by |
| // renderer. |
| if (!prefetch_container->IsRendererInitiated()) { |
| return false; |
| } |
| RenderFrameHostImpl* initiator_rfh = RenderFrameHostImpl::FromID( |
| prefetch_container->GetReferringRenderFrameHostId()); |
| return initiator_rfh && |
| RenderFrameDevToolsAgentHost::GetFor(initiator_rfh) != nullptr; |
| }(); |
| |
| // Normally PreloadingAttemptImpl::ShouldHoldback() eventually computes its |
| // `holdback_status_`, but we forcely set the status in two special cases |
| // below, by calling PreloadingAttemptImpl::SetHoldbackStatus(). |
| // As its comment describes, this is expected to be called only once. |
| |
| if (devtools_client_exist) { |
| // 1. When developers debug Speculation Rules Prefetch using DevTools, |
| // always set status to kAllowed for developer experience. |
| prefetch_container->preloading_attempt()->SetHoldbackStatus( |
| PreloadingHoldbackStatus::kAllowed); |
| } else if (prefetch_container->HasOverriddenHoldbackStatus()) { |
| // 2. If PrefetchContainer has custom overridden status, set that value. |
| prefetch_container->preloading_attempt()->SetHoldbackStatus( |
| prefetch_container->GetOverriddenHoldbackStatus()); |
| } |
| |
| if (prefetch_container->preloading_attempt()->ShouldHoldback()) { |
| prefetch_container->SetLoadState( |
| PrefetchContainer::LoadState::kFailedHeldback); |
| prefetch_container->SetPrefetchStatus(PrefetchStatus::kPrefetchHeldback); |
| return true; |
| } |
| return false; |
| } |
| |
| BrowserContext* BrowserContextFromFrameTreeNodeId( |
| FrameTreeNodeId frame_tree_node_id) { |
| WebContents* web_content = |
| WebContents::FromFrameTreeNodeId(frame_tree_node_id); |
| if (!web_content) { |
| return nullptr; |
| } |
| return web_content->GetBrowserContext(); |
| } |
| |
| void RecordRedirectResult(PrefetchRedirectResult result) { |
| UMA_HISTOGRAM_ENUMERATION("PrefetchProxy.Redirect.Result", result); |
| } |
| |
| void RecordRedirectNetworkContextTransition( |
| bool previous_requires_isolated_network_context, |
| bool redirect_requires_isolated_network_context) { |
| PrefetchRedirectNetworkContextTransition transition; |
| if (!previous_requires_isolated_network_context && |
| !redirect_requires_isolated_network_context) { |
| transition = PrefetchRedirectNetworkContextTransition::kDefaultToDefault; |
| } |
| if (!previous_requires_isolated_network_context && |
| redirect_requires_isolated_network_context) { |
| transition = PrefetchRedirectNetworkContextTransition::kDefaultToIsolated; |
| } |
| if (previous_requires_isolated_network_context && |
| !redirect_requires_isolated_network_context) { |
| transition = PrefetchRedirectNetworkContextTransition::kIsolatedToDefault; |
| } |
| if (previous_requires_isolated_network_context && |
| redirect_requires_isolated_network_context) { |
| transition = PrefetchRedirectNetworkContextTransition::kIsolatedToIsolated; |
| } |
| |
| UMA_HISTOGRAM_ENUMERATION( |
| "PrefetchProxy.Redirect.NetworkContextStateTransition", transition); |
| } |
| |
| void OnIsolatedCookieCopyComplete(PrefetchContainer::Reader reader) { |
| if (reader) { |
| reader.OnIsolatedCookieCopyComplete(); |
| } |
| } |
| |
| bool IsReferrerPolicySufficientlyStrict( |
| const network::mojom::ReferrerPolicy& referrer_policy) { |
| // https://github.com/WICG/nav-speculation/blob/main/prefetch.bs#L606 |
| // "", "`strict-origin-when-cross-origin`", "`strict-origin`", |
| // "`same-origin`", "`no-referrer`". |
| switch (referrer_policy) { |
| case network::mojom::ReferrerPolicy::kDefault: |
| case network::mojom::ReferrerPolicy::kStrictOriginWhenCrossOrigin: |
| case network::mojom::ReferrerPolicy::kSameOrigin: |
| case network::mojom::ReferrerPolicy::kStrictOrigin: |
| return true; |
| case network::mojom::ReferrerPolicy::kAlways: |
| case network::mojom::ReferrerPolicy::kNoReferrerWhenDowngrade: |
| case network::mojom::ReferrerPolicy::kNever: |
| case network::mojom::ReferrerPolicy::kOrigin: |
| case network::mojom::ReferrerPolicy::kOriginWhenCrossOrigin: |
| return false; |
| } |
| } |
| |
| } // namespace |
| |
| // static |
| PrefetchService* PrefetchService::GetFromFrameTreeNodeId( |
| FrameTreeNodeId frame_tree_node_id) { |
| BrowserContext* browser_context = |
| BrowserContextFromFrameTreeNodeId(frame_tree_node_id); |
| if (!browser_context) { |
| return nullptr; |
| } |
| return BrowserContextImpl::From(browser_context)->GetPrefetchService(); |
| } |
| |
| void PrefetchService::SetFromFrameTreeNodeIdForTesting( |
| FrameTreeNodeId frame_tree_node_id, |
| std::unique_ptr<PrefetchService> prefetch_service) { |
| BrowserContext* browser_context = |
| BrowserContextFromFrameTreeNodeId(frame_tree_node_id); |
| CHECK(browser_context); |
| return BrowserContextImpl::From(browser_context) |
| ->SetPrefetchServiceForTesting(std::move(prefetch_service)); // IN-TEST |
| } |
| |
| PrefetchService::PrefetchService(BrowserContext* browser_context) |
| : browser_context_(browser_context), |
| delegate_(GetContentClient()->browser()->CreatePrefetchServiceDelegate( |
| browser_context_)), |
| prefetch_proxy_configurator_( |
| PrefetchProxyConfigurator::MaybeCreatePrefetchProxyConfigurator( |
| PrefetchProxyHost(delegate_ |
| ? delegate_->GetDefaultPrefetchProxyHost() |
| : GURL("")), |
| delegate_ ? delegate_->GetAPIKey() : "")), |
| origin_prober_(std::make_unique<PrefetchOriginProber>( |
| browser_context_, |
| PrefetchDNSCanaryCheckURL( |
| delegate_ ? delegate_->GetDefaultDNSCanaryCheckURL() : GURL("")), |
| PrefetchTLSCanaryCheckURL( |
| delegate_ ? delegate_->GetDefaultTLSCanaryCheckURL() |
| : GURL("")))) {} |
| |
| PrefetchService::~PrefetchService() = default; |
| |
| void PrefetchService::SetPrefetchServiceDelegateForTesting( |
| std::unique_ptr<PrefetchServiceDelegate> delegate) { |
| DCHECK(!delegate_); |
| delegate_ = std::move(delegate); |
| } |
| |
| PrefetchOriginProber* PrefetchService::GetPrefetchOriginProber() const { |
| return origin_prober_.get(); |
| } |
| |
| void PrefetchService::AddPrefetchContainerWithoutStartingPrefetch( |
| std::unique_ptr<PrefetchContainer> owned_prefetch_container) { |
| base::WeakPtr<PrefetchContainer> prefetch_container = |
| owned_prefetch_container->GetWeakPtr(); |
| DCHECK(prefetch_container); |
| auto prefetch_container_key = prefetch_container->key(); |
| |
| RecordExistingPrefetchWithMatchingURL(prefetch_container); |
| |
| enum class Action { |
| kTakeOldWithMigration, |
| kReplaceOldWithNew, |
| kTakeNew, |
| }; |
| |
| // The comment below might be old. Currently, |
| // `PrefetchDocumentManager::PrefetchUrl()` reject colficting prefetches |
| // execpt for ahead of prerender case. |
| // |
| // TODO(crbug.com/371179869): Integrate these two processes. |
| // |
| // A newly submitted prefetch could already be in |owned_prefetches_| if and |
| // only if: |
| // 1) There was a same origin navigaition that used the same renderer. |
| // 2) Both pages requested a prefetch for the same URL. |
| // 3) The prefetch from the first page had at least started its network |
| // request (which would mean that it is in |owned_prefetches_| and owned |
| // by the prefetch service). |
| // If this happens, then we just delete the old prefetch and add the new |
| // prefetch to |owned_prefetches_|. |
| // |
| // Note that we might replace this by preserving existing prefetch and |
| // additional works, e.g. adding some properties to the old one and prolonging |
| // cacheable duration, to prevent additional fetch. See also |
| // https://chromium-review.googlesource.com/c/chromium/src/+/3880874/comment/5ecccbf7_8fbcba96/ |
| // |
| // TODO(crbug.com/372186548): Revisit the merging process and comments here |
| // and below. |
| auto prefetch_iter = owned_prefetches_.find(prefetch_container_key); |
| Action action = [&]() { |
| if (prefetch_iter == owned_prefetches_.end()) { |
| return Action::kTakeNew; |
| } |
| PrefetchContainer& prefetch_container_old = *prefetch_iter->second; |
| |
| if (!base::FeatureList::IsEnabled( |
| features::kPrerender2FallbackPrefetchSpecRules)) { |
| return Action::kReplaceOldWithNew; |
| } |
| |
| switch ( |
| prefetch_container_old.GetServableState(PrefetchCacheableDuration())) { |
| case PrefetchContainer::ServableState::kNotServable: |
| return Action::kReplaceOldWithNew; |
| case PrefetchContainer::ServableState::kShouldBlockUntilEligibilityGot: |
| case PrefetchContainer::ServableState::kShouldBlockUntilHeadReceived: |
| case PrefetchContainer::ServableState::kServable: |
| // nop |
| break; |
| } |
| |
| // Take preload pipeline info of prefetch ahead of prerender. |
| // |
| // Consider the following screnario (especially, in the same SpecRules and |
| // upgrading ): |
| // |
| // - A document adds SpecRules of prefetch A for URL X. |
| // - A document adds SpecRules of prerender B' for URL X. |
| // |
| // With `kPrerender2FallbackPrefetchSpecRules`, B' triggers prefetch ahead |
| // of prerender B for URL X. Sites use SpecRules A+B' with expectation |
| // "prefetch X then prerender X", but the order of |
| // `PrefetchService::AddPrefetchContainer*()` for A and B is unstable in |
| // general. |
| // |
| // `PrerenderHost` of B' needs to know eligibility and status of B. We use |
| // `PreloadPipelineInfo` for this purpose. |
| // |
| // - If A is followed by B, take A and migrate B into A. A inherits |
| // `PreloadPipelineInfo` of B. |
| // - If B is followed by A, just reject A (by |
| // `PrefetchDocumentManager::PrefetchUrl()`). |
| // |
| // See also tests `PrerendererImplBrowserTestPrefetchAhead.*`. |
| |
| return Action::kTakeOldWithMigration; |
| }(); |
| |
| switch (action) { |
| case Action::kTakeOldWithMigration: |
| prefetch_iter->second->MigrateNewlyAdded( |
| std::move(owned_prefetch_container)); |
| break; |
| case Action::kReplaceOldWithNew: |
| ResetPrefetchContainer(prefetch_iter->second->GetWeakPtr()); |
| owned_prefetches_[prefetch_container_key] = |
| std::move(owned_prefetch_container); |
| break; |
| case Action::kTakeNew: |
| owned_prefetches_[prefetch_container_key] = |
| std::move(owned_prefetch_container); |
| break; |
| } |
| } |
| |
| bool PrefetchService::IsPrefetchDuplicate( |
| GURL& url, |
| std::optional<net::HttpNoVarySearchData> no_vary_search_hint) { |
| TRACE_EVENT0("loading", "PrefetchService::IsPrefetchDuplicate"); |
| for (const auto& [key, prefetch_container] : owned_prefetches_) { |
| if (IsPrefetchStale(prefetch_container->GetWeakPtr())) { |
| continue; |
| } |
| |
| // We will only compare the URLs if the no-vary-search hints match for |
| // determinism. This is because comparing URLs with different no-vary-search |
| // hints will change the outcome of the comparison based on the order the |
| // requests happened in. |
| // |
| // This approach optimizes for determinism over minimizing wasted |
| // or redundant prefetches. |
| bool nvs_hints_match = |
| no_vary_search_hint == prefetch_container->GetNoVarySearchHint(); |
| if (!nvs_hints_match) { |
| continue; |
| } |
| |
| bool urls_equal; |
| if (no_vary_search_hint) { |
| urls_equal = no_vary_search_hint->AreEquivalent(url, key.url()); |
| } else { |
| // If there is no no-vary-search hint, just compare the URLs. |
| urls_equal = url == key.url(); |
| } |
| |
| if (!urls_equal) { |
| continue; |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| bool PrefetchService::IsPrefetchAttemptFailedOrDiscardedInternal( |
| base::PassKey<PrefetchDocumentManager>, |
| PrefetchContainer::Key key) const { |
| auto it = owned_prefetches_.find(key); |
| if (it == owned_prefetches_.end() || !it->second) { |
| return true; |
| } |
| |
| const std::unique_ptr<PrefetchContainer>& container = it->second; |
| if (!container->HasPrefetchStatus()) { |
| return false; // the container is not processed yet |
| } |
| |
| switch (container->GetPrefetchStatus()) { |
| case PrefetchStatus::kPrefetchSuccessful: |
| case PrefetchStatus::kPrefetchResponseUsed: |
| return false; |
| case PrefetchStatus::kPrefetchIneligibleUserHasCookies: |
| case PrefetchStatus::kPrefetchIneligibleUserHasServiceWorker: |
| case PrefetchStatus::kPrefetchIneligibleUserHasServiceWorkerNoFetchHandler: |
| case PrefetchStatus::kPrefetchIneligibleRedirectFromServiceWorker: |
| case PrefetchStatus::kPrefetchIneligibleRedirectToServiceWorker: |
| case PrefetchStatus::kPrefetchIneligibleSchemeIsNotHttps: |
| case PrefetchStatus::kPrefetchIneligibleNonDefaultStoragePartition: |
| case PrefetchStatus::kPrefetchIneligibleRetryAfter: |
| case PrefetchStatus::kPrefetchIneligiblePrefetchProxyNotAvailable: |
| case PrefetchStatus::kPrefetchIneligibleHostIsNonUnique: |
| case PrefetchStatus::kPrefetchIneligibleDataSaverEnabled: |
| case PrefetchStatus::kPrefetchIneligibleBatterySaverEnabled: |
| case PrefetchStatus::kPrefetchIneligiblePreloadingDisabled: |
| case PrefetchStatus::kPrefetchIneligibleExistingProxy: |
| case PrefetchStatus::kPrefetchIsStale: |
| case PrefetchStatus::kPrefetchNotUsedProbeFailed: |
| case PrefetchStatus::kPrefetchNotStarted: |
| case PrefetchStatus::kPrefetchNotFinishedInTime: |
| case PrefetchStatus::kPrefetchFailedNetError: |
| case PrefetchStatus::kPrefetchFailedNon2XX: |
| case PrefetchStatus::kPrefetchFailedMIMENotSupported: |
| case PrefetchStatus::kPrefetchIsPrivacyDecoy: |
| case PrefetchStatus::kPrefetchNotUsedCookiesChanged: |
| case PrefetchStatus::kPrefetchHeldback: |
| case PrefetchStatus::kPrefetchFailedInvalidRedirect: |
| case PrefetchStatus::kPrefetchFailedIneligibleRedirect: |
| case PrefetchStatus:: |
| kPrefetchIneligibleSameSiteCrossOriginPrefetchRequiredProxy: |
| case PrefetchStatus::kPrefetchEvictedAfterCandidateRemoved: |
| case PrefetchStatus::kPrefetchEvictedForNewerPrefetch: |
| case PrefetchStatus::kPrefetchEvictedAfterBrowsingDataRemoved: |
| return true; |
| } |
| } |
| |
| bool PrefetchService::IsPrefetchStale( |
| base::WeakPtr<PrefetchContainer> prefetch_container) { |
| TRACE_EVENT0("loading", "PrefetchService::IsPrefetchStale"); |
| if (!prefetch_container) { |
| return true; |
| } |
| |
| // `PrefetchContainer::LoadState` check. |
| PrefetchContainer::LoadState load_state = prefetch_container->GetLoadState(); |
| if (load_state == PrefetchContainer::LoadState::kFailedIneligible || |
| load_state == PrefetchContainer::LoadState::kFailedHeldback) { |
| return true; |
| } |
| |
| // `PrefetchContainer::ServableState` check. |
| PrefetchContainer::ServableState servable_state = |
| prefetch_container->GetServableState(PrefetchCacheableDuration()); |
| if (servable_state == PrefetchContainer::ServableState::kNotServable) { |
| return true; |
| } |
| return false; |
| } |
| |
| std::unique_ptr<PrefetchHandle> PrefetchService::AddPrefetchContainerWithHandle( |
| std::unique_ptr<PrefetchContainer> owned_prefetch_container) { |
| base::WeakPtr<PrefetchContainer> prefetch_container = |
| owned_prefetch_container->GetWeakPtr(); |
| AddPrefetchContainerWithoutStartingPrefetch( |
| std::move(owned_prefetch_container)); |
| |
| if (prefetch_container) { |
| PrefetchUrl(prefetch_container); |
| } |
| |
| return std::make_unique<PrefetchHandleImpl>(GetWeakPtr(), prefetch_container); |
| } |
| |
| void PrefetchService::AddPrefetchContainerWithoutStartingPrefetchForTesting( |
| std::unique_ptr<PrefetchContainer> prefetch_container) { |
| AddPrefetchContainerWithoutStartingPrefetch(std::move(prefetch_container)); |
| } |
| |
| void PrefetchService::PrefetchUrl( |
| base::WeakPtr<PrefetchContainer> prefetch_container) { |
| CHECK(prefetch_container); |
| TRACE_EVENT0("loading", "PrefetchService::PrefetchUrl"); |
| |
| if (delegate_) { |
| // If pre* actions are disabled then don't prefetch. |
| switch (delegate_->IsSomePreloadingEnabled()) { |
| case PreloadingEligibility::kEligible: |
| break; |
| case PreloadingEligibility::kDataSaverEnabled: |
| OnGotEligibilityForNonRedirect( |
| std::move(prefetch_container), |
| PreloadingEligibility::kDataSaverEnabled); |
| return; |
| case PreloadingEligibility::kBatterySaverEnabled: |
| OnGotEligibilityForNonRedirect( |
| std::move(prefetch_container), |
| PreloadingEligibility::kBatterySaverEnabled); |
| return; |
| case PreloadingEligibility::kPreloadingDisabled: |
| OnGotEligibilityForNonRedirect( |
| std::move(prefetch_container), |
| PreloadingEligibility::kPreloadingDisabled); |
| return; |
| default: |
| DVLOG(1) << *prefetch_container |
| << ": not prefetched (PrefetchServiceDelegate)"; |
| return; |
| } |
| |
| const auto& prefetch_type = prefetch_container->GetPrefetchType(); |
| if (prefetch_type.IsProxyRequiredWhenCrossOrigin()) { |
| bool allow_all_domains = |
| PrefetchAllowAllDomains() || |
| (PrefetchAllowAllDomainsForExtendedPreloading() && |
| delegate_->IsExtendedPreloadingEnabled()); |
| if (!allow_all_domains && |
| prefetch_container->GetReferringOrigin().has_value() && |
| !delegate_->IsDomainInPrefetchAllowList( |
| prefetch_container->GetReferringOrigin().value().GetURL())) { |
| DVLOG(1) << *prefetch_container |
| << ": not prefetched (not in allow list)"; |
| return; |
| } |
| } |
| |
| // TODO(crbug.com/40946257): Current code doesn't support PageLoadMetrics |
| // when the prefetch is initiated by browser. |
| if (prefetch_container->IsRendererInitiated()) { |
| if (auto* rfh = RenderFrameHost::FromID( |
| prefetch_container->GetReferringRenderFrameHostId())) { |
| if (auto* web_contents = WebContents::FromRenderFrameHost(rfh)) { |
| delegate_->OnPrefetchLikely(web_contents); |
| } |
| } |
| } |
| } |
| |
| // Note that this is initial URL of the prefetch. So, this is immutable over |
| // `PrefetchContainer`'s lifetime. And it is alive until the call of |
| // `CheckHasServiceWorker()`. |
| const GURL& url = prefetch_container->GetURL(); |
| |
| if (GetDelayEligibilityCheckForTesting()) { |
| GetDelayEligibilityCheckForTesting().Run( // IN-TEST |
| base::BindOnce(&PrefetchService::CheckEligibilityOfPrefetch, |
| weak_method_factory_.GetWeakPtr(), |
| std::move(prefetch_container), url, |
| /*redirect_data=*/std::nullopt)); |
| return; |
| } |
| |
| CheckEligibilityOfPrefetch(std::move(prefetch_container), url, |
| /*redirect_data=*/std::nullopt); |
| } |
| |
| void PrefetchService::CheckEligibilityOfPrefetch( |
| base::WeakPtr<PrefetchContainer> prefetch_container, |
| const GURL& url, |
| std::optional< |
| std::pair<net::RedirectInfo, network::mojom::URLResponseHeadPtr>> |
| redirect_data) { |
| CHECK(prefetch_container); |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("loading", |
| "PrefetchService::CheckEligibility", this); |
| |
| // Inject failure in tests. |
| if (GetForceIneligibilityForTesting().has_value()) { |
| OnGotEligibility(std::move(prefetch_container), std::move(redirect_data), |
| GetForceIneligibilityForTesting().value() // IN-TEST |
| ); |
| return; |
| } |
| |
| // TODO(crbug.com/40215782): Clean up the following checks by: 1) |
| // moving each check to a separate function, and 2) requiring that failed |
| // checks provide a PrefetchStatus related to the check. |
| |
| // While a registry-controlled domain could still resolve to a non-publicly |
| // routable IP, this allows hosts which are very unlikely to work via the |
| // proxy to be discarded immediately. |
| // |
| // Conditions on the outer-most if block: |
| // Host-uniqueness check is only applied to proxied prefetches, where that |
| // matters. Also, we bypass the check for the test hosts, since we run the |
| // test web servers on the localhost or private networks, where the check |
| // fails. |
| if (prefetch_container->IsProxyRequiredForURL(url) && |
| !ShouldPrefetchBypassProxyForTestHost(url.host())) { |
| bool is_host_non_unique = |
| g_host_non_unique_filter |
| ? g_host_non_unique_filter(url.HostNoBrackets()) |
| : net::IsHostnameNonUnique(url.HostNoBrackets()); |
| if (is_host_non_unique) { |
| OnGotEligibility(std::move(prefetch_container), std::move(redirect_data), |
| PreloadingEligibility::kHostIsNonUnique); |
| return; |
| } |
| } |
| |
| // Only HTTP(S) URLs which are believed to be secure are eligible. |
| // For proxied prefetches, we only want HTTPS URLs. |
| // For non-proxied prefetches, other URLs (notably localhost HTTP) is also |
| // acceptable. This is common during development. |
| const bool is_secure_http = prefetch_container->IsProxyRequiredForURL(url) |
| ? url.SchemeIs(url::kHttpsScheme) |
| : (url.SchemeIsHTTPOrHTTPS() && |
| network::IsUrlPotentiallyTrustworthy(url)); |
| if (!is_secure_http) { |
| OnGotEligibility(std::move(prefetch_container), std::move(redirect_data), |
| PreloadingEligibility::kSchemeIsNotHttps); |
| return; |
| } |
| |
| // Fail the prefetch (or more precisely, PrefetchContainer::SinglePrefetch) |
| // early if it is going to go through a proxy, and we know that it is not |
| // available. |
| if (prefetch_container->IsProxyRequiredForURL(url) && |
| !ShouldPrefetchBypassProxyForTestHost(url.host()) && |
| (!prefetch_proxy_configurator_ || |
| !prefetch_proxy_configurator_->IsPrefetchProxyAvailable())) { |
| OnGotEligibility(std::move(prefetch_container), std::move(redirect_data), |
| PreloadingEligibility::kPrefetchProxyNotAvailable); |
| return; |
| } |
| |
| // Only the default storage partition is supported since that is where we |
| // check for service workers and existing cookies. |
| StoragePartition* default_storage_partition = |
| browser_context_->GetDefaultStoragePartition(); |
| if (default_storage_partition != |
| browser_context_->GetStoragePartitionForUrl(url, |
| /*can_create=*/false)) { |
| OnGotEligibility(std::move(prefetch_container), std::move(redirect_data), |
| PreloadingEligibility::kNonDefaultStoragePartition); |
| return; |
| } |
| |
| // If we have recently received a "retry-after" for the origin, then don't |
| // send new prefetches. |
| if (delegate_ && !delegate_->IsOriginOutsideRetryAfterWindow(url)) { |
| OnGotEligibility(std::move(prefetch_container), std::move(redirect_data), |
| PreloadingEligibility::kRetryAfter); |
| return; |
| } |
| |
| CheckHasServiceWorker(std::move(prefetch_container), url, |
| std::move(redirect_data)); |
| } |
| |
| void PrefetchService::CheckHasServiceWorker( |
| base::WeakPtr<PrefetchContainer> prefetch_container, |
| const GURL& url, |
| std::optional< |
| std::pair<net::RedirectInfo, network::mojom::URLResponseHeadPtr>> |
| redirect_data) { |
| CHECK(prefetch_container); |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN0( |
| "loading", "PrefetchService::CheckHasServiceWorker", this); |
| |
| if (redirect_data) { |
| switch (prefetch_container->service_worker_state()) { |
| case PrefetchServiceWorkerState::kDisallowed: |
| break; |
| |
| case PrefetchServiceWorkerState::kAllowed: |
| // Should have been transitioned out already. |
| NOTREACHED(); |
| |
| case PrefetchServiceWorkerState::kControlled: |
| // Currently we disallow redirects from ServiceWorker-controlled |
| // prefetches. |
| OnGotEligibility(std::move(prefetch_container), |
| std::move(redirect_data), |
| PreloadingEligibility::kRedirectFromServiceWorker); |
| return; |
| } |
| } else { |
| switch (prefetch_container->service_worker_state()) { |
| case PrefetchServiceWorkerState::kDisallowed: |
| break; |
| |
| case PrefetchServiceWorkerState::kAllowed: |
| // The controlling ServiceWorker will be checked by |
| // `ServiceWorkerMainResourceLoaderInterceptor` from |
| // `PrefetchStreamingURLLoader`, not here during eligibility check. |
| OnGotServiceWorkerResult(std::move(prefetch_container), url, |
| std::move(redirect_data), base::Time::Now(), |
| ServiceWorkerCapability::NO_SERVICE_WORKER); |
| return; |
| |
| case PrefetchServiceWorkerState::kControlled: |
| NOTREACHED(); |
| } |
| } |
| |
| // This service worker check assumes that the prefetch will only ever be |
| // performed in a first-party context (main frame prefetch). At the moment |
| // that is true but if it ever changes then the StorageKey will need to be |
| // constructed with the top-level site to ensure correct partitioning. |
| ServiceWorkerContext* service_worker_context = |
| g_service_worker_context_for_testing |
| ? g_service_worker_context_for_testing |
| : browser_context_->GetDefaultStoragePartition() |
| ->GetServiceWorkerContext(); |
| CHECK(service_worker_context); |
| auto key = blink::StorageKey::CreateFirstParty(url::Origin::Create(url)); |
| // Check `MaybeHasRegistrationForStorageKey` first as it is much faster than |
| // calling `CheckHasServiceWorker`. |
| auto has_registration_for_storage_key = |
| service_worker_context->MaybeHasRegistrationForStorageKey(key); |
| if (prefetch_container->HasPreloadingAttempt()) { |
| auto* preloading_attempt = static_cast<PreloadingAttemptImpl*>( |
| prefetch_container->preloading_attempt().get()); |
| CHECK(preloading_attempt); |
| preloading_attempt->SetServiceWorkerRegisteredCheck( |
| has_registration_for_storage_key |
| ? PreloadingAttemptImpl::ServiceWorkerRegisteredCheck::kPath |
| : PreloadingAttemptImpl::ServiceWorkerRegisteredCheck::kOriginOnly); |
| } |
| if (!has_registration_for_storage_key) { |
| OnGotServiceWorkerResult(std::move(prefetch_container), url, |
| std::move(redirect_data), base::Time::Now(), |
| ServiceWorkerCapability::NO_SERVICE_WORKER); |
| return; |
| } |
| // Start recording here the start of the check for Service Worker registration |
| // for url. |
| service_worker_context->CheckHasServiceWorker( |
| url, key, |
| base::BindOnce(&PrefetchService::OnGotServiceWorkerResult, |
| weak_method_factory_.GetWeakPtr(), |
| std::move(prefetch_container), url, |
| std::move(redirect_data), base::Time::Now())); |
| } |
| |
| void PrefetchService::OnGotServiceWorkerResult( |
| base::WeakPtr<PrefetchContainer> prefetch_container, |
| const GURL& url, |
| std::optional<std::pair<net::RedirectInfo, |
| network::mojom::URLResponseHeadPtr>> redirect_data, |
| base::Time check_has_service_worker_start_time, |
| ServiceWorkerCapability service_worker_capability) { |
| TRACE_EVENT_NESTABLE_ASYNC_END0( |
| "loading", "PrefetchService::CheckHasServiceWorker", this); |
| TRACE_EVENT0("loading", "PrefetchService::OnGotServiceWorkerResult"); |
| if (!prefetch_container) { |
| OnGotEligibility(std::move(prefetch_container), std::move(redirect_data), |
| PreloadingEligibility::kEligible); |
| return; |
| } |
| CHECK(prefetch_container); |
| if (prefetch_container->HasPreloadingAttempt()) { |
| const auto duration = |
| base::Time::Now() - check_has_service_worker_start_time; |
| auto* preloading_attempt = static_cast<PreloadingAttemptImpl*>( |
| prefetch_container->preloading_attempt().get()); |
| CHECK(preloading_attempt); |
| preloading_attempt->SetServiceWorkerRegisteredCheckDuration(duration); |
| } |
| // Note that after ServiceWorker+Prefetch support is implemented, |
| // - For ServiceWorker-eligible prefetches, |
| // `ServiceWorkerCapability::NO_SERVICE_WORKER` is passed here and thus the |
| // ServiceWorker-related ineligibility values here are not used. |
| // - For ServiceWorker-ineligible prefetches (e.g. cross-site prefetches), |
| // they still goes through the checks below and the ServiceWorker-related |
| // ineligibility values here are still valid and used. |
| switch (service_worker_capability) { |
| case ServiceWorkerCapability::NO_SERVICE_WORKER: |
| break; |
| case ServiceWorkerCapability::SERVICE_WORKER_NO_FETCH_HANDLER: |
| if (base::FeatureList::IsEnabled( |
| features::kPrefetchServiceWorkerNoFetchHandlerFix)) { |
| OnGotEligibility( |
| std::move(prefetch_container), std::move(redirect_data), |
| PreloadingEligibility::kUserHasServiceWorkerNoFetchHandler); |
| return; |
| } |
| break; |
| case ServiceWorkerCapability::SERVICE_WORKER_WITH_FETCH_HANDLER: { |
| auto eligibility = redirect_data |
| ? PreloadingEligibility::kRedirectToServiceWorker |
| : PreloadingEligibility::kUserHasServiceWorker; |
| OnGotEligibility(std::move(prefetch_container), std::move(redirect_data), |
| eligibility); |
| return; |
| } |
| } |
| // This blocks same-site cross-origin prefetches that require the prefetch |
| // proxy. Same-site prefetches are made using the default network context, and |
| // the prefetch request cannot be configured to use the proxy in that network |
| // context. |
| // TODO(crbug.com/40265797): Allow same-site cross-origin prefetches |
| // that require the prefetch proxy to be made. |
| if (prefetch_container->IsProxyRequiredForURL(url) && |
| !prefetch_container |
| ->IsIsolatedNetworkContextRequiredForCurrentPrefetch()) { |
| OnGotEligibility( |
| std::move(prefetch_container), std::move(redirect_data), |
| PreloadingEligibility::kSameSiteCrossOriginPrefetchRequiredProxy); |
| return; |
| } |
| // We do not need to check the cookies of prefetches that do not need an |
| // isolated network context. |
| if (!prefetch_container |
| ->IsIsolatedNetworkContextRequiredForCurrentPrefetch()) { |
| OnGotEligibility(std::move(prefetch_container), std::move(redirect_data), |
| PreloadingEligibility::kEligible); |
| return; |
| } |
| |
| StoragePartition* default_storage_partition = |
| browser_context_->GetDefaultStoragePartition(); |
| CHECK(default_storage_partition); |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("loading", "PrefetchService::CheckCookies", |
| this); |
| net::CookieOptions options = net::CookieOptions::MakeAllInclusive(); |
| options.set_return_excluded_cookies(); |
| default_storage_partition->GetCookieManagerForBrowserProcess()->GetCookieList( |
| url, options, net::CookiePartitionKeyCollection::Todo(), |
| base::BindOnce(&PrefetchService::OnGotCookiesForEligibilityCheck, |
| weak_method_factory_.GetWeakPtr(), |
| std::move(prefetch_container), url, |
| std::move(redirect_data))); |
| } |
| |
| void PrefetchService::OnGotCookiesForEligibilityCheck( |
| base::WeakPtr<PrefetchContainer> prefetch_container, |
| const GURL& url, |
| std::optional<std::pair<net::RedirectInfo, |
| network::mojom::URLResponseHeadPtr>> redirect_data, |
| const net::CookieAccessResultList& cookie_list, |
| const net::CookieAccessResultList& excluded_cookies) { |
| TRACE_EVENT_NESTABLE_ASYNC_END0("loading", "PrefetchService::CheckCookies", |
| this); |
| TRACE_EVENT0("loading", "PrefetchService::OnGotCookiesForEligibilityCheck"); |
| if (!prefetch_container) { |
| OnGotEligibility(std::move(prefetch_container), std::move(redirect_data), |
| PreloadingEligibility::kEligible); |
| return; |
| } |
| |
| if (!cookie_list.empty()) { |
| OnGotEligibility(std::move(prefetch_container), std::move(redirect_data), |
| PreloadingEligibility::kUserHasCookies); |
| return; |
| } |
| |
| if (base::FeatureList::IsEnabled( |
| features::kPrefetchStateContaminationMitigation)) { |
| // The cookie eligibility check just happened, and we might proceed anyway. |
| // We might therefore need to delay further processing to the extent |
| // required to obscure the outcome of this check from the current site. |
| // TODO(crbug.com/40946257): Currently browser-initiated prefetch will |
| // always be marked as cross-site contaminated since there is no initiator |
| // rfh. Revisit and add proper handlings. |
| auto* initiator_rfh = RenderFrameHost::FromID( |
| prefetch_container->GetReferringRenderFrameHostId()); |
| const bool is_contamination_exempt = |
| delegate_ && initiator_rfh && |
| delegate_->IsContaminationExempt(initiator_rfh->GetLastCommittedURL()); |
| if (!is_contamination_exempt) { |
| prefetch_container->MarkCrossSiteContaminated(); |
| } |
| } |
| |
| // Cookies are tricky because cookies for different paths or a higher level |
| // domain (e.g.: m.foo.com and foo.com) may not show up in |cookie_list|, but |
| // they will show up in |excluded_cookies|. To check for any cookies for a |
| // domain, compare the domains of the prefetched |url| and the domains of all |
| // the returned cookies. |
| bool excluded_cookie_has_tld = false; |
| for (const auto& cookie_result : excluded_cookies) { |
| if (cookie_result.cookie.IsExpired(base::Time::Now())) { |
| // Expired cookies don't count. |
| continue; |
| } |
| |
| if (url.DomainIs(cookie_result.cookie.DomainWithoutDot())) { |
| excluded_cookie_has_tld = true; |
| break; |
| } |
| } |
| |
| if (excluded_cookie_has_tld) { |
| OnGotEligibility(std::move(prefetch_container), std::move(redirect_data), |
| PreloadingEligibility::kUserHasCookies); |
| return; |
| } |
| |
| StartProxyLookupCheck(std::move(prefetch_container), url, |
| std::move(redirect_data)); |
| } |
| |
| void PrefetchService::StartProxyLookupCheck( |
| base::WeakPtr<PrefetchContainer> prefetch_container, |
| const GURL& url, |
| std::optional< |
| std::pair<net::RedirectInfo, network::mojom::URLResponseHeadPtr>> |
| redirect_data) { |
| // Same origin prefetches (which use the default network context and cannot |
| // use the prefetch proxy) can use the existing proxy settings. |
| // TODO(crbug.com/40231580): Copy proxy settings over to the isolated |
| // network context for the prefetch in order to allow non-private cross origin |
| // prefetches to be made using the existing proxy settings. |
| if (!prefetch_container |
| ->IsIsolatedNetworkContextRequiredForCurrentPrefetch()) { |
| OnGotEligibility(std::move(prefetch_container), std::move(redirect_data), |
| PreloadingEligibility::kEligible); |
| return; |
| } |
| |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("loading", "PrefetchService::ProxyCheck", |
| this); |
| // Start proxy check for this prefetch, and give ownership of the |
| // |ProxyLookupClientImpl| to |prefetch_container|. |
| prefetch_container->TakeProxyLookupClient( |
| std::make_unique<ProxyLookupClientImpl>( |
| url, |
| base::BindOnce(&PrefetchService::OnGotProxyLookupResult, |
| weak_method_factory_.GetWeakPtr(), |
| std::move(prefetch_container), |
| std::move(redirect_data)), |
| g_network_context_for_proxy_lookup_for_testing |
| ? g_network_context_for_proxy_lookup_for_testing |
| : browser_context_->GetDefaultStoragePartition() |
| ->GetNetworkContext())); |
| } |
| |
| void PrefetchService::OnGotProxyLookupResult( |
| base::WeakPtr<PrefetchContainer> prefetch_container, |
| std::optional<std::pair<net::RedirectInfo, |
| network::mojom::URLResponseHeadPtr>> redirect_data, |
| bool has_proxy) { |
| TRACE_EVENT_NESTABLE_ASYNC_END0("loading", "PrefetchService::ProxyCheck", |
| this); |
| TRACE_EVENT0("loading", "PrefetchService::OnGotProxyLookupResult"); |
| if (!prefetch_container) { |
| OnGotEligibility(std::move(prefetch_container), std::move(redirect_data), |
| PreloadingEligibility::kEligible); |
| return; |
| } |
| |
| prefetch_container->ReleaseProxyLookupClient(); |
| if (has_proxy) { |
| OnGotEligibility(std::move(prefetch_container), std::move(redirect_data), |
| PreloadingEligibility::kExistingProxy); |
| return; |
| } |
| |
| OnGotEligibility(std::move(prefetch_container), std::move(redirect_data), |
| PreloadingEligibility::kEligible); |
| } |
| |
| void PrefetchService::OnGotEligibility( |
| base::WeakPtr<PrefetchContainer> prefetch_container, |
| std::optional<std::pair<net::RedirectInfo, |
| network::mojom::URLResponseHeadPtr>> redirect_data, |
| PreloadingEligibility eligibility) { |
| TRACE_EVENT_NESTABLE_ASYNC_END0("loading", |
| "PrefetchService::CheckEligibility", this); |
| TRACE_EVENT0("loading", "PrefetchService::OnGotEligibility"); |
| if (redirect_data.has_value()) { |
| OnGotEligibilityForRedirect(std::move(prefetch_container), |
| std::move(std::get<0>(redirect_data.value())), |
| std::move(std::get<1>(redirect_data.value())), |
| eligibility); |
| } else { |
| OnGotEligibilityForNonRedirect(std::move(prefetch_container), eligibility); |
| } |
| } |
| |
| void PrefetchService::OnGotEligibilityForNonRedirect( |
| base::WeakPtr<PrefetchContainer> prefetch_container, |
| PreloadingEligibility eligibility) { |
| if (!prefetch_container) { |
| return; |
| } |
| TRACE_EVENT0("loading", "PrefetchService::OnGotEligibilityForNonRedirect"); |
| |
| const bool eligible = eligibility == PreloadingEligibility::kEligible; |
| bool is_decoy = false; |
| if (!eligible) { |
| is_decoy = |
| prefetch_container->IsProxyRequiredForURL( |
| prefetch_container->GetURL()) && |
| ShouldConsiderDecoyRequestForStatus(eligibility) && |
| PrefetchServiceSendDecoyRequestForIneligblePrefetch( |
| delegate_ ? delegate_->DisableDecoysBasedOnUserSettings() : false); |
| } |
| // The prefetch decoy is pushed onto the queue and the network request will be |
| // dispatched, but the response will not be used. Thus it is eligible but a |
| // failure. |
| prefetch_container->SetIsDecoy(is_decoy); |
| if (is_decoy) { |
| prefetch_container->OnEligibilityCheckComplete( |
| PreloadingEligibility::kEligible); |
| } else { |
| prefetch_container->OnEligibilityCheckComplete(eligibility); |
| } |
| |
| if (!eligible && !is_decoy) { |
| DVLOG(1) |
| << *prefetch_container |
| << ": not prefetched (not eligible nor decoy. PreloadingEligibility=" |
| << static_cast<int>(eligibility) << ")"; |
| return; |
| } |
| |
| if (!is_decoy) { |
| prefetch_container->SetPrefetchStatus(PrefetchStatus::kPrefetchNotStarted); |
| |
| // Registers a cookie listener for this prefetch if it is using an isolated |
| // network context. If the cookies in the default partition associated with |
| // this URL change after this point, then the prefetched resources should |
| // not be served. |
| if (prefetch_container |
| ->IsIsolatedNetworkContextRequiredForCurrentPrefetch()) { |
| prefetch_container->RegisterCookieListener( |
| browser_context_->GetDefaultStoragePartition() |
| ->GetCookieManagerForBrowserProcess()); |
| } |
| } |
| prefetch_queue_.push_back(prefetch_container); |
| |
| // Calling |Prefetch| could result in a prefetch being deleted, so |
| // |prefetch_container| should not be used after this call. |
| Prefetch(); |
| } |
| |
| void PrefetchService::OnGotEligibilityForRedirect( |
| base::WeakPtr<PrefetchContainer> prefetch_container, |
| net::RedirectInfo redirect_info, |
| network::mojom::URLResponseHeadPtr redirect_head, |
| PreloadingEligibility eligibility) { |
| if (!prefetch_container) { |
| return; |
| } |
| TRACE_EVENT0("loading", "PrefetchService::OnGotEligibilityForRedirect"); |
| |
| const bool eligible = eligibility == PreloadingEligibility::kEligible; |
| RecordRedirectResult(eligible |
| ? PrefetchRedirectResult::kSuccessRedirectFollowed |
| : PrefetchRedirectResult::kFailedIneligible); |
| |
| // If the redirect is ineligible, the prefetch may change into a decoy. |
| bool is_decoy = false; |
| if (!eligible) { |
| is_decoy = |
| prefetch_container->IsProxyRequiredForURL(redirect_info.new_url) && |
| ShouldConsiderDecoyRequestForStatus(eligibility) && |
| PrefetchServiceSendDecoyRequestForIneligblePrefetch( |
| delegate_ ? delegate_->DisableDecoysBasedOnUserSettings() : false); |
| } |
| prefetch_container->SetIsDecoy(prefetch_container->IsDecoy() || is_decoy); |
| |
| // Inform the prefetch container of the result of the eligibility check |
| if (prefetch_container->IsDecoy()) { |
| prefetch_container->OnEligibilityCheckComplete( |
| PreloadingEligibility::kEligible); |
| } else { |
| prefetch_container->OnEligibilityCheckComplete(eligibility); |
| if (eligible && |
| prefetch_container |
| ->IsIsolatedNetworkContextRequiredForCurrentPrefetch()) { |
| prefetch_container->RegisterCookieListener( |
| browser_context_->GetDefaultStoragePartition() |
| ->GetCookieManagerForBrowserProcess()); |
| } |
| } |
| |
| // TODO(crbug.com/396133768): Consider setting appropriate PrefetchStatus. |
| auto streaming_url_loader = prefetch_container->GetStreamingURLLoader(); |
| if (!streaming_url_loader) { |
| if (active_prefetch_ == prefetch_container->key()) { |
| active_prefetch_ = std::nullopt; |
| Prefetch(); |
| } |
| return; |
| } |
| |
| // If the redirect is not eligible and the prefetch is not a decoy, then stop |
| // the prefetch. |
| if (!eligible && !prefetch_container->IsDecoy()) { |
| DCHECK(active_prefetch_ == prefetch_container->key()); |
| active_prefetch_ = std::nullopt; |
| streaming_url_loader->HandleRedirect( |
| PrefetchRedirectStatus::kFail, redirect_info, std::move(redirect_head)); |
| |
| Prefetch(); |
| |
| return; |
| } |
| |
| prefetch_container->NotifyPrefetchRequestWillBeSent(&redirect_head); |
| |
| // If the redirect requires a change in network contexts, then stop the |
| // current streaming URL loader and start a new streaming URL loader for the |
| // redirect URL. |
| if (prefetch_container |
| ->IsIsolatedNetworkContextRequiredForCurrentPrefetch() != |
| prefetch_container |
| ->IsIsolatedNetworkContextRequiredForPreviousRedirectHop()) { |
| streaming_url_loader->HandleRedirect( |
| PrefetchRedirectStatus::kSwitchNetworkContext, redirect_info, |
| std::move(redirect_head)); |
| // The new ResponseReader is associated with the new streaming URL loader at |
| // the PrefetchStreamingURLLoader constructor. |
| SendPrefetchRequest(prefetch_container); |
| |
| return; |
| } |
| |
| // Otherwise, follow the redirect in the same streaming URL loader. |
| streaming_url_loader->HandleRedirect(PrefetchRedirectStatus::kFollow, |
| redirect_info, std::move(redirect_head)); |
| // Associate the new ResponseReader with the current streaming URL loader. |
| streaming_url_loader->SetResponseReader( |
| prefetch_container->GetResponseReaderForCurrentPrefetch()); |
| } |
| |
| void PrefetchService::Prefetch() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // Asserts that re-entrancy doesn't happen. |
| #if DCHECK_IS_ON() |
| DCHECK(!prefetch_reentrancy_guard_); |
| base::AutoReset reset_guard(&prefetch_reentrancy_guard_, true); |
| #endif |
| |
| if (PrefetchCloseIdleSockets()) { |
| for (const auto& iter : owned_prefetches_) { |
| if (iter.second) { |
| iter.second->CloseIdleConnections(); |
| } |
| } |
| } |
| |
| base::WeakPtr<PrefetchContainer> next_prefetch = nullptr; |
| base::WeakPtr<PrefetchContainer> prefetch_to_evict = nullptr; |
| while ((std::tie(next_prefetch, prefetch_to_evict) = |
| PopNextPrefetchContainer()) != |
| std::make_tuple(nullptr, nullptr)) { |
| StartSinglePrefetch(next_prefetch, prefetch_to_evict); |
| } |
| } |
| |
| std::tuple<base::WeakPtr<PrefetchContainer>, base::WeakPtr<PrefetchContainer>> |
| PrefetchService::PopNextPrefetchContainer() { |
| auto new_end = std::remove_if( |
| prefetch_queue_.begin(), prefetch_queue_.end(), |
| [&](const base::WeakPtr<PrefetchContainer>& prefetch_container) { |
| // Remove all prefetches from queue that no longer exist. |
| return !prefetch_container; |
| }); |
| prefetch_queue_.erase(new_end, prefetch_queue_.end()); |
| |
| // Don't start any new prefetches if we are currently running one. |
| if (active_prefetch_.has_value()) { |
| DVLOG(1) << "PrefetchService::PopNextPrefetchContainer: already running a " |
| "prefetch."; |
| return std::make_tuple(nullptr, nullptr); |
| } |
| |
| base::WeakPtr<PrefetchContainer> prefetch_to_evict; |
| // Get the first prefetch can be prefetched currently. For the triggers |
| // managed by PrefetchDocumentManager, this depends on the state of the |
| // initiating document, and the number of completed prefetches (this can also |
| // result in previously completed prefetches being evicted). |
| auto prefetch_iter = std::ranges::find_if( |
| prefetch_queue_, |
| [&](const base::WeakPtr<PrefetchContainer>& prefetch_container) { |
| if (!prefetch_container->IsRendererInitiated()) { |
| // TODO(crbug.com/40946257): Revisit the resource limits and |
| // conditions for starting browser-initiated prefetch. |
| return true; |
| } |
| |
| auto* prefetch_document_manager = |
| prefetch_container->GetPrefetchDocumentManager(); |
| // If there is no manager in renderer-inititaed prefetch (can happen |
| // only in tests), just bypass the check. |
| if (!prefetch_document_manager) { |
| return true; |
| } |
| bool can_prefetch_now = false; |
| std::tie(can_prefetch_now, prefetch_to_evict) = |
| prefetch_document_manager->CanPrefetchNow(prefetch_container.get()); |
| // |prefetch_to_evict| should only be set if |can_prefetch_now| is true. |
| DCHECK(!prefetch_to_evict || can_prefetch_now); |
| return can_prefetch_now; |
| }); |
| if (prefetch_iter == prefetch_queue_.end()) { |
| return std::make_tuple(nullptr, nullptr); |
| } |
| |
| base::WeakPtr<PrefetchContainer> next_prefetch_container = *prefetch_iter; |
| prefetch_queue_.erase(prefetch_iter); |
| return std::make_tuple(next_prefetch_container, prefetch_to_evict); |
| } |
| |
| void PrefetchService::OnPrefetchTimeout( |
| base::WeakPtr<PrefetchContainer> prefetch_container) { |
| prefetch_container->SetPrefetchStatus(PrefetchStatus::kPrefetchIsStale); |
| ResetPrefetchContainer(prefetch_container); |
| |
| if (!active_prefetch_) { |
| Prefetch(); |
| } |
| } |
| |
| void PrefetchService::MayReleasePrefetch( |
| base::WeakPtr<PrefetchContainer> prefetch_container) { |
| if (!prefetch_container) { |
| return; |
| } |
| |
| if (!base::Contains(owned_prefetches_, prefetch_container->key())) { |
| return; |
| } |
| |
| ResetPrefetchContainer(prefetch_container); |
| } |
| |
| void PrefetchService::ResetPrefetchContainer( |
| base::WeakPtr<PrefetchContainer> prefetch_container) { |
| CHECK(prefetch_container); |
| auto it = owned_prefetches_.find(prefetch_container->key()); |
| CHECK(it != owned_prefetches_.end()); |
| CHECK_EQ(it->second.get(), prefetch_container.get()); |
| |
| if (active_prefetch_ == prefetch_container->key()) { |
| active_prefetch_ = std::nullopt; |
| } |
| |
| owned_prefetches_.erase(it); |
| } |
| |
| void PrefetchService::OnCandidatesUpdated() { |
| if (!active_prefetch_) { |
| Prefetch(); |
| } |
| } |
| |
| void PrefetchService::StartSinglePrefetch( |
| base::WeakPtr<PrefetchContainer> prefetch_container, |
| base::WeakPtr<PrefetchContainer> prefetch_to_evict) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| CHECK(prefetch_container); |
| CHECK_EQ(prefetch_container->GetLoadState(), |
| PrefetchContainer::LoadState::kEligible); |
| |
| // Do not prefetch for a Holdback control group. Called after the checks in |
| // `PopNextPrefetchContainer` because we want to compare against the |
| // prefetches that would have been dispatched. |
| if (CheckAndSetPrefetchHoldbackStatus(prefetch_container)) { |
| DVLOG(1) << *prefetch_container |
| << ": not prefetched (holdback control group)"; |
| return; |
| } |
| |
| prefetch_container->OnPrefetchStarted(); |
| |
| // Checks if the `PrefetchContainer` has a specific TTL (Time-to-Live) |
| // configured. If a TTL is configured, the prefetch container will be eligible |
| // for removal after the TTL expires. Otherwise, it will remain alive |
| // indefinitely. |
| // |
| // The default TTL is determined by |
| // `PrefetchContainerDefaultTtlInPrefetchService()`, which may return a zero |
| // or negative value, indicating an indefinite TTL. |
| prefetch_container->StartTimeoutTimerIfNeeded( |
| base::BindOnce(&PrefetchService::OnPrefetchTimeout, |
| weak_method_factory_.GetWeakPtr(), prefetch_container)); |
| |
| if (prefetch_to_evict) { |
| prefetch_to_evict->SetPrefetchStatus( |
| PrefetchStatus::kPrefetchEvictedForNewerPrefetch); |
| ResetPrefetchContainer(prefetch_to_evict); |
| } |
| |
| active_prefetch_.emplace(prefetch_container->key()); |
| |
| if (!prefetch_container->IsDecoy()) { |
| // The status is updated to be successful or failed when it finishes. |
| prefetch_container->SetPrefetchStatus( |
| PrefetchStatus::kPrefetchNotFinishedInTime); |
| } |
| |
| net::HttpRequestHeaders additional_headers; |
| additional_headers.SetHeader( |
| net::HttpRequestHeaders::kAccept, |
| FrameAcceptHeaderValue(/*allow_sxg_responses=*/true, browser_context_)); |
| prefetch_container->MakeResourceRequest(additional_headers); |
| |
| prefetch_container->NotifyPrefetchRequestWillBeSent( |
| /*redirect_head=*/nullptr); |
| |
| SendPrefetchRequest(prefetch_container); |
| |
| PrefetchDocumentManager* prefetch_document_manager = |
| prefetch_container->GetPrefetchDocumentManager(); |
| if (prefetch_container->GetPrefetchType().IsProxyRequiredWhenCrossOrigin() && |
| !prefetch_container->IsDecoy() && |
| (!prefetch_document_manager || |
| !prefetch_document_manager->HaveCanaryChecksStarted())) { |
| // TODO(crbug.com/40946257): Currently browser-initiated prefetch will |
| // always perform canary checks since there is no PrefetchDocumentManager. |
| // Revisit and add proper handlings. |
| |
| // Make sure canary checks have run so we know the result by the time we |
| // want to use the prefetch. Checking the canary cache can be a slow and |
| // blocking operation (see crbug.com/1266018), so we only do this for the |
| // first non-decoy prefetch we make on the page. |
| // TODO(crbug.com/40801832): once this bug is fixed, fire off canary check |
| // regardless of whether the request is a decoy or not. |
| origin_prober_->RunCanaryChecksIfNeeded(); |
| |
| if (prefetch_document_manager) { |
| prefetch_document_manager->OnCanaryChecksStarted(); |
| } |
| } |
| |
| // Start a spare renderer now so that it will be ready by the time it is |
| // useful to have. |
| if (ShouldStartSpareRenderer()) { |
| SpareRenderProcessHostManager::Get().WarmupSpare(browser_context_); |
| } |
| } |
| |
| void PrefetchService::SendPrefetchRequest( |
| base::WeakPtr<PrefetchContainer> prefetch_container) { |
| TRACE_EVENT("loading", "PrefetchService::SendPrefetchRequest"); |
| net::NetworkTrafficAnnotationTag traffic_annotation = |
| net::DefineNetworkTrafficAnnotation("speculation_rules_prefetch", |
| R"( |
| semantics { |
| sender: "Speculation Rules Prefetch Loader" |
| description: |
| "Prefetches the mainframe HTML of a page specified via " |
| "speculation rules. This is done out-of-band of normal " |
| "prefetches to allow total isolation of this request from the " |
| "rest of browser traffic and user state like cookies and cache." |
| trigger: |
| "Used only when this feature and speculation rules feature are " |
| "enabled." |
| data: "None." |
| destination: WEBSITE |
| } |
| policy { |
| cookies_allowed: NO |
| setting: |
| "Users can control this via a setting specific to each content " |
| "embedder." |
| policy_exception_justification: "Not implemented." |
| })"); |
| |
| auto streaming_loader = PrefetchStreamingURLLoader::CreateAndStart( |
| GetURLLoaderFactoryForCurrentPrefetch(prefetch_container), |
| *prefetch_container->GetResourceRequest(), traffic_annotation, |
| PrefetchTimeoutDuration(), |
| base::BindOnce(&PrefetchService::OnPrefetchResponseStarted, |
| base::Unretained(this), prefetch_container), |
| base::BindOnce(&PrefetchService::OnPrefetchResponseCompleted, |
| base::Unretained(this), prefetch_container), |
| base::BindRepeating(&PrefetchService::OnPrefetchRedirect, |
| base::Unretained(this), prefetch_container), |
| base::BindOnce(&PrefetchContainer::OnDeterminedHead, prefetch_container), |
| prefetch_container->GetResponseReaderForCurrentPrefetch(), |
| prefetch_container->service_worker_state(), browser_context_, |
| base::BindOnce(&PrefetchContainer::OnServiceWorkerStateDetermined, |
| prefetch_container)); |
| prefetch_container->SetStreamingURLLoader(std::move(streaming_loader)); |
| |
| DVLOG(1) << *prefetch_container << ": PrefetchStreamingURLLoader is created."; |
| } |
| |
| scoped_refptr<network::SharedURLLoaderFactory> |
| PrefetchService::GetURLLoaderFactoryForCurrentPrefetch( |
| base::WeakPtr<PrefetchContainer> prefetch_container) { |
| DCHECK(prefetch_container); |
| if (g_url_loader_factory_for_testing) { |
| return base::WrapRefCounted(g_url_loader_factory_for_testing); |
| } |
| return prefetch_container->GetOrCreateNetworkContextForCurrentPrefetch() |
| ->GetURLLoaderFactory(this); |
| } |
| |
| void PrefetchService::OnPrefetchRedirect( |
| base::WeakPtr<PrefetchContainer> prefetch_container, |
| const net::RedirectInfo& redirect_info, |
| network::mojom::URLResponseHeadPtr redirect_head) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!prefetch_container) { |
| RecordRedirectResult(PrefetchRedirectResult::kFailedNullPrefetch); |
| return; |
| } |
| |
| DCHECK(active_prefetch_ == prefetch_container->key()); |
| |
| // Update the prefetch's referrer in case a redirect requires a change in |
| // network context and a new request needs to be started. |
| const auto new_referrer_policy = |
| blink::ReferrerUtils::NetToMojoReferrerPolicy( |
| redirect_info.new_referrer_policy); |
| |
| std::optional<PrefetchRedirectResult> failure; |
| if (redirect_info.new_method != "GET") { |
| failure = PrefetchRedirectResult::kFailedInvalidMethod; |
| } else if (!redirect_head->headers || |
| redirect_head->headers->response_code() < 300 || |
| redirect_head->headers->response_code() >= 400) { |
| failure = PrefetchRedirectResult::kFailedInvalidResponseCode; |
| } else if (!net::SchemefulSite::IsSameSite( |
| prefetch_container->GetCurrentURL(), redirect_info.new_url) && |
| !IsReferrerPolicySufficientlyStrict(new_referrer_policy)) { |
| // The new referrer policy is not sufficiently strict to allow cross-site |
| // redirects. |
| failure = PrefetchRedirectResult::kFailedInsufficientReferrerPolicy; |
| } |
| |
| if (failure) { |
| DCHECK(active_prefetch_ == prefetch_container->key()); |
| active_prefetch_ = std::nullopt; |
| prefetch_container->SetPrefetchStatus( |
| PrefetchStatus::kPrefetchFailedInvalidRedirect); |
| if (auto streaming_url_loader = |
| prefetch_container->GetStreamingURLLoader()) { |
| streaming_url_loader->HandleRedirect(PrefetchRedirectStatus::kFail, |
| redirect_info, |
| std::move(redirect_head)); |
| } |
| |
| Prefetch(); |
| RecordRedirectResult(*failure); |
| return; |
| } |
| |
| prefetch_container->AddRedirectHop(redirect_info); |
| prefetch_container->UpdateReferrer(GURL(redirect_info.new_referrer), |
| new_referrer_policy); |
| |
| RecordRedirectNetworkContextTransition( |
| prefetch_container |
| ->IsIsolatedNetworkContextRequiredForPreviousRedirectHop(), |
| prefetch_container->IsIsolatedNetworkContextRequiredForCurrentPrefetch()); |
| |
| auto redirect_data = std::make_optional< |
| std::pair<net::RedirectInfo, network::mojom::URLResponseHeadPtr>>( |
| {redirect_info, std::move(redirect_head)}); |
| |
| if (GetDelayEligibilityCheckForTesting()) { |
| GetDelayEligibilityCheckForTesting().Run( // IN-TEST |
| base::BindOnce(&PrefetchService::CheckEligibilityOfPrefetch, |
| weak_method_factory_.GetWeakPtr(), |
| std::move(prefetch_container), redirect_info.new_url, |
| std::move(redirect_data))); |
| return; |
| } |
| |
| CheckEligibilityOfPrefetch(std::move(prefetch_container), |
| redirect_info.new_url, std::move(redirect_data)); |
| } |
| |
| std::optional<PrefetchErrorOnResponseReceived> |
| PrefetchService::OnPrefetchResponseStarted( |
| base::WeakPtr<PrefetchContainer> prefetch_container, |
| network::mojom::URLResponseHead* head) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!prefetch_container || prefetch_container->IsDecoy()) { |
| return PrefetchErrorOnResponseReceived::kPrefetchWasDecoy; |
| } |
| |
| if (!head) { |
| return PrefetchErrorOnResponseReceived::kFailedInvalidHead; |
| } |
| |
| if (prefetch_container && prefetch_container->IsCrossSiteContaminated()) { |
| head->is_prefetch_with_cross_site_contamination = true; |
| } |
| |
| prefetch_container->NotifyPrefetchResponseReceived(*head); |
| |
| if (!head->headers) { |
| return PrefetchErrorOnResponseReceived::kFailedInvalidHeaders; |
| } |
| |
| RecordPrefetchProxyPrefetchMainframeTotalTime(head); |
| RecordPrefetchProxyPrefetchMainframeConnectTime(head); |
| |
| int response_code = head->headers->response_code(); |
| RecordPrefetchProxyPrefetchMainframeRespCode(response_code); |
| if (response_code < 200 || response_code >= 300) { |
| prefetch_container->SetPrefetchStatus( |
| PrefetchStatus::kPrefetchFailedNon2XX); |
| |
| if (response_code == net::HTTP_SERVICE_UNAVAILABLE) { |
| base::TimeDelta retry_after; |
| std::string retry_after_string; |
| if (head->headers->EnumerateHeader(nullptr, "Retry-After", |
| &retry_after_string) && |
| net::HttpUtil::ParseRetryAfterHeader( |
| retry_after_string, base::Time::Now(), &retry_after) && |
| delegate_) { |
| // Cap the retry after value to a maximum. |
| static constexpr base::TimeDelta max_retry_after = base::Days(7); |
| if (retry_after > max_retry_after) { |
| retry_after = max_retry_after; |
| } |
| |
| delegate_->ReportOriginRetryAfter(prefetch_container->GetURL(), |
| retry_after); |
| } |
| } |
| return PrefetchErrorOnResponseReceived::kFailedNon2XX; |
| } |
| |
| if (PrefetchServiceHTMLOnly() && head->mime_type != "text/html") { |
| prefetch_container->SetPrefetchStatus( |
| PrefetchStatus::kPrefetchFailedMIMENotSupported); |
| return PrefetchErrorOnResponseReceived::kFailedMIMENotSupported; |
| } |
| return std::nullopt; |
| } |
| |
| void PrefetchService::OnPrefetchResponseCompleted( |
| base::WeakPtr<PrefetchContainer> prefetch_container, |
| const network::URLLoaderCompletionStatus& completion_status) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| DVLOG(1) << "PrefetchService::OnPrefetchResponseCompleted"; |
| if (!prefetch_container) { |
| return; |
| } |
| |
| DCHECK(active_prefetch_ == prefetch_container->key()); |
| active_prefetch_ = std::nullopt; |
| |
| prefetch_container->OnPrefetchComplete(completion_status); |
| |
| Prefetch(); |
| } |
| |
| void PrefetchService::CopyIsolatedCookies( |
| const PrefetchContainer::Reader& reader) { |
| DCHECK(reader); |
| |
| if (!reader.GetCurrentNetworkContextToServe()) { |
| // Not set in unit tests. |
| return; |
| } |
| |
| // We only need to copy cookies if the prefetch used an isolated network |
| // context. |
| if (!reader.IsIsolatedNetworkContextRequiredToServe()) { |
| return; |
| } |
| |
| reader.OnIsolatedCookieCopyStart(); |
| net::CookieOptions options = net::CookieOptions::MakeAllInclusive(); |
| reader.GetCurrentNetworkContextToServe()->GetCookieManager()->GetCookieList( |
| reader.GetCurrentURLToServe(), options, |
| net::CookiePartitionKeyCollection::Todo(), |
| base::BindOnce(&PrefetchService::OnGotIsolatedCookiesForCopy, |
| weak_method_factory_.GetWeakPtr(), reader.Clone())); |
| } |
| |
| void PrefetchService::OnGotIsolatedCookiesForCopy( |
| PrefetchContainer::Reader reader, |
| const net::CookieAccessResultList& cookie_list, |
| const net::CookieAccessResultList& excluded_cookies) { |
| reader.OnIsolatedCookiesReadCompleteAndWriteStart(); |
| RecordPrefetchProxyPrefetchMainframeCookiesToCopy(cookie_list.size()); |
| |
| if (cookie_list.empty()) { |
| reader.OnIsolatedCookieCopyComplete(); |
| return; |
| } |
| |
| const auto current_url = reader.GetCurrentURLToServe(); |
| |
| base::RepeatingClosure barrier = base::BarrierClosure( |
| cookie_list.size(), |
| base::BindOnce(&OnIsolatedCookieCopyComplete, std::move(reader))); |
| |
| net::CookieOptions options = net::CookieOptions::MakeAllInclusive(); |
| for (const net::CookieWithAccessResult& cookie : cookie_list) { |
| browser_context_->GetDefaultStoragePartition() |
| ->GetCookieManagerForBrowserProcess() |
| ->SetCanonicalCookie(cookie.cookie, current_url, options, |
| base::BindOnce(&CookieSetHelper, barrier)); |
| } |
| } |
| |
| void PrefetchService::DumpPrefetchesForDebug() const { |
| #if DCHECK_IS_ON() |
| std::ostringstream ss; |
| ss << "PrefetchService[" << this << "]:" << std::endl; |
| |
| ss << "Owned:" << std::endl; |
| for (const auto& entry : owned_prefetches_) { |
| ss << *entry.second << std::endl; |
| } |
| |
| DVLOG(1) << ss.str(); |
| #endif // DCHECK_IS_ON() |
| } |
| |
| std::pair< |
| std::vector<PrefetchContainer*>, |
| base::flat_map<PrefetchContainer::Key, PrefetchContainer::ServableState>> |
| PrefetchService::CollectMatchCandidates( |
| const PrefetchContainer::Key& key, |
| bool is_nav_prerender, |
| base::WeakPtr<PrefetchServingPageMetricsContainer> |
| serving_page_metrics_container) { |
| return CollectMatchCandidatesGeneric( |
| owned_prefetches_, key, is_nav_prerender, |
| std::move(serving_page_metrics_container)); |
| } |
| |
| base::WeakPtr<PrefetchContainer> PrefetchService::MatchUrl( |
| const PrefetchContainer::Key& key) const { |
| return no_vary_search::MatchUrl(key, owned_prefetches_); |
| } |
| |
| std::vector<std::pair<GURL, base::WeakPtr<PrefetchContainer>>> |
| PrefetchService::GetAllForUrlWithoutRefAndQueryForTesting( |
| const PrefetchContainer::Key& key) const { |
| return no_vary_search::GetAllForUrlWithoutRefAndQueryForTesting( |
| key, owned_prefetches_); |
| } |
| |
| // static |
| void PrefetchService::SetServiceWorkerContextForTesting( |
| ServiceWorkerContext* context) { |
| g_service_worker_context_for_testing = context; |
| } |
| |
| // static |
| void PrefetchService::SetHostNonUniqueFilterForTesting( |
| bool (*filter)(std::string_view)) { |
| g_host_non_unique_filter = filter; |
| } |
| |
| // static |
| void PrefetchService::SetURLLoaderFactoryForTesting( |
| network::SharedURLLoaderFactory* url_loader_factory) { |
| g_url_loader_factory_for_testing = url_loader_factory; |
| } |
| |
| // static |
| void PrefetchService::SetNetworkContextForProxyLookupForTesting( |
| network::mojom::NetworkContext* network_context) { |
| g_network_context_for_proxy_lookup_for_testing = network_context; |
| } |
| |
| // static |
| void PrefetchService::SetDelayEligibilityCheckForTesting( |
| DelayEligibilityCheckForTesting callback) { |
| GetDelayEligibilityCheckForTesting() = // IN-TEST |
| std::move(callback); |
| } |
| |
| // static |
| void PrefetchService::SetForceIneligibilityForTesting( |
| PreloadingEligibility eligibility) { |
| GetForceIneligibilityForTesting() = // IN-TEST |
| eligibility; |
| } |
| |
| base::WeakPtr<PrefetchService> PrefetchService::GetWeakPtr() { |
| return weak_method_factory_.GetWeakPtr(); |
| } |
| |
| void PrefetchService::RecordExistingPrefetchWithMatchingURL( |
| base::WeakPtr<PrefetchContainer> prefetch_container) const { |
| bool matching_prefetch = false; |
| int num_matching_prefetches = 0; |
| |
| int num_matching_eligible_prefetch = 0; |
| int num_matching_servable_prefetch = 0; |
| int num_matching_prefetch_same_referrer = 0; |
| int num_matching_prefetch_same_rfh = 0; |
| |
| for (const auto& prefetch_iter : owned_prefetches_) { |
| if (prefetch_iter.second && |
| prefetch_iter.second->GetURL() == prefetch_container->GetURL()) { |
| matching_prefetch = true; |
| num_matching_prefetches++; |
| |
| if (prefetch_iter.second->IsInitialPrefetchEligible()) { |
| num_matching_eligible_prefetch++; |
| } |
| |
| switch ( |
| prefetch_iter.second->GetServableState(PrefetchCacheableDuration())) { |
| case PrefetchContainer::ServableState::kNotServable: |
| case PrefetchContainer::ServableState::kShouldBlockUntilHeadReceived: |
| case PrefetchContainer::ServableState::kShouldBlockUntilEligibilityGot: |
| break; |
| case PrefetchContainer::ServableState::kServable: |
| if (!prefetch_iter.second->HasPrefetchBeenConsideredToServe()) { |
| num_matching_servable_prefetch++; |
| } |
| break; |
| } |
| |
| if (prefetch_iter.second->HasSameReferringURLForMetrics( |
| *prefetch_container)) { |
| num_matching_prefetch_same_referrer++; |
| } |
| |
| if (prefetch_iter.second->GetReferringRenderFrameHostId() == |
| prefetch_container->GetReferringRenderFrameHostId()) { |
| num_matching_prefetch_same_rfh++; |
| } |
| } |
| } |
| |
| base::UmaHistogramBoolean( |
| "PrefetchProxy.Prefetch.ExistingPrefetchWithMatchingURL", |
| matching_prefetch); |
| base::UmaHistogramCounts100( |
| "PrefetchProxy.Prefetch.NumExistingPrefetchWithMatchingURL", |
| num_matching_prefetches); |
| |
| if (matching_prefetch) { |
| base::UmaHistogramCounts100( |
| "PrefetchProxy.Prefetch.NumExistingEligiblePrefetchWithMatchingURL", |
| num_matching_eligible_prefetch); |
| base::UmaHistogramCounts100( |
| "PrefetchProxy.Prefetch.NumExistingServablePrefetchWithMatchingURL", |
| num_matching_servable_prefetch); |
| base::UmaHistogramCounts100( |
| "PrefetchProxy.Prefetch.NumExistingPrefetchWithMatchingURLAndReferrer", |
| num_matching_prefetch_same_referrer); |
| base::UmaHistogramCounts100( |
| "PrefetchProxy.Prefetch." |
| "NumExistingPrefetchWithMatchingURLAndRenderFrameHost", |
| num_matching_prefetch_same_rfh); |
| } |
| } |
| |
| void PrefetchService::EvictPrefetchesForBrowsingDataRemoval( |
| const StoragePartition::StorageKeyMatcherFunction& storage_key_filter, |
| PrefetchStatus status) { |
| // TODO(crbug.com/40262310): Handle for prefetches from non-SpeculationRules |
| std::vector<base::WeakPtr<PrefetchContainer>> prefetches_to_reset; |
| for (const auto& prefetch_iter : owned_prefetches_) { |
| base::WeakPtr<PrefetchContainer> prefetch_container = |
| prefetch_iter.second->GetWeakPtr(); |
| CHECK(prefetch_container); |
| std::optional<url::Origin> referring_origin = |
| prefetch_container->GetReferringOrigin(); |
| if (referring_origin.has_value() && |
| storage_key_filter.Run( |
| blink::StorageKey::CreateFirstParty(referring_origin.value()))) { |
| prefetch_container->SetPrefetchStatus(status); |
| prefetches_to_reset.push_back(prefetch_container); |
| } |
| } |
| |
| for (const auto& prefetch_container : prefetches_to_reset) { |
| ResetPrefetchContainer(prefetch_container); |
| } |
| } |
| |
| } // namespace content |