1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/prerender/prerender_manager.h"
12 #include "base/bind.h"
13 #include "base/bind_helpers.h"
14 #include "base/logging.h"
15 #include "base/memory/weak_ptr.h"
16 #include "base/metrics/histogram.h"
17 #include "base/prefs/pref_service.h"
18 #include "base/stl_util.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/time/time.h"
21 #include "base/timer/elapsed_timer.h"
22 #include "base/values.h"
23 #include "chrome/browser/browser_process.h"
24 #include "chrome/browser/chrome_notification_types.h"
25 #include "chrome/browser/common/cancelable_request.h"
26 #include "chrome/browser/history/history_service_factory.h"
27 #include "chrome/browser/net/chrome_cookie_notification_details.h"
28 #include "chrome/browser/predictors/predictor_database.h"
29 #include "chrome/browser/predictors/predictor_database_factory.h"
30 #include "chrome/browser/prerender/prerender_condition.h"
31 #include "chrome/browser/prerender/prerender_contents.h"
32 #include "chrome/browser/prerender/prerender_field_trial.h"
33 #include "chrome/browser/prerender/prerender_final_status.h"
34 #include "chrome/browser/prerender/prerender_handle.h"
35 #include "chrome/browser/prerender/prerender_histograms.h"
36 #include "chrome/browser/prerender/prerender_history.h"
37 #include "chrome/browser/prerender/prerender_local_predictor.h"
38 #include "chrome/browser/prerender/prerender_manager_factory.h"
39 #include "chrome/browser/prerender/prerender_tab_helper.h"
40 #include "chrome/browser/prerender/prerender_tracker.h"
41 #include "chrome/browser/prerender/prerender_util.h"
42 #include "chrome/browser/profiles/profile.h"
43 #include "chrome/browser/search/search.h"
44 #include "chrome/browser/tab_contents/tab_util.h"
45 #include "chrome/browser/ui/browser_navigator.h"
46 #include "chrome/browser/ui/tab_contents/core_tab_helper.h"
47 #include "chrome/browser/ui/tab_contents/core_tab_helper_delegate.h"
48 #include "chrome/common/chrome_switches.h"
49 #include "chrome/common/pref_names.h"
50 #include "chrome/common/prerender_messages.h"
51 #include "chrome/common/prerender_types.h"
52 #include "content/public/browser/browser_thread.h"
53 #include "content/public/browser/devtools_agent_host.h"
54 #include "content/public/browser/navigation_controller.h"
55 #include "content/public/browser/notification_service.h"
56 #include "content/public/browser/notification_source.h"
57 #include "content/public/browser/render_frame_host.h"
58 #include "content/public/browser/render_process_host.h"
59 #include "content/public/browser/render_view_host.h"
60 #include "content/public/browser/session_storage_namespace.h"
61 #include "content/public/browser/web_contents.h"
62 #include "content/public/browser/web_contents_delegate.h"
63 #include "content/public/browser/web_contents_view.h"
64 #include "content/public/common/url_constants.h"
65 #include "extensions/common/constants.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_APP_TERMINATING &&
123 final_status != FINAL_STATUS_WINDOW_OPENER &&
124 final_status != FINAL_STATUS_CACHE_OR_HISTORY_CLEARED &&
125 final_status != FINAL_STATUS_CANCELLED &&
126 final_status != FINAL_STATUS_DEVTOOLS_ATTACHED &&
127 final_status != FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING &&
128 final_status != FINAL_STATUS_PAGE_BEING_CAPTURED &&
129 final_status != FINAL_STATUS_NAVIGATION_UNCOMMITTED;
132 void CheckIfCookiesExistForDomainResultOnUIThread(
133 const net::CookieMonster::HasCookiesForETLDP1Callback& callback,
134 bool cookies_exist) {
135 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
136 callback.Run(cookies_exist);
139 void CheckIfCookiesExistForDomainResultOnIOThread(
140 const net::CookieMonster::HasCookiesForETLDP1Callback& callback,
141 bool cookies_exist) {
142 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
143 BrowserThread::PostTask(
146 base::Bind(&CheckIfCookiesExistForDomainResultOnUIThread,
151 void CheckIfCookiesExistForDomainOnIOThread(
152 net::URLRequestContextGetter* rq_context,
153 const std::string& domain_key,
154 const net::CookieMonster::HasCookiesForETLDP1Callback& callback) {
155 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
156 net::CookieStore* cookie_store =
157 rq_context->GetURLRequestContext()->cookie_store();
158 cookie_store->GetCookieMonster()->HasCookiesForETLDP1Async(
160 base::Bind(&CheckIfCookiesExistForDomainResultOnIOThread, callback));
165 class PrerenderManager::OnCloseWebContentsDeleter
166 : public content::WebContentsDelegate,
167 public base::SupportsWeakPtr<
168 PrerenderManager::OnCloseWebContentsDeleter> {
170 OnCloseWebContentsDeleter(PrerenderManager* manager,
174 suppressed_dialog_(false) {
175 tab_->SetDelegate(this);
176 base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
177 base::Bind(&OnCloseWebContentsDeleter::ScheduleWebContentsForDeletion,
179 base::TimeDelta::FromSeconds(kDeleteWithExtremePrejudiceSeconds));
182 virtual void CloseContents(WebContents* source) OVERRIDE {
183 DCHECK_EQ(tab_, source);
184 ScheduleWebContentsForDeletion(false);
187 virtual void SwappedOut(WebContents* source) OVERRIDE {
188 DCHECK_EQ(tab_, source);
189 ScheduleWebContentsForDeletion(false);
192 virtual bool ShouldSuppressDialogs() OVERRIDE {
193 // Use this as a proxy for getting statistics on how often we fail to honor
194 // the beforeunload event.
195 suppressed_dialog_ = true;
200 static const int kDeleteWithExtremePrejudiceSeconds = 3;
202 void ScheduleWebContentsForDeletion(bool timeout) {
203 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterTimeout", timeout);
204 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterSuppressedDialog",
206 tab_->SetDelegate(NULL);
207 manager_->ScheduleDeleteOldWebContents(tab_.release(), this);
208 // |this| is deleted at this point.
211 PrerenderManager* manager_;
212 scoped_ptr<WebContents> tab_;
213 bool suppressed_dialog_;
215 DISALLOW_COPY_AND_ASSIGN(OnCloseWebContentsDeleter);
219 int PrerenderManager::prerenders_per_session_count_ = 0;
222 PrerenderManager::PrerenderManagerMode PrerenderManager::mode_ =
223 PRERENDER_MODE_ENABLED;
225 struct PrerenderManager::NavigationRecord {
226 NavigationRecord(const GURL& url, base::TimeTicks time)
232 base::TimeTicks time;
235 PrerenderManager::PrerenderManager(Profile* profile,
236 PrerenderTracker* prerender_tracker)
237 : enabled_(profile && profile->GetPrefs() &&
238 profile->GetPrefs()->GetBoolean(prefs::kNetworkPredictionEnabled)),
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 // There are some assumptions that the PrerenderManager is on the UI thread.
249 // Any other checks simply make sure that the PrerenderManager is accessed on
250 // the same thread that it was created on.
251 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
253 if (IsLocalPredictorEnabled())
254 local_predictor_.reset(new PrerenderLocalPredictor(this));
256 if (IsLoggedInPredictorEnabled() && !profile_->IsOffTheRecord()) {
257 predictors::PredictorDatabase* predictor_db =
258 predictors::PredictorDatabaseFactory::GetForProfile(profile);
260 logged_in_predictor_table_ = predictor_db->logged_in_table();
261 scoped_ptr<LoggedInStateMap> new_state_map(new LoggedInStateMap);
262 LoggedInStateMap* new_state_map_ptr = new_state_map.get();
263 BrowserThread::PostTaskAndReply(
264 BrowserThread::DB, FROM_HERE,
265 base::Bind(&LoggedInPredictorTable::GetAllData,
266 logged_in_predictor_table_,
268 base::Bind(&PrerenderManager::LoggedInPredictorDataReceived,
270 base::Passed(&new_state_map)));
274 // Certain experiments override our default config_ values.
275 switch (PrerenderManager::GetMode()) {
276 case PrerenderManager::PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP:
277 config_.max_link_concurrency = 4;
278 config_.max_link_concurrency_per_launcher = 2;
280 case PrerenderManager::PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP:
281 config_.time_to_live = base::TimeDelta::FromMinutes(15);
287 notification_registrar_.Add(
288 this, chrome::NOTIFICATION_COOKIE_CHANGED,
289 content::NotificationService::AllBrowserContextsAndSources());
291 MediaCaptureDevicesDispatcher::GetInstance()->AddObserver(this);
294 PrerenderManager::~PrerenderManager() {
295 MediaCaptureDevicesDispatcher::GetInstance()->RemoveObserver(this);
297 // The earlier call to BrowserContextKeyedService::Shutdown() should have
298 // emptied these vectors already.
299 DCHECK(active_prerenders_.empty());
300 DCHECK(to_delete_prerenders_.empty());
303 void PrerenderManager::Shutdown() {
304 DestroyAllContents(FINAL_STATUS_MANAGER_SHUTDOWN);
305 STLDeleteElements(&prerender_conditions_);
306 on_close_web_contents_deleters_.clear();
307 // Must happen before |profile_| is set to NULL as
308 // |local_predictor_| accesses it.
309 if (local_predictor_)
310 local_predictor_->Shutdown();
313 DCHECK(active_prerenders_.empty());
316 PrerenderHandle* PrerenderManager::AddPrerenderFromLinkRelPrerender(
320 const uint32 rel_types,
321 const content::Referrer& referrer,
322 const gfx::Size& size) {
323 Origin origin = rel_types & PrerenderRelTypePrerender ?
324 ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN :
325 ORIGIN_LINK_REL_NEXT;
326 SessionStorageNamespace* session_storage_namespace = NULL;
327 // Unit tests pass in a process_id == -1.
328 if (process_id != -1) {
329 RenderViewHost* source_render_view_host =
330 RenderViewHost::FromID(process_id, route_id);
331 if (!source_render_view_host)
333 WebContents* source_web_contents =
334 WebContents::FromRenderViewHost(source_render_view_host);
335 if (!source_web_contents)
337 if (origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN &&
338 source_web_contents->GetURL().host() == url.host()) {
339 origin = ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN;
341 // TODO(ajwong): This does not correctly handle storage for isolated apps.
342 session_storage_namespace =
343 source_web_contents->GetController()
344 .GetDefaultSessionStorageNamespace();
347 return AddPrerender(origin, process_id, url, referrer, size,
348 session_storage_namespace);
351 PrerenderHandle* PrerenderManager::AddPrerenderFromOmnibox(
353 SessionStorageNamespace* session_storage_namespace,
354 const gfx::Size& size) {
355 if (!IsOmniboxEnabled(profile_))
357 return AddPrerender(ORIGIN_OMNIBOX, -1, url, content::Referrer(), size,
358 session_storage_namespace);
361 PrerenderHandle* PrerenderManager::AddPrerenderFromLocalPredictor(
363 SessionStorageNamespace* session_storage_namespace,
364 const gfx::Size& size) {
365 return AddPrerender(ORIGIN_LOCAL_PREDICTOR, -1, url, content::Referrer(),
366 size, session_storage_namespace);
369 PrerenderHandle* PrerenderManager::AddPrerenderFromExternalRequest(
371 const content::Referrer& referrer,
372 SessionStorageNamespace* session_storage_namespace,
373 const gfx::Size& size) {
374 return AddPrerender(ORIGIN_EXTERNAL_REQUEST, -1, url, referrer, size,
375 session_storage_namespace);
378 PrerenderHandle* PrerenderManager::AddPrerenderForInstant(
380 content::SessionStorageNamespace* session_storage_namespace,
381 const gfx::Size& size) {
382 DCHECK(chrome::ShouldPrefetchSearchResults());
383 return AddPrerender(ORIGIN_INSTANT, -1, url, content::Referrer(), size,
384 session_storage_namespace);
387 void PrerenderManager::CancelAllPrerenders() {
388 DCHECK(CalledOnValidThread());
389 while (!active_prerenders_.empty()) {
390 PrerenderContents* prerender_contents =
391 active_prerenders_.front()->contents();
392 prerender_contents->Destroy(FINAL_STATUS_CANCELLED);
396 bool PrerenderManager::MaybeUsePrerenderedPage(const GURL& url,
397 chrome::NavigateParams* params) {
398 DCHECK(CalledOnValidThread());
400 content::WebContents* web_contents = params->target_contents;
401 DCHECK(!IsWebContentsPrerendering(web_contents, NULL));
403 // Don't prerender if the navigation involves some special parameters.
404 if (params->uses_post || !params->extra_headers.empty())
408 to_delete_prerenders_.clear();
410 // First, try to find prerender data with the correct session storage
412 // TODO(ajwong): This doesn't handle isolated apps correctly.
413 PrerenderData* prerender_data = FindPrerenderData(
415 web_contents->GetController().GetDefaultSessionStorageNamespace());
417 // If this failed, we may still find a prerender for the same URL, but a
418 // different session storage namespace. If we do, we might have to perform
420 if (!prerender_data) {
421 prerender_data = FindPrerenderData(url, NULL);
423 RecordEvent(prerender_data->contents(),
424 PRERENDER_EVENT_SWAPIN_CANDIDATE_NAMESPACE_MATCHES);
429 RecordEvent(prerender_data->contents(), PRERENDER_EVENT_SWAPIN_CANDIDATE);
430 DCHECK(prerender_data->contents());
432 // If there is currently a merge pending for this prerender data, don't swap.
433 if (prerender_data->pending_swap())
436 // Abort any existing pending swap on the target contents.
437 PrerenderData* pending_swap =
438 FindPrerenderDataForTargetContents(web_contents);
440 pending_swap->ClearPendingSwap();
441 DCHECK(FindPrerenderDataForTargetContents(web_contents) == NULL);
444 RecordEvent(prerender_data->contents(),
445 PRERENDER_EVENT_SWAPIN_NO_MERGE_PENDING);
446 SessionStorageNamespace* target_namespace =
447 web_contents->GetController().GetDefaultSessionStorageNamespace();
448 SessionStorageNamespace* prerender_namespace =
449 prerender_data->contents()->GetSessionStorageNamespace();
450 // Only when actually prerendering is session storage namespace merging an
451 // issue. For the control group, it will be assumed that the merge succeeded.
452 if (prerender_namespace && prerender_namespace != target_namespace &&
453 !prerender_namespace->IsAliasOf(target_namespace)) {
454 if (!ShouldMergeSessionStorageNamespaces()) {
455 RecordEvent(prerender_data->contents(),
456 PRERENDER_EVENT_SWAPIN_MERGING_DISABLED);
459 RecordEvent(prerender_data->contents(),
460 PRERENDER_EVENT_SWAPIN_ISSUING_MERGE);
461 prerender_data->set_pending_swap(new PendingSwap(
462 this, web_contents, prerender_data, url,
463 params->should_replace_current_entry));
464 prerender_data->pending_swap()->BeginSwap();
465 // Although this returns false, creating a PendingSwap registers with
466 // PrerenderTracker to throttle MAIN_FRAME navigations while the swap is
471 // No need to merge; swap synchronously.
472 WebContents* new_web_contents = SwapInternal(
473 url, web_contents, prerender_data,
474 params->should_replace_current_entry);
475 if (!new_web_contents)
478 // Record the new target_contents for the callers.
479 params->target_contents = new_web_contents;
483 WebContents* PrerenderManager::SwapInternal(
485 WebContents* web_contents,
486 PrerenderData* prerender_data,
487 bool should_replace_current_entry) {
488 DCHECK(CalledOnValidThread());
489 DCHECK(!IsWebContentsPrerendering(web_contents, NULL));
491 // Only swap if the target WebContents has a CoreTabHelper delegate to swap
492 // out of it. For a normal WebContents, this is if it is in a TabStripModel.
493 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(web_contents);
494 if (!core_tab_helper || !core_tab_helper->delegate()) {
495 RecordEvent(prerender_data->contents(), PRERENDER_EVENT_SWAPIN_NO_DELEGATE);
499 PrerenderTabHelper* target_tab_helper =
500 PrerenderTabHelper::FromWebContents(web_contents);
501 if (!target_tab_helper) {
506 if (IsNoSwapInExperiment(prerender_data->contents()->experiment_id()))
509 if (WebContents* new_web_contents =
510 prerender_data->contents()->prerender_contents()) {
511 if (web_contents == new_web_contents)
512 return NULL; // Do not swap in to ourself.
514 // We cannot swap in if there is no last committed entry, because we would
515 // show a blank page under an existing entry from the current tab. Even if
516 // there is a pending entry, it may not commit.
517 // TODO(creis): If there is a pending navigation and no last committed
518 // entry, we might be able to transfer the network request instead.
519 if (!new_web_contents->GetController().CanPruneAllButLastCommitted()) {
520 // Abort this prerender so it is not used later. http://crbug.com/292121
521 prerender_data->contents()->Destroy(FINAL_STATUS_NAVIGATION_UNCOMMITTED);
526 // Do not use the prerendered version if there is an opener object.
527 if (web_contents->HasOpener()) {
528 prerender_data->contents()->Destroy(FINAL_STATUS_WINDOW_OPENER);
532 // Do not swap in the prerender if the current WebContents is being captured.
533 if (web_contents->GetCapturerCount() > 0) {
534 prerender_data->contents()->Destroy(FINAL_STATUS_PAGE_BEING_CAPTURED);
538 // If we are just in the control group (which can be detected by noticing
539 // that prerendering hasn't even started yet), record that |web_contents| now
540 // would be showing a prerendered contents, but otherwise, don't do anything.
541 if (!prerender_data->contents()->prerendering_has_started()) {
542 target_tab_helper->WouldHavePrerenderedNextLoad(
543 prerender_data->contents()->origin());
544 prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED);
548 // Don't use prerendered pages if debugger is attached to the tab.
549 // See http://crbug.com/98541
550 if (content::DevToolsAgentHost::IsDebuggerAttached(web_contents)) {
551 DestroyAndMarkMatchCompleteAsUsed(prerender_data->contents(),
552 FINAL_STATUS_DEVTOOLS_ATTACHED);
556 // If the prerendered page is in the middle of a cross-site navigation,
557 // don't swap it in because there isn't a good way to merge histories.
558 if (prerender_data->contents()->IsCrossSiteNavigationPending()) {
559 DestroyAndMarkMatchCompleteAsUsed(
560 prerender_data->contents(),
561 FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING);
565 // For bookkeeping purposes, we need to mark this WebContents to
566 // reflect that it would have been prerendered.
567 if (GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP) {
568 target_tab_helper->WouldHavePrerenderedNextLoad(
569 prerender_data->contents()->origin());
570 prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED);
574 // At this point, we've determined that we will use the prerender.
575 if (prerender_data->pending_swap())
576 prerender_data->pending_swap()->set_swap_successful(true);
577 ScopedVector<PrerenderData>::iterator to_erase =
578 FindIteratorForPrerenderContents(prerender_data->contents());
579 DCHECK(active_prerenders_.end() != to_erase);
580 DCHECK_EQ(prerender_data, *to_erase);
581 scoped_ptr<PrerenderContents>
582 prerender_contents(prerender_data->ReleaseContents());
583 active_prerenders_.erase(to_erase);
585 if (!prerender_contents->load_start_time().is_null()) {
586 histograms_->RecordTimeUntilUsed(
587 prerender_contents->origin(),
588 GetCurrentTimeTicks() - prerender_contents->load_start_time());
591 histograms_->RecordPerSessionCount(prerender_contents->origin(),
592 ++prerenders_per_session_count_);
593 histograms_->RecordUsedPrerender(prerender_contents->origin());
595 // Mark prerender as used.
596 prerender_contents->PrepareForUse();
598 WebContents* new_web_contents =
599 prerender_contents->ReleasePrerenderContents();
600 WebContents* old_web_contents = web_contents;
601 DCHECK(new_web_contents);
602 DCHECK(old_web_contents);
604 // Merge the browsing history.
605 new_web_contents->GetController().CopyStateFromAndPrune(
606 &old_web_contents->GetController(),
607 should_replace_current_entry);
608 CoreTabHelper::FromWebContents(old_web_contents)->delegate()->
609 SwapTabContents(old_web_contents,
612 prerender_contents->has_finished_loading());
613 prerender_contents->CommitHistory(new_web_contents);
615 // Update PPLT metrics:
616 // If the tab has finished loading, record a PPLT of 0.
617 // If the tab is still loading, reset its start time to the current time.
618 PrerenderTabHelper* prerender_tab_helper =
619 PrerenderTabHelper::FromWebContents(new_web_contents);
620 DCHECK(prerender_tab_helper != NULL);
621 prerender_tab_helper->PrerenderSwappedIn();
623 if (old_web_contents->NeedToFireBeforeUnload()) {
624 // Schedule the delete to occur after the tab has run its unload handlers.
625 // TODO(davidben): Honor the beforeunload event. http://crbug.com/304932
626 on_close_web_contents_deleters_.push_back(
627 new OnCloseWebContentsDeleter(this, old_web_contents));
628 old_web_contents->GetRenderViewHost()->
629 FirePageBeforeUnload(false);
631 // No unload handler to run, so delete asap.
632 ScheduleDeleteOldWebContents(old_web_contents, NULL);
635 // TODO(cbentzel): Should prerender_contents move to the pending delete
636 // list, instead of deleting directly here?
637 AddToHistory(prerender_contents.get());
638 RecordNavigation(url);
639 return new_web_contents;
642 void PrerenderManager::MoveEntryToPendingDelete(PrerenderContents* entry,
643 FinalStatus final_status) {
644 DCHECK(CalledOnValidThread());
647 ScopedVector<PrerenderData>::iterator it =
648 FindIteratorForPrerenderContents(entry);
649 DCHECK(it != active_prerenders_.end());
651 // If this PrerenderContents is being deleted due to a cancellation any time
652 // after the prerender has started then we need to create a dummy replacement
653 // for PPLT accounting purposes for the Match Complete group. This is the case
654 // if the cancellation is for any reason that would not occur in the control
656 if (entry->prerendering_has_started() &&
657 entry->match_complete_status() ==
658 PrerenderContents::MATCH_COMPLETE_DEFAULT &&
659 NeedMatchCompleteDummyForFinalStatus(final_status) &&
660 ActuallyPrerendering()) {
661 // TODO(tburkard): I'd like to DCHECK that we are actually prerendering.
662 // However, what if new conditions are added and
663 // NeedMatchCompleteDummyForFinalStatus is not being updated. Not sure
664 // what's the best thing to do here. For now, I will just check whether
665 // we are actually prerendering.
666 (*it)->MakeIntoMatchCompleteReplacement();
668 to_delete_prerenders_.push_back(*it);
669 (*it)->ClearPendingSwap();
670 active_prerenders_.weak_erase(it);
673 // Destroy the old WebContents relatively promptly to reduce resource usage.
677 void PrerenderManager::RecordPageLoadTimeNotSwappedIn(
679 base::TimeDelta page_load_time,
681 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
682 histograms_->RecordPageLoadTimeNotSwappedIn(origin, page_load_time, url);
685 void PrerenderManager::RecordPerceivedPageLoadTime(
687 NavigationType navigation_type,
688 base::TimeDelta perceived_page_load_time,
689 double fraction_plt_elapsed_at_swap_in,
691 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
695 histograms_->RecordPerceivedPageLoadTime(
696 origin, perceived_page_load_time, navigation_type, url);
698 if (navigation_type == NAVIGATION_TYPE_PRERENDERED) {
699 histograms_->RecordPercentLoadDoneAtSwapin(
700 origin, fraction_plt_elapsed_at_swap_in);
702 if (local_predictor_) {
703 local_predictor_->OnPLTEventForURL(url, perceived_page_load_time);
707 void PrerenderManager::set_enabled(bool enabled) {
708 DCHECK(CalledOnValidThread());
713 PrerenderManager::PrerenderManagerMode PrerenderManager::GetMode() {
718 void PrerenderManager::SetMode(PrerenderManagerMode mode) {
723 const char* PrerenderManager::GetModeString() {
725 case PRERENDER_MODE_DISABLED:
727 case PRERENDER_MODE_ENABLED:
728 case PRERENDER_MODE_EXPERIMENT_PRERENDER_GROUP:
730 case PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP:
732 case PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP:
734 case PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP:
736 case PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP:
738 case PRERENDER_MODE_MAX:
740 NOTREACHED() << "Invalid PrerenderManager mode.";
747 bool PrerenderManager::IsPrerenderingPossible() {
748 return GetMode() != PRERENDER_MODE_DISABLED;
752 bool PrerenderManager::ActuallyPrerendering() {
753 return IsPrerenderingPossible() && !IsControlGroup(kNoExperiment);
757 bool PrerenderManager::IsControlGroup(uint8 experiment_id) {
758 return GetMode() == PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP ||
759 IsControlGroupExperiment(experiment_id);
763 bool PrerenderManager::IsNoUseGroup() {
764 return GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP;
767 bool PrerenderManager::IsWebContentsPrerendering(
768 const WebContents* web_contents,
769 Origin* origin) const {
770 DCHECK(CalledOnValidThread());
771 if (PrerenderContents* prerender_contents =
772 GetPrerenderContents(web_contents)) {
774 *origin = prerender_contents->origin();
780 bool PrerenderManager::HasPrerenderedUrl(
782 content::WebContents* web_contents) const {
783 content::SessionStorageNamespace* session_storage_namespace = web_contents->
784 GetController().GetDefaultSessionStorageNamespace();
786 for (ScopedVector<PrerenderData>::const_iterator it =
787 active_prerenders_.begin();
788 it != active_prerenders_.end(); ++it) {
789 PrerenderContents* prerender_contents = (*it)->contents();
790 if (prerender_contents->Matches(url, session_storage_namespace)) {
797 PrerenderContents* PrerenderManager::GetPrerenderContents(
798 const content::WebContents* web_contents) const {
799 DCHECK(CalledOnValidThread());
800 for (ScopedVector<PrerenderData>::const_iterator it =
801 active_prerenders_.begin();
802 it != active_prerenders_.end(); ++it) {
803 WebContents* prerender_web_contents =
804 (*it)->contents()->prerender_contents();
805 if (prerender_web_contents == web_contents) {
806 return (*it)->contents();
810 // Also check the pending-deletion list. If the prerender is in pending
811 // delete, anyone with a handle on the WebContents needs to know.
812 for (ScopedVector<PrerenderData>::const_iterator it =
813 to_delete_prerenders_.begin();
814 it != to_delete_prerenders_.end(); ++it) {
815 WebContents* prerender_web_contents =
816 (*it)->contents()->prerender_contents();
817 if (prerender_web_contents == web_contents) {
818 return (*it)->contents();
824 PrerenderContents* PrerenderManager::GetPrerenderContentsForRoute(
826 int route_id) const {
827 content::WebContents* web_contents =
828 tab_util::GetWebContentsByID(child_id, route_id);
829 if (web_contents == NULL)
831 return GetPrerenderContents(web_contents);
834 const std::vector<WebContents*>
835 PrerenderManager::GetAllPrerenderingContents() const {
836 DCHECK(CalledOnValidThread());
837 std::vector<WebContents*> result;
839 for (ScopedVector<PrerenderData>::const_iterator it =
840 active_prerenders_.begin();
841 it != active_prerenders_.end(); ++it) {
842 if (WebContents* contents = (*it)->contents()->prerender_contents())
843 result.push_back(contents);
849 bool PrerenderManager::HasRecentlyBeenNavigatedTo(Origin origin,
851 DCHECK(CalledOnValidThread());
853 CleanUpOldNavigations();
854 std::list<NavigationRecord>::const_reverse_iterator end = navigations_.rend();
855 for (std::list<NavigationRecord>::const_reverse_iterator it =
856 navigations_.rbegin();
859 if (it->url == url) {
860 base::TimeDelta delta = GetCurrentTimeTicks() - it->time;
861 histograms_->RecordTimeSinceLastRecentVisit(origin, delta);
870 bool PrerenderManager::IsValidHttpMethod(const std::string& method) {
871 // method has been canonicalized to upper case at this point so we can just
873 DCHECK_EQ(method, StringToUpperASCII(method));
874 for (size_t i = 0; i < arraysize(kValidHttpMethods); ++i) {
875 if (method.compare(kValidHttpMethods[i]) == 0)
883 bool PrerenderManager::DoesURLHaveValidScheme(const GURL& url) {
884 return (url.SchemeIsHTTPOrHTTPS() ||
885 url.SchemeIs(extensions::kExtensionScheme) ||
886 url.SchemeIs("data"));
890 bool PrerenderManager::DoesSubresourceURLHaveValidScheme(const GURL& url) {
891 return DoesURLHaveValidScheme(url) || url == GURL(content::kAboutBlankURL);
894 base::DictionaryValue* PrerenderManager::GetAsValue() const {
895 DCHECK(CalledOnValidThread());
896 base::DictionaryValue* dict_value = new base::DictionaryValue();
897 dict_value->Set("history", prerender_history_->GetEntriesAsValue());
898 dict_value->Set("active", GetActivePrerendersAsValue());
899 dict_value->SetBoolean("enabled", enabled_);
900 dict_value->SetBoolean("omnibox_enabled", IsOmniboxEnabled(profile_));
901 // If prerender is disabled via a flag this method is not even called.
902 std::string enabled_note;
903 if (IsControlGroup(kNoExperiment))
904 enabled_note += "(Control group: Not actually prerendering) ";
906 enabled_note += "(No-use group: Not swapping in prerendered pages) ";
907 if (GetMode() == PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP)
909 "(15 min TTL group: Extended prerender eviction to 15 mins) ";
910 dict_value->SetString("enabled_note", enabled_note);
914 void PrerenderManager::ClearData(int clear_flags) {
915 DCHECK_GE(clear_flags, 0);
916 DCHECK_LT(clear_flags, CLEAR_MAX);
917 if (clear_flags & CLEAR_PRERENDER_CONTENTS)
918 DestroyAllContents(FINAL_STATUS_CACHE_OR_HISTORY_CLEARED);
919 // This has to be second, since destroying prerenders can add to the history.
920 if (clear_flags & CLEAR_PRERENDER_HISTORY)
921 prerender_history_->Clear();
924 void PrerenderManager::RecordFinalStatusWithMatchCompleteStatus(
927 PrerenderContents::MatchCompleteStatus mc_status,
928 FinalStatus final_status) const {
929 histograms_->RecordFinalStatus(origin,
935 void PrerenderManager::AddCondition(const PrerenderCondition* condition) {
936 prerender_conditions_.push_back(condition);
939 void PrerenderManager::RecordNavigation(const GURL& url) {
940 DCHECK(CalledOnValidThread());
942 navigations_.push_back(NavigationRecord(url, GetCurrentTimeTicks()));
943 CleanUpOldNavigations();
947 struct PrerenderManager::PrerenderData::OrderByExpiryTime {
948 bool operator()(const PrerenderData* a, const PrerenderData* b) const {
949 return a->expiry_time() < b->expiry_time();
953 PrerenderManager::PrerenderData::PrerenderData(PrerenderManager* manager,
954 PrerenderContents* contents,
955 base::TimeTicks expiry_time)
959 expiry_time_(expiry_time) {
960 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
963 PrerenderManager::PrerenderData::~PrerenderData() {
966 void PrerenderManager::PrerenderData::MakeIntoMatchCompleteReplacement() {
968 contents_->set_match_complete_status(
969 PrerenderContents::MATCH_COMPLETE_REPLACED);
970 PrerenderData* to_delete = new PrerenderData(manager_, contents_.release(),
972 contents_.reset(to_delete->contents_->CreateMatchCompleteReplacement());
973 manager_->to_delete_prerenders_.push_back(to_delete);
976 void PrerenderManager::PrerenderData::OnHandleCreated(PrerenderHandle* handle) {
977 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
979 contents_->AddObserver(handle);
982 void PrerenderManager::PrerenderData::OnHandleNavigatedAway(
983 PrerenderHandle* handle) {
984 DCHECK_LT(0, handle_count_);
985 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
986 // We intentionally don't decrement the handle count here, so that the
987 // prerender won't be canceled until it times out.
988 manager_->SourceNavigatedAway(this);
991 void PrerenderManager::PrerenderData::OnHandleCanceled(
992 PrerenderHandle* handle) {
993 DCHECK_LT(0, handle_count_);
994 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
996 if (--handle_count_ == 0) {
997 // This will eventually remove this object from active_prerenders_.
998 contents_->Destroy(FINAL_STATUS_CANCELLED);
1002 void PrerenderManager::PrerenderData::ClearPendingSwap() {
1003 pending_swap_.reset(NULL);
1006 PrerenderContents* PrerenderManager::PrerenderData::ReleaseContents() {
1007 return contents_.release();
1010 PrerenderManager::PendingSwap::PendingSwap(
1011 PrerenderManager* manager,
1012 content::WebContents* target_contents,
1013 PrerenderData* prerender_data,
1015 bool should_replace_current_entry)
1016 : content::WebContentsObserver(target_contents),
1018 prerender_data_(prerender_data),
1020 should_replace_current_entry_(should_replace_current_entry),
1021 start_time_(base::TimeTicks::Now()),
1022 seen_target_route_id_(false),
1023 swap_successful_(false),
1024 weak_factory_(this) {
1027 PrerenderManager::PendingSwap::~PendingSwap() {
1028 manager_->prerender_tracker()->RemovePrerenderPendingSwap(
1029 target_route_id_, swap_successful_);
1032 WebContents* PrerenderManager::PendingSwap::target_contents() const {
1033 return web_contents();
1036 void PrerenderManager::PendingSwap::BeginSwap() {
1037 if (g_hang_session_storage_merges_for_testing)
1040 SessionStorageNamespace* target_namespace =
1041 target_contents()->GetController().GetDefaultSessionStorageNamespace();
1042 SessionStorageNamespace* prerender_namespace =
1043 prerender_data_->contents()->GetSessionStorageNamespace();
1045 prerender_namespace->Merge(
1046 true, prerender_data_->contents()->child_id(),
1048 base::Bind(&PrerenderManager::PendingSwap::OnMergeCompleted,
1049 weak_factory_.GetWeakPtr()));
1051 merge_timeout_.Start(
1053 base::TimeDelta::FromMilliseconds(
1054 kSessionStorageNamespaceMergeTimeoutMs),
1055 this, &PrerenderManager::PendingSwap::OnMergeTimeout);
1058 void PrerenderManager::PendingSwap::AboutToNavigateRenderView(
1059 RenderViewHost* render_view_host) {
1060 if (seen_target_route_id_) {
1061 // A second navigation began browser-side.
1062 prerender_data_->ClearPendingSwap();
1066 seen_target_route_id_ = true;
1067 target_route_id_ = PrerenderTracker::ChildRouteIdPair(
1068 render_view_host->GetMainFrame()->GetProcess()->GetID(),
1069 render_view_host->GetMainFrame()->GetRoutingID());
1070 manager_->prerender_tracker()->AddPrerenderPendingSwap(
1071 target_route_id_, url_);
1074 void PrerenderManager::PendingSwap::ProvisionalChangeToMainFrameUrl(
1076 content::RenderFrameHost* render_frame_host) {
1077 // We must only cancel the pending swap if the |url| navigated to is not
1078 // the URL being attempted to be swapped in. That's because in the normal
1079 // flow, a ProvisionalChangeToMainFrameUrl will happen for the URL attempted
1080 // to be swapped in immediately after the pending swap has issued its merge.
1082 prerender_data_->ClearPendingSwap();
1085 void PrerenderManager::PendingSwap::DidCommitProvisionalLoadForFrame(
1087 const base::string16& frame_unique_name,
1089 const GURL& validated_url,
1090 content::PageTransition transition_type,
1091 content::RenderViewHost* render_view_host){
1094 prerender_data_->ClearPendingSwap();
1097 void PrerenderManager::PendingSwap::DidFailProvisionalLoad(
1099 const base::string16& frame_unique_name,
1101 const GURL& validated_url,
1103 const base::string16& error_description,
1104 content::RenderViewHost* render_view_host) {
1107 prerender_data_->ClearPendingSwap();
1110 void PrerenderManager::PendingSwap::WebContentsDestroyed(
1111 content::WebContents* web_contents) {
1112 prerender_data_->ClearPendingSwap();
1115 void PrerenderManager::PendingSwap::RecordEvent(PrerenderEvent event) const {
1116 manager_->RecordEvent(prerender_data_->contents(), event);
1119 void PrerenderManager::PendingSwap::OnMergeCompleted(
1120 SessionStorageNamespace::MergeResult result) {
1121 UMA_HISTOGRAM_TIMES("Prerender.SessionStorageNamespaceMergeTime",
1122 base::TimeTicks::Now() - start_time_);
1123 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_MERGE_DONE);
1125 // Log the exact merge result in a histogram.
1127 case SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_FOUND:
1128 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NAMESPACE_NOT_FOUND);
1130 case SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_ALIAS:
1131 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NAMESPACE_NOT_ALIAS);
1133 case SessionStorageNamespace::MERGE_RESULT_NOT_LOGGING:
1134 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NOT_LOGGING);
1136 case SessionStorageNamespace::MERGE_RESULT_NO_TRANSACTIONS:
1137 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NO_TRANSACTIONS);
1139 case SessionStorageNamespace::MERGE_RESULT_TOO_MANY_TRANSACTIONS:
1140 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_TOO_MANY_TRANSACTIONS);
1142 case SessionStorageNamespace::MERGE_RESULT_NOT_MERGEABLE:
1143 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NOT_MERGEABLE);
1145 case SessionStorageNamespace::MERGE_RESULT_MERGEABLE:
1146 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_MERGEABLE);
1152 if (result != SessionStorageNamespace::MERGE_RESULT_MERGEABLE &&
1153 result != SessionStorageNamespace::MERGE_RESULT_NO_TRANSACTIONS) {
1154 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_MERGE_FAILED);
1155 prerender_data_->ClearPendingSwap();
1159 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_SWAPPING_IN);
1161 // Note that SwapInternal will, on success, delete |prerender_data_| and
1162 // |this|. It will also delete |this| in some failure cases. Pass in a new
1163 // GURL object rather than a reference to |url_|. Also hold on to |manager_|
1164 // and |prerender_data_|.
1166 // TODO(davidben): Can we make this less fragile?
1167 PrerenderManager* manager = manager_;
1168 PrerenderData* prerender_data = prerender_data_;
1169 WebContents* new_web_contents = manager_->SwapInternal(
1170 GURL(url_), target_contents(), prerender_data_,
1171 should_replace_current_entry_);
1172 if (!new_web_contents) {
1173 manager->RecordEvent(prerender_data->contents(),
1174 PRERENDER_EVENT_MERGE_RESULT_SWAPIN_FAILED);
1175 // Depending on whether SwapInternal called Destroy() or simply failed to
1176 // swap, |this| may or may not be deleted. Either way, if the swap failed,
1177 // |prerender_data| is deleted asynchronously, so this call is a no-op if
1178 // |this| is already gone.
1179 prerender_data->ClearPendingSwap();
1183 void PrerenderManager::PendingSwap::OnMergeTimeout() {
1184 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_TIMED_OUT);
1185 prerender_data_->ClearPendingSwap();
1188 void PrerenderManager::SetPrerenderContentsFactory(
1189 PrerenderContents::Factory* prerender_contents_factory) {
1190 DCHECK(CalledOnValidThread());
1191 prerender_contents_factory_.reset(prerender_contents_factory);
1194 void PrerenderManager::SourceNavigatedAway(PrerenderData* prerender_data) {
1195 // The expiry time of our prerender data will likely change because of
1196 // this navigation. This requires a resort of active_prerenders_.
1197 ScopedVector<PrerenderData>::iterator it =
1198 std::find(active_prerenders_.begin(), active_prerenders_.end(),
1200 if (it == active_prerenders_.end())
1203 (*it)->set_expiry_time(
1204 std::min((*it)->expiry_time(),
1205 GetExpiryTimeForNavigatedAwayPrerender()));
1206 SortActivePrerenders();
1210 PrerenderHandle* PrerenderManager::AddPrerender(
1213 const GURL& url_arg,
1214 const content::Referrer& referrer,
1215 const gfx::Size& size,
1216 SessionStorageNamespace* session_storage_namespace) {
1217 DCHECK(CalledOnValidThread());
1222 if ((origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN ||
1223 origin == ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN) &&
1224 IsGoogleSearchResultURL(referrer.url)) {
1225 origin = ORIGIN_GWS_PRERENDER;
1230 uint8 experiment = GetQueryStringBasedExperiment(url_arg);
1231 if (IsControlGroup(experiment) &&
1232 MaybeGetQueryStringBasedAliasURL(url, &alias_url)) {
1236 // From here on, we will record a FinalStatus so we need to register with the
1237 // histogram tracking.
1238 histograms_->RecordPrerender(origin, url_arg);
1240 if (PrerenderData* preexisting_prerender_data =
1241 FindPrerenderData(url, session_storage_namespace)) {
1242 RecordFinalStatus(origin, experiment, FINAL_STATUS_DUPLICATE);
1243 return new PrerenderHandle(preexisting_prerender_data);
1246 // Do not prerender if there are too many render processes, and we would
1247 // have to use an existing one. We do not want prerendering to happen in
1248 // a shared process, so that we can always reliably lower the CPU
1249 // priority for prerendering.
1250 // In single-process mode, ShouldTryToUseExistingProcessHost() always returns
1251 // true, so that case needs to be explicitly checked for.
1252 // TODO(tburkard): Figure out how to cancel prerendering in the opposite
1253 // case, when a new tab is added to a process used for prerendering.
1254 // On Android we do reuse processes as we have a limited number of them and we
1255 // still want the benefits of prerendering even when several tabs are open.
1256 #if !defined(OS_ANDROID)
1257 if (content::RenderProcessHost::ShouldTryToUseExistingProcessHost(
1259 !content::RenderProcessHost::run_renderer_in_process()) {
1260 RecordFinalStatus(origin, experiment, FINAL_STATUS_TOO_MANY_PROCESSES);
1265 // Check if enough time has passed since the last prerender.
1266 if (!DoesRateLimitAllowPrerender(origin)) {
1267 // Cancel the prerender. We could add it to the pending prerender list but
1268 // this doesn't make sense as the next prerender request will be triggered
1269 // by a navigation and is unlikely to be the same site.
1270 RecordFinalStatus(origin, experiment, FINAL_STATUS_RATE_LIMIT_EXCEEDED);
1274 PrerenderContents* prerender_contents = CreatePrerenderContents(
1275 url, referrer, origin, experiment);
1276 DCHECK(prerender_contents);
1277 active_prerenders_.push_back(
1278 new PrerenderData(this, prerender_contents,
1279 GetExpiryTimeForNewPrerender(origin)));
1280 if (!prerender_contents->Init()) {
1281 DCHECK(active_prerenders_.end() ==
1282 FindIteratorForPrerenderContents(prerender_contents));
1286 histograms_->RecordPrerenderStarted(origin);
1287 DCHECK(!prerender_contents->prerendering_has_started());
1289 PrerenderHandle* prerender_handle =
1290 new PrerenderHandle(active_prerenders_.back());
1291 SortActivePrerenders();
1293 last_prerender_start_time_ = GetCurrentTimeTicks();
1295 gfx::Size contents_size =
1296 size.IsEmpty() ? config_.default_tab_bounds.size() : size;
1298 prerender_contents->StartPrerendering(process_id, contents_size,
1299 session_storage_namespace);
1301 DCHECK(IsControlGroup(experiment) ||
1302 prerender_contents->prerendering_has_started());
1304 if (GetMode() == PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP)
1305 histograms_->RecordConcurrency(active_prerenders_.size());
1307 // Query the history to see if the URL being prerendered has ever been
1309 HistoryService* history_service = HistoryServiceFactory::GetForProfile(
1310 profile_, Profile::EXPLICIT_ACCESS);
1311 if (history_service) {
1312 history_service->QueryURL(
1315 &query_url_consumer_,
1316 base::Bind(&PrerenderManager::OnHistoryServiceDidQueryURL,
1317 base::Unretained(this),
1322 StartSchedulingPeriodicCleanups();
1323 return prerender_handle;
1326 void PrerenderManager::StartSchedulingPeriodicCleanups() {
1327 DCHECK(CalledOnValidThread());
1328 if (repeating_timer_.IsRunning())
1330 repeating_timer_.Start(FROM_HERE,
1331 base::TimeDelta::FromMilliseconds(kPeriodicCleanupIntervalMs),
1333 &PrerenderManager::PeriodicCleanup);
1336 void PrerenderManager::StopSchedulingPeriodicCleanups() {
1337 DCHECK(CalledOnValidThread());
1338 repeating_timer_.Stop();
1341 void PrerenderManager::PeriodicCleanup() {
1342 DCHECK(CalledOnValidThread());
1344 base::ElapsedTimer resource_timer;
1346 // Grab a copy of the current PrerenderContents pointers, so that we
1347 // will not interfere with potential deletions of the list.
1348 std::vector<PrerenderContents*>
1349 prerender_contents(active_prerenders_.size());
1350 std::transform(active_prerenders_.begin(), active_prerenders_.end(),
1351 prerender_contents.begin(),
1352 std::mem_fun(&PrerenderData::contents));
1354 // And now check for prerenders using too much memory.
1355 std::for_each(prerender_contents.begin(), prerender_contents.end(),
1357 &PrerenderContents::DestroyWhenUsingTooManyResources));
1359 // Measure how long the resource checks took. http://crbug.com/305419.
1360 UMA_HISTOGRAM_TIMES("Prerender.PeriodicCleanupResourceCheckTime",
1361 resource_timer.Elapsed());
1363 base::ElapsedTimer cleanup_timer;
1365 // Perform deferred cleanup work.
1366 DeleteOldWebContents();
1368 if (active_prerenders_.empty())
1369 StopSchedulingPeriodicCleanups();
1371 to_delete_prerenders_.clear();
1373 // Measure how long a the various cleanup tasks took. http://crbug.com/305419.
1374 UMA_HISTOGRAM_TIMES("Prerender.PeriodicCleanupDeleteContentsTime",
1375 cleanup_timer.Elapsed());
1378 void PrerenderManager::PostCleanupTask() {
1379 DCHECK(CalledOnValidThread());
1380 base::MessageLoop::current()->PostTask(
1382 base::Bind(&PrerenderManager::PeriodicCleanup, AsWeakPtr()));
1385 base::TimeTicks PrerenderManager::GetExpiryTimeForNewPrerender(
1386 Origin origin) const {
1387 base::TimeDelta ttl = config_.time_to_live;
1388 if (origin == ORIGIN_LOCAL_PREDICTOR)
1389 ttl = base::TimeDelta::FromSeconds(GetLocalPredictorTTLSeconds());
1390 return GetCurrentTimeTicks() + ttl;
1393 base::TimeTicks PrerenderManager::GetExpiryTimeForNavigatedAwayPrerender()
1395 return GetCurrentTimeTicks() + config_.abandon_time_to_live;
1398 void PrerenderManager::DeleteOldEntries() {
1399 DCHECK(CalledOnValidThread());
1400 while (!active_prerenders_.empty()) {
1401 PrerenderData* prerender_data = active_prerenders_.front();
1402 DCHECK(prerender_data);
1403 DCHECK(prerender_data->contents());
1405 if (prerender_data->expiry_time() > GetCurrentTimeTicks())
1407 prerender_data->contents()->Destroy(FINAL_STATUS_TIMED_OUT);
1411 base::Time PrerenderManager::GetCurrentTime() const {
1412 return base::Time::Now();
1415 base::TimeTicks PrerenderManager::GetCurrentTimeTicks() const {
1416 return base::TimeTicks::Now();
1419 PrerenderContents* PrerenderManager::CreatePrerenderContents(
1421 const content::Referrer& referrer,
1423 uint8 experiment_id) {
1424 DCHECK(CalledOnValidThread());
1425 return prerender_contents_factory_->CreatePrerenderContents(
1426 this, profile_, url, referrer, origin, experiment_id);
1429 void PrerenderManager::SortActivePrerenders() {
1430 std::sort(active_prerenders_.begin(), active_prerenders_.end(),
1431 PrerenderData::OrderByExpiryTime());
1434 PrerenderManager::PrerenderData* PrerenderManager::FindPrerenderData(
1436 const SessionStorageNamespace* session_storage_namespace) {
1437 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1438 it != active_prerenders_.end(); ++it) {
1439 if ((*it)->contents()->Matches(url, session_storage_namespace))
1445 PrerenderManager::PrerenderData*
1446 PrerenderManager::FindPrerenderDataForTargetContents(
1447 WebContents* target_contents) {
1448 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1449 it != active_prerenders_.end(); ++it) {
1450 if ((*it)->pending_swap() &&
1451 (*it)->pending_swap()->target_contents() == target_contents)
1457 ScopedVector<PrerenderManager::PrerenderData>::iterator
1458 PrerenderManager::FindIteratorForPrerenderContents(
1459 PrerenderContents* prerender_contents) {
1460 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1461 it != active_prerenders_.end(); ++it) {
1462 if (prerender_contents == (*it)->contents())
1465 return active_prerenders_.end();
1468 bool PrerenderManager::DoesRateLimitAllowPrerender(Origin origin) const {
1469 DCHECK(CalledOnValidThread());
1470 base::TimeDelta elapsed_time =
1471 GetCurrentTimeTicks() - last_prerender_start_time_;
1472 histograms_->RecordTimeBetweenPrerenderRequests(origin, elapsed_time);
1473 if (!config_.rate_limit_enabled)
1475 return elapsed_time >=
1476 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs);
1479 void PrerenderManager::DeleteOldWebContents() {
1480 while (!old_web_contents_list_.empty()) {
1481 WebContents* web_contents = old_web_contents_list_.front();
1482 old_web_contents_list_.pop_front();
1483 // TODO(dominich): should we use Instant Unload Handler here?
1484 delete web_contents;
1488 void PrerenderManager::CleanUpOldNavigations() {
1489 DCHECK(CalledOnValidThread());
1491 // Cutoff. Navigations before this cutoff can be discarded.
1492 base::TimeTicks cutoff = GetCurrentTimeTicks() -
1493 base::TimeDelta::FromMilliseconds(kNavigationRecordWindowMs);
1494 while (!navigations_.empty()) {
1495 if (navigations_.front().time > cutoff)
1497 navigations_.pop_front();
1501 void PrerenderManager::ScheduleDeleteOldWebContents(
1503 OnCloseWebContentsDeleter* deleter) {
1504 old_web_contents_list_.push_back(tab);
1508 ScopedVector<OnCloseWebContentsDeleter>::iterator i = std::find(
1509 on_close_web_contents_deleters_.begin(),
1510 on_close_web_contents_deleters_.end(),
1512 DCHECK(i != on_close_web_contents_deleters_.end());
1513 on_close_web_contents_deleters_.erase(i);
1517 void PrerenderManager::AddToHistory(PrerenderContents* contents) {
1518 PrerenderHistory::Entry entry(contents->prerender_url(),
1519 contents->final_status(),
1522 prerender_history_->AddEntry(entry);
1525 base::Value* PrerenderManager::GetActivePrerendersAsValue() const {
1526 base::ListValue* list_value = new base::ListValue();
1527 for (ScopedVector<PrerenderData>::const_iterator it =
1528 active_prerenders_.begin();
1529 it != active_prerenders_.end(); ++it) {
1530 if (base::Value* prerender_value = (*it)->contents()->GetAsValue())
1531 list_value->Append(prerender_value);
1536 void PrerenderManager::DestroyAllContents(FinalStatus final_status) {
1537 DeleteOldWebContents();
1538 while (!active_prerenders_.empty()) {
1539 PrerenderContents* contents = active_prerenders_.front()->contents();
1540 contents->Destroy(final_status);
1542 to_delete_prerenders_.clear();
1545 void PrerenderManager::DestroyAndMarkMatchCompleteAsUsed(
1546 PrerenderContents* prerender_contents,
1547 FinalStatus final_status) {
1548 prerender_contents->set_match_complete_status(
1549 PrerenderContents::MATCH_COMPLETE_REPLACED);
1550 histograms_->RecordFinalStatus(prerender_contents->origin(),
1551 prerender_contents->experiment_id(),
1552 PrerenderContents::MATCH_COMPLETE_REPLACEMENT,
1553 FINAL_STATUS_WOULD_HAVE_BEEN_USED);
1554 prerender_contents->Destroy(final_status);
1557 void PrerenderManager::RecordFinalStatus(Origin origin,
1558 uint8 experiment_id,
1559 FinalStatus final_status) const {
1560 RecordFinalStatusWithMatchCompleteStatus(
1561 origin, experiment_id,
1562 PrerenderContents::MATCH_COMPLETE_DEFAULT,
1566 bool PrerenderManager::IsEnabled() const {
1567 DCHECK(CalledOnValidThread());
1570 for (std::list<const PrerenderCondition*>::const_iterator it =
1571 prerender_conditions_.begin();
1572 it != prerender_conditions_.end();
1574 const PrerenderCondition* condition = *it;
1575 if (!condition->CanPrerender())
1581 void PrerenderManager::Observe(int type,
1582 const content::NotificationSource& source,
1583 const content::NotificationDetails& details) {
1584 Profile* profile = content::Source<Profile>(source).ptr();
1585 if (!profile || !profile_->IsSameProfile(profile) ||
1586 profile->IsOffTheRecord()) {
1589 DCHECK(type == chrome::NOTIFICATION_COOKIE_CHANGED);
1590 CookieChanged(content::Details<ChromeCookieDetails>(details).ptr());
1593 void PrerenderManager::OnCreatingAudioStream(int render_process_id,
1594 int render_frame_id) {
1595 content::RenderFrameHost* render_frame_host =
1596 content::RenderFrameHost::FromID(render_process_id, render_frame_id);
1597 WebContents* tab = WebContents::FromRenderFrameHost(render_frame_host);
1601 PrerenderContents* prerender_contents = GetPrerenderContents(tab);
1602 if (!prerender_contents)
1605 prerender_contents->Destroy(prerender::FINAL_STATUS_CREATING_AUDIO_STREAM);
1608 void PrerenderManager::RecordLikelyLoginOnURL(const GURL& url) {
1609 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1610 if (!url.SchemeIsHTTPOrHTTPS())
1612 if (logged_in_predictor_table_.get()) {
1613 BrowserThread::PostTask(
1616 base::Bind(&LoggedInPredictorTable::AddDomainFromURL,
1617 logged_in_predictor_table_,
1620 std::string key = LoggedInPredictorTable::GetKey(url);
1621 if (!logged_in_state_.get())
1623 if (logged_in_state_->count(key))
1625 (*logged_in_state_)[key] = base::Time::Now().ToInternalValue();
1628 void PrerenderManager::CheckIfLikelyLoggedInOnURL(
1630 bool* lookup_result,
1631 bool* database_was_present,
1632 const base::Closure& result_cb) {
1633 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1634 if (!logged_in_predictor_table_.get()) {
1635 *database_was_present = false;
1636 *lookup_result = false;
1637 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, result_cb);
1640 BrowserThread::PostTaskAndReply(
1641 BrowserThread::DB, FROM_HERE,
1642 base::Bind(&LoggedInPredictorTable::HasUserLoggedIn,
1643 logged_in_predictor_table_,
1646 database_was_present),
1651 void PrerenderManager::CookieChanged(ChromeCookieDetails* details) {
1652 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1654 if (!logged_in_predictor_table_.get())
1657 // We only care when a cookie has been removed.
1658 if (!details->removed)
1661 std::string domain_key =
1662 LoggedInPredictorTable::GetKeyFromDomain(details->cookie->Domain());
1664 // If we have no record of this domain as a potentially logged in domain,
1665 // nothing to do here.
1666 if (logged_in_state_.get() && logged_in_state_->count(domain_key) < 1)
1669 net::URLRequestContextGetter* rq_context = profile_->GetRequestContext();
1673 BrowserThread::PostTask(
1674 BrowserThread::IO, FROM_HERE,
1675 base::Bind(&CheckIfCookiesExistForDomainOnIOThread,
1676 base::Unretained(rq_context),
1679 &PrerenderManager::CookieChangedAnyCookiesLeftLookupResult,
1685 void PrerenderManager::CookieChangedAnyCookiesLeftLookupResult(
1686 const std::string& domain_key,
1687 bool cookies_exist) {
1688 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1693 if (logged_in_predictor_table_.get()) {
1694 BrowserThread::PostTask(BrowserThread::DB,
1696 base::Bind(&LoggedInPredictorTable::DeleteDomain,
1697 logged_in_predictor_table_,
1701 if (logged_in_state_.get())
1702 logged_in_state_->erase(domain_key);
1705 void PrerenderManager::LoggedInPredictorDataReceived(
1706 scoped_ptr<LoggedInStateMap> new_map) {
1707 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1708 logged_in_state_.swap(new_map);
1711 void PrerenderManager::RecordEvent(PrerenderContents* contents,
1712 PrerenderEvent event) const {
1714 histograms_->RecordEvent(ORIGIN_NONE, kNoExperiment, event);
1716 histograms_->RecordEvent(contents->origin(), contents->experiment_id(),
1721 void PrerenderManager::RecordCookieEvent(int process_id,
1724 const GURL& frame_url,
1725 PrerenderContents::CookieEvent event,
1726 const net::CookieList* cookie_list) {
1727 RenderFrameHost* rfh = RenderFrameHost::FromID(process_id, frame_id);
1728 WebContents* web_contents = WebContents::FromRenderFrameHost(rfh);
1732 bool is_main_frame = (rfh == web_contents->GetMainFrame());
1734 PrerenderContents* prerender_contents =
1735 PrerenderContents::FromWebContents(web_contents);
1737 if (!prerender_contents)
1740 base::Time earliest_create_date;
1741 if (event == PrerenderContents::COOKIE_EVENT_SEND) {
1742 if (!cookie_list || cookie_list->empty())
1744 for (size_t i = 0; i < cookie_list->size(); i++) {
1745 if (earliest_create_date.is_null() ||
1746 (*cookie_list)[i].CreationDate() < earliest_create_date) {
1747 earliest_create_date = (*cookie_list)[i].CreationDate();
1752 prerender_contents->RecordCookieEvent(event,
1753 is_main_frame && url == frame_url,
1754 earliest_create_date);
1757 void PrerenderManager::RecordCookieStatus(Origin origin,
1758 uint8 experiment_id,
1759 int cookie_status) const {
1760 histograms_->RecordCookieStatus(origin, experiment_id, cookie_status);
1763 void PrerenderManager::OnHistoryServiceDidQueryURL(
1765 uint8 experiment_id,
1766 CancelableRequestProvider::Handle handle,
1768 const history::URLRow* url_row,
1769 history::VisitVector* visists) {
1770 histograms_->RecordPrerenderPageVisitedStatus(origin, experiment_id, success);
1774 void PrerenderManager::HangSessionStorageMergesForTesting() {
1775 g_hang_session_storage_merges_for_testing = true;
1778 void PrerenderManager::RecordNetworkBytes(bool used, int64 prerender_bytes) {
1779 if (!ActuallyPrerendering())
1781 int64 recent_profile_bytes =
1782 profile_network_bytes_ - last_recorded_profile_network_bytes_;
1783 last_recorded_profile_network_bytes_ = profile_network_bytes_;
1784 DCHECK_GE(recent_profile_bytes, 0);
1785 histograms_->RecordNetworkBytes(used, prerender_bytes, recent_profile_bytes);
1788 void PrerenderManager::AddProfileNetworkBytesIfEnabled(int64 bytes) {
1789 DCHECK_GE(bytes, 0);
1790 if (IsEnabled() && ActuallyPrerendering())
1791 profile_network_bytes_ += bytes;
1794 } // namespace prerender