Cast: Stop logging kVideoFrameSentToEncoder and rename a couple events.
[chromium-blink-merge.git] / chrome / browser / prerender / prerender_manager.cc
blobaa615b1117c763bea092871580fb6979d3fdb546
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"
7 #include <algorithm>
8 #include <functional>
9 #include <string>
10 #include <vector>
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/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;
76 namespace prerender {
78 namespace {
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[] = {
85 "GET",
86 "HEAD",
87 "OPTIONS",
88 "POST",
89 "TRACE",
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;
133 void CheckIfCookiesExistForDomainResultOnUIThread(
134 const net::CookieMonster::HasCookiesForETLDP1Callback& callback,
135 bool cookies_exist) {
136 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
137 callback.Run(cookies_exist);
140 void CheckIfCookiesExistForDomainResultOnIOThread(
141 const net::CookieMonster::HasCookiesForETLDP1Callback& callback,
142 bool cookies_exist) {
143 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
144 BrowserThread::PostTask(
145 BrowserThread::UI,
146 FROM_HERE,
147 base::Bind(&CheckIfCookiesExistForDomainResultOnUIThread,
148 callback,
149 cookies_exist));
152 void CheckIfCookiesExistForDomainOnIOThread(
153 net::URLRequestContextGetter* rq_context,
154 const std::string& domain_key,
155 const net::CookieMonster::HasCookiesForETLDP1Callback& callback) {
156 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
157 net::CookieStore* cookie_store =
158 rq_context->GetURLRequestContext()->cookie_store();
159 cookie_store->GetCookieMonster()->HasCookiesForETLDP1Async(
160 domain_key,
161 base::Bind(&CheckIfCookiesExistForDomainResultOnIOThread, callback));
164 } // namespace
166 class PrerenderManager::OnCloseWebContentsDeleter
167 : public content::WebContentsDelegate,
168 public base::SupportsWeakPtr<
169 PrerenderManager::OnCloseWebContentsDeleter> {
170 public:
171 OnCloseWebContentsDeleter(PrerenderManager* manager,
172 WebContents* tab)
173 : manager_(manager),
174 tab_(tab),
175 suppressed_dialog_(false) {
176 tab_->SetDelegate(this);
177 base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
178 base::Bind(&OnCloseWebContentsDeleter::ScheduleWebContentsForDeletion,
179 AsWeakPtr(), true),
180 base::TimeDelta::FromSeconds(kDeleteWithExtremePrejudiceSeconds));
183 virtual void CloseContents(WebContents* source) OVERRIDE {
184 DCHECK_EQ(tab_, source);
185 ScheduleWebContentsForDeletion(false);
188 virtual void SwappedOut(WebContents* source) OVERRIDE {
189 DCHECK_EQ(tab_, source);
190 ScheduleWebContentsForDeletion(false);
193 virtual bool ShouldSuppressDialogs() OVERRIDE {
194 // Use this as a proxy for getting statistics on how often we fail to honor
195 // the beforeunload event.
196 suppressed_dialog_ = true;
197 return true;
200 private:
201 static const int kDeleteWithExtremePrejudiceSeconds = 3;
203 void ScheduleWebContentsForDeletion(bool timeout) {
204 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterTimeout", timeout);
205 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterSuppressedDialog",
206 suppressed_dialog_);
207 tab_->SetDelegate(NULL);
208 manager_->ScheduleDeleteOldWebContents(tab_.release(), this);
209 // |this| is deleted at this point.
212 PrerenderManager* manager_;
213 scoped_ptr<WebContents> tab_;
214 bool suppressed_dialog_;
216 DISALLOW_COPY_AND_ASSIGN(OnCloseWebContentsDeleter);
219 // static
220 int PrerenderManager::prerenders_per_session_count_ = 0;
222 // static
223 PrerenderManager::PrerenderManagerMode PrerenderManager::mode_ =
224 PRERENDER_MODE_ENABLED;
226 struct PrerenderManager::NavigationRecord {
227 NavigationRecord(const GURL& url, base::TimeTicks time)
228 : url(url),
229 time(time) {
232 GURL url;
233 base::TimeTicks time;
236 PrerenderManager::PrerenderManager(Profile* profile,
237 PrerenderTracker* prerender_tracker)
238 : enabled_(profile && profile->GetPrefs() &&
239 profile->GetPrefs()->GetBoolean(prefs::kNetworkPredictionEnabled)),
240 profile_(profile),
241 prerender_tracker_(prerender_tracker),
242 prerender_contents_factory_(PrerenderContents::CreateFactory()),
243 last_prerender_start_time_(GetCurrentTimeTicks() -
244 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs)),
245 prerender_history_(new PrerenderHistory(kHistoryLength)),
246 histograms_(new PrerenderHistograms()),
247 profile_network_bytes_(0),
248 last_recorded_profile_network_bytes_(0) {
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);
260 if (predictor_db) {
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_,
268 new_state_map_ptr),
269 base::Bind(&PrerenderManager::LoggedInPredictorDataReceived,
270 AsWeakPtr(),
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;
280 break;
281 case PrerenderManager::PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP:
282 config_.time_to_live = base::TimeDelta::FromMinutes(15);
283 break;
284 default:
285 break;
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());
308 void PrerenderManager::Shutdown() {
309 DestroyAllContents(FINAL_STATUS_MANAGER_SHUTDOWN);
310 STLDeleteElements(&prerender_conditions_);
311 on_close_web_contents_deleters_.clear();
312 // Must happen before |profile_| is set to NULL as
313 // |local_predictor_| accesses it.
314 if (local_predictor_)
315 local_predictor_->Shutdown();
316 profile_ = NULL;
318 DCHECK(active_prerenders_.empty());
321 PrerenderHandle* PrerenderManager::AddPrerenderFromLinkRelPrerender(
322 int process_id,
323 int route_id,
324 const GURL& url,
325 const uint32 rel_types,
326 const content::Referrer& referrer,
327 const gfx::Size& size) {
328 Origin origin = rel_types & PrerenderRelTypePrerender ?
329 ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN :
330 ORIGIN_LINK_REL_NEXT;
331 SessionStorageNamespace* session_storage_namespace = NULL;
332 // Unit tests pass in a process_id == -1.
333 if (process_id != -1) {
334 RenderViewHost* source_render_view_host =
335 RenderViewHost::FromID(process_id, route_id);
336 if (!source_render_view_host)
337 return NULL;
338 WebContents* source_web_contents =
339 WebContents::FromRenderViewHost(source_render_view_host);
340 if (!source_web_contents)
341 return NULL;
342 if (origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN &&
343 source_web_contents->GetURL().host() == url.host()) {
344 origin = ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN;
346 // TODO(ajwong): This does not correctly handle storage for isolated apps.
347 session_storage_namespace =
348 source_web_contents->GetController()
349 .GetDefaultSessionStorageNamespace();
352 return AddPrerender(origin, process_id, url, referrer, size,
353 session_storage_namespace);
356 PrerenderHandle* PrerenderManager::AddPrerenderFromOmnibox(
357 const GURL& url,
358 SessionStorageNamespace* session_storage_namespace,
359 const gfx::Size& size) {
360 if (!IsOmniboxEnabled(profile_))
361 return NULL;
362 return AddPrerender(ORIGIN_OMNIBOX, -1, url, content::Referrer(), size,
363 session_storage_namespace);
366 PrerenderHandle* PrerenderManager::AddPrerenderFromLocalPredictor(
367 const GURL& url,
368 SessionStorageNamespace* session_storage_namespace,
369 const gfx::Size& size) {
370 return AddPrerender(ORIGIN_LOCAL_PREDICTOR, -1, url, content::Referrer(),
371 size, session_storage_namespace);
374 PrerenderHandle* PrerenderManager::AddPrerenderFromExternalRequest(
375 const GURL& url,
376 const content::Referrer& referrer,
377 SessionStorageNamespace* session_storage_namespace,
378 const gfx::Size& size) {
379 return AddPrerender(ORIGIN_EXTERNAL_REQUEST, -1, url, referrer, size,
380 session_storage_namespace);
383 PrerenderHandle* PrerenderManager::AddPrerenderForInstant(
384 const GURL& url,
385 content::SessionStorageNamespace* session_storage_namespace,
386 const gfx::Size& size) {
387 DCHECK(chrome::ShouldPrefetchSearchResults());
388 return AddPrerender(ORIGIN_INSTANT, -1, url, content::Referrer(), size,
389 session_storage_namespace);
392 void PrerenderManager::CancelAllPrerenders() {
393 DCHECK(CalledOnValidThread());
394 while (!active_prerenders_.empty()) {
395 PrerenderContents* prerender_contents =
396 active_prerenders_.front()->contents();
397 prerender_contents->Destroy(FINAL_STATUS_CANCELLED);
401 bool PrerenderManager::MaybeUsePrerenderedPage(const GURL& url,
402 chrome::NavigateParams* params) {
403 DCHECK(CalledOnValidThread());
405 content::WebContents* web_contents = params->target_contents;
406 DCHECK(!IsWebContentsPrerendering(web_contents, NULL));
408 // Don't prerender if the navigation involves some special parameters.
409 if (params->uses_post || !params->extra_headers.empty())
410 return false;
412 DeleteOldEntries();
413 to_delete_prerenders_.clear();
415 // First, try to find prerender data with the correct session storage
416 // namespace.
417 // TODO(ajwong): This doesn't handle isolated apps correctly.
418 PrerenderData* prerender_data = FindPrerenderData(
419 url,
420 web_contents->GetController().GetDefaultSessionStorageNamespace());
422 // If this failed, we may still find a prerender for the same URL, but a
423 // different session storage namespace. If we do, we might have to perform
424 // a merge.
425 if (!prerender_data) {
426 prerender_data = FindPrerenderData(url, NULL);
427 } else {
428 RecordEvent(prerender_data->contents(),
429 PRERENDER_EVENT_SWAPIN_CANDIDATE_NAMESPACE_MATCHES);
432 if (!prerender_data)
433 return false;
434 RecordEvent(prerender_data->contents(), PRERENDER_EVENT_SWAPIN_CANDIDATE);
435 DCHECK(prerender_data->contents());
437 // If there is currently a merge pending for this prerender data, don't swap.
438 if (prerender_data->pending_swap())
439 return false;
441 // Abort any existing pending swap on the target contents.
442 PrerenderData* pending_swap =
443 FindPrerenderDataForTargetContents(web_contents);
444 if (pending_swap) {
445 pending_swap->ClearPendingSwap();
446 DCHECK(FindPrerenderDataForTargetContents(web_contents) == NULL);
449 RecordEvent(prerender_data->contents(),
450 PRERENDER_EVENT_SWAPIN_NO_MERGE_PENDING);
451 SessionStorageNamespace* target_namespace =
452 web_contents->GetController().GetDefaultSessionStorageNamespace();
453 SessionStorageNamespace* prerender_namespace =
454 prerender_data->contents()->GetSessionStorageNamespace();
455 // Only when actually prerendering is session storage namespace merging an
456 // issue. For the control group, it will be assumed that the merge succeeded.
457 if (prerender_namespace && prerender_namespace != target_namespace &&
458 !prerender_namespace->IsAliasOf(target_namespace)) {
459 if (!ShouldMergeSessionStorageNamespaces()) {
460 RecordEvent(prerender_data->contents(),
461 PRERENDER_EVENT_SWAPIN_MERGING_DISABLED);
462 return false;
464 RecordEvent(prerender_data->contents(),
465 PRERENDER_EVENT_SWAPIN_ISSUING_MERGE);
466 prerender_data->set_pending_swap(new PendingSwap(
467 this, web_contents, prerender_data, url,
468 params->should_replace_current_entry));
469 prerender_data->pending_swap()->BeginSwap();
470 // Although this returns false, creating a PendingSwap registers with
471 // PrerenderTracker to throttle MAIN_FRAME navigations while the swap is
472 // pending.
473 return false;
476 // No need to merge; swap synchronously.
477 WebContents* new_web_contents = SwapInternal(
478 url, web_contents, prerender_data,
479 params->should_replace_current_entry);
480 if (!new_web_contents)
481 return false;
483 // Record the new target_contents for the callers.
484 params->target_contents = new_web_contents;
485 return true;
488 WebContents* PrerenderManager::SwapInternal(
489 const GURL& url,
490 WebContents* web_contents,
491 PrerenderData* prerender_data,
492 bool should_replace_current_entry) {
493 DCHECK(CalledOnValidThread());
494 DCHECK(!IsWebContentsPrerendering(web_contents, NULL));
496 // Only swap if the target WebContents has a CoreTabHelper delegate to swap
497 // out of it. For a normal WebContents, this is if it is in a TabStripModel.
498 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(web_contents);
499 if (!core_tab_helper || !core_tab_helper->delegate()) {
500 RecordEvent(prerender_data->contents(), PRERENDER_EVENT_SWAPIN_NO_DELEGATE);
501 return NULL;
504 PrerenderTabHelper* target_tab_helper =
505 PrerenderTabHelper::FromWebContents(web_contents);
506 if (!target_tab_helper) {
507 NOTREACHED();
508 return NULL;
511 if (IsNoSwapInExperiment(prerender_data->contents()->experiment_id()))
512 return NULL;
514 if (WebContents* new_web_contents =
515 prerender_data->contents()->prerender_contents()) {
516 if (web_contents == new_web_contents)
517 return NULL; // Do not swap in to ourself.
519 // We cannot swap in if there is no last committed entry, because we would
520 // show a blank page under an existing entry from the current tab. Even if
521 // there is a pending entry, it may not commit.
522 // TODO(creis): If there is a pending navigation and no last committed
523 // entry, we might be able to transfer the network request instead.
524 if (!new_web_contents->GetController().CanPruneAllButLastCommitted()) {
525 // Abort this prerender so it is not used later. http://crbug.com/292121
526 prerender_data->contents()->Destroy(FINAL_STATUS_NAVIGATION_UNCOMMITTED);
527 return NULL;
531 // Do not use the prerendered version if there is an opener object.
532 if (web_contents->HasOpener()) {
533 prerender_data->contents()->Destroy(FINAL_STATUS_WINDOW_OPENER);
534 return NULL;
537 // Do not swap in the prerender if the current WebContents is being captured.
538 if (web_contents->GetCapturerCount() > 0) {
539 prerender_data->contents()->Destroy(FINAL_STATUS_PAGE_BEING_CAPTURED);
540 return NULL;
543 // If we are just in the control group (which can be detected by noticing
544 // that prerendering hasn't even started yet), record that |web_contents| now
545 // would be showing a prerendered contents, but otherwise, don't do anything.
546 if (!prerender_data->contents()->prerendering_has_started()) {
547 target_tab_helper->WouldHavePrerenderedNextLoad(
548 prerender_data->contents()->origin());
549 prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED);
550 return NULL;
553 // Don't use prerendered pages if debugger is attached to the tab.
554 // See http://crbug.com/98541
555 if (content::DevToolsAgentHost::IsDebuggerAttached(web_contents)) {
556 DestroyAndMarkMatchCompleteAsUsed(prerender_data->contents(),
557 FINAL_STATUS_DEVTOOLS_ATTACHED);
558 return NULL;
561 // If the prerendered page is in the middle of a cross-site navigation,
562 // don't swap it in because there isn't a good way to merge histories.
563 if (prerender_data->contents()->IsCrossSiteNavigationPending()) {
564 DestroyAndMarkMatchCompleteAsUsed(
565 prerender_data->contents(),
566 FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING);
567 return NULL;
570 // For bookkeeping purposes, we need to mark this WebContents to
571 // reflect that it would have been prerendered.
572 if (GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP) {
573 target_tab_helper->WouldHavePrerenderedNextLoad(
574 prerender_data->contents()->origin());
575 prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED);
576 return NULL;
579 // At this point, we've determined that we will use the prerender.
580 if (!prerender_data->contents()->load_start_time().is_null()) {
581 histograms_->RecordTimeUntilUsed(
582 prerender_data->contents()->origin(),
583 GetCurrentTimeTicks() - prerender_data->contents()->load_start_time());
585 histograms_->RecordAbandonTimeUntilUsed(
586 prerender_data->contents()->origin(),
587 prerender_data->abandon_time().is_null() ?
588 base::TimeDelta() :
589 GetCurrentTimeTicks() - prerender_data->abandon_time());
591 histograms_->RecordPerSessionCount(prerender_data->contents()->origin(),
592 ++prerenders_per_session_count_);
593 histograms_->RecordUsedPrerender(prerender_data->contents()->origin());
595 if (prerender_data->pending_swap())
596 prerender_data->pending_swap()->set_swap_successful(true);
597 ScopedVector<PrerenderData>::iterator to_erase =
598 FindIteratorForPrerenderContents(prerender_data->contents());
599 DCHECK(active_prerenders_.end() != to_erase);
600 DCHECK_EQ(prerender_data, *to_erase);
601 scoped_ptr<PrerenderContents>
602 prerender_contents(prerender_data->ReleaseContents());
603 active_prerenders_.erase(to_erase);
605 // Mark prerender as used.
606 prerender_contents->PrepareForUse();
608 WebContents* new_web_contents =
609 prerender_contents->ReleasePrerenderContents();
610 WebContents* old_web_contents = web_contents;
611 DCHECK(new_web_contents);
612 DCHECK(old_web_contents);
614 // Merge the browsing history.
615 new_web_contents->GetController().CopyStateFromAndPrune(
616 &old_web_contents->GetController(),
617 should_replace_current_entry);
618 CoreTabHelper::FromWebContents(old_web_contents)->delegate()->
619 SwapTabContents(old_web_contents,
620 new_web_contents,
621 true,
622 prerender_contents->has_finished_loading());
623 prerender_contents->CommitHistory(new_web_contents);
625 // Update PPLT metrics:
626 // If the tab has finished loading, record a PPLT of 0.
627 // If the tab is still loading, reset its start time to the current time.
628 PrerenderTabHelper* prerender_tab_helper =
629 PrerenderTabHelper::FromWebContents(new_web_contents);
630 DCHECK(prerender_tab_helper != NULL);
631 prerender_tab_helper->PrerenderSwappedIn();
633 if (old_web_contents->NeedToFireBeforeUnload()) {
634 // Schedule the delete to occur after the tab has run its unload handlers.
635 // TODO(davidben): Honor the beforeunload event. http://crbug.com/304932
636 on_close_web_contents_deleters_.push_back(
637 new OnCloseWebContentsDeleter(this, old_web_contents));
638 old_web_contents->DispatchBeforeUnload(false);
639 } else {
640 // No unload handler to run, so delete asap.
641 ScheduleDeleteOldWebContents(old_web_contents, NULL);
644 // TODO(cbentzel): Should prerender_contents move to the pending delete
645 // list, instead of deleting directly here?
646 AddToHistory(prerender_contents.get());
647 RecordNavigation(url);
648 return new_web_contents;
651 void PrerenderManager::MoveEntryToPendingDelete(PrerenderContents* entry,
652 FinalStatus final_status) {
653 DCHECK(CalledOnValidThread());
654 DCHECK(entry);
656 ScopedVector<PrerenderData>::iterator it =
657 FindIteratorForPrerenderContents(entry);
658 DCHECK(it != active_prerenders_.end());
660 // If this PrerenderContents is being deleted due to a cancellation any time
661 // after the prerender has started then we need to create a dummy replacement
662 // for PPLT accounting purposes for the Match Complete group. This is the case
663 // if the cancellation is for any reason that would not occur in the control
664 // group case.
665 if (entry->prerendering_has_started() &&
666 entry->match_complete_status() ==
667 PrerenderContents::MATCH_COMPLETE_DEFAULT &&
668 NeedMatchCompleteDummyForFinalStatus(final_status) &&
669 ActuallyPrerendering()) {
670 // TODO(tburkard): I'd like to DCHECK that we are actually prerendering.
671 // However, what if new conditions are added and
672 // NeedMatchCompleteDummyForFinalStatus is not being updated. Not sure
673 // what's the best thing to do here. For now, I will just check whether
674 // we are actually prerendering.
675 (*it)->MakeIntoMatchCompleteReplacement();
676 } else {
677 to_delete_prerenders_.push_back(*it);
678 (*it)->ClearPendingSwap();
679 active_prerenders_.weak_erase(it);
682 // Destroy the old WebContents relatively promptly to reduce resource usage.
683 PostCleanupTask();
686 void PrerenderManager::RecordPageLoadTimeNotSwappedIn(
687 Origin origin,
688 base::TimeDelta page_load_time,
689 const GURL& url) {
690 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
691 histograms_->RecordPageLoadTimeNotSwappedIn(origin, page_load_time, url);
694 void PrerenderManager::RecordPerceivedPageLoadTime(
695 Origin origin,
696 NavigationType navigation_type,
697 base::TimeDelta perceived_page_load_time,
698 double fraction_plt_elapsed_at_swap_in,
699 const GURL& url) {
700 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
701 if (!IsEnabled())
702 return;
704 histograms_->RecordPerceivedPageLoadTime(
705 origin, perceived_page_load_time, navigation_type, url);
707 if (navigation_type == NAVIGATION_TYPE_PRERENDERED) {
708 histograms_->RecordPercentLoadDoneAtSwapin(
709 origin, fraction_plt_elapsed_at_swap_in);
711 if (local_predictor_) {
712 local_predictor_->OnPLTEventForURL(url, perceived_page_load_time);
716 void PrerenderManager::set_enabled(bool enabled) {
717 DCHECK(CalledOnValidThread());
718 enabled_ = enabled;
721 // static
722 PrerenderManager::PrerenderManagerMode PrerenderManager::GetMode() {
723 return mode_;
726 // static
727 void PrerenderManager::SetMode(PrerenderManagerMode mode) {
728 mode_ = mode;
731 // static
732 const char* PrerenderManager::GetModeString() {
733 switch (mode_) {
734 case PRERENDER_MODE_DISABLED:
735 return "_Disabled";
736 case PRERENDER_MODE_ENABLED:
737 case PRERENDER_MODE_EXPERIMENT_PRERENDER_GROUP:
738 return "_Enabled";
739 case PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP:
740 return "_Control";
741 case PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP:
742 return "_Multi";
743 case PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP:
744 return "_15MinTTL";
745 case PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP:
746 return "_NoUse";
747 case PRERENDER_MODE_MAX:
748 default:
749 NOTREACHED() << "Invalid PrerenderManager mode.";
750 break;
752 return "";
755 // static
756 bool PrerenderManager::IsPrerenderingPossible() {
757 return GetMode() != PRERENDER_MODE_DISABLED;
760 // static
761 bool PrerenderManager::ActuallyPrerendering() {
762 return IsPrerenderingPossible() && !IsControlGroup(kNoExperiment);
765 // static
766 bool PrerenderManager::IsControlGroup(uint8 experiment_id) {
767 return GetMode() == PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP ||
768 IsControlGroupExperiment(experiment_id);
771 // static
772 bool PrerenderManager::IsNoUseGroup() {
773 return GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP;
776 bool PrerenderManager::IsWebContentsPrerendering(
777 const WebContents* web_contents,
778 Origin* origin) const {
779 DCHECK(CalledOnValidThread());
780 if (PrerenderContents* prerender_contents =
781 GetPrerenderContents(web_contents)) {
782 if (origin)
783 *origin = prerender_contents->origin();
784 return true;
786 return false;
789 bool PrerenderManager::HasPrerenderedUrl(
790 GURL url,
791 content::WebContents* web_contents) const {
792 content::SessionStorageNamespace* session_storage_namespace = web_contents->
793 GetController().GetDefaultSessionStorageNamespace();
795 for (ScopedVector<PrerenderData>::const_iterator it =
796 active_prerenders_.begin();
797 it != active_prerenders_.end(); ++it) {
798 PrerenderContents* prerender_contents = (*it)->contents();
799 if (prerender_contents->Matches(url, session_storage_namespace)) {
800 return true;
803 return false;
806 PrerenderContents* PrerenderManager::GetPrerenderContents(
807 const content::WebContents* web_contents) const {
808 DCHECK(CalledOnValidThread());
809 for (ScopedVector<PrerenderData>::const_iterator it =
810 active_prerenders_.begin();
811 it != active_prerenders_.end(); ++it) {
812 WebContents* prerender_web_contents =
813 (*it)->contents()->prerender_contents();
814 if (prerender_web_contents == web_contents) {
815 return (*it)->contents();
819 // Also check the pending-deletion list. If the prerender is in pending
820 // delete, anyone with a handle on the WebContents needs to know.
821 for (ScopedVector<PrerenderData>::const_iterator it =
822 to_delete_prerenders_.begin();
823 it != to_delete_prerenders_.end(); ++it) {
824 WebContents* prerender_web_contents =
825 (*it)->contents()->prerender_contents();
826 if (prerender_web_contents == web_contents) {
827 return (*it)->contents();
830 return NULL;
833 PrerenderContents* PrerenderManager::GetPrerenderContentsForRoute(
834 int child_id,
835 int route_id) const {
836 content::WebContents* web_contents =
837 tab_util::GetWebContentsByID(child_id, route_id);
838 if (web_contents == NULL)
839 return NULL;
840 return GetPrerenderContents(web_contents);
843 const std::vector<WebContents*>
844 PrerenderManager::GetAllPrerenderingContents() const {
845 DCHECK(CalledOnValidThread());
846 std::vector<WebContents*> result;
848 for (ScopedVector<PrerenderData>::const_iterator it =
849 active_prerenders_.begin();
850 it != active_prerenders_.end(); ++it) {
851 if (WebContents* contents = (*it)->contents()->prerender_contents())
852 result.push_back(contents);
855 return result;
858 bool PrerenderManager::HasRecentlyBeenNavigatedTo(Origin origin,
859 const GURL& url) {
860 DCHECK(CalledOnValidThread());
862 CleanUpOldNavigations();
863 std::list<NavigationRecord>::const_reverse_iterator end = navigations_.rend();
864 for (std::list<NavigationRecord>::const_reverse_iterator it =
865 navigations_.rbegin();
866 it != end;
867 ++it) {
868 if (it->url == url) {
869 base::TimeDelta delta = GetCurrentTimeTicks() - it->time;
870 histograms_->RecordTimeSinceLastRecentVisit(origin, delta);
871 return true;
875 return false;
878 // static
879 bool PrerenderManager::IsValidHttpMethod(const std::string& method) {
880 // method has been canonicalized to upper case at this point so we can just
881 // compare them.
882 DCHECK_EQ(method, StringToUpperASCII(method));
883 for (size_t i = 0; i < arraysize(kValidHttpMethods); ++i) {
884 if (method.compare(kValidHttpMethods[i]) == 0)
885 return true;
888 return false;
891 // static
892 bool PrerenderManager::DoesURLHaveValidScheme(const GURL& url) {
893 return (url.SchemeIsHTTPOrHTTPS() ||
894 url.SchemeIs(extensions::kExtensionScheme) ||
895 url.SchemeIs("data"));
898 // static
899 bool PrerenderManager::DoesSubresourceURLHaveValidScheme(const GURL& url) {
900 return DoesURLHaveValidScheme(url) || url == GURL(content::kAboutBlankURL);
903 base::DictionaryValue* PrerenderManager::GetAsValue() const {
904 DCHECK(CalledOnValidThread());
905 base::DictionaryValue* dict_value = new base::DictionaryValue();
906 dict_value->Set("history", prerender_history_->GetEntriesAsValue());
907 dict_value->Set("active", GetActivePrerendersAsValue());
908 dict_value->SetBoolean("enabled", enabled_);
909 dict_value->SetBoolean("omnibox_enabled", IsOmniboxEnabled(profile_));
910 // If prerender is disabled via a flag this method is not even called.
911 std::string enabled_note;
912 if (IsControlGroup(kNoExperiment))
913 enabled_note += "(Control group: Not actually prerendering) ";
914 if (IsNoUseGroup())
915 enabled_note += "(No-use group: Not swapping in prerendered pages) ";
916 if (GetMode() == PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP)
917 enabled_note +=
918 "(15 min TTL group: Extended prerender eviction to 15 mins) ";
919 dict_value->SetString("enabled_note", enabled_note);
920 return dict_value;
923 void PrerenderManager::ClearData(int clear_flags) {
924 DCHECK_GE(clear_flags, 0);
925 DCHECK_LT(clear_flags, CLEAR_MAX);
926 if (clear_flags & CLEAR_PRERENDER_CONTENTS)
927 DestroyAllContents(FINAL_STATUS_CACHE_OR_HISTORY_CLEARED);
928 // This has to be second, since destroying prerenders can add to the history.
929 if (clear_flags & CLEAR_PRERENDER_HISTORY)
930 prerender_history_->Clear();
933 void PrerenderManager::RecordFinalStatusWithMatchCompleteStatus(
934 Origin origin,
935 uint8 experiment_id,
936 PrerenderContents::MatchCompleteStatus mc_status,
937 FinalStatus final_status) const {
938 histograms_->RecordFinalStatus(origin,
939 experiment_id,
940 mc_status,
941 final_status);
944 void PrerenderManager::AddCondition(const PrerenderCondition* condition) {
945 prerender_conditions_.push_back(condition);
948 void PrerenderManager::RecordNavigation(const GURL& url) {
949 DCHECK(CalledOnValidThread());
951 navigations_.push_back(NavigationRecord(url, GetCurrentTimeTicks()));
952 CleanUpOldNavigations();
955 // protected
956 struct PrerenderManager::PrerenderData::OrderByExpiryTime {
957 bool operator()(const PrerenderData* a, const PrerenderData* b) const {
958 return a->expiry_time() < b->expiry_time();
962 PrerenderManager::PrerenderData::PrerenderData(PrerenderManager* manager,
963 PrerenderContents* contents,
964 base::TimeTicks expiry_time)
965 : manager_(manager),
966 contents_(contents),
967 handle_count_(0),
968 expiry_time_(expiry_time) {
969 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
972 PrerenderManager::PrerenderData::~PrerenderData() {
975 void PrerenderManager::PrerenderData::MakeIntoMatchCompleteReplacement() {
976 DCHECK(contents_);
977 contents_->set_match_complete_status(
978 PrerenderContents::MATCH_COMPLETE_REPLACED);
979 PrerenderData* to_delete = new PrerenderData(manager_, contents_.release(),
980 expiry_time_);
981 contents_.reset(to_delete->contents_->CreateMatchCompleteReplacement());
982 manager_->to_delete_prerenders_.push_back(to_delete);
985 void PrerenderManager::PrerenderData::OnHandleCreated(PrerenderHandle* handle) {
986 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
987 ++handle_count_;
988 contents_->AddObserver(handle);
991 void PrerenderManager::PrerenderData::OnHandleNavigatedAway(
992 PrerenderHandle* handle) {
993 DCHECK_LT(0, handle_count_);
994 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
995 if (abandon_time_.is_null())
996 abandon_time_ = base::TimeTicks::Now();
997 // We intentionally don't decrement the handle count here, so that the
998 // prerender won't be canceled until it times out.
999 manager_->SourceNavigatedAway(this);
1002 void PrerenderManager::PrerenderData::OnHandleCanceled(
1003 PrerenderHandle* handle) {
1004 DCHECK_LT(0, handle_count_);
1005 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
1007 if (--handle_count_ == 0) {
1008 // This will eventually remove this object from active_prerenders_.
1009 contents_->Destroy(FINAL_STATUS_CANCELLED);
1013 void PrerenderManager::PrerenderData::ClearPendingSwap() {
1014 pending_swap_.reset(NULL);
1017 PrerenderContents* PrerenderManager::PrerenderData::ReleaseContents() {
1018 return contents_.release();
1021 PrerenderManager::PendingSwap::PendingSwap(
1022 PrerenderManager* manager,
1023 content::WebContents* target_contents,
1024 PrerenderData* prerender_data,
1025 const GURL& url,
1026 bool should_replace_current_entry)
1027 : content::WebContentsObserver(target_contents),
1028 manager_(manager),
1029 prerender_data_(prerender_data),
1030 url_(url),
1031 should_replace_current_entry_(should_replace_current_entry),
1032 start_time_(base::TimeTicks::Now()),
1033 seen_target_route_id_(false),
1034 swap_successful_(false),
1035 weak_factory_(this) {
1038 PrerenderManager::PendingSwap::~PendingSwap() {
1039 manager_->prerender_tracker()->RemovePrerenderPendingSwap(
1040 target_route_id_, swap_successful_);
1043 WebContents* PrerenderManager::PendingSwap::target_contents() const {
1044 return web_contents();
1047 void PrerenderManager::PendingSwap::BeginSwap() {
1048 if (g_hang_session_storage_merges_for_testing)
1049 return;
1051 SessionStorageNamespace* target_namespace =
1052 target_contents()->GetController().GetDefaultSessionStorageNamespace();
1053 SessionStorageNamespace* prerender_namespace =
1054 prerender_data_->contents()->GetSessionStorageNamespace();
1056 prerender_namespace->Merge(
1057 true, prerender_data_->contents()->child_id(),
1058 target_namespace,
1059 base::Bind(&PrerenderManager::PendingSwap::OnMergeCompleted,
1060 weak_factory_.GetWeakPtr()));
1062 merge_timeout_.Start(
1063 FROM_HERE,
1064 base::TimeDelta::FromMilliseconds(
1065 kSessionStorageNamespaceMergeTimeoutMs),
1066 this, &PrerenderManager::PendingSwap::OnMergeTimeout);
1069 void PrerenderManager::PendingSwap::AboutToNavigateRenderView(
1070 RenderViewHost* render_view_host) {
1071 if (seen_target_route_id_) {
1072 // A second navigation began browser-side.
1073 prerender_data_->ClearPendingSwap();
1074 return;
1077 seen_target_route_id_ = true;
1078 target_route_id_ = PrerenderTracker::ChildRouteIdPair(
1079 render_view_host->GetMainFrame()->GetProcess()->GetID(),
1080 render_view_host->GetMainFrame()->GetRoutingID());
1081 manager_->prerender_tracker()->AddPrerenderPendingSwap(
1082 target_route_id_, url_);
1085 void PrerenderManager::PendingSwap::ProvisionalChangeToMainFrameUrl(
1086 const GURL& url,
1087 content::RenderFrameHost* render_frame_host) {
1088 // We must only cancel the pending swap if the |url| navigated to is not
1089 // the URL being attempted to be swapped in. That's because in the normal
1090 // flow, a ProvisionalChangeToMainFrameUrl will happen for the URL attempted
1091 // to be swapped in immediately after the pending swap has issued its merge.
1092 if (url != url_)
1093 prerender_data_->ClearPendingSwap();
1096 void PrerenderManager::PendingSwap::DidCommitProvisionalLoadForFrame(
1097 int64 frame_id,
1098 const base::string16& frame_unique_name,
1099 bool is_main_frame,
1100 const GURL& validated_url,
1101 content::PageTransition transition_type,
1102 content::RenderViewHost* render_view_host){
1103 if (!is_main_frame)
1104 return;
1105 prerender_data_->ClearPendingSwap();
1108 void PrerenderManager::PendingSwap::DidFailProvisionalLoad(
1109 int64 frame_id,
1110 const base::string16& frame_unique_name,
1111 bool is_main_frame,
1112 const GURL& validated_url,
1113 int error_code,
1114 const base::string16& error_description,
1115 content::RenderViewHost* render_view_host) {
1116 if (!is_main_frame)
1117 return;
1118 prerender_data_->ClearPendingSwap();
1121 void PrerenderManager::PendingSwap::WebContentsDestroyed(
1122 content::WebContents* web_contents) {
1123 prerender_data_->ClearPendingSwap();
1126 void PrerenderManager::PendingSwap::RecordEvent(PrerenderEvent event) const {
1127 manager_->RecordEvent(prerender_data_->contents(), event);
1130 void PrerenderManager::PendingSwap::OnMergeCompleted(
1131 SessionStorageNamespace::MergeResult result) {
1132 UMA_HISTOGRAM_TIMES("Prerender.SessionStorageNamespaceMergeTime",
1133 base::TimeTicks::Now() - start_time_);
1134 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_MERGE_DONE);
1136 // Log the exact merge result in a histogram.
1137 switch (result) {
1138 case SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_FOUND:
1139 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NAMESPACE_NOT_FOUND);
1140 break;
1141 case SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_ALIAS:
1142 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NAMESPACE_NOT_ALIAS);
1143 break;
1144 case SessionStorageNamespace::MERGE_RESULT_NOT_LOGGING:
1145 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NOT_LOGGING);
1146 break;
1147 case SessionStorageNamespace::MERGE_RESULT_NO_TRANSACTIONS:
1148 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NO_TRANSACTIONS);
1149 break;
1150 case SessionStorageNamespace::MERGE_RESULT_TOO_MANY_TRANSACTIONS:
1151 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_TOO_MANY_TRANSACTIONS);
1152 break;
1153 case SessionStorageNamespace::MERGE_RESULT_NOT_MERGEABLE:
1154 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NOT_MERGEABLE);
1155 break;
1156 case SessionStorageNamespace::MERGE_RESULT_MERGEABLE:
1157 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_MERGEABLE);
1158 break;
1159 default:
1160 NOTREACHED();
1163 if (result != SessionStorageNamespace::MERGE_RESULT_MERGEABLE &&
1164 result != SessionStorageNamespace::MERGE_RESULT_NO_TRANSACTIONS) {
1165 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_MERGE_FAILED);
1166 prerender_data_->ClearPendingSwap();
1167 return;
1170 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_SWAPPING_IN);
1172 // Note that SwapInternal will, on success, delete |prerender_data_| and
1173 // |this|. It will also delete |this| in some failure cases. Pass in a new
1174 // GURL object rather than a reference to |url_|. Also hold on to |manager_|
1175 // and |prerender_data_|.
1177 // TODO(davidben): Can we make this less fragile?
1178 PrerenderManager* manager = manager_;
1179 PrerenderData* prerender_data = prerender_data_;
1180 WebContents* new_web_contents = manager_->SwapInternal(
1181 GURL(url_), target_contents(), prerender_data_,
1182 should_replace_current_entry_);
1183 if (!new_web_contents) {
1184 manager->RecordEvent(prerender_data->contents(),
1185 PRERENDER_EVENT_MERGE_RESULT_SWAPIN_FAILED);
1186 // Depending on whether SwapInternal called Destroy() or simply failed to
1187 // swap, |this| may or may not be deleted. Either way, if the swap failed,
1188 // |prerender_data| is deleted asynchronously, so this call is a no-op if
1189 // |this| is already gone.
1190 prerender_data->ClearPendingSwap();
1194 void PrerenderManager::PendingSwap::OnMergeTimeout() {
1195 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_TIMED_OUT);
1196 prerender_data_->ClearPendingSwap();
1199 void PrerenderManager::SetPrerenderContentsFactory(
1200 PrerenderContents::Factory* prerender_contents_factory) {
1201 DCHECK(CalledOnValidThread());
1202 prerender_contents_factory_.reset(prerender_contents_factory);
1205 void PrerenderManager::SourceNavigatedAway(PrerenderData* prerender_data) {
1206 // The expiry time of our prerender data will likely change because of
1207 // this navigation. This requires a resort of active_prerenders_.
1208 ScopedVector<PrerenderData>::iterator it =
1209 std::find(active_prerenders_.begin(), active_prerenders_.end(),
1210 prerender_data);
1211 if (it == active_prerenders_.end())
1212 return;
1214 (*it)->set_expiry_time(
1215 std::min((*it)->expiry_time(),
1216 GetExpiryTimeForNavigatedAwayPrerender()));
1217 SortActivePrerenders();
1220 // private
1221 PrerenderHandle* PrerenderManager::AddPrerender(
1222 Origin origin,
1223 int process_id,
1224 const GURL& url_arg,
1225 const content::Referrer& referrer,
1226 const gfx::Size& size,
1227 SessionStorageNamespace* session_storage_namespace) {
1228 DCHECK(CalledOnValidThread());
1230 if (!IsEnabled())
1231 return NULL;
1233 if ((origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN ||
1234 origin == ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN) &&
1235 IsGoogleSearchResultURL(referrer.url)) {
1236 origin = ORIGIN_GWS_PRERENDER;
1239 GURL url = url_arg;
1240 GURL alias_url;
1241 uint8 experiment = GetQueryStringBasedExperiment(url_arg);
1242 if (IsControlGroup(experiment) &&
1243 MaybeGetQueryStringBasedAliasURL(url, &alias_url)) {
1244 url = alias_url;
1247 // From here on, we will record a FinalStatus so we need to register with the
1248 // histogram tracking.
1249 histograms_->RecordPrerender(origin, url_arg);
1251 if (PrerenderData* preexisting_prerender_data =
1252 FindPrerenderData(url, session_storage_namespace)) {
1253 RecordFinalStatus(origin, experiment, FINAL_STATUS_DUPLICATE);
1254 return new PrerenderHandle(preexisting_prerender_data);
1257 // Do not prerender if there are too many render processes, and we would
1258 // have to use an existing one. We do not want prerendering to happen in
1259 // a shared process, so that we can always reliably lower the CPU
1260 // priority for prerendering.
1261 // In single-process mode, ShouldTryToUseExistingProcessHost() always returns
1262 // true, so that case needs to be explicitly checked for.
1263 // TODO(tburkard): Figure out how to cancel prerendering in the opposite
1264 // case, when a new tab is added to a process used for prerendering.
1265 // TODO(ppi): Check whether there are usually enough render processes
1266 // available on Android. If not, kill an existing renderers so that we can
1267 // create a new one.
1268 if (content::RenderProcessHost::ShouldTryToUseExistingProcessHost(
1269 profile_, url) &&
1270 !content::RenderProcessHost::run_renderer_in_process()) {
1271 RecordFinalStatus(origin, experiment, FINAL_STATUS_TOO_MANY_PROCESSES);
1272 return NULL;
1275 // Check if enough time has passed since the last prerender.
1276 if (!DoesRateLimitAllowPrerender(origin)) {
1277 // Cancel the prerender. We could add it to the pending prerender list but
1278 // this doesn't make sense as the next prerender request will be triggered
1279 // by a navigation and is unlikely to be the same site.
1280 RecordFinalStatus(origin, experiment, FINAL_STATUS_RATE_LIMIT_EXCEEDED);
1281 return NULL;
1284 PrerenderContents* prerender_contents = CreatePrerenderContents(
1285 url, referrer, origin, experiment);
1286 DCHECK(prerender_contents);
1287 active_prerenders_.push_back(
1288 new PrerenderData(this, prerender_contents,
1289 GetExpiryTimeForNewPrerender(origin)));
1290 if (!prerender_contents->Init()) {
1291 DCHECK(active_prerenders_.end() ==
1292 FindIteratorForPrerenderContents(prerender_contents));
1293 return NULL;
1296 histograms_->RecordPrerenderStarted(origin);
1297 DCHECK(!prerender_contents->prerendering_has_started());
1299 PrerenderHandle* prerender_handle =
1300 new PrerenderHandle(active_prerenders_.back());
1301 SortActivePrerenders();
1303 last_prerender_start_time_ = GetCurrentTimeTicks();
1305 gfx::Size contents_size =
1306 size.IsEmpty() ? config_.default_tab_bounds.size() : size;
1308 prerender_contents->StartPrerendering(process_id, contents_size,
1309 session_storage_namespace);
1311 DCHECK(IsControlGroup(experiment) ||
1312 prerender_contents->prerendering_has_started());
1314 if (GetMode() == PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP)
1315 histograms_->RecordConcurrency(active_prerenders_.size());
1317 // Query the history to see if the URL being prerendered has ever been
1318 // visited before.
1319 HistoryService* history_service = HistoryServiceFactory::GetForProfile(
1320 profile_, Profile::EXPLICIT_ACCESS);
1321 if (history_service) {
1322 history_service->QueryURL(
1323 url,
1324 false,
1325 &query_url_consumer_,
1326 base::Bind(&PrerenderManager::OnHistoryServiceDidQueryURL,
1327 base::Unretained(this),
1328 origin,
1329 experiment));
1332 StartSchedulingPeriodicCleanups();
1333 return prerender_handle;
1336 void PrerenderManager::StartSchedulingPeriodicCleanups() {
1337 DCHECK(CalledOnValidThread());
1338 if (repeating_timer_.IsRunning())
1339 return;
1340 repeating_timer_.Start(FROM_HERE,
1341 base::TimeDelta::FromMilliseconds(kPeriodicCleanupIntervalMs),
1342 this,
1343 &PrerenderManager::PeriodicCleanup);
1346 void PrerenderManager::StopSchedulingPeriodicCleanups() {
1347 DCHECK(CalledOnValidThread());
1348 repeating_timer_.Stop();
1351 void PrerenderManager::PeriodicCleanup() {
1352 DCHECK(CalledOnValidThread());
1354 base::ElapsedTimer resource_timer;
1356 // Grab a copy of the current PrerenderContents pointers, so that we
1357 // will not interfere with potential deletions of the list.
1358 std::vector<PrerenderContents*>
1359 prerender_contents(active_prerenders_.size());
1360 std::transform(active_prerenders_.begin(), active_prerenders_.end(),
1361 prerender_contents.begin(),
1362 std::mem_fun(&PrerenderData::contents));
1364 // And now check for prerenders using too much memory.
1365 std::for_each(prerender_contents.begin(), prerender_contents.end(),
1366 std::mem_fun(
1367 &PrerenderContents::DestroyWhenUsingTooManyResources));
1369 // Measure how long the resource checks took. http://crbug.com/305419.
1370 UMA_HISTOGRAM_TIMES("Prerender.PeriodicCleanupResourceCheckTime",
1371 resource_timer.Elapsed());
1373 base::ElapsedTimer cleanup_timer;
1375 // Perform deferred cleanup work.
1376 DeleteOldWebContents();
1377 DeleteOldEntries();
1378 if (active_prerenders_.empty())
1379 StopSchedulingPeriodicCleanups();
1381 to_delete_prerenders_.clear();
1383 // Measure how long a the various cleanup tasks took. http://crbug.com/305419.
1384 UMA_HISTOGRAM_TIMES("Prerender.PeriodicCleanupDeleteContentsTime",
1385 cleanup_timer.Elapsed());
1388 void PrerenderManager::PostCleanupTask() {
1389 DCHECK(CalledOnValidThread());
1390 base::MessageLoop::current()->PostTask(
1391 FROM_HERE,
1392 base::Bind(&PrerenderManager::PeriodicCleanup, AsWeakPtr()));
1395 base::TimeTicks PrerenderManager::GetExpiryTimeForNewPrerender(
1396 Origin origin) const {
1397 base::TimeDelta ttl = config_.time_to_live;
1398 if (origin == ORIGIN_LOCAL_PREDICTOR)
1399 ttl = base::TimeDelta::FromSeconds(GetLocalPredictorTTLSeconds());
1400 return GetCurrentTimeTicks() + ttl;
1403 base::TimeTicks PrerenderManager::GetExpiryTimeForNavigatedAwayPrerender()
1404 const {
1405 return GetCurrentTimeTicks() + config_.abandon_time_to_live;
1408 void PrerenderManager::DeleteOldEntries() {
1409 DCHECK(CalledOnValidThread());
1410 while (!active_prerenders_.empty()) {
1411 PrerenderData* prerender_data = active_prerenders_.front();
1412 DCHECK(prerender_data);
1413 DCHECK(prerender_data->contents());
1415 if (prerender_data->expiry_time() > GetCurrentTimeTicks())
1416 return;
1417 prerender_data->contents()->Destroy(FINAL_STATUS_TIMED_OUT);
1421 base::Time PrerenderManager::GetCurrentTime() const {
1422 return base::Time::Now();
1425 base::TimeTicks PrerenderManager::GetCurrentTimeTicks() const {
1426 return base::TimeTicks::Now();
1429 PrerenderContents* PrerenderManager::CreatePrerenderContents(
1430 const GURL& url,
1431 const content::Referrer& referrer,
1432 Origin origin,
1433 uint8 experiment_id) {
1434 DCHECK(CalledOnValidThread());
1435 return prerender_contents_factory_->CreatePrerenderContents(
1436 this, profile_, url, referrer, origin, experiment_id);
1439 void PrerenderManager::SortActivePrerenders() {
1440 std::sort(active_prerenders_.begin(), active_prerenders_.end(),
1441 PrerenderData::OrderByExpiryTime());
1444 PrerenderManager::PrerenderData* PrerenderManager::FindPrerenderData(
1445 const GURL& url,
1446 const SessionStorageNamespace* session_storage_namespace) {
1447 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1448 it != active_prerenders_.end(); ++it) {
1449 if ((*it)->contents()->Matches(url, session_storage_namespace))
1450 return *it;
1452 return NULL;
1455 PrerenderManager::PrerenderData*
1456 PrerenderManager::FindPrerenderDataForTargetContents(
1457 WebContents* target_contents) {
1458 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1459 it != active_prerenders_.end(); ++it) {
1460 if ((*it)->pending_swap() &&
1461 (*it)->pending_swap()->target_contents() == target_contents)
1462 return *it;
1464 return NULL;
1467 ScopedVector<PrerenderManager::PrerenderData>::iterator
1468 PrerenderManager::FindIteratorForPrerenderContents(
1469 PrerenderContents* prerender_contents) {
1470 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1471 it != active_prerenders_.end(); ++it) {
1472 if (prerender_contents == (*it)->contents())
1473 return it;
1475 return active_prerenders_.end();
1478 bool PrerenderManager::DoesRateLimitAllowPrerender(Origin origin) const {
1479 DCHECK(CalledOnValidThread());
1480 base::TimeDelta elapsed_time =
1481 GetCurrentTimeTicks() - last_prerender_start_time_;
1482 histograms_->RecordTimeBetweenPrerenderRequests(origin, elapsed_time);
1483 if (!config_.rate_limit_enabled)
1484 return true;
1485 return elapsed_time >=
1486 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs);
1489 void PrerenderManager::DeleteOldWebContents() {
1490 while (!old_web_contents_list_.empty()) {
1491 WebContents* web_contents = old_web_contents_list_.front();
1492 old_web_contents_list_.pop_front();
1493 // TODO(dominich): should we use Instant Unload Handler here?
1494 delete web_contents;
1498 void PrerenderManager::CleanUpOldNavigations() {
1499 DCHECK(CalledOnValidThread());
1501 // Cutoff. Navigations before this cutoff can be discarded.
1502 base::TimeTicks cutoff = GetCurrentTimeTicks() -
1503 base::TimeDelta::FromMilliseconds(kNavigationRecordWindowMs);
1504 while (!navigations_.empty()) {
1505 if (navigations_.front().time > cutoff)
1506 break;
1507 navigations_.pop_front();
1511 void PrerenderManager::ScheduleDeleteOldWebContents(
1512 WebContents* tab,
1513 OnCloseWebContentsDeleter* deleter) {
1514 old_web_contents_list_.push_back(tab);
1515 PostCleanupTask();
1517 if (deleter) {
1518 ScopedVector<OnCloseWebContentsDeleter>::iterator i = std::find(
1519 on_close_web_contents_deleters_.begin(),
1520 on_close_web_contents_deleters_.end(),
1521 deleter);
1522 DCHECK(i != on_close_web_contents_deleters_.end());
1523 on_close_web_contents_deleters_.erase(i);
1527 void PrerenderManager::AddToHistory(PrerenderContents* contents) {
1528 PrerenderHistory::Entry entry(contents->prerender_url(),
1529 contents->final_status(),
1530 contents->origin(),
1531 base::Time::Now());
1532 prerender_history_->AddEntry(entry);
1535 base::Value* PrerenderManager::GetActivePrerendersAsValue() const {
1536 base::ListValue* list_value = new base::ListValue();
1537 for (ScopedVector<PrerenderData>::const_iterator it =
1538 active_prerenders_.begin();
1539 it != active_prerenders_.end(); ++it) {
1540 if (base::Value* prerender_value = (*it)->contents()->GetAsValue())
1541 list_value->Append(prerender_value);
1543 return list_value;
1546 void PrerenderManager::DestroyAllContents(FinalStatus final_status) {
1547 DeleteOldWebContents();
1548 while (!active_prerenders_.empty()) {
1549 PrerenderContents* contents = active_prerenders_.front()->contents();
1550 contents->Destroy(final_status);
1552 to_delete_prerenders_.clear();
1555 void PrerenderManager::DestroyAndMarkMatchCompleteAsUsed(
1556 PrerenderContents* prerender_contents,
1557 FinalStatus final_status) {
1558 prerender_contents->set_match_complete_status(
1559 PrerenderContents::MATCH_COMPLETE_REPLACED);
1560 histograms_->RecordFinalStatus(prerender_contents->origin(),
1561 prerender_contents->experiment_id(),
1562 PrerenderContents::MATCH_COMPLETE_REPLACEMENT,
1563 FINAL_STATUS_WOULD_HAVE_BEEN_USED);
1564 prerender_contents->Destroy(final_status);
1567 void PrerenderManager::RecordFinalStatus(Origin origin,
1568 uint8 experiment_id,
1569 FinalStatus final_status) const {
1570 RecordFinalStatusWithMatchCompleteStatus(
1571 origin, experiment_id,
1572 PrerenderContents::MATCH_COMPLETE_DEFAULT,
1573 final_status);
1576 bool PrerenderManager::IsEnabled() const {
1577 DCHECK(CalledOnValidThread());
1578 if (!enabled_)
1579 return false;
1580 for (std::list<const PrerenderCondition*>::const_iterator it =
1581 prerender_conditions_.begin();
1582 it != prerender_conditions_.end();
1583 ++it) {
1584 const PrerenderCondition* condition = *it;
1585 if (!condition->CanPrerender())
1586 return false;
1588 return true;
1591 void PrerenderManager::Observe(int type,
1592 const content::NotificationSource& source,
1593 const content::NotificationDetails& details) {
1594 switch (type) {
1595 case chrome::NOTIFICATION_COOKIE_CHANGED: {
1596 Profile* profile = content::Source<Profile>(source).ptr();
1597 if (!profile || !profile_->IsSameProfile(profile) ||
1598 profile->IsOffTheRecord()) {
1599 return;
1601 CookieChanged(content::Details<ChromeCookieDetails>(details).ptr());
1602 break;
1604 case chrome::NOTIFICATION_PROFILE_DESTROYED:
1605 DestroyAllContents(FINAL_STATUS_PROFILE_DESTROYED);
1606 on_close_web_contents_deleters_.clear();
1607 break;
1608 default:
1609 NOTREACHED() << "Unexpected notification sent.";
1610 break;
1614 void PrerenderManager::OnCreatingAudioStream(int render_process_id,
1615 int render_frame_id) {
1616 content::RenderFrameHost* render_frame_host =
1617 content::RenderFrameHost::FromID(render_process_id, render_frame_id);
1618 WebContents* tab = WebContents::FromRenderFrameHost(render_frame_host);
1619 if (!tab)
1620 return;
1622 PrerenderContents* prerender_contents = GetPrerenderContents(tab);
1623 if (!prerender_contents)
1624 return;
1626 prerender_contents->Destroy(prerender::FINAL_STATUS_CREATING_AUDIO_STREAM);
1629 void PrerenderManager::RecordLikelyLoginOnURL(const GURL& url) {
1630 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1631 if (!url.SchemeIsHTTPOrHTTPS())
1632 return;
1633 if (logged_in_predictor_table_.get()) {
1634 BrowserThread::PostTask(
1635 BrowserThread::DB,
1636 FROM_HERE,
1637 base::Bind(&LoggedInPredictorTable::AddDomainFromURL,
1638 logged_in_predictor_table_,
1639 url));
1641 std::string key = LoggedInPredictorTable::GetKey(url);
1642 if (!logged_in_state_.get())
1643 return;
1644 if (logged_in_state_->count(key))
1645 return;
1646 (*logged_in_state_)[key] = base::Time::Now().ToInternalValue();
1649 void PrerenderManager::CheckIfLikelyLoggedInOnURL(
1650 const GURL& url,
1651 bool* lookup_result,
1652 bool* database_was_present,
1653 const base::Closure& result_cb) {
1654 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1655 if (!logged_in_predictor_table_.get()) {
1656 *database_was_present = false;
1657 *lookup_result = false;
1658 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, result_cb);
1659 return;
1661 BrowserThread::PostTaskAndReply(
1662 BrowserThread::DB, FROM_HERE,
1663 base::Bind(&LoggedInPredictorTable::HasUserLoggedIn,
1664 logged_in_predictor_table_,
1665 url,
1666 lookup_result,
1667 database_was_present),
1668 result_cb);
1672 void PrerenderManager::CookieChanged(ChromeCookieDetails* details) {
1673 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1675 if (!logged_in_predictor_table_.get())
1676 return;
1678 // We only care when a cookie has been removed.
1679 if (!details->removed)
1680 return;
1682 std::string domain_key =
1683 LoggedInPredictorTable::GetKeyFromDomain(details->cookie->Domain());
1685 // If we have no record of this domain as a potentially logged in domain,
1686 // nothing to do here.
1687 if (logged_in_state_.get() && logged_in_state_->count(domain_key) < 1)
1688 return;
1690 net::URLRequestContextGetter* rq_context = profile_->GetRequestContext();
1691 if (!rq_context)
1692 return;
1694 BrowserThread::PostTask(
1695 BrowserThread::IO, FROM_HERE,
1696 base::Bind(&CheckIfCookiesExistForDomainOnIOThread,
1697 base::Unretained(rq_context),
1698 domain_key,
1699 base::Bind(
1700 &PrerenderManager::CookieChangedAnyCookiesLeftLookupResult,
1701 AsWeakPtr(),
1702 domain_key)
1706 void PrerenderManager::CookieChangedAnyCookiesLeftLookupResult(
1707 const std::string& domain_key,
1708 bool cookies_exist) {
1709 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1711 if (cookies_exist)
1712 return;
1714 if (logged_in_predictor_table_.get()) {
1715 BrowserThread::PostTask(BrowserThread::DB,
1716 FROM_HERE,
1717 base::Bind(&LoggedInPredictorTable::DeleteDomain,
1718 logged_in_predictor_table_,
1719 domain_key));
1722 if (logged_in_state_.get())
1723 logged_in_state_->erase(domain_key);
1726 void PrerenderManager::LoggedInPredictorDataReceived(
1727 scoped_ptr<LoggedInStateMap> new_map) {
1728 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1729 logged_in_state_.swap(new_map);
1732 void PrerenderManager::RecordEvent(PrerenderContents* contents,
1733 PrerenderEvent event) const {
1734 if (!contents)
1735 histograms_->RecordEvent(ORIGIN_NONE, kNoExperiment, event);
1736 else
1737 histograms_->RecordEvent(contents->origin(), contents->experiment_id(),
1738 event);
1741 // static
1742 void PrerenderManager::RecordCookieEvent(int process_id,
1743 int frame_id,
1744 const GURL& url,
1745 const GURL& frame_url,
1746 bool is_for_blocking_resource,
1747 PrerenderContents::CookieEvent event,
1748 const net::CookieList* cookie_list) {
1749 RenderFrameHost* rfh = RenderFrameHost::FromID(process_id, frame_id);
1750 WebContents* web_contents = WebContents::FromRenderFrameHost(rfh);
1751 if (!web_contents)
1752 return;
1754 bool is_main_frame = (rfh == web_contents->GetMainFrame());
1756 bool is_third_party_cookie =
1757 (!frame_url.is_empty() &&
1758 !net::registry_controlled_domains::SameDomainOrHost(
1759 url, frame_url,
1760 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES));
1762 PrerenderContents* prerender_contents =
1763 PrerenderContents::FromWebContents(web_contents);
1765 if (!prerender_contents)
1766 return;
1768 base::Time earliest_create_date;
1769 if (event == PrerenderContents::COOKIE_EVENT_SEND) {
1770 if (!cookie_list || cookie_list->empty())
1771 return;
1772 for (size_t i = 0; i < cookie_list->size(); i++) {
1773 if (earliest_create_date.is_null() ||
1774 (*cookie_list)[i].CreationDate() < earliest_create_date) {
1775 earliest_create_date = (*cookie_list)[i].CreationDate();
1780 prerender_contents->RecordCookieEvent(event,
1781 is_main_frame && url == frame_url,
1782 is_third_party_cookie,
1783 is_for_blocking_resource,
1784 earliest_create_date);
1787 void PrerenderManager::RecordCookieStatus(Origin origin,
1788 uint8 experiment_id,
1789 int cookie_status) const {
1790 histograms_->RecordCookieStatus(origin, experiment_id, cookie_status);
1793 void PrerenderManager::RecordCookieSendType(Origin origin,
1794 uint8 experiment_id,
1795 int cookie_send_type) const {
1796 histograms_->RecordCookieSendType(origin, experiment_id, cookie_send_type);
1799 void PrerenderManager::OnHistoryServiceDidQueryURL(
1800 Origin origin,
1801 uint8 experiment_id,
1802 CancelableRequestProvider::Handle handle,
1803 bool success,
1804 const history::URLRow* url_row,
1805 history::VisitVector* visists) {
1806 histograms_->RecordPrerenderPageVisitedStatus(origin, experiment_id, success);
1809 // static
1810 void PrerenderManager::HangSessionStorageMergesForTesting() {
1811 g_hang_session_storage_merges_for_testing = true;
1814 void PrerenderManager::RecordNetworkBytes(bool used, int64 prerender_bytes) {
1815 if (!ActuallyPrerendering())
1816 return;
1817 int64 recent_profile_bytes =
1818 profile_network_bytes_ - last_recorded_profile_network_bytes_;
1819 last_recorded_profile_network_bytes_ = profile_network_bytes_;
1820 DCHECK_GE(recent_profile_bytes, 0);
1821 histograms_->RecordNetworkBytes(used, prerender_bytes, recent_profile_bytes);
1824 void PrerenderManager::AddProfileNetworkBytesIfEnabled(int64 bytes) {
1825 DCHECK_GE(bytes, 0);
1826 if (IsEnabled() && ActuallyPrerendering())
1827 profile_network_bytes_ += bytes;
1830 } // namespace prerender