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.
5 #include "chrome/browser/prerender/prerender_manager.h"
12 #include "base/bind.h"
13 #include "base/bind_helpers.h"
14 #include "base/logging.h"
15 #include "base/memory/weak_ptr.h"
16 #include "base/metrics/histogram.h"
17 #include "base/prefs/pref_service.h"
18 #include "base/stl_util.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/time/time.h"
21 #include "base/values.h"
22 #include "chrome/browser/browser_process.h"
23 #include "chrome/browser/chrome_notification_types.h"
24 #include "chrome/browser/common/cancelable_request.h"
25 #include "chrome/browser/favicon/favicon_tab_helper.h"
26 #include "chrome/browser/net/chrome_cookie_notification_details.h"
27 #include "chrome/browser/predictors/predictor_database.h"
28 #include "chrome/browser/predictors/predictor_database_factory.h"
29 #include "chrome/browser/prerender/prerender_condition.h"
30 #include "chrome/browser/prerender/prerender_contents.h"
31 #include "chrome/browser/prerender/prerender_field_trial.h"
32 #include "chrome/browser/prerender/prerender_final_status.h"
33 #include "chrome/browser/prerender/prerender_handle.h"
34 #include "chrome/browser/prerender/prerender_histograms.h"
35 #include "chrome/browser/prerender/prerender_history.h"
36 #include "chrome/browser/prerender/prerender_local_predictor.h"
37 #include "chrome/browser/prerender/prerender_manager_factory.h"
38 #include "chrome/browser/prerender/prerender_tab_helper.h"
39 #include "chrome/browser/prerender/prerender_tracker.h"
40 #include "chrome/browser/prerender/prerender_util.h"
41 #include "chrome/browser/profiles/profile.h"
42 #include "chrome/browser/search/search.h"
43 #include "chrome/browser/tab_contents/tab_util.h"
44 #include "chrome/browser/ui/browser_navigator.h"
45 #include "chrome/browser/ui/tab_contents/core_tab_helper.h"
46 #include "chrome/browser/ui/tab_contents/core_tab_helper_delegate.h"
47 #include "chrome/common/chrome_switches.h"
48 #include "chrome/common/pref_names.h"
49 #include "chrome/common/prerender_messages.h"
50 #include "content/public/browser/browser_thread.h"
51 #include "content/public/browser/devtools_agent_host.h"
52 #include "content/public/browser/navigation_controller.h"
53 #include "content/public/browser/notification_service.h"
54 #include "content/public/browser/notification_source.h"
55 #include "content/public/browser/render_process_host.h"
56 #include "content/public/browser/render_view_host.h"
57 #include "content/public/browser/session_storage_namespace.h"
58 #include "content/public/browser/web_contents.h"
59 #include "content/public/browser/web_contents_delegate.h"
60 #include "content/public/browser/web_contents_view.h"
61 #include "content/public/common/favicon_url.h"
62 #include "content/public/common/url_constants.h"
63 #include "extensions/common/constants.h"
64 #include "net/url_request/url_request_context.h"
65 #include "net/url_request/url_request_context_getter.h"
67 using content::BrowserThread
;
68 using content::RenderViewHost
;
69 using content::SessionStorageNamespace
;
70 using content::WebContents
;
71 using predictors::LoggedInPredictorTable
;
77 // Time interval at which periodic cleanups are performed.
78 const int kPeriodicCleanupIntervalMs
= 1000;
80 // Valid HTTP methods for prerendering.
81 const char* const kValidHttpMethods
[] = {
89 // Length of prerender history, for display in chrome://net-internals
90 const int kHistoryLength
= 100;
92 // Indicates whether a Prerender has been cancelled such that we need
93 // a dummy replacement for the purpose of recording the correct PPLT for
94 // the Match Complete case.
95 // Traditionally, "Match" means that a prerendered page was actually visited &
96 // the prerender was used. Our goal is to have "Match" cases line up in the
97 // control group & the experiment group, so that we can make meaningful
98 // comparisons of improvements. However, in the control group, since we don't
99 // actually perform prerenders, many of the cancellation reasons cannot be
100 // detected. Therefore, in the Prerender group, when we cancel for one of these
101 // reasons, we keep track of a dummy Prerender representing what we would
102 // have in the control group. If that dummy prerender in the prerender group
103 // would then be swapped in (but isn't actually b/c it's a dummy), we record
104 // this as a MatchComplete. This allows us to compare MatchComplete's
105 // across Prerender & Control group which ideally should be lining up.
106 // This ensures that there is no bias in terms of the page load times
107 // of the pages forming the difference between the two sets.
109 bool NeedMatchCompleteDummyForFinalStatus(FinalStatus final_status
) {
110 return final_status
!= FINAL_STATUS_USED
&&
111 final_status
!= FINAL_STATUS_TIMED_OUT
&&
112 final_status
!= FINAL_STATUS_MANAGER_SHUTDOWN
&&
113 final_status
!= FINAL_STATUS_APP_TERMINATING
&&
114 final_status
!= FINAL_STATUS_WINDOW_OPENER
&&
115 final_status
!= FINAL_STATUS_CACHE_OR_HISTORY_CLEARED
&&
116 final_status
!= FINAL_STATUS_CANCELLED
&&
117 final_status
!= FINAL_STATUS_DEVTOOLS_ATTACHED
&&
118 final_status
!= FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING
&&
119 final_status
!= FINAL_STATUS_PAGE_BEING_CAPTURED
&&
120 final_status
!= FINAL_STATUS_NAVIGATION_UNCOMMITTED
;
123 void CheckIfCookiesExistForDomainResultOnUIThread(
124 const net::CookieMonster::HasCookiesForETLDP1Callback
& callback
,
125 bool cookies_exist
) {
126 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
127 callback
.Run(cookies_exist
);
130 void CheckIfCookiesExistForDomainResultOnIOThread(
131 const net::CookieMonster::HasCookiesForETLDP1Callback
& callback
,
132 bool cookies_exist
) {
133 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
134 BrowserThread::PostTask(
137 base::Bind(&CheckIfCookiesExistForDomainResultOnUIThread
,
142 void CheckIfCookiesExistForDomainOnIOThread(
143 net::URLRequestContextGetter
* rq_context
,
144 const std::string
& domain_key
,
145 const net::CookieMonster::HasCookiesForETLDP1Callback
& callback
) {
146 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
147 net::CookieStore
* cookie_store
=
148 rq_context
->GetURLRequestContext()->cookie_store();
149 cookie_store
->GetCookieMonster()->HasCookiesForETLDP1Async(
151 base::Bind(&CheckIfCookiesExistForDomainResultOnIOThread
, callback
));
156 class PrerenderManager::OnCloseWebContentsDeleter
157 : public content::WebContentsDelegate
,
158 public base::SupportsWeakPtr
<
159 PrerenderManager::OnCloseWebContentsDeleter
> {
161 OnCloseWebContentsDeleter(PrerenderManager
* manager
,
165 suppressed_dialog_(false) {
166 tab_
->SetDelegate(this);
167 base::MessageLoop::current()->PostDelayedTask(FROM_HERE
,
168 base::Bind(&OnCloseWebContentsDeleter::ScheduleWebContentsForDeletion
,
170 base::TimeDelta::FromSeconds(kDeleteWithExtremePrejudiceSeconds
));
173 virtual void CloseContents(WebContents
* source
) OVERRIDE
{
174 DCHECK_EQ(tab_
, source
);
175 ScheduleWebContentsForDeletion(false);
178 virtual void SwappedOut(WebContents
* source
) OVERRIDE
{
179 DCHECK_EQ(tab_
, source
);
180 ScheduleWebContentsForDeletion(false);
183 virtual bool ShouldSuppressDialogs() OVERRIDE
{
184 // Use this as a proxy for getting statistics on how often we fail to honor
185 // the beforeunload event.
186 suppressed_dialog_
= true;
191 static const int kDeleteWithExtremePrejudiceSeconds
= 3;
193 void ScheduleWebContentsForDeletion(bool timeout
) {
194 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterTimeout", timeout
);
195 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterSuppressedDialog",
197 tab_
->SetDelegate(NULL
);
198 manager_
->ScheduleDeleteOldWebContents(tab_
.release(), this);
199 // |this| is deleted at this point.
202 PrerenderManager
* manager_
;
203 scoped_ptr
<WebContents
> tab_
;
204 bool suppressed_dialog_
;
206 DISALLOW_COPY_AND_ASSIGN(OnCloseWebContentsDeleter
);
210 bool PrerenderManager::is_prefetch_enabled_
= false;
213 int PrerenderManager::prerenders_per_session_count_
= 0;
216 PrerenderManager::PrerenderManagerMode
PrerenderManager::mode_
=
217 PRERENDER_MODE_ENABLED
;
219 struct PrerenderManager::NavigationRecord
{
220 NavigationRecord(const GURL
& url
, base::TimeTicks time
)
226 base::TimeTicks time
;
229 PrerenderManager::PrerenderedWebContentsData::
230 PrerenderedWebContentsData(Origin origin
) : origin(origin
) {
233 PrerenderManager::WouldBePrerenderedWebContentsData::
234 WouldBePrerenderedWebContentsData(Origin origin
)
236 state(WAITING_FOR_PROVISIONAL_LOAD
) {
239 PrerenderManager::PrerenderManager(Profile
* profile
,
240 PrerenderTracker
* prerender_tracker
)
241 : enabled_(profile
&& profile
->GetPrefs() &&
242 profile
->GetPrefs()->GetBoolean(prefs::kNetworkPredictionEnabled
)),
244 prerender_tracker_(prerender_tracker
),
245 prerender_contents_factory_(PrerenderContents::CreateFactory()),
246 last_prerender_start_time_(GetCurrentTimeTicks() -
247 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs
)),
248 prerender_history_(new PrerenderHistory(kHistoryLength
)),
249 histograms_(new PrerenderHistograms()) {
250 // There are some assumptions that the PrerenderManager is on the UI thread.
251 // Any other checks simply make sure that the PrerenderManager is accessed on
252 // the same thread that it was created on.
253 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
255 if (IsLocalPredictorEnabled())
256 local_predictor_
.reset(new PrerenderLocalPredictor(this));
258 if (IsLoggedInPredictorEnabled() && !profile_
->IsOffTheRecord()) {
259 predictors::PredictorDatabase
* predictor_db
=
260 predictors::PredictorDatabaseFactory::GetForProfile(profile
);
262 logged_in_predictor_table_
= predictor_db
->logged_in_table();
263 scoped_ptr
<LoggedInStateMap
> new_state_map(new LoggedInStateMap
);
264 LoggedInStateMap
* new_state_map_ptr
= new_state_map
.get();
265 BrowserThread::PostTaskAndReply(
266 BrowserThread::DB
, FROM_HERE
,
267 base::Bind(&LoggedInPredictorTable::GetAllData
,
268 logged_in_predictor_table_
,
270 base::Bind(&PrerenderManager::LoggedInPredictorDataReceived
,
272 base::Passed(&new_state_map
)));
276 // Certain experiments override our default config_ values.
277 switch (PrerenderManager::GetMode()) {
278 case PrerenderManager::PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP
:
279 config_
.max_link_concurrency
= 4;
280 config_
.max_link_concurrency_per_launcher
= 2;
282 case PrerenderManager::PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP
:
283 config_
.time_to_live
= base::TimeDelta::FromMinutes(15);
289 notification_registrar_
.Add(
290 this, chrome::NOTIFICATION_COOKIE_CHANGED
,
291 content::NotificationService::AllBrowserContextsAndSources());
293 MediaCaptureDevicesDispatcher::GetInstance()->AddObserver(this);
296 PrerenderManager::~PrerenderManager() {
297 MediaCaptureDevicesDispatcher::GetInstance()->RemoveObserver(this);
299 // The earlier call to BrowserContextKeyedService::Shutdown() should have
300 // emptied these vectors already.
301 DCHECK(active_prerenders_
.empty());
302 DCHECK(to_delete_prerenders_
.empty());
305 void PrerenderManager::Shutdown() {
306 DestroyAllContents(FINAL_STATUS_MANAGER_SHUTDOWN
);
307 STLDeleteElements(&prerender_conditions_
);
308 on_close_web_contents_deleters_
.clear();
309 // Must happen before |profile_| is set to NULL as
310 // |local_predictor_| accesses it.
311 if (local_predictor_
)
312 local_predictor_
->Shutdown();
315 DCHECK(active_prerenders_
.empty());
318 PrerenderHandle
* PrerenderManager::AddPrerenderFromLinkRelPrerender(
322 const content::Referrer
& referrer
,
323 const gfx::Size
& size
) {
324 #if defined(OS_ANDROID)
325 // TODO(jcivelli): http://crbug.com/113322 We should have an option to disable
326 // link-prerender and enable omnibox-prerender only.
329 DCHECK(!size
.IsEmpty());
330 Origin origin
= ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN
;
331 SessionStorageNamespace
* session_storage_namespace
= NULL
;
332 // Unit tests pass in a process_id == -1.
333 if (process_id
!= -1) {
334 RenderViewHost
* source_render_view_host
=
335 RenderViewHost::FromID(process_id
, route_id
);
336 if (!source_render_view_host
)
338 WebContents
* source_web_contents
=
339 WebContents::FromRenderViewHost(source_render_view_host
);
340 if (!source_web_contents
)
342 if (source_web_contents
->GetURL().host() == url
.host())
343 origin
= ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN
;
344 // TODO(ajwong): This does not correctly handle storage for isolated apps.
345 session_storage_namespace
=
346 source_web_contents
->GetController()
347 .GetDefaultSessionStorageNamespace();
350 // If the prerender request comes from a recently cancelled prerender that
351 // |this| still owns, then abort the prerender.
352 for (ScopedVector
<PrerenderData
>::iterator it
= to_delete_prerenders_
.begin();
353 it
!= to_delete_prerenders_
.end(); ++it
) {
354 PrerenderContents
* prerender_contents
= (*it
)->contents();
355 int contents_child_id
;
356 int contents_route_id
;
357 if (prerender_contents
->GetChildId(&contents_child_id
) &&
358 prerender_contents
->GetRouteId(&contents_route_id
)) {
359 if (contents_child_id
== process_id
&& contents_route_id
== route_id
)
364 if (PrerenderData
* parent_prerender_data
=
365 FindPrerenderDataForChildAndRoute(process_id
, route_id
)) {
366 // Instead of prerendering from inside of a running prerender, we will defer
367 // this request until its launcher is made visible.
368 if (PrerenderContents
* contents
= parent_prerender_data
->contents()) {
369 PrerenderHandle
* prerender_handle
=
370 new PrerenderHandle(static_cast<PrerenderData
*>(NULL
));
371 scoped_ptr
<PrerenderContents::PendingPrerenderInfo
>
372 pending_prerender_info(new PrerenderContents::PendingPrerenderInfo(
373 prerender_handle
->weak_ptr_factory_
.GetWeakPtr(),
374 origin
, url
, referrer
, size
));
376 contents
->AddPendingPrerender(pending_prerender_info
.Pass());
377 return prerender_handle
;
381 return AddPrerender(origin
, process_id
, url
, referrer
, size
,
382 session_storage_namespace
);
386 PrerenderHandle
* PrerenderManager::AddPrerenderFromOmnibox(
388 SessionStorageNamespace
* session_storage_namespace
,
389 const gfx::Size
& size
) {
390 if (!IsOmniboxEnabled(profile_
))
392 return AddPrerender(ORIGIN_OMNIBOX
, -1, url
, content::Referrer(), size
,
393 session_storage_namespace
);
396 PrerenderHandle
* PrerenderManager::AddPrerenderFromLocalPredictor(
398 SessionStorageNamespace
* session_storage_namespace
,
399 const gfx::Size
& size
) {
400 return AddPrerender(ORIGIN_LOCAL_PREDICTOR
, -1, url
, content::Referrer(),
401 size
, session_storage_namespace
);
404 PrerenderHandle
* PrerenderManager::AddPrerenderFromExternalRequest(
406 const content::Referrer
& referrer
,
407 SessionStorageNamespace
* session_storage_namespace
,
408 const gfx::Size
& size
) {
409 return AddPrerender(ORIGIN_EXTERNAL_REQUEST
, -1, url
, referrer
, size
,
410 session_storage_namespace
);
413 PrerenderHandle
* PrerenderManager::AddPrerenderForInstant(
415 content::SessionStorageNamespace
* session_storage_namespace
,
416 const gfx::Size
& size
) {
417 DCHECK(chrome::ShouldPrefetchSearchResults());
418 return AddPrerender(ORIGIN_INSTANT
, -1, url
, content::Referrer(), size
,
419 session_storage_namespace
);
422 void PrerenderManager::DestroyPrerenderForRenderView(
423 int process_id
, int view_id
, FinalStatus final_status
) {
424 DCHECK(CalledOnValidThread());
425 if (PrerenderData
* prerender_data
=
426 FindPrerenderDataForChildAndRoute(process_id
, view_id
)) {
427 prerender_data
->contents()->Destroy(final_status
);
431 void PrerenderManager::CancelAllPrerenders() {
432 DCHECK(CalledOnValidThread());
433 while (!active_prerenders_
.empty()) {
434 PrerenderContents
* prerender_contents
=
435 active_prerenders_
.front()->contents();
436 prerender_contents
->Destroy(FINAL_STATUS_CANCELLED
);
440 bool PrerenderManager::MaybeUsePrerenderedPage(const GURL
& url
,
441 chrome::NavigateParams
* params
) {
442 DCHECK(CalledOnValidThread());
444 content::WebContents
* web_contents
= params
->target_contents
;
445 DCHECK(!IsWebContentsPrerendering(web_contents
, NULL
));
447 // Don't prerender if the navigation involves some special parameters.
448 if (params
->uses_post
|| !params
->extra_headers
.empty())
452 to_delete_prerenders_
.clear();
453 // TODO(ajwong): This doesn't handle isolated apps correctly.
454 PrerenderData
* prerender_data
= FindPrerenderData(
456 web_contents
->GetController().GetDefaultSessionStorageNamespace());
459 DCHECK(prerender_data
->contents());
460 if (IsNoSwapInExperiment(prerender_data
->contents()->experiment_id()))
463 if (WebContents
* new_web_contents
=
464 prerender_data
->contents()->prerender_contents()) {
465 if (web_contents
== new_web_contents
)
466 return false; // Do not swap in to ourself.
468 // We cannot swap in if there is no last committed entry, because we would
469 // show a blank page under an existing entry from the current tab. Even if
470 // there is a pending entry, it may not commit.
471 // TODO(creis): If there is a pending navigation and no last committed
472 // entry, we might be able to transfer the network request instead.
473 if (!new_web_contents
->GetController().CanPruneAllButLastCommitted()) {
474 // Abort this prerender so it is not used later. http://crbug.com/292121
475 prerender_data
->contents()->Destroy(FINAL_STATUS_NAVIGATION_UNCOMMITTED
);
480 // Do not use the prerendered version if there is an opener object.
481 if (web_contents
->HasOpener()) {
482 prerender_data
->contents()->Destroy(FINAL_STATUS_WINDOW_OPENER
);
486 // Do not swap in the prerender if the current WebContents is being captured.
487 if (web_contents
->GetCapturerCount() > 0) {
488 prerender_data
->contents()->Destroy(FINAL_STATUS_PAGE_BEING_CAPTURED
);
492 // If we are just in the control group (which can be detected by noticing
493 // that prerendering hasn't even started yet), record that |web_contents| now
494 // would be showing a prerendered contents, but otherwise, don't do anything.
495 if (!prerender_data
->contents()->prerendering_has_started()) {
496 MarkWebContentsAsWouldBePrerendered(web_contents
,
497 prerender_data
->contents()->origin());
498 prerender_data
->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED
);
502 // Don't use prerendered pages if debugger is attached to the tab.
503 // See http://crbug.com/98541
504 if (content::DevToolsAgentHost::IsDebuggerAttached(web_contents
)) {
505 DestroyAndMarkMatchCompleteAsUsed(prerender_data
->contents(),
506 FINAL_STATUS_DEVTOOLS_ATTACHED
);
510 // If the prerendered page is in the middle of a cross-site navigation,
511 // don't swap it in because there isn't a good way to merge histories.
512 if (prerender_data
->contents()->IsCrossSiteNavigationPending()) {
513 DestroyAndMarkMatchCompleteAsUsed(
514 prerender_data
->contents(),
515 FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING
);
519 // For bookkeeping purposes, we need to mark this WebContents to
520 // reflect that it would have been prerendered.
521 if (GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP
) {
522 MarkWebContentsAsWouldBePrerendered(web_contents
,
523 prerender_data
->contents()->origin());
524 prerender_data
->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED
);
528 int child_id
, route_id
;
529 CHECK(prerender_data
->contents()->GetChildId(&child_id
));
530 CHECK(prerender_data
->contents()->GetRouteId(&route_id
));
532 // Try to set the prerendered page as used, so any subsequent attempts to
533 // cancel on other threads will fail. If this fails because the prerender
534 // was already cancelled, possibly on another thread, fail.
535 if (!prerender_tracker_
->TryUse(child_id
, route_id
))
538 // At this point, we've determined that we will use the prerender.
539 ScopedVector
<PrerenderData
>::iterator to_erase
=
540 FindIteratorForPrerenderContents(prerender_data
->contents());
541 DCHECK(active_prerenders_
.end() != to_erase
);
542 DCHECK_EQ(prerender_data
, *to_erase
);
543 scoped_ptr
<PrerenderContents
>
544 prerender_contents(prerender_data
->ReleaseContents());
545 active_prerenders_
.erase(to_erase
);
547 if (!prerender_contents
->load_start_time().is_null()) {
548 histograms_
->RecordTimeUntilUsed(
549 prerender_contents
->origin(),
550 GetCurrentTimeTicks() - prerender_contents
->load_start_time());
553 histograms_
->RecordPerSessionCount(prerender_contents
->origin(),
554 ++prerenders_per_session_count_
);
555 histograms_
->RecordUsedPrerender(prerender_contents
->origin());
556 prerender_contents
->SetFinalStatus(FINAL_STATUS_USED
);
558 RenderViewHost
* new_render_view_host
=
559 prerender_contents
->prerender_contents()->GetRenderViewHost();
560 new_render_view_host
->Send(
561 new PrerenderMsg_SetIsPrerendering(new_render_view_host
->GetRoutingID(),
564 // Start pending prerender requests from the PrerenderContents, if there are
566 prerender_contents
->PrepareForUse();
568 WebContents
* new_web_contents
=
569 prerender_contents
->ReleasePrerenderContents();
570 WebContents
* old_web_contents
= web_contents
;
571 DCHECK(new_web_contents
);
572 DCHECK(old_web_contents
);
574 MarkWebContentsAsPrerendered(new_web_contents
, prerender_contents
->origin());
576 // Merge the browsing history.
577 new_web_contents
->GetController().CopyStateFromAndPrune(
578 &old_web_contents
->GetController());
579 CoreTabHelper::FromWebContents(old_web_contents
)->delegate()->
580 SwapTabContents(old_web_contents
, new_web_contents
);
581 prerender_contents
->CommitHistory(new_web_contents
);
583 // Record the new target_contents for the callers.
584 params
->target_contents
= new_web_contents
;
586 GURL icon_url
= prerender_contents
->icon_url();
588 if (!icon_url
.is_empty()) {
589 #if defined(OS_ANDROID)
590 // Do the delayed icon fetch since we didn't download
591 // the favicon during prerendering on mobile devices.
592 FaviconTabHelper
* favicon_tap_helper
=
593 FaviconTabHelper::FromWebContents(new_web_contents
);
594 favicon_tap_helper
->set_should_fetch_icons(true);
595 favicon_tap_helper
->FetchFavicon(icon_url
);
596 #endif // defined(OS_ANDROID)
598 std::vector
<content::FaviconURL
> urls
;
599 urls
.push_back(content::FaviconURL(icon_url
, content::FaviconURL::FAVICON
));
600 FaviconTabHelper::FromWebContents(new_web_contents
)->
601 DidUpdateFaviconURL(prerender_contents
->page_id(), urls
);
604 // Update PPLT metrics:
605 // If the tab has finished loading, record a PPLT of 0.
606 // If the tab is still loading, reset its start time to the current time.
607 PrerenderTabHelper
* prerender_tab_helper
=
608 PrerenderTabHelper::FromWebContents(new_web_contents
);
609 DCHECK(prerender_tab_helper
!= NULL
);
610 prerender_tab_helper
->PrerenderSwappedIn();
612 if (old_web_contents
->NeedToFireBeforeUnload()) {
613 // Schedule the delete to occur after the tab has run its unload handlers.
614 // TODO(davidben): Honor the beforeunload event. http://crbug.com/304932
615 on_close_web_contents_deleters_
.push_back(
616 new OnCloseWebContentsDeleter(this, old_web_contents
));
617 old_web_contents
->GetRenderViewHost()->
618 FirePageBeforeUnload(false);
620 // No unload handler to run, so delete asap.
621 ScheduleDeleteOldWebContents(old_web_contents
, NULL
);
624 // TODO(cbentzel): Should prerender_contents move to the pending delete
625 // list, instead of deleting directly here?
626 AddToHistory(prerender_contents
.get());
627 RecordNavigation(url
);
631 void PrerenderManager::MoveEntryToPendingDelete(PrerenderContents
* entry
,
632 FinalStatus final_status
) {
633 DCHECK(CalledOnValidThread());
636 ScopedVector
<PrerenderData
>::iterator it
=
637 FindIteratorForPrerenderContents(entry
);
638 DCHECK(it
!= active_prerenders_
.end());
640 // If this PrerenderContents is being deleted due to a cancellation any time
641 // after the prerender has started then we need to create a dummy replacement
642 // for PPLT accounting purposes for the Match Complete group. This is the case
643 // if the cancellation is for any reason that would not occur in the control
645 if (entry
->prerendering_has_started() &&
646 entry
->match_complete_status() ==
647 PrerenderContents::MATCH_COMPLETE_DEFAULT
&&
648 NeedMatchCompleteDummyForFinalStatus(final_status
) &&
649 ActuallyPrerendering()) {
650 // TODO(tburkard): I'd like to DCHECK that we are actually prerendering.
651 // However, what if new conditions are added and
652 // NeedMatchCompleteDummyForFinalStatus is not being updated. Not sure
653 // what's the best thing to do here. For now, I will just check whether
654 // we are actually prerendering.
655 (*it
)->MakeIntoMatchCompleteReplacement();
657 to_delete_prerenders_
.push_back(*it
);
658 active_prerenders_
.weak_erase(it
);
661 // Destroy the old WebContents relatively promptly to reduce resource usage.
666 void PrerenderManager::RecordPerceivedPageLoadTime(
667 base::TimeDelta perceived_page_load_time
,
668 double fraction_plt_elapsed_at_swap_in
,
669 WebContents
* web_contents
,
671 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
672 PrerenderManager
* prerender_manager
=
673 PrerenderManagerFactory::GetForProfile(
674 Profile::FromBrowserContext(web_contents
->GetBrowserContext()));
675 if (!prerender_manager
)
677 if (!prerender_manager
->IsEnabled())
680 Origin prerender_origin
= ORIGIN_NONE
;
681 if (prerender_manager
->IsWebContentsPrerendering(web_contents
,
682 &prerender_origin
)) {
683 prerender_manager
->histograms_
->RecordPageLoadTimeNotSwappedIn(
684 prerender_origin
, perceived_page_load_time
, url
);
688 bool was_prerender
= prerender_manager
->IsWebContentsPrerendered(
689 web_contents
, &prerender_origin
);
690 bool was_complete_prerender
= was_prerender
||
691 prerender_manager
->WouldWebContentsBePrerendered(web_contents
,
693 prerender_manager
->histograms_
->RecordPerceivedPageLoadTime(
694 prerender_origin
, perceived_page_load_time
, was_prerender
,
695 was_complete_prerender
, url
);
698 prerender_manager
->histograms_
->RecordPercentLoadDoneAtSwapin(
699 prerender_origin
, fraction_plt_elapsed_at_swap_in
);
701 if (prerender_manager
->local_predictor_
.get()) {
702 prerender_manager
->local_predictor_
->
703 OnPLTEventForURL(url
, perceived_page_load_time
);
707 void PrerenderManager::RecordFractionPixelsFinalAtSwapin(
708 content::WebContents
* web_contents
,
710 Origin origin
= ORIGIN_NONE
;
711 bool is_prerendered
= IsWebContentsPrerendered(web_contents
, &origin
);
712 DCHECK(is_prerendered
);
713 histograms_
->RecordFractionPixelsFinalAtSwapin(origin
, fraction
);
716 void PrerenderManager::set_enabled(bool enabled
) {
717 DCHECK(CalledOnValidThread());
722 bool PrerenderManager::IsPrefetchEnabled() {
723 return is_prefetch_enabled_
;
727 void PrerenderManager::SetIsPrefetchEnabled(bool value
) {
728 is_prefetch_enabled_
= value
;
732 PrerenderManager::PrerenderManagerMode
PrerenderManager::GetMode() {
737 void PrerenderManager::SetMode(PrerenderManagerMode mode
) {
742 const char* PrerenderManager::GetModeString() {
744 case PRERENDER_MODE_DISABLED
:
746 case PRERENDER_MODE_ENABLED
:
747 case PRERENDER_MODE_EXPERIMENT_PRERENDER_GROUP
:
749 case PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP
:
751 case PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP
:
753 case PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP
:
755 case PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP
:
757 case PRERENDER_MODE_MAX
:
759 NOTREACHED() << "Invalid PrerenderManager mode.";
766 bool PrerenderManager::IsPrerenderingPossible() {
767 return GetMode() != PRERENDER_MODE_DISABLED
;
771 bool PrerenderManager::ActuallyPrerendering() {
772 return IsPrerenderingPossible() && !IsControlGroup(kNoExperiment
);
776 bool PrerenderManager::IsControlGroup(uint8 experiment_id
) {
777 return GetMode() == PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP
||
778 IsControlGroupExperiment(experiment_id
);
782 bool PrerenderManager::IsNoUseGroup() {
783 return GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP
;
786 bool PrerenderManager::IsWebContentsPrerendering(
787 const WebContents
* web_contents
,
788 Origin
* origin
) const {
789 DCHECK(CalledOnValidThread());
790 if (PrerenderContents
* prerender_contents
=
791 GetPrerenderContents(web_contents
)) {
793 *origin
= prerender_contents
->origin();
797 // Also look through the pending-deletion list.
798 for (ScopedVector
<PrerenderData
>::const_iterator it
=
799 to_delete_prerenders_
.begin();
800 it
!= to_delete_prerenders_
.end();
802 if (PrerenderContents
* prerender_contents
= (*it
)->contents()) {
803 WebContents
* prerender_web_contents
=
804 prerender_contents
->prerender_contents();
805 if (prerender_web_contents
== web_contents
) {
807 *origin
= prerender_contents
->origin();
816 bool PrerenderManager::HasPrerenderedUrl(
818 content::WebContents
* web_contents
) const {
819 content::SessionStorageNamespace
* session_storage_namespace
= web_contents
->
820 GetController().GetDefaultSessionStorageNamespace();
822 for (ScopedVector
<PrerenderData
>::const_iterator it
=
823 active_prerenders_
.begin();
824 it
!= active_prerenders_
.end(); ++it
) {
825 PrerenderContents
* prerender_contents
= (*it
)->contents();
826 if (prerender_contents
->Matches(url
, session_storage_namespace
)) {
833 PrerenderContents
* PrerenderManager::GetPrerenderContents(
834 const content::WebContents
* web_contents
) const {
835 DCHECK(CalledOnValidThread());
836 for (ScopedVector
<PrerenderData
>::const_iterator it
=
837 active_prerenders_
.begin();
838 it
!= active_prerenders_
.end(); ++it
) {
839 WebContents
* prerender_web_contents
=
840 (*it
)->contents()->prerender_contents();
841 if (prerender_web_contents
== web_contents
) {
842 return (*it
)->contents();
848 const std::vector
<WebContents
*>
849 PrerenderManager::GetAllPrerenderingContents() const {
850 DCHECK(CalledOnValidThread());
851 std::vector
<WebContents
*> result
;
853 for (ScopedVector
<PrerenderData
>::const_iterator it
=
854 active_prerenders_
.begin();
855 it
!= active_prerenders_
.end(); ++it
) {
856 if (WebContents
* contents
= (*it
)->contents()->prerender_contents())
857 result
.push_back(contents
);
863 void PrerenderManager::MarkWebContentsAsPrerendered(WebContents
* web_contents
,
865 DCHECK(CalledOnValidThread());
866 prerendered_web_contents_data_
.insert(
867 base::hash_map
<content::WebContents
*,
868 PrerenderedWebContentsData
>::value_type(
869 web_contents
, PrerenderedWebContentsData(origin
)));
872 void PrerenderManager::MarkWebContentsAsWouldBePrerendered(
873 WebContents
* web_contents
,
875 DCHECK(CalledOnValidThread());
876 would_be_prerendered_map_
.insert(
877 base::hash_map
<content::WebContents
*,
878 WouldBePrerenderedWebContentsData
>::value_type(
880 WouldBePrerenderedWebContentsData(origin
)));
883 void PrerenderManager::MarkWebContentsAsNotPrerendered(
884 WebContents
* web_contents
) {
885 DCHECK(CalledOnValidThread());
886 prerendered_web_contents_data_
.erase(web_contents
);
887 base::hash_map
<content::WebContents
*, WouldBePrerenderedWebContentsData
>::
888 iterator it
= would_be_prerendered_map_
.find(web_contents
);
889 if (it
!= would_be_prerendered_map_
.end()) {
890 if (it
->second
.state
==
891 WouldBePrerenderedWebContentsData::WAITING_FOR_PROVISIONAL_LOAD
) {
893 WouldBePrerenderedWebContentsData::SEEN_PROVISIONAL_LOAD
;
895 would_be_prerendered_map_
.erase(it
);
900 bool PrerenderManager::IsWebContentsPrerendered(
901 content::WebContents
* web_contents
,
902 Origin
* origin
) const {
903 DCHECK(CalledOnValidThread());
904 base::hash_map
<content::WebContents
*, PrerenderedWebContentsData
>::
905 const_iterator it
= prerendered_web_contents_data_
.find(web_contents
);
906 if (it
== prerendered_web_contents_data_
.end())
909 *origin
= it
->second
.origin
;
913 bool PrerenderManager::WouldWebContentsBePrerendered(
914 WebContents
* web_contents
,
915 Origin
* origin
) const {
916 DCHECK(CalledOnValidThread());
917 base::hash_map
<content::WebContents
*, WouldBePrerenderedWebContentsData
>::
918 const_iterator it
= would_be_prerendered_map_
.find(web_contents
);
919 if (it
== would_be_prerendered_map_
.end())
922 *origin
= it
->second
.origin
;
926 bool PrerenderManager::HasRecentlyBeenNavigatedTo(Origin origin
,
928 DCHECK(CalledOnValidThread());
930 CleanUpOldNavigations();
931 std::list
<NavigationRecord
>::const_reverse_iterator end
= navigations_
.rend();
932 for (std::list
<NavigationRecord
>::const_reverse_iterator it
=
933 navigations_
.rbegin();
936 if (it
->url
== url
) {
937 base::TimeDelta delta
= GetCurrentTimeTicks() - it
->time
;
938 histograms_
->RecordTimeSinceLastRecentVisit(origin
, delta
);
947 bool PrerenderManager::IsValidHttpMethod(const std::string
& method
) {
948 // method has been canonicalized to upper case at this point so we can just
950 DCHECK_EQ(method
, StringToUpperASCII(method
));
951 for (size_t i
= 0; i
< arraysize(kValidHttpMethods
); ++i
) {
952 if (method
.compare(kValidHttpMethods
[i
]) == 0)
960 bool PrerenderManager::DoesURLHaveValidScheme(const GURL
& url
) {
961 return (url
.SchemeIsHTTPOrHTTPS() ||
962 url
.SchemeIs(extensions::kExtensionScheme
) ||
963 url
.SchemeIs("data"));
967 bool PrerenderManager::DoesSubresourceURLHaveValidScheme(const GURL
& url
) {
968 return DoesURLHaveValidScheme(url
) || url
== GURL(content::kAboutBlankURL
);
971 DictionaryValue
* PrerenderManager::GetAsValue() const {
972 DCHECK(CalledOnValidThread());
973 DictionaryValue
* dict_value
= new DictionaryValue();
974 dict_value
->Set("history", prerender_history_
->GetEntriesAsValue());
975 dict_value
->Set("active", GetActivePrerendersAsValue());
976 dict_value
->SetBoolean("enabled", enabled_
);
977 dict_value
->SetBoolean("omnibox_enabled", IsOmniboxEnabled(profile_
));
978 // If prerender is disabled via a flag this method is not even called.
979 std::string enabled_note
;
980 if (IsControlGroup(kNoExperiment
))
981 enabled_note
+= "(Control group: Not actually prerendering) ";
983 enabled_note
+= "(No-use group: Not swapping in prerendered pages) ";
984 if (GetMode() == PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP
)
986 "(15 min TTL group: Extended prerender eviction to 15 mins) ";
987 dict_value
->SetString("enabled_note", enabled_note
);
991 void PrerenderManager::ClearData(int clear_flags
) {
992 DCHECK_GE(clear_flags
, 0);
993 DCHECK_LT(clear_flags
, CLEAR_MAX
);
994 if (clear_flags
& CLEAR_PRERENDER_CONTENTS
)
995 DestroyAllContents(FINAL_STATUS_CACHE_OR_HISTORY_CLEARED
);
996 // This has to be second, since destroying prerenders can add to the history.
997 if (clear_flags
& CLEAR_PRERENDER_HISTORY
)
998 prerender_history_
->Clear();
1001 void PrerenderManager::RecordFinalStatusWithMatchCompleteStatus(
1003 uint8 experiment_id
,
1004 PrerenderContents::MatchCompleteStatus mc_status
,
1005 FinalStatus final_status
) const {
1006 histograms_
->RecordFinalStatus(origin
,
1012 void PrerenderManager::AddCondition(const PrerenderCondition
* condition
) {
1013 prerender_conditions_
.push_back(condition
);
1016 void PrerenderManager::RecordNavigation(const GURL
& url
) {
1017 DCHECK(CalledOnValidThread());
1019 navigations_
.push_back(NavigationRecord(url
, GetCurrentTimeTicks()));
1020 CleanUpOldNavigations();
1024 struct PrerenderManager::PrerenderData::OrderByExpiryTime
{
1025 bool operator()(const PrerenderData
* a
, const PrerenderData
* b
) const {
1026 return a
->expiry_time() < b
->expiry_time();
1030 PrerenderManager::PrerenderData::PrerenderData(PrerenderManager
* manager
,
1031 PrerenderContents
* contents
,
1032 base::TimeTicks expiry_time
)
1033 : manager_(manager
),
1034 contents_(contents
),
1036 expiry_time_(expiry_time
) {
1037 DCHECK_NE(static_cast<PrerenderContents
*>(NULL
), contents_
);
1040 PrerenderManager::PrerenderData::~PrerenderData() {
1043 void PrerenderManager::PrerenderData::MakeIntoMatchCompleteReplacement() {
1045 contents_
->set_match_complete_status(
1046 PrerenderContents::MATCH_COMPLETE_REPLACED
);
1047 PrerenderData
* to_delete
= new PrerenderData(manager_
, contents_
.release(),
1049 contents_
.reset(to_delete
->contents_
->CreateMatchCompleteReplacement());
1050 manager_
->to_delete_prerenders_
.push_back(to_delete
);
1053 void PrerenderManager::PrerenderData::OnHandleCreated(PrerenderHandle
* handle
) {
1054 DCHECK_NE(static_cast<PrerenderContents
*>(NULL
), contents_
);
1056 contents_
->AddObserver(handle
);
1059 void PrerenderManager::PrerenderData::OnHandleNavigatedAway(
1060 PrerenderHandle
* handle
) {
1061 DCHECK_LT(0, handle_count_
);
1062 DCHECK_NE(static_cast<PrerenderContents
*>(NULL
), contents_
);
1063 // We intentionally don't decrement the handle count here, so that the
1064 // prerender won't be canceled until it times out.
1065 manager_
->SourceNavigatedAway(this);
1068 void PrerenderManager::PrerenderData::OnHandleCanceled(
1069 PrerenderHandle
* handle
) {
1070 DCHECK_LT(0, handle_count_
);
1071 DCHECK_NE(static_cast<PrerenderContents
*>(NULL
), contents_
);
1073 if (--handle_count_
== 0) {
1074 // This will eventually remove this object from active_prerenders_.
1075 contents_
->Destroy(FINAL_STATUS_CANCELLED
);
1079 PrerenderContents
* PrerenderManager::PrerenderData::ReleaseContents() {
1080 return contents_
.release();
1083 void PrerenderManager::SetPrerenderContentsFactory(
1084 PrerenderContents::Factory
* prerender_contents_factory
) {
1085 DCHECK(CalledOnValidThread());
1086 prerender_contents_factory_
.reset(prerender_contents_factory
);
1089 void PrerenderManager::StartPendingPrerenders(
1090 const int process_id
,
1091 ScopedVector
<PrerenderContents::PendingPrerenderInfo
>* pending_prerenders
,
1092 content::SessionStorageNamespace
* session_storage_namespace
) {
1093 for (ScopedVector
<PrerenderContents::PendingPrerenderInfo
>::iterator
1094 it
= pending_prerenders
->begin();
1095 it
!= pending_prerenders
->end(); ++it
) {
1096 PrerenderContents::PendingPrerenderInfo
* info
= *it
;
1097 PrerenderHandle
* existing_prerender_handle
=
1098 info
->weak_prerender_handle
.get();
1099 if (!existing_prerender_handle
)
1102 DCHECK(!existing_prerender_handle
->IsPrerendering());
1103 DCHECK(process_id
== -1 || session_storage_namespace
);
1105 scoped_ptr
<PrerenderHandle
> new_prerender_handle(AddPrerender(
1106 info
->origin
, process_id
,
1107 info
->url
, info
->referrer
, info
->size
,
1108 session_storage_namespace
));
1109 if (new_prerender_handle
) {
1110 // AddPrerender has returned a new prerender handle to us. We want to make
1111 // |existing_prerender_handle| active, so move the underlying
1112 // PrerenderData to our new handle.
1113 existing_prerender_handle
->AdoptPrerenderDataFrom(
1114 new_prerender_handle
.get());
1120 void PrerenderManager::SourceNavigatedAway(PrerenderData
* prerender_data
) {
1121 // The expiry time of our prerender data will likely change because of
1122 // this navigation. This requires a resort of active_prerenders_.
1123 ScopedVector
<PrerenderData
>::iterator it
=
1124 std::find(active_prerenders_
.begin(), active_prerenders_
.end(),
1126 if (it
== active_prerenders_
.end())
1129 (*it
)->set_expiry_time(
1130 std::min((*it
)->expiry_time(),
1131 GetExpiryTimeForNavigatedAwayPrerender()));
1132 SortActivePrerenders();
1136 PrerenderHandle
* PrerenderManager::AddPrerender(
1139 const GURL
& url_arg
,
1140 const content::Referrer
& referrer
,
1141 const gfx::Size
& size
,
1142 SessionStorageNamespace
* session_storage_namespace
) {
1143 DCHECK(CalledOnValidThread());
1148 if ((origin
== ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN
||
1149 origin
== ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN
) &&
1150 IsGoogleSearchResultURL(referrer
.url
)) {
1151 origin
= ORIGIN_GWS_PRERENDER
;
1156 uint8 experiment
= GetQueryStringBasedExperiment(url_arg
);
1157 if (IsControlGroup(experiment
) &&
1158 MaybeGetQueryStringBasedAliasURL(url
, &alias_url
)) {
1162 // From here on, we will record a FinalStatus so we need to register with the
1163 // histogram tracking.
1164 histograms_
->RecordPrerender(origin
, url_arg
);
1166 if (PrerenderData
* preexisting_prerender_data
=
1167 FindPrerenderData(url
, session_storage_namespace
)) {
1168 RecordFinalStatus(origin
, experiment
, FINAL_STATUS_DUPLICATE
);
1169 return new PrerenderHandle(preexisting_prerender_data
);
1172 // Do not prerender if there are too many render processes, and we would
1173 // have to use an existing one. We do not want prerendering to happen in
1174 // a shared process, so that we can always reliably lower the CPU
1175 // priority for prerendering.
1176 // In single-process mode, ShouldTryToUseExistingProcessHost() always returns
1177 // true, so that case needs to be explicitly checked for.
1178 // TODO(tburkard): Figure out how to cancel prerendering in the opposite
1179 // case, when a new tab is added to a process used for prerendering.
1180 // On Android we do reuse processes as we have a limited number of them and we
1181 // still want the benefits of prerendering even when several tabs are open.
1182 #if !defined(OS_ANDROID)
1183 if (content::RenderProcessHost::ShouldTryToUseExistingProcessHost(
1185 !content::RenderProcessHost::run_renderer_in_process()) {
1186 RecordFinalStatus(origin
, experiment
, FINAL_STATUS_TOO_MANY_PROCESSES
);
1191 // Check if enough time has passed since the last prerender.
1192 if (!DoesRateLimitAllowPrerender(origin
)) {
1193 // Cancel the prerender. We could add it to the pending prerender list but
1194 // this doesn't make sense as the next prerender request will be triggered
1195 // by a navigation and is unlikely to be the same site.
1196 RecordFinalStatus(origin
, experiment
, FINAL_STATUS_RATE_LIMIT_EXCEEDED
);
1200 PrerenderContents
* prerender_contents
= CreatePrerenderContents(
1201 url
, referrer
, origin
, experiment
);
1202 DCHECK(prerender_contents
);
1203 active_prerenders_
.push_back(
1204 new PrerenderData(this, prerender_contents
,
1205 GetExpiryTimeForNewPrerender(origin
)));
1206 if (!prerender_contents
->Init()) {
1207 DCHECK(active_prerenders_
.end() ==
1208 FindIteratorForPrerenderContents(prerender_contents
));
1212 histograms_
->RecordPrerenderStarted(origin
);
1213 DCHECK(!prerender_contents
->prerendering_has_started());
1215 PrerenderHandle
* prerender_handle
=
1216 new PrerenderHandle(active_prerenders_
.back());
1217 SortActivePrerenders();
1219 last_prerender_start_time_
= GetCurrentTimeTicks();
1221 gfx::Size contents_size
=
1222 size
.IsEmpty() ? config_
.default_tab_bounds
.size() : size
;
1224 prerender_contents
->StartPrerendering(process_id
, contents_size
,
1225 session_storage_namespace
);
1227 DCHECK(IsControlGroup(experiment
) ||
1228 prerender_contents
->prerendering_has_started());
1230 if (GetMode() == PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP
)
1231 histograms_
->RecordConcurrency(active_prerenders_
.size());
1233 StartSchedulingPeriodicCleanups();
1234 return prerender_handle
;
1237 void PrerenderManager::StartSchedulingPeriodicCleanups() {
1238 DCHECK(CalledOnValidThread());
1239 if (repeating_timer_
.IsRunning())
1241 repeating_timer_
.Start(FROM_HERE
,
1242 base::TimeDelta::FromMilliseconds(kPeriodicCleanupIntervalMs
),
1244 &PrerenderManager::PeriodicCleanup
);
1247 void PrerenderManager::StopSchedulingPeriodicCleanups() {
1248 DCHECK(CalledOnValidThread());
1249 repeating_timer_
.Stop();
1252 void PrerenderManager::PeriodicCleanup() {
1253 DCHECK(CalledOnValidThread());
1254 DeleteOldWebContents();
1256 if (active_prerenders_
.empty())
1257 StopSchedulingPeriodicCleanups();
1259 // Grab a copy of the current PrerenderContents pointers, so that we
1260 // will not interfere with potential deletions of the list.
1261 std::vector
<PrerenderContents
*>
1262 prerender_contents(active_prerenders_
.size());
1263 std::transform(active_prerenders_
.begin(), active_prerenders_
.end(),
1264 prerender_contents
.begin(),
1265 std::mem_fun(&PrerenderData::contents
));
1267 // And now check for prerenders using too much memory.
1268 std::for_each(prerender_contents
.begin(), prerender_contents
.end(),
1270 &PrerenderContents::DestroyWhenUsingTooManyResources
));
1272 to_delete_prerenders_
.clear();
1275 void PrerenderManager::PostCleanupTask() {
1276 DCHECK(CalledOnValidThread());
1277 base::MessageLoop::current()->PostTask(
1279 base::Bind(&PrerenderManager::PeriodicCleanup
, AsWeakPtr()));
1282 base::TimeTicks
PrerenderManager::GetExpiryTimeForNewPrerender(
1283 Origin origin
) const {
1284 base::TimeDelta ttl
= config_
.time_to_live
;
1285 if (origin
== ORIGIN_LOCAL_PREDICTOR
)
1286 ttl
= base::TimeDelta::FromSeconds(GetLocalPredictorTTLSeconds());
1287 return GetCurrentTimeTicks() + ttl
;
1290 base::TimeTicks
PrerenderManager::GetExpiryTimeForNavigatedAwayPrerender()
1292 return GetCurrentTimeTicks() + config_
.abandon_time_to_live
;
1295 void PrerenderManager::DeleteOldEntries() {
1296 DCHECK(CalledOnValidThread());
1297 while (!active_prerenders_
.empty()) {
1298 PrerenderData
* prerender_data
= active_prerenders_
.front();
1299 DCHECK(prerender_data
);
1300 DCHECK(prerender_data
->contents());
1302 if (prerender_data
->expiry_time() > GetCurrentTimeTicks())
1304 prerender_data
->contents()->Destroy(FINAL_STATUS_TIMED_OUT
);
1308 base::Time
PrerenderManager::GetCurrentTime() const {
1309 return base::Time::Now();
1312 base::TimeTicks
PrerenderManager::GetCurrentTimeTicks() const {
1313 return base::TimeTicks::Now();
1316 PrerenderContents
* PrerenderManager::CreatePrerenderContents(
1318 const content::Referrer
& referrer
,
1320 uint8 experiment_id
) {
1321 DCHECK(CalledOnValidThread());
1322 return prerender_contents_factory_
->CreatePrerenderContents(
1323 this, profile_
, url
, referrer
, origin
, experiment_id
);
1326 void PrerenderManager::SortActivePrerenders() {
1327 std::sort(active_prerenders_
.begin(), active_prerenders_
.end(),
1328 PrerenderData::OrderByExpiryTime());
1331 PrerenderManager::PrerenderData
* PrerenderManager::FindPrerenderData(
1333 const SessionStorageNamespace
* session_storage_namespace
) {
1334 for (ScopedVector
<PrerenderData
>::iterator it
= active_prerenders_
.begin();
1335 it
!= active_prerenders_
.end(); ++it
) {
1336 if ((*it
)->contents()->Matches(url
, session_storage_namespace
))
1342 PrerenderManager::PrerenderData
*
1343 PrerenderManager::FindPrerenderDataForChildAndRoute(
1344 const int child_id
, const int route_id
) {
1345 for (ScopedVector
<PrerenderData
>::iterator it
= active_prerenders_
.begin();
1346 it
!= active_prerenders_
.end(); ++it
) {
1347 PrerenderContents
* prerender_contents
= (*it
)->contents();
1349 int contents_child_id
;
1350 if (!prerender_contents
->GetChildId(&contents_child_id
))
1352 int contents_route_id
;
1353 if (!prerender_contents
->GetRouteId(&contents_route_id
))
1356 if (contents_child_id
== child_id
&& contents_route_id
== route_id
)
1362 ScopedVector
<PrerenderManager::PrerenderData
>::iterator
1363 PrerenderManager::FindIteratorForPrerenderContents(
1364 PrerenderContents
* prerender_contents
) {
1365 for (ScopedVector
<PrerenderData
>::iterator it
= active_prerenders_
.begin();
1366 it
!= active_prerenders_
.end(); ++it
) {
1367 if (prerender_contents
== (*it
)->contents())
1370 return active_prerenders_
.end();
1373 bool PrerenderManager::DoesRateLimitAllowPrerender(Origin origin
) const {
1374 DCHECK(CalledOnValidThread());
1375 base::TimeDelta elapsed_time
=
1376 GetCurrentTimeTicks() - last_prerender_start_time_
;
1377 histograms_
->RecordTimeBetweenPrerenderRequests(origin
, elapsed_time
);
1378 if (!config_
.rate_limit_enabled
)
1380 return elapsed_time
>=
1381 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs
);
1384 void PrerenderManager::DeleteOldWebContents() {
1385 while (!old_web_contents_list_
.empty()) {
1386 WebContents
* web_contents
= old_web_contents_list_
.front();
1387 old_web_contents_list_
.pop_front();
1388 // TODO(dominich): should we use Instant Unload Handler here?
1389 delete web_contents
;
1393 void PrerenderManager::CleanUpOldNavigations() {
1394 DCHECK(CalledOnValidThread());
1396 // Cutoff. Navigations before this cutoff can be discarded.
1397 base::TimeTicks cutoff
= GetCurrentTimeTicks() -
1398 base::TimeDelta::FromMilliseconds(kNavigationRecordWindowMs
);
1399 while (!navigations_
.empty()) {
1400 if (navigations_
.front().time
> cutoff
)
1402 navigations_
.pop_front();
1406 void PrerenderManager::ScheduleDeleteOldWebContents(
1408 OnCloseWebContentsDeleter
* deleter
) {
1409 old_web_contents_list_
.push_back(tab
);
1413 ScopedVector
<OnCloseWebContentsDeleter
>::iterator i
= std::find(
1414 on_close_web_contents_deleters_
.begin(),
1415 on_close_web_contents_deleters_
.end(),
1417 DCHECK(i
!= on_close_web_contents_deleters_
.end());
1418 on_close_web_contents_deleters_
.erase(i
);
1422 void PrerenderManager::AddToHistory(PrerenderContents
* contents
) {
1423 PrerenderHistory::Entry
entry(contents
->prerender_url(),
1424 contents
->final_status(),
1427 prerender_history_
->AddEntry(entry
);
1430 Value
* PrerenderManager::GetActivePrerendersAsValue() const {
1431 ListValue
* list_value
= new ListValue();
1432 for (ScopedVector
<PrerenderData
>::const_iterator it
=
1433 active_prerenders_
.begin();
1434 it
!= active_prerenders_
.end(); ++it
) {
1435 if (Value
* prerender_value
= (*it
)->contents()->GetAsValue())
1436 list_value
->Append(prerender_value
);
1441 void PrerenderManager::DestroyAllContents(FinalStatus final_status
) {
1442 DeleteOldWebContents();
1443 while (!active_prerenders_
.empty()) {
1444 PrerenderContents
* contents
= active_prerenders_
.front()->contents();
1445 contents
->Destroy(final_status
);
1447 to_delete_prerenders_
.clear();
1450 void PrerenderManager::DestroyAndMarkMatchCompleteAsUsed(
1451 PrerenderContents
* prerender_contents
,
1452 FinalStatus final_status
) {
1453 prerender_contents
->set_match_complete_status(
1454 PrerenderContents::MATCH_COMPLETE_REPLACED
);
1455 histograms_
->RecordFinalStatus(prerender_contents
->origin(),
1456 prerender_contents
->experiment_id(),
1457 PrerenderContents::MATCH_COMPLETE_REPLACEMENT
,
1458 FINAL_STATUS_WOULD_HAVE_BEEN_USED
);
1459 prerender_contents
->Destroy(final_status
);
1462 void PrerenderManager::RecordFinalStatus(Origin origin
,
1463 uint8 experiment_id
,
1464 FinalStatus final_status
) const {
1465 RecordFinalStatusWithMatchCompleteStatus(
1466 origin
, experiment_id
,
1467 PrerenderContents::MATCH_COMPLETE_DEFAULT
,
1471 bool PrerenderManager::IsEnabled() const {
1472 DCHECK(CalledOnValidThread());
1475 for (std::list
<const PrerenderCondition
*>::const_iterator it
=
1476 prerender_conditions_
.begin();
1477 it
!= prerender_conditions_
.end();
1479 const PrerenderCondition
* condition
= *it
;
1480 if (!condition
->CanPrerender())
1486 PrerenderManager
* FindPrerenderManagerUsingRenderProcessId(
1487 int render_process_id
) {
1488 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1489 content::RenderProcessHost
* render_process_host
=
1490 content::RenderProcessHost::FromID(render_process_id
);
1491 // Each render process is guaranteed to only hold RenderViews owned by the
1492 // same BrowserContext. This is enforced by
1493 // RenderProcessHost::GetExistingProcessHost.
1494 if (!render_process_host
|| !render_process_host
->GetBrowserContext())
1496 Profile
* profile
= Profile::FromBrowserContext(
1497 render_process_host
->GetBrowserContext());
1500 return PrerenderManagerFactory::GetInstance()->GetForProfile(profile
);
1503 void PrerenderManager::Observe(int type
,
1504 const content::NotificationSource
& source
,
1505 const content::NotificationDetails
& details
) {
1506 Profile
* profile
= content::Source
<Profile
>(source
).ptr();
1507 if (!profile
|| !profile_
->IsSameProfile(profile
) ||
1508 profile
->IsOffTheRecord()) {
1511 DCHECK(type
== chrome::NOTIFICATION_COOKIE_CHANGED
);
1512 CookieChanged(content::Details
<ChromeCookieDetails
>(details
).ptr());
1515 void PrerenderManager::OnCreatingAudioStream(int render_process_id
,
1516 int render_view_id
) {
1517 WebContents
* tab
= tab_util::GetWebContentsByID(
1518 render_process_id
, render_view_id
);
1522 if (!IsWebContentsPrerendering(tab
, NULL
))
1525 prerender_tracker()->TryCancel(
1526 render_process_id
, render_view_id
,
1527 prerender::FINAL_STATUS_CREATING_AUDIO_STREAM
);
1530 void PrerenderManager::RecordLikelyLoginOnURL(const GURL
& url
) {
1531 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1532 if (!url
.SchemeIsHTTPOrHTTPS())
1534 if (logged_in_predictor_table_
.get()) {
1535 BrowserThread::PostTask(
1538 base::Bind(&LoggedInPredictorTable::AddDomainFromURL
,
1539 logged_in_predictor_table_
,
1542 std::string key
= LoggedInPredictorTable::GetKey(url
);
1543 if (!logged_in_state_
.get())
1545 if (logged_in_state_
->count(key
))
1547 (*logged_in_state_
)[key
] = base::Time::Now().ToInternalValue();
1550 void PrerenderManager::CheckIfLikelyLoggedInOnURL(
1552 bool* lookup_result
,
1553 bool* database_was_present
,
1554 const base::Closure
& result_cb
) {
1555 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1556 if (!logged_in_predictor_table_
.get()) {
1557 *database_was_present
= false;
1558 *lookup_result
= false;
1559 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
, result_cb
);
1562 BrowserThread::PostTaskAndReply(
1563 BrowserThread::DB
, FROM_HERE
,
1564 base::Bind(&LoggedInPredictorTable::HasUserLoggedIn
,
1565 logged_in_predictor_table_
,
1568 database_was_present
),
1573 void PrerenderManager::CookieChanged(ChromeCookieDetails
* details
) {
1574 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1576 if (!logged_in_predictor_table_
.get())
1579 // We only care when a cookie has been removed.
1580 if (!details
->removed
)
1583 std::string domain_key
=
1584 LoggedInPredictorTable::GetKeyFromDomain(details
->cookie
->Domain());
1586 // If we have no record of this domain as a potentially logged in domain,
1587 // nothing to do here.
1588 if (logged_in_state_
.get() && logged_in_state_
->count(domain_key
) < 1)
1591 net::URLRequestContextGetter
* rq_context
= profile_
->GetRequestContext();
1595 BrowserThread::PostTask(
1596 BrowserThread::IO
, FROM_HERE
,
1597 base::Bind(&CheckIfCookiesExistForDomainOnIOThread
,
1598 base::Unretained(rq_context
),
1601 &PrerenderManager::CookieChangedAnyCookiesLeftLookupResult
,
1607 void PrerenderManager::CookieChangedAnyCookiesLeftLookupResult(
1608 const std::string
& domain_key
,
1609 bool cookies_exist
) {
1610 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1615 if (logged_in_predictor_table_
.get()) {
1616 BrowserThread::PostTask(BrowserThread::DB
,
1618 base::Bind(&LoggedInPredictorTable::DeleteDomain
,
1619 logged_in_predictor_table_
,
1623 if (logged_in_state_
.get())
1624 logged_in_state_
->erase(domain_key
);
1627 void PrerenderManager::LoggedInPredictorDataReceived(
1628 scoped_ptr
<LoggedInStateMap
> new_map
) {
1629 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1630 logged_in_state_
.swap(new_map
);
1633 } // namespace prerender