Avi Drissman | 4e1b7bc3 | 2022-09-15 14:03:50 | [diff] [blame] | 1 | // Copyright 2021 The Chromium Authors |
Robert Lin | c37fb58 | 2021-11-11 03:18:47 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
Sreeja Kamishetty | c227f7a | 2022-07-08 22:33:15 | [diff] [blame] | 5 | #include "content/browser/preloading/prerender/prerender_handle_impl.h" |
Robert Lin | c37fb58 | 2021-11-11 03:18:47 | [diff] [blame] | 6 | |
Hiroki Nakagawa | 56dbf147 | 2025-01-29 08:40:38 | [diff] [blame] | 7 | #include <limits> |
| 8 | |
Yoshiki Tanioka | 49b4cfb | 2022-10-20 09:25:31 | [diff] [blame] | 9 | #include "content/browser/preloading/prerender/prerender_final_status.h" |
Lei Zhang | 5686e52 | 2023-03-02 17:33:10 | [diff] [blame] | 10 | #include "content/browser/preloading/prerender/prerender_host.h" |
| 11 | #include "content/browser/preloading/prerender/prerender_host_registry.h" |
Hiroshige Hayashizaki | 583b496 | 2025-08-19 18:58:26 | [diff] [blame] | 12 | #include "content/public/browser/preloading_data.h" |
Kouhei Ueno | 3f37992b | 2023-11-09 23:29:02 | [diff] [blame] | 13 | #include "content/public/browser/preloading_trigger_type.h" |
Lingqi Chi | 60cff325 | 2023-12-05 02:32:55 | [diff] [blame] | 14 | #include "url/gurl.h" |
Asami Doi | 158ffb42 | 2022-01-26 08:43:05 | [diff] [blame] | 15 | |
Robert Lin | c37fb58 | 2021-11-11 03:18:47 | [diff] [blame] | 16 | namespace content { |
| 17 | |
Hiroki Nakagawa | 86e06d3a | 2025-01-22 04:51:42 | [diff] [blame] | 18 | namespace { |
| 19 | |
Hiroki Nakagawa | 56dbf147 | 2025-01-29 08:40:38 | [diff] [blame] | 20 | int32_t GetNextHandleId() { |
| 21 | static int32_t next_handle_id = 1; |
| 22 | CHECK_LT(next_handle_id, std::numeric_limits<int32_t>::max()); |
| 23 | return next_handle_id++; |
| 24 | } |
| 25 | |
Hiroki Nakagawa | 86e06d3a | 2025-01-22 04:51:42 | [diff] [blame] | 26 | // Returns true when the error callback should be fired. The callback does not |
| 27 | // need to be fired when prerendering succeed but is never activated, or it is |
| 28 | // intentinally cancelled by an embedder (e.g., calling the cancellation API). |
| 29 | // Otherwise, the callback should be fired. |
| 30 | bool ShouldFireErrorCallback(PrerenderFinalStatus status) { |
| 31 | switch (status) { |
| 32 | case PrerenderFinalStatus::kActivated: |
| 33 | NOTREACHED(); |
| 34 | |
| 35 | // Prerendering is not activated. |
| 36 | case PrerenderFinalStatus::kDestroyed: |
| 37 | return false; |
| 38 | |
| 39 | case PrerenderFinalStatus::kLowEndDevice: |
| 40 | case PrerenderFinalStatus::kInvalidSchemeRedirect: |
| 41 | case PrerenderFinalStatus::kInvalidSchemeNavigation: |
| 42 | case PrerenderFinalStatus::kNavigationRequestBlockedByCsp: |
Hiroki Nakagawa | 86e06d3a | 2025-01-22 04:51:42 | [diff] [blame] | 43 | case PrerenderFinalStatus::kMojoBinderPolicy: |
| 44 | case PrerenderFinalStatus::kRendererProcessCrashed: |
| 45 | case PrerenderFinalStatus::kRendererProcessKilled: |
| 46 | case PrerenderFinalStatus::kDownload: |
| 47 | return true; |
| 48 | |
| 49 | // Prerendering is intentionally cancelled by the app or not activated. |
| 50 | case PrerenderFinalStatus::kTriggerDestroyed: |
| 51 | return false; |
| 52 | |
| 53 | case PrerenderFinalStatus::kNavigationNotCommitted: |
| 54 | case PrerenderFinalStatus::kNavigationBadHttpStatus: |
| 55 | case PrerenderFinalStatus::kClientCertRequested: |
| 56 | case PrerenderFinalStatus::kNavigationRequestNetworkError: |
| 57 | case PrerenderFinalStatus::kCancelAllHostsForTesting: |
| 58 | case PrerenderFinalStatus::kDidFailLoad: |
| 59 | case PrerenderFinalStatus::kStop: |
| 60 | case PrerenderFinalStatus::kSslCertificateError: |
| 61 | case PrerenderFinalStatus::kLoginAuthRequested: |
| 62 | case PrerenderFinalStatus::kUaChangeRequiresReload: |
| 63 | case PrerenderFinalStatus::kBlockedByClient: |
| 64 | case PrerenderFinalStatus::kMixedContent: |
| 65 | case PrerenderFinalStatus::kTriggerBackgrounded: |
| 66 | case PrerenderFinalStatus::kMemoryLimitExceeded: |
| 67 | case PrerenderFinalStatus::kDataSaverEnabled: |
| 68 | case PrerenderFinalStatus::kTriggerUrlHasEffectiveUrl: |
| 69 | case PrerenderFinalStatus::kActivatedBeforeStarted: |
| 70 | case PrerenderFinalStatus::kInactivePageRestriction: |
| 71 | case PrerenderFinalStatus::kStartFailed: |
| 72 | case PrerenderFinalStatus::kTimeoutBackgrounded: |
| 73 | case PrerenderFinalStatus::kCrossSiteRedirectInInitialNavigation: |
| 74 | case PrerenderFinalStatus::kCrossSiteNavigationInInitialNavigation: |
| 75 | case PrerenderFinalStatus:: |
| 76 | kSameSiteCrossOriginRedirectNotOptInInInitialNavigation: |
| 77 | case PrerenderFinalStatus:: |
| 78 | kSameSiteCrossOriginNavigationNotOptInInInitialNavigation: |
| 79 | case PrerenderFinalStatus::kActivationNavigationParameterMismatch: |
| 80 | case PrerenderFinalStatus::kActivatedInBackground: |
| 81 | case PrerenderFinalStatus::kActivationNavigationDestroyedBeforeSuccess: |
| 82 | return true; |
| 83 | |
| 84 | // The associated tab is closed. |
| 85 | case PrerenderFinalStatus::kTabClosedByUserGesture: |
| 86 | case PrerenderFinalStatus::kTabClosedWithoutUserGesture: |
| 87 | return false; |
| 88 | |
| 89 | case PrerenderFinalStatus::kPrimaryMainFrameRendererProcessCrashed: |
| 90 | case PrerenderFinalStatus::kPrimaryMainFrameRendererProcessKilled: |
| 91 | case PrerenderFinalStatus::kActivationFramePolicyNotCompatible: |
| 92 | case PrerenderFinalStatus::kPreloadingDisabled: |
| 93 | case PrerenderFinalStatus::kBatterySaverEnabled: |
| 94 | case PrerenderFinalStatus::kActivatedDuringMainFrameNavigation: |
| 95 | case PrerenderFinalStatus::kPreloadingUnsupportedByWebContents: |
| 96 | case PrerenderFinalStatus::kCrossSiteRedirectInMainFrameNavigation: |
| 97 | case PrerenderFinalStatus::kCrossSiteNavigationInMainFrameNavigation: |
| 98 | case PrerenderFinalStatus:: |
| 99 | kSameSiteCrossOriginRedirectNotOptInInMainFrameNavigation: |
| 100 | case PrerenderFinalStatus:: |
| 101 | kSameSiteCrossOriginNavigationNotOptInInMainFrameNavigation: |
| 102 | case PrerenderFinalStatus::kMemoryPressureOnTrigger: |
| 103 | case PrerenderFinalStatus::kMemoryPressureAfterTriggered: |
| 104 | case PrerenderFinalStatus::kPrerenderingDisabledByDevTools: |
| 105 | return true; |
| 106 | |
| 107 | // This is used for speculation rules, not for embedder triggers. |
| 108 | case PrerenderFinalStatus::kSpeculationRuleRemoved: |
| 109 | NOTREACHED(); |
| 110 | |
| 111 | case PrerenderFinalStatus::kActivatedWithAuxiliaryBrowsingContexts: |
| 112 | return true; |
| 113 | |
| 114 | // These are used for speculation rules, not for embedder triggers. |
Takashi Nakayama | 978f0a15 | 2025-06-17 08:26:25 | [diff] [blame] | 115 | case PrerenderFinalStatus::kMaxNumOfRunningImmediatePrerendersExceeded: |
| 116 | case PrerenderFinalStatus::kMaxNumOfRunningNonImmediatePrerendersExceeded: |
Hiroki Nakagawa | 86e06d3a | 2025-01-22 04:51:42 | [diff] [blame] | 117 | NOTREACHED(); |
| 118 | |
| 119 | case PrerenderFinalStatus::kMaxNumOfRunningEmbedderPrerendersExceeded: |
| 120 | case PrerenderFinalStatus::kPrerenderingUrlHasEffectiveUrl: |
| 121 | case PrerenderFinalStatus::kRedirectedPrerenderingUrlHasEffectiveUrl: |
| 122 | case PrerenderFinalStatus::kActivationUrlHasEffectiveUrl: |
| 123 | case PrerenderFinalStatus::kJavaScriptInterfaceAdded: |
| 124 | case PrerenderFinalStatus::kJavaScriptInterfaceRemoved: |
| 125 | case PrerenderFinalStatus::kAllPrerenderingCanceled: |
| 126 | return true; |
| 127 | |
| 128 | // window.close() is called in a prerendered page. |
| 129 | case PrerenderFinalStatus::kWindowClosed: |
| 130 | return false; |
| 131 | |
| 132 | case PrerenderFinalStatus::kSlowNetwork: |
| 133 | return true; |
| 134 | |
| 135 | case PrerenderFinalStatus::kOtherPrerenderedPageActivated: |
| 136 | return false; |
| 137 | |
Hiroki Nakagawa | 86e06d3a | 2025-01-22 04:51:42 | [diff] [blame] | 138 | case PrerenderFinalStatus::kPrerenderFailedDuringPrefetch: |
| 139 | return true; |
Steven Wei | 893cb02c | 2025-02-12 20:51:30 | [diff] [blame] | 140 | |
| 141 | // Prerendering is intentionally canceled by the Delete Browsing Data |
| 142 | // option or with Clear-Site-Data response headers. |
| 143 | case PrerenderFinalStatus::kBrowsingDataRemoved: |
| 144 | return false; |
Jiacheng Guo | cfca196d | 2025-07-30 02:13:20 | [diff] [blame] | 145 | // The PrerenderHost is reused by another prerender request. |
| 146 | case PrerenderFinalStatus::kPrerenderHostReused: |
| 147 | return false; |
Hiroki Nakagawa | 86e06d3a | 2025-01-22 04:51:42 | [diff] [blame] | 148 | } |
| 149 | } |
| 150 | |
| 151 | } // namespace |
| 152 | |
Robert Lin | c37fb58 | 2021-11-11 03:18:47 | [diff] [blame] | 153 | PrerenderHandleImpl::PrerenderHandleImpl( |
| 154 | base::WeakPtr<PrerenderHostRegistry> prerender_host_registry, |
Avi Drissman | dcc8e68 | 2024-09-04 14:14:48 | [diff] [blame] | 155 | FrameTreeNodeId frame_tree_node_id, |
Hiroki Nakagawa | e628b45 | 2025-03-29 13:08:06 | [diff] [blame] | 156 | const GURL& prerendering_url, |
| 157 | std::optional<net::HttpNoVarySearchData> no_vary_search_hint) |
Hiroki Nakagawa | 56dbf147 | 2025-01-29 08:40:38 | [diff] [blame] | 158 | : handle_id_(GetNextHandleId()), |
| 159 | prerender_host_registry_(std::move(prerender_host_registry)), |
Robert Lin | 624a0fe | 2021-12-08 22:58:30 | [diff] [blame] | 160 | frame_tree_node_id_(frame_tree_node_id), |
Hiroki Nakagawa | e628b45 | 2025-03-29 13:08:06 | [diff] [blame] | 161 | prerendering_url_(prerendering_url), |
| 162 | no_vary_search_hint_(std::move(no_vary_search_hint)) { |
Ho Cheung | 13d432c2 | 2023-03-28 08:39:42 | [diff] [blame] | 163 | CHECK(!prerendering_url_.is_empty()); |
Lingqi Chi | 9067997 | 2022-08-15 04:09:32 | [diff] [blame] | 164 | // PrerenderHandleImpl is now designed only for embedder triggers. If you use |
Asami Doi | 158ffb42 | 2022-01-26 08:43:05 | [diff] [blame] | 165 | // this handle for other triggers, please make sure to update the logging etc. |
Hiroki Nakagawa | b6b8b2e | 2024-12-04 01:24:21 | [diff] [blame] | 166 | auto* prerender_host = GetPrerenderHost(); |
Prudhvikumar Bommana | e34ac67 | 2024-01-10 18:23:44 | [diff] [blame] | 167 | CHECK(prerender_host); |
| 168 | CHECK_EQ(prerender_host->trigger_type(), PreloadingTriggerType::kEmbedder); |
Hiroki Nakagawa | b6b8b2e | 2024-12-04 01:24:21 | [diff] [blame] | 169 | prerender_host->AddObserver(this); |
Robert Lin | 624a0fe | 2021-12-08 22:58:30 | [diff] [blame] | 170 | } |
Robert Lin | c37fb58 | 2021-11-11 03:18:47 | [diff] [blame] | 171 | |
| 172 | PrerenderHandleImpl::~PrerenderHandleImpl() { |
Jiacheng Guo | 4391a96 | 2025-07-30 02:14:20 | [diff] [blame] | 173 | // GetPrerenderHost() fetches the PrerenderHost by the frame_tree_node_id_. |
| 174 | // If the underlying PrerenderHost is reused, frame_tree_node_id_ will |
| 175 | // be reset and prerender_host will be nullptr. The reused host will |
| 176 | // not be cancelled. |
Hiroki Nakagawa | b6b8b2e | 2024-12-04 01:24:21 | [diff] [blame] | 177 | PrerenderHost* prerender_host = GetPrerenderHost(); |
| 178 | if (!prerender_host) { |
| 179 | return; |
Robert Lin | c37fb58 | 2021-11-11 03:18:47 | [diff] [blame] | 180 | } |
Hiroki Nakagawa | b6b8b2e | 2024-12-04 01:24:21 | [diff] [blame] | 181 | prerender_host->RemoveObserver(this); |
| 182 | |
| 183 | prerender_host_registry_->CancelHost(frame_tree_node_id_, |
| 184 | PrerenderFinalStatus::kTriggerDestroyed); |
Robert Lin | c37fb58 | 2021-11-11 03:18:47 | [diff] [blame] | 185 | } |
| 186 | |
Hiroki Nakagawa | 56dbf147 | 2025-01-29 08:40:38 | [diff] [blame] | 187 | int32_t PrerenderHandleImpl::GetHandleId() const { |
| 188 | return handle_id_; |
| 189 | } |
| 190 | |
Lingqi Chi | 60cff325 | 2023-12-05 02:32:55 | [diff] [blame] | 191 | const GURL& PrerenderHandleImpl::GetInitialPrerenderingUrl() const { |
Robert Lin | 624a0fe | 2021-12-08 22:58:30 | [diff] [blame] | 192 | return prerendering_url_; |
| 193 | } |
| 194 | |
Hiroki Nakagawa | e628b45 | 2025-03-29 13:08:06 | [diff] [blame] | 195 | const std::optional<net::HttpNoVarySearchData>& |
| 196 | PrerenderHandleImpl::GetNoVarySearchHint() const { |
| 197 | return no_vary_search_hint_; |
| 198 | } |
| 199 | |
Robert Lin | 6d825ba | 2022-02-10 03:35:40 | [diff] [blame] | 200 | base::WeakPtr<PrerenderHandle> PrerenderHandleImpl::GetWeakPtr() { |
| 201 | return weak_factory_.GetWeakPtr(); |
| 202 | } |
| 203 | |
Sreeja Kamishetty | 26e40d2 | 2022-10-17 17:54:35 | [diff] [blame] | 204 | void PrerenderHandleImpl::SetPreloadingAttemptFailureReason( |
| 205 | PreloadingFailureReason reason) { |
Hiroki Nakagawa | b6b8b2e | 2024-12-04 01:24:21 | [diff] [blame] | 206 | auto* prerender_host = GetPrerenderHost(); |
| 207 | if (!prerender_host || !prerender_host->preloading_attempt()) { |
Simon Pelchat | b8f226d | 2023-02-17 05:06:59 | [diff] [blame] | 208 | return; |
| 209 | } |
Sreeja Kamishetty | 26e40d2 | 2022-10-17 17:54:35 | [diff] [blame] | 210 | prerender_host->preloading_attempt()->SetFailureReason(reason); |
| 211 | } |
| 212 | |
Hiroki Nakagawa | 3154fcfd | 2025-01-29 03:14:13 | [diff] [blame] | 213 | void PrerenderHandleImpl::AddActivationCallback( |
Hiroki Nakagawa | b6b8b2e | 2024-12-04 01:24:21 | [diff] [blame] | 214 | base::OnceClosure activation_callback) { |
Hiroki Nakagawa | 3154fcfd | 2025-01-29 03:14:13 | [diff] [blame] | 215 | CHECK_EQ(State::kValid, state_); |
Hiroki Nakagawa | b6b8b2e | 2024-12-04 01:24:21 | [diff] [blame] | 216 | CHECK(activation_callback); |
Hiroki Nakagawa | 3154fcfd | 2025-01-29 03:14:13 | [diff] [blame] | 217 | activation_callbacks_.push_back(std::move(activation_callback)); |
Hiroki Nakagawa | b6b8b2e | 2024-12-04 01:24:21 | [diff] [blame] | 218 | } |
| 219 | |
Hiroki Nakagawa | 3154fcfd | 2025-01-29 03:14:13 | [diff] [blame] | 220 | void PrerenderHandleImpl::AddErrorCallback(base::OnceClosure error_callback) { |
| 221 | CHECK_EQ(State::kValid, state_); |
Hiroki Nakagawa | 1075a94f | 2025-01-16 12:26:53 | [diff] [blame] | 222 | CHECK(error_callback); |
Hiroki Nakagawa | 3154fcfd | 2025-01-29 03:14:13 | [diff] [blame] | 223 | error_callbacks_.push_back(std::move(error_callback)); |
| 224 | } |
| 225 | |
| 226 | bool PrerenderHandleImpl::IsValid() const { |
| 227 | switch (state_) { |
| 228 | case State::kValid: |
| 229 | return true; |
| 230 | case State::kActivated: |
| 231 | case State::kCanceled: |
| 232 | return false; |
| 233 | } |
Hiroki Nakagawa | 1075a94f | 2025-01-16 12:26:53 | [diff] [blame] | 234 | } |
| 235 | |
Hiroki Nakagawa | b6b8b2e | 2024-12-04 01:24:21 | [diff] [blame] | 236 | void PrerenderHandleImpl::OnActivated() { |
Hiroki Nakagawa | 3154fcfd | 2025-01-29 03:14:13 | [diff] [blame] | 237 | CHECK_EQ(State::kValid, state_); |
| 238 | state_ = State::kActivated; |
Hiroki Nakagawa | 1075a94f | 2025-01-16 12:26:53 | [diff] [blame] | 239 | |
| 240 | // An error should not be reported after activation. |
Hiroki Nakagawa | 3154fcfd | 2025-01-29 03:14:13 | [diff] [blame] | 241 | error_callbacks_.clear(); |
Hiroki Nakagawa | 1075a94f | 2025-01-16 12:26:53 | [diff] [blame] | 242 | |
Hiroki Nakagawa | 3154fcfd | 2025-01-29 03:14:13 | [diff] [blame] | 243 | std::vector<base::OnceClosure> callbacks; |
| 244 | callbacks.swap(activation_callbacks_); |
| 245 | // Don't touch `this` after this line, as a callback could destroy `this`. |
| 246 | for (auto& callback : callbacks) { |
| 247 | std::move(callback).Run(); |
Hiroki Nakagawa | b6b8b2e | 2024-12-04 01:24:21 | [diff] [blame] | 248 | } |
| 249 | } |
| 250 | |
Hiroki Nakagawa | 1075a94f | 2025-01-16 12:26:53 | [diff] [blame] | 251 | void PrerenderHandleImpl::OnFailed(PrerenderFinalStatus status) { |
Hiroki Nakagawa | 3154fcfd | 2025-01-29 03:14:13 | [diff] [blame] | 252 | CHECK_EQ(State::kValid, state_); |
| 253 | state_ = State::kCanceled; |
| 254 | |
| 255 | // An activation never happen after cancellation. |
| 256 | activation_callbacks_.clear(); |
Hiroki Nakagawa | 1075a94f | 2025-01-16 12:26:53 | [diff] [blame] | 257 | |
Hiroki Nakagawa | 86e06d3a | 2025-01-22 04:51:42 | [diff] [blame] | 258 | if (!ShouldFireErrorCallback(status)) { |
Hiroki Nakagawa | 3154fcfd | 2025-01-29 03:14:13 | [diff] [blame] | 259 | error_callbacks_.clear(); |
Hiroki Nakagawa | 86e06d3a | 2025-01-22 04:51:42 | [diff] [blame] | 260 | return; |
| 261 | } |
Hiroki Nakagawa | 1075a94f | 2025-01-16 12:26:53 | [diff] [blame] | 262 | |
| 263 | // TODO(crbug.com/41490450): Pass a cancellation reason to the callback. |
| 264 | // Note that we should not expose detailed reasons to prevent embedders from |
| 265 | // depending on them. Such an implicit contract with embedders would impair |
| 266 | // flexibility of internal implementation. |
Hiroki Nakagawa | 3154fcfd | 2025-01-29 03:14:13 | [diff] [blame] | 267 | std::vector<base::OnceClosure> callbacks; |
| 268 | callbacks.swap(error_callbacks_); |
| 269 | // Don't touch `this` after this line, as a callback could destroy `this`. |
| 270 | for (auto& callback : callbacks) { |
| 271 | std::move(callback).Run(); |
| 272 | } |
Hiroki Nakagawa | 1075a94f | 2025-01-16 12:26:53 | [diff] [blame] | 273 | } |
| 274 | |
Jiacheng Guo | 4391a96 | 2025-07-30 02:14:20 | [diff] [blame] | 275 | void PrerenderHandleImpl::OnHostReused() { |
| 276 | // Since the frame_tree_node_id_ is reused by the new PrerenderHost, we will |
| 277 | // stop tracking the FrameTree and reset frame_tree_node_id_. |
| 278 | // TODO(crbug.com/434826191): Add a new unique identifier for the |
| 279 | // PrerenderHost. |
| 280 | frame_tree_node_id_ = FrameTreeNodeId(); |
| 281 | } |
| 282 | |
Hiroki Nakagawa | b6b8b2e | 2024-12-04 01:24:21 | [diff] [blame] | 283 | PrerenderHost* PrerenderHandleImpl::GetPrerenderHost() { |
| 284 | auto* prerender_frame_tree_node = |
| 285 | FrameTreeNode::GloballyFindByID(frame_tree_node_id_); |
| 286 | if (!prerender_frame_tree_node) { |
| 287 | return nullptr; |
| 288 | } |
| 289 | PrerenderHost& prerender_host = |
| 290 | PrerenderHost::GetFromFrameTreeNode(*prerender_frame_tree_node); |
| 291 | return &prerender_host; |
| 292 | } |
| 293 | |
Robert Lin | c37fb58 | 2021-11-11 03:18:47 | [diff] [blame] | 294 | } // namespace content |