|  | // Copyright 2017 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "chrome/browser/predictors/loading_predictor.h" | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <vector> | 
|  |  | 
|  | #include "base/metrics/histogram_functions.h" | 
|  | #include "base/time/time.h" | 
|  | #include "base/trace_event/trace_event.h" | 
|  | #include "build/build_config.h" | 
|  | #include "chrome/browser/browser_process.h" | 
|  | #include "chrome/browser/predictors/lcp_critical_path_predictor/lcp_critical_path_predictor_util.h" | 
|  | #include "chrome/browser/predictors/lcp_critical_path_predictor/prewarm_http_disk_cache_manager.h" | 
|  | #include "chrome/browser/predictors/loading_data_collector.h" | 
|  | #include "chrome/browser/predictors/loading_stats_collector.h" | 
|  | #include "chrome/browser/predictors/predictors_features.h" | 
|  | #include "chrome/browser/predictors/predictors_traffic_annotations.h" | 
|  | #include "chrome/browser/predictors/resource_prefetch_predictor.h" | 
|  | #include "content/public/browser/browser_thread.h" | 
|  | #include "content/public/browser/service_worker_context.h" | 
|  | #include "content/public/browser/storage_partition.h" | 
|  | #include "content/public/common/origin_util.h" | 
|  | #include "net/base/network_anonymization_key.h" | 
|  | #include "services/network/public/cpp/network_quality_tracker.h" | 
|  | #include "services/network/public/cpp/request_destination.h" | 
|  | #include "third_party/blink/public/common/features.h" | 
|  | #include "third_party/blink/public/common/storage_key/storage_key.h" | 
|  | #include "url/origin.h" | 
|  |  | 
|  | #if BUILDFLAG(IS_ANDROID) | 
|  | #include "base/android/radio_utils.h" | 
|  | #include "base/power_monitor/power_monitor.h" | 
|  | #endif  // BUILDFLAG(IS_ANDROID) | 
|  |  | 
|  | namespace features { | 
|  |  | 
|  | // If enabled, suppresses LoadingPredictor (https://crbug.com/350519234) | 
|  | BASE_FEATURE(kSuppressesLoadingPredictorOnSlowNetwork, | 
|  | base::FEATURE_DISABLED_BY_DEFAULT); | 
|  |  | 
|  | const base::FeatureParam<base::TimeDelta> | 
|  | kSuppressesLoadingPredictorOnSlowNetworkThreshold{ | 
|  | &kSuppressesLoadingPredictorOnSlowNetwork, "slow_network_threshold", | 
|  | base::Milliseconds(208)}; | 
|  |  | 
|  | }  // namespace features | 
|  |  | 
|  | namespace predictors { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | const base::TimeDelta kMinDelayBetweenPreresolveRequests = base::Seconds(60); | 
|  | const base::TimeDelta kMinDelayBetweenPreconnectRequests = base::Seconds(10); | 
|  |  | 
|  | // Returns true iff |prediction| is not empty. | 
|  | bool AddInitialUrlToPreconnectPrediction(const GURL& initial_url, | 
|  | PreconnectPrediction* prediction) { | 
|  | url::Origin initial_origin = url::Origin::Create(initial_url); | 
|  | // Open minimum 2 sockets to the main frame host to speed up the loading if a | 
|  | // main page has a redirect to the same host. This is because there can be a | 
|  | // race between reading the server redirect response and sending a new request | 
|  | // while the connection is still in use. | 
|  | static const int kMinSockets = 2; | 
|  |  | 
|  | if (!prediction->requests.empty() && | 
|  | prediction->requests.front().origin == initial_origin) { | 
|  | prediction->requests.front().num_sockets = | 
|  | std::max(prediction->requests.front().num_sockets, kMinSockets); | 
|  | } else if (!initial_origin.opaque() && | 
|  | (initial_origin.scheme() == url::kHttpScheme || | 
|  | initial_origin.scheme() == url::kHttpsScheme)) { | 
|  | prediction->requests.emplace(prediction->requests.begin(), initial_origin, | 
|  | kMinSockets, | 
|  | net::NetworkAnonymizationKey::CreateSameSite( | 
|  | net::SchemefulSite(initial_origin))); | 
|  | } | 
|  |  | 
|  | return !prediction->requests.empty(); | 
|  | } | 
|  |  | 
|  | void MaybeWarmUpServiceWorker(const GURL& url, Profile* profile) { | 
|  | static const bool kEnabled = | 
|  | base::FeatureList::IsEnabled( | 
|  | blink::features::kSpeculativeServiceWorkerWarmUp) && | 
|  | base::GetFieldTrialParamByFeatureAsBool( | 
|  | blink::features::kSpeculativeServiceWorkerWarmUp, | 
|  | "sw_warm_up_from_loading_predictor", true); | 
|  | if (!kEnabled) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!profile) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | // TODO(jbroman): Allow a non-default storage partition. | 
|  | content::StoragePartition* storage_partition = | 
|  | profile->GetDefaultStoragePartition(); | 
|  |  | 
|  | if (!storage_partition) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | content::ServiceWorkerContext* service_worker_context = | 
|  | storage_partition->GetServiceWorkerContext(); | 
|  |  | 
|  | if (!service_worker_context) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!content::OriginCanAccessServiceWorkers(url)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | const blink::StorageKey key = | 
|  | blink::StorageKey::CreateFirstParty(url::Origin::Create(url)); | 
|  |  | 
|  | if (!service_worker_context->MaybeHasRegistrationForStorageKey(key)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | service_worker_context->WarmUpServiceWorker(url, key, base::DoNothing()); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | LoadingPredictor::LoadingPredictor(const LoadingPredictorConfig& config, | 
|  | Profile* profile) | 
|  | : config_(config), | 
|  | profile_(profile), | 
|  | resource_prefetch_predictor_( | 
|  | std::make_unique<ResourcePrefetchPredictor>(config, profile)), | 
|  | stats_collector_(std::make_unique<LoadingStatsCollector>( | 
|  | resource_prefetch_predictor_.get())), | 
|  | loading_data_collector_(std::make_unique<LoadingDataCollector>( | 
|  | resource_prefetch_predictor_.get(), | 
|  | stats_collector_.get(), | 
|  | config)) {} | 
|  |  | 
|  | LoadingPredictor::~LoadingPredictor() { | 
|  | DCHECK(shutdown_); | 
|  | } | 
|  |  | 
|  | bool LoadingPredictor::PrepareForPageLoad( | 
|  | const std::optional<url::Origin>& initiator_origin, | 
|  | const GURL& url, | 
|  | HintOrigin origin, | 
|  | bool preconnectable, | 
|  | std::optional<PreconnectPrediction> preconnect_prediction) { | 
|  | CHECK(!shutdown_); | 
|  |  | 
|  | TRACE_EVENT("loading", "LoadingPredictor::PrepareForPageLoad"); | 
|  |  | 
|  | // Suppresses network activities. | 
|  | static const bool kSuppressesLoadingPredictorOnSlowNetworkIsEnabled = | 
|  | base::FeatureList::IsEnabled( | 
|  | features::kSuppressesLoadingPredictorOnSlowNetwork); | 
|  | static const base::TimeDelta kSlowNetworkThreshold = | 
|  | features::kSuppressesLoadingPredictorOnSlowNetworkThreshold.Get(); | 
|  | if (kSuppressesLoadingPredictorOnSlowNetworkIsEnabled && g_browser_process && | 
|  | g_browser_process->network_quality_tracker()) { | 
|  | const bool is_slow_network = | 
|  | g_browser_process->network_quality_tracker()->GetHttpRTT() > | 
|  | kSlowNetworkThreshold; | 
|  | base::UmaHistogramBoolean("LoadingPredictor.IsSlowNetwork", | 
|  | is_slow_network); | 
|  | if (is_slow_network) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Prewarm disk cache before preconnecting network. | 
|  | MaybePrewarmResources(initiator_origin, url); | 
|  |  | 
|  | MaybeWarmUpServiceWorker(url, profile_); | 
|  |  | 
|  | if (origin == HintOrigin::OMNIBOX) { | 
|  | // Omnibox hints are lightweight and need a special treatment. | 
|  | HandleHintByOrigin(url, preconnectable, /*only_allow_https=*/false, | 
|  | omnibox_preconnect_data_); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | if (origin == HintOrigin::BOOKMARK_BAR) { | 
|  | // Bookmark hints are lightweight and need a special treatment. | 
|  | HandleHintByOrigin(url, /*preconnectable=*/true, /*only_allow_https=*/true, | 
|  | bookmark_bar_preconnect_data_); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | if (origin == HintOrigin::NEW_TAB_PAGE) { | 
|  | // New Tab Page hints are lightweight and need a special treatment. | 
|  | HandleHintByOrigin(url, /*preconnectable=*/true, /*only_allow_https=*/true, | 
|  | new_tab_page_preconnect_data_); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | PreconnectPrediction prediction; | 
|  | bool has_local_preconnect_prediction = false; | 
|  | if (origin == HintOrigin::OPTIMIZATION_GUIDE) { | 
|  | CHECK(preconnect_prediction); | 
|  | prediction = *preconnect_prediction; | 
|  | } else { | 
|  | CHECK(!preconnect_prediction); | 
|  | if (features::ShouldUseLocalPredictions()) { | 
|  | has_local_preconnect_prediction = | 
|  | resource_prefetch_predictor_->PredictPreconnectOrigins(url, | 
|  | &prediction); | 
|  | } | 
|  | if (active_hints_.find(url) != active_hints_.end() && | 
|  | has_local_preconnect_prediction) { | 
|  | // We are currently preconnecting using the local preconnect prediction. | 
|  | // Do not proceed further. | 
|  | return true; | 
|  | } | 
|  | // Try to preconnect to the |url| even if the predictor has no | 
|  | // prediction. | 
|  | AddInitialUrlToPreconnectPrediction(url, &prediction); | 
|  | } | 
|  |  | 
|  | resource_prefetch_predictor()->GetPreconnectAndPrefetchRequest( | 
|  | initiator_origin, url, prediction); | 
|  |  | 
|  | // Return early if we do not have any requests. | 
|  | if (prediction.requests.empty() && prediction.prefetch_requests.empty()) | 
|  | return false; | 
|  |  | 
|  | ++total_hints_activated_; | 
|  | active_hints_.emplace(url, base::TimeTicks::Now()); | 
|  | if (IsPreconnectEnabled()) { | 
|  | MaybeAddPreconnect(url, std::move(prediction)); | 
|  | } | 
|  | return has_local_preconnect_prediction || preconnect_prediction; | 
|  | } | 
|  |  | 
|  | void LoadingPredictor::CancelPageLoadHint(const GURL& url) { | 
|  | if (shutdown_) | 
|  | return; | 
|  |  | 
|  | CancelActiveHint(active_hints_.find(url)); | 
|  | } | 
|  |  | 
|  | void LoadingPredictor::StartInitialization() { | 
|  | if (shutdown_) | 
|  | return; | 
|  |  | 
|  | resource_prefetch_predictor_->StartInitialization(); | 
|  | } | 
|  |  | 
|  | LoadingDataCollector* LoadingPredictor::loading_data_collector() { | 
|  | return loading_data_collector_.get(); | 
|  | } | 
|  |  | 
|  | ResourcePrefetchPredictor* LoadingPredictor::resource_prefetch_predictor() { | 
|  | return resource_prefetch_predictor_.get(); | 
|  | } | 
|  |  | 
|  | content::PreconnectManager* LoadingPredictor::preconnect_manager() { | 
|  | CHECK(!shutdown_); | 
|  | if (!preconnect_manager_) { | 
|  | preconnect_manager_ = | 
|  | content::PreconnectManager::Create(GetWeakPtr(), profile_); | 
|  | } | 
|  |  | 
|  | return preconnect_manager_.get(); | 
|  | } | 
|  |  | 
|  | PrefetchManager* LoadingPredictor::prefetch_manager() { | 
|  | CHECK( | 
|  | base::FeatureList::IsEnabled(features::kLoadingPredictorPrefetch) || | 
|  | base::FeatureList::IsEnabled(blink::features::kLCPPPrefetchSubresource)); | 
|  | CHECK(!shutdown_); | 
|  |  | 
|  | if (!prefetch_manager_) { | 
|  | prefetch_manager_ = | 
|  | std::make_unique<PrefetchManager>(GetWeakPtr(), profile_); | 
|  | } | 
|  |  | 
|  | return prefetch_manager_.get(); | 
|  | } | 
|  |  | 
|  | void LoadingPredictor::Shutdown() { | 
|  | DCHECK(!shutdown_); | 
|  | resource_prefetch_predictor_->Shutdown(); | 
|  | preconnect_manager_.reset(); | 
|  | shutdown_ = true; | 
|  | } | 
|  |  | 
|  | void LoadingPredictor::OnNavigationStarted(NavigationId navigation_id, | 
|  | ukm::SourceId ukm_source_id, | 
|  | const GURL& main_frame_url, | 
|  | base::TimeTicks creation_time) { | 
|  | CHECK(!shutdown_); | 
|  |  | 
|  | TRACE_EVENT("loading", "LoadingPredictor::OnNavigationStarted"); | 
|  |  | 
|  | loading_data_collector()->RecordStartNavigation( | 
|  | navigation_id, ukm_source_id, main_frame_url, creation_time); | 
|  | CleanupAbandonedHintsAndNavigations(navigation_id); | 
|  | active_navigations_.emplace(navigation_id, | 
|  | NavigationInfo{main_frame_url, creation_time}); | 
|  | active_urls_to_navigations_[main_frame_url].insert(navigation_id); | 
|  | } | 
|  |  | 
|  | void LoadingPredictor::OnNavigationFinished(NavigationId navigation_id, | 
|  | const GURL& old_main_frame_url, | 
|  | const GURL& new_main_frame_url, | 
|  | bool is_error_page) { | 
|  | if (shutdown_) | 
|  | return; | 
|  |  | 
|  | loading_data_collector()->RecordFinishNavigation( | 
|  | navigation_id, new_main_frame_url, is_error_page); | 
|  | if (active_urls_to_navigations_.find(old_main_frame_url) != | 
|  | active_urls_to_navigations_.end()) { | 
|  | active_urls_to_navigations_[old_main_frame_url].erase(navigation_id); | 
|  | if (active_urls_to_navigations_[old_main_frame_url].empty()) { | 
|  | active_urls_to_navigations_.erase(old_main_frame_url); | 
|  | } | 
|  | } | 
|  | active_navigations_.erase(navigation_id); | 
|  | CancelPageLoadHint(old_main_frame_url); | 
|  | } | 
|  |  | 
|  | std::map<GURL, base::TimeTicks>::iterator LoadingPredictor::CancelActiveHint( | 
|  | std::map<GURL, base::TimeTicks>::iterator hint_it) { | 
|  | if (hint_it == active_hints_.end()) | 
|  | return hint_it; | 
|  |  | 
|  | const GURL& url = hint_it->first; | 
|  | MaybeRemovePreconnect(url); | 
|  | return active_hints_.erase(hint_it); | 
|  | } | 
|  |  | 
|  | void LoadingPredictor::CleanupAbandonedHintsAndNavigations( | 
|  | NavigationId navigation_id) { | 
|  | base::TimeTicks time_now = base::TimeTicks::Now(); | 
|  | const base::TimeDelta max_navigation_age = | 
|  | base::Seconds(config_.max_navigation_lifetime_seconds); | 
|  |  | 
|  | // Hints. | 
|  | for (auto it = active_hints_.begin(); it != active_hints_.end();) { | 
|  | base::TimeDelta prefetch_age = time_now - it->second; | 
|  | if (prefetch_age > max_navigation_age) { | 
|  | // Will go to the last bucket in the duration reported in | 
|  | // CancelActiveHint() meaning that the duration was unlimited. | 
|  | it = CancelActiveHint(it); | 
|  | } else { | 
|  | ++it; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Navigations. | 
|  | for (auto it = active_navigations_.begin(); | 
|  | it != active_navigations_.end();) { | 
|  | if ((it->first == navigation_id) || | 
|  | (time_now - it->second.creation_time > max_navigation_age)) { | 
|  | CancelActiveHint(active_hints_.find(it->second.main_frame_url)); | 
|  | it = active_navigations_.erase(it); | 
|  | } else { | 
|  | ++it; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void LoadingPredictor::MaybeAddPreconnect(const GURL& url, | 
|  | PreconnectPrediction prediction) { | 
|  | CHECK(!shutdown_); | 
|  | if (!prediction.prefetch_requests.empty()) { | 
|  | CHECK(base::FeatureList::IsEnabled(features::kLoadingPredictorPrefetch) || | 
|  | base::FeatureList::IsEnabled( | 
|  | blink::features::kLCPPPrefetchSubresource)); | 
|  | prefetch_manager()->Start(url, std::move(prediction.prefetch_requests)); | 
|  | } | 
|  |  | 
|  | if (!prediction.requests.empty()) | 
|  | preconnect_manager()->Start(url, std::move(prediction.requests), | 
|  | kLoadingPredictorPreconnectTrafficAnnotation); | 
|  | } | 
|  |  | 
|  | void LoadingPredictor::MaybeRemovePreconnect(const GURL& url) { | 
|  | DCHECK(!shutdown_); | 
|  | if (preconnect_manager_) | 
|  | preconnect_manager_->Stop(url); | 
|  | if (prefetch_manager_) | 
|  | prefetch_manager_->Stop(url); | 
|  | } | 
|  |  | 
|  | bool LoadingPredictor::HandleHintByOrigin(const GURL& url, | 
|  | bool preconnectable, | 
|  | bool only_allow_https, | 
|  | PreconnectData& preconnect_data) { | 
|  | if (!url.is_valid() || !url.has_host() || !IsPreconnectEnabled() || | 
|  | (only_allow_https && url.GetScheme() != url::kHttpsScheme)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | const url::Origin origin = url::Origin::Create(url); | 
|  | // When constructing an Origin from a GURL results in an opaque origin, the | 
|  | // resulting origin is guaranteed to be unique; trying to create another | 
|  | // origin from the same URL will result in a different unique opaque origin, | 
|  | // so any preconnect attempt would never be used anyway. | 
|  | if (origin.opaque()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Tracking whether this is a new origin request. If so, then | 
|  | // preconnect/presolve immediately. If the origins are the same, then | 
|  | // preconnect/presolve after a given threshold. | 
|  | const bool is_new_origin = origin != preconnect_data.last_origin_; | 
|  | preconnect_data.last_origin_ = origin; | 
|  | const net::SchemefulSite site = net::SchemefulSite(origin); | 
|  | const auto network_anonymization_key = | 
|  | net::NetworkAnonymizationKey::CreateSameSite(site); | 
|  | base::TimeTicks now = base::TimeTicks::Now(); | 
|  | if (preconnectable) { | 
|  | if (is_new_origin || now - preconnect_data.last_preconnect_time_ >= | 
|  | kMinDelayBetweenPreconnectRequests) { | 
|  | preconnect_data.last_preconnect_time_ = now; | 
|  | preconnect_manager()->StartPreconnectUrl( | 
|  | url, true, network_anonymization_key, | 
|  | kLoadingPredictorPreconnectTrafficAnnotation, | 
|  | /*storage_partition_config=*/nullptr, | 
|  | /*keepalive_config=*/std::nullopt, mojo::NullRemote()); | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | if (is_new_origin || now - preconnect_data.last_preresolve_time_ >= | 
|  | kMinDelayBetweenPreresolveRequests) { | 
|  | preconnect_data.last_preresolve_time_ = now; | 
|  | preconnect_manager()->StartPreresolveHost( | 
|  | url, network_anonymization_key, | 
|  | kLoadingPredictorPreconnectTrafficAnnotation, | 
|  | /*storage_partition_config=*/nullptr); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | void LoadingPredictor::PreconnectInitiated(const GURL& url, | 
|  | const GURL& preconnect_url) { | 
|  | DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
|  | if (shutdown_) | 
|  | return; | 
|  |  | 
|  | auto nav_id_set_it = active_urls_to_navigations_.find(url); | 
|  | if (nav_id_set_it == active_urls_to_navigations_.end()) | 
|  | return; | 
|  |  | 
|  | for (const auto& nav_id : nav_id_set_it->second) | 
|  | loading_data_collector_->RecordPreconnectInitiated(nav_id, preconnect_url); | 
|  | } | 
|  |  | 
|  | void LoadingPredictor::PreconnectFinished( | 
|  | std::unique_ptr<content::PreconnectStats> stats) { | 
|  | DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
|  | if (shutdown_) | 
|  | return; | 
|  |  | 
|  | DCHECK(stats); | 
|  | active_hints_.erase(stats->url); | 
|  | } | 
|  |  | 
|  | bool LoadingPredictor::IsPreconnectEnabled() { | 
|  | return IsPreconnectAllowed(profile_); | 
|  | } | 
|  |  | 
|  | void LoadingPredictor::PrefetchInitiated(const GURL& url, | 
|  | const GURL& prefetch_url) { | 
|  | DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
|  | if (shutdown_) | 
|  | return; | 
|  |  | 
|  | auto nav_id_set_it = active_urls_to_navigations_.find(url); | 
|  | if (nav_id_set_it == active_urls_to_navigations_.end()) | 
|  | return; | 
|  |  | 
|  | for (const auto& nav_id : nav_id_set_it->second) | 
|  | loading_data_collector_->RecordPrefetchInitiated(nav_id, prefetch_url); | 
|  | } | 
|  |  | 
|  | void LoadingPredictor::PrefetchFinished(std::unique_ptr<PrefetchStats> stats) { | 
|  | DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 
|  | if (shutdown_) | 
|  | return; | 
|  |  | 
|  | active_hints_.erase(stats->url); | 
|  | } | 
|  |  | 
|  | void LoadingPredictor::PreconnectURLIfAllowed( | 
|  | const GURL& url, | 
|  | bool allow_credentials, | 
|  | const net::NetworkAnonymizationKey& network_anonymization_key, | 
|  | const net::NetworkTrafficAnnotationTag& traffic_annotation, | 
|  | const content::StoragePartitionConfig* storage_partition_config) { | 
|  | if (!url.is_valid() || !url.has_host() || !IsPreconnectEnabled()) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | preconnect_manager()->StartPreconnectUrl( | 
|  | url, allow_credentials, network_anonymization_key, traffic_annotation, | 
|  | storage_partition_config, /*keepalive_config=*/std::nullopt, | 
|  | mojo::NullRemote()); | 
|  | } | 
|  |  | 
|  | void LoadingPredictor::MaybePrewarmResources( | 
|  | const std::optional<url::Origin>& initiator_origin, | 
|  | const GURL& top_frame_main_resource_url) { | 
|  | if (!base::FeatureList::IsEnabled( | 
|  | blink::features::kHttpDiskCachePrewarming)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (shutdown_) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | TRACE_EVENT("loading", "LoadingPredictor::MaybePrewarmResources"); | 
|  |  | 
|  | if (!top_frame_main_resource_url.is_valid() || | 
|  | !top_frame_main_resource_url.SchemeIsHTTPOrHTTPS()) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | std::optional<LcppStat> lcpp_stat = | 
|  | resource_prefetch_predictor()->GetLcppStat(initiator_origin, | 
|  | top_frame_main_resource_url); | 
|  |  | 
|  | if (!lcpp_stat || !IsValidLcppStat(*lcpp_stat)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!prewarm_http_disk_cache_manager_) { | 
|  | // TODO(jbroman): Allow a non-default storage partition. | 
|  | prewarm_http_disk_cache_manager_ = | 
|  | std::make_unique<PrewarmHttpDiskCacheManager>( | 
|  | profile_->GetDefaultStoragePartition() | 
|  | ->GetURLLoaderFactoryForBrowserProcess()); | 
|  | } | 
|  |  | 
|  | prewarm_http_disk_cache_manager_->MaybePrewarmResources( | 
|  | initiator_origin, top_frame_main_resource_url, | 
|  | PredictFetchedSubresourceUrls(*lcpp_stat)); | 
|  | } | 
|  |  | 
|  | }  // namespace predictors |