blob: 3ba349973a267953c38335261fc452d3b5431539 [file] [log] [blame]
Iman Saboori31bed872022-12-09 15:41:311// Copyright 2022 The Chromium Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Iman Saboori31bed872022-12-09 15:41:315#include "content/browser/preloading/prerenderer_impl.h"
6
HuanPo Linc8111d52025-06-23 06:19:447#include <algorithm>
Andrew Rayskiyf65990362024-02-27 18:43:248#include <vector>
9
Tom Sepez3e425102025-07-23 12:16:2010#include "base/compiler_specific.h"
Liviu Tinta44e732aa2024-05-28 15:52:3611#include "base/feature_list.h"
Lei Zhangab09dd842025-05-17 08:06:4812#include "base/strings/stringprintf.h"
Liviu Tinta44e732aa2024-05-28 15:52:3613#include "content/browser/preloading/prefetch/no_vary_search_helper.h"
kenoss9d57ee7b2024-06-03 01:36:4314#include "content/browser/preloading/prefetch/prefetch_document_manager.h"
Iman Saboori31bed872022-12-09 15:41:3115#include "content/browser/preloading/preloading.h"
Adithya Srinivasan38e0c402023-07-19 16:12:5816#include "content/browser/preloading/preloading_attempt_impl.h"
Kevin McNee98e068a2024-04-09 20:12:3417#include "content/browser/preloading/preloading_data_impl.h"
Kouhei Ueno63293752023-11-14 07:41:5018#include "content/browser/preloading/preloading_trigger_type_impl.h"
Iman Saboori31bed872022-12-09 15:41:3119#include "content/browser/preloading/prerender/prerender_attributes.h"
Taiyo Mizuhashi295ccbdf2023-09-13 09:17:0220#include "content/browser/preloading/prerender/prerender_features.h"
Samar Chehade-Lepleuxce1baf22023-05-16 09:50:0921#include "content/browser/preloading/prerender/prerender_final_status.h"
Iman Saboori31bed872022-12-09 15:41:3122#include "content/browser/preloading/prerender/prerender_host_registry.h"
23#include "content/browser/preloading/prerender/prerender_metrics.h"
24#include "content/browser/preloading/prerender/prerender_navigation_utils.h"
25#include "content/browser/preloading/prerender/prerender_new_tab_handle.h"
Takashi Nakayama9eb7988f2025-06-25 06:14:0026#include "content/browser/preloading/speculation_rules/speculation_rules_util.h"
Iman Saboori31bed872022-12-09 15:41:3127#include "content/browser/renderer_host/render_frame_host_delegate.h"
Domenic Denicolaff44ecf2023-11-10 05:20:4128#include "content/public/browser/preloading_trigger_type.h"
Iman Saboori31bed872022-12-09 15:41:3129#include "content/public/browser/web_contents.h"
Liviu Tinta44e732aa2024-05-28 15:52:3630#include "third_party/blink/public/common/features.h"
Iman Saboori31bed872022-12-09 15:41:3131
32namespace content {
33
Lingqi Chi844e05d42025-07-23 02:50:5034namespace {
35PreloadingType ConvertSpeculationActionToPreloadingType(
36 blink::mojom::SpeculationAction action) {
37 switch (action) {
38 case blink::mojom::SpeculationAction::kPrerender:
39 return PreloadingType::kPrerender;
40 case blink::mojom::SpeculationAction::kPrerenderUntilScript:
41 return PreloadingType::kPrerenderUntilScript;
42 case blink::mojom::SpeculationAction::kPrefetch:
43 case blink::mojom::SpeculationAction::kPrefetchWithSubresources:
44 NOTREACHED();
45 }
46}
47
Lingqi Chibc88ab52025-07-24 00:54:1448bool ShouldPauseJavaScriptExecution(blink::mojom::SpeculationAction action) {
49 switch (action) {
50 case blink::mojom::SpeculationAction::kPrerender:
51 return false;
52 case blink::mojom::SpeculationAction::kPrerenderUntilScript:
53 return true;
54 case blink::mojom::SpeculationAction::kPrefetch:
55 case blink::mojom::SpeculationAction::kPrefetchWithSubresources:
56 NOTREACHED();
57 }
58}
59
Lingqi Chi844e05d42025-07-23 02:50:5060} // namespace
61
Iman Saboori31bed872022-12-09 15:41:3162struct PrerendererImpl::PrerenderInfo {
Domenic Denicolaff44ecf2023-11-10 05:20:4163 blink::mojom::SpeculationInjectionType injection_type;
Taiyo Mizuhashi5470a522023-08-21 05:05:5664 blink::mojom::SpeculationEagerness eagerness;
HuanPo Linc8111d52025-06-23 06:19:4465 bool is_target_blank;
Avi Drissmandcc8e682024-09-04 14:14:4866 FrameTreeNodeId prerender_host_id;
Iman Saboori31bed872022-12-09 15:41:3167 GURL url;
HuanPo Linc8111d52025-06-23 06:19:4468
69 PrerenderInfo() = default;
70 explicit PrerenderInfo(
71 const blink::mojom::SpeculationCandidatePtr& candidate);
72
73 static bool PrerenderInfoComparator(const PrerenderInfo& p1,
74 const PrerenderInfo& p2);
75
76 bool operator<(const PrerenderInfo& p) const {
77 return PrerenderInfoComparator(*this, p);
78 }
79
80 bool operator==(const PrerenderInfo& p) const {
81 return !PrerenderInfoComparator(*this, p) &&
82 !PrerenderInfoComparator(p, *this);
83 }
Iman Saboori31bed872022-12-09 15:41:3184};
85
HuanPo Linc8111d52025-06-23 06:19:4486bool PrerendererImpl::PrerenderInfo::PrerenderInfoComparator(
87 const PrerenderInfo& p1,
88 const PrerenderInfo& p2) {
89 if (p1.url != p2.url) {
90 return p1.url < p2.url;
91 }
92
93 return p1.is_target_blank < p2.is_target_blank;
94}
95
96// `prerender_host_id` is not provided by `SpeculationCandidatePtr`, so
97// FrameTreeNodeId() is assigned instead. The value should be updated once it is
98// available.
99PrerendererImpl::PrerenderInfo::PrerenderInfo(
100 const blink::mojom::SpeculationCandidatePtr& candidate)
101 : injection_type(candidate->injection_type),
102 eagerness(candidate->eagerness),
103 is_target_blank(candidate->target_browsing_context_name_hint ==
104 blink::mojom::SpeculationTargetHint::kBlank),
105 url(candidate->url) {}
106
Johanna9fe85f2023-01-17 10:15:43107PrerendererImpl::PrerendererImpl(RenderFrameHost& render_frame_host)
Iman Saboori31bed872022-12-09 15:41:31108 : WebContentsObserver(WebContents::FromRenderFrameHost(&render_frame_host)),
109 render_frame_host_(render_frame_host) {
Johanna9fe85f2023-01-17 10:15:43110 DCHECK_CURRENTLY_ON(BrowserThread::UI);
Iman Saboori31bed872022-12-09 15:41:31111 auto& rfhi = static_cast<RenderFrameHostImpl&>(render_frame_host);
112 registry_ = rfhi.delegate()->GetPrerenderHostRegistry()->GetWeakPtr();
Taiyo Mizuhashi295ccbdf2023-09-13 09:17:02113 if (registry_) {
114 observation_.Observe(registry_.get());
115 }
Taiyo Mizuhashi93c5f07d2023-10-06 05:01:35116 ResetReceivedPrerendersCountForMetrics();
Yoichi Osatofed56ff82024-02-29 01:13:45117 if (base::FeatureList::IsEnabled(
118 blink::features::kLCPTimingPredictorPrerender2)) {
119 blocked_ = true;
120 }
Iman Saboori31bed872022-12-09 15:41:31121}
lingqi2abab0472023-04-19 05:56:33122
Iman Saboori31bed872022-12-09 15:41:31123PrerendererImpl::~PrerendererImpl() {
Johanna9fe85f2023-01-17 10:15:43124 DCHECK_CURRENTLY_ON(BrowserThread::UI);
Samar Chehade-Lepleuxce1baf22023-05-16 09:50:09125 CancelStartedPrerenders();
Taiyo Mizuhashi93c5f07d2023-10-06 05:01:35126 RecordReceivedPrerendersCountToMetrics();
127 ResetReceivedPrerendersCountForMetrics();
Iman Saboori31bed872022-12-09 15:41:31128}
129
130void PrerendererImpl::PrimaryPageChanged(Page& page) {
131 // Listen to the change of the primary page. Since only the primary page can
132 // trigger speculationrules, the change of the primary page indicates that the
133 // trigger associated with this host is destroyed, so the browser should
134 // cancel the prerenders that are initiated by it.
135 // We cannot do it in the destructor only, because DocumentService can be
136 // deleted asynchronously, but we want to make sure to cancel prerendering
137 // before the next primary page swaps in so that the next page can trigger a
138 // new prerender without hitting the max number of running prerenders.
Johanna9fe85f2023-01-17 10:15:43139 DCHECK_CURRENTLY_ON(BrowserThread::UI);
Samar Chehade-Lepleuxce1baf22023-05-16 09:50:09140 CancelStartedPrerenders();
Taiyo Mizuhashi93c5f07d2023-10-06 05:01:35141 RecordReceivedPrerendersCountToMetrics();
142 ResetReceivedPrerendersCountForMetrics();
Iman Saboori31bed872022-12-09 15:41:31143}
144
145// TODO(isaboori) Part of the logic in |ProcessCandidatesForPrerender| method is
146// about making preloading decisions and could be moved to PreloadingDecider
147// class.
148void PrerendererImpl::ProcessCandidatesForPrerender(
149 const std::vector<blink::mojom::SpeculationCandidatePtr>& candidates) {
150 if (!registry_)
151 return;
152
153 // Extract only the candidates which apply to prerender, and sort them by URL
154 // so we can efficiently compare them to `started_prerenders_`.
Taiyo Mizuhashif277ece2024-01-12 17:29:28155 std::vector<std::pair<size_t, blink::mojom::SpeculationCandidatePtr>>
156 prerender_candidates;
Iman Saboori31bed872022-12-09 15:41:31157 for (const auto& candidate : candidates) {
Lingqi Chi844e05d42025-07-23 02:50:50158 if (candidate->action == blink::mojom::SpeculationAction::kPrerender ||
159 candidate->action ==
160 blink::mojom::SpeculationAction::kPrerenderUntilScript) {
Taiyo Mizuhashif277ece2024-01-12 17:29:28161 prerender_candidates.emplace_back(prerender_candidates.size(),
162 candidate.Clone());
163 }
Iman Saboori31bed872022-12-09 15:41:31164 }
Taiyo Mizuhashif277ece2024-01-12 17:29:28165
HuanPo Linc8111d52025-06-23 06:19:44166 std::ranges::stable_sort(
167 prerender_candidates, std::less<>(),
168 [](const auto& p) { return PrerenderInfo(p.second); });
Taiyo Mizuhashif277ece2024-01-12 17:29:28169 std::vector<std::pair<size_t, blink::mojom::SpeculationCandidatePtr>>
170 candidates_to_start;
Iman Saboori31bed872022-12-09 15:41:31171
172 // Collects the host ids corresponding to the URLs that are removed from the
173 // speculation rules. These hosts are cancelled later.
Avi Drissmandcc8e682024-09-04 14:14:48174 std::vector<FrameTreeNodeId> removed_prerender_rules;
Iman Saboori31bed872022-12-09 15:41:31175
176 // Compare the sorted candidate and started prerender lists to one another.
177 // Since they are sorted, we process the lexicographically earlier of the two
HuanPo Linc8111d52025-06-23 06:19:44178 // PrerenderInfos pointed at by the iterators, and compare the range of
179 // entries in each that match that PrerenderInfo.
Iman Saboori31bed872022-12-09 15:41:31180 //
HuanPo Linc8111d52025-06-23 06:19:44181 // PrerenderInfos which are present in the prerender list but not the
182 // candidate list can no longer proceed and are cancelled.
Iman Saboori31bed872022-12-09 15:41:31183 //
HuanPo Linc8111d52025-06-23 06:19:44184 // PrerenderInfos which are present in the candidate list but not the
185 // prerender list could be started and are gathered in `candidates_to_start`.
Iman Saboori31bed872022-12-09 15:41:31186 auto candidate_it = prerender_candidates.begin();
187 auto started_it = started_prerenders_.begin();
188 while (candidate_it != prerender_candidates.end() ||
189 started_it != started_prerenders_.end()) {
HuanPo Linc8111d52025-06-23 06:19:44190 // Select the lesser of the two PrerenderInfos to diff.
191 PrerenderInfo prerender_info;
192 if (started_it == started_prerenders_.end()) {
193 prerender_info = PrerenderInfo(candidate_it->second);
194 } else if (candidate_it == prerender_candidates.end()) {
195 prerender_info = *started_it;
196 } else {
197 prerender_info =
198 std::min(PrerenderInfo(candidate_it->second), *started_it);
199 }
Iman Saboori31bed872022-12-09 15:41:31200
HuanPo Linc8111d52025-06-23 06:19:44201 // Select the ranges from both that match the PrerenderInfo in question.
Peter Kasting1557e5f2025-01-28 01:14:08202 auto equal_prerender_end = std::ranges::find_if(
Iman Saboori31bed872022-12-09 15:41:31203 started_it, started_prerenders_.end(),
HuanPo Linc8111d52025-06-23 06:19:44204 [&](const auto& started) { return started != prerender_info; });
Tom Sepez3e425102025-07-23 12:16:20205 base::span<PrerenderInfo> UNSAFE_TODO(
206 matching_prerenders(started_it, equal_prerender_end));
Peter Kasting1557e5f2025-01-28 01:14:08207 auto equal_candidate_end = std::ranges::find_if(
HuanPo Linc8111d52025-06-23 06:19:44208 candidate_it, prerender_candidates.end(), [&](const auto& candidate) {
209 return PrerenderInfo(candidate.second) != prerender_info;
210 });
Taiyo Mizuhashif277ece2024-01-12 17:29:28211 base::span<std::pair<size_t, blink::mojom::SpeculationCandidatePtr>>
Tom Sepez3e425102025-07-23 12:16:20212 UNSAFE_TODO(matching_candidates(candidate_it, equal_candidate_end));
Iman Saboori31bed872022-12-09 15:41:31213
214 // Decide what started prerenders to cancel.
215 for (PrerenderInfo& prerender : matching_prerenders) {
Avi Drissmandcc8e682024-09-04 14:14:48216 if (prerender.prerender_host_id.is_null()) {
Iman Saboori31bed872022-12-09 15:41:31217 continue;
Avi Drissmandcc8e682024-09-04 14:14:48218 }
Iman Saboori31bed872022-12-09 15:41:31219 // TODO(jbroman): This doesn't currently care about other aspects, like
220 // the referrer. This doesn't presently matter, but in the future we might
HuanPo Linc8111d52025-06-23 06:19:44221 // want to cancel if there are candidates which match by PrerenderInfo but
222 // none of which permit this prerender.
Iman Saboori31bed872022-12-09 15:41:31223 if (matching_candidates.empty()) {
224 removed_prerender_rules.push_back(prerender.prerender_host_id);
Iman Saboori31bed872022-12-09 15:41:31225 }
226 }
227
228 // Decide what new candidates to start.
HuanPo Linc8111d52025-06-23 06:19:44229 // For now, start one candidate per target hint for a URL only if there are
230 // no matching prerenders. We could be cleverer in the future.
Iman Saboori31bed872022-12-09 15:41:31231 if (matching_prerenders.empty()) {
Peter Kasting1b56352f2024-11-17 20:47:33232 CHECK(!matching_candidates.empty());
HuanPo Linc8111d52025-06-23 06:19:44233
234 std::set<PrerenderInfo> processed_prerender_info;
235
236 for (auto& matching_candidate : matching_candidates) {
237 PrerenderInfo matching_candidate_prerender_info =
238 PrerenderInfo(matching_candidate.second);
239 if (processed_prerender_info
240 .insert(std::move(matching_candidate_prerender_info))
241 .second) {
242 candidates_to_start.push_back(std::move(matching_candidate));
243 }
244 }
Iman Saboori31bed872022-12-09 15:41:31245 }
246
247 // Advance the iterators past all matching entries.
248 candidate_it = equal_candidate_end;
249 started_it = equal_prerender_end;
250 }
251
kenoss087ae232024-12-10 05:06:04252 std::vector<GURL> urls;
253 for (auto ftn_id : removed_prerender_rules) {
254 if (PrerenderHost* prerender_host =
255 registry_->FindNonReservedHostById(ftn_id)) {
256 urls.push_back(prerender_host->GetInitialUrl());
257 }
258 }
Avi Drissmandcc8e682024-09-04 14:14:48259 std::set<FrameTreeNodeId> canceled_prerender_rules_set =
260 registry_->CancelHosts(
261 removed_prerender_rules,
262 PrerenderCancellationReason(
263 PrerenderFinalStatus::kSpeculationRuleRemoved));
kenoss087ae232024-12-10 05:06:04264 if (base::FeatureList::IsEnabled(
265 features::kPrerender2FallbackPrefetchSpecRules)) {
266 WebContents* web_contents =
267 WebContents::FromRenderFrameHost(&render_frame_host_.get());
268 auto* prefetch_document_manager =
269 content::PrefetchDocumentManager::GetOrCreateForCurrentDocument(
270 web_contents->GetPrimaryMainFrame());
271 for (const auto& url : urls) {
272 prefetch_document_manager->ResetPrefetchAheadOfPrerenderIfExist(url);
273 }
274 }
Lingqi Chi47f60002022-12-13 05:29:08275
Taiyo Mizuhashia4ac7d22024-08-13 23:53:31276 // Canceled prerenders by kSpeculationRuleRemoved should have already been
277 // removed from `started_prerenders_` via `OnCancel`.
Hiroki Nakagawa8821cd882024-08-01 08:19:46278 CHECK(std::find_if(started_prerenders_.begin(), started_prerenders_.end(),
279 [&](const PrerenderInfo& x) {
Taiyo Mizuhashia4ac7d22024-08-13 23:53:31280 return base::Contains(canceled_prerender_rules_set,
Hiroki Nakagawa8821cd882024-08-01 08:19:46281 x.prerender_host_id);
282 }) == started_prerenders_.end());
Iman Saboori31bed872022-12-09 15:41:31283
Taiyo Mizuhashif277ece2024-01-12 17:29:28284 // Actually start the candidates in their original order once the diffing is
285 // done.
Peter Kasting1557e5f2025-01-28 01:14:08286 std::ranges::sort(candidates_to_start, std::less<>(),
287 [](const auto& p) { return p.first; });
Taiyo Mizuhashif277ece2024-01-12 17:29:28288 for (const auto& [_, candidate] : candidates_to_start) {
Kevin McNee98e068a2024-04-09 20:12:34289 PreloadingTriggerType trigger_type =
290 PreloadingTriggerTypeFromSpeculationInjectionType(
291 candidate->injection_type);
Takashi Nakayama978f0a152025-06-17 08:26:25292 // Immediate candidates are enacted by the same predictor that creates them.
Kevin McNee98e068a2024-04-09 20:12:34293 PreloadingPredictor enacting_predictor =
294 GetPredictorForPreloadingTriggerType(trigger_type);
Kevin McNee842eb0a2024-04-11 20:14:16295 MaybePrerender(candidate, enacting_predictor, PreloadingConfidence{100});
Iman Saboori31bed872022-12-09 15:41:31296 }
297}
298
Yoichi Osatofed56ff82024-02-29 01:13:45299void PrerendererImpl::OnLCPPredicted() {
300 blocked_ = false;
Kevin McNee842eb0a2024-04-11 20:14:16301 for (auto& [candidate, enacting_predictor, confidence] :
302 std::move(blocked_candidates_)) {
303 MaybePrerender(candidate, enacting_predictor, confidence);
Yoichi Osatofed56ff82024-02-29 01:13:45304 }
305}
306
Iman Saboori31bed872022-12-09 15:41:31307bool PrerendererImpl::MaybePrerender(
Kevin McNee98e068a2024-04-09 20:12:34308 const blink::mojom::SpeculationCandidatePtr& candidate,
Kevin McNee842eb0a2024-04-11 20:14:16309 const PreloadingPredictor& enacting_predictor,
310 PreloadingConfidence confidence) {
Lingqi Chi844e05d42025-07-23 02:50:50311 // Check actions. Only Prerender and PrerenderUntilScript are allowed.
312 switch (candidate->action) {
313 case blink::mojom::SpeculationAction::kPrerender:
314 case blink::mojom::SpeculationAction::kPrerenderUntilScript:
315 break;
316 default:
317 NOTREACHED();
318 }
Hiroki Nakagawa3f1fa5a92024-08-16 15:44:12319
320 // Prerendering is not allowed in fenced frames.
321 if (render_frame_host_->IsNestedWithinFencedFrame()) {
322 render_frame_host_->AddMessageToConsole(
323 blink::mojom::ConsoleMessageLevel::kWarning,
324 "The SpeculationRules API does not support prerendering in fenced "
325 "frames.");
326 return false;
327 }
328
Yoichi Osato32361d12024-10-30 04:56:55329 WebContents* web_contents =
330 WebContents::FromRenderFrameHost(&render_frame_host_.get());
331 static_cast<PreloadingDataImpl*>(
332 PreloadingData::GetOrCreateForWebContents(web_contents))
333 ->SetHasSpeculationRulesPrerender();
Yoichi Osatofed56ff82024-02-29 01:13:45334 if (blocked_) {
Kevin McNee842eb0a2024-04-11 20:14:16335 blocked_candidates_.emplace_back(candidate->Clone(), enacting_predictor,
336 confidence);
Yoichi Osatofed56ff82024-02-29 01:13:45337 return false;
338 }
Takashi Toyoshima27e8bb9b2023-06-27 10:03:05339
340 // Prerendering frames should not trigger any prerender request.
341 CHECK(!render_frame_host_->IsInLifecycleState(
342 RenderFrameHost::LifecycleState::kPrerendering));
Iman Saboori31bed872022-12-09 15:41:31343
344 if (!registry_)
345 return false;
346
347 auto& rfhi = static_cast<RenderFrameHostImpl&>(render_frame_host_.get());
Iman Saboori31bed872022-12-09 15:41:31348
HuanPo Linc8111d52025-06-23 06:19:44349 // `prerender_host_id` is not available yet.
350 PrerenderInfo prerender_info(candidate);
351
Peter Kasting1557e5f2025-01-28 01:14:08352 auto [begin, end] = std::ranges::equal_range(
HuanPo Linc8111d52025-06-23 06:19:44353 started_prerenders_.begin(), started_prerenders_.end(), prerender_info,
354 PrerenderInfo::PrerenderInfoComparator);
355 // cannot currently start a second prerender with the same URL and target_hint
Iman Saboori31bed872022-12-09 15:41:31356 if (begin != end) {
357 return false;
358 }
359
360 GetContentClient()->browser()->LogWebFeatureForCurrentPage(
361 &rfhi, blink::mojom::WebFeature::kSpeculationRulesPrerender);
Yoichi Osatob60bb012024-05-23 00:58:23362
Taiyo Mizuhashi93c5f07d2023-10-06 05:01:35363 IncrementReceivedPrerendersCountForMetrics(
Kouhei Ueno63293752023-11-14 07:41:50364 PreloadingTriggerTypeFromSpeculationInjectionType(
365 candidate->injection_type),
366 candidate->eagerness);
Iman Saboori31bed872022-12-09 15:41:31367
Alison Gale770f3fc2024-04-27 00:39:58368 // TODO(crbug.com/40168192): Remove it after supporting cross-site
Iman Saboori31bed872022-12-09 15:41:31369 // prerender.
Hiroki Nakagawa1500e7a2023-01-31 07:44:55370 if (!prerender_navigation_utils::IsSameSite(candidate->url,
371 rfhi.GetLastCommittedOrigin())) {
372 rfhi.AddMessageToConsole(
373 blink::mojom::ConsoleMessageLevel::kWarning,
374 base::StringPrintf(
375 "The SpeculationRules API does not support cross-site prerender "
Hiroki Nakagawaae693cc2023-06-12 08:34:15376 "yet (initiator origin: %s, prerender origin: %s). "
Hiroki Nakagawa1500e7a2023-01-31 07:44:55377 "https://crbug.com/1176054 tracks cross-site support.",
378 rfhi.GetLastCommittedOrigin().Serialize().c_str(),
379 url::Origin::Create(candidate->url).Serialize().c_str()));
Iman Saboori31bed872022-12-09 15:41:31380 }
381
Rulong Chen(陈汝龙)bf12169c2024-12-16 05:38:16382 std::optional<net::HttpNoVarySearchData> no_vary_search_hint;
Hiroki Nakagawad176addb2025-03-07 07:04:15383 if (candidate->no_vary_search_hint) {
Rulong Chen(陈汝龙)bf12169c2024-12-16 05:38:16384 no_vary_search_hint = no_vary_search::ParseHttpNoVarySearchDataFromMojom(
385 candidate->no_vary_search_hint);
Liviu Tinta44e732aa2024-05-28 15:52:36386 }
387
Takashi Nakayama9eb7988f2025-06-25 06:14:00388 const bool should_warm_up_compositor = base::FeatureList::IsEnabled(
389 IsImmediateSpeculationEagerness(candidate->eagerness)
390 ? features::kPrerender2WarmUpCompositorForImmediate
391 : features::kPrerender2WarmUpCompositorForNonImmediate);
Taiyo Mizuhashiabe1fc52025-06-23 04:00:19392
Iman Saboori31bed872022-12-09 15:41:31393 PrerenderAttributes attributes(
Kouhei Ueno63293752023-11-14 07:41:50394 candidate->url,
395 PreloadingTriggerTypeFromSpeculationInjectionType(
396 candidate->injection_type),
Hiroki Nakagawa134ada62023-10-16 10:50:26397 /*embedder_histogram_suffix=*/"",
Hiroki Nakagawaeeecc56e2025-03-27 02:57:30398 SpeculationRulesParams(candidate->target_browsing_context_name_hint,
Hiroki Nakagawac91e36222025-05-02 11:00:27399 candidate->eagerness,
400 SpeculationRulesTags(candidate->tags)),
Hiroki Nakagawaeeecc56e2025-03-27 02:57:30401 Referrer{*candidate->referrer}, no_vary_search_hint, &rfhi,
402 web_contents->GetWeakPtr(), ui::PAGE_TRANSITION_LINK,
Taiyo Mizuhashiabe1fc52025-06-23 04:00:19403 should_warm_up_compositor,
Lingqi Chia2efa8c2024-11-08 10:20:29404 /*should_prepare_paint_tree=*/false,
Lingqi Chibc88ab52025-07-24 00:54:14405 ShouldPauseJavaScriptExecution(candidate->action),
Arthur Sonzognifd1e08d2024-03-05 15:59:36406 /*url_match_predicate=*/{},
407 /*prerender_navigation_handle_callback=*/{},
kenossc3f9a2c2025-03-06 15:35:51408 PreloadPipelineInfoImpl::Create(
Lingqi Chi844e05d42025-07-23 02:50:50409 /*planned_max_preloading_type=*/
410 ConvertSpeculationActionToPreloadingType(candidate->action)),
Jiacheng Guoea20d392025-07-07 01:08:14411 /*allow_reuse=*/false);
Iman Saboori31bed872022-12-09 15:41:31412
Kouhei Uenoa234a8e2023-11-15 05:24:42413 PreloadingTriggerType trigger_type =
414 PreloadingTriggerTypeFromSpeculationInjectionType(
415 candidate->injection_type);
Kevin McNee98e068a2024-04-09 20:12:34416 PreloadingPredictor creating_predictor =
Kouhei Uenoa234a8e2023-11-15 05:24:42417 GetPredictorForPreloadingTriggerType(trigger_type);
HuanPo Linc8111d52025-06-23 06:19:44418 prerender_info.prerender_host_id = [&] {
Alison Gale47d1537d2024-04-19 21:31:46419 // TODO(crbug.com/40235424): Handle the case where multiple speculation
420 // rules have the same URL but its `target_browsing_context_name_hint` is
Taiyo Mizuhashi3a4c3d42023-11-09 10:27:14421 // different. In the current implementation, only the first rule is
422 // triggered.
423 switch (candidate->target_browsing_context_name_hint) {
424 case blink::mojom::SpeculationTargetHint::kBlank: {
425 if (base::FeatureList::IsEnabled(
426 blink::features::kPrerender2InNewTab)) {
Robert Lin3354b292025-03-17 02:19:57427 GetContentClient()->browser()->LogWebFeatureForCurrentPage(
428 &rfhi,
429 blink::mojom::WebFeature::kSpeculationRulesTargetHintBlank);
Taiyo Mizuhashi3a4c3d42023-11-09 10:27:14430 // For the prerender-in-new-tab, PreloadingAttempt will be managed by
431 // a prerender WebContents to be created later.
Kevin McNee98e068a2024-04-09 20:12:34432 return registry_->CreateAndStartHostForNewTab(
Kevin McNee842eb0a2024-04-11 20:14:16433 attributes, creating_predictor, enacting_predictor, confidence);
Taiyo Mizuhashi3a4c3d42023-11-09 10:27:14434 }
435 // Handle the rule as kNoHint if the prerender-in-new-tab is not
436 // enabled.
437 [[fallthrough]];
Iman Saboori31bed872022-12-09 15:41:31438 }
Taiyo Mizuhashi3a4c3d42023-11-09 10:27:14439 case blink::mojom::SpeculationTargetHint::kNoHint:
440 case blink::mojom::SpeculationTargetHint::kSelf: {
kenoss9d57ee7b2024-06-03 01:36:43441 if (base::FeatureList::IsEnabled(
442 features::kPrerender2FallbackPrefetchSpecRules)) {
443 auto* prefetch_document_manager =
444 content::PrefetchDocumentManager::GetOrCreateForCurrentDocument(
445 web_contents->GetPrimaryMainFrame());
446 prefetch_document_manager->PrefetchAheadOfPrerender(
kenoss3bd73b82024-10-10 20:33:49447 attributes.preload_pipeline_info, candidate.Clone(),
448 enacting_predictor);
kenoss9d57ee7b2024-06-03 01:36:43449 }
450
Taiyo Mizuhashi3a4c3d42023-11-09 10:27:14451 // Create new PreloadingAttempt and pass all the values corresponding to
452 // this prerendering attempt.
453 auto* preloading_data =
Kevin McNee98e068a2024-04-09 20:12:34454 PreloadingDataImpl::GetOrCreateForWebContents(web_contents);
Taiyo Mizuhashi3a4c3d42023-11-09 10:27:14455 PreloadingURLMatchCallback same_url_matcher =
456 PreloadingData::GetSameURLMatcher(candidate->url);
kenoss9d57ee7b2024-06-03 01:36:43457
Taiyo Mizuhashi3a4c3d42023-11-09 10:27:14458 auto* preloading_attempt = static_cast<PreloadingAttemptImpl*>(
459 preloading_data->AddPreloadingAttempt(
Kevin McNee98e068a2024-04-09 20:12:34460 creating_predictor, enacting_predictor,
Lingqi Chi844e05d42025-07-23 02:50:50461 ConvertSpeculationActionToPreloadingType(candidate->action),
462 std::move(same_url_matcher),
Taiyo Mizuhashi3a4c3d42023-11-09 10:27:14463 web_contents->GetPrimaryMainFrame()->GetPageUkmSourceId()));
464 preloading_attempt->SetSpeculationEagerness(candidate->eagerness);
465 return registry_->CreateAndStartHost(attributes, preloading_attempt);
Taiyo Mizuhashi295ccbdf2023-09-13 09:17:02466 }
Iman Saboori31bed872022-12-09 15:41:31467 }
Taiyo Mizuhashi3a4c3d42023-11-09 10:27:14468 }();
469
Hiroki Nakagawa8821cd882024-08-01 08:19:46470 // An existing prerender may be canceled to start a new prerender, and
471 // `started_prerenders_` may be modified through this cancellation. Therefore,
472 // it is needed to re-calculate the right place here on `started_prerenders_`
473 // for new candidates.
Peter Kasting1557e5f2025-01-28 01:14:08474 end = std::ranges::upper_bound(started_prerenders_.begin(),
HuanPo Linc8111d52025-06-23 06:19:44475 started_prerenders_.end(), prerender_info,
476 PrerenderInfo::PrerenderInfoComparator);
Taiyo Mizuhashi3a4c3d42023-11-09 10:27:14477
HuanPo Linc8111d52025-06-23 06:19:44478 started_prerenders_.insert(end, std::move(prerender_info));
Taiyo Mizuhashi3a4c3d42023-11-09 10:27:14479
Iman Saboori31bed872022-12-09 15:41:31480 return true;
481}
482
483bool PrerendererImpl::ShouldWaitForPrerenderResult(const GURL& url) {
HuanPo Linc8111d52025-06-23 06:19:44484 // This function is used to check whetehr a prerender is started to avoid
485 // starting prefetch in OnPointerDown, OnPointerHover or other heuristic
486 // methods which don't take target_hint into consideration. So unlike other
487 // functions in this file, this part uses `url` only instead of
488 // `PrerenderInfo` which consists of target_hint information.
Peter Kasting1557e5f2025-01-28 01:14:08489 auto [begin, end] = std::ranges::equal_range(
Iman Saboori31bed872022-12-09 15:41:31490 started_prerenders_.begin(), started_prerenders_.end(), url,
491 std::less<>(), &PrerenderInfo::url);
492 for (auto it = begin; it != end; ++it) {
Avi Drissmandcc8e682024-09-04 14:14:48493 if (it->prerender_host_id.is_null()) {
Iman Saboori31bed872022-12-09 15:41:31494 return false;
495 }
496 }
497 return begin != end;
498}
499
Avi Drissmandcc8e682024-09-04 14:14:48500void PrerendererImpl::OnCancel(FrameTreeNodeId host_frame_tree_node_id,
Taiyo Mizuhashi295ccbdf2023-09-13 09:17:02501 const PrerenderCancellationReason& reason) {
Taiyo Mizuhashi295ccbdf2023-09-13 09:17:02502 switch (reason.final_status()) {
Alison Gale770f3fc2024-04-27 00:39:58503 // TODO(crbug.com/40275452): Support other final status cases.
Taiyo Mizuhashi352f6d62024-06-24 06:22:59504 case PrerenderFinalStatus::kTimeoutBackgrounded:
Takashi Nakayama978f0a152025-06-17 08:26:25505 case PrerenderFinalStatus::kMaxNumOfRunningNonImmediatePrerendersExceeded:
Taiyo Mizuhashi5694b822023-09-26 16:10:30506 case PrerenderFinalStatus::kSpeculationRuleRemoved: {
507 auto erasing_prerender_it = std::find_if(
508 started_prerenders_.begin(), started_prerenders_.end(),
509 [&](const PrerenderInfo& prerender_info) {
510 return prerender_info.prerender_host_id == host_frame_tree_node_id;
511 });
Taiyo Mizuhashi295ccbdf2023-09-13 09:17:02512
Taiyo Mizuhashi5694b822023-09-26 16:10:30513 if (erasing_prerender_it != started_prerenders_.end()) {
514 auto url = erasing_prerender_it->url;
515 started_prerenders_.erase(erasing_prerender_it);
516
517 // Notify PreloadingDecider.
518 prerender_cancellation_callback_.Run(url);
519 }
Taiyo Mizuhashi295ccbdf2023-09-13 09:17:02520 break;
Taiyo Mizuhashi5694b822023-09-26 16:10:30521 }
Taiyo Mizuhashi295ccbdf2023-09-13 09:17:02522 default:
523 break;
524 }
525}
526
527void PrerendererImpl::OnRegistryDestroyed() {
528 observation_.Reset();
529}
530
531void PrerendererImpl::SetPrerenderCancellationCallback(
532 PrerenderCancellationCallback callback) {
533 prerender_cancellation_callback_ = std::move(callback);
534}
535
Samar Chehade-Lepleuxce1baf22023-05-16 09:50:09536void PrerendererImpl::CancelStartedPrerenders() {
Iman Saboori31bed872022-12-09 15:41:31537 if (registry_) {
Avi Drissmandcc8e682024-09-04 14:14:48538 std::vector<FrameTreeNodeId> started_prerender_ids;
Iman Saboori31bed872022-12-09 15:41:31539 for (auto& prerender_info : started_prerenders_) {
540 started_prerender_ids.push_back(prerender_info.prerender_host_id);
541 }
Samar Chehade-Lepleuxce1baf22023-05-16 09:50:09542 registry_->CancelHosts(
543 started_prerender_ids,
544 PrerenderCancellationReason(PrerenderFinalStatus::kTriggerDestroyed));
Iman Saboori31bed872022-12-09 15:41:31545 }
546
547 started_prerenders_.clear();
Iman Saboori31bed872022-12-09 15:41:31548}
549
kenoss4858a802024-10-11 06:50:56550void PrerendererImpl::CancelStartedPrerendersForTesting() {
551 CancelStartedPrerenders();
552}
553
Taiyo Mizuhashi93c5f07d2023-10-06 05:01:35554void PrerendererImpl::ResetReceivedPrerendersCountForMetrics() {
Taiyo Mizuhashi5470a522023-08-21 05:05:56555 for (auto trigger_type :
Kouhei Ueno3f37992b2023-11-09 23:29:02556 {PreloadingTriggerType::kSpeculationRule,
557 PreloadingTriggerType::kSpeculationRuleFromIsolatedWorld}) {
Taiyo Mizuhashi93c5f07d2023-10-06 05:01:35558 received_prerenders_by_eagerness_[trigger_type].fill({});
559 }
560}
Taiyo Mizuhashi5470a522023-08-21 05:05:56561
Taiyo Mizuhashi93c5f07d2023-10-06 05:01:35562void PrerendererImpl::IncrementReceivedPrerendersCountForMetrics(
Kouhei Ueno3f37992b2023-11-09 23:29:02563 PreloadingTriggerType trigger_type,
Taiyo Mizuhashi93c5f07d2023-10-06 05:01:35564 blink::mojom::SpeculationEagerness eagerness) {
565 received_prerenders_by_eagerness_[trigger_type]
566 [static_cast<size_t>(eagerness)]++;
567}
568
569void PrerendererImpl::RecordReceivedPrerendersCountToMetrics() {
570 for (auto trigger_type :
Kouhei Ueno3f37992b2023-11-09 23:29:02571 {PreloadingTriggerType::kSpeculationRule,
572 PreloadingTriggerType::kSpeculationRuleFromIsolatedWorld}) {
Taiyo Mizuhashi93c5f07d2023-10-06 05:01:35573 int conservative =
574 received_prerenders_by_eagerness_[trigger_type][static_cast<size_t>(
575 blink::mojom::SpeculationEagerness::kConservative)];
576 int moderate =
577 received_prerenders_by_eagerness_[trigger_type][static_cast<size_t>(
578 blink::mojom::SpeculationEagerness::kModerate)];
Takashi Nakayama9eb7988f2025-06-25 06:14:00579 int eager =
580 received_prerenders_by_eagerness_[trigger_type][static_cast<size_t>(
581 blink::mojom::SpeculationEagerness::kEager)];
Takashi Nakayama978f0a152025-06-17 08:26:25582 int immediate =
Taiyo Mizuhashi93c5f07d2023-10-06 05:01:35583 received_prerenders_by_eagerness_[trigger_type][static_cast<size_t>(
Takashi Nakayama978f0a152025-06-17 08:26:25584 blink::mojom::SpeculationEagerness::kImmediate)];
Taiyo Mizuhashi93c5f07d2023-10-06 05:01:35585
586 // This will record zero when
587 // 1) there are no started prerenders eventually. Also noted that if
Taiyo Mizuhashi5470a522023-08-21 05:05:56588 // there is no rule set, PreloadingDecider won't be created (which means
589 // PrerenderImpl also won't be created), so it cannot be reached this
590 // code path at the first place.
Taiyo Mizuhashi93c5f07d2023-10-06 05:01:35591 // 2) when the corresponding RFH lives but is inactive (such as the case in
592 // BFCache) after once PrimaryPageChanged was called and the recorded
593 // number was reset (As long as PreloadingDecider (which has the same
Taiyo Mizuhashi5470a522023-08-21 05:05:56594 // lifetime with a document) that owns this (PrerenderImpl) lives, this
Taiyo Mizuhashi93c5f07d2023-10-06 05:01:35595 // function will be called per PrimaryPageChanged).
Taiyo Mizuhashi5470a522023-08-21 05:05:56596 //
597 // Avoids recording these cases uniformly.
Takashi Nakayama9eb7988f2025-06-25 06:14:00598 if (conservative + moderate + eager + immediate == 0) {
Taiyo Mizuhashi5470a522023-08-21 05:05:56599 continue;
600 }
601
602 // Record per single eagerness.
603 RecordReceivedPrerendersPerPrimaryPageChangedCount(
604 conservative, trigger_type, "Conservative");
605 RecordReceivedPrerendersPerPrimaryPageChangedCount(moderate, trigger_type,
606 "Moderate");
Takashi Nakayamab12a5062025-08-04 04:44:11607 RecordReceivedPrerendersPerPrimaryPageChangedCount(eager, trigger_type,
608 "Eager2");
609 RecordReceivedPrerendersPerPrimaryPageChangedCount(immediate, trigger_type,
610 "Immediate2");
Taiyo Mizuhashi5470a522023-08-21 05:05:56611 }
612}
613
Iman Saboori31bed872022-12-09 15:41:31614} // namespace content