blob: e93f446c470d1950066cfecb371cf2241782841c [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/preloading/prerender/prerender_handle_impl.h"
#include <limits>
#include "content/browser/preloading/prerender/prerender_final_status.h"
#include "content/browser/preloading/prerender/prerender_host.h"
#include "content/browser/preloading/prerender/prerender_host_registry.h"
#include "content/public/browser/preloading_trigger_type.h"
#include "url/gurl.h"
namespace content {
namespace {
int32_t GetNextHandleId() {
static int32_t next_handle_id = 1;
CHECK_LT(next_handle_id, std::numeric_limits<int32_t>::max());
return next_handle_id++;
}
// Returns true when the error callback should be fired. The callback does not
// need to be fired when prerendering succeed but is never activated, or it is
// intentinally cancelled by an embedder (e.g., calling the cancellation API).
// Otherwise, the callback should be fired.
bool ShouldFireErrorCallback(PrerenderFinalStatus status) {
switch (status) {
case PrerenderFinalStatus::kActivated:
NOTREACHED();
// Prerendering is not activated.
case PrerenderFinalStatus::kDestroyed:
return false;
case PrerenderFinalStatus::kLowEndDevice:
case PrerenderFinalStatus::kInvalidSchemeRedirect:
case PrerenderFinalStatus::kInvalidSchemeNavigation:
case PrerenderFinalStatus::kNavigationRequestBlockedByCsp:
case PrerenderFinalStatus::kMainFrameNavigation:
case PrerenderFinalStatus::kMojoBinderPolicy:
case PrerenderFinalStatus::kRendererProcessCrashed:
case PrerenderFinalStatus::kRendererProcessKilled:
case PrerenderFinalStatus::kDownload:
return true;
// Prerendering is intentionally cancelled by the app or not activated.
case PrerenderFinalStatus::kTriggerDestroyed:
return false;
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::kActivatedBeforeStarted:
case PrerenderFinalStatus::kInactivePageRestriction:
case PrerenderFinalStatus::kStartFailed:
case PrerenderFinalStatus::kTimeoutBackgrounded:
case PrerenderFinalStatus::kCrossSiteRedirectInInitialNavigation:
case PrerenderFinalStatus::kCrossSiteNavigationInInitialNavigation:
case PrerenderFinalStatus::
kSameSiteCrossOriginRedirectNotOptInInInitialNavigation:
case PrerenderFinalStatus::
kSameSiteCrossOriginNavigationNotOptInInInitialNavigation:
case PrerenderFinalStatus::kActivationNavigationParameterMismatch:
case PrerenderFinalStatus::kActivatedInBackground:
case PrerenderFinalStatus::kActivationNavigationDestroyedBeforeSuccess:
return true;
// The associated tab is closed.
case PrerenderFinalStatus::kTabClosedByUserGesture:
case PrerenderFinalStatus::kTabClosedWithoutUserGesture:
return false;
case PrerenderFinalStatus::kPrimaryMainFrameRendererProcessCrashed:
case PrerenderFinalStatus::kPrimaryMainFrameRendererProcessKilled:
case PrerenderFinalStatus::kActivationFramePolicyNotCompatible:
case PrerenderFinalStatus::kPreloadingDisabled:
case PrerenderFinalStatus::kBatterySaverEnabled:
case PrerenderFinalStatus::kActivatedDuringMainFrameNavigation:
case PrerenderFinalStatus::kPreloadingUnsupportedByWebContents:
case PrerenderFinalStatus::kCrossSiteRedirectInMainFrameNavigation:
case PrerenderFinalStatus::kCrossSiteNavigationInMainFrameNavigation:
case PrerenderFinalStatus::
kSameSiteCrossOriginRedirectNotOptInInMainFrameNavigation:
case PrerenderFinalStatus::
kSameSiteCrossOriginNavigationNotOptInInMainFrameNavigation:
case PrerenderFinalStatus::kMemoryPressureOnTrigger:
case PrerenderFinalStatus::kMemoryPressureAfterTriggered:
case PrerenderFinalStatus::kPrerenderingDisabledByDevTools:
return true;
// This is used for speculation rules, not for embedder triggers.
case PrerenderFinalStatus::kSpeculationRuleRemoved:
NOTREACHED();
case PrerenderFinalStatus::kActivatedWithAuxiliaryBrowsingContexts:
return true;
// These are used for speculation rules, not for embedder triggers.
case PrerenderFinalStatus::kMaxNumOfRunningEagerPrerendersExceeded:
case PrerenderFinalStatus::kMaxNumOfRunningNonEagerPrerendersExceeded:
NOTREACHED();
case PrerenderFinalStatus::kMaxNumOfRunningEmbedderPrerendersExceeded:
case PrerenderFinalStatus::kPrerenderingUrlHasEffectiveUrl:
case PrerenderFinalStatus::kRedirectedPrerenderingUrlHasEffectiveUrl:
case PrerenderFinalStatus::kActivationUrlHasEffectiveUrl:
case PrerenderFinalStatus::kJavaScriptInterfaceAdded:
case PrerenderFinalStatus::kJavaScriptInterfaceRemoved:
case PrerenderFinalStatus::kAllPrerenderingCanceled:
return true;
// window.close() is called in a prerendered page.
case PrerenderFinalStatus::kWindowClosed:
return false;
case PrerenderFinalStatus::kSlowNetwork:
return true;
case PrerenderFinalStatus::kOtherPrerenderedPageActivated:
return false;
case PrerenderFinalStatus::kPrerenderFailedDuringPrefetch:
return true;
// Prerendering is intentionally canceled by the Delete Browsing Data
// option or with Clear-Site-Data response headers.
case PrerenderFinalStatus::kBrowsingDataRemoved:
return false;
}
}
} // namespace
PrerenderHandleImpl::PrerenderHandleImpl(
base::WeakPtr<PrerenderHostRegistry> prerender_host_registry,
FrameTreeNodeId frame_tree_node_id,
const GURL& prerendering_url,
std::optional<net::HttpNoVarySearchData> no_vary_search_hint)
: handle_id_(GetNextHandleId()),
prerender_host_registry_(std::move(prerender_host_registry)),
frame_tree_node_id_(frame_tree_node_id),
prerendering_url_(prerendering_url),
no_vary_search_hint_(std::move(no_vary_search_hint)) {
CHECK(!prerendering_url_.is_empty());
// PrerenderHandleImpl is now designed only for embedder triggers. If you use
// this handle for other triggers, please make sure to update the logging etc.
auto* prerender_host = GetPrerenderHost();
CHECK(prerender_host);
CHECK_EQ(prerender_host->trigger_type(), PreloadingTriggerType::kEmbedder);
prerender_host->AddObserver(this);
}
PrerenderHandleImpl::~PrerenderHandleImpl() {
PrerenderHost* prerender_host = GetPrerenderHost();
if (!prerender_host) {
return;
}
prerender_host->RemoveObserver(this);
prerender_host_registry_->CancelHost(frame_tree_node_id_,
PrerenderFinalStatus::kTriggerDestroyed);
}
int32_t PrerenderHandleImpl::GetHandleId() const {
return handle_id_;
}
const GURL& PrerenderHandleImpl::GetInitialPrerenderingUrl() const {
return prerendering_url_;
}
const std::optional<net::HttpNoVarySearchData>&
PrerenderHandleImpl::GetNoVarySearchHint() const {
return no_vary_search_hint_;
}
base::WeakPtr<PrerenderHandle> PrerenderHandleImpl::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
void PrerenderHandleImpl::SetPreloadingAttemptFailureReason(
PreloadingFailureReason reason) {
auto* prerender_host = GetPrerenderHost();
if (!prerender_host || !prerender_host->preloading_attempt()) {
return;
}
prerender_host->preloading_attempt()->SetFailureReason(reason);
}
void PrerenderHandleImpl::AddActivationCallback(
base::OnceClosure activation_callback) {
CHECK_EQ(State::kValid, state_);
CHECK(activation_callback);
activation_callbacks_.push_back(std::move(activation_callback));
}
void PrerenderHandleImpl::AddErrorCallback(base::OnceClosure error_callback) {
CHECK_EQ(State::kValid, state_);
CHECK(error_callback);
error_callbacks_.push_back(std::move(error_callback));
}
bool PrerenderHandleImpl::IsValid() const {
switch (state_) {
case State::kValid:
return true;
case State::kActivated:
case State::kCanceled:
return false;
}
}
void PrerenderHandleImpl::OnActivated() {
CHECK_EQ(State::kValid, state_);
state_ = State::kActivated;
// An error should not be reported after activation.
error_callbacks_.clear();
std::vector<base::OnceClosure> callbacks;
callbacks.swap(activation_callbacks_);
// Don't touch `this` after this line, as a callback could destroy `this`.
for (auto& callback : callbacks) {
std::move(callback).Run();
}
}
void PrerenderHandleImpl::OnFailed(PrerenderFinalStatus status) {
CHECK_EQ(State::kValid, state_);
state_ = State::kCanceled;
// An activation never happen after cancellation.
activation_callbacks_.clear();
if (!ShouldFireErrorCallback(status)) {
error_callbacks_.clear();
return;
}
// TODO(crbug.com/41490450): Pass a cancellation reason to the callback.
// Note that we should not expose detailed reasons to prevent embedders from
// depending on them. Such an implicit contract with embedders would impair
// flexibility of internal implementation.
std::vector<base::OnceClosure> callbacks;
callbacks.swap(error_callbacks_);
// Don't touch `this` after this line, as a callback could destroy `this`.
for (auto& callback : callbacks) {
std::move(callback).Run();
}
}
PrerenderHost* PrerenderHandleImpl::GetPrerenderHost() {
auto* prerender_frame_tree_node =
FrameTreeNode::GloballyFindByID(frame_tree_node_id_);
if (!prerender_frame_tree_node) {
return nullptr;
}
PrerenderHost& prerender_host =
PrerenderHost::GetFromFrameTreeNode(*prerender_frame_tree_node);
return &prerender_host;
}
} // namespace content