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