| // 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 |