blob: 2616102e24ff58c00a7f8a9bbb3f46c02511a29b [file] [log] [blame]
// 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