1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/no_state_prefetch/browser/prerender_manager.h"
6
7 #include <stddef.h>
8
9 #include <algorithm>
10 #include <functional>
11 #include <string>
12 #include <utility>
13 #include <vector>
14
15 #include "base/bind.h"
16 #include "base/callback_helpers.h"
17 #include "base/check_op.h"
18 #include "base/location.h"
19 #include "base/macros.h"
20 #include "base/memory/ptr_util.h"
21 #include "base/metrics/field_trial.h"
22 #include "base/metrics/histogram_macros.h"
23 #include "base/notreached.h"
24 #include "base/single_thread_task_runner.h"
25 #include "base/stl_util.h"
26 #include "base/strings/string_util.h"
27 #include "base/system/sys_info.h"
28 #include "base/threading/thread_task_runner_handle.h"
29 #include "base/time/default_tick_clock.h"
30 #include "base/time/time.h"
31 #include "base/timer/elapsed_timer.h"
32 #include "base/values.h"
33 #include "components/content_settings/core/browser/cookie_settings.h"
34 #include "components/no_state_prefetch/browser/prerender_contents.h"
35 #include "components/no_state_prefetch/browser/prerender_field_trial.h"
36 #include "components/no_state_prefetch/browser/prerender_handle.h"
37 #include "components/no_state_prefetch/browser/prerender_histograms.h"
38 #include "components/no_state_prefetch/browser/prerender_history.h"
39 #include "components/no_state_prefetch/browser/prerender_manager_delegate.h"
40 #include "components/no_state_prefetch/browser/prerender_util.h"
41 #include "components/no_state_prefetch/common/prerender_final_status.h"
42 #include "components/no_state_prefetch/common/prerender_types.mojom.h"
43 #include "content/public/browser/browser_thread.h"
44 #include "content/public/browser/navigation_controller.h"
45 #include "content/public/browser/render_frame_host.h"
46 #include "content/public/browser/render_process_host.h"
47 #include "content/public/browser/render_view_host.h"
48 #include "content/public/browser/session_storage_namespace.h"
49 #include "content/public/browser/site_instance.h"
50 #include "content/public/browser/web_contents.h"
51 #include "content/public/browser/web_contents_delegate.h"
52 #include "content/public/common/url_constants.h"
53 #include "net/http/http_cache.h"
54 #include "net/http/http_request_headers.h"
55 #include "ui/gfx/geometry/rect.h"
56
57 using content::BrowserThread;
58 using content::RenderViewHost;
59 using content::SessionStorageNamespace;
60 using content::WebContents;
61
62 namespace prerender {
63
64 namespace {
65
66 // Time interval at which periodic cleanups are performed.
67 constexpr base::TimeDelta kPeriodicCleanupInterval =
68 base::TimeDelta::FromMilliseconds(1000);
69
70 // Time interval after which OnCloseWebContentsDeleter will schedule a
71 // WebContents for deletion.
72 constexpr base::TimeDelta kDeleteWithExtremePrejudice =
73 base::TimeDelta::FromSeconds(3);
74
75 // Length of prerender history, for display in chrome://net-internals
76 constexpr int kHistoryLength = 100;
77
78 } // namespace
79
80 class PrerenderManager::OnCloseWebContentsDeleter
81 : public content::WebContentsDelegate,
82 public base::SupportsWeakPtr<
83 PrerenderManager::OnCloseWebContentsDeleter> {
84 public:
OnCloseWebContentsDeleter(PrerenderManager * manager,std::unique_ptr<WebContents> tab)85 OnCloseWebContentsDeleter(PrerenderManager* manager,
86 std::unique_ptr<WebContents> tab)
87 : manager_(manager), tab_(std::move(tab)) {
88 tab_->SetDelegate(this);
89 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
90 FROM_HERE,
91 base::BindOnce(
92 &OnCloseWebContentsDeleter::ScheduleWebContentsForDeletion,
93 AsWeakPtr(), /*timeout=*/true),
94 kDeleteWithExtremePrejudice);
95 }
96
CloseContents(WebContents * source)97 void CloseContents(WebContents* source) override {
98 DCHECK_EQ(tab_.get(), source);
99 ScheduleWebContentsForDeletion(/*timeout=*/false);
100 }
101
102 private:
ScheduleWebContentsForDeletion(bool timeout)103 void ScheduleWebContentsForDeletion(bool timeout) {
104 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterTimeout", timeout);
105 tab_->SetDelegate(nullptr);
106 manager_->ScheduleDeleteOldWebContents(std::move(tab_), this);
107 // |this| is deleted at this point.
108 }
109
110 PrerenderManager* const manager_;
111 std::unique_ptr<WebContents> tab_;
112
113 DISALLOW_COPY_AND_ASSIGN(OnCloseWebContentsDeleter);
114 };
115
116 PrerenderManagerObserver::~PrerenderManagerObserver() = default;
117
118 struct PrerenderManager::NavigationRecord {
NavigationRecordprerender::PrerenderManager::NavigationRecord119 NavigationRecord(const GURL& url, base::TimeTicks time, Origin origin)
120 : url(url), time(time), origin(origin) {}
121
122 GURL url;
123 base::TimeTicks time;
124 Origin origin;
125 FinalStatus final_status = FINAL_STATUS_UNKNOWN;
126 };
127
PrerenderManager(content::BrowserContext * browser_context,std::unique_ptr<PrerenderManagerDelegate> delegate)128 PrerenderManager::PrerenderManager(
129 content::BrowserContext* browser_context,
130 std::unique_ptr<PrerenderManagerDelegate> delegate)
131 : browser_context_(browser_context),
132 delegate_(std::move(delegate)),
133 prerender_contents_factory_(PrerenderContents::CreateFactory()),
134 prerender_history_(std::make_unique<PrerenderHistory>(kHistoryLength)),
135 histograms_(std::make_unique<PrerenderHistograms>()),
136 tick_clock_(base::DefaultTickClock::GetInstance()) {
137 DCHECK_CURRENTLY_ON(BrowserThread::UI);
138
139 last_prerender_start_time_ =
140 GetCurrentTimeTicks() -
141 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs);
142 }
143
~PrerenderManager()144 PrerenderManager::~PrerenderManager() {
145 // The earlier call to KeyedService::Shutdown() should have
146 // emptied these vectors already.
147 DCHECK(active_prerenders_.empty());
148 DCHECK(to_delete_prerenders_.empty());
149
150 for (auto* host : prerender_process_hosts_) {
151 host->RemoveObserver(this);
152 }
153 }
154
Shutdown()155 void PrerenderManager::Shutdown() {
156 DestroyAllContents(FINAL_STATUS_PROFILE_DESTROYED);
157 on_close_web_contents_deleters_.clear();
158 browser_context_ = nullptr;
159
160 DCHECK(active_prerenders_.empty());
161 }
162
163 std::unique_ptr<PrerenderHandle>
AddPrerenderFromLinkRelPrerender(int process_id,int route_id,const GURL & url,blink::mojom::PrerenderRelType rel_type,const content::Referrer & referrer,const url::Origin & initiator_origin,const gfx::Size & size)164 PrerenderManager::AddPrerenderFromLinkRelPrerender(
165 int process_id,
166 int route_id,
167 const GURL& url,
168 blink::mojom::PrerenderRelType rel_type,
169 const content::Referrer& referrer,
170 const url::Origin& initiator_origin,
171 const gfx::Size& size) {
172 Origin origin = ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN;
173 switch (rel_type) {
174 case blink::mojom::PrerenderRelType::kPrerender:
175 origin = ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN;
176 break;
177 case blink::mojom::PrerenderRelType::kNext:
178 origin = ORIGIN_LINK_REL_NEXT;
179 break;
180 }
181
182 SessionStorageNamespace* session_storage_namespace = nullptr;
183 // Unit tests pass in a process_id == -1.
184 if (process_id != -1) {
185 RenderViewHost* source_render_view_host =
186 RenderViewHost::FromID(process_id, route_id);
187 if (!source_render_view_host)
188 return nullptr;
189 WebContents* source_web_contents =
190 WebContents::FromRenderViewHost(source_render_view_host);
191 if (!source_web_contents)
192 return nullptr;
193 if (origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN &&
194 source_web_contents->GetURL().host_piece() == url.host_piece()) {
195 origin = ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN;
196 }
197 // TODO(ajwong): This does not correctly handle storage for isolated apps.
198 session_storage_namespace = source_web_contents->GetController()
199 .GetDefaultSessionStorageNamespace();
200 }
201 return AddPrerenderWithPreconnectFallback(origin, url, referrer,
202 initiator_origin, gfx::Rect(size),
203 session_storage_namespace);
204 }
205
AddPrerenderFromOmnibox(const GURL & url,SessionStorageNamespace * session_storage_namespace,const gfx::Size & size)206 std::unique_ptr<PrerenderHandle> PrerenderManager::AddPrerenderFromOmnibox(
207 const GURL& url,
208 SessionStorageNamespace* session_storage_namespace,
209 const gfx::Size& size) {
210 return AddPrerenderWithPreconnectFallback(
211 ORIGIN_OMNIBOX, url, content::Referrer(), base::nullopt, gfx::Rect(size),
212 session_storage_namespace);
213 }
214
215 std::unique_ptr<PrerenderHandle>
AddPrerenderFromNavigationPredictor(const GURL & url,SessionStorageNamespace * session_storage_namespace,const gfx::Size & size)216 PrerenderManager::AddPrerenderFromNavigationPredictor(
217 const GURL& url,
218 SessionStorageNamespace* session_storage_namespace,
219 const gfx::Size& size) {
220 return AddPrerenderWithPreconnectFallback(
221 ORIGIN_NAVIGATION_PREDICTOR, url, content::Referrer(), base::nullopt,
222 gfx::Rect(size), session_storage_namespace);
223 }
224
AddIsolatedPrerender(const GURL & url,SessionStorageNamespace * session_storage_namespace,const gfx::Size & size)225 std::unique_ptr<PrerenderHandle> PrerenderManager::AddIsolatedPrerender(
226 const GURL& url,
227 SessionStorageNamespace* session_storage_namespace,
228 const gfx::Size& size) {
229 // The preconnect fallback won't happen.
230 return AddPrerenderWithPreconnectFallback(
231 ORIGIN_ISOLATED_PRERENDER, url, content::Referrer(), base::nullopt,
232 gfx::Rect(size), session_storage_namespace);
233 }
234
235 std::unique_ptr<PrerenderHandle>
AddPrerenderFromExternalRequest(const GURL & url,const content::Referrer & referrer,SessionStorageNamespace * session_storage_namespace,const gfx::Rect & bounds)236 PrerenderManager::AddPrerenderFromExternalRequest(
237 const GURL& url,
238 const content::Referrer& referrer,
239 SessionStorageNamespace* session_storage_namespace,
240 const gfx::Rect& bounds) {
241 return AddPrerenderWithPreconnectFallback(ORIGIN_EXTERNAL_REQUEST, url,
242 referrer, base::nullopt, bounds,
243 session_storage_namespace);
244 }
245
246 std::unique_ptr<PrerenderHandle>
AddForcedPrerenderFromExternalRequest(const GURL & url,const content::Referrer & referrer,SessionStorageNamespace * session_storage_namespace,const gfx::Rect & bounds)247 PrerenderManager::AddForcedPrerenderFromExternalRequest(
248 const GURL& url,
249 const content::Referrer& referrer,
250 SessionStorageNamespace* session_storage_namespace,
251 const gfx::Rect& bounds) {
252 return AddPrerenderWithPreconnectFallback(
253 ORIGIN_EXTERNAL_REQUEST_FORCED_PRERENDER, url, referrer, base::nullopt,
254 bounds, session_storage_namespace);
255 }
256
CancelAllPrerenders()257 void PrerenderManager::CancelAllPrerenders() {
258 DCHECK_CURRENTLY_ON(BrowserThread::UI);
259 while (!active_prerenders_.empty()) {
260 PrerenderContents* prerender_contents =
261 active_prerenders_.front()->contents();
262 prerender_contents->Destroy(FINAL_STATUS_CANCELLED);
263 }
264 }
265
MoveEntryToPendingDelete(PrerenderContents * entry,FinalStatus final_status)266 void PrerenderManager::MoveEntryToPendingDelete(PrerenderContents* entry,
267 FinalStatus final_status) {
268 DCHECK_CURRENTLY_ON(BrowserThread::UI);
269 DCHECK(entry);
270
271 auto it = FindIteratorForPrerenderContents(entry);
272 DCHECK(it != active_prerenders_.end());
273 to_delete_prerenders_.push_back(std::move(*it));
274 active_prerenders_.erase(it);
275 // Destroy the old WebContents relatively promptly to reduce resource usage.
276 PostCleanupTask();
277 }
278
IsWebContentsPrerendering(const WebContents * web_contents) const279 bool PrerenderManager::IsWebContentsPrerendering(
280 const WebContents* web_contents) const {
281 DCHECK_CURRENTLY_ON(BrowserThread::UI);
282 return GetPrerenderContents(web_contents);
283 }
284
GetPrerenderContents(const content::WebContents * web_contents) const285 PrerenderContents* PrerenderManager::GetPrerenderContents(
286 const content::WebContents* web_contents) const {
287 DCHECK_CURRENTLY_ON(BrowserThread::UI);
288 for (const auto& prerender : active_prerenders_) {
289 WebContents* prerender_web_contents =
290 prerender->contents()->prerender_contents();
291 if (prerender_web_contents == web_contents) {
292 return prerender->contents();
293 }
294 }
295
296 // Also check the pending-deletion list. If the prerender is in pending
297 // delete, anyone with a handle on the WebContents needs to know.
298 for (const auto& prerender : to_delete_prerenders_) {
299 WebContents* prerender_web_contents =
300 prerender->contents()->prerender_contents();
301 if (prerender_web_contents == web_contents) {
302 return prerender->contents();
303 }
304 }
305 return nullptr;
306 }
307
GetPrerenderContentsForRoute(int child_id,int route_id) const308 PrerenderContents* PrerenderManager::GetPrerenderContentsForRoute(
309 int child_id,
310 int route_id) const {
311 WebContents* web_contents = nullptr;
312 RenderViewHost* render_view_host = RenderViewHost::FromID(child_id, route_id);
313 web_contents = WebContents::FromRenderViewHost(render_view_host);
314 return web_contents ? GetPrerenderContents(web_contents) : nullptr;
315 }
316
317 std::vector<WebContents*>
GetAllNoStatePrefetchingContentsForTesting() const318 PrerenderManager::GetAllNoStatePrefetchingContentsForTesting() const {
319 DCHECK_CURRENTLY_ON(BrowserThread::UI);
320 std::vector<WebContents*> result;
321
322 for (const auto& prerender : active_prerenders_) {
323 WebContents* contents = prerender->contents()->prerender_contents();
324 if (contents && prerender->contents()->prerender_mode() ==
325 prerender::mojom::PrerenderMode::kPrefetchOnly) {
326 result.push_back(contents);
327 }
328 }
329
330 return result;
331 }
332
HasRecentlyBeenNavigatedTo(Origin origin,const GURL & url)333 bool PrerenderManager::HasRecentlyBeenNavigatedTo(Origin origin,
334 const GURL& url) {
335 DCHECK_CURRENTLY_ON(BrowserThread::UI);
336
337 CleanUpOldNavigations(&navigations_, base::TimeDelta::FromMilliseconds(
338 kNavigationRecordWindowMs));
339 for (auto it = navigations_.rbegin(); it != navigations_.rend(); ++it) {
340 if (it->url == url)
341 return true;
342 }
343
344 return false;
345 }
346
CopyAsValue() const347 std::unique_ptr<base::DictionaryValue> PrerenderManager::CopyAsValue() const {
348 DCHECK_CURRENTLY_ON(BrowserThread::UI);
349
350 auto dict_value = std::make_unique<base::DictionaryValue>();
351 dict_value->Set("history", prerender_history_->CopyEntriesAsValue());
352 dict_value->Set("active", GetActivePrerendersAsValue());
353 dict_value->SetBoolean("enabled",
354 delegate_->IsNetworkPredictionPreferenceEnabled());
355 dict_value->SetString("disabled_note",
356 delegate_->GetReasonForDisablingPrediction());
357 // If prerender is disabled via a flag this method is not even called.
358 std::string enabled_note;
359 dict_value->SetString("enabled_note", enabled_note);
360 return dict_value;
361 }
362
ClearData(int clear_flags)363 void PrerenderManager::ClearData(int clear_flags) {
364 DCHECK_GE(clear_flags, 0);
365 DCHECK_LT(clear_flags, CLEAR_MAX);
366 if (clear_flags & CLEAR_PRERENDER_CONTENTS)
367 DestroyAllContents(FINAL_STATUS_CACHE_OR_HISTORY_CLEARED);
368 // This has to be second, since destroying prerenders can add to the history.
369 if (clear_flags & CLEAR_PRERENDER_HISTORY)
370 prerender_history_->Clear();
371 }
372
RecordFinalStatus(Origin origin,FinalStatus final_status) const373 void PrerenderManager::RecordFinalStatus(Origin origin,
374 FinalStatus final_status) const {
375 histograms_->RecordFinalStatus(origin, final_status);
376 }
377
RecordNavigation(const GURL & url)378 void PrerenderManager::RecordNavigation(const GURL& url) {
379 DCHECK_CURRENTLY_ON(BrowserThread::UI);
380
381 navigations_.emplace_back(url, GetCurrentTimeTicks(), ORIGIN_NONE);
382 CleanUpOldNavigations(&navigations_, base::TimeDelta::FromMilliseconds(
383 kNavigationRecordWindowMs));
384 }
385
386 struct PrerenderManager::PrerenderData::OrderByExpiryTime {
operator ()prerender::PrerenderManager::PrerenderData::OrderByExpiryTime387 bool operator()(const std::unique_ptr<PrerenderData>& a,
388 const std::unique_ptr<PrerenderData>& b) const {
389 return a->expiry_time() < b->expiry_time();
390 }
391 };
392
PrerenderData(PrerenderManager * manager,std::unique_ptr<PrerenderContents> contents,base::TimeTicks expiry_time)393 PrerenderManager::PrerenderData::PrerenderData(
394 PrerenderManager* manager,
395 std::unique_ptr<PrerenderContents> contents,
396 base::TimeTicks expiry_time)
397 : manager_(manager),
398 contents_(std::move(contents)),
399 expiry_time_(expiry_time) {
400 DCHECK(contents_);
401 }
402
403 PrerenderManager::PrerenderData::~PrerenderData() = default;
404
OnHandleCreated(PrerenderHandle * handle)405 void PrerenderManager::PrerenderData::OnHandleCreated(PrerenderHandle* handle) {
406 DCHECK(contents_);
407 ++handle_count_;
408 contents_->AddObserver(handle);
409 }
410
OnHandleNavigatedAway(PrerenderHandle * handle)411 void PrerenderManager::PrerenderData::OnHandleNavigatedAway(
412 PrerenderHandle* handle) {
413 DCHECK_LT(0, handle_count_);
414 DCHECK(contents_);
415 if (abandon_time_.is_null())
416 abandon_time_ = base::TimeTicks::Now();
417 // We intentionally don't decrement the handle count here, so that the
418 // prerender won't be canceled until it times out.
419 manager_->SourceNavigatedAway(this);
420 }
421
OnHandleCanceled(PrerenderHandle * handle)422 void PrerenderManager::PrerenderData::OnHandleCanceled(
423 PrerenderHandle* handle) {
424 DCHECK_LT(0, handle_count_);
425 DCHECK(contents_);
426
427 if (--handle_count_ == 0) {
428 // This will eventually remove this object from |active_prerenders_|.
429 contents_->Destroy(FINAL_STATUS_CANCELLED);
430 }
431 }
432
433 std::unique_ptr<PrerenderContents>
ReleaseContents()434 PrerenderManager::PrerenderData::ReleaseContents() {
435 return std::move(contents_);
436 }
437
SourceNavigatedAway(PrerenderData * prerender_data)438 void PrerenderManager::SourceNavigatedAway(PrerenderData* prerender_data) {
439 // The expiry time of our prerender data will likely change because of
440 // this navigation. This requires a re-sort of |active_prerenders_|.
441 for (auto it = active_prerenders_.begin(); it != active_prerenders_.end();
442 ++it) {
443 PrerenderData* data = it->get();
444 if (data == prerender_data) {
445 data->set_expiry_time(std::min(data->expiry_time(),
446 GetExpiryTimeForNavigatedAwayPrerender()));
447 SortActivePrerenders();
448 return;
449 }
450 }
451 }
452
IsLowEndDevice() const453 bool PrerenderManager::IsLowEndDevice() const {
454 return base::SysInfo::IsLowEndDevice();
455 }
456
IsPredictionEnabled(Origin origin)457 bool PrerenderManager::IsPredictionEnabled(Origin origin) {
458 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
459
460 // <link rel=prerender> and <link rel=next> origins ignore the network state
461 // and the privacy
462 // settings. Web developers should be able prefetch with all possible privacy
463 // settings. This would avoid web devs coming up with creative ways to
464 // prefetch in cases they are not allowed to do so.
465 if (origin == ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN ||
466 origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN ||
467 origin == ORIGIN_LINK_REL_NEXT) {
468 return true;
469 }
470
471 // TODO(crbug.com/1121970): Remove this check once we're no longer running the
472 // experiment "PredictivePrefetchingAllowedOnAllConnectionTypes".
473 if (delegate_->IsPredictionDisabledDueToNetwork(origin))
474 return false;
475
476 return delegate_->IsNetworkPredictionPreferenceEnabled();
477 }
478
MaybePreconnect(Origin origin,const GURL & url_arg) const479 void PrerenderManager::MaybePreconnect(Origin origin,
480 const GURL& url_arg) const {
481 delegate_->MaybePreconnect(url_arg);
482 }
483
484 std::unique_ptr<PrerenderHandle>
AddPrerenderWithPreconnectFallback(Origin origin,const GURL & url_arg,const content::Referrer & referrer,const base::Optional<url::Origin> & initiator_origin,const gfx::Rect & bounds,SessionStorageNamespace * session_storage_namespace)485 PrerenderManager::AddPrerenderWithPreconnectFallback(
486 Origin origin,
487 const GURL& url_arg,
488 const content::Referrer& referrer,
489 const base::Optional<url::Origin>& initiator_origin,
490 const gfx::Rect& bounds,
491 SessionStorageNamespace* session_storage_namespace) {
492 DCHECK_CURRENTLY_ON(BrowserThread::UI);
493
494 // Disallow NSPing link-rel:next URLs.
495 // See https://bugs.chromium.org/p/chromium/issues/detail?id=1158209.
496 if (origin == ORIGIN_LINK_REL_NEXT) {
497 SkipPrerenderContentsAndMaybePreconnect(
498 url_arg, origin, FINAL_STATUS_LINK_REL_NEXT_NOT_ALLOWED);
499 return nullptr;
500 }
501
502 // Disallow prerendering on low end devices.
503 if (IsLowEndDevice()) {
504 SkipPrerenderContentsAndMaybePreconnect(url_arg, origin,
505 FINAL_STATUS_LOW_END_DEVICE);
506 return nullptr;
507 }
508
509 if ((origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN ||
510 origin == ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN) &&
511 IsGoogleOriginURL(referrer.url)) {
512 origin = ORIGIN_GWS_PRERENDER;
513 }
514
515 GURL url = url_arg;
516
517 if (delegate_->GetCookieSettings()->ShouldBlockThirdPartyCookies()) {
518 SkipPrerenderContentsAndMaybePreconnect(
519 url, origin, FINAL_STATUS_BLOCK_THIRD_PARTY_COOKIES);
520 return nullptr;
521 }
522
523 if (!IsPredictionEnabled(origin)) {
524 FinalStatus final_status =
525 delegate_->IsPredictionDisabledDueToNetwork(origin)
526 ? FINAL_STATUS_CELLULAR_NETWORK
527 : FINAL_STATUS_PRERENDERING_DISABLED;
528 SkipPrerenderContentsAndMaybePreconnect(url, origin, final_status);
529 return nullptr;
530 }
531
532 if (PrerenderData* preexisting_prerender_data =
533 FindPrerenderData(url, session_storage_namespace)) {
534 SkipPrerenderContentsAndMaybePreconnect(url, origin,
535 FINAL_STATUS_DUPLICATE);
536 return base::WrapUnique(new PrerenderHandle(preexisting_prerender_data));
537 }
538
539 base::TimeDelta prefetch_age;
540 GetPrefetchInformation(url, &prefetch_age, nullptr /* final_status*/,
541 nullptr /* origin */);
542 if (!prefetch_age.is_zero() &&
543 prefetch_age <
544 base::TimeDelta::FromMinutes(net::HttpCache::kPrefetchReuseMins)) {
545 SkipPrerenderContentsAndMaybePreconnect(url, origin,
546 FINAL_STATUS_DUPLICATE);
547 return nullptr;
548 }
549
550 // Do not prerender if there are too many render processes, and we would
551 // have to use an existing one. We do not want prerendering to happen in
552 // a shared process, so that we can always reliably lower the CPU
553 // priority for prerendering.
554 // In single-process mode, ShouldTryToUseExistingProcessHost() always returns
555 // true, so that case needs to be explicitly checked for.
556 // TODO(tburkard): Figure out how to cancel prerendering in the opposite
557 // case, when a new tab is added to a process used for prerendering.
558 // TODO(ppi): Check whether there are usually enough render processes
559 // available on Android. If not, kill an existing renderers so that we can
560 // create a new one.
561 if (content::RenderProcessHost::ShouldTryToUseExistingProcessHost(
562 browser_context_, url) &&
563 !content::RenderProcessHost::run_renderer_in_process()) {
564 SkipPrerenderContentsAndMaybePreconnect(url, origin,
565 FINAL_STATUS_TOO_MANY_PROCESSES);
566 return nullptr;
567 }
568
569 // Check if enough time has passed since the last prerender.
570 if (!DoesRateLimitAllowPrerender(origin)) {
571 // Cancel the prerender. We could add it to the pending prerender list but
572 // this doesn't make sense as the next prerender request will be triggered
573 // by a navigation and is unlikely to be the same site.
574 SkipPrerenderContentsAndMaybePreconnect(url, origin,
575 FINAL_STATUS_RATE_LIMIT_EXCEEDED);
576 return nullptr;
577 }
578
579 // Record the URL in the prefetch list, even when in full prerender mode, to
580 // enable metrics comparisons.
581 prefetches_.emplace_back(url, GetCurrentTimeTicks(), origin);
582
583 // If this is GWS and we are in the holdback, skip the prefetch. Record the
584 // status as holdback, so we can analyze via UKM.
585 if (origin == ORIGIN_GWS_PRERENDER &&
586 base::FeatureList::IsEnabled(kGWSPrefetchHoldback)) {
587 // Set the holdback status on the prefetch entry.
588 SetPrefetchFinalStatusForUrl(url, FINAL_STATUS_GWS_HOLDBACK);
589 SkipPrerenderContentsAndMaybePreconnect(url, origin,
590 FINAL_STATUS_GWS_HOLDBACK);
591 return nullptr;
592 }
593
594 // If this is Navigation predictor and we are in the holdback, skip the
595 // prefetch. Record the status as holdback, so we can analyze via UKM.
596 if (origin == ORIGIN_NAVIGATION_PREDICTOR &&
597 base::FeatureList::IsEnabled(kNavigationPredictorPrefetchHoldback)) {
598 // Set the holdback status on the prefetch entry.
599 SetPrefetchFinalStatusForUrl(url,
600 FINAL_STATUS_NAVIGATION_PREDICTOR_HOLDBACK);
601 SkipPrerenderContentsAndMaybePreconnect(
602 url, origin, FINAL_STATUS_NAVIGATION_PREDICTOR_HOLDBACK);
603 return nullptr;
604 }
605
606 std::unique_ptr<PrerenderContents> prerender_contents =
607 CreatePrerenderContents(url, referrer, initiator_origin, origin);
608 DCHECK(prerender_contents);
609 PrerenderContents* prerender_contents_ptr = prerender_contents.get();
610 prerender_contents_ptr->SetPrerenderMode(
611 prerender::mojom::PrerenderMode::kPrefetchOnly);
612 active_prerenders_.push_back(
613 std::make_unique<PrerenderData>(this, std::move(prerender_contents),
614 GetExpiryTimeForNewPrerender(origin)));
615 if (!prerender_contents_ptr->Init()) {
616 DCHECK(active_prerenders_.end() ==
617 FindIteratorForPrerenderContents(prerender_contents_ptr));
618 return nullptr;
619 }
620
621 DCHECK(!prerender_contents_ptr->prerendering_has_started());
622
623 std::unique_ptr<PrerenderHandle> prerender_handle =
624 base::WrapUnique(new PrerenderHandle(active_prerenders_.back().get()));
625 SortActivePrerenders();
626
627 last_prerender_start_time_ = GetCurrentTimeTicks();
628
629 gfx::Rect contents_bounds =
630 bounds.IsEmpty() ? config_.default_tab_bounds : bounds;
631
632 prerender_contents_ptr->StartPrerendering(contents_bounds,
633 session_storage_namespace);
634
635 DCHECK(prerender_contents_ptr->prerendering_has_started());
636
637 StartSchedulingPeriodicCleanups();
638 return prerender_handle;
639 }
640
StartSchedulingPeriodicCleanups()641 void PrerenderManager::StartSchedulingPeriodicCleanups() {
642 DCHECK_CURRENTLY_ON(BrowserThread::UI);
643 if (repeating_timer_.IsRunning())
644 return;
645
646 repeating_timer_.Start(FROM_HERE, kPeriodicCleanupInterval, this,
647 &PrerenderManager::PeriodicCleanup);
648 }
649
StopSchedulingPeriodicCleanups()650 void PrerenderManager::StopSchedulingPeriodicCleanups() {
651 DCHECK_CURRENTLY_ON(BrowserThread::UI);
652 repeating_timer_.Stop();
653 }
654
PeriodicCleanup()655 void PrerenderManager::PeriodicCleanup() {
656 DCHECK_CURRENTLY_ON(BrowserThread::UI);
657
658 base::ElapsedTimer resource_timer;
659
660 // Grab a copy of the current PrerenderContents pointers, so that we
661 // will not interfere with potential deletions of the list.
662 std::vector<PrerenderContents*> prerender_contents;
663 prerender_contents.reserve(active_prerenders_.size());
664 for (auto& prerender : active_prerenders_)
665 prerender_contents.push_back(prerender->contents());
666
667 // And now check for prerenders using too much memory.
668 for (auto* contents : prerender_contents)
669 contents->DestroyWhenUsingTooManyResources();
670
671 base::ElapsedTimer cleanup_timer;
672
673 // Perform deferred cleanup work.
674 DeleteOldWebContents();
675 DeleteOldEntries();
676 if (active_prerenders_.empty())
677 StopSchedulingPeriodicCleanups();
678
679 DeleteToDeletePrerenders();
680
681 CleanUpOldNavigations(&prefetches_, base::TimeDelta::FromMinutes(30));
682 }
683
PostCleanupTask()684 void PrerenderManager::PostCleanupTask() {
685 DCHECK_CURRENTLY_ON(BrowserThread::UI);
686 base::ThreadTaskRunnerHandle::Get()->PostTask(
687 FROM_HERE, base::BindOnce(&PrerenderManager::PeriodicCleanup,
688 weak_factory_.GetWeakPtr()));
689 }
690
GetExpiryTimeForNewPrerender(Origin origin) const691 base::TimeTicks PrerenderManager::GetExpiryTimeForNewPrerender(
692 Origin origin) const {
693 return GetCurrentTimeTicks() + config_.time_to_live;
694 }
695
GetExpiryTimeForNavigatedAwayPrerender() const696 base::TimeTicks PrerenderManager::GetExpiryTimeForNavigatedAwayPrerender()
697 const {
698 return GetCurrentTimeTicks() + config_.abandon_time_to_live;
699 }
700
DeleteOldEntries()701 void PrerenderManager::DeleteOldEntries() {
702 DCHECK_CURRENTLY_ON(BrowserThread::UI);
703 while (!active_prerenders_.empty()) {
704 auto& prerender_data = active_prerenders_.front();
705 DCHECK(prerender_data);
706 DCHECK(prerender_data->contents());
707
708 if (prerender_data->expiry_time() > GetCurrentTimeTicks())
709 return;
710 prerender_data->contents()->Destroy(FINAL_STATUS_TIMED_OUT);
711 }
712 }
713
DeleteToDeletePrerenders()714 void PrerenderManager::DeleteToDeletePrerenders() {
715 DCHECK_CURRENTLY_ON(BrowserThread::UI);
716 // Delete the items one by one (after removing from the vector) as deleting
717 // the WebContents may trigger a call to GetPrerenderContents(), which
718 // iterates over |to_delete_prerenders_|.
719 while (!to_delete_prerenders_.empty()) {
720 std::unique_ptr<PrerenderData> prerender_data =
721 std::move(to_delete_prerenders_.back());
722 to_delete_prerenders_.pop_back();
723 }
724 }
725
GetCurrentTime() const726 base::Time PrerenderManager::GetCurrentTime() const {
727 return base::Time::Now();
728 }
729
GetCurrentTimeTicks() const730 base::TimeTicks PrerenderManager::GetCurrentTimeTicks() const {
731 return tick_clock_->NowTicks();
732 }
733
SetTickClockForTesting(const base::TickClock * tick_clock)734 void PrerenderManager::SetTickClockForTesting(
735 const base::TickClock* tick_clock) {
736 tick_clock_ = tick_clock;
737 }
738
AddObserver(std::unique_ptr<PrerenderManagerObserver> observer)739 void PrerenderManager::AddObserver(
740 std::unique_ptr<PrerenderManagerObserver> observer) {
741 observers_.push_back(std::move(observer));
742 }
743
CreatePrerenderContents(const GURL & url,const content::Referrer & referrer,const base::Optional<url::Origin> & initiator_origin,Origin origin)744 std::unique_ptr<PrerenderContents> PrerenderManager::CreatePrerenderContents(
745 const GURL& url,
746 const content::Referrer& referrer,
747 const base::Optional<url::Origin>& initiator_origin,
748 Origin origin) {
749 DCHECK_CURRENTLY_ON(BrowserThread::UI);
750 return base::WrapUnique(prerender_contents_factory_->CreatePrerenderContents(
751 delegate_->GetPrerenderContentsDelegate(), this, browser_context_, url,
752 referrer, initiator_origin, origin));
753 }
754
SortActivePrerenders()755 void PrerenderManager::SortActivePrerenders() {
756 std::sort(active_prerenders_.begin(), active_prerenders_.end(),
757 PrerenderData::OrderByExpiryTime());
758 }
759
FindPrerenderData(const GURL & url,SessionStorageNamespace * session_storage_namespace)760 PrerenderManager::PrerenderData* PrerenderManager::FindPrerenderData(
761 const GURL& url,
762 SessionStorageNamespace* session_storage_namespace) {
763 for (const auto& prerender : active_prerenders_) {
764 PrerenderContents* contents = prerender->contents();
765 if (contents->Matches(url, session_storage_namespace))
766 return prerender.get();
767 }
768 return nullptr;
769 }
770
771 PrerenderManager::PrerenderDataVector::iterator
FindIteratorForPrerenderContents(PrerenderContents * prerender_contents)772 PrerenderManager::FindIteratorForPrerenderContents(
773 PrerenderContents* prerender_contents) {
774 for (auto it = active_prerenders_.begin(); it != active_prerenders_.end();
775 ++it) {
776 if ((*it)->contents() == prerender_contents)
777 return it;
778 }
779 return active_prerenders_.end();
780 }
781
DoesRateLimitAllowPrerender(Origin origin) const782 bool PrerenderManager::DoesRateLimitAllowPrerender(Origin origin) const {
783 DCHECK_CURRENTLY_ON(BrowserThread::UI);
784
785 // Allow navigation predictor to manage its own rate limit.
786 if (origin == ORIGIN_NAVIGATION_PREDICTOR)
787 return true;
788 base::TimeDelta elapsed_time =
789 GetCurrentTimeTicks() - last_prerender_start_time_;
790 if (!config_.rate_limit_enabled)
791 return true;
792 return elapsed_time >=
793 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs);
794 }
795
DeleteOldWebContents()796 void PrerenderManager::DeleteOldWebContents() {
797 old_web_contents_list_.clear();
798 }
799
GetPrefetchInformation(const GURL & url,base::TimeDelta * prefetch_age,FinalStatus * final_status,Origin * origin)800 bool PrerenderManager::GetPrefetchInformation(const GURL& url,
801 base::TimeDelta* prefetch_age,
802 FinalStatus* final_status,
803 Origin* origin) {
804 CleanUpOldNavigations(&prefetches_, base::TimeDelta::FromMinutes(30));
805
806 if (prefetch_age)
807 *prefetch_age = base::TimeDelta();
808 if (final_status)
809 *final_status = FINAL_STATUS_MAX;
810 if (origin)
811 *origin = ORIGIN_NONE;
812
813 for (auto it = prefetches_.crbegin(); it != prefetches_.crend(); ++it) {
814 if (it->url == url) {
815 if (prefetch_age)
816 *prefetch_age = GetCurrentTimeTicks() - it->time;
817 if (final_status)
818 *final_status = it->final_status;
819 if (origin)
820 *origin = it->origin;
821 return true;
822 }
823 }
824 return false;
825 }
826
SetPrefetchFinalStatusForUrl(const GURL & url,FinalStatus final_status)827 void PrerenderManager::SetPrefetchFinalStatusForUrl(const GURL& url,
828 FinalStatus final_status) {
829 for (auto it = prefetches_.rbegin(); it != prefetches_.rend(); ++it) {
830 if (it->url == url) {
831 it->final_status = final_status;
832 break;
833 }
834 }
835 }
836
HasRecentlyPrefetchedUrlForTesting(const GURL & url)837 bool PrerenderManager::HasRecentlyPrefetchedUrlForTesting(const GURL& url) {
838 return std::any_of(prefetches_.cbegin(), prefetches_.cend(),
839 [url](const NavigationRecord& r) {
840 return r.url == url &&
841 r.final_status ==
842 FINAL_STATUS_NOSTATE_PREFETCH_FINISHED;
843 });
844 }
845
OnPrefetchUsed(const GURL & url)846 void PrerenderManager::OnPrefetchUsed(const GURL& url) {
847 // Loading a prefetched URL resets the revalidation bypass. Remove all
848 // matching urls from the prefetch list for more accurate metrics.
849 base::EraseIf(prefetches_,
850 [url](const NavigationRecord& r) { return r.url == url; });
851 }
852
CleanUpOldNavigations(std::vector<NavigationRecord> * navigations,base::TimeDelta max_age)853 void PrerenderManager::CleanUpOldNavigations(
854 std::vector<NavigationRecord>* navigations,
855 base::TimeDelta max_age) {
856 DCHECK_CURRENTLY_ON(BrowserThread::UI);
857
858 // Cutoff. Navigations before this cutoff can be discarded.
859 base::TimeTicks cutoff = GetCurrentTimeTicks() - max_age;
860 auto it = navigations->begin();
861 for (; it != navigations->end(); ++it) {
862 if (it->time > cutoff)
863 break;
864 }
865 navigations->erase(navigations->begin(), it);
866 }
867
ScheduleDeleteOldWebContents(std::unique_ptr<WebContents> tab,OnCloseWebContentsDeleter * deleter)868 void PrerenderManager::ScheduleDeleteOldWebContents(
869 std::unique_ptr<WebContents> tab,
870 OnCloseWebContentsDeleter* deleter) {
871 old_web_contents_list_.push_back(std::move(tab));
872 PostCleanupTask();
873
874 if (!deleter)
875 return;
876
877 for (auto it = on_close_web_contents_deleters_.begin();
878 it != on_close_web_contents_deleters_.end(); ++it) {
879 if (it->get() == deleter) {
880 on_close_web_contents_deleters_.erase(it);
881 return;
882 }
883 }
884 NOTREACHED();
885 }
886
AddToHistory(PrerenderContents * contents)887 void PrerenderManager::AddToHistory(PrerenderContents* contents) {
888 PrerenderHistory::Entry entry(contents->prerender_url(),
889 contents->final_status(), contents->origin(),
890 base::Time::Now());
891 prerender_history_->AddEntry(entry);
892 }
893
GetActivePrerendersAsValue() const894 std::unique_ptr<base::ListValue> PrerenderManager::GetActivePrerendersAsValue()
895 const {
896 auto list_value = std::make_unique<base::ListValue>();
897 for (const auto& prerender : active_prerenders_) {
898 auto prerender_value = prerender->contents()->GetAsValue();
899 if (prerender_value)
900 list_value->Append(std::move(prerender_value));
901 }
902 return list_value;
903 }
904
DestroyAllContents(FinalStatus final_status)905 void PrerenderManager::DestroyAllContents(FinalStatus final_status) {
906 DeleteOldWebContents();
907 while (!active_prerenders_.empty()) {
908 PrerenderContents* contents = active_prerenders_.front()->contents();
909 contents->Destroy(final_status);
910 }
911 DeleteToDeletePrerenders();
912 }
913
SkipPrerenderContentsAndMaybePreconnect(const GURL & url,Origin origin,FinalStatus final_status) const914 void PrerenderManager::SkipPrerenderContentsAndMaybePreconnect(
915 const GURL& url,
916 Origin origin,
917 FinalStatus final_status) const {
918 PrerenderHistory::Entry entry(url, final_status, origin, base::Time::Now());
919 prerender_history_->AddEntry(entry);
920 histograms_->RecordFinalStatus(origin, final_status);
921
922 if (origin == ORIGIN_ISOLATED_PRERENDER) {
923 // Prefetch Proxy should not preconnect since that can't be done in a fully
924 // isolated way.
925 return;
926 }
927
928 if (origin == ORIGIN_LINK_REL_NEXT)
929 return;
930
931 if (final_status == FINAL_STATUS_LOW_END_DEVICE ||
932 final_status == FINAL_STATUS_CELLULAR_NETWORK ||
933 final_status == FINAL_STATUS_DUPLICATE ||
934 final_status == FINAL_STATUS_TOO_MANY_PROCESSES) {
935 MaybePreconnect(origin, url);
936 }
937
938 static_assert(
939 FINAL_STATUS_MAX == FINAL_STATUS_LINK_REL_NEXT_NOT_ALLOWED + 1,
940 "Consider whether a failed prerender should fallback to preconnect");
941 }
942
RecordNetworkBytesConsumed(Origin origin,int64_t prerender_bytes)943 void PrerenderManager::RecordNetworkBytesConsumed(Origin origin,
944 int64_t prerender_bytes) {
945 int64_t recent_browser_context_bytes =
946 browser_context_network_bytes_ -
947 last_recorded_browser_context_network_bytes_;
948 last_recorded_browser_context_network_bytes_ = browser_context_network_bytes_;
949 DCHECK_GE(recent_browser_context_bytes, 0);
950 histograms_->RecordNetworkBytesConsumed(origin, prerender_bytes,
951 recent_browser_context_bytes);
952 }
953
AddPrerenderProcessHost(content::RenderProcessHost * process_host)954 void PrerenderManager::AddPrerenderProcessHost(
955 content::RenderProcessHost* process_host) {
956 DCHECK_CURRENTLY_ON(BrowserThread::UI);
957 bool inserted = prerender_process_hosts_.insert(process_host).second;
958 DCHECK(inserted);
959 process_host->AddObserver(this);
960 }
961
MayReuseProcessHost(content::RenderProcessHost * process_host)962 bool PrerenderManager::MayReuseProcessHost(
963 content::RenderProcessHost* process_host) {
964 DCHECK_CURRENTLY_ON(BrowserThread::UI);
965 // Isolate prerender processes to make the resource monitoring check more
966 // accurate.
967 return !base::Contains(prerender_process_hosts_, process_host);
968 }
969
RenderProcessHostDestroyed(content::RenderProcessHost * host)970 void PrerenderManager::RenderProcessHostDestroyed(
971 content::RenderProcessHost* host) {
972 DCHECK_CURRENTLY_ON(BrowserThread::UI);
973 size_t erased = prerender_process_hosts_.erase(host);
974 DCHECK_EQ(1u, erased);
975 }
976
AsWeakPtr()977 base::WeakPtr<PrerenderManager> PrerenderManager::AsWeakPtr() {
978 return weak_factory_.GetWeakPtr();
979 }
980
ClearPrefetchInformationForTesting()981 void PrerenderManager::ClearPrefetchInformationForTesting() {
982 prefetches_.clear();
983 }
984
985 std::unique_ptr<PrerenderHandle>
AddPrerenderWithPreconnectFallbackForTesting(Origin origin,const GURL & url,const base::Optional<url::Origin> & initiator_origin)986 PrerenderManager::AddPrerenderWithPreconnectFallbackForTesting(
987 Origin origin,
988 const GURL& url,
989 const base::Optional<url::Origin>& initiator_origin) {
990 return AddPrerenderWithPreconnectFallback(
991 origin, url, content::Referrer(), initiator_origin, gfx::Rect(), nullptr);
992 }
993
SetPrerenderContentsFactoryForTest(PrerenderContents::Factory * prerender_contents_factory)994 void PrerenderManager::SetPrerenderContentsFactoryForTest(
995 PrerenderContents::Factory* prerender_contents_factory) {
996 DCHECK_CURRENTLY_ON(BrowserThread::UI);
997 prerender_contents_factory_.reset(prerender_contents_factory);
998 }
999
1000 } // namespace prerender
1001