|  | // Copyright 2018 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_tab_helper.h" | 
|  |  | 
|  | #include <memory> | 
|  | #include <set> | 
|  | #include <string> | 
|  |  | 
|  | #include "base/check_is_test.h" | 
|  | #include "base/command_line.h" | 
|  | #include "base/metrics/histogram_functions.h" | 
|  | #include "base/metrics/histogram_macros.h" | 
|  | #include "base/timer/elapsed_timer.h" | 
|  | #include "base/trace_event/trace_event.h" | 
|  | #include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h" | 
|  | #include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h" | 
|  | #include "chrome/browser/predictors/lcp_critical_path_predictor/lcp_critical_path_predictor_util.h" | 
|  | #include "chrome/browser/predictors/loading_predictor.h" | 
|  | #include "chrome/browser/predictors/loading_predictor_factory.h" | 
|  | #include "chrome/browser/predictors/predictors_enums.h" | 
|  | #include "chrome/browser/predictors/predictors_features.h" | 
|  | #include "chrome/browser/predictors/predictors_switches.h" | 
|  | #include "chrome/browser/preloading/prefetch/no_state_prefetch/no_state_prefetch_manager_factory.h" | 
|  | #include "chrome/browser/profiles/profile.h" | 
|  | #include "chrome/common/chrome_features.h" | 
|  | #include "components/google/core/common/google_util.h" | 
|  | #include "components/no_state_prefetch/browser/no_state_prefetch_manager.h" | 
|  | #include "components/optimization_guide/core/hints/optimization_guide_decider.h" | 
|  | #include "components/optimization_guide/proto/hints.pb.h" | 
|  | #include "content/public/browser/browser_thread.h" | 
|  | #include "content/public/browser/navigation_handle.h" | 
|  | #include "content/public/browser/render_frame_host.h" | 
|  | #include "net/base/network_anonymization_key.h" | 
|  | #include "services/metrics/public/cpp/ukm_source_id.h" | 
|  | #include "services/network/public/mojom/fetch_api.mojom.h" | 
|  | #include "third_party/blink/public/common/features.h" | 
|  | #include "third_party/blink/public/common/loader/lcp_critical_path_predictor_util.h" | 
|  | #include "third_party/blink/public/mojom/lcp_critical_path_predictor/lcp_critical_path_predictor.mojom.h" | 
|  | #include "third_party/blink/public/mojom/loader/resource_load_info.mojom.h" | 
|  |  | 
|  | using content::BrowserThread; | 
|  |  | 
|  | namespace predictors { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | constexpr char kLoadingPredictorOptimizationHintsReceiveStatusHistogram[] = | 
|  | "LoadingPredictor.OptimizationHintsReceiveStatus"; | 
|  |  | 
|  | // Called only for subresources. | 
|  | // platform/loader/fetch/README.md in blink contains more details on | 
|  | // prioritization as well as links to all of the relevant places in the code | 
|  | // where priority is determined. If the priority logic is updated here, be sure | 
|  | // to update the other code as needed. | 
|  | net::RequestPriority GetRequestPriority( | 
|  | network::mojom::RequestDestination request_destination) { | 
|  | switch (request_destination) { | 
|  | case network::mojom::RequestDestination::kStyle: | 
|  | return net::HIGHEST; | 
|  |  | 
|  | case network::mojom::RequestDestination::kFont: | 
|  | case network::mojom::RequestDestination::kScript: | 
|  | case network::mojom::RequestDestination::kJson: | 
|  | return net::MEDIUM; | 
|  |  | 
|  | case network::mojom::RequestDestination::kEmpty: | 
|  | case network::mojom::RequestDestination::kAudio: | 
|  | case network::mojom::RequestDestination::kAudioWorklet: | 
|  | case network::mojom::RequestDestination::kDocument: | 
|  | case network::mojom::RequestDestination::kEmbed: | 
|  | case network::mojom::RequestDestination::kFrame: | 
|  | case network::mojom::RequestDestination::kIframe: | 
|  | case network::mojom::RequestDestination::kImage: | 
|  | case network::mojom::RequestDestination::kManifest: | 
|  | case network::mojom::RequestDestination::kObject: | 
|  | case network::mojom::RequestDestination::kPaintWorklet: | 
|  | case network::mojom::RequestDestination::kReport: | 
|  | case network::mojom::RequestDestination::kServiceWorker: | 
|  | case network::mojom::RequestDestination::kSharedWorker: | 
|  | case network::mojom::RequestDestination::kTrack: | 
|  | case network::mojom::RequestDestination::kVideo: | 
|  | case network::mojom::RequestDestination::kWebBundle: | 
|  | case network::mojom::RequestDestination::kWorker: | 
|  | case network::mojom::RequestDestination::kXslt: | 
|  | case network::mojom::RequestDestination::kFencedframe: | 
|  | case network::mojom::RequestDestination::kWebIdentity: | 
|  | case network::mojom::RequestDestination::kDictionary: | 
|  | case network::mojom::RequestDestination::kSpeculationRules: | 
|  | case network::mojom::RequestDestination::kSharedStorageWorklet: | 
|  | return net::LOWEST; | 
|  | } | 
|  | } | 
|  |  | 
|  | bool IsHandledNavigation(content::NavigationHandle* navigation_handle) { | 
|  | content::WebContents* web_contents = navigation_handle->GetWebContents(); | 
|  |  | 
|  | prerender::NoStatePrefetchManager* no_state_prefetch_manager = | 
|  | prerender::NoStatePrefetchManagerFactory::GetForBrowserContext( | 
|  | web_contents->GetBrowserContext()); | 
|  | if (no_state_prefetch_manager && | 
|  | no_state_prefetch_manager->IsWebContentsPrefetching(web_contents)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return navigation_handle->IsInPrimaryMainFrame() && | 
|  | !navigation_handle->IsSameDocument() && | 
|  | !navigation_handle->IsPageActivation() && | 
|  | navigation_handle->GetURL().SchemeIsHTTPOrHTTPS(); | 
|  | } | 
|  |  | 
|  | network::mojom::RequestDestination GetDestination( | 
|  | optimization_guide::proto::ResourceType type) { | 
|  | switch (type) { | 
|  | case optimization_guide::proto::RESOURCE_TYPE_UNKNOWN: | 
|  | return network::mojom::RequestDestination::kEmpty; | 
|  | case optimization_guide::proto::RESOURCE_TYPE_CSS: | 
|  | return network::mojom::RequestDestination::kStyle; | 
|  | case optimization_guide::proto::RESOURCE_TYPE_SCRIPT: | 
|  | return network::mojom::RequestDestination::kScript; | 
|  | } | 
|  | } | 
|  |  | 
|  | bool ShouldPrefetchDestination(network::mojom::RequestDestination destination) { | 
|  | switch (features::kLoadingPredictorPrefetchSubresourceType.Get()) { | 
|  | case features::PrefetchSubresourceType::kAll: | 
|  | return true; | 
|  | case features::PrefetchSubresourceType::kCss: | 
|  | return destination == network::mojom::RequestDestination::kStyle; | 
|  | case features::PrefetchSubresourceType::kJsAndCss: | 
|  | return destination == network::mojom::RequestDestination::kScript || | 
|  | destination == network::mojom::RequestDestination::kStyle; | 
|  | } | 
|  | NOTREACHED(); | 
|  | } | 
|  |  | 
|  | // Util class for recording the status for when we received optimization hints | 
|  | // for navigations that we requested them for. | 
|  | class ScopedOptimizationHintsReceiveStatusRecorder { | 
|  | public: | 
|  | ScopedOptimizationHintsReceiveStatusRecorder() | 
|  | : status_(OptimizationHintsReceiveStatus::kUnknown) {} | 
|  | ~ScopedOptimizationHintsReceiveStatusRecorder() { | 
|  | DCHECK_NE(status_, OptimizationHintsReceiveStatus::kUnknown); | 
|  | UMA_HISTOGRAM_ENUMERATION( | 
|  | kLoadingPredictorOptimizationHintsReceiveStatusHistogram, status_); | 
|  | } | 
|  |  | 
|  | void set_status(OptimizationHintsReceiveStatus status) { status_ = status; } | 
|  |  | 
|  | private: | 
|  | OptimizationHintsReceiveStatus status_; | 
|  | }; | 
|  |  | 
|  | bool ShouldConsultOptimizationGuide(const GURL& current_main_frame_url, | 
|  | content::WebContents* web_contents) { | 
|  | GURL previous_main_frame_url = web_contents->GetLastCommittedURL(); | 
|  |  | 
|  | // Consult the Optimization Guide on all cross-origin page loads. | 
|  | return url::Origin::Create(current_main_frame_url) != | 
|  | url::Origin::Create(previous_main_frame_url); | 
|  | } | 
|  |  | 
|  | // These values are persisted to logs. Entries should not be renumbered and | 
|  | // numeric values should never be reused. | 
|  | enum class LcppHintStatus { | 
|  | kSucceedToSet = 0, | 
|  | kNoLcppData = 1, | 
|  | kInvalidLcppStat = 2, | 
|  | kConversionFailure = 3, | 
|  | kMaxValue = kConversionFailure, | 
|  | }; | 
|  |  | 
|  | std::optional<blink::mojom::LCPCriticalPathPredictorNavigationTimeHint> | 
|  | GetLCPPHint(content::NavigationHandle& navigation_handle, | 
|  | LoadingPredictor& predictor) { | 
|  | std::optional<LcppStat> lcpp_stat = | 
|  | predictor.resource_prefetch_predictor()->GetLcppStat( | 
|  | navigation_handle.GetInitiatorOrigin(), navigation_handle.GetURL()); | 
|  | if (!lcpp_stat) { | 
|  | base::UmaHistogramEnumeration( | 
|  | "LoadingPredictor.SetLCPPNavigationHint.Status", | 
|  | LcppHintStatus::kNoLcppData); | 
|  | return std::nullopt; | 
|  | } | 
|  | if (!IsValidLcppStat(*lcpp_stat)) { | 
|  | base::UmaHistogramEnumeration( | 
|  | "LoadingPredictor.SetLCPPNavigationHint.Status", | 
|  | LcppHintStatus::kInvalidLcppStat); | 
|  | return std::nullopt; | 
|  | } | 
|  |  | 
|  | return ConvertLcppStatToLCPCriticalPathPredictorNavigationTimeHint( | 
|  | *lcpp_stat); | 
|  | } | 
|  | // Attach LCP Critical Path Predictor hint to NavigationHandle, so that it | 
|  | // would be sent to the renderer process upon navigation commit. | 
|  | void MaybeSetLCPPNavigationHint(content::NavigationHandle& navigation_handle, | 
|  | LoadingPredictor& predictor) { | 
|  | TRACE_EVENT("navigation", "MaybeSetLCPPNavigationHint"); | 
|  | base::ElapsedTimer timer; | 
|  | if (!blink::LcppEnabled() || !navigation_handle.IsInOutermostMainFrame() || | 
|  | navigation_handle.IsSameDocument()) { | 
|  | return; | 
|  | } | 
|  | const GURL& navigation_url = navigation_handle.GetURL(); | 
|  | if (!navigation_url.is_valid() || !navigation_url.SchemeIsHTTPOrHTTPS()) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | std::optional<blink::mojom::LCPCriticalPathPredictorNavigationTimeHint> hint = | 
|  | GetLCPPHint(navigation_handle, predictor); | 
|  | if (predictor.IsLCPPTestingEnabled()) { | 
|  | CHECK_IS_TEST(); | 
|  | if (!hint) { | 
|  | hint = blink::mojom::LCPCriticalPathPredictorNavigationTimeHint( | 
|  | {}, {}, {}, {}, {}, {}, /*for_testing=*/false); | 
|  | } | 
|  | hint->for_testing = true; | 
|  | } | 
|  | if (hint) { | 
|  | navigation_handle.SetLCPPNavigationHint(*hint); | 
|  | base::UmaHistogramEnumeration( | 
|  | "LoadingPredictor.SetLCPPNavigationHint.Status", | 
|  | LcppHintStatus::kSucceedToSet); | 
|  | base::UmaHistogramTimes("LoadingPredictor.SetLCPPNavigationHint.Time", | 
|  | timer.Elapsed()); | 
|  | } else { | 
|  | base::UmaHistogramEnumeration( | 
|  | "LoadingPredictor.SetLCPPNavigationHint.Status", | 
|  | LcppHintStatus::kConversionFailure); | 
|  | } | 
|  | } | 
|  |  | 
|  | void MaybePrewarmMainResourceAndSubresourcesOnNavigation( | 
|  | content::NavigationHandle& navigation_handle, | 
|  | LoadingPredictor& predictor) { | 
|  | TRACE_EVENT("navigation", | 
|  | "MaybePrewarmMainResourceAndSubresourcesOnNavigation"); | 
|  | static const bool enabled = | 
|  | base::FeatureList::IsEnabled(blink::features::kHttpDiskCachePrewarming) && | 
|  | blink::features::kHttpDiskCachePrewarmingTriggerOnNavigation.Get(); | 
|  | if (!enabled || !navigation_handle.IsInOutermostMainFrame() || | 
|  | navigation_handle.IsSameDocument()) { | 
|  | return; | 
|  | } | 
|  | predictor.MaybePrewarmResources(navigation_handle.GetInitiatorOrigin(), | 
|  | navigation_handle.GetURL()); | 
|  | } | 
|  |  | 
|  | NavigationId GetNextId() { | 
|  | static NavigationId::Generator generator; | 
|  | return generator.GenerateNextId(); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | LoadingPredictorTabHelper::PageData::PageData() : navigation_id_(GetNextId()) {} | 
|  | LoadingPredictorTabHelper::PageData::~PageData() = default; | 
|  |  | 
|  | LoadingPredictorTabHelper::PageData* | 
|  | LoadingPredictorTabHelper::PageData::GetForNavigationHandle( | 
|  | content::NavigationHandle& navigation_handle) { | 
|  | auto* navigation_holder = | 
|  | NavigationPageDataHolder::GetForNavigationHandle(navigation_handle); | 
|  | if (!navigation_holder) | 
|  | return nullptr; | 
|  |  | 
|  | return navigation_holder->page_data_.get(); | 
|  | } | 
|  |  | 
|  | LoadingPredictorTabHelper::PageData& | 
|  | LoadingPredictorTabHelper::PageData::CreateForNavigationHandle( | 
|  | content::NavigationHandle& navigation_handle) { | 
|  | NavigationPageDataHolder* navigation_holder = | 
|  | NavigationPageDataHolder::GetOrCreateForNavigationHandle( | 
|  | navigation_handle); | 
|  | navigation_holder->page_data_->navigation_page_data_holder_ = | 
|  | navigation_holder->weak_factory_.GetWeakPtr(); | 
|  | return *navigation_holder->page_data_; | 
|  | } | 
|  |  | 
|  | LoadingPredictorTabHelper::PageData* | 
|  | LoadingPredictorTabHelper::PageData::GetForDocument( | 
|  | content::RenderFrameHost& render_frame_host) { | 
|  | DocumentPageDataHolder* document_holder = | 
|  | DocumentPageDataHolder::GetForCurrentDocument(&render_frame_host); | 
|  | if (!document_holder) | 
|  | return nullptr; | 
|  |  | 
|  | return document_holder->page_data_.get(); | 
|  | } | 
|  |  | 
|  | void LoadingPredictorTabHelper::PageData:: | 
|  | TransferFromNavigationHandleToDocument( | 
|  | content::NavigationHandle& navigation_handle, | 
|  | content::RenderFrameHost& render_frame_host) { | 
|  | auto* navigation_holder = | 
|  | NavigationPageDataHolder::GetForNavigationHandle(navigation_handle); | 
|  | DCHECK(navigation_holder); | 
|  |  | 
|  | auto* document_holder = | 
|  | DocumentPageDataHolder::GetOrCreateForCurrentDocument(&render_frame_host); | 
|  | document_holder->page_data_ = std::move(navigation_holder->page_data_); | 
|  | document_holder->page_data_->document_page_data_holder_ = | 
|  | document_holder->weak_factory_.GetWeakPtr(); | 
|  |  | 
|  | NavigationPageDataHolder::DeleteForNavigationHandle(navigation_handle); | 
|  | } | 
|  |  | 
|  | LoadingPredictorTabHelper::DocumentPageDataHolder::DocumentPageDataHolder( | 
|  | content::RenderFrameHost* rfh) | 
|  | : content::DocumentUserData<DocumentPageDataHolder>(rfh), | 
|  | page_data_(base::MakeRefCounted<PageData>()) {} | 
|  |  | 
|  | LoadingPredictorTabHelper::DocumentPageDataHolder::~DocumentPageDataHolder() { | 
|  | if (page_data_->predictor_) { | 
|  | page_data_->predictor_->loading_data_collector()->RecordPageDestroyed( | 
|  | page_data_->navigation_id_, | 
|  | page_data_->last_optimization_guide_prediction_); | 
|  | } | 
|  | page_data_->last_optimization_guide_prediction_ = std::nullopt; | 
|  | } | 
|  |  | 
|  | LoadingPredictorTabHelper::NavigationPageDataHolder::NavigationPageDataHolder( | 
|  | content::NavigationHandle& navigation_handle) | 
|  | : page_data_(base::MakeRefCounted<PageData>()), | 
|  | navigation_handle_(navigation_handle.GetSafeRef()) {} | 
|  | LoadingPredictorTabHelper::NavigationPageDataHolder:: | 
|  | ~NavigationPageDataHolder() = default; | 
|  |  | 
|  | LoadingPredictorTabHelper::LoadingPredictorTabHelper( | 
|  | content::WebContents* web_contents) | 
|  | : content::WebContentsObserver(web_contents), | 
|  | content::WebContentsUserData<LoadingPredictorTabHelper>(*web_contents) { | 
|  | Profile* profile = | 
|  | Profile::FromBrowserContext(web_contents->GetBrowserContext()); | 
|  | auto* predictor = LoadingPredictorFactory::GetForProfile(profile); | 
|  | if (predictor) | 
|  | predictor_ = predictor->GetWeakPtr(); | 
|  | if (base::FeatureList::IsEnabled( | 
|  | features::kLoadingPredictorUseOptimizationGuide)) { | 
|  | optimization_guide_decider_ = | 
|  | OptimizationGuideKeyedServiceFactory::GetForProfile(profile); | 
|  | if (optimization_guide_decider_) { | 
|  | optimization_guide_decider_->RegisterOptimizationTypes( | 
|  | {optimization_guide::proto::LOADING_PREDICTOR}); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | LoadingPredictorTabHelper::~LoadingPredictorTabHelper() = default; | 
|  |  | 
|  | void LoadingPredictorTabHelper::DidStartNavigation( | 
|  | content::NavigationHandle* navigation_handle) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | TRACE_EVENT("navigation", "LoadingPredictorTabHelper::DidStartNavigation"); | 
|  |  | 
|  | if (!predictor_ || predictor_->WasShutdown()) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | MaybeSetLCPPNavigationHint(*navigation_handle, *predictor_); | 
|  |  | 
|  | MaybePrewarmMainResourceAndSubresourcesOnNavigation(*navigation_handle, | 
|  | *predictor_); | 
|  |  | 
|  | if (!IsHandledNavigation(navigation_handle)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | const bool should_consult_optimization_guide = ShouldConsultOptimizationGuide( | 
|  | navigation_handle->GetURL(), web_contents()); | 
|  |  | 
|  | PageData& page_data = PageData::CreateForNavigationHandle(*navigation_handle); | 
|  | page_data.predictor_ = predictor_; | 
|  |  | 
|  | predictor_->OnNavigationStarted( | 
|  | page_data.navigation_id_, | 
|  | ukm::ConvertToSourceId(navigation_handle->GetNavigationId(), | 
|  | ukm::SourceIdType::NAVIGATION_ID), | 
|  | navigation_handle->GetURL(), navigation_handle->NavigationStart()); | 
|  |  | 
|  | if (base::FeatureList::IsEnabled( | 
|  | blink::features::kLCPPPrefetchSubresourceAsync)) { | 
|  | base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( | 
|  | FROM_HERE, | 
|  | base::BindOnce( | 
|  | &LoadingPredictorTabHelper::PrepareForPageLoad, | 
|  | weak_ptr_factory_.GetWeakPtr(), base::WrapRefCounted(&page_data), | 
|  | navigation_handle->GetInitiatorOrigin(), | 
|  | navigation_handle->GetURL(), should_consult_optimization_guide)); | 
|  | } else { | 
|  | PrepareForPageLoad(base::WrapRefCounted(&page_data), | 
|  | navigation_handle->GetInitiatorOrigin(), | 
|  | navigation_handle->GetURL(), | 
|  | should_consult_optimization_guide); | 
|  | } | 
|  | } | 
|  |  | 
|  | void LoadingPredictorTabHelper::PrepareForPageLoad( | 
|  | scoped_refptr<PageData> page_data, | 
|  | const std::optional<url::Origin> initiator_origin, | 
|  | const GURL main_frame_url, | 
|  | bool should_consult_optimization_guide) { | 
|  | TRACE_EVENT("navigation", "LoadingPredictorTabHelper::PrepareForPageLoad."); | 
|  | is_prepare_for_pageload_called_for_testing_ = true; | 
|  | if (!predictor_ || predictor_->WasShutdown()) { | 
|  | return; | 
|  | } | 
|  | page_data->has_local_preconnect_predictions_for_current_navigation_ = | 
|  | predictor_->PrepareForPageLoad(initiator_origin, main_frame_url, | 
|  | HintOrigin::NAVIGATION); | 
|  |  | 
|  | if ((page_data->has_local_preconnect_predictions_for_current_navigation_ && | 
|  | !features::ShouldAlwaysRetrieveOptimizationGuidePredictions()) || | 
|  | !optimization_guide_decider_ || !should_consult_optimization_guide) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | TRACE_EVENT("navigation", | 
|  | "LoadingPredictorTabHelper::PrepareForPageLoad." | 
|  | "OptimizationGuidePrediction"); | 
|  | page_data->last_optimization_guide_prediction_ = | 
|  | OptimizationGuidePrediction(); | 
|  | page_data->last_optimization_guide_prediction_->decision = | 
|  | optimization_guide::OptimizationGuideDecision::kUnknown; | 
|  |  | 
|  | optimization_guide_decider_->CanApplyOptimization( | 
|  | main_frame_url, optimization_guide::proto::LOADING_PREDICTOR, | 
|  | base::BindOnce( | 
|  | &LoadingPredictorTabHelper::OnOptimizationGuideDecision, | 
|  | weak_ptr_factory_.GetWeakPtr(), std::move(page_data), | 
|  | initiator_origin, main_frame_url, | 
|  | !page_data | 
|  | ->has_local_preconnect_predictions_for_current_navigation_)); | 
|  | } | 
|  |  | 
|  | void LoadingPredictorTabHelper::DidRedirectNavigation( | 
|  | content::NavigationHandle* navigation_handle) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | if (!predictor_) | 
|  | return; | 
|  |  | 
|  | MaybeSetLCPPNavigationHint(*navigation_handle, *predictor_); | 
|  |  | 
|  | MaybePrewarmMainResourceAndSubresourcesOnNavigation(*navigation_handle, | 
|  | *predictor_); | 
|  |  | 
|  | if (!IsHandledNavigation(navigation_handle)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | auto* page_data = PageData::GetForNavigationHandle(*navigation_handle); | 
|  | // PageData may not be created in DidStartNavigation if IsHandledNavigation() | 
|  | // changes after the start of the navigation. | 
|  | if (!page_data) | 
|  | return; | 
|  |  | 
|  | const auto& redirect_chain = navigation_handle->GetRedirectChain(); | 
|  | auto redirect_size = redirect_chain.size(); | 
|  | CHECK_GE(redirect_size, 2U); | 
|  | bool is_same_origin_redirect = | 
|  | url::Origin::Create(redirect_chain[redirect_size - 2]) == | 
|  | url::Origin::Create(navigation_handle->GetURL()); | 
|  |  | 
|  | // If we are not trying to get an optimization guide prediction for this page | 
|  | // load, just return. | 
|  | if (!optimization_guide_decider_ || | 
|  | !page_data->last_optimization_guide_prediction_) | 
|  | return; | 
|  |  | 
|  | // Get an updated prediction for the navigation. | 
|  | optimization_guide_decider_->CanApplyOptimization( | 
|  | navigation_handle->GetURL(), optimization_guide::proto::LOADING_PREDICTOR, | 
|  | base::BindOnce( | 
|  | &LoadingPredictorTabHelper::OnOptimizationGuideDecision, | 
|  | weak_ptr_factory_.GetWeakPtr(), base::WrapRefCounted(page_data), | 
|  | navigation_handle->GetInitiatorOrigin(), navigation_handle->GetURL(), | 
|  | !(page_data | 
|  | ->has_local_preconnect_predictions_for_current_navigation_ && | 
|  | is_same_origin_redirect))); | 
|  | } | 
|  |  | 
|  | void LoadingPredictorTabHelper::DidFinishNavigation( | 
|  | content::NavigationHandle* navigation_handle) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | if (!predictor_) | 
|  | return; | 
|  |  | 
|  | if (!IsHandledNavigation(navigation_handle)) | 
|  | return; | 
|  |  | 
|  | auto* page_data = PageData::GetForNavigationHandle(*navigation_handle); | 
|  | // PageData may not be created in DidStartNavigation if IsHandledNavigation() | 
|  | // changes after the start of the navigation. | 
|  | if (!page_data) | 
|  | return; | 
|  |  | 
|  | predictor_->OnNavigationFinished( | 
|  | page_data->navigation_id_, navigation_handle->GetRedirectChain().front(), | 
|  | navigation_handle->GetURL(), navigation_handle->IsErrorPage()); | 
|  |  | 
|  | // Transfer the state from the NavigationHandle to the (committed) main | 
|  | // document. | 
|  | if (!navigation_handle->HasCommitted()) | 
|  | return; | 
|  | page_data->has_committed_ = true; | 
|  | PageData::TransferFromNavigationHandleToDocument( | 
|  | *navigation_handle, *navigation_handle->GetRenderFrameHost()); | 
|  | } | 
|  |  | 
|  | void LoadingPredictorTabHelper::ResourceLoadComplete( | 
|  | content::RenderFrameHost* render_frame_host, | 
|  | const content::GlobalRequestID& request_id, | 
|  | const blink::mojom::ResourceLoadInfo& resource_load_info) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | if (!predictor_) | 
|  | return; | 
|  |  | 
|  | bool is_main_frame = render_frame_host->GetParent() == nullptr; | 
|  | if (!is_main_frame) | 
|  | return; | 
|  |  | 
|  | auto* page_data = PageData::GetForDocument(*render_frame_host); | 
|  | if (!page_data) | 
|  | return; | 
|  |  | 
|  | predictor_->loading_data_collector()->RecordResourceLoadComplete( | 
|  | page_data->navigation_id_, resource_load_info); | 
|  | } | 
|  |  | 
|  | void LoadingPredictorTabHelper::DidLoadResourceFromMemoryCache( | 
|  | content::RenderFrameHost* render_frame_host, | 
|  | const GURL& url, | 
|  | const std::string& mime_type, | 
|  | network::mojom::RequestDestination request_destination) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | if (!predictor_) | 
|  | return; | 
|  |  | 
|  | auto* page_data = PageData::GetForDocument(*render_frame_host); | 
|  | if (!page_data) | 
|  | return; | 
|  |  | 
|  | blink::mojom::ResourceLoadInfo resource_load_info; | 
|  | resource_load_info.original_url = url; | 
|  | resource_load_info.final_url = url; | 
|  | resource_load_info.mime_type = mime_type; | 
|  | resource_load_info.request_destination = request_destination; | 
|  | resource_load_info.method = "GET"; | 
|  | resource_load_info.request_priority = | 
|  | GetRequestPriority(resource_load_info.request_destination); | 
|  | resource_load_info.network_info = | 
|  | blink::mojom::CommonNetworkInfo::New(false, false, std::nullopt); | 
|  | predictor_->loading_data_collector()->RecordResourceLoadComplete( | 
|  | page_data->navigation_id_, resource_load_info); | 
|  | } | 
|  |  | 
|  | void LoadingPredictorTabHelper::DocumentOnLoadCompletedInPrimaryMainFrame() { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  | if (!predictor_) | 
|  | return; | 
|  |  | 
|  | auto* page_data = | 
|  | PageData::GetForDocument(*web_contents()->GetPrimaryMainFrame()); | 
|  | if (!page_data) | 
|  | return; | 
|  |  | 
|  | predictor_->loading_data_collector()->RecordMainFrameLoadComplete( | 
|  | page_data->navigation_id_); | 
|  | } | 
|  |  | 
|  | void LoadingPredictorTabHelper::OnOptimizationGuideDecision( | 
|  | scoped_refptr<PageData> page_data, | 
|  | const std::optional<url::Origin>& initiator_origin, | 
|  | const GURL& main_frame_url, | 
|  | bool should_add_preconnects_to_prediction, | 
|  | optimization_guide::OptimizationGuideDecision decision, | 
|  | const optimization_guide::OptimizationMetadata& metadata) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  |  | 
|  | if (!predictor_) | 
|  | return; | 
|  |  | 
|  | ScopedOptimizationHintsReceiveStatusRecorder recorder; | 
|  |  | 
|  | if (!page_data->has_committed_) { | 
|  | if (!page_data->navigation_page_data_holder_ || | 
|  | page_data->navigation_page_data_holder_->navigation_handle_->GetURL() != | 
|  | main_frame_url) { | 
|  | // The current navigation has either redirected or a new one has started, | 
|  | // so return. | 
|  | recorder.set_status( | 
|  | OptimizationHintsReceiveStatus::kAfterRedirectOrNextNavigationStart); | 
|  | return; | 
|  | } | 
|  | } | 
|  | if (page_data->has_committed_) { | 
|  | // There is not a pending navigation. | 
|  | recorder.set_status(OptimizationHintsReceiveStatus::kAfterNavigationFinish); | 
|  |  | 
|  | // This hint is no longer relevant, so return. | 
|  | if (!page_data->document_page_data_holder_) | 
|  | return; | 
|  |  | 
|  | // If we get here, we have not navigated away from the navigation we | 
|  | // received hints for. Proceed to get the preconnect prediction so we can | 
|  | // see how the predicted resources match what was actually fetched for | 
|  | // counterfactual logging purposes. | 
|  | } else { | 
|  | recorder.set_status( | 
|  | OptimizationHintsReceiveStatus::kBeforeNavigationFinish); | 
|  | } | 
|  |  | 
|  | if (!page_data->last_optimization_guide_prediction_) { | 
|  | // Data for the navigation has already been recorded, do not proceed any | 
|  | // further, even for counterfactual logging. | 
|  | return; | 
|  | } | 
|  |  | 
|  | page_data->last_optimization_guide_prediction_->decision = decision; | 
|  | page_data->last_optimization_guide_prediction_ | 
|  | ->optimization_guide_prediction_arrived = base::TimeTicks::Now(); | 
|  |  | 
|  | if (decision != optimization_guide::OptimizationGuideDecision::kTrue) | 
|  | return; | 
|  |  | 
|  | if (!metadata.loading_predictor_metadata()) { | 
|  | // Metadata is not applicable, so just log an unknown decision. | 
|  | page_data->last_optimization_guide_prediction_->decision = | 
|  | optimization_guide::OptimizationGuideDecision::kUnknown; | 
|  | return; | 
|  | } | 
|  |  | 
|  | PreconnectPrediction prediction; | 
|  | url::Origin main_frame_origin = url::Origin::Create(main_frame_url); | 
|  | net::SchemefulSite main_frame_site = net::SchemefulSite(main_frame_url); | 
|  | auto network_anonymization_key = | 
|  | net::NetworkAnonymizationKey::CreateSameSite(main_frame_site); | 
|  |  | 
|  | std::set<url::Origin> predicted_origins; | 
|  | std::vector<GURL> predicted_subresources; | 
|  | const auto lp_metadata = metadata.loading_predictor_metadata(); | 
|  | for (const auto& subresource : lp_metadata->subresources()) { | 
|  | GURL subresource_url(subresource.url()); | 
|  | if (!subresource_url.is_valid()) | 
|  | continue; | 
|  | predicted_subresources.push_back(subresource_url); | 
|  | if (!subresource.preconnect_only() && | 
|  | base::FeatureList::IsEnabled(features::kLoadingPredictorPrefetch)) { | 
|  | network::mojom::RequestDestination destination = | 
|  | GetDestination(subresource.resource_type()); | 
|  | if (ShouldPrefetchDestination(destination)) { | 
|  | // TODO(falken): Detect duplicates. | 
|  | prediction.prefetch_requests.emplace_back(subresource_url, destination); | 
|  | } | 
|  | } else if (should_add_preconnects_to_prediction) { | 
|  | url::Origin subresource_origin = url::Origin::Create(subresource_url); | 
|  | if (subresource_origin == main_frame_origin) { | 
|  | // We are already connecting to the main frame origin by default, so | 
|  | // don't include this in the prediction. | 
|  | continue; | 
|  | } | 
|  | if (predicted_origins.find(subresource_origin) != predicted_origins.end()) | 
|  | continue; | 
|  | predicted_origins.insert(subresource_origin); | 
|  | prediction.requests.emplace_back(subresource_origin, 1, | 
|  | network_anonymization_key); | 
|  | } | 
|  | } | 
|  |  | 
|  | page_data->last_optimization_guide_prediction_->preconnect_prediction = | 
|  | prediction; | 
|  | page_data->last_optimization_guide_prediction_->predicted_subresources = | 
|  | predicted_subresources; | 
|  |  | 
|  | // Only prepare page load if the navigation is still pending and we want to | 
|  | // use the predictions to pre* subresources. | 
|  | if (!page_data->document_page_data_holder_ && | 
|  | features::ShouldUseOptimizationGuidePredictions()) { | 
|  | predictor_->PrepareForPageLoad(initiator_origin, main_frame_url, | 
|  | HintOrigin::OPTIMIZATION_GUIDE, | 
|  | /*preconnectable=*/false, prediction); | 
|  | } | 
|  | } | 
|  |  | 
|  | NAVIGATION_HANDLE_USER_DATA_KEY_IMPL( | 
|  | LoadingPredictorTabHelper::NavigationPageDataHolder); | 
|  | DOCUMENT_USER_DATA_KEY_IMPL(LoadingPredictorTabHelper::DocumentPageDataHolder); | 
|  | WEB_CONTENTS_USER_DATA_KEY_IMPL(LoadingPredictorTabHelper); | 
|  |  | 
|  | }  // namespace predictors |