Remove the old signature of NotificationManager::closePersistent().
[chromium-blink-merge.git] / chrome / browser / prerender / prerender_manager.cc
blob4feae2be12bf4ae62f1bd9a03d46fb79477f523a
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/url_request/url_request_context.h"
64 #include "net/url_request/url_request_context_getter.h"
66 using content::BrowserThread;
67 using content::RenderViewHost;
68 using content::SessionStorageNamespace;
69 using content::WebContents;
70 using predictors::LoggedInPredictorTable;
72 namespace prerender {
74 namespace {
76 // Time interval at which periodic cleanups are performed.
77 const int kPeriodicCleanupIntervalMs = 1000;
79 // Valid HTTP methods for prerendering.
80 const char* const kValidHttpMethods[] = {
81 "GET",
82 "HEAD",
83 "OPTIONS",
84 "POST",
85 "TRACE",
88 // Length of prerender history, for display in chrome://net-internals
89 const int kHistoryLength = 100;
91 // Indicates whether a Prerender has been cancelled such that we need
92 // a dummy replacement for the purpose of recording the correct PPLT for
93 // the Match Complete case.
94 // Traditionally, "Match" means that a prerendered page was actually visited &
95 // the prerender was used. Our goal is to have "Match" cases line up in the
96 // control group & the experiment group, so that we can make meaningful
97 // comparisons of improvements. However, in the control group, since we don't
98 // actually perform prerenders, many of the cancellation reasons cannot be
99 // detected. Therefore, in the Prerender group, when we cancel for one of these
100 // reasons, we keep track of a dummy Prerender representing what we would
101 // have in the control group. If that dummy prerender in the prerender group
102 // would then be swapped in (but isn't actually b/c it's a dummy), we record
103 // this as a MatchComplete. This allows us to compare MatchComplete's
104 // across Prerender & Control group which ideally should be lining up.
105 // This ensures that there is no bias in terms of the page load times
106 // of the pages forming the difference between the two sets.
108 bool NeedMatchCompleteDummyForFinalStatus(FinalStatus final_status) {
109 return final_status != FINAL_STATUS_USED &&
110 final_status != FINAL_STATUS_TIMED_OUT &&
111 final_status != FINAL_STATUS_MANAGER_SHUTDOWN &&
112 final_status != FINAL_STATUS_PROFILE_DESTROYED &&
113 final_status != FINAL_STATUS_APP_TERMINATING &&
114 final_status != FINAL_STATUS_WINDOW_OPENER &&
115 final_status != FINAL_STATUS_CACHE_OR_HISTORY_CLEARED &&
116 final_status != FINAL_STATUS_CANCELLED &&
117 final_status != FINAL_STATUS_DEVTOOLS_ATTACHED &&
118 final_status != FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING &&
119 final_status != FINAL_STATUS_PAGE_BEING_CAPTURED &&
120 final_status != FINAL_STATUS_NAVIGATION_UNCOMMITTED &&
121 final_status != FINAL_STATUS_NON_EMPTY_BROWSING_INSTANCE;
124 void CheckIfCookiesExistForDomainResultOnUIThread(
125 const net::CookieMonster::HasCookiesForETLDP1Callback& callback,
126 bool cookies_exist) {
127 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
128 callback.Run(cookies_exist);
131 void CheckIfCookiesExistForDomainResultOnIOThread(
132 const net::CookieMonster::HasCookiesForETLDP1Callback& callback,
133 bool cookies_exist) {
134 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
135 BrowserThread::PostTask(
136 BrowserThread::UI,
137 FROM_HERE,
138 base::Bind(&CheckIfCookiesExistForDomainResultOnUIThread,
139 callback,
140 cookies_exist));
143 void CheckIfCookiesExistForDomainOnIOThread(
144 net::URLRequestContextGetter* rq_context,
145 const std::string& domain_key,
146 const net::CookieMonster::HasCookiesForETLDP1Callback& callback) {
147 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
148 net::CookieStore* cookie_store =
149 rq_context->GetURLRequestContext()->cookie_store();
150 cookie_store->GetCookieMonster()->HasCookiesForETLDP1Async(
151 domain_key,
152 base::Bind(&CheckIfCookiesExistForDomainResultOnIOThread, callback));
155 } // namespace
157 class PrerenderManager::OnCloseWebContentsDeleter
158 : public content::WebContentsDelegate,
159 public base::SupportsWeakPtr<
160 PrerenderManager::OnCloseWebContentsDeleter> {
161 public:
162 OnCloseWebContentsDeleter(PrerenderManager* manager,
163 WebContents* tab)
164 : manager_(manager),
165 tab_(tab),
166 suppressed_dialog_(false) {
167 tab_->SetDelegate(this);
168 base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
169 base::Bind(&OnCloseWebContentsDeleter::ScheduleWebContentsForDeletion,
170 AsWeakPtr(), true),
171 base::TimeDelta::FromSeconds(kDeleteWithExtremePrejudiceSeconds));
174 void CloseContents(WebContents* source) override {
175 DCHECK_EQ(tab_, source);
176 ScheduleWebContentsForDeletion(false);
179 void SwappedOut(WebContents* source) override {
180 DCHECK_EQ(tab_, source);
181 ScheduleWebContentsForDeletion(false);
184 bool ShouldSuppressDialogs(WebContents* source) override {
185 // Use this as a proxy for getting statistics on how often we fail to honor
186 // the beforeunload event.
187 DCHECK_EQ(tab_, source);
188 suppressed_dialog_ = true;
189 return true;
192 private:
193 static const int kDeleteWithExtremePrejudiceSeconds = 3;
195 void ScheduleWebContentsForDeletion(bool timeout) {
196 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterTimeout", timeout);
197 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterSuppressedDialog",
198 suppressed_dialog_);
199 tab_->SetDelegate(NULL);
200 manager_->ScheduleDeleteOldWebContents(tab_.release(), this);
201 // |this| is deleted at this point.
204 PrerenderManager* manager_;
205 scoped_ptr<WebContents> tab_;
206 bool suppressed_dialog_;
208 DISALLOW_COPY_AND_ASSIGN(OnCloseWebContentsDeleter);
211 // static
212 int PrerenderManager::prerenders_per_session_count_ = 0;
214 // static
215 PrerenderManager::PrerenderManagerMode PrerenderManager::mode_ =
216 PRERENDER_MODE_ENABLED;
218 struct PrerenderManager::NavigationRecord {
219 NavigationRecord(const GURL& url, base::TimeTicks time)
220 : url(url),
221 time(time) {
224 GURL url;
225 base::TimeTicks time;
228 PrerenderManager::PrerenderManager(Profile* profile)
229 : profile_(profile),
230 prerender_contents_factory_(PrerenderContents::CreateFactory()),
231 last_prerender_start_time_(GetCurrentTimeTicks() -
232 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs)),
233 prerender_history_(new PrerenderHistory(kHistoryLength)),
234 histograms_(new PrerenderHistograms()),
235 profile_network_bytes_(0),
236 last_recorded_profile_network_bytes_(0) {
237 // There are some assumptions that the PrerenderManager is on the UI thread.
238 // Any other checks simply make sure that the PrerenderManager is accessed on
239 // the same thread that it was created on.
240 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
242 if (IsLocalPredictorEnabled())
243 local_predictor_.reset(new PrerenderLocalPredictor(this));
245 if (IsLoggedInPredictorEnabled() && !profile_->IsOffTheRecord()) {
246 predictors::PredictorDatabase* predictor_db =
247 predictors::PredictorDatabaseFactory::GetForProfile(profile);
248 if (predictor_db) {
249 logged_in_predictor_table_ = predictor_db->logged_in_table();
250 scoped_ptr<LoggedInStateMap> new_state_map(new LoggedInStateMap);
251 LoggedInStateMap* new_state_map_ptr = new_state_map.get();
252 BrowserThread::PostTaskAndReply(
253 BrowserThread::DB, FROM_HERE,
254 base::Bind(&LoggedInPredictorTable::GetAllData,
255 logged_in_predictor_table_,
256 new_state_map_ptr),
257 base::Bind(&PrerenderManager::LoggedInPredictorDataReceived,
258 AsWeakPtr(),
259 base::Passed(&new_state_map)));
263 // Certain experiments override our default config_ values.
264 switch (PrerenderManager::GetMode()) {
265 case PrerenderManager::PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP:
266 config_.max_link_concurrency = 4;
267 config_.max_link_concurrency_per_launcher = 2;
268 break;
269 case PrerenderManager::PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP:
270 config_.time_to_live = base::TimeDelta::FromMinutes(15);
271 break;
272 default:
273 break;
276 notification_registrar_.Add(
277 this, chrome::NOTIFICATION_COOKIE_CHANGED,
278 content::NotificationService::AllBrowserContextsAndSources());
280 notification_registrar_.Add(
281 this, chrome::NOTIFICATION_PROFILE_DESTROYED,
282 content::Source<Profile>(profile_));
284 MediaCaptureDevicesDispatcher::GetInstance()->AddObserver(this);
287 PrerenderManager::~PrerenderManager() {
288 MediaCaptureDevicesDispatcher::GetInstance()->RemoveObserver(this);
290 // The earlier call to KeyedService::Shutdown() should have
291 // emptied these vectors already.
292 DCHECK(active_prerenders_.empty());
293 DCHECK(to_delete_prerenders_.empty());
295 for (PrerenderProcessSet::const_iterator it =
296 prerender_process_hosts_.begin();
297 it != prerender_process_hosts_.end();
298 ++it) {
299 (*it)->RemoveObserver(this);
303 void PrerenderManager::Shutdown() {
304 DestroyAllContents(FINAL_STATUS_MANAGER_SHUTDOWN);
305 on_close_web_contents_deleters_.clear();
306 // Must happen before |profile_| is set to NULL as
307 // |local_predictor_| accesses it.
308 if (local_predictor_)
309 local_predictor_->Shutdown();
310 profile_ = NULL;
312 DCHECK(active_prerenders_.empty());
315 PrerenderHandle* PrerenderManager::AddPrerenderFromLinkRelPrerender(
316 int process_id,
317 int route_id,
318 const GURL& url,
319 const uint32 rel_types,
320 const content::Referrer& referrer,
321 const gfx::Size& size) {
322 Origin origin = rel_types & PrerenderRelTypePrerender ?
323 ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN :
324 ORIGIN_LINK_REL_NEXT;
325 SessionStorageNamespace* session_storage_namespace = NULL;
326 // Unit tests pass in a process_id == -1.
327 if (process_id != -1) {
328 RenderViewHost* source_render_view_host =
329 RenderViewHost::FromID(process_id, route_id);
330 if (!source_render_view_host)
331 return NULL;
332 WebContents* source_web_contents =
333 WebContents::FromRenderViewHost(source_render_view_host);
334 if (!source_web_contents)
335 return NULL;
336 if (origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN &&
337 source_web_contents->GetURL().host() == url.host()) {
338 origin = ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN;
340 // TODO(ajwong): This does not correctly handle storage for isolated apps.
341 session_storage_namespace =
342 source_web_contents->GetController()
343 .GetDefaultSessionStorageNamespace();
346 return AddPrerender(origin, url, referrer, size, session_storage_namespace);
349 PrerenderHandle* PrerenderManager::AddPrerenderFromOmnibox(
350 const GURL& url,
351 SessionStorageNamespace* session_storage_namespace,
352 const gfx::Size& size) {
353 if (!IsOmniboxEnabled(profile_))
354 return NULL;
355 return AddPrerender(ORIGIN_OMNIBOX, url, content::Referrer(), size,
356 session_storage_namespace);
359 PrerenderHandle* PrerenderManager::AddPrerenderFromLocalPredictor(
360 const GURL& url,
361 SessionStorageNamespace* session_storage_namespace,
362 const gfx::Size& size) {
363 return AddPrerender(ORIGIN_LOCAL_PREDICTOR, url, content::Referrer(),
364 size, session_storage_namespace);
367 PrerenderHandle* PrerenderManager::AddPrerenderFromExternalRequest(
368 const GURL& url,
369 const content::Referrer& referrer,
370 SessionStorageNamespace* session_storage_namespace,
371 const gfx::Size& size) {
372 return AddPrerender(ORIGIN_EXTERNAL_REQUEST, url, referrer, size,
373 session_storage_namespace);
376 PrerenderHandle* PrerenderManager::AddPrerenderForInstant(
377 const GURL& url,
378 content::SessionStorageNamespace* session_storage_namespace,
379 const gfx::Size& size) {
380 DCHECK(chrome::ShouldPrefetchSearchResults());
381 return AddPrerender(ORIGIN_INSTANT, url, content::Referrer(), size,
382 session_storage_namespace);
385 void PrerenderManager::CancelAllPrerenders() {
386 DCHECK(CalledOnValidThread());
387 while (!active_prerenders_.empty()) {
388 PrerenderContents* prerender_contents =
389 active_prerenders_.front()->contents();
390 prerender_contents->Destroy(FINAL_STATUS_CANCELLED);
394 bool PrerenderManager::MaybeUsePrerenderedPage(const GURL& url,
395 chrome::NavigateParams* params) {
396 DCHECK(CalledOnValidThread());
398 content::WebContents* web_contents = params->target_contents;
399 DCHECK(!IsWebContentsPrerendering(web_contents, NULL));
401 // Don't prerender if the navigation involves some special parameters.
402 if (params->uses_post || !params->extra_headers.empty())
403 return false;
405 DeleteOldEntries();
406 to_delete_prerenders_.clear();
408 // First, try to find prerender data with the correct session storage
409 // namespace.
410 // TODO(ajwong): This doesn't handle isolated apps correctly.
411 PrerenderData* prerender_data = FindPrerenderData(
412 url,
413 web_contents->GetController().GetDefaultSessionStorageNamespace());
414 if (!prerender_data)
415 return false;
416 DCHECK(prerender_data->contents());
418 WebContents* new_web_contents = SwapInternal(
419 url, web_contents, prerender_data,
420 params->should_replace_current_entry);
421 if (!new_web_contents)
422 return false;
424 // Record the new target_contents for the callers.
425 params->target_contents = new_web_contents;
426 return true;
429 WebContents* PrerenderManager::SwapInternal(
430 const GURL& url,
431 WebContents* web_contents,
432 PrerenderData* prerender_data,
433 bool should_replace_current_entry) {
434 DCHECK(CalledOnValidThread());
435 DCHECK(!IsWebContentsPrerendering(web_contents, NULL));
437 // Only swap if the target WebContents has a CoreTabHelper delegate to swap
438 // out of it. For a normal WebContents, this is if it is in a TabStripModel.
439 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(web_contents);
440 if (!core_tab_helper || !core_tab_helper->delegate())
441 return NULL;
443 PrerenderTabHelper* target_tab_helper =
444 PrerenderTabHelper::FromWebContents(web_contents);
445 if (!target_tab_helper) {
446 NOTREACHED();
447 return NULL;
450 if (IsNoSwapInExperiment(prerender_data->contents()->experiment_id()))
451 return NULL;
453 if (WebContents* new_web_contents =
454 prerender_data->contents()->prerender_contents()) {
455 if (web_contents == new_web_contents)
456 return NULL; // Do not swap in to ourself.
458 // We cannot swap in if there is no last committed entry, because we would
459 // show a blank page under an existing entry from the current tab. Even if
460 // there is a pending entry, it may not commit.
461 // TODO(creis): If there is a pending navigation and no last committed
462 // entry, we might be able to transfer the network request instead.
463 if (!new_web_contents->GetController().CanPruneAllButLastCommitted()) {
464 // Abort this prerender so it is not used later. http://crbug.com/292121
465 prerender_data->contents()->Destroy(FINAL_STATUS_NAVIGATION_UNCOMMITTED);
466 return NULL;
470 // Do not swap if the target WebContents is not the only WebContents in its
471 // current BrowsingInstance.
472 if (web_contents->GetSiteInstance()->GetRelatedActiveContentsCount() != 1u) {
473 DCHECK_GT(
474 web_contents->GetSiteInstance()->GetRelatedActiveContentsCount(), 1u);
475 prerender_data->contents()->Destroy(
476 FINAL_STATUS_NON_EMPTY_BROWSING_INSTANCE);
477 return NULL;
480 // Do not use the prerendered version if there is an opener object.
481 if (web_contents->HasOpener()) {
482 prerender_data->contents()->Destroy(FINAL_STATUS_WINDOW_OPENER);
483 return NULL;
486 // Do not swap in the prerender if the current WebContents is being captured.
487 if (web_contents->GetCapturerCount() > 0) {
488 prerender_data->contents()->Destroy(FINAL_STATUS_PAGE_BEING_CAPTURED);
489 return NULL;
492 // If we are just in the control group (which can be detected by noticing
493 // that prerendering hasn't even started yet), record that |web_contents| now
494 // would be showing a prerendered contents, but otherwise, don't do anything.
495 if (!prerender_data->contents()->prerendering_has_started()) {
496 target_tab_helper->WouldHavePrerenderedNextLoad(
497 prerender_data->contents()->origin());
498 prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED);
499 return NULL;
502 // Don't use prerendered pages if debugger is attached to the tab.
503 // See http://crbug.com/98541
504 if (content::DevToolsAgentHost::IsDebuggerAttached(web_contents)) {
505 DestroyAndMarkMatchCompleteAsUsed(prerender_data->contents(),
506 FINAL_STATUS_DEVTOOLS_ATTACHED);
507 return NULL;
510 // If the prerendered page is in the middle of a cross-site navigation,
511 // don't swap it in because there isn't a good way to merge histories.
512 if (prerender_data->contents()->IsCrossSiteNavigationPending()) {
513 DestroyAndMarkMatchCompleteAsUsed(
514 prerender_data->contents(),
515 FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING);
516 return NULL;
519 // For bookkeeping purposes, we need to mark this WebContents to
520 // reflect that it would have been prerendered.
521 if (GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP) {
522 target_tab_helper->WouldHavePrerenderedNextLoad(
523 prerender_data->contents()->origin());
524 prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED);
525 return NULL;
528 // At this point, we've determined that we will use the prerender.
529 content::RenderProcessHost* process_host =
530 prerender_data->contents()->GetRenderViewHost()->GetProcess();
531 process_host->RemoveObserver(this);
532 prerender_process_hosts_.erase(process_host);
533 if (!prerender_data->contents()->load_start_time().is_null()) {
534 histograms_->RecordTimeUntilUsed(
535 prerender_data->contents()->origin(),
536 GetCurrentTimeTicks() - prerender_data->contents()->load_start_time());
538 histograms_->RecordAbandonTimeUntilUsed(
539 prerender_data->contents()->origin(),
540 prerender_data->abandon_time().is_null() ?
541 base::TimeDelta() :
542 GetCurrentTimeTicks() - prerender_data->abandon_time());
544 histograms_->RecordPerSessionCount(prerender_data->contents()->origin(),
545 ++prerenders_per_session_count_);
546 histograms_->RecordUsedPrerender(prerender_data->contents()->origin());
548 ScopedVector<PrerenderData>::iterator to_erase =
549 FindIteratorForPrerenderContents(prerender_data->contents());
550 DCHECK(active_prerenders_.end() != to_erase);
551 DCHECK_EQ(prerender_data, *to_erase);
552 scoped_ptr<PrerenderContents>
553 prerender_contents(prerender_data->ReleaseContents());
554 active_prerenders_.erase(to_erase);
556 // Mark prerender as used.
557 prerender_contents->PrepareForUse();
559 WebContents* new_web_contents =
560 prerender_contents->ReleasePrerenderContents();
561 WebContents* old_web_contents = web_contents;
562 DCHECK(new_web_contents);
563 DCHECK(old_web_contents);
565 // Merge the browsing history.
566 new_web_contents->GetController().CopyStateFromAndPrune(
567 &old_web_contents->GetController(),
568 should_replace_current_entry);
569 CoreTabHelper::FromWebContents(old_web_contents)->delegate()->
570 SwapTabContents(old_web_contents,
571 new_web_contents,
572 true,
573 prerender_contents->has_finished_loading());
574 prerender_contents->CommitHistory(new_web_contents);
576 // Update PPLT metrics:
577 // If the tab has finished loading, record a PPLT of 0.
578 // If the tab is still loading, reset its start time to the current time.
579 PrerenderTabHelper* prerender_tab_helper =
580 PrerenderTabHelper::FromWebContents(new_web_contents);
581 DCHECK(prerender_tab_helper != NULL);
582 prerender_tab_helper->PrerenderSwappedIn();
584 if (old_web_contents->NeedToFireBeforeUnload()) {
585 // Schedule the delete to occur after the tab has run its unload handlers.
586 // TODO(davidben): Honor the beforeunload event. http://crbug.com/304932
587 on_close_web_contents_deleters_.push_back(
588 new OnCloseWebContentsDeleter(this, old_web_contents));
589 old_web_contents->DispatchBeforeUnload(false);
590 } else {
591 // No unload handler to run, so delete asap.
592 ScheduleDeleteOldWebContents(old_web_contents, NULL);
595 // TODO(cbentzel): Should prerender_contents move to the pending delete
596 // list, instead of deleting directly here?
597 AddToHistory(prerender_contents.get());
598 RecordNavigation(url);
599 return new_web_contents;
602 void PrerenderManager::MoveEntryToPendingDelete(PrerenderContents* entry,
603 FinalStatus final_status) {
604 DCHECK(CalledOnValidThread());
605 DCHECK(entry);
607 ScopedVector<PrerenderData>::iterator it =
608 FindIteratorForPrerenderContents(entry);
609 DCHECK(it != active_prerenders_.end());
611 // If this PrerenderContents is being deleted due to a cancellation any time
612 // after the prerender has started then we need to create a dummy replacement
613 // for PPLT accounting purposes for the Match Complete group. This is the case
614 // if the cancellation is for any reason that would not occur in the control
615 // group case.
616 if (entry->prerendering_has_started() &&
617 entry->match_complete_status() ==
618 PrerenderContents::MATCH_COMPLETE_DEFAULT &&
619 NeedMatchCompleteDummyForFinalStatus(final_status) &&
620 ActuallyPrerendering() &&
621 GetMode() == PRERENDER_MODE_EXPERIMENT_MATCH_COMPLETE_GROUP) {
622 // TODO(tburkard): I'd like to DCHECK that we are actually prerendering.
623 // However, what if new conditions are added and
624 // NeedMatchCompleteDummyForFinalStatus is not being updated. Not sure
625 // what's the best thing to do here. For now, I will just check whether
626 // we are actually prerendering.
627 (*it)->MakeIntoMatchCompleteReplacement();
628 } else {
629 to_delete_prerenders_.push_back(*it);
630 active_prerenders_.weak_erase(it);
633 // Destroy the old WebContents relatively promptly to reduce resource usage.
634 PostCleanupTask();
637 void PrerenderManager::RecordPageLoadTimeNotSwappedIn(
638 Origin origin,
639 base::TimeDelta page_load_time,
640 const GURL& url) {
641 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
642 histograms_->RecordPageLoadTimeNotSwappedIn(origin, page_load_time, url);
645 void PrerenderManager::RecordPerceivedPageLoadTime(
646 Origin origin,
647 NavigationType navigation_type,
648 base::TimeDelta perceived_page_load_time,
649 double fraction_plt_elapsed_at_swap_in,
650 const GURL& url) {
651 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
652 if (!IsEnabled())
653 return;
655 histograms_->RecordPerceivedPageLoadTime(
656 origin, perceived_page_load_time, navigation_type, url);
658 if (navigation_type == NAVIGATION_TYPE_PRERENDERED) {
659 histograms_->RecordPercentLoadDoneAtSwapin(
660 origin, fraction_plt_elapsed_at_swap_in);
662 if (local_predictor_) {
663 local_predictor_->OnPLTEventForURL(url, perceived_page_load_time);
667 // static
668 PrerenderManager::PrerenderManagerMode PrerenderManager::GetMode() {
669 return mode_;
672 // static
673 void PrerenderManager::SetMode(PrerenderManagerMode mode) {
674 mode_ = mode;
677 // static
678 const char* PrerenderManager::GetModeString() {
679 switch (mode_) {
680 case PRERENDER_MODE_DISABLED:
681 return "_Disabled";
682 case PRERENDER_MODE_ENABLED:
683 case PRERENDER_MODE_EXPERIMENT_PRERENDER_GROUP:
684 return "_Enabled";
685 case PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP:
686 return "_Control";
687 case PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP:
688 return "_Multi";
689 case PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP:
690 return "_15MinTTL";
691 case PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP:
692 return "_NoUse";
693 case PRERENDER_MODE_EXPERIMENT_MATCH_COMPLETE_GROUP:
694 return "_MatchComplete";
695 case PRERENDER_MODE_MAX:
696 default:
697 NOTREACHED() << "Invalid PrerenderManager mode.";
698 break;
700 return "";
703 // static
704 bool PrerenderManager::IsPrerenderingPossible() {
705 return GetMode() != PRERENDER_MODE_DISABLED;
708 // static
709 bool PrerenderManager::ActuallyPrerendering() {
710 return IsPrerenderingPossible() && !IsControlGroup(kNoExperiment);
713 // static
714 bool PrerenderManager::IsControlGroup(uint8 experiment_id) {
715 return GetMode() == PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP ||
716 IsControlGroupExperiment(experiment_id);
719 // static
720 bool PrerenderManager::IsNoUseGroup() {
721 return GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP;
724 bool PrerenderManager::IsWebContentsPrerendering(
725 const WebContents* web_contents,
726 Origin* origin) const {
727 DCHECK(CalledOnValidThread());
728 if (PrerenderContents* prerender_contents =
729 GetPrerenderContents(web_contents)) {
730 if (origin)
731 *origin = prerender_contents->origin();
732 return true;
734 return false;
737 bool PrerenderManager::HasPrerenderedUrl(
738 GURL url,
739 content::WebContents* web_contents) const {
740 content::SessionStorageNamespace* session_storage_namespace = web_contents->
741 GetController().GetDefaultSessionStorageNamespace();
743 for (ScopedVector<PrerenderData>::const_iterator it =
744 active_prerenders_.begin();
745 it != active_prerenders_.end(); ++it) {
746 PrerenderContents* prerender_contents = (*it)->contents();
747 if (prerender_contents->Matches(url, session_storage_namespace)) {
748 return true;
751 return false;
754 PrerenderContents* PrerenderManager::GetPrerenderContents(
755 const content::WebContents* web_contents) const {
756 DCHECK(CalledOnValidThread());
757 for (ScopedVector<PrerenderData>::const_iterator it =
758 active_prerenders_.begin();
759 it != active_prerenders_.end(); ++it) {
760 WebContents* prerender_web_contents =
761 (*it)->contents()->prerender_contents();
762 if (prerender_web_contents == web_contents) {
763 return (*it)->contents();
767 // Also check the pending-deletion list. If the prerender is in pending
768 // delete, anyone with a handle on the WebContents needs to know.
769 for (ScopedVector<PrerenderData>::const_iterator it =
770 to_delete_prerenders_.begin();
771 it != to_delete_prerenders_.end(); ++it) {
772 WebContents* prerender_web_contents =
773 (*it)->contents()->prerender_contents();
774 if (prerender_web_contents == web_contents) {
775 return (*it)->contents();
778 return NULL;
781 PrerenderContents* PrerenderManager::GetPrerenderContentsForRoute(
782 int child_id,
783 int route_id) const {
784 content::WebContents* web_contents =
785 tab_util::GetWebContentsByID(child_id, route_id);
786 if (web_contents == NULL)
787 return NULL;
788 return GetPrerenderContents(web_contents);
791 const std::vector<WebContents*>
792 PrerenderManager::GetAllPrerenderingContents() const {
793 DCHECK(CalledOnValidThread());
794 std::vector<WebContents*> result;
796 for (ScopedVector<PrerenderData>::const_iterator it =
797 active_prerenders_.begin();
798 it != active_prerenders_.end(); ++it) {
799 if (WebContents* contents = (*it)->contents()->prerender_contents())
800 result.push_back(contents);
803 return result;
806 bool PrerenderManager::HasRecentlyBeenNavigatedTo(Origin origin,
807 const GURL& url) {
808 DCHECK(CalledOnValidThread());
810 CleanUpOldNavigations();
811 std::list<NavigationRecord>::const_reverse_iterator end = navigations_.rend();
812 for (std::list<NavigationRecord>::const_reverse_iterator it =
813 navigations_.rbegin();
814 it != end;
815 ++it) {
816 if (it->url == url) {
817 base::TimeDelta delta = GetCurrentTimeTicks() - it->time;
818 histograms_->RecordTimeSinceLastRecentVisit(origin, delta);
819 return true;
823 return false;
826 // static
827 bool PrerenderManager::IsValidHttpMethod(const std::string& method) {
828 // method has been canonicalized to upper case at this point so we can just
829 // compare them.
830 DCHECK_EQ(method, StringToUpperASCII(method));
831 for (size_t i = 0; i < arraysize(kValidHttpMethods); ++i) {
832 if (method.compare(kValidHttpMethods[i]) == 0)
833 return true;
836 return false;
839 // static
840 bool PrerenderManager::DoesURLHaveValidScheme(const GURL& url) {
841 return (url.SchemeIsHTTPOrHTTPS() ||
842 url.SchemeIs(extensions::kExtensionScheme) ||
843 url.SchemeIs("data"));
846 // static
847 bool PrerenderManager::DoesSubresourceURLHaveValidScheme(const GURL& url) {
848 return DoesURLHaveValidScheme(url) || url == GURL(url::kAboutBlankURL);
851 base::DictionaryValue* PrerenderManager::GetAsValue() const {
852 DCHECK(CalledOnValidThread());
853 base::DictionaryValue* dict_value = new base::DictionaryValue();
854 dict_value->Set("history", prerender_history_->GetEntriesAsValue());
855 dict_value->Set("active", GetActivePrerendersAsValue());
856 dict_value->SetBoolean("enabled", IsEnabled());
857 dict_value->SetBoolean("omnibox_enabled", IsOmniboxEnabled(profile_));
858 // If prerender is disabled via a flag this method is not even called.
859 std::string enabled_note;
860 if (IsControlGroup(kNoExperiment))
861 enabled_note += "(Control group: Not actually prerendering) ";
862 if (IsNoUseGroup())
863 enabled_note += "(No-use group: Not swapping in prerendered pages) ";
864 if (GetMode() == PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP)
865 enabled_note +=
866 "(15 min TTL group: Extended prerender eviction to 15 mins) ";
867 dict_value->SetString("enabled_note", enabled_note);
868 return dict_value;
871 void PrerenderManager::ClearData(int clear_flags) {
872 DCHECK_GE(clear_flags, 0);
873 DCHECK_LT(clear_flags, CLEAR_MAX);
874 if (clear_flags & CLEAR_PRERENDER_CONTENTS)
875 DestroyAllContents(FINAL_STATUS_CACHE_OR_HISTORY_CLEARED);
876 // This has to be second, since destroying prerenders can add to the history.
877 if (clear_flags & CLEAR_PRERENDER_HISTORY)
878 prerender_history_->Clear();
881 void PrerenderManager::RecordFinalStatusWithMatchCompleteStatus(
882 Origin origin,
883 uint8 experiment_id,
884 PrerenderContents::MatchCompleteStatus mc_status,
885 FinalStatus final_status) const {
886 histograms_->RecordFinalStatus(origin,
887 experiment_id,
888 mc_status,
889 final_status);
892 void PrerenderManager::RecordNavigation(const GURL& url) {
893 DCHECK(CalledOnValidThread());
895 navigations_.push_back(NavigationRecord(url, GetCurrentTimeTicks()));
896 CleanUpOldNavigations();
899 // protected
900 struct PrerenderManager::PrerenderData::OrderByExpiryTime {
901 bool operator()(const PrerenderData* a, const PrerenderData* b) const {
902 return a->expiry_time() < b->expiry_time();
906 PrerenderManager::PrerenderData::PrerenderData(PrerenderManager* manager,
907 PrerenderContents* contents,
908 base::TimeTicks expiry_time)
909 : manager_(manager),
910 contents_(contents),
911 handle_count_(0),
912 expiry_time_(expiry_time) {
913 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
916 PrerenderManager::PrerenderData::~PrerenderData() {
919 void PrerenderManager::PrerenderData::MakeIntoMatchCompleteReplacement() {
920 DCHECK(contents_);
921 contents_->set_match_complete_status(
922 PrerenderContents::MATCH_COMPLETE_REPLACED);
923 PrerenderData* to_delete = new PrerenderData(manager_, contents_.release(),
924 expiry_time_);
925 contents_.reset(to_delete->contents_->CreateMatchCompleteReplacement());
926 manager_->to_delete_prerenders_.push_back(to_delete);
929 void PrerenderManager::PrerenderData::OnHandleCreated(PrerenderHandle* handle) {
930 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
931 ++handle_count_;
932 contents_->AddObserver(handle);
935 void PrerenderManager::PrerenderData::OnHandleNavigatedAway(
936 PrerenderHandle* handle) {
937 DCHECK_LT(0, handle_count_);
938 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
939 if (abandon_time_.is_null())
940 abandon_time_ = base::TimeTicks::Now();
941 // We intentionally don't decrement the handle count here, so that the
942 // prerender won't be canceled until it times out.
943 manager_->SourceNavigatedAway(this);
946 void PrerenderManager::PrerenderData::OnHandleCanceled(
947 PrerenderHandle* handle) {
948 DCHECK_LT(0, handle_count_);
949 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
951 if (--handle_count_ == 0) {
952 // This will eventually remove this object from active_prerenders_.
953 contents_->Destroy(FINAL_STATUS_CANCELLED);
957 PrerenderContents* PrerenderManager::PrerenderData::ReleaseContents() {
958 return contents_.release();
961 void PrerenderManager::SetPrerenderContentsFactory(
962 PrerenderContents::Factory* prerender_contents_factory) {
963 DCHECK(CalledOnValidThread());
964 prerender_contents_factory_.reset(prerender_contents_factory);
967 void PrerenderManager::SourceNavigatedAway(PrerenderData* prerender_data) {
968 // The expiry time of our prerender data will likely change because of
969 // this navigation. This requires a resort of active_prerenders_.
970 ScopedVector<PrerenderData>::iterator it =
971 std::find(active_prerenders_.begin(), active_prerenders_.end(),
972 prerender_data);
973 if (it == active_prerenders_.end())
974 return;
976 (*it)->set_expiry_time(
977 std::min((*it)->expiry_time(),
978 GetExpiryTimeForNavigatedAwayPrerender()));
979 SortActivePrerenders();
982 // private
983 PrerenderHandle* PrerenderManager::AddPrerender(
984 Origin origin,
985 const GURL& url_arg,
986 const content::Referrer& referrer,
987 const gfx::Size& size,
988 SessionStorageNamespace* session_storage_namespace) {
989 DCHECK(CalledOnValidThread());
991 if (!IsEnabled())
992 return NULL;
994 if ((origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN ||
995 origin == ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN) &&
996 IsGoogleSearchResultURL(referrer.url)) {
997 origin = ORIGIN_GWS_PRERENDER;
1000 GURL url = url_arg;
1001 GURL alias_url;
1002 uint8 experiment = GetQueryStringBasedExperiment(url_arg);
1003 if (IsControlGroup(experiment) &&
1004 MaybeGetQueryStringBasedAliasURL(url, &alias_url)) {
1005 url = alias_url;
1008 // From here on, we will record a FinalStatus so we need to register with the
1009 // histogram tracking.
1010 histograms_->RecordPrerender(origin, url_arg);
1012 if (PrerenderData* preexisting_prerender_data =
1013 FindPrerenderData(url, session_storage_namespace)) {
1014 RecordFinalStatusWithoutCreatingPrerenderContents(
1015 url, origin, experiment, FINAL_STATUS_DUPLICATE);
1016 return new PrerenderHandle(preexisting_prerender_data);
1019 // Do not prerender if there are too many render processes, and we would
1020 // have to use an existing one. We do not want prerendering to happen in
1021 // a shared process, so that we can always reliably lower the CPU
1022 // priority for prerendering.
1023 // In single-process mode, ShouldTryToUseExistingProcessHost() always returns
1024 // true, so that case needs to be explicitly checked for.
1025 // TODO(tburkard): Figure out how to cancel prerendering in the opposite
1026 // case, when a new tab is added to a process used for prerendering.
1027 // TODO(ppi): Check whether there are usually enough render processes
1028 // available on Android. If not, kill an existing renderers so that we can
1029 // create a new one.
1030 if (content::RenderProcessHost::ShouldTryToUseExistingProcessHost(
1031 profile_, url) &&
1032 !content::RenderProcessHost::run_renderer_in_process()) {
1033 RecordFinalStatusWithoutCreatingPrerenderContents(
1034 url, origin, experiment, FINAL_STATUS_TOO_MANY_PROCESSES);
1035 return NULL;
1038 // Check if enough time has passed since the last prerender.
1039 if (!DoesRateLimitAllowPrerender(origin)) {
1040 // Cancel the prerender. We could add it to the pending prerender list but
1041 // this doesn't make sense as the next prerender request will be triggered
1042 // by a navigation and is unlikely to be the same site.
1043 RecordFinalStatusWithoutCreatingPrerenderContents(
1044 url, origin, experiment, FINAL_STATUS_RATE_LIMIT_EXCEEDED);
1045 return NULL;
1048 PrerenderContents* prerender_contents = CreatePrerenderContents(
1049 url, referrer, origin, experiment);
1050 DCHECK(prerender_contents);
1051 active_prerenders_.push_back(
1052 new PrerenderData(this, prerender_contents,
1053 GetExpiryTimeForNewPrerender(origin)));
1054 if (!prerender_contents->Init()) {
1055 DCHECK(active_prerenders_.end() ==
1056 FindIteratorForPrerenderContents(prerender_contents));
1057 return NULL;
1060 histograms_->RecordPrerenderStarted(origin);
1061 DCHECK(!prerender_contents->prerendering_has_started());
1063 PrerenderHandle* prerender_handle =
1064 new PrerenderHandle(active_prerenders_.back());
1065 SortActivePrerenders();
1067 last_prerender_start_time_ = GetCurrentTimeTicks();
1069 gfx::Size contents_size =
1070 size.IsEmpty() ? config_.default_tab_bounds.size() : size;
1072 prerender_contents->StartPrerendering(contents_size,
1073 session_storage_namespace);
1075 DCHECK(IsControlGroup(experiment) ||
1076 prerender_contents->prerendering_has_started() ||
1077 (origin == ORIGIN_LOCAL_PREDICTOR &&
1078 IsLocalPredictorPrerenderAlwaysControlEnabled()));
1080 if (GetMode() == PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP)
1081 histograms_->RecordConcurrency(active_prerenders_.size());
1083 // Query the history to see if the URL being prerendered has ever been
1084 // visited before.
1085 history::HistoryService* history_service =
1086 HistoryServiceFactory::GetForProfile(profile_,
1087 ServiceAccessType::EXPLICIT_ACCESS);
1088 if (history_service) {
1089 history_service->QueryURL(
1090 url,
1091 false,
1092 base::Bind(&PrerenderManager::OnHistoryServiceDidQueryURL,
1093 base::Unretained(this),
1094 origin,
1095 experiment),
1096 &query_url_tracker_);
1099 StartSchedulingPeriodicCleanups();
1100 return prerender_handle;
1103 void PrerenderManager::StartSchedulingPeriodicCleanups() {
1104 DCHECK(CalledOnValidThread());
1105 if (repeating_timer_.IsRunning())
1106 return;
1107 repeating_timer_.Start(FROM_HERE,
1108 base::TimeDelta::FromMilliseconds(kPeriodicCleanupIntervalMs),
1109 this,
1110 &PrerenderManager::PeriodicCleanup);
1113 void PrerenderManager::StopSchedulingPeriodicCleanups() {
1114 DCHECK(CalledOnValidThread());
1115 repeating_timer_.Stop();
1118 void PrerenderManager::PeriodicCleanup() {
1119 DCHECK(CalledOnValidThread());
1121 base::ElapsedTimer resource_timer;
1123 // Grab a copy of the current PrerenderContents pointers, so that we
1124 // will not interfere with potential deletions of the list.
1125 std::vector<PrerenderContents*>
1126 prerender_contents(active_prerenders_.size());
1127 std::transform(active_prerenders_.begin(), active_prerenders_.end(),
1128 prerender_contents.begin(),
1129 std::mem_fun(&PrerenderData::contents));
1131 // And now check for prerenders using too much memory.
1132 std::for_each(prerender_contents.begin(), prerender_contents.end(),
1133 std::mem_fun(
1134 &PrerenderContents::DestroyWhenUsingTooManyResources));
1136 // Measure how long the resource checks took. http://crbug.com/305419.
1137 UMA_HISTOGRAM_TIMES("Prerender.PeriodicCleanupResourceCheckTime",
1138 resource_timer.Elapsed());
1140 base::ElapsedTimer cleanup_timer;
1142 // Perform deferred cleanup work.
1143 DeleteOldWebContents();
1144 DeleteOldEntries();
1145 if (active_prerenders_.empty())
1146 StopSchedulingPeriodicCleanups();
1148 to_delete_prerenders_.clear();
1150 // Measure how long a the various cleanup tasks took. http://crbug.com/305419.
1151 UMA_HISTOGRAM_TIMES("Prerender.PeriodicCleanupDeleteContentsTime",
1152 cleanup_timer.Elapsed());
1155 void PrerenderManager::PostCleanupTask() {
1156 DCHECK(CalledOnValidThread());
1157 base::MessageLoop::current()->PostTask(
1158 FROM_HERE,
1159 base::Bind(&PrerenderManager::PeriodicCleanup, AsWeakPtr()));
1162 base::TimeTicks PrerenderManager::GetExpiryTimeForNewPrerender(
1163 Origin origin) const {
1164 base::TimeDelta ttl = config_.time_to_live;
1165 if (origin == ORIGIN_LOCAL_PREDICTOR)
1166 ttl = base::TimeDelta::FromSeconds(GetLocalPredictorTTLSeconds());
1167 return GetCurrentTimeTicks() + ttl;
1170 base::TimeTicks PrerenderManager::GetExpiryTimeForNavigatedAwayPrerender()
1171 const {
1172 return GetCurrentTimeTicks() + config_.abandon_time_to_live;
1175 void PrerenderManager::DeleteOldEntries() {
1176 DCHECK(CalledOnValidThread());
1177 while (!active_prerenders_.empty()) {
1178 PrerenderData* prerender_data = active_prerenders_.front();
1179 DCHECK(prerender_data);
1180 DCHECK(prerender_data->contents());
1182 if (prerender_data->expiry_time() > GetCurrentTimeTicks())
1183 return;
1184 prerender_data->contents()->Destroy(FINAL_STATUS_TIMED_OUT);
1188 base::Time PrerenderManager::GetCurrentTime() const {
1189 return base::Time::Now();
1192 base::TimeTicks PrerenderManager::GetCurrentTimeTicks() const {
1193 return base::TimeTicks::Now();
1196 PrerenderContents* PrerenderManager::CreatePrerenderContents(
1197 const GURL& url,
1198 const content::Referrer& referrer,
1199 Origin origin,
1200 uint8 experiment_id) {
1201 DCHECK(CalledOnValidThread());
1202 return prerender_contents_factory_->CreatePrerenderContents(
1203 this, profile_, url, referrer, origin, experiment_id);
1206 void PrerenderManager::SortActivePrerenders() {
1207 std::sort(active_prerenders_.begin(), active_prerenders_.end(),
1208 PrerenderData::OrderByExpiryTime());
1211 PrerenderManager::PrerenderData* PrerenderManager::FindPrerenderData(
1212 const GURL& url,
1213 const SessionStorageNamespace* session_storage_namespace) {
1214 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1215 it != active_prerenders_.end(); ++it) {
1216 if ((*it)->contents()->Matches(url, session_storage_namespace))
1217 return *it;
1219 return NULL;
1222 ScopedVector<PrerenderManager::PrerenderData>::iterator
1223 PrerenderManager::FindIteratorForPrerenderContents(
1224 PrerenderContents* prerender_contents) {
1225 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1226 it != active_prerenders_.end(); ++it) {
1227 if (prerender_contents == (*it)->contents())
1228 return it;
1230 return active_prerenders_.end();
1233 bool PrerenderManager::DoesRateLimitAllowPrerender(Origin origin) const {
1234 DCHECK(CalledOnValidThread());
1235 base::TimeDelta elapsed_time =
1236 GetCurrentTimeTicks() - last_prerender_start_time_;
1237 histograms_->RecordTimeBetweenPrerenderRequests(origin, elapsed_time);
1238 if (!config_.rate_limit_enabled)
1239 return true;
1240 // The LocalPredictor may issue multiple prerenders simultaneously (if so
1241 // configured), so no throttling.
1242 if (origin == ORIGIN_LOCAL_PREDICTOR)
1243 return true;
1244 return elapsed_time >=
1245 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs);
1248 void PrerenderManager::DeleteOldWebContents() {
1249 while (!old_web_contents_list_.empty()) {
1250 WebContents* web_contents = old_web_contents_list_.front();
1251 old_web_contents_list_.pop_front();
1252 // TODO(dominich): should we use Instant Unload Handler here?
1253 delete web_contents;
1257 void PrerenderManager::CleanUpOldNavigations() {
1258 DCHECK(CalledOnValidThread());
1260 // Cutoff. Navigations before this cutoff can be discarded.
1261 base::TimeTicks cutoff = GetCurrentTimeTicks() -
1262 base::TimeDelta::FromMilliseconds(kNavigationRecordWindowMs);
1263 while (!navigations_.empty()) {
1264 if (navigations_.front().time > cutoff)
1265 break;
1266 navigations_.pop_front();
1270 void PrerenderManager::ScheduleDeleteOldWebContents(
1271 WebContents* tab,
1272 OnCloseWebContentsDeleter* deleter) {
1273 old_web_contents_list_.push_back(tab);
1274 PostCleanupTask();
1276 if (deleter) {
1277 ScopedVector<OnCloseWebContentsDeleter>::iterator i = std::find(
1278 on_close_web_contents_deleters_.begin(),
1279 on_close_web_contents_deleters_.end(),
1280 deleter);
1281 DCHECK(i != on_close_web_contents_deleters_.end());
1282 on_close_web_contents_deleters_.erase(i);
1286 void PrerenderManager::AddToHistory(PrerenderContents* contents) {
1287 PrerenderHistory::Entry entry(contents->prerender_url(),
1288 contents->final_status(),
1289 contents->origin(),
1290 base::Time::Now());
1291 prerender_history_->AddEntry(entry);
1294 base::Value* PrerenderManager::GetActivePrerendersAsValue() const {
1295 base::ListValue* list_value = new base::ListValue();
1296 for (ScopedVector<PrerenderData>::const_iterator it =
1297 active_prerenders_.begin();
1298 it != active_prerenders_.end(); ++it) {
1299 if (base::Value* prerender_value = (*it)->contents()->GetAsValue())
1300 list_value->Append(prerender_value);
1302 return list_value;
1305 void PrerenderManager::DestroyAllContents(FinalStatus final_status) {
1306 DeleteOldWebContents();
1307 while (!active_prerenders_.empty()) {
1308 PrerenderContents* contents = active_prerenders_.front()->contents();
1309 contents->Destroy(final_status);
1311 to_delete_prerenders_.clear();
1314 void PrerenderManager::DestroyAndMarkMatchCompleteAsUsed(
1315 PrerenderContents* prerender_contents,
1316 FinalStatus final_status) {
1317 prerender_contents->set_match_complete_status(
1318 PrerenderContents::MATCH_COMPLETE_REPLACED);
1319 histograms_->RecordFinalStatus(prerender_contents->origin(),
1320 prerender_contents->experiment_id(),
1321 PrerenderContents::MATCH_COMPLETE_REPLACEMENT,
1322 FINAL_STATUS_WOULD_HAVE_BEEN_USED);
1323 prerender_contents->Destroy(final_status);
1326 void PrerenderManager::RecordFinalStatusWithoutCreatingPrerenderContents(
1327 const GURL& url, Origin origin, uint8 experiment_id,
1328 FinalStatus final_status) const {
1329 PrerenderHistory::Entry entry(url, final_status, origin, base::Time::Now());
1330 prerender_history_->AddEntry(entry);
1331 RecordFinalStatusWithMatchCompleteStatus(
1332 origin, experiment_id,
1333 PrerenderContents::MATCH_COMPLETE_DEFAULT,
1334 final_status);
1337 void PrerenderManager::Observe(int type,
1338 const content::NotificationSource& source,
1339 const content::NotificationDetails& details) {
1340 switch (type) {
1341 case chrome::NOTIFICATION_COOKIE_CHANGED: {
1342 Profile* profile = content::Source<Profile>(source).ptr();
1343 if (!profile || !profile_->IsSameProfile(profile) ||
1344 profile->IsOffTheRecord()) {
1345 return;
1347 CookieChanged(content::Details<ChromeCookieDetails>(details).ptr());
1348 break;
1350 case chrome::NOTIFICATION_PROFILE_DESTROYED:
1351 DestroyAllContents(FINAL_STATUS_PROFILE_DESTROYED);
1352 on_close_web_contents_deleters_.clear();
1353 break;
1354 default:
1355 NOTREACHED() << "Unexpected notification sent.";
1356 break;
1360 void PrerenderManager::OnCreatingAudioStream(int render_process_id,
1361 int render_frame_id) {
1362 content::RenderFrameHost* render_frame_host =
1363 content::RenderFrameHost::FromID(render_process_id, render_frame_id);
1364 WebContents* tab = WebContents::FromRenderFrameHost(render_frame_host);
1365 if (!tab)
1366 return;
1368 PrerenderContents* prerender_contents = GetPrerenderContents(tab);
1369 if (!prerender_contents)
1370 return;
1372 prerender_contents->Destroy(prerender::FINAL_STATUS_CREATING_AUDIO_STREAM);
1375 void PrerenderManager::RecordLikelyLoginOnURL(const GURL& url) {
1376 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1377 if (!url.SchemeIsHTTPOrHTTPS())
1378 return;
1379 if (logged_in_predictor_table_.get()) {
1380 BrowserThread::PostTask(
1381 BrowserThread::DB,
1382 FROM_HERE,
1383 base::Bind(&LoggedInPredictorTable::AddDomainFromURL,
1384 logged_in_predictor_table_,
1385 url));
1387 std::string key = LoggedInPredictorTable::GetKey(url);
1388 if (!logged_in_state_.get())
1389 return;
1390 if (logged_in_state_->count(key))
1391 return;
1392 (*logged_in_state_)[key] = base::Time::Now().ToInternalValue();
1395 void PrerenderManager::CheckIfLikelyLoggedInOnURL(
1396 const GURL& url,
1397 bool* lookup_result,
1398 bool* database_was_present,
1399 const base::Closure& result_cb) {
1400 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1401 if (!logged_in_predictor_table_.get()) {
1402 *database_was_present = false;
1403 *lookup_result = false;
1404 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, result_cb);
1405 return;
1407 BrowserThread::PostTaskAndReply(
1408 BrowserThread::DB, FROM_HERE,
1409 base::Bind(&LoggedInPredictorTable::HasUserLoggedIn,
1410 logged_in_predictor_table_,
1411 url,
1412 lookup_result,
1413 database_was_present),
1414 result_cb);
1418 void PrerenderManager::CookieChanged(ChromeCookieDetails* details) {
1419 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1421 if (!logged_in_predictor_table_.get())
1422 return;
1424 // We only care when a cookie has been removed.
1425 if (!details->removed)
1426 return;
1428 std::string domain_key =
1429 LoggedInPredictorTable::GetKeyFromDomain(details->cookie->Domain());
1431 // If we have no record of this domain as a potentially logged in domain,
1432 // nothing to do here.
1433 if (logged_in_state_.get() && logged_in_state_->count(domain_key) < 1)
1434 return;
1436 net::URLRequestContextGetter* rq_context = profile_->GetRequestContext();
1437 if (!rq_context)
1438 return;
1440 BrowserThread::PostTask(
1441 BrowserThread::IO, FROM_HERE,
1442 base::Bind(&CheckIfCookiesExistForDomainOnIOThread,
1443 base::Unretained(rq_context),
1444 domain_key,
1445 base::Bind(
1446 &PrerenderManager::CookieChangedAnyCookiesLeftLookupResult,
1447 AsWeakPtr(),
1448 domain_key)
1452 void PrerenderManager::CookieChangedAnyCookiesLeftLookupResult(
1453 const std::string& domain_key,
1454 bool cookies_exist) {
1455 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1457 if (cookies_exist)
1458 return;
1460 if (logged_in_predictor_table_.get()) {
1461 BrowserThread::PostTask(BrowserThread::DB,
1462 FROM_HERE,
1463 base::Bind(&LoggedInPredictorTable::DeleteDomain,
1464 logged_in_predictor_table_,
1465 domain_key));
1468 if (logged_in_state_.get())
1469 logged_in_state_->erase(domain_key);
1472 void PrerenderManager::LoggedInPredictorDataReceived(
1473 scoped_ptr<LoggedInStateMap> new_map) {
1474 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1475 logged_in_state_.swap(new_map);
1478 void PrerenderManager::OnHistoryServiceDidQueryURL(
1479 Origin origin,
1480 uint8 experiment_id,
1481 bool success,
1482 const history::URLRow& url_row,
1483 const history::VisitVector& /*visits*/) {
1484 histograms_->RecordPrerenderPageVisitedStatus(origin, experiment_id, success);
1487 void PrerenderManager::RecordNetworkBytes(Origin origin,
1488 bool used,
1489 int64 prerender_bytes) {
1490 if (!ActuallyPrerendering())
1491 return;
1492 int64 recent_profile_bytes =
1493 profile_network_bytes_ - last_recorded_profile_network_bytes_;
1494 last_recorded_profile_network_bytes_ = profile_network_bytes_;
1495 DCHECK_GE(recent_profile_bytes, 0);
1496 histograms_->RecordNetworkBytes(
1497 origin, used, prerender_bytes, recent_profile_bytes);
1500 bool PrerenderManager::IsEnabled() const {
1501 DCHECK(CalledOnValidThread());
1503 return chrome_browser_net::CanPrefetchAndPrerenderUI(profile_->GetPrefs());
1506 void PrerenderManager::AddProfileNetworkBytesIfEnabled(int64 bytes) {
1507 DCHECK_GE(bytes, 0);
1508 if (IsEnabled() && ActuallyPrerendering())
1509 profile_network_bytes_ += bytes;
1512 void PrerenderManager::AddPrerenderProcessHost(
1513 content::RenderProcessHost* process_host) {
1514 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1515 DCHECK(prerender_process_hosts_.find(process_host) ==
1516 prerender_process_hosts_.end());
1517 prerender_process_hosts_.insert(process_host);
1518 process_host->AddObserver(this);
1521 bool PrerenderManager::MayReuseProcessHost(
1522 content::RenderProcessHost* process_host) {
1523 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1524 // Isolate prerender processes to make the resource monitoring check more
1525 // accurate.
1526 return (prerender_process_hosts_.find(process_host) ==
1527 prerender_process_hosts_.end());
1530 void PrerenderManager::RenderProcessHostDestroyed(
1531 content::RenderProcessHost* host) {
1532 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1533 size_t erased = prerender_process_hosts_.erase(host);
1534 DCHECK_EQ(1u, erased);
1537 } // namespace prerender