#include "content/browser/preloading/prefetch/prefetch_container.h"
#include "base/debug/crash_logging.h"
#include "base/debug/dump_without_crashing.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/notimplemented.h"
#include "base/notreached.h"
#include "base/strings/strcat.h"
#include "base/time/time.h"
#include "components/variations/net/variations_http_headers.h"
#include "content/browser/devtools/devtools_agent_host_impl.h"
#include "content/browser/devtools/devtools_instrumentation.h"
#include "content/browser/devtools/network_service_devtools_observer.h"
#include "content/browser/loader/navigation_url_loader_impl.h"
#include "content/browser/preloading/prefetch/no_vary_search_helper.h"
#include "content/browser/preloading/prefetch/prefetch_cookie_listener.h"
#include "content/browser/preloading/prefetch/prefetch_document_manager.h"
#include "content/browser/preloading/prefetch/prefetch_features.h"
#include "content/browser/preloading/prefetch/prefetch_match_resolver.h"
#include "content/browser/preloading/prefetch/prefetch_network_context.h"
#include "content/browser/preloading/prefetch/prefetch_params.h"
#include "content/browser/preloading/prefetch/prefetch_request.h"
#include "content/browser/preloading/prefetch/prefetch_response_reader.h"
#include "content/browser/preloading/prefetch/prefetch_servable_state.h"
#include "content/browser/preloading/prefetch/prefetch_serving_handle.h"
#include "content/browser/preloading/prefetch/prefetch_serving_page_metrics_container.h"
#include "content/browser/preloading/prefetch/prefetch_single_redirect_hop.h"
#include "content/browser/preloading/prefetch/prefetch_status.h"
#include "content/browser/preloading/prefetch/prefetch_streaming_url_loader.h"
#include "content/browser/preloading/prefetch/prefetch_type.h"
#include "content/browser/preloading/preloading_attempt_impl.h"
#include "content/browser/preloading/preloading_trigger_type_impl.h"
#include "content/browser/preloading/prerender/prerender_features.h"
#include "content/browser/preloading/proxy_lookup_client_impl.h"
#include "content/browser/preloading/speculation_rules/speculation_rules_tags.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/navigation_controller_impl.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/public/browser/client_hints.h"
#include "content/public/browser/frame_accept_header.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/prefetch_request_status_listener.h"
#include "content/public/browser/preloading.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/common/content_features.h"
#include "net/base/load_flags.h"
#include "net/base/load_timing_info.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_request_info.h"
#include "net/url_request/redirect_util.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "services/network/public/cpp/client_hints.h"
#include "services/network/public/cpp/devtools_observer_util.h"
#include "services/network/public/cpp/resource_request.h"
#include "third_party/blink/public/common/client_hints/client_hints.h"
#include "third_party/blink/public/common/navigation/preloading_headers.h"
#include "third_party/blink/public/common/user_agent/user_agent_metadata.h"
#include "url/gurl.h"
namespace content {
namespace {
PrefetchStatus PrefetchStatusFromIneligibleReason(
PreloadingEligibility eligibility) {
switch (eligibility) {
case PreloadingEligibility::kBatterySaverEnabled:
return PrefetchStatus::kPrefetchIneligibleBatterySaverEnabled;
case PreloadingEligibility::kDataSaverEnabled:
return PrefetchStatus::kPrefetchIneligibleDataSaverEnabled;
case PreloadingEligibility::kExistingProxy:
return PrefetchStatus::kPrefetchIneligibleExistingProxy;
case PreloadingEligibility::kHostIsNonUnique:
return PrefetchStatus::kPrefetchIneligibleHostIsNonUnique;
case PreloadingEligibility::kNonDefaultStoragePartition:
return PrefetchStatus::kPrefetchIneligibleNonDefaultStoragePartition;
case PreloadingEligibility::kPrefetchProxyNotAvailable:
return PrefetchStatus::kPrefetchIneligiblePrefetchProxyNotAvailable;
case PreloadingEligibility::kPreloadingDisabled:
return PrefetchStatus::kPrefetchIneligiblePreloadingDisabled;
case PreloadingEligibility::kRetryAfter:
return PrefetchStatus::kPrefetchIneligibleRetryAfter;
case PreloadingEligibility::kSameSiteCrossOriginPrefetchRequiredProxy:
return PrefetchStatus::
kPrefetchIneligibleSameSiteCrossOriginPrefetchRequiredProxy;
case PreloadingEligibility::kSchemeIsNotHttps:
return PrefetchStatus::kPrefetchIneligibleSchemeIsNotHttps;
case PreloadingEligibility::kUserHasCookies:
return PrefetchStatus::kPrefetchIneligibleUserHasCookies;
case PreloadingEligibility::kUserHasServiceWorker:
return PrefetchStatus::kPrefetchIneligibleUserHasServiceWorker;
case PreloadingEligibility::kUserHasServiceWorkerNoFetchHandler:
return PrefetchStatus::
kPrefetchIneligibleUserHasServiceWorkerNoFetchHandler;
case PreloadingEligibility::kRedirectFromServiceWorker:
return PrefetchStatus::kPrefetchIneligibleRedirectFromServiceWorker;
case PreloadingEligibility::kRedirectToServiceWorker:
return PrefetchStatus::kPrefetchIneligibleRedirectToServiceWorker;
case PreloadingEligibility::kEligible:
default:
NOTREACHED();
}
}
std::optional<PreloadingTriggeringOutcome> TriggeringOutcomeFromStatus(
PrefetchStatus prefetch_status) {
switch (prefetch_status) {
case PrefetchStatus::kPrefetchNotFinishedInTime:
return PreloadingTriggeringOutcome::kRunning;
case PrefetchStatus::kPrefetchSuccessful:
return PreloadingTriggeringOutcome::kReady;
case PrefetchStatus::kPrefetchResponseUsed:
return PreloadingTriggeringOutcome::kSuccess;
case PrefetchStatus::kPrefetchIsPrivacyDecoy:
case PrefetchStatus::kPrefetchIsStale:
case PrefetchStatus::kPrefetchFailedNetError:
case PrefetchStatus::kPrefetchFailedNon2XX:
case PrefetchStatus::kPrefetchFailedMIMENotSupported:
case PrefetchStatus::kPrefetchFailedInvalidRedirect:
case PrefetchStatus::kPrefetchFailedIneligibleRedirect:
case PrefetchStatus::kPrefetchEvictedAfterCandidateRemoved:
case PrefetchStatus::kPrefetchEvictedForNewerPrefetch:
case PrefetchStatus::kPrefetchIneligibleUserHasServiceWorker:
case PrefetchStatus::kPrefetchIneligibleUserHasServiceWorkerNoFetchHandler:
case PrefetchStatus::kPrefetchIneligibleRedirectFromServiceWorker:
case PrefetchStatus::kPrefetchIneligibleRedirectToServiceWorker:
case PrefetchStatus::kPrefetchIneligibleSchemeIsNotHttps:
case PrefetchStatus::kPrefetchIneligibleNonDefaultStoragePartition:
case PrefetchStatus::kPrefetchIneligibleHostIsNonUnique:
case PrefetchStatus::kPrefetchIneligibleDataSaverEnabled:
case PrefetchStatus::kPrefetchIneligibleBatterySaverEnabled:
case PrefetchStatus::kPrefetchIneligiblePreloadingDisabled:
case PrefetchStatus::kPrefetchIneligibleExistingProxy:
case PrefetchStatus::kPrefetchIneligibleUserHasCookies:
case PrefetchStatus::kPrefetchIneligibleRetryAfter:
case PrefetchStatus::kPrefetchNotUsedCookiesChanged:
case PrefetchStatus::kPrefetchNotUsedProbeFailed:
case PrefetchStatus::
kPrefetchIneligibleSameSiteCrossOriginPrefetchRequiredProxy:
case PrefetchStatus::kPrefetchIneligiblePrefetchProxyNotAvailable:
case PrefetchStatus::kPrefetchEvictedAfterBrowsingDataRemoved:
return PreloadingTriggeringOutcome::kFailure;
case PrefetchStatus::kPrefetchHeldback:
case PrefetchStatus::kPrefetchNotStarted:
return std::nullopt;
}
return std::nullopt;
}
bool StatusUpdateIsPossibleAfterFailure(PrefetchStatus status) {
switch (status) {
case PrefetchStatus::kPrefetchEvictedAfterCandidateRemoved:
case PrefetchStatus::kPrefetchIsStale:
case PrefetchStatus::kPrefetchEvictedForNewerPrefetch:
case PrefetchStatus::kPrefetchEvictedAfterBrowsingDataRemoved: {
CHECK(TriggeringOutcomeFromStatus(status) ==
PreloadingTriggeringOutcome::kFailure);
return true;
}
case PrefetchStatus::kPrefetchNotFinishedInTime:
case PrefetchStatus::kPrefetchSuccessful:
case PrefetchStatus::kPrefetchResponseUsed:
case PrefetchStatus::kPrefetchIsPrivacyDecoy:
case PrefetchStatus::kPrefetchFailedNetError:
case PrefetchStatus::kPrefetchFailedNon2XX:
case PrefetchStatus::kPrefetchFailedMIMENotSupported:
case PrefetchStatus::kPrefetchFailedInvalidRedirect:
case PrefetchStatus::kPrefetchFailedIneligibleRedirect:
case PrefetchStatus::kPrefetchIneligibleUserHasServiceWorker:
case PrefetchStatus::kPrefetchIneligibleUserHasServiceWorkerNoFetchHandler:
case PrefetchStatus::kPrefetchIneligibleRedirectFromServiceWorker:
case PrefetchStatus::kPrefetchIneligibleRedirectToServiceWorker:
case PrefetchStatus::kPrefetchIneligibleSchemeIsNotHttps:
case PrefetchStatus::kPrefetchIneligibleNonDefaultStoragePartition:
case PrefetchStatus::kPrefetchIneligibleHostIsNonUnique:
case PrefetchStatus::kPrefetchIneligibleDataSaverEnabled:
case PrefetchStatus::kPrefetchIneligibleBatterySaverEnabled:
case PrefetchStatus::kPrefetchIneligiblePreloadingDisabled:
case PrefetchStatus::kPrefetchIneligibleExistingProxy:
case PrefetchStatus::kPrefetchIneligibleUserHasCookies:
case PrefetchStatus::kPrefetchIneligibleRetryAfter:
case PrefetchStatus::kPrefetchNotUsedCookiesChanged:
case PrefetchStatus::kPrefetchNotUsedProbeFailed:
case PrefetchStatus::
kPrefetchIneligibleSameSiteCrossOriginPrefetchRequiredProxy:
case PrefetchStatus::kPrefetchHeldback:
case PrefetchStatus::kPrefetchNotStarted:
case PrefetchStatus::kPrefetchIneligiblePrefetchProxyNotAvailable:
return false;
}
}
void RecordPrefetchProxyPrefetchMainframeNetError(int net_error) {
base::UmaHistogramSparse("PrefetchProxy.Prefetch.Mainframe.NetError",
std::abs(net_error));
}
void RecordPrefetchProxyPrefetchMainframeBodyLength(int64_t body_length) {
UMA_HISTOGRAM_COUNTS_10M("PrefetchProxy.Prefetch.Mainframe.BodyLength",
body_length);
}
bool CalculateIsLikelyAheadOfPrerender(
const PreloadPipelineInfoImpl& preload_pipeline_info) {
if (!features::UsePrefetchPrerenderIntegration()) {
return false;
}
switch (preload_pipeline_info.planned_max_preloading_type()) {
case PreloadingType::kPrefetch:
return false;
case PreloadingType::kPrerender:
case PreloadingType::kPrerenderUntilScript:
return true;
case PreloadingType::kUnspecified:
case PreloadingType::kPreconnect:
case PreloadingType::kNoStatePrefetch:
case PreloadingType::kLinkPreview:
NOTREACHED();
}
}
PrefetchContainer::PrefetchResponseCompletedCallbackForTesting&
GetPrefetchResponseCompletedCallbackForTesting() {
static base::NoDestructor<
PrefetchContainer::PrefetchResponseCompletedCallbackForTesting>
prefetch_response_completed_callback_for_testing;
return *prefetch_response_completed_callback_for_testing;
}
void AddAwAdditionalHeaders(net::HttpRequestHeaders& request_headers,
const net::HttpRequestHeaders& additional_headers) {
if (base::FeatureList::IsEnabled(
features::kPreloadingRespectUserAgentOverride)) {
net::HttpRequestHeaders additional_headers_without_ua = additional_headers;
additional_headers_without_ua.RemoveHeader(
net::HttpRequestHeaders::kUserAgent);
request_headers.MergeFrom(additional_headers_without_ua);
} else {
request_headers.MergeFrom(additional_headers);
}
}
}
std::unique_ptr<PrefetchContainer> PrefetchContainer::Create(
base::PassKey<PrefetchService>,
std::unique_ptr<const PrefetchRequest> request) {
return std::make_unique<PrefetchContainer>(base::PassKey<PrefetchContainer>(),
std::move(request));
}
std::unique_ptr<PrefetchContainer> PrefetchContainer::CreateForTesting(
std::unique_ptr<const PrefetchRequest> request) {
return std::make_unique<PrefetchContainer>(base::PassKey<PrefetchContainer>(),
std::move(request));
}
PrefetchContainer::PrefetchContainer(
base::PassKey<PrefetchContainer>,
std::unique_ptr<const PrefetchRequest> request)
: request_(std::move(request)),
referrer_(request_->initial_referrer()),
request_id_(base::UnguessableToken::Create().ToString()) {
CHECK(request_);
is_likely_ahead_of_prerender_ =
CalculateIsLikelyAheadOfPrerender(request_->preload_pipeline_info());
redirect_chain_.push_back(std::make_unique<PrefetchSingleRedirectHop>(
*this, GetURL(), IsCrossSiteRequest(url::Origin::Create(GetURL()))));
if (!features::IsPrefetchServiceWorkerEnabled(request_->browser_context()) ||
IsIsolatedNetworkContextRequiredForCurrentPrefetch()) {
service_worker_state_ = PrefetchServiceWorkerState::kDisallowed;
}
}
PrefetchContainer::~PrefetchContainer() {
is_in_dtor_ = true;
OnWillBeDestroyed();
CancelStreamingURLLoaderIfNotServing();
MaybeRecordPrefetchStatusToUMA(
prefetch_status_.value_or(PrefetchStatus::kPrefetchNotStarted));
RecordPrefetchDurationHistogram();
RecordPrefetchMatchMissedToPrefetchStartedHistogram();
RecordPrefetchContainerServedCountHistogram();
ukm::SourceId ukm_source_id = ukm::kInvalidSourceId;
if (auto* renderer_initiator_info = request().GetRendererInitiatorInfo()) {
ukm_source_id = renderer_initiator_info->ukm_source_id();
}
ukm::builders::PrefetchProxy_PrefetchedResource builder(ukm_source_id);
builder.SetResourceType( 1);
builder.SetStatus(static_cast<int>(
prefetch_status_.value_or(PrefetchStatus::kPrefetchNotStarted)));
builder.SetLinkClicked(served_count_ > 0);
if (GetNonRedirectResponseReader()) {
GetNonRedirectResponseReader()->RecordOnPrefetchContainerDestroyed(
base::PassKey<PrefetchContainer>(), builder);
}
if (probe_result_) {
builder.SetISPFilteringStatus(static_cast<int>(probe_result_.value()));
}
builder.Record(ukm::UkmRecorder::Get());
if (auto* renderer_initiator_info = request().GetRendererInitiatorInfo()) {
if (renderer_initiator_info->prefetch_document_manager()) {
renderer_initiator_info->prefetch_document_manager()
->PrefetchWillBeDestroyed(this);
}
}
}
void PrefetchContainer::OnWillBeDestroyed() {
for (auto& observer : observers_) {
observer.OnWillBeDestroyed(*this);
}
}
PrefetchServingHandle PrefetchContainer::CreateServingHandle() {
return PrefetchServingHandle(GetWeakPtr(), 0);
}
const std::vector<std::unique_ptr<PrefetchSingleRedirectHop>>&
PrefetchContainer::redirect_chain(base::PassKey<PrefetchServingHandle>) const {
return redirect_chain_;
}
void PrefetchContainer::SetProbeResult(base::PassKey<PrefetchServingHandle>,
PrefetchProbeResult probe_result) {
probe_result_ = probe_result;
}
std::optional<PreloadingTriggeringOutcome>
PrefetchContainer::TriggeringOutcomeFromStatusForServingHandle(
base::PassKey<PrefetchServingHandle>,
PrefetchStatus prefetch_status) {
return TriggeringOutcomeFromStatus(prefetch_status);
}
void PrefetchContainer::SetTriggeringOutcomeAndFailureReasonFromStatus(
PrefetchStatus new_prefetch_status) {
std::optional<PrefetchStatus> old_prefetch_status = prefetch_status_;
if (old_prefetch_status &&
old_prefetch_status.value() == PrefetchStatus::kPrefetchResponseUsed) {
return;
}
if (old_prefetch_status &&
(TriggeringOutcomeFromStatus(old_prefetch_status.value()) ==
PreloadingTriggeringOutcome::kFailure)) {
if (StatusUpdateIsPossibleAfterFailure(new_prefetch_status)) {
CHECK(TriggeringOutcomeFromStatus(new_prefetch_status) ==
PreloadingTriggeringOutcome::kFailure);
return;
} else {
SCOPED_CRASH_KEY_NUMBER("PrefetchContainer", "prefetch_status_from",
static_cast<int>(old_prefetch_status.value()));
SCOPED_CRASH_KEY_NUMBER("PrefetchContainer", "prefetch_status_to",
static_cast<int>(new_prefetch_status));
NOTREACHED() << "PrefetchStatus illegal transition: "
"(old_prefetch_status, new_prefetch_status) = ("
<< static_cast<int>(old_prefetch_status.value()) << ", "
<< static_cast<int>(new_prefetch_status) << ")";
}
}
if (TriggeringOutcomeFromStatus(new_prefetch_status) ==
PreloadingTriggeringOutcome::kFailure ||
new_prefetch_status == PrefetchStatus::kPrefetchResponseUsed) {
MaybeRecordPrefetchStatusToUMA(new_prefetch_status);
}
if (request().attempt()) {
switch (new_prefetch_status) {
case PrefetchStatus::kPrefetchNotFinishedInTime:
request().attempt()->SetTriggeringOutcome(
PreloadingTriggeringOutcome::kRunning);
break;
case PrefetchStatus::kPrefetchSuccessful:
request().attempt()->SetTriggeringOutcome(
PreloadingTriggeringOutcome::kReady);
break;
case PrefetchStatus::kPrefetchResponseUsed:
if (old_prefetch_status && old_prefetch_status.value() !=
PrefetchStatus::kPrefetchSuccessful) {
request().attempt()->SetTriggeringOutcome(
PreloadingTriggeringOutcome::kReady);
}
request().attempt()->SetTriggeringOutcome(
PreloadingTriggeringOutcome::kSuccess);
break;
case PrefetchStatus::kPrefetchIsPrivacyDecoy:
case PrefetchStatus::kPrefetchFailedNetError:
case PrefetchStatus::kPrefetchFailedNon2XX:
case PrefetchStatus::kPrefetchFailedMIMENotSupported:
case PrefetchStatus::kPrefetchFailedInvalidRedirect:
case PrefetchStatus::kPrefetchFailedIneligibleRedirect:
case PrefetchStatus::kPrefetchNotUsedProbeFailed:
case PrefetchStatus::kPrefetchNotUsedCookiesChanged:
case PrefetchStatus::kPrefetchEvictedAfterCandidateRemoved:
case PrefetchStatus::kPrefetchEvictedForNewerPrefetch:
case PrefetchStatus::kPrefetchIsStale:
case PrefetchStatus::kPrefetchEvictedAfterBrowsingDataRemoved:
request().attempt()->SetFailureReason(
ToPreloadingFailureReason(new_prefetch_status));
break;
case PrefetchStatus::kPrefetchHeldback:
case PrefetchStatus::kPrefetchNotStarted:
break;
case PrefetchStatus::kPrefetchIneligibleUserHasServiceWorker:
case PrefetchStatus::
kPrefetchIneligibleUserHasServiceWorkerNoFetchHandler:
case PrefetchStatus::kPrefetchIneligibleRedirectFromServiceWorker:
case PrefetchStatus::kPrefetchIneligibleRedirectToServiceWorker:
case PrefetchStatus::kPrefetchIneligibleSchemeIsNotHttps:
case PrefetchStatus::kPrefetchIneligibleNonDefaultStoragePartition:
case PrefetchStatus::kPrefetchIneligibleHostIsNonUnique:
case PrefetchStatus::kPrefetchIneligibleDataSaverEnabled:
case PrefetchStatus::kPrefetchIneligibleBatterySaverEnabled:
case PrefetchStatus::kPrefetchIneligiblePreloadingDisabled:
case PrefetchStatus::kPrefetchIneligibleExistingProxy:
case PrefetchStatus::kPrefetchIneligibleUserHasCookies:
case PrefetchStatus::kPrefetchIneligibleRetryAfter:
case PrefetchStatus::kPrefetchIneligiblePrefetchProxyNotAvailable:
case PrefetchStatus::
kPrefetchIneligibleSameSiteCrossOriginPrefetchRequiredProxy:
NOTIMPLEMENTED();
}
}
}
void PrefetchContainer::SetPrefetchStatusWithoutUpdatingTriggeringOutcome(
PrefetchStatus prefetch_status) {
prefetch_status_ = prefetch_status;
request().preload_pipeline_info().SetPrefetchStatus(prefetch_status);
for (auto& preload_pipeline_info : inherited_preload_pipeline_infos_) {
preload_pipeline_info->SetPrefetchStatus(prefetch_status);
}
if (auto* renderer_initiator_info = request().GetRendererInitiatorInfo()) {
std::optional<PreloadingTriggeringOutcome> preloading_trigger_outcome =
TriggeringOutcomeFromStatus(prefetch_status);
if (renderer_initiator_info->devtools_navigation_token().has_value() &&
preloading_trigger_outcome.has_value()) {
devtools_instrumentation::DidUpdatePrefetchStatus(
FrameTreeNode::From(renderer_initiator_info->GetRenderFrameHost()),
renderer_initiator_info->devtools_navigation_token().value(),
GetURL(), request().preload_pipeline_info().id(),
preloading_trigger_outcome.value(), prefetch_status, RequestId());
}
}
}
void PrefetchContainer::SetPrefetchMatchMissedTimeForMetrics(
base::TimeTicks time) {
CHECK(!time_prefetch_match_missed_);
time_prefetch_match_missed_ = time;
}
void PrefetchContainer::SetPrefetchStatus(PrefetchStatus prefetch_status) {
switch (GetLoadState()) {
case LoadState::kStarted:
case LoadState::kDeterminedHead:
case LoadState::kFailedDeterminedHead:
case LoadState::kCompleted:
case LoadState::kFailed:
SetTriggeringOutcomeAndFailureReasonFromStatus(prefetch_status);
break;
case LoadState::kNotStarted:
case LoadState::kEligible:
case LoadState::kFailedIneligible:
case LoadState::kFailedHeldback:
break;
}
SetPrefetchStatusWithoutUpdatingTriggeringOutcome(prefetch_status);
}
PrefetchStatus PrefetchContainer::GetPrefetchStatus() const {
DCHECK(prefetch_status_);
return prefetch_status_.value();
}
void PrefetchContainer::TakeProxyLookupClient(
std::unique_ptr<ProxyLookupClientImpl> proxy_lookup_client) {
DCHECK(!proxy_lookup_client_);
proxy_lookup_client_ = std::move(proxy_lookup_client);
}
std::unique_ptr<ProxyLookupClientImpl>
PrefetchContainer::ReleaseProxyLookupClient() {
DCHECK(proxy_lookup_client_);
return std::move(proxy_lookup_client_);
}
PrefetchNetworkContext*
PrefetchContainer::GetOrCreateNetworkContextForCurrentPrefetch() {
bool is_isolated_network_context_required =
IsIsolatedNetworkContextRequiredForCurrentPrefetch();
PrefetchNetworkContext* network_context =
GetNetworkContext(is_isolated_network_context_required);
if (network_context) {
return network_context;
}
GlobalRenderFrameHostId referring_render_frame_host_id;
if (auto* renderer_initiator_info = request().GetRendererInitiatorInfo()) {
referring_render_frame_host_id =
renderer_initiator_info->GetRenderFrameHostId();
}
auto owned_network_context = std::make_unique<PrefetchNetworkContext>(
is_isolated_network_context_required, request().prefetch_type(),
referring_render_frame_host_id, request().referring_origin());
network_context = owned_network_context.get();
network_contexts_.emplace(is_isolated_network_context_required,
std::move(owned_network_context));
return network_context;
}
PrefetchNetworkContext* PrefetchContainer::GetNetworkContext(
bool is_isolated_network_context_required) const {
const auto network_context_itr =
network_contexts_.find(is_isolated_network_context_required);
if (network_context_itr == network_contexts_.end()) {
return nullptr;
}
return network_context_itr->second.get();
}
void PrefetchContainer::CloseIdleConnections() {
for (const auto& network_context_itr : network_contexts_) {
CHECK(network_context_itr.second);
network_context_itr.second->CloseIdleConnections();
}
}
void PrefetchContainer::SetLoadState(LoadState new_load_state) {
if (base::FeatureList::IsEnabled(features::kPrefetchGracefulNotification)) {
CHECK(!is_in_dtor_);
}
switch (new_load_state) {
case LoadState::kNotStarted:
NOTREACHED();
case LoadState::kEligible:
case LoadState::kFailedIneligible:
CHECK_EQ(load_state_, LoadState::kNotStarted);
break;
case LoadState::kStarted:
case LoadState::kFailedHeldback:
CHECK_EQ(load_state_, LoadState::kEligible);
break;
case LoadState::kDeterminedHead:
case LoadState::kFailedDeterminedHead:
CHECK_EQ(load_state_, LoadState::kStarted);
break;
case LoadState::kCompleted:
CHECK_EQ(load_state_, LoadState::kDeterminedHead);
break;
case LoadState::kFailed:
CHECK(load_state_ == LoadState::kDeterminedHead ||
load_state_ == LoadState::kFailedDeterminedHead);
break;
}
DVLOG(1) << (*this) << " LoadState " << load_state_ << " -> "
<< new_load_state;
load_state_ = new_load_state;
}
PrefetchContainer::LoadState PrefetchContainer::GetLoadState() const {
return load_state_;
}
void PrefetchContainer::OnAddedToPrefetchService() {
prefetch_container_metrics_.time_added_to_prefetch_service =
base::TimeTicks::Now();
}
void PrefetchContainer::OnEligibilityCheckComplete(
PreloadingEligibility eligibility) {
request().preload_pipeline_info().SetPrefetchEligibility(eligibility);
for (auto& preload_pipeline_info : inherited_preload_pipeline_infos_) {
preload_pipeline_info->SetPrefetchEligibility(eligibility);
}
bool is_eligible = eligibility == PreloadingEligibility::kEligible;
if (redirect_chain_.size() == 1) {
if (is_eligible) {
SetLoadState(LoadState::kEligible);
} else {
SetLoadState(LoadState::kFailedIneligible);
PrefetchStatus new_prefetch_status =
PrefetchStatusFromIneligibleReason(eligibility);
MaybeRecordPrefetchStatusToUMA(new_prefetch_status);
SetPrefetchStatusWithoutUpdatingTriggeringOutcome(new_prefetch_status);
OnInitialPrefetchFailedIneligible(eligibility);
}
if (request().attempt()) {
request().attempt()->SetEligibility(eligibility);
}
prefetch_container_metrics_.time_initial_eligibility_got =
base::TimeTicks::Now();
if (auto* renderer_initiator_info = request().GetRendererInitiatorInfo()) {
if (renderer_initiator_info->prefetch_document_manager()) {
renderer_initiator_info->prefetch_document_manager()
->OnEligibilityCheckComplete(is_eligible);
}
}
for (auto& observer : observers_) {
observer.OnGotInitialEligibility(*this, eligibility);
}
} else {
if (!is_eligible) {
SetPrefetchStatus(PrefetchStatus::kPrefetchFailedIneligibleRedirect);
}
}
}
void PrefetchContainer::AddRedirectHop(const net::RedirectInfo& redirect_info) {
CHECK(resource_request_);
net::HttpRequestHeaders updated_headers;
std::vector<std::string> headers_to_remove = {variations::kClientDataHeader};
updated_headers.SetHeader(blink::kSecPurposeHeaderName,
GetSecPurposeHeaderValue(redirect_info.new_url));
if (base::FeatureList::IsEnabled(features::kPrefetchClientHints)) {
const auto& client_hints = network::GetClientHintToNameMap();
headers_to_remove.reserve(headers_to_remove.size() + client_hints.size());
for (const auto& [_, header] : client_hints) {
headers_to_remove.push_back(header);
}
}
headers_to_remove.push_back(blink::kSecSpeculationTagsHeaderName);
if (request().speculation_rules_tags().has_value() &&
!IsCrossSiteRequest(url::Origin::Create(redirect_info.new_url))) {
std::optional<std::string> serialized_list =
request().speculation_rules_tags()->ConvertStringToHeaderString();
CHECK(serialized_list.has_value());
updated_headers.SetHeader(blink::kSecSpeculationTagsHeaderName,
serialized_list.value());
}
AddClientHintsHeaders(url::Origin::Create(redirect_info.new_url),
&updated_headers);
std::erase_if(headers_to_remove, [&](const std::string& header) {
return updated_headers.HasHeader(header);
});
bool should_clear_upload = false;
net::RedirectUtil::UpdateHttpRequest(
resource_request_->url, resource_request_->method, redirect_info,
std::move(headers_to_remove), std::move(updated_headers),
&resource_request_->headers, &should_clear_upload);
CHECK(!should_clear_upload);
resource_request_->url = redirect_info.new_url;
resource_request_->method = redirect_info.new_method;
resource_request_->site_for_cookies = redirect_info.new_site_for_cookies;
resource_request_->trusted_params->isolation_info =
resource_request_->trusted_params->isolation_info.CreateForRedirect(
url::Origin::Create(resource_request_->url));
resource_request_->referrer = GURL(redirect_info.new_referrer);
resource_request_->referrer_policy = redirect_info.new_referrer_policy;
AddXClientDataHeader(*resource_request_.get());
redirect_chain_.push_back(std::make_unique<PrefetchSingleRedirectHop>(
*this, redirect_info.new_url,
IsCrossSiteRequest(url::Origin::Create(redirect_info.new_url))));
}
bool PrefetchContainer::IsCrossSiteRequest(const url::Origin& origin) const {
return request().referring_origin().has_value() &&
!net::SchemefulSite::IsSameSite(request().referring_origin().value(),
origin);
}
bool PrefetchContainer::IsCrossOriginRequest(const url::Origin& origin) const {
return request().referring_origin().has_value() &&
!request().referring_origin().value().IsSameOriginWith(origin);
}
void PrefetchContainer::MarkCrossSiteContaminated() {
is_cross_site_contaminated_ = true;
}
void PrefetchContainer::AddXClientDataHeader(
network::ResourceRequest& resource_request) {
if (request().browser_context()) {
variations::AppendVariationsHeader(
resource_request.url,
request().browser_context()->IsOffTheRecord()
? variations::InIncognito::kYes
: variations::InIncognito::kNo,
variations::SignedIn::kNo, &resource_request);
}
}
void PrefetchContainer::RegisterCookieListener(
network::mojom::CookieManager* cookie_manager) {
PrefetchSingleRedirectHop& this_prefetch =
GetCurrentSingleRedirectHopToPrefetch();
this_prefetch.cookie_listener_ = PrefetchCookieListener::MakeAndRegister(
this_prefetch.url_, cookie_manager);
}
void PrefetchContainer::PauseAllCookieListeners() {
for (const auto& single_redirect_hop : redirect_chain_) {
if (single_redirect_hop->cookie_listener_) {
single_redirect_hop->cookie_listener_->PauseListening();
}
}
}
void PrefetchContainer::ResumeAllCookieListeners() {
for (const auto& single_redirect_hop : redirect_chain_) {
if (single_redirect_hop->cookie_listener_) {
single_redirect_hop->cookie_listener_->ResumeListening();
}
}
}
void PrefetchContainer::SetStreamingURLLoader(
base::WeakPtr<PrefetchStreamingURLLoader> streaming_loader) {
CHECK(!streaming_loader_ || streaming_loader_->IsDeletionScheduledForCHECK());
streaming_loader_ = std::move(streaming_loader);
}
base::WeakPtr<PrefetchStreamingURLLoader>
PrefetchContainer::GetStreamingURLLoader() const {
if (!streaming_loader_ || streaming_loader_->IsDeletionScheduledForCHECK()) {
return nullptr;
}
return streaming_loader_;
}
bool PrefetchContainer::IsStreamingURLLoaderDeletionScheduledForTesting()
const {
return streaming_loader_ && streaming_loader_->IsDeletionScheduledForCHECK();
}
const PrefetchResponseReader* PrefetchContainer::GetNonRedirectResponseReader()
const {
CHECK(!redirect_chain_.empty());
if (!redirect_chain_.back()->response_reader_->GetHead()) {
return nullptr;
}
return redirect_chain_.back()->response_reader_.get();
}
const network::mojom::URLResponseHead* PrefetchContainer::GetNonRedirectHead()
const {
return GetNonRedirectResponseReader()
? GetNonRedirectResponseReader()->GetHead()
: nullptr;
}
void PrefetchContainer::CancelStreamingURLLoaderIfNotServing() {
if (!streaming_loader_) {
return;
}
streaming_loader_->CancelIfNotServing();
streaming_loader_.reset();
}
void PrefetchContainer::OnDeterminedHead(bool is_successful_determined_head) {
if (base::FeatureList::IsEnabled(features::kPrefetchGracefulNotification) &&
is_in_dtor_) {
return;
}
SetLoadState(is_successful_determined_head
? LoadState::kDeterminedHead
: LoadState::kFailedDeterminedHead);
if (GetNonRedirectHead()) {
prefetch_container_metrics_.time_header_determined_successfully =
base::TimeTicks::Now();
}
MaybeSetNoVarySearchData();
for (auto& observer : observers_) {
observer.OnDeterminedHead(*this);
}
}
void PrefetchContainer::MaybeSetNoVarySearchData() {
CHECK(!no_vary_search_data_.has_value());
if (!GetNonRedirectHead()) {
return;
}
RenderFrameHostImpl* rfhi_can_be_null = nullptr;
if (auto* renderer_initiator_info = request().GetRendererInitiatorInfo()) {
rfhi_can_be_null = renderer_initiator_info->GetRenderFrameHost();
}
no_vary_search_data_ = no_vary_search::ProcessHead(
*GetNonRedirectHead(), GetURL(), rfhi_can_be_null);
}
void PrefetchContainer::StartTimeoutTimerIfNeeded(
base::OnceClosure on_timeout_callback) {
if (request().ttl().is_positive()) {
CHECK(!timeout_timer_);
timeout_timer_ = std::make_unique<base::OneShotTimer>();
timeout_timer_->Start(FROM_HERE, request().ttl(),
std::move(on_timeout_callback));
}
}
void PrefetchContainer::SetPrefetchResponseCompletedCallbackForTesting(
PrefetchResponseCompletedCallbackForTesting callback) {
GetPrefetchResponseCompletedCallbackForTesting() =
std::move(callback);
}
void PrefetchContainer::OnPrefetchCompleteInternal(
const network::URLLoaderCompletionStatus& completion_status) {
DVLOG(1) << *this << "::OnPrefetchComplete";
UMA_HISTOGRAM_COUNTS_100("PrefetchProxy.Prefetch.RedirectChainSize",
redirect_chain_.size());
if (GetNonRedirectResponseReader()) {
UpdatePrefetchRequestMetrics(
GetNonRedirectResponseReader()->GetHead());
UpdateServingPageMetrics();
} else {
DVLOG(1) << *this << "::OnPrefetchComplete:"
<< "no non redirect response reader";
}
if (IsDecoy()) {
SetPrefetchStatus(PrefetchStatus::kPrefetchIsPrivacyDecoy);
return;
}
NotifyPrefetchRequestComplete(completion_status);
int net_error = completion_status.error_code;
int64_t body_length = completion_status.decoded_body_length;
RecordPrefetchProxyPrefetchMainframeNetError(net_error);
DCHECK(HasPrefetchStatus());
if (GetPrefetchStatus() == PrefetchStatus::kPrefetchNotFinishedInTime) {
SetPrefetchStatus(net_error == net::OK
? PrefetchStatus::kPrefetchSuccessful
: PrefetchStatus::kPrefetchFailedNetError);
UpdateServingPageMetrics();
}
if (net_error == net::OK) {
prefetch_container_metrics_.time_prefetch_completed_successfully =
base::TimeTicks::Now();
RecordPrefetchProxyPrefetchMainframeBodyLength(body_length);
}
const PrefetchStatus prefetch_status = GetPrefetchStatus();
if (prefetch_status == PrefetchStatus::kPrefetchSuccessful) {
if (auto* renderer_initiator_info = request().GetRendererInitiatorInfo()) {
if (renderer_initiator_info->prefetch_document_manager()) {
renderer_initiator_info->prefetch_document_manager()
->OnPrefetchSuccessful(this);
}
}
}
if (auto* browser_initiator_info = request().GetBrowserInitiatorInfo()) {
if (auto* listener = browser_initiator_info->request_status_listener()) {
switch (prefetch_status) {
case PrefetchStatus::kPrefetchSuccessful:
case PrefetchStatus::kPrefetchResponseUsed:
listener->OnPrefetchResponseCompleted();
break;
case PrefetchStatus::kPrefetchFailedNon2XX: {
int response_code =
GetNonRedirectHead()
? GetNonRedirectHead()->headers->response_code()
: 0;
listener->OnPrefetchResponseServerError(response_code);
break;
}
default:
listener->OnPrefetchResponseError();
break;
}
}
}
}
void PrefetchContainer::OnPrefetchComplete(
bool is_success,
const network::URLLoaderCompletionStatus& completion_status) {
SetLoadState(is_success ? LoadState::kCompleted : LoadState::kFailed);
OnPrefetchCompleteInternal(completion_status);
std::optional<int> response_code = std::nullopt;
int net_error = completion_status.error_code;
if (net_error == net::OK && GetNonRedirectHead() &&
GetNonRedirectHead()->headers) {
response_code = GetNonRedirectHead()->headers->response_code();
}
for (auto& observer : observers_) {
observer.OnPrefetchCompletedOrFailed(*this, completion_status,
response_code);
}
if (GetPrefetchResponseCompletedCallbackForTesting()) {
GetPrefetchResponseCompletedCallbackForTesting().Run(
GetWeakPtr());
}
}
void PrefetchContainer::UpdatePrefetchRequestMetrics(
const network::mojom::URLResponseHead* head) {
DVLOG(1) << *this << "::UpdatePrefetchRequestMetrics:"
<< "head = " << head;
if (head)
header_latency_ =
head->load_timing.receive_headers_end - head->load_timing.request_start;
}
PrefetchServableState PrefetchContainer::GetServableState(
base::TimeDelta cacheable_duration) const {
auto is_known_allowed_exception =
[&](PrefetchServableState servable_state,
const PrefetchMatchResolverAction& match_resolver_action) {
if (servable_state ==
PrefetchServableState::kShouldBlockUntilHeadReceived &&
match_resolver_action.kind() ==
PrefetchMatchResolverAction::ActionKind::kDrop &&
match_resolver_action.prefetch_container_load_state() ==
PrefetchContainer::LoadState::kFailedDeterminedHead) {
return true;
}
return false;
};
PrefetchServableState servable_state =
GetServableStateInternal(cacheable_duration);
PrefetchMatchResolverAction match_resolver_action =
GetMatchResolverAction(cacheable_duration);
if (servable_state != match_resolver_action.ToServableState() &&
!is_known_allowed_exception(servable_state, match_resolver_action)) {
SCOPED_CRASH_KEY_NUMBER(
"PrefetchContainer", "GSS_ssma",
GetCodeOfPrefetchServableStateAndPrefetchMatchResolverActionForDebug(
servable_state, match_resolver_action));
DUMP_WILL_BE_NOTREACHED();
}
return servable_state;
}
PrefetchServableState PrefetchContainer::GetServableStateInternal(
base::TimeDelta cacheable_duration) const {
if (GetNonRedirectResponseReader() &&
GetNonRedirectResponseReader()->Servable(cacheable_duration)) {
return PrefetchServableState::kServable;
}
DVLOG(1) << *this << "(GetServableState)"
<< "(streaming_loader=" << GetStreamingURLLoader().get()
<< ", LoadState=" << load_state_ << ")";
if (GetStreamingURLLoader() &&
redirect_chain_.back()->response_reader_->IsWaitingForResponse()) {
return PrefetchServableState::kShouldBlockUntilHeadReceived;
}
if (features::UsePrefetchPrerenderIntegration()) {
switch (load_state_) {
case LoadState::kNotStarted:
case LoadState::kEligible:
return PrefetchServableState::kShouldBlockUntilEligibilityGot;
case LoadState::kFailedIneligible:
case LoadState::kStarted:
case LoadState::kDeterminedHead:
case LoadState::kFailedDeterminedHead:
case LoadState::kCompleted:
case LoadState::kFailed:
case LoadState::kFailedHeldback:
break;
}
}
return PrefetchServableState::kNotServable;
}
PrefetchMatchResolverAction PrefetchContainer::GetMatchResolverAction(
base::TimeDelta cacheable_duration) const {
switch (load_state_) {
case LoadState::kNotStarted:
if (features::UsePrefetchPrerenderIntegration()) {
return PrefetchMatchResolverAction(
PrefetchMatchResolverAction::ActionKind::kWait, load_state_,
std::nullopt);
} else {
return PrefetchMatchResolverAction(
PrefetchMatchResolverAction::ActionKind::kDrop, load_state_,
std::nullopt);
}
case LoadState::kEligible:
if (features::UsePrefetchPrerenderIntegration()) {
return PrefetchMatchResolverAction(
PrefetchMatchResolverAction::ActionKind::kWait, load_state_,
std::nullopt);
} else {
return PrefetchMatchResolverAction(
PrefetchMatchResolverAction::ActionKind::kDrop, load_state_,
std::nullopt);
}
case LoadState::kStarted:
return PrefetchMatchResolverAction(
PrefetchMatchResolverAction::ActionKind::kWait, load_state_,
std::nullopt);
case LoadState::kDeterminedHead: {
const bool is_expired = false;
return PrefetchMatchResolverAction(
PrefetchMatchResolverAction::ActionKind::kMaybeServe, load_state_,
is_expired);
}
case LoadState::kFailedDeterminedHead:
return PrefetchMatchResolverAction(
PrefetchMatchResolverAction::ActionKind::kDrop, load_state_,
std::nullopt);
case LoadState::kCompleted: {
CHECK(!redirect_chain_.empty());
CHECK_EQ(redirect_chain_.back()->response_reader_->load_state(),
PrefetchResponseReader::LoadState::kCompleted);
CHECK(GetNonRedirectResponseReader());
const bool is_expired =
!GetNonRedirectResponseReader()->Servable(cacheable_duration);
return PrefetchMatchResolverAction(
PrefetchMatchResolverAction::ActionKind::kMaybeServe, load_state_,
is_expired);
}
case LoadState::kFailedHeldback:
case LoadState::kFailedIneligible:
case LoadState::kFailed:
return PrefetchMatchResolverAction(
PrefetchMatchResolverAction::ActionKind::kDrop, load_state_,
std::nullopt);
}
}
PrefetchSingleRedirectHop&
PrefetchContainer::GetCurrentSingleRedirectHopToPrefetch() const {
CHECK(redirect_chain_.size() > 0);
return *redirect_chain_[redirect_chain_.size() - 1];
}
const PrefetchSingleRedirectHop&
PrefetchContainer::GetPreviousSingleRedirectHopToPrefetch() const {
CHECK(redirect_chain_.size() > 1);
return *redirect_chain_[redirect_chain_.size() - 2];
}
void PrefetchContainer::SetServingPageMetrics(
base::WeakPtr<PrefetchServingPageMetricsContainer>
serving_page_metrics_container) {
serving_page_metrics_container_ = serving_page_metrics_container;
}
void PrefetchContainer::UpdateServingPageMetrics() {
DVLOG(1) << *this << "::UpdateServingPageMetrics:"
<< "serving_page_metrics_container_ = "
<< serving_page_metrics_container_.get();
if (!serving_page_metrics_container_) {
return;
}
serving_page_metrics_container_->SetRequiredPrivatePrefetchProxy(
request().prefetch_type().IsProxyRequiredWhenCrossOrigin());
serving_page_metrics_container_->SetPrefetchHeaderLatency(
GetPrefetchHeaderLatency());
if (HasPrefetchStatus()) {
serving_page_metrics_container_->SetPrefetchStatus(GetPrefetchStatus());
}
}
void PrefetchContainer::SimulatePrefetchEligibleForTest() {
if (request().attempt()) {
request().attempt()->SetEligibility(PreloadingEligibility::kEligible);
request().attempt()->SetHoldbackStatus(PreloadingHoldbackStatus::kAllowed);
}
SetLoadState(LoadState::kEligible);
SetPrefetchStatus(PrefetchStatus::kPrefetchNotStarted);
}
void PrefetchContainer::SimulatePrefetchStartedForTest() {
SetLoadState(LoadState::kStarted);
SetPrefetchStatus(PrefetchStatus::kPrefetchNotFinishedInTime);
}
void PrefetchContainer::SimulatePrefetchCompletedForTest() {
SetPrefetchStatus(PrefetchStatus::kPrefetchSuccessful);
}
void PrefetchContainer::SimulatePrefetchFailedIneligibleForTest(
PreloadingEligibility eligibility) {
CHECK_NE(PreloadingEligibility::kEligible, eligibility);
if (request().attempt()) {
request().attempt()->SetEligibility(eligibility);
}
SetLoadState(LoadState::kFailedIneligible);
}
void PrefetchContainer::OnDetectedCookiesChange(
std::optional<bool>
is_unblock_for_cookies_changed_triggered_by_this_prefetch_container) {
if (on_detected_cookies_change_called_) {
return;
}
on_detected_cookies_change_called_ = true;
if (prefetch_status_ &&
TriggeringOutcomeFromStatus(prefetch_status_.value()) ==
PreloadingTriggeringOutcome::kFailure) {
SCOPED_CRASH_KEY_NUMBER("PrefetchContainer", "ODCC2_from",
static_cast<int>(prefetch_status_.value()));
if (is_unblock_for_cookies_changed_triggered_by_this_prefetch_container
.has_value()) {
SCOPED_CRASH_KEY_BOOL(
"PrefetchContainer", "ODCC2_iufcctbtpc",
is_unblock_for_cookies_changed_triggered_by_this_prefetch_container
.value());
}
base::debug::DumpWithoutCrashing();
return;
}
CHECK_NE(GetPrefetchStatus(), PrefetchStatus::kPrefetchNotUsedCookiesChanged);
SetPrefetchStatus(PrefetchStatus::kPrefetchNotUsedCookiesChanged);
UpdateServingPageMetrics();
if (base::FeatureList::IsEnabled(
features::kPrefetchAsyncCancelOnCookiesChange)) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&PrefetchContainer::CancelStreamingURLLoaderIfNotServing,
GetWeakPtr()));
} else {
CancelStreamingURLLoaderIfNotServing();
}
}
void PrefetchContainer::OnPrefetchStarted() {
SetLoadState(PrefetchContainer::LoadState::kStarted);
prefetch_container_metrics_.time_prefetch_started = base::TimeTicks::Now();
}
GURL PrefetchContainer::GetCurrentURL() const {
return GetCurrentSingleRedirectHopToPrefetch().url_;
}
GURL PrefetchContainer::GetPreviousURL() const {
return GetPreviousSingleRedirectHopToPrefetch().url_;
}
bool PrefetchContainer::IsIsolatedNetworkContextRequiredForCurrentPrefetch()
const {
const PrefetchSingleRedirectHop& this_prefetch =
GetCurrentSingleRedirectHopToPrefetch();
return this_prefetch.is_isolated_network_context_required_;
}
bool PrefetchContainer::IsIsolatedNetworkContextRequiredForPreviousRedirectHop()
const {
const PrefetchSingleRedirectHop& previous_prefetch =
GetPreviousSingleRedirectHopToPrefetch();
return previous_prefetch.is_isolated_network_context_required_;
}
base::WeakPtr<PrefetchResponseReader>
PrefetchContainer::GetResponseReaderForCurrentPrefetch() {
const PrefetchSingleRedirectHop& this_prefetch =
GetCurrentSingleRedirectHopToPrefetch();
CHECK(this_prefetch.response_reader_);
return this_prefetch.response_reader_->GetWeakPtr();
}
bool PrefetchContainer::IsProxyRequiredForURL(const GURL& url) const {
return IsCrossOriginRequest(url::Origin::Create(url)) &&
request().prefetch_type().IsProxyRequiredWhenCrossOrigin();
}
void PrefetchContainer::MakeResourceRequest() {
const GURL& url = GetURL();
url::Origin origin = url::Origin::Create(url);
net::IsolationInfo isolation_info = net::IsolationInfo::Create(
net::IsolationInfo::RequestType::kMainFrame, origin, origin,
net::SiteForCookies::FromOrigin(origin));
auto priority = [&] {
if (request().priority().has_value()) {
switch (request().priority().value()) {
case PrefetchPriority::kLow:
return net::RequestPriority::IDLE;
case PrefetchPriority::kMedium:
return net::RequestPriority::LOW;
case PrefetchPriority::kHigh:
return net::RequestPriority::MEDIUM;
case PrefetchPriority::kHighest:
return net::RequestPriority::HIGHEST;
}
}
if (IsSpeculationRuleType(request().prefetch_type().trigger_type())) {
blink::mojom::SpeculationEagerness eagerness =
request().prefetch_type().GetEagerness();
switch (eagerness) {
case blink::mojom::SpeculationEagerness::kConservative:
return net::RequestPriority::MEDIUM;
case blink::mojom::SpeculationEagerness::kModerate:
return net::RequestPriority::LOW;
case blink::mojom::SpeculationEagerness::kEager:
case blink::mojom::SpeculationEagerness::kImmediate:
return net::RequestPriority::IDLE;
}
} else {
if (base::FeatureList::IsEnabled(
features::kPrefetchNetworkPriorityForEmbedders)) {
return net::RequestPriority::MEDIUM;
} else {
return net::RequestPriority::IDLE;
}
}
}();
mojo::PendingRemote<network::mojom::DevToolsObserver>
devtools_observer_remote;
if (std::optional<mojo::PendingRemote<network::mojom::DevToolsObserver>>
devtools_observer = MakeSelfOwnedNetworkServiceDevToolsObserver()) {
devtools_observer_remote = std::move(devtools_observer.value());
}
const bool is_main_frame = true;
auto resource_request = CreateResourceRequestForNavigation(
net::HttpRequestHeaders::kGetMethod, url,
network::mojom::RequestDestination::kDocument, referrer_, isolation_info,
std::move(devtools_observer_remote), priority, is_main_frame);
resource_request->load_flags = net::LOAD_PREFETCH;
if (request().should_bypass_http_cache()) {
resource_request->load_flags |= net::LOAD_DISABLE_CACHE;
}
AddAwAdditionalHeaders(resource_request->headers,
request().additional_headers());
CHECK(request().browser_context());
resource_request->headers.SetHeader(
net::HttpRequestHeaders::kAccept,
FrameAcceptHeaderValue(true,
request().browser_context()));
if (!base::FeatureList::IsEnabled(
blink::features::kRemovePurposeHeaderForPrefetch)) {
resource_request->headers.SetHeader(blink::kPurposeHeaderName,
blink::kSecPurposePrefetchHeaderValue);
}
resource_request->headers.SetHeader(blink::kSecPurposeHeaderName,
GetSecPurposeHeaderValue(url));
resource_request->headers.SetHeader("Upgrade-Insecure-Requests", "1");
if (request().speculation_rules_tags().has_value() &&
!IsCrossSiteRequest(origin)) {
std::optional<std::string> serialized_list =
request().speculation_rules_tags()->ConvertStringToHeaderString();
CHECK(serialized_list.has_value());
resource_request->headers.SetHeader(blink::kSecSpeculationTagsHeaderName,
serialized_list.value());
}
resource_request->devtools_request_id = RequestId();
MaybeApplyOverrideForUserAgentHeader(*resource_request);
AddClientHintsHeaders(origin, &resource_request->headers);
if (request().should_append_variations_header()) {
AddXClientDataHeader(*resource_request.get());
}
CHECK(!resource_request->skip_service_worker);
resource_request_ = std::move(resource_request);
}
void PrefetchContainer::UpdateReferrer(
const GURL& new_referrer_url,
const network::mojom::ReferrerPolicy& new_referrer_policy) {
referrer_.url = new_referrer_url;
referrer_.policy = new_referrer_policy;
}
const PrefetchKey& PrefetchContainer::key() const {
return request().key();
}
const GURL& PrefetchContainer::GetURL() const {
return request().key().url();
}
const std::optional<net::HttpNoVarySearchData>&
PrefetchContainer::GetNoVarySearchHint() const {
return request().no_vary_search_hint();
}
bool PrefetchContainer::ShouldApplyUserAgentOverride(
const GURL& request_url) const {
if (!base::FeatureList::IsEnabled(
features::kPreloadingRespectUserAgentOverride)) {
return false;
}
WebContents* referring_web_contents =
request().referring_web_contents().get();
if (!referring_web_contents) {
return false;
}
if (const blink::UserAgentOverride& ua_override =
referring_web_contents->GetUserAgentOverride();
ua_override.ua_string_override.empty()) {
return false;
}
raw_ptr<WebContentsDelegate> delegate = referring_web_contents->GetDelegate();
NavigationController::UserAgentOverrideOption option =
delegate ? delegate->ShouldOverrideUserAgentForPreloading(request_url)
: NavigationController::UA_OVERRIDE_INHERIT;
auto* render_frame_host = referring_web_contents->GetPrimaryMainFrame();
CHECK(render_frame_host);
auto& nav_controller = static_cast<NavigationControllerImpl&>(
render_frame_host->GetController());
return nav_controller.ShouldOverrideUserAgentInNextNavigation(option);
}
void PrefetchContainer::MaybeApplyOverrideForUserAgentHeader(
network::ResourceRequest& resource_request) {
if (!ShouldApplyUserAgentOverride(resource_request.url)) {
return;
}
WebContents* referring_web_contents =
request_->referring_web_contents().get();
if (!referring_web_contents) {
return;
}
const blink::UserAgentOverride& ua_override =
referring_web_contents->GetUserAgentOverride();
CHECK(!ua_override.ua_string_override.empty());
resource_request.headers.SetHeader(net::HttpRequestHeaders::kUserAgent,
ua_override.ua_string_override);
}
void PrefetchContainer::AddClientHintsHeaders(
const url::Origin& origin,
net::HttpRequestHeaders* request_headers) {
if (!base::FeatureList::IsEnabled(features::kPrefetchClientHints)) {
return;
}
if (!request().browser_context()) {
return;
}
ClientHintsControllerDelegate* client_hints_delegate =
request().browser_context()->GetClientHintsControllerDelegate();
if (!client_hints_delegate) {
return;
}
auto* referring_ftn = base::FeatureList::IsEnabled(
features::kPrefetchDevtoolsUserAgentOverride) &&
request().referring_web_contents()
? FrameTreeNode::From(RenderFrameHostImpl::FromID(
request()
.referring_web_contents()
->GetPrimaryMainFrame()
->GetGlobalId()))
: nullptr;
const bool is_ua_override_on = false;
net::HttpRequestHeaders client_hints_headers;
if (request().is_javascript_enabled()) {
AddClientHintsHeadersToPrefetchNavigation(
origin, &client_hints_headers, request().browser_context(),
client_hints_delegate, is_ua_override_on, referring_ftn);
if (base::FeatureList::IsEnabled(
features::kPrefetchDevtoolsUserAgentOverride) &&
referring_ftn && RenderFrameDevToolsAgentHost::GetFor(referring_ftn)) {
devtools_instrumentation::ApplyEmulationOverrides(
RenderFrameDevToolsAgentHost::GetFor(referring_ftn),
&client_hints_headers);
}
}
const bool is_cross_site = IsCrossSiteRequest(origin);
const auto cross_site_behavior =
features::kPrefetchClientHintsCrossSiteBehavior.Get();
if (!is_cross_site ||
cross_site_behavior ==
features::PrefetchClientHintsCrossSiteBehavior::kAll) {
request_headers->MergeFrom(client_hints_headers);
} else if (cross_site_behavior ==
features::PrefetchClientHintsCrossSiteBehavior::kLowEntropy) {
for (const auto& [ch, header] : network::GetClientHintToNameMap()) {
if (blink::IsClientHintSentByDefault(ch)) {
std::optional<std::string> header_value =
client_hints_headers.GetHeader(header);
if (header_value) {
request_headers->SetHeader(header, std::move(header_value).value());
}
}
}
}
}
std::ostream& operator<<(std::ostream& ostream,
const PrefetchContainer& prefetch_container) {
return ostream << "PrefetchContainer[" << &prefetch_container
<< ", Key=" << prefetch_container.key() << "]";
}
std::ostream& operator<<(std::ostream& ostream,
PrefetchContainer::LoadState state) {
switch (state) {
case PrefetchContainer::LoadState::kNotStarted:
return ostream << "NotStarted";
case PrefetchContainer::LoadState::kEligible:
return ostream << "Eligible";
case PrefetchContainer::LoadState::kFailedIneligible:
return ostream << "FailedIneligible";
case PrefetchContainer::LoadState::kStarted:
return ostream << "Started";
case PrefetchContainer::LoadState::kDeterminedHead:
return ostream << "DeterminedHead";
case PrefetchContainer::LoadState::kFailedDeterminedHead:
return ostream << "FailedDeterminedHead";
case PrefetchContainer::LoadState::kCompleted:
return ostream << "Completed";
case PrefetchContainer::LoadState::kFailed:
return ostream << "Failed";
case PrefetchContainer::LoadState::kFailedHeldback:
return ostream << "FailedHeldback";
}
}
const char* PrefetchContainer::GetSecPurposeHeaderValue(
const GURL& request_url) const {
switch (request().preload_pipeline_info().planned_max_preloading_type()) {
case PreloadingType::kPrefetch:
if (IsProxyRequiredForURL(request_url)) {
return blink::kSecPurposePrefetchAnonymousClientIpHeaderValue;
} else {
return blink::kSecPurposePrefetchHeaderValue;
}
case PreloadingType::kPrerenderUntilScript:
case PreloadingType::kPrerender:
if (IsProxyRequiredForURL(request_url)) {
NOTREACHED();
} else {
return blink::kSecPurposePrefetchPrerenderHeaderValue;
}
case PreloadingType::kUnspecified:
case PreloadingType::kPreconnect:
case PreloadingType::kNoStatePrefetch:
case PreloadingType::kLinkPreview:
NOTREACHED();
}
}
void PrefetchContainer::OnInitialPrefetchFailedIneligible(
PreloadingEligibility eligibility) {
CHECK(redirect_chain_.size() == 1);
CHECK_NE(eligibility, PreloadingEligibility::kEligible);
if (auto* browser_initiator_info = request().GetBrowserInitiatorInfo()) {
if (auto* listener = browser_initiator_info->request_status_listener()) {
listener->OnPrefetchStartFailedGeneric();
}
}
}
void PrefetchContainer::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void PrefetchContainer::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
bool PrefetchContainer::IsExactMatch(const GURL& url) const {
return url == GetURL();
}
bool PrefetchContainer::IsNoVarySearchHeaderMatch(const GURL& url) const {
const std::optional<net::HttpNoVarySearchData>& no_vary_search_data =
GetNoVarySearchData();
return no_vary_search_data &&
no_vary_search_data->AreEquivalent(url, GetURL());
}
bool PrefetchContainer::ShouldWaitForNoVarySearchHeader(const GURL& url) const {
const std::optional<net::HttpNoVarySearchData>& no_vary_search_hint =
request().no_vary_search_hint();
return !GetNonRedirectHead() && no_vary_search_hint &&
no_vary_search_hint->AreEquivalent(url, GetURL());
}
void PrefetchContainer::OnUnregisterCandidate(
const GURL& navigated_url,
bool is_served,
PrefetchPotentialCandidateServingResult serving_result,
bool is_nav_prerender,
std::optional<base::TimeDelta> blocked_duration) {
if (is_served) {
served_count_++;
UMA_HISTOGRAM_COUNTS_100("PrefetchProxy.AfterClick.RedirectChainSize",
redirect_chain_.size());
}
RecordPrefetchMatchingBlockedNavigationHistogram(blocked_duration.has_value(),
is_nav_prerender);
RecordBlockUntilHeadDurationHistogram(blocked_duration, is_served,
is_nav_prerender);
RecordPrefetchPotentialCandidateServingResultHistogram(serving_result);
if (request().attempt()) {
static_cast<PreloadingAttemptImpl*>(request().attempt())
->SetIsAccurateTriggering(navigated_url);
}
}
void PrefetchContainer::MergeNewPrefetchRequest(
std::unique_ptr<const PrefetchRequest> prefetch_request) {
scoped_refptr<PreloadPipelineInfoImpl> added_preload_pipeline_info =
base::WrapRefCounted(&prefetch_request->preload_pipeline_info());
added_preload_pipeline_info->SetPrefetchEligibility(
request().preload_pipeline_info().prefetch_eligibility());
if (auto prefetch_status =
request().preload_pipeline_info().prefetch_status()) {
added_preload_pipeline_info->SetPrefetchStatus(*prefetch_status);
}
inherited_preload_pipeline_infos_.push_back(
std::move(added_preload_pipeline_info));
is_likely_ahead_of_prerender_ |= CalculateIsLikelyAheadOfPrerender(
prefetch_request->preload_pipeline_info());
}
void PrefetchContainer::NotifyPrefetchRequestWillBeSent(
const network::mojom::URLResponseHeadPtr* redirect_head) {
if (IsDecoy()) {
return;
}
auto* renderer_initiator_info = request().GetRendererInitiatorInfo();
if (!renderer_initiator_info) {
return;
}
auto* rfh = renderer_initiator_info->GetRenderFrameHost();
auto* ftn = FrameTreeNode::From(rfh);
if (!rfh) {
return;
}
if (redirect_head && *redirect_head) {
const network::mojom::URLResponseHeadDevToolsInfoPtr info =
network::ExtractDevToolsInfo(**redirect_head);
const GURL url = GetPreviousURL();
std::pair<const GURL&, const network::mojom::URLResponseHeadDevToolsInfo&>
redirect_info{url, *info.get()};
devtools_instrumentation::OnPrefetchRequestWillBeSent(
*ftn, RequestId(), rfh->GetLastCommittedURL(), *GetResourceRequest(),
std::move(redirect_info));
} else {
devtools_instrumentation::OnPrefetchRequestWillBeSent(
*ftn, RequestId(), rfh->GetLastCommittedURL(), *GetResourceRequest(),
std::nullopt);
}
}
void PrefetchContainer::NotifyPrefetchResponseReceived(
const network::mojom::URLResponseHead& head) {
CHECK(!IsDecoy());
prefetch_container_metrics_.time_url_request_started =
head.load_timing.request_start;
prefetch_container_metrics_.time_domain_lookup_started =
head.load_timing.connect_timing.domain_lookup_start;
if (head.load_timing_internal_info.has_value()) {
prefetch_container_metrics_.create_stream_delay =
head.load_timing_internal_info->create_stream_delay;
prefetch_container_metrics_.connected_callback_delay =
head.load_timing_internal_info->connected_callback_delay;
prefetch_container_metrics_.initialize_stream_delay =
head.load_timing_internal_info->initialize_stream_delay;
}
auto* renderer_initiator_info = request().GetRendererInitiatorInfo();
if (!renderer_initiator_info) {
return;
}
auto* ftn =
FrameTreeNode::From(renderer_initiator_info->GetRenderFrameHost());
if (!ftn) {
return;
}
devtools_instrumentation::OnPrefetchResponseReceived(ftn, RequestId(),
GetCurrentURL(), head);
}
void PrefetchContainer::NotifyPrefetchRequestComplete(
const network::URLLoaderCompletionStatus& completion_status) {
CHECK(!IsDecoy());
auto* renderer_initiator_info = request().GetRendererInitiatorInfo();
if (!renderer_initiator_info) {
return;
}
auto* ftn =
FrameTreeNode::From(renderer_initiator_info->GetRenderFrameHost());
if (!ftn) {
return;
}
devtools_instrumentation::OnPrefetchRequestComplete(ftn, RequestId(),
completion_status);
}
std::optional<mojo::PendingRemote<network::mojom::DevToolsObserver>>
PrefetchContainer::MakeSelfOwnedNetworkServiceDevToolsObserver() {
if (IsDecoy()) {
return std::nullopt;
}
auto* renderer_initiator_info = request().GetRendererInitiatorInfo();
if (!renderer_initiator_info) {
return std::nullopt;
}
auto* ftn =
FrameTreeNode::From(renderer_initiator_info->GetRenderFrameHost());
if (!ftn) {
return std::nullopt;
}
return NetworkServiceDevToolsObserver::MakeSelfOwned(ftn);
}
std::string PrefetchContainer::GetMetricsSuffix() const {
std::optional<std::string> embedder_histogram_suffix;
if (auto* browser_initiator_info = request().GetBrowserInitiatorInfo()) {
embedder_histogram_suffix =
browser_initiator_info->embedder_histogram_suffix();
}
return GetMetricsSuffixTriggerTypeAndEagerness(request().prefetch_type(),
embedder_histogram_suffix);
}
bool PrefetchContainer::HasPreloadPipelineInfoForMetrics(
const PreloadPipelineInfo& other) const {
if (&request().preload_pipeline_info() == &other) {
return true;
}
for (const auto& preload_pipeline_info : inherited_preload_pipeline_infos_) {
if (preload_pipeline_info.get() == &other) {
return true;
}
}
return false;
}
void PrefetchContainer::MaybeRecordPrefetchStatusToUMA(
PrefetchStatus prefetch_status) {
if (prefetch_status_recorded_to_uma_) {
return;
}
base::UmaHistogramEnumeration("Preloading.Prefetch.PrefetchStatus",
prefetch_status);
prefetch_status_recorded_to_uma_ = true;
}
void PrefetchContainer::OnServiceWorkerStateDetermined(
PrefetchServiceWorkerState service_worker_state) {
switch (service_worker_state_) {
case PrefetchServiceWorkerState::kDisallowed:
CHECK_EQ(service_worker_state, PrefetchServiceWorkerState::kDisallowed);
break;
case PrefetchServiceWorkerState::kAllowed:
CHECK_NE(service_worker_state, PrefetchServiceWorkerState::kAllowed);
service_worker_state_ = service_worker_state;
break;
case PrefetchServiceWorkerState::kControlled:
NOTREACHED();
}
}
void PrefetchContainer::RecordPrefetchDurationHistogram() {
if (!prefetch_container_metrics_.time_added_to_prefetch_service.has_value()) {
return;
}
if (!prefetch_container_metrics_.time_initial_eligibility_got.has_value()) {
return;
}
base::UmaHistogramTimes(
base::StrCat({
"Prefetch.PrefetchContainer.AddedToInitialEligibility.",
GetMetricsSuffix(),
}),
prefetch_container_metrics_.time_initial_eligibility_got.value() -
prefetch_container_metrics_.time_added_to_prefetch_service.value());
if (!prefetch_container_metrics_.time_prefetch_started.has_value()) {
return;
}
base::UmaHistogramTimes(
base::StrCat({
"Prefetch.PrefetchContainer.AddedToPrefetchStarted.",
GetMetricsSuffix(),
}),
prefetch_container_metrics_.time_prefetch_started.value() -
prefetch_container_metrics_.time_added_to_prefetch_service.value());
base::UmaHistogramTimes(
base::StrCat({
"Prefetch.PrefetchContainer.InitialEligibilityToPrefetchStarted.",
GetMetricsSuffix(),
}),
prefetch_container_metrics_.time_prefetch_started.value() -
prefetch_container_metrics_.time_initial_eligibility_got.value());
if (!prefetch_container_metrics_.time_url_request_started.has_value()) {
return;
}
base::UmaHistogramTimes(
base::StrCat({
"Prefetch.PrefetchContainer.AddedToURLRequestStarted.",
GetMetricsSuffix(),
}),
prefetch_container_metrics_.time_url_request_started.value() -
prefetch_container_metrics_.time_added_to_prefetch_service.value());
base::UmaHistogramTimes(
base::StrCat({
"Prefetch.PrefetchContainer.PrefetchStartedToURLRequestStarted.",
GetMetricsSuffix(),
}),
prefetch_container_metrics_.time_url_request_started.value() -
prefetch_container_metrics_.time_prefetch_started.value());
CHECK(prefetch_container_metrics_.time_domain_lookup_started.has_value());
base::UmaHistogramTimes(
base::StrCat({
"Prefetch.PrefetchContainer.AddedToDomainLookupStarted.",
GetMetricsSuffix(),
}),
prefetch_container_metrics_.time_domain_lookup_started.value() -
prefetch_container_metrics_.time_added_to_prefetch_service.value());
base::UmaHistogramTimes(
base::StrCat({
"Prefetch.PrefetchContainer.PrefetchStartedToDomainLookupStarted.",
GetMetricsSuffix(),
}),
prefetch_container_metrics_.time_domain_lookup_started.value() -
prefetch_container_metrics_.time_prefetch_started.value());
if (prefetch_container_metrics_.create_stream_delay.has_value()) {
base::UmaHistogramTimes(base::StrCat({
"Prefetch.PrefetchContainer.CreateStreamDelay.",
GetMetricsSuffix(),
}),
*prefetch_container_metrics_.create_stream_delay);
}
if (prefetch_container_metrics_.connected_callback_delay.has_value()) {
base::UmaHistogramTimes(
base::StrCat({
"Prefetch.Prefetchcontainer.ConnectedCallbackDelay.",
GetMetricsSuffix(),
}),
*prefetch_container_metrics_.connected_callback_delay);
}
if (prefetch_container_metrics_.initialize_stream_delay) {
base::UmaHistogramTimes(
base::StrCat({
"Prefetch.Prefetchcontainer.InitializeStreamDelay.",
GetMetricsSuffix(),
}),
*prefetch_container_metrics_.initialize_stream_delay);
}
if (!prefetch_container_metrics_.time_header_determined_successfully
.has_value()) {
return;
}
base::UmaHistogramTimes(
base::StrCat({
"Prefetch.PrefetchContainer.AddedToHeaderDeterminedSuccessfully.",
GetMetricsSuffix(),
}),
prefetch_container_metrics_.time_header_determined_successfully.value() -
prefetch_container_metrics_.time_added_to_prefetch_service.value());
base::UmaHistogramTimes(
base::StrCat({
"Prefetch.PrefetchContainer."
"PrefetchStartedToHeaderDeterminedSuccessfully.",
GetMetricsSuffix(),
}),
prefetch_container_metrics_.time_header_determined_successfully.value() -
prefetch_container_metrics_.time_prefetch_started.value());
if (!prefetch_container_metrics_.time_prefetch_completed_successfully
.has_value()) {
return;
}
base::UmaHistogramTimes(
base::StrCat({
"Prefetch.PrefetchContainer.AddedToPrefetchCompletedSuccessfully.",
GetMetricsSuffix(),
}),
prefetch_container_metrics_.time_prefetch_completed_successfully.value() -
prefetch_container_metrics_.time_added_to_prefetch_service.value());
base::UmaHistogramTimes(
base::StrCat({
"Prefetch.PrefetchContainer."
"PrefetchStartedToPrefetchCompletedSuccessfully.",
GetMetricsSuffix(),
}),
prefetch_container_metrics_.time_prefetch_completed_successfully.value() -
prefetch_container_metrics_.time_prefetch_started.value());
}
void PrefetchContainer::RecordPrefetchMatchMissedToPrefetchStartedHistogram() {
if (prefetch_container_metrics_.time_prefetch_started.has_value() &&
time_prefetch_match_missed_.has_value()) {
base::UmaHistogramTimes(
base::StrCat({
"Prefetch.PrefetchContainer.PrefetchMatchMissedToPrefetchStarted.",
GetMetricsSuffix(),
}),
prefetch_container_metrics_.time_prefetch_started.value() -
time_prefetch_match_missed_.value());
}
}
void PrefetchContainer::RecordPrefetchMatchingBlockedNavigationHistogram(
bool blocked_until_head,
bool is_nav_prerender) {
base::UmaHistogramBoolean(
base::StrCat(
{"Prefetch.PrefetchMatchingBlockedNavigation.PerMatchingCandidate.",
GetMetricsSuffix()}),
blocked_until_head);
base::UmaHistogramBoolean(
base::StrCat(
{"Prefetch.PrefetchMatchingBlockedNavigation.PerMatchingCandidate.",
is_nav_prerender ? "Prerender." : "NonPrerender.",
GetMetricsSuffix()}),
blocked_until_head);
}
void PrefetchContainer::RecordBlockUntilHeadDurationHistogram(
const std::optional<base::TimeDelta>& blocked_duration,
bool served,
bool is_nav_prerender) {
base::UmaHistogramTimes(
base::StrCat({"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.",
served ? "Served." : "NotServed.", GetMetricsSuffix()}),
blocked_duration.value_or(base::Seconds(0)));
base::UmaHistogramTimes(
base::StrCat({"Prefetch.BlockUntilHeadDuration.PerMatchingCandidate.",
is_nav_prerender ? "Prerender." : "NonPrerender.",
served ? "Served." : "NotServed.", GetMetricsSuffix()}),
blocked_duration.value_or(base::Seconds(0)));
}
void PrefetchContainer::RecordPrefetchPotentialCandidateServingResultHistogram(
PrefetchPotentialCandidateServingResult serving_result) {
base::UmaHistogramEnumeration(
base::StrCat({"Prefetch.PrefetchPotentialCandidateServingResult."
"PerMatchingCandidate.",
GetMetricsSuffix()}),
serving_result);
}
void PrefetchContainer::RecordPrefetchContainerServedCountHistogram() {
base::UmaHistogramCounts100(
base::StrCat(
{"Prefetch.PrefetchContainer.ServedCount.", GetMetricsSuffix()}),
served_count_);
}
}