blob: b05114e66bb3e9cb4c93fd9f2ed073e03fdcc41b [file] [log] [blame]
Iman Saboori03a661d2022-11-17 04:37:591// 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"
Iman Saboori03a661d2022-11-17 04:37:596
Arthur Sonzognid5ce01f72024-12-13 13:35:287#include <array>
8
Lei Zhang0a85e65a2025-05-23 19:22:069#include "base/strings/string_number_conversions.h"
Iman Saboori03a661d2022-11-17 04:37:5910#include "base/test/scoped_feature_list.h"
Kevin McNee842eb0a2024-04-11 20:14:1611#include "content/browser/preloading/preloading_confidence.h"
Taiyo Mizuhashif8dcaed2023-10-05 18:38:5512#include "content/browser/preloading/prerender/prerender_features.h"
Lei Zhang5686e522023-03-02 17:33:1013#include "content/browser/preloading/prerender/prerender_host_registry.h"
Iman Saboori03a661d2022-11-17 04:37:5914#include "content/public/browser/web_contents_delegate.h"
15#include "content/public/common/content_client.h"
Johanna4bf1cd2022-11-30 23:59:3916#include "content/public/test/prerender_test_util.h"
Iman Saboori03a661d2022-11-17 04:37:5917#include "content/public/test/test_browser_context.h"
18#include "content/public/test/test_renderer_host.h"
19#include "content/test/test_content_browser_client.h"
20#include "content/test/test_web_contents.h"
21#include "testing/gtest/include/gtest/gtest.h"
22
23namespace content {
24namespace {
25
Iman Saboori03a661d2022-11-17 04:37:5926class PrerendererTest : public RenderViewHostTestHarness {
27 public:
Johanna4bf1cd2022-11-30 23:59:3928 PrerendererTest() = default;
Iman Saboori03a661d2022-11-17 04:37:5929
30 void SetUp() override {
31 RenderViewHostTestHarness::SetUp();
32
33 browser_context_ = std::make_unique<TestBrowserContext>();
34 web_contents_ = TestWebContents::Create(
35 browser_context_.get(),
36 SiteInstanceImpl::Create(browser_context_.get()));
Johannbecce082023-01-17 05:14:3237 web_contents_delegate_ =
38 std::make_unique<test::ScopedPrerenderWebContentsDelegate>(
39 *web_contents_);
Iman Saboori03a661d2022-11-17 04:37:5940 web_contents_->NavigateAndCommit(GURL("https://example.com"));
41 }
42
43 void TearDown() override {
44 web_contents_.reset();
45 browser_context_.reset();
46 RenderViewHostTestHarness::TearDown();
47 }
48
49 RenderFrameHostImpl* GetRenderFrameHost() {
50 return web_contents_->GetPrimaryMainFrame();
51 }
52
53 GURL GetSameOriginUrl(const std::string& path) {
54 return GURL("https://example.com" + path);
55 }
56
57 GURL GetCrossSiteUrl(const std::string& path) {
58 return GURL("https://example2.com" + path);
59 }
60
61 PrerenderHostRegistry* GetPrerenderHostRegistry() const {
62 return web_contents_->GetPrerenderHostRegistry();
63 }
64
65 blink::mojom::SpeculationCandidatePtr CreatePrerenderCandidate(
66 const GURL& url) {
67 auto candidate = blink::mojom::SpeculationCandidate::New();
68 candidate->action = blink::mojom::SpeculationAction::kPrerender;
69 candidate->url = url;
70 candidate->referrer = blink::mojom::Referrer::New();
Takashi Nakayama978f0a152025-06-17 08:26:2571 candidate->eagerness = blink::mojom::SpeculationEagerness::kImmediate;
Hiroki Nakagawac91e36222025-05-02 11:00:2772 candidate->tags = {std::nullopt};
Taiyo Mizuhashif277ece2024-01-12 17:29:2873 return candidate;
74 }
75
76 blink::mojom::SpeculationCandidatePtr CreatePrerenderCandidateWithEagerness(
77 const GURL& url,
78 blink::mojom::SpeculationEagerness eagerness) {
79 blink::mojom::SpeculationCandidatePtr candidate =
80 CreatePrerenderCandidate(url);
81 candidate->eagerness = eagerness;
Iman Saboori03a661d2022-11-17 04:37:5982 return candidate;
83 }
84
85 private:
Johanna4bf1cd2022-11-30 23:59:3986 test::ScopedPrerenderFeatureList prerender_feature_list_;
Iman Saboori03a661d2022-11-17 04:37:5987 std::unique_ptr<TestBrowserContext> browser_context_;
88 std::unique_ptr<TestWebContents> web_contents_;
Johannbecce082023-01-17 05:14:3289 std::unique_ptr<test::ScopedPrerenderWebContentsDelegate>
90 web_contents_delegate_;
Iman Saboori03a661d2022-11-17 04:37:5991};
92
93// Tests that Prerenderer starts prerendering when it receives prerender
94// speculation candidates.
95TEST_F(PrerendererTest, StartPrerender) {
96 PrerenderHostRegistry* registry = GetPrerenderHostRegistry();
Iman Saboori31bed872022-12-09 15:41:3197 PrerendererImpl prerenderer(*GetRenderFrameHost());
Iman Saboori03a661d2022-11-17 04:37:5998
99 const GURL kPrerenderingUrl = GetSameOriginUrl("/empty.html");
100 std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
101 candidates.push_back(CreatePrerenderCandidate(kPrerenderingUrl));
102
Adithya Srinivasan94ec2fea2023-05-01 17:02:15103 prerenderer.ProcessCandidatesForPrerender(std::move(candidates));
Iman Saboori03a661d2022-11-17 04:37:59104 EXPECT_TRUE(registry->FindHostByUrlForTesting(kPrerenderingUrl));
105}
106
Yoichi Osatofed56ff82024-02-29 01:13:45107// Tests that Prerenderer should not start prerendering when
108// kLCPTimingPredictorPrerender2 is enabled and until OnLCPPredicted is called.
109TEST_F(PrerendererTest, LCPTimingPredictorPrerender2) {
110 base::test::ScopedFeatureList scoped_feature_list;
111 scoped_feature_list.InitAndEnableFeature(
112 blink::features::kLCPTimingPredictorPrerender2);
113
114 PrerenderHostRegistry* registry = GetPrerenderHostRegistry();
115 PrerendererImpl prerenderer(*GetRenderFrameHost());
116
117 const GURL kPrerenderingUrl = GetSameOriginUrl("/empty.html");
118 std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
119 candidates.push_back(CreatePrerenderCandidate(kPrerenderingUrl));
120
121 prerenderer.ProcessCandidatesForPrerender(std::move(candidates));
122 EXPECT_FALSE(registry->FindHostByUrlForTesting(kPrerenderingUrl));
123
124 prerenderer.OnLCPPredicted();
125 EXPECT_TRUE(registry->FindHostByUrlForTesting(kPrerenderingUrl));
126}
127
Iman Saboori03a661d2022-11-17 04:37:59128// Tests that Prerenderer will skip a cross-site candidate even if it is the
129// first prerender candidate in the candidate list.
130TEST_F(PrerendererTest, ProcessFirstSameOriginPrerenderCandidate) {
131 PrerenderHostRegistry* registry = GetPrerenderHostRegistry();
Iman Saboori31bed872022-12-09 15:41:31132 PrerendererImpl prerenderer(*GetRenderFrameHost());
Iman Saboori03a661d2022-11-17 04:37:59133
134 const GURL kFirstPrerenderingUrlCrossSite = GetCrossSiteUrl("/title.html");
135 const GURL kSecondPrerenderingUrlSameOrigin =
136 GetSameOriginUrl("/title1.html");
137 std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
138 candidates.push_back(
139 CreatePrerenderCandidate(kFirstPrerenderingUrlCrossSite));
140 candidates.push_back(
141 CreatePrerenderCandidate(kSecondPrerenderingUrlSameOrigin));
142
Adithya Srinivasan94ec2fea2023-05-01 17:02:15143 prerenderer.ProcessCandidatesForPrerender(std::move(candidates));
Iman Saboori03a661d2022-11-17 04:37:59144
145 // The first prerender candidate is a cross-site one, so Prerenderer should
146 // not prerender it.
147 EXPECT_FALSE(
148 registry->FindHostByUrlForTesting(kFirstPrerenderingUrlCrossSite));
149 // The second element in this list is the first same-origin prerender
150 // candidate, so Prerenderer should prerender this candidate.
151 EXPECT_TRUE(
152 registry->FindHostByUrlForTesting(kSecondPrerenderingUrlSameOrigin));
153}
154
Taiyo Mizuhashif277ece2024-01-12 17:29:28155class PrerenderHostRegistryObserver : public PrerenderHostRegistry::Observer {
156 public:
157 explicit PrerenderHostRegistryObserver(
158 PrerenderHostRegistry* prerender_host_registry) {
159 observation_.Observe(prerender_host_registry);
160 }
161
162 void OnTrigger(const GURL& url) override { trigger_sequence_.push_back(url); }
163
164 void OnRegistryDestroyed() override { observation_.Reset(); }
165
166 const std::vector<GURL>& GetTriggerSequence() { return trigger_sequence_; }
167
168 private:
169 base::ScopedObservation<PrerenderHostRegistry,
170 PrerenderHostRegistry::Observer>
171 observation_{this};
172
173 std::vector<GURL> trigger_sequence_;
174};
175
176// Test that ProcessCandidatesForPrerender will trigger candidates in the same
177// order as the input of the candidate list (crbug.com/1505301).
178TEST_F(PrerendererTest, TriggerPrerenderWithInsertionOrder) {
179 PrerenderHostRegistry* registry = GetPrerenderHostRegistry();
180 PrerenderHostRegistryObserver observer{registry};
181 PrerendererImpl prerenderer(*GetRenderFrameHost());
182
183 std::vector<GURL> urls = {
184 GetSameOriginUrl("/empty.html?a"),
185 GetSameOriginUrl("/empty.html?c"),
186 GetSameOriginUrl("/empty.html?b"),
187 };
188
189 std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
190 for (const auto& url : urls) {
191 candidates.push_back(CreatePrerenderCandidate(url));
192 }
193
194 prerenderer.ProcessCandidatesForPrerender(std::move(candidates));
195
196 for (const auto& url : urls) {
197 ASSERT_TRUE(registry->FindHostByUrlForTesting(url));
198 }
199 EXPECT_EQ(observer.GetTriggerSequence(), urls);
200}
201
Iman Saboori03a661d2022-11-17 04:37:59202// Tests that Prerenderer will remove the rendered host, if the url is removed
203// from candidates list.
204TEST_F(PrerendererTest, RemoveRendererHostAfterCandidateRemoved) {
205 PrerenderHostRegistry* registry = GetPrerenderHostRegistry();
Iman Saboori31bed872022-12-09 15:41:31206 PrerendererImpl prerenderer(*GetRenderFrameHost());
Iman Saboori03a661d2022-11-17 04:37:59207
Arthur Sonzognid5ce01f72024-12-13 13:35:28208 const auto urls = std::to_array<GURL>(
209 {GetSameOriginUrl("/title1.html"), GetSameOriginUrl("/title2.html")});
Iman Saboori03a661d2022-11-17 04:37:59210 std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
211 for (const auto& url : urls) {
212 candidates.push_back(CreatePrerenderCandidate(url));
213 }
Adithya Srinivasan94ec2fea2023-05-01 17:02:15214 prerenderer.ProcessCandidatesForPrerender(std::move(candidates));
Iman Saboori03a661d2022-11-17 04:37:59215 EXPECT_TRUE(registry->FindHostByUrlForTesting(urls[0]));
216 EXPECT_TRUE(registry->FindHostByUrlForTesting(urls[1]));
217
218 std::vector<blink::mojom::SpeculationCandidatePtr> new_candidates;
219 new_candidates.push_back(CreatePrerenderCandidate(urls[1]));
Adithya Srinivasan94ec2fea2023-05-01 17:02:15220 prerenderer.ProcessCandidatesForPrerender(std::move(new_candidates));
Iman Saboori03a661d2022-11-17 04:37:59221 EXPECT_FALSE(registry->FindHostByUrlForTesting(urls[0]));
222 EXPECT_TRUE(registry->FindHostByUrlForTesting(urls[1]));
223
224 prerenderer.ProcessCandidatesForPrerender(
225 std::vector<blink::mojom::SpeculationCandidatePtr>{});
226 EXPECT_FALSE(registry->FindHostByUrlForTesting(urls[0]));
227 EXPECT_FALSE(registry->FindHostByUrlForTesting(urls[1]));
228}
229
Taiyo Mizuhashif8dcaed2023-10-05 18:38:55230// Tests that Prerenderer will remove the host if the host is canceled with
Takashi Nakayama978f0a152025-06-17 08:26:25231// non-immediate limit, and the canceled host can be reprocessed.
232TEST_F(PrerendererTest, RemoveRendererHostAfterNonImmediateLimitCancel) {
Taiyo Mizuhashif8dcaed2023-10-05 18:38:55233 PrerenderHostRegistry* registry = GetPrerenderHostRegistry();
234 PrerendererImpl prerenderer(*GetRenderFrameHost());
235
236 std::vector<GURL> urls;
237
238 // Prerender as many times as limit + 1. All prerenders should be started
239 // once.
Domenic Denicola959e57a2025-06-11 09:16:09240 for (int i = 0;
Takashi Nakayama978f0a152025-06-17 08:26:25241 i < PrerenderHostRegistry::
242 kMaxRunningSpeculationRulesNonImmediatePrerenders +
243 1;
Taiyo Mizuhashif8dcaed2023-10-05 18:38:55244 i++) {
245 const GURL url = GetSameOriginUrl("/empty.html?" + base::ToString(i));
246 urls.push_back(url);
247 blink::mojom::SpeculationCandidatePtr candidate =
248 CreatePrerenderCandidateWithEagerness(
249 url, blink::mojom::SpeculationEagerness::kConservative);
Kevin McNee98e068a2024-04-09 20:12:34250 prerenderer.MaybePrerender(std::move(candidate),
Kevin McNee842eb0a2024-04-11 20:14:16251 preloading_predictor::kUnspecified,
252 PreloadingConfidence{100});
Taiyo Mizuhashif8dcaed2023-10-05 18:38:55253
254 EXPECT_TRUE(registry->FindHostByUrlForTesting(url));
255 }
256
Domenic Denicola959e57a2025-06-11 09:16:09257 for (int i = 0;
Takashi Nakayama978f0a152025-06-17 08:26:25258 i < PrerenderHostRegistry::
259 kMaxRunningSpeculationRulesNonImmediatePrerenders +
260 1;
Taiyo Mizuhashif8dcaed2023-10-05 18:38:55261 i++) {
262 if (i == 0) {
263 // The first (= oldest) prerender should be removed since the (limit +
264 // 1)-th prerender was started.
265 EXPECT_FALSE(registry->FindHostByUrlForTesting(urls[i]));
266 } else {
267 EXPECT_TRUE(registry->FindHostByUrlForTesting(urls[i]));
268 }
269 }
270
271 // Retrigger canceled host. It should be started and instead the second oldest
272 // prerender should be canceled.
273 blink::mojom::SpeculationCandidatePtr candidate =
274 CreatePrerenderCandidateWithEagerness(
275 urls[0], blink::mojom::SpeculationEagerness::kConservative);
Kevin McNee98e068a2024-04-09 20:12:34276 prerenderer.MaybePrerender(std::move(candidate),
Kevin McNee842eb0a2024-04-11 20:14:16277 preloading_predictor::kUnspecified,
278 PreloadingConfidence{100});
Domenic Denicola959e57a2025-06-11 09:16:09279 for (int i = 0;
Takashi Nakayama978f0a152025-06-17 08:26:25280 i < PrerenderHostRegistry::
281 kMaxRunningSpeculationRulesNonImmediatePrerenders +
282 1;
Taiyo Mizuhashif8dcaed2023-10-05 18:38:55283 i++) {
284 if (i == 1) {
285 EXPECT_FALSE(registry->FindHostByUrlForTesting(urls[i]));
286 } else {
287 EXPECT_TRUE(registry->FindHostByUrlForTesting(urls[i]));
288 }
289 }
290}
291
Iman Saboori31bed872022-12-09 15:41:31292// Tests that it is possible to start a prerender using MaybePrerender and
293// ShouldWaitForPrerenderResult methods.
294TEST_F(PrerendererTest, MaybePrerenderAndShouldWaitForPrerenderResult) {
295 PrerenderHostRegistry* registry = GetPrerenderHostRegistry();
296 PrerendererImpl prerenderer(*GetRenderFrameHost());
297
298 const GURL kUrlToCancel = GetSameOriginUrl("/to_cancel.html");
299 std::vector<blink::mojom::SpeculationCandidatePtr> candidateToCancel;
300 candidateToCancel.push_back(CreatePrerenderCandidate(kUrlToCancel));
301
302 // Candidate is not processed yet. So, it should return false.
303 EXPECT_FALSE(prerenderer.ShouldWaitForPrerenderResult(kUrlToCancel));
304 // Process the candidate.
Adithya Srinivasan94ec2fea2023-05-01 17:02:15305 prerenderer.ProcessCandidatesForPrerender(std::move(candidateToCancel));
Iman Saboori31bed872022-12-09 15:41:31306 EXPECT_TRUE(prerenderer.ShouldWaitForPrerenderResult(kUrlToCancel));
307 // Cancel the prerender
308 prerenderer.ProcessCandidatesForPrerender(
309 std::vector<blink::mojom::SpeculationCandidatePtr>{});
310 EXPECT_FALSE(prerenderer.ShouldWaitForPrerenderResult(kUrlToCancel));
311
312 const GURL kPrerenderingUrl = GetSameOriginUrl("/empty.html");
313 const auto candidate = CreatePrerenderCandidate(kPrerenderingUrl);
314
315 // Candidate is not processed yet. So, it should return false.
316 EXPECT_FALSE(prerenderer.ShouldWaitForPrerenderResult(kPrerenderingUrl));
317 // MaybePrerender the candidate and check if ShouldWaitForPrerenderResult
318 // returns true.
Kevin McNee98e068a2024-04-09 20:12:34319 EXPECT_TRUE(prerenderer.MaybePrerender(candidate,
Kevin McNee842eb0a2024-04-11 20:14:16320 preloading_predictor::kUnspecified,
321 PreloadingConfidence{100}));
Iman Saboori31bed872022-12-09 15:41:31322 EXPECT_TRUE(prerenderer.ShouldWaitForPrerenderResult(kPrerenderingUrl));
323 EXPECT_TRUE(registry->FindHostByUrlForTesting(kPrerenderingUrl));
324}
325
Iman Saboori03a661d2022-11-17 04:37:59326} // namespace
327} // namespace content