| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_tab_helper.h" |
| |
| #include <string> |
| |
| #include "base/barrier_closure.h" |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/command_line.h" |
| #include "base/containers/adapters.h" |
| #include "base/feature_list.h" |
| #include "base/metrics/histogram.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/observer_list.h" |
| #include "base/rand_util.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/chrome_content_browser_client.h" |
| #include "chrome/browser/navigation_predictor/navigation_predictor_keyed_service_factory.h" |
| #include "chrome/browser/prefetch/no_state_prefetch/no_state_prefetch_manager_factory.h" |
| #include "chrome/browser/prefetch/prefetch_headers.h" |
| #include "chrome/browser/prefetch/prefetch_prefs.h" |
| #include "chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_features.h" |
| #include "chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_network_context_client.h" |
| #include "chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_origin_decider.h" |
| #include "chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_origin_prober.h" |
| #include "chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_params.h" |
| #include "chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_prefetch_metrics_collector.h" |
| #include "chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_proxy_configurator.h" |
| #include "chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_service.h" |
| #include "chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_service_factory.h" |
| #include "chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_subresource_manager.h" |
| #include "chrome/browser/prefetch/prefetch_proxy/prefetch_type.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "components/google/core/common/google_util.h" |
| #include "components/language/core/browser/pref_names.h" |
| #include "components/no_state_prefetch/browser/no_state_prefetch_manager.h" |
| #include "components/page_load_metrics/browser/metrics_web_contents_observer.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/search_engines/template_url_service.h" |
| #include "components/version_info/version_info.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/network_service_instance.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/storage_partition.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/content_constants.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/common/user_agent.h" |
| #include "mojo/public/cpp/bindings/self_owned_receiver.h" |
| #include "net/base/isolation_info.h" |
| #include "net/base/load_flags.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/url_util.h" |
| #include "net/cookies/cookie_store.h" |
| #include "net/http/http_status_code.h" |
| #include "net/http/http_util.h" |
| #include "net/traffic_annotation/network_traffic_annotation.h" |
| #include "services/cert_verifier/public/mojom/cert_verifier_service_factory.mojom.h" |
| #include "services/network/public/cpp/resource_request.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| #include "services/network/public/cpp/simple_url_loader.h" |
| #include "services/network/public/cpp/wrapper_shared_url_loader_factory.h" |
| #include "services/network/public/mojom/cookie_manager.mojom.h" |
| #include "services/network/public/mojom/network_context.mojom.h" |
| #include "services/network/public/mojom/network_service.mojom.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "third_party/blink/public/common/storage_key/storage_key.h" |
| #include "url/origin.h" |
| |
| namespace { |
| |
| bool (*g_host_non_unique_filter)(base::StringPiece) = nullptr; |
| |
| absl::optional<base::TimeDelta> GetTotalPrefetchTime( |
| 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 absl::nullopt; |
| |
| return end - start; |
| } |
| |
| absl::optional<base::TimeDelta> GetPrefetchConnectTime( |
| 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 absl::nullopt; |
| |
| return end - start; |
| } |
| |
| void InformPLMOfLikelyPrefetching(content::WebContents* web_contents) { |
| page_load_metrics::MetricsWebContentsObserver* metrics_web_contents_observer = |
| page_load_metrics::MetricsWebContentsObserver::FromWebContents( |
| web_contents); |
| if (!metrics_web_contents_observer) |
| return; |
| |
| metrics_web_contents_observer->OnPrefetchLikely(); |
| } |
| |
| void OnGotCookieList( |
| const GURL& url, |
| PrefetchProxyTabHelper::OnEligibilityResultCallback result_callback, |
| const net::CookieAccessResultList& cookie_list, |
| const net::CookieAccessResultList& excluded_cookies) { |
| if (!cookie_list.empty()) { |
| std::move(result_callback) |
| .Run(url, false, |
| PrefetchProxyPrefetchStatus::kPrefetchNotEligibleUserHasCookies); |
| return; |
| } |
| |
| // 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) { |
| std::move(result_callback) |
| .Run(url, false, |
| PrefetchProxyPrefetchStatus::kPrefetchNotEligibleUserHasCookies); |
| return; |
| } |
| |
| std::move(result_callback).Run(url, true, absl::nullopt); |
| } |
| |
| void CookieSetHelper(base::RepeatingClosure run_me, |
| net::CookieAccessResult access_result) { |
| run_me.Run(); |
| } |
| |
| bool ShouldStartSpareRenderer() { |
| if (!PrefetchProxyStartsSpareRenderer()) { |
| return false; |
| } |
| |
| for (content::RenderProcessHost::iterator iter( |
| content::RenderProcessHost::AllHostsIterator()); |
| !iter.IsAtEnd(); iter.Advance()) { |
| if (iter.GetCurrentValue()->IsUnused()) { |
| // There is already a spare renderer. |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool ShouldConsiderDecoyRequestForStatus(PrefetchProxyPrefetchStatus status) { |
| switch (status) { |
| case PrefetchProxyPrefetchStatus::kPrefetchNotEligibleUserHasCookies: |
| case PrefetchProxyPrefetchStatus::kPrefetchNotEligibleUserHasServiceWorker: |
| // If the prefetch is not eligible because of cookie or a service worker, |
| // then maybe send a decoy. |
| return true; |
| case PrefetchProxyPrefetchStatus::kPrefetchNotEligibleGoogleDomain: |
| case PrefetchProxyPrefetchStatus::kPrefetchNotEligibleSchemeIsNotHttps: |
| case PrefetchProxyPrefetchStatus:: |
| kPrefetchNotEligibleNonDefaultStoragePartition: |
| case PrefetchProxyPrefetchStatus::kPrefetchPositionIneligible: |
| case PrefetchProxyPrefetchStatus::kPrefetchIneligibleRetryAfter: |
| case PrefetchProxyPrefetchStatus::kPrefetchProxyNotAvailable: |
| case PrefetchProxyPrefetchStatus::kPrefetchNotEligibleHostIsNonUnique: |
| // These statuses don't relate to any user state, so don't send a decoy |
| // request. |
| return false; |
| case PrefetchProxyPrefetchStatus::kPrefetchUsedNoProbe: |
| case PrefetchProxyPrefetchStatus::kPrefetchUsedProbeSuccess: |
| case PrefetchProxyPrefetchStatus::kPrefetchNotUsedProbeFailed: |
| case PrefetchProxyPrefetchStatus::kPrefetchNotStarted: |
| case PrefetchProxyPrefetchStatus::kPrefetchNotFinishedInTime: |
| case PrefetchProxyPrefetchStatus::kPrefetchFailedNetError: |
| case PrefetchProxyPrefetchStatus::kPrefetchFailedNon2XX: |
| case PrefetchProxyPrefetchStatus::kPrefetchFailedNotHTML: |
| case PrefetchProxyPrefetchStatus::kPrefetchSuccessful: |
| case PrefetchProxyPrefetchStatus::kNavigatedToLinkNotOnSRP: |
| case PrefetchProxyPrefetchStatus::kSubresourceThrottled: |
| case PrefetchProxyPrefetchStatus::kPrefetchUsedNoProbeWithNSP: |
| case PrefetchProxyPrefetchStatus::kPrefetchUsedProbeSuccessWithNSP: |
| case PrefetchProxyPrefetchStatus::kPrefetchNotUsedProbeFailedWithNSP: |
| case PrefetchProxyPrefetchStatus::kPrefetchUsedNoProbeNSPAttemptDenied: |
| case PrefetchProxyPrefetchStatus::kPrefetchUsedProbeSuccessNSPAttemptDenied: |
| case PrefetchProxyPrefetchStatus:: |
| kPrefetchNotUsedProbeFailedNSPAttemptDenied: |
| case PrefetchProxyPrefetchStatus::kPrefetchUsedNoProbeNSPNotStarted: |
| case PrefetchProxyPrefetchStatus::kPrefetchUsedProbeSuccessNSPNotStarted: |
| case PrefetchProxyPrefetchStatus::kPrefetchNotUsedProbeFailedNSPNotStarted: |
| case PrefetchProxyPrefetchStatus::kPrefetchIsPrivacyDecoy: |
| case PrefetchProxyPrefetchStatus::kPrefetchIsStale: |
| case PrefetchProxyPrefetchStatus::kPrefetchIsStaleWithNSP: |
| case PrefetchProxyPrefetchStatus::kPrefetchIsStaleNSPAttemptDenied: |
| case PrefetchProxyPrefetchStatus::kPrefetchIsStaleNSPNotStarted: |
| case PrefetchProxyPrefetchStatus::kPrefetchNotUsedCookiesChanged: |
| case PrefetchProxyPrefetchStatus::kPrefetchFailedRedirectsDisabled: |
| // These statuses should not be returned by the eligibility checks, and |
| // thus not be passed in here. |
| NOTREACHED(); |
| return false; |
| } |
| } |
| |
| void RecordPrefetchProxyPrefetchMainframeCookiesToCopy( |
| size_t cookie_list_size) { |
| UMA_HISTOGRAM_COUNTS_100("PrefetchProxy.Prefetch.Mainframe.CookiesToCopy", |
| cookie_list_size); |
| } |
| |
| } // namespace |
| |
| PrefetchProxyTabHelper::PrefetchMetrics::PrefetchMetrics() = default; |
| PrefetchProxyTabHelper::PrefetchMetrics::~PrefetchMetrics() = default; |
| |
| PrefetchProxyTabHelper::AfterSRPMetrics::AfterSRPMetrics() = default; |
| PrefetchProxyTabHelper::AfterSRPMetrics::AfterSRPMetrics( |
| const AfterSRPMetrics& other) = default; |
| PrefetchProxyTabHelper::AfterSRPMetrics::~AfterSRPMetrics() = default; |
| |
| PrefetchProxyTabHelper::CurrentPageLoad::CurrentPageLoad( |
| content::NavigationHandle* handle) |
| : profile_(handle ? Profile::FromBrowserContext( |
| handle->GetWebContents()->GetBrowserContext()) |
| : nullptr), |
| navigation_start_(handle ? handle->NavigationStart() : base::TimeTicks()), |
| srp_metrics_( |
| base::MakeRefCounted<PrefetchProxyTabHelper::PrefetchMetrics>()) {} |
| |
| PrefetchProxyTabHelper::CurrentPageLoad::~CurrentPageLoad() { |
| if (PrefetchProxyStartsSpareRenderer()) { |
| UMA_HISTOGRAM_COUNTS_100("PrefetchProxy.SpareRenderer.CountStartedOnSRP", |
| number_of_spare_renderers_started_); |
| } |
| |
| if (!profile_) |
| return; |
| |
| PrefetchProxyService* service = |
| PrefetchProxyServiceFactory::GetForProfile(profile_); |
| if (!service) { |
| return; |
| } |
| |
| for (const auto& iter : prefetch_containers_) { |
| PrefetchContainer::NoStatePrefetchStatus nsp_status = |
| iter.second->GetNoStatePrefetchStatus(); |
| if (nsp_status != PrefetchContainer::NoStatePrefetchStatus::kNotStarted && |
| nsp_status != PrefetchContainer::NoStatePrefetchStatus::kFailed) { |
| service->DestroySubresourceManagerForURL(iter.second->GetUrl()); |
| } |
| } |
| } |
| |
| static content::ServiceWorkerContext* g_service_worker_context_for_test = |
| nullptr; |
| |
| // static |
| void PrefetchProxyTabHelper::SetServiceWorkerContextForTest( |
| content::ServiceWorkerContext* context) { |
| g_service_worker_context_for_test = context; |
| } |
| |
| // static |
| void PrefetchProxyTabHelper::SetHostNonUniqueFilterForTest( |
| bool (*filter)(base::StringPiece)) { |
| g_host_non_unique_filter = filter; |
| } |
| |
| // static |
| void PrefetchProxyTabHelper::ResetHostNonUniqueFilterForTest() { |
| g_host_non_unique_filter = nullptr; |
| } |
| |
| PrefetchProxyTabHelper::PrefetchProxyTabHelper( |
| content::WebContents* web_contents) |
| : content::WebContentsObserver(web_contents), |
| content::WebContentsUserData<PrefetchProxyTabHelper>(*web_contents) { |
| page_ = std::make_unique<CurrentPageLoad>(nullptr); |
| profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext()); |
| |
| NavigationPredictorKeyedService* navigation_predictor_service = |
| NavigationPredictorKeyedServiceFactory::GetForProfile(profile_); |
| if (navigation_predictor_service) { |
| navigation_predictor_service->AddObserver(this); |
| } |
| |
| // Make sure the global service is up and running so that the service worker |
| // registrations can be queried before the first navigation prediction. |
| PrefetchProxyServiceFactory::GetForProfile(profile_); |
| } |
| |
| PrefetchProxyTabHelper::~PrefetchProxyTabHelper() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| NavigationPredictorKeyedService* navigation_predictor_service = |
| NavigationPredictorKeyedServiceFactory::GetForProfile(profile_); |
| if (navigation_predictor_service) { |
| navigation_predictor_service->RemoveObserver(this); |
| } |
| } |
| |
| void PrefetchProxyTabHelper::AddObserverForTesting(Observer* observer) { |
| observer_list_.AddObserver(observer); |
| } |
| |
| void PrefetchProxyTabHelper::RemoveObserverForTesting(Observer* observer) { |
| observer_list_.RemoveObserver(observer); |
| } |
| |
| network::mojom::NetworkContext* |
| PrefetchProxyTabHelper::GetIsolatedContextForTesting(const GURL& url) const { |
| PrefetchProxyNetworkContext* network_context = |
| page_->GetNetworkContextForUrl(url); |
| if (!network_context) |
| return nullptr; |
| return network_context->GetNetworkContext(); |
| } |
| |
| absl::optional<PrefetchProxyTabHelper::AfterSRPMetrics> |
| PrefetchProxyTabHelper::after_srp_metrics() const { |
| if (page_->after_srp_metrics_) { |
| return *(page_->after_srp_metrics_); |
| } |
| return absl::nullopt; |
| } |
| |
| // static |
| bool PrefetchProxyTabHelper::IsProfileEligible(Profile* profile) { |
| if (profile->IsOffTheRecord()) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool PrefetchProxyTabHelper::IsProfileEligible() const { |
| return IsProfileEligible(profile_); |
| } |
| |
| void PrefetchProxyTabHelper::DidStartNavigation( |
| content::NavigationHandle* navigation_handle) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!navigation_handle->IsInPrimaryMainFrame()) { |
| return; |
| } |
| |
| // This check is only relevant for detecting AMP pages. For this feature, AMP |
| // pages won't get sped up any so just ignore them. |
| if (navigation_handle->IsSameDocument()) { |
| return; |
| } |
| |
| // Don't take any actions during a prefetch since it was probably triggered |
| // by another instance of this class and we don't want to interfere. |
| prerender::NoStatePrefetchManager* no_state_prefetch_manager = |
| prerender::NoStatePrefetchManagerFactory::GetForBrowserContext(profile_); |
| if (no_state_prefetch_manager && |
| no_state_prefetch_manager->IsWebContentsPrefetching(web_contents())) { |
| return; |
| } |
| |
| // User is navigating, don't bother prefetching further. |
| page_->url_loaders_.clear(); |
| |
| if (page_->srp_metrics_->prefetch_attempted_count_ > 0) { |
| UMA_HISTOGRAM_COUNTS_100( |
| "PrefetchProxy.Prefetch.Mainframe.TotalRedirects", |
| page_->srp_metrics_->prefetch_total_redirect_count_); |
| } |
| |
| const GURL& url = navigation_handle->GetURL(); |
| |
| // If the cookies associated with |url| have changed since the initial |
| // eligibility check, then we shouldn't serve prefetched resources. |
| if (HaveCookiesChanged(url)) { |
| OnPrefetchStatusUpdate( |
| url, PrefetchProxyPrefetchStatus::kPrefetchNotUsedCookiesChanged); |
| return; |
| } |
| |
| PrepareToServe(url); |
| } |
| |
| void PrefetchProxyTabHelper::PrepareToServe(const GURL& url) { |
| // TODO(https://crbug.com/1238926): At this point in the navigation it's not |
| // guaranteed that we serve the prefetch, so consider moving the cookies to |
| // the interception path for robustness. |
| auto prefetch_container_iter = page_->prefetch_containers_.find(url); |
| if (prefetch_container_iter != page_->prefetch_containers_.end() && |
| prefetch_container_iter->second->HasPrefetchedResponse()) { |
| // Content older than 5 minutes should not be served. |
| if (prefetch_container_iter->second->IsPrefetchedResponseValid( |
| PrefetchProxyCacheableDuration())) { |
| // Start copying any needed cookies over to the main profile if this page |
| // was prefetched. |
| CopyIsolatedCookiesOnAfterSRPClick(prefetch_container_iter->second.get()); |
| } |
| } |
| |
| // Notify the subresource manager (if applicable) that its page is being |
| // navigated to so that the prefetched subresources can be used from cache. |
| PrefetchProxyService* service = |
| PrefetchProxyServiceFactory::GetForProfile(profile_); |
| if (!service) |
| return; |
| |
| PrefetchProxySubresourceManager* subresource_manager = |
| service->GetSubresourceManagerForURL(url); |
| if (!subresource_manager) |
| return; |
| |
| subresource_manager->NotifyPageNavigatedToAfterSRP(); |
| } |
| |
| void PrefetchProxyTabHelper::NotifyPrefetchProbeLatency( |
| base::TimeDelta probe_latency) { |
| page_->probe_latency_ = probe_latency; |
| } |
| |
| void PrefetchProxyTabHelper::ReportProbeResult( |
| const GURL& url, |
| PrefetchProxyProbeResult result) { |
| if (!page_->prefetch_metrics_collector_) { |
| return; |
| } |
| page_->prefetch_metrics_collector_->OnMainframeNavigationProbeResult(url, |
| result); |
| } |
| |
| void PrefetchProxyTabHelper::OnPrefetchStatusUpdate( |
| const GURL& url, |
| PrefetchProxyPrefetchStatus usage) { |
| auto prefetch_container_iter = page_->prefetch_containers_.find(url); |
| if (prefetch_container_iter != page_->prefetch_containers_.end()) |
| prefetch_container_iter->second->SetPrefetchStatus(usage); |
| } |
| |
| PrefetchProxyPrefetchStatus |
| PrefetchProxyTabHelper::MaybeUpdatePrefetchStatusWithNSPContext( |
| PrefetchContainer* prefetch_container) const { |
| DCHECK(prefetch_container); |
| |
| switch (prefetch_container->GetPrefetchStatus()) { |
| // These are the statuses we want to update. |
| case PrefetchProxyPrefetchStatus::kPrefetchUsedNoProbe: |
| case PrefetchProxyPrefetchStatus::kPrefetchUsedProbeSuccess: |
| case PrefetchProxyPrefetchStatus::kPrefetchNotUsedProbeFailed: |
| case PrefetchProxyPrefetchStatus::kPrefetchIsStale: |
| break; |
| // These statuses are not applicable since the prefetch was not used after |
| // the click. |
| case PrefetchProxyPrefetchStatus::kPrefetchNotStarted: |
| case PrefetchProxyPrefetchStatus::kPrefetchNotEligibleGoogleDomain: |
| case PrefetchProxyPrefetchStatus::kPrefetchNotEligibleUserHasCookies: |
| case PrefetchProxyPrefetchStatus::kPrefetchNotEligibleUserHasServiceWorker: |
| case PrefetchProxyPrefetchStatus::kPrefetchNotEligibleSchemeIsNotHttps: |
| case PrefetchProxyPrefetchStatus:: |
| kPrefetchNotEligibleNonDefaultStoragePartition: |
| case PrefetchProxyPrefetchStatus::kPrefetchNotFinishedInTime: |
| case PrefetchProxyPrefetchStatus::kPrefetchFailedNetError: |
| case PrefetchProxyPrefetchStatus::kPrefetchFailedNon2XX: |
| case PrefetchProxyPrefetchStatus::kPrefetchFailedNotHTML: |
| case PrefetchProxyPrefetchStatus::kPrefetchSuccessful: |
| case PrefetchProxyPrefetchStatus::kNavigatedToLinkNotOnSRP: |
| case PrefetchProxyPrefetchStatus::kSubresourceThrottled: |
| case PrefetchProxyPrefetchStatus::kPrefetchPositionIneligible: |
| case PrefetchProxyPrefetchStatus::kPrefetchIneligibleRetryAfter: |
| case PrefetchProxyPrefetchStatus::kPrefetchProxyNotAvailable: |
| case PrefetchProxyPrefetchStatus::kPrefetchIsPrivacyDecoy: |
| case PrefetchProxyPrefetchStatus::kPrefetchNotUsedCookiesChanged: |
| case PrefetchProxyPrefetchStatus::kPrefetchFailedRedirectsDisabled: |
| case PrefetchProxyPrefetchStatus::kPrefetchNotEligibleHostIsNonUnique: |
| return prefetch_container->GetPrefetchStatus(); |
| // These statuses we are going to update to, and this is the only place that |
| // they are set so they are not expected to be passed in. |
| case PrefetchProxyPrefetchStatus::kPrefetchUsedNoProbeWithNSP: |
| case PrefetchProxyPrefetchStatus::kPrefetchUsedProbeSuccessWithNSP: |
| case PrefetchProxyPrefetchStatus::kPrefetchNotUsedProbeFailedWithNSP: |
| case PrefetchProxyPrefetchStatus::kPrefetchUsedNoProbeNSPAttemptDenied: |
| case PrefetchProxyPrefetchStatus::kPrefetchUsedProbeSuccessNSPAttemptDenied: |
| case PrefetchProxyPrefetchStatus:: |
| kPrefetchNotUsedProbeFailedNSPAttemptDenied: |
| case PrefetchProxyPrefetchStatus::kPrefetchUsedNoProbeNSPNotStarted: |
| case PrefetchProxyPrefetchStatus::kPrefetchUsedProbeSuccessNSPNotStarted: |
| case PrefetchProxyPrefetchStatus::kPrefetchNotUsedProbeFailedNSPNotStarted: |
| case PrefetchProxyPrefetchStatus::kPrefetchIsStaleWithNSP: |
| case PrefetchProxyPrefetchStatus::kPrefetchIsStaleNSPAttemptDenied: |
| case PrefetchProxyPrefetchStatus::kPrefetchIsStaleNSPNotStarted: |
| NOTREACHED(); |
| return prefetch_container->GetPrefetchStatus(); |
| } |
| |
| PrefetchContainer::NoStatePrefetchStatus nsp_status = |
| prefetch_container->GetNoStatePrefetchStatus(); |
| |
| if (nsp_status == PrefetchContainer::NoStatePrefetchStatus::kNotStarted) { |
| return prefetch_container->GetPrefetchStatus(); |
| } |
| |
| if (nsp_status == PrefetchContainer::NoStatePrefetchStatus::kSucceeded) { |
| switch (prefetch_container->GetPrefetchStatus()) { |
| case PrefetchProxyPrefetchStatus::kPrefetchUsedNoProbe: |
| return PrefetchProxyPrefetchStatus::kPrefetchUsedNoProbeWithNSP; |
| case PrefetchProxyPrefetchStatus::kPrefetchUsedProbeSuccess: |
| return PrefetchProxyPrefetchStatus::kPrefetchUsedProbeSuccessWithNSP; |
| case PrefetchProxyPrefetchStatus::kPrefetchNotUsedProbeFailed: |
| return PrefetchProxyPrefetchStatus::kPrefetchNotUsedProbeFailedWithNSP; |
| case PrefetchProxyPrefetchStatus::kPrefetchIsStale: |
| return PrefetchProxyPrefetchStatus::kPrefetchIsStaleWithNSP; |
| default: |
| break; |
| } |
| } |
| |
| if (nsp_status == PrefetchContainer::NoStatePrefetchStatus::kFailed) { |
| switch (prefetch_container->GetPrefetchStatus()) { |
| case PrefetchProxyPrefetchStatus::kPrefetchUsedNoProbe: |
| return PrefetchProxyPrefetchStatus:: |
| kPrefetchUsedNoProbeNSPAttemptDenied; |
| case PrefetchProxyPrefetchStatus::kPrefetchUsedProbeSuccess: |
| return PrefetchProxyPrefetchStatus:: |
| kPrefetchUsedProbeSuccessNSPAttemptDenied; |
| case PrefetchProxyPrefetchStatus::kPrefetchNotUsedProbeFailed: |
| return PrefetchProxyPrefetchStatus:: |
| kPrefetchNotUsedProbeFailedNSPAttemptDenied; |
| case PrefetchProxyPrefetchStatus::kPrefetchIsStale: |
| return PrefetchProxyPrefetchStatus::kPrefetchIsStaleNSPAttemptDenied; |
| default: |
| break; |
| } |
| } |
| |
| if (nsp_status == PrefetchContainer::NoStatePrefetchStatus::kInProgress) { |
| switch (prefetch_container->GetPrefetchStatus()) { |
| case PrefetchProxyPrefetchStatus::kPrefetchUsedNoProbe: |
| return PrefetchProxyPrefetchStatus::kPrefetchUsedNoProbeNSPNotStarted; |
| case PrefetchProxyPrefetchStatus::kPrefetchUsedProbeSuccess: |
| return PrefetchProxyPrefetchStatus:: |
| kPrefetchUsedProbeSuccessNSPNotStarted; |
| case PrefetchProxyPrefetchStatus::kPrefetchNotUsedProbeFailed: |
| return PrefetchProxyPrefetchStatus:: |
| kPrefetchNotUsedProbeFailedNSPNotStarted; |
| case PrefetchProxyPrefetchStatus::kPrefetchIsStale: |
| return PrefetchProxyPrefetchStatus::kPrefetchIsStaleNSPNotStarted; |
| default: |
| break; |
| } |
| } |
| |
| NOTREACHED(); |
| return prefetch_container->GetPrefetchStatus(); |
| } |
| |
| std::unique_ptr<PrefetchProxyTabHelper::AfterSRPMetrics> |
| PrefetchProxyTabHelper::ComputeAfterSRPMetricsBeforeCommit( |
| content::NavigationHandle* handle) const { |
| if (page_->srp_metrics_->predicted_urls_count_ <= 0) { |
| return nullptr; |
| } |
| |
| auto metrics = std::make_unique<AfterSRPMetrics>(); |
| |
| metrics->url_ = handle->GetURL(); |
| metrics->prefetch_eligible_count_ = |
| page_->srp_metrics_->prefetch_eligible_count_; |
| |
| metrics->probe_latency_ = page_->probe_latency_; |
| |
| // Check every url in the redirect chain for a status, starting at the end |
| // and working backwards. Note: When a redirect chain is eligible all the |
| // way to the end, the status is already propagated. But if a redirect was |
| // not eligible then this will find its last known status. |
| DCHECK(!handle->GetRedirectChain().empty()); |
| absl::optional<PrefetchProxyPrefetchStatus> status; |
| absl::optional<size_t> prediction_position; |
| for (const GURL& chain_url : base::Reversed(handle->GetRedirectChain())) { |
| auto container_iter = page_->prefetch_containers_.find(chain_url); |
| if (!status && container_iter != page_->prefetch_containers_.end() && |
| container_iter->second->HasPrefetchStatus()) { |
| status = |
| MaybeUpdatePrefetchStatusWithNSPContext(container_iter->second.get()); |
| } |
| |
| // Same check for the original prediction ordering. |
| if (!prediction_position && |
| container_iter != page_->prefetch_containers_.end()) { |
| prediction_position = |
| container_iter->second->GetOriginalPredictionIndex(); |
| } |
| } |
| |
| if (status) { |
| metrics->prefetch_status_ = *status; |
| } else { |
| metrics->prefetch_status_ = |
| PrefetchProxyPrefetchStatus::kNavigatedToLinkNotOnSRP; |
| } |
| metrics->clicked_link_srp_position_ = prediction_position; |
| |
| return metrics; |
| } |
| |
| void PrefetchProxyTabHelper::DidFinishNavigation( |
| content::NavigationHandle* navigation_handle) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!navigation_handle->IsInPrimaryMainFrame()) { |
| return; |
| } |
| |
| // This check is only relevant for detecting AMP pages. For this feature, AMP |
| // pages won't get sped up any so just ignore them. |
| if (navigation_handle->IsSameDocument()) { |
| return; |
| } |
| |
| if (!navigation_handle->HasCommitted()) { |
| return; |
| } |
| |
| // Don't take any actions during a prefetch since it was probably triggered |
| // by another instance of this class and we don't want to interfere. |
| prerender::NoStatePrefetchManager* no_state_prefetch_manager = |
| prerender::NoStatePrefetchManagerFactory::GetForBrowserContext(profile_); |
| if (no_state_prefetch_manager && |
| no_state_prefetch_manager->IsWebContentsPrefetching(web_contents())) { |
| return; |
| } |
| |
| // Ensure there's no ongoing prefetches. |
| page_->url_loaders_.clear(); |
| |
| GURL url = navigation_handle->GetURL(); |
| |
| std::unique_ptr<CurrentPageLoad> new_page = |
| std::make_unique<CurrentPageLoad>(navigation_handle); |
| |
| if (page_->srp_metrics_->predicted_urls_count_ > 0) { |
| page_->prefetch_metrics_collector_->OnMainframeNavigatedTo(url); |
| |
| // If the previous page load was a Google SRP, the AfterSRPMetrics class |
| // needs to be created now from the SRP's |page_| and then set on the new |
| // one when we set it at the end of this method. |
| new_page->after_srp_metrics_ = |
| ComputeAfterSRPMetricsBeforeCommit(navigation_handle); |
| |
| // See if the page being navigated to was prefetched. If so, copy over its |
| // subresource manager and networking pipes. |
| PrefetchProxyService* service = |
| PrefetchProxyServiceFactory::GetForProfile(profile_); |
| std::unique_ptr<PrefetchProxySubresourceManager> manager = |
| service->TakeSubresourceManagerForURL(url); |
| if (manager) { |
| new_page->subresource_manager_ = std::move(manager); |
| |
| if (PrefetchProxyUseIndividualNetworkContextsForEachPrefetch()) { |
| auto prefetch_container_iter = page_->prefetch_containers_.find(url); |
| if (prefetch_container_iter != page_->prefetch_containers_.end() && |
| prefetch_container_iter->second->GetNetworkContext()) { |
| new_page->previous_network_context_ = |
| prefetch_container_iter->second->ReleaseNetworkContext(); |
| } |
| } else { |
| new_page->previous_network_context_ = |
| std::move(page_->network_context_); |
| } |
| } |
| } |
| |
| // |page_| is reset on commit so that any available cached prefetches that |
| // result from a redirect get used. |
| page_ = std::move(new_page); |
| } |
| |
| void PrefetchProxyTabHelper::OnVisibilityChanged( |
| content::Visibility visibility) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!PrefetchProxyIsEnabled()) { |
| return; |
| } |
| |
| // Start prefetching if the tab has become visible and prefetching is |
| // inactive. Hidden and occluded visibility is ignored here so that pending |
| // prefetches can finish. |
| if (visibility == content::Visibility::VISIBLE && !PrefetchingActive()) |
| Prefetch(); |
| } |
| |
| std::unique_ptr<PrefetchedMainframeResponseContainer> |
| PrefetchProxyTabHelper::TakePrefetchResponse(const GURL& url) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| auto prefetch_container_iter = page_->prefetch_containers_.find(url); |
| if (prefetch_container_iter == page_->prefetch_containers_.end()) |
| return nullptr; |
| |
| if (!prefetch_container_iter->second->HasPrefetchedResponse()) |
| return nullptr; |
| |
| // Content older than 5 minutes should not be served. |
| if (!prefetch_container_iter->second->IsPrefetchedResponseValid( |
| PrefetchProxyCacheableDuration())) { |
| prefetch_container_iter->second->SetPrefetchStatus( |
| PrefetchProxyPrefetchStatus::kPrefetchIsStale); |
| return nullptr; |
| } |
| |
| return prefetch_container_iter->second->ReleasePrefetchedResponse(); |
| } |
| |
| std::unique_ptr<PrefetchedMainframeResponseContainer> |
| PrefetchProxyTabHelper::CopyPrefetchResponseForNSP( |
| PrefetchContainer* prefetch_container) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(prefetch_container); |
| |
| if (!prefetch_container->HasPrefetchedResponse()) |
| return nullptr; |
| |
| return prefetch_container->ClonePrefetchedResponse(); |
| } |
| |
| bool PrefetchProxyTabHelper::PrefetchingActive() const { |
| return page_ && !page_->url_loaders_.empty(); |
| } |
| |
| void PrefetchProxyTabHelper::Prefetch() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(PrefetchProxyIsEnabled()); |
| |
| if (!page_->srp_metrics_->navigation_to_prefetch_start_.has_value()) { |
| page_->srp_metrics_->navigation_to_prefetch_start_ = |
| base::TimeTicks::Now() - page_->navigation_start_; |
| DCHECK_GT(page_->srp_metrics_->navigation_to_prefetch_start_.value(), |
| base::TimeDelta()); |
| } |
| |
| if (PrefetchProxyCloseIdleSockets()) { |
| if (page_->network_context_) { |
| page_->network_context_->CloseIdleConnections(); |
| } |
| |
| for (const auto& iter : page_->prefetch_containers_) { |
| if (iter.second->GetNetworkContext()) { |
| iter.second->GetNetworkContext()->CloseIdleConnections(); |
| } |
| } |
| } |
| |
| if (web_contents()->GetVisibility() != content::Visibility::VISIBLE) { |
| // |OnVisibilityChanged| will restart prefetching when the tab becomes |
| // visible again. |
| return; |
| } |
| |
| DCHECK_GT(PrefetchProxyMaximumNumberOfConcurrentPrefetches(), 0U); |
| |
| while ( |
| // Checks that the total number of prefetches has not been met. |
| !(PrefetchProxyMaximumNumberOfPrefetches().has_value() && |
| page_->decoy_requests_attempted_ + |
| page_->srp_metrics_->prefetch_attempted_count_ >= |
| PrefetchProxyMaximumNumberOfPrefetches().value()) && |
| |
| // Checks that there are still urls to prefetch. |
| !page_->urls_to_prefetch_.empty() && |
| |
| // Checks that the max number of concurrent prefetches has not been met. |
| page_->url_loaders_.size() < |
| PrefetchProxyMaximumNumberOfConcurrentPrefetches()) { |
| StartSinglePrefetch(); |
| } |
| } |
| |
| void PrefetchProxyTabHelper::StartSinglePrefetch() { |
| DCHECK(!page_->urls_to_prefetch_.empty()); |
| DCHECK(!(PrefetchProxyMaximumNumberOfPrefetches().has_value() && |
| page_->decoy_requests_attempted_ + |
| page_->srp_metrics_->prefetch_attempted_count_ >= |
| PrefetchProxyMaximumNumberOfPrefetches().value())); |
| DCHECK(page_->url_loaders_.size() < |
| PrefetchProxyMaximumNumberOfConcurrentPrefetches()); |
| |
| PrefetchContainer* prefetch_container = page_->urls_to_prefetch_[0]; |
| page_->urls_to_prefetch_.erase(page_->urls_to_prefetch_.begin()); |
| |
| // Only update these metrics on normal prefetches. |
| if (!prefetch_container->IsDecoy()) { |
| page_->srp_metrics_->prefetch_attempted_count_++; |
| // The status is updated to be successful or failed when it finishes. |
| prefetch_container->SetPrefetchStatus( |
| PrefetchProxyPrefetchStatus::kPrefetchNotFinishedInTime); |
| } else { |
| page_->decoy_requests_attempted_++; |
| prefetch_container->SetPrefetchStatus( |
| PrefetchProxyPrefetchStatus::kPrefetchIsPrivacyDecoy); |
| } |
| |
| url::Origin origin = url::Origin::Create(prefetch_container->GetUrl()); |
| net::IsolationInfo isolation_info = net::IsolationInfo::Create( |
| net::IsolationInfo::RequestType::kMainFrame, origin, origin, |
| net::SiteForCookies::FromOrigin(origin)); |
| network::ResourceRequest::TrustedParams trusted_params; |
| trusted_params.isolation_info = isolation_info; |
| |
| std::unique_ptr<network::ResourceRequest> request = |
| std::make_unique<network::ResourceRequest>(); |
| request->url = prefetch_container->GetUrl(); |
| request->method = "GET"; |
| request->enable_load_timing = true; |
| request->load_flags = net::LOAD_DISABLE_CACHE | net::LOAD_PREFETCH; |
| request->credentials_mode = network::mojom::CredentialsMode::kInclude; |
| request->headers.SetHeader(content::kCorsExemptPurposeHeaderName, "prefetch"); |
| request->headers.SetHeader( |
| prefetch::headers::kSecPurposeHeaderName, |
| prefetch_container->GetPrefetchType().IsProxyRequired() |
| ? prefetch::headers::kSecPurposePrefetchAnonymousClientIpHeaderValue |
| : prefetch::headers::kSecPurposePrefetchHeaderValue); |
| // Remove the user agent header if it was set so that the network context's |
| // default is used. |
| request->headers.RemoveHeader("User-Agent"); |
| request->trusted_params = trusted_params; |
| request->site_for_cookies = trusted_params.isolation_info.site_for_cookies(); |
| |
| net::NetworkTrafficAnnotationTag traffic_annotation = |
| net::DefineNetworkTrafficAnnotation("navigation_predictor_srp_prefetch", |
| R"( |
| semantics { |
| sender: "Navigation Predictor SRP Prefetch Loader" |
| description: |
| "Prefetches the mainframe HTML of a page linked from a Google " |
| "Search Result Page (SRP). 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 for sites off of Google SRPs (Search Result Pages) only " |
| "for Lite mode users when the feature is enabled." |
| data: "None." |
| destination: WEBSITE |
| } |
| policy { |
| cookies_allowed: NO |
| setting: |
| "Users can control Lite mode on Android via the settings menu. " |
| "Lite mode is not available on iOS, and on desktop only for " |
| "developer testing." |
| policy_exception_justification: "Not implemented." |
| })"); |
| |
| std::unique_ptr<network::SimpleURLLoader> loader = |
| network::SimpleURLLoader::Create(std::move(request), traffic_annotation); |
| |
| // base::Unretained is safe because |loader| is owned by |this|. |
| loader->SetOnRedirectCallback(base::BindRepeating( |
| &PrefetchProxyTabHelper::OnPrefetchRedirect, base::Unretained(this), |
| loader.get(), prefetch_container->GetUrl())); |
| loader->SetAllowHttpErrorResults(true); |
| loader->SetTimeoutDuration(PrefetchProxyTimeoutDuration()); |
| loader->DownloadToString( |
| GetURLLoaderFactory(prefetch_container->GetUrl()), |
| base::BindOnce(&PrefetchProxyTabHelper::OnPrefetchComplete, |
| base::Unretained(this), loader.get(), |
| prefetch_container->GetUrl(), isolation_info), |
| PrefetchProxyMainframeBodyLengthLimit()); |
| |
| page_->url_loaders_.emplace(std::move(loader)); |
| |
| if (!prefetch_container->IsDecoy() && |
| page_->srp_metrics_->prefetch_attempted_count_ == 1) { |
| // 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/1266018): once this bug is fixed, fire off canary check |
| // regardless of whether the request is a decoy or not. |
| PrefetchProxyService* service = |
| PrefetchProxyServiceFactory::GetForProfile(profile_); |
| service->origin_prober()->RunCanaryChecksIfNeeded(); |
| } |
| |
| // Start a spare renderer now so that it will be ready by the time it is |
| // useful to have. |
| if (ShouldStartSpareRenderer()) { |
| StartSpareRenderer(); |
| } |
| } |
| |
| void PrefetchProxyTabHelper::OnPrefetchRedirect( |
| network::SimpleURLLoader* loader, |
| const GURL& original_url, |
| const net::RedirectInfo& redirect_info, |
| const network::mojom::URLResponseHead& response_head, |
| std::vector<std::string>* removed_headers) { |
| DCHECK(PrefetchingActive()); |
| |
| // Currently all redirects are disabled when using the prefetch proxy. See |
| // crbug.com/1266876 for more details. |
| OnPrefetchStatusUpdate( |
| original_url, |
| PrefetchProxyPrefetchStatus::kPrefetchFailedRedirectsDisabled); |
| |
| // Cancels the current request. |
| DCHECK(page_->url_loaders_.find(loader) != page_->url_loaders_.end()); |
| page_->url_loaders_.erase(page_->url_loaders_.find(loader)); |
| |
| // Continue prefetching other urls. |
| Prefetch(); |
| } |
| |
| void PrefetchProxyTabHelper::OnPrefetchComplete( |
| network::SimpleURLLoader* loader, |
| const GURL& url, |
| const net::IsolationInfo& isolation_info, |
| std::unique_ptr<std::string> body) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(PrefetchingActive()); |
| |
| auto prefetch_container_iter = page_->prefetch_containers_.find(url); |
| DCHECK(prefetch_container_iter != page_->prefetch_containers_.end()); |
| |
| if (prefetch_container_iter->second->IsDecoy()) { |
| if (loader->CompletionStatus()) { |
| page_->prefetch_metrics_collector_->OnDecoyPrefetchComplete( |
| url, prefetch_container_iter->second->GetOriginalPredictionIndex(), |
| loader->ResponseInfo() ? loader->ResponseInfo()->Clone() : nullptr, |
| loader->CompletionStatus().value()); |
| } |
| |
| for (auto& observer : observer_list_) { |
| observer.OnDecoyPrefetchCompleted(url); |
| } |
| |
| // Do nothing with the response, i.e.: don't cache it. |
| |
| // Cancels the current request. |
| DCHECK(page_->url_loaders_.find(loader) != page_->url_loaders_.end()); |
| page_->url_loaders_.erase(page_->url_loaders_.find(loader)); |
| |
| Prefetch(); |
| return; |
| } |
| |
| base::UmaHistogramSparse("PrefetchProxy.Prefetch.Mainframe.NetError", |
| std::abs(loader->NetError())); |
| |
| if (loader->CompletionStatus()) { |
| page_->prefetch_metrics_collector_->OnMainframeResourcePrefetched( |
| url, prefetch_container_iter->second->GetOriginalPredictionIndex(), |
| loader->ResponseInfo() ? loader->ResponseInfo()->Clone() : nullptr, |
| loader->CompletionStatus().value()); |
| } |
| |
| if (loader->NetError() != net::OK) { |
| prefetch_container_iter->second->SetPrefetchStatus( |
| PrefetchProxyPrefetchStatus::kPrefetchFailedNetError); |
| |
| for (auto& observer : observer_list_) { |
| observer.OnPrefetchCompletedWithError(url, loader->NetError()); |
| } |
| } |
| |
| if (loader->NetError() == net::OK && body && loader->ResponseInfo()) { |
| network::mojom::URLResponseHeadPtr head = loader->ResponseInfo()->Clone(); |
| |
| // Verifies that the request was made using the prefetch proxy if required, |
| // or made directly if the proxy was not required. |
| DCHECK( |
| !head->proxy_server.is_direct() == |
| prefetch_container_iter->second->GetPrefetchType().IsProxyRequired()); |
| |
| HandlePrefetchResponse(prefetch_container_iter->second.get(), |
| isolation_info, std::move(head), std::move(body)); |
| } |
| |
| DCHECK(page_->url_loaders_.find(loader) != page_->url_loaders_.end()); |
| page_->url_loaders_.erase(page_->url_loaders_.find(loader)); |
| |
| Prefetch(); |
| } |
| |
| void PrefetchProxyTabHelper::HandlePrefetchResponse( |
| PrefetchContainer* prefetch_container, |
| const net::IsolationInfo& isolation_info, |
| network::mojom::URLResponseHeadPtr head, |
| std::unique_ptr<std::string> body) { |
| DCHECK(!head->was_fetched_via_cache); |
| DCHECK(prefetch_container); |
| |
| if (!head->headers) |
| return; |
| |
| UMA_HISTOGRAM_COUNTS_10M("PrefetchProxy.Prefetch.Mainframe.BodyLength", |
| body->size()); |
| |
| absl::optional<base::TimeDelta> total_time = GetTotalPrefetchTime(head.get()); |
| if (total_time) { |
| UMA_HISTOGRAM_CUSTOM_TIMES("PrefetchProxy.Prefetch.Mainframe.TotalTime", |
| *total_time, base::Milliseconds(10), |
| base::Seconds(30), 100); |
| } |
| |
| absl::optional<base::TimeDelta> connect_time = |
| GetPrefetchConnectTime(head.get()); |
| if (connect_time) { |
| UMA_HISTOGRAM_TIMES("PrefetchProxy.Prefetch.Mainframe.ConnectTime", |
| *connect_time); |
| } |
| |
| int response_code = head->headers->response_code(); |
| |
| base::UmaHistogramSparse("PrefetchProxy.Prefetch.Mainframe.RespCode", |
| response_code); |
| |
| if (response_code < 200 || response_code >= 300) { |
| prefetch_container->SetPrefetchStatus( |
| PrefetchProxyPrefetchStatus::kPrefetchFailedNon2XX); |
| for (auto& observer : observer_list_) { |
| observer.OnPrefetchCompletedWithError(prefetch_container->GetUrl(), |
| response_code); |
| } |
| |
| 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)) { |
| PrefetchProxyService* service = |
| PrefetchProxyServiceFactory::GetForProfile(profile_); |
| service->origin_decider()->ReportOriginRetryAfter( |
| prefetch_container->GetUrl(), retry_after); |
| } |
| } |
| |
| return; |
| } |
| |
| if (head->mime_type != "text/html") { |
| prefetch_container->SetPrefetchStatus( |
| PrefetchProxyPrefetchStatus::kPrefetchFailedNotHTML); |
| return; |
| } |
| |
| prefetch_container->SetPrefetchedResponse( |
| std::make_unique<PrefetchedMainframeResponseContainer>( |
| isolation_info, std::move(head), std::move(body))); |
| page_->srp_metrics_->prefetch_successful_count_++; |
| |
| prefetch_container->SetPrefetchStatus( |
| PrefetchProxyPrefetchStatus::kPrefetchSuccessful); |
| |
| MaybeDoNoStatePrefetch(prefetch_container); |
| |
| for (auto& observer : observer_list_) { |
| observer.OnPrefetchCompletedSuccessfully(prefetch_container->GetUrl()); |
| } |
| } |
| |
| void PrefetchProxyTabHelper::MaybeDoNoStatePrefetch( |
| PrefetchContainer* prefetch_container) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(prefetch_container); |
| |
| if (!PrefetchProxyNoStatePrefetchSubresources()) { |
| return; |
| } |
| |
| // Not all prefetches are eligible for NSP, which fetches subresources. |
| if (!prefetch_container->GetPrefetchType().AllowedToPrefetchSubresources()) |
| return; |
| |
| page_->urls_to_no_state_prefetch_.push_back(prefetch_container); |
| prefetch_container->SetNoStatePrefetchStatus( |
| PrefetchContainer::NoStatePrefetchStatus::kInProgress); |
| DoNoStatePrefetch(); |
| } |
| |
| void PrefetchProxyTabHelper::DoNoStatePrefetch() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (page_->urls_to_no_state_prefetch_.empty()) { |
| return; |
| } |
| |
| // Ensure there is not an active navigation. |
| if (web_contents()->GetController().GetPendingEntry()) { |
| return; |
| } |
| |
| absl::optional<size_t> max_attempts = |
| PrefetchProxyMaximumNumberOfNoStatePrefetchAttempts(); |
| if (max_attempts.has_value() && |
| page_->number_of_no_state_prefetch_attempts_ >= max_attempts.value()) { |
| return; |
| } |
| |
| prerender::NoStatePrefetchManager* no_state_prefetch_manager = |
| prerender::NoStatePrefetchManagerFactory::GetForBrowserContext(profile_); |
| if (!no_state_prefetch_manager) { |
| return; |
| } |
| |
| PrefetchProxyService* service = |
| PrefetchProxyServiceFactory::GetForProfile(profile_); |
| if (!service) { |
| return; |
| } |
| |
| PrefetchContainer* prefetch_container = page_->urls_to_no_state_prefetch_[0]; |
| |
| // Don't start another NSP until the previous one finishes. |
| { |
| PrefetchProxySubresourceManager* manager = |
| service->GetSubresourceManagerForURL(prefetch_container->GetUrl()); |
| if (manager && manager->has_nsp_handle()) { |
| return; |
| } |
| } |
| |
| // The manager must be created here so that the mainframe response can be |
| // given to the URLLoaderInterceptor in this call stack, but may be destroyed |
| // before the end of the method if the handle is not created. |
| PrefetchProxySubresourceManager* manager = service->OnAboutToNoStatePrefetch( |
| prefetch_container->GetUrl(), |
| CopyPrefetchResponseForNSP(prefetch_container)); |
| DCHECK_EQ(manager, |
| service->GetSubresourceManagerForURL(prefetch_container->GetUrl())); |
| |
| manager->SetPrefetchMetricsCollector(page_->prefetch_metrics_collector_); |
| |
| DCHECK(page_->GetNetworkContextForUrl(prefetch_container->GetUrl())); |
| manager->SetCreateIsolatedLoaderFactoryCallback(base::BindRepeating( |
| &PrefetchProxyNetworkContext::CreateNewUrlLoaderFactory, |
| page_->GetNetworkContextForUrl(prefetch_container->GetUrl()) |
| ->GetWeakPtr())); |
| |
| content::SessionStorageNamespace* session_storage_namespace = |
| web_contents()->GetController().GetDefaultSessionStorageNamespace(); |
| gfx::Size size = web_contents()->GetContainerBounds().size(); |
| |
| std::unique_ptr<prerender::NoStatePrefetchHandle> handle = |
| no_state_prefetch_manager->AddIsolatedPrerender( |
| prefetch_container->GetUrl(), session_storage_namespace, size); |
| |
| if (!handle) { |
| // Clean up the prefetch response in |service| since it wasn't used. |
| service->DestroySubresourceManagerForURL(prefetch_container->GetUrl()); |
| // Don't use |manager| again! |
| |
| prefetch_container->SetNoStatePrefetchStatus( |
| PrefetchContainer::NoStatePrefetchStatus::kFailed); |
| |
| // Try the next URL. |
| page_->urls_to_no_state_prefetch_.erase( |
| page_->urls_to_no_state_prefetch_.begin()); |
| DoNoStatePrefetch(); |
| return; |
| } |
| |
| page_->number_of_no_state_prefetch_attempts_++; |
| |
| // It is possible for the manager to be destroyed during the NoStatePrefetch |
| // navigation. If this happens, abort the NSP and try again. |
| manager = service->GetSubresourceManagerForURL(prefetch_container->GetUrl()); |
| if (!manager) { |
| handle->OnCancel(); |
| handle.reset(); |
| |
| prefetch_container->SetNoStatePrefetchStatus( |
| PrefetchContainer::NoStatePrefetchStatus::kFailed); |
| |
| // Try the next URL. |
| page_->urls_to_no_state_prefetch_.erase( |
| page_->urls_to_no_state_prefetch_.begin()); |
| DoNoStatePrefetch(); |
| return; |
| } |
| |
| manager->ManageNoStatePrefetch( |
| std::move(handle), |
| base::BindOnce(&PrefetchProxyTabHelper::OnPrerenderDone, |
| weak_factory_.GetWeakPtr(), prefetch_container->GetUrl())); |
| } |
| |
| void PrefetchProxyTabHelper::OnPrerenderDone(const GURL& url) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // The completed NSP probably consumed a previously started spare renderer, so |
| // kick off another one if needed. |
| if (ShouldStartSpareRenderer()) { |
| StartSpareRenderer(); |
| } |
| |
| // It is possible that this is run as a callback after a navigation has |
| // already happened and |page_| is now a different instance than when the |
| // prerender was started. In this case, just return. |
| |
| if (page_->urls_to_no_state_prefetch_.empty() || |
| url != page_->urls_to_no_state_prefetch_[0]->GetUrl()) { |
| return; |
| } |
| |
| auto prefetch_container_iter = page_->prefetch_containers_.find(url); |
| if (prefetch_container_iter == page_->prefetch_containers_.end()) |
| return; |
| |
| prefetch_container_iter->second->SetNoStatePrefetchStatus( |
| PrefetchContainer::NoStatePrefetchStatus::kSucceeded); |
| |
| for (auto& observer : observer_list_) { |
| observer.OnNoStatePrefetchFinished(); |
| } |
| |
| page_->urls_to_no_state_prefetch_.erase( |
| page_->urls_to_no_state_prefetch_.begin()); |
| |
| DoNoStatePrefetch(); |
| } |
| |
| void PrefetchProxyTabHelper::StartSpareRenderer() { |
| page_->number_of_spare_renderers_started_++; |
| content::RenderProcessHost::WarmupSpareRenderProcessHost(profile_); |
| } |
| |
| void PrefetchProxyTabHelper::PrefetchSpeculationCandidates( |
| const std::vector<std::pair<GURL, PrefetchType>>& prefetches, |
| const GURL& source_document_url) { |
| // Use navigation predictor by default. |
| if (!PrefetchProxyUseSpeculationRules()) |
| return; |
| |
| // For IP-private prefetches, using the Google proxy needs to be restricted to |
| // first party sites unless users opted-in to extended preloading. |
| std::vector<std::pair<GURL, PrefetchType>> filtered_prefetches = prefetches; |
| const bool allow_all_domains = |
| PrefetchProxyAllowAllDomains() || |
| (PrefetchProxyAllowAllDomainsForExtendedPreloading() && |
| prefetch::GetPreloadPagesState(*profile_->GetPrefs()) == |
| prefetch::PreloadPagesState::kExtendedPreloading); |
| if (!allow_all_domains && |
| !IsGoogleDomainUrl(source_document_url, google_util::ALLOW_SUBDOMAIN, |
| google_util::ALLOW_NON_STANDARD_PORTS) && |
| !IsYoutubeDomainUrl(source_document_url, google_util::ALLOW_SUBDOMAIN, |
| google_util::ALLOW_NON_STANDARD_PORTS)) { |
| // Filter out prefetches that require the Google proxy. |
| auto new_end = |
| std::remove_if(filtered_prefetches.begin(), filtered_prefetches.end(), |
| [](const std::pair<GURL, PrefetchType>& prefetch) { |
| return prefetch.second.IsProxyRequired(); |
| }); |
| filtered_prefetches.erase(new_end, filtered_prefetches.end()); |
| } |
| |
| PrefetchUrls(filtered_prefetches); |
| } |
| |
| void PrefetchProxyTabHelper::OnPredictionUpdated( |
| const absl::optional<NavigationPredictorKeyedService::Prediction> |
| prediction) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // Use speculation rules API instead of navigation predictor. |
| if (PrefetchProxyUseSpeculationRules()) |
| return; |
| |
| if (!prediction.has_value()) { |
| return; |
| } |
| |
| if (prediction->prediction_source() != |
| NavigationPredictorKeyedService::PredictionSource:: |
| kAnchorElementsParsedFromWebPage) { |
| return; |
| } |
| |
| if (prediction.value().web_contents() != web_contents()) { |
| // We only care about predictions in this tab. |
| return; |
| } |
| |
| const absl::optional<GURL>& source_document_url = |
| prediction->source_document_url(); |
| |
| if (!source_document_url || source_document_url->is_empty()) |
| return; |
| |
| if (!google_util::IsGoogleSearchUrl(source_document_url.value())) { |
| return; |
| } |
| |
| // For the navigation predictor approach, we assume all predicted URLs are |
| // eligible for NSP. |
| std::vector<std::pair<GURL, PrefetchType>> prefetches; |
| for (const auto& url : prediction.value().sorted_predicted_urls()) { |
| prefetches.emplace_back(url, |
| PrefetchType(/*use_isolated_network_context=*/true, |
| /*use_prefetch_proxy=*/true, |
| /*can_prefetch_subresources=*/true)); |
| } |
| PrefetchUrls(prefetches); |
| } |
| |
| void PrefetchProxyTabHelper::PrefetchUrls( |
| const std::vector<std::pair<GURL, PrefetchType>>& prefetch_targets) { |
| if (!PrefetchProxyIsEnabled()) { |
| return; |
| } |
| |
| if (!IsProfileEligible()) { |
| return; |
| } |
| |
| // This checks whether the user has disabled pre* actions in the settings UI. |
| if (!prefetch::IsSomePreloadingEnabled(*profile_->GetPrefs())) { |
| return; |
| } |
| |
| if (!page_->prefetch_metrics_collector_) { |
| page_->prefetch_metrics_collector_ = |
| base::MakeRefCounted<PrefetchProxyPrefetchMetricsCollector>( |
| page_->navigation_start_, |
| web_contents()->GetMainFrame()->GetPageUkmSourceId()); |
| } |
| |
| // Add new prefetches, and update the type for any existing prefetches. |
| std::vector<std::pair<GURL, PrefetchType>> new_targets; |
| for (const auto& prefetch_with_type : prefetch_targets) { |
| auto prefetch_container_iter = |
| page_->prefetch_containers_.find(prefetch_with_type.first); |
| if (prefetch_container_iter == page_->prefetch_containers_.end()) { |
| new_targets.push_back(prefetch_with_type); |
| |
| // It is possible, since it is not stipulated by the API contract, that |
| // the navigation predictor will issue multiple predictions during a |
| // single page load. Additional predictions should be treated as appending |
| // to the ordering of previous predictions. |
| page_->prefetch_containers_[prefetch_with_type.first] = |
| std::make_unique<PrefetchContainer>( |
| prefetch_with_type.first, prefetch_with_type.second, |
| page_->prefetch_containers_.size()); |
| } else if (prefetch_with_type.second != |
| prefetch_container_iter->second->GetPrefetchType()) { |
| prefetch_container_iter->second->ChangePrefetchType( |
| prefetch_with_type.second); |
| } |
| } |
| |
| // It's very likely we'll prefetch something at this point, so inform PLM to |
| // start tracking metrics. |
| InformPLMOfLikelyPrefetching(web_contents()); |
| |
| page_->srp_metrics_->predicted_urls_count_ += new_targets.size(); |
| |
| for (const auto& prefetch_with_type : new_targets) { |
| CheckEligibilityOfURL( |
| profile_, prefetch_with_type.first, prefetch_with_type.second, |
| base::BindOnce(&PrefetchProxyTabHelper::OnGotEligibilityResult, |
| weak_factory_.GetWeakPtr())); |
| } |
| } |
| |
| // static |
| content::ServiceWorkerContext* PrefetchProxyTabHelper::GetServiceWorkerContext( |
| Profile* profile) { |
| if (g_service_worker_context_for_test) |
| return g_service_worker_context_for_test; |
| return profile->GetDefaultStoragePartition()->GetServiceWorkerContext(); |
| } |
| |
| // static |
| std::pair<bool, absl::optional<PrefetchProxyPrefetchStatus>> |
| PrefetchProxyTabHelper::CheckEligibilityOfURLSansUserData( |
| Profile* profile, |
| const GURL& url, |
| const PrefetchType& prefetch_type) { |
| if (!IsProfileEligible(profile)) { |
| return std::make_pair(false, absl::nullopt); |
| } |
| |
| if (!PrefetchProxyUseSpeculationRules() && |
| google_util::IsGoogleAssociatedDomainUrl(url)) { |
| return std::make_pair( |
| false, PrefetchProxyPrefetchStatus::kPrefetchNotEligibleGoogleDomain); |
| } |
| |
| // 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. |
| if (prefetch_type.IsProxyRequired() && |
| (g_host_non_unique_filter |
| ? g_host_non_unique_filter(url.HostNoBracketsPiece()) |
| : net::IsHostnameNonUnique(url.HostNoBrackets()))) { |
| return std::make_pair( |
| false, |
| PrefetchProxyPrefetchStatus::kPrefetchNotEligibleHostIsNonUnique); |
| } |
| |
| if (!url.SchemeIs(url::kHttpsScheme)) { |
| return std::make_pair( |
| false, |
| PrefetchProxyPrefetchStatus::kPrefetchNotEligibleSchemeIsNotHttps); |
| } |
| |
| PrefetchProxyService* prefetch_proxy_service = |
| PrefetchProxyServiceFactory::GetForProfile(profile); |
| if (!prefetch_proxy_service) { |
| return std::make_pair(false, absl::nullopt); |
| } |
| |
| if (prefetch_type.IsProxyRequired() && |
| !prefetch_proxy_service->proxy_configurator() |
| ->IsPrefetchProxyAvailable()) { |
| return std::make_pair( |
| false, PrefetchProxyPrefetchStatus::kPrefetchProxyNotAvailable); |
| } |
| |
| return std::make_pair(true, absl::nullopt); |
| } |
| |
| // static |
| void PrefetchProxyTabHelper::CheckEligibilityOfURL( |
| Profile* profile, |
| const GURL& url, |
| const PrefetchType& prefetch_type, |
| OnEligibilityResultCallback result_callback) { |
| auto no_user_data_check = |
| CheckEligibilityOfURLSansUserData(profile, url, prefetch_type); |
| if (!no_user_data_check.first) { |
| std::move(result_callback).Run(url, false, no_user_data_check.second); |
| return; |
| } |
| |
| content::StoragePartition* default_storage_partition = |
| profile->GetDefaultStoragePartition(); |
| |
| // Only the default storage partition is supported since that is the only |
| // place where service workers are observed by |
| // |PrefetchProxyServiceWorkersObserver|. |
| if (default_storage_partition != |
| profile->GetStoragePartitionForUrl(url, |
| /*can_create=*/false)) { |
| std::move(result_callback) |
| .Run(url, false, |
| PrefetchProxyPrefetchStatus:: |
| kPrefetchNotEligibleNonDefaultStoragePartition); |
| return; |
| } |
| |
| PrefetchProxyService* prefetch_proxy_service = |
| PrefetchProxyServiceFactory::GetForProfile(profile); |
| if (!prefetch_proxy_service) { |
| std::move(result_callback).Run(url, false, absl::nullopt); |
| return; |
| } |
| |
| if (!prefetch_proxy_service->origin_decider() |
| ->IsOriginOutsideRetryAfterWindow(url)) { |
| std::move(result_callback) |
| .Run(url, false, |
| PrefetchProxyPrefetchStatus::kPrefetchIneligibleRetryAfter); |
| return; |
| } |
| |
| content::ServiceWorkerContext* service_worker_context_ = |
| GetServiceWorkerContext(profile); |
| |
| // 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. |
| bool site_has_service_worker = |
| service_worker_context_->MaybeHasRegistrationForStorageKey( |
| blink::StorageKey(url::Origin::Create(url))); |
| if (site_has_service_worker) { |
| std::move(result_callback) |
| .Run(url, false, |
| PrefetchProxyPrefetchStatus:: |
| kPrefetchNotEligibleUserHasServiceWorker); |
| return; |
| } |
| |
| // We don't have to check the cookies for prefetches that use the default |
| // network context instead of an isolated network context. |
| if (!prefetch_type.IsIsolatedNetworkContextRequired()) { |
| std::move(result_callback).Run(url, true, absl::nullopt); |
| return; |
| } |
| |
| net::CookieOptions options = net::CookieOptions::MakeAllInclusive(); |
| options.set_return_excluded_cookies(); |
| default_storage_partition->GetCookieManagerForBrowserProcess()->GetCookieList( |
| url, options, net::CookiePartitionKeyCollection::Todo(), |
| base::BindOnce(&OnGotCookieList, url, std::move(result_callback))); |
| } |
| |
| void PrefetchProxyTabHelper::OnGotEligibilityResult( |
| const GURL& url, |
| bool eligible, |
| absl::optional<PrefetchProxyPrefetchStatus> status) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // It is possible that this callback is being run late. That is, after the |
| // user has navigated away from the origin SRP. To detect this, check if the |
| // url exists in the set of predicted urls. If it doesn't, do nothing. |
| auto prefetch_container_iter = page_->prefetch_containers_.find(url); |
| if (prefetch_container_iter == page_->prefetch_containers_.end()) { |
| return; |
| } |
| PrefetchContainer* prefetch_container = prefetch_container_iter->second.get(); |
| |
| if (!eligible) { |
| if (status) { |
| prefetch_container->SetPrefetchStatus(*status); |
| if (page_->prefetch_metrics_collector_) { |
| page_->prefetch_metrics_collector_->OnMainframeResourceNotEligible( |
| url, prefetch_container->GetOriginalPredictionIndex(), *status); |
| } |
| |
| // Consider whether to send a decoy request to mask any user state (i.e.: |
| // cookies), and if so randomly decide whether to send a decoy request. |
| if (prefetch_container->GetPrefetchType().IsProxyRequired() && |
| ShouldConsiderDecoyRequestForStatus(*status) && |
| PrefetchProxySendDecoyRequestForIneligiblePrefetch( |
| profile_->GetPrefs())) { |
| prefetch_container->SetIsDecoy(true); |
| page_->urls_to_prefetch_.push_back(prefetch_container); |
| prefetch_container->SetPrefetchStatus( |
| PrefetchProxyPrefetchStatus::kPrefetchIsPrivacyDecoy); |
| Prefetch(); |
| } |
| } |
| |
| return; |
| } |
| |
| // TODO(robertogden): Consider adding redirect URLs to the front of the list. |
| page_->urls_to_prefetch_.push_back(prefetch_container); |
| page_->srp_metrics_->prefetch_eligible_count_++; |
| prefetch_container->SetPrefetchStatus( |
| PrefetchProxyPrefetchStatus::kPrefetchNotStarted); |
| |
| // Check that we won't go above the allowable size. |
| if (prefetch_container->GetOriginalPredictionIndex() < |
| sizeof(page_->srp_metrics_->ordered_eligible_pages_bitmask_) * 8) { |
| page_->srp_metrics_->ordered_eligible_pages_bitmask_ |= |
| 1 << prefetch_container->GetOriginalPredictionIndex(); |
| } |
| |
| if (!PrefetchProxyShouldPrefetchPosition( |
| prefetch_container->GetOriginalPredictionIndex())) { |
| prefetch_container->SetPrefetchStatus( |
| PrefetchProxyPrefetchStatus::kPrefetchPositionIneligible); |
| return; |
| } |
| |
| Prefetch(); |
| |
| // 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->GetPrefetchType() |
| .IsIsolatedNetworkContextRequired()) { |
| prefetch_container->RegisterCookieListener( |
| profile_->GetDefaultStoragePartition() |
| ->GetCookieManagerForBrowserProcess()); |
| } |
| |
| for (auto& observer : observer_list_) { |
| observer.OnNewEligiblePrefetchStarted(); |
| } |
| } |
| |
| bool PrefetchProxyTabHelper::IsWaitingForAfterSRPCookiesCopy() const { |
| switch (page_->cookie_copy_status_) { |
| case CookieCopyStatus::kNoNavigation: |
| case CookieCopyStatus::kCopyComplete: |
| return false; |
| case CookieCopyStatus::kWaitingForCopy: |
| return true; |
| } |
| } |
| |
| void PrefetchProxyTabHelper::SetOnAfterSRPCookieCopyCompleteCallback( |
| base::OnceClosure callback) { |
| // We don't expect a callback unless there's something to wait on. |
| DCHECK(IsWaitingForAfterSRPCookiesCopy()); |
| |
| page_->on_after_srp_cookie_copy_complete_ = std::move(callback); |
| } |
| |
| void PrefetchProxyTabHelper::CopyIsolatedCookiesOnAfterSRPClick( |
| PrefetchContainer* prefetch_container) { |
| DCHECK(prefetch_container); |
| |
| if (!page_->GetNetworkContextForUrl(prefetch_container->GetUrl())) { |
| // Not set in unit tests. |
| return; |
| } |
| |
| // We only need to copy cookies if the prefetch used an isolated network |
| // context. |
| if (!prefetch_container->GetPrefetchType() |
| .IsIsolatedNetworkContextRequired()) { |
| RecordPrefetchProxyPrefetchMainframeCookiesToCopy(0U); |
| return; |
| } |
| |
| // We don't want the cookie listener for this URL to get the changes from the |
| // copy. |
| prefetch_container->StopCookieListener(); |
| |
| page_->cookie_copy_status_ = CookieCopyStatus::kWaitingForCopy; |
| |
| net::CookieOptions options = net::CookieOptions::MakeAllInclusive(); |
| page_->GetNetworkContextForUrl(prefetch_container->GetUrl()) |
| ->GetCookieManager() |
| ->GetCookieList( |
| prefetch_container->GetUrl(), options, |
| net::CookiePartitionKeyCollection::Todo(), |
| base::BindOnce( |
| &PrefetchProxyTabHelper::OnGotIsolatedCookiesToCopyAfterSRPClick, |
| weak_factory_.GetWeakPtr(), prefetch_container->GetUrl())); |
| } |
| |
| void PrefetchProxyTabHelper::OnGotIsolatedCookiesToCopyAfterSRPClick( |
| const GURL& url, |
| const net::CookieAccessResultList& cookie_list, |
| const net::CookieAccessResultList& excluded_cookies) { |
| DCHECK(IsWaitingForAfterSRPCookiesCopy()); |
| DCHECK(page_->prefetch_containers_.find(url) != |
| page_->prefetch_containers_.end()); |
| |
| RecordPrefetchProxyPrefetchMainframeCookiesToCopy(cookie_list.size()); |
| |
| if (cookie_list.empty()) { |
| OnCopiedIsolatedCookiesAfterSRPClick(); |
| return; |
| } |
| |
| // When |barrier| is run |cookie_list.size()| times, it will run |
| // |OnCopiedIsolatedCookiesAfterSRPClick|. |
| base::RepeatingClosure barrier = base::BarrierClosure( |
| cookie_list.size(), |
| base::BindOnce( |
| &PrefetchProxyTabHelper::OnCopiedIsolatedCookiesAfterSRPClick, |
| weak_factory_.GetWeakPtr())); |
| |
| content::StoragePartition* default_storage_partition = |
| profile_->GetDefaultStoragePartition(); |
| net::CookieOptions options = net::CookieOptions::MakeAllInclusive(); |
| |
| for (const net::CookieWithAccessResult& cookie : cookie_list) { |
| default_storage_partition->GetCookieManagerForBrowserProcess() |
| ->SetCanonicalCookie(cookie.cookie, url, options, |
| base::BindOnce(&CookieSetHelper, barrier)); |
| } |
| } |
| |
| void PrefetchProxyTabHelper::OnCopiedIsolatedCookiesAfterSRPClick() { |
| DCHECK(IsWaitingForAfterSRPCookiesCopy()); |
| |
| page_->cookie_copy_status_ = CookieCopyStatus::kCopyComplete; |
| if (page_->on_after_srp_cookie_copy_complete_) { |
| std::move(page_->on_after_srp_cookie_copy_complete_).Run(); |
| } |
| } |
| |
| network::mojom::URLLoaderFactory* PrefetchProxyTabHelper::GetURLLoaderFactory( |
| const GURL& url) { |
| if (!page_->GetNetworkContextForUrl(url)) |
| page_->CreateNetworkContextForUrl(url); |
| DCHECK(page_->GetNetworkContextForUrl(url)); |
| return page_->GetNetworkContextForUrl(url)->GetUrlLoaderFactory(); |
| } |
| |
| bool PrefetchProxyTabHelper::HaveCookiesChanged(const GURL& url) const { |
| auto prefetch_container_iter = page_->prefetch_containers_.find(url); |
| if (prefetch_container_iter == page_->prefetch_containers_.end()) |
| return false; |
| return prefetch_container_iter->second->HaveCookiesChanged(); |
| } |
| |
| void PrefetchProxyTabHelper::CurrentPageLoad::CreateNetworkContextForUrl( |
| const GURL& url) { |
| if (PrefetchProxyUseIndividualNetworkContextsForEachPrefetch()) { |
| auto prefetch_container_iter = prefetch_containers_.find(url); |
| if (prefetch_container_iter != prefetch_containers_.end()) |
| prefetch_container_iter->second->CreateNetworkContextForPrefetch( |
| profile_); |
| return; |
| } |
| network_context_ = std::make_unique<PrefetchProxyNetworkContext>( |
| profile_, /*is_isolated=*/true, /*use_proxy=*/true); |
| } |
| |
| PrefetchProxyNetworkContext* |
| PrefetchProxyTabHelper::CurrentPageLoad::GetNetworkContextForUrl( |
| const GURL& url) const { |
| if (PrefetchProxyUseIndividualNetworkContextsForEachPrefetch()) { |
| auto prefetch_container_iter = prefetch_containers_.find(url); |
| if (prefetch_container_iter == prefetch_containers_.end()) |
| return nullptr; |
| return prefetch_container_iter->second->GetNetworkContext(); |
| } |
| return network_context_.get(); |
| } |
| |
| WEB_CONTENTS_USER_DATA_KEY_IMPL(PrefetchProxyTabHelper); |