Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / chrome / browser / prerender / prerender_manager.cc
blobea4bba111536acb9d665c6cc904640be56e7e3c8
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/timer/elapsed_timer.h"
22 #include "base/values.h"
23 #include "chrome/browser/browser_process.h"
24 #include "chrome/browser/chrome_notification_types.h"
25 #include "chrome/browser/history/history_service_factory.h"
26 #include "chrome/browser/net/chrome_cookie_notification_details.h"
27 #include "chrome/browser/net/prediction_options.h"
28 #include "chrome/browser/predictors/predictor_database.h"
29 #include "chrome/browser/predictors/predictor_database_factory.h"
30 #include "chrome/browser/prerender/prerender_condition.h"
31 #include "chrome/browser/prerender/prerender_contents.h"
32 #include "chrome/browser/prerender/prerender_field_trial.h"
33 #include "chrome/browser/prerender/prerender_final_status.h"
34 #include "chrome/browser/prerender/prerender_handle.h"
35 #include "chrome/browser/prerender/prerender_histograms.h"
36 #include "chrome/browser/prerender/prerender_history.h"
37 #include "chrome/browser/prerender/prerender_local_predictor.h"
38 #include "chrome/browser/prerender/prerender_manager_factory.h"
39 #include "chrome/browser/prerender/prerender_tab_helper.h"
40 #include "chrome/browser/prerender/prerender_tracker.h"
41 #include "chrome/browser/prerender/prerender_util.h"
42 #include "chrome/browser/profiles/profile.h"
43 #include "chrome/browser/search/search.h"
44 #include "chrome/browser/tab_contents/tab_util.h"
45 #include "chrome/browser/ui/browser_navigator.h"
46 #include "chrome/browser/ui/tab_contents/core_tab_helper.h"
47 #include "chrome/browser/ui/tab_contents/core_tab_helper_delegate.h"
48 #include "chrome/common/chrome_switches.h"
49 #include "chrome/common/pref_names.h"
50 #include "chrome/common/prerender_messages.h"
51 #include "chrome/common/prerender_types.h"
52 #include "content/public/browser/browser_thread.h"
53 #include "content/public/browser/devtools_agent_host.h"
54 #include "content/public/browser/navigation_controller.h"
55 #include "content/public/browser/notification_service.h"
56 #include "content/public/browser/notification_source.h"
57 #include "content/public/browser/render_frame_host.h"
58 #include "content/public/browser/render_process_host.h"
59 #include "content/public/browser/render_view_host.h"
60 #include "content/public/browser/session_storage_namespace.h"
61 #include "content/public/browser/site_instance.h"
62 #include "content/public/browser/storage_partition.h"
63 #include "content/public/browser/web_contents.h"
64 #include "content/public/browser/web_contents_delegate.h"
65 #include "content/public/common/url_constants.h"
66 #include "extensions/common/constants.h"
67 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
68 #include "net/url_request/url_request_context.h"
69 #include "net/url_request/url_request_context_getter.h"
71 using content::BrowserThread;
72 using content::RenderViewHost;
73 using content::RenderFrameHost;
74 using content::SessionStorageNamespace;
75 using content::WebContents;
76 using predictors::LoggedInPredictorTable;
78 namespace prerender {
80 namespace {
82 // Time interval at which periodic cleanups are performed.
83 const int kPeriodicCleanupIntervalMs = 1000;
85 // Valid HTTP methods for prerendering.
86 const char* const kValidHttpMethods[] = {
87 "GET",
88 "HEAD",
89 "OPTIONS",
90 "POST",
91 "TRACE",
94 // Length of prerender history, for display in chrome://net-internals
95 const int kHistoryLength = 100;
97 // Timeout, in ms, for a session storage namespace merge.
98 const int kSessionStorageNamespaceMergeTimeoutMs = 500;
100 // If true, all session storage merges hang indefinitely.
101 bool g_hang_session_storage_merges_for_testing = false;
103 // Indicates whether a Prerender has been cancelled such that we need
104 // a dummy replacement for the purpose of recording the correct PPLT for
105 // the Match Complete case.
106 // Traditionally, "Match" means that a prerendered page was actually visited &
107 // the prerender was used. Our goal is to have "Match" cases line up in the
108 // control group & the experiment group, so that we can make meaningful
109 // comparisons of improvements. However, in the control group, since we don't
110 // actually perform prerenders, many of the cancellation reasons cannot be
111 // detected. Therefore, in the Prerender group, when we cancel for one of these
112 // reasons, we keep track of a dummy Prerender representing what we would
113 // have in the control group. If that dummy prerender in the prerender group
114 // would then be swapped in (but isn't actually b/c it's a dummy), we record
115 // this as a MatchComplete. This allows us to compare MatchComplete's
116 // across Prerender & Control group which ideally should be lining up.
117 // This ensures that there is no bias in terms of the page load times
118 // of the pages forming the difference between the two sets.
120 bool NeedMatchCompleteDummyForFinalStatus(FinalStatus final_status) {
121 return final_status != FINAL_STATUS_USED &&
122 final_status != FINAL_STATUS_TIMED_OUT &&
123 final_status != FINAL_STATUS_MANAGER_SHUTDOWN &&
124 final_status != FINAL_STATUS_PROFILE_DESTROYED &&
125 final_status != FINAL_STATUS_APP_TERMINATING &&
126 final_status != FINAL_STATUS_WINDOW_OPENER &&
127 final_status != FINAL_STATUS_CACHE_OR_HISTORY_CLEARED &&
128 final_status != FINAL_STATUS_CANCELLED &&
129 final_status != FINAL_STATUS_DEVTOOLS_ATTACHED &&
130 final_status != FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING &&
131 final_status != FINAL_STATUS_PAGE_BEING_CAPTURED &&
132 final_status != FINAL_STATUS_NAVIGATION_UNCOMMITTED &&
133 final_status != FINAL_STATUS_NON_EMPTY_BROWSING_INSTANCE;
136 void CheckIfCookiesExistForDomainResultOnUIThread(
137 const net::CookieMonster::HasCookiesForETLDP1Callback& callback,
138 bool cookies_exist) {
139 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
140 callback.Run(cookies_exist);
143 void CheckIfCookiesExistForDomainResultOnIOThread(
144 const net::CookieMonster::HasCookiesForETLDP1Callback& callback,
145 bool cookies_exist) {
146 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
147 BrowserThread::PostTask(
148 BrowserThread::UI,
149 FROM_HERE,
150 base::Bind(&CheckIfCookiesExistForDomainResultOnUIThread,
151 callback,
152 cookies_exist));
155 void CheckIfCookiesExistForDomainOnIOThread(
156 net::URLRequestContextGetter* rq_context,
157 const std::string& domain_key,
158 const net::CookieMonster::HasCookiesForETLDP1Callback& callback) {
159 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
160 net::CookieStore* cookie_store =
161 rq_context->GetURLRequestContext()->cookie_store();
162 cookie_store->GetCookieMonster()->HasCookiesForETLDP1Async(
163 domain_key,
164 base::Bind(&CheckIfCookiesExistForDomainResultOnIOThread, callback));
167 } // namespace
169 class PrerenderManager::OnCloseWebContentsDeleter
170 : public content::WebContentsDelegate,
171 public base::SupportsWeakPtr<
172 PrerenderManager::OnCloseWebContentsDeleter> {
173 public:
174 OnCloseWebContentsDeleter(PrerenderManager* manager,
175 WebContents* tab)
176 : manager_(manager),
177 tab_(tab),
178 suppressed_dialog_(false) {
179 tab_->SetDelegate(this);
180 base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
181 base::Bind(&OnCloseWebContentsDeleter::ScheduleWebContentsForDeletion,
182 AsWeakPtr(), true),
183 base::TimeDelta::FromSeconds(kDeleteWithExtremePrejudiceSeconds));
186 virtual void CloseContents(WebContents* source) OVERRIDE {
187 DCHECK_EQ(tab_, source);
188 ScheduleWebContentsForDeletion(false);
191 virtual void SwappedOut(WebContents* source) OVERRIDE {
192 DCHECK_EQ(tab_, source);
193 ScheduleWebContentsForDeletion(false);
196 virtual bool ShouldSuppressDialogs() OVERRIDE {
197 // Use this as a proxy for getting statistics on how often we fail to honor
198 // the beforeunload event.
199 suppressed_dialog_ = true;
200 return true;
203 private:
204 static const int kDeleteWithExtremePrejudiceSeconds = 3;
206 void ScheduleWebContentsForDeletion(bool timeout) {
207 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterTimeout", timeout);
208 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterSuppressedDialog",
209 suppressed_dialog_);
210 tab_->SetDelegate(NULL);
211 manager_->ScheduleDeleteOldWebContents(tab_.release(), this);
212 // |this| is deleted at this point.
215 PrerenderManager* manager_;
216 scoped_ptr<WebContents> tab_;
217 bool suppressed_dialog_;
219 DISALLOW_COPY_AND_ASSIGN(OnCloseWebContentsDeleter);
222 // static
223 int PrerenderManager::prerenders_per_session_count_ = 0;
225 // static
226 PrerenderManager::PrerenderManagerMode PrerenderManager::mode_ =
227 PRERENDER_MODE_ENABLED;
229 struct PrerenderManager::NavigationRecord {
230 NavigationRecord(const GURL& url, base::TimeTicks time)
231 : url(url),
232 time(time) {
235 GURL url;
236 base::TimeTicks time;
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 profile_network_bytes_(0),
251 last_recorded_profile_network_bytes_(0),
252 cookie_store_loaded_(false) {
253 // There are some assumptions that the PrerenderManager is on the UI thread.
254 // Any other checks simply make sure that the PrerenderManager is accessed on
255 // the same thread that it was created on.
256 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
258 if (IsLocalPredictorEnabled())
259 local_predictor_.reset(new PrerenderLocalPredictor(this));
261 if (IsLoggedInPredictorEnabled() && !profile_->IsOffTheRecord()) {
262 predictors::PredictorDatabase* predictor_db =
263 predictors::PredictorDatabaseFactory::GetForProfile(profile);
264 if (predictor_db) {
265 logged_in_predictor_table_ = predictor_db->logged_in_table();
266 scoped_ptr<LoggedInStateMap> new_state_map(new LoggedInStateMap);
267 LoggedInStateMap* new_state_map_ptr = new_state_map.get();
268 BrowserThread::PostTaskAndReply(
269 BrowserThread::DB, FROM_HERE,
270 base::Bind(&LoggedInPredictorTable::GetAllData,
271 logged_in_predictor_table_,
272 new_state_map_ptr),
273 base::Bind(&PrerenderManager::LoggedInPredictorDataReceived,
274 AsWeakPtr(),
275 base::Passed(&new_state_map)));
279 // Certain experiments override our default config_ values.
280 switch (PrerenderManager::GetMode()) {
281 case PrerenderManager::PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP:
282 config_.max_link_concurrency = 4;
283 config_.max_link_concurrency_per_launcher = 2;
284 break;
285 case PrerenderManager::PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP:
286 config_.time_to_live = base::TimeDelta::FromMinutes(15);
287 break;
288 default:
289 break;
292 notification_registrar_.Add(
293 this, chrome::NOTIFICATION_COOKIE_CHANGED,
294 content::NotificationService::AllBrowserContextsAndSources());
296 notification_registrar_.Add(
297 this, chrome::NOTIFICATION_PROFILE_DESTROYED,
298 content::Source<Profile>(profile_));
300 MediaCaptureDevicesDispatcher::GetInstance()->AddObserver(this);
303 PrerenderManager::~PrerenderManager() {
304 MediaCaptureDevicesDispatcher::GetInstance()->RemoveObserver(this);
306 // The earlier call to KeyedService::Shutdown() should have
307 // emptied these vectors already.
308 DCHECK(active_prerenders_.empty());
309 DCHECK(to_delete_prerenders_.empty());
311 for (PrerenderProcessSet::const_iterator it =
312 prerender_process_hosts_.begin();
313 it != prerender_process_hosts_.end();
314 ++it) {
315 (*it)->RemoveObserver(this);
319 void PrerenderManager::Shutdown() {
320 DestroyAllContents(FINAL_STATUS_MANAGER_SHUTDOWN);
321 STLDeleteElements(&prerender_conditions_);
322 on_close_web_contents_deleters_.clear();
323 // Must happen before |profile_| is set to NULL as
324 // |local_predictor_| accesses it.
325 if (local_predictor_)
326 local_predictor_->Shutdown();
327 profile_ = NULL;
329 DCHECK(active_prerenders_.empty());
332 PrerenderHandle* PrerenderManager::AddPrerenderFromLinkRelPrerender(
333 int process_id,
334 int route_id,
335 const GURL& url,
336 const uint32 rel_types,
337 const content::Referrer& referrer,
338 const gfx::Size& size) {
339 Origin origin = rel_types & PrerenderRelTypePrerender ?
340 ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN :
341 ORIGIN_LINK_REL_NEXT;
342 SessionStorageNamespace* session_storage_namespace = NULL;
343 // Unit tests pass in a process_id == -1.
344 if (process_id != -1) {
345 RenderViewHost* source_render_view_host =
346 RenderViewHost::FromID(process_id, route_id);
347 if (!source_render_view_host)
348 return NULL;
349 WebContents* source_web_contents =
350 WebContents::FromRenderViewHost(source_render_view_host);
351 if (!source_web_contents)
352 return NULL;
353 if (origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN &&
354 source_web_contents->GetURL().host() == url.host()) {
355 origin = ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN;
357 // TODO(ajwong): This does not correctly handle storage for isolated apps.
358 session_storage_namespace =
359 source_web_contents->GetController()
360 .GetDefaultSessionStorageNamespace();
363 return AddPrerender(origin, process_id, url, referrer, size,
364 session_storage_namespace);
367 PrerenderHandle* PrerenderManager::AddPrerenderFromOmnibox(
368 const GURL& url,
369 SessionStorageNamespace* session_storage_namespace,
370 const gfx::Size& size) {
371 if (!IsOmniboxEnabled(profile_))
372 return NULL;
373 return AddPrerender(ORIGIN_OMNIBOX, -1, url, content::Referrer(), size,
374 session_storage_namespace);
377 PrerenderHandle* PrerenderManager::AddPrerenderFromLocalPredictor(
378 const GURL& url,
379 SessionStorageNamespace* session_storage_namespace,
380 const gfx::Size& size) {
381 return AddPrerender(ORIGIN_LOCAL_PREDICTOR, -1, url, content::Referrer(),
382 size, session_storage_namespace);
385 PrerenderHandle* PrerenderManager::AddPrerenderFromExternalRequest(
386 const GURL& url,
387 const content::Referrer& referrer,
388 SessionStorageNamespace* session_storage_namespace,
389 const gfx::Size& size) {
390 return AddPrerender(ORIGIN_EXTERNAL_REQUEST, -1, url, referrer, size,
391 session_storage_namespace);
394 PrerenderHandle* PrerenderManager::AddPrerenderForInstant(
395 const GURL& url,
396 content::SessionStorageNamespace* session_storage_namespace,
397 const gfx::Size& size) {
398 DCHECK(chrome::ShouldPrefetchSearchResults());
399 return AddPrerender(ORIGIN_INSTANT, -1, url, content::Referrer(), size,
400 session_storage_namespace);
403 void PrerenderManager::CancelAllPrerenders() {
404 DCHECK(CalledOnValidThread());
405 while (!active_prerenders_.empty()) {
406 PrerenderContents* prerender_contents =
407 active_prerenders_.front()->contents();
408 prerender_contents->Destroy(FINAL_STATUS_CANCELLED);
412 bool PrerenderManager::MaybeUsePrerenderedPage(const GURL& url,
413 chrome::NavigateParams* params) {
414 DCHECK(CalledOnValidThread());
416 content::WebContents* web_contents = params->target_contents;
417 DCHECK(!IsWebContentsPrerendering(web_contents, NULL));
419 // Don't prerender if the navigation involves some special parameters.
420 if (params->uses_post || !params->extra_headers.empty())
421 return false;
423 DeleteOldEntries();
424 to_delete_prerenders_.clear();
426 // First, try to find prerender data with the correct session storage
427 // namespace.
428 // TODO(ajwong): This doesn't handle isolated apps correctly.
429 PrerenderData* prerender_data = FindPrerenderData(
430 url,
431 web_contents->GetController().GetDefaultSessionStorageNamespace());
433 // If this failed, we may still find a prerender for the same URL, but a
434 // different session storage namespace. If we do, we might have to perform
435 // a merge.
436 if (!prerender_data) {
437 prerender_data = FindPrerenderData(url, NULL);
438 } else {
439 RecordEvent(prerender_data->contents(),
440 PRERENDER_EVENT_SWAPIN_CANDIDATE_NAMESPACE_MATCHES);
443 if (!prerender_data)
444 return false;
445 RecordEvent(prerender_data->contents(), PRERENDER_EVENT_SWAPIN_CANDIDATE);
446 DCHECK(prerender_data->contents());
448 // If there is currently a merge pending for this prerender data, don't swap.
449 if (prerender_data->pending_swap())
450 return false;
452 // Abort any existing pending swap on the target contents.
453 PrerenderData* pending_swap =
454 FindPrerenderDataForTargetContents(web_contents);
455 if (pending_swap) {
456 pending_swap->ClearPendingSwap();
457 DCHECK(FindPrerenderDataForTargetContents(web_contents) == NULL);
460 RecordEvent(prerender_data->contents(),
461 PRERENDER_EVENT_SWAPIN_NO_MERGE_PENDING);
462 SessionStorageNamespace* target_namespace =
463 web_contents->GetController().GetDefaultSessionStorageNamespace();
464 SessionStorageNamespace* prerender_namespace =
465 prerender_data->contents()->GetSessionStorageNamespace();
466 // Only when actually prerendering is session storage namespace merging an
467 // issue. For the control group, it will be assumed that the merge succeeded.
468 if (prerender_namespace && prerender_namespace != target_namespace &&
469 !prerender_namespace->IsAliasOf(target_namespace)) {
470 if (!ShouldMergeSessionStorageNamespaces()) {
471 RecordEvent(prerender_data->contents(),
472 PRERENDER_EVENT_SWAPIN_MERGING_DISABLED);
473 return false;
475 RecordEvent(prerender_data->contents(),
476 PRERENDER_EVENT_SWAPIN_ISSUING_MERGE);
477 prerender_data->set_pending_swap(new PendingSwap(
478 this, web_contents, prerender_data, url,
479 params->should_replace_current_entry));
480 prerender_data->pending_swap()->BeginSwap();
481 // Although this returns false, creating a PendingSwap registers with
482 // PrerenderTracker to throttle MAIN_FRAME navigations while the swap is
483 // pending.
484 return false;
487 // No need to merge; swap synchronously.
488 WebContents* new_web_contents = SwapInternal(
489 url, web_contents, prerender_data,
490 params->should_replace_current_entry);
491 if (!new_web_contents)
492 return false;
494 // Record the new target_contents for the callers.
495 params->target_contents = new_web_contents;
496 return true;
499 WebContents* PrerenderManager::SwapInternal(
500 const GURL& url,
501 WebContents* web_contents,
502 PrerenderData* prerender_data,
503 bool should_replace_current_entry) {
504 DCHECK(CalledOnValidThread());
505 DCHECK(!IsWebContentsPrerendering(web_contents, NULL));
507 // Only swap if the target WebContents has a CoreTabHelper delegate to swap
508 // out of it. For a normal WebContents, this is if it is in a TabStripModel.
509 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(web_contents);
510 if (!core_tab_helper || !core_tab_helper->delegate()) {
511 RecordEvent(prerender_data->contents(), PRERENDER_EVENT_SWAPIN_NO_DELEGATE);
512 return NULL;
515 PrerenderTabHelper* target_tab_helper =
516 PrerenderTabHelper::FromWebContents(web_contents);
517 if (!target_tab_helper) {
518 NOTREACHED();
519 return NULL;
522 if (IsNoSwapInExperiment(prerender_data->contents()->experiment_id()))
523 return NULL;
525 if (WebContents* new_web_contents =
526 prerender_data->contents()->prerender_contents()) {
527 if (web_contents == new_web_contents)
528 return NULL; // Do not swap in to ourself.
530 // We cannot swap in if there is no last committed entry, because we would
531 // show a blank page under an existing entry from the current tab. Even if
532 // there is a pending entry, it may not commit.
533 // TODO(creis): If there is a pending navigation and no last committed
534 // entry, we might be able to transfer the network request instead.
535 if (!new_web_contents->GetController().CanPruneAllButLastCommitted()) {
536 // Abort this prerender so it is not used later. http://crbug.com/292121
537 prerender_data->contents()->Destroy(FINAL_STATUS_NAVIGATION_UNCOMMITTED);
538 return NULL;
542 // Do not swap if the target WebContents is not the only WebContents in its
543 // current BrowsingInstance.
544 if (web_contents->GetSiteInstance()->GetRelatedActiveContentsCount() != 1u) {
545 DCHECK_GT(
546 web_contents->GetSiteInstance()->GetRelatedActiveContentsCount(), 1u);
547 prerender_data->contents()->Destroy(
548 FINAL_STATUS_NON_EMPTY_BROWSING_INSTANCE);
549 return NULL;
552 // Do not use the prerendered version if there is an opener object.
553 if (web_contents->HasOpener()) {
554 prerender_data->contents()->Destroy(FINAL_STATUS_WINDOW_OPENER);
555 return NULL;
558 // Do not swap in the prerender if the current WebContents is being captured.
559 if (web_contents->GetCapturerCount() > 0) {
560 prerender_data->contents()->Destroy(FINAL_STATUS_PAGE_BEING_CAPTURED);
561 return NULL;
564 // If we are just in the control group (which can be detected by noticing
565 // that prerendering hasn't even started yet), record that |web_contents| now
566 // would be showing a prerendered contents, but otherwise, don't do anything.
567 if (!prerender_data->contents()->prerendering_has_started()) {
568 target_tab_helper->WouldHavePrerenderedNextLoad(
569 prerender_data->contents()->origin());
570 prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED);
571 return NULL;
574 // Don't use prerendered pages if debugger is attached to the tab.
575 // See http://crbug.com/98541
576 if (content::DevToolsAgentHost::IsDebuggerAttached(web_contents)) {
577 DestroyAndMarkMatchCompleteAsUsed(prerender_data->contents(),
578 FINAL_STATUS_DEVTOOLS_ATTACHED);
579 return NULL;
582 // If the prerendered page is in the middle of a cross-site navigation,
583 // don't swap it in because there isn't a good way to merge histories.
584 if (prerender_data->contents()->IsCrossSiteNavigationPending()) {
585 DestroyAndMarkMatchCompleteAsUsed(
586 prerender_data->contents(),
587 FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING);
588 return NULL;
591 // For bookkeeping purposes, we need to mark this WebContents to
592 // reflect that it would have been prerendered.
593 if (GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP) {
594 target_tab_helper->WouldHavePrerenderedNextLoad(
595 prerender_data->contents()->origin());
596 prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED);
597 return NULL;
600 // At this point, we've determined that we will use the prerender.
601 content::RenderProcessHost* process_host =
602 prerender_data->contents()->GetRenderViewHost()->GetProcess();
603 prerender_process_hosts_.erase(process_host);
604 BrowserThread::PostTask(
605 BrowserThread::IO, FROM_HERE,
606 base::Bind(&PrerenderTracker::RemovePrerenderCookieStoreOnIOThread,
607 base::Unretained(prerender_tracker()), process_host->GetID(),
608 true));
609 if (!prerender_data->contents()->load_start_time().is_null()) {
610 histograms_->RecordTimeUntilUsed(
611 prerender_data->contents()->origin(),
612 GetCurrentTimeTicks() - prerender_data->contents()->load_start_time());
614 histograms_->RecordAbandonTimeUntilUsed(
615 prerender_data->contents()->origin(),
616 prerender_data->abandon_time().is_null() ?
617 base::TimeDelta() :
618 GetCurrentTimeTicks() - prerender_data->abandon_time());
620 histograms_->RecordPerSessionCount(prerender_data->contents()->origin(),
621 ++prerenders_per_session_count_);
622 histograms_->RecordUsedPrerender(prerender_data->contents()->origin());
624 if (prerender_data->pending_swap())
625 prerender_data->pending_swap()->set_swap_successful(true);
626 ScopedVector<PrerenderData>::iterator to_erase =
627 FindIteratorForPrerenderContents(prerender_data->contents());
628 DCHECK(active_prerenders_.end() != to_erase);
629 DCHECK_EQ(prerender_data, *to_erase);
630 scoped_ptr<PrerenderContents>
631 prerender_contents(prerender_data->ReleaseContents());
632 active_prerenders_.erase(to_erase);
634 // Mark prerender as used.
635 prerender_contents->PrepareForUse();
637 WebContents* new_web_contents =
638 prerender_contents->ReleasePrerenderContents();
639 WebContents* old_web_contents = web_contents;
640 DCHECK(new_web_contents);
641 DCHECK(old_web_contents);
643 // Merge the browsing history.
644 new_web_contents->GetController().CopyStateFromAndPrune(
645 &old_web_contents->GetController(),
646 should_replace_current_entry);
647 CoreTabHelper::FromWebContents(old_web_contents)->delegate()->
648 SwapTabContents(old_web_contents,
649 new_web_contents,
650 true,
651 prerender_contents->has_finished_loading());
652 prerender_contents->CommitHistory(new_web_contents);
654 // Update PPLT metrics:
655 // If the tab has finished loading, record a PPLT of 0.
656 // If the tab is still loading, reset its start time to the current time.
657 PrerenderTabHelper* prerender_tab_helper =
658 PrerenderTabHelper::FromWebContents(new_web_contents);
659 DCHECK(prerender_tab_helper != NULL);
660 prerender_tab_helper->PrerenderSwappedIn();
662 if (old_web_contents->NeedToFireBeforeUnload()) {
663 // Schedule the delete to occur after the tab has run its unload handlers.
664 // TODO(davidben): Honor the beforeunload event. http://crbug.com/304932
665 on_close_web_contents_deleters_.push_back(
666 new OnCloseWebContentsDeleter(this, old_web_contents));
667 old_web_contents->DispatchBeforeUnload(false);
668 } else {
669 // No unload handler to run, so delete asap.
670 ScheduleDeleteOldWebContents(old_web_contents, NULL);
673 // TODO(cbentzel): Should prerender_contents move to the pending delete
674 // list, instead of deleting directly here?
675 AddToHistory(prerender_contents.get());
676 RecordNavigation(url);
677 return new_web_contents;
680 void PrerenderManager::MoveEntryToPendingDelete(PrerenderContents* entry,
681 FinalStatus final_status) {
682 DCHECK(CalledOnValidThread());
683 DCHECK(entry);
685 ScopedVector<PrerenderData>::iterator it =
686 FindIteratorForPrerenderContents(entry);
687 DCHECK(it != active_prerenders_.end());
689 // If this PrerenderContents is being deleted due to a cancellation any time
690 // after the prerender has started then we need to create a dummy replacement
691 // for PPLT accounting purposes for the Match Complete group. This is the case
692 // if the cancellation is for any reason that would not occur in the control
693 // group case.
694 if (entry->prerendering_has_started() &&
695 entry->match_complete_status() ==
696 PrerenderContents::MATCH_COMPLETE_DEFAULT &&
697 NeedMatchCompleteDummyForFinalStatus(final_status) &&
698 ActuallyPrerendering()) {
699 // TODO(tburkard): I'd like to DCHECK that we are actually prerendering.
700 // However, what if new conditions are added and
701 // NeedMatchCompleteDummyForFinalStatus is not being updated. Not sure
702 // what's the best thing to do here. For now, I will just check whether
703 // we are actually prerendering.
704 (*it)->MakeIntoMatchCompleteReplacement();
705 } else {
706 to_delete_prerenders_.push_back(*it);
707 (*it)->ClearPendingSwap();
708 active_prerenders_.weak_erase(it);
711 // Destroy the old WebContents relatively promptly to reduce resource usage.
712 PostCleanupTask();
715 void PrerenderManager::RecordPageLoadTimeNotSwappedIn(
716 Origin origin,
717 base::TimeDelta page_load_time,
718 const GURL& url) {
719 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
720 histograms_->RecordPageLoadTimeNotSwappedIn(origin, page_load_time, url);
723 void PrerenderManager::RecordPerceivedPageLoadTime(
724 Origin origin,
725 NavigationType navigation_type,
726 base::TimeDelta perceived_page_load_time,
727 double fraction_plt_elapsed_at_swap_in,
728 const GURL& url) {
729 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
730 if (!IsEnabled())
731 return;
733 histograms_->RecordPerceivedPageLoadTime(
734 origin, perceived_page_load_time, navigation_type, url);
736 if (navigation_type == NAVIGATION_TYPE_PRERENDERED) {
737 histograms_->RecordPercentLoadDoneAtSwapin(
738 origin, fraction_plt_elapsed_at_swap_in);
740 if (local_predictor_) {
741 local_predictor_->OnPLTEventForURL(url, perceived_page_load_time);
745 void PrerenderManager::set_enabled(bool enabled) {
746 DCHECK(CalledOnValidThread());
747 enabled_ = enabled;
750 // static
751 PrerenderManager::PrerenderManagerMode PrerenderManager::GetMode() {
752 return mode_;
755 // static
756 void PrerenderManager::SetMode(PrerenderManagerMode mode) {
757 mode_ = mode;
760 // static
761 const char* PrerenderManager::GetModeString() {
762 switch (mode_) {
763 case PRERENDER_MODE_DISABLED:
764 return "_Disabled";
765 case PRERENDER_MODE_ENABLED:
766 case PRERENDER_MODE_EXPERIMENT_PRERENDER_GROUP:
767 return "_Enabled";
768 case PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP:
769 return "_Control";
770 case PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP:
771 return "_Multi";
772 case PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP:
773 return "_15MinTTL";
774 case PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP:
775 return "_NoUse";
776 case PRERENDER_MODE_MAX:
777 default:
778 NOTREACHED() << "Invalid PrerenderManager mode.";
779 break;
781 return "";
784 // static
785 bool PrerenderManager::IsPrerenderingPossible() {
786 return GetMode() != PRERENDER_MODE_DISABLED;
789 // static
790 bool PrerenderManager::ActuallyPrerendering() {
791 return IsPrerenderingPossible() && !IsControlGroup(kNoExperiment);
794 // static
795 bool PrerenderManager::IsControlGroup(uint8 experiment_id) {
796 return GetMode() == PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP ||
797 IsControlGroupExperiment(experiment_id);
800 // static
801 bool PrerenderManager::IsNoUseGroup() {
802 return GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP;
805 bool PrerenderManager::IsWebContentsPrerendering(
806 const WebContents* web_contents,
807 Origin* origin) const {
808 DCHECK(CalledOnValidThread());
809 if (PrerenderContents* prerender_contents =
810 GetPrerenderContents(web_contents)) {
811 if (origin)
812 *origin = prerender_contents->origin();
813 return true;
815 return false;
818 bool PrerenderManager::HasPrerenderedUrl(
819 GURL url,
820 content::WebContents* web_contents) const {
821 content::SessionStorageNamespace* session_storage_namespace = web_contents->
822 GetController().GetDefaultSessionStorageNamespace();
824 for (ScopedVector<PrerenderData>::const_iterator it =
825 active_prerenders_.begin();
826 it != active_prerenders_.end(); ++it) {
827 PrerenderContents* prerender_contents = (*it)->contents();
828 if (prerender_contents->Matches(url, session_storage_namespace)) {
829 return true;
832 return false;
835 PrerenderContents* PrerenderManager::GetPrerenderContents(
836 const content::WebContents* web_contents) const {
837 DCHECK(CalledOnValidThread());
838 for (ScopedVector<PrerenderData>::const_iterator it =
839 active_prerenders_.begin();
840 it != active_prerenders_.end(); ++it) {
841 WebContents* prerender_web_contents =
842 (*it)->contents()->prerender_contents();
843 if (prerender_web_contents == web_contents) {
844 return (*it)->contents();
848 // Also check the pending-deletion list. If the prerender is in pending
849 // delete, anyone with a handle on the WebContents needs to know.
850 for (ScopedVector<PrerenderData>::const_iterator it =
851 to_delete_prerenders_.begin();
852 it != to_delete_prerenders_.end(); ++it) {
853 WebContents* prerender_web_contents =
854 (*it)->contents()->prerender_contents();
855 if (prerender_web_contents == web_contents) {
856 return (*it)->contents();
859 return NULL;
862 PrerenderContents* PrerenderManager::GetPrerenderContentsForRoute(
863 int child_id,
864 int route_id) const {
865 content::WebContents* web_contents =
866 tab_util::GetWebContentsByID(child_id, route_id);
867 if (web_contents == NULL)
868 return NULL;
869 return GetPrerenderContents(web_contents);
872 const std::vector<WebContents*>
873 PrerenderManager::GetAllPrerenderingContents() const {
874 DCHECK(CalledOnValidThread());
875 std::vector<WebContents*> result;
877 for (ScopedVector<PrerenderData>::const_iterator it =
878 active_prerenders_.begin();
879 it != active_prerenders_.end(); ++it) {
880 if (WebContents* contents = (*it)->contents()->prerender_contents())
881 result.push_back(contents);
884 return result;
887 bool PrerenderManager::HasRecentlyBeenNavigatedTo(Origin origin,
888 const GURL& url) {
889 DCHECK(CalledOnValidThread());
891 CleanUpOldNavigations();
892 std::list<NavigationRecord>::const_reverse_iterator end = navigations_.rend();
893 for (std::list<NavigationRecord>::const_reverse_iterator it =
894 navigations_.rbegin();
895 it != end;
896 ++it) {
897 if (it->url == url) {
898 base::TimeDelta delta = GetCurrentTimeTicks() - it->time;
899 histograms_->RecordTimeSinceLastRecentVisit(origin, delta);
900 return true;
904 return false;
907 // static
908 bool PrerenderManager::IsValidHttpMethod(const std::string& method) {
909 // method has been canonicalized to upper case at this point so we can just
910 // compare them.
911 DCHECK_EQ(method, StringToUpperASCII(method));
912 for (size_t i = 0; i < arraysize(kValidHttpMethods); ++i) {
913 if (method.compare(kValidHttpMethods[i]) == 0)
914 return true;
917 return false;
920 // static
921 bool PrerenderManager::DoesURLHaveValidScheme(const GURL& url) {
922 return (url.SchemeIsHTTPOrHTTPS() ||
923 url.SchemeIs(extensions::kExtensionScheme) ||
924 url.SchemeIs("data"));
927 // static
928 bool PrerenderManager::DoesSubresourceURLHaveValidScheme(const GURL& url) {
929 return DoesURLHaveValidScheme(url) || url == GURL(url::kAboutBlankURL);
932 base::DictionaryValue* PrerenderManager::GetAsValue() const {
933 DCHECK(CalledOnValidThread());
934 base::DictionaryValue* dict_value = new base::DictionaryValue();
935 dict_value->Set("history", prerender_history_->GetEntriesAsValue());
936 dict_value->Set("active", GetActivePrerendersAsValue());
937 dict_value->SetBoolean("enabled", enabled_);
938 dict_value->SetBoolean("omnibox_enabled", IsOmniboxEnabled(profile_));
939 // If prerender is disabled via a flag this method is not even called.
940 std::string enabled_note;
941 if (IsControlGroup(kNoExperiment))
942 enabled_note += "(Control group: Not actually prerendering) ";
943 if (IsNoUseGroup())
944 enabled_note += "(No-use group: Not swapping in prerendered pages) ";
945 if (GetMode() == PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP)
946 enabled_note +=
947 "(15 min TTL group: Extended prerender eviction to 15 mins) ";
948 dict_value->SetString("enabled_note", enabled_note);
949 return dict_value;
952 void PrerenderManager::ClearData(int clear_flags) {
953 DCHECK_GE(clear_flags, 0);
954 DCHECK_LT(clear_flags, CLEAR_MAX);
955 if (clear_flags & CLEAR_PRERENDER_CONTENTS)
956 DestroyAllContents(FINAL_STATUS_CACHE_OR_HISTORY_CLEARED);
957 // This has to be second, since destroying prerenders can add to the history.
958 if (clear_flags & CLEAR_PRERENDER_HISTORY)
959 prerender_history_->Clear();
962 void PrerenderManager::RecordFinalStatusWithMatchCompleteStatus(
963 Origin origin,
964 uint8 experiment_id,
965 PrerenderContents::MatchCompleteStatus mc_status,
966 FinalStatus final_status) const {
967 histograms_->RecordFinalStatus(origin,
968 experiment_id,
969 mc_status,
970 final_status);
973 void PrerenderManager::AddCondition(const PrerenderCondition* condition) {
974 prerender_conditions_.push_back(condition);
977 void PrerenderManager::RecordNavigation(const GURL& url) {
978 DCHECK(CalledOnValidThread());
980 navigations_.push_back(NavigationRecord(url, GetCurrentTimeTicks()));
981 CleanUpOldNavigations();
984 // protected
985 struct PrerenderManager::PrerenderData::OrderByExpiryTime {
986 bool operator()(const PrerenderData* a, const PrerenderData* b) const {
987 return a->expiry_time() < b->expiry_time();
991 PrerenderManager::PrerenderData::PrerenderData(PrerenderManager* manager,
992 PrerenderContents* contents,
993 base::TimeTicks expiry_time)
994 : manager_(manager),
995 contents_(contents),
996 handle_count_(0),
997 expiry_time_(expiry_time) {
998 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
1001 PrerenderManager::PrerenderData::~PrerenderData() {
1004 void PrerenderManager::PrerenderData::MakeIntoMatchCompleteReplacement() {
1005 DCHECK(contents_);
1006 contents_->set_match_complete_status(
1007 PrerenderContents::MATCH_COMPLETE_REPLACED);
1008 PrerenderData* to_delete = new PrerenderData(manager_, contents_.release(),
1009 expiry_time_);
1010 contents_.reset(to_delete->contents_->CreateMatchCompleteReplacement());
1011 manager_->to_delete_prerenders_.push_back(to_delete);
1014 void PrerenderManager::PrerenderData::OnHandleCreated(PrerenderHandle* handle) {
1015 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
1016 ++handle_count_;
1017 contents_->AddObserver(handle);
1020 void PrerenderManager::PrerenderData::OnHandleNavigatedAway(
1021 PrerenderHandle* handle) {
1022 DCHECK_LT(0, handle_count_);
1023 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
1024 if (abandon_time_.is_null())
1025 abandon_time_ = base::TimeTicks::Now();
1026 // We intentionally don't decrement the handle count here, so that the
1027 // prerender won't be canceled until it times out.
1028 manager_->SourceNavigatedAway(this);
1031 void PrerenderManager::PrerenderData::OnHandleCanceled(
1032 PrerenderHandle* handle) {
1033 DCHECK_LT(0, handle_count_);
1034 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
1036 if (--handle_count_ == 0) {
1037 // This will eventually remove this object from active_prerenders_.
1038 contents_->Destroy(FINAL_STATUS_CANCELLED);
1042 void PrerenderManager::PrerenderData::ClearPendingSwap() {
1043 pending_swap_.reset(NULL);
1046 PrerenderContents* PrerenderManager::PrerenderData::ReleaseContents() {
1047 return contents_.release();
1050 PrerenderManager::PendingSwap::PendingSwap(
1051 PrerenderManager* manager,
1052 content::WebContents* target_contents,
1053 PrerenderData* prerender_data,
1054 const GURL& url,
1055 bool should_replace_current_entry)
1056 : content::WebContentsObserver(target_contents),
1057 manager_(manager),
1058 prerender_data_(prerender_data),
1059 url_(url),
1060 should_replace_current_entry_(should_replace_current_entry),
1061 start_time_(base::TimeTicks::Now()),
1062 seen_target_route_id_(false),
1063 swap_successful_(false),
1064 weak_factory_(this) {
1067 PrerenderManager::PendingSwap::~PendingSwap() {
1068 manager_->prerender_tracker()->RemovePrerenderPendingSwap(
1069 target_route_id_, swap_successful_);
1072 WebContents* PrerenderManager::PendingSwap::target_contents() const {
1073 return web_contents();
1076 void PrerenderManager::PendingSwap::BeginSwap() {
1077 if (g_hang_session_storage_merges_for_testing)
1078 return;
1080 SessionStorageNamespace* target_namespace =
1081 target_contents()->GetController().GetDefaultSessionStorageNamespace();
1082 SessionStorageNamespace* prerender_namespace =
1083 prerender_data_->contents()->GetSessionStorageNamespace();
1085 prerender_namespace->Merge(
1086 true, prerender_data_->contents()->child_id(),
1087 target_namespace,
1088 base::Bind(&PrerenderManager::PendingSwap::OnMergeCompleted,
1089 weak_factory_.GetWeakPtr()));
1091 merge_timeout_.Start(
1092 FROM_HERE,
1093 base::TimeDelta::FromMilliseconds(
1094 kSessionStorageNamespaceMergeTimeoutMs),
1095 this, &PrerenderManager::PendingSwap::OnMergeTimeout);
1098 void PrerenderManager::PendingSwap::AboutToNavigateRenderView(
1099 RenderViewHost* render_view_host) {
1100 if (seen_target_route_id_) {
1101 // A second navigation began browser-side.
1102 prerender_data_->ClearPendingSwap();
1103 return;
1106 seen_target_route_id_ = true;
1107 target_route_id_ = PrerenderTracker::ChildRouteIdPair(
1108 render_view_host->GetMainFrame()->GetProcess()->GetID(),
1109 render_view_host->GetMainFrame()->GetRoutingID());
1110 manager_->prerender_tracker()->AddPrerenderPendingSwap(
1111 target_route_id_, url_);
1114 void PrerenderManager::PendingSwap::ProvisionalChangeToMainFrameUrl(
1115 const GURL& url,
1116 content::RenderFrameHost* render_frame_host) {
1117 // We must only cancel the pending swap if the |url| navigated to is not
1118 // the URL being attempted to be swapped in. That's because in the normal
1119 // flow, a ProvisionalChangeToMainFrameUrl will happen for the URL attempted
1120 // to be swapped in immediately after the pending swap has issued its merge.
1121 if (url != url_)
1122 prerender_data_->ClearPendingSwap();
1125 void PrerenderManager::PendingSwap::DidCommitProvisionalLoadForFrame(
1126 content::RenderFrameHost* render_frame_host,
1127 const GURL& validated_url,
1128 content::PageTransition transition_type) {
1129 if (render_frame_host->GetParent())
1130 return;
1131 prerender_data_->ClearPendingSwap();
1134 void PrerenderManager::PendingSwap::DidFailProvisionalLoad(
1135 content::RenderFrameHost* render_frame_host,
1136 const GURL& validated_url,
1137 int error_code,
1138 const base::string16& error_description) {
1139 if (render_frame_host->GetParent())
1140 return;
1141 prerender_data_->ClearPendingSwap();
1144 void PrerenderManager::PendingSwap::WebContentsDestroyed() {
1145 prerender_data_->ClearPendingSwap();
1148 void PrerenderManager::PendingSwap::RecordEvent(PrerenderEvent event) const {
1149 manager_->RecordEvent(prerender_data_->contents(), event);
1152 void PrerenderManager::PendingSwap::OnMergeCompleted(
1153 SessionStorageNamespace::MergeResult result) {
1154 UMA_HISTOGRAM_TIMES("Prerender.SessionStorageNamespaceMergeTime",
1155 base::TimeTicks::Now() - start_time_);
1156 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_MERGE_DONE);
1158 // Log the exact merge result in a histogram.
1159 switch (result) {
1160 case SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_FOUND:
1161 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NAMESPACE_NOT_FOUND);
1162 break;
1163 case SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_ALIAS:
1164 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NAMESPACE_NOT_ALIAS);
1165 break;
1166 case SessionStorageNamespace::MERGE_RESULT_NOT_LOGGING:
1167 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NOT_LOGGING);
1168 break;
1169 case SessionStorageNamespace::MERGE_RESULT_NO_TRANSACTIONS:
1170 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NO_TRANSACTIONS);
1171 break;
1172 case SessionStorageNamespace::MERGE_RESULT_TOO_MANY_TRANSACTIONS:
1173 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_TOO_MANY_TRANSACTIONS);
1174 break;
1175 case SessionStorageNamespace::MERGE_RESULT_NOT_MERGEABLE:
1176 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NOT_MERGEABLE);
1177 break;
1178 case SessionStorageNamespace::MERGE_RESULT_MERGEABLE:
1179 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_MERGEABLE);
1180 break;
1181 default:
1182 NOTREACHED();
1185 if (result != SessionStorageNamespace::MERGE_RESULT_MERGEABLE &&
1186 result != SessionStorageNamespace::MERGE_RESULT_NO_TRANSACTIONS) {
1187 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_MERGE_FAILED);
1188 prerender_data_->ClearPendingSwap();
1189 return;
1192 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_SWAPPING_IN);
1194 // Note that SwapInternal will, on success, delete |prerender_data_| and
1195 // |this|. It will also delete |this| in some failure cases. Pass in a new
1196 // GURL object rather than a reference to |url_|. Also hold on to |manager_|
1197 // and |prerender_data_|.
1199 // TODO(davidben): Can we make this less fragile?
1200 PrerenderManager* manager = manager_;
1201 PrerenderData* prerender_data = prerender_data_;
1202 WebContents* new_web_contents = manager_->SwapInternal(
1203 GURL(url_), target_contents(), prerender_data_,
1204 should_replace_current_entry_);
1205 if (!new_web_contents) {
1206 manager->RecordEvent(prerender_data->contents(),
1207 PRERENDER_EVENT_MERGE_RESULT_SWAPIN_FAILED);
1208 // Depending on whether SwapInternal called Destroy() or simply failed to
1209 // swap, |this| may or may not be deleted. Either way, if the swap failed,
1210 // |prerender_data| is deleted asynchronously, so this call is a no-op if
1211 // |this| is already gone.
1212 prerender_data->ClearPendingSwap();
1216 void PrerenderManager::PendingSwap::OnMergeTimeout() {
1217 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_TIMED_OUT);
1218 prerender_data_->ClearPendingSwap();
1221 void PrerenderManager::SetPrerenderContentsFactory(
1222 PrerenderContents::Factory* prerender_contents_factory) {
1223 DCHECK(CalledOnValidThread());
1224 prerender_contents_factory_.reset(prerender_contents_factory);
1227 void PrerenderManager::SourceNavigatedAway(PrerenderData* prerender_data) {
1228 // The expiry time of our prerender data will likely change because of
1229 // this navigation. This requires a resort of active_prerenders_.
1230 ScopedVector<PrerenderData>::iterator it =
1231 std::find(active_prerenders_.begin(), active_prerenders_.end(),
1232 prerender_data);
1233 if (it == active_prerenders_.end())
1234 return;
1236 (*it)->set_expiry_time(
1237 std::min((*it)->expiry_time(),
1238 GetExpiryTimeForNavigatedAwayPrerender()));
1239 SortActivePrerenders();
1242 net::URLRequestContextGetter* PrerenderManager::GetURLRequestContext() {
1243 return content::BrowserContext::GetDefaultStoragePartition(profile_)->
1244 GetURLRequestContext();
1248 // private
1249 PrerenderHandle* PrerenderManager::AddPrerender(
1250 Origin origin,
1251 int process_id,
1252 const GURL& url_arg,
1253 const content::Referrer& referrer,
1254 const gfx::Size& size,
1255 SessionStorageNamespace* session_storage_namespace) {
1256 DCHECK(CalledOnValidThread());
1258 if (!IsEnabled())
1259 return NULL;
1261 if ((origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN ||
1262 origin == ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN) &&
1263 IsGoogleSearchResultURL(referrer.url)) {
1264 origin = ORIGIN_GWS_PRERENDER;
1267 GURL url = url_arg;
1268 GURL alias_url;
1269 uint8 experiment = GetQueryStringBasedExperiment(url_arg);
1270 if (IsControlGroup(experiment) &&
1271 MaybeGetQueryStringBasedAliasURL(url, &alias_url)) {
1272 url = alias_url;
1275 // From here on, we will record a FinalStatus so we need to register with the
1276 // histogram tracking.
1277 histograms_->RecordPrerender(origin, url_arg);
1279 if (PrerenderData* preexisting_prerender_data =
1280 FindPrerenderData(url, session_storage_namespace)) {
1281 RecordFinalStatusWithoutCreatingPrerenderContents(
1282 url, origin, experiment, FINAL_STATUS_DUPLICATE);
1283 return new PrerenderHandle(preexisting_prerender_data);
1286 // Do not prerender if there are too many render processes, and we would
1287 // have to use an existing one. We do not want prerendering to happen in
1288 // a shared process, so that we can always reliably lower the CPU
1289 // priority for prerendering.
1290 // In single-process mode, ShouldTryToUseExistingProcessHost() always returns
1291 // true, so that case needs to be explicitly checked for.
1292 // TODO(tburkard): Figure out how to cancel prerendering in the opposite
1293 // case, when a new tab is added to a process used for prerendering.
1294 // TODO(ppi): Check whether there are usually enough render processes
1295 // available on Android. If not, kill an existing renderers so that we can
1296 // create a new one.
1297 if (content::RenderProcessHost::ShouldTryToUseExistingProcessHost(
1298 profile_, url) &&
1299 !content::RenderProcessHost::run_renderer_in_process()) {
1300 RecordFinalStatusWithoutCreatingPrerenderContents(
1301 url, origin, experiment, FINAL_STATUS_TOO_MANY_PROCESSES);
1302 return NULL;
1305 // Check if enough time has passed since the last prerender.
1306 if (!DoesRateLimitAllowPrerender(origin)) {
1307 // Cancel the prerender. We could add it to the pending prerender list but
1308 // this doesn't make sense as the next prerender request will be triggered
1309 // by a navigation and is unlikely to be the same site.
1310 RecordFinalStatusWithoutCreatingPrerenderContents(
1311 url, origin, experiment, FINAL_STATUS_RATE_LIMIT_EXCEEDED);
1312 return NULL;
1315 if (IsPrerenderCookieStoreEnabled() && !cookie_store_loaded()) {
1316 // Only prerender if the cookie store for this profile has been loaded.
1317 // This is required by PrerenderCookieMonster.
1318 RecordFinalStatusWithoutCreatingPrerenderContents(
1319 url, origin, experiment, FINAL_STATUS_COOKIE_STORE_NOT_LOADED);
1320 return NULL;
1323 PrerenderContents* prerender_contents = CreatePrerenderContents(
1324 url, referrer, origin, experiment);
1325 DCHECK(prerender_contents);
1326 active_prerenders_.push_back(
1327 new PrerenderData(this, prerender_contents,
1328 GetExpiryTimeForNewPrerender(origin)));
1329 if (!prerender_contents->Init()) {
1330 DCHECK(active_prerenders_.end() ==
1331 FindIteratorForPrerenderContents(prerender_contents));
1332 return NULL;
1335 histograms_->RecordPrerenderStarted(origin);
1336 DCHECK(!prerender_contents->prerendering_has_started());
1338 PrerenderHandle* prerender_handle =
1339 new PrerenderHandle(active_prerenders_.back());
1340 SortActivePrerenders();
1342 last_prerender_start_time_ = GetCurrentTimeTicks();
1344 gfx::Size contents_size =
1345 size.IsEmpty() ? config_.default_tab_bounds.size() : size;
1347 net::URLRequestContextGetter* request_context =
1348 (IsPrerenderCookieStoreEnabled() ? GetURLRequestContext() : NULL);
1350 prerender_contents->StartPrerendering(process_id, contents_size,
1351 session_storage_namespace,
1352 request_context);
1354 DCHECK(IsControlGroup(experiment) ||
1355 prerender_contents->prerendering_has_started() ||
1356 (origin == ORIGIN_LOCAL_PREDICTOR &&
1357 IsLocalPredictorPrerenderAlwaysControlEnabled()));
1359 if (GetMode() == PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP)
1360 histograms_->RecordConcurrency(active_prerenders_.size());
1362 // Query the history to see if the URL being prerendered has ever been
1363 // visited before.
1364 HistoryService* history_service = HistoryServiceFactory::GetForProfile(
1365 profile_, Profile::EXPLICIT_ACCESS);
1366 if (history_service) {
1367 history_service->QueryURL(
1368 url,
1369 false,
1370 base::Bind(&PrerenderManager::OnHistoryServiceDidQueryURL,
1371 base::Unretained(this),
1372 origin,
1373 experiment),
1374 &query_url_tracker_);
1377 StartSchedulingPeriodicCleanups();
1378 return prerender_handle;
1381 void PrerenderManager::StartSchedulingPeriodicCleanups() {
1382 DCHECK(CalledOnValidThread());
1383 if (repeating_timer_.IsRunning())
1384 return;
1385 repeating_timer_.Start(FROM_HERE,
1386 base::TimeDelta::FromMilliseconds(kPeriodicCleanupIntervalMs),
1387 this,
1388 &PrerenderManager::PeriodicCleanup);
1391 void PrerenderManager::StopSchedulingPeriodicCleanups() {
1392 DCHECK(CalledOnValidThread());
1393 repeating_timer_.Stop();
1396 void PrerenderManager::PeriodicCleanup() {
1397 DCHECK(CalledOnValidThread());
1399 base::ElapsedTimer resource_timer;
1401 // Grab a copy of the current PrerenderContents pointers, so that we
1402 // will not interfere with potential deletions of the list.
1403 std::vector<PrerenderContents*>
1404 prerender_contents(active_prerenders_.size());
1405 std::transform(active_prerenders_.begin(), active_prerenders_.end(),
1406 prerender_contents.begin(),
1407 std::mem_fun(&PrerenderData::contents));
1409 // And now check for prerenders using too much memory.
1410 std::for_each(prerender_contents.begin(), prerender_contents.end(),
1411 std::mem_fun(
1412 &PrerenderContents::DestroyWhenUsingTooManyResources));
1414 // Measure how long the resource checks took. http://crbug.com/305419.
1415 UMA_HISTOGRAM_TIMES("Prerender.PeriodicCleanupResourceCheckTime",
1416 resource_timer.Elapsed());
1418 base::ElapsedTimer cleanup_timer;
1420 // Perform deferred cleanup work.
1421 DeleteOldWebContents();
1422 DeleteOldEntries();
1423 if (active_prerenders_.empty())
1424 StopSchedulingPeriodicCleanups();
1426 to_delete_prerenders_.clear();
1428 // Measure how long a the various cleanup tasks took. http://crbug.com/305419.
1429 UMA_HISTOGRAM_TIMES("Prerender.PeriodicCleanupDeleteContentsTime",
1430 cleanup_timer.Elapsed());
1433 void PrerenderManager::PostCleanupTask() {
1434 DCHECK(CalledOnValidThread());
1435 base::MessageLoop::current()->PostTask(
1436 FROM_HERE,
1437 base::Bind(&PrerenderManager::PeriodicCleanup, AsWeakPtr()));
1440 base::TimeTicks PrerenderManager::GetExpiryTimeForNewPrerender(
1441 Origin origin) const {
1442 base::TimeDelta ttl = config_.time_to_live;
1443 if (origin == ORIGIN_LOCAL_PREDICTOR)
1444 ttl = base::TimeDelta::FromSeconds(GetLocalPredictorTTLSeconds());
1445 return GetCurrentTimeTicks() + ttl;
1448 base::TimeTicks PrerenderManager::GetExpiryTimeForNavigatedAwayPrerender()
1449 const {
1450 return GetCurrentTimeTicks() + config_.abandon_time_to_live;
1453 void PrerenderManager::DeleteOldEntries() {
1454 DCHECK(CalledOnValidThread());
1455 while (!active_prerenders_.empty()) {
1456 PrerenderData* prerender_data = active_prerenders_.front();
1457 DCHECK(prerender_data);
1458 DCHECK(prerender_data->contents());
1460 if (prerender_data->expiry_time() > GetCurrentTimeTicks())
1461 return;
1462 prerender_data->contents()->Destroy(FINAL_STATUS_TIMED_OUT);
1466 base::Time PrerenderManager::GetCurrentTime() const {
1467 return base::Time::Now();
1470 base::TimeTicks PrerenderManager::GetCurrentTimeTicks() const {
1471 return base::TimeTicks::Now();
1474 PrerenderContents* PrerenderManager::CreatePrerenderContents(
1475 const GURL& url,
1476 const content::Referrer& referrer,
1477 Origin origin,
1478 uint8 experiment_id) {
1479 DCHECK(CalledOnValidThread());
1480 return prerender_contents_factory_->CreatePrerenderContents(
1481 this, profile_, url, referrer, origin, experiment_id);
1484 void PrerenderManager::SortActivePrerenders() {
1485 std::sort(active_prerenders_.begin(), active_prerenders_.end(),
1486 PrerenderData::OrderByExpiryTime());
1489 PrerenderManager::PrerenderData* PrerenderManager::FindPrerenderData(
1490 const GURL& url,
1491 const SessionStorageNamespace* session_storage_namespace) {
1492 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1493 it != active_prerenders_.end(); ++it) {
1494 if ((*it)->contents()->Matches(url, session_storage_namespace))
1495 return *it;
1497 return NULL;
1500 PrerenderManager::PrerenderData*
1501 PrerenderManager::FindPrerenderDataForTargetContents(
1502 WebContents* target_contents) {
1503 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1504 it != active_prerenders_.end(); ++it) {
1505 if ((*it)->pending_swap() &&
1506 (*it)->pending_swap()->target_contents() == target_contents)
1507 return *it;
1509 return NULL;
1512 ScopedVector<PrerenderManager::PrerenderData>::iterator
1513 PrerenderManager::FindIteratorForPrerenderContents(
1514 PrerenderContents* prerender_contents) {
1515 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1516 it != active_prerenders_.end(); ++it) {
1517 if (prerender_contents == (*it)->contents())
1518 return it;
1520 return active_prerenders_.end();
1523 bool PrerenderManager::DoesRateLimitAllowPrerender(Origin origin) const {
1524 DCHECK(CalledOnValidThread());
1525 base::TimeDelta elapsed_time =
1526 GetCurrentTimeTicks() - last_prerender_start_time_;
1527 histograms_->RecordTimeBetweenPrerenderRequests(origin, elapsed_time);
1528 if (!config_.rate_limit_enabled)
1529 return true;
1530 // The LocalPredictor may issue multiple prerenders simultaneously (if so
1531 // configured), so no throttling.
1532 if (origin == ORIGIN_LOCAL_PREDICTOR)
1533 return true;
1534 return elapsed_time >=
1535 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs);
1538 void PrerenderManager::DeleteOldWebContents() {
1539 while (!old_web_contents_list_.empty()) {
1540 WebContents* web_contents = old_web_contents_list_.front();
1541 old_web_contents_list_.pop_front();
1542 // TODO(dominich): should we use Instant Unload Handler here?
1543 delete web_contents;
1547 void PrerenderManager::CleanUpOldNavigations() {
1548 DCHECK(CalledOnValidThread());
1550 // Cutoff. Navigations before this cutoff can be discarded.
1551 base::TimeTicks cutoff = GetCurrentTimeTicks() -
1552 base::TimeDelta::FromMilliseconds(kNavigationRecordWindowMs);
1553 while (!navigations_.empty()) {
1554 if (navigations_.front().time > cutoff)
1555 break;
1556 navigations_.pop_front();
1560 void PrerenderManager::ScheduleDeleteOldWebContents(
1561 WebContents* tab,
1562 OnCloseWebContentsDeleter* deleter) {
1563 old_web_contents_list_.push_back(tab);
1564 PostCleanupTask();
1566 if (deleter) {
1567 ScopedVector<OnCloseWebContentsDeleter>::iterator i = std::find(
1568 on_close_web_contents_deleters_.begin(),
1569 on_close_web_contents_deleters_.end(),
1570 deleter);
1571 DCHECK(i != on_close_web_contents_deleters_.end());
1572 on_close_web_contents_deleters_.erase(i);
1576 void PrerenderManager::AddToHistory(PrerenderContents* contents) {
1577 PrerenderHistory::Entry entry(contents->prerender_url(),
1578 contents->final_status(),
1579 contents->origin(),
1580 base::Time::Now());
1581 prerender_history_->AddEntry(entry);
1584 base::Value* PrerenderManager::GetActivePrerendersAsValue() const {
1585 base::ListValue* list_value = new base::ListValue();
1586 for (ScopedVector<PrerenderData>::const_iterator it =
1587 active_prerenders_.begin();
1588 it != active_prerenders_.end(); ++it) {
1589 if (base::Value* prerender_value = (*it)->contents()->GetAsValue())
1590 list_value->Append(prerender_value);
1592 return list_value;
1595 void PrerenderManager::DestroyAllContents(FinalStatus final_status) {
1596 DeleteOldWebContents();
1597 while (!active_prerenders_.empty()) {
1598 PrerenderContents* contents = active_prerenders_.front()->contents();
1599 contents->Destroy(final_status);
1601 to_delete_prerenders_.clear();
1604 void PrerenderManager::DestroyAndMarkMatchCompleteAsUsed(
1605 PrerenderContents* prerender_contents,
1606 FinalStatus final_status) {
1607 prerender_contents->set_match_complete_status(
1608 PrerenderContents::MATCH_COMPLETE_REPLACED);
1609 histograms_->RecordFinalStatus(prerender_contents->origin(),
1610 prerender_contents->experiment_id(),
1611 PrerenderContents::MATCH_COMPLETE_REPLACEMENT,
1612 FINAL_STATUS_WOULD_HAVE_BEEN_USED);
1613 prerender_contents->Destroy(final_status);
1616 void PrerenderManager::RecordFinalStatusWithoutCreatingPrerenderContents(
1617 const GURL& url, Origin origin, uint8 experiment_id,
1618 FinalStatus final_status) const {
1619 PrerenderHistory::Entry entry(url, final_status, origin, base::Time::Now());
1620 prerender_history_->AddEntry(entry);
1621 RecordFinalStatusWithMatchCompleteStatus(
1622 origin, experiment_id,
1623 PrerenderContents::MATCH_COMPLETE_DEFAULT,
1624 final_status);
1627 void PrerenderManager::Observe(int type,
1628 const content::NotificationSource& source,
1629 const content::NotificationDetails& details) {
1630 switch (type) {
1631 case chrome::NOTIFICATION_COOKIE_CHANGED: {
1632 Profile* profile = content::Source<Profile>(source).ptr();
1633 if (!profile || !profile_->IsSameProfile(profile) ||
1634 profile->IsOffTheRecord()) {
1635 return;
1637 CookieChanged(content::Details<ChromeCookieDetails>(details).ptr());
1638 break;
1640 case chrome::NOTIFICATION_PROFILE_DESTROYED:
1641 DestroyAllContents(FINAL_STATUS_PROFILE_DESTROYED);
1642 on_close_web_contents_deleters_.clear();
1643 break;
1644 default:
1645 NOTREACHED() << "Unexpected notification sent.";
1646 break;
1650 void PrerenderManager::OnCreatingAudioStream(int render_process_id,
1651 int render_frame_id) {
1652 content::RenderFrameHost* render_frame_host =
1653 content::RenderFrameHost::FromID(render_process_id, render_frame_id);
1654 WebContents* tab = WebContents::FromRenderFrameHost(render_frame_host);
1655 if (!tab)
1656 return;
1658 PrerenderContents* prerender_contents = GetPrerenderContents(tab);
1659 if (!prerender_contents)
1660 return;
1662 prerender_contents->Destroy(prerender::FINAL_STATUS_CREATING_AUDIO_STREAM);
1665 void PrerenderManager::RecordLikelyLoginOnURL(const GURL& url) {
1666 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1667 if (!url.SchemeIsHTTPOrHTTPS())
1668 return;
1669 if (logged_in_predictor_table_.get()) {
1670 BrowserThread::PostTask(
1671 BrowserThread::DB,
1672 FROM_HERE,
1673 base::Bind(&LoggedInPredictorTable::AddDomainFromURL,
1674 logged_in_predictor_table_,
1675 url));
1677 std::string key = LoggedInPredictorTable::GetKey(url);
1678 if (!logged_in_state_.get())
1679 return;
1680 if (logged_in_state_->count(key))
1681 return;
1682 (*logged_in_state_)[key] = base::Time::Now().ToInternalValue();
1685 void PrerenderManager::CheckIfLikelyLoggedInOnURL(
1686 const GURL& url,
1687 bool* lookup_result,
1688 bool* database_was_present,
1689 const base::Closure& result_cb) {
1690 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1691 if (!logged_in_predictor_table_.get()) {
1692 *database_was_present = false;
1693 *lookup_result = false;
1694 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, result_cb);
1695 return;
1697 BrowserThread::PostTaskAndReply(
1698 BrowserThread::DB, FROM_HERE,
1699 base::Bind(&LoggedInPredictorTable::HasUserLoggedIn,
1700 logged_in_predictor_table_,
1701 url,
1702 lookup_result,
1703 database_was_present),
1704 result_cb);
1708 void PrerenderManager::CookieChanged(ChromeCookieDetails* details) {
1709 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1711 if (!logged_in_predictor_table_.get())
1712 return;
1714 // We only care when a cookie has been removed.
1715 if (!details->removed)
1716 return;
1718 std::string domain_key =
1719 LoggedInPredictorTable::GetKeyFromDomain(details->cookie->Domain());
1721 // If we have no record of this domain as a potentially logged in domain,
1722 // nothing to do here.
1723 if (logged_in_state_.get() && logged_in_state_->count(domain_key) < 1)
1724 return;
1726 net::URLRequestContextGetter* rq_context = profile_->GetRequestContext();
1727 if (!rq_context)
1728 return;
1730 BrowserThread::PostTask(
1731 BrowserThread::IO, FROM_HERE,
1732 base::Bind(&CheckIfCookiesExistForDomainOnIOThread,
1733 base::Unretained(rq_context),
1734 domain_key,
1735 base::Bind(
1736 &PrerenderManager::CookieChangedAnyCookiesLeftLookupResult,
1737 AsWeakPtr(),
1738 domain_key)
1742 void PrerenderManager::CookieChangedAnyCookiesLeftLookupResult(
1743 const std::string& domain_key,
1744 bool cookies_exist) {
1745 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1747 if (cookies_exist)
1748 return;
1750 if (logged_in_predictor_table_.get()) {
1751 BrowserThread::PostTask(BrowserThread::DB,
1752 FROM_HERE,
1753 base::Bind(&LoggedInPredictorTable::DeleteDomain,
1754 logged_in_predictor_table_,
1755 domain_key));
1758 if (logged_in_state_.get())
1759 logged_in_state_->erase(domain_key);
1762 void PrerenderManager::LoggedInPredictorDataReceived(
1763 scoped_ptr<LoggedInStateMap> new_map) {
1764 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1765 logged_in_state_.swap(new_map);
1768 void PrerenderManager::RecordEvent(PrerenderContents* contents,
1769 PrerenderEvent event) const {
1770 if (!contents)
1771 histograms_->RecordEvent(ORIGIN_NONE, kNoExperiment, event);
1772 else
1773 histograms_->RecordEvent(contents->origin(), contents->experiment_id(),
1774 event);
1777 // static
1778 void PrerenderManager::RecordCookieEvent(int process_id,
1779 int frame_id,
1780 const GURL& url,
1781 const GURL& frame_url,
1782 bool is_for_blocking_resource,
1783 PrerenderContents::CookieEvent event,
1784 const net::CookieList* cookie_list) {
1785 RenderFrameHost* rfh = RenderFrameHost::FromID(process_id, frame_id);
1786 WebContents* web_contents = WebContents::FromRenderFrameHost(rfh);
1787 if (!web_contents)
1788 return;
1790 bool is_main_frame = (rfh == web_contents->GetMainFrame());
1792 bool is_third_party_cookie =
1793 (!frame_url.is_empty() &&
1794 !net::registry_controlled_domains::SameDomainOrHost(
1795 url, frame_url,
1796 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES));
1798 PrerenderContents* prerender_contents =
1799 PrerenderContents::FromWebContents(web_contents);
1801 if (!prerender_contents)
1802 return;
1804 base::Time earliest_create_date;
1805 if (event == PrerenderContents::COOKIE_EVENT_SEND) {
1806 if (!cookie_list || cookie_list->empty())
1807 return;
1808 for (size_t i = 0; i < cookie_list->size(); i++) {
1809 if (earliest_create_date.is_null() ||
1810 (*cookie_list)[i].CreationDate() < earliest_create_date) {
1811 earliest_create_date = (*cookie_list)[i].CreationDate();
1816 prerender_contents->RecordCookieEvent(event,
1817 is_main_frame && url == frame_url,
1818 is_third_party_cookie,
1819 is_for_blocking_resource,
1820 earliest_create_date);
1823 void PrerenderManager::RecordCookieStatus(Origin origin,
1824 uint8 experiment_id,
1825 int cookie_status) const {
1826 histograms_->RecordCookieStatus(origin, experiment_id, cookie_status);
1829 void PrerenderManager::RecordCookieSendType(Origin origin,
1830 uint8 experiment_id,
1831 int cookie_send_type) const {
1832 histograms_->RecordCookieSendType(origin, experiment_id, cookie_send_type);
1835 void PrerenderManager::OnHistoryServiceDidQueryURL(
1836 Origin origin,
1837 uint8 experiment_id,
1838 bool success,
1839 const history::URLRow& url_row,
1840 const history::VisitVector& /*visits*/) {
1841 histograms_->RecordPrerenderPageVisitedStatus(origin, experiment_id, success);
1844 // static
1845 void PrerenderManager::HangSessionStorageMergesForTesting() {
1846 g_hang_session_storage_merges_for_testing = true;
1849 void PrerenderManager::RecordNetworkBytes(Origin origin,
1850 bool used,
1851 int64 prerender_bytes) {
1852 if (!ActuallyPrerendering())
1853 return;
1854 int64 recent_profile_bytes =
1855 profile_network_bytes_ - last_recorded_profile_network_bytes_;
1856 last_recorded_profile_network_bytes_ = profile_network_bytes_;
1857 DCHECK_GE(recent_profile_bytes, 0);
1858 histograms_->RecordNetworkBytes(
1859 origin, used, prerender_bytes, recent_profile_bytes);
1862 bool PrerenderManager::IsEnabled() const {
1863 DCHECK(CalledOnValidThread());
1865 // TODO(bnc): remove conditional as per crbug.com/334602.
1866 if (profile_ && profile_->GetPrefs() &&
1867 profile_->GetPrefs()->GetInteger(prefs::kNetworkPredictionOptions) !=
1868 chrome_browser_net::NETWORK_PREDICTION_UNSET) {
1869 return chrome_browser_net::CanPrefetchAndPrerenderUI(profile_->GetPrefs());
1871 // TODO(bnc): remove rest of method as per crbug.com/334602.
1872 if (!enabled_)
1873 return false;
1874 for (std::list<const PrerenderCondition*>::const_iterator it =
1875 prerender_conditions_.begin();
1876 it != prerender_conditions_.end();
1877 ++it) {
1878 const PrerenderCondition* condition = *it;
1879 if (!condition->CanPrerender())
1880 return false;
1882 return true;
1885 void PrerenderManager::AddProfileNetworkBytesIfEnabled(int64 bytes) {
1886 DCHECK_GE(bytes, 0);
1887 if (IsEnabled() && ActuallyPrerendering())
1888 profile_network_bytes_ += bytes;
1891 void PrerenderManager::OnCookieStoreLoaded() {
1892 cookie_store_loaded_ = true;
1893 if (!on_cookie_store_loaded_cb_for_testing_.is_null())
1894 on_cookie_store_loaded_cb_for_testing_.Run();
1897 void PrerenderManager::AddPrerenderProcessHost(
1898 content::RenderProcessHost* process_host) {
1899 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1900 DCHECK(prerender_process_hosts_.find(process_host) ==
1901 prerender_process_hosts_.end());
1902 prerender_process_hosts_.insert(process_host);
1903 process_host->AddObserver(this);
1906 bool PrerenderManager::MayReuseProcessHost(
1907 content::RenderProcessHost* process_host) {
1908 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1909 // If prerender cookie stores are disabled, there is no need to require
1910 // isolated prerender processes.
1911 if (!IsPrerenderCookieStoreEnabled())
1912 return true;
1913 return (prerender_process_hosts_.find(process_host) ==
1914 prerender_process_hosts_.end());
1917 void PrerenderManager::RenderProcessHostDestroyed(
1918 content::RenderProcessHost* host) {
1919 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1920 prerender_process_hosts_.erase(host);
1921 BrowserThread::PostTask(
1922 BrowserThread::IO, FROM_HERE,
1923 base::Bind(&PrerenderTracker::RemovePrerenderCookieStoreOnIOThread,
1924 base::Unretained(prerender_tracker()), host->GetID(), false));
1927 } // namespace prerender