blob: 628cb0d89a7efc82b82821cc611962fa9a4eca53 [file] [log] [blame]
Iman Saboori86e1b6d2023-08-28 18:42:351// Copyright 2023 The Chromium Authors
Iman Saboori16071722022-11-04 14:59:462// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/browser/preloading/preloading_decider.h"
Iman Saboori64dc4de2022-11-29 18:58:066
Kevin McNee842eb0a2024-04-11 20:14:167#include <algorithm>
8#include <cmath>
Md Hasibul Hasana963a9342024-04-03 10:15:149#include <string_view>
Andrew Rayskiyf65990362024-02-27 18:43:2410#include <vector>
11
Takashi Nakayamae234c492025-08-04 03:16:4812#include "base/check_is_test.h"
Liviu Tinta8a3a7bc2023-05-23 00:44:3413#include "base/check_op.h"
Jeremy Roman2cf0e772023-03-13 23:20:0014#include "base/containers/enum_set.h"
Taiyo Mizuhashi295ccbdf2023-09-13 09:17:0215#include "base/feature_list.h"
Kevin McNee842eb0a2024-04-11 20:14:1616#include "base/numerics/safe_conversions.h"
Jeremy Roman2cf0e772023-03-13 23:20:0017#include "base/strings/string_split.h"
Adithya Srinivasan19b84bf2024-09-06 14:41:5318#include "content/browser/devtools/devtools_instrumentation.h"
kenossec072852025-02-19 07:07:5519#include "content/browser/devtools/devtools_preload_storage.h"
Liviu Tinta8a3a7bc2023-05-23 00:44:3420#include "content/browser/preloading/prefetch/no_vary_search_helper.h"
Adithya Srinivasand1c68ba2023-05-30 19:54:2521#include "content/browser/preloading/prefetch/prefetch_document_manager.h"
22#include "content/browser/preloading/prefetch/prefetch_params.h"
Iman Saboori64dc4de2022-11-29 18:58:0623#include "content/browser/preloading/preloading.h"
Kevin McNee842eb0a2024-04-11 20:14:1624#include "content/browser/preloading/preloading_confidence.h"
Iman Saboori31f09492023-06-26 20:15:2025#include "content/browser/preloading/preloading_data_impl.h"
Kouhei Uenoa234a8e2023-11-15 05:24:4226#include "content/browser/preloading/preloading_trigger_type_impl.h"
Taiyo Mizuhashi295ccbdf2023-09-13 09:17:0227#include "content/browser/preloading/prerender/prerender_features.h"
Iman Saboori31bed872022-12-09 15:41:3128#include "content/browser/preloading/prerenderer_impl.h"
Takashi Nakayama9eb7988f2025-06-25 06:14:0029#include "content/browser/preloading/speculation_rules/speculation_rules_util.h"
Iman Saboori64dc4de2022-11-29 18:58:0630#include "content/browser/renderer_host/render_frame_host_impl.h"
Iman Saboorieda025c72023-05-12 18:55:2931#include "content/public/browser/navigation_handle.h"
William Liu238a9e52023-01-23 20:32:4032#include "content/public/browser/preloading.h"
Adithya Srinivasand1c68ba2023-05-30 19:54:2533#include "content/public/browser/weak_document_ptr.h"
Iman Saboori64dc4de2022-11-29 18:58:0634#include "content/public/browser/web_contents.h"
Jeremy Roman2cf0e772023-03-13 23:20:0035#include "third_party/blink/public/common/features.h"
Iman Saboori50d51092023-06-09 22:17:3036#include "third_party/blink/public/mojom/preloading/anchor_element_interaction_host.mojom.h"
Takashi Nakayamae234c492025-08-04 03:16:4837#include "third_party/blink/public/mojom/speculation_rules/speculation_rules.mojom-data-view.h"
38#include "third_party/blink/public/mojom/speculation_rules/speculation_rules.mojom-forward.h"
Iman Saboori16071722022-11-04 14:59:4639
40namespace content {
Jeremy Roman2cf0e772023-03-13 23:20:0041
42namespace {
43
Takashi Nakayama80498ee2025-07-31 05:31:3144content::PreloadingDecider::EagernessSet EagernessSetFromFeatureParam(
45 std::string_view value) {
46 content::PreloadingDecider::EagernessSet set;
Md Hasibul Hasana963a9342024-04-03 10:15:1447 for (std::string_view piece : base::SplitStringPiece(
Jeremy Roman2cf0e772023-03-13 23:20:0048 value, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) {
49 if (piece == "conservative") {
50 set.Put(blink::mojom::SpeculationEagerness::kConservative);
51 } else if (piece == "moderate") {
52 set.Put(blink::mojom::SpeculationEagerness::kModerate);
53 }
54 }
55 return set;
56}
57
Taiyo Mizuhashi59355fc2023-10-10 19:05:3958void OnPrefetchDestroyed(WeakDocumentPtr document, const GURL& url) {
Adithya Srinivasand1c68ba2023-05-30 19:54:2559 PreloadingDecider* preloading_decider =
60 PreloadingDecider::GetForCurrentDocument(
61 document.AsRenderFrameHostIfValid());
62 if (preloading_decider) {
Taiyo Mizuhashi295ccbdf2023-09-13 09:17:0263 preloading_decider->OnPreloadDiscarded(
64 {url, blink::mojom::SpeculationAction::kPrefetch});
65 }
66}
67
Taiyo Mizuhashi59355fc2023-10-10 19:05:3968void OnPrerenderCanceled(WeakDocumentPtr document, const GURL& url) {
Taiyo Mizuhashi295ccbdf2023-09-13 09:17:0269 PreloadingDecider* preloading_decider =
70 PreloadingDecider::GetForCurrentDocument(
71 document.AsRenderFrameHostIfValid());
72 if (preloading_decider) {
73 preloading_decider->OnPreloadDiscarded(
74 {url, blink::mojom::SpeculationAction::kPrerender});
Adithya Srinivasand1c68ba2023-05-30 19:54:2575 }
76}
77
Kevin McNee98e068a2024-04-09 20:12:3478bool PredictionOccursInOtherWebContents(
79 const blink::mojom::SpeculationCandidate& candidate) {
80 return base::FeatureList::IsEnabled(blink::features::kPrerender2InNewTab) &&
81 candidate.action == blink::mojom::SpeculationAction::kPrerender &&
82 candidate.target_browsing_context_name_hint ==
83 blink::mojom::SpeculationTargetHint::kBlank;
84}
85
Jeremy Roman2cf0e772023-03-13 23:20:0086} // namespace
87
Kevin McNee842eb0a2024-04-11 20:14:1688class PreloadingDecider::BehaviorConfig {
89 public:
90 BehaviorConfig()
91 : ml_model_eagerness_{blink::mojom::SpeculationEagerness::kModerate},
92 ml_model_enacts_candidates_(
93 blink::features::kPreloadingModelEnactCandidates.Get()),
94 ml_model_prefetch_moderate_threshold_{std::clamp(
95 blink::features::kPreloadingModelPrefetchModerateThreshold.Get(),
96 0,
97 100)},
98 ml_model_prerender_moderate_threshold_{std::clamp(
99 blink::features::kPreloadingModelPrerenderModerateThreshold.Get(),
100 0,
101 100)} {
Kevin McNee842eb0a2024-04-11 20:14:16102 pointer_down_eagerness_ =
Domenic Denicolad9b38f92025-06-06 06:53:53103 EagernessSet{blink::mojom::SpeculationEagerness::kConservative,
104 blink::mojom::SpeculationEagerness::kModerate};
Jeremy Roman2cf0e772023-03-13 23:20:00105
Kevin McNee842eb0a2024-04-11 20:14:16106 pointer_hover_eagerness_ =
Domenic Denicola89af2012025-06-06 06:58:33107 EagernessSet{blink::mojom::SpeculationEagerness::kModerate};
Adithya Srinivasan7239d4f2024-11-05 15:35:53108
Takashi Nakayamae234c492025-08-04 03:16:48109 if (base::FeatureList::IsEnabled(
110 blink::features::kPreloadingEagerHeuristics)) {
111 pointer_down_eagerness_.Put(blink::mojom::SpeculationEagerness::kEager);
112 pointer_hover_eagerness_.Put(blink::mojom::SpeculationEagerness::kEager);
113 }
114
115 CHECK(pointer_down_eagerness_.HasAll(pointer_hover_eagerness_));
116
Adithya Srinivasan7239d4f2024-11-05 15:35:53117 static const base::FeatureParam<std::string> kViewportHeuristicEagerness{
118 &blink::features::kPreloadingViewportHeuristics,
119 "viewport_heuristic_eagerness", "moderate"};
120 viewport_heuristic_eagerness_ =
121 EagernessSetFromFeatureParam(kViewportHeuristicEagerness.Get());
Jeremy Roman2cf0e772023-03-13 23:20:00122 }
123
124 EagernessSet EagernessSetForPredictor(
125 const PreloadingPredictor& predictor) const {
Kevin McNee031e28c2024-03-21 20:55:00126 if (predictor == preloading_predictor::kUrlPointerDownOnAnchor) {
Kevin McNee842eb0a2024-04-11 20:14:16127 return pointer_down_eagerness_;
Kevin McNee031e28c2024-03-21 20:55:00128 } else if (predictor == preloading_predictor::kUrlPointerHoverOnAnchor) {
Kevin McNee842eb0a2024-04-11 20:14:16129 return pointer_hover_eagerness_;
Adithya Srinivasan7239d4f2024-11-05 15:35:53130 } else if (predictor == preloading_predictor::kViewportHeuristic) {
131 return viewport_heuristic_eagerness_;
Kevin McNee842eb0a2024-04-11 20:14:16132 } else if (predictor ==
133 preloading_predictor::kPreloadingHeuristicsMLModel) {
134 return ml_model_eagerness_;
Jeremy Roman2cf0e772023-03-13 23:20:00135 } else {
Peter Boström01ab59a2024-08-15 02:39:49136 NOTREACHED() << "unexpected predictor " << predictor.name() << "/"
137 << predictor.ukm_value();
Jeremy Roman2cf0e772023-03-13 23:20:00138 }
139 }
140
Kevin McNee842eb0a2024-04-11 20:14:16141 PreloadingConfidence GetThreshold(
142 const PreloadingPredictor& predictor,
143 blink::mojom::SpeculationAction action) const {
144 if (predictor == preloading_predictor::kUrlPointerDownOnAnchor) {
145 return kNoThreshold;
146 } else if (predictor == preloading_predictor::kUrlPointerHoverOnAnchor) {
147 return kNoThreshold;
Adithya Srinivasan7239d4f2024-11-05 15:35:53148 } else if (predictor == preloading_predictor::kViewportHeuristic) {
149 return kNoThreshold;
Kevin McNee842eb0a2024-04-11 20:14:16150 } else if (predictor ==
151 preloading_predictor::kPreloadingHeuristicsMLModel) {
152 switch (action) {
153 case blink::mojom::SpeculationAction::kPrefetch:
154 case blink::mojom::SpeculationAction::kPrefetchWithSubresources:
155 return ml_model_prefetch_moderate_threshold_;
Lingqi Chib19879f2025-07-16 05:48:13156 // TODO(https://crbug.com/428500219): Revisit the threshold for
157 // prerender-until-script; it could be lower than the threshold for
158 // prerender.
159 case blink::mojom::SpeculationAction::kPrerenderUntilScript:
Kevin McNee842eb0a2024-04-11 20:14:16160 case blink::mojom::SpeculationAction::kPrerender:
161 return ml_model_prerender_moderate_threshold_;
162 }
163 } else {
Peter Boström01ab59a2024-08-15 02:39:49164 NOTREACHED() << "unexpected predictor " << predictor.name() << "/"
165 << predictor.ukm_value();
Kevin McNee842eb0a2024-04-11 20:14:16166 }
167 }
168
169 bool ml_model_enacts_candidates() const {
170 return ml_model_enacts_candidates_;
171 }
172
173 private:
174 // Any confidence value is >= kNoThreshold, so the associated action will
175 // happen regardless of the confidence value.
176 static constexpr PreloadingConfidence kNoThreshold{0};
177
178 EagernessSet pointer_down_eagerness_;
179 EagernessSet pointer_hover_eagerness_;
Adithya Srinivasan7239d4f2024-11-05 15:35:53180 EagernessSet viewport_heuristic_eagerness_;
Kevin McNee842eb0a2024-04-11 20:14:16181 const EagernessSet ml_model_eagerness_;
182 const bool ml_model_enacts_candidates_ = false;
183 const PreloadingConfidence ml_model_prefetch_moderate_threshold_{
184 kNoThreshold};
185 const PreloadingConfidence ml_model_prerender_moderate_threshold_{
186 kNoThreshold};
Jeremy Roman2cf0e772023-03-13 23:20:00187};
188
Iman Saboori16071722022-11-04 14:59:46189DOCUMENT_USER_DATA_KEY_IMPL(PreloadingDecider);
190
Johanna9fe85f2023-01-17 10:15:43191PreloadingDecider::PreloadingDecider(RenderFrameHost* rfh)
Iman Saboori64dc4de2022-11-29 18:58:06192 : DocumentUserData<PreloadingDecider>(rfh),
Jeremy Roman2cf0e772023-03-13 23:20:00193 behavior_config_(std::make_unique<BehaviorConfig>()),
Iman Saboori64dc4de2022-11-29 18:58:06194 observer_for_testing_(nullptr),
195 preconnector_(render_frame_host()),
196 prefetcher_(render_frame_host()),
Adithya Srinivasand1c68ba2023-05-30 19:54:25197 prerenderer_(std::make_unique<PrerendererImpl>(render_frame_host())) {
Adithya Srinivasan9d1b6fa2024-08-29 15:04:35198 PrefetchDocumentManager::GetOrCreateForCurrentDocument(rfh)
199 ->SetPrefetchDestructionCallback(
200 base::BindRepeating(&OnPrefetchDestroyed, rfh->GetWeakDocumentPtr()));
Taiyo Mizuhashi295ccbdf2023-09-13 09:17:02201
Hiroki Nakagawa8821cd882024-08-01 08:19:46202 prerenderer_->SetPrerenderCancellationCallback(
203 base::BindRepeating(&OnPrerenderCanceled, rfh->GetWeakDocumentPtr()));
kenossec072852025-02-19 07:07:55204
205 // Forcibly create `DevToolsPreloadStorage` before we use it in
206 // `devtools_instrumentation`.
207 //
208 // For more details, see
209 // https://docs.google.com/document/d/1ZP7lYrtqZL9jC2xXieNY_UBMJL1sCrfmzTB8K6v4sD4/edit?resourcekey=0-fkbeQhkT3PhBb9FnnPgnZA&tab=t.e4x3d1nxzmy3#heading=h.4lvl0yr9vmh7
210 //
211 // We found that there is a case that
212 // `devtools_instrumentation::DidUpdatePrerenderStatus()` in the same stack
213 // that called `DocumentAssociatedData::dtor()`. The former needs an instance
214 // of `DevToolsPreloadStorage`. If we call
215 // `DevToolsPreloadStorage::GetOrCreateForCurrentDocument()` there, the call
216 // may try to create an instance, but it is forbidden as the holder
217 // `DocumentAssociatedData` is in dtor and will crash.
218 //
219 // To mitigate this crash, we'll call `GetOrCreateForCurrentDocument()` here
220 // and use `GetForCurrentDocument()` in
221 // `devtools_instrumentation::DidUpdatePrerenderStatus()`.
222 //
223 // This works because:
224 //
225 // - The issue happens only on speculation rules preload, not
226 // browser-initiated preloads. This is because browser-initiated preloads
227 // don't emit CDP events as they don't have an initiator document, thus
228 // don't use `DevToolsPreloadStorage`. This is guaranteed by
229 // `DevToolsPrerenderAttempt::SetFailureReason()`. Therefore, we do this
230 // workaround in `PreloadingDecider`, not the common layer.
231 // So, we can assume that the below
232 // `DevToolsPreloadStorage::GetOrCreateForCurrentDocument()` is called and
233 // an instance basically exists.
234 // - `SupportsUserData::ClearAllUserData()` (which is called from
235 // `DocumentAssociatedData::dtor()`) swaps user data with an empty map and
236 // then drops the swapped map at the end of scope, which calls each dtor.
237 // https://source.chromium.org/chromium/chromium/src/+/main:base/supports_user_data.cc;l=142;drc=5f14562c01775211a40ebc3056d0a773c3569008
238 // So, `DevToolsPreloadStorage::GetForCurrentDocument()` returns non null
239 // pointer iff the call is before `DocumentAssociatedData::dtor()` call. We
240 // can branch by the condition.
241 //
242 // Note that this is just a short-term fix. We are planning to fix the root
243 // cause.
244 //
245 // TODO(crbug.com/394631076): Fix the root cause and revert this.
246 DevToolsPreloadStorage::GetOrCreateForCurrentDocument(rfh);
Adithya Srinivasand1c68ba2023-05-30 19:54:25247}
Iman Saboori16071722022-11-04 14:59:46248
249PreloadingDecider::~PreloadingDecider() = default;
250
Kevin McNee842eb0a2024-04-11 20:14:16251void PreloadingDecider::AddPreloadingPrediction(
252 const GURL& url,
253 PreloadingPredictor predictor,
254 PreloadingConfidence confidence) {
Iman Saboori64dc4de2022-11-29 18:58:06255 WebContents* web_contents =
256 WebContents::FromRenderFrameHost(&render_frame_host());
257 auto* preloading_data =
Kevin McNee842eb0a2024-04-11 20:14:16258 PreloadingDataImpl::GetOrCreateForWebContents(web_contents);
HuanPo Lin12ce3b5fb42024-03-04 09:01:14259 ukm::SourceId triggered_primary_page_source_id =
260 web_contents->GetPrimaryMainFrame()->GetPageUkmSourceId();
Iman Saboori64dc4de2022-11-29 18:58:06261 preloading_data->AddPreloadingPrediction(
Kevin McNee842eb0a2024-04-11 20:14:16262 predictor, confidence, PreloadingData::GetSameURLMatcher(url),
HuanPo Lin12ce3b5fb42024-03-04 09:01:14263 triggered_primary_page_source_id);
Iman Saboori9455dfa2022-11-18 19:43:43264}
265
Iman Saboori16071722022-11-04 14:59:46266void PreloadingDecider::OnPointerDown(const GURL& url) {
Iman Saboori9455dfa2022-11-18 19:43:43267 if (observer_for_testing_) {
268 observer_for_testing_->OnPointerDown(url);
269 }
Adithya Srinivasanecb97f22024-09-25 19:02:26270 MaybeEnactCandidate(url, preloading_predictor::kUrlPointerDownOnAnchor,
271 PreloadingConfidence{100},
Takashi Nakayama80498ee2025-07-31 05:31:31272 /*fallback_to_preconnect=*/true,
273 /*eagerness_to_exclude=*/{});
Iman Saboori16071722022-11-04 14:59:46274}
275
Iman Saboori86e1b6d2023-08-28 18:42:35276void PreloadingDecider::OnPreloadingHeuristicsModelDone(const GURL& url,
277 float score) {
Kevin McNee031e28c2024-03-21 20:55:00278 CHECK(base::FeatureList::IsEnabled(
279 blink::features::kPreloadingHeuristicsMLModel));
Iman Saboori86e1b6d2023-08-28 18:42:35280 WebContents* web_contents =
281 WebContents::FromRenderFrameHost(&render_frame_host());
282 auto* preloading_data = static_cast<PreloadingDataImpl*>(
283 PreloadingData::GetOrCreateForWebContents(web_contents));
284 preloading_data->AddExperimentalPreloadingPrediction(
285 /*name=*/"OnPreloadingHeuristicsMLModel",
286 /*url_match_predicate=*/PreloadingData::GetSameURLMatcher(url),
287 /*score=*/score,
288 /*min_score=*/0.0,
289 /*max_score=*/1.0,
290 /*buckets=*/100);
Kevin McNee842eb0a2024-04-11 20:14:16291
292 if (!behavior_config_->ml_model_enacts_candidates()) {
293 return;
294 }
295
296 ml_model_available_ = true;
297
298 const PreloadingConfidence confidence{std::clamp(
299 base::saturated_cast<int>(std::nearbyint(score * 100.f)), 0, 100)};
300
301 MaybeEnactCandidate(url, preloading_predictor::kPreloadingHeuristicsMLModel,
Takashi Nakayama80498ee2025-07-31 05:31:31302 confidence, /*fallback_to_preconnect=*/false,
303 /*eagerness_to_exclude=*/{});
Iman Saboori86e1b6d2023-08-28 18:42:35304}
305
Iman Saboori50d51092023-06-09 22:17:30306void PreloadingDecider::OnPointerHover(
307 const GURL& url,
Takashi Nakayamae234c492025-08-04 03:16:48308 blink::mojom::AnchorElementPointerDataPtr mouse_data,
309 blink::mojom::SpeculationEagerness target_eagerness) {
310 // In non-test code, target eagerness must be either "moderate" or "eager".
311 if (target_eagerness != blink::mojom::SpeculationEagerness::kModerate &&
312 target_eagerness != blink::mojom::SpeculationEagerness::kEager) {
313 CHECK_IS_TEST();
314 return;
315 }
316
Iman Saboori64dc4de2022-11-29 18:58:06317 if (observer_for_testing_) {
Takashi Nakayamae234c492025-08-04 03:16:48318 observer_for_testing_->OnPointerHover(url, target_eagerness);
Iman Saboori64dc4de2022-11-29 18:58:06319 }
Iman Saboori31f09492023-06-26 20:15:20320
321 WebContents* web_contents =
322 WebContents::FromRenderFrameHost(&render_frame_host());
323 auto* preloading_data = static_cast<PreloadingDataImpl*>(
324 PreloadingData::GetOrCreateForWebContents(web_contents));
325 preloading_data->AddExperimentalPreloadingPrediction(
326 /*name=*/"OnPointerHoverWithMotionEstimator",
327 /*url_match_predicate=*/PreloadingData::GetSameURLMatcher(url),
328 /*score=*/std::clamp(mouse_data->mouse_velocity, 0.0, 500.0),
329 /*min_score=*/0,
330 /*max_score=*/500,
331 /*buckets=*/100);
332
Adithya Srinivasanecb97f22024-09-25 19:02:26333 // Preconnecting on hover events should not be done if the link is not safe
334 // to prefetch or prerender.
335 constexpr bool fallback_to_preconnect = false;
Takashi Nakayamae234c492025-08-04 03:16:48336 // Filter `kModerate` for the "eager" mouse hover to prevent false preloading.
337 EagernessSet eagerness_to_exclude;
338 if (base::FeatureList::IsEnabled(
339 blink::features::kPreloadingEagerHeuristics)) {
340 eagerness_to_exclude = EagernessSet::All();
341 eagerness_to_exclude.Remove(target_eagerness);
342 }
Adithya Srinivasanecb97f22024-09-25 19:02:26343 MaybeEnactCandidate(url, preloading_predictor::kUrlPointerHoverOnAnchor,
Takashi Nakayama80498ee2025-07-31 05:31:31344 PreloadingConfidence{100}, fallback_to_preconnect,
Takashi Nakayamae234c492025-08-04 03:16:48345 eagerness_to_exclude);
Iman Saboori03a661d2022-11-17 04:37:59346}
347
Adithya Srinivasan7239d4f2024-11-05 15:35:53348void PreloadingDecider::OnViewportHeuristicTriggered(const GURL& url) {
349 CHECK(base::FeatureList::IsEnabled(
350 blink::features::kPreloadingViewportHeuristics));
351 static const base::FeatureParam<bool> kShouldEnactCandidates{
352 &blink::features::kPreloadingViewportHeuristics, "enact_candidates",
353 false};
354 const bool should_enact_candidates = kShouldEnactCandidates.Get();
355 if (!should_enact_candidates) {
356 AddPreloadingPrediction(url, preloading_predictor::kViewportHeuristic,
357 PreloadingConfidence(100));
358 return;
359 }
360
Takashi Nakayama80498ee2025-07-31 05:31:31361 MaybeEnactCandidate(
362 url, preloading_predictor::kViewportHeuristic, PreloadingConfidence{100},
363 /*fallback_to_preconnect=*/false, /*eagerness_to_exclude=*/{});
Adithya Srinivasan7239d4f2024-11-05 15:35:53364}
365
Kevin McNee74133292024-03-08 22:17:33366void PreloadingDecider::MaybeEnactCandidate(
367 const GURL& url,
Kevin McNee98e068a2024-04-09 20:12:34368 const PreloadingPredictor& enacting_predictor,
Kevin McNee842eb0a2024-04-11 20:14:16369 PreloadingConfidence confidence,
Takashi Nakayama80498ee2025-07-31 05:31:31370 bool fallback_to_preconnect,
371 EagernessSet eagerness_to_exclude) {
372 if (const auto [found, added_prediction] = MaybePrerender(
373 url, enacting_predictor, confidence, eagerness_to_exclude);
Kevin McNee98e068a2024-04-09 20:12:34374 found) {
375 // If the prediction is associated with another WebContents, don't duplicate
376 // it here.
377 if (!added_prediction) {
Kevin McNee842eb0a2024-04-11 20:14:16378 AddPreloadingPrediction(url, enacting_predictor, confidence);
Kevin McNee98e068a2024-04-09 20:12:34379 }
Kevin McNee74133292024-03-08 22:17:33380 return;
381 }
Kevin McNee98e068a2024-04-09 20:12:34382
Kevin McNee842eb0a2024-04-11 20:14:16383 AddPreloadingPrediction(url, enacting_predictor, confidence);
Kevin McNee98e068a2024-04-09 20:12:34384
Kevin McNee74133292024-03-08 22:17:33385 if (ShouldWaitForPrerenderResult(url)) {
386 // If there is a prerender in progress already, don't attempt a prefetch.
387 return;
388 }
389
Takashi Nakayama80498ee2025-07-31 05:31:31390 if (MaybePrefetch(url, enacting_predictor, confidence,
391 eagerness_to_exclude)) {
Kevin McNee74133292024-03-08 22:17:33392 return;
393 }
394 // Ideally it is preferred to fallback to preconnect asynchronously if a
395 // prefetch attempt fails. We should revisit it later perhaps after having
396 // data showing it is worth doing so.
397 if (!fallback_to_preconnect || ShouldWaitForPrefetchResult(url)) {
398 return;
399 }
400 preconnector_.MaybePreconnect(url);
401}
402
Liviu Tinta8a3a7bc2023-05-23 00:44:34403void PreloadingDecider::AddStandbyCandidate(
404 const blink::mojom::SpeculationCandidatePtr& candidate) {
405 SpeculationCandidateKey key{candidate->url, candidate->action};
406 on_standby_candidates_[key].push_back(candidate.Clone());
407
408 GURL::Replacements replacements;
409 replacements.ClearRef();
410 replacements.ClearQuery();
411 if (candidate->no_vary_search_hint) {
412 SpeculationCandidateKey key_no_vary_search{
413 candidate->url.ReplaceComponents(replacements), candidate->action};
414 no_vary_search_hint_on_standby_candidates_[key_no_vary_search].insert(key);
415 }
416}
417
418void PreloadingDecider::RemoveStandbyCandidate(
419 const SpeculationCandidateKey key) {
420 GURL::Replacements replacements;
421 replacements.ClearRef();
422 replacements.ClearQuery();
423 SpeculationCandidateKey key_no_vary_search{
424 key.first.ReplaceComponents(replacements), key.second};
425 auto it = no_vary_search_hint_on_standby_candidates_.find(key_no_vary_search);
426 if (it != no_vary_search_hint_on_standby_candidates_.end()) {
427 it->second.erase(key);
Liviu Tintad6bf5062023-05-24 19:17:00428 if (it->second.empty()) {
429 no_vary_search_hint_on_standby_candidates_.erase(it);
430 }
Liviu Tinta8a3a7bc2023-05-23 00:44:34431 }
432 on_standby_candidates_.erase(key);
433}
434
435void PreloadingDecider::ClearStandbyCandidates() {
436 no_vary_search_hint_on_standby_candidates_.clear();
437 on_standby_candidates_.clear();
438}
439
Iman Saboori03a661d2022-11-17 04:37:59440void PreloadingDecider::UpdateSpeculationCandidates(
441 std::vector<blink::mojom::SpeculationCandidatePtr>& candidates) {
Johanna9fe85f2023-01-17 10:15:43442 DCHECK_CURRENTLY_ON(BrowserThread::UI);
Iman Saboori03a661d2022-11-17 04:37:59443 if (observer_for_testing_) {
444 observer_for_testing_->UpdateSpeculationCandidates(candidates);
445 }
Adithya Srinivasan19b84bf2024-09-06 14:41:53446 devtools_instrumentation::DidUpdateSpeculationCandidates(render_frame_host(),
447 candidates);
Iman Saboori64dc4de2022-11-29 18:58:06448
Iman Saboorieda025c72023-05-12 18:55:29449 WebContents* web_contents =
450 WebContents::FromRenderFrameHost(&render_frame_host());
Iman Saboori31f09492023-06-26 20:15:20451 auto* preloading_data = static_cast<PreloadingDataImpl*>(
452 PreloadingData::GetOrCreateForWebContents(web_contents));
Iman Saboorieda025c72023-05-12 18:55:29453 preloading_data->SetIsNavigationInDomainCallback(
454 content_preloading_predictor::kSpeculationRules,
455 base::BindRepeating([](NavigationHandle* navigation_handle) -> bool {
456 return ui::PageTransitionIsWebTriggerable(
Hiroki Nakagawae8c3ae132024-08-26 10:12:54457 navigation_handle->GetPageTransition());
Iman Saboorieda025c72023-05-12 18:55:29458 }));
Kevin McNee031e28c2024-03-21 20:55:00459 PredictorDomainCallback is_new_link_nav =
Adithya Srinivasan9c5b28b2024-10-21 22:29:23460 base::BindRepeating(&PreloadingDataImpl::IsLinkClickNavigation);
Adithya Srinivasanecb97f22024-09-25 19:02:26461 preloading_data->SetIsNavigationInDomainCallback(
462 preloading_predictor::kUrlPointerDownOnAnchor, is_new_link_nav);
463 preloading_data->SetIsNavigationInDomainCallback(
464 preloading_predictor::kUrlPointerHoverOnAnchor, is_new_link_nav);
Kevin McNee842eb0a2024-04-11 20:14:16465 if (base::FeatureList::IsEnabled(
466 blink::features::kPreloadingHeuristicsMLModel) &&
467 behavior_config_->ml_model_enacts_candidates()) {
468 preloading_data->SetIsNavigationInDomainCallback(
469 preloading_predictor::kPreloadingHeuristicsMLModel, is_new_link_nav);
470 }
Adithya Srinivasan3d9bf5b2024-10-31 20:44:54471 if (base::FeatureList::IsEnabled(
472 blink::features::kPreloadingViewportHeuristics)) {
473 preloading_data->SetIsNavigationInDomainCallback(
474 preloading_predictor::kViewportHeuristic, is_new_link_nav);
475 }
Iman Saboorieda025c72023-05-12 18:55:29476
Iman Saboori64dc4de2022-11-29 18:58:06477 // Here we look for all preloading candidates that are safe to perform, but
478 // their eagerness level is not high enough to perform without the trigger
479 // form link selection heuristics logic. We then remove them from the
480 // |candidates| list to prevent them from being initiated and will add them
481 // to |on_standby_candidates_| to be later considered by the heuristics logic.
482 auto should_mark_as_on_standby = [&](const auto& candidate) {
Iman Saboori31bed872022-12-09 15:41:31483 SpeculationCandidateKey key{candidate->url, candidate->action};
Takashi Nakayama9eb7988f2025-06-25 06:14:00484 if (!IsImmediateSpeculationEagerness(candidate->eagerness) &&
Iman Saboori31bed872022-12-09 15:41:31485 processed_candidates_.find(key) == processed_candidates_.end()) {
Kevin McNee98e068a2024-04-09 20:12:34486 // A PreloadingPrediction is intentionally not created for these
Takashi Nakayama978f0a152025-06-17 08:26:25487 // candidates. Non-immediate rules aren't predictions per se, but a
Kevin McNee98e068a2024-04-09 20:12:34488 // declaration to the browser that preloading would be safe.
Liviu Tinta8a3a7bc2023-05-23 00:44:34489 AddStandbyCandidate(candidate);
Iman Saboori64dc4de2022-11-29 18:58:06490 // TODO(isaboori) In current implementation, after calling prefetcher
491 // ProcessCandidatesForPrefetch, the prefetch_service starts checking the
492 // eligibility of the candidates and it will add any eligible candidates
493 // to the prefetch_queue_starts and starts prefetching them as soon as
494 // possible. For that reason here we remove on-standby candidates from the
495 // list. The prefetch service should be updated to let us pass the
496 // on-standby candidates to prefetch_service from here to let it check
497 // their eligibility right away without starting to prefetch them. It
498 // should also be possible to trigger the start of the prefetch based on
499 // heuristics.
500 return true;
501 }
502
Adithya Srinivasand1c68ba2023-05-30 19:54:25503 processed_candidates_[key].push_back(candidate.Clone());
504
Alison Gale81f4f2c72024-04-22 19:33:31505 // TODO(crbug.com/40230530): Pass the action requested by speculation rules
Iman Saboori64dc4de2022-11-29 18:58:06506 // to PreloadingPrediction.
HuanPo Lin12ce3b5fb42024-03-04 09:01:14507 // A new web contents will be created for the case of prerendering into a
508 // new tab, so recording PreloadingPrediction is delayed until
509 // PrerenderNewTabHandle::StartPrerendering.
Kevin McNee98e068a2024-04-09 20:12:34510 bool add_preloading_prediction =
511 !PredictionOccursInOtherWebContents(*candidate);
HuanPo Lin12ce3b5fb42024-03-04 09:01:14512
513 if (add_preloading_prediction) {
514 PreloadingTriggerType trigger_type =
515 PreloadingTriggerTypeFromSpeculationInjectionType(
516 candidate->injection_type);
Takashi Nakayama978f0a152025-06-17 08:26:25517 // Immediate candidates are enacted by the same predictor that creates
518 // them.
Kevin McNee98e068a2024-04-09 20:12:34519 PreloadingPredictor enacting_predictor =
HuanPo Lin12ce3b5fb42024-03-04 09:01:14520 GetPredictorForPreloadingTriggerType(trigger_type);
Kevin McNee842eb0a2024-04-11 20:14:16521 AddPreloadingPrediction(candidate->url, std::move(enacting_predictor),
522 PreloadingConfidence{100});
HuanPo Lin12ce3b5fb42024-03-04 09:01:14523 }
Iman Saboori64dc4de2022-11-29 18:58:06524
525 return false;
526 };
527
Liviu Tinta8a3a7bc2023-05-23 00:44:34528 ClearStandbyCandidates();
Adithya Srinivasand1c68ba2023-05-30 19:54:25529
530 // The lists of SpeculationCandidates cached in |processed_candidates_| will
531 // be stale now, so we clear the lists now and repopulate them below.
532 for (auto& entry : processed_candidates_) {
533 entry.second.clear();
534 }
535
Takashi Nakayama978f0a152025-06-17 08:26:25536 // Move immediage candidates to the front. This will avoid unnecessarily
537 // marking some non-immediate candidates as on-standby when there is an
538 // immediate candidate with the same URL that will be processed immediately.
Takashi Nakayama9eb7988f2025-06-25 06:14:00539 std::ranges::stable_partition(candidates, [](const auto& candidate) {
540 return IsImmediateSpeculationEagerness(candidate->eagerness);
Adithya Srinivasand1c68ba2023-05-30 19:54:25541 });
542
Takashi Nakayama978f0a152025-06-17 08:26:25543 // The candidates remaining after this call will be all immediate candidates,
544 // and all non-immediate candidates whose (url, action) pair has already been
Adithya Srinivasand1c68ba2023-05-30 19:54:25545 // processed.
Andrew Rayskiyf65990362024-02-27 18:43:24546 std::erase_if(candidates, should_mark_as_on_standby);
Iman Saboori64dc4de2022-11-29 18:58:06547
HuanPo Lin5860e8d2025-03-31 00:52:36548 // TODO(crbug.com/381687257): Combine all speculation rules tags merging logic
549 // in PreloadingDecider to reduce code redundancy.
Takashi Nakayama978f0a152025-06-17 08:26:25550 // Aggregate all tags for immediate candidates.
HuanPo Lin5860e8d2025-03-31 00:52:36551 std::map<SpeculationCandidateKey, std::vector<std::optional<std::string>>>
Takashi Nakayama978f0a152025-06-17 08:26:25552 tags_map_for_immediate_preloading;
HuanPo Lin5860e8d2025-03-31 00:52:36553 for (auto& candidate : candidates) {
Takashi Nakayama9eb7988f2025-06-25 06:14:00554 if (!IsImmediateSpeculationEagerness(candidate->eagerness)) {
HuanPo Lin5860e8d2025-03-31 00:52:36555 continue;
556 }
557
558 SpeculationCandidateKey key{candidate->url, candidate->action};
559 for (const auto& tag : candidate->tags) {
Takashi Nakayama978f0a152025-06-17 08:26:25560 tags_map_for_immediate_preloading[key].push_back(tag);
HuanPo Lin5860e8d2025-03-31 00:52:36561 }
562 }
563
564 for (auto& candidate : candidates) {
Takashi Nakayama9eb7988f2025-06-25 06:14:00565 if (!IsImmediateSpeculationEagerness(candidate->eagerness)) {
HuanPo Lin5860e8d2025-03-31 00:52:36566 continue;
567 }
568
569 SpeculationCandidateKey key{candidate->url, candidate->action};
Takashi Nakayama978f0a152025-06-17 08:26:25570 if (tags_map_for_immediate_preloading.count(key) != 0) {
571 candidate->tags = tags_map_for_immediate_preloading[key];
HuanPo Lin5860e8d2025-03-31 00:52:36572 }
573 }
574
Adithya Srinivasan94ec2fea2023-05-01 17:02:15575 prefetcher_.ProcessCandidatesForPrefetch(candidates);
Robert Lina5f5ca72023-03-23 02:44:48576
Adithya Srinivasan94ec2fea2023-05-01 17:02:15577 prerenderer_->ProcessCandidatesForPrerender(candidates);
Iman Saboori03a661d2022-11-17 04:37:59578}
579
Yoichi Osatofed56ff82024-02-29 01:13:45580void PreloadingDecider::OnLCPPredicted() {
581 prerenderer_->OnLCPPredicted();
582}
583
HuanPo Lin9b730372025-03-28 05:50:45584std::vector<std::optional<std::string>>
585PreloadingDecider::GetMergedSpeculationTagsFromSuitableCandidates(
586 const PreloadingDecider::SpeculationCandidateKey& lookup_key,
587 const PreloadingPredictor& enacting_predictor,
Takashi Nakayama80498ee2025-07-31 05:31:31588 PreloadingConfidence confidence,
589 EagernessSet eagerness_to_exclude) {
HuanPo Lin9b730372025-03-28 05:50:45590 std::vector<std::optional<std::string>> merged_tags;
591
Rulong Chen(陈汝龙)88e06882025-07-14 12:57:24592 // Find all suitable candidates.
Takashi Nakayama80498ee2025-07-31 05:31:31593 auto suitable_candidates = FindSuitableCandidates(
594 lookup_key, enacting_predictor, confidence, eagerness_to_exclude);
HuanPo Lin9b730372025-03-28 05:50:45595
Rulong Chen(陈汝龙)88e06882025-07-14 12:57:24596 // Iterate through all suitable candidates and merge their tags.
597 for (const auto& candidate_pair : suitable_candidates) {
598 for (const auto& tag : candidate_pair.second->tags) {
HuanPo Lin9b730372025-03-28 05:50:45599 if (!base::Contains(merged_tags, tag)) {
600 merged_tags.push_back(tag);
601 }
602 }
603 }
604
605 return merged_tags;
606}
607
Kevin McNee98e068a2024-04-09 20:12:34608bool PreloadingDecider::MaybePrefetch(
609 const GURL& url,
Kevin McNee842eb0a2024-04-11 20:14:16610 const PreloadingPredictor& enacting_predictor,
Takashi Nakayama80498ee2025-07-31 05:31:31611 PreloadingConfidence confidence,
612 EagernessSet eagerness_to_exclude) {
Jeremy Roman2cf0e772023-03-13 23:20:00613 SpeculationCandidateKey key{url, blink::mojom::SpeculationAction::kPrefetch};
HuanPo Lin9b730372025-03-28 05:50:45614 std::vector<std::optional<std::string>> merged_tags =
Takashi Nakayama80498ee2025-07-31 05:31:31615 GetMergedSpeculationTagsFromSuitableCandidates(
616 key, enacting_predictor, confidence, eagerness_to_exclude);
Liviu Tinta52efaa062024-06-14 22:56:42617 std::optional<std::pair<PreloadingDecider::SpeculationCandidateKey,
618 blink::mojom::SpeculationCandidatePtr>>
Takashi Nakayama80498ee2025-07-31 05:31:31619 matched_candidate_pair = GetMatchedPreloadingCandidate(
620 key, enacting_predictor, confidence, eagerness_to_exclude);
Liviu Tinta52efaa062024-06-14 22:56:42621 if (!matched_candidate_pair.has_value()) {
622 return false;
623 }
624
625 key = matched_candidate_pair.value().first;
HuanPo Lin9b730372025-03-28 05:50:45626 matched_candidate_pair.value().second->tags = merged_tags;
Liviu Tinta52efaa062024-06-14 22:56:42627 bool result = prefetcher_.MaybePrefetch(
628 std::move(matched_candidate_pair.value().second), enacting_predictor);
Liviu Tinta8a3a7bc2023-05-23 00:44:34629
630 auto it = on_standby_candidates_.find(key);
Alex Gough42aed902024-07-23 03:17:47631 CHECK(it != on_standby_candidates_.end());
Liviu Tinta52efaa062024-06-14 22:56:42632 std::vector<blink::mojom::SpeculationCandidatePtr> candidates_for_key =
633 std::move(it->second);
634 RemoveStandbyCandidate(key);
635 processed_candidates_[std::move(key)] = std::move(candidates_for_key);
636 return result;
637}
638
639std::optional<std::pair<PreloadingDecider::SpeculationCandidateKey,
640 blink::mojom::SpeculationCandidatePtr>>
641PreloadingDecider::GetMatchedPreloadingCandidate(
642 const PreloadingDecider::SpeculationCandidateKey& lookup_key,
643 const PreloadingPredictor& enacting_predictor,
Takashi Nakayama80498ee2025-07-31 05:31:31644 PreloadingConfidence confidence,
645 EagernessSet eagerness_to_exclude) const {
Rulong Chen(陈汝龙)88e06882025-07-14 12:57:24646 // Find all suitable candidates.
Takashi Nakayama80498ee2025-07-31 05:31:31647 auto suitable_candidates = FindSuitableCandidates(
648 lookup_key, enacting_predictor, confidence, eagerness_to_exclude);
Liviu Tinta52efaa062024-06-14 22:56:42649
Rulong Chen(陈汝龙)88e06882025-07-14 12:57:24650 if (suitable_candidates.empty()) {
Liviu Tinta52efaa062024-06-14 22:56:42651 return std::nullopt;
652 }
653
Rulong Chen(陈汝龙)88e06882025-07-14 12:57:24654 // Return the first suitable candidate if any are found.
655 return std::move(suitable_candidates[0]);
Liviu Tinta52efaa062024-06-14 22:56:42656}
657
Rulong Chen(陈汝龙)88e06882025-07-14 12:57:24658// Enumerates all NVS-matched candidates and invokes the visitor for each match.
659// If the visitor returns true, enumeration stops early.
660template <typename Visitor>
661void PreloadingDecider::EnumerateNoVarySearchMatchedCandidates(
662 const SpeculationCandidateKey& lookup_key,
Liviu Tinta52efaa062024-06-14 22:56:42663 const PreloadingPredictor& enacting_predictor,
Rulong Chen(陈汝龙)88e06882025-07-14 12:57:24664 PreloadingConfidence confidence,
Takashi Nakayama80498ee2025-07-31 05:31:31665 EagernessSet eagerness_to_exclude,
Rulong Chen(陈汝龙)88e06882025-07-14 12:57:24666 Visitor&& visitor) const {
667 // Remove query and ref from the URL for NVS matching.
Liviu Tinta52efaa062024-06-14 22:56:42668 GURL::Replacements replacements;
669 replacements.ClearRef();
670 replacements.ClearQuery();
671 const GURL url_without_query_and_ref =
672 lookup_key.first.ReplaceComponents(replacements);
673 auto nvs_it = no_vary_search_hint_on_standby_candidates_.find(
674 {url_without_query_and_ref, lookup_key.second});
675 if (nvs_it == no_vary_search_hint_on_standby_candidates_.end()) {
Rulong Chen(陈汝龙)88e06882025-07-14 12:57:24676 return;
Liviu Tinta52efaa062024-06-14 22:56:42677 }
Rulong Chen(陈汝龙)88e06882025-07-14 12:57:24678
Liviu Tinta52efaa062024-06-14 22:56:42679 for (const auto& standby_key : nvs_it->second) {
680 CHECK_EQ(standby_key.second, lookup_key.second);
681 const GURL& preload_url = standby_key.first;
Liviu Tinta52efaa062024-06-14 22:56:42682 auto standby_it = on_standby_candidates_.find(standby_key);
683 CHECK(standby_it != on_standby_candidates_.end());
Rulong Chen(陈汝龙)88e06882025-07-14 12:57:24684
685 for (const auto& on_standby_candidate : standby_it->second) {
686 if (on_standby_candidate->no_vary_search_hint &&
687 no_vary_search::ParseHttpNoVarySearchDataFromMojom(
688 on_standby_candidate->no_vary_search_hint)
689 .AreEquivalent(lookup_key.first, preload_url) &&
690 IsSuitableCandidate(on_standby_candidate, enacting_predictor,
Takashi Nakayama80498ee2025-07-31 05:31:31691 confidence, standby_key.second,
692 eagerness_to_exclude)) {
Rulong Chen(陈汝龙)88e06882025-07-14 12:57:24693 // If visitor returns true, stop enumeration early.
694 if (visitor(standby_key, on_standby_candidate)) {
695 return;
696 }
697 }
698 }
699 }
700}
701
702std::vector<std::pair<PreloadingDecider::SpeculationCandidateKey,
703 blink::mojom::SpeculationCandidatePtr>>
704PreloadingDecider::FindSuitableCandidates(
705 const PreloadingDecider::SpeculationCandidateKey& lookup_key,
706 const PreloadingPredictor& enacting_predictor,
Takashi Nakayama80498ee2025-07-31 05:31:31707 PreloadingConfidence confidence,
708 EagernessSet eagerness_to_exclude) const {
Rulong Chen(陈汝龙)88e06882025-07-14 12:57:24709 std::vector<
710 std::pair<SpeculationCandidateKey, blink::mojom::SpeculationCandidatePtr>>
711 suitable_candidates;
712
713 // First, attempt a direct lookup for the exact key.
714 auto it = on_standby_candidates_.find(lookup_key);
715 if (it != on_standby_candidates_.end()) {
716 for (const auto& candidate : it->second) {
717 if (IsSuitableCandidate(candidate, enacting_predictor, confidence,
Takashi Nakayama80498ee2025-07-31 05:31:31718 lookup_key.second, eagerness_to_exclude)) {
Rulong Chen(陈汝龙)88e06882025-07-14 12:57:24719 suitable_candidates.emplace_back(lookup_key, candidate.Clone());
720 }
Liviu Tinta8a3a7bc2023-05-23 00:44:34721 }
722 }
723
Rulong Chen(陈汝龙)88e06882025-07-14 12:57:24724 // If a direct match is found, return early.
725 if (!suitable_candidates.empty()) {
726 return suitable_candidates;
Liviu Tinta8a3a7bc2023-05-23 00:44:34727 }
728
Rulong Chen(陈汝龙)88e06882025-07-14 12:57:24729 // Use NVS matching to collect all suitable candidates.
730 EnumerateNoVarySearchMatchedCandidates(
Takashi Nakayama80498ee2025-07-31 05:31:31731 lookup_key, enacting_predictor, confidence, eagerness_to_exclude,
Rulong Chen(陈汝龙)88e06882025-07-14 12:57:24732 [&](const SpeculationCandidateKey& standby_key,
733 const blink::mojom::SpeculationCandidatePtr& candidate) {
734 suitable_candidates.emplace_back(standby_key, candidate.Clone());
735 return false; // Continue enumeration to collect all matches.
736 });
737
738 return suitable_candidates;
739}
740
Iman Saboori64dc4de2022-11-29 18:58:06741bool PreloadingDecider::ShouldWaitForPrefetchResult(const GURL& url) {
Liviu Tinta8a3a7bc2023-05-23 00:44:34742 // TODO(liviutinta): Don't implement any No-Vary-Search hint matching here
743 // for now. It is not clear how to match `url` with a `processed_candidate`.
744 // Also, for a No-Vary-Search hint matched candidate we might end up not
745 // using the processed_candidate at all. We will revisit this later.
Iman Saboori64dc4de2022-11-29 18:58:06746 auto it = processed_candidates_.find(
747 {url, blink::mojom::SpeculationAction::kPrefetch});
Iman Saboori86e1b6d2023-08-28 18:42:35748 if (it == processed_candidates_.end()) {
Iman Saboori64dc4de2022-11-29 18:58:06749 return false;
Iman Saboori86e1b6d2023-08-28 18:42:35750 }
Iman Saboori64dc4de2022-11-29 18:58:06751 return !prefetcher_.IsPrefetchAttemptFailedOrDiscarded(url);
752}
753
Kevin McNee98e068a2024-04-09 20:12:34754std::pair<bool, bool> PreloadingDecider::MaybePrerender(
755 const GURL& url,
Kevin McNee842eb0a2024-04-11 20:14:16756 const PreloadingPredictor& enacting_predictor,
Takashi Nakayama80498ee2025-07-31 05:31:31757 PreloadingConfidence confidence,
758 EagernessSet eagerness_to_exclude) {
Kevin McNee98e068a2024-04-09 20:12:34759 std::pair<bool, bool> result{false, false};
Iman Saboori31bed872022-12-09 15:41:31760 SpeculationCandidateKey key{url, blink::mojom::SpeculationAction::kPrerender};
HuanPo Lin9262183f2025-03-31 11:33:23761 std::vector<std::optional<std::string>> merged_tags =
Takashi Nakayama80498ee2025-07-31 05:31:31762 GetMergedSpeculationTagsFromSuitableCandidates(
763 key, enacting_predictor, confidence, eagerness_to_exclude);
Liviu Tinta52efaa062024-06-14 22:56:42764 std::optional<std::pair<PreloadingDecider::SpeculationCandidateKey,
765 blink::mojom::SpeculationCandidatePtr>>
Takashi Nakayama80498ee2025-07-31 05:31:31766 matched_candidate_pair = GetMatchedPreloadingCandidate(
767 key, enacting_predictor, confidence, eagerness_to_exclude);
Liviu Tinta52efaa062024-06-14 22:56:42768 if (!matched_candidate_pair.has_value()) {
Kevin McNee98e068a2024-04-09 20:12:34769 return result;
Iman Saboori31bed872022-12-09 15:41:31770 }
771
Liviu Tinta52efaa062024-06-14 22:56:42772 key = matched_candidate_pair.value().first;
HuanPo Lin9262183f2025-03-31 11:33:23773 matched_candidate_pair.value().second->tags = merged_tags;
Liviu Tinta52efaa062024-06-14 22:56:42774 blink::mojom::SpeculationCandidatePtr candidate =
775 std::move(matched_candidate_pair.value().second);
Kevin McNee842eb0a2024-04-11 20:14:16776 result.first =
Liviu Tinta52efaa062024-06-14 22:56:42777 prerenderer_->MaybePrerender(candidate, enacting_predictor, confidence);
Kevin McNee98e068a2024-04-09 20:12:34778
779 result.second =
Liviu Tinta52efaa062024-06-14 22:56:42780 result.first && PredictionOccursInOtherWebContents(*candidate);
Iman Saboori31bed872022-12-09 15:41:31781
Liviu Tinta52efaa062024-06-14 22:56:42782 auto it = on_standby_candidates_.find(key);
Alex Gough42aed902024-07-23 03:17:47783 CHECK(it != on_standby_candidates_.end());
Adithya Srinivasand1c68ba2023-05-30 19:54:25784 std::vector<blink::mojom::SpeculationCandidatePtr> processed =
785 std::move(it->second);
Liviu Tinta8a3a7bc2023-05-23 00:44:34786 RemoveStandbyCandidate(it->first);
Adithya Srinivasand1c68ba2023-05-30 19:54:25787 processed_candidates_[std::move(key)] = std::move(processed);
Iman Saboori31bed872022-12-09 15:41:31788 return result;
789}
790
791bool PreloadingDecider::ShouldWaitForPrerenderResult(const GURL& url) {
792 auto it = processed_candidates_.find(
793 {url, blink::mojom::SpeculationAction::kPrerender});
Iman Saboori86e1b6d2023-08-28 18:42:35794 if (it == processed_candidates_.end()) {
Iman Saboori31bed872022-12-09 15:41:31795 return false;
Iman Saboori86e1b6d2023-08-28 18:42:35796 }
Iman Saboori31bed872022-12-09 15:41:31797 return prerenderer_->ShouldWaitForPrerenderResult(url);
798}
799
Jeremy Roman2cf0e772023-03-13 23:20:00800bool PreloadingDecider::IsSuitableCandidate(
801 const blink::mojom::SpeculationCandidatePtr& candidate,
Kevin McNee842eb0a2024-04-11 20:14:16802 const PreloadingPredictor& predictor,
803 PreloadingConfidence confidence,
Takashi Nakayama80498ee2025-07-31 05:31:31804 blink::mojom::SpeculationAction action,
805 EagernessSet eagerness_to_exclude) const {
Kevin McNee842eb0a2024-04-11 20:14:16806 EagernessSet eagerness_set_for_predictor =
807 behavior_config_->EagernessSetForPredictor(predictor);
Takashi Nakayama80498ee2025-07-31 05:31:31808 eagerness_set_for_predictor.RemoveAll(eagerness_to_exclude);
Kevin McNee842eb0a2024-04-11 20:14:16809
810 // If the ML model is available, its decisions supersede the hover heuristic.
811 if (ml_model_available_ &&
812 predictor == preloading_predictor::kUrlPointerHoverOnAnchor) {
813 eagerness_set_for_predictor.RemoveAll(
814 behavior_config_->EagernessSetForPredictor(
815 preloading_predictor::kPreloadingHeuristicsMLModel));
816 }
817
818 return eagerness_set_for_predictor.Has(candidate->eagerness) &&
819 confidence >= behavior_config_->GetThreshold(predictor, action);
Jeremy Roman2cf0e772023-03-13 23:20:00820}
821
Iman Saboori64dc4de2022-11-29 18:58:06822PreloadingDeciderObserverForTesting* PreloadingDecider::SetObserverForTesting(
823 PreloadingDeciderObserverForTesting* observer) {
824 return std::exchange(observer_for_testing_, observer);
825}
826
Taiyo Mizuhashif3098ea2023-10-11 13:14:26827Prerenderer& PreloadingDecider::GetPrerendererForTesting() {
828 CHECK(prerenderer_);
829 return *prerenderer_;
830}
831
Iman Saboori31bed872022-12-09 15:41:31832std::unique_ptr<Prerenderer> PreloadingDecider::SetPrerendererForTesting(
833 std::unique_ptr<Prerenderer> prerenderer) {
Taiyo Mizuhashif3098ea2023-10-11 13:14:26834 prerenderer->SetPrerenderCancellationCallback(base::BindRepeating(
835 &OnPrerenderCanceled, render_frame_host().GetWeakDocumentPtr()));
Iman Saboori31bed872022-12-09 15:41:31836 return std::exchange(prerenderer_, std::move(prerenderer));
837}
838
839bool PreloadingDecider::IsOnStandByForTesting(
840 const GURL& url,
Kevin McNee031e28c2024-03-21 20:55:00841 blink::mojom::SpeculationAction action) const {
842 return on_standby_candidates_.contains({url, action});
843}
844
845bool PreloadingDecider::HasCandidatesForTesting() const {
846 return !on_standby_candidates_.empty() ||
847 !no_vary_search_hint_on_standby_candidates_.empty() ||
848 !processed_candidates_.empty();
Iman Saboori31bed872022-12-09 15:41:31849}
850
Taiyo Mizuhashi295ccbdf2023-09-13 09:17:02851void PreloadingDecider::OnPreloadDiscarded(SpeculationCandidateKey key) {
Adithya Srinivasand1c68ba2023-05-30 19:54:25852 auto it = processed_candidates_.find(key);
kenoss18aa9592024-06-24 09:26:16853 // If the preload is triggered outside of `PreloadingDecider`, ignore it.
854 // Currently, `PrerendererImpl` triggers prefetch ahead of prerender.
855 if (it == processed_candidates_.end()) {
856 return;
857 }
858
Adithya Srinivasand1c68ba2023-05-30 19:54:25859 std::vector<blink::mojom::SpeculationCandidatePtr> candidates =
860 std::move(it->second);
861 processed_candidates_.erase(it);
862 for (const auto& candidate : candidates) {
Takashi Nakayama9eb7988f2025-06-25 06:14:00863 if (!IsImmediateSpeculationEagerness(candidate->eagerness)) {
Adithya Srinivasand1c68ba2023-05-30 19:54:25864 AddStandbyCandidate(candidate);
865 }
Alison Gale770f3fc2024-04-27 00:39:58866 // TODO(crbug.com/40064525): Add support for the case where |candidate|'s
Takashi Nakayama9eb7988f2025-06-25 06:14:00867 // eagerness is immediate one like `kImmediate`. In a scenario where the
868 // prefetch evicted is a non-immediate prefetch, we could theoretically
869 // reprefetch using the immediate candidate (and have it use the immediate
870 // prefetch quota). In that scenario, perhaps not evicting and just making
871 // the prefetch use the immediate limit might be a better option too. In the
872 // case where an immediate prefetch is evicted, we don't want to immediately
873 // try and reprefetch the candidate; it would defeat the purpose of evicting
874 // in the first place, and due to a possible-rentrancy into
Takashi Nakayama978f0a152025-06-17 08:26:25875 // PrefetchService::Prefetch(), it could cause us to exceed the limit.
Taiyo Mizuhashi295ccbdf2023-09-13 09:17:02876
Takashi Nakayama9eb7988f2025-06-25 06:14:00877 // TODO(crbug.com/40275452): Add implementation for immediate cases for
Taiyo Mizuhashi295ccbdf2023-09-13 09:17:02878 // prerender.
Adithya Srinivasand1c68ba2023-05-30 19:54:25879 }
880}
881
Iman Saboori16071722022-11-04 14:59:46882} // namespace content