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 // Timeout, in ms, for a session storage namespace merge.
96 const int kSessionStorageNamespaceMergeTimeoutMs = 500;
98 // If true, all session storage merges hang indefinitely.
99 bool g_hang_session_storage_merges_for_testing = false;
101 // Indicates whether a Prerender has been cancelled such that we need
102 // a dummy replacement for the purpose of recording the correct PPLT for
103 // the Match Complete case.
104 // Traditionally, "Match" means that a prerendered page was actually visited &
105 // the prerender was used. Our goal is to have "Match" cases line up in the
106 // control group & the experiment group, so that we can make meaningful
107 // comparisons of improvements. However, in the control group, since we don't
108 // actually perform prerenders, many of the cancellation reasons cannot be
109 // detected. Therefore, in the Prerender group, when we cancel for one of these
110 // reasons, we keep track of a dummy Prerender representing what we would
111 // have in the control group. If that dummy prerender in the prerender group
112 // would then be swapped in (but isn't actually b/c it's a dummy), we record
113 // this as a MatchComplete. This allows us to compare MatchComplete's
114 // across Prerender & Control group which ideally should be lining up.
115 // This ensures that there is no bias in terms of the page load times
116 // of the pages forming the difference between the two sets.
118 bool NeedMatchCompleteDummyForFinalStatus(FinalStatus final_status) {
119 return final_status != FINAL_STATUS_USED &&
120 final_status != FINAL_STATUS_TIMED_OUT &&
121 final_status != FINAL_STATUS_MANAGER_SHUTDOWN &&
122 final_status != FINAL_STATUS_PROFILE_DESTROYED &&
123 final_status != FINAL_STATUS_APP_TERMINATING &&
124 final_status != FINAL_STATUS_WINDOW_OPENER &&
125 final_status != FINAL_STATUS_CACHE_OR_HISTORY_CLEARED &&
126 final_status != FINAL_STATUS_CANCELLED &&
127 final_status != FINAL_STATUS_DEVTOOLS_ATTACHED &&
128 final_status != FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING &&
129 final_status != FINAL_STATUS_PAGE_BEING_CAPTURED &&
130 final_status != FINAL_STATUS_NAVIGATION_UNCOMMITTED &&
131 final_status != FINAL_STATUS_NON_EMPTY_BROWSING_INSTANCE;
134 void CheckIfCookiesExistForDomainResultOnUIThread(
135 const net::CookieMonster::HasCookiesForETLDP1Callback& callback,
136 bool cookies_exist) {
137 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
138 callback.Run(cookies_exist);
141 void CheckIfCookiesExistForDomainResultOnIOThread(
142 const net::CookieMonster::HasCookiesForETLDP1Callback& callback,
143 bool cookies_exist) {
144 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
145 BrowserThread::PostTask(
148 base::Bind(&CheckIfCookiesExistForDomainResultOnUIThread,
153 void CheckIfCookiesExistForDomainOnIOThread(
154 net::URLRequestContextGetter* rq_context,
155 const std::string& domain_key,
156 const net::CookieMonster::HasCookiesForETLDP1Callback& callback) {
157 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
158 net::CookieStore* cookie_store =
159 rq_context->GetURLRequestContext()->cookie_store();
160 cookie_store->GetCookieMonster()->HasCookiesForETLDP1Async(
162 base::Bind(&CheckIfCookiesExistForDomainResultOnIOThread, callback));
167 class PrerenderManager::OnCloseWebContentsDeleter
168 : public content::WebContentsDelegate,
169 public base::SupportsWeakPtr<
170 PrerenderManager::OnCloseWebContentsDeleter> {
172 OnCloseWebContentsDeleter(PrerenderManager* manager,
176 suppressed_dialog_(false) {
177 tab_->SetDelegate(this);
178 base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
179 base::Bind(&OnCloseWebContentsDeleter::ScheduleWebContentsForDeletion,
181 base::TimeDelta::FromSeconds(kDeleteWithExtremePrejudiceSeconds));
184 void CloseContents(WebContents* source) override {
185 DCHECK_EQ(tab_, source);
186 ScheduleWebContentsForDeletion(false);
189 void SwappedOut(WebContents* source) override {
190 DCHECK_EQ(tab_, source);
191 ScheduleWebContentsForDeletion(false);
194 bool ShouldSuppressDialogs() override {
195 // Use this as a proxy for getting statistics on how often we fail to honor
196 // the beforeunload event.
197 suppressed_dialog_ = true;
202 static const int kDeleteWithExtremePrejudiceSeconds = 3;
204 void ScheduleWebContentsForDeletion(bool timeout) {
205 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterTimeout", timeout);
206 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterSuppressedDialog",
208 tab_->SetDelegate(NULL);
209 manager_->ScheduleDeleteOldWebContents(tab_.release(), this);
210 // |this| is deleted at this point.
213 PrerenderManager* manager_;
214 scoped_ptr<WebContents> tab_;
215 bool suppressed_dialog_;
217 DISALLOW_COPY_AND_ASSIGN(OnCloseWebContentsDeleter);
221 int PrerenderManager::prerenders_per_session_count_ = 0;
224 PrerenderManager::PrerenderManagerMode PrerenderManager::mode_ =
225 PRERENDER_MODE_ENABLED;
227 struct PrerenderManager::NavigationRecord {
228 NavigationRecord(const GURL& url, base::TimeTicks time)
234 base::TimeTicks time;
237 PrerenderManager::PrerenderManager(Profile* profile,
238 PrerenderTracker* prerender_tracker)
240 prerender_tracker_(prerender_tracker),
241 prerender_contents_factory_(PrerenderContents::CreateFactory()),
242 last_prerender_start_time_(GetCurrentTimeTicks() -
243 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs)),
244 prerender_history_(new PrerenderHistory(kHistoryLength)),
245 histograms_(new PrerenderHistograms()),
246 profile_network_bytes_(0),
247 last_recorded_profile_network_bytes_(0),
248 cookie_store_loaded_(false) {
249 // There are some assumptions that the PrerenderManager is on the UI thread.
250 // Any other checks simply make sure that the PrerenderManager is accessed on
251 // the same thread that it was created on.
252 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
254 if (IsLocalPredictorEnabled())
255 local_predictor_.reset(new PrerenderLocalPredictor(this));
257 if (IsLoggedInPredictorEnabled() && !profile_->IsOffTheRecord()) {
258 predictors::PredictorDatabase* predictor_db =
259 predictors::PredictorDatabaseFactory::GetForProfile(profile);
261 logged_in_predictor_table_ = predictor_db->logged_in_table();
262 scoped_ptr<LoggedInStateMap> new_state_map(new LoggedInStateMap);
263 LoggedInStateMap* new_state_map_ptr = new_state_map.get();
264 BrowserThread::PostTaskAndReply(
265 BrowserThread::DB, FROM_HERE,
266 base::Bind(&LoggedInPredictorTable::GetAllData,
267 logged_in_predictor_table_,
269 base::Bind(&PrerenderManager::LoggedInPredictorDataReceived,
271 base::Passed(&new_state_map)));
275 // Certain experiments override our default config_ values.
276 switch (PrerenderManager::GetMode()) {
277 case PrerenderManager::PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP:
278 config_.max_link_concurrency = 4;
279 config_.max_link_concurrency_per_launcher = 2;
281 case PrerenderManager::PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP:
282 config_.time_to_live = base::TimeDelta::FromMinutes(15);
288 notification_registrar_.Add(
289 this, chrome::NOTIFICATION_COOKIE_CHANGED,
290 content::NotificationService::AllBrowserContextsAndSources());
292 notification_registrar_.Add(
293 this, chrome::NOTIFICATION_PROFILE_DESTROYED,
294 content::Source<Profile>(profile_));
296 MediaCaptureDevicesDispatcher::GetInstance()->AddObserver(this);
299 PrerenderManager::~PrerenderManager() {
300 MediaCaptureDevicesDispatcher::GetInstance()->RemoveObserver(this);
302 // The earlier call to KeyedService::Shutdown() should have
303 // emptied these vectors already.
304 DCHECK(active_prerenders_.empty());
305 DCHECK(to_delete_prerenders_.empty());
307 for (PrerenderProcessSet::const_iterator it =
308 prerender_process_hosts_.begin();
309 it != prerender_process_hosts_.end();
311 (*it)->RemoveObserver(this);
315 void PrerenderManager::Shutdown() {
316 DestroyAllContents(FINAL_STATUS_MANAGER_SHUTDOWN);
317 on_close_web_contents_deleters_.clear();
318 // Must happen before |profile_| is set to NULL as
319 // |local_predictor_| accesses it.
320 if (local_predictor_)
321 local_predictor_->Shutdown();
324 DCHECK(active_prerenders_.empty());
327 PrerenderHandle* PrerenderManager::AddPrerenderFromLinkRelPrerender(
331 const uint32 rel_types,
332 const content::Referrer& referrer,
333 const gfx::Size& size) {
334 Origin origin = rel_types & PrerenderRelTypePrerender ?
335 ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN :
336 ORIGIN_LINK_REL_NEXT;
337 SessionStorageNamespace* session_storage_namespace = NULL;
338 // Unit tests pass in a process_id == -1.
339 if (process_id != -1) {
340 RenderViewHost* source_render_view_host =
341 RenderViewHost::FromID(process_id, route_id);
342 if (!source_render_view_host)
344 WebContents* source_web_contents =
345 WebContents::FromRenderViewHost(source_render_view_host);
346 if (!source_web_contents)
348 if (origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN &&
349 source_web_contents->GetURL().host() == url.host()) {
350 origin = ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN;
352 // TODO(ajwong): This does not correctly handle storage for isolated apps.
353 session_storage_namespace =
354 source_web_contents->GetController()
355 .GetDefaultSessionStorageNamespace();
358 return AddPrerender(origin, process_id, url, referrer, size,
359 session_storage_namespace);
362 PrerenderHandle* PrerenderManager::AddPrerenderFromOmnibox(
364 SessionStorageNamespace* session_storage_namespace,
365 const gfx::Size& size) {
366 if (!IsOmniboxEnabled(profile_))
368 return AddPrerender(ORIGIN_OMNIBOX, -1, url, content::Referrer(), size,
369 session_storage_namespace);
372 PrerenderHandle* PrerenderManager::AddPrerenderFromLocalPredictor(
374 SessionStorageNamespace* session_storage_namespace,
375 const gfx::Size& size) {
376 return AddPrerender(ORIGIN_LOCAL_PREDICTOR, -1, url, content::Referrer(),
377 size, session_storage_namespace);
380 PrerenderHandle* PrerenderManager::AddPrerenderFromExternalRequest(
382 const content::Referrer& referrer,
383 SessionStorageNamespace* session_storage_namespace,
384 const gfx::Size& size) {
385 return AddPrerender(ORIGIN_EXTERNAL_REQUEST, -1, url, referrer, size,
386 session_storage_namespace);
389 PrerenderHandle* PrerenderManager::AddPrerenderForInstant(
391 content::SessionStorageNamespace* session_storage_namespace,
392 const gfx::Size& size) {
393 DCHECK(chrome::ShouldPrefetchSearchResults());
394 return AddPrerender(ORIGIN_INSTANT, -1, url, content::Referrer(), size,
395 session_storage_namespace);
398 void PrerenderManager::CancelAllPrerenders() {
399 DCHECK(CalledOnValidThread());
400 while (!active_prerenders_.empty()) {
401 PrerenderContents* prerender_contents =
402 active_prerenders_.front()->contents();
403 prerender_contents->Destroy(FINAL_STATUS_CANCELLED);
407 bool PrerenderManager::MaybeUsePrerenderedPage(const GURL& url,
408 chrome::NavigateParams* params) {
409 DCHECK(CalledOnValidThread());
411 content::WebContents* web_contents = params->target_contents;
412 DCHECK(!IsWebContentsPrerendering(web_contents, NULL));
414 // Don't prerender if the navigation involves some special parameters.
415 if (params->uses_post || !params->extra_headers.empty())
419 to_delete_prerenders_.clear();
421 // First, try to find prerender data with the correct session storage
423 // TODO(ajwong): This doesn't handle isolated apps correctly.
424 PrerenderData* prerender_data = FindPrerenderData(
426 web_contents->GetController().GetDefaultSessionStorageNamespace());
428 // If this failed, we may still find a prerender for the same URL, but a
429 // different session storage namespace. If we do, we might have to perform
431 if (!prerender_data) {
432 prerender_data = FindPrerenderData(url, NULL);
434 RecordEvent(prerender_data->contents(),
435 PRERENDER_EVENT_SWAPIN_CANDIDATE_NAMESPACE_MATCHES);
440 RecordEvent(prerender_data->contents(), PRERENDER_EVENT_SWAPIN_CANDIDATE);
441 DCHECK(prerender_data->contents());
443 // If there is currently a merge pending for this prerender data, don't swap.
444 if (prerender_data->pending_swap())
447 // Abort any existing pending swap on the target contents.
448 PrerenderData* pending_swap =
449 FindPrerenderDataForTargetContents(web_contents);
451 pending_swap->ClearPendingSwap();
452 DCHECK(FindPrerenderDataForTargetContents(web_contents) == NULL);
455 RecordEvent(prerender_data->contents(),
456 PRERENDER_EVENT_SWAPIN_NO_MERGE_PENDING);
457 SessionStorageNamespace* target_namespace =
458 web_contents->GetController().GetDefaultSessionStorageNamespace();
459 SessionStorageNamespace* prerender_namespace =
460 prerender_data->contents()->GetSessionStorageNamespace();
461 // Only when actually prerendering is session storage namespace merging an
462 // issue. For the control group, it will be assumed that the merge succeeded.
463 if (prerender_namespace && prerender_namespace != target_namespace &&
464 !prerender_namespace->IsAliasOf(target_namespace)) {
465 if (!ShouldMergeSessionStorageNamespaces()) {
466 RecordEvent(prerender_data->contents(),
467 PRERENDER_EVENT_SWAPIN_MERGING_DISABLED);
470 RecordEvent(prerender_data->contents(),
471 PRERENDER_EVENT_SWAPIN_ISSUING_MERGE);
472 prerender_data->set_pending_swap(new PendingSwap(
473 this, web_contents, prerender_data, url,
474 params->should_replace_current_entry));
475 prerender_data->pending_swap()->BeginSwap();
476 // Although this returns false, creating a PendingSwap registers with
477 // PrerenderTracker to throttle MAIN_FRAME navigations while the swap is
482 // No need to merge; swap synchronously.
483 WebContents* new_web_contents = SwapInternal(
484 url, web_contents, prerender_data,
485 params->should_replace_current_entry);
486 if (!new_web_contents)
489 // Record the new target_contents for the callers.
490 params->target_contents = new_web_contents;
494 WebContents* PrerenderManager::SwapInternal(
496 WebContents* web_contents,
497 PrerenderData* prerender_data,
498 bool should_replace_current_entry) {
499 DCHECK(CalledOnValidThread());
500 DCHECK(!IsWebContentsPrerendering(web_contents, NULL));
502 // Only swap if the target WebContents has a CoreTabHelper delegate to swap
503 // out of it. For a normal WebContents, this is if it is in a TabStripModel.
504 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(web_contents);
505 if (!core_tab_helper || !core_tab_helper->delegate()) {
506 RecordEvent(prerender_data->contents(), PRERENDER_EVENT_SWAPIN_NO_DELEGATE);
510 PrerenderTabHelper* target_tab_helper =
511 PrerenderTabHelper::FromWebContents(web_contents);
512 if (!target_tab_helper) {
517 if (IsNoSwapInExperiment(prerender_data->contents()->experiment_id()))
520 if (WebContents* new_web_contents =
521 prerender_data->contents()->prerender_contents()) {
522 if (web_contents == new_web_contents)
523 return NULL; // Do not swap in to ourself.
525 // We cannot swap in if there is no last committed entry, because we would
526 // show a blank page under an existing entry from the current tab. Even if
527 // there is a pending entry, it may not commit.
528 // TODO(creis): If there is a pending navigation and no last committed
529 // entry, we might be able to transfer the network request instead.
530 if (!new_web_contents->GetController().CanPruneAllButLastCommitted()) {
531 // Abort this prerender so it is not used later. http://crbug.com/292121
532 prerender_data->contents()->Destroy(FINAL_STATUS_NAVIGATION_UNCOMMITTED);
537 // Do not swap if the target WebContents is not the only WebContents in its
538 // current BrowsingInstance.
539 if (web_contents->GetSiteInstance()->GetRelatedActiveContentsCount() != 1u) {
541 web_contents->GetSiteInstance()->GetRelatedActiveContentsCount(), 1u);
542 prerender_data->contents()->Destroy(
543 FINAL_STATUS_NON_EMPTY_BROWSING_INSTANCE);
547 // Do not use the prerendered version if there is an opener object.
548 if (web_contents->HasOpener()) {
549 prerender_data->contents()->Destroy(FINAL_STATUS_WINDOW_OPENER);
553 // Do not swap in the prerender if the current WebContents is being captured.
554 if (web_contents->GetCapturerCount() > 0) {
555 prerender_data->contents()->Destroy(FINAL_STATUS_PAGE_BEING_CAPTURED);
559 // If we are just in the control group (which can be detected by noticing
560 // that prerendering hasn't even started yet), record that |web_contents| now
561 // would be showing a prerendered contents, but otherwise, don't do anything.
562 if (!prerender_data->contents()->prerendering_has_started()) {
563 target_tab_helper->WouldHavePrerenderedNextLoad(
564 prerender_data->contents()->origin());
565 prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED);
569 // Don't use prerendered pages if debugger is attached to the tab.
570 // See http://crbug.com/98541
571 if (content::DevToolsAgentHost::IsDebuggerAttached(web_contents)) {
572 DestroyAndMarkMatchCompleteAsUsed(prerender_data->contents(),
573 FINAL_STATUS_DEVTOOLS_ATTACHED);
577 // If the prerendered page is in the middle of a cross-site navigation,
578 // don't swap it in because there isn't a good way to merge histories.
579 if (prerender_data->contents()->IsCrossSiteNavigationPending()) {
580 DestroyAndMarkMatchCompleteAsUsed(
581 prerender_data->contents(),
582 FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING);
586 // For bookkeeping purposes, we need to mark this WebContents to
587 // reflect that it would have been prerendered.
588 if (GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP) {
589 target_tab_helper->WouldHavePrerenderedNextLoad(
590 prerender_data->contents()->origin());
591 prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED);
595 // At this point, we've determined that we will use the prerender.
596 content::RenderProcessHost* process_host =
597 prerender_data->contents()->GetRenderViewHost()->GetProcess();
598 prerender_process_hosts_.erase(process_host);
599 BrowserThread::PostTask(
600 BrowserThread::IO, FROM_HERE,
601 base::Bind(&PrerenderTracker::RemovePrerenderCookieStoreOnIOThread,
602 base::Unretained(prerender_tracker()), process_host->GetID(),
604 if (!prerender_data->contents()->load_start_time().is_null()) {
605 histograms_->RecordTimeUntilUsed(
606 prerender_data->contents()->origin(),
607 GetCurrentTimeTicks() - prerender_data->contents()->load_start_time());
609 histograms_->RecordAbandonTimeUntilUsed(
610 prerender_data->contents()->origin(),
611 prerender_data->abandon_time().is_null() ?
613 GetCurrentTimeTicks() - prerender_data->abandon_time());
615 histograms_->RecordPerSessionCount(prerender_data->contents()->origin(),
616 ++prerenders_per_session_count_);
617 histograms_->RecordUsedPrerender(prerender_data->contents()->origin());
619 if (prerender_data->pending_swap())
620 prerender_data->pending_swap()->set_swap_successful(true);
621 ScopedVector<PrerenderData>::iterator to_erase =
622 FindIteratorForPrerenderContents(prerender_data->contents());
623 DCHECK(active_prerenders_.end() != to_erase);
624 DCHECK_EQ(prerender_data, *to_erase);
625 scoped_ptr<PrerenderContents>
626 prerender_contents(prerender_data->ReleaseContents());
627 active_prerenders_.erase(to_erase);
629 // Mark prerender as used.
630 prerender_contents->PrepareForUse();
632 WebContents* new_web_contents =
633 prerender_contents->ReleasePrerenderContents();
634 WebContents* old_web_contents = web_contents;
635 DCHECK(new_web_contents);
636 DCHECK(old_web_contents);
638 // Merge the browsing history.
639 new_web_contents->GetController().CopyStateFromAndPrune(
640 &old_web_contents->GetController(),
641 should_replace_current_entry);
642 CoreTabHelper::FromWebContents(old_web_contents)->delegate()->
643 SwapTabContents(old_web_contents,
646 prerender_contents->has_finished_loading());
647 prerender_contents->CommitHistory(new_web_contents);
649 // Update PPLT metrics:
650 // If the tab has finished loading, record a PPLT of 0.
651 // If the tab is still loading, reset its start time to the current time.
652 PrerenderTabHelper* prerender_tab_helper =
653 PrerenderTabHelper::FromWebContents(new_web_contents);
654 DCHECK(prerender_tab_helper != NULL);
655 prerender_tab_helper->PrerenderSwappedIn();
657 if (old_web_contents->NeedToFireBeforeUnload()) {
658 // Schedule the delete to occur after the tab has run its unload handlers.
659 // TODO(davidben): Honor the beforeunload event. http://crbug.com/304932
660 on_close_web_contents_deleters_.push_back(
661 new OnCloseWebContentsDeleter(this, old_web_contents));
662 old_web_contents->DispatchBeforeUnload(false);
664 // No unload handler to run, so delete asap.
665 ScheduleDeleteOldWebContents(old_web_contents, NULL);
668 // TODO(cbentzel): Should prerender_contents move to the pending delete
669 // list, instead of deleting directly here?
670 AddToHistory(prerender_contents.get());
671 RecordNavigation(url);
672 return new_web_contents;
675 void PrerenderManager::MoveEntryToPendingDelete(PrerenderContents* entry,
676 FinalStatus final_status) {
677 DCHECK(CalledOnValidThread());
680 ScopedVector<PrerenderData>::iterator it =
681 FindIteratorForPrerenderContents(entry);
682 DCHECK(it != active_prerenders_.end());
684 // If this PrerenderContents is being deleted due to a cancellation any time
685 // after the prerender has started then we need to create a dummy replacement
686 // for PPLT accounting purposes for the Match Complete group. This is the case
687 // if the cancellation is for any reason that would not occur in the control
689 if (entry->prerendering_has_started() &&
690 entry->match_complete_status() ==
691 PrerenderContents::MATCH_COMPLETE_DEFAULT &&
692 NeedMatchCompleteDummyForFinalStatus(final_status) &&
693 ActuallyPrerendering() &&
694 GetMode() == PRERENDER_MODE_EXPERIMENT_MATCH_COMPLETE_GROUP) {
695 // TODO(tburkard): I'd like to DCHECK that we are actually prerendering.
696 // However, what if new conditions are added and
697 // NeedMatchCompleteDummyForFinalStatus is not being updated. Not sure
698 // what's the best thing to do here. For now, I will just check whether
699 // we are actually prerendering.
700 (*it)->MakeIntoMatchCompleteReplacement();
702 to_delete_prerenders_.push_back(*it);
703 (*it)->ClearPendingSwap();
704 active_prerenders_.weak_erase(it);
707 // Destroy the old WebContents relatively promptly to reduce resource usage.
711 void PrerenderManager::RecordPageLoadTimeNotSwappedIn(
713 base::TimeDelta page_load_time,
715 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
716 histograms_->RecordPageLoadTimeNotSwappedIn(origin, page_load_time, url);
719 void PrerenderManager::RecordPerceivedPageLoadTime(
721 NavigationType navigation_type,
722 base::TimeDelta perceived_page_load_time,
723 double fraction_plt_elapsed_at_swap_in,
725 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
729 histograms_->RecordPerceivedPageLoadTime(
730 origin, perceived_page_load_time, navigation_type, url);
732 if (navigation_type == NAVIGATION_TYPE_PRERENDERED) {
733 histograms_->RecordPercentLoadDoneAtSwapin(
734 origin, fraction_plt_elapsed_at_swap_in);
736 if (local_predictor_) {
737 local_predictor_->OnPLTEventForURL(url, perceived_page_load_time);
742 PrerenderManager::PrerenderManagerMode PrerenderManager::GetMode() {
747 void PrerenderManager::SetMode(PrerenderManagerMode mode) {
752 const char* PrerenderManager::GetModeString() {
754 case PRERENDER_MODE_DISABLED:
756 case PRERENDER_MODE_ENABLED:
757 case PRERENDER_MODE_EXPERIMENT_PRERENDER_GROUP:
759 case PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP:
761 case PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP:
763 case PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP:
765 case PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP:
767 case PRERENDER_MODE_EXPERIMENT_MATCH_COMPLETE_GROUP:
768 return "_MatchComplete";
769 case PRERENDER_MODE_MAX:
771 NOTREACHED() << "Invalid PrerenderManager mode.";
778 bool PrerenderManager::IsPrerenderingPossible() {
779 return GetMode() != PRERENDER_MODE_DISABLED;
783 bool PrerenderManager::ActuallyPrerendering() {
784 return IsPrerenderingPossible() && !IsControlGroup(kNoExperiment);
788 bool PrerenderManager::IsControlGroup(uint8 experiment_id) {
789 return GetMode() == PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP ||
790 IsControlGroupExperiment(experiment_id);
794 bool PrerenderManager::IsNoUseGroup() {
795 return GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP;
798 bool PrerenderManager::IsWebContentsPrerendering(
799 const WebContents* web_contents,
800 Origin* origin) const {
801 DCHECK(CalledOnValidThread());
802 if (PrerenderContents* prerender_contents =
803 GetPrerenderContents(web_contents)) {
805 *origin = prerender_contents->origin();
811 bool PrerenderManager::HasPrerenderedUrl(
813 content::WebContents* web_contents) const {
814 content::SessionStorageNamespace* session_storage_namespace = web_contents->
815 GetController().GetDefaultSessionStorageNamespace();
817 for (ScopedVector<PrerenderData>::const_iterator it =
818 active_prerenders_.begin();
819 it != active_prerenders_.end(); ++it) {
820 PrerenderContents* prerender_contents = (*it)->contents();
821 if (prerender_contents->Matches(url, session_storage_namespace)) {
828 PrerenderContents* PrerenderManager::GetPrerenderContents(
829 const content::WebContents* web_contents) const {
830 DCHECK(CalledOnValidThread());
831 for (ScopedVector<PrerenderData>::const_iterator it =
832 active_prerenders_.begin();
833 it != active_prerenders_.end(); ++it) {
834 WebContents* prerender_web_contents =
835 (*it)->contents()->prerender_contents();
836 if (prerender_web_contents == web_contents) {
837 return (*it)->contents();
841 // Also check the pending-deletion list. If the prerender is in pending
842 // delete, anyone with a handle on the WebContents needs to know.
843 for (ScopedVector<PrerenderData>::const_iterator it =
844 to_delete_prerenders_.begin();
845 it != to_delete_prerenders_.end(); ++it) {
846 WebContents* prerender_web_contents =
847 (*it)->contents()->prerender_contents();
848 if (prerender_web_contents == web_contents) {
849 return (*it)->contents();
855 PrerenderContents* PrerenderManager::GetPrerenderContentsForRoute(
857 int route_id) const {
858 content::WebContents* web_contents =
859 tab_util::GetWebContentsByID(child_id, route_id);
860 if (web_contents == NULL)
862 return GetPrerenderContents(web_contents);
865 const std::vector<WebContents*>
866 PrerenderManager::GetAllPrerenderingContents() const {
867 DCHECK(CalledOnValidThread());
868 std::vector<WebContents*> result;
870 for (ScopedVector<PrerenderData>::const_iterator it =
871 active_prerenders_.begin();
872 it != active_prerenders_.end(); ++it) {
873 if (WebContents* contents = (*it)->contents()->prerender_contents())
874 result.push_back(contents);
880 bool PrerenderManager::HasRecentlyBeenNavigatedTo(Origin origin,
882 DCHECK(CalledOnValidThread());
884 CleanUpOldNavigations();
885 std::list<NavigationRecord>::const_reverse_iterator end = navigations_.rend();
886 for (std::list<NavigationRecord>::const_reverse_iterator it =
887 navigations_.rbegin();
890 if (it->url == url) {
891 base::TimeDelta delta = GetCurrentTimeTicks() - it->time;
892 histograms_->RecordTimeSinceLastRecentVisit(origin, delta);
901 bool PrerenderManager::IsValidHttpMethod(const std::string& method) {
902 // method has been canonicalized to upper case at this point so we can just
904 DCHECK_EQ(method, StringToUpperASCII(method));
905 for (size_t i = 0; i < arraysize(kValidHttpMethods); ++i) {
906 if (method.compare(kValidHttpMethods[i]) == 0)
914 bool PrerenderManager::DoesURLHaveValidScheme(const GURL& url) {
915 return (url.SchemeIsHTTPOrHTTPS() ||
916 url.SchemeIs(extensions::kExtensionScheme) ||
917 url.SchemeIs("data"));
921 bool PrerenderManager::DoesSubresourceURLHaveValidScheme(const GURL& url) {
922 return DoesURLHaveValidScheme(url) || url == GURL(url::kAboutBlankURL);
925 base::DictionaryValue* PrerenderManager::GetAsValue() const {
926 DCHECK(CalledOnValidThread());
927 base::DictionaryValue* dict_value = new base::DictionaryValue();
928 dict_value->Set("history", prerender_history_->GetEntriesAsValue());
929 dict_value->Set("active", GetActivePrerendersAsValue());
930 dict_value->SetBoolean("enabled", IsEnabled());
931 dict_value->SetBoolean("omnibox_enabled", IsOmniboxEnabled(profile_));
932 // If prerender is disabled via a flag this method is not even called.
933 std::string enabled_note;
934 if (IsControlGroup(kNoExperiment))
935 enabled_note += "(Control group: Not actually prerendering) ";
937 enabled_note += "(No-use group: Not swapping in prerendered pages) ";
938 if (GetMode() == PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP)
940 "(15 min TTL group: Extended prerender eviction to 15 mins) ";
941 dict_value->SetString("enabled_note", enabled_note);
945 void PrerenderManager::ClearData(int clear_flags) {
946 DCHECK_GE(clear_flags, 0);
947 DCHECK_LT(clear_flags, CLEAR_MAX);
948 if (clear_flags & CLEAR_PRERENDER_CONTENTS)
949 DestroyAllContents(FINAL_STATUS_CACHE_OR_HISTORY_CLEARED);
950 // This has to be second, since destroying prerenders can add to the history.
951 if (clear_flags & CLEAR_PRERENDER_HISTORY)
952 prerender_history_->Clear();
955 void PrerenderManager::RecordFinalStatusWithMatchCompleteStatus(
958 PrerenderContents::MatchCompleteStatus mc_status,
959 FinalStatus final_status) const {
960 histograms_->RecordFinalStatus(origin,
966 void PrerenderManager::RecordNavigation(const GURL& url) {
967 DCHECK(CalledOnValidThread());
969 navigations_.push_back(NavigationRecord(url, GetCurrentTimeTicks()));
970 CleanUpOldNavigations();
974 struct PrerenderManager::PrerenderData::OrderByExpiryTime {
975 bool operator()(const PrerenderData* a, const PrerenderData* b) const {
976 return a->expiry_time() < b->expiry_time();
980 PrerenderManager::PrerenderData::PrerenderData(PrerenderManager* manager,
981 PrerenderContents* contents,
982 base::TimeTicks expiry_time)
986 expiry_time_(expiry_time) {
987 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
990 PrerenderManager::PrerenderData::~PrerenderData() {
993 void PrerenderManager::PrerenderData::MakeIntoMatchCompleteReplacement() {
995 contents_->set_match_complete_status(
996 PrerenderContents::MATCH_COMPLETE_REPLACED);
997 PrerenderData* to_delete = new PrerenderData(manager_, contents_.release(),
999 contents_.reset(to_delete->contents_->CreateMatchCompleteReplacement());
1000 manager_->to_delete_prerenders_.push_back(to_delete);
1003 void PrerenderManager::PrerenderData::OnHandleCreated(PrerenderHandle* handle) {
1004 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
1006 contents_->AddObserver(handle);
1009 void PrerenderManager::PrerenderData::OnHandleNavigatedAway(
1010 PrerenderHandle* handle) {
1011 DCHECK_LT(0, handle_count_);
1012 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
1013 if (abandon_time_.is_null())
1014 abandon_time_ = base::TimeTicks::Now();
1015 // We intentionally don't decrement the handle count here, so that the
1016 // prerender won't be canceled until it times out.
1017 manager_->SourceNavigatedAway(this);
1020 void PrerenderManager::PrerenderData::OnHandleCanceled(
1021 PrerenderHandle* handle) {
1022 DCHECK_LT(0, handle_count_);
1023 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
1025 if (--handle_count_ == 0) {
1026 // This will eventually remove this object from active_prerenders_.
1027 contents_->Destroy(FINAL_STATUS_CANCELLED);
1031 void PrerenderManager::PrerenderData::ClearPendingSwap() {
1032 pending_swap_.reset(NULL);
1035 PrerenderContents* PrerenderManager::PrerenderData::ReleaseContents() {
1036 return contents_.release();
1039 PrerenderManager::PendingSwap::PendingSwap(
1040 PrerenderManager* manager,
1041 content::WebContents* target_contents,
1042 PrerenderData* prerender_data,
1044 bool should_replace_current_entry)
1045 : content::WebContentsObserver(target_contents),
1047 prerender_data_(prerender_data),
1049 should_replace_current_entry_(should_replace_current_entry),
1050 start_time_(base::TimeTicks::Now()),
1051 seen_target_route_id_(false),
1052 swap_successful_(false),
1053 weak_factory_(this) {
1056 PrerenderManager::PendingSwap::~PendingSwap() {
1057 manager_->prerender_tracker()->RemovePrerenderPendingSwap(
1058 target_route_id_, swap_successful_);
1061 void PrerenderManager::PendingSwap::BeginSwap() {
1062 if (g_hang_session_storage_merges_for_testing)
1065 SessionStorageNamespace* target_namespace =
1066 web_contents()->GetController().GetDefaultSessionStorageNamespace();
1067 SessionStorageNamespace* prerender_namespace =
1068 prerender_data_->contents()->GetSessionStorageNamespace();
1070 prerender_namespace->Merge(
1071 true, prerender_data_->contents()->child_id(),
1073 base::Bind(&PrerenderManager::PendingSwap::OnMergeCompleted,
1074 weak_factory_.GetWeakPtr()));
1076 merge_timeout_.Start(
1078 base::TimeDelta::FromMilliseconds(
1079 kSessionStorageNamespaceMergeTimeoutMs),
1080 this, &PrerenderManager::PendingSwap::OnMergeTimeout);
1083 void PrerenderManager::PendingSwap::AboutToNavigateRenderView(
1084 RenderViewHost* render_view_host) {
1085 if (seen_target_route_id_) {
1086 // A second navigation began browser-side.
1087 prerender_data_->ClearPendingSwap();
1091 seen_target_route_id_ = true;
1092 target_route_id_ = PrerenderTracker::ChildRouteIdPair(
1093 render_view_host->GetMainFrame()->GetProcess()->GetID(),
1094 render_view_host->GetMainFrame()->GetRoutingID());
1095 manager_->prerender_tracker()->AddPrerenderPendingSwap(
1096 target_route_id_, url_);
1099 void PrerenderManager::PendingSwap::DidStartProvisionalLoadForFrame(
1100 content::RenderFrameHost* render_frame_host,
1101 const GURL& validated_url,
1103 bool is_iframe_srcdoc) {
1104 if (render_frame_host->GetParent())
1107 // We must only cancel the pending swap if the url navigated to is not
1108 // the URL being attempted to be swapped in. That's because in the normal
1109 // flow, a ProvisionalChangeToMainFrameUrl will happen for the URL attempted
1110 // to be swapped in immediately after the pending swap has issued its merge.
1111 if (validated_url != url_)
1112 prerender_data_->ClearPendingSwap();
1115 void PrerenderManager::PendingSwap::DidCommitProvisionalLoadForFrame(
1116 content::RenderFrameHost* render_frame_host,
1117 const GURL& validated_url,
1118 ui::PageTransition transition_type) {
1119 if (render_frame_host->GetParent())
1121 prerender_data_->ClearPendingSwap();
1124 void PrerenderManager::PendingSwap::DidFailProvisionalLoad(
1125 content::RenderFrameHost* render_frame_host,
1126 const GURL& validated_url,
1128 const base::string16& error_description) {
1129 if (render_frame_host->GetParent())
1131 prerender_data_->ClearPendingSwap();
1134 void PrerenderManager::PendingSwap::WebContentsDestroyed() {
1135 prerender_data_->ClearPendingSwap();
1138 void PrerenderManager::PendingSwap::RecordEvent(PrerenderEvent event) const {
1139 manager_->RecordEvent(prerender_data_->contents(), event);
1142 void PrerenderManager::PendingSwap::OnMergeCompleted(
1143 SessionStorageNamespace::MergeResult result) {
1144 UMA_HISTOGRAM_TIMES("Prerender.SessionStorageNamespaceMergeTime",
1145 base::TimeTicks::Now() - start_time_);
1146 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_MERGE_DONE);
1148 // Log the exact merge result in a histogram.
1150 case SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_FOUND:
1151 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NAMESPACE_NOT_FOUND);
1153 case SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_ALIAS:
1154 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NAMESPACE_NOT_ALIAS);
1156 case SessionStorageNamespace::MERGE_RESULT_NOT_LOGGING:
1157 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NOT_LOGGING);
1159 case SessionStorageNamespace::MERGE_RESULT_NO_TRANSACTIONS:
1160 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NO_TRANSACTIONS);
1162 case SessionStorageNamespace::MERGE_RESULT_TOO_MANY_TRANSACTIONS:
1163 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_TOO_MANY_TRANSACTIONS);
1165 case SessionStorageNamespace::MERGE_RESULT_NOT_MERGEABLE:
1166 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NOT_MERGEABLE);
1168 case SessionStorageNamespace::MERGE_RESULT_MERGEABLE:
1169 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_MERGEABLE);
1175 if (result != SessionStorageNamespace::MERGE_RESULT_MERGEABLE &&
1176 result != SessionStorageNamespace::MERGE_RESULT_NO_TRANSACTIONS) {
1177 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_MERGE_FAILED);
1178 prerender_data_->ClearPendingSwap();
1182 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_SWAPPING_IN);
1184 // Note that SwapInternal will, on success, delete |prerender_data_| and
1185 // |this|. It will also delete |this| in some failure cases. Pass in a new
1186 // GURL object rather than a reference to |url_|. Also hold on to |manager_|
1187 // and |prerender_data_|.
1189 // TODO(davidben): Can we make this less fragile?
1190 PrerenderManager* manager = manager_;
1191 PrerenderData* prerender_data = prerender_data_;
1192 WebContents* new_web_contents =
1193 manager_->SwapInternal(GURL(url_),
1196 should_replace_current_entry_);
1197 if (!new_web_contents) {
1198 manager->RecordEvent(prerender_data->contents(),
1199 PRERENDER_EVENT_MERGE_RESULT_SWAPIN_FAILED);
1200 // Depending on whether SwapInternal called Destroy() or simply failed to
1201 // swap, |this| may or may not be deleted. Either way, if the swap failed,
1202 // |prerender_data| is deleted asynchronously, so this call is a no-op if
1203 // |this| is already gone.
1204 prerender_data->ClearPendingSwap();
1208 void PrerenderManager::PendingSwap::OnMergeTimeout() {
1209 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_TIMED_OUT);
1210 prerender_data_->ClearPendingSwap();
1213 void PrerenderManager::SetPrerenderContentsFactory(
1214 PrerenderContents::Factory* prerender_contents_factory) {
1215 DCHECK(CalledOnValidThread());
1216 prerender_contents_factory_.reset(prerender_contents_factory);
1219 void PrerenderManager::SourceNavigatedAway(PrerenderData* prerender_data) {
1220 // The expiry time of our prerender data will likely change because of
1221 // this navigation. This requires a resort of active_prerenders_.
1222 ScopedVector<PrerenderData>::iterator it =
1223 std::find(active_prerenders_.begin(), active_prerenders_.end(),
1225 if (it == active_prerenders_.end())
1228 (*it)->set_expiry_time(
1229 std::min((*it)->expiry_time(),
1230 GetExpiryTimeForNavigatedAwayPrerender()));
1231 SortActivePrerenders();
1234 net::URLRequestContextGetter* PrerenderManager::GetURLRequestContext() {
1235 return content::BrowserContext::GetDefaultStoragePartition(profile_)->
1236 GetURLRequestContext();
1241 PrerenderHandle* PrerenderManager::AddPrerender(
1244 const GURL& url_arg,
1245 const content::Referrer& referrer,
1246 const gfx::Size& size,
1247 SessionStorageNamespace* session_storage_namespace) {
1248 DCHECK(CalledOnValidThread());
1253 if ((origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN ||
1254 origin == ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN) &&
1255 IsGoogleSearchResultURL(referrer.url)) {
1256 origin = ORIGIN_GWS_PRERENDER;
1261 uint8 experiment = GetQueryStringBasedExperiment(url_arg);
1262 if (IsControlGroup(experiment) &&
1263 MaybeGetQueryStringBasedAliasURL(url, &alias_url)) {
1267 // From here on, we will record a FinalStatus so we need to register with the
1268 // histogram tracking.
1269 histograms_->RecordPrerender(origin, url_arg);
1271 if (PrerenderData* preexisting_prerender_data =
1272 FindPrerenderData(url, session_storage_namespace)) {
1273 RecordFinalStatusWithoutCreatingPrerenderContents(
1274 url, origin, experiment, FINAL_STATUS_DUPLICATE);
1275 return new PrerenderHandle(preexisting_prerender_data);
1278 // Do not prerender if there are too many render processes, and we would
1279 // have to use an existing one. We do not want prerendering to happen in
1280 // a shared process, so that we can always reliably lower the CPU
1281 // priority for prerendering.
1282 // In single-process mode, ShouldTryToUseExistingProcessHost() always returns
1283 // true, so that case needs to be explicitly checked for.
1284 // TODO(tburkard): Figure out how to cancel prerendering in the opposite
1285 // case, when a new tab is added to a process used for prerendering.
1286 // TODO(ppi): Check whether there are usually enough render processes
1287 // available on Android. If not, kill an existing renderers so that we can
1288 // create a new one.
1289 if (content::RenderProcessHost::ShouldTryToUseExistingProcessHost(
1291 !content::RenderProcessHost::run_renderer_in_process()) {
1292 RecordFinalStatusWithoutCreatingPrerenderContents(
1293 url, origin, experiment, FINAL_STATUS_TOO_MANY_PROCESSES);
1297 // Check if enough time has passed since the last prerender.
1298 if (!DoesRateLimitAllowPrerender(origin)) {
1299 // Cancel the prerender. We could add it to the pending prerender list but
1300 // this doesn't make sense as the next prerender request will be triggered
1301 // by a navigation and is unlikely to be the same site.
1302 RecordFinalStatusWithoutCreatingPrerenderContents(
1303 url, origin, experiment, FINAL_STATUS_RATE_LIMIT_EXCEEDED);
1307 if (IsPrerenderCookieStoreEnabled() && !cookie_store_loaded()) {
1308 // Only prerender if the cookie store for this profile has been loaded.
1309 // This is required by PrerenderCookieMonster.
1310 RecordFinalStatusWithoutCreatingPrerenderContents(
1311 url, origin, experiment, FINAL_STATUS_COOKIE_STORE_NOT_LOADED);
1315 PrerenderContents* prerender_contents = CreatePrerenderContents(
1316 url, referrer, origin, experiment);
1317 DCHECK(prerender_contents);
1318 active_prerenders_.push_back(
1319 new PrerenderData(this, prerender_contents,
1320 GetExpiryTimeForNewPrerender(origin)));
1321 if (!prerender_contents->Init()) {
1322 DCHECK(active_prerenders_.end() ==
1323 FindIteratorForPrerenderContents(prerender_contents));
1327 histograms_->RecordPrerenderStarted(origin);
1328 DCHECK(!prerender_contents->prerendering_has_started());
1330 PrerenderHandle* prerender_handle =
1331 new PrerenderHandle(active_prerenders_.back());
1332 SortActivePrerenders();
1334 last_prerender_start_time_ = GetCurrentTimeTicks();
1336 gfx::Size contents_size =
1337 size.IsEmpty() ? config_.default_tab_bounds.size() : size;
1339 net::URLRequestContextGetter* request_context =
1340 (IsPrerenderCookieStoreEnabled() ? GetURLRequestContext() : NULL);
1342 prerender_contents->StartPrerendering(process_id, contents_size,
1343 session_storage_namespace,
1346 DCHECK(IsControlGroup(experiment) ||
1347 prerender_contents->prerendering_has_started() ||
1348 (origin == ORIGIN_LOCAL_PREDICTOR &&
1349 IsLocalPredictorPrerenderAlwaysControlEnabled()));
1351 if (GetMode() == PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP)
1352 histograms_->RecordConcurrency(active_prerenders_.size());
1354 // Query the history to see if the URL being prerendered has ever been
1356 HistoryService* history_service = HistoryServiceFactory::GetForProfile(
1357 profile_, Profile::EXPLICIT_ACCESS);
1358 if (history_service) {
1359 history_service->QueryURL(
1362 base::Bind(&PrerenderManager::OnHistoryServiceDidQueryURL,
1363 base::Unretained(this),
1366 &query_url_tracker_);
1369 StartSchedulingPeriodicCleanups();
1370 return prerender_handle;
1373 void PrerenderManager::StartSchedulingPeriodicCleanups() {
1374 DCHECK(CalledOnValidThread());
1375 if (repeating_timer_.IsRunning())
1377 repeating_timer_.Start(FROM_HERE,
1378 base::TimeDelta::FromMilliseconds(kPeriodicCleanupIntervalMs),
1380 &PrerenderManager::PeriodicCleanup);
1383 void PrerenderManager::StopSchedulingPeriodicCleanups() {
1384 DCHECK(CalledOnValidThread());
1385 repeating_timer_.Stop();
1388 void PrerenderManager::PeriodicCleanup() {
1389 DCHECK(CalledOnValidThread());
1391 base::ElapsedTimer resource_timer;
1393 // Grab a copy of the current PrerenderContents pointers, so that we
1394 // will not interfere with potential deletions of the list.
1395 std::vector<PrerenderContents*>
1396 prerender_contents(active_prerenders_.size());
1397 std::transform(active_prerenders_.begin(), active_prerenders_.end(),
1398 prerender_contents.begin(),
1399 std::mem_fun(&PrerenderData::contents));
1401 // And now check for prerenders using too much memory.
1402 std::for_each(prerender_contents.begin(), prerender_contents.end(),
1404 &PrerenderContents::DestroyWhenUsingTooManyResources));
1406 // Measure how long the resource checks took. http://crbug.com/305419.
1407 UMA_HISTOGRAM_TIMES("Prerender.PeriodicCleanupResourceCheckTime",
1408 resource_timer.Elapsed());
1410 base::ElapsedTimer cleanup_timer;
1412 // Perform deferred cleanup work.
1413 DeleteOldWebContents();
1415 if (active_prerenders_.empty())
1416 StopSchedulingPeriodicCleanups();
1418 to_delete_prerenders_.clear();
1420 // Measure how long a the various cleanup tasks took. http://crbug.com/305419.
1421 UMA_HISTOGRAM_TIMES("Prerender.PeriodicCleanupDeleteContentsTime",
1422 cleanup_timer.Elapsed());
1425 void PrerenderManager::PostCleanupTask() {
1426 DCHECK(CalledOnValidThread());
1427 base::MessageLoop::current()->PostTask(
1429 base::Bind(&PrerenderManager::PeriodicCleanup, AsWeakPtr()));
1432 base::TimeTicks PrerenderManager::GetExpiryTimeForNewPrerender(
1433 Origin origin) const {
1434 base::TimeDelta ttl = config_.time_to_live;
1435 if (origin == ORIGIN_LOCAL_PREDICTOR)
1436 ttl = base::TimeDelta::FromSeconds(GetLocalPredictorTTLSeconds());
1437 return GetCurrentTimeTicks() + ttl;
1440 base::TimeTicks PrerenderManager::GetExpiryTimeForNavigatedAwayPrerender()
1442 return GetCurrentTimeTicks() + config_.abandon_time_to_live;
1445 void PrerenderManager::DeleteOldEntries() {
1446 DCHECK(CalledOnValidThread());
1447 while (!active_prerenders_.empty()) {
1448 PrerenderData* prerender_data = active_prerenders_.front();
1449 DCHECK(prerender_data);
1450 DCHECK(prerender_data->contents());
1452 if (prerender_data->expiry_time() > GetCurrentTimeTicks())
1454 prerender_data->contents()->Destroy(FINAL_STATUS_TIMED_OUT);
1458 base::Time PrerenderManager::GetCurrentTime() const {
1459 return base::Time::Now();
1462 base::TimeTicks PrerenderManager::GetCurrentTimeTicks() const {
1463 return base::TimeTicks::Now();
1466 PrerenderContents* PrerenderManager::CreatePrerenderContents(
1468 const content::Referrer& referrer,
1470 uint8 experiment_id) {
1471 DCHECK(CalledOnValidThread());
1472 return prerender_contents_factory_->CreatePrerenderContents(
1473 this, profile_, url, referrer, origin, experiment_id);
1476 void PrerenderManager::SortActivePrerenders() {
1477 std::sort(active_prerenders_.begin(), active_prerenders_.end(),
1478 PrerenderData::OrderByExpiryTime());
1481 PrerenderManager::PrerenderData* PrerenderManager::FindPrerenderData(
1483 const SessionStorageNamespace* session_storage_namespace) {
1484 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1485 it != active_prerenders_.end(); ++it) {
1486 if ((*it)->contents()->Matches(url, session_storage_namespace))
1492 PrerenderManager::PrerenderData*
1493 PrerenderManager::FindPrerenderDataForTargetContents(
1494 WebContents* target_contents) {
1495 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1496 it != active_prerenders_.end(); ++it) {
1497 if ((*it)->pending_swap() &&
1498 (*it)->pending_swap()->web_contents() == target_contents)
1504 ScopedVector<PrerenderManager::PrerenderData>::iterator
1505 PrerenderManager::FindIteratorForPrerenderContents(
1506 PrerenderContents* prerender_contents) {
1507 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1508 it != active_prerenders_.end(); ++it) {
1509 if (prerender_contents == (*it)->contents())
1512 return active_prerenders_.end();
1515 bool PrerenderManager::DoesRateLimitAllowPrerender(Origin origin) const {
1516 DCHECK(CalledOnValidThread());
1517 base::TimeDelta elapsed_time =
1518 GetCurrentTimeTicks() - last_prerender_start_time_;
1519 histograms_->RecordTimeBetweenPrerenderRequests(origin, elapsed_time);
1520 if (!config_.rate_limit_enabled)
1522 // The LocalPredictor may issue multiple prerenders simultaneously (if so
1523 // configured), so no throttling.
1524 if (origin == ORIGIN_LOCAL_PREDICTOR)
1526 return elapsed_time >=
1527 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs);
1530 void PrerenderManager::DeleteOldWebContents() {
1531 while (!old_web_contents_list_.empty()) {
1532 WebContents* web_contents = old_web_contents_list_.front();
1533 old_web_contents_list_.pop_front();
1534 // TODO(dominich): should we use Instant Unload Handler here?
1535 delete web_contents;
1539 void PrerenderManager::CleanUpOldNavigations() {
1540 DCHECK(CalledOnValidThread());
1542 // Cutoff. Navigations before this cutoff can be discarded.
1543 base::TimeTicks cutoff = GetCurrentTimeTicks() -
1544 base::TimeDelta::FromMilliseconds(kNavigationRecordWindowMs);
1545 while (!navigations_.empty()) {
1546 if (navigations_.front().time > cutoff)
1548 navigations_.pop_front();
1552 void PrerenderManager::ScheduleDeleteOldWebContents(
1554 OnCloseWebContentsDeleter* deleter) {
1555 old_web_contents_list_.push_back(tab);
1559 ScopedVector<OnCloseWebContentsDeleter>::iterator i = std::find(
1560 on_close_web_contents_deleters_.begin(),
1561 on_close_web_contents_deleters_.end(),
1563 DCHECK(i != on_close_web_contents_deleters_.end());
1564 on_close_web_contents_deleters_.erase(i);
1568 void PrerenderManager::AddToHistory(PrerenderContents* contents) {
1569 PrerenderHistory::Entry entry(contents->prerender_url(),
1570 contents->final_status(),
1573 prerender_history_->AddEntry(entry);
1576 base::Value* PrerenderManager::GetActivePrerendersAsValue() const {
1577 base::ListValue* list_value = new base::ListValue();
1578 for (ScopedVector<PrerenderData>::const_iterator it =
1579 active_prerenders_.begin();
1580 it != active_prerenders_.end(); ++it) {
1581 if (base::Value* prerender_value = (*it)->contents()->GetAsValue())
1582 list_value->Append(prerender_value);
1587 void PrerenderManager::DestroyAllContents(FinalStatus final_status) {
1588 DeleteOldWebContents();
1589 while (!active_prerenders_.empty()) {
1590 PrerenderContents* contents = active_prerenders_.front()->contents();
1591 contents->Destroy(final_status);
1593 to_delete_prerenders_.clear();
1596 void PrerenderManager::DestroyAndMarkMatchCompleteAsUsed(
1597 PrerenderContents* prerender_contents,
1598 FinalStatus final_status) {
1599 prerender_contents->set_match_complete_status(
1600 PrerenderContents::MATCH_COMPLETE_REPLACED);
1601 histograms_->RecordFinalStatus(prerender_contents->origin(),
1602 prerender_contents->experiment_id(),
1603 PrerenderContents::MATCH_COMPLETE_REPLACEMENT,
1604 FINAL_STATUS_WOULD_HAVE_BEEN_USED);
1605 prerender_contents->Destroy(final_status);
1608 void PrerenderManager::RecordFinalStatusWithoutCreatingPrerenderContents(
1609 const GURL& url, Origin origin, uint8 experiment_id,
1610 FinalStatus final_status) const {
1611 PrerenderHistory::Entry entry(url, final_status, origin, base::Time::Now());
1612 prerender_history_->AddEntry(entry);
1613 RecordFinalStatusWithMatchCompleteStatus(
1614 origin, experiment_id,
1615 PrerenderContents::MATCH_COMPLETE_DEFAULT,
1619 void PrerenderManager::Observe(int type,
1620 const content::NotificationSource& source,
1621 const content::NotificationDetails& details) {
1623 case chrome::NOTIFICATION_COOKIE_CHANGED: {
1624 Profile* profile = content::Source<Profile>(source).ptr();
1625 if (!profile || !profile_->IsSameProfile(profile) ||
1626 profile->IsOffTheRecord()) {
1629 CookieChanged(content::Details<ChromeCookieDetails>(details).ptr());
1632 case chrome::NOTIFICATION_PROFILE_DESTROYED:
1633 DestroyAllContents(FINAL_STATUS_PROFILE_DESTROYED);
1634 on_close_web_contents_deleters_.clear();
1637 NOTREACHED() << "Unexpected notification sent.";
1642 void PrerenderManager::OnCreatingAudioStream(int render_process_id,
1643 int render_frame_id) {
1644 content::RenderFrameHost* render_frame_host =
1645 content::RenderFrameHost::FromID(render_process_id, render_frame_id);
1646 WebContents* tab = WebContents::FromRenderFrameHost(render_frame_host);
1650 PrerenderContents* prerender_contents = GetPrerenderContents(tab);
1651 if (!prerender_contents)
1654 prerender_contents->Destroy(prerender::FINAL_STATUS_CREATING_AUDIO_STREAM);
1657 void PrerenderManager::RecordLikelyLoginOnURL(const GURL& url) {
1658 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1659 if (!url.SchemeIsHTTPOrHTTPS())
1661 if (logged_in_predictor_table_.get()) {
1662 BrowserThread::PostTask(
1665 base::Bind(&LoggedInPredictorTable::AddDomainFromURL,
1666 logged_in_predictor_table_,
1669 std::string key = LoggedInPredictorTable::GetKey(url);
1670 if (!logged_in_state_.get())
1672 if (logged_in_state_->count(key))
1674 (*logged_in_state_)[key] = base::Time::Now().ToInternalValue();
1677 void PrerenderManager::CheckIfLikelyLoggedInOnURL(
1679 bool* lookup_result,
1680 bool* database_was_present,
1681 const base::Closure& result_cb) {
1682 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1683 if (!logged_in_predictor_table_.get()) {
1684 *database_was_present = false;
1685 *lookup_result = false;
1686 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, result_cb);
1689 BrowserThread::PostTaskAndReply(
1690 BrowserThread::DB, FROM_HERE,
1691 base::Bind(&LoggedInPredictorTable::HasUserLoggedIn,
1692 logged_in_predictor_table_,
1695 database_was_present),
1700 void PrerenderManager::CookieChanged(ChromeCookieDetails* details) {
1701 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1703 if (!logged_in_predictor_table_.get())
1706 // We only care when a cookie has been removed.
1707 if (!details->removed)
1710 std::string domain_key =
1711 LoggedInPredictorTable::GetKeyFromDomain(details->cookie->Domain());
1713 // If we have no record of this domain as a potentially logged in domain,
1714 // nothing to do here.
1715 if (logged_in_state_.get() && logged_in_state_->count(domain_key) < 1)
1718 net::URLRequestContextGetter* rq_context = profile_->GetRequestContext();
1722 BrowserThread::PostTask(
1723 BrowserThread::IO, FROM_HERE,
1724 base::Bind(&CheckIfCookiesExistForDomainOnIOThread,
1725 base::Unretained(rq_context),
1728 &PrerenderManager::CookieChangedAnyCookiesLeftLookupResult,
1734 void PrerenderManager::CookieChangedAnyCookiesLeftLookupResult(
1735 const std::string& domain_key,
1736 bool cookies_exist) {
1737 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1742 if (logged_in_predictor_table_.get()) {
1743 BrowserThread::PostTask(BrowserThread::DB,
1745 base::Bind(&LoggedInPredictorTable::DeleteDomain,
1746 logged_in_predictor_table_,
1750 if (logged_in_state_.get())
1751 logged_in_state_->erase(domain_key);
1754 void PrerenderManager::LoggedInPredictorDataReceived(
1755 scoped_ptr<LoggedInStateMap> new_map) {
1756 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1757 logged_in_state_.swap(new_map);
1760 void PrerenderManager::RecordEvent(PrerenderContents* contents,
1761 PrerenderEvent event) const {
1763 histograms_->RecordEvent(ORIGIN_NONE, kNoExperiment, event);
1765 histograms_->RecordEvent(contents->origin(), contents->experiment_id(),
1770 void PrerenderManager::RecordCookieEvent(int process_id,
1773 const GURL& frame_url,
1774 bool is_for_blocking_resource,
1775 PrerenderContents::CookieEvent event,
1776 const net::CookieList* cookie_list) {
1777 RenderFrameHost* rfh = RenderFrameHost::FromID(process_id, frame_id);
1778 WebContents* web_contents = WebContents::FromRenderFrameHost(rfh);
1782 bool is_main_frame = (rfh == web_contents->GetMainFrame());
1784 bool is_third_party_cookie =
1785 (!frame_url.is_empty() &&
1786 !net::registry_controlled_domains::SameDomainOrHost(
1788 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES));
1790 PrerenderContents* prerender_contents =
1791 PrerenderContents::FromWebContents(web_contents);
1793 if (!prerender_contents)
1796 base::Time earliest_create_date;
1797 if (event == PrerenderContents::COOKIE_EVENT_SEND) {
1798 if (!cookie_list || cookie_list->empty())
1800 for (size_t i = 0; i < cookie_list->size(); i++) {
1801 if (earliest_create_date.is_null() ||
1802 (*cookie_list)[i].CreationDate() < earliest_create_date) {
1803 earliest_create_date = (*cookie_list)[i].CreationDate();
1808 prerender_contents->RecordCookieEvent(event,
1809 is_main_frame && url == frame_url,
1810 is_third_party_cookie,
1811 is_for_blocking_resource,
1812 earliest_create_date);
1815 void PrerenderManager::RecordCookieStatus(Origin origin,
1816 uint8 experiment_id,
1817 int cookie_status) const {
1818 histograms_->RecordCookieStatus(origin, experiment_id, cookie_status);
1821 void PrerenderManager::RecordCookieSendType(Origin origin,
1822 uint8 experiment_id,
1823 int cookie_send_type) const {
1824 histograms_->RecordCookieSendType(origin, experiment_id, cookie_send_type);
1827 void PrerenderManager::OnHistoryServiceDidQueryURL(
1829 uint8 experiment_id,
1831 const history::URLRow& url_row,
1832 const history::VisitVector& /*visits*/) {
1833 histograms_->RecordPrerenderPageVisitedStatus(origin, experiment_id, success);
1837 void PrerenderManager::HangSessionStorageMergesForTesting() {
1838 g_hang_session_storage_merges_for_testing = true;
1841 void PrerenderManager::RecordNetworkBytes(Origin origin,
1843 int64 prerender_bytes) {
1844 if (!ActuallyPrerendering())
1846 int64 recent_profile_bytes =
1847 profile_network_bytes_ - last_recorded_profile_network_bytes_;
1848 last_recorded_profile_network_bytes_ = profile_network_bytes_;
1849 DCHECK_GE(recent_profile_bytes, 0);
1850 histograms_->RecordNetworkBytes(
1851 origin, used, prerender_bytes, recent_profile_bytes);
1854 bool PrerenderManager::IsEnabled() const {
1855 DCHECK(CalledOnValidThread());
1857 return chrome_browser_net::CanPrefetchAndPrerenderUI(profile_->GetPrefs());
1860 void PrerenderManager::AddProfileNetworkBytesIfEnabled(int64 bytes) {
1861 DCHECK_GE(bytes, 0);
1862 if (IsEnabled() && ActuallyPrerendering())
1863 profile_network_bytes_ += bytes;
1866 void PrerenderManager::OnCookieStoreLoaded() {
1867 cookie_store_loaded_ = true;
1868 if (!on_cookie_store_loaded_cb_for_testing_.is_null())
1869 on_cookie_store_loaded_cb_for_testing_.Run();
1872 void PrerenderManager::AddPrerenderProcessHost(
1873 content::RenderProcessHost* process_host) {
1874 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1875 DCHECK(prerender_process_hosts_.find(process_host) ==
1876 prerender_process_hosts_.end());
1877 prerender_process_hosts_.insert(process_host);
1878 process_host->AddObserver(this);
1881 bool PrerenderManager::MayReuseProcessHost(
1882 content::RenderProcessHost* process_host) {
1883 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1884 // If prerender cookie stores are disabled, there is no need to require
1885 // isolated prerender processes.
1886 if (!IsPrerenderCookieStoreEnabled())
1888 return (prerender_process_hosts_.find(process_host) ==
1889 prerender_process_hosts_.end());
1892 void PrerenderManager::RenderProcessHostDestroyed(
1893 content::RenderProcessHost* host) {
1894 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1895 prerender_process_hosts_.erase(host);
1896 BrowserThread::PostTask(
1897 BrowserThread::IO, FROM_HERE,
1898 base::Bind(&PrerenderTracker::RemovePrerenderCookieStoreOnIOThread,
1899 base::Unretained(prerender_tracker()), host->GetID(), false));
1902 } // namespace prerender