Give names to all utility processes.
[chromium-blink-merge.git] / chrome / browser / prerender / prerender_manager.cc
blob629e113f32a34419f5afe021e611aafb3ae8956a
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/net/chrome_cookie_notification_details.h"
25 #include "chrome/browser/net/prediction_options.h"
26 #include "chrome/browser/predictors/predictor_database.h"
27 #include "chrome/browser/predictors/predictor_database_factory.h"
28 #include "chrome/browser/prerender/prerender_contents.h"
29 #include "chrome/browser/prerender/prerender_field_trial.h"
30 #include "chrome/browser/prerender/prerender_final_status.h"
31 #include "chrome/browser/prerender/prerender_handle.h"
32 #include "chrome/browser/prerender/prerender_histograms.h"
33 #include "chrome/browser/prerender/prerender_history.h"
34 #include "chrome/browser/prerender/prerender_local_predictor.h"
35 #include "chrome/browser/prerender/prerender_manager_factory.h"
36 #include "chrome/browser/prerender/prerender_tab_helper.h"
37 #include "chrome/browser/prerender/prerender_util.h"
38 #include "chrome/browser/profiles/profile.h"
39 #include "chrome/browser/search/search.h"
40 #include "chrome/browser/tab_contents/tab_util.h"
41 #include "chrome/browser/ui/browser_navigator.h"
42 #include "chrome/browser/ui/tab_contents/core_tab_helper.h"
43 #include "chrome/browser/ui/tab_contents/core_tab_helper_delegate.h"
44 #include "chrome/common/chrome_switches.h"
45 #include "chrome/common/prerender_messages.h"
46 #include "chrome/common/prerender_types.h"
47 #include "content/public/browser/browser_thread.h"
48 #include "content/public/browser/devtools_agent_host.h"
49 #include "content/public/browser/navigation_controller.h"
50 #include "content/public/browser/notification_service.h"
51 #include "content/public/browser/notification_source.h"
52 #include "content/public/browser/render_frame_host.h"
53 #include "content/public/browser/render_process_host.h"
54 #include "content/public/browser/render_view_host.h"
55 #include "content/public/browser/resource_request_details.h"
56 #include "content/public/browser/session_storage_namespace.h"
57 #include "content/public/browser/site_instance.h"
58 #include "content/public/browser/web_contents.h"
59 #include "content/public/browser/web_contents_delegate.h"
60 #include "content/public/common/url_constants.h"
61 #include "extensions/common/constants.h"
62 #include "net/url_request/url_request_context.h"
63 #include "net/url_request/url_request_context_getter.h"
65 using content::BrowserThread;
66 using content::RenderViewHost;
67 using content::SessionStorageNamespace;
68 using content::WebContents;
69 using predictors::LoggedInPredictorTable;
71 namespace prerender {
73 namespace {
75 // Time interval at which periodic cleanups are performed.
76 const int kPeriodicCleanupIntervalMs = 1000;
78 // Valid HTTP methods for prerendering.
79 const char* const kValidHttpMethods[] = {
80 "GET",
81 "HEAD",
82 "OPTIONS",
83 "POST",
84 "TRACE",
87 // Length of prerender history, for display in chrome://net-internals
88 const int kHistoryLength = 100;
90 // Indicates whether a Prerender has been cancelled such that we need
91 // a dummy replacement for the purpose of recording the correct PPLT for
92 // the Match Complete case.
93 // Traditionally, "Match" means that a prerendered page was actually visited &
94 // the prerender was used. Our goal is to have "Match" cases line up in the
95 // control group & the experiment group, so that we can make meaningful
96 // comparisons of improvements. However, in the control group, since we don't
97 // actually perform prerenders, many of the cancellation reasons cannot be
98 // detected. Therefore, in the Prerender group, when we cancel for one of these
99 // reasons, we keep track of a dummy Prerender representing what we would
100 // have in the control group. If that dummy prerender in the prerender group
101 // would then be swapped in (but isn't actually b/c it's a dummy), we record
102 // this as a MatchComplete. This allows us to compare MatchComplete's
103 // across Prerender & Control group which ideally should be lining up.
104 // This ensures that there is no bias in terms of the page load times
105 // of the pages forming the difference between the two sets.
107 bool NeedMatchCompleteDummyForFinalStatus(FinalStatus final_status) {
108 return final_status != FINAL_STATUS_USED &&
109 final_status != FINAL_STATUS_TIMED_OUT &&
110 final_status != FINAL_STATUS_MANAGER_SHUTDOWN &&
111 final_status != FINAL_STATUS_PROFILE_DESTROYED &&
112 final_status != FINAL_STATUS_APP_TERMINATING &&
113 final_status != FINAL_STATUS_WINDOW_OPENER &&
114 final_status != FINAL_STATUS_CACHE_OR_HISTORY_CLEARED &&
115 final_status != FINAL_STATUS_CANCELLED &&
116 final_status != FINAL_STATUS_DEVTOOLS_ATTACHED &&
117 final_status != FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING &&
118 final_status != FINAL_STATUS_PAGE_BEING_CAPTURED &&
119 final_status != FINAL_STATUS_NAVIGATION_UNCOMMITTED &&
120 final_status != FINAL_STATUS_NON_EMPTY_BROWSING_INSTANCE;
123 void CheckIfCookiesExistForDomainResultOnUIThread(
124 const net::CookieMonster::HasCookiesForETLDP1Callback& callback,
125 bool cookies_exist) {
126 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
127 callback.Run(cookies_exist);
130 void CheckIfCookiesExistForDomainResultOnIOThread(
131 const net::CookieMonster::HasCookiesForETLDP1Callback& callback,
132 bool cookies_exist) {
133 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
134 BrowserThread::PostTask(
135 BrowserThread::UI,
136 FROM_HERE,
137 base::Bind(&CheckIfCookiesExistForDomainResultOnUIThread,
138 callback,
139 cookies_exist));
142 void CheckIfCookiesExistForDomainOnIOThread(
143 net::URLRequestContextGetter* rq_context,
144 const std::string& domain_key,
145 const net::CookieMonster::HasCookiesForETLDP1Callback& callback) {
146 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
147 net::CookieStore* cookie_store =
148 rq_context->GetURLRequestContext()->cookie_store();
149 cookie_store->GetCookieMonster()->HasCookiesForETLDP1Async(
150 domain_key,
151 base::Bind(&CheckIfCookiesExistForDomainResultOnIOThread, callback));
154 } // namespace
156 class PrerenderManager::OnCloseWebContentsDeleter
157 : public content::WebContentsDelegate,
158 public base::SupportsWeakPtr<
159 PrerenderManager::OnCloseWebContentsDeleter> {
160 public:
161 OnCloseWebContentsDeleter(PrerenderManager* manager,
162 WebContents* tab)
163 : manager_(manager),
164 tab_(tab),
165 suppressed_dialog_(false) {
166 tab_->SetDelegate(this);
167 base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
168 base::Bind(&OnCloseWebContentsDeleter::ScheduleWebContentsForDeletion,
169 AsWeakPtr(), true),
170 base::TimeDelta::FromSeconds(kDeleteWithExtremePrejudiceSeconds));
173 void CloseContents(WebContents* source) override {
174 DCHECK_EQ(tab_, source);
175 ScheduleWebContentsForDeletion(false);
178 void SwappedOut(WebContents* source) override {
179 DCHECK_EQ(tab_, source);
180 ScheduleWebContentsForDeletion(false);
183 bool ShouldSuppressDialogs(WebContents* source) override {
184 // Use this as a proxy for getting statistics on how often we fail to honor
185 // the beforeunload event.
186 DCHECK_EQ(tab_, source);
187 suppressed_dialog_ = true;
188 return true;
191 private:
192 static const int kDeleteWithExtremePrejudiceSeconds = 3;
194 void ScheduleWebContentsForDeletion(bool timeout) {
195 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterTimeout", timeout);
196 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterSuppressedDialog",
197 suppressed_dialog_);
198 tab_->SetDelegate(NULL);
199 manager_->ScheduleDeleteOldWebContents(tab_.release(), this);
200 // |this| is deleted at this point.
203 PrerenderManager* manager_;
204 scoped_ptr<WebContents> tab_;
205 bool suppressed_dialog_;
207 DISALLOW_COPY_AND_ASSIGN(OnCloseWebContentsDeleter);
210 // static
211 int PrerenderManager::prerenders_per_session_count_ = 0;
213 // static
214 PrerenderManager::PrerenderManagerMode PrerenderManager::mode_ =
215 PRERENDER_MODE_ENABLED;
217 struct PrerenderManager::NavigationRecord {
218 NavigationRecord(const GURL& url, base::TimeTicks time)
219 : url(url),
220 time(time) {
223 GURL url;
224 base::TimeTicks time;
227 PrerenderManager::PrerenderManager(Profile* profile)
228 : profile_(profile),
229 prerender_contents_factory_(PrerenderContents::CreateFactory()),
230 last_prerender_start_time_(GetCurrentTimeTicks() -
231 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs)),
232 prerender_history_(new PrerenderHistory(kHistoryLength)),
233 histograms_(new PrerenderHistograms()),
234 profile_network_bytes_(0),
235 last_recorded_profile_network_bytes_(0) {
236 // There are some assumptions that the PrerenderManager is on the UI thread.
237 // Any other checks simply make sure that the PrerenderManager is accessed on
238 // the same thread that it was created on.
239 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
241 if (IsLocalPredictorEnabled())
242 local_predictor_.reset(new PrerenderLocalPredictor(this));
244 if (IsLoggedInPredictorEnabled() && !profile_->IsOffTheRecord()) {
245 predictors::PredictorDatabase* predictor_db =
246 predictors::PredictorDatabaseFactory::GetForProfile(profile);
247 if (predictor_db) {
248 logged_in_predictor_table_ = predictor_db->logged_in_table();
249 scoped_ptr<LoggedInStateMap> new_state_map(new LoggedInStateMap);
250 LoggedInStateMap* new_state_map_ptr = new_state_map.get();
251 BrowserThread::PostTaskAndReply(
252 BrowserThread::DB, FROM_HERE,
253 base::Bind(&LoggedInPredictorTable::GetAllData,
254 logged_in_predictor_table_,
255 new_state_map_ptr),
256 base::Bind(&PrerenderManager::LoggedInPredictorDataReceived,
257 AsWeakPtr(),
258 base::Passed(&new_state_map)));
262 // Certain experiments override our default config_ values.
263 switch (PrerenderManager::GetMode()) {
264 case PrerenderManager::PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP:
265 config_.max_link_concurrency = 4;
266 config_.max_link_concurrency_per_launcher = 2;
267 break;
268 case PrerenderManager::PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP:
269 config_.time_to_live = base::TimeDelta::FromMinutes(15);
270 break;
271 default:
272 break;
275 notification_registrar_.Add(
276 this, chrome::NOTIFICATION_COOKIE_CHANGED,
277 content::NotificationService::AllBrowserContextsAndSources());
279 notification_registrar_.Add(
280 this, chrome::NOTIFICATION_PROFILE_DESTROYED,
281 content::Source<Profile>(profile_));
283 MediaCaptureDevicesDispatcher::GetInstance()->AddObserver(this);
286 PrerenderManager::~PrerenderManager() {
287 MediaCaptureDevicesDispatcher::GetInstance()->RemoveObserver(this);
289 // The earlier call to KeyedService::Shutdown() should have
290 // emptied these vectors already.
291 DCHECK(active_prerenders_.empty());
292 DCHECK(to_delete_prerenders_.empty());
294 for (PrerenderProcessSet::const_iterator it =
295 prerender_process_hosts_.begin();
296 it != prerender_process_hosts_.end();
297 ++it) {
298 (*it)->RemoveObserver(this);
302 void PrerenderManager::Shutdown() {
303 DestroyAllContents(FINAL_STATUS_MANAGER_SHUTDOWN);
304 on_close_web_contents_deleters_.clear();
305 // Must happen before |profile_| is set to NULL as
306 // |local_predictor_| accesses it.
307 if (local_predictor_)
308 local_predictor_->Shutdown();
309 profile_ = NULL;
311 DCHECK(active_prerenders_.empty());
314 PrerenderHandle* PrerenderManager::AddPrerenderFromLinkRelPrerender(
315 int process_id,
316 int route_id,
317 const GURL& url,
318 const uint32 rel_types,
319 const content::Referrer& referrer,
320 const gfx::Size& size) {
321 Origin origin = rel_types & PrerenderRelTypePrerender ?
322 ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN :
323 ORIGIN_LINK_REL_NEXT;
324 SessionStorageNamespace* session_storage_namespace = NULL;
325 // Unit tests pass in a process_id == -1.
326 if (process_id != -1) {
327 RenderViewHost* source_render_view_host =
328 RenderViewHost::FromID(process_id, route_id);
329 if (!source_render_view_host)
330 return NULL;
331 WebContents* source_web_contents =
332 WebContents::FromRenderViewHost(source_render_view_host);
333 if (!source_web_contents)
334 return NULL;
335 if (origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN &&
336 source_web_contents->GetURL().host() == url.host()) {
337 origin = ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN;
339 // TODO(ajwong): This does not correctly handle storage for isolated apps.
340 session_storage_namespace =
341 source_web_contents->GetController()
342 .GetDefaultSessionStorageNamespace();
345 return AddPrerender(origin, url, referrer, size, session_storage_namespace);
348 PrerenderHandle* PrerenderManager::AddPrerenderFromOmnibox(
349 const GURL& url,
350 SessionStorageNamespace* session_storage_namespace,
351 const gfx::Size& size) {
352 if (!IsOmniboxEnabled(profile_))
353 return NULL;
354 return AddPrerender(ORIGIN_OMNIBOX, url, content::Referrer(), size,
355 session_storage_namespace);
358 PrerenderHandle* PrerenderManager::AddPrerenderFromLocalPredictor(
359 const GURL& url,
360 SessionStorageNamespace* session_storage_namespace,
361 const gfx::Size& size) {
362 return AddPrerender(ORIGIN_LOCAL_PREDICTOR, url, content::Referrer(),
363 size, session_storage_namespace);
366 PrerenderHandle* PrerenderManager::AddPrerenderFromExternalRequest(
367 const GURL& url,
368 const content::Referrer& referrer,
369 SessionStorageNamespace* session_storage_namespace,
370 const gfx::Size& size) {
371 return AddPrerender(ORIGIN_EXTERNAL_REQUEST, url, referrer, size,
372 session_storage_namespace);
375 PrerenderHandle* PrerenderManager::AddPrerenderForInstant(
376 const GURL& url,
377 content::SessionStorageNamespace* session_storage_namespace,
378 const gfx::Size& size) {
379 DCHECK(chrome::ShouldPrefetchSearchResults());
380 return AddPrerender(ORIGIN_INSTANT, url, content::Referrer(), size,
381 session_storage_namespace);
384 void PrerenderManager::CancelAllPrerenders() {
385 DCHECK(CalledOnValidThread());
386 while (!active_prerenders_.empty()) {
387 PrerenderContents* prerender_contents =
388 active_prerenders_.front()->contents();
389 prerender_contents->Destroy(FINAL_STATUS_CANCELLED);
393 bool PrerenderManager::MaybeUsePrerenderedPage(const GURL& url,
394 chrome::NavigateParams* params) {
395 DCHECK(CalledOnValidThread());
397 content::WebContents* web_contents = params->target_contents;
398 DCHECK(!IsWebContentsPrerendering(web_contents, NULL));
400 // Don't prerender if the navigation involves some special parameters.
401 if (params->uses_post || !params->extra_headers.empty())
402 return false;
404 DeleteOldEntries();
405 to_delete_prerenders_.clear();
407 // First, try to find prerender data with the correct session storage
408 // namespace.
409 // TODO(ajwong): This doesn't handle isolated apps correctly.
410 PrerenderData* prerender_data = FindPrerenderData(
411 url,
412 web_contents->GetController().GetDefaultSessionStorageNamespace());
413 if (!prerender_data)
414 return false;
415 DCHECK(prerender_data->contents());
417 WebContents* new_web_contents = SwapInternal(
418 url, web_contents, prerender_data,
419 params->should_replace_current_entry);
420 if (!new_web_contents)
421 return false;
423 // Record the new target_contents for the callers.
424 params->target_contents = new_web_contents;
425 return true;
428 WebContents* PrerenderManager::SwapInternal(
429 const GURL& url,
430 WebContents* web_contents,
431 PrerenderData* prerender_data,
432 bool should_replace_current_entry) {
433 DCHECK(CalledOnValidThread());
434 DCHECK(!IsWebContentsPrerendering(web_contents, NULL));
436 // Only swap if the target WebContents has a CoreTabHelper delegate to swap
437 // out of it. For a normal WebContents, this is if it is in a TabStripModel.
438 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(web_contents);
439 if (!core_tab_helper || !core_tab_helper->delegate())
440 return NULL;
442 PrerenderTabHelper* target_tab_helper =
443 PrerenderTabHelper::FromWebContents(web_contents);
444 if (!target_tab_helper) {
445 NOTREACHED();
446 return NULL;
449 if (IsNoSwapInExperiment(prerender_data->contents()->experiment_id()))
450 return NULL;
452 if (WebContents* new_web_contents =
453 prerender_data->contents()->prerender_contents()) {
454 if (web_contents == new_web_contents)
455 return NULL; // Do not swap in to ourself.
457 // We cannot swap in if there is no last committed entry, because we would
458 // show a blank page under an existing entry from the current tab. Even if
459 // there is a pending entry, it may not commit.
460 // TODO(creis): If there is a pending navigation and no last committed
461 // entry, we might be able to transfer the network request instead.
462 if (!new_web_contents->GetController().CanPruneAllButLastCommitted()) {
463 // Abort this prerender so it is not used later. http://crbug.com/292121
464 prerender_data->contents()->Destroy(FINAL_STATUS_NAVIGATION_UNCOMMITTED);
465 return NULL;
469 // Do not swap if the target WebContents is not the only WebContents in its
470 // current BrowsingInstance.
471 if (web_contents->GetSiteInstance()->GetRelatedActiveContentsCount() != 1u) {
472 DCHECK_GT(
473 web_contents->GetSiteInstance()->GetRelatedActiveContentsCount(), 1u);
474 prerender_data->contents()->Destroy(
475 FINAL_STATUS_NON_EMPTY_BROWSING_INSTANCE);
476 return NULL;
479 // Do not use the prerendered version if there is an opener object.
480 if (web_contents->HasOpener()) {
481 prerender_data->contents()->Destroy(FINAL_STATUS_WINDOW_OPENER);
482 return NULL;
485 // Do not swap in the prerender if the current WebContents is being captured.
486 if (web_contents->GetCapturerCount() > 0) {
487 prerender_data->contents()->Destroy(FINAL_STATUS_PAGE_BEING_CAPTURED);
488 return NULL;
491 // If we are just in the control group (which can be detected by noticing
492 // that prerendering hasn't even started yet), record that |web_contents| now
493 // would be showing a prerendered contents, but otherwise, don't do anything.
494 if (!prerender_data->contents()->prerendering_has_started()) {
495 target_tab_helper->WouldHavePrerenderedNextLoad(
496 prerender_data->contents()->origin());
497 prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED);
498 return NULL;
501 // Don't use prerendered pages if debugger is attached to the tab.
502 // See http://crbug.com/98541
503 if (content::DevToolsAgentHost::IsDebuggerAttached(web_contents)) {
504 DestroyAndMarkMatchCompleteAsUsed(prerender_data->contents(),
505 FINAL_STATUS_DEVTOOLS_ATTACHED);
506 return NULL;
509 // If the prerendered page is in the middle of a cross-site navigation,
510 // don't swap it in because there isn't a good way to merge histories.
511 if (prerender_data->contents()->IsCrossSiteNavigationPending()) {
512 DestroyAndMarkMatchCompleteAsUsed(
513 prerender_data->contents(),
514 FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING);
515 return NULL;
518 // For bookkeeping purposes, we need to mark this WebContents to
519 // reflect that it would have been prerendered.
520 if (GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP) {
521 target_tab_helper->WouldHavePrerenderedNextLoad(
522 prerender_data->contents()->origin());
523 prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED);
524 return NULL;
527 // At this point, we've determined that we will use the prerender.
528 content::RenderProcessHost* process_host =
529 prerender_data->contents()->GetRenderViewHost()->GetProcess();
530 process_host->RemoveObserver(this);
531 prerender_process_hosts_.erase(process_host);
532 if (!prerender_data->contents()->load_start_time().is_null()) {
533 histograms_->RecordTimeUntilUsed(
534 prerender_data->contents()->origin(),
535 GetCurrentTimeTicks() - prerender_data->contents()->load_start_time());
537 histograms_->RecordAbandonTimeUntilUsed(
538 prerender_data->contents()->origin(),
539 prerender_data->abandon_time().is_null() ?
540 base::TimeDelta() :
541 GetCurrentTimeTicks() - prerender_data->abandon_time());
543 histograms_->RecordPerSessionCount(prerender_data->contents()->origin(),
544 ++prerenders_per_session_count_);
545 histograms_->RecordUsedPrerender(prerender_data->contents()->origin());
547 ScopedVector<PrerenderData>::iterator to_erase =
548 FindIteratorForPrerenderContents(prerender_data->contents());
549 DCHECK(active_prerenders_.end() != to_erase);
550 DCHECK_EQ(prerender_data, *to_erase);
551 scoped_ptr<PrerenderContents>
552 prerender_contents(prerender_data->ReleaseContents());
553 active_prerenders_.erase(to_erase);
555 // Mark prerender as used.
556 prerender_contents->PrepareForUse();
558 WebContents* new_web_contents =
559 prerender_contents->ReleasePrerenderContents();
560 WebContents* old_web_contents = web_contents;
561 DCHECK(new_web_contents);
562 DCHECK(old_web_contents);
564 // Merge the browsing history.
565 new_web_contents->GetController().CopyStateFromAndPrune(
566 &old_web_contents->GetController(),
567 should_replace_current_entry);
568 CoreTabHelper::FromWebContents(old_web_contents)->delegate()->
569 SwapTabContents(old_web_contents,
570 new_web_contents,
571 true,
572 prerender_contents->has_finished_loading());
573 prerender_contents->CommitHistory(new_web_contents);
575 // Update PPLT metrics:
576 // If the tab has finished loading, record a PPLT of 0.
577 // If the tab is still loading, reset its start time to the current time.
578 PrerenderTabHelper* prerender_tab_helper =
579 PrerenderTabHelper::FromWebContents(new_web_contents);
580 DCHECK(prerender_tab_helper != NULL);
581 prerender_tab_helper->PrerenderSwappedIn();
583 if (old_web_contents->NeedToFireBeforeUnload()) {
584 // Schedule the delete to occur after the tab has run its unload handlers.
585 // TODO(davidben): Honor the beforeunload event. http://crbug.com/304932
586 on_close_web_contents_deleters_.push_back(
587 new OnCloseWebContentsDeleter(this, old_web_contents));
588 old_web_contents->DispatchBeforeUnload(false);
589 } else {
590 // No unload handler to run, so delete asap.
591 ScheduleDeleteOldWebContents(old_web_contents, NULL);
594 // TODO(cbentzel): Should prerender_contents move to the pending delete
595 // list, instead of deleting directly here?
596 AddToHistory(prerender_contents.get());
597 RecordNavigation(url);
598 return new_web_contents;
601 void PrerenderManager::MoveEntryToPendingDelete(PrerenderContents* entry,
602 FinalStatus final_status) {
603 DCHECK(CalledOnValidThread());
604 DCHECK(entry);
606 ScopedVector<PrerenderData>::iterator it =
607 FindIteratorForPrerenderContents(entry);
608 DCHECK(it != active_prerenders_.end());
610 // If this PrerenderContents is being deleted due to a cancellation any time
611 // after the prerender has started then we need to create a dummy replacement
612 // for PPLT accounting purposes for the Match Complete group. This is the case
613 // if the cancellation is for any reason that would not occur in the control
614 // group case.
615 if (entry->prerendering_has_started() &&
616 entry->match_complete_status() ==
617 PrerenderContents::MATCH_COMPLETE_DEFAULT &&
618 NeedMatchCompleteDummyForFinalStatus(final_status) &&
619 ActuallyPrerendering() &&
620 GetMode() == PRERENDER_MODE_EXPERIMENT_MATCH_COMPLETE_GROUP) {
621 // TODO(tburkard): I'd like to DCHECK that we are actually prerendering.
622 // However, what if new conditions are added and
623 // NeedMatchCompleteDummyForFinalStatus is not being updated. Not sure
624 // what's the best thing to do here. For now, I will just check whether
625 // we are actually prerendering.
626 (*it)->MakeIntoMatchCompleteReplacement();
627 } else {
628 to_delete_prerenders_.push_back(*it);
629 active_prerenders_.weak_erase(it);
632 // Destroy the old WebContents relatively promptly to reduce resource usage.
633 PostCleanupTask();
636 void PrerenderManager::RecordPageLoadTimeNotSwappedIn(
637 Origin origin,
638 base::TimeDelta page_load_time,
639 const GURL& url) {
640 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
641 histograms_->RecordPageLoadTimeNotSwappedIn(origin, page_load_time, url);
644 void PrerenderManager::RecordPerceivedPageLoadTime(
645 Origin origin,
646 NavigationType navigation_type,
647 base::TimeDelta perceived_page_load_time,
648 double fraction_plt_elapsed_at_swap_in,
649 const GURL& url) {
650 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
651 if (!IsEnabled())
652 return;
654 histograms_->RecordPerceivedPageLoadTime(
655 origin, perceived_page_load_time, navigation_type, url);
657 if (navigation_type == NAVIGATION_TYPE_PRERENDERED) {
658 histograms_->RecordPercentLoadDoneAtSwapin(
659 origin, fraction_plt_elapsed_at_swap_in);
661 if (local_predictor_) {
662 local_predictor_->OnPLTEventForURL(url, perceived_page_load_time);
666 // static
667 PrerenderManager::PrerenderManagerMode PrerenderManager::GetMode() {
668 return mode_;
671 // static
672 void PrerenderManager::SetMode(PrerenderManagerMode mode) {
673 mode_ = mode;
676 // static
677 const char* PrerenderManager::GetModeString() {
678 switch (mode_) {
679 case PRERENDER_MODE_DISABLED:
680 return "_Disabled";
681 case PRERENDER_MODE_ENABLED:
682 case PRERENDER_MODE_EXPERIMENT_PRERENDER_GROUP:
683 return "_Enabled";
684 case PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP:
685 return "_Control";
686 case PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP:
687 return "_Multi";
688 case PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP:
689 return "_15MinTTL";
690 case PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP:
691 return "_NoUse";
692 case PRERENDER_MODE_EXPERIMENT_MATCH_COMPLETE_GROUP:
693 return "_MatchComplete";
694 case PRERENDER_MODE_MAX:
695 default:
696 NOTREACHED() << "Invalid PrerenderManager mode.";
697 break;
699 return "";
702 // static
703 bool PrerenderManager::IsPrerenderingPossible() {
704 return GetMode() != PRERENDER_MODE_DISABLED;
707 // static
708 bool PrerenderManager::ActuallyPrerendering() {
709 return IsPrerenderingPossible() && !IsControlGroup(kNoExperiment);
712 // static
713 bool PrerenderManager::IsControlGroup(uint8 experiment_id) {
714 return GetMode() == PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP ||
715 IsControlGroupExperiment(experiment_id);
718 // static
719 bool PrerenderManager::IsNoUseGroup() {
720 return GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP;
723 bool PrerenderManager::IsWebContentsPrerendering(
724 const WebContents* web_contents,
725 Origin* origin) const {
726 DCHECK(CalledOnValidThread());
727 if (PrerenderContents* prerender_contents =
728 GetPrerenderContents(web_contents)) {
729 if (origin)
730 *origin = prerender_contents->origin();
731 return true;
733 return false;
736 bool PrerenderManager::HasPrerenderedUrl(
737 GURL url,
738 content::WebContents* web_contents) const {
739 content::SessionStorageNamespace* session_storage_namespace = web_contents->
740 GetController().GetDefaultSessionStorageNamespace();
742 for (ScopedVector<PrerenderData>::const_iterator it =
743 active_prerenders_.begin();
744 it != active_prerenders_.end(); ++it) {
745 PrerenderContents* prerender_contents = (*it)->contents();
746 if (prerender_contents->Matches(url, session_storage_namespace)) {
747 return true;
750 return false;
753 PrerenderContents* PrerenderManager::GetPrerenderContents(
754 const content::WebContents* web_contents) const {
755 DCHECK(CalledOnValidThread());
756 for (ScopedVector<PrerenderData>::const_iterator it =
757 active_prerenders_.begin();
758 it != active_prerenders_.end(); ++it) {
759 WebContents* prerender_web_contents =
760 (*it)->contents()->prerender_contents();
761 if (prerender_web_contents == web_contents) {
762 return (*it)->contents();
766 // Also check the pending-deletion list. If the prerender is in pending
767 // delete, anyone with a handle on the WebContents needs to know.
768 for (ScopedVector<PrerenderData>::const_iterator it =
769 to_delete_prerenders_.begin();
770 it != to_delete_prerenders_.end(); ++it) {
771 WebContents* prerender_web_contents =
772 (*it)->contents()->prerender_contents();
773 if (prerender_web_contents == web_contents) {
774 return (*it)->contents();
777 return NULL;
780 PrerenderContents* PrerenderManager::GetPrerenderContentsForRoute(
781 int child_id,
782 int route_id) const {
783 content::WebContents* web_contents =
784 tab_util::GetWebContentsByID(child_id, route_id);
785 if (web_contents == NULL)
786 return NULL;
787 return GetPrerenderContents(web_contents);
790 const std::vector<WebContents*>
791 PrerenderManager::GetAllPrerenderingContents() const {
792 DCHECK(CalledOnValidThread());
793 std::vector<WebContents*> result;
795 for (ScopedVector<PrerenderData>::const_iterator it =
796 active_prerenders_.begin();
797 it != active_prerenders_.end(); ++it) {
798 if (WebContents* contents = (*it)->contents()->prerender_contents())
799 result.push_back(contents);
802 return result;
805 bool PrerenderManager::HasRecentlyBeenNavigatedTo(Origin origin,
806 const GURL& url) {
807 DCHECK(CalledOnValidThread());
809 CleanUpOldNavigations();
810 std::list<NavigationRecord>::const_reverse_iterator end = navigations_.rend();
811 for (std::list<NavigationRecord>::const_reverse_iterator it =
812 navigations_.rbegin();
813 it != end;
814 ++it) {
815 if (it->url == url) {
816 base::TimeDelta delta = GetCurrentTimeTicks() - it->time;
817 histograms_->RecordTimeSinceLastRecentVisit(origin, delta);
818 return true;
822 return false;
825 // static
826 bool PrerenderManager::IsValidHttpMethod(const std::string& method) {
827 // method has been canonicalized to upper case at this point so we can just
828 // compare them.
829 DCHECK_EQ(method, StringToUpperASCII(method));
830 for (size_t i = 0; i < arraysize(kValidHttpMethods); ++i) {
831 if (method.compare(kValidHttpMethods[i]) == 0)
832 return true;
835 return false;
838 // static
839 bool PrerenderManager::DoesURLHaveValidScheme(const GURL& url) {
840 return (url.SchemeIsHTTPOrHTTPS() ||
841 url.SchemeIs(extensions::kExtensionScheme) ||
842 url.SchemeIs("data"));
845 // static
846 bool PrerenderManager::DoesSubresourceURLHaveValidScheme(const GURL& url) {
847 return DoesURLHaveValidScheme(url) || url == GURL(url::kAboutBlankURL);
850 base::DictionaryValue* PrerenderManager::GetAsValue() const {
851 DCHECK(CalledOnValidThread());
852 base::DictionaryValue* dict_value = new base::DictionaryValue();
853 dict_value->Set("history", prerender_history_->GetEntriesAsValue());
854 dict_value->Set("active", GetActivePrerendersAsValue());
855 dict_value->SetBoolean("enabled", IsEnabled());
856 dict_value->SetBoolean("omnibox_enabled", IsOmniboxEnabled(profile_));
857 // If prerender is disabled via a flag this method is not even called.
858 std::string enabled_note;
859 if (IsControlGroup(kNoExperiment))
860 enabled_note += "(Control group: Not actually prerendering) ";
861 if (IsNoUseGroup())
862 enabled_note += "(No-use group: Not swapping in prerendered pages) ";
863 if (GetMode() == PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP)
864 enabled_note +=
865 "(15 min TTL group: Extended prerender eviction to 15 mins) ";
866 dict_value->SetString("enabled_note", enabled_note);
867 return dict_value;
870 void PrerenderManager::ClearData(int clear_flags) {
871 DCHECK_GE(clear_flags, 0);
872 DCHECK_LT(clear_flags, CLEAR_MAX);
873 if (clear_flags & CLEAR_PRERENDER_CONTENTS)
874 DestroyAllContents(FINAL_STATUS_CACHE_OR_HISTORY_CLEARED);
875 // This has to be second, since destroying prerenders can add to the history.
876 if (clear_flags & CLEAR_PRERENDER_HISTORY)
877 prerender_history_->Clear();
880 void PrerenderManager::RecordFinalStatusWithMatchCompleteStatus(
881 Origin origin,
882 uint8 experiment_id,
883 PrerenderContents::MatchCompleteStatus mc_status,
884 FinalStatus final_status) const {
885 histograms_->RecordFinalStatus(origin,
886 experiment_id,
887 mc_status,
888 final_status);
891 void PrerenderManager::RecordNavigation(const GURL& url) {
892 DCHECK(CalledOnValidThread());
894 navigations_.push_back(NavigationRecord(url, GetCurrentTimeTicks()));
895 CleanUpOldNavigations();
898 // protected
899 struct PrerenderManager::PrerenderData::OrderByExpiryTime {
900 bool operator()(const PrerenderData* a, const PrerenderData* b) const {
901 return a->expiry_time() < b->expiry_time();
905 PrerenderManager::PrerenderData::PrerenderData(PrerenderManager* manager,
906 PrerenderContents* contents,
907 base::TimeTicks expiry_time)
908 : manager_(manager),
909 contents_(contents),
910 handle_count_(0),
911 expiry_time_(expiry_time) {
912 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
915 PrerenderManager::PrerenderData::~PrerenderData() {
918 void PrerenderManager::PrerenderData::MakeIntoMatchCompleteReplacement() {
919 DCHECK(contents_);
920 contents_->set_match_complete_status(
921 PrerenderContents::MATCH_COMPLETE_REPLACED);
922 PrerenderData* to_delete = new PrerenderData(manager_, contents_.release(),
923 expiry_time_);
924 contents_.reset(to_delete->contents_->CreateMatchCompleteReplacement());
925 manager_->to_delete_prerenders_.push_back(to_delete);
928 void PrerenderManager::PrerenderData::OnHandleCreated(PrerenderHandle* handle) {
929 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
930 ++handle_count_;
931 contents_->AddObserver(handle);
934 void PrerenderManager::PrerenderData::OnHandleNavigatedAway(
935 PrerenderHandle* handle) {
936 DCHECK_LT(0, handle_count_);
937 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
938 if (abandon_time_.is_null())
939 abandon_time_ = base::TimeTicks::Now();
940 // We intentionally don't decrement the handle count here, so that the
941 // prerender won't be canceled until it times out.
942 manager_->SourceNavigatedAway(this);
945 void PrerenderManager::PrerenderData::OnHandleCanceled(
946 PrerenderHandle* handle) {
947 DCHECK_LT(0, handle_count_);
948 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
950 if (--handle_count_ == 0) {
951 // This will eventually remove this object from active_prerenders_.
952 contents_->Destroy(FINAL_STATUS_CANCELLED);
956 PrerenderContents* PrerenderManager::PrerenderData::ReleaseContents() {
957 return contents_.release();
960 void PrerenderManager::SetPrerenderContentsFactory(
961 PrerenderContents::Factory* prerender_contents_factory) {
962 DCHECK(CalledOnValidThread());
963 prerender_contents_factory_.reset(prerender_contents_factory);
966 void PrerenderManager::SourceNavigatedAway(PrerenderData* prerender_data) {
967 // The expiry time of our prerender data will likely change because of
968 // this navigation. This requires a resort of active_prerenders_.
969 ScopedVector<PrerenderData>::iterator it =
970 std::find(active_prerenders_.begin(), active_prerenders_.end(),
971 prerender_data);
972 if (it == active_prerenders_.end())
973 return;
975 (*it)->set_expiry_time(
976 std::min((*it)->expiry_time(),
977 GetExpiryTimeForNavigatedAwayPrerender()));
978 SortActivePrerenders();
981 // private
982 PrerenderHandle* PrerenderManager::AddPrerender(
983 Origin origin,
984 const GURL& url_arg,
985 const content::Referrer& referrer,
986 const gfx::Size& size,
987 SessionStorageNamespace* session_storage_namespace) {
988 DCHECK(CalledOnValidThread());
990 if (!IsEnabled())
991 return NULL;
993 if ((origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN ||
994 origin == ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN) &&
995 IsGoogleSearchResultURL(referrer.url)) {
996 origin = ORIGIN_GWS_PRERENDER;
999 GURL url = url_arg;
1000 GURL alias_url;
1001 uint8 experiment = GetQueryStringBasedExperiment(url_arg);
1002 if (IsControlGroup(experiment) &&
1003 MaybeGetQueryStringBasedAliasURL(url, &alias_url)) {
1004 url = alias_url;
1007 // From here on, we will record a FinalStatus so we need to register with the
1008 // histogram tracking.
1009 histograms_->RecordPrerender(origin, url_arg);
1011 if (PrerenderData* preexisting_prerender_data =
1012 FindPrerenderData(url, session_storage_namespace)) {
1013 RecordFinalStatusWithoutCreatingPrerenderContents(
1014 url, origin, experiment, FINAL_STATUS_DUPLICATE);
1015 return new PrerenderHandle(preexisting_prerender_data);
1018 // Do not prerender if there are too many render processes, and we would
1019 // have to use an existing one. We do not want prerendering to happen in
1020 // a shared process, so that we can always reliably lower the CPU
1021 // priority for prerendering.
1022 // In single-process mode, ShouldTryToUseExistingProcessHost() always returns
1023 // true, so that case needs to be explicitly checked for.
1024 // TODO(tburkard): Figure out how to cancel prerendering in the opposite
1025 // case, when a new tab is added to a process used for prerendering.
1026 // TODO(ppi): Check whether there are usually enough render processes
1027 // available on Android. If not, kill an existing renderers so that we can
1028 // create a new one.
1029 if (content::RenderProcessHost::ShouldTryToUseExistingProcessHost(
1030 profile_, url) &&
1031 !content::RenderProcessHost::run_renderer_in_process()) {
1032 RecordFinalStatusWithoutCreatingPrerenderContents(
1033 url, origin, experiment, FINAL_STATUS_TOO_MANY_PROCESSES);
1034 return NULL;
1037 // Check if enough time has passed since the last prerender.
1038 if (!DoesRateLimitAllowPrerender(origin)) {
1039 // Cancel the prerender. We could add it to the pending prerender list but
1040 // this doesn't make sense as the next prerender request will be triggered
1041 // by a navigation and is unlikely to be the same site.
1042 RecordFinalStatusWithoutCreatingPrerenderContents(
1043 url, origin, experiment, FINAL_STATUS_RATE_LIMIT_EXCEEDED);
1044 return NULL;
1047 PrerenderContents* prerender_contents = CreatePrerenderContents(
1048 url, referrer, origin, experiment);
1049 DCHECK(prerender_contents);
1050 active_prerenders_.push_back(
1051 new PrerenderData(this, prerender_contents,
1052 GetExpiryTimeForNewPrerender(origin)));
1053 if (!prerender_contents->Init()) {
1054 DCHECK(active_prerenders_.end() ==
1055 FindIteratorForPrerenderContents(prerender_contents));
1056 return NULL;
1059 histograms_->RecordPrerenderStarted(origin);
1060 DCHECK(!prerender_contents->prerendering_has_started());
1062 PrerenderHandle* prerender_handle =
1063 new PrerenderHandle(active_prerenders_.back());
1064 SortActivePrerenders();
1066 last_prerender_start_time_ = GetCurrentTimeTicks();
1068 gfx::Size contents_size =
1069 size.IsEmpty() ? config_.default_tab_bounds.size() : size;
1071 prerender_contents->StartPrerendering(contents_size,
1072 session_storage_namespace);
1074 DCHECK(IsControlGroup(experiment) ||
1075 prerender_contents->prerendering_has_started() ||
1076 (origin == ORIGIN_LOCAL_PREDICTOR &&
1077 IsLocalPredictorPrerenderAlwaysControlEnabled()));
1079 if (GetMode() == PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP)
1080 histograms_->RecordConcurrency(active_prerenders_.size());
1082 StartSchedulingPeriodicCleanups();
1083 return prerender_handle;
1086 void PrerenderManager::StartSchedulingPeriodicCleanups() {
1087 DCHECK(CalledOnValidThread());
1088 if (repeating_timer_.IsRunning())
1089 return;
1090 repeating_timer_.Start(FROM_HERE,
1091 base::TimeDelta::FromMilliseconds(kPeriodicCleanupIntervalMs),
1092 this,
1093 &PrerenderManager::PeriodicCleanup);
1096 void PrerenderManager::StopSchedulingPeriodicCleanups() {
1097 DCHECK(CalledOnValidThread());
1098 repeating_timer_.Stop();
1101 void PrerenderManager::PeriodicCleanup() {
1102 DCHECK(CalledOnValidThread());
1104 base::ElapsedTimer resource_timer;
1106 // Grab a copy of the current PrerenderContents pointers, so that we
1107 // will not interfere with potential deletions of the list.
1108 std::vector<PrerenderContents*>
1109 prerender_contents(active_prerenders_.size());
1110 std::transform(active_prerenders_.begin(), active_prerenders_.end(),
1111 prerender_contents.begin(),
1112 std::mem_fun(&PrerenderData::contents));
1114 // And now check for prerenders using too much memory.
1115 std::for_each(prerender_contents.begin(), prerender_contents.end(),
1116 std::mem_fun(
1117 &PrerenderContents::DestroyWhenUsingTooManyResources));
1119 // Measure how long the resource checks took. http://crbug.com/305419.
1120 UMA_HISTOGRAM_TIMES("Prerender.PeriodicCleanupResourceCheckTime",
1121 resource_timer.Elapsed());
1123 base::ElapsedTimer cleanup_timer;
1125 // Perform deferred cleanup work.
1126 DeleteOldWebContents();
1127 DeleteOldEntries();
1128 if (active_prerenders_.empty())
1129 StopSchedulingPeriodicCleanups();
1131 to_delete_prerenders_.clear();
1133 // Measure how long a the various cleanup tasks took. http://crbug.com/305419.
1134 UMA_HISTOGRAM_TIMES("Prerender.PeriodicCleanupDeleteContentsTime",
1135 cleanup_timer.Elapsed());
1138 void PrerenderManager::PostCleanupTask() {
1139 DCHECK(CalledOnValidThread());
1140 base::MessageLoop::current()->PostTask(
1141 FROM_HERE,
1142 base::Bind(&PrerenderManager::PeriodicCleanup, AsWeakPtr()));
1145 base::TimeTicks PrerenderManager::GetExpiryTimeForNewPrerender(
1146 Origin origin) const {
1147 base::TimeDelta ttl = config_.time_to_live;
1148 if (origin == ORIGIN_LOCAL_PREDICTOR)
1149 ttl = base::TimeDelta::FromSeconds(GetLocalPredictorTTLSeconds());
1150 return GetCurrentTimeTicks() + ttl;
1153 base::TimeTicks PrerenderManager::GetExpiryTimeForNavigatedAwayPrerender()
1154 const {
1155 return GetCurrentTimeTicks() + config_.abandon_time_to_live;
1158 void PrerenderManager::DeleteOldEntries() {
1159 DCHECK(CalledOnValidThread());
1160 while (!active_prerenders_.empty()) {
1161 PrerenderData* prerender_data = active_prerenders_.front();
1162 DCHECK(prerender_data);
1163 DCHECK(prerender_data->contents());
1165 if (prerender_data->expiry_time() > GetCurrentTimeTicks())
1166 return;
1167 prerender_data->contents()->Destroy(FINAL_STATUS_TIMED_OUT);
1171 base::Time PrerenderManager::GetCurrentTime() const {
1172 return base::Time::Now();
1175 base::TimeTicks PrerenderManager::GetCurrentTimeTicks() const {
1176 return base::TimeTicks::Now();
1179 PrerenderContents* PrerenderManager::CreatePrerenderContents(
1180 const GURL& url,
1181 const content::Referrer& referrer,
1182 Origin origin,
1183 uint8 experiment_id) {
1184 DCHECK(CalledOnValidThread());
1185 return prerender_contents_factory_->CreatePrerenderContents(
1186 this, profile_, url, referrer, origin, experiment_id);
1189 void PrerenderManager::SortActivePrerenders() {
1190 std::sort(active_prerenders_.begin(), active_prerenders_.end(),
1191 PrerenderData::OrderByExpiryTime());
1194 PrerenderManager::PrerenderData* PrerenderManager::FindPrerenderData(
1195 const GURL& url,
1196 const SessionStorageNamespace* session_storage_namespace) {
1197 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1198 it != active_prerenders_.end(); ++it) {
1199 if ((*it)->contents()->Matches(url, session_storage_namespace))
1200 return *it;
1202 return NULL;
1205 ScopedVector<PrerenderManager::PrerenderData>::iterator
1206 PrerenderManager::FindIteratorForPrerenderContents(
1207 PrerenderContents* prerender_contents) {
1208 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1209 it != active_prerenders_.end(); ++it) {
1210 if (prerender_contents == (*it)->contents())
1211 return it;
1213 return active_prerenders_.end();
1216 bool PrerenderManager::DoesRateLimitAllowPrerender(Origin origin) const {
1217 DCHECK(CalledOnValidThread());
1218 base::TimeDelta elapsed_time =
1219 GetCurrentTimeTicks() - last_prerender_start_time_;
1220 histograms_->RecordTimeBetweenPrerenderRequests(origin, elapsed_time);
1221 if (!config_.rate_limit_enabled)
1222 return true;
1223 // The LocalPredictor may issue multiple prerenders simultaneously (if so
1224 // configured), so no throttling.
1225 if (origin == ORIGIN_LOCAL_PREDICTOR)
1226 return true;
1227 return elapsed_time >=
1228 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs);
1231 void PrerenderManager::DeleteOldWebContents() {
1232 while (!old_web_contents_list_.empty()) {
1233 WebContents* web_contents = old_web_contents_list_.front();
1234 old_web_contents_list_.pop_front();
1235 // TODO(dominich): should we use Instant Unload Handler here?
1236 delete web_contents;
1240 void PrerenderManager::CleanUpOldNavigations() {
1241 DCHECK(CalledOnValidThread());
1243 // Cutoff. Navigations before this cutoff can be discarded.
1244 base::TimeTicks cutoff = GetCurrentTimeTicks() -
1245 base::TimeDelta::FromMilliseconds(kNavigationRecordWindowMs);
1246 while (!navigations_.empty()) {
1247 if (navigations_.front().time > cutoff)
1248 break;
1249 navigations_.pop_front();
1253 void PrerenderManager::ScheduleDeleteOldWebContents(
1254 WebContents* tab,
1255 OnCloseWebContentsDeleter* deleter) {
1256 old_web_contents_list_.push_back(tab);
1257 PostCleanupTask();
1259 if (deleter) {
1260 ScopedVector<OnCloseWebContentsDeleter>::iterator i = std::find(
1261 on_close_web_contents_deleters_.begin(),
1262 on_close_web_contents_deleters_.end(),
1263 deleter);
1264 DCHECK(i != on_close_web_contents_deleters_.end());
1265 on_close_web_contents_deleters_.erase(i);
1269 void PrerenderManager::AddToHistory(PrerenderContents* contents) {
1270 PrerenderHistory::Entry entry(contents->prerender_url(),
1271 contents->final_status(),
1272 contents->origin(),
1273 base::Time::Now());
1274 prerender_history_->AddEntry(entry);
1277 base::Value* PrerenderManager::GetActivePrerendersAsValue() const {
1278 base::ListValue* list_value = new base::ListValue();
1279 for (ScopedVector<PrerenderData>::const_iterator it =
1280 active_prerenders_.begin();
1281 it != active_prerenders_.end(); ++it) {
1282 if (base::Value* prerender_value = (*it)->contents()->GetAsValue())
1283 list_value->Append(prerender_value);
1285 return list_value;
1288 void PrerenderManager::DestroyAllContents(FinalStatus final_status) {
1289 DeleteOldWebContents();
1290 while (!active_prerenders_.empty()) {
1291 PrerenderContents* contents = active_prerenders_.front()->contents();
1292 contents->Destroy(final_status);
1294 to_delete_prerenders_.clear();
1297 void PrerenderManager::DestroyAndMarkMatchCompleteAsUsed(
1298 PrerenderContents* prerender_contents,
1299 FinalStatus final_status) {
1300 prerender_contents->set_match_complete_status(
1301 PrerenderContents::MATCH_COMPLETE_REPLACED);
1302 histograms_->RecordFinalStatus(prerender_contents->origin(),
1303 prerender_contents->experiment_id(),
1304 PrerenderContents::MATCH_COMPLETE_REPLACEMENT,
1305 FINAL_STATUS_WOULD_HAVE_BEEN_USED);
1306 prerender_contents->Destroy(final_status);
1309 void PrerenderManager::RecordFinalStatusWithoutCreatingPrerenderContents(
1310 const GURL& url, Origin origin, uint8 experiment_id,
1311 FinalStatus final_status) const {
1312 PrerenderHistory::Entry entry(url, final_status, origin, base::Time::Now());
1313 prerender_history_->AddEntry(entry);
1314 RecordFinalStatusWithMatchCompleteStatus(
1315 origin, experiment_id,
1316 PrerenderContents::MATCH_COMPLETE_DEFAULT,
1317 final_status);
1320 void PrerenderManager::Observe(int type,
1321 const content::NotificationSource& source,
1322 const content::NotificationDetails& details) {
1323 switch (type) {
1324 case chrome::NOTIFICATION_COOKIE_CHANGED: {
1325 Profile* profile = content::Source<Profile>(source).ptr();
1326 if (!profile || !profile_->IsSameProfile(profile) ||
1327 profile->IsOffTheRecord()) {
1328 return;
1330 CookieChanged(content::Details<ChromeCookieDetails>(details).ptr());
1331 break;
1333 case chrome::NOTIFICATION_PROFILE_DESTROYED:
1334 DestroyAllContents(FINAL_STATUS_PROFILE_DESTROYED);
1335 on_close_web_contents_deleters_.clear();
1336 break;
1337 default:
1338 NOTREACHED() << "Unexpected notification sent.";
1339 break;
1343 void PrerenderManager::OnCreatingAudioStream(int render_process_id,
1344 int render_frame_id) {
1345 content::RenderFrameHost* render_frame_host =
1346 content::RenderFrameHost::FromID(render_process_id, render_frame_id);
1347 WebContents* tab = WebContents::FromRenderFrameHost(render_frame_host);
1348 if (!tab)
1349 return;
1351 PrerenderContents* prerender_contents = GetPrerenderContents(tab);
1352 if (!prerender_contents)
1353 return;
1355 prerender_contents->Destroy(prerender::FINAL_STATUS_CREATING_AUDIO_STREAM);
1358 void PrerenderManager::RecordLikelyLoginOnURL(const GURL& url) {
1359 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1360 if (!url.SchemeIsHTTPOrHTTPS())
1361 return;
1362 if (logged_in_predictor_table_.get()) {
1363 BrowserThread::PostTask(
1364 BrowserThread::DB,
1365 FROM_HERE,
1366 base::Bind(&LoggedInPredictorTable::AddDomainFromURL,
1367 logged_in_predictor_table_,
1368 url));
1370 std::string key = LoggedInPredictorTable::GetKey(url);
1371 if (!logged_in_state_.get())
1372 return;
1373 if (logged_in_state_->count(key))
1374 return;
1375 (*logged_in_state_)[key] = base::Time::Now().ToInternalValue();
1378 void PrerenderManager::CheckIfLikelyLoggedInOnURL(
1379 const GURL& url,
1380 bool* lookup_result,
1381 bool* database_was_present,
1382 const base::Closure& result_cb) {
1383 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1384 if (!logged_in_predictor_table_.get()) {
1385 *database_was_present = false;
1386 *lookup_result = false;
1387 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, result_cb);
1388 return;
1390 BrowserThread::PostTaskAndReply(
1391 BrowserThread::DB, FROM_HERE,
1392 base::Bind(&LoggedInPredictorTable::HasUserLoggedIn,
1393 logged_in_predictor_table_,
1394 url,
1395 lookup_result,
1396 database_was_present),
1397 result_cb);
1401 void PrerenderManager::CookieChanged(ChromeCookieDetails* details) {
1402 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1404 if (!logged_in_predictor_table_.get())
1405 return;
1407 // We only care when a cookie has been removed.
1408 if (!details->removed)
1409 return;
1411 std::string domain_key =
1412 LoggedInPredictorTable::GetKeyFromDomain(details->cookie->Domain());
1414 // If we have no record of this domain as a potentially logged in domain,
1415 // nothing to do here.
1416 if (logged_in_state_.get() && logged_in_state_->count(domain_key) < 1)
1417 return;
1419 net::URLRequestContextGetter* rq_context = profile_->GetRequestContext();
1420 if (!rq_context)
1421 return;
1423 BrowserThread::PostTask(
1424 BrowserThread::IO, FROM_HERE,
1425 base::Bind(&CheckIfCookiesExistForDomainOnIOThread,
1426 base::Unretained(rq_context),
1427 domain_key,
1428 base::Bind(
1429 &PrerenderManager::CookieChangedAnyCookiesLeftLookupResult,
1430 AsWeakPtr(),
1431 domain_key)
1435 void PrerenderManager::CookieChangedAnyCookiesLeftLookupResult(
1436 const std::string& domain_key,
1437 bool cookies_exist) {
1438 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1440 if (cookies_exist)
1441 return;
1443 if (logged_in_predictor_table_.get()) {
1444 BrowserThread::PostTask(BrowserThread::DB,
1445 FROM_HERE,
1446 base::Bind(&LoggedInPredictorTable::DeleteDomain,
1447 logged_in_predictor_table_,
1448 domain_key));
1451 if (logged_in_state_.get())
1452 logged_in_state_->erase(domain_key);
1455 void PrerenderManager::LoggedInPredictorDataReceived(
1456 scoped_ptr<LoggedInStateMap> new_map) {
1457 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1458 logged_in_state_.swap(new_map);
1461 void PrerenderManager::RecordNetworkBytes(Origin origin,
1462 bool used,
1463 int64 prerender_bytes) {
1464 if (!ActuallyPrerendering())
1465 return;
1466 int64 recent_profile_bytes =
1467 profile_network_bytes_ - last_recorded_profile_network_bytes_;
1468 last_recorded_profile_network_bytes_ = profile_network_bytes_;
1469 DCHECK_GE(recent_profile_bytes, 0);
1470 histograms_->RecordNetworkBytes(
1471 origin, used, prerender_bytes, recent_profile_bytes);
1474 bool PrerenderManager::IsEnabled() const {
1475 DCHECK(CalledOnValidThread());
1477 return chrome_browser_net::CanPrefetchAndPrerenderUI(profile_->GetPrefs());
1480 void PrerenderManager::AddProfileNetworkBytesIfEnabled(int64 bytes) {
1481 DCHECK_GE(bytes, 0);
1482 if (IsEnabled() && ActuallyPrerendering())
1483 profile_network_bytes_ += bytes;
1486 void PrerenderManager::AddPrerenderProcessHost(
1487 content::RenderProcessHost* process_host) {
1488 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1489 DCHECK(prerender_process_hosts_.find(process_host) ==
1490 prerender_process_hosts_.end());
1491 prerender_process_hosts_.insert(process_host);
1492 process_host->AddObserver(this);
1495 bool PrerenderManager::MayReuseProcessHost(
1496 content::RenderProcessHost* process_host) {
1497 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1498 // Isolate prerender processes to make the resource monitoring check more
1499 // accurate.
1500 return (prerender_process_hosts_.find(process_host) ==
1501 prerender_process_hosts_.end());
1504 void PrerenderManager::RenderProcessHostDestroyed(
1505 content::RenderProcessHost* host) {
1506 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1507 size_t erased = prerender_process_hosts_.erase(host);
1508 DCHECK_EQ(1u, erased);
1511 } // namespace prerender