#include "content/browser/preloading/prerender/prerender_host_registry.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/memory/memory_pressure_monitor.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/observer_list.h"
#include "base/system/sys_info.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "content/browser/devtools/devtools_instrumentation.h"
#include "content/browser/devtools/render_frame_devtools_agent_host.h"
#include "content/browser/preloading/preloading_data_impl.h"
#include "content/browser/preloading/preloading_trigger_type_impl.h"
#include "content/browser/preloading/prerender/devtools_prerender_attempt.h"
#include "content/browser/preloading/prerender/prerender_features.h"
#include "content/browser/preloading/prerender/prerender_final_status.h"
#include "content/browser/preloading/prerender/prerender_metrics.h"
#include "content/browser/preloading/prerender/prerender_navigation_utils.h"
#include "content/browser/preloading/prerender/prerender_new_tab_handle.h"
#include "content/browser/preloading/speculation_rules/speculation_rules_util.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/frame.mojom.h"
#include "content/public/browser/client_hints_controller_delegate.h"
#include "content/public/browser/preloading.h"
#include "content/public/browser/preloading_data.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/visibility.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
#include "net/base/load_flags.h"
#include "services/network/public/cpp/network_quality_tracker.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
#include "third_party/blink/public/common/features.h"
#include "url/gurl.h"
namespace content {
namespace {
bool IsBackground(Visibility visibility) {
switch (visibility) {
case Visibility::HIDDEN:
case Visibility::OCCLUDED:
return true;
case Visibility::VISIBLE:
return false;
}
}
bool DeviceHasEnoughMemoryForPrerender() {
if (!base::FeatureList::IsEnabled(blink::features::kPrerender2MemoryControls))
return true;
static constexpr int kDefaultMemoryThresholdMb =
#if BUILDFLAG(IS_ANDROID)
1700;
#elif BUILDFLAG(ARKWEB_NETWORK_LOAD)
1700;
#else
0;
#endif
int memory_threshold_mb = base::GetFieldTrialParamByFeatureAsInt(
blink::features::kPrerender2MemoryControls,
blink::features::kPrerender2MemoryThresholdParamName,
kDefaultMemoryThresholdMb);
return base::SysInfo::AmountOfPhysicalMemory().InMiB() > memory_threshold_mb;
}
base::MemoryPressureLevel GetCurrentMemoryPressureLevel() {
if (!base::FeatureList::IsEnabled(
blink::features::kPrerender2MemoryControls)) {
return base::MEMORY_PRESSURE_LEVEL_NONE;
}
auto* monitor = base::MemoryPressureMonitor::Get();
if (!monitor) {
return base::MEMORY_PRESSURE_LEVEL_NONE;
}
return monitor->GetCurrentPressureLevel(
base::MemoryPressureMonitorTag::kPrerenderHostRegistry);
}
std::unique_ptr<network::SimpleURLLoader> CreateHttpCacheQueryingResourceLoad(
const GURL& back_url) {
std::unique_ptr<network::ResourceRequest> request =
std::make_unique<network::ResourceRequest>();
request->url = back_url;
request->load_flags =
net::LOAD_ONLY_FROM_CACHE | net::LOAD_SKIP_CACHE_VALIDATION;
if (base::FeatureList::IsEnabled(
blink::features::kAvoidTrustedParamsCopies)) {
request->trusted_params = network::ResourceRequest::TrustedParams();
url::Origin origin = url::Origin::Create(back_url);
request->trusted_params->isolation_info = net::IsolationInfo::Create(
net::IsolationInfo::RequestType::kMainFrame, origin, origin,
net::SiteForCookies::FromOrigin(origin));
request->site_for_cookies =
request->trusted_params->isolation_info.site_for_cookies();
} else {
url::Origin origin = url::Origin::Create(back_url);
net::IsolationInfo isolation_info = net::IsolationInfo::Create(
net::IsolationInfo::RequestType::kMainFrame, origin, origin,
net::SiteForCookies::FromOrigin(origin));
network::ResourceRequest::TrustedParams trusted_params;
trusted_params.isolation_info = isolation_info;
request->trusted_params = trusted_params;
request->site_for_cookies =
trusted_params.isolation_info.site_for_cookies();
}
request->credentials_mode = network::mojom::CredentialsMode::kOmit;
request->skip_service_worker = true;
request->do_not_prompt_for_login = true;
CHECK(!request->SendsCookies());
CHECK(!request->SavesCookies());
constexpr net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("back_navigation_cache_query",
R"(
semantics {
sender: "Prerender"
description:
"This is not actually a network request. It is used internally by the browser to determine if the HTTP cache would be used if the user were to navigate back in session history. It only checks the cache and does not hit the network."
trigger:
"When the user performs an action that would suggest that they intend to navigate back soon. Examples include hovering the mouse over the back button and the start of a gestural back navigation."
user_data {
type: NONE
}
data: "None. The request doesn't hit the network."
destination: LOCAL
internal {
contacts {
email: "chrome-brapp-loading@chromium.org"
}
}
last_reviewed: "2023-03-24"
}
policy {
cookies_allowed: NO
setting:
"This is not controlled by a setting."
policy_exception_justification: "This is not a network request."
})");
return network::SimpleURLLoader::Create(std::move(request),
traffic_annotation);
}
bool IsNavigationInSessionHistoryPredictorDomain(NavigationHandle* handle) {
CHECK(handle->IsInPrimaryMainFrame());
CHECK(!handle->IsSameDocument());
if (handle->IsRendererInitiated()) {
return false;
}
if (!(handle->GetPageTransition() & ui::PAGE_TRANSITION_FORWARD_BACK)) {
return false;
}
if (handle->IsPost()) {
return false;
}
if (handle->IsServedFromBackForwardCache()) {
return false;
}
if (!handle->GetURL().SchemeIsHTTPOrHTTPS()) {
return false;
}
return true;
}
PreloadingEligibility ToEligibility(PrerenderFinalStatus status) {
switch (status) {
case PrerenderFinalStatus::kActivated:
case PrerenderFinalStatus::kDestroyed:
NOTREACHED();
case PrerenderFinalStatus::kLowEndDevice:
return PreloadingEligibility::kLowMemory;
case PrerenderFinalStatus::kInvalidSchemeRedirect:
NOTREACHED();
case PrerenderFinalStatus::kInvalidSchemeNavigation:
return PreloadingEligibility::kHttpOrHttpsOnly;
case PrerenderFinalStatus::kNavigationRequestBlockedByCsp:
case PrerenderFinalStatus::kMojoBinderPolicy:
case PrerenderFinalStatus::kRendererProcessCrashed:
case PrerenderFinalStatus::kRendererProcessKilled:
case PrerenderFinalStatus::kDownload:
case PrerenderFinalStatus::kTriggerDestroyed:
case PrerenderFinalStatus::kNavigationNotCommitted:
case PrerenderFinalStatus::kNavigationBadHttpStatus:
case PrerenderFinalStatus::kClientCertRequested:
case PrerenderFinalStatus::kNavigationRequestNetworkError:
case PrerenderFinalStatus::kCancelAllHostsForTesting:
case PrerenderFinalStatus::kDidFailLoad:
case PrerenderFinalStatus::kStop:
case PrerenderFinalStatus::kSslCertificateError:
case PrerenderFinalStatus::kLoginAuthRequested:
case PrerenderFinalStatus::kUaChangeRequiresReload:
case PrerenderFinalStatus::kBlockedByClient:
case PrerenderFinalStatus::kMixedContent:
NOTREACHED();
case PrerenderFinalStatus::kTriggerBackgrounded:
return PreloadingEligibility::kHidden;
case PrerenderFinalStatus::kMemoryLimitExceeded:
NOTREACHED();
case PrerenderFinalStatus::kDataSaverEnabled:
return PreloadingEligibility::kDataSaverEnabled;
case PrerenderFinalStatus::kTriggerUrlHasEffectiveUrl:
return PreloadingEligibility::kHasEffectiveUrl;
case PrerenderFinalStatus::kActivatedBeforeStarted:
case PrerenderFinalStatus::kInactivePageRestriction:
case PrerenderFinalStatus::kStartFailed:
case PrerenderFinalStatus::kTimeoutBackgrounded:
case PrerenderFinalStatus::kCrossSiteRedirectInInitialNavigation:
NOTREACHED();
case PrerenderFinalStatus::kCrossSiteNavigationInInitialNavigation:
return PreloadingEligibility::kCrossOrigin;
case PrerenderFinalStatus::
kSameSiteCrossOriginRedirectNotOptInInInitialNavigation:
case PrerenderFinalStatus::
kSameSiteCrossOriginNavigationNotOptInInInitialNavigation:
case PrerenderFinalStatus::kActivationNavigationParameterMismatch:
case PrerenderFinalStatus::kActivatedInBackground:
case PrerenderFinalStatus::kActivationNavigationDestroyedBeforeSuccess:
case PrerenderFinalStatus::kTabClosedByUserGesture:
case PrerenderFinalStatus::kTabClosedWithoutUserGesture:
case PrerenderFinalStatus::kPrimaryMainFrameRendererProcessCrashed:
case PrerenderFinalStatus::kPrimaryMainFrameRendererProcessKilled:
case PrerenderFinalStatus::kActivationFramePolicyNotCompatible:
NOTREACHED();
case PrerenderFinalStatus::kPreloadingDisabled:
return PreloadingEligibility::kPreloadingDisabled;
case PrerenderFinalStatus::kBatterySaverEnabled:
return PreloadingEligibility::kBatterySaverEnabled;
case PrerenderFinalStatus::kActivatedDuringMainFrameNavigation:
NOTREACHED();
case PrerenderFinalStatus::kPreloadingUnsupportedByWebContents:
return PreloadingEligibility::kPreloadingUnsupportedByWebContents;
case PrerenderFinalStatus::kCrossSiteRedirectInMainFrameNavigation:
case PrerenderFinalStatus::kCrossSiteNavigationInMainFrameNavigation:
case PrerenderFinalStatus::
kSameSiteCrossOriginRedirectNotOptInInMainFrameNavigation:
case PrerenderFinalStatus::
kSameSiteCrossOriginNavigationNotOptInInMainFrameNavigation:
NOTREACHED();
case PrerenderFinalStatus::kMemoryPressureOnTrigger:
return PreloadingEligibility::kMemoryPressure;
case PrerenderFinalStatus::kMemoryPressureAfterTriggered:
NOTREACHED();
case PrerenderFinalStatus::kPrerenderingDisabledByDevTools:
return PreloadingEligibility::kPreloadingDisabledByDevTools;
case PrerenderFinalStatus::kSpeculationRuleRemoved:
case PrerenderFinalStatus::kActivatedWithAuxiliaryBrowsingContexts:
case PrerenderFinalStatus::kMaxNumOfRunningImmediatePrerendersExceeded:
case PrerenderFinalStatus::kMaxNumOfRunningNonImmediatePrerendersExceeded:
case PrerenderFinalStatus::kMaxNumOfRunningEmbedderPrerendersExceeded:
NOTREACHED();
case PrerenderFinalStatus::kPrerenderingUrlHasEffectiveUrl:
case PrerenderFinalStatus::kRedirectedPrerenderingUrlHasEffectiveUrl:
case PrerenderFinalStatus::kActivationUrlHasEffectiveUrl:
return PreloadingEligibility::kHasEffectiveUrl;
case PrerenderFinalStatus::kJavaScriptInterfaceAdded:
case PrerenderFinalStatus::kJavaScriptInterfaceRemoved:
case PrerenderFinalStatus::kAllPrerenderingCanceled:
case PrerenderFinalStatus::kWindowClosed:
case PrerenderFinalStatus::kOtherPrerenderedPageActivated:
NOTREACHED();
case PrerenderFinalStatus::kSlowNetwork:
return PreloadingEligibility::kSlowNetwork;
case PrerenderFinalStatus::kPrerenderFailedDuringPrefetch:
case PrerenderFinalStatus::kBrowsingDataRemoved:
NOTREACHED();
case PrerenderFinalStatus::kPrerenderHostReused:
NOTREACHED();
}
NOTREACHED();
}
template <class ContainerType>
void DestructPrerenderHosts(ContainerType& hosts) {
ContainerType temp;
hosts.swap(temp);
}
class PrerenderHostBuilder {
public:
explicit PrerenderHostBuilder(PreloadingAttempt* attempt);
~PrerenderHostBuilder();
PrerenderHostBuilder(const PrerenderHostBuilder&) = delete;
PrerenderHostBuilder& operator=(const PrerenderHostBuilder&) = delete;
PrerenderHostBuilder(PrerenderHostBuilder&&) = delete;
PrerenderHostBuilder& operator=(PrerenderHostBuilder&&) = delete;
std::unique_ptr<PrerenderHost> Build(
std::unique_ptr<PrerenderHost> reuse_host,
const PrerenderAttributes& attributes,
WebContentsImpl& prerender_web_contents);
void RejectAsNotEligible(const PrerenderAttributes& attributes,
PrerenderFinalStatus status);
void RejectAsDuplicate();
void RejectAsFailure(const PrerenderAttributes& attributes,
PrerenderFinalStatus status);
void RejectDueToHoldback();
void SetHoldbackOverride(PreloadingHoldbackStatus status);
bool CheckIfShouldHoldback();
void Drop();
private:
bool IsDropped() const;
raw_ptr<PreloadingAttempt> attempt_;
std::unique_ptr<DevToolsPrerenderAttempt> devtools_attempt_;
};
PrerenderHostBuilder::PrerenderHostBuilder(PreloadingAttempt* attempt)
: attempt_(attempt),
devtools_attempt_(std::make_unique<DevToolsPrerenderAttempt>()) {}
PrerenderHostBuilder::~PrerenderHostBuilder() {
CHECK(IsDropped());
}
void PrerenderHostBuilder::Drop() {
attempt_ = nullptr;
devtools_attempt_.reset();
}
bool PrerenderHostBuilder::IsDropped() const {
return devtools_attempt_ == nullptr;
}
std::unique_ptr<PrerenderHost> PrerenderHostBuilder::Build(
std::unique_ptr<PrerenderHost> reuse_host,
const PrerenderAttributes& attributes,
WebContentsImpl& prerender_web_contents) {
CHECK(!IsDropped());
std::unique_ptr<PrerenderHost> prerender_host;
prerender_host = std::make_unique<PrerenderHost>(
std::move(reuse_host), attributes, prerender_web_contents,
attempt_ ? attempt_->GetWeakPtr() : nullptr,
std::move(devtools_attempt_));
Drop();
return prerender_host;
}
void PrerenderHostBuilder::RejectAsNotEligible(
const PrerenderAttributes& attributes,
PrerenderFinalStatus status) {
CHECK(!IsDropped());
if (attempt_) {
attempt_->SetEligibility(ToEligibility(status));
}
devtools_attempt_->SetFailureReason(attributes, status);
RecordFailedPrerenderFinalStatus(PrerenderCancellationReason(status),
attributes);
Drop();
}
bool PrerenderHostBuilder::CheckIfShouldHoldback() {
CHECK(!IsDropped());
return attempt_ && attempt_->ShouldHoldback();
}
void PrerenderHostBuilder::RejectDueToHoldback() {
CHECK(!IsDropped());
Drop();
}
void PrerenderHostBuilder::RejectAsDuplicate() {
CHECK(!IsDropped());
if (attempt_) {
attempt_->SetTriggeringOutcome(PreloadingTriggeringOutcome::kDuplicate);
}
Drop();
}
void PrerenderHostBuilder::SetHoldbackOverride(
PreloadingHoldbackStatus status) {
if (!attempt_) {
return;
}
attempt_->SetHoldbackStatus(status);
}
void PrerenderHostBuilder::RejectAsFailure(
const PrerenderAttributes& attributes,
PrerenderFinalStatus status) {
CHECK(!IsDropped());
if (attempt_) {
attempt_->SetFailureReason(ToPreloadingFailureReason(status));
}
devtools_attempt_->SetFailureReason(attributes, status);
RecordFailedPrerenderFinalStatus(PrerenderCancellationReason(status),
attributes);
Drop();
}
bool IsSlowNetwork(WebContents* web_contents) {
static const base::TimeDelta kSlowNetworkThreshold =
features::kSuppressesPrerenderingOnSlowNetworkThreshold.Get();
return web_contents && web_contents->GetBrowserContext() &&
web_contents->GetBrowserContext()
->GetClientHintsControllerDelegate() &&
web_contents->GetBrowserContext()
->GetClientHintsControllerDelegate()
->GetNetworkQualityTracker() &&
web_contents->GetBrowserContext()
->GetClientHintsControllerDelegate()
->GetNetworkQualityTracker()
->GetHttpRTT() > kSlowNetworkThreshold;
}
}
PrerenderHostRegistry::PrerenderHostRegistry(WebContents& web_contents)
: memory_pressure_listener_registration_(
FROM_HERE,
base::MemoryPressureListenerTag::kPrerenderHostRegistry,
this) {
Observe(&web_contents);
}
PrerenderHostRegistry::~PrerenderHostRegistry() {
CHECK(web_contents());
PrerenderFinalStatus final_status =
web_contents()->GetClosedByUserGesture()
? PrerenderFinalStatus::kTabClosedByUserGesture
: PrerenderFinalStatus::kTabClosedWithoutUserGesture;
CancelAllHosts(final_status);
DestructPrerenderHosts(to_be_deleted_hosts_);
DestructPrerenderHosts(pending_deletion_hosts_);
Observe(nullptr);
for (Observer& obs : observers_)
obs.OnRegistryDestroyed();
}
void PrerenderHostRegistry::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void PrerenderHostRegistry::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
FrameTreeNodeId PrerenderHostRegistry::CreateAndStartHost(
const PrerenderAttributes& attributes,
PreloadingAttempt* attempt) {
#if BUILDFLAG(ARKWEB_NETWORK_LOAD)
if (attributes.trigger_type != PreloadingTriggerType::kEmbedder) {
RecordFailedPrerenderFinalStatus(
PrerenderCancellationReason(PrerenderFinalStatus::kPreloadingDisabled),
attributes);
if (attempt) {
attempt->SetEligibility(PreloadingEligibility::kPreloadingDisabled);
}
return FrameTreeNodeId();
}
#endif
std::string recorded_url =
attributes.initiator_origin.has_value()
? attributes.initiator_origin.value().GetURL().spec()
: "(empty_url)";
TRACE_EVENT("navigation", "PrerenderHostRegistry::CreateAndStartHost",
"attributes", attributes, "initiator_origin", recorded_url);
CHECK(attributes.initiator_web_contents);
auto& initiator_web_contents =
static_cast<WebContentsImpl&>(*attributes.initiator_web_contents);
auto& prerender_web_contents = static_cast<WebContentsImpl&>(*web_contents());
FrameTreeNodeId frame_tree_node_id;
std::optional<blink::mojom::SpeculationEagerness> eagerness =
attributes.GetEagerness();
{
RenderFrameHostImpl* initiator_rfh =
attributes.IsBrowserInitiated()
? nullptr
: RenderFrameHostImpl::FromFrameToken(
attributes.initiator_process_id,
attributes.initiator_frame_token.value());
absl::Cleanup notify_trigger = [this, &attributes] {
NotifyTrigger(attributes.prerendering_url);
};
auto builder = PrerenderHostBuilder(attempt);
if (initiator_web_contents.GetDelegate() == nullptr) {
builder.Drop();
return FrameTreeNodeId();
}
if (!base::FeatureList::IsEnabled(blink::features::kPrerender2)) {
builder.RejectAsNotEligible(attributes,
PrerenderFinalStatus::kPreloadingDisabled);
return FrameTreeNodeId();
}
switch (initiator_web_contents.GetDelegate()->IsPrerender2Supported(
initiator_web_contents, attributes.trigger_type)) {
case PreloadingEligibility::kEligible:
break;
case PreloadingEligibility::kPreloadingDisabled:
builder.RejectAsNotEligible(attributes,
PrerenderFinalStatus::kPreloadingDisabled);
return FrameTreeNodeId();
case PreloadingEligibility::kDataSaverEnabled:
builder.RejectAsNotEligible(attributes,
PrerenderFinalStatus::kDataSaverEnabled);
return FrameTreeNodeId();
case PreloadingEligibility::kBatterySaverEnabled:
builder.RejectAsNotEligible(attributes,
PrerenderFinalStatus::kBatterySaverEnabled);
return FrameTreeNodeId();
case PreloadingEligibility::kPreloadingUnsupportedByWebContents:
builder.RejectAsNotEligible(
attributes,
PrerenderFinalStatus::kPreloadingUnsupportedByWebContents);
return FrameTreeNodeId();
default:
NOTREACHED();
}
if (attributes.trigger_type == PreloadingTriggerType::kEmbedder &&
initiator_web_contents.GetVisibility() == Visibility::HIDDEN) {
builder.RejectAsNotEligible(attributes,
PrerenderFinalStatus::kTriggerBackgrounded);
return FrameTreeNodeId();
}
if (!DeviceHasEnoughMemoryForPrerender()) {
builder.RejectAsNotEligible(attributes,
PrerenderFinalStatus::kLowEndDevice);
return FrameTreeNodeId();
}
switch (GetCurrentMemoryPressureLevel()) {
case base::MEMORY_PRESSURE_LEVEL_NONE:
case base::MEMORY_PRESSURE_LEVEL_MODERATE:
break;
case base::MEMORY_PRESSURE_LEVEL_CRITICAL:
builder.RejectAsNotEligible(
attributes, PrerenderFinalStatus::kMemoryPressureOnTrigger);
return FrameTreeNodeId();
}
static const bool kSuppressesPrerenderingOnSlowNetworkIsEnabled =
base::FeatureList::IsEnabled(
features::kSuppressesPrerenderingOnSlowNetwork);
if (kSuppressesPrerenderingOnSlowNetworkIsEnabled &&
IsSlowNetwork(web_contents())) {
builder.RejectAsNotEligible(attributes,
PrerenderFinalStatus::kSlowNetwork);
return FrameTreeNodeId();
}
if (!attributes.IsBrowserInitiated() &&
!prerender_navigation_utils::IsSameSite(
attributes.prerendering_url, attributes.initiator_origin.value())) {
builder.RejectAsNotEligible(
attributes,
PrerenderFinalStatus::kCrossSiteNavigationInInitialNavigation);
return FrameTreeNodeId();
}
if (!attributes.prerendering_url.SchemeIsHTTPOrHTTPS()) {
builder.RejectAsNotEligible(
attributes, PrerenderFinalStatus::kInvalidSchemeNavigation);
return FrameTreeNodeId();
}
auto* browser_context = prerender_web_contents.GetBrowserContext();
if (SiteInstanceImpl::HasEffectiveURL(browser_context,
initiator_web_contents.GetURL())) {
builder.RejectAsNotEligible(
attributes, PrerenderFinalStatus::kTriggerUrlHasEffectiveUrl);
return FrameTreeNodeId();
}
if (SiteInstanceImpl::HasEffectiveURL(browser_context,
attributes.prerendering_url)) {
builder.RejectAsNotEligible(
attributes, PrerenderFinalStatus::kPrerenderingUrlHasEffectiveUrl);
return FrameTreeNodeId();
}
if (initiator_rfh && initiator_rfh->frame_tree() &&
!devtools_instrumentation::IsPrerenderAllowed(
*initiator_rfh->frame_tree())) {
builder.RejectAsNotEligible(
attributes, PrerenderFinalStatus::kPrerenderingDisabledByDevTools);
return FrameTreeNodeId();
}
if (attempt)
attempt->SetEligibility(PreloadingEligibility::kEligible);
bool has_devtools_open =
initiator_rfh &&
RenderFrameDevToolsAgentHost::GetFor(initiator_rfh) != nullptr;
if (has_devtools_open) {
builder.SetHoldbackOverride(PreloadingHoldbackStatus::kAllowed);
} else if (attributes.holdback_status_override !=
PreloadingHoldbackStatus::kUnspecified) {
builder.SetHoldbackOverride(attributes.holdback_status_override);
}
if (builder.CheckIfShouldHoldback()) {
builder.RejectDueToHoldback();
return FrameTreeNodeId();
}
for (auto& iter : prerender_host_by_frame_tree_node_id_) {
if (iter.second->GetInitialUrl() == attributes.prerendering_url) {
builder.RejectAsDuplicate();
return FrameTreeNodeId();
}
}
std::unique_ptr<PrerenderHost> reuse_host;
if (base::FeatureList::IsEnabled(features::kPrerender2ReuseHost)) {
reuse_host = FindAndTakePrerenderHostToReuse(attributes);
}
if (!initiator_web_contents.GetPrerenderHostRegistry()
->IsAllowedToStartPrerenderingForTrigger(attributes.trigger_type,
eagerness)) {
PrerenderFinalStatus final_status;
switch (GetPrerenderLimitGroup(attributes.trigger_type, eagerness)) {
case PrerenderLimitGroup::kSpeculationRulesImmediate:
final_status =
PrerenderFinalStatus::kMaxNumOfRunningImmediatePrerendersExceeded;
break;
case PrerenderLimitGroup::kSpeculationRulesNonImmediate:
final_status = PrerenderFinalStatus::
kMaxNumOfRunningNonImmediatePrerendersExceeded;
break;
case PrerenderLimitGroup::kEmbedder:
final_status =
PrerenderFinalStatus::kMaxNumOfRunningEmbedderPrerendersExceeded;
break;
}
builder.RejectAsFailure(attributes, final_status);
if (reuse_host) {
prerender_host_by_frame_tree_node_id_[reuse_host
->frame_tree_node_id()] =
std::move(reuse_host);
}
return FrameTreeNodeId();
}
if (reuse_host) {
reuse_host->NotifyReused();
} else {
base::UmaHistogramCounts100(
"Prerender.Experimental.ReusePrerenderHost.PrerenderHostCount.Failed",
prerender_host_by_frame_tree_node_id_.size());
}
base::UmaHistogramBoolean("Prerender.Experimental.FoundReusePrerenderHost",
reuse_host != nullptr);
std::unique_ptr<PrerenderHost> prerender_host = builder.Build(
std::move(reuse_host), attributes, prerender_web_contents);
frame_tree_node_id = prerender_host->frame_tree_node_id();
CHECK(!base::Contains(prerender_host_by_frame_tree_node_id_,
frame_tree_node_id));
prerender_host_by_frame_tree_node_id_[frame_tree_node_id] =
std::move(prerender_host);
if (GetPrerenderLimitGroup(attributes.trigger_type, eagerness) ==
PrerenderLimitGroup::kSpeculationRulesNonImmediate) {
non_immediate_prerender_host_id_by_arrival_order_.push_back(
frame_tree_node_id);
}
}
switch (attributes.trigger_type) {
case PreloadingTriggerType::kSpeculationRule:
case PreloadingTriggerType::kSpeculationRuleFromIsolatedWorld:
case PreloadingTriggerType::kSpeculationRuleFromAutoSpeculationRules:
pending_prerenders_.push_back(frame_tree_node_id);
if (running_prerender_host_id_.is_null()) {
if (IsBackground(initiator_web_contents.GetVisibility()) &&
!initiator_web_contents.GetPrerenderHostRegistry()
->PrerenderCanBeStartedWhenInitiatorIsInBackground()) {
if (web_contents() != &initiator_web_contents) {
return FrameTreeNodeId();
}
break;
}
FrameTreeNodeId started_frame_tree_node_id =
StartPrerendering(FrameTreeNodeId());
CHECK(started_frame_tree_node_id == frame_tree_node_id ||
started_frame_tree_node_id.is_null());
frame_tree_node_id = started_frame_tree_node_id;
}
break;
case PreloadingTriggerType::kEmbedder:
frame_tree_node_id = StartPrerendering(frame_tree_node_id);
break;
}
return frame_tree_node_id;
}
FrameTreeNodeId PrerenderHostRegistry::CreateAndStartHostForNewTab(
const PrerenderAttributes& attributes,
const PreloadingPredictor& creating_predictor,
const PreloadingPredictor& enacting_predictor,
PreloadingConfidence confidence) {
CHECK(IsSpeculationRuleType(attributes.trigger_type));
std::string recorded_url =
attributes.initiator_origin.has_value()
? attributes.initiator_origin.value().GetURL().spec()
: "(empty_url)";
TRACE_EVENT("navigation",
"PrerenderHostRegistry::CreateAndStartHostForNewTab",
"attributes", attributes, "initiator_origin", recorded_url);
auto handle = std::make_unique<PrerenderNewTabHandle>(
attributes, *web_contents()->GetBrowserContext());
FrameTreeNodeId prerender_host_id = handle->StartPrerendering(
creating_predictor, enacting_predictor, confidence);
if (prerender_host_id.is_null()) {
return FrameTreeNodeId();
}
prerender_new_tab_handle_by_frame_tree_node_id_[prerender_host_id] =
std::move(handle);
if (GetPrerenderLimitGroup(attributes.trigger_type,
attributes.GetEagerness()) ==
PrerenderLimitGroup::kSpeculationRulesNonImmediate) {
non_immediate_prerender_host_id_by_arrival_order_.push_back(
prerender_host_id);
}
return prerender_host_id;
}
FrameTreeNodeId PrerenderHostRegistry::StartPrerendering(
FrameTreeNodeId frame_tree_node_id) {
if (frame_tree_node_id.is_null()) {
CHECK(running_prerender_host_id_.is_null());
while (!pending_prerenders_.empty()) {
FrameTreeNodeId host_id = pending_prerenders_.front();
auto found = prerender_host_by_frame_tree_node_id_.find(host_id);
if (found == prerender_host_by_frame_tree_node_id_.end()) {
pending_prerenders_.pop_front();
continue;
}
PrerenderHost* prerender_host = found->second.get();
CHECK(prerender_host->initiator_web_contents());
WebContentsImpl* initiator_web_contents = static_cast<WebContentsImpl*>(
prerender_host->initiator_web_contents().get());
if (IsBackground(initiator_web_contents->GetVisibility())) {
if (!initiator_web_contents->GetPrerenderHostRegistry()
->PrerenderCanBeStartedWhenInitiatorIsInBackground()) {
return FrameTreeNodeId();
}
}
pending_prerenders_.pop_front();
frame_tree_node_id = host_id;
break;
}
if (frame_tree_node_id.is_null()) {
return FrameTreeNodeId();
}
}
auto prerender_host_it =
prerender_host_by_frame_tree_node_id_.find(frame_tree_node_id);
CHECK(prerender_host_it != prerender_host_by_frame_tree_node_id_.end());
PrerenderHost& prerender_host = *prerender_host_it->second;
devtools_instrumentation::WillInitiatePrerender(
prerender_host.GetPrerenderFrameTree());
if (!prerender_host.StartPrerendering()) {
CancelHost(frame_tree_node_id, PrerenderFinalStatus::kStartFailed);
return FrameTreeNodeId();
}
switch (prerender_host_by_frame_tree_node_id_[frame_tree_node_id]
->trigger_type()) {
case PreloadingTriggerType::kSpeculationRule:
case PreloadingTriggerType::kSpeculationRuleFromIsolatedWorld:
case PreloadingTriggerType::kSpeculationRuleFromAutoSpeculationRules:
running_prerender_host_id_ = frame_tree_node_id;
break;
case PreloadingTriggerType::kEmbedder:
break;
}
RecordPrerenderTriggered(
prerender_host_by_frame_tree_node_id_[frame_tree_node_id]
->initiator_ukm_id());
return frame_tree_node_id;
}
std::set<FrameTreeNodeId> PrerenderHostRegistry::CancelHosts(
const std::vector<FrameTreeNodeId>& frame_tree_node_ids,
const PrerenderCancellationReason& reason) {
TRACE_EVENT("navigation", "PrerenderHostRegistry::CancelHosts",
"frame_tree_node_ids", frame_tree_node_ids);
CHECK(!reserved_prerender_host_);
#if BUILDFLAG(ARKWEB_NETWORK_LOAD)
LOG(DEBUG) << "Cancel Prerendered host, reason = "
<< static_cast<int>(reason.final_status());
#endif
std::set<FrameTreeNodeId> cancelled_ids;
for (FrameTreeNodeId host_id : frame_tree_node_ids) {
if (CancelHostInternal(host_id, reason) ||
CancelNewTabHostInternal(host_id, reason)) {
cancelled_ids.insert(host_id);
}
}
if (running_prerender_host_id_.is_null()) {
StartPrerendering(FrameTreeNodeId());
}
return cancelled_ids;
}
bool PrerenderHostRegistry::CancelHost(FrameTreeNodeId frame_tree_node_id,
PrerenderFinalStatus final_status) {
return CancelHost(frame_tree_node_id,
PrerenderCancellationReason(final_status));
}
bool PrerenderHostRegistry::CancelHost(
FrameTreeNodeId frame_tree_node_id,
const PrerenderCancellationReason& reason) {
TRACE_EVENT("navigation", "PrerenderHostRegistry::CancelHost",
"frame_tree_node_id", frame_tree_node_id);
std::set<FrameTreeNodeId> cancelled_ids =
CancelHosts({frame_tree_node_id}, reason);
return !cancelled_ids.empty();
}
void PrerenderHostRegistry::CancelHostsForTriggers(
std::vector<PreloadingTriggerType> trigger_types,
const PrerenderCancellationReason& reason) {
TRACE_EVENT("navigation", "PrerenderHostRegistry::CancelHostsForTrigger",
"trigger_type", trigger_types[0]);
std::vector<FrameTreeNodeId> ids_to_be_deleted;
for (auto& iter : prerender_host_by_frame_tree_node_id_) {
if (base::Contains(trigger_types, iter.second->trigger_type())) {
ids_to_be_deleted.push_back(iter.first);
}
}
for (auto& iter : prerender_new_tab_handle_by_frame_tree_node_id_) {
if (base::Contains(trigger_types, iter.second->trigger_type())) {
CHECK(IsSpeculationRuleType(iter.second->trigger_type()));
ids_to_be_deleted.push_back(iter.first);
}
}
CancelHosts(ids_to_be_deleted, reason);
}
void PrerenderHostRegistry::CancelAllHosts(PrerenderFinalStatus final_status) {
CHECK(!reserved_prerender_host_);
PrerenderCancellationReason reason(final_status);
while (!prerender_host_by_frame_tree_node_id_.empty()) {
CancelHostInternal(prerender_host_by_frame_tree_node_id_.begin()->first,
reason);
}
while (!prerender_new_tab_handle_by_frame_tree_node_id_.empty()) {
CancelNewTabHostInternal(
prerender_new_tab_handle_by_frame_tree_node_id_.begin()->first, reason);
}
pending_prerenders_.clear();
}
bool PrerenderHostRegistry::CancelHostInternal(
FrameTreeNodeId frame_tree_node_id,
const PrerenderCancellationReason& reason) {
auto iter = prerender_host_by_frame_tree_node_id_.find(frame_tree_node_id);
if (iter == prerender_host_by_frame_tree_node_id_.end()) {
return false;
}
if (running_prerender_host_id_ == frame_tree_node_id) {
running_prerender_host_id_ = FrameTreeNodeId();
}
std::unique_ptr<PrerenderHost> prerender_host = std::move(iter->second);
prerender_host_by_frame_tree_node_id_.erase(iter);
prerender_host->OnWillBeCancelled(reason);
reason.ReportMetrics(prerender_host->GetHistogramSuffix());
NotifyCancel(prerender_host->frame_tree_node_id(), reason);
WebContentsImpl* initiator_web_contents = static_cast<WebContentsImpl*>(
prerender_host->initiator_web_contents().get());
if (initiator_web_contents && web_contents() != initiator_web_contents &&
!initiator_web_contents->IsBeingDestroyed()) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
base::IgnoreResult(
&PrerenderHostRegistry::CancelNewTabHostInternal),
initiator_web_contents->GetPrerenderHostRegistry()->GetWeakPtr(),
frame_tree_node_id,
PrerenderCancellationReason(reason.final_status())));
}
ScheduleToDeleteAbandonedHost(std::move(prerender_host), reason);
return true;
}
bool PrerenderHostRegistry::CancelNewTabHostInternal(
FrameTreeNodeId frame_tree_node_id,
const PrerenderCancellationReason& reason) {
auto iter =
prerender_new_tab_handle_by_frame_tree_node_id_.find(frame_tree_node_id);
if (iter == prerender_new_tab_handle_by_frame_tree_node_id_.end()) {
return false;
}
CHECK_NE(running_prerender_host_id_, frame_tree_node_id);
std::unique_ptr<PrerenderNewTabHandle> handle = std::move(iter->second);
prerender_new_tab_handle_by_frame_tree_node_id_.erase(iter);
NotifyCancel(frame_tree_node_id, reason);
if (reason.final_status() == PrerenderFinalStatus::kSpeculationRuleRemoved) {
auto& new_tab_registry = handle->GetPrerenderHostRegistry();
new_tab_registry.SchedulePendingDeletionPrerenderNewTabHandle(
std::move(handle));
new_tab_registry.CancelHost(frame_tree_node_id, reason);
} else {
handle->CancelPrerendering(reason);
}
return true;
}
FrameTreeNodeId PrerenderHostRegistry::FindPotentialHostToActivate(
NavigationRequest& navigation_request) {
TRACE_EVENT(
"navigation", "PrerenderHostRegistry::FindPotentialHostToActivate",
"navigation_url", navigation_request.GetURL().spec(), "render_frame_host",
navigation_request.frame_tree_node()->current_frame_host());
if (!navigation_request.IsInPrimaryMainFrame()) {
return FrameTreeNodeId();
}
std::vector<PrerenderHost*> matchable_hosts;
for (const auto& [host_id, host] : prerender_host_by_frame_tree_node_id_) {
if (host->IsUrlMatch(navigation_request.GetURL())) {
matchable_hosts.push_back(host.get());
}
}
for (const auto& [host_id, host] : prerender_host_by_frame_tree_node_id_) {
if (host->IsNoVarySearchHintUrlMatch(navigation_request.GetURL())) {
matchable_hosts.push_back(host.get());
}
}
RecordPotentialPrerenderProcessReuse(!matchable_hosts.empty(),
navigation_request.GetURL());
if (matchable_hosts.empty()) {
return FrameTreeNodeId();
}
PrerenderHost* host = *matchable_hosts.begin();
#if BUILDFLAG(ARKWEB_NETWORK_LOAD)
LOG(DEBUG) << "Found a Prerender host.";
#endif
base::UmaHistogramCounts100(
"Prerender.Experimental.MatchableHostCountOnActivation",
matchable_hosts.size());
if (!host->GetInitialNavigationId().has_value()) {
CancelHost(host->frame_tree_node_id(),
PrerenderFinalStatus::kActivatedBeforeStarted);
return FrameTreeNodeId();
}
return CanNavigationActivateHost(navigation_request, *host)
? host->frame_tree_node_id()
: FrameTreeNodeId();
}
std::optional<ReservedPrerenderHostInfo>
PrerenderHostRegistry::ReserveHostToActivate(
NavigationRequest& navigation_request,
FrameTreeNodeId expected_host_id) {
RenderFrameHostImpl* render_frame_host =
navigation_request.frame_tree_node()->current_frame_host();
TRACE_EVENT("navigation", "PrerenderHostRegistry::ReserveHostToActivate",
"navigation_url", navigation_request.GetURL().spec(),
"render_frame_host", render_frame_host);
CHECK(navigation_request.IsInPrimaryMainFrame());
auto it = prerender_host_by_frame_tree_node_id_.find(expected_host_id);
if (it == prerender_host_by_frame_tree_node_id_.end()) {
return std::nullopt;
}
PrerenderHost& host_ref = *it->second;
std::optional<UrlMatchType> match_type =
host_ref.IsUrlMatch(navigation_request.GetURL());
if (!match_type.has_value()) {
return std::nullopt;
}
if (!CanNavigationActivateHost(navigation_request, host_ref)) {
return std::nullopt;
}
FrameTreeNodeId host_id = host_ref.frame_tree_node_id();
auto& prerender_frame_tree = host_ref.GetPrerenderFrameTree();
if (prerender_frame_tree.root()->HasNavigation()) {
CancelHost(host_id,
PrerenderFinalStatus::kActivatedDuringMainFrameNavigation);
return std::nullopt;
}
std::unique_ptr<PrerenderHost> host =
std::move(prerender_host_by_frame_tree_node_id_[host_id]);
prerender_host_by_frame_tree_node_id_.erase(host_id);
CHECK_EQ(host_id, host->frame_tree_node_id());
CHECK(host->IsUrlMatch(navigation_request.GetURL()));
if (match_type.value() == UrlMatchType::kNoVarySearch) {
GetContentClient()->browser()->LogWebFeatureForCurrentPage(
web_contents()->GetPrimaryMainFrame(),
blink::mojom::WebFeature::kNoVarySearchPrerender);
}
CHECK(!reserved_prerender_host_);
reserved_prerender_host_ = std::move(host);
return ReservedPrerenderHostInfo(
host_id, reserved_prerender_host_->trigger_type(),
reserved_prerender_host_->embedder_histogram_suffix(),
reserved_prerender_host_->host_reused());
}
RenderFrameHostImpl* PrerenderHostRegistry::GetRenderFrameHostForReservedHost(
FrameTreeNodeId frame_tree_node_id) {
if (!reserved_prerender_host_)
return nullptr;
CHECK_EQ(frame_tree_node_id, reserved_prerender_host_->frame_tree_node_id());
return reserved_prerender_host_->GetPrerenderedMainFrameHost();
}
std::unique_ptr<StoredPage> PrerenderHostRegistry::ActivateReservedHost(
FrameTreeNodeId frame_tree_node_id,
NavigationRequest& navigation_request) {
CHECK(reserved_prerender_host_);
CHECK_EQ(frame_tree_node_id, reserved_prerender_host_->frame_tree_node_id());
std::unique_ptr<PrerenderHost> prerender_host =
std::move(reserved_prerender_host_);
return prerender_host->Activate(navigation_request);
}
void PrerenderHostRegistry::OnActivationFinished(
FrameTreeNodeId frame_tree_node_id) {
CHECK(!base::Contains(prerender_host_by_frame_tree_node_id_,
frame_tree_node_id));
if (!reserved_prerender_host_) {
return;
}
CHECK_EQ(frame_tree_node_id, reserved_prerender_host_->frame_tree_node_id());
ScheduleToDeleteAbandonedHost(
std::move(reserved_prerender_host_),
PrerenderCancellationReason(
PrerenderFinalStatus::kActivationNavigationDestroyedBeforeSuccess));
}
PrerenderHost* PrerenderHostRegistry::FindNonReservedHostById(
FrameTreeNodeId frame_tree_node_id) {
auto id_iter = prerender_host_by_frame_tree_node_id_.find(frame_tree_node_id);
if (id_iter == prerender_host_by_frame_tree_node_id_.end())
return nullptr;
return id_iter->second.get();
}
bool PrerenderHostRegistry::HasReservedHost() const {
return !!reserved_prerender_host_;
}
std::unique_ptr<WebContentsImpl>
PrerenderHostRegistry::TakePreCreatedWebContentsForNewTabIfExists(
const mojom::CreateNewWindowParams& create_new_window_params,
const WebContents::CreateParams& web_contents_create_params) {
if (!create_new_window_params.opener_suppressed ||
create_new_window_params.is_form_submission ||
create_new_window_params.pip_options) {
return nullptr;
}
for (auto& iter : prerender_new_tab_handle_by_frame_tree_node_id_) {
std::unique_ptr<WebContentsImpl> web_contents =
iter.second->TakeWebContentsIfAvailable(create_new_window_params,
web_contents_create_params);
if (web_contents) {
prerender_new_tab_handle_by_frame_tree_node_id_.erase(iter);
return web_contents;
}
}
return nullptr;
}
std::vector<FrameTree*> PrerenderHostRegistry::GetPrerenderFrameTrees() {
std::vector<FrameTree*> result;
for (auto& i : prerender_host_by_frame_tree_node_id_) {
result.push_back(&i.second->GetPrerenderFrameTree());
}
if (reserved_prerender_host_)
result.push_back(&reserved_prerender_host_->GetPrerenderFrameTree());
return result;
}
PrerenderHost* PrerenderHostRegistry::FindHostByUrlForTesting(
const GURL& prerendering_url) {
for (auto& iter : prerender_host_by_frame_tree_node_id_) {
if (iter.second->IsUrlMatch(prerendering_url)) {
return iter.second.get();
}
}
return nullptr;
}
PrerenderHost* PrerenderHostRegistry::FindPrewarmSearchResultHostForTesting(
const GURL& search_prewarm_url) {
for (auto& iter : prerender_host_by_frame_tree_node_id_) {
if (iter.second->GetInitialUrl() == search_prewarm_url) {
return iter.second.get();
}
}
return nullptr;
}
bool PrerenderHostRegistry::HasNewTabHandleByIdForTesting(
FrameTreeNodeId frame_tree_node_id) {
return prerender_new_tab_handle_by_frame_tree_node_id_.contains(
frame_tree_node_id);
}
void PrerenderHostRegistry::CancelAllHostsForTesting() {
CHECK(!reserved_prerender_host_)
<< "It is not possible to cancel a reserved host, so they must not exist "
"when trying to cancel all hosts";
for (auto& iter : prerender_host_by_frame_tree_node_id_) {
ScheduleToDeleteAbandonedHost(
std::move(iter.second),
PrerenderCancellationReason(
PrerenderFinalStatus::kCancelAllHostsForTesting));
}
prerender_host_by_frame_tree_node_id_.clear();
pending_prerenders_.clear();
}
void PrerenderHostRegistry::BackNavigationLikely(
PreloadingPredictor predictor) {
if (http_cache_query_loader_) {
return;
}
PreloadingDataImpl* preloading_data =
PreloadingDataImpl::GetOrCreateForWebContents(web_contents());
preloading_data->SetIsNavigationInDomainCallback(
predictor,
base::BindRepeating(IsNavigationInSessionHistoryPredictorDomain));
WebContentsImpl* contents = static_cast<WebContentsImpl*>(web_contents());
NavigationControllerImpl& controller = contents->GetController();
const std::optional<int> target_index = controller.GetIndexForGoBack();
if (!target_index.has_value()) {
RecordPrerenderBackNavigationEligibility(
predictor, PrerenderBackNavigationEligibility::kNoBackEntry, nullptr);
return;
}
NavigationEntryImpl* back_entry = controller.GetEntryAtIndex(*target_index);
CHECK(back_entry);
const GURL& back_url = back_entry->GetURL();
if (controller.GetBackForwardCache()
.GetOrEvictEntry(back_entry->GetUniqueID())
.has_value()) {
RecordPrerenderBackNavigationEligibility(
predictor, PrerenderBackNavigationEligibility::kBfcacheEntryExists,
nullptr);
return;
}
PreloadingURLMatchCallback same_url_matcher =
PreloadingData::GetSameURLMatcher(back_url);
ukm::SourceId triggered_primary_page_source_id =
web_contents()->GetPrimaryMainFrame()->GetPageUkmSourceId();
preloading_data->AddPreloadingPrediction(predictor, PreloadingConfidence{100},
same_url_matcher,
triggered_primary_page_source_id);
PreloadingAttempt* attempt = preloading_data->AddPreloadingAttempt(
predictor, PreloadingType::kPrerender, same_url_matcher,
triggered_primary_page_source_id);
if (back_entry->GetMainFrameDocumentSequenceNumber() ==
controller.GetLastCommittedEntry()
->GetMainFrameDocumentSequenceNumber()) {
RecordPrerenderBackNavigationEligibility(
predictor, PrerenderBackNavigationEligibility::kTargetIsSameDocument,
attempt);
return;
}
if (back_entry->root_node()->frame_entry->method() != "GET") {
RecordPrerenderBackNavigationEligibility(
predictor, PrerenderBackNavigationEligibility::kMethodNotGet, attempt);
return;
}
if (prerender_navigation_utils::IsDisallowedHttpResponseCode(
back_entry->GetHttpStatusCode())) {
RecordPrerenderBackNavigationEligibility(
predictor,
PrerenderBackNavigationEligibility::kTargetIsFailedNavigation, attempt);
return;
}
if (!back_url.SchemeIsHTTPOrHTTPS()) {
RecordPrerenderBackNavigationEligibility(
predictor, PrerenderBackNavigationEligibility::kTargetIsNonHttp,
attempt);
return;
}
if (prerender_navigation_utils::IsSameSite(
back_url,
contents->GetPrimaryMainFrame()->GetLastCommittedOrigin())) {
RecordPrerenderBackNavigationEligibility(
predictor, PrerenderBackNavigationEligibility::kTargetIsSameSite,
attempt);
return;
}
SiteInstanceImpl* entry_site_instance = back_entry->site_instance();
if (entry_site_instance) {
const bool current_and_target_related =
contents->GetSiteInstance()->IsRelatedSiteInstance(entry_site_instance);
const size_t allowable_related_count = current_and_target_related ? 1u : 0u;
if (entry_site_instance->GetRelatedActiveContentsCount() >
allowable_related_count) {
RecordPrerenderBackNavigationEligibility(
predictor, PrerenderBackNavigationEligibility::kRelatedActiveContents,
attempt);
return;
}
}
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory =
contents->GetPrimaryMainFrame()
->GetStoragePartition()
->GetURLLoaderFactoryForBrowserProcess();
http_cache_query_loader_ = CreateHttpCacheQueryingResourceLoad(back_url);
http_cache_query_loader_->DownloadHeadersOnly(
url_loader_factory.get(),
base::BindOnce(&PrerenderHostRegistry::OnBackResourceCacheResult,
base::Unretained(this), predictor, attempt->GetWeakPtr(),
back_url));
}
void PrerenderHostRegistry::OnBackResourceCacheResult(
PreloadingPredictor predictor,
base::WeakPtr<PreloadingAttempt> attempt,
GURL back_url,
scoped_refptr<net::HttpResponseHeaders> headers) {
std::unique_ptr<network::SimpleURLLoader> http_cache_query_loader =
std::move(http_cache_query_loader_);
if (!http_cache_query_loader->LoadedFromCache()) {
CHECK_NE(http_cache_query_loader->NetError(), net::OK);
RecordPrerenderBackNavigationEligibility(
predictor, PrerenderBackNavigationEligibility::kNoHttpCacheEntry,
attempt.get());
return;
}
RecordPrerenderBackNavigationEligibility(
predictor, PrerenderBackNavigationEligibility::kEligible, attempt.get());
if (attempt) {
attempt->SetHoldbackStatus(PreloadingHoldbackStatus::kAllowed);
attempt->SetTriggeringOutcome(PreloadingTriggeringOutcome::kNoOp);
}
}
base::WeakPtr<PrerenderHostRegistry> PrerenderHostRegistry::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
PrerenderHostId PrerenderHostRegistry::GetPrerenderHostIdForNavigation(
NavigationRequest* navigation_request) {
PrerenderHost* prerender_host = nullptr;
if (navigation_request->IsInPrerenderedMainFrame()) {
prerender_host = &PrerenderHost::GetFromFrameTreeNode(
*navigation_request->frame_tree_node());
} else {
FrameTreeNodeId main_frame_host_id = navigation_request->frame_tree_node()
->frame_tree()
.root()
->frame_tree_node_id();
prerender_host = FindNonReservedHostById(main_frame_host_id);
}
if (prerender_host) {
return prerender_host->prerender_host_id();
}
return PrerenderHostId();
}
void PrerenderHostRegistry::DidStartNavigation(
NavigationHandle* navigation_handle) {
auto* navigation_request = NavigationRequest::From(navigation_handle);
if (!navigation_request->IsInPrerenderedMainFrame() ||
navigation_request->IsSameDocument()) {
return;
}
auto& prerender_host = PrerenderHost::GetFromFrameTreeNode(
*navigation_request->frame_tree_node());
prerender_host.DidStartNavigation(navigation_handle);
}
void PrerenderHostRegistry::ReadyToCommitNavigation(
NavigationHandle* navigation_handle) {
auto* navigation_request = NavigationRequest::From(navigation_handle);
if (!navigation_request->IsInPrerenderedMainFrame() ||
navigation_request->IsSameDocument()) {
return;
}
auto& prerender_host = PrerenderHost::GetFromFrameTreeNode(
*navigation_request->frame_tree_node());
prerender_host.ReadyToCommitNavigation(navigation_handle);
}
void PrerenderHostRegistry::DidFinishNavigation(
NavigationHandle* navigation_handle) {
auto* navigation_request = NavigationRequest::From(navigation_handle);
if (navigation_request->IsSameDocument())
return;
FrameTreeNodeId main_frame_host_id = navigation_request->frame_tree_node()
->frame_tree()
.root()
->frame_tree_node_id();
PrerenderHost* prerender_host = FindNonReservedHostById(main_frame_host_id);
if (!prerender_host)
return;
prerender_host->DidFinishNavigation(navigation_handle);
if (running_prerender_host_id_ == main_frame_host_id) {
running_prerender_host_id_ = FrameTreeNodeId();
StartPrerendering(FrameTreeNodeId());
}
}
void PrerenderHostRegistry::OnVisibilityChanged(Visibility visibility) {
if (IsBackground(visibility)) {
if (timeout_timer_for_embedder_.IsRunning() ||
timeout_timer_for_speculation_rules_.IsRunning()) {
return;
}
timeout_timer_for_embedder_.SetTaskRunner(GetTimerTaskRunner());
timeout_timer_for_speculation_rules_.SetTaskRunner(GetTimerTaskRunner());
timeout_timer_for_embedder_.Start(
FROM_HERE, kTimeToLiveInBackgroundForEmbedder,
base::BindOnce(&PrerenderHostRegistry::CancelHostsForTriggers,
base::Unretained(this),
std::vector({PreloadingTriggerType::kEmbedder}),
PrerenderCancellationReason(
PrerenderFinalStatus::kTimeoutBackgrounded)));
timeout_timer_for_speculation_rules_.Start(
FROM_HERE, kTimeToLiveInBackgroundForSpeculationRules,
base::BindOnce(
&PrerenderHostRegistry::CancelHostsForTriggers,
base::Unretained(this),
std::vector(
{PreloadingTriggerType::kSpeculationRule,
PreloadingTriggerType::kSpeculationRuleFromIsolatedWorld}),
PrerenderCancellationReason(
PrerenderFinalStatus::kTimeoutBackgrounded)));
} else {
timeout_timer_for_embedder_.Stop();
timeout_timer_for_speculation_rules_.Stop();
if (running_prerender_host_id_.is_null()) {
StartPrerendering(FrameTreeNodeId());
}
}
}
void PrerenderHostRegistry::PrimaryMainFrameRenderProcessGone(
base::TerminationStatus status) {
CancelAllHosts(
status == base::TERMINATION_STATUS_PROCESS_CRASHED
? PrerenderFinalStatus::kPrimaryMainFrameRendererProcessCrashed
: PrerenderFinalStatus::kPrimaryMainFrameRendererProcessKilled);
}
bool PrerenderHostRegistry::CanNavigationActivateHost(
NavigationRequest& navigation_request,
PrerenderHost& host) {
RenderFrameHostImpl* render_frame_host =
navigation_request.frame_tree_node()->current_frame_host();
TRACE_EVENT("navigation", "PrerenderHostRegistry::CanNavigationActivateHost",
"navigation_url", navigation_request.GetURL().spec(),
"render_frame_host", render_frame_host);
if (SiteInstanceImpl::HasEffectiveURL(web_contents()->GetBrowserContext(),
navigation_request.GetURL())) {
CancelHost(host.frame_tree_node_id(),
PrerenderFinalStatus::kActivationUrlHasEffectiveUrl);
return false;
}
SiteInstance* site_instance = render_frame_host->GetSiteInstance();
if (site_instance->GetRelatedActiveContentsCount() != 1u) {
CancelHost(host.frame_tree_node_id(),
PrerenderFinalStatus::kActivatedWithAuxiliaryBrowsingContexts);
return false;
}
CHECK(host.initiator_web_contents());
if (web_contents()->GetVisibility() == Visibility::HIDDEN &&
host.initiator_web_contents()->GetVisibility() == Visibility::HIDDEN) {
CancelHost(host.frame_tree_node_id(),
PrerenderFinalStatus::kActivatedInBackground);
return false;
}
{
PrerenderCancellationReason reason = PrerenderCancellationReason::
CreateCandidateReasonForActivationParameterMismatch();
if (!host.AreInitialPrerenderNavigationParamsCompatibleWithNavigation(
navigation_request, reason)) {
CancelHost(host.frame_tree_node_id(), reason);
return false;
}
}
if (!host.IsFramePolicyCompatibleWithPrimaryFrameTree()) {
CancelHost(host.frame_tree_node_id(),
PrerenderFinalStatus::kActivationFramePolicyNotCompatible);
return false;
}
std::vector<FrameTreeNodeId> cancelled_prerenders;
for (const auto& [host_id, _] : prerender_host_by_frame_tree_node_id_) {
if (host_id != host.frame_tree_node_id()) {
cancelled_prerenders.push_back(host_id);
}
}
for (const auto& [host_id, _] :
prerender_new_tab_handle_by_frame_tree_node_id_) {
cancelled_prerenders.push_back(host_id);
}
CancelHosts(cancelled_prerenders,
PrerenderCancellationReason(
PrerenderFinalStatus::kOtherPrerenderedPageActivated));
pending_prerenders_.clear();
return true;
}
void PrerenderHostRegistry::DeletePendingDeletionHosts(
FrameTreeNodeId prerender_host_id) {
std::unique_ptr<PrerenderHost> prerender_host =
std::move(pending_deletion_hosts_[prerender_host_id]);
pending_deletion_hosts_.erase(prerender_host_id);
DestructPrerenderHosts(prerender_host);
if (pending_deletion_new_tab_prerender_handle_) {
base::SingleThreadTaskRunner::GetCurrentDefault()->DeleteSoon(
FROM_HERE, std::move(pending_deletion_new_tab_prerender_handle_));
}
}
void PrerenderHostRegistry::SchedulePendingDeletionPrerenderNewTabHandle(
std::unique_ptr<PrerenderNewTabHandle> handle) {
CHECK(!pending_deletion_new_tab_prerender_handle_);
pending_deletion_new_tab_prerender_handle_ = std::move(handle);
}
void PrerenderHostRegistry::ScheduleToDeleteAbandonedHost(
std::unique_ptr<PrerenderHost> prerender_host,
const PrerenderCancellationReason& cancellation_reason) {
prerender_host->RecordFailedFinalStatus(PassKey(), cancellation_reason);
if (base::FeatureList::IsEnabled(
blink::features::kPageHideEventForPrerender2)) {
if (cancellation_reason.final_status() ==
PrerenderFinalStatus::kSpeculationRuleRemoved) {
RenderFrameHostImpl* rfhi = prerender_host->GetPrerenderedMainFrameHost();
FrameTreeNodeId prerender_host_id = prerender_host->frame_tree_node_id();
pending_deletion_hosts_[prerender_host_id] = std::move(prerender_host);
rfhi->ClosePage(RenderFrameHostImpl::ClosePageSource::kPrerenderDiscard,
base::BindRepeating(
&PrerenderHostRegistry::DeletePendingDeletionHosts,
weak_factory_.GetWeakPtr(), prerender_host_id));
return;
}
}
to_be_deleted_hosts_.push_back(std::move(prerender_host));
if (to_be_deleted_hosts_.size() > 1) {
return;
}
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&PrerenderHostRegistry::DeleteAbandonedHosts,
weak_factory_.GetWeakPtr()));
}
void PrerenderHostRegistry::DeleteAbandonedHosts() {
DestructPrerenderHosts(to_be_deleted_hosts_);
}
void PrerenderHostRegistry::NotifyTrigger(const GURL& url) {
for (Observer& obs : observers_) {
obs.OnTrigger(url);
}
}
void PrerenderHostRegistry::NotifyCancel(
FrameTreeNodeId host_frame_tree_node_id,
const PrerenderCancellationReason& reason) {
for (Observer& obs : observers_) {
obs.OnCancel(host_frame_tree_node_id, reason);
}
}
PrerenderHostRegistry::PrerenderLimitGroup
PrerenderHostRegistry::GetPrerenderLimitGroup(
PreloadingTriggerType trigger_type,
std::optional<blink::mojom::SpeculationEagerness> eagerness) {
switch (trigger_type) {
case PreloadingTriggerType::kSpeculationRule:
case PreloadingTriggerType::kSpeculationRuleFromIsolatedWorld:
case PreloadingTriggerType::kSpeculationRuleFromAutoSpeculationRules:
CHECK(eagerness.has_value());
return IsImmediateSpeculationEagerness(eagerness.value())
? PrerenderLimitGroup::kSpeculationRulesImmediate
: PrerenderLimitGroup::kSpeculationRulesNonImmediate;
case PreloadingTriggerType::kEmbedder:
return PrerenderLimitGroup::kEmbedder;
}
}
int PrerenderHostRegistry::GetHostCountByLimitGroup(
PrerenderLimitGroup limit_group) {
int host_count = 0;
for (const auto& [_, host] : prerender_host_by_frame_tree_node_id_) {
if (GetPrerenderLimitGroup(host->trigger_type(), host->eagerness()) ==
limit_group) {
++host_count;
}
}
for (const auto& [_, handle] :
prerender_new_tab_handle_by_frame_tree_node_id_) {
if (GetPrerenderLimitGroup(handle->trigger_type(), handle->eagerness()) ==
limit_group) {
++host_count;
}
}
return host_count;
}
bool PrerenderHostRegistry::IsAllowedToStartPrerenderingForTrigger(
PreloadingTriggerType trigger_type,
std::optional<blink::mojom::SpeculationEagerness> eagerness) {
PrerenderLimitGroup limit_group =
GetPrerenderLimitGroup(trigger_type, eagerness);
switch (limit_group) {
case PrerenderLimitGroup::kSpeculationRulesImmediate: {
int host_count = GetHostCountByLimitGroup(limit_group);
return host_count < kMaxRunningSpeculationRulesImmediatePrerenders;
}
case PrerenderLimitGroup::kSpeculationRulesNonImmediate: {
int host_count = GetHostCountByLimitGroup(limit_group);
if (host_count >= kMaxRunningSpeculationRulesNonImmediatePrerenders) {
FrameTreeNodeId oldest_prerender_host_id;
do {
oldest_prerender_host_id =
non_immediate_prerender_host_id_by_arrival_order_.front();
non_immediate_prerender_host_id_by_arrival_order_.pop_front();
} while (!prerender_host_by_frame_tree_node_id_.contains(
oldest_prerender_host_id) &&
!prerender_new_tab_handle_by_frame_tree_node_id_.contains(
oldest_prerender_host_id));
CHECK(CancelHost(oldest_prerender_host_id,
PrerenderFinalStatus::
kMaxNumOfRunningNonImmediatePrerendersExceeded));
CHECK_LT(GetHostCountByLimitGroup(limit_group),
kMaxRunningSpeculationRulesNonImmediatePrerenders);
}
return true;
}
case PrerenderLimitGroup::kEmbedder:
return IsAllowedToStartPrerenderingForEmbedder();
}
}
void PrerenderHostRegistry::OnMemoryPressure(
base::MemoryPressureLevel memory_pressure_level) {
if (!base::FeatureList::IsEnabled(
blink::features::kPrerender2MemoryControls)) {
return;
}
switch (memory_pressure_level) {
case base::MEMORY_PRESSURE_LEVEL_NONE:
case base::MEMORY_PRESSURE_LEVEL_MODERATE:
break;
case base::MEMORY_PRESSURE_LEVEL_CRITICAL:
CancelAllHosts(PrerenderFinalStatus::kMemoryPressureAfterTriggered);
break;
}
}
scoped_refptr<base::SingleThreadTaskRunner>
PrerenderHostRegistry::GetTimerTaskRunner() {
return timer_task_runner_for_testing_
? timer_task_runner_for_testing_
: base::SingleThreadTaskRunner::GetCurrentDefault();
}
void PrerenderHostRegistry::SetTaskRunnerForTesting(
scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
timer_task_runner_for_testing_ = std::move(task_runner);
}
bool PrerenderHostRegistry::PrerenderCanBeStartedWhenInitiatorIsInBackground() {
if (running_prerender_host_id_) {
return false;
}
if (prerender_host_by_frame_tree_node_id_.size() -
pending_prerenders_.size() >=
1) {
return false;
}
if (prerender_new_tab_handle_by_frame_tree_node_id_.size() >= 1) {
return false;
}
return true;
}
bool PrerenderHostRegistry::IsAllowedToStartPrerenderingForEmbedder() {
int host_count = GetHostCountByLimitGroup(PrerenderLimitGroup::kEmbedder);
return web_contents()->GetDelegate()->AllowedPrerenderingCount(
*web_contents()) > host_count;
}
void PrerenderHostRegistry::CancelHostsByOriginFilter(
const StoragePartition::StorageKeyMatcherFunction& storage_key_filter,
PrerenderFinalStatus final_status) {
std::vector<FrameTreeNodeId> ids_to_be_deleted;
for (const auto& [host_id, host] : prerender_host_by_frame_tree_node_id_) {
if (host->initiator_origin().has_value()) {
if (storage_key_filter.Run(blink::StorageKey::CreateFirstParty(
host->initiator_origin().value()))) {
ids_to_be_deleted.push_back(host_id);
}
}
}
for (const auto& [host_id, handle] :
prerender_new_tab_handle_by_frame_tree_node_id_) {
if (handle->initiator_origin().has_value()) {
if (storage_key_filter.Run(blink::StorageKey::CreateFirstParty(
handle->initiator_origin().value()))) {
ids_to_be_deleted.push_back(host_id);
}
} else {
CHECK(prerender_new_tab_handle_by_frame_tree_node_id_.empty());
}
}
if (!ids_to_be_deleted.empty()) {
CancelHosts(ids_to_be_deleted, PrerenderCancellationReason(final_status));
}
}
void PrerenderHostRegistry::RecordPotentialPrerenderProcessReuse(
bool kHasMatchableHosts,
const GURL& navigation_url) {
static constexpr char kPrerenderProcessReuseUMAName[] =
"Prerender.Experimental.PrerenderProcessReuseAvailability";
if (kHasMatchableHosts) {
base::UmaHistogramEnumeration(
kPrerenderProcessReuseUMAName,
PrerenderProcessReuseAvailability::kHasMatchableHosts);
return;
}
bool has_same_origin_host =
std::find_if(prerender_host_by_frame_tree_node_id_.begin(),
prerender_host_by_frame_tree_node_id_.end(),
[&navigation_url](const auto& pair) {
return pair.second->IsUrlSameOrigin(navigation_url);
}) != prerender_host_by_frame_tree_node_id_.end();
bool has_same_site_host =
std::find_if(prerender_host_by_frame_tree_node_id_.begin(),
prerender_host_by_frame_tree_node_id_.end(),
[&navigation_url](const auto& pair) {
return pair.second->IsUrlSameSite(navigation_url);
}) != prerender_host_by_frame_tree_node_id_.end();
PrerenderProcessReuseAvailability availability =
PrerenderProcessReuseAvailability::kNoSameOriginOrSiteHosts;
if (has_same_origin_host) {
availability = PrerenderProcessReuseAvailability::kHasSameOriginHosts;
} else if (has_same_site_host) {
availability = PrerenderProcessReuseAvailability::kHasSameSiteHosts;
}
base::UmaHistogramEnumeration(kPrerenderProcessReuseUMAName, availability);
}
std::unique_ptr<PrerenderHost>
PrerenderHostRegistry::FindAndTakePrerenderHostToReuse(
const PrerenderAttributes& attributes) {
const GURL prerender_url = attributes.prerendering_url;
auto iter = std::find_if(prerender_host_by_frame_tree_node_id_.begin(),
prerender_host_by_frame_tree_node_id_.end(),
[&prerender_url](const auto& pair) {
return pair.second->IsUrlSameSite(prerender_url) &&
pair.second->IsReusable();
});
if (iter != prerender_host_by_frame_tree_node_id_.end()) {
std::unique_ptr<PrerenderHost> reuse_host = std::move(iter->second);
prerender_host_by_frame_tree_node_id_.erase(iter);
return reuse_host;
}
return nullptr;
}
}