Prevent chrome://net-internals/#export from flickering
[chromium-blink-merge.git] / chrome / browser / prerender / prerender_manager.cc
blob1bfed3b9eb81f7b2c0488c7fbaa9e15d3c49d997
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_util.h"
39 #include "chrome/browser/profiles/profile.h"
40 #include "chrome/browser/search/search.h"
41 #include "chrome/browser/tab_contents/tab_util.h"
42 #include "chrome/browser/ui/browser_navigator.h"
43 #include "chrome/browser/ui/tab_contents/core_tab_helper.h"
44 #include "chrome/browser/ui/tab_contents/core_tab_helper_delegate.h"
45 #include "chrome/common/chrome_switches.h"
46 #include "chrome/common/prerender_messages.h"
47 #include "chrome/common/prerender_types.h"
48 #include "content/public/browser/browser_thread.h"
49 #include "content/public/browser/devtools_agent_host.h"
50 #include "content/public/browser/navigation_controller.h"
51 #include "content/public/browser/notification_service.h"
52 #include "content/public/browser/notification_source.h"
53 #include "content/public/browser/render_frame_host.h"
54 #include "content/public/browser/render_process_host.h"
55 #include "content/public/browser/render_view_host.h"
56 #include "content/public/browser/resource_request_details.h"
57 #include "content/public/browser/session_storage_namespace.h"
58 #include "content/public/browser/site_instance.h"
59 #include "content/public/browser/web_contents.h"
60 #include "content/public/browser/web_contents_delegate.h"
61 #include "content/public/common/url_constants.h"
62 #include "extensions/common/constants.h"
63 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
64 #include "net/url_request/url_request_context.h"
65 #include "net/url_request/url_request_context_getter.h"
67 using content::BrowserThread;
68 using content::RenderViewHost;
69 using content::RenderFrameHost;
70 using content::SessionStorageNamespace;
71 using content::WebContents;
72 using predictors::LoggedInPredictorTable;
74 namespace prerender {
76 namespace {
78 // Time interval at which periodic cleanups are performed.
79 const int kPeriodicCleanupIntervalMs = 1000;
81 // Valid HTTP methods for prerendering.
82 const char* const kValidHttpMethods[] = {
83 "GET",
84 "HEAD",
85 "OPTIONS",
86 "POST",
87 "TRACE",
90 // Length of prerender history, for display in chrome://net-internals
91 const int kHistoryLength = 100;
93 // Indicates whether a Prerender has been cancelled such that we need
94 // a dummy replacement for the purpose of recording the correct PPLT for
95 // the Match Complete case.
96 // Traditionally, "Match" means that a prerendered page was actually visited &
97 // the prerender was used. Our goal is to have "Match" cases line up in the
98 // control group & the experiment group, so that we can make meaningful
99 // comparisons of improvements. However, in the control group, since we don't
100 // actually perform prerenders, many of the cancellation reasons cannot be
101 // detected. Therefore, in the Prerender group, when we cancel for one of these
102 // reasons, we keep track of a dummy Prerender representing what we would
103 // have in the control group. If that dummy prerender in the prerender group
104 // would then be swapped in (but isn't actually b/c it's a dummy), we record
105 // this as a MatchComplete. This allows us to compare MatchComplete's
106 // across Prerender & Control group which ideally should be lining up.
107 // This ensures that there is no bias in terms of the page load times
108 // of the pages forming the difference between the two sets.
110 bool NeedMatchCompleteDummyForFinalStatus(FinalStatus final_status) {
111 return final_status != FINAL_STATUS_USED &&
112 final_status != FINAL_STATUS_TIMED_OUT &&
113 final_status != FINAL_STATUS_MANAGER_SHUTDOWN &&
114 final_status != FINAL_STATUS_PROFILE_DESTROYED &&
115 final_status != FINAL_STATUS_APP_TERMINATING &&
116 final_status != FINAL_STATUS_WINDOW_OPENER &&
117 final_status != FINAL_STATUS_CACHE_OR_HISTORY_CLEARED &&
118 final_status != FINAL_STATUS_CANCELLED &&
119 final_status != FINAL_STATUS_DEVTOOLS_ATTACHED &&
120 final_status != FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING &&
121 final_status != FINAL_STATUS_PAGE_BEING_CAPTURED &&
122 final_status != FINAL_STATUS_NAVIGATION_UNCOMMITTED &&
123 final_status != FINAL_STATUS_NON_EMPTY_BROWSING_INSTANCE;
126 void CheckIfCookiesExistForDomainResultOnUIThread(
127 const net::CookieMonster::HasCookiesForETLDP1Callback& callback,
128 bool cookies_exist) {
129 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
130 callback.Run(cookies_exist);
133 void CheckIfCookiesExistForDomainResultOnIOThread(
134 const net::CookieMonster::HasCookiesForETLDP1Callback& callback,
135 bool cookies_exist) {
136 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
137 BrowserThread::PostTask(
138 BrowserThread::UI,
139 FROM_HERE,
140 base::Bind(&CheckIfCookiesExistForDomainResultOnUIThread,
141 callback,
142 cookies_exist));
145 void CheckIfCookiesExistForDomainOnIOThread(
146 net::URLRequestContextGetter* rq_context,
147 const std::string& domain_key,
148 const net::CookieMonster::HasCookiesForETLDP1Callback& callback) {
149 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
150 net::CookieStore* cookie_store =
151 rq_context->GetURLRequestContext()->cookie_store();
152 cookie_store->GetCookieMonster()->HasCookiesForETLDP1Async(
153 domain_key,
154 base::Bind(&CheckIfCookiesExistForDomainResultOnIOThread, callback));
157 } // namespace
159 class PrerenderManager::OnCloseWebContentsDeleter
160 : public content::WebContentsDelegate,
161 public base::SupportsWeakPtr<
162 PrerenderManager::OnCloseWebContentsDeleter> {
163 public:
164 OnCloseWebContentsDeleter(PrerenderManager* manager,
165 WebContents* tab)
166 : manager_(manager),
167 tab_(tab),
168 suppressed_dialog_(false) {
169 tab_->SetDelegate(this);
170 base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
171 base::Bind(&OnCloseWebContentsDeleter::ScheduleWebContentsForDeletion,
172 AsWeakPtr(), true),
173 base::TimeDelta::FromSeconds(kDeleteWithExtremePrejudiceSeconds));
176 void CloseContents(WebContents* source) override {
177 DCHECK_EQ(tab_, source);
178 ScheduleWebContentsForDeletion(false);
181 void SwappedOut(WebContents* source) override {
182 DCHECK_EQ(tab_, source);
183 ScheduleWebContentsForDeletion(false);
186 bool ShouldSuppressDialogs(WebContents* source) override {
187 // Use this as a proxy for getting statistics on how often we fail to honor
188 // the beforeunload event.
189 DCHECK_EQ(tab_, source);
190 suppressed_dialog_ = true;
191 return true;
194 private:
195 static const int kDeleteWithExtremePrejudiceSeconds = 3;
197 void ScheduleWebContentsForDeletion(bool timeout) {
198 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterTimeout", timeout);
199 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterSuppressedDialog",
200 suppressed_dialog_);
201 tab_->SetDelegate(NULL);
202 manager_->ScheduleDeleteOldWebContents(tab_.release(), this);
203 // |this| is deleted at this point.
206 PrerenderManager* manager_;
207 scoped_ptr<WebContents> tab_;
208 bool suppressed_dialog_;
210 DISALLOW_COPY_AND_ASSIGN(OnCloseWebContentsDeleter);
213 // static
214 int PrerenderManager::prerenders_per_session_count_ = 0;
216 // static
217 PrerenderManager::PrerenderManagerMode PrerenderManager::mode_ =
218 PRERENDER_MODE_ENABLED;
220 struct PrerenderManager::NavigationRecord {
221 NavigationRecord(const GURL& url, base::TimeTicks time)
222 : url(url),
223 time(time) {
226 GURL url;
227 base::TimeTicks time;
230 PrerenderManager::PrerenderManager(Profile* profile)
231 : profile_(profile),
232 prerender_contents_factory_(PrerenderContents::CreateFactory()),
233 last_prerender_start_time_(GetCurrentTimeTicks() -
234 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs)),
235 prerender_history_(new PrerenderHistory(kHistoryLength)),
236 histograms_(new PrerenderHistograms()),
237 profile_network_bytes_(0),
238 last_recorded_profile_network_bytes_(0) {
239 // There are some assumptions that the PrerenderManager is on the UI thread.
240 // Any other checks simply make sure that the PrerenderManager is accessed on
241 // the same thread that it was created on.
242 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
244 if (IsLocalPredictorEnabled())
245 local_predictor_.reset(new PrerenderLocalPredictor(this));
247 if (IsLoggedInPredictorEnabled() && !profile_->IsOffTheRecord()) {
248 predictors::PredictorDatabase* predictor_db =
249 predictors::PredictorDatabaseFactory::GetForProfile(profile);
250 if (predictor_db) {
251 logged_in_predictor_table_ = predictor_db->logged_in_table();
252 scoped_ptr<LoggedInStateMap> new_state_map(new LoggedInStateMap);
253 LoggedInStateMap* new_state_map_ptr = new_state_map.get();
254 BrowserThread::PostTaskAndReply(
255 BrowserThread::DB, FROM_HERE,
256 base::Bind(&LoggedInPredictorTable::GetAllData,
257 logged_in_predictor_table_,
258 new_state_map_ptr),
259 base::Bind(&PrerenderManager::LoggedInPredictorDataReceived,
260 AsWeakPtr(),
261 base::Passed(&new_state_map)));
265 // Certain experiments override our default config_ values.
266 switch (PrerenderManager::GetMode()) {
267 case PrerenderManager::PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP:
268 config_.max_link_concurrency = 4;
269 config_.max_link_concurrency_per_launcher = 2;
270 break;
271 case PrerenderManager::PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP:
272 config_.time_to_live = base::TimeDelta::FromMinutes(15);
273 break;
274 default:
275 break;
278 notification_registrar_.Add(
279 this, chrome::NOTIFICATION_COOKIE_CHANGED,
280 content::NotificationService::AllBrowserContextsAndSources());
282 notification_registrar_.Add(
283 this, chrome::NOTIFICATION_PROFILE_DESTROYED,
284 content::Source<Profile>(profile_));
286 MediaCaptureDevicesDispatcher::GetInstance()->AddObserver(this);
289 PrerenderManager::~PrerenderManager() {
290 MediaCaptureDevicesDispatcher::GetInstance()->RemoveObserver(this);
292 // The earlier call to KeyedService::Shutdown() should have
293 // emptied these vectors already.
294 DCHECK(active_prerenders_.empty());
295 DCHECK(to_delete_prerenders_.empty());
297 for (PrerenderProcessSet::const_iterator it =
298 prerender_process_hosts_.begin();
299 it != prerender_process_hosts_.end();
300 ++it) {
301 (*it)->RemoveObserver(this);
305 void PrerenderManager::Shutdown() {
306 DestroyAllContents(FINAL_STATUS_MANAGER_SHUTDOWN);
307 on_close_web_contents_deleters_.clear();
308 // Must happen before |profile_| is set to NULL as
309 // |local_predictor_| accesses it.
310 if (local_predictor_)
311 local_predictor_->Shutdown();
312 profile_ = NULL;
314 DCHECK(active_prerenders_.empty());
317 PrerenderHandle* PrerenderManager::AddPrerenderFromLinkRelPrerender(
318 int process_id,
319 int route_id,
320 const GURL& url,
321 const uint32 rel_types,
322 const content::Referrer& referrer,
323 const gfx::Size& size) {
324 Origin origin = rel_types & PrerenderRelTypePrerender ?
325 ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN :
326 ORIGIN_LINK_REL_NEXT;
327 SessionStorageNamespace* session_storage_namespace = NULL;
328 // Unit tests pass in a process_id == -1.
329 if (process_id != -1) {
330 RenderViewHost* source_render_view_host =
331 RenderViewHost::FromID(process_id, route_id);
332 if (!source_render_view_host)
333 return NULL;
334 WebContents* source_web_contents =
335 WebContents::FromRenderViewHost(source_render_view_host);
336 if (!source_web_contents)
337 return NULL;
338 if (origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN &&
339 source_web_contents->GetURL().host() == url.host()) {
340 origin = ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN;
342 // TODO(ajwong): This does not correctly handle storage for isolated apps.
343 session_storage_namespace =
344 source_web_contents->GetController()
345 .GetDefaultSessionStorageNamespace();
348 return AddPrerender(origin, url, referrer, size, session_storage_namespace);
351 PrerenderHandle* PrerenderManager::AddPrerenderFromOmnibox(
352 const GURL& url,
353 SessionStorageNamespace* session_storage_namespace,
354 const gfx::Size& size) {
355 if (!IsOmniboxEnabled(profile_))
356 return NULL;
357 return AddPrerender(ORIGIN_OMNIBOX, url, content::Referrer(), size,
358 session_storage_namespace);
361 PrerenderHandle* PrerenderManager::AddPrerenderFromLocalPredictor(
362 const GURL& url,
363 SessionStorageNamespace* session_storage_namespace,
364 const gfx::Size& size) {
365 return AddPrerender(ORIGIN_LOCAL_PREDICTOR, url, content::Referrer(),
366 size, session_storage_namespace);
369 PrerenderHandle* PrerenderManager::AddPrerenderFromExternalRequest(
370 const GURL& url,
371 const content::Referrer& referrer,
372 SessionStorageNamespace* session_storage_namespace,
373 const gfx::Size& size) {
374 return AddPrerender(ORIGIN_EXTERNAL_REQUEST, url, referrer, size,
375 session_storage_namespace);
378 PrerenderHandle* PrerenderManager::AddPrerenderForInstant(
379 const GURL& url,
380 content::SessionStorageNamespace* session_storage_namespace,
381 const gfx::Size& size) {
382 DCHECK(chrome::ShouldPrefetchSearchResults());
383 return AddPrerender(ORIGIN_INSTANT, url, content::Referrer(), size,
384 session_storage_namespace);
387 void PrerenderManager::CancelAllPrerenders() {
388 DCHECK(CalledOnValidThread());
389 while (!active_prerenders_.empty()) {
390 PrerenderContents* prerender_contents =
391 active_prerenders_.front()->contents();
392 prerender_contents->Destroy(FINAL_STATUS_CANCELLED);
396 bool PrerenderManager::MaybeUsePrerenderedPage(const GURL& url,
397 chrome::NavigateParams* params) {
398 DCHECK(CalledOnValidThread());
400 content::WebContents* web_contents = params->target_contents;
401 DCHECK(!IsWebContentsPrerendering(web_contents, NULL));
403 // Don't prerender if the navigation involves some special parameters.
404 if (params->uses_post || !params->extra_headers.empty())
405 return false;
407 DeleteOldEntries();
408 to_delete_prerenders_.clear();
410 // First, try to find prerender data with the correct session storage
411 // namespace.
412 // TODO(ajwong): This doesn't handle isolated apps correctly.
413 PrerenderData* prerender_data = FindPrerenderData(
414 url,
415 web_contents->GetController().GetDefaultSessionStorageNamespace());
416 if (!prerender_data)
417 return false;
418 DCHECK(prerender_data->contents());
420 WebContents* new_web_contents = SwapInternal(
421 url, web_contents, prerender_data,
422 params->should_replace_current_entry);
423 if (!new_web_contents)
424 return false;
426 // Record the new target_contents for the callers.
427 params->target_contents = new_web_contents;
428 return true;
431 WebContents* PrerenderManager::SwapInternal(
432 const GURL& url,
433 WebContents* web_contents,
434 PrerenderData* prerender_data,
435 bool should_replace_current_entry) {
436 DCHECK(CalledOnValidThread());
437 DCHECK(!IsWebContentsPrerendering(web_contents, NULL));
439 // Only swap if the target WebContents has a CoreTabHelper delegate to swap
440 // out of it. For a normal WebContents, this is if it is in a TabStripModel.
441 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(web_contents);
442 if (!core_tab_helper || !core_tab_helper->delegate())
443 return NULL;
445 PrerenderTabHelper* target_tab_helper =
446 PrerenderTabHelper::FromWebContents(web_contents);
447 if (!target_tab_helper) {
448 NOTREACHED();
449 return NULL;
452 if (IsNoSwapInExperiment(prerender_data->contents()->experiment_id()))
453 return NULL;
455 if (WebContents* new_web_contents =
456 prerender_data->contents()->prerender_contents()) {
457 if (web_contents == new_web_contents)
458 return NULL; // Do not swap in to ourself.
460 // We cannot swap in if there is no last committed entry, because we would
461 // show a blank page under an existing entry from the current tab. Even if
462 // there is a pending entry, it may not commit.
463 // TODO(creis): If there is a pending navigation and no last committed
464 // entry, we might be able to transfer the network request instead.
465 if (!new_web_contents->GetController().CanPruneAllButLastCommitted()) {
466 // Abort this prerender so it is not used later. http://crbug.com/292121
467 prerender_data->contents()->Destroy(FINAL_STATUS_NAVIGATION_UNCOMMITTED);
468 return NULL;
472 // Do not swap if the target WebContents is not the only WebContents in its
473 // current BrowsingInstance.
474 if (web_contents->GetSiteInstance()->GetRelatedActiveContentsCount() != 1u) {
475 DCHECK_GT(
476 web_contents->GetSiteInstance()->GetRelatedActiveContentsCount(), 1u);
477 prerender_data->contents()->Destroy(
478 FINAL_STATUS_NON_EMPTY_BROWSING_INSTANCE);
479 return NULL;
482 // Do not use the prerendered version if there is an opener object.
483 if (web_contents->HasOpener()) {
484 prerender_data->contents()->Destroy(FINAL_STATUS_WINDOW_OPENER);
485 return NULL;
488 // Do not swap in the prerender if the current WebContents is being captured.
489 if (web_contents->GetCapturerCount() > 0) {
490 prerender_data->contents()->Destroy(FINAL_STATUS_PAGE_BEING_CAPTURED);
491 return NULL;
494 // If we are just in the control group (which can be detected by noticing
495 // that prerendering hasn't even started yet), record that |web_contents| now
496 // would be showing a prerendered contents, but otherwise, don't do anything.
497 if (!prerender_data->contents()->prerendering_has_started()) {
498 target_tab_helper->WouldHavePrerenderedNextLoad(
499 prerender_data->contents()->origin());
500 prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED);
501 return NULL;
504 // Don't use prerendered pages if debugger is attached to the tab.
505 // See http://crbug.com/98541
506 if (content::DevToolsAgentHost::IsDebuggerAttached(web_contents)) {
507 DestroyAndMarkMatchCompleteAsUsed(prerender_data->contents(),
508 FINAL_STATUS_DEVTOOLS_ATTACHED);
509 return NULL;
512 // If the prerendered page is in the middle of a cross-site navigation,
513 // don't swap it in because there isn't a good way to merge histories.
514 if (prerender_data->contents()->IsCrossSiteNavigationPending()) {
515 DestroyAndMarkMatchCompleteAsUsed(
516 prerender_data->contents(),
517 FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING);
518 return NULL;
521 // For bookkeeping purposes, we need to mark this WebContents to
522 // reflect that it would have been prerendered.
523 if (GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP) {
524 target_tab_helper->WouldHavePrerenderedNextLoad(
525 prerender_data->contents()->origin());
526 prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED);
527 return NULL;
530 // At this point, we've determined that we will use the prerender.
531 content::RenderProcessHost* process_host =
532 prerender_data->contents()->GetRenderViewHost()->GetProcess();
533 process_host->RemoveObserver(this);
534 prerender_process_hosts_.erase(process_host);
535 if (!prerender_data->contents()->load_start_time().is_null()) {
536 histograms_->RecordTimeUntilUsed(
537 prerender_data->contents()->origin(),
538 GetCurrentTimeTicks() - prerender_data->contents()->load_start_time());
540 histograms_->RecordAbandonTimeUntilUsed(
541 prerender_data->contents()->origin(),
542 prerender_data->abandon_time().is_null() ?
543 base::TimeDelta() :
544 GetCurrentTimeTicks() - prerender_data->abandon_time());
546 histograms_->RecordPerSessionCount(prerender_data->contents()->origin(),
547 ++prerenders_per_session_count_);
548 histograms_->RecordUsedPrerender(prerender_data->contents()->origin());
550 ScopedVector<PrerenderData>::iterator to_erase =
551 FindIteratorForPrerenderContents(prerender_data->contents());
552 DCHECK(active_prerenders_.end() != to_erase);
553 DCHECK_EQ(prerender_data, *to_erase);
554 scoped_ptr<PrerenderContents>
555 prerender_contents(prerender_data->ReleaseContents());
556 active_prerenders_.erase(to_erase);
558 // Mark prerender as used.
559 prerender_contents->PrepareForUse();
561 WebContents* new_web_contents =
562 prerender_contents->ReleasePrerenderContents();
563 WebContents* old_web_contents = web_contents;
564 DCHECK(new_web_contents);
565 DCHECK(old_web_contents);
567 // Merge the browsing history.
568 new_web_contents->GetController().CopyStateFromAndPrune(
569 &old_web_contents->GetController(),
570 should_replace_current_entry);
571 CoreTabHelper::FromWebContents(old_web_contents)->delegate()->
572 SwapTabContents(old_web_contents,
573 new_web_contents,
574 true,
575 prerender_contents->has_finished_loading());
576 prerender_contents->CommitHistory(new_web_contents);
578 // Update PPLT metrics:
579 // If the tab has finished loading, record a PPLT of 0.
580 // If the tab is still loading, reset its start time to the current time.
581 PrerenderTabHelper* prerender_tab_helper =
582 PrerenderTabHelper::FromWebContents(new_web_contents);
583 DCHECK(prerender_tab_helper != NULL);
584 prerender_tab_helper->PrerenderSwappedIn();
586 if (old_web_contents->NeedToFireBeforeUnload()) {
587 // Schedule the delete to occur after the tab has run its unload handlers.
588 // TODO(davidben): Honor the beforeunload event. http://crbug.com/304932
589 on_close_web_contents_deleters_.push_back(
590 new OnCloseWebContentsDeleter(this, old_web_contents));
591 old_web_contents->DispatchBeforeUnload(false);
592 } else {
593 // No unload handler to run, so delete asap.
594 ScheduleDeleteOldWebContents(old_web_contents, NULL);
597 // TODO(cbentzel): Should prerender_contents move to the pending delete
598 // list, instead of deleting directly here?
599 AddToHistory(prerender_contents.get());
600 RecordNavigation(url);
601 return new_web_contents;
604 void PrerenderManager::MoveEntryToPendingDelete(PrerenderContents* entry,
605 FinalStatus final_status) {
606 DCHECK(CalledOnValidThread());
607 DCHECK(entry);
609 ScopedVector<PrerenderData>::iterator it =
610 FindIteratorForPrerenderContents(entry);
611 DCHECK(it != active_prerenders_.end());
613 // If this PrerenderContents is being deleted due to a cancellation any time
614 // after the prerender has started then we need to create a dummy replacement
615 // for PPLT accounting purposes for the Match Complete group. This is the case
616 // if the cancellation is for any reason that would not occur in the control
617 // group case.
618 if (entry->prerendering_has_started() &&
619 entry->match_complete_status() ==
620 PrerenderContents::MATCH_COMPLETE_DEFAULT &&
621 NeedMatchCompleteDummyForFinalStatus(final_status) &&
622 ActuallyPrerendering() &&
623 GetMode() == PRERENDER_MODE_EXPERIMENT_MATCH_COMPLETE_GROUP) {
624 // TODO(tburkard): I'd like to DCHECK that we are actually prerendering.
625 // However, what if new conditions are added and
626 // NeedMatchCompleteDummyForFinalStatus is not being updated. Not sure
627 // what's the best thing to do here. For now, I will just check whether
628 // we are actually prerendering.
629 (*it)->MakeIntoMatchCompleteReplacement();
630 } else {
631 to_delete_prerenders_.push_back(*it);
632 active_prerenders_.weak_erase(it);
635 // Destroy the old WebContents relatively promptly to reduce resource usage.
636 PostCleanupTask();
639 void PrerenderManager::RecordPageLoadTimeNotSwappedIn(
640 Origin origin,
641 base::TimeDelta page_load_time,
642 const GURL& url) {
643 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
644 histograms_->RecordPageLoadTimeNotSwappedIn(origin, page_load_time, url);
647 void PrerenderManager::RecordPerceivedPageLoadTime(
648 Origin origin,
649 NavigationType navigation_type,
650 base::TimeDelta perceived_page_load_time,
651 double fraction_plt_elapsed_at_swap_in,
652 const GURL& url) {
653 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
654 if (!IsEnabled())
655 return;
657 histograms_->RecordPerceivedPageLoadTime(
658 origin, perceived_page_load_time, navigation_type, url);
660 if (navigation_type == NAVIGATION_TYPE_PRERENDERED) {
661 histograms_->RecordPercentLoadDoneAtSwapin(
662 origin, fraction_plt_elapsed_at_swap_in);
664 if (local_predictor_) {
665 local_predictor_->OnPLTEventForURL(url, perceived_page_load_time);
669 // static
670 PrerenderManager::PrerenderManagerMode PrerenderManager::GetMode() {
671 return mode_;
674 // static
675 void PrerenderManager::SetMode(PrerenderManagerMode mode) {
676 mode_ = mode;
679 // static
680 const char* PrerenderManager::GetModeString() {
681 switch (mode_) {
682 case PRERENDER_MODE_DISABLED:
683 return "_Disabled";
684 case PRERENDER_MODE_ENABLED:
685 case PRERENDER_MODE_EXPERIMENT_PRERENDER_GROUP:
686 return "_Enabled";
687 case PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP:
688 return "_Control";
689 case PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP:
690 return "_Multi";
691 case PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP:
692 return "_15MinTTL";
693 case PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP:
694 return "_NoUse";
695 case PRERENDER_MODE_EXPERIMENT_MATCH_COMPLETE_GROUP:
696 return "_MatchComplete";
697 case PRERENDER_MODE_MAX:
698 default:
699 NOTREACHED() << "Invalid PrerenderManager mode.";
700 break;
702 return "";
705 // static
706 bool PrerenderManager::IsPrerenderingPossible() {
707 return GetMode() != PRERENDER_MODE_DISABLED;
710 // static
711 bool PrerenderManager::ActuallyPrerendering() {
712 return IsPrerenderingPossible() && !IsControlGroup(kNoExperiment);
715 // static
716 bool PrerenderManager::IsControlGroup(uint8 experiment_id) {
717 return GetMode() == PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP ||
718 IsControlGroupExperiment(experiment_id);
721 // static
722 bool PrerenderManager::IsNoUseGroup() {
723 return GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP;
726 bool PrerenderManager::IsWebContentsPrerendering(
727 const WebContents* web_contents,
728 Origin* origin) const {
729 DCHECK(CalledOnValidThread());
730 if (PrerenderContents* prerender_contents =
731 GetPrerenderContents(web_contents)) {
732 if (origin)
733 *origin = prerender_contents->origin();
734 return true;
736 return false;
739 bool PrerenderManager::HasPrerenderedUrl(
740 GURL url,
741 content::WebContents* web_contents) const {
742 content::SessionStorageNamespace* session_storage_namespace = web_contents->
743 GetController().GetDefaultSessionStorageNamespace();
745 for (ScopedVector<PrerenderData>::const_iterator it =
746 active_prerenders_.begin();
747 it != active_prerenders_.end(); ++it) {
748 PrerenderContents* prerender_contents = (*it)->contents();
749 if (prerender_contents->Matches(url, session_storage_namespace)) {
750 return true;
753 return false;
756 PrerenderContents* PrerenderManager::GetPrerenderContents(
757 const content::WebContents* web_contents) const {
758 DCHECK(CalledOnValidThread());
759 for (ScopedVector<PrerenderData>::const_iterator it =
760 active_prerenders_.begin();
761 it != active_prerenders_.end(); ++it) {
762 WebContents* prerender_web_contents =
763 (*it)->contents()->prerender_contents();
764 if (prerender_web_contents == web_contents) {
765 return (*it)->contents();
769 // Also check the pending-deletion list. If the prerender is in pending
770 // delete, anyone with a handle on the WebContents needs to know.
771 for (ScopedVector<PrerenderData>::const_iterator it =
772 to_delete_prerenders_.begin();
773 it != to_delete_prerenders_.end(); ++it) {
774 WebContents* prerender_web_contents =
775 (*it)->contents()->prerender_contents();
776 if (prerender_web_contents == web_contents) {
777 return (*it)->contents();
780 return NULL;
783 PrerenderContents* PrerenderManager::GetPrerenderContentsForRoute(
784 int child_id,
785 int route_id) const {
786 content::WebContents* web_contents =
787 tab_util::GetWebContentsByID(child_id, route_id);
788 if (web_contents == NULL)
789 return NULL;
790 return GetPrerenderContents(web_contents);
793 const std::vector<WebContents*>
794 PrerenderManager::GetAllPrerenderingContents() const {
795 DCHECK(CalledOnValidThread());
796 std::vector<WebContents*> result;
798 for (ScopedVector<PrerenderData>::const_iterator it =
799 active_prerenders_.begin();
800 it != active_prerenders_.end(); ++it) {
801 if (WebContents* contents = (*it)->contents()->prerender_contents())
802 result.push_back(contents);
805 return result;
808 bool PrerenderManager::HasRecentlyBeenNavigatedTo(Origin origin,
809 const GURL& url) {
810 DCHECK(CalledOnValidThread());
812 CleanUpOldNavigations();
813 std::list<NavigationRecord>::const_reverse_iterator end = navigations_.rend();
814 for (std::list<NavigationRecord>::const_reverse_iterator it =
815 navigations_.rbegin();
816 it != end;
817 ++it) {
818 if (it->url == url) {
819 base::TimeDelta delta = GetCurrentTimeTicks() - it->time;
820 histograms_->RecordTimeSinceLastRecentVisit(origin, delta);
821 return true;
825 return false;
828 // static
829 bool PrerenderManager::IsValidHttpMethod(const std::string& method) {
830 // method has been canonicalized to upper case at this point so we can just
831 // compare them.
832 DCHECK_EQ(method, StringToUpperASCII(method));
833 for (size_t i = 0; i < arraysize(kValidHttpMethods); ++i) {
834 if (method.compare(kValidHttpMethods[i]) == 0)
835 return true;
838 return false;
841 // static
842 bool PrerenderManager::DoesURLHaveValidScheme(const GURL& url) {
843 return (url.SchemeIsHTTPOrHTTPS() ||
844 url.SchemeIs(extensions::kExtensionScheme) ||
845 url.SchemeIs("data"));
848 // static
849 bool PrerenderManager::DoesSubresourceURLHaveValidScheme(const GURL& url) {
850 return DoesURLHaveValidScheme(url) || url == GURL(url::kAboutBlankURL);
853 base::DictionaryValue* PrerenderManager::GetAsValue() const {
854 DCHECK(CalledOnValidThread());
855 base::DictionaryValue* dict_value = new base::DictionaryValue();
856 dict_value->Set("history", prerender_history_->GetEntriesAsValue());
857 dict_value->Set("active", GetActivePrerendersAsValue());
858 dict_value->SetBoolean("enabled", IsEnabled());
859 dict_value->SetBoolean("omnibox_enabled", IsOmniboxEnabled(profile_));
860 // If prerender is disabled via a flag this method is not even called.
861 std::string enabled_note;
862 if (IsControlGroup(kNoExperiment))
863 enabled_note += "(Control group: Not actually prerendering) ";
864 if (IsNoUseGroup())
865 enabled_note += "(No-use group: Not swapping in prerendered pages) ";
866 if (GetMode() == PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP)
867 enabled_note +=
868 "(15 min TTL group: Extended prerender eviction to 15 mins) ";
869 dict_value->SetString("enabled_note", enabled_note);
870 return dict_value;
873 void PrerenderManager::ClearData(int clear_flags) {
874 DCHECK_GE(clear_flags, 0);
875 DCHECK_LT(clear_flags, CLEAR_MAX);
876 if (clear_flags & CLEAR_PRERENDER_CONTENTS)
877 DestroyAllContents(FINAL_STATUS_CACHE_OR_HISTORY_CLEARED);
878 // This has to be second, since destroying prerenders can add to the history.
879 if (clear_flags & CLEAR_PRERENDER_HISTORY)
880 prerender_history_->Clear();
883 void PrerenderManager::RecordFinalStatusWithMatchCompleteStatus(
884 Origin origin,
885 uint8 experiment_id,
886 PrerenderContents::MatchCompleteStatus mc_status,
887 FinalStatus final_status) const {
888 histograms_->RecordFinalStatus(origin,
889 experiment_id,
890 mc_status,
891 final_status);
894 void PrerenderManager::RecordNavigation(const GURL& url) {
895 DCHECK(CalledOnValidThread());
897 navigations_.push_back(NavigationRecord(url, GetCurrentTimeTicks()));
898 CleanUpOldNavigations();
901 // protected
902 struct PrerenderManager::PrerenderData::OrderByExpiryTime {
903 bool operator()(const PrerenderData* a, const PrerenderData* b) const {
904 return a->expiry_time() < b->expiry_time();
908 PrerenderManager::PrerenderData::PrerenderData(PrerenderManager* manager,
909 PrerenderContents* contents,
910 base::TimeTicks expiry_time)
911 : manager_(manager),
912 contents_(contents),
913 handle_count_(0),
914 expiry_time_(expiry_time) {
915 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
918 PrerenderManager::PrerenderData::~PrerenderData() {
921 void PrerenderManager::PrerenderData::MakeIntoMatchCompleteReplacement() {
922 DCHECK(contents_);
923 contents_->set_match_complete_status(
924 PrerenderContents::MATCH_COMPLETE_REPLACED);
925 PrerenderData* to_delete = new PrerenderData(manager_, contents_.release(),
926 expiry_time_);
927 contents_.reset(to_delete->contents_->CreateMatchCompleteReplacement());
928 manager_->to_delete_prerenders_.push_back(to_delete);
931 void PrerenderManager::PrerenderData::OnHandleCreated(PrerenderHandle* handle) {
932 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
933 ++handle_count_;
934 contents_->AddObserver(handle);
937 void PrerenderManager::PrerenderData::OnHandleNavigatedAway(
938 PrerenderHandle* handle) {
939 DCHECK_LT(0, handle_count_);
940 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
941 if (abandon_time_.is_null())
942 abandon_time_ = base::TimeTicks::Now();
943 // We intentionally don't decrement the handle count here, so that the
944 // prerender won't be canceled until it times out.
945 manager_->SourceNavigatedAway(this);
948 void PrerenderManager::PrerenderData::OnHandleCanceled(
949 PrerenderHandle* handle) {
950 DCHECK_LT(0, handle_count_);
951 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
953 if (--handle_count_ == 0) {
954 // This will eventually remove this object from active_prerenders_.
955 contents_->Destroy(FINAL_STATUS_CANCELLED);
959 PrerenderContents* PrerenderManager::PrerenderData::ReleaseContents() {
960 return contents_.release();
963 void PrerenderManager::SetPrerenderContentsFactory(
964 PrerenderContents::Factory* prerender_contents_factory) {
965 DCHECK(CalledOnValidThread());
966 prerender_contents_factory_.reset(prerender_contents_factory);
969 void PrerenderManager::SourceNavigatedAway(PrerenderData* prerender_data) {
970 // The expiry time of our prerender data will likely change because of
971 // this navigation. This requires a resort of active_prerenders_.
972 ScopedVector<PrerenderData>::iterator it =
973 std::find(active_prerenders_.begin(), active_prerenders_.end(),
974 prerender_data);
975 if (it == active_prerenders_.end())
976 return;
978 (*it)->set_expiry_time(
979 std::min((*it)->expiry_time(),
980 GetExpiryTimeForNavigatedAwayPrerender()));
981 SortActivePrerenders();
984 // private
985 PrerenderHandle* PrerenderManager::AddPrerender(
986 Origin origin,
987 const GURL& url_arg,
988 const content::Referrer& referrer,
989 const gfx::Size& size,
990 SessionStorageNamespace* session_storage_namespace) {
991 DCHECK(CalledOnValidThread());
993 if (!IsEnabled())
994 return NULL;
996 if ((origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN ||
997 origin == ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN) &&
998 IsGoogleSearchResultURL(referrer.url)) {
999 origin = ORIGIN_GWS_PRERENDER;
1002 GURL url = url_arg;
1003 GURL alias_url;
1004 uint8 experiment = GetQueryStringBasedExperiment(url_arg);
1005 if (IsControlGroup(experiment) &&
1006 MaybeGetQueryStringBasedAliasURL(url, &alias_url)) {
1007 url = alias_url;
1010 // From here on, we will record a FinalStatus so we need to register with the
1011 // histogram tracking.
1012 histograms_->RecordPrerender(origin, url_arg);
1014 if (PrerenderData* preexisting_prerender_data =
1015 FindPrerenderData(url, session_storage_namespace)) {
1016 RecordFinalStatusWithoutCreatingPrerenderContents(
1017 url, origin, experiment, FINAL_STATUS_DUPLICATE);
1018 return new PrerenderHandle(preexisting_prerender_data);
1021 // Do not prerender if there are too many render processes, and we would
1022 // have to use an existing one. We do not want prerendering to happen in
1023 // a shared process, so that we can always reliably lower the CPU
1024 // priority for prerendering.
1025 // In single-process mode, ShouldTryToUseExistingProcessHost() always returns
1026 // true, so that case needs to be explicitly checked for.
1027 // TODO(tburkard): Figure out how to cancel prerendering in the opposite
1028 // case, when a new tab is added to a process used for prerendering.
1029 // TODO(ppi): Check whether there are usually enough render processes
1030 // available on Android. If not, kill an existing renderers so that we can
1031 // create a new one.
1032 if (content::RenderProcessHost::ShouldTryToUseExistingProcessHost(
1033 profile_, url) &&
1034 !content::RenderProcessHost::run_renderer_in_process()) {
1035 RecordFinalStatusWithoutCreatingPrerenderContents(
1036 url, origin, experiment, FINAL_STATUS_TOO_MANY_PROCESSES);
1037 return NULL;
1040 // Check if enough time has passed since the last prerender.
1041 if (!DoesRateLimitAllowPrerender(origin)) {
1042 // Cancel the prerender. We could add it to the pending prerender list but
1043 // this doesn't make sense as the next prerender request will be triggered
1044 // by a navigation and is unlikely to be the same site.
1045 RecordFinalStatusWithoutCreatingPrerenderContents(
1046 url, origin, experiment, FINAL_STATUS_RATE_LIMIT_EXCEEDED);
1047 return NULL;
1050 PrerenderContents* prerender_contents = CreatePrerenderContents(
1051 url, referrer, origin, experiment);
1052 DCHECK(prerender_contents);
1053 active_prerenders_.push_back(
1054 new PrerenderData(this, prerender_contents,
1055 GetExpiryTimeForNewPrerender(origin)));
1056 if (!prerender_contents->Init()) {
1057 DCHECK(active_prerenders_.end() ==
1058 FindIteratorForPrerenderContents(prerender_contents));
1059 return NULL;
1062 histograms_->RecordPrerenderStarted(origin);
1063 DCHECK(!prerender_contents->prerendering_has_started());
1065 PrerenderHandle* prerender_handle =
1066 new PrerenderHandle(active_prerenders_.back());
1067 SortActivePrerenders();
1069 last_prerender_start_time_ = GetCurrentTimeTicks();
1071 gfx::Size contents_size =
1072 size.IsEmpty() ? config_.default_tab_bounds.size() : size;
1074 prerender_contents->StartPrerendering(contents_size,
1075 session_storage_namespace);
1077 DCHECK(IsControlGroup(experiment) ||
1078 prerender_contents->prerendering_has_started() ||
1079 (origin == ORIGIN_LOCAL_PREDICTOR &&
1080 IsLocalPredictorPrerenderAlwaysControlEnabled()));
1082 if (GetMode() == PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP)
1083 histograms_->RecordConcurrency(active_prerenders_.size());
1085 // Query the history to see if the URL being prerendered has ever been
1086 // visited before.
1087 HistoryService* history_service = HistoryServiceFactory::GetForProfile(
1088 profile_, ServiceAccessType::EXPLICIT_ACCESS);
1089 if (history_service) {
1090 history_service->QueryURL(
1091 url,
1092 false,
1093 base::Bind(&PrerenderManager::OnHistoryServiceDidQueryURL,
1094 base::Unretained(this),
1095 origin,
1096 experiment),
1097 &query_url_tracker_);
1100 StartSchedulingPeriodicCleanups();
1101 return prerender_handle;
1104 void PrerenderManager::StartSchedulingPeriodicCleanups() {
1105 DCHECK(CalledOnValidThread());
1106 if (repeating_timer_.IsRunning())
1107 return;
1108 repeating_timer_.Start(FROM_HERE,
1109 base::TimeDelta::FromMilliseconds(kPeriodicCleanupIntervalMs),
1110 this,
1111 &PrerenderManager::PeriodicCleanup);
1114 void PrerenderManager::StopSchedulingPeriodicCleanups() {
1115 DCHECK(CalledOnValidThread());
1116 repeating_timer_.Stop();
1119 void PrerenderManager::PeriodicCleanup() {
1120 DCHECK(CalledOnValidThread());
1122 base::ElapsedTimer resource_timer;
1124 // Grab a copy of the current PrerenderContents pointers, so that we
1125 // will not interfere with potential deletions of the list.
1126 std::vector<PrerenderContents*>
1127 prerender_contents(active_prerenders_.size());
1128 std::transform(active_prerenders_.begin(), active_prerenders_.end(),
1129 prerender_contents.begin(),
1130 std::mem_fun(&PrerenderData::contents));
1132 // And now check for prerenders using too much memory.
1133 std::for_each(prerender_contents.begin(), prerender_contents.end(),
1134 std::mem_fun(
1135 &PrerenderContents::DestroyWhenUsingTooManyResources));
1137 // Measure how long the resource checks took. http://crbug.com/305419.
1138 UMA_HISTOGRAM_TIMES("Prerender.PeriodicCleanupResourceCheckTime",
1139 resource_timer.Elapsed());
1141 base::ElapsedTimer cleanup_timer;
1143 // Perform deferred cleanup work.
1144 DeleteOldWebContents();
1145 DeleteOldEntries();
1146 if (active_prerenders_.empty())
1147 StopSchedulingPeriodicCleanups();
1149 to_delete_prerenders_.clear();
1151 // Measure how long a the various cleanup tasks took. http://crbug.com/305419.
1152 UMA_HISTOGRAM_TIMES("Prerender.PeriodicCleanupDeleteContentsTime",
1153 cleanup_timer.Elapsed());
1156 void PrerenderManager::PostCleanupTask() {
1157 DCHECK(CalledOnValidThread());
1158 base::MessageLoop::current()->PostTask(
1159 FROM_HERE,
1160 base::Bind(&PrerenderManager::PeriodicCleanup, AsWeakPtr()));
1163 base::TimeTicks PrerenderManager::GetExpiryTimeForNewPrerender(
1164 Origin origin) const {
1165 base::TimeDelta ttl = config_.time_to_live;
1166 if (origin == ORIGIN_LOCAL_PREDICTOR)
1167 ttl = base::TimeDelta::FromSeconds(GetLocalPredictorTTLSeconds());
1168 return GetCurrentTimeTicks() + ttl;
1171 base::TimeTicks PrerenderManager::GetExpiryTimeForNavigatedAwayPrerender()
1172 const {
1173 return GetCurrentTimeTicks() + config_.abandon_time_to_live;
1176 void PrerenderManager::DeleteOldEntries() {
1177 DCHECK(CalledOnValidThread());
1178 while (!active_prerenders_.empty()) {
1179 PrerenderData* prerender_data = active_prerenders_.front();
1180 DCHECK(prerender_data);
1181 DCHECK(prerender_data->contents());
1183 if (prerender_data->expiry_time() > GetCurrentTimeTicks())
1184 return;
1185 prerender_data->contents()->Destroy(FINAL_STATUS_TIMED_OUT);
1189 base::Time PrerenderManager::GetCurrentTime() const {
1190 return base::Time::Now();
1193 base::TimeTicks PrerenderManager::GetCurrentTimeTicks() const {
1194 return base::TimeTicks::Now();
1197 PrerenderContents* PrerenderManager::CreatePrerenderContents(
1198 const GURL& url,
1199 const content::Referrer& referrer,
1200 Origin origin,
1201 uint8 experiment_id) {
1202 DCHECK(CalledOnValidThread());
1203 return prerender_contents_factory_->CreatePrerenderContents(
1204 this, profile_, url, referrer, origin, experiment_id);
1207 void PrerenderManager::SortActivePrerenders() {
1208 std::sort(active_prerenders_.begin(), active_prerenders_.end(),
1209 PrerenderData::OrderByExpiryTime());
1212 PrerenderManager::PrerenderData* PrerenderManager::FindPrerenderData(
1213 const GURL& url,
1214 const SessionStorageNamespace* session_storage_namespace) {
1215 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1216 it != active_prerenders_.end(); ++it) {
1217 if ((*it)->contents()->Matches(url, session_storage_namespace))
1218 return *it;
1220 return NULL;
1223 ScopedVector<PrerenderManager::PrerenderData>::iterator
1224 PrerenderManager::FindIteratorForPrerenderContents(
1225 PrerenderContents* prerender_contents) {
1226 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1227 it != active_prerenders_.end(); ++it) {
1228 if (prerender_contents == (*it)->contents())
1229 return it;
1231 return active_prerenders_.end();
1234 bool PrerenderManager::DoesRateLimitAllowPrerender(Origin origin) const {
1235 DCHECK(CalledOnValidThread());
1236 base::TimeDelta elapsed_time =
1237 GetCurrentTimeTicks() - last_prerender_start_time_;
1238 histograms_->RecordTimeBetweenPrerenderRequests(origin, elapsed_time);
1239 if (!config_.rate_limit_enabled)
1240 return true;
1241 // The LocalPredictor may issue multiple prerenders simultaneously (if so
1242 // configured), so no throttling.
1243 if (origin == ORIGIN_LOCAL_PREDICTOR)
1244 return true;
1245 return elapsed_time >=
1246 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs);
1249 void PrerenderManager::DeleteOldWebContents() {
1250 while (!old_web_contents_list_.empty()) {
1251 WebContents* web_contents = old_web_contents_list_.front();
1252 old_web_contents_list_.pop_front();
1253 // TODO(dominich): should we use Instant Unload Handler here?
1254 delete web_contents;
1258 void PrerenderManager::CleanUpOldNavigations() {
1259 DCHECK(CalledOnValidThread());
1261 // Cutoff. Navigations before this cutoff can be discarded.
1262 base::TimeTicks cutoff = GetCurrentTimeTicks() -
1263 base::TimeDelta::FromMilliseconds(kNavigationRecordWindowMs);
1264 while (!navigations_.empty()) {
1265 if (navigations_.front().time > cutoff)
1266 break;
1267 navigations_.pop_front();
1271 void PrerenderManager::ScheduleDeleteOldWebContents(
1272 WebContents* tab,
1273 OnCloseWebContentsDeleter* deleter) {
1274 old_web_contents_list_.push_back(tab);
1275 PostCleanupTask();
1277 if (deleter) {
1278 ScopedVector<OnCloseWebContentsDeleter>::iterator i = std::find(
1279 on_close_web_contents_deleters_.begin(),
1280 on_close_web_contents_deleters_.end(),
1281 deleter);
1282 DCHECK(i != on_close_web_contents_deleters_.end());
1283 on_close_web_contents_deleters_.erase(i);
1287 void PrerenderManager::AddToHistory(PrerenderContents* contents) {
1288 PrerenderHistory::Entry entry(contents->prerender_url(),
1289 contents->final_status(),
1290 contents->origin(),
1291 base::Time::Now());
1292 prerender_history_->AddEntry(entry);
1295 base::Value* PrerenderManager::GetActivePrerendersAsValue() const {
1296 base::ListValue* list_value = new base::ListValue();
1297 for (ScopedVector<PrerenderData>::const_iterator it =
1298 active_prerenders_.begin();
1299 it != active_prerenders_.end(); ++it) {
1300 if (base::Value* prerender_value = (*it)->contents()->GetAsValue())
1301 list_value->Append(prerender_value);
1303 return list_value;
1306 void PrerenderManager::DestroyAllContents(FinalStatus final_status) {
1307 DeleteOldWebContents();
1308 while (!active_prerenders_.empty()) {
1309 PrerenderContents* contents = active_prerenders_.front()->contents();
1310 contents->Destroy(final_status);
1312 to_delete_prerenders_.clear();
1315 void PrerenderManager::DestroyAndMarkMatchCompleteAsUsed(
1316 PrerenderContents* prerender_contents,
1317 FinalStatus final_status) {
1318 prerender_contents->set_match_complete_status(
1319 PrerenderContents::MATCH_COMPLETE_REPLACED);
1320 histograms_->RecordFinalStatus(prerender_contents->origin(),
1321 prerender_contents->experiment_id(),
1322 PrerenderContents::MATCH_COMPLETE_REPLACEMENT,
1323 FINAL_STATUS_WOULD_HAVE_BEEN_USED);
1324 prerender_contents->Destroy(final_status);
1327 void PrerenderManager::RecordFinalStatusWithoutCreatingPrerenderContents(
1328 const GURL& url, Origin origin, uint8 experiment_id,
1329 FinalStatus final_status) const {
1330 PrerenderHistory::Entry entry(url, final_status, origin, base::Time::Now());
1331 prerender_history_->AddEntry(entry);
1332 RecordFinalStatusWithMatchCompleteStatus(
1333 origin, experiment_id,
1334 PrerenderContents::MATCH_COMPLETE_DEFAULT,
1335 final_status);
1338 void PrerenderManager::Observe(int type,
1339 const content::NotificationSource& source,
1340 const content::NotificationDetails& details) {
1341 switch (type) {
1342 case chrome::NOTIFICATION_COOKIE_CHANGED: {
1343 Profile* profile = content::Source<Profile>(source).ptr();
1344 if (!profile || !profile_->IsSameProfile(profile) ||
1345 profile->IsOffTheRecord()) {
1346 return;
1348 CookieChanged(content::Details<ChromeCookieDetails>(details).ptr());
1349 break;
1351 case chrome::NOTIFICATION_PROFILE_DESTROYED:
1352 DestroyAllContents(FINAL_STATUS_PROFILE_DESTROYED);
1353 on_close_web_contents_deleters_.clear();
1354 break;
1355 default:
1356 NOTREACHED() << "Unexpected notification sent.";
1357 break;
1361 void PrerenderManager::OnCreatingAudioStream(int render_process_id,
1362 int render_frame_id) {
1363 content::RenderFrameHost* render_frame_host =
1364 content::RenderFrameHost::FromID(render_process_id, render_frame_id);
1365 WebContents* tab = WebContents::FromRenderFrameHost(render_frame_host);
1366 if (!tab)
1367 return;
1369 PrerenderContents* prerender_contents = GetPrerenderContents(tab);
1370 if (!prerender_contents)
1371 return;
1373 prerender_contents->Destroy(prerender::FINAL_STATUS_CREATING_AUDIO_STREAM);
1376 void PrerenderManager::RecordLikelyLoginOnURL(const GURL& url) {
1377 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1378 if (!url.SchemeIsHTTPOrHTTPS())
1379 return;
1380 if (logged_in_predictor_table_.get()) {
1381 BrowserThread::PostTask(
1382 BrowserThread::DB,
1383 FROM_HERE,
1384 base::Bind(&LoggedInPredictorTable::AddDomainFromURL,
1385 logged_in_predictor_table_,
1386 url));
1388 std::string key = LoggedInPredictorTable::GetKey(url);
1389 if (!logged_in_state_.get())
1390 return;
1391 if (logged_in_state_->count(key))
1392 return;
1393 (*logged_in_state_)[key] = base::Time::Now().ToInternalValue();
1396 void PrerenderManager::CheckIfLikelyLoggedInOnURL(
1397 const GURL& url,
1398 bool* lookup_result,
1399 bool* database_was_present,
1400 const base::Closure& result_cb) {
1401 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1402 if (!logged_in_predictor_table_.get()) {
1403 *database_was_present = false;
1404 *lookup_result = false;
1405 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, result_cb);
1406 return;
1408 BrowserThread::PostTaskAndReply(
1409 BrowserThread::DB, FROM_HERE,
1410 base::Bind(&LoggedInPredictorTable::HasUserLoggedIn,
1411 logged_in_predictor_table_,
1412 url,
1413 lookup_result,
1414 database_was_present),
1415 result_cb);
1419 void PrerenderManager::CookieChanged(ChromeCookieDetails* details) {
1420 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1422 if (!logged_in_predictor_table_.get())
1423 return;
1425 // We only care when a cookie has been removed.
1426 if (!details->removed)
1427 return;
1429 std::string domain_key =
1430 LoggedInPredictorTable::GetKeyFromDomain(details->cookie->Domain());
1432 // If we have no record of this domain as a potentially logged in domain,
1433 // nothing to do here.
1434 if (logged_in_state_.get() && logged_in_state_->count(domain_key) < 1)
1435 return;
1437 net::URLRequestContextGetter* rq_context = profile_->GetRequestContext();
1438 if (!rq_context)
1439 return;
1441 BrowserThread::PostTask(
1442 BrowserThread::IO, FROM_HERE,
1443 base::Bind(&CheckIfCookiesExistForDomainOnIOThread,
1444 base::Unretained(rq_context),
1445 domain_key,
1446 base::Bind(
1447 &PrerenderManager::CookieChangedAnyCookiesLeftLookupResult,
1448 AsWeakPtr(),
1449 domain_key)
1453 void PrerenderManager::CookieChangedAnyCookiesLeftLookupResult(
1454 const std::string& domain_key,
1455 bool cookies_exist) {
1456 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1458 if (cookies_exist)
1459 return;
1461 if (logged_in_predictor_table_.get()) {
1462 BrowserThread::PostTask(BrowserThread::DB,
1463 FROM_HERE,
1464 base::Bind(&LoggedInPredictorTable::DeleteDomain,
1465 logged_in_predictor_table_,
1466 domain_key));
1469 if (logged_in_state_.get())
1470 logged_in_state_->erase(domain_key);
1473 void PrerenderManager::LoggedInPredictorDataReceived(
1474 scoped_ptr<LoggedInStateMap> new_map) {
1475 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1476 logged_in_state_.swap(new_map);
1479 // static
1480 void PrerenderManager::RecordCookieEvent(int process_id,
1481 int frame_id,
1482 const GURL& url,
1483 const GURL& frame_url,
1484 bool is_for_blocking_resource,
1485 PrerenderContents::CookieEvent event,
1486 const net::CookieList* cookie_list) {
1487 RenderFrameHost* rfh = RenderFrameHost::FromID(process_id, frame_id);
1488 WebContents* web_contents = WebContents::FromRenderFrameHost(rfh);
1489 if (!web_contents)
1490 return;
1492 bool is_main_frame = (rfh == web_contents->GetMainFrame());
1494 bool is_third_party_cookie =
1495 (!frame_url.is_empty() &&
1496 !net::registry_controlled_domains::SameDomainOrHost(
1497 url, frame_url,
1498 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES));
1500 PrerenderContents* prerender_contents =
1501 PrerenderContents::FromWebContents(web_contents);
1503 if (!prerender_contents)
1504 return;
1506 base::Time earliest_create_date;
1507 if (event == PrerenderContents::COOKIE_EVENT_SEND) {
1508 if (!cookie_list || cookie_list->empty())
1509 return;
1510 for (size_t i = 0; i < cookie_list->size(); i++) {
1511 if (earliest_create_date.is_null() ||
1512 (*cookie_list)[i].CreationDate() < earliest_create_date) {
1513 earliest_create_date = (*cookie_list)[i].CreationDate();
1518 prerender_contents->RecordCookieEvent(event,
1519 is_main_frame && url == frame_url,
1520 is_third_party_cookie,
1521 is_for_blocking_resource,
1522 earliest_create_date);
1525 void PrerenderManager::RecordCookieStatus(Origin origin,
1526 uint8 experiment_id,
1527 int cookie_status) const {
1528 histograms_->RecordCookieStatus(origin, experiment_id, cookie_status);
1531 void PrerenderManager::RecordCookieSendType(Origin origin,
1532 uint8 experiment_id,
1533 int cookie_send_type) const {
1534 histograms_->RecordCookieSendType(origin, experiment_id, cookie_send_type);
1537 void PrerenderManager::OnHistoryServiceDidQueryURL(
1538 Origin origin,
1539 uint8 experiment_id,
1540 bool success,
1541 const history::URLRow& url_row,
1542 const history::VisitVector& /*visits*/) {
1543 histograms_->RecordPrerenderPageVisitedStatus(origin, experiment_id, success);
1546 void PrerenderManager::RecordNetworkBytes(Origin origin,
1547 bool used,
1548 int64 prerender_bytes) {
1549 if (!ActuallyPrerendering())
1550 return;
1551 int64 recent_profile_bytes =
1552 profile_network_bytes_ - last_recorded_profile_network_bytes_;
1553 last_recorded_profile_network_bytes_ = profile_network_bytes_;
1554 DCHECK_GE(recent_profile_bytes, 0);
1555 histograms_->RecordNetworkBytes(
1556 origin, used, prerender_bytes, recent_profile_bytes);
1559 bool PrerenderManager::IsEnabled() const {
1560 DCHECK(CalledOnValidThread());
1562 return chrome_browser_net::CanPrefetchAndPrerenderUI(profile_->GetPrefs());
1565 void PrerenderManager::AddProfileNetworkBytesIfEnabled(int64 bytes) {
1566 DCHECK_GE(bytes, 0);
1567 if (IsEnabled() && ActuallyPrerendering())
1568 profile_network_bytes_ += bytes;
1571 void PrerenderManager::AddPrerenderProcessHost(
1572 content::RenderProcessHost* process_host) {
1573 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1574 DCHECK(prerender_process_hosts_.find(process_host) ==
1575 prerender_process_hosts_.end());
1576 prerender_process_hosts_.insert(process_host);
1577 process_host->AddObserver(this);
1580 bool PrerenderManager::MayReuseProcessHost(
1581 content::RenderProcessHost* process_host) {
1582 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1583 // Isolate prerender processes to make the resource monitoring check more
1584 // accurate.
1585 return (prerender_process_hosts_.find(process_host) ==
1586 prerender_process_hosts_.end());
1589 void PrerenderManager::RenderProcessHostDestroyed(
1590 content::RenderProcessHost* host) {
1591 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1592 size_t erased = prerender_process_hosts_.erase(host);
1593 DCHECK_EQ(1u, erased);
1596 } // namespace prerender