Renamed CanPruneAllButVisible and PruneAllButVisible in the Navigation Controller...
[chromium-blink-merge.git] / chrome / browser / prerender / prerender_manager.cc
blobb0cadbbe8cd2790b1a8be70fb9cab6cfbe65c5ce
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/prerender/prerender_manager.h"
7 #include <algorithm>
8 #include <functional>
9 #include <string>
10 #include <vector>
12 #include "base/bind.h"
13 #include "base/bind_helpers.h"
14 #include "base/logging.h"
15 #include "base/memory/weak_ptr.h"
16 #include "base/metrics/histogram.h"
17 #include "base/prefs/pref_service.h"
18 #include "base/stl_util.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/time/time.h"
21 #include "base/values.h"
22 #include "chrome/browser/browser_process.h"
23 #include "chrome/browser/chrome_notification_types.h"
24 #include "chrome/browser/common/cancelable_request.h"
25 #include "chrome/browser/favicon/favicon_tab_helper.h"
26 #include "chrome/browser/net/chrome_cookie_notification_details.h"
27 #include "chrome/browser/predictors/predictor_database.h"
28 #include "chrome/browser/predictors/predictor_database_factory.h"
29 #include "chrome/browser/prerender/prerender_condition.h"
30 #include "chrome/browser/prerender/prerender_contents.h"
31 #include "chrome/browser/prerender/prerender_field_trial.h"
32 #include "chrome/browser/prerender/prerender_final_status.h"
33 #include "chrome/browser/prerender/prerender_handle.h"
34 #include "chrome/browser/prerender/prerender_histograms.h"
35 #include "chrome/browser/prerender/prerender_history.h"
36 #include "chrome/browser/prerender/prerender_local_predictor.h"
37 #include "chrome/browser/prerender/prerender_manager_factory.h"
38 #include "chrome/browser/prerender/prerender_tab_helper.h"
39 #include "chrome/browser/prerender/prerender_tracker.h"
40 #include "chrome/browser/prerender/prerender_util.h"
41 #include "chrome/browser/profiles/profile.h"
42 #include "chrome/browser/search/search.h"
43 #include "chrome/browser/tab_contents/tab_util.h"
44 #include "chrome/browser/ui/browser_navigator.h"
45 #include "chrome/browser/ui/tab_contents/core_tab_helper.h"
46 #include "chrome/browser/ui/tab_contents/core_tab_helper_delegate.h"
47 #include "chrome/common/chrome_switches.h"
48 #include "chrome/common/pref_names.h"
49 #include "chrome/common/prerender_messages.h"
50 #include "content/public/browser/browser_thread.h"
51 #include "content/public/browser/devtools_agent_host.h"
52 #include "content/public/browser/navigation_controller.h"
53 #include "content/public/browser/notification_service.h"
54 #include "content/public/browser/notification_source.h"
55 #include "content/public/browser/render_process_host.h"
56 #include "content/public/browser/render_view_host.h"
57 #include "content/public/browser/session_storage_namespace.h"
58 #include "content/public/browser/web_contents.h"
59 #include "content/public/browser/web_contents_delegate.h"
60 #include "content/public/browser/web_contents_view.h"
61 #include "content/public/common/favicon_url.h"
62 #include "content/public/common/url_constants.h"
63 #include "extensions/common/constants.h"
64 #include "net/url_request/url_request_context.h"
65 #include "net/url_request/url_request_context_getter.h"
67 using content::BrowserThread;
68 using content::RenderViewHost;
69 using content::SessionStorageNamespace;
70 using content::WebContents;
71 using predictors::LoggedInPredictorTable;
73 namespace prerender {
75 namespace {
77 // Time interval at which periodic cleanups are performed.
78 const int kPeriodicCleanupIntervalMs = 1000;
80 // Valid HTTP methods for prerendering.
81 const char* const kValidHttpMethods[] = {
82 "GET",
83 "HEAD",
84 "OPTIONS",
85 "POST",
86 "TRACE",
89 // Length of prerender history, for display in chrome://net-internals
90 const int kHistoryLength = 100;
92 // Indicates whether a Prerender has been cancelled such that we need
93 // a dummy replacement for the purpose of recording the correct PPLT for
94 // the Match Complete case.
95 // Traditionally, "Match" means that a prerendered page was actually visited &
96 // the prerender was used. Our goal is to have "Match" cases line up in the
97 // control group & the experiment group, so that we can make meaningful
98 // comparisons of improvements. However, in the control group, since we don't
99 // actually perform prerenders, many of the cancellation reasons cannot be
100 // detected. Therefore, in the Prerender group, when we cancel for one of these
101 // reasons, we keep track of a dummy Prerender representing what we would
102 // have in the control group. If that dummy prerender in the prerender group
103 // would then be swapped in (but isn't actually b/c it's a dummy), we record
104 // this as a MatchComplete. This allows us to compare MatchComplete's
105 // across Prerender & Control group which ideally should be lining up.
106 // This ensures that there is no bias in terms of the page load times
107 // of the pages forming the difference between the two sets.
109 bool NeedMatchCompleteDummyForFinalStatus(FinalStatus final_status) {
110 return final_status != FINAL_STATUS_USED &&
111 final_status != FINAL_STATUS_TIMED_OUT &&
112 final_status != FINAL_STATUS_MANAGER_SHUTDOWN &&
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;
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 virtual void CloseContents(WebContents* source) OVERRIDE {
174 DCHECK_EQ(tab_, source);
175 ScheduleWebContentsForDeletion(false);
178 virtual void SwappedOut(WebContents* source) OVERRIDE {
179 DCHECK_EQ(tab_, source);
180 ScheduleWebContentsForDeletion(false);
183 virtual bool ShouldSuppressDialogs() OVERRIDE {
184 // Use this as a proxy for getting statistics on how often we fail to honor
185 // the beforeunload event.
186 suppressed_dialog_ = true;
187 return true;
190 private:
191 static const int kDeleteWithExtremePrejudiceSeconds = 3;
193 void ScheduleWebContentsForDeletion(bool timeout) {
194 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterTimeout", timeout);
195 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterSuppressedDialog",
196 suppressed_dialog_);
197 tab_->SetDelegate(NULL);
198 manager_->ScheduleDeleteOldWebContents(tab_.release(), this);
199 // |this| is deleted at this point.
202 PrerenderManager* manager_;
203 scoped_ptr<WebContents> tab_;
204 bool suppressed_dialog_;
206 DISALLOW_COPY_AND_ASSIGN(OnCloseWebContentsDeleter);
209 // static
210 bool PrerenderManager::is_prefetch_enabled_ = false;
212 // static
213 int PrerenderManager::prerenders_per_session_count_ = 0;
215 // static
216 PrerenderManager::PrerenderManagerMode PrerenderManager::mode_ =
217 PRERENDER_MODE_ENABLED;
219 struct PrerenderManager::NavigationRecord {
220 NavigationRecord(const GURL& url, base::TimeTicks time)
221 : url(url),
222 time(time) {
225 GURL url;
226 base::TimeTicks time;
229 PrerenderManager::PrerenderedWebContentsData::
230 PrerenderedWebContentsData(Origin origin) : origin(origin) {
233 PrerenderManager::WouldBePrerenderedWebContentsData::
234 WouldBePrerenderedWebContentsData(Origin origin)
235 : origin(origin),
236 state(WAITING_FOR_PROVISIONAL_LOAD) {
239 PrerenderManager::PrerenderManager(Profile* profile,
240 PrerenderTracker* prerender_tracker)
241 : enabled_(profile && profile->GetPrefs() &&
242 profile->GetPrefs()->GetBoolean(prefs::kNetworkPredictionEnabled)),
243 profile_(profile),
244 prerender_tracker_(prerender_tracker),
245 prerender_contents_factory_(PrerenderContents::CreateFactory()),
246 last_prerender_start_time_(GetCurrentTimeTicks() -
247 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs)),
248 prerender_history_(new PrerenderHistory(kHistoryLength)),
249 histograms_(new PrerenderHistograms()) {
250 // There are some assumptions that the PrerenderManager is on the UI thread.
251 // Any other checks simply make sure that the PrerenderManager is accessed on
252 // the same thread that it was created on.
253 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
255 if (IsLocalPredictorEnabled())
256 local_predictor_.reset(new PrerenderLocalPredictor(this));
258 if (IsLoggedInPredictorEnabled() && !profile_->IsOffTheRecord()) {
259 predictors::PredictorDatabase* predictor_db =
260 predictors::PredictorDatabaseFactory::GetForProfile(profile);
261 if (predictor_db) {
262 logged_in_predictor_table_ = predictor_db->logged_in_table();
263 scoped_ptr<LoggedInStateMap> new_state_map(new LoggedInStateMap);
264 LoggedInStateMap* new_state_map_ptr = new_state_map.get();
265 BrowserThread::PostTaskAndReply(
266 BrowserThread::DB, FROM_HERE,
267 base::Bind(&LoggedInPredictorTable::GetAllData,
268 logged_in_predictor_table_,
269 new_state_map_ptr),
270 base::Bind(&PrerenderManager::LoggedInPredictorDataReceived,
271 AsWeakPtr(),
272 base::Passed(&new_state_map)));
276 // Certain experiments override our default config_ values.
277 switch (PrerenderManager::GetMode()) {
278 case PrerenderManager::PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP:
279 config_.max_link_concurrency = 4;
280 config_.max_link_concurrency_per_launcher = 2;
281 break;
282 case PrerenderManager::PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP:
283 config_.time_to_live = base::TimeDelta::FromMinutes(15);
284 break;
285 default:
286 break;
289 notification_registrar_.Add(
290 this, chrome::NOTIFICATION_COOKIE_CHANGED,
291 content::NotificationService::AllBrowserContextsAndSources());
293 MediaCaptureDevicesDispatcher::GetInstance()->AddObserver(this);
296 PrerenderManager::~PrerenderManager() {
297 MediaCaptureDevicesDispatcher::GetInstance()->RemoveObserver(this);
299 // The earlier call to BrowserContextKeyedService::Shutdown() should have
300 // emptied these vectors already.
301 DCHECK(active_prerenders_.empty());
302 DCHECK(to_delete_prerenders_.empty());
305 void PrerenderManager::Shutdown() {
306 DestroyAllContents(FINAL_STATUS_MANAGER_SHUTDOWN);
307 STLDeleteElements(&prerender_conditions_);
308 on_close_web_contents_deleters_.clear();
309 // Must happen before |profile_| is set to NULL as
310 // |local_predictor_| accesses it.
311 if (local_predictor_)
312 local_predictor_->Shutdown();
313 profile_ = NULL;
315 DCHECK(active_prerenders_.empty());
318 PrerenderHandle* PrerenderManager::AddPrerenderFromLinkRelPrerender(
319 int process_id,
320 int route_id,
321 const GURL& url,
322 const content::Referrer& referrer,
323 const gfx::Size& size) {
324 #if defined(OS_ANDROID)
325 // TODO(jcivelli): http://crbug.com/113322 We should have an option to disable
326 // link-prerender and enable omnibox-prerender only.
327 return NULL;
328 #else
329 DCHECK(!size.IsEmpty());
330 Origin origin = ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN;
331 SessionStorageNamespace* session_storage_namespace = NULL;
332 // Unit tests pass in a process_id == -1.
333 if (process_id != -1) {
334 RenderViewHost* source_render_view_host =
335 RenderViewHost::FromID(process_id, route_id);
336 if (!source_render_view_host)
337 return NULL;
338 WebContents* source_web_contents =
339 WebContents::FromRenderViewHost(source_render_view_host);
340 if (!source_web_contents)
341 return NULL;
342 if (source_web_contents->GetURL().host() == url.host())
343 origin = ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN;
344 // TODO(ajwong): This does not correctly handle storage for isolated apps.
345 session_storage_namespace =
346 source_web_contents->GetController()
347 .GetDefaultSessionStorageNamespace();
350 // If the prerender request comes from a recently cancelled prerender that
351 // |this| still owns, then abort the prerender.
352 for (ScopedVector<PrerenderData>::iterator it = to_delete_prerenders_.begin();
353 it != to_delete_prerenders_.end(); ++it) {
354 PrerenderContents* prerender_contents = (*it)->contents();
355 int contents_child_id;
356 int contents_route_id;
357 if (prerender_contents->GetChildId(&contents_child_id) &&
358 prerender_contents->GetRouteId(&contents_route_id)) {
359 if (contents_child_id == process_id && contents_route_id == route_id)
360 return NULL;
364 if (PrerenderData* parent_prerender_data =
365 FindPrerenderDataForChildAndRoute(process_id, route_id)) {
366 // Instead of prerendering from inside of a running prerender, we will defer
367 // this request until its launcher is made visible.
368 if (PrerenderContents* contents = parent_prerender_data->contents()) {
369 PrerenderHandle* prerender_handle =
370 new PrerenderHandle(static_cast<PrerenderData*>(NULL));
371 scoped_ptr<PrerenderContents::PendingPrerenderInfo>
372 pending_prerender_info(new PrerenderContents::PendingPrerenderInfo(
373 prerender_handle->weak_ptr_factory_.GetWeakPtr(),
374 origin, url, referrer, size));
376 contents->AddPendingPrerender(pending_prerender_info.Pass());
377 return prerender_handle;
381 return AddPrerender(origin, process_id, url, referrer, size,
382 session_storage_namespace);
383 #endif
386 PrerenderHandle* PrerenderManager::AddPrerenderFromOmnibox(
387 const GURL& url,
388 SessionStorageNamespace* session_storage_namespace,
389 const gfx::Size& size) {
390 if (!IsOmniboxEnabled(profile_))
391 return NULL;
392 return AddPrerender(ORIGIN_OMNIBOX, -1, url, content::Referrer(), size,
393 session_storage_namespace);
396 PrerenderHandle* PrerenderManager::AddPrerenderFromLocalPredictor(
397 const GURL& url,
398 SessionStorageNamespace* session_storage_namespace,
399 const gfx::Size& size) {
400 return AddPrerender(ORIGIN_LOCAL_PREDICTOR, -1, url, content::Referrer(),
401 size, session_storage_namespace);
404 PrerenderHandle* PrerenderManager::AddPrerenderFromExternalRequest(
405 const GURL& url,
406 const content::Referrer& referrer,
407 SessionStorageNamespace* session_storage_namespace,
408 const gfx::Size& size) {
409 return AddPrerender(ORIGIN_EXTERNAL_REQUEST, -1, url, referrer, size,
410 session_storage_namespace);
413 PrerenderHandle* PrerenderManager::AddPrerenderForInstant(
414 const GURL& url,
415 content::SessionStorageNamespace* session_storage_namespace,
416 const gfx::Size& size) {
417 DCHECK(chrome::ShouldPrefetchSearchResults());
418 return AddPrerender(ORIGIN_INSTANT, -1, url, content::Referrer(), size,
419 session_storage_namespace);
422 void PrerenderManager::DestroyPrerenderForRenderView(
423 int process_id, int view_id, FinalStatus final_status) {
424 DCHECK(CalledOnValidThread());
425 if (PrerenderData* prerender_data =
426 FindPrerenderDataForChildAndRoute(process_id, view_id)) {
427 prerender_data->contents()->Destroy(final_status);
431 void PrerenderManager::CancelAllPrerenders() {
432 DCHECK(CalledOnValidThread());
433 while (!active_prerenders_.empty()) {
434 PrerenderContents* prerender_contents =
435 active_prerenders_.front()->contents();
436 prerender_contents->Destroy(FINAL_STATUS_CANCELLED);
440 bool PrerenderManager::MaybeUsePrerenderedPage(const GURL& url,
441 chrome::NavigateParams* params) {
442 DCHECK(CalledOnValidThread());
444 content::WebContents* web_contents = params->target_contents;
445 DCHECK(!IsWebContentsPrerendering(web_contents, NULL));
447 // Don't prerender if the navigation involves some special parameters.
448 if (params->uses_post || !params->extra_headers.empty())
449 return false;
451 DeleteOldEntries();
452 to_delete_prerenders_.clear();
453 // TODO(ajwong): This doesn't handle isolated apps correctly.
454 PrerenderData* prerender_data = FindPrerenderData(
455 url,
456 web_contents->GetController().GetDefaultSessionStorageNamespace());
457 if (!prerender_data)
458 return false;
459 DCHECK(prerender_data->contents());
460 if (IsNoSwapInExperiment(prerender_data->contents()->experiment_id()))
461 return false;
463 if (WebContents* new_web_contents =
464 prerender_data->contents()->prerender_contents()) {
465 if (web_contents == new_web_contents)
466 return false; // Do not swap in to ourself.
468 // We cannot swap in if there is no last committed entry, because we would
469 // show a blank page under an existing entry from the current tab. Even if
470 // there is a pending entry, it may not commit.
471 // TODO(creis): If there is a pending navigation and no last committed
472 // entry, we might be able to transfer the network request instead.
473 if (!new_web_contents->GetController().CanPruneAllButLastCommitted()) {
474 // Abort this prerender so it is not used later. http://crbug.com/292121
475 prerender_data->contents()->Destroy(FINAL_STATUS_NAVIGATION_UNCOMMITTED);
476 return false;
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 false;
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 false;
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 MarkWebContentsAsWouldBePrerendered(web_contents,
497 prerender_data->contents()->origin());
498 prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED);
499 return false;
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 false;
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 false;
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 MarkWebContentsAsWouldBePrerendered(web_contents,
523 prerender_data->contents()->origin());
524 prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED);
525 return false;
528 int child_id, route_id;
529 CHECK(prerender_data->contents()->GetChildId(&child_id));
530 CHECK(prerender_data->contents()->GetRouteId(&route_id));
532 // Try to set the prerendered page as used, so any subsequent attempts to
533 // cancel on other threads will fail. If this fails because the prerender
534 // was already cancelled, possibly on another thread, fail.
535 if (!prerender_tracker_->TryUse(child_id, route_id))
536 return false;
538 // At this point, we've determined that we will use the prerender.
539 ScopedVector<PrerenderData>::iterator to_erase =
540 FindIteratorForPrerenderContents(prerender_data->contents());
541 DCHECK(active_prerenders_.end() != to_erase);
542 DCHECK_EQ(prerender_data, *to_erase);
543 scoped_ptr<PrerenderContents>
544 prerender_contents(prerender_data->ReleaseContents());
545 active_prerenders_.erase(to_erase);
547 if (!prerender_contents->load_start_time().is_null()) {
548 histograms_->RecordTimeUntilUsed(
549 prerender_contents->origin(),
550 GetCurrentTimeTicks() - prerender_contents->load_start_time());
553 histograms_->RecordPerSessionCount(prerender_contents->origin(),
554 ++prerenders_per_session_count_);
555 histograms_->RecordUsedPrerender(prerender_contents->origin());
556 prerender_contents->SetFinalStatus(FINAL_STATUS_USED);
558 RenderViewHost* new_render_view_host =
559 prerender_contents->prerender_contents()->GetRenderViewHost();
560 new_render_view_host->Send(
561 new PrerenderMsg_SetIsPrerendering(new_render_view_host->GetRoutingID(),
562 false));
564 // Start pending prerender requests from the PrerenderContents, if there are
565 // any.
566 prerender_contents->PrepareForUse();
568 WebContents* new_web_contents =
569 prerender_contents->ReleasePrerenderContents();
570 WebContents* old_web_contents = web_contents;
571 DCHECK(new_web_contents);
572 DCHECK(old_web_contents);
574 MarkWebContentsAsPrerendered(new_web_contents, prerender_contents->origin());
576 // Merge the browsing history.
577 new_web_contents->GetController().CopyStateFromAndPrune(
578 &old_web_contents->GetController());
579 CoreTabHelper::FromWebContents(old_web_contents)->delegate()->
580 SwapTabContents(old_web_contents, new_web_contents);
581 prerender_contents->CommitHistory(new_web_contents);
583 // Record the new target_contents for the callers.
584 params->target_contents = new_web_contents;
586 GURL icon_url = prerender_contents->icon_url();
588 if (!icon_url.is_empty()) {
589 #if defined(OS_ANDROID)
590 // Do the delayed icon fetch since we didn't download
591 // the favicon during prerendering on mobile devices.
592 FaviconTabHelper * favicon_tap_helper =
593 FaviconTabHelper::FromWebContents(new_web_contents);
594 favicon_tap_helper->set_should_fetch_icons(true);
595 favicon_tap_helper->FetchFavicon(icon_url);
596 #endif // defined(OS_ANDROID)
598 std::vector<content::FaviconURL> urls;
599 urls.push_back(content::FaviconURL(icon_url, content::FaviconURL::FAVICON));
600 FaviconTabHelper::FromWebContents(new_web_contents)->
601 DidUpdateFaviconURL(prerender_contents->page_id(), urls);
604 // Update PPLT metrics:
605 // If the tab has finished loading, record a PPLT of 0.
606 // If the tab is still loading, reset its start time to the current time.
607 PrerenderTabHelper* prerender_tab_helper =
608 PrerenderTabHelper::FromWebContents(new_web_contents);
609 DCHECK(prerender_tab_helper != NULL);
610 prerender_tab_helper->PrerenderSwappedIn();
612 if (old_web_contents->NeedToFireBeforeUnload()) {
613 // Schedule the delete to occur after the tab has run its unload handlers.
614 // TODO(davidben): Honor the beforeunload event. http://crbug.com/304932
615 on_close_web_contents_deleters_.push_back(
616 new OnCloseWebContentsDeleter(this, old_web_contents));
617 old_web_contents->GetRenderViewHost()->
618 FirePageBeforeUnload(false);
619 } else {
620 // No unload handler to run, so delete asap.
621 ScheduleDeleteOldWebContents(old_web_contents, NULL);
624 // TODO(cbentzel): Should prerender_contents move to the pending delete
625 // list, instead of deleting directly here?
626 AddToHistory(prerender_contents.get());
627 RecordNavigation(url);
628 return true;
631 void PrerenderManager::MoveEntryToPendingDelete(PrerenderContents* entry,
632 FinalStatus final_status) {
633 DCHECK(CalledOnValidThread());
634 DCHECK(entry);
636 ScopedVector<PrerenderData>::iterator it =
637 FindIteratorForPrerenderContents(entry);
638 DCHECK(it != active_prerenders_.end());
640 // If this PrerenderContents is being deleted due to a cancellation any time
641 // after the prerender has started then we need to create a dummy replacement
642 // for PPLT accounting purposes for the Match Complete group. This is the case
643 // if the cancellation is for any reason that would not occur in the control
644 // group case.
645 if (entry->prerendering_has_started() &&
646 entry->match_complete_status() ==
647 PrerenderContents::MATCH_COMPLETE_DEFAULT &&
648 NeedMatchCompleteDummyForFinalStatus(final_status) &&
649 ActuallyPrerendering()) {
650 // TODO(tburkard): I'd like to DCHECK that we are actually prerendering.
651 // However, what if new conditions are added and
652 // NeedMatchCompleteDummyForFinalStatus is not being updated. Not sure
653 // what's the best thing to do here. For now, I will just check whether
654 // we are actually prerendering.
655 (*it)->MakeIntoMatchCompleteReplacement();
656 } else {
657 to_delete_prerenders_.push_back(*it);
658 active_prerenders_.weak_erase(it);
661 // Destroy the old WebContents relatively promptly to reduce resource usage.
662 PostCleanupTask();
665 // static
666 void PrerenderManager::RecordPerceivedPageLoadTime(
667 base::TimeDelta perceived_page_load_time,
668 double fraction_plt_elapsed_at_swap_in,
669 WebContents* web_contents,
670 const GURL& url) {
671 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
672 PrerenderManager* prerender_manager =
673 PrerenderManagerFactory::GetForProfile(
674 Profile::FromBrowserContext(web_contents->GetBrowserContext()));
675 if (!prerender_manager)
676 return;
677 if (!prerender_manager->IsEnabled())
678 return;
680 Origin prerender_origin = ORIGIN_NONE;
681 if (prerender_manager->IsWebContentsPrerendering(web_contents,
682 &prerender_origin)) {
683 prerender_manager->histograms_->RecordPageLoadTimeNotSwappedIn(
684 prerender_origin, perceived_page_load_time, url);
685 return;
688 bool was_prerender = prerender_manager->IsWebContentsPrerendered(
689 web_contents, &prerender_origin);
690 bool was_complete_prerender = was_prerender ||
691 prerender_manager->WouldWebContentsBePrerendered(web_contents,
692 &prerender_origin);
693 prerender_manager->histograms_->RecordPerceivedPageLoadTime(
694 prerender_origin, perceived_page_load_time, was_prerender,
695 was_complete_prerender, url);
697 if (was_prerender) {
698 prerender_manager->histograms_->RecordPercentLoadDoneAtSwapin(
699 prerender_origin, fraction_plt_elapsed_at_swap_in);
701 if (prerender_manager->local_predictor_.get()) {
702 prerender_manager->local_predictor_->
703 OnPLTEventForURL(url, perceived_page_load_time);
707 void PrerenderManager::RecordFractionPixelsFinalAtSwapin(
708 content::WebContents* web_contents,
709 double fraction) {
710 Origin origin = ORIGIN_NONE;
711 bool is_prerendered = IsWebContentsPrerendered(web_contents, &origin);
712 DCHECK(is_prerendered);
713 histograms_->RecordFractionPixelsFinalAtSwapin(origin, fraction);
716 void PrerenderManager::set_enabled(bool enabled) {
717 DCHECK(CalledOnValidThread());
718 enabled_ = enabled;
721 // static
722 bool PrerenderManager::IsPrefetchEnabled() {
723 return is_prefetch_enabled_;
726 // static
727 void PrerenderManager::SetIsPrefetchEnabled(bool value) {
728 is_prefetch_enabled_ = value;
731 // static
732 PrerenderManager::PrerenderManagerMode PrerenderManager::GetMode() {
733 return mode_;
736 // static
737 void PrerenderManager::SetMode(PrerenderManagerMode mode) {
738 mode_ = mode;
741 // static
742 const char* PrerenderManager::GetModeString() {
743 switch (mode_) {
744 case PRERENDER_MODE_DISABLED:
745 return "_Disabled";
746 case PRERENDER_MODE_ENABLED:
747 case PRERENDER_MODE_EXPERIMENT_PRERENDER_GROUP:
748 return "_Enabled";
749 case PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP:
750 return "_Control";
751 case PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP:
752 return "_Multi";
753 case PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP:
754 return "_15MinTTL";
755 case PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP:
756 return "_NoUse";
757 case PRERENDER_MODE_MAX:
758 default:
759 NOTREACHED() << "Invalid PrerenderManager mode.";
760 break;
762 return "";
765 // static
766 bool PrerenderManager::IsPrerenderingPossible() {
767 return GetMode() != PRERENDER_MODE_DISABLED;
770 // static
771 bool PrerenderManager::ActuallyPrerendering() {
772 return IsPrerenderingPossible() && !IsControlGroup(kNoExperiment);
775 // static
776 bool PrerenderManager::IsControlGroup(uint8 experiment_id) {
777 return GetMode() == PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP ||
778 IsControlGroupExperiment(experiment_id);
781 // static
782 bool PrerenderManager::IsNoUseGroup() {
783 return GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP;
786 bool PrerenderManager::IsWebContentsPrerendering(
787 const WebContents* web_contents,
788 Origin* origin) const {
789 DCHECK(CalledOnValidThread());
790 if (PrerenderContents* prerender_contents =
791 GetPrerenderContents(web_contents)) {
792 if (origin)
793 *origin = prerender_contents->origin();
794 return true;
797 // Also look through the pending-deletion list.
798 for (ScopedVector<PrerenderData>::const_iterator it =
799 to_delete_prerenders_.begin();
800 it != to_delete_prerenders_.end();
801 ++it) {
802 if (PrerenderContents* prerender_contents = (*it)->contents()) {
803 WebContents* prerender_web_contents =
804 prerender_contents->prerender_contents();
805 if (prerender_web_contents == web_contents) {
806 if (origin)
807 *origin = prerender_contents->origin();
808 return true;
813 return false;
816 bool PrerenderManager::HasPrerenderedUrl(
817 GURL url,
818 content::WebContents* web_contents) const {
819 content::SessionStorageNamespace* session_storage_namespace = web_contents->
820 GetController().GetDefaultSessionStorageNamespace();
822 for (ScopedVector<PrerenderData>::const_iterator it =
823 active_prerenders_.begin();
824 it != active_prerenders_.end(); ++it) {
825 PrerenderContents* prerender_contents = (*it)->contents();
826 if (prerender_contents->Matches(url, session_storage_namespace)) {
827 return true;
830 return false;
833 PrerenderContents* PrerenderManager::GetPrerenderContents(
834 const content::WebContents* web_contents) const {
835 DCHECK(CalledOnValidThread());
836 for (ScopedVector<PrerenderData>::const_iterator it =
837 active_prerenders_.begin();
838 it != active_prerenders_.end(); ++it) {
839 WebContents* prerender_web_contents =
840 (*it)->contents()->prerender_contents();
841 if (prerender_web_contents == web_contents) {
842 return (*it)->contents();
845 return NULL;
848 const std::vector<WebContents*>
849 PrerenderManager::GetAllPrerenderingContents() const {
850 DCHECK(CalledOnValidThread());
851 std::vector<WebContents*> result;
853 for (ScopedVector<PrerenderData>::const_iterator it =
854 active_prerenders_.begin();
855 it != active_prerenders_.end(); ++it) {
856 if (WebContents* contents = (*it)->contents()->prerender_contents())
857 result.push_back(contents);
860 return result;
863 void PrerenderManager::MarkWebContentsAsPrerendered(WebContents* web_contents,
864 Origin origin) {
865 DCHECK(CalledOnValidThread());
866 prerendered_web_contents_data_.insert(
867 base::hash_map<content::WebContents*,
868 PrerenderedWebContentsData>::value_type(
869 web_contents, PrerenderedWebContentsData(origin)));
872 void PrerenderManager::MarkWebContentsAsWouldBePrerendered(
873 WebContents* web_contents,
874 Origin origin) {
875 DCHECK(CalledOnValidThread());
876 would_be_prerendered_map_.insert(
877 base::hash_map<content::WebContents*,
878 WouldBePrerenderedWebContentsData>::value_type(
879 web_contents,
880 WouldBePrerenderedWebContentsData(origin)));
883 void PrerenderManager::MarkWebContentsAsNotPrerendered(
884 WebContents* web_contents) {
885 DCHECK(CalledOnValidThread());
886 prerendered_web_contents_data_.erase(web_contents);
887 base::hash_map<content::WebContents*, WouldBePrerenderedWebContentsData>::
888 iterator it = would_be_prerendered_map_.find(web_contents);
889 if (it != would_be_prerendered_map_.end()) {
890 if (it->second.state ==
891 WouldBePrerenderedWebContentsData::WAITING_FOR_PROVISIONAL_LOAD) {
892 it->second.state =
893 WouldBePrerenderedWebContentsData::SEEN_PROVISIONAL_LOAD;
894 } else {
895 would_be_prerendered_map_.erase(it);
900 bool PrerenderManager::IsWebContentsPrerendered(
901 content::WebContents* web_contents,
902 Origin* origin) const {
903 DCHECK(CalledOnValidThread());
904 base::hash_map<content::WebContents*, PrerenderedWebContentsData>::
905 const_iterator it = prerendered_web_contents_data_.find(web_contents);
906 if (it == prerendered_web_contents_data_.end())
907 return false;
908 if (origin)
909 *origin = it->second.origin;
910 return true;
913 bool PrerenderManager::WouldWebContentsBePrerendered(
914 WebContents* web_contents,
915 Origin* origin) const {
916 DCHECK(CalledOnValidThread());
917 base::hash_map<content::WebContents*, WouldBePrerenderedWebContentsData>::
918 const_iterator it = would_be_prerendered_map_.find(web_contents);
919 if (it == would_be_prerendered_map_.end())
920 return false;
921 if (origin)
922 *origin = it->second.origin;
923 return true;
926 bool PrerenderManager::HasRecentlyBeenNavigatedTo(Origin origin,
927 const GURL& url) {
928 DCHECK(CalledOnValidThread());
930 CleanUpOldNavigations();
931 std::list<NavigationRecord>::const_reverse_iterator end = navigations_.rend();
932 for (std::list<NavigationRecord>::const_reverse_iterator it =
933 navigations_.rbegin();
934 it != end;
935 ++it) {
936 if (it->url == url) {
937 base::TimeDelta delta = GetCurrentTimeTicks() - it->time;
938 histograms_->RecordTimeSinceLastRecentVisit(origin, delta);
939 return true;
943 return false;
946 // static
947 bool PrerenderManager::IsValidHttpMethod(const std::string& method) {
948 // method has been canonicalized to upper case at this point so we can just
949 // compare them.
950 DCHECK_EQ(method, StringToUpperASCII(method));
951 for (size_t i = 0; i < arraysize(kValidHttpMethods); ++i) {
952 if (method.compare(kValidHttpMethods[i]) == 0)
953 return true;
956 return false;
959 // static
960 bool PrerenderManager::DoesURLHaveValidScheme(const GURL& url) {
961 return (url.SchemeIsHTTPOrHTTPS() ||
962 url.SchemeIs(extensions::kExtensionScheme) ||
963 url.SchemeIs("data"));
966 // static
967 bool PrerenderManager::DoesSubresourceURLHaveValidScheme(const GURL& url) {
968 return DoesURLHaveValidScheme(url) || url == GURL(content::kAboutBlankURL);
971 DictionaryValue* PrerenderManager::GetAsValue() const {
972 DCHECK(CalledOnValidThread());
973 DictionaryValue* dict_value = new DictionaryValue();
974 dict_value->Set("history", prerender_history_->GetEntriesAsValue());
975 dict_value->Set("active", GetActivePrerendersAsValue());
976 dict_value->SetBoolean("enabled", enabled_);
977 dict_value->SetBoolean("omnibox_enabled", IsOmniboxEnabled(profile_));
978 // If prerender is disabled via a flag this method is not even called.
979 std::string enabled_note;
980 if (IsControlGroup(kNoExperiment))
981 enabled_note += "(Control group: Not actually prerendering) ";
982 if (IsNoUseGroup())
983 enabled_note += "(No-use group: Not swapping in prerendered pages) ";
984 if (GetMode() == PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP)
985 enabled_note +=
986 "(15 min TTL group: Extended prerender eviction to 15 mins) ";
987 dict_value->SetString("enabled_note", enabled_note);
988 return dict_value;
991 void PrerenderManager::ClearData(int clear_flags) {
992 DCHECK_GE(clear_flags, 0);
993 DCHECK_LT(clear_flags, CLEAR_MAX);
994 if (clear_flags & CLEAR_PRERENDER_CONTENTS)
995 DestroyAllContents(FINAL_STATUS_CACHE_OR_HISTORY_CLEARED);
996 // This has to be second, since destroying prerenders can add to the history.
997 if (clear_flags & CLEAR_PRERENDER_HISTORY)
998 prerender_history_->Clear();
1001 void PrerenderManager::RecordFinalStatusWithMatchCompleteStatus(
1002 Origin origin,
1003 uint8 experiment_id,
1004 PrerenderContents::MatchCompleteStatus mc_status,
1005 FinalStatus final_status) const {
1006 histograms_->RecordFinalStatus(origin,
1007 experiment_id,
1008 mc_status,
1009 final_status);
1012 void PrerenderManager::AddCondition(const PrerenderCondition* condition) {
1013 prerender_conditions_.push_back(condition);
1016 void PrerenderManager::RecordNavigation(const GURL& url) {
1017 DCHECK(CalledOnValidThread());
1019 navigations_.push_back(NavigationRecord(url, GetCurrentTimeTicks()));
1020 CleanUpOldNavigations();
1023 // protected
1024 struct PrerenderManager::PrerenderData::OrderByExpiryTime {
1025 bool operator()(const PrerenderData* a, const PrerenderData* b) const {
1026 return a->expiry_time() < b->expiry_time();
1030 PrerenderManager::PrerenderData::PrerenderData(PrerenderManager* manager,
1031 PrerenderContents* contents,
1032 base::TimeTicks expiry_time)
1033 : manager_(manager),
1034 contents_(contents),
1035 handle_count_(0),
1036 expiry_time_(expiry_time) {
1037 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
1040 PrerenderManager::PrerenderData::~PrerenderData() {
1043 void PrerenderManager::PrerenderData::MakeIntoMatchCompleteReplacement() {
1044 DCHECK(contents_);
1045 contents_->set_match_complete_status(
1046 PrerenderContents::MATCH_COMPLETE_REPLACED);
1047 PrerenderData* to_delete = new PrerenderData(manager_, contents_.release(),
1048 expiry_time_);
1049 contents_.reset(to_delete->contents_->CreateMatchCompleteReplacement());
1050 manager_->to_delete_prerenders_.push_back(to_delete);
1053 void PrerenderManager::PrerenderData::OnHandleCreated(PrerenderHandle* handle) {
1054 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
1055 ++handle_count_;
1056 contents_->AddObserver(handle);
1059 void PrerenderManager::PrerenderData::OnHandleNavigatedAway(
1060 PrerenderHandle* handle) {
1061 DCHECK_LT(0, handle_count_);
1062 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
1063 // We intentionally don't decrement the handle count here, so that the
1064 // prerender won't be canceled until it times out.
1065 manager_->SourceNavigatedAway(this);
1068 void PrerenderManager::PrerenderData::OnHandleCanceled(
1069 PrerenderHandle* handle) {
1070 DCHECK_LT(0, handle_count_);
1071 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
1073 if (--handle_count_ == 0) {
1074 // This will eventually remove this object from active_prerenders_.
1075 contents_->Destroy(FINAL_STATUS_CANCELLED);
1079 PrerenderContents* PrerenderManager::PrerenderData::ReleaseContents() {
1080 return contents_.release();
1083 void PrerenderManager::SetPrerenderContentsFactory(
1084 PrerenderContents::Factory* prerender_contents_factory) {
1085 DCHECK(CalledOnValidThread());
1086 prerender_contents_factory_.reset(prerender_contents_factory);
1089 void PrerenderManager::StartPendingPrerenders(
1090 const int process_id,
1091 ScopedVector<PrerenderContents::PendingPrerenderInfo>* pending_prerenders,
1092 content::SessionStorageNamespace* session_storage_namespace) {
1093 for (ScopedVector<PrerenderContents::PendingPrerenderInfo>::iterator
1094 it = pending_prerenders->begin();
1095 it != pending_prerenders->end(); ++it) {
1096 PrerenderContents::PendingPrerenderInfo* info = *it;
1097 PrerenderHandle* existing_prerender_handle =
1098 info->weak_prerender_handle.get();
1099 if (!existing_prerender_handle)
1100 continue;
1102 DCHECK(!existing_prerender_handle->IsPrerendering());
1103 DCHECK(process_id == -1 || session_storage_namespace);
1105 scoped_ptr<PrerenderHandle> new_prerender_handle(AddPrerender(
1106 info->origin, process_id,
1107 info->url, info->referrer, info->size,
1108 session_storage_namespace));
1109 if (new_prerender_handle) {
1110 // AddPrerender has returned a new prerender handle to us. We want to make
1111 // |existing_prerender_handle| active, so move the underlying
1112 // PrerenderData to our new handle.
1113 existing_prerender_handle->AdoptPrerenderDataFrom(
1114 new_prerender_handle.get());
1115 continue;
1120 void PrerenderManager::SourceNavigatedAway(PrerenderData* prerender_data) {
1121 // The expiry time of our prerender data will likely change because of
1122 // this navigation. This requires a resort of active_prerenders_.
1123 ScopedVector<PrerenderData>::iterator it =
1124 std::find(active_prerenders_.begin(), active_prerenders_.end(),
1125 prerender_data);
1126 if (it == active_prerenders_.end())
1127 return;
1129 (*it)->set_expiry_time(
1130 std::min((*it)->expiry_time(),
1131 GetExpiryTimeForNavigatedAwayPrerender()));
1132 SortActivePrerenders();
1135 // private
1136 PrerenderHandle* PrerenderManager::AddPrerender(
1137 Origin origin,
1138 int process_id,
1139 const GURL& url_arg,
1140 const content::Referrer& referrer,
1141 const gfx::Size& size,
1142 SessionStorageNamespace* session_storage_namespace) {
1143 DCHECK(CalledOnValidThread());
1145 if (!IsEnabled())
1146 return NULL;
1148 if ((origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN ||
1149 origin == ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN) &&
1150 IsGoogleSearchResultURL(referrer.url)) {
1151 origin = ORIGIN_GWS_PRERENDER;
1154 GURL url = url_arg;
1155 GURL alias_url;
1156 uint8 experiment = GetQueryStringBasedExperiment(url_arg);
1157 if (IsControlGroup(experiment) &&
1158 MaybeGetQueryStringBasedAliasURL(url, &alias_url)) {
1159 url = alias_url;
1162 // From here on, we will record a FinalStatus so we need to register with the
1163 // histogram tracking.
1164 histograms_->RecordPrerender(origin, url_arg);
1166 if (PrerenderData* preexisting_prerender_data =
1167 FindPrerenderData(url, session_storage_namespace)) {
1168 RecordFinalStatus(origin, experiment, FINAL_STATUS_DUPLICATE);
1169 return new PrerenderHandle(preexisting_prerender_data);
1172 // Do not prerender if there are too many render processes, and we would
1173 // have to use an existing one. We do not want prerendering to happen in
1174 // a shared process, so that we can always reliably lower the CPU
1175 // priority for prerendering.
1176 // In single-process mode, ShouldTryToUseExistingProcessHost() always returns
1177 // true, so that case needs to be explicitly checked for.
1178 // TODO(tburkard): Figure out how to cancel prerendering in the opposite
1179 // case, when a new tab is added to a process used for prerendering.
1180 // On Android we do reuse processes as we have a limited number of them and we
1181 // still want the benefits of prerendering even when several tabs are open.
1182 #if !defined(OS_ANDROID)
1183 if (content::RenderProcessHost::ShouldTryToUseExistingProcessHost(
1184 profile_, url) &&
1185 !content::RenderProcessHost::run_renderer_in_process()) {
1186 RecordFinalStatus(origin, experiment, FINAL_STATUS_TOO_MANY_PROCESSES);
1187 return NULL;
1189 #endif
1191 // Check if enough time has passed since the last prerender.
1192 if (!DoesRateLimitAllowPrerender(origin)) {
1193 // Cancel the prerender. We could add it to the pending prerender list but
1194 // this doesn't make sense as the next prerender request will be triggered
1195 // by a navigation and is unlikely to be the same site.
1196 RecordFinalStatus(origin, experiment, FINAL_STATUS_RATE_LIMIT_EXCEEDED);
1197 return NULL;
1200 PrerenderContents* prerender_contents = CreatePrerenderContents(
1201 url, referrer, origin, experiment);
1202 DCHECK(prerender_contents);
1203 active_prerenders_.push_back(
1204 new PrerenderData(this, prerender_contents,
1205 GetExpiryTimeForNewPrerender(origin)));
1206 if (!prerender_contents->Init()) {
1207 DCHECK(active_prerenders_.end() ==
1208 FindIteratorForPrerenderContents(prerender_contents));
1209 return NULL;
1212 histograms_->RecordPrerenderStarted(origin);
1213 DCHECK(!prerender_contents->prerendering_has_started());
1215 PrerenderHandle* prerender_handle =
1216 new PrerenderHandle(active_prerenders_.back());
1217 SortActivePrerenders();
1219 last_prerender_start_time_ = GetCurrentTimeTicks();
1221 gfx::Size contents_size =
1222 size.IsEmpty() ? config_.default_tab_bounds.size() : size;
1224 prerender_contents->StartPrerendering(process_id, contents_size,
1225 session_storage_namespace);
1227 DCHECK(IsControlGroup(experiment) ||
1228 prerender_contents->prerendering_has_started());
1230 if (GetMode() == PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP)
1231 histograms_->RecordConcurrency(active_prerenders_.size());
1233 StartSchedulingPeriodicCleanups();
1234 return prerender_handle;
1237 void PrerenderManager::StartSchedulingPeriodicCleanups() {
1238 DCHECK(CalledOnValidThread());
1239 if (repeating_timer_.IsRunning())
1240 return;
1241 repeating_timer_.Start(FROM_HERE,
1242 base::TimeDelta::FromMilliseconds(kPeriodicCleanupIntervalMs),
1243 this,
1244 &PrerenderManager::PeriodicCleanup);
1247 void PrerenderManager::StopSchedulingPeriodicCleanups() {
1248 DCHECK(CalledOnValidThread());
1249 repeating_timer_.Stop();
1252 void PrerenderManager::PeriodicCleanup() {
1253 DCHECK(CalledOnValidThread());
1254 DeleteOldWebContents();
1255 DeleteOldEntries();
1256 if (active_prerenders_.empty())
1257 StopSchedulingPeriodicCleanups();
1259 // Grab a copy of the current PrerenderContents pointers, so that we
1260 // will not interfere with potential deletions of the list.
1261 std::vector<PrerenderContents*>
1262 prerender_contents(active_prerenders_.size());
1263 std::transform(active_prerenders_.begin(), active_prerenders_.end(),
1264 prerender_contents.begin(),
1265 std::mem_fun(&PrerenderData::contents));
1267 // And now check for prerenders using too much memory.
1268 std::for_each(prerender_contents.begin(), prerender_contents.end(),
1269 std::mem_fun(
1270 &PrerenderContents::DestroyWhenUsingTooManyResources));
1272 to_delete_prerenders_.clear();
1275 void PrerenderManager::PostCleanupTask() {
1276 DCHECK(CalledOnValidThread());
1277 base::MessageLoop::current()->PostTask(
1278 FROM_HERE,
1279 base::Bind(&PrerenderManager::PeriodicCleanup, AsWeakPtr()));
1282 base::TimeTicks PrerenderManager::GetExpiryTimeForNewPrerender(
1283 Origin origin) const {
1284 base::TimeDelta ttl = config_.time_to_live;
1285 if (origin == ORIGIN_LOCAL_PREDICTOR)
1286 ttl = base::TimeDelta::FromSeconds(GetLocalPredictorTTLSeconds());
1287 return GetCurrentTimeTicks() + ttl;
1290 base::TimeTicks PrerenderManager::GetExpiryTimeForNavigatedAwayPrerender()
1291 const {
1292 return GetCurrentTimeTicks() + config_.abandon_time_to_live;
1295 void PrerenderManager::DeleteOldEntries() {
1296 DCHECK(CalledOnValidThread());
1297 while (!active_prerenders_.empty()) {
1298 PrerenderData* prerender_data = active_prerenders_.front();
1299 DCHECK(prerender_data);
1300 DCHECK(prerender_data->contents());
1302 if (prerender_data->expiry_time() > GetCurrentTimeTicks())
1303 return;
1304 prerender_data->contents()->Destroy(FINAL_STATUS_TIMED_OUT);
1308 base::Time PrerenderManager::GetCurrentTime() const {
1309 return base::Time::Now();
1312 base::TimeTicks PrerenderManager::GetCurrentTimeTicks() const {
1313 return base::TimeTicks::Now();
1316 PrerenderContents* PrerenderManager::CreatePrerenderContents(
1317 const GURL& url,
1318 const content::Referrer& referrer,
1319 Origin origin,
1320 uint8 experiment_id) {
1321 DCHECK(CalledOnValidThread());
1322 return prerender_contents_factory_->CreatePrerenderContents(
1323 this, profile_, url, referrer, origin, experiment_id);
1326 void PrerenderManager::SortActivePrerenders() {
1327 std::sort(active_prerenders_.begin(), active_prerenders_.end(),
1328 PrerenderData::OrderByExpiryTime());
1331 PrerenderManager::PrerenderData* PrerenderManager::FindPrerenderData(
1332 const GURL& url,
1333 const SessionStorageNamespace* session_storage_namespace) {
1334 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1335 it != active_prerenders_.end(); ++it) {
1336 if ((*it)->contents()->Matches(url, session_storage_namespace))
1337 return *it;
1339 return NULL;
1342 PrerenderManager::PrerenderData*
1343 PrerenderManager::FindPrerenderDataForChildAndRoute(
1344 const int child_id, const int route_id) {
1345 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1346 it != active_prerenders_.end(); ++it) {
1347 PrerenderContents* prerender_contents = (*it)->contents();
1349 int contents_child_id;
1350 if (!prerender_contents->GetChildId(&contents_child_id))
1351 continue;
1352 int contents_route_id;
1353 if (!prerender_contents->GetRouteId(&contents_route_id))
1354 continue;
1356 if (contents_child_id == child_id && contents_route_id == route_id)
1357 return *it;
1359 return NULL;
1362 ScopedVector<PrerenderManager::PrerenderData>::iterator
1363 PrerenderManager::FindIteratorForPrerenderContents(
1364 PrerenderContents* prerender_contents) {
1365 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1366 it != active_prerenders_.end(); ++it) {
1367 if (prerender_contents == (*it)->contents())
1368 return it;
1370 return active_prerenders_.end();
1373 bool PrerenderManager::DoesRateLimitAllowPrerender(Origin origin) const {
1374 DCHECK(CalledOnValidThread());
1375 base::TimeDelta elapsed_time =
1376 GetCurrentTimeTicks() - last_prerender_start_time_;
1377 histograms_->RecordTimeBetweenPrerenderRequests(origin, elapsed_time);
1378 if (!config_.rate_limit_enabled)
1379 return true;
1380 return elapsed_time >=
1381 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs);
1384 void PrerenderManager::DeleteOldWebContents() {
1385 while (!old_web_contents_list_.empty()) {
1386 WebContents* web_contents = old_web_contents_list_.front();
1387 old_web_contents_list_.pop_front();
1388 // TODO(dominich): should we use Instant Unload Handler here?
1389 delete web_contents;
1393 void PrerenderManager::CleanUpOldNavigations() {
1394 DCHECK(CalledOnValidThread());
1396 // Cutoff. Navigations before this cutoff can be discarded.
1397 base::TimeTicks cutoff = GetCurrentTimeTicks() -
1398 base::TimeDelta::FromMilliseconds(kNavigationRecordWindowMs);
1399 while (!navigations_.empty()) {
1400 if (navigations_.front().time > cutoff)
1401 break;
1402 navigations_.pop_front();
1406 void PrerenderManager::ScheduleDeleteOldWebContents(
1407 WebContents* tab,
1408 OnCloseWebContentsDeleter* deleter) {
1409 old_web_contents_list_.push_back(tab);
1410 PostCleanupTask();
1412 if (deleter) {
1413 ScopedVector<OnCloseWebContentsDeleter>::iterator i = std::find(
1414 on_close_web_contents_deleters_.begin(),
1415 on_close_web_contents_deleters_.end(),
1416 deleter);
1417 DCHECK(i != on_close_web_contents_deleters_.end());
1418 on_close_web_contents_deleters_.erase(i);
1422 void PrerenderManager::AddToHistory(PrerenderContents* contents) {
1423 PrerenderHistory::Entry entry(contents->prerender_url(),
1424 contents->final_status(),
1425 contents->origin(),
1426 base::Time::Now());
1427 prerender_history_->AddEntry(entry);
1430 Value* PrerenderManager::GetActivePrerendersAsValue() const {
1431 ListValue* list_value = new ListValue();
1432 for (ScopedVector<PrerenderData>::const_iterator it =
1433 active_prerenders_.begin();
1434 it != active_prerenders_.end(); ++it) {
1435 if (Value* prerender_value = (*it)->contents()->GetAsValue())
1436 list_value->Append(prerender_value);
1438 return list_value;
1441 void PrerenderManager::DestroyAllContents(FinalStatus final_status) {
1442 DeleteOldWebContents();
1443 while (!active_prerenders_.empty()) {
1444 PrerenderContents* contents = active_prerenders_.front()->contents();
1445 contents->Destroy(final_status);
1447 to_delete_prerenders_.clear();
1450 void PrerenderManager::DestroyAndMarkMatchCompleteAsUsed(
1451 PrerenderContents* prerender_contents,
1452 FinalStatus final_status) {
1453 prerender_contents->set_match_complete_status(
1454 PrerenderContents::MATCH_COMPLETE_REPLACED);
1455 histograms_->RecordFinalStatus(prerender_contents->origin(),
1456 prerender_contents->experiment_id(),
1457 PrerenderContents::MATCH_COMPLETE_REPLACEMENT,
1458 FINAL_STATUS_WOULD_HAVE_BEEN_USED);
1459 prerender_contents->Destroy(final_status);
1462 void PrerenderManager::RecordFinalStatus(Origin origin,
1463 uint8 experiment_id,
1464 FinalStatus final_status) const {
1465 RecordFinalStatusWithMatchCompleteStatus(
1466 origin, experiment_id,
1467 PrerenderContents::MATCH_COMPLETE_DEFAULT,
1468 final_status);
1471 bool PrerenderManager::IsEnabled() const {
1472 DCHECK(CalledOnValidThread());
1473 if (!enabled_)
1474 return false;
1475 for (std::list<const PrerenderCondition*>::const_iterator it =
1476 prerender_conditions_.begin();
1477 it != prerender_conditions_.end();
1478 ++it) {
1479 const PrerenderCondition* condition = *it;
1480 if (!condition->CanPrerender())
1481 return false;
1483 return true;
1486 PrerenderManager* FindPrerenderManagerUsingRenderProcessId(
1487 int render_process_id) {
1488 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1489 content::RenderProcessHost* render_process_host =
1490 content::RenderProcessHost::FromID(render_process_id);
1491 // Each render process is guaranteed to only hold RenderViews owned by the
1492 // same BrowserContext. This is enforced by
1493 // RenderProcessHost::GetExistingProcessHost.
1494 if (!render_process_host || !render_process_host->GetBrowserContext())
1495 return NULL;
1496 Profile* profile = Profile::FromBrowserContext(
1497 render_process_host->GetBrowserContext());
1498 if (!profile)
1499 return NULL;
1500 return PrerenderManagerFactory::GetInstance()->GetForProfile(profile);
1503 void PrerenderManager::Observe(int type,
1504 const content::NotificationSource& source,
1505 const content::NotificationDetails& details) {
1506 Profile* profile = content::Source<Profile>(source).ptr();
1507 if (!profile || !profile_->IsSameProfile(profile) ||
1508 profile->IsOffTheRecord()) {
1509 return;
1511 DCHECK(type == chrome::NOTIFICATION_COOKIE_CHANGED);
1512 CookieChanged(content::Details<ChromeCookieDetails>(details).ptr());
1515 void PrerenderManager::OnCreatingAudioStream(int render_process_id,
1516 int render_view_id) {
1517 WebContents* tab = tab_util::GetWebContentsByID(
1518 render_process_id, render_view_id);
1519 if (!tab)
1520 return;
1522 if (!IsWebContentsPrerendering(tab, NULL))
1523 return;
1525 prerender_tracker()->TryCancel(
1526 render_process_id, render_view_id,
1527 prerender::FINAL_STATUS_CREATING_AUDIO_STREAM);
1530 void PrerenderManager::RecordLikelyLoginOnURL(const GURL& url) {
1531 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1532 if (!url.SchemeIsHTTPOrHTTPS())
1533 return;
1534 if (logged_in_predictor_table_.get()) {
1535 BrowserThread::PostTask(
1536 BrowserThread::DB,
1537 FROM_HERE,
1538 base::Bind(&LoggedInPredictorTable::AddDomainFromURL,
1539 logged_in_predictor_table_,
1540 url));
1542 std::string key = LoggedInPredictorTable::GetKey(url);
1543 if (!logged_in_state_.get())
1544 return;
1545 if (logged_in_state_->count(key))
1546 return;
1547 (*logged_in_state_)[key] = base::Time::Now().ToInternalValue();
1550 void PrerenderManager::CheckIfLikelyLoggedInOnURL(
1551 const GURL& url,
1552 bool* lookup_result,
1553 bool* database_was_present,
1554 const base::Closure& result_cb) {
1555 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1556 if (!logged_in_predictor_table_.get()) {
1557 *database_was_present = false;
1558 *lookup_result = false;
1559 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, result_cb);
1560 return;
1562 BrowserThread::PostTaskAndReply(
1563 BrowserThread::DB, FROM_HERE,
1564 base::Bind(&LoggedInPredictorTable::HasUserLoggedIn,
1565 logged_in_predictor_table_,
1566 url,
1567 lookup_result,
1568 database_was_present),
1569 result_cb);
1573 void PrerenderManager::CookieChanged(ChromeCookieDetails* details) {
1574 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1576 if (!logged_in_predictor_table_.get())
1577 return;
1579 // We only care when a cookie has been removed.
1580 if (!details->removed)
1581 return;
1583 std::string domain_key =
1584 LoggedInPredictorTable::GetKeyFromDomain(details->cookie->Domain());
1586 // If we have no record of this domain as a potentially logged in domain,
1587 // nothing to do here.
1588 if (logged_in_state_.get() && logged_in_state_->count(domain_key) < 1)
1589 return;
1591 net::URLRequestContextGetter* rq_context = profile_->GetRequestContext();
1592 if (!rq_context)
1593 return;
1595 BrowserThread::PostTask(
1596 BrowserThread::IO, FROM_HERE,
1597 base::Bind(&CheckIfCookiesExistForDomainOnIOThread,
1598 base::Unretained(rq_context),
1599 domain_key,
1600 base::Bind(
1601 &PrerenderManager::CookieChangedAnyCookiesLeftLookupResult,
1602 AsWeakPtr(),
1603 domain_key)
1607 void PrerenderManager::CookieChangedAnyCookiesLeftLookupResult(
1608 const std::string& domain_key,
1609 bool cookies_exist) {
1610 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1612 if (cookies_exist)
1613 return;
1615 if (logged_in_predictor_table_.get()) {
1616 BrowserThread::PostTask(BrowserThread::DB,
1617 FROM_HERE,
1618 base::Bind(&LoggedInPredictorTable::DeleteDomain,
1619 logged_in_predictor_table_,
1620 domain_key));
1623 if (logged_in_state_.get())
1624 logged_in_state_->erase(domain_key);
1627 void PrerenderManager::LoggedInPredictorDataReceived(
1628 scoped_ptr<LoggedInStateMap> new_map) {
1629 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1630 logged_in_state_.swap(new_map);
1633 } // namespace prerender