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