#include "content/browser/preloading/prerender/prerender_host.h"
#include <memory>
#include <optional>
#include "base/check_is_test.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/observer_list.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "base/trace_event/named_trigger.h"
#include "base/trace_event/typed_macros.h"
#include "content/browser/client_hints/client_hints.h"
#include "content/browser/devtools/devtools_instrumentation.h"
#include "content/browser/preloading/prefetch/no_vary_search_helper.h"
#include "content/browser/preloading/preloading_attempt_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_host_registry.h"
#include "content/browser/preloading/prerender/prerender_metrics.h"
#include "content/browser/preloading/prerender/prerender_navigation_utils.h"
#include "content/browser/preloading/speculation_rules/speculation_rules_util.h"
#include "content/browser/renderer_host/frame_tree.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/navigation_controller_impl.h"
#include "content/browser/renderer_host/navigation_entry_restore_context_impl.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/page_impl.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/preloading_trigger_type.h"
#include "content/public/browser/web_contents_delegate.h"
#if BUILDFLAG(ARKWEB_NETWORK_LOAD)
#include "content/public/common/content_switches.h"
#endif
#include "content/public/common/referrer.h"
#include "net/base/load_flags.h"
#include "net/http/http_request_headers.h"
#include "services/network/public/mojom/supports_loading_mode.mojom.h"
#include "third_party/blink/public/common/client_hints/enabled_client_hints.h"
#include "third_party/blink/public/common/navigation/preloading_headers.h"
#include "url/origin.h"
#if BUILDFLAG(IS_ANDROID)
#include "base/strings/stringprintf.h"
#endif
namespace content {
namespace {
BASE_FEATURE(kCreatePrerenderSiteInstanceWithURL,
base::FEATURE_DISABLED_BY_DEFAULT);
base::OnceCallback<void(FrameTreeNodeId)>& GetHostCreationCallback() {
static base::NoDestructor<base::OnceCallback<void(FrameTreeNodeId)>>
host_creation_callback;
return *host_creation_callback;
}
void CheckPrerenderAttributes(const PrerenderAttributes& attributes) {
if (attributes.IsBrowserInitiated()) {
CHECK(!attributes.initiator_origin.has_value());
CHECK(!attributes.initiator_frame_token.has_value());
CHECK_EQ(attributes.initiator_process_id,
ChildProcessHost::kInvalidUniqueID);
CHECK_EQ(attributes.initiator_ukm_id, ukm::kInvalidSourceId);
CHECK(attributes.initiator_frame_tree_node_id.is_null());
} else {
CHECK(attributes.initiator_origin.has_value());
CHECK(attributes.initiator_frame_token.has_value());
CHECK_NE(attributes.initiator_process_id,
ChildProcessHost::kInvalidUniqueID);
CHECK_NE(attributes.initiator_ukm_id, ukm::kInvalidSourceId);
CHECK(attributes.initiator_frame_tree_node_id);
}
}
#if BUILDFLAG(IS_ANDROID)
std::string SerializeHttpRequestHeaders(
const net::HttpRequestHeaders& headers) {
CHECK(!headers.IsEmpty());
std::string output;
for (const auto& header : headers.GetHeaderVector()) {
base::StringAppendF(&output, "%s: %s\n", header.key.c_str(),
header.value.c_str());
}
output.append("\n");
return output;
}
#endif
PrerenderHostId NextPrerenderHostId() {
static PrerenderHostId::Generator generator;
return generator.GenerateNextId();
}
}
PrerenderHost::PrerenderFrameTreeDelegate::PrerenderFrameTreeDelegate(
BrowserContext* browser_context,
WebContentsImpl& web_contents,
PrerenderHost& prerender_host)
: prerender_host_(prerender_host),
frame_tree_(
std::make_unique<FrameTree>(browser_context,
this,
this,
&web_contents,
&web_contents,
&web_contents,
&web_contents,
&web_contents,
&web_contents,
FrameTree::Type::kPrerender)) {}
void PrerenderHost::PrerenderFrameTreeDelegate::DidStopLoading() {
if (on_wait_loading_finished_) {
std::move(on_wait_loading_finished_).Run(LoadingOutcome::kLoadingCompleted);
}
}
bool PrerenderHost::PrerenderFrameTreeDelegate::IsHidden() {
return true;
}
FrameTree* PrerenderHost::PrerenderFrameTreeDelegate::LoadingTree() {
return frame_tree_.get();
}
FrameTreeNodeId
PrerenderHost::PrerenderFrameTreeDelegate::GetOuterDelegateFrameTreeNodeId() {
return FrameTreeNodeId();
}
RenderFrameHostImpl*
PrerenderHost::PrerenderFrameTreeDelegate::GetProspectiveOuterDocument() {
return nullptr;
}
void PrerenderHost::PrerenderFrameTreeDelegate::SetFocusedFrame(
FrameTreeNode* node,
SiteInstanceGroup* source) {
NOTREACHED();
}
FrameTree* PrerenderHost::PrerenderFrameTreeDelegate::
GetOwnedDocumentPictureInPictureFrameTree() {
return nullptr;
}
FrameTree* PrerenderHost::PrerenderFrameTreeDelegate::
GetDocumentPictureInPictureOpenerFrameTree() {
return nullptr;
}
bool PrerenderHost::PrerenderFrameTreeDelegate::
OnRenderFrameProxyVisibilityChanged(
RenderFrameProxyHost* render_frame_proxy_host,
blink::mojom::FrameVisibility visibility) {
return false;
}
void PrerenderHost::PrerenderFrameTreeDelegate::
ActivateAndShowRepostFormWarningDialog() {
frame_tree_->controller().CancelPendingReload();
}
bool PrerenderHost::PrerenderFrameTreeDelegate::ShouldPreserveAbortedURLs() {
return false;
}
#if BUILDFLAG(IS_ANDROID)
scoped_refptr<viz::RasterContextProvider>
PrerenderHost::PrerenderFrameTreeDelegate::GetRasterContextProvider() {
NOTREACHED();
}
gfx::ColorSpace PrerenderHost::PrerenderFrameTreeDelegate::GetOutputColorSpace(
gfx::ContentColorUsage color_usage,
bool needs_alpha) {
NOTREACHED();
}
#endif
PrerenderHost::LoadingOutcome
PrerenderHost::PrerenderFrameTreeDelegate::WaitForLoadStopForTesting() {
LoadingOutcome status = LoadingOutcome::kLoadingCompleted;
if (!frame_tree_->IsLoadingIncludingInnerFrameTrees() &&
prerender_host_->GetInitialNavigationId().has_value()) {
return status;
}
base::RunLoop loop;
on_wait_loading_finished_ = base::BindOnce(
[](base::OnceClosure on_close, LoadingOutcome* result,
LoadingOutcome status) {
*result = status;
std::move(on_close).Run();
},
loop.QuitClosure(), &status);
loop.Run();
if (status != PrerenderHost::LoadingOutcome::kPrerenderingCancelled) {
on_wait_loading_finished_.Reset();
}
return status;
}
PrerenderHost::PrerenderFrameTreeDelegate::~PrerenderFrameTreeDelegate() {
if (frame_tree_) {
frame_tree_->Shutdown();
}
if (on_wait_loading_finished_) {
std::move(on_wait_loading_finished_)
.Run(PrerenderHost::LoadingOutcome::kPrerenderingCancelled);
}
}
PrerenderHost* PrerenderHost::GetFromFrameTreeNodeIfPrerendering(
FrameTreeNode& frame_tree_node) {
if (!frame_tree_node.frame_tree().is_prerendering()) {
return nullptr;
}
return &GetFromFrameTreeNode(frame_tree_node);
}
PrerenderHost& PrerenderHost::GetFromFrameTreeNode(
FrameTreeNode& frame_tree_node) {
return GetFromFrameTree(&frame_tree_node.frame_tree());
}
PrerenderHost& PrerenderHost::GetFromFrameTree(FrameTree* frame_tree) {
CHECK(frame_tree);
CHECK(frame_tree->is_prerendering());
return *(static_cast<PrerenderHost::PrerenderFrameTreeDelegate*>(
frame_tree->delegate())
->prerender_host_);
}
bool PrerenderHost::AreHttpRequestHeadersCompatible(
const std::string& potential_activation_headers_str,
#if BUILDFLAG(IS_ANDROID)
const std::string& potential_activation_additional_headers_str,
#endif
const std::string& prerender_headers_str,
PreloadingTriggerType trigger_type,
const std::string& histogram_suffix,
bool allow_x_header_mismatch,
PrerenderCancellationReason& reason) {
net::HttpRequestHeaders prerender_headers;
prerender_headers.AddHeadersFromString(prerender_headers_str);
net::HttpRequestHeaders potential_activation_headers;
potential_activation_headers.AddHeadersFromString(
potential_activation_headers_str);
#if BUILDFLAG(IS_ANDROID)
potential_activation_headers.AddHeadersFromString(
potential_activation_additional_headers_str);
#endif
prerender_headers.RemoveHeader(blink::kPurposeHeaderName);
potential_activation_headers.RemoveHeader(blink::kPurposeHeaderName);
prerender_headers.RemoveHeader(blink::kSecPurposeHeaderName);
potential_activation_headers.RemoveHeader(blink::kSecPurposeHeaderName);
prerender_headers.RemoveHeader(blink::kSecSpeculationTagsHeaderName);
CHECK(!potential_activation_headers.HasHeader(
blink::kSecSpeculationTagsHeaderName));
prerender_headers.RemoveHeader("RTT");
potential_activation_headers.RemoveHeader("RTT");
prerender_headers.RemoveHeader("Downlink");
potential_activation_headers.RemoveHeader("Downlink");
#if BUILDFLAG(IS_ANDROID)
if (trigger_type == PreloadingTriggerType::kEmbedder) {
prerender_headers.RemoveHeader("X-Geo");
potential_activation_headers.RemoveHeader("X-Geo");
}
#endif
prerender_headers.RemoveHeader("viewport-width");
potential_activation_headers.RemoveHeader("viewport-width");
prerender_headers.RemoveHeader("sec-ch-viewport-width");
potential_activation_headers.RemoveHeader("sec-ch-viewport-width");
prerender_headers.RemoveHeader("sec-ch-viewport-height");
potential_activation_headers.RemoveHeader("sec-ch-viewport-height");
if (allow_x_header_mismatch) {
std::set<std::string> headers_to_be_removed;
for (net::HttpRequestHeaders::Iterator it(prerender_headers);
it.GetNext();) {
if (it.name().starts_with("X-") || it.name().starts_with("x-")) {
headers_to_be_removed.insert(it.name());
}
}
for (net::HttpRequestHeaders::Iterator it(potential_activation_headers);
it.GetNext();) {
if (it.name().starts_with("X-") || it.name().starts_with("x-")) {
headers_to_be_removed.insert(it.name());
}
}
for (const std::string& name : headers_to_be_removed) {
prerender_headers.RemoveHeader(name);
potential_activation_headers.RemoveHeader(name);
}
}
return PrerenderHost::IsActivationHeaderMatch(potential_activation_headers,
prerender_headers, reason);
}
void PrerenderHost::SetHostCreationCallbackForTesting(
base::OnceCallback<void(FrameTreeNodeId host_id)> callback) {
GetHostCreationCallback() = std::move(callback);
}
PrerenderHost::PrerenderHost(
std::unique_ptr<PrerenderHost> reuse_host,
const PrerenderAttributes& attributes,
WebContentsImpl& web_contents,
base::WeakPtr<PreloadingAttempt> attempt,
std::unique_ptr<DevToolsPrerenderAttempt> devtools_attempt)
: attributes_(attributes),
prerender_host_id_(NextPrerenderHostId()),
metric_suffix_(
GeneratePrerenderHistogramSuffix(trigger_type(),
embedder_histogram_suffix())),
attempt_(std::move(attempt)),
devtools_attempt_(std::move(devtools_attempt)),
web_contents_(web_contents),
host_reused_(reuse_host) {
#if BUILDFLAG(IS_ANDROID)
if (trigger_type() == PreloadingTriggerType::kSpeculationRule) {
base::trace_event::EmitNamedTrigger("sp-prerender-start");
}
#endif
CheckPrerenderAttributes(attributes_);
SetTriggeringOutcome(PreloadingTriggeringOutcome::kTriggeredButPending);
if (reuse_host) {
reuse_host->RecordFailedFinalStatusImpl(PrerenderCancellationReason(
PrerenderFinalStatus::kPrerenderHostReused));
if (reuse_host->frame_tree_delegate_->on_wait_loading_finished_) {
std::move(reuse_host->frame_tree_delegate_->on_wait_loading_finished_)
.Run(PrerenderHost::LoadingOutcome::kPrerenderingCancelled);
}
frame_tree_delegate_ = std::move(reuse_host->frame_tree_delegate_);
GetFrameTree()->root()->ResetNavigationRequest(
NavigationDiscardReason::kExplicitCancellation);
frame_tree_delegate_->prerender_host_ = *this;
} else {
frame_tree_delegate_ = std::make_unique<PrerenderFrameTreeDelegate>(
web_contents.GetBrowserContext(), web_contents, *this);
scoped_refptr<SiteInstanceImpl> site_instance =
base::FeatureList::IsEnabled(kCreatePrerenderSiteInstanceWithURL)
? SiteInstanceImpl::CreateForURL(web_contents.GetBrowserContext(),
attributes.prerendering_url)
: SiteInstanceImpl::Create(web_contents.GetBrowserContext());
GetFrameTree()->Init(site_instance.get(),
false,
"", nullptr,
blink::FramePolicy(),
base::UnguessableToken::Create());
GetFrameTree()->controller().SetSessionStorageNamespace(
site_instance->GetStoragePartitionConfig(),
web_contents_->GetPrimaryFrameTree()
.controller()
.GetSessionStorageNamespace(
site_instance->GetStoragePartitionConfig()));
web_contents_->NotifySwappedFromRenderManager(
nullptr,
GetFrameTree()->root()->render_manager()->current_frame_host());
}
frame_tree_node_id_ = GetFrameTree()->root()->frame_tree_node_id();
if (GetHostCreationCallback()) {
CHECK_IS_TEST();
std::move(GetHostCreationCallback()).Run(frame_tree_node_id_);
}
}
bool PrerenderHost::IsActivationHeaderMatch(
const net::HttpRequestHeaders& potential_activation_headers,
const net::HttpRequestHeaders& prerender_headers,
PrerenderCancellationReason& reason) {
using HeaderPair = net::HttpRequestHeaders::HeaderKeyValuePair;
auto cmp = [](const HeaderPair& a, const HeaderPair& b) {
return a.key < b.key;
};
auto lower_case = [](HeaderPair& x) { x.key = base::ToLowerASCII(x.key); };
auto same_predicate = [](const HeaderPair& a, const HeaderPair& b) {
return a.key == b.key && base::EqualsCaseInsensitiveASCII(a.value, b.value);
};
std::vector<HeaderPair> potential_header_list(
potential_activation_headers.GetHeaderVector());
std::vector<HeaderPair> prerender_header_list(
prerender_headers.GetHeaderVector());
std::for_each(potential_header_list.begin(), potential_header_list.end(),
lower_case);
std::for_each(prerender_header_list.begin(), prerender_header_list.end(),
lower_case);
std::sort(potential_header_list.begin(), potential_header_list.end(), cmp);
std::sort(prerender_header_list.begin(), prerender_header_list.end(), cmp);
std::unique_ptr<std::vector<PrerenderMismatchedHeaders>> mismatched_headers =
std::make_unique<std::vector<PrerenderMismatchedHeaders>>();
auto prerender_header_list_it = prerender_header_list.begin();
auto potential_header_list_it = potential_header_list.begin();
while (prerender_header_list_it != prerender_header_list.end() &&
potential_header_list_it != potential_header_list.end()) {
if (same_predicate(*prerender_header_list_it, *potential_header_list_it)) {
prerender_header_list_it++;
potential_header_list_it++;
} else if (prerender_header_list_it->key == potential_header_list_it->key) {
mismatched_headers->emplace_back(prerender_header_list_it->key,
prerender_header_list_it->value,
potential_header_list_it->value);
prerender_header_list_it++;
potential_header_list_it++;
} else if (prerender_header_list_it->key < potential_header_list_it->key) {
mismatched_headers->emplace_back(prerender_header_list_it->key,
prerender_header_list_it->value,
std::nullopt);
prerender_header_list_it++;
} else {
mismatched_headers->emplace_back(potential_header_list_it->key,
std::nullopt,
potential_header_list_it->value);
potential_header_list_it++;
}
}
while (prerender_header_list_it != prerender_header_list.end()) {
mismatched_headers->emplace_back(prerender_header_list_it->key,
prerender_header_list_it->value,
std::nullopt);
prerender_header_list_it++;
}
while (potential_header_list_it != potential_header_list.end()) {
mismatched_headers->emplace_back(potential_header_list_it->key,
std::nullopt,
potential_header_list_it->value);
potential_header_list_it++;
}
if (mismatched_headers->empty()) {
return true;
}
reason.SetPrerenderMismatchedHeaders(std::move(mismatched_headers));
return false;
}
PrerenderHost::~PrerenderHost() {
if (!final_status_.has_value()) {
RecordFailedFinalStatusImpl(
PrerenderCancellationReason(PrerenderFinalStatus::kDestroyed));
}
for (auto& observer : observers_) {
observer.OnHostDestroyed(final_status_.value());
}
}
bool PrerenderHost::StartPrerendering() {
TRACE_EVENT("navigation", "PrerenderHost::StartPrerendering");
SetTriggeringOutcome(PreloadingTriggeringOutcome::kRunning);
NavigationController::LoadURLParams load_url_params(
attributes_.prerendering_url);
load_url_params.initiator_origin = attributes_.initiator_origin;
load_url_params.initiator_process_id = attributes_.initiator_process_id;
load_url_params.initiator_frame_token = attributes_.initiator_frame_token;
#if BUILDFLAG(IS_ANDROID)
if (!attributes_.additional_headers.IsEmpty()) {
load_url_params.extra_headers =
SerializeHttpRequestHeaders(attributes_.additional_headers);
}
#endif
load_url_params.is_renderer_initiated = !attributes_.IsBrowserInitiated();
load_url_params.transition_type =
ui::PageTransitionFromInt(attributes_.transition_type);
#if BUILDFLAG(ARKWEB_NETWORK_LOAD)
load_url_params.extra_headers = attributes_.extra_headers;
#endif
load_url_params.referrer = attributes_.referrer;
load_url_params.override_user_agent =
web_contents_->GetDelegate()->ShouldOverrideUserAgentForPreloading(
attributes_.prerendering_url);
base::WeakPtr<NavigationHandle> created_navigation_handle =
GetNavigationController().LoadURLWithParams(load_url_params);
if (!created_navigation_handle) {
return false;
}
if (attributes_.prerender_navigation_handle_callback) {
attributes_.prerender_navigation_handle_callback.Run(
*created_navigation_handle);
}
if (final_status_.has_value()) {
return false;
}
NavigationRequest* navigation_request =
NavigationRequest::From(created_navigation_handle.get());
if (initial_navigation_id_.has_value()) {
CHECK_EQ(*initial_navigation_id_,
created_navigation_handle->GetNavigationId());
CHECK(begin_params_);
CHECK(common_params_);
} else if (navigation_request->state() ==
NavigationRequest::WAITING_FOR_RENDERER_RESPONSE) {
CHECK(!begin_params_);
CHECK(!common_params_);
} else {
return false;
}
CHECK_GE(navigation_request->state(),
NavigationRequest::WAITING_FOR_RENDERER_RESPONSE);
#if BUILDFLAG(ARKWEB_NETWORK_LOAD)
if (begin_params_ &&
base::CommandLine::ForCurrentProcess()->HasSwitch(::switches::kEnableNwebEx)) {
begin_params_->headers = navigation_request->begin_params().headers;
}
#endif
return true;
}
void PrerenderHost::DidStartNavigation(NavigationHandle* navigation_handle) {
auto* navigation_request = NavigationRequest::From(navigation_handle);
CHECK(navigation_request->IsInPrerenderedMainFrame());
if (IsInitialNavigation(*navigation_request)) {
return;
}
is_ready_for_activation_ = false;
}
void PrerenderHost::ReadyToCommitNavigation(
NavigationHandle* navigation_handle) {
CHECK(navigation_handle);
auto* navigation_request = NavigationRequest::From(navigation_handle);
CHECK(navigation_request->IsInPrerenderedMainFrame());
CHECK(GetFrameTree());
CHECK_EQ(GetFrameTree(),
&navigation_request->frame_tree_node()->frame_tree());
if (!IsInitialNavigation(*navigation_request)) {
return;
}
bool has_no_vary_search_with_parse_error_header = false;
if (navigation_request->response() &&
navigation_request->response()->parsed_headers) {
const network::mojom::ParsedHeadersPtr& parsed_headers =
navigation_request->response()->parsed_headers;
if (parsed_headers->no_vary_search_with_parse_error) {
has_no_vary_search_with_parse_error_header = true;
MaybeSetNoVarySearch(*parsed_headers->no_vary_search_with_parse_error);
}
const bool is_prerender_2_cross_origin_iframes_enabled =
attributes_.enable_cross_origin_prerender_iframes ||
base::FeatureList::IsEnabled(
blink::features::kPrerender2CrossOriginIframes);
if (is_prerender_2_cross_origin_iframes_enabled &&
base::Contains(
parsed_headers->supports_loading_mode,
network::mojom::LoadingMode::kPrerenderCrossOriginFrames)) {
allow_cross_origin_subframe_navigation_ = true;
}
}
if (!has_no_vary_search_with_parse_error_header) {
CHECK(!no_vary_search_.has_value());
CHECK(!no_vary_search_parse_error_.has_value());
}
were_headers_received_ = true;
for (auto& observer : observers_) {
observer.OnHeadersReceived(*navigation_handle);
}
}
void PrerenderHost::DidFinishNavigation(NavigationHandle* navigation_handle) {
auto* navigation_request = NavigationRequest::From(navigation_handle);
CHECK_EQ(&(navigation_request->frame_tree_node()->frame_tree()),
GetFrameTree());
CHECK(navigation_request->GetPrerenderHostId());
if (prerender_host_id_ != navigation_request->GetPrerenderHostId()) {
return;
}
if (PreloadServingMetricsCapsule::IsFeatureEnabled()) {
if (!prerender_initial_preload_serving_metrics_) {
auto& initial_preload_serving_metrics_holder =
*PreloadServingMetricsHolder::GetOrCreateForNavigationHandle(
*navigation_handle);
prerender_initial_preload_serving_metrics_ =
initial_preload_serving_metrics_holder.Take();
}
}
const bool is_prerender_main_frame =
navigation_request->GetFrameTreeNodeId() == frame_tree_node_id_;
net::Error net_error = navigation_request->GetNetErrorCode();
std::optional<PrerenderFinalStatus> status;
if (net_error == net::Error::ERR_BLOCKED_BY_CSP) {
status = PrerenderFinalStatus::kNavigationRequestBlockedByCsp;
} else if (net_error == net::Error::ERR_BLOCKED_BY_CLIENT) {
status = PrerenderFinalStatus::kBlockedByClient;
} else if (is_prerender_main_frame && net_error != net::Error::OK) {
status = PrerenderFinalStatus::kNavigationRequestNetworkError;
} else if (is_prerender_main_frame && !navigation_request->HasCommitted()) {
status = PrerenderFinalStatus::kNavigationNotCommitted;
}
if (status.has_value()) {
Cancel(*status);
return;
}
if (is_prerender_main_frame && !final_status_) {
CHECK(!is_ready_for_activation_);
is_ready_for_activation_ = true;
SetTriggeringOutcome(PreloadingTriggeringOutcome::kReady);
}
}
std::unique_ptr<StoredPage> PrerenderHost::Activate(
NavigationRequest& navigation_request) {
TRACE_EVENT("navigation", "PrerenderHost::Activate", "navigation_request",
&navigation_request);
CHECK(is_ready_for_activation_);
is_ready_for_activation_ = false;
FrameTree& target_frame_tree = web_contents_->GetPrimaryFrameTree();
CHECK(!GetFrameTree()->root()->HasNavigation());
FrameTree::NodeRange node_range = GetFrameTree()->Nodes();
std::vector<FrameTreeNode*> subframe_nodes(std::next(node_range.begin()),
node_range.end());
blink::mojom::FrameReplicationState prior_replication_state =
GetFrameTree()->root()->current_replication_state();
prior_replication_state.has_received_user_gesture_before_nav =
navigation_request.frame_tree_node()
->has_received_user_gesture_before_nav();
std::unique_ptr<StoredPage> page =
GetFrameTree()->root()->render_manager()->TakePrerenderedPage();
CHECK(page);
if (allow_cross_origin_subframe_navigation_) {
page->render_frame_host()
->GetPage()
.NotifyCrossOriginSubframePrerenderIsAllowed();
}
NavigationEntryRestoreContextImpl context;
std::unique_ptr<NavigationEntryImpl> nav_entry =
GetNavigationController()
.GetEntryWithUniqueID(page->render_frame_host()->nav_entry_id())
->CloneWithoutSharing(&context);
navigation_request.SetPrerenderActivationNavigationState(
std::move(nav_entry), prior_replication_state);
CHECK_EQ(&target_frame_tree,
&navigation_request.frame_tree_node()->frame_tree());
CHECK(!page->render_frame_host()->GetParentOrOuterDocumentOrEmbedder());
page->render_frame_host()->SetFrameTreeNode(*(target_frame_tree.root()));
page->render_frame_host()->SetRenderFrameHostOwner(target_frame_tree.root());
page->render_frame_host()->frame_tree_node()->set_frame_name_for_activation(
prior_replication_state.unique_name, prior_replication_state.name);
for (auto& it : page->proxy_hosts()) {
it.second->set_frame_tree_node(*(target_frame_tree.root()));
}
for (FrameTreeNode* subframe_node : subframe_nodes) {
subframe_node->SetFrameTree(target_frame_tree);
}
frame_tree_delegate_.reset();
page->render_frame_host()->ForEachRenderFrameHostImplIncludingSpeculative(
[this](RenderFrameHostImpl* rfh) {
rfh->render_view_host()->SetFrameTreeVisibility(
web_contents_->GetPageVisibilityState());
});
for (auto& observer : observers_) {
observer.OnActivated();
}
BrowserContext* browser_context =
target_frame_tree.controller().GetBrowserContext();
ClientHintsControllerDelegate* client_hints_delegate =
browser_context->GetClientHintsControllerDelegate();
if (client_hints_delegate) {
for (auto& [origin, client_hint] : client_hints_type_) {
PersistAcceptCH(origin, *(target_frame_tree.root()),
client_hints_delegate, client_hint);
}
}
if (PreloadServingMetricsCapsule::IsFeatureEnabled()) {
auto& activation_preload_serving_metrics_holder =
*PreloadServingMetricsHolder::GetOrCreateForNavigationHandle(
navigation_request);
activation_preload_serving_metrics_holder
.SetPrerenderInitialPreloadServingMetrics(
std::move(prerender_initial_preload_serving_metrics_));
}
RecordActivation(navigation_request);
SetTriggeringOutcome(PreloadingTriggeringOutcome::kSuccess);
devtools_instrumentation::DidActivatePrerender(
navigation_request, initiator_devtools_navigation_token());
return page;
}
bool PrerenderHost::IsFramePolicyCompatibleWithPrimaryFrameTree() {
FrameTreeNode* prerender_root_ftn = GetFrameTree()->root();
FrameTreeNode* primary_root_ftn = web_contents_->GetPrimaryFrameTree().root();
if (prerender_root_ftn->pending_frame_policy() != blink::FramePolicy()) {
return false;
}
if (primary_root_ftn->pending_frame_policy() != blink::FramePolicy()) {
return false;
}
if (prerender_root_ftn->current_replication_state().frame_policy !=
primary_root_ftn->current_replication_state().frame_policy) {
return false;
}
return true;
}
bool PrerenderHost::AreInitialPrerenderNavigationParamsCompatibleWithNavigation(
NavigationRequest& navigation_request,
PrerenderCancellationReason& reason) {
CHECK(navigation_request.IsInPrimaryMainFrame());
if (!common_params_ || !begin_params_) {
return false;
}
bool allow_partial_mismatch =
web_contents_->GetDelegate()->ShouldAllowPartialParamMismatchOfPrerender2(
navigation_request);
#if BUILDFLAG(ARKWEB_NETWORK_LOAD)
allow_partial_mismatch =
(allow_partial_mismatch ||
(((navigation_request.common_params().transition &
~ui::PAGE_TRANSITION_CLIENT_REDIRECT) ==
(ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR)) &&
common_params_->transition ==
(ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FROM_API)));
#endif
ActivationNavigationParamsMatch result =
AreBeginNavigationParamsCompatibleWithNavigation(
navigation_request.common_params().url,
navigation_request.begin_params(), allow_partial_mismatch, reason);
if (result != ActivationNavigationParamsMatch::kOk) {
RecordPrerenderActivationNavigationParamsMatch(result,
GetHistogramSuffix());
return false;
}
result = AreCommonNavigationParamsCompatibleWithNavigation(
navigation_request.common_params(), allow_partial_mismatch);
if (result != ActivationNavigationParamsMatch::kOk) {
RecordPrerenderActivationNavigationParamsMatch(result,
GetHistogramSuffix());
return false;
}
RecordPrerenderActivationNavigationParamsMatch(
ActivationNavigationParamsMatch::kOk, GetHistogramSuffix());
return true;
}
#if BUILDFLAG(IS_ANDROID)
BASE_FEATURE(kPrerenderActivationMismatchWebViewWorkaround,
base::FEATURE_ENABLED_BY_DEFAULT);
#endif
PrerenderHost::ActivationNavigationParamsMatch
PrerenderHost::AreBeginNavigationParamsCompatibleWithNavigation(
const GURL& potential_activation_url,
const blink::mojom::BeginNavigationParams& potential_activation,
bool allow_partial_mismatch,
PrerenderCancellationReason& reason) {
CHECK(begin_params_);
if (!allow_partial_mismatch && (potential_activation.initiator_frame_token !=
begin_params_->initiator_frame_token)) {
return ActivationNavigationParamsMatch::kInitiatorFrameToken;
}
#if BUILDFLAG(IS_ANDROID)
std::string activation_additional_headers_str;
bool workaround_enabled = base::FeatureList::IsEnabled(
kPrerenderActivationMismatchWebViewWorkaround);
if (!workaround_enabled || !IsSpeculationRuleType(trigger_type())) {
activation_additional_headers_str =
web_contents_->GetBrowserContext()->GetExtraHeadersForUrl(
potential_activation_url);
}
#endif
if (!AreHttpRequestHeadersCompatible(potential_activation.headers,
#if BUILDFLAG(IS_ANDROID)
activation_additional_headers_str,
#endif
begin_params_->headers, trigger_type(),
GetHistogramSuffix(),
allow_partial_mismatch, reason)) {
return ActivationNavigationParamsMatch::kHttpRequestHeader;
}
int cache_load_flags = net::LOAD_VALIDATE_CACHE | net::LOAD_BYPASS_CACHE |
net::LOAD_DISABLE_CACHE;
if (potential_activation.load_flags & cache_load_flags) {
return ActivationNavigationParamsMatch::kCacheLoadFlags;
}
if (potential_activation.load_flags != begin_params_->load_flags) {
return ActivationNavigationParamsMatch::kLoadFlags;
}
if (potential_activation.skip_service_worker !=
begin_params_->skip_service_worker) {
return ActivationNavigationParamsMatch::kSkipServiceWorker;
}
if (potential_activation.mixed_content_context_type !=
begin_params_->mixed_content_context_type) {
return ActivationNavigationParamsMatch::kMixedContentContextType;
}
CHECK(!begin_params_->is_form_submission);
if (potential_activation.is_form_submission !=
begin_params_->is_form_submission) {
return ActivationNavigationParamsMatch::kIsFormSubmission;
}
if (potential_activation.searchable_form_url !=
begin_params_->searchable_form_url) {
return ActivationNavigationParamsMatch::kSearchableFormUrl;
}
if (potential_activation.searchable_form_encoding !=
begin_params_->searchable_form_encoding) {
return ActivationNavigationParamsMatch::kSearchableFormEncoding;
}
CHECK(!begin_params_->trust_token_params);
if (potential_activation.trust_token_params !=
begin_params_->trust_token_params) {
return ActivationNavigationParamsMatch::kTrustTokenParams;
}
CHECK_EQ(begin_params_->request_context_type,
blink::mojom::RequestContextType::LOCATION);
switch (potential_activation.request_context_type) {
case blink::mojom::RequestContextType::HYPERLINK:
case blink::mojom::RequestContextType::LOCATION:
break;
default:
return ActivationNavigationParamsMatch::kRequestContextType;
}
CHECK(!begin_params_->impression);
if (potential_activation.impression.has_value()) {
return ActivationNavigationParamsMatch::kImpressionHasValue;
}
return ActivationNavigationParamsMatch::kOk;
}
PrerenderHost::ActivationNavigationParamsMatch
PrerenderHost::AreCommonNavigationParamsCompatibleWithNavigation(
const blink::mojom::CommonNavigationParams& potential_activation,
bool allow_partial_mismatch) {
CHECK(common_params_);
if (attributes_.url_match_predicate) {
CHECK(attributes_.url_match_predicate.Run(potential_activation.url,
std::nullopt));
} else if (no_vary_search_.has_value()) {
CHECK(no_vary_search_->AreEquivalent(potential_activation.url,
common_params_->url));
} else if (no_vary_search_hint().has_value()) {
CHECK(no_vary_search_hint()->AreEquivalent(potential_activation.url,
common_params_->url));
} else {
CHECK_EQ(potential_activation.url, common_params_->url);
}
if (!allow_partial_mismatch && (potential_activation.initiator_origin !=
common_params_->initiator_origin)) {
return ActivationNavigationParamsMatch::kInitiatorOrigin;
}
int32_t potential_activation_transition =
potential_activation.transition & ~ui::PAGE_TRANSITION_CLIENT_REDIRECT;
if (!allow_partial_mismatch &&
(potential_activation_transition != common_params_->transition)) {
RecordPrerenderActivationTransition(potential_activation_transition,
GetHistogramSuffix());
return ActivationNavigationParamsMatch::kTransition;
}
CHECK_EQ(common_params_->navigation_type,
blink::mojom::NavigationType::DIFFERENT_DOCUMENT);
if (potential_activation.navigation_type != common_params_->navigation_type) {
return ActivationNavigationParamsMatch::kNavigationType;
}
CHECK(common_params_->base_url_for_data_url.is_empty());
if (potential_activation.base_url_for_data_url !=
common_params_->base_url_for_data_url) {
return ActivationNavigationParamsMatch::kBaseUrlForDataUrl;
}
CHECK_EQ(potential_activation.method, common_params_->method);
CHECK(!common_params_->post_data);
if (potential_activation.post_data != common_params_->post_data) {
return ActivationNavigationParamsMatch::kPostData;
}
CHECK(!common_params_->started_from_context_menu);
if (potential_activation.started_from_context_menu !=
common_params_->started_from_context_menu) {
return ActivationNavigationParamsMatch::kStartedFromContextMenu;
}
if (potential_activation.initiator_origin_trial_features !=
common_params_->initiator_origin_trial_features) {
return ActivationNavigationParamsMatch::kInitiatorOriginTrialFeature;
}
if (potential_activation.href_translate != common_params_->href_translate) {
return ActivationNavigationParamsMatch::kHrefTranslate;
}
CHECK(!common_params_->is_history_navigation_in_new_child_frame);
if (potential_activation.is_history_navigation_in_new_child_frame !=
common_params_->is_history_navigation_in_new_child_frame) {
return ActivationNavigationParamsMatch::kIsHistoryNavigationInNewChildFrame;
}
if (potential_activation.request_destination !=
common_params_->request_destination) {
return ActivationNavigationParamsMatch::kRequestDestination;
}
return ActivationNavigationParamsMatch::kOk;
}
RenderFrameHostImpl* PrerenderHost::GetPrerenderedMainFrameHost() {
CHECK(GetFrameTree());
CHECK(GetFrameTree()->root()->current_frame_host());
return GetFrameTree()->root()->current_frame_host();
}
FrameTree& PrerenderHost::GetPrerenderFrameTree() {
CHECK(GetFrameTree());
return *GetFrameTree();
}
void PrerenderHost::RecordFailedFinalStatus(
base::PassKey<PrerenderHostRegistry>,
const PrerenderCancellationReason& reason) {
RecordFailedFinalStatusImpl(reason);
}
void PrerenderHost::RecordFailedFinalStatusImpl(
const PrerenderCancellationReason& reason) {
CHECK(!final_status_);
CHECK_NE(reason.final_status(), PrerenderFinalStatus::kActivated);
final_status_ = reason.final_status();
RecordFailedPrerenderFinalStatus(reason, attributes_);
SetFailureReason(reason);
for (auto& observer : observers_) {
observer.OnFailed(final_status_.value());
}
}
void PrerenderHost::RecordActivation(NavigationRequest& navigation_request) {
CHECK(!final_status_);
final_status_ = PrerenderFinalStatus::kActivated;
ReportSuccessActivation(attributes_,
navigation_request.GetNextPageUkmSourceId());
}
PrerenderHost::LoadingOutcome PrerenderHost::WaitForLoadStopForTesting() {
return frame_tree_delegate_->WaitForLoadStopForTesting();
}
const GURL& PrerenderHost::GetInitialUrl() const {
return attributes_.prerendering_url;
}
void PrerenderHost::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void PrerenderHost::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
std::optional<int64_t> PrerenderHost::GetInitialNavigationId() const {
return initial_navigation_id_;
}
void PrerenderHost::SetInitialNavigation(NavigationRequest* navigation) {
CHECK(!initial_navigation_id_.has_value());
initial_navigation_id_ = navigation->GetNavigationId();
begin_params_ = navigation->begin_params().Clone();
common_params_ = navigation->common_params().Clone();
CHECK_EQ(common_params_->should_check_main_world_csp,
network::mojom::CSPDisposition::CHECK);
}
void PrerenderHost::SetTriggeringOutcome(PreloadingTriggeringOutcome outcome) {
if (attempt_) {
attempt_->SetTriggeringOutcome(outcome);
}
if (devtools_attempt_) {
devtools_attempt_->SetTriggeringOutcome(attributes_, outcome);
}
}
void PrerenderHost::SetFailureReason(
const PrerenderCancellationReason& reason) {
switch (reason.final_status()) {
case PrerenderFinalStatus::kTriggerDestroyed:
case PrerenderFinalStatus::kActivatedBeforeStarted:
case PrerenderFinalStatus::kTabClosedByUserGesture:
case PrerenderFinalStatus::kTabClosedWithoutUserGesture:
case PrerenderFinalStatus::kSpeculationRuleRemoved:
case PrerenderFinalStatus::kOtherPrerenderedPageActivated:
case PrerenderFinalStatus::kPrerenderHostReused:
return;
case PrerenderFinalStatus::kDestroyed:
case PrerenderFinalStatus::kLowEndDevice:
case PrerenderFinalStatus::kInvalidSchemeRedirect:
case PrerenderFinalStatus::kInvalidSchemeNavigation:
case PrerenderFinalStatus::kNavigationRequestBlockedByCsp:
case PrerenderFinalStatus::kMojoBinderPolicy:
case PrerenderFinalStatus::kRendererProcessCrashed:
case PrerenderFinalStatus::kRendererProcessKilled:
case PrerenderFinalStatus::kDownload:
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:
case PrerenderFinalStatus::kTriggerBackgrounded:
case PrerenderFinalStatus::kMemoryLimitExceeded:
case PrerenderFinalStatus::kDataSaverEnabled:
case PrerenderFinalStatus::kTriggerUrlHasEffectiveUrl:
case PrerenderFinalStatus::kInactivePageRestriction:
case PrerenderFinalStatus::kStartFailed:
case PrerenderFinalStatus::kTimeoutBackgrounded:
case PrerenderFinalStatus::kCrossSiteNavigationInInitialNavigation:
case PrerenderFinalStatus::kCrossSiteRedirectInInitialNavigation:
case PrerenderFinalStatus::
kSameSiteCrossOriginRedirectNotOptInInInitialNavigation:
case PrerenderFinalStatus::
kSameSiteCrossOriginNavigationNotOptInInInitialNavigation:
case PrerenderFinalStatus::kActivationNavigationParameterMismatch:
case PrerenderFinalStatus::kActivatedInBackground:
case PrerenderFinalStatus::kActivationNavigationDestroyedBeforeSuccess:
case PrerenderFinalStatus::kPrimaryMainFrameRendererProcessCrashed:
case PrerenderFinalStatus::kPrimaryMainFrameRendererProcessKilled:
case PrerenderFinalStatus::kActivationFramePolicyNotCompatible:
case PrerenderFinalStatus::kPreloadingDisabled:
case PrerenderFinalStatus::kBatterySaverEnabled:
case PrerenderFinalStatus::kActivatedDuringMainFrameNavigation:
case PrerenderFinalStatus::kPreloadingUnsupportedByWebContents:
case PrerenderFinalStatus::
kSameSiteCrossOriginNavigationNotOptInInMainFrameNavigation:
case PrerenderFinalStatus::
kSameSiteCrossOriginRedirectNotOptInInMainFrameNavigation:
case PrerenderFinalStatus::kCrossSiteNavigationInMainFrameNavigation:
case PrerenderFinalStatus::kCrossSiteRedirectInMainFrameNavigation:
case PrerenderFinalStatus::kMemoryPressureOnTrigger:
case PrerenderFinalStatus::kMemoryPressureAfterTriggered:
case PrerenderFinalStatus::kPrerenderingDisabledByDevTools:
case PrerenderFinalStatus::kActivatedWithAuxiliaryBrowsingContexts:
case PrerenderFinalStatus::kMaxNumOfRunningImmediatePrerendersExceeded:
case PrerenderFinalStatus::kMaxNumOfRunningNonImmediatePrerendersExceeded:
case PrerenderFinalStatus::kMaxNumOfRunningEmbedderPrerendersExceeded:
case PrerenderFinalStatus::kPrerenderingUrlHasEffectiveUrl:
case PrerenderFinalStatus::kRedirectedPrerenderingUrlHasEffectiveUrl:
case PrerenderFinalStatus::kActivationUrlHasEffectiveUrl:
case PrerenderFinalStatus::kJavaScriptInterfaceAdded:
case PrerenderFinalStatus::kJavaScriptInterfaceRemoved:
case PrerenderFinalStatus::kAllPrerenderingCanceled:
case PrerenderFinalStatus::kWindowClosed:
case PrerenderFinalStatus::kSlowNetwork:
case PrerenderFinalStatus::kPrerenderFailedDuringPrefetch:
case PrerenderFinalStatus::kBrowsingDataRemoved:
if (attempt_) {
attempt_->SetFailureReason(
ToPreloadingFailureReason(reason.final_status()));
attempt_.reset();
}
if (devtools_attempt_) {
devtools_attempt_->SetFailureReason(attributes_, reason);
devtools_attempt_.reset();
}
return;
case PrerenderFinalStatus::kActivated:
NOTREACHED();
}
}
std::optional<UrlMatchType> PrerenderHost::IsUrlMatch(const GURL& url) const {
if (!url::IsSameOriginWith(attributes_.prerendering_url, url)) {
return std::nullopt;
}
std::optional<UrlMatchType> result;
if (GetInitialUrl() == url) {
result = UrlMatchType::kExact;
}
if (!result && no_vary_search_.has_value() &&
no_vary_search_->AreEquivalent(GetInitialUrl(), url)) {
result = UrlMatchType::kNoVarySearch;
}
if (!attributes_.url_match_predicate) {
return result;
}
if (attributes_.url_match_predicate.Run(url, result)) {
return UrlMatchType::kURLPredicateMatch;
}
return std::nullopt;
}
bool PrerenderHost::IsNoVarySearchHintUrlMatch(const GURL& url) const {
if (!url::IsSameOriginWith(attributes_.prerendering_url, url)) {
return false;
}
if (attributes_.url_match_predicate) {
return false;
}
if (GetInitialUrl() == url) {
return false;
}
if (!were_headers_received()) {
if (no_vary_search_hint().has_value() &&
no_vary_search_hint()->AreEquivalent(GetInitialUrl(), url)) {
return true;
}
}
return false;
}
bool PrerenderHost::IsUrlSameOrigin(const GURL& url) const {
return url::IsSameOriginWith(GetInitialUrl(), url);
}
bool PrerenderHost::IsUrlSameSite(const GURL& url) const {
return prerender_navigation_utils::IsSameSite(
url, url::Origin::Create(GetInitialUrl()));
}
void PrerenderHost::OnAcceptClientHintChanged(
const url::Origin& origin,
const std::vector<network::mojom::WebClientHintsType>& client_hints_type) {
client_hints_type_[origin] = client_hints_type;
}
void PrerenderHost::GetAllowedClientHintsOnPage(
const url::Origin& origin,
blink::EnabledClientHints* client_hints) const {
auto it = client_hints_type_.find(origin);
if (it == client_hints_type_.end()) {
return;
}
for (const auto& hint : it->second) {
client_hints->SetIsEnabled(hint, true);
}
}
std::string PrerenderHost::GetHistogramSuffix() const {
return metric_suffix_;
}
void PrerenderHost::Cancel(PrerenderFinalStatus status) {
TRACE_EVENT("navigation", "PrerenderHost::Cancel", "final_status", status);
if (final_status_) {
return;
}
RenderFrameHostImpl* host = PrerenderHost::GetPrerenderedMainFrameHost();
CHECK(host);
PrerenderHostRegistry* registry =
host->delegate()->GetPrerenderHostRegistry();
CHECK(registry);
registry->CancelHost(frame_tree_node_id_, status);
}
void PrerenderHost::MaybeSetNoVarySearch(
network::mojom::NoVarySearchWithParseError&
no_vary_search_with_parse_error) {
CHECK(!no_vary_search_);
CHECK(!no_vary_search_parse_error_);
if (no_vary_search_with_parse_error.is_parse_error()) {
no_vary_search_parse_error_ =
no_vary_search_with_parse_error.get_parse_error();
return;
}
CHECK(no_vary_search_with_parse_error.is_no_vary_search());
net::HttpNoVarySearchData no_vary_search =
no_vary_search::ParseHttpNoVarySearchDataFromMojom(
no_vary_search_with_parse_error.get_no_vary_search());
if (attempt_) {
static_cast<PreloadingAttemptImpl*>(attempt_.get())
->SetNoVarySearchMatchPredicate(base::BindRepeating(
[](net::HttpNoVarySearchData no_vary_search, const GURL& a,
const GURL& b) { return no_vary_search.AreEquivalent(a, b); },
no_vary_search, GetInitialUrl()));
}
no_vary_search_ = std::move(no_vary_search);
}
bool PrerenderHost::IsInitialNavigation(
const NavigationRequest& navigation_request) const {
return GetInitialNavigationId() == navigation_request.GetNavigationId();
}
base::TimeDelta PrerenderHost::WaitUntilHeadTimeout() {
int timeout_in_milliseconds = 0;
if (IsSpeculationRuleType(attributes_.trigger_type)) {
CHECK(eagerness().has_value());
switch (eagerness().value()) {
case blink::mojom::SpeculationEagerness::kImmediate:
case blink::mojom::SpeculationEagerness::kEager:
timeout_in_milliseconds =
features::kPrerender2NoVarySearchWaitForHeadersTimeoutEagerPrerender
.Get();
break;
case blink::mojom::SpeculationEagerness::kModerate:
timeout_in_milliseconds =
features::
kPrerender2NoVarySearchWaitForHeadersTimeoutModeratePrerender
.Get();
break;
case blink::mojom::SpeculationEagerness::kConservative:
timeout_in_milliseconds =
features::
kPrerender2NoVarySearchWaitForHeadersTimeoutConservativePrerender
.Get();
break;
}
} else {
timeout_in_milliseconds =
features::kPrerender2NoVarySearchWaitForHeadersTimeoutForEmbedders
.Get();
}
return base::Milliseconds(timeout_in_milliseconds);
}
void PrerenderHost::OnWaitingForHeadersStarted(
NavigationHandle& navigation_handle,
WaitingForHeadersStartedReason reason) {
CHECK(GetFrameTree());
for (auto& observer : observers_) {
observer.OnWaitingForHeadersStarted(navigation_handle, reason);
}
}
void PrerenderHost::OnWaitingForHeadersFinished(
WaitingForHeadersFinishedReason reason) {
CHECK(GetFrameTree());
base::UmaHistogramEnumeration(
"Prerender.Experimental.WaitingForHeadersFinishedReason" +
GetHistogramSuffix(),
reason);
for (auto& observer : observers_) {
observer.OnWaitingForHeadersFinished(reason);
}
}
bool PrerenderHost::ShouldAbortNavigationBecausePrefetchUnavailable() const {
CHECK(features::UsePrefetchPrerenderIntegration());
auto is_prefetch_used =
[](const std::optional<PrefetchStatus>& prefetch_status) -> bool {
if (!prefetch_status.has_value()) {
return false;
}
switch (prefetch_status.value()) {
case PrefetchStatus::kPrefetchResponseUsed:
return true;
case PrefetchStatus::kPrefetchNotUsedProbeFailed:
case PrefetchStatus::kPrefetchNotStarted:
case PrefetchStatus::kPrefetchIneligibleUserHasCookies:
case PrefetchStatus::kPrefetchIneligibleUserHasServiceWorker:
case PrefetchStatus::
kPrefetchIneligibleUserHasServiceWorkerNoFetchHandler:
case PrefetchStatus::kPrefetchIneligibleRedirectFromServiceWorker:
case PrefetchStatus::kPrefetchIneligibleRedirectToServiceWorker:
case PrefetchStatus::kPrefetchIneligibleSchemeIsNotHttps:
case PrefetchStatus::kPrefetchIneligibleNonDefaultStoragePartition:
case PrefetchStatus::kPrefetchNotFinishedInTime:
case PrefetchStatus::kPrefetchFailedNetError:
case PrefetchStatus::kPrefetchFailedNon2XX:
case PrefetchStatus::kPrefetchFailedMIMENotSupported:
case PrefetchStatus::kPrefetchSuccessful:
case PrefetchStatus::kPrefetchIneligibleRetryAfter:
case PrefetchStatus::kPrefetchIneligiblePrefetchProxyNotAvailable:
case PrefetchStatus::kPrefetchIsPrivacyDecoy:
case PrefetchStatus::kPrefetchIsStale:
case PrefetchStatus::kPrefetchNotUsedCookiesChanged:
case PrefetchStatus::kPrefetchIneligibleHostIsNonUnique:
case PrefetchStatus::kPrefetchIneligibleDataSaverEnabled:
case PrefetchStatus::kPrefetchIneligibleExistingProxy:
case PrefetchStatus::kPrefetchHeldback:
case PrefetchStatus::kPrefetchFailedInvalidRedirect:
case PrefetchStatus::kPrefetchFailedIneligibleRedirect:
case PrefetchStatus::
kPrefetchIneligibleSameSiteCrossOriginPrefetchRequiredProxy:
case PrefetchStatus::kPrefetchIneligibleBatterySaverEnabled:
case PrefetchStatus::kPrefetchIneligiblePreloadingDisabled:
case PrefetchStatus::kPrefetchEvictedAfterCandidateRemoved:
case PrefetchStatus::kPrefetchEvictedForNewerPrefetch:
case PrefetchStatus::kPrefetchEvictedAfterBrowsingDataRemoved:
return false;
}
};
auto is_ineligibility_admissible =
[](PreloadingEligibility prefetch_eligibility) -> bool {
switch (prefetch_eligibility) {
case PreloadingEligibility::kUserHasServiceWorker:
case PreloadingEligibility::kUserHasServiceWorkerNoFetchHandler:
case PreloadingEligibility::kRedirectFromServiceWorker:
case PreloadingEligibility::kRedirectToServiceWorker:
case PreloadingEligibility::kSchemeIsNotHttps:
return true;
case PreloadingEligibility::kEligible:
case PreloadingEligibility::kUnspecified:
case PreloadingEligibility::kPreloadingDisabled:
case PreloadingEligibility::kHidden:
case PreloadingEligibility::kCrossOrigin:
case PreloadingEligibility::kLowMemory:
case PreloadingEligibility::kJavascriptDisabled:
case PreloadingEligibility::kDataSaverEnabled:
case PreloadingEligibility::kHasEffectiveUrl:
case PreloadingEligibility::kSingleProcess:
case PreloadingEligibility::kLinkRelNext:
case PreloadingEligibility::kThirdPartyCookies:
case PreloadingEligibility::kPreloadingInvokedWithinTimelimit:
case PreloadingEligibility::kRendererProcessLimitExceeded:
case PreloadingEligibility::kBatterySaverEnabled:
case PreloadingEligibility::kPreloadingUnsupportedByWebContents:
case PreloadingEligibility::kMemoryPressure:
case PreloadingEligibility::kPreloadingDisabledByDevTools:
case PreloadingEligibility::kHttpsOnly:
case PreloadingEligibility::kHttpOrHttpsOnly:
case PreloadingEligibility::kSlowNetwork:
case PreloadingEligibility::kUserHasCookies:
case PreloadingEligibility::kNonDefaultStoragePartition:
case PreloadingEligibility::kRetryAfter:
case PreloadingEligibility::kPrefetchProxyNotAvailable:
case PreloadingEligibility::kHostIsNonUnique:
case PreloadingEligibility::kExistingProxy:
case PreloadingEligibility::kSameSiteCrossOriginPrefetchRequiredProxy:
case PreloadingEligibility::kPreloadingEligibilityContentEnd:
case PreloadingEligibility::kPreloadingEligibilityContentStart2:
case PreloadingEligibility::kPreloadingEligibilityContentEnd2:
return false;
}
};
auto nav_is_likely_handled_by_earlier_interceptor =
[](PreloadingEligibility prefetch_eligibility) -> bool {
return prefetch_eligibility == PreloadingEligibility::kUnspecified;
};
if (is_prefetch_used(attributes_.preload_pipeline_info->prefetch_status())) {
return false;
}
if (is_ineligibility_admissible(
attributes_.preload_pipeline_info->prefetch_eligibility())) {
return false;
}
if (nav_is_likely_handled_by_earlier_interceptor(
attributes_.preload_pipeline_info->prefetch_eligibility())) {
return false;
}
return true;
}
void PrerenderHost::AddAdditionalRequestHeaders(
net::HttpRequestHeaders& headers,
FrameTreeNode& navigating_frame_tree_node) {
CHECK_EQ(&navigating_frame_tree_node.frame_tree(), &GetPrerenderFrameTree());
headers.SetHeader(blink::kSecPurposeHeaderName,
blink::kSecPurposePrefetchPrerenderHeaderValue);
if (!base::FeatureList::IsEnabled(
blink::features::kRemovePurposeHeaderForPrefetch)) {
headers.SetHeader(blink::kPurposeHeaderName,
blink::kSecPurposePrefetchHeaderValue);
}
std::optional<SpeculationRulesTags> tags = attributes_.GetTags();
if (navigating_frame_tree_node.IsMainFrame() &&
!GetInitialNavigationId().has_value() && tags.has_value()) {
headers.SetHeader(blink::kSecSpeculationTagsHeaderName,
tags->ConvertStringToHeaderString().value());
}
}
void PrerenderHost::NotifyReused() {
for (auto& observer : observers_) {
observer.OnHostReused();
}
}
void PrerenderHost::OnWillBeCancelled(
const PrerenderCancellationReason& reason) {
if (!PreloadServingMetricsCapsule::IsFeatureEnabled()) {
return;
}
[&]() {
if (prerender_initial_preload_serving_metrics_) {
return;
}
auto* frame_tree_node =
FrameTreeNode::GloballyFindByID(frame_tree_node_id_);
if (!frame_tree_node) {
return;
}
NavigationRequest* navigation_request =
frame_tree_node->navigation_request();
if (!navigation_request) {
return;
}
auto& initial_preload_serving_metrics_holder =
*PreloadServingMetricsHolder::GetOrCreateForNavigationHandle(
*navigation_request);
prerender_initial_preload_serving_metrics_ =
initial_preload_serving_metrics_holder.Take();
}();
if (prerender_initial_preload_serving_metrics_) {
prerender_initial_preload_serving_metrics_
->RecordMetricsForPrerenderInitialNavigationFailed();
}
}
bool PrerenderHost::IsInitiatorOverridingUserAgent() {
NavigationEntry* last_entry = nullptr;
if (initiator_frame_tree_node_id()) {
last_entry = FrameTreeNode::GloballyFindByID(initiator_frame_tree_node_id())
->frame_tree()
.controller()
.GetLastCommittedEntry();
} else if (initiator_web_contents()) {
last_entry = initiator_web_contents()
->GetPrimaryMainFrame()
->GetController()
.GetLastCommittedEntry();
}
return last_entry && last_entry->GetIsOverridingUserAgent();
}
base::WeakPtr<PrerenderHost> PrerenderHost::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
}