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/strings/utf_string_conversions.h"
19 #include "base/time/time.h"
20 #include "base/timer/elapsed_timer.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/history/history_service_factory.h"
25 #include "chrome/browser/net/chrome_cookie_notification_details.h"
26 #include "chrome/browser/net/prediction_options.h"
27 #include "chrome/browser/predictors/predictor_database.h"
28 #include "chrome/browser/predictors/predictor_database_factory.h"
29 #include "chrome/browser/prerender/prerender_contents.h"
30 #include "chrome/browser/prerender/prerender_field_trial.h"
31 #include "chrome/browser/prerender/prerender_final_status.h"
32 #include "chrome/browser/prerender/prerender_handle.h"
33 #include "chrome/browser/prerender/prerender_histograms.h"
34 #include "chrome/browser/prerender/prerender_history.h"
35 #include "chrome/browser/prerender/prerender_local_predictor.h"
36 #include "chrome/browser/prerender/prerender_manager_factory.h"
37 #include "chrome/browser/prerender/prerender_tab_helper.h"
38 #include "chrome/browser/prerender/prerender_tracker.h"
39 #include "chrome/browser/prerender/prerender_util.h"
40 #include "chrome/browser/profiles/profile.h"
41 #include "chrome/browser/search/search.h"
42 #include "chrome/browser/tab_contents/tab_util.h"
43 #include "chrome/browser/ui/browser_navigator.h"
44 #include "chrome/browser/ui/tab_contents/core_tab_helper.h"
45 #include "chrome/browser/ui/tab_contents/core_tab_helper_delegate.h"
46 #include "chrome/common/chrome_switches.h"
47 #include "chrome/common/prerender_messages.h"
48 #include "chrome/common/prerender_types.h"
49 #include "content/public/browser/browser_thread.h"
50 #include "content/public/browser/devtools_agent_host.h"
51 #include "content/public/browser/navigation_controller.h"
52 #include "content/public/browser/notification_service.h"
53 #include "content/public/browser/notification_source.h"
54 #include "content/public/browser/render_frame_host.h"
55 #include "content/public/browser/render_process_host.h"
56 #include "content/public/browser/render_view_host.h"
57 #include "content/public/browser/resource_request_details.h"
58 #include "content/public/browser/session_storage_namespace.h"
59 #include "content/public/browser/site_instance.h"
60 #include "content/public/browser/storage_partition.h"
61 #include "content/public/browser/web_contents.h"
62 #include "content/public/browser/web_contents_delegate.h"
63 #include "content/public/common/url_constants.h"
64 #include "extensions/common/constants.h"
65 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
66 #include "net/url_request/url_request_context.h"
67 #include "net/url_request/url_request_context_getter.h"
69 using content::BrowserThread
;
70 using content::RenderViewHost
;
71 using content::RenderFrameHost
;
72 using content::SessionStorageNamespace
;
73 using content::WebContents
;
74 using predictors::LoggedInPredictorTable
;
80 // Time interval at which periodic cleanups are performed.
81 const int kPeriodicCleanupIntervalMs
= 1000;
83 // Valid HTTP methods for prerendering.
84 const char* const kValidHttpMethods
[] = {
92 // Length of prerender history, for display in chrome://net-internals
93 const int kHistoryLength
= 100;
95 // Indicates whether a Prerender has been cancelled such that we need
96 // a dummy replacement for the purpose of recording the correct PPLT for
97 // the Match Complete case.
98 // Traditionally, "Match" means that a prerendered page was actually visited &
99 // the prerender was used. Our goal is to have "Match" cases line up in the
100 // control group & the experiment group, so that we can make meaningful
101 // comparisons of improvements. However, in the control group, since we don't
102 // actually perform prerenders, many of the cancellation reasons cannot be
103 // detected. Therefore, in the Prerender group, when we cancel for one of these
104 // reasons, we keep track of a dummy Prerender representing what we would
105 // have in the control group. If that dummy prerender in the prerender group
106 // would then be swapped in (but isn't actually b/c it's a dummy), we record
107 // this as a MatchComplete. This allows us to compare MatchComplete's
108 // across Prerender & Control group which ideally should be lining up.
109 // This ensures that there is no bias in terms of the page load times
110 // of the pages forming the difference between the two sets.
112 bool NeedMatchCompleteDummyForFinalStatus(FinalStatus final_status
) {
113 return final_status
!= FINAL_STATUS_USED
&&
114 final_status
!= FINAL_STATUS_TIMED_OUT
&&
115 final_status
!= FINAL_STATUS_MANAGER_SHUTDOWN
&&
116 final_status
!= FINAL_STATUS_PROFILE_DESTROYED
&&
117 final_status
!= FINAL_STATUS_APP_TERMINATING
&&
118 final_status
!= FINAL_STATUS_WINDOW_OPENER
&&
119 final_status
!= FINAL_STATUS_CACHE_OR_HISTORY_CLEARED
&&
120 final_status
!= FINAL_STATUS_CANCELLED
&&
121 final_status
!= FINAL_STATUS_DEVTOOLS_ATTACHED
&&
122 final_status
!= FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING
&&
123 final_status
!= FINAL_STATUS_PAGE_BEING_CAPTURED
&&
124 final_status
!= FINAL_STATUS_NAVIGATION_UNCOMMITTED
&&
125 final_status
!= FINAL_STATUS_NON_EMPTY_BROWSING_INSTANCE
;
128 void CheckIfCookiesExistForDomainResultOnUIThread(
129 const net::CookieMonster::HasCookiesForETLDP1Callback
& callback
,
130 bool cookies_exist
) {
131 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
132 callback
.Run(cookies_exist
);
135 void CheckIfCookiesExistForDomainResultOnIOThread(
136 const net::CookieMonster::HasCookiesForETLDP1Callback
& callback
,
137 bool cookies_exist
) {
138 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
139 BrowserThread::PostTask(
142 base::Bind(&CheckIfCookiesExistForDomainResultOnUIThread
,
147 void CheckIfCookiesExistForDomainOnIOThread(
148 net::URLRequestContextGetter
* rq_context
,
149 const std::string
& domain_key
,
150 const net::CookieMonster::HasCookiesForETLDP1Callback
& callback
) {
151 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
152 net::CookieStore
* cookie_store
=
153 rq_context
->GetURLRequestContext()->cookie_store();
154 cookie_store
->GetCookieMonster()->HasCookiesForETLDP1Async(
156 base::Bind(&CheckIfCookiesExistForDomainResultOnIOThread
, callback
));
161 class PrerenderManager::OnCloseWebContentsDeleter
162 : public content::WebContentsDelegate
,
163 public base::SupportsWeakPtr
<
164 PrerenderManager::OnCloseWebContentsDeleter
> {
166 OnCloseWebContentsDeleter(PrerenderManager
* manager
,
170 suppressed_dialog_(false) {
171 tab_
->SetDelegate(this);
172 base::MessageLoop::current()->PostDelayedTask(FROM_HERE
,
173 base::Bind(&OnCloseWebContentsDeleter::ScheduleWebContentsForDeletion
,
175 base::TimeDelta::FromSeconds(kDeleteWithExtremePrejudiceSeconds
));
178 void CloseContents(WebContents
* source
) override
{
179 DCHECK_EQ(tab_
, source
);
180 ScheduleWebContentsForDeletion(false);
183 void SwappedOut(WebContents
* source
) override
{
184 DCHECK_EQ(tab_
, source
);
185 ScheduleWebContentsForDeletion(false);
188 bool ShouldSuppressDialogs(WebContents
* source
) override
{
189 // Use this as a proxy for getting statistics on how often we fail to honor
190 // the beforeunload event.
191 DCHECK_EQ(tab_
, source
);
192 suppressed_dialog_
= true;
197 static const int kDeleteWithExtremePrejudiceSeconds
= 3;
199 void ScheduleWebContentsForDeletion(bool timeout
) {
200 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterTimeout", timeout
);
201 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterSuppressedDialog",
203 tab_
->SetDelegate(NULL
);
204 manager_
->ScheduleDeleteOldWebContents(tab_
.release(), this);
205 // |this| is deleted at this point.
208 PrerenderManager
* manager_
;
209 scoped_ptr
<WebContents
> tab_
;
210 bool suppressed_dialog_
;
212 DISALLOW_COPY_AND_ASSIGN(OnCloseWebContentsDeleter
);
216 int PrerenderManager::prerenders_per_session_count_
= 0;
219 PrerenderManager::PrerenderManagerMode
PrerenderManager::mode_
=
220 PRERENDER_MODE_ENABLED
;
222 struct PrerenderManager::NavigationRecord
{
223 NavigationRecord(const GURL
& url
, base::TimeTicks time
)
229 base::TimeTicks time
;
232 PrerenderManager::PrerenderManager(Profile
* profile
,
233 PrerenderTracker
* prerender_tracker
)
235 prerender_tracker_(prerender_tracker
),
236 prerender_contents_factory_(PrerenderContents::CreateFactory()),
237 last_prerender_start_time_(GetCurrentTimeTicks() -
238 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs
)),
239 prerender_history_(new PrerenderHistory(kHistoryLength
)),
240 histograms_(new PrerenderHistograms()),
241 profile_network_bytes_(0),
242 last_recorded_profile_network_bytes_(0),
243 cookie_store_loaded_(false) {
244 // There are some assumptions that the PrerenderManager is on the UI thread.
245 // Any other checks simply make sure that the PrerenderManager is accessed on
246 // the same thread that it was created on.
247 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
249 if (IsLocalPredictorEnabled())
250 local_predictor_
.reset(new PrerenderLocalPredictor(this));
252 if (IsLoggedInPredictorEnabled() && !profile_
->IsOffTheRecord()) {
253 predictors::PredictorDatabase
* predictor_db
=
254 predictors::PredictorDatabaseFactory::GetForProfile(profile
);
256 logged_in_predictor_table_
= predictor_db
->logged_in_table();
257 scoped_ptr
<LoggedInStateMap
> new_state_map(new LoggedInStateMap
);
258 LoggedInStateMap
* new_state_map_ptr
= new_state_map
.get();
259 BrowserThread::PostTaskAndReply(
260 BrowserThread::DB
, FROM_HERE
,
261 base::Bind(&LoggedInPredictorTable::GetAllData
,
262 logged_in_predictor_table_
,
264 base::Bind(&PrerenderManager::LoggedInPredictorDataReceived
,
266 base::Passed(&new_state_map
)));
270 // Certain experiments override our default config_ values.
271 switch (PrerenderManager::GetMode()) {
272 case PrerenderManager::PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP
:
273 config_
.max_link_concurrency
= 4;
274 config_
.max_link_concurrency_per_launcher
= 2;
276 case PrerenderManager::PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP
:
277 config_
.time_to_live
= base::TimeDelta::FromMinutes(15);
283 notification_registrar_
.Add(
284 this, chrome::NOTIFICATION_COOKIE_CHANGED
,
285 content::NotificationService::AllBrowserContextsAndSources());
287 notification_registrar_
.Add(
288 this, chrome::NOTIFICATION_PROFILE_DESTROYED
,
289 content::Source
<Profile
>(profile_
));
291 MediaCaptureDevicesDispatcher::GetInstance()->AddObserver(this);
294 PrerenderManager::~PrerenderManager() {
295 MediaCaptureDevicesDispatcher::GetInstance()->RemoveObserver(this);
297 // The earlier call to KeyedService::Shutdown() should have
298 // emptied these vectors already.
299 DCHECK(active_prerenders_
.empty());
300 DCHECK(to_delete_prerenders_
.empty());
302 for (PrerenderProcessSet::const_iterator it
=
303 prerender_process_hosts_
.begin();
304 it
!= prerender_process_hosts_
.end();
306 (*it
)->RemoveObserver(this);
310 void PrerenderManager::Shutdown() {
311 DestroyAllContents(FINAL_STATUS_MANAGER_SHUTDOWN
);
312 on_close_web_contents_deleters_
.clear();
313 // Must happen before |profile_| is set to NULL as
314 // |local_predictor_| accesses it.
315 if (local_predictor_
)
316 local_predictor_
->Shutdown();
319 DCHECK(active_prerenders_
.empty());
322 PrerenderHandle
* PrerenderManager::AddPrerenderFromLinkRelPrerender(
326 const uint32 rel_types
,
327 const content::Referrer
& referrer
,
328 const gfx::Size
& size
) {
329 Origin origin
= rel_types
& PrerenderRelTypePrerender
?
330 ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN
:
331 ORIGIN_LINK_REL_NEXT
;
332 SessionStorageNamespace
* session_storage_namespace
= NULL
;
333 // Unit tests pass in a process_id == -1.
334 if (process_id
!= -1) {
335 RenderViewHost
* source_render_view_host
=
336 RenderViewHost::FromID(process_id
, route_id
);
337 if (!source_render_view_host
)
339 WebContents
* source_web_contents
=
340 WebContents::FromRenderViewHost(source_render_view_host
);
341 if (!source_web_contents
)
343 if (origin
== ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN
&&
344 source_web_contents
->GetURL().host() == url
.host()) {
345 origin
= ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN
;
347 // TODO(ajwong): This does not correctly handle storage for isolated apps.
348 session_storage_namespace
=
349 source_web_contents
->GetController()
350 .GetDefaultSessionStorageNamespace();
353 return AddPrerender(origin
, url
, referrer
, size
, session_storage_namespace
);
356 PrerenderHandle
* PrerenderManager::AddPrerenderFromOmnibox(
358 SessionStorageNamespace
* session_storage_namespace
,
359 const gfx::Size
& size
) {
360 if (!IsOmniboxEnabled(profile_
))
362 return AddPrerender(ORIGIN_OMNIBOX
, url
, content::Referrer(), size
,
363 session_storage_namespace
);
366 PrerenderHandle
* PrerenderManager::AddPrerenderFromLocalPredictor(
368 SessionStorageNamespace
* session_storage_namespace
,
369 const gfx::Size
& size
) {
370 return AddPrerender(ORIGIN_LOCAL_PREDICTOR
, url
, content::Referrer(),
371 size
, session_storage_namespace
);
374 PrerenderHandle
* PrerenderManager::AddPrerenderFromExternalRequest(
376 const content::Referrer
& referrer
,
377 SessionStorageNamespace
* session_storage_namespace
,
378 const gfx::Size
& size
) {
379 return AddPrerender(ORIGIN_EXTERNAL_REQUEST
, url
, referrer
, size
,
380 session_storage_namespace
);
383 PrerenderHandle
* PrerenderManager::AddPrerenderForInstant(
385 content::SessionStorageNamespace
* session_storage_namespace
,
386 const gfx::Size
& size
) {
387 DCHECK(chrome::ShouldPrefetchSearchResults());
388 return AddPrerender(ORIGIN_INSTANT
, url
, content::Referrer(), size
,
389 session_storage_namespace
);
392 void PrerenderManager::CancelAllPrerenders() {
393 DCHECK(CalledOnValidThread());
394 while (!active_prerenders_
.empty()) {
395 PrerenderContents
* prerender_contents
=
396 active_prerenders_
.front()->contents();
397 prerender_contents
->Destroy(FINAL_STATUS_CANCELLED
);
401 bool PrerenderManager::MaybeUsePrerenderedPage(const GURL
& url
,
402 chrome::NavigateParams
* params
) {
403 DCHECK(CalledOnValidThread());
405 content::WebContents
* web_contents
= params
->target_contents
;
406 DCHECK(!IsWebContentsPrerendering(web_contents
, NULL
));
408 // Don't prerender if the navigation involves some special parameters.
409 if (params
->uses_post
|| !params
->extra_headers
.empty())
413 to_delete_prerenders_
.clear();
415 // First, try to find prerender data with the correct session storage
417 // TODO(ajwong): This doesn't handle isolated apps correctly.
418 PrerenderData
* prerender_data
= FindPrerenderData(
420 web_contents
->GetController().GetDefaultSessionStorageNamespace());
423 DCHECK(prerender_data
->contents());
425 WebContents
* new_web_contents
= SwapInternal(
426 url
, web_contents
, prerender_data
,
427 params
->should_replace_current_entry
);
428 if (!new_web_contents
)
431 // Record the new target_contents for the callers.
432 params
->target_contents
= new_web_contents
;
436 WebContents
* PrerenderManager::SwapInternal(
438 WebContents
* web_contents
,
439 PrerenderData
* prerender_data
,
440 bool should_replace_current_entry
) {
441 DCHECK(CalledOnValidThread());
442 DCHECK(!IsWebContentsPrerendering(web_contents
, NULL
));
444 // Only swap if the target WebContents has a CoreTabHelper delegate to swap
445 // out of it. For a normal WebContents, this is if it is in a TabStripModel.
446 CoreTabHelper
* core_tab_helper
= CoreTabHelper::FromWebContents(web_contents
);
447 if (!core_tab_helper
|| !core_tab_helper
->delegate())
450 PrerenderTabHelper
* target_tab_helper
=
451 PrerenderTabHelper::FromWebContents(web_contents
);
452 if (!target_tab_helper
) {
457 if (IsNoSwapInExperiment(prerender_data
->contents()->experiment_id()))
460 if (WebContents
* new_web_contents
=
461 prerender_data
->contents()->prerender_contents()) {
462 if (web_contents
== new_web_contents
)
463 return NULL
; // Do not swap in to ourself.
465 // We cannot swap in if there is no last committed entry, because we would
466 // show a blank page under an existing entry from the current tab. Even if
467 // there is a pending entry, it may not commit.
468 // TODO(creis): If there is a pending navigation and no last committed
469 // entry, we might be able to transfer the network request instead.
470 if (!new_web_contents
->GetController().CanPruneAllButLastCommitted()) {
471 // Abort this prerender so it is not used later. http://crbug.com/292121
472 prerender_data
->contents()->Destroy(FINAL_STATUS_NAVIGATION_UNCOMMITTED
);
477 // Do not swap if the target WebContents is not the only WebContents in its
478 // current BrowsingInstance.
479 if (web_contents
->GetSiteInstance()->GetRelatedActiveContentsCount() != 1u) {
481 web_contents
->GetSiteInstance()->GetRelatedActiveContentsCount(), 1u);
482 prerender_data
->contents()->Destroy(
483 FINAL_STATUS_NON_EMPTY_BROWSING_INSTANCE
);
487 // Do not use the prerendered version if there is an opener object.
488 if (web_contents
->HasOpener()) {
489 prerender_data
->contents()->Destroy(FINAL_STATUS_WINDOW_OPENER
);
493 // Do not swap in the prerender if the current WebContents is being captured.
494 if (web_contents
->GetCapturerCount() > 0) {
495 prerender_data
->contents()->Destroy(FINAL_STATUS_PAGE_BEING_CAPTURED
);
499 // If we are just in the control group (which can be detected by noticing
500 // that prerendering hasn't even started yet), record that |web_contents| now
501 // would be showing a prerendered contents, but otherwise, don't do anything.
502 if (!prerender_data
->contents()->prerendering_has_started()) {
503 target_tab_helper
->WouldHavePrerenderedNextLoad(
504 prerender_data
->contents()->origin());
505 prerender_data
->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED
);
509 // Don't use prerendered pages if debugger is attached to the tab.
510 // See http://crbug.com/98541
511 if (content::DevToolsAgentHost::IsDebuggerAttached(web_contents
)) {
512 DestroyAndMarkMatchCompleteAsUsed(prerender_data
->contents(),
513 FINAL_STATUS_DEVTOOLS_ATTACHED
);
517 // If the prerendered page is in the middle of a cross-site navigation,
518 // don't swap it in because there isn't a good way to merge histories.
519 if (prerender_data
->contents()->IsCrossSiteNavigationPending()) {
520 DestroyAndMarkMatchCompleteAsUsed(
521 prerender_data
->contents(),
522 FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING
);
526 // For bookkeeping purposes, we need to mark this WebContents to
527 // reflect that it would have been prerendered.
528 if (GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP
) {
529 target_tab_helper
->WouldHavePrerenderedNextLoad(
530 prerender_data
->contents()->origin());
531 prerender_data
->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED
);
535 // At this point, we've determined that we will use the prerender.
536 content::RenderProcessHost
* process_host
=
537 prerender_data
->contents()->GetRenderViewHost()->GetProcess();
538 process_host
->RemoveObserver(this);
539 prerender_process_hosts_
.erase(process_host
);
540 BrowserThread::PostTask(
541 BrowserThread::IO
, FROM_HERE
,
542 base::Bind(&PrerenderTracker::RemovePrerenderCookieStoreOnIOThread
,
543 base::Unretained(prerender_tracker()), process_host
->GetID(),
545 if (!prerender_data
->contents()->load_start_time().is_null()) {
546 histograms_
->RecordTimeUntilUsed(
547 prerender_data
->contents()->origin(),
548 GetCurrentTimeTicks() - prerender_data
->contents()->load_start_time());
550 histograms_
->RecordAbandonTimeUntilUsed(
551 prerender_data
->contents()->origin(),
552 prerender_data
->abandon_time().is_null() ?
554 GetCurrentTimeTicks() - prerender_data
->abandon_time());
556 histograms_
->RecordPerSessionCount(prerender_data
->contents()->origin(),
557 ++prerenders_per_session_count_
);
558 histograms_
->RecordUsedPrerender(prerender_data
->contents()->origin());
560 ScopedVector
<PrerenderData
>::iterator to_erase
=
561 FindIteratorForPrerenderContents(prerender_data
->contents());
562 DCHECK(active_prerenders_
.end() != to_erase
);
563 DCHECK_EQ(prerender_data
, *to_erase
);
564 scoped_ptr
<PrerenderContents
>
565 prerender_contents(prerender_data
->ReleaseContents());
566 active_prerenders_
.erase(to_erase
);
568 // Mark prerender as used.
569 prerender_contents
->PrepareForUse();
571 WebContents
* new_web_contents
=
572 prerender_contents
->ReleasePrerenderContents();
573 WebContents
* old_web_contents
= web_contents
;
574 DCHECK(new_web_contents
);
575 DCHECK(old_web_contents
);
577 // Merge the browsing history.
578 new_web_contents
->GetController().CopyStateFromAndPrune(
579 &old_web_contents
->GetController(),
580 should_replace_current_entry
);
581 CoreTabHelper::FromWebContents(old_web_contents
)->delegate()->
582 SwapTabContents(old_web_contents
,
585 prerender_contents
->has_finished_loading());
586 prerender_contents
->CommitHistory(new_web_contents
);
588 // Update PPLT metrics:
589 // If the tab has finished loading, record a PPLT of 0.
590 // If the tab is still loading, reset its start time to the current time.
591 PrerenderTabHelper
* prerender_tab_helper
=
592 PrerenderTabHelper::FromWebContents(new_web_contents
);
593 DCHECK(prerender_tab_helper
!= NULL
);
594 prerender_tab_helper
->PrerenderSwappedIn();
596 if (old_web_contents
->NeedToFireBeforeUnload()) {
597 // Schedule the delete to occur after the tab has run its unload handlers.
598 // TODO(davidben): Honor the beforeunload event. http://crbug.com/304932
599 on_close_web_contents_deleters_
.push_back(
600 new OnCloseWebContentsDeleter(this, old_web_contents
));
601 old_web_contents
->DispatchBeforeUnload(false);
603 // No unload handler to run, so delete asap.
604 ScheduleDeleteOldWebContents(old_web_contents
, NULL
);
607 // TODO(cbentzel): Should prerender_contents move to the pending delete
608 // list, instead of deleting directly here?
609 AddToHistory(prerender_contents
.get());
610 RecordNavigation(url
);
611 return new_web_contents
;
614 void PrerenderManager::MoveEntryToPendingDelete(PrerenderContents
* entry
,
615 FinalStatus final_status
) {
616 DCHECK(CalledOnValidThread());
619 ScopedVector
<PrerenderData
>::iterator it
=
620 FindIteratorForPrerenderContents(entry
);
621 DCHECK(it
!= active_prerenders_
.end());
623 // If this PrerenderContents is being deleted due to a cancellation any time
624 // after the prerender has started then we need to create a dummy replacement
625 // for PPLT accounting purposes for the Match Complete group. This is the case
626 // if the cancellation is for any reason that would not occur in the control
628 if (entry
->prerendering_has_started() &&
629 entry
->match_complete_status() ==
630 PrerenderContents::MATCH_COMPLETE_DEFAULT
&&
631 NeedMatchCompleteDummyForFinalStatus(final_status
) &&
632 ActuallyPrerendering() &&
633 GetMode() == PRERENDER_MODE_EXPERIMENT_MATCH_COMPLETE_GROUP
) {
634 // TODO(tburkard): I'd like to DCHECK that we are actually prerendering.
635 // However, what if new conditions are added and
636 // NeedMatchCompleteDummyForFinalStatus is not being updated. Not sure
637 // what's the best thing to do here. For now, I will just check whether
638 // we are actually prerendering.
639 (*it
)->MakeIntoMatchCompleteReplacement();
641 to_delete_prerenders_
.push_back(*it
);
642 active_prerenders_
.weak_erase(it
);
645 // Destroy the old WebContents relatively promptly to reduce resource usage.
649 void PrerenderManager::RecordPageLoadTimeNotSwappedIn(
651 base::TimeDelta page_load_time
,
653 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
654 histograms_
->RecordPageLoadTimeNotSwappedIn(origin
, page_load_time
, url
);
657 void PrerenderManager::RecordPerceivedPageLoadTime(
659 NavigationType navigation_type
,
660 base::TimeDelta perceived_page_load_time
,
661 double fraction_plt_elapsed_at_swap_in
,
663 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
667 histograms_
->RecordPerceivedPageLoadTime(
668 origin
, perceived_page_load_time
, navigation_type
, url
);
670 if (navigation_type
== NAVIGATION_TYPE_PRERENDERED
) {
671 histograms_
->RecordPercentLoadDoneAtSwapin(
672 origin
, fraction_plt_elapsed_at_swap_in
);
674 if (local_predictor_
) {
675 local_predictor_
->OnPLTEventForURL(url
, perceived_page_load_time
);
680 PrerenderManager::PrerenderManagerMode
PrerenderManager::GetMode() {
685 void PrerenderManager::SetMode(PrerenderManagerMode mode
) {
690 const char* PrerenderManager::GetModeString() {
692 case PRERENDER_MODE_DISABLED
:
694 case PRERENDER_MODE_ENABLED
:
695 case PRERENDER_MODE_EXPERIMENT_PRERENDER_GROUP
:
697 case PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP
:
699 case PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP
:
701 case PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP
:
703 case PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP
:
705 case PRERENDER_MODE_EXPERIMENT_MATCH_COMPLETE_GROUP
:
706 return "_MatchComplete";
707 case PRERENDER_MODE_MAX
:
709 NOTREACHED() << "Invalid PrerenderManager mode.";
716 bool PrerenderManager::IsPrerenderingPossible() {
717 return GetMode() != PRERENDER_MODE_DISABLED
;
721 bool PrerenderManager::ActuallyPrerendering() {
722 return IsPrerenderingPossible() && !IsControlGroup(kNoExperiment
);
726 bool PrerenderManager::IsControlGroup(uint8 experiment_id
) {
727 return GetMode() == PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP
||
728 IsControlGroupExperiment(experiment_id
);
732 bool PrerenderManager::IsNoUseGroup() {
733 return GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP
;
736 bool PrerenderManager::IsWebContentsPrerendering(
737 const WebContents
* web_contents
,
738 Origin
* origin
) const {
739 DCHECK(CalledOnValidThread());
740 if (PrerenderContents
* prerender_contents
=
741 GetPrerenderContents(web_contents
)) {
743 *origin
= prerender_contents
->origin();
749 bool PrerenderManager::HasPrerenderedUrl(
751 content::WebContents
* web_contents
) const {
752 content::SessionStorageNamespace
* session_storage_namespace
= web_contents
->
753 GetController().GetDefaultSessionStorageNamespace();
755 for (ScopedVector
<PrerenderData
>::const_iterator it
=
756 active_prerenders_
.begin();
757 it
!= active_prerenders_
.end(); ++it
) {
758 PrerenderContents
* prerender_contents
= (*it
)->contents();
759 if (prerender_contents
->Matches(url
, session_storage_namespace
)) {
766 PrerenderContents
* PrerenderManager::GetPrerenderContents(
767 const content::WebContents
* web_contents
) const {
768 DCHECK(CalledOnValidThread());
769 for (ScopedVector
<PrerenderData
>::const_iterator it
=
770 active_prerenders_
.begin();
771 it
!= active_prerenders_
.end(); ++it
) {
772 WebContents
* prerender_web_contents
=
773 (*it
)->contents()->prerender_contents();
774 if (prerender_web_contents
== web_contents
) {
775 return (*it
)->contents();
779 // Also check the pending-deletion list. If the prerender is in pending
780 // delete, anyone with a handle on the WebContents needs to know.
781 for (ScopedVector
<PrerenderData
>::const_iterator it
=
782 to_delete_prerenders_
.begin();
783 it
!= to_delete_prerenders_
.end(); ++it
) {
784 WebContents
* prerender_web_contents
=
785 (*it
)->contents()->prerender_contents();
786 if (prerender_web_contents
== web_contents
) {
787 return (*it
)->contents();
793 PrerenderContents
* PrerenderManager::GetPrerenderContentsForRoute(
795 int route_id
) const {
796 content::WebContents
* web_contents
=
797 tab_util::GetWebContentsByID(child_id
, route_id
);
798 if (web_contents
== NULL
)
800 return GetPrerenderContents(web_contents
);
803 const std::vector
<WebContents
*>
804 PrerenderManager::GetAllPrerenderingContents() const {
805 DCHECK(CalledOnValidThread());
806 std::vector
<WebContents
*> result
;
808 for (ScopedVector
<PrerenderData
>::const_iterator it
=
809 active_prerenders_
.begin();
810 it
!= active_prerenders_
.end(); ++it
) {
811 if (WebContents
* contents
= (*it
)->contents()->prerender_contents())
812 result
.push_back(contents
);
818 bool PrerenderManager::HasRecentlyBeenNavigatedTo(Origin origin
,
820 DCHECK(CalledOnValidThread());
822 CleanUpOldNavigations();
823 std::list
<NavigationRecord
>::const_reverse_iterator end
= navigations_
.rend();
824 for (std::list
<NavigationRecord
>::const_reverse_iterator it
=
825 navigations_
.rbegin();
828 if (it
->url
== url
) {
829 base::TimeDelta delta
= GetCurrentTimeTicks() - it
->time
;
830 histograms_
->RecordTimeSinceLastRecentVisit(origin
, delta
);
839 bool PrerenderManager::IsValidHttpMethod(const std::string
& method
) {
840 // method has been canonicalized to upper case at this point so we can just
842 DCHECK_EQ(method
, StringToUpperASCII(method
));
843 for (size_t i
= 0; i
< arraysize(kValidHttpMethods
); ++i
) {
844 if (method
.compare(kValidHttpMethods
[i
]) == 0)
852 bool PrerenderManager::DoesURLHaveValidScheme(const GURL
& url
) {
853 return (url
.SchemeIsHTTPOrHTTPS() ||
854 url
.SchemeIs(extensions::kExtensionScheme
) ||
855 url
.SchemeIs("data"));
859 bool PrerenderManager::DoesSubresourceURLHaveValidScheme(const GURL
& url
) {
860 return DoesURLHaveValidScheme(url
) || url
== GURL(url::kAboutBlankURL
);
863 base::DictionaryValue
* PrerenderManager::GetAsValue() const {
864 DCHECK(CalledOnValidThread());
865 base::DictionaryValue
* dict_value
= new base::DictionaryValue();
866 dict_value
->Set("history", prerender_history_
->GetEntriesAsValue());
867 dict_value
->Set("active", GetActivePrerendersAsValue());
868 dict_value
->SetBoolean("enabled", IsEnabled());
869 dict_value
->SetBoolean("omnibox_enabled", IsOmniboxEnabled(profile_
));
870 // If prerender is disabled via a flag this method is not even called.
871 std::string enabled_note
;
872 if (IsControlGroup(kNoExperiment
))
873 enabled_note
+= "(Control group: Not actually prerendering) ";
875 enabled_note
+= "(No-use group: Not swapping in prerendered pages) ";
876 if (GetMode() == PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP
)
878 "(15 min TTL group: Extended prerender eviction to 15 mins) ";
879 dict_value
->SetString("enabled_note", enabled_note
);
883 void PrerenderManager::ClearData(int clear_flags
) {
884 DCHECK_GE(clear_flags
, 0);
885 DCHECK_LT(clear_flags
, CLEAR_MAX
);
886 if (clear_flags
& CLEAR_PRERENDER_CONTENTS
)
887 DestroyAllContents(FINAL_STATUS_CACHE_OR_HISTORY_CLEARED
);
888 // This has to be second, since destroying prerenders can add to the history.
889 if (clear_flags
& CLEAR_PRERENDER_HISTORY
)
890 prerender_history_
->Clear();
893 void PrerenderManager::RecordFinalStatusWithMatchCompleteStatus(
896 PrerenderContents::MatchCompleteStatus mc_status
,
897 FinalStatus final_status
) const {
898 histograms_
->RecordFinalStatus(origin
,
904 void PrerenderManager::RecordNavigation(const GURL
& url
) {
905 DCHECK(CalledOnValidThread());
907 navigations_
.push_back(NavigationRecord(url
, GetCurrentTimeTicks()));
908 CleanUpOldNavigations();
912 struct PrerenderManager::PrerenderData::OrderByExpiryTime
{
913 bool operator()(const PrerenderData
* a
, const PrerenderData
* b
) const {
914 return a
->expiry_time() < b
->expiry_time();
918 PrerenderManager::PrerenderData::PrerenderData(PrerenderManager
* manager
,
919 PrerenderContents
* contents
,
920 base::TimeTicks expiry_time
)
924 expiry_time_(expiry_time
) {
925 DCHECK_NE(static_cast<PrerenderContents
*>(NULL
), contents_
);
928 PrerenderManager::PrerenderData::~PrerenderData() {
931 void PrerenderManager::PrerenderData::MakeIntoMatchCompleteReplacement() {
933 contents_
->set_match_complete_status(
934 PrerenderContents::MATCH_COMPLETE_REPLACED
);
935 PrerenderData
* to_delete
= new PrerenderData(manager_
, contents_
.release(),
937 contents_
.reset(to_delete
->contents_
->CreateMatchCompleteReplacement());
938 manager_
->to_delete_prerenders_
.push_back(to_delete
);
941 void PrerenderManager::PrerenderData::OnHandleCreated(PrerenderHandle
* handle
) {
942 DCHECK_NE(static_cast<PrerenderContents
*>(NULL
), contents_
);
944 contents_
->AddObserver(handle
);
947 void PrerenderManager::PrerenderData::OnHandleNavigatedAway(
948 PrerenderHandle
* handle
) {
949 DCHECK_LT(0, handle_count_
);
950 DCHECK_NE(static_cast<PrerenderContents
*>(NULL
), contents_
);
951 if (abandon_time_
.is_null())
952 abandon_time_
= base::TimeTicks::Now();
953 // We intentionally don't decrement the handle count here, so that the
954 // prerender won't be canceled until it times out.
955 manager_
->SourceNavigatedAway(this);
958 void PrerenderManager::PrerenderData::OnHandleCanceled(
959 PrerenderHandle
* handle
) {
960 DCHECK_LT(0, handle_count_
);
961 DCHECK_NE(static_cast<PrerenderContents
*>(NULL
), contents_
);
963 if (--handle_count_
== 0) {
964 // This will eventually remove this object from active_prerenders_.
965 contents_
->Destroy(FINAL_STATUS_CANCELLED
);
969 PrerenderContents
* PrerenderManager::PrerenderData::ReleaseContents() {
970 return contents_
.release();
973 void PrerenderManager::SetPrerenderContentsFactory(
974 PrerenderContents::Factory
* prerender_contents_factory
) {
975 DCHECK(CalledOnValidThread());
976 prerender_contents_factory_
.reset(prerender_contents_factory
);
979 void PrerenderManager::SourceNavigatedAway(PrerenderData
* prerender_data
) {
980 // The expiry time of our prerender data will likely change because of
981 // this navigation. This requires a resort of active_prerenders_.
982 ScopedVector
<PrerenderData
>::iterator it
=
983 std::find(active_prerenders_
.begin(), active_prerenders_
.end(),
985 if (it
== active_prerenders_
.end())
988 (*it
)->set_expiry_time(
989 std::min((*it
)->expiry_time(),
990 GetExpiryTimeForNavigatedAwayPrerender()));
991 SortActivePrerenders();
994 net::URLRequestContextGetter
* PrerenderManager::GetURLRequestContext() {
995 return content::BrowserContext::GetDefaultStoragePartition(profile_
)->
996 GetURLRequestContext();
1001 PrerenderHandle
* PrerenderManager::AddPrerender(
1003 const GURL
& url_arg
,
1004 const content::Referrer
& referrer
,
1005 const gfx::Size
& size
,
1006 SessionStorageNamespace
* session_storage_namespace
) {
1007 DCHECK(CalledOnValidThread());
1012 if ((origin
== ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN
||
1013 origin
== ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN
) &&
1014 IsGoogleSearchResultURL(referrer
.url
)) {
1015 origin
= ORIGIN_GWS_PRERENDER
;
1020 uint8 experiment
= GetQueryStringBasedExperiment(url_arg
);
1021 if (IsControlGroup(experiment
) &&
1022 MaybeGetQueryStringBasedAliasURL(url
, &alias_url
)) {
1026 // From here on, we will record a FinalStatus so we need to register with the
1027 // histogram tracking.
1028 histograms_
->RecordPrerender(origin
, url_arg
);
1030 if (PrerenderData
* preexisting_prerender_data
=
1031 FindPrerenderData(url
, session_storage_namespace
)) {
1032 RecordFinalStatusWithoutCreatingPrerenderContents(
1033 url
, origin
, experiment
, FINAL_STATUS_DUPLICATE
);
1034 return new PrerenderHandle(preexisting_prerender_data
);
1037 // Do not prerender if there are too many render processes, and we would
1038 // have to use an existing one. We do not want prerendering to happen in
1039 // a shared process, so that we can always reliably lower the CPU
1040 // priority for prerendering.
1041 // In single-process mode, ShouldTryToUseExistingProcessHost() always returns
1042 // true, so that case needs to be explicitly checked for.
1043 // TODO(tburkard): Figure out how to cancel prerendering in the opposite
1044 // case, when a new tab is added to a process used for prerendering.
1045 // TODO(ppi): Check whether there are usually enough render processes
1046 // available on Android. If not, kill an existing renderers so that we can
1047 // create a new one.
1048 if (content::RenderProcessHost::ShouldTryToUseExistingProcessHost(
1050 !content::RenderProcessHost::run_renderer_in_process()) {
1051 RecordFinalStatusWithoutCreatingPrerenderContents(
1052 url
, origin
, experiment
, FINAL_STATUS_TOO_MANY_PROCESSES
);
1056 // Check if enough time has passed since the last prerender.
1057 if (!DoesRateLimitAllowPrerender(origin
)) {
1058 // Cancel the prerender. We could add it to the pending prerender list but
1059 // this doesn't make sense as the next prerender request will be triggered
1060 // by a navigation and is unlikely to be the same site.
1061 RecordFinalStatusWithoutCreatingPrerenderContents(
1062 url
, origin
, experiment
, FINAL_STATUS_RATE_LIMIT_EXCEEDED
);
1066 if (IsPrerenderCookieStoreEnabled() && !cookie_store_loaded()) {
1067 // Only prerender if the cookie store for this profile has been loaded.
1068 // This is required by PrerenderCookieMonster.
1069 RecordFinalStatusWithoutCreatingPrerenderContents(
1070 url
, origin
, experiment
, FINAL_STATUS_COOKIE_STORE_NOT_LOADED
);
1074 PrerenderContents
* prerender_contents
= CreatePrerenderContents(
1075 url
, referrer
, origin
, experiment
);
1076 DCHECK(prerender_contents
);
1077 active_prerenders_
.push_back(
1078 new PrerenderData(this, prerender_contents
,
1079 GetExpiryTimeForNewPrerender(origin
)));
1080 if (!prerender_contents
->Init()) {
1081 DCHECK(active_prerenders_
.end() ==
1082 FindIteratorForPrerenderContents(prerender_contents
));
1086 histograms_
->RecordPrerenderStarted(origin
);
1087 DCHECK(!prerender_contents
->prerendering_has_started());
1089 PrerenderHandle
* prerender_handle
=
1090 new PrerenderHandle(active_prerenders_
.back());
1091 SortActivePrerenders();
1093 last_prerender_start_time_
= GetCurrentTimeTicks();
1095 gfx::Size contents_size
=
1096 size
.IsEmpty() ? config_
.default_tab_bounds
.size() : size
;
1098 net::URLRequestContextGetter
* request_context
=
1099 (IsPrerenderCookieStoreEnabled() ? GetURLRequestContext() : NULL
);
1101 prerender_contents
->StartPrerendering(contents_size
,
1102 session_storage_namespace
,
1105 DCHECK(IsControlGroup(experiment
) ||
1106 prerender_contents
->prerendering_has_started() ||
1107 (origin
== ORIGIN_LOCAL_PREDICTOR
&&
1108 IsLocalPredictorPrerenderAlwaysControlEnabled()));
1110 if (GetMode() == PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP
)
1111 histograms_
->RecordConcurrency(active_prerenders_
.size());
1113 // Query the history to see if the URL being prerendered has ever been
1115 HistoryService
* history_service
= HistoryServiceFactory::GetForProfile(
1116 profile_
, ServiceAccessType::EXPLICIT_ACCESS
);
1117 if (history_service
) {
1118 history_service
->QueryURL(
1121 base::Bind(&PrerenderManager::OnHistoryServiceDidQueryURL
,
1122 base::Unretained(this),
1125 &query_url_tracker_
);
1128 StartSchedulingPeriodicCleanups();
1129 return prerender_handle
;
1132 void PrerenderManager::StartSchedulingPeriodicCleanups() {
1133 DCHECK(CalledOnValidThread());
1134 if (repeating_timer_
.IsRunning())
1136 repeating_timer_
.Start(FROM_HERE
,
1137 base::TimeDelta::FromMilliseconds(kPeriodicCleanupIntervalMs
),
1139 &PrerenderManager::PeriodicCleanup
);
1142 void PrerenderManager::StopSchedulingPeriodicCleanups() {
1143 DCHECK(CalledOnValidThread());
1144 repeating_timer_
.Stop();
1147 void PrerenderManager::PeriodicCleanup() {
1148 DCHECK(CalledOnValidThread());
1150 base::ElapsedTimer resource_timer
;
1152 // Grab a copy of the current PrerenderContents pointers, so that we
1153 // will not interfere with potential deletions of the list.
1154 std::vector
<PrerenderContents
*>
1155 prerender_contents(active_prerenders_
.size());
1156 std::transform(active_prerenders_
.begin(), active_prerenders_
.end(),
1157 prerender_contents
.begin(),
1158 std::mem_fun(&PrerenderData::contents
));
1160 // And now check for prerenders using too much memory.
1161 std::for_each(prerender_contents
.begin(), prerender_contents
.end(),
1163 &PrerenderContents::DestroyWhenUsingTooManyResources
));
1165 // Measure how long the resource checks took. http://crbug.com/305419.
1166 UMA_HISTOGRAM_TIMES("Prerender.PeriodicCleanupResourceCheckTime",
1167 resource_timer
.Elapsed());
1169 base::ElapsedTimer cleanup_timer
;
1171 // Perform deferred cleanup work.
1172 DeleteOldWebContents();
1174 if (active_prerenders_
.empty())
1175 StopSchedulingPeriodicCleanups();
1177 to_delete_prerenders_
.clear();
1179 // Measure how long a the various cleanup tasks took. http://crbug.com/305419.
1180 UMA_HISTOGRAM_TIMES("Prerender.PeriodicCleanupDeleteContentsTime",
1181 cleanup_timer
.Elapsed());
1184 void PrerenderManager::PostCleanupTask() {
1185 DCHECK(CalledOnValidThread());
1186 base::MessageLoop::current()->PostTask(
1188 base::Bind(&PrerenderManager::PeriodicCleanup
, AsWeakPtr()));
1191 base::TimeTicks
PrerenderManager::GetExpiryTimeForNewPrerender(
1192 Origin origin
) const {
1193 base::TimeDelta ttl
= config_
.time_to_live
;
1194 if (origin
== ORIGIN_LOCAL_PREDICTOR
)
1195 ttl
= base::TimeDelta::FromSeconds(GetLocalPredictorTTLSeconds());
1196 return GetCurrentTimeTicks() + ttl
;
1199 base::TimeTicks
PrerenderManager::GetExpiryTimeForNavigatedAwayPrerender()
1201 return GetCurrentTimeTicks() + config_
.abandon_time_to_live
;
1204 void PrerenderManager::DeleteOldEntries() {
1205 DCHECK(CalledOnValidThread());
1206 while (!active_prerenders_
.empty()) {
1207 PrerenderData
* prerender_data
= active_prerenders_
.front();
1208 DCHECK(prerender_data
);
1209 DCHECK(prerender_data
->contents());
1211 if (prerender_data
->expiry_time() > GetCurrentTimeTicks())
1213 prerender_data
->contents()->Destroy(FINAL_STATUS_TIMED_OUT
);
1217 base::Time
PrerenderManager::GetCurrentTime() const {
1218 return base::Time::Now();
1221 base::TimeTicks
PrerenderManager::GetCurrentTimeTicks() const {
1222 return base::TimeTicks::Now();
1225 PrerenderContents
* PrerenderManager::CreatePrerenderContents(
1227 const content::Referrer
& referrer
,
1229 uint8 experiment_id
) {
1230 DCHECK(CalledOnValidThread());
1231 return prerender_contents_factory_
->CreatePrerenderContents(
1232 this, profile_
, url
, referrer
, origin
, experiment_id
);
1235 void PrerenderManager::SortActivePrerenders() {
1236 std::sort(active_prerenders_
.begin(), active_prerenders_
.end(),
1237 PrerenderData::OrderByExpiryTime());
1240 PrerenderManager::PrerenderData
* PrerenderManager::FindPrerenderData(
1242 const SessionStorageNamespace
* session_storage_namespace
) {
1243 for (ScopedVector
<PrerenderData
>::iterator it
= active_prerenders_
.begin();
1244 it
!= active_prerenders_
.end(); ++it
) {
1245 if ((*it
)->contents()->Matches(url
, session_storage_namespace
))
1251 ScopedVector
<PrerenderManager::PrerenderData
>::iterator
1252 PrerenderManager::FindIteratorForPrerenderContents(
1253 PrerenderContents
* prerender_contents
) {
1254 for (ScopedVector
<PrerenderData
>::iterator it
= active_prerenders_
.begin();
1255 it
!= active_prerenders_
.end(); ++it
) {
1256 if (prerender_contents
== (*it
)->contents())
1259 return active_prerenders_
.end();
1262 bool PrerenderManager::DoesRateLimitAllowPrerender(Origin origin
) const {
1263 DCHECK(CalledOnValidThread());
1264 base::TimeDelta elapsed_time
=
1265 GetCurrentTimeTicks() - last_prerender_start_time_
;
1266 histograms_
->RecordTimeBetweenPrerenderRequests(origin
, elapsed_time
);
1267 if (!config_
.rate_limit_enabled
)
1269 // The LocalPredictor may issue multiple prerenders simultaneously (if so
1270 // configured), so no throttling.
1271 if (origin
== ORIGIN_LOCAL_PREDICTOR
)
1273 return elapsed_time
>=
1274 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs
);
1277 void PrerenderManager::DeleteOldWebContents() {
1278 while (!old_web_contents_list_
.empty()) {
1279 WebContents
* web_contents
= old_web_contents_list_
.front();
1280 old_web_contents_list_
.pop_front();
1281 // TODO(dominich): should we use Instant Unload Handler here?
1282 delete web_contents
;
1286 void PrerenderManager::CleanUpOldNavigations() {
1287 DCHECK(CalledOnValidThread());
1289 // Cutoff. Navigations before this cutoff can be discarded.
1290 base::TimeTicks cutoff
= GetCurrentTimeTicks() -
1291 base::TimeDelta::FromMilliseconds(kNavigationRecordWindowMs
);
1292 while (!navigations_
.empty()) {
1293 if (navigations_
.front().time
> cutoff
)
1295 navigations_
.pop_front();
1299 void PrerenderManager::ScheduleDeleteOldWebContents(
1301 OnCloseWebContentsDeleter
* deleter
) {
1302 old_web_contents_list_
.push_back(tab
);
1306 ScopedVector
<OnCloseWebContentsDeleter
>::iterator i
= std::find(
1307 on_close_web_contents_deleters_
.begin(),
1308 on_close_web_contents_deleters_
.end(),
1310 DCHECK(i
!= on_close_web_contents_deleters_
.end());
1311 on_close_web_contents_deleters_
.erase(i
);
1315 void PrerenderManager::AddToHistory(PrerenderContents
* contents
) {
1316 PrerenderHistory::Entry
entry(contents
->prerender_url(),
1317 contents
->final_status(),
1320 prerender_history_
->AddEntry(entry
);
1323 base::Value
* PrerenderManager::GetActivePrerendersAsValue() const {
1324 base::ListValue
* list_value
= new base::ListValue();
1325 for (ScopedVector
<PrerenderData
>::const_iterator it
=
1326 active_prerenders_
.begin();
1327 it
!= active_prerenders_
.end(); ++it
) {
1328 if (base::Value
* prerender_value
= (*it
)->contents()->GetAsValue())
1329 list_value
->Append(prerender_value
);
1334 void PrerenderManager::DestroyAllContents(FinalStatus final_status
) {
1335 DeleteOldWebContents();
1336 while (!active_prerenders_
.empty()) {
1337 PrerenderContents
* contents
= active_prerenders_
.front()->contents();
1338 contents
->Destroy(final_status
);
1340 to_delete_prerenders_
.clear();
1343 void PrerenderManager::DestroyAndMarkMatchCompleteAsUsed(
1344 PrerenderContents
* prerender_contents
,
1345 FinalStatus final_status
) {
1346 prerender_contents
->set_match_complete_status(
1347 PrerenderContents::MATCH_COMPLETE_REPLACED
);
1348 histograms_
->RecordFinalStatus(prerender_contents
->origin(),
1349 prerender_contents
->experiment_id(),
1350 PrerenderContents::MATCH_COMPLETE_REPLACEMENT
,
1351 FINAL_STATUS_WOULD_HAVE_BEEN_USED
);
1352 prerender_contents
->Destroy(final_status
);
1355 void PrerenderManager::RecordFinalStatusWithoutCreatingPrerenderContents(
1356 const GURL
& url
, Origin origin
, uint8 experiment_id
,
1357 FinalStatus final_status
) const {
1358 PrerenderHistory::Entry
entry(url
, final_status
, origin
, base::Time::Now());
1359 prerender_history_
->AddEntry(entry
);
1360 RecordFinalStatusWithMatchCompleteStatus(
1361 origin
, experiment_id
,
1362 PrerenderContents::MATCH_COMPLETE_DEFAULT
,
1366 void PrerenderManager::Observe(int type
,
1367 const content::NotificationSource
& source
,
1368 const content::NotificationDetails
& details
) {
1370 case chrome::NOTIFICATION_COOKIE_CHANGED
: {
1371 Profile
* profile
= content::Source
<Profile
>(source
).ptr();
1372 if (!profile
|| !profile_
->IsSameProfile(profile
) ||
1373 profile
->IsOffTheRecord()) {
1376 CookieChanged(content::Details
<ChromeCookieDetails
>(details
).ptr());
1379 case chrome::NOTIFICATION_PROFILE_DESTROYED
:
1380 DestroyAllContents(FINAL_STATUS_PROFILE_DESTROYED
);
1381 on_close_web_contents_deleters_
.clear();
1384 NOTREACHED() << "Unexpected notification sent.";
1389 void PrerenderManager::OnCreatingAudioStream(int render_process_id
,
1390 int render_frame_id
) {
1391 content::RenderFrameHost
* render_frame_host
=
1392 content::RenderFrameHost::FromID(render_process_id
, render_frame_id
);
1393 WebContents
* tab
= WebContents::FromRenderFrameHost(render_frame_host
);
1397 PrerenderContents
* prerender_contents
= GetPrerenderContents(tab
);
1398 if (!prerender_contents
)
1401 prerender_contents
->Destroy(prerender::FINAL_STATUS_CREATING_AUDIO_STREAM
);
1404 void PrerenderManager::RecordLikelyLoginOnURL(const GURL
& url
) {
1405 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1406 if (!url
.SchemeIsHTTPOrHTTPS())
1408 if (logged_in_predictor_table_
.get()) {
1409 BrowserThread::PostTask(
1412 base::Bind(&LoggedInPredictorTable::AddDomainFromURL
,
1413 logged_in_predictor_table_
,
1416 std::string key
= LoggedInPredictorTable::GetKey(url
);
1417 if (!logged_in_state_
.get())
1419 if (logged_in_state_
->count(key
))
1421 (*logged_in_state_
)[key
] = base::Time::Now().ToInternalValue();
1424 void PrerenderManager::CheckIfLikelyLoggedInOnURL(
1426 bool* lookup_result
,
1427 bool* database_was_present
,
1428 const base::Closure
& result_cb
) {
1429 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1430 if (!logged_in_predictor_table_
.get()) {
1431 *database_was_present
= false;
1432 *lookup_result
= false;
1433 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
, result_cb
);
1436 BrowserThread::PostTaskAndReply(
1437 BrowserThread::DB
, FROM_HERE
,
1438 base::Bind(&LoggedInPredictorTable::HasUserLoggedIn
,
1439 logged_in_predictor_table_
,
1442 database_was_present
),
1447 void PrerenderManager::CookieChanged(ChromeCookieDetails
* details
) {
1448 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1450 if (!logged_in_predictor_table_
.get())
1453 // We only care when a cookie has been removed.
1454 if (!details
->removed
)
1457 std::string domain_key
=
1458 LoggedInPredictorTable::GetKeyFromDomain(details
->cookie
->Domain());
1460 // If we have no record of this domain as a potentially logged in domain,
1461 // nothing to do here.
1462 if (logged_in_state_
.get() && logged_in_state_
->count(domain_key
) < 1)
1465 net::URLRequestContextGetter
* rq_context
= profile_
->GetRequestContext();
1469 BrowserThread::PostTask(
1470 BrowserThread::IO
, FROM_HERE
,
1471 base::Bind(&CheckIfCookiesExistForDomainOnIOThread
,
1472 base::Unretained(rq_context
),
1475 &PrerenderManager::CookieChangedAnyCookiesLeftLookupResult
,
1481 void PrerenderManager::CookieChangedAnyCookiesLeftLookupResult(
1482 const std::string
& domain_key
,
1483 bool cookies_exist
) {
1484 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1489 if (logged_in_predictor_table_
.get()) {
1490 BrowserThread::PostTask(BrowserThread::DB
,
1492 base::Bind(&LoggedInPredictorTable::DeleteDomain
,
1493 logged_in_predictor_table_
,
1497 if (logged_in_state_
.get())
1498 logged_in_state_
->erase(domain_key
);
1501 void PrerenderManager::LoggedInPredictorDataReceived(
1502 scoped_ptr
<LoggedInStateMap
> new_map
) {
1503 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1504 logged_in_state_
.swap(new_map
);
1508 void PrerenderManager::RecordCookieEvent(int process_id
,
1511 const GURL
& frame_url
,
1512 bool is_for_blocking_resource
,
1513 PrerenderContents::CookieEvent event
,
1514 const net::CookieList
* cookie_list
) {
1515 RenderFrameHost
* rfh
= RenderFrameHost::FromID(process_id
, frame_id
);
1516 WebContents
* web_contents
= WebContents::FromRenderFrameHost(rfh
);
1520 bool is_main_frame
= (rfh
== web_contents
->GetMainFrame());
1522 bool is_third_party_cookie
=
1523 (!frame_url
.is_empty() &&
1524 !net::registry_controlled_domains::SameDomainOrHost(
1526 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES
));
1528 PrerenderContents
* prerender_contents
=
1529 PrerenderContents::FromWebContents(web_contents
);
1531 if (!prerender_contents
)
1534 base::Time earliest_create_date
;
1535 if (event
== PrerenderContents::COOKIE_EVENT_SEND
) {
1536 if (!cookie_list
|| cookie_list
->empty())
1538 for (size_t i
= 0; i
< cookie_list
->size(); i
++) {
1539 if (earliest_create_date
.is_null() ||
1540 (*cookie_list
)[i
].CreationDate() < earliest_create_date
) {
1541 earliest_create_date
= (*cookie_list
)[i
].CreationDate();
1546 prerender_contents
->RecordCookieEvent(event
,
1547 is_main_frame
&& url
== frame_url
,
1548 is_third_party_cookie
,
1549 is_for_blocking_resource
,
1550 earliest_create_date
);
1553 void PrerenderManager::RecordCookieStatus(Origin origin
,
1554 uint8 experiment_id
,
1555 int cookie_status
) const {
1556 histograms_
->RecordCookieStatus(origin
, experiment_id
, cookie_status
);
1559 void PrerenderManager::RecordCookieSendType(Origin origin
,
1560 uint8 experiment_id
,
1561 int cookie_send_type
) const {
1562 histograms_
->RecordCookieSendType(origin
, experiment_id
, cookie_send_type
);
1565 void PrerenderManager::OnHistoryServiceDidQueryURL(
1567 uint8 experiment_id
,
1569 const history::URLRow
& url_row
,
1570 const history::VisitVector
& /*visits*/) {
1571 histograms_
->RecordPrerenderPageVisitedStatus(origin
, experiment_id
, success
);
1574 void PrerenderManager::RecordNetworkBytes(Origin origin
,
1576 int64 prerender_bytes
) {
1577 if (!ActuallyPrerendering())
1579 int64 recent_profile_bytes
=
1580 profile_network_bytes_
- last_recorded_profile_network_bytes_
;
1581 last_recorded_profile_network_bytes_
= profile_network_bytes_
;
1582 DCHECK_GE(recent_profile_bytes
, 0);
1583 histograms_
->RecordNetworkBytes(
1584 origin
, used
, prerender_bytes
, recent_profile_bytes
);
1587 bool PrerenderManager::IsEnabled() const {
1588 DCHECK(CalledOnValidThread());
1590 return chrome_browser_net::CanPrefetchAndPrerenderUI(profile_
->GetPrefs());
1593 void PrerenderManager::AddProfileNetworkBytesIfEnabled(int64 bytes
) {
1594 DCHECK_GE(bytes
, 0);
1595 if (IsEnabled() && ActuallyPrerendering())
1596 profile_network_bytes_
+= bytes
;
1599 void PrerenderManager::OnCookieStoreLoaded() {
1600 cookie_store_loaded_
= true;
1601 if (!on_cookie_store_loaded_cb_for_testing_
.is_null())
1602 on_cookie_store_loaded_cb_for_testing_
.Run();
1605 void PrerenderManager::AddPrerenderProcessHost(
1606 content::RenderProcessHost
* process_host
) {
1607 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1608 DCHECK(prerender_process_hosts_
.find(process_host
) ==
1609 prerender_process_hosts_
.end());
1610 prerender_process_hosts_
.insert(process_host
);
1611 process_host
->AddObserver(this);
1614 bool PrerenderManager::MayReuseProcessHost(
1615 content::RenderProcessHost
* process_host
) {
1616 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1617 // If prerender cookie stores are disabled, there is no need to require
1618 // isolated prerender processes.
1619 if (!IsPrerenderCookieStoreEnabled())
1621 return (prerender_process_hosts_
.find(process_host
) ==
1622 prerender_process_hosts_
.end());
1625 void PrerenderManager::RenderProcessHostDestroyed(
1626 content::RenderProcessHost
* host
) {
1627 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1628 size_t erased
= prerender_process_hosts_
.erase(host
);
1629 DCHECK_EQ(1u, erased
);
1630 BrowserThread::PostTask(
1631 BrowserThread::IO
, FROM_HERE
,
1632 base::Bind(&PrerenderTracker::RemovePrerenderCookieStoreOnIOThread
,
1633 base::Unretained(prerender_tracker()), host
->GetID(), false));
1636 } // namespace prerender