Roll src/third_party/WebKit 8b42d1d:744641d (svn 186770:186771)
[chromium-blink-merge.git] / chrome / browser / prerender / prerender_manager.cc
blobc7f84edd8f6ec580632dd9fccc1568982944d57a
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/prerender/prerender_manager.h"
7 #include <algorithm>
8 #include <functional>
9 #include <string>
10 #include <vector>
12 #include "base/bind.h"
13 #include "base/bind_helpers.h"
14 #include "base/logging.h"
15 #include "base/memory/weak_ptr.h"
16 #include "base/metrics/histogram.h"
17 #include "base/prefs/pref_service.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "base/time/time.h"
20 #include "base/timer/elapsed_timer.h"
21 #include "base/values.h"
22 #include "chrome/browser/browser_process.h"
23 #include "chrome/browser/chrome_notification_types.h"
24 #include "chrome/browser/history/history_service_factory.h"
25 #include "chrome/browser/net/chrome_cookie_notification_details.h"
26 #include "chrome/browser/net/prediction_options.h"
27 #include "chrome/browser/predictors/predictor_database.h"
28 #include "chrome/browser/predictors/predictor_database_factory.h"
29 #include "chrome/browser/prerender/prerender_contents.h"
30 #include "chrome/browser/prerender/prerender_field_trial.h"
31 #include "chrome/browser/prerender/prerender_final_status.h"
32 #include "chrome/browser/prerender/prerender_handle.h"
33 #include "chrome/browser/prerender/prerender_histograms.h"
34 #include "chrome/browser/prerender/prerender_history.h"
35 #include "chrome/browser/prerender/prerender_local_predictor.h"
36 #include "chrome/browser/prerender/prerender_manager_factory.h"
37 #include "chrome/browser/prerender/prerender_tab_helper.h"
38 #include "chrome/browser/prerender/prerender_tracker.h"
39 #include "chrome/browser/prerender/prerender_util.h"
40 #include "chrome/browser/profiles/profile.h"
41 #include "chrome/browser/search/search.h"
42 #include "chrome/browser/tab_contents/tab_util.h"
43 #include "chrome/browser/ui/browser_navigator.h"
44 #include "chrome/browser/ui/tab_contents/core_tab_helper.h"
45 #include "chrome/browser/ui/tab_contents/core_tab_helper_delegate.h"
46 #include "chrome/common/chrome_switches.h"
47 #include "chrome/common/prerender_messages.h"
48 #include "chrome/common/prerender_types.h"
49 #include "content/public/browser/browser_thread.h"
50 #include "content/public/browser/devtools_agent_host.h"
51 #include "content/public/browser/navigation_controller.h"
52 #include "content/public/browser/notification_service.h"
53 #include "content/public/browser/notification_source.h"
54 #include "content/public/browser/render_frame_host.h"
55 #include "content/public/browser/render_process_host.h"
56 #include "content/public/browser/render_view_host.h"
57 #include "content/public/browser/resource_request_details.h"
58 #include "content/public/browser/session_storage_namespace.h"
59 #include "content/public/browser/site_instance.h"
60 #include "content/public/browser/storage_partition.h"
61 #include "content/public/browser/web_contents.h"
62 #include "content/public/browser/web_contents_delegate.h"
63 #include "content/public/common/url_constants.h"
64 #include "extensions/common/constants.h"
65 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
66 #include "net/url_request/url_request_context.h"
67 #include "net/url_request/url_request_context_getter.h"
69 using content::BrowserThread;
70 using content::RenderViewHost;
71 using content::RenderFrameHost;
72 using content::SessionStorageNamespace;
73 using content::WebContents;
74 using predictors::LoggedInPredictorTable;
76 namespace prerender {
78 namespace {
80 // Time interval at which periodic cleanups are performed.
81 const int kPeriodicCleanupIntervalMs = 1000;
83 // Valid HTTP methods for prerendering.
84 const char* const kValidHttpMethods[] = {
85 "GET",
86 "HEAD",
87 "OPTIONS",
88 "POST",
89 "TRACE",
92 // Length of prerender history, for display in chrome://net-internals
93 const int kHistoryLength = 100;
95 // Timeout, in ms, for a session storage namespace merge.
96 const int kSessionStorageNamespaceMergeTimeoutMs = 500;
98 // If true, all session storage merges hang indefinitely.
99 bool g_hang_session_storage_merges_for_testing = false;
101 // Indicates whether a Prerender has been cancelled such that we need
102 // a dummy replacement for the purpose of recording the correct PPLT for
103 // the Match Complete case.
104 // Traditionally, "Match" means that a prerendered page was actually visited &
105 // the prerender was used. Our goal is to have "Match" cases line up in the
106 // control group & the experiment group, so that we can make meaningful
107 // comparisons of improvements. However, in the control group, since we don't
108 // actually perform prerenders, many of the cancellation reasons cannot be
109 // detected. Therefore, in the Prerender group, when we cancel for one of these
110 // reasons, we keep track of a dummy Prerender representing what we would
111 // have in the control group. If that dummy prerender in the prerender group
112 // would then be swapped in (but isn't actually b/c it's a dummy), we record
113 // this as a MatchComplete. This allows us to compare MatchComplete's
114 // across Prerender & Control group which ideally should be lining up.
115 // This ensures that there is no bias in terms of the page load times
116 // of the pages forming the difference between the two sets.
118 bool NeedMatchCompleteDummyForFinalStatus(FinalStatus final_status) {
119 return final_status != FINAL_STATUS_USED &&
120 final_status != FINAL_STATUS_TIMED_OUT &&
121 final_status != FINAL_STATUS_MANAGER_SHUTDOWN &&
122 final_status != FINAL_STATUS_PROFILE_DESTROYED &&
123 final_status != FINAL_STATUS_APP_TERMINATING &&
124 final_status != FINAL_STATUS_WINDOW_OPENER &&
125 final_status != FINAL_STATUS_CACHE_OR_HISTORY_CLEARED &&
126 final_status != FINAL_STATUS_CANCELLED &&
127 final_status != FINAL_STATUS_DEVTOOLS_ATTACHED &&
128 final_status != FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING &&
129 final_status != FINAL_STATUS_PAGE_BEING_CAPTURED &&
130 final_status != FINAL_STATUS_NAVIGATION_UNCOMMITTED &&
131 final_status != FINAL_STATUS_NON_EMPTY_BROWSING_INSTANCE;
134 void CheckIfCookiesExistForDomainResultOnUIThread(
135 const net::CookieMonster::HasCookiesForETLDP1Callback& callback,
136 bool cookies_exist) {
137 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
138 callback.Run(cookies_exist);
141 void CheckIfCookiesExistForDomainResultOnIOThread(
142 const net::CookieMonster::HasCookiesForETLDP1Callback& callback,
143 bool cookies_exist) {
144 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
145 BrowserThread::PostTask(
146 BrowserThread::UI,
147 FROM_HERE,
148 base::Bind(&CheckIfCookiesExistForDomainResultOnUIThread,
149 callback,
150 cookies_exist));
153 void CheckIfCookiesExistForDomainOnIOThread(
154 net::URLRequestContextGetter* rq_context,
155 const std::string& domain_key,
156 const net::CookieMonster::HasCookiesForETLDP1Callback& callback) {
157 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
158 net::CookieStore* cookie_store =
159 rq_context->GetURLRequestContext()->cookie_store();
160 cookie_store->GetCookieMonster()->HasCookiesForETLDP1Async(
161 domain_key,
162 base::Bind(&CheckIfCookiesExistForDomainResultOnIOThread, callback));
165 } // namespace
167 class PrerenderManager::OnCloseWebContentsDeleter
168 : public content::WebContentsDelegate,
169 public base::SupportsWeakPtr<
170 PrerenderManager::OnCloseWebContentsDeleter> {
171 public:
172 OnCloseWebContentsDeleter(PrerenderManager* manager,
173 WebContents* tab)
174 : manager_(manager),
175 tab_(tab),
176 suppressed_dialog_(false) {
177 tab_->SetDelegate(this);
178 base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
179 base::Bind(&OnCloseWebContentsDeleter::ScheduleWebContentsForDeletion,
180 AsWeakPtr(), true),
181 base::TimeDelta::FromSeconds(kDeleteWithExtremePrejudiceSeconds));
184 void CloseContents(WebContents* source) override {
185 DCHECK_EQ(tab_, source);
186 ScheduleWebContentsForDeletion(false);
189 void SwappedOut(WebContents* source) override {
190 DCHECK_EQ(tab_, source);
191 ScheduleWebContentsForDeletion(false);
194 bool ShouldSuppressDialogs(WebContents* source) override {
195 // Use this as a proxy for getting statistics on how often we fail to honor
196 // the beforeunload event.
197 DCHECK_EQ(tab_, source);
198 suppressed_dialog_ = true;
199 return true;
202 private:
203 static const int kDeleteWithExtremePrejudiceSeconds = 3;
205 void ScheduleWebContentsForDeletion(bool timeout) {
206 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterTimeout", timeout);
207 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterSuppressedDialog",
208 suppressed_dialog_);
209 tab_->SetDelegate(NULL);
210 manager_->ScheduleDeleteOldWebContents(tab_.release(), this);
211 // |this| is deleted at this point.
214 PrerenderManager* manager_;
215 scoped_ptr<WebContents> tab_;
216 bool suppressed_dialog_;
218 DISALLOW_COPY_AND_ASSIGN(OnCloseWebContentsDeleter);
221 // static
222 int PrerenderManager::prerenders_per_session_count_ = 0;
224 // static
225 PrerenderManager::PrerenderManagerMode PrerenderManager::mode_ =
226 PRERENDER_MODE_ENABLED;
228 struct PrerenderManager::NavigationRecord {
229 NavigationRecord(const GURL& url, base::TimeTicks time)
230 : url(url),
231 time(time) {
234 GURL url;
235 base::TimeTicks time;
238 PrerenderManager::PrerenderManager(Profile* profile,
239 PrerenderTracker* prerender_tracker)
240 : profile_(profile),
241 prerender_tracker_(prerender_tracker),
242 prerender_contents_factory_(PrerenderContents::CreateFactory()),
243 last_prerender_start_time_(GetCurrentTimeTicks() -
244 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs)),
245 prerender_history_(new PrerenderHistory(kHistoryLength)),
246 histograms_(new PrerenderHistograms()),
247 profile_network_bytes_(0),
248 last_recorded_profile_network_bytes_(0),
249 cookie_store_loaded_(false) {
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 notification_registrar_.Add(
294 this, chrome::NOTIFICATION_PROFILE_DESTROYED,
295 content::Source<Profile>(profile_));
297 MediaCaptureDevicesDispatcher::GetInstance()->AddObserver(this);
300 PrerenderManager::~PrerenderManager() {
301 MediaCaptureDevicesDispatcher::GetInstance()->RemoveObserver(this);
303 // The earlier call to KeyedService::Shutdown() should have
304 // emptied these vectors already.
305 DCHECK(active_prerenders_.empty());
306 DCHECK(to_delete_prerenders_.empty());
308 for (PrerenderProcessSet::const_iterator it =
309 prerender_process_hosts_.begin();
310 it != prerender_process_hosts_.end();
311 ++it) {
312 (*it)->RemoveObserver(this);
316 void PrerenderManager::Shutdown() {
317 DestroyAllContents(FINAL_STATUS_MANAGER_SHUTDOWN);
318 on_close_web_contents_deleters_.clear();
319 // Must happen before |profile_| is set to NULL as
320 // |local_predictor_| accesses it.
321 if (local_predictor_)
322 local_predictor_->Shutdown();
323 profile_ = NULL;
325 DCHECK(active_prerenders_.empty());
328 PrerenderHandle* PrerenderManager::AddPrerenderFromLinkRelPrerender(
329 int process_id,
330 int route_id,
331 const GURL& url,
332 const uint32 rel_types,
333 const content::Referrer& referrer,
334 const gfx::Size& size) {
335 Origin origin = rel_types & PrerenderRelTypePrerender ?
336 ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN :
337 ORIGIN_LINK_REL_NEXT;
338 SessionStorageNamespace* session_storage_namespace = NULL;
339 // Unit tests pass in a process_id == -1.
340 if (process_id != -1) {
341 RenderViewHost* source_render_view_host =
342 RenderViewHost::FromID(process_id, route_id);
343 if (!source_render_view_host)
344 return NULL;
345 WebContents* source_web_contents =
346 WebContents::FromRenderViewHost(source_render_view_host);
347 if (!source_web_contents)
348 return NULL;
349 if (origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN &&
350 source_web_contents->GetURL().host() == url.host()) {
351 origin = ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN;
353 // TODO(ajwong): This does not correctly handle storage for isolated apps.
354 session_storage_namespace =
355 source_web_contents->GetController()
356 .GetDefaultSessionStorageNamespace();
359 return AddPrerender(origin, process_id, url, referrer, size,
360 session_storage_namespace);
363 PrerenderHandle* PrerenderManager::AddPrerenderFromOmnibox(
364 const GURL& url,
365 SessionStorageNamespace* session_storage_namespace,
366 const gfx::Size& size) {
367 if (!IsOmniboxEnabled(profile_))
368 return NULL;
369 return AddPrerender(ORIGIN_OMNIBOX, -1, url, content::Referrer(), size,
370 session_storage_namespace);
373 PrerenderHandle* PrerenderManager::AddPrerenderFromLocalPredictor(
374 const GURL& url,
375 SessionStorageNamespace* session_storage_namespace,
376 const gfx::Size& size) {
377 return AddPrerender(ORIGIN_LOCAL_PREDICTOR, -1, url, content::Referrer(),
378 size, session_storage_namespace);
381 PrerenderHandle* PrerenderManager::AddPrerenderFromExternalRequest(
382 const GURL& url,
383 const content::Referrer& referrer,
384 SessionStorageNamespace* session_storage_namespace,
385 const gfx::Size& size) {
386 return AddPrerender(ORIGIN_EXTERNAL_REQUEST, -1, url, referrer, size,
387 session_storage_namespace);
390 PrerenderHandle* PrerenderManager::AddPrerenderForInstant(
391 const GURL& url,
392 content::SessionStorageNamespace* session_storage_namespace,
393 const gfx::Size& size) {
394 DCHECK(chrome::ShouldPrefetchSearchResults());
395 return AddPrerender(ORIGIN_INSTANT, -1, url, content::Referrer(), size,
396 session_storage_namespace);
399 void PrerenderManager::CancelAllPrerenders() {
400 DCHECK(CalledOnValidThread());
401 while (!active_prerenders_.empty()) {
402 PrerenderContents* prerender_contents =
403 active_prerenders_.front()->contents();
404 prerender_contents->Destroy(FINAL_STATUS_CANCELLED);
408 bool PrerenderManager::MaybeUsePrerenderedPage(const GURL& url,
409 chrome::NavigateParams* params) {
410 DCHECK(CalledOnValidThread());
412 content::WebContents* web_contents = params->target_contents;
413 DCHECK(!IsWebContentsPrerendering(web_contents, NULL));
415 // Don't prerender if the navigation involves some special parameters.
416 if (params->uses_post || !params->extra_headers.empty())
417 return false;
419 DeleteOldEntries();
420 to_delete_prerenders_.clear();
422 // First, try to find prerender data with the correct session storage
423 // namespace.
424 // TODO(ajwong): This doesn't handle isolated apps correctly.
425 PrerenderData* prerender_data = FindPrerenderData(
426 url,
427 web_contents->GetController().GetDefaultSessionStorageNamespace());
429 // If this failed, we may still find a prerender for the same URL, but a
430 // different session storage namespace. If we do, we might have to perform
431 // a merge.
432 if (!prerender_data) {
433 prerender_data = FindPrerenderData(url, NULL);
434 } else {
435 RecordEvent(prerender_data->contents(),
436 PRERENDER_EVENT_SWAPIN_CANDIDATE_NAMESPACE_MATCHES);
439 if (!prerender_data)
440 return false;
441 RecordEvent(prerender_data->contents(), PRERENDER_EVENT_SWAPIN_CANDIDATE);
442 DCHECK(prerender_data->contents());
444 // If there is currently a merge pending for this prerender data, don't swap.
445 if (prerender_data->pending_swap())
446 return false;
448 // Abort any existing pending swap on the target contents.
449 PrerenderData* pending_swap =
450 FindPrerenderDataForTargetContents(web_contents);
451 if (pending_swap) {
452 pending_swap->ClearPendingSwap();
453 DCHECK(FindPrerenderDataForTargetContents(web_contents) == NULL);
456 RecordEvent(prerender_data->contents(),
457 PRERENDER_EVENT_SWAPIN_NO_MERGE_PENDING);
458 SessionStorageNamespace* target_namespace =
459 web_contents->GetController().GetDefaultSessionStorageNamespace();
460 SessionStorageNamespace* prerender_namespace =
461 prerender_data->contents()->GetSessionStorageNamespace();
462 // Only when actually prerendering is session storage namespace merging an
463 // issue. For the control group, it will be assumed that the merge succeeded.
464 if (prerender_namespace && prerender_namespace != target_namespace &&
465 !prerender_namespace->IsAliasOf(target_namespace)) {
466 if (!ShouldMergeSessionStorageNamespaces()) {
467 RecordEvent(prerender_data->contents(),
468 PRERENDER_EVENT_SWAPIN_MERGING_DISABLED);
469 return false;
471 RecordEvent(prerender_data->contents(),
472 PRERENDER_EVENT_SWAPIN_ISSUING_MERGE);
473 prerender_data->set_pending_swap(new PendingSwap(
474 this, web_contents, prerender_data, url,
475 params->should_replace_current_entry));
476 prerender_data->pending_swap()->BeginSwap();
477 // Although this returns false, creating a PendingSwap registers with
478 // PrerenderTracker to throttle MAIN_FRAME navigations while the swap is
479 // pending.
480 return false;
483 // No need to merge; swap synchronously.
484 WebContents* new_web_contents = SwapInternal(
485 url, web_contents, prerender_data,
486 params->should_replace_current_entry);
487 if (!new_web_contents)
488 return false;
490 // Record the new target_contents for the callers.
491 params->target_contents = new_web_contents;
492 return true;
495 WebContents* PrerenderManager::SwapInternal(
496 const GURL& url,
497 WebContents* web_contents,
498 PrerenderData* prerender_data,
499 bool should_replace_current_entry) {
500 DCHECK(CalledOnValidThread());
501 DCHECK(!IsWebContentsPrerendering(web_contents, NULL));
503 // Only swap if the target WebContents has a CoreTabHelper delegate to swap
504 // out of it. For a normal WebContents, this is if it is in a TabStripModel.
505 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(web_contents);
506 if (!core_tab_helper || !core_tab_helper->delegate()) {
507 RecordEvent(prerender_data->contents(), PRERENDER_EVENT_SWAPIN_NO_DELEGATE);
508 return NULL;
511 PrerenderTabHelper* target_tab_helper =
512 PrerenderTabHelper::FromWebContents(web_contents);
513 if (!target_tab_helper) {
514 NOTREACHED();
515 return NULL;
518 if (IsNoSwapInExperiment(prerender_data->contents()->experiment_id()))
519 return NULL;
521 if (WebContents* new_web_contents =
522 prerender_data->contents()->prerender_contents()) {
523 if (web_contents == new_web_contents)
524 return NULL; // Do not swap in to ourself.
526 // We cannot swap in if there is no last committed entry, because we would
527 // show a blank page under an existing entry from the current tab. Even if
528 // there is a pending entry, it may not commit.
529 // TODO(creis): If there is a pending navigation and no last committed
530 // entry, we might be able to transfer the network request instead.
531 if (!new_web_contents->GetController().CanPruneAllButLastCommitted()) {
532 // Abort this prerender so it is not used later. http://crbug.com/292121
533 prerender_data->contents()->Destroy(FINAL_STATUS_NAVIGATION_UNCOMMITTED);
534 return NULL;
538 // Do not swap if the target WebContents is not the only WebContents in its
539 // current BrowsingInstance.
540 if (web_contents->GetSiteInstance()->GetRelatedActiveContentsCount() != 1u) {
541 DCHECK_GT(
542 web_contents->GetSiteInstance()->GetRelatedActiveContentsCount(), 1u);
543 prerender_data->contents()->Destroy(
544 FINAL_STATUS_NON_EMPTY_BROWSING_INSTANCE);
545 return NULL;
548 // Do not use the prerendered version if there is an opener object.
549 if (web_contents->HasOpener()) {
550 prerender_data->contents()->Destroy(FINAL_STATUS_WINDOW_OPENER);
551 return NULL;
554 // Do not swap in the prerender if the current WebContents is being captured.
555 if (web_contents->GetCapturerCount() > 0) {
556 prerender_data->contents()->Destroy(FINAL_STATUS_PAGE_BEING_CAPTURED);
557 return NULL;
560 // If we are just in the control group (which can be detected by noticing
561 // that prerendering hasn't even started yet), record that |web_contents| now
562 // would be showing a prerendered contents, but otherwise, don't do anything.
563 if (!prerender_data->contents()->prerendering_has_started()) {
564 target_tab_helper->WouldHavePrerenderedNextLoad(
565 prerender_data->contents()->origin());
566 prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED);
567 return NULL;
570 // Don't use prerendered pages if debugger is attached to the tab.
571 // See http://crbug.com/98541
572 if (content::DevToolsAgentHost::IsDebuggerAttached(web_contents)) {
573 DestroyAndMarkMatchCompleteAsUsed(prerender_data->contents(),
574 FINAL_STATUS_DEVTOOLS_ATTACHED);
575 return NULL;
578 // If the prerendered page is in the middle of a cross-site navigation,
579 // don't swap it in because there isn't a good way to merge histories.
580 if (prerender_data->contents()->IsCrossSiteNavigationPending()) {
581 DestroyAndMarkMatchCompleteAsUsed(
582 prerender_data->contents(),
583 FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING);
584 return NULL;
587 // For bookkeeping purposes, we need to mark this WebContents to
588 // reflect that it would have been prerendered.
589 if (GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP) {
590 target_tab_helper->WouldHavePrerenderedNextLoad(
591 prerender_data->contents()->origin());
592 prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED);
593 return NULL;
596 // At this point, we've determined that we will use the prerender.
597 content::RenderProcessHost* process_host =
598 prerender_data->contents()->GetRenderViewHost()->GetProcess();
599 prerender_process_hosts_.erase(process_host);
600 BrowserThread::PostTask(
601 BrowserThread::IO, FROM_HERE,
602 base::Bind(&PrerenderTracker::RemovePrerenderCookieStoreOnIOThread,
603 base::Unretained(prerender_tracker()), process_host->GetID(),
604 true));
605 if (!prerender_data->contents()->load_start_time().is_null()) {
606 histograms_->RecordTimeUntilUsed(
607 prerender_data->contents()->origin(),
608 GetCurrentTimeTicks() - prerender_data->contents()->load_start_time());
610 histograms_->RecordAbandonTimeUntilUsed(
611 prerender_data->contents()->origin(),
612 prerender_data->abandon_time().is_null() ?
613 base::TimeDelta() :
614 GetCurrentTimeTicks() - prerender_data->abandon_time());
616 histograms_->RecordPerSessionCount(prerender_data->contents()->origin(),
617 ++prerenders_per_session_count_);
618 histograms_->RecordUsedPrerender(prerender_data->contents()->origin());
620 if (prerender_data->pending_swap())
621 prerender_data->pending_swap()->set_swap_successful(true);
622 ScopedVector<PrerenderData>::iterator to_erase =
623 FindIteratorForPrerenderContents(prerender_data->contents());
624 DCHECK(active_prerenders_.end() != to_erase);
625 DCHECK_EQ(prerender_data, *to_erase);
626 scoped_ptr<PrerenderContents>
627 prerender_contents(prerender_data->ReleaseContents());
628 active_prerenders_.erase(to_erase);
630 // Mark prerender as used.
631 prerender_contents->PrepareForUse();
633 WebContents* new_web_contents =
634 prerender_contents->ReleasePrerenderContents();
635 WebContents* old_web_contents = web_contents;
636 DCHECK(new_web_contents);
637 DCHECK(old_web_contents);
639 // Merge the browsing history.
640 new_web_contents->GetController().CopyStateFromAndPrune(
641 &old_web_contents->GetController(),
642 should_replace_current_entry);
643 CoreTabHelper::FromWebContents(old_web_contents)->delegate()->
644 SwapTabContents(old_web_contents,
645 new_web_contents,
646 true,
647 prerender_contents->has_finished_loading());
648 prerender_contents->CommitHistory(new_web_contents);
650 // Update PPLT metrics:
651 // If the tab has finished loading, record a PPLT of 0.
652 // If the tab is still loading, reset its start time to the current time.
653 PrerenderTabHelper* prerender_tab_helper =
654 PrerenderTabHelper::FromWebContents(new_web_contents);
655 DCHECK(prerender_tab_helper != NULL);
656 prerender_tab_helper->PrerenderSwappedIn();
658 if (old_web_contents->NeedToFireBeforeUnload()) {
659 // Schedule the delete to occur after the tab has run its unload handlers.
660 // TODO(davidben): Honor the beforeunload event. http://crbug.com/304932
661 on_close_web_contents_deleters_.push_back(
662 new OnCloseWebContentsDeleter(this, old_web_contents));
663 old_web_contents->DispatchBeforeUnload(false);
664 } else {
665 // No unload handler to run, so delete asap.
666 ScheduleDeleteOldWebContents(old_web_contents, NULL);
669 // TODO(cbentzel): Should prerender_contents move to the pending delete
670 // list, instead of deleting directly here?
671 AddToHistory(prerender_contents.get());
672 RecordNavigation(url);
673 return new_web_contents;
676 void PrerenderManager::MoveEntryToPendingDelete(PrerenderContents* entry,
677 FinalStatus final_status) {
678 DCHECK(CalledOnValidThread());
679 DCHECK(entry);
681 ScopedVector<PrerenderData>::iterator it =
682 FindIteratorForPrerenderContents(entry);
683 DCHECK(it != active_prerenders_.end());
685 // If this PrerenderContents is being deleted due to a cancellation any time
686 // after the prerender has started then we need to create a dummy replacement
687 // for PPLT accounting purposes for the Match Complete group. This is the case
688 // if the cancellation is for any reason that would not occur in the control
689 // group case.
690 if (entry->prerendering_has_started() &&
691 entry->match_complete_status() ==
692 PrerenderContents::MATCH_COMPLETE_DEFAULT &&
693 NeedMatchCompleteDummyForFinalStatus(final_status) &&
694 ActuallyPrerendering() &&
695 GetMode() == PRERENDER_MODE_EXPERIMENT_MATCH_COMPLETE_GROUP) {
696 // TODO(tburkard): I'd like to DCHECK that we are actually prerendering.
697 // However, what if new conditions are added and
698 // NeedMatchCompleteDummyForFinalStatus is not being updated. Not sure
699 // what's the best thing to do here. For now, I will just check whether
700 // we are actually prerendering.
701 (*it)->MakeIntoMatchCompleteReplacement();
702 } else {
703 to_delete_prerenders_.push_back(*it);
704 (*it)->ClearPendingSwap();
705 active_prerenders_.weak_erase(it);
708 // Destroy the old WebContents relatively promptly to reduce resource usage.
709 PostCleanupTask();
712 void PrerenderManager::RecordPageLoadTimeNotSwappedIn(
713 Origin origin,
714 base::TimeDelta page_load_time,
715 const GURL& url) {
716 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
717 histograms_->RecordPageLoadTimeNotSwappedIn(origin, page_load_time, url);
720 void PrerenderManager::RecordPerceivedPageLoadTime(
721 Origin origin,
722 NavigationType navigation_type,
723 base::TimeDelta perceived_page_load_time,
724 double fraction_plt_elapsed_at_swap_in,
725 const GURL& url) {
726 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
727 if (!IsEnabled())
728 return;
730 histograms_->RecordPerceivedPageLoadTime(
731 origin, perceived_page_load_time, navigation_type, url);
733 if (navigation_type == NAVIGATION_TYPE_PRERENDERED) {
734 histograms_->RecordPercentLoadDoneAtSwapin(
735 origin, fraction_plt_elapsed_at_swap_in);
737 if (local_predictor_) {
738 local_predictor_->OnPLTEventForURL(url, perceived_page_load_time);
742 // static
743 PrerenderManager::PrerenderManagerMode PrerenderManager::GetMode() {
744 return mode_;
747 // static
748 void PrerenderManager::SetMode(PrerenderManagerMode mode) {
749 mode_ = mode;
752 // static
753 const char* PrerenderManager::GetModeString() {
754 switch (mode_) {
755 case PRERENDER_MODE_DISABLED:
756 return "_Disabled";
757 case PRERENDER_MODE_ENABLED:
758 case PRERENDER_MODE_EXPERIMENT_PRERENDER_GROUP:
759 return "_Enabled";
760 case PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP:
761 return "_Control";
762 case PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP:
763 return "_Multi";
764 case PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP:
765 return "_15MinTTL";
766 case PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP:
767 return "_NoUse";
768 case PRERENDER_MODE_EXPERIMENT_MATCH_COMPLETE_GROUP:
769 return "_MatchComplete";
770 case PRERENDER_MODE_MAX:
771 default:
772 NOTREACHED() << "Invalid PrerenderManager mode.";
773 break;
775 return "";
778 // static
779 bool PrerenderManager::IsPrerenderingPossible() {
780 return GetMode() != PRERENDER_MODE_DISABLED;
783 // static
784 bool PrerenderManager::ActuallyPrerendering() {
785 return IsPrerenderingPossible() && !IsControlGroup(kNoExperiment);
788 // static
789 bool PrerenderManager::IsControlGroup(uint8 experiment_id) {
790 return GetMode() == PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP ||
791 IsControlGroupExperiment(experiment_id);
794 // static
795 bool PrerenderManager::IsNoUseGroup() {
796 return GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP;
799 bool PrerenderManager::IsWebContentsPrerendering(
800 const WebContents* web_contents,
801 Origin* origin) const {
802 DCHECK(CalledOnValidThread());
803 if (PrerenderContents* prerender_contents =
804 GetPrerenderContents(web_contents)) {
805 if (origin)
806 *origin = prerender_contents->origin();
807 return true;
809 return false;
812 bool PrerenderManager::HasPrerenderedUrl(
813 GURL url,
814 content::WebContents* web_contents) const {
815 content::SessionStorageNamespace* session_storage_namespace = web_contents->
816 GetController().GetDefaultSessionStorageNamespace();
818 for (ScopedVector<PrerenderData>::const_iterator it =
819 active_prerenders_.begin();
820 it != active_prerenders_.end(); ++it) {
821 PrerenderContents* prerender_contents = (*it)->contents();
822 if (prerender_contents->Matches(url, session_storage_namespace)) {
823 return true;
826 return false;
829 PrerenderContents* PrerenderManager::GetPrerenderContents(
830 const content::WebContents* web_contents) const {
831 DCHECK(CalledOnValidThread());
832 for (ScopedVector<PrerenderData>::const_iterator it =
833 active_prerenders_.begin();
834 it != active_prerenders_.end(); ++it) {
835 WebContents* prerender_web_contents =
836 (*it)->contents()->prerender_contents();
837 if (prerender_web_contents == web_contents) {
838 return (*it)->contents();
842 // Also check the pending-deletion list. If the prerender is in pending
843 // delete, anyone with a handle on the WebContents needs to know.
844 for (ScopedVector<PrerenderData>::const_iterator it =
845 to_delete_prerenders_.begin();
846 it != to_delete_prerenders_.end(); ++it) {
847 WebContents* prerender_web_contents =
848 (*it)->contents()->prerender_contents();
849 if (prerender_web_contents == web_contents) {
850 return (*it)->contents();
853 return NULL;
856 PrerenderContents* PrerenderManager::GetPrerenderContentsForRoute(
857 int child_id,
858 int route_id) const {
859 content::WebContents* web_contents =
860 tab_util::GetWebContentsByID(child_id, route_id);
861 if (web_contents == NULL)
862 return NULL;
863 return GetPrerenderContents(web_contents);
866 const std::vector<WebContents*>
867 PrerenderManager::GetAllPrerenderingContents() const {
868 DCHECK(CalledOnValidThread());
869 std::vector<WebContents*> result;
871 for (ScopedVector<PrerenderData>::const_iterator it =
872 active_prerenders_.begin();
873 it != active_prerenders_.end(); ++it) {
874 if (WebContents* contents = (*it)->contents()->prerender_contents())
875 result.push_back(contents);
878 return result;
881 bool PrerenderManager::HasRecentlyBeenNavigatedTo(Origin origin,
882 const GURL& url) {
883 DCHECK(CalledOnValidThread());
885 CleanUpOldNavigations();
886 std::list<NavigationRecord>::const_reverse_iterator end = navigations_.rend();
887 for (std::list<NavigationRecord>::const_reverse_iterator it =
888 navigations_.rbegin();
889 it != end;
890 ++it) {
891 if (it->url == url) {
892 base::TimeDelta delta = GetCurrentTimeTicks() - it->time;
893 histograms_->RecordTimeSinceLastRecentVisit(origin, delta);
894 return true;
898 return false;
901 // static
902 bool PrerenderManager::IsValidHttpMethod(const std::string& method) {
903 // method has been canonicalized to upper case at this point so we can just
904 // compare them.
905 DCHECK_EQ(method, StringToUpperASCII(method));
906 for (size_t i = 0; i < arraysize(kValidHttpMethods); ++i) {
907 if (method.compare(kValidHttpMethods[i]) == 0)
908 return true;
911 return false;
914 // static
915 bool PrerenderManager::DoesURLHaveValidScheme(const GURL& url) {
916 return (url.SchemeIsHTTPOrHTTPS() ||
917 url.SchemeIs(extensions::kExtensionScheme) ||
918 url.SchemeIs("data"));
921 // static
922 bool PrerenderManager::DoesSubresourceURLHaveValidScheme(const GURL& url) {
923 return DoesURLHaveValidScheme(url) || url == GURL(url::kAboutBlankURL);
926 base::DictionaryValue* PrerenderManager::GetAsValue() const {
927 DCHECK(CalledOnValidThread());
928 base::DictionaryValue* dict_value = new base::DictionaryValue();
929 dict_value->Set("history", prerender_history_->GetEntriesAsValue());
930 dict_value->Set("active", GetActivePrerendersAsValue());
931 dict_value->SetBoolean("enabled", IsEnabled());
932 dict_value->SetBoolean("omnibox_enabled", IsOmniboxEnabled(profile_));
933 // If prerender is disabled via a flag this method is not even called.
934 std::string enabled_note;
935 if (IsControlGroup(kNoExperiment))
936 enabled_note += "(Control group: Not actually prerendering) ";
937 if (IsNoUseGroup())
938 enabled_note += "(No-use group: Not swapping in prerendered pages) ";
939 if (GetMode() == PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP)
940 enabled_note +=
941 "(15 min TTL group: Extended prerender eviction to 15 mins) ";
942 dict_value->SetString("enabled_note", enabled_note);
943 return dict_value;
946 void PrerenderManager::ClearData(int clear_flags) {
947 DCHECK_GE(clear_flags, 0);
948 DCHECK_LT(clear_flags, CLEAR_MAX);
949 if (clear_flags & CLEAR_PRERENDER_CONTENTS)
950 DestroyAllContents(FINAL_STATUS_CACHE_OR_HISTORY_CLEARED);
951 // This has to be second, since destroying prerenders can add to the history.
952 if (clear_flags & CLEAR_PRERENDER_HISTORY)
953 prerender_history_->Clear();
956 void PrerenderManager::RecordFinalStatusWithMatchCompleteStatus(
957 Origin origin,
958 uint8 experiment_id,
959 PrerenderContents::MatchCompleteStatus mc_status,
960 FinalStatus final_status) const {
961 histograms_->RecordFinalStatus(origin,
962 experiment_id,
963 mc_status,
964 final_status);
967 void PrerenderManager::RecordNavigation(const GURL& url) {
968 DCHECK(CalledOnValidThread());
970 navigations_.push_back(NavigationRecord(url, GetCurrentTimeTicks()));
971 CleanUpOldNavigations();
974 // protected
975 struct PrerenderManager::PrerenderData::OrderByExpiryTime {
976 bool operator()(const PrerenderData* a, const PrerenderData* b) const {
977 return a->expiry_time() < b->expiry_time();
981 PrerenderManager::PrerenderData::PrerenderData(PrerenderManager* manager,
982 PrerenderContents* contents,
983 base::TimeTicks expiry_time)
984 : manager_(manager),
985 contents_(contents),
986 handle_count_(0),
987 expiry_time_(expiry_time) {
988 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
991 PrerenderManager::PrerenderData::~PrerenderData() {
994 void PrerenderManager::PrerenderData::MakeIntoMatchCompleteReplacement() {
995 DCHECK(contents_);
996 contents_->set_match_complete_status(
997 PrerenderContents::MATCH_COMPLETE_REPLACED);
998 PrerenderData* to_delete = new PrerenderData(manager_, contents_.release(),
999 expiry_time_);
1000 contents_.reset(to_delete->contents_->CreateMatchCompleteReplacement());
1001 manager_->to_delete_prerenders_.push_back(to_delete);
1004 void PrerenderManager::PrerenderData::OnHandleCreated(PrerenderHandle* handle) {
1005 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
1006 ++handle_count_;
1007 contents_->AddObserver(handle);
1010 void PrerenderManager::PrerenderData::OnHandleNavigatedAway(
1011 PrerenderHandle* handle) {
1012 DCHECK_LT(0, handle_count_);
1013 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
1014 if (abandon_time_.is_null())
1015 abandon_time_ = base::TimeTicks::Now();
1016 // We intentionally don't decrement the handle count here, so that the
1017 // prerender won't be canceled until it times out.
1018 manager_->SourceNavigatedAway(this);
1021 void PrerenderManager::PrerenderData::OnHandleCanceled(
1022 PrerenderHandle* handle) {
1023 DCHECK_LT(0, handle_count_);
1024 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
1026 if (--handle_count_ == 0) {
1027 // This will eventually remove this object from active_prerenders_.
1028 contents_->Destroy(FINAL_STATUS_CANCELLED);
1032 void PrerenderManager::PrerenderData::ClearPendingSwap() {
1033 pending_swap_.reset(NULL);
1036 PrerenderContents* PrerenderManager::PrerenderData::ReleaseContents() {
1037 return contents_.release();
1040 PrerenderManager::PendingSwap::PendingSwap(
1041 PrerenderManager* manager,
1042 content::WebContents* target_contents,
1043 PrerenderData* prerender_data,
1044 const GURL& url,
1045 bool should_replace_current_entry)
1046 : content::WebContentsObserver(target_contents),
1047 manager_(manager),
1048 prerender_data_(prerender_data),
1049 url_(url),
1050 should_replace_current_entry_(should_replace_current_entry),
1051 start_time_(base::TimeTicks::Now()),
1052 seen_target_route_id_(false),
1053 swap_successful_(false),
1054 weak_factory_(this) {
1057 PrerenderManager::PendingSwap::~PendingSwap() {
1058 manager_->prerender_tracker()->RemovePrerenderPendingSwap(
1059 target_route_id_, swap_successful_);
1062 void PrerenderManager::PendingSwap::BeginSwap() {
1063 if (g_hang_session_storage_merges_for_testing)
1064 return;
1066 SessionStorageNamespace* target_namespace =
1067 web_contents()->GetController().GetDefaultSessionStorageNamespace();
1068 SessionStorageNamespace* prerender_namespace =
1069 prerender_data_->contents()->GetSessionStorageNamespace();
1071 prerender_namespace->Merge(
1072 true, prerender_data_->contents()->child_id(),
1073 target_namespace,
1074 base::Bind(&PrerenderManager::PendingSwap::OnMergeCompleted,
1075 weak_factory_.GetWeakPtr()));
1077 merge_timeout_.Start(
1078 FROM_HERE,
1079 base::TimeDelta::FromMilliseconds(
1080 kSessionStorageNamespaceMergeTimeoutMs),
1081 this, &PrerenderManager::PendingSwap::OnMergeTimeout);
1084 void PrerenderManager::PendingSwap::AboutToNavigateRenderFrame(
1085 RenderFrameHost* render_frame_host) {
1086 // TODO(davidben): Update prerendering for --site-per-process.
1087 if (render_frame_host->GetParent())
1088 return;
1090 if (seen_target_route_id_) {
1091 // A second navigation began browser-side.
1092 prerender_data_->ClearPendingSwap();
1093 return;
1096 seen_target_route_id_ = true;
1097 target_route_id_ = PrerenderTracker::ChildRouteIdPair(
1098 render_frame_host->GetProcess()->GetID(),
1099 render_frame_host->GetRoutingID());
1100 manager_->prerender_tracker()->AddPrerenderPendingSwap(
1101 target_route_id_, url_);
1104 void PrerenderManager::PendingSwap::DidStartProvisionalLoadForFrame(
1105 content::RenderFrameHost* render_frame_host,
1106 const GURL& validated_url,
1107 bool is_error_page,
1108 bool is_iframe_srcdoc) {
1109 if (render_frame_host->GetParent())
1110 return;
1112 // We must only cancel the pending swap if the url navigated to is not
1113 // the URL being attempted to be swapped in. That's because in the normal
1114 // flow, a ProvisionalChangeToMainFrameUrl will happen for the URL attempted
1115 // to be swapped in immediately after the pending swap has issued its merge.
1116 if (validated_url != url_)
1117 prerender_data_->ClearPendingSwap();
1120 void PrerenderManager::PendingSwap::DidCommitProvisionalLoadForFrame(
1121 content::RenderFrameHost* render_frame_host,
1122 const GURL& validated_url,
1123 ui::PageTransition transition_type) {
1124 if (render_frame_host->GetParent())
1125 return;
1126 prerender_data_->ClearPendingSwap();
1129 void PrerenderManager::PendingSwap::DidFailProvisionalLoad(
1130 content::RenderFrameHost* render_frame_host,
1131 const GURL& validated_url,
1132 int error_code,
1133 const base::string16& error_description) {
1134 if (render_frame_host->GetParent())
1135 return;
1136 prerender_data_->ClearPendingSwap();
1139 void PrerenderManager::PendingSwap::WebContentsDestroyed() {
1140 prerender_data_->ClearPendingSwap();
1143 void PrerenderManager::PendingSwap::RecordEvent(PrerenderEvent event) const {
1144 manager_->RecordEvent(prerender_data_->contents(), event);
1147 void PrerenderManager::PendingSwap::OnMergeCompleted(
1148 SessionStorageNamespace::MergeResult result) {
1149 UMA_HISTOGRAM_TIMES("Prerender.SessionStorageNamespaceMergeTime",
1150 base::TimeTicks::Now() - start_time_);
1151 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_MERGE_DONE);
1153 // Log the exact merge result in a histogram.
1154 switch (result) {
1155 case SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_FOUND:
1156 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NAMESPACE_NOT_FOUND);
1157 break;
1158 case SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_ALIAS:
1159 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NAMESPACE_NOT_ALIAS);
1160 break;
1161 case SessionStorageNamespace::MERGE_RESULT_NOT_LOGGING:
1162 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NOT_LOGGING);
1163 break;
1164 case SessionStorageNamespace::MERGE_RESULT_NO_TRANSACTIONS:
1165 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NO_TRANSACTIONS);
1166 break;
1167 case SessionStorageNamespace::MERGE_RESULT_TOO_MANY_TRANSACTIONS:
1168 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_TOO_MANY_TRANSACTIONS);
1169 break;
1170 case SessionStorageNamespace::MERGE_RESULT_NOT_MERGEABLE:
1171 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NOT_MERGEABLE);
1172 break;
1173 case SessionStorageNamespace::MERGE_RESULT_MERGEABLE:
1174 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_MERGEABLE);
1175 break;
1176 default:
1177 NOTREACHED();
1180 if (result != SessionStorageNamespace::MERGE_RESULT_MERGEABLE &&
1181 result != SessionStorageNamespace::MERGE_RESULT_NO_TRANSACTIONS) {
1182 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_MERGE_FAILED);
1183 prerender_data_->ClearPendingSwap();
1184 return;
1187 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_SWAPPING_IN);
1189 // Note that SwapInternal will, on success, delete |prerender_data_| and
1190 // |this|. It will also delete |this| in some failure cases. Pass in a new
1191 // GURL object rather than a reference to |url_|. Also hold on to |manager_|
1192 // and |prerender_data_|.
1194 // TODO(davidben): Can we make this less fragile?
1195 PrerenderManager* manager = manager_;
1196 PrerenderData* prerender_data = prerender_data_;
1197 WebContents* new_web_contents =
1198 manager_->SwapInternal(GURL(url_),
1199 web_contents(),
1200 prerender_data_,
1201 should_replace_current_entry_);
1202 if (!new_web_contents) {
1203 manager->RecordEvent(prerender_data->contents(),
1204 PRERENDER_EVENT_MERGE_RESULT_SWAPIN_FAILED);
1205 // Depending on whether SwapInternal called Destroy() or simply failed to
1206 // swap, |this| may or may not be deleted. Either way, if the swap failed,
1207 // |prerender_data| is deleted asynchronously, so this call is a no-op if
1208 // |this| is already gone.
1209 prerender_data->ClearPendingSwap();
1213 void PrerenderManager::PendingSwap::OnMergeTimeout() {
1214 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_TIMED_OUT);
1215 prerender_data_->ClearPendingSwap();
1218 void PrerenderManager::SetPrerenderContentsFactory(
1219 PrerenderContents::Factory* prerender_contents_factory) {
1220 DCHECK(CalledOnValidThread());
1221 prerender_contents_factory_.reset(prerender_contents_factory);
1224 void PrerenderManager::SourceNavigatedAway(PrerenderData* prerender_data) {
1225 // The expiry time of our prerender data will likely change because of
1226 // this navigation. This requires a resort of active_prerenders_.
1227 ScopedVector<PrerenderData>::iterator it =
1228 std::find(active_prerenders_.begin(), active_prerenders_.end(),
1229 prerender_data);
1230 if (it == active_prerenders_.end())
1231 return;
1233 (*it)->set_expiry_time(
1234 std::min((*it)->expiry_time(),
1235 GetExpiryTimeForNavigatedAwayPrerender()));
1236 SortActivePrerenders();
1239 net::URLRequestContextGetter* PrerenderManager::GetURLRequestContext() {
1240 return content::BrowserContext::GetDefaultStoragePartition(profile_)->
1241 GetURLRequestContext();
1245 // private
1246 PrerenderHandle* PrerenderManager::AddPrerender(
1247 Origin origin,
1248 int process_id,
1249 const GURL& url_arg,
1250 const content::Referrer& referrer,
1251 const gfx::Size& size,
1252 SessionStorageNamespace* session_storage_namespace) {
1253 DCHECK(CalledOnValidThread());
1255 if (!IsEnabled())
1256 return NULL;
1258 if ((origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN ||
1259 origin == ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN) &&
1260 IsGoogleSearchResultURL(referrer.url)) {
1261 origin = ORIGIN_GWS_PRERENDER;
1264 GURL url = url_arg;
1265 GURL alias_url;
1266 uint8 experiment = GetQueryStringBasedExperiment(url_arg);
1267 if (IsControlGroup(experiment) &&
1268 MaybeGetQueryStringBasedAliasURL(url, &alias_url)) {
1269 url = alias_url;
1272 // From here on, we will record a FinalStatus so we need to register with the
1273 // histogram tracking.
1274 histograms_->RecordPrerender(origin, url_arg);
1276 if (PrerenderData* preexisting_prerender_data =
1277 FindPrerenderData(url, session_storage_namespace)) {
1278 RecordFinalStatusWithoutCreatingPrerenderContents(
1279 url, origin, experiment, FINAL_STATUS_DUPLICATE);
1280 return new PrerenderHandle(preexisting_prerender_data);
1283 // Do not prerender if there are too many render processes, and we would
1284 // have to use an existing one. We do not want prerendering to happen in
1285 // a shared process, so that we can always reliably lower the CPU
1286 // priority for prerendering.
1287 // In single-process mode, ShouldTryToUseExistingProcessHost() always returns
1288 // true, so that case needs to be explicitly checked for.
1289 // TODO(tburkard): Figure out how to cancel prerendering in the opposite
1290 // case, when a new tab is added to a process used for prerendering.
1291 // TODO(ppi): Check whether there are usually enough render processes
1292 // available on Android. If not, kill an existing renderers so that we can
1293 // create a new one.
1294 if (content::RenderProcessHost::ShouldTryToUseExistingProcessHost(
1295 profile_, url) &&
1296 !content::RenderProcessHost::run_renderer_in_process()) {
1297 RecordFinalStatusWithoutCreatingPrerenderContents(
1298 url, origin, experiment, FINAL_STATUS_TOO_MANY_PROCESSES);
1299 return NULL;
1302 // Check if enough time has passed since the last prerender.
1303 if (!DoesRateLimitAllowPrerender(origin)) {
1304 // Cancel the prerender. We could add it to the pending prerender list but
1305 // this doesn't make sense as the next prerender request will be triggered
1306 // by a navigation and is unlikely to be the same site.
1307 RecordFinalStatusWithoutCreatingPrerenderContents(
1308 url, origin, experiment, FINAL_STATUS_RATE_LIMIT_EXCEEDED);
1309 return NULL;
1312 if (IsPrerenderCookieStoreEnabled() && !cookie_store_loaded()) {
1313 // Only prerender if the cookie store for this profile has been loaded.
1314 // This is required by PrerenderCookieMonster.
1315 RecordFinalStatusWithoutCreatingPrerenderContents(
1316 url, origin, experiment, FINAL_STATUS_COOKIE_STORE_NOT_LOADED);
1317 return NULL;
1320 PrerenderContents* prerender_contents = CreatePrerenderContents(
1321 url, referrer, origin, experiment);
1322 DCHECK(prerender_contents);
1323 active_prerenders_.push_back(
1324 new PrerenderData(this, prerender_contents,
1325 GetExpiryTimeForNewPrerender(origin)));
1326 if (!prerender_contents->Init()) {
1327 DCHECK(active_prerenders_.end() ==
1328 FindIteratorForPrerenderContents(prerender_contents));
1329 return NULL;
1332 histograms_->RecordPrerenderStarted(origin);
1333 DCHECK(!prerender_contents->prerendering_has_started());
1335 PrerenderHandle* prerender_handle =
1336 new PrerenderHandle(active_prerenders_.back());
1337 SortActivePrerenders();
1339 last_prerender_start_time_ = GetCurrentTimeTicks();
1341 gfx::Size contents_size =
1342 size.IsEmpty() ? config_.default_tab_bounds.size() : size;
1344 net::URLRequestContextGetter* request_context =
1345 (IsPrerenderCookieStoreEnabled() ? GetURLRequestContext() : NULL);
1347 prerender_contents->StartPrerendering(process_id, contents_size,
1348 session_storage_namespace,
1349 request_context);
1351 DCHECK(IsControlGroup(experiment) ||
1352 prerender_contents->prerendering_has_started() ||
1353 (origin == ORIGIN_LOCAL_PREDICTOR &&
1354 IsLocalPredictorPrerenderAlwaysControlEnabled()));
1356 if (GetMode() == PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP)
1357 histograms_->RecordConcurrency(active_prerenders_.size());
1359 // Query the history to see if the URL being prerendered has ever been
1360 // visited before.
1361 HistoryService* history_service = HistoryServiceFactory::GetForProfile(
1362 profile_, Profile::EXPLICIT_ACCESS);
1363 if (history_service) {
1364 history_service->QueryURL(
1365 url,
1366 false,
1367 base::Bind(&PrerenderManager::OnHistoryServiceDidQueryURL,
1368 base::Unretained(this),
1369 origin,
1370 experiment),
1371 &query_url_tracker_);
1374 StartSchedulingPeriodicCleanups();
1375 return prerender_handle;
1378 void PrerenderManager::StartSchedulingPeriodicCleanups() {
1379 DCHECK(CalledOnValidThread());
1380 if (repeating_timer_.IsRunning())
1381 return;
1382 repeating_timer_.Start(FROM_HERE,
1383 base::TimeDelta::FromMilliseconds(kPeriodicCleanupIntervalMs),
1384 this,
1385 &PrerenderManager::PeriodicCleanup);
1388 void PrerenderManager::StopSchedulingPeriodicCleanups() {
1389 DCHECK(CalledOnValidThread());
1390 repeating_timer_.Stop();
1393 void PrerenderManager::PeriodicCleanup() {
1394 DCHECK(CalledOnValidThread());
1396 base::ElapsedTimer resource_timer;
1398 // Grab a copy of the current PrerenderContents pointers, so that we
1399 // will not interfere with potential deletions of the list.
1400 std::vector<PrerenderContents*>
1401 prerender_contents(active_prerenders_.size());
1402 std::transform(active_prerenders_.begin(), active_prerenders_.end(),
1403 prerender_contents.begin(),
1404 std::mem_fun(&PrerenderData::contents));
1406 // And now check for prerenders using too much memory.
1407 std::for_each(prerender_contents.begin(), prerender_contents.end(),
1408 std::mem_fun(
1409 &PrerenderContents::DestroyWhenUsingTooManyResources));
1411 // Measure how long the resource checks took. http://crbug.com/305419.
1412 UMA_HISTOGRAM_TIMES("Prerender.PeriodicCleanupResourceCheckTime",
1413 resource_timer.Elapsed());
1415 base::ElapsedTimer cleanup_timer;
1417 // Perform deferred cleanup work.
1418 DeleteOldWebContents();
1419 DeleteOldEntries();
1420 if (active_prerenders_.empty())
1421 StopSchedulingPeriodicCleanups();
1423 to_delete_prerenders_.clear();
1425 // Measure how long a the various cleanup tasks took. http://crbug.com/305419.
1426 UMA_HISTOGRAM_TIMES("Prerender.PeriodicCleanupDeleteContentsTime",
1427 cleanup_timer.Elapsed());
1430 void PrerenderManager::PostCleanupTask() {
1431 DCHECK(CalledOnValidThread());
1432 base::MessageLoop::current()->PostTask(
1433 FROM_HERE,
1434 base::Bind(&PrerenderManager::PeriodicCleanup, AsWeakPtr()));
1437 base::TimeTicks PrerenderManager::GetExpiryTimeForNewPrerender(
1438 Origin origin) const {
1439 base::TimeDelta ttl = config_.time_to_live;
1440 if (origin == ORIGIN_LOCAL_PREDICTOR)
1441 ttl = base::TimeDelta::FromSeconds(GetLocalPredictorTTLSeconds());
1442 return GetCurrentTimeTicks() + ttl;
1445 base::TimeTicks PrerenderManager::GetExpiryTimeForNavigatedAwayPrerender()
1446 const {
1447 return GetCurrentTimeTicks() + config_.abandon_time_to_live;
1450 void PrerenderManager::DeleteOldEntries() {
1451 DCHECK(CalledOnValidThread());
1452 while (!active_prerenders_.empty()) {
1453 PrerenderData* prerender_data = active_prerenders_.front();
1454 DCHECK(prerender_data);
1455 DCHECK(prerender_data->contents());
1457 if (prerender_data->expiry_time() > GetCurrentTimeTicks())
1458 return;
1459 prerender_data->contents()->Destroy(FINAL_STATUS_TIMED_OUT);
1463 base::Time PrerenderManager::GetCurrentTime() const {
1464 return base::Time::Now();
1467 base::TimeTicks PrerenderManager::GetCurrentTimeTicks() const {
1468 return base::TimeTicks::Now();
1471 PrerenderContents* PrerenderManager::CreatePrerenderContents(
1472 const GURL& url,
1473 const content::Referrer& referrer,
1474 Origin origin,
1475 uint8 experiment_id) {
1476 DCHECK(CalledOnValidThread());
1477 return prerender_contents_factory_->CreatePrerenderContents(
1478 this, profile_, url, referrer, origin, experiment_id);
1481 void PrerenderManager::SortActivePrerenders() {
1482 std::sort(active_prerenders_.begin(), active_prerenders_.end(),
1483 PrerenderData::OrderByExpiryTime());
1486 PrerenderManager::PrerenderData* PrerenderManager::FindPrerenderData(
1487 const GURL& url,
1488 const SessionStorageNamespace* session_storage_namespace) {
1489 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1490 it != active_prerenders_.end(); ++it) {
1491 if ((*it)->contents()->Matches(url, session_storage_namespace))
1492 return *it;
1494 return NULL;
1497 PrerenderManager::PrerenderData*
1498 PrerenderManager::FindPrerenderDataForTargetContents(
1499 WebContents* target_contents) {
1500 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1501 it != active_prerenders_.end(); ++it) {
1502 if ((*it)->pending_swap() &&
1503 (*it)->pending_swap()->web_contents() == target_contents)
1504 return *it;
1506 return NULL;
1509 ScopedVector<PrerenderManager::PrerenderData>::iterator
1510 PrerenderManager::FindIteratorForPrerenderContents(
1511 PrerenderContents* prerender_contents) {
1512 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1513 it != active_prerenders_.end(); ++it) {
1514 if (prerender_contents == (*it)->contents())
1515 return it;
1517 return active_prerenders_.end();
1520 bool PrerenderManager::DoesRateLimitAllowPrerender(Origin origin) const {
1521 DCHECK(CalledOnValidThread());
1522 base::TimeDelta elapsed_time =
1523 GetCurrentTimeTicks() - last_prerender_start_time_;
1524 histograms_->RecordTimeBetweenPrerenderRequests(origin, elapsed_time);
1525 if (!config_.rate_limit_enabled)
1526 return true;
1527 // The LocalPredictor may issue multiple prerenders simultaneously (if so
1528 // configured), so no throttling.
1529 if (origin == ORIGIN_LOCAL_PREDICTOR)
1530 return true;
1531 return elapsed_time >=
1532 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs);
1535 void PrerenderManager::DeleteOldWebContents() {
1536 while (!old_web_contents_list_.empty()) {
1537 WebContents* web_contents = old_web_contents_list_.front();
1538 old_web_contents_list_.pop_front();
1539 // TODO(dominich): should we use Instant Unload Handler here?
1540 delete web_contents;
1544 void PrerenderManager::CleanUpOldNavigations() {
1545 DCHECK(CalledOnValidThread());
1547 // Cutoff. Navigations before this cutoff can be discarded.
1548 base::TimeTicks cutoff = GetCurrentTimeTicks() -
1549 base::TimeDelta::FromMilliseconds(kNavigationRecordWindowMs);
1550 while (!navigations_.empty()) {
1551 if (navigations_.front().time > cutoff)
1552 break;
1553 navigations_.pop_front();
1557 void PrerenderManager::ScheduleDeleteOldWebContents(
1558 WebContents* tab,
1559 OnCloseWebContentsDeleter* deleter) {
1560 old_web_contents_list_.push_back(tab);
1561 PostCleanupTask();
1563 if (deleter) {
1564 ScopedVector<OnCloseWebContentsDeleter>::iterator i = std::find(
1565 on_close_web_contents_deleters_.begin(),
1566 on_close_web_contents_deleters_.end(),
1567 deleter);
1568 DCHECK(i != on_close_web_contents_deleters_.end());
1569 on_close_web_contents_deleters_.erase(i);
1573 void PrerenderManager::AddToHistory(PrerenderContents* contents) {
1574 PrerenderHistory::Entry entry(contents->prerender_url(),
1575 contents->final_status(),
1576 contents->origin(),
1577 base::Time::Now());
1578 prerender_history_->AddEntry(entry);
1581 base::Value* PrerenderManager::GetActivePrerendersAsValue() const {
1582 base::ListValue* list_value = new base::ListValue();
1583 for (ScopedVector<PrerenderData>::const_iterator it =
1584 active_prerenders_.begin();
1585 it != active_prerenders_.end(); ++it) {
1586 if (base::Value* prerender_value = (*it)->contents()->GetAsValue())
1587 list_value->Append(prerender_value);
1589 return list_value;
1592 void PrerenderManager::DestroyAllContents(FinalStatus final_status) {
1593 DeleteOldWebContents();
1594 while (!active_prerenders_.empty()) {
1595 PrerenderContents* contents = active_prerenders_.front()->contents();
1596 contents->Destroy(final_status);
1598 to_delete_prerenders_.clear();
1601 void PrerenderManager::DestroyAndMarkMatchCompleteAsUsed(
1602 PrerenderContents* prerender_contents,
1603 FinalStatus final_status) {
1604 prerender_contents->set_match_complete_status(
1605 PrerenderContents::MATCH_COMPLETE_REPLACED);
1606 histograms_->RecordFinalStatus(prerender_contents->origin(),
1607 prerender_contents->experiment_id(),
1608 PrerenderContents::MATCH_COMPLETE_REPLACEMENT,
1609 FINAL_STATUS_WOULD_HAVE_BEEN_USED);
1610 prerender_contents->Destroy(final_status);
1613 void PrerenderManager::RecordFinalStatusWithoutCreatingPrerenderContents(
1614 const GURL& url, Origin origin, uint8 experiment_id,
1615 FinalStatus final_status) const {
1616 PrerenderHistory::Entry entry(url, final_status, origin, base::Time::Now());
1617 prerender_history_->AddEntry(entry);
1618 RecordFinalStatusWithMatchCompleteStatus(
1619 origin, experiment_id,
1620 PrerenderContents::MATCH_COMPLETE_DEFAULT,
1621 final_status);
1624 void PrerenderManager::Observe(int type,
1625 const content::NotificationSource& source,
1626 const content::NotificationDetails& details) {
1627 switch (type) {
1628 case chrome::NOTIFICATION_COOKIE_CHANGED: {
1629 Profile* profile = content::Source<Profile>(source).ptr();
1630 if (!profile || !profile_->IsSameProfile(profile) ||
1631 profile->IsOffTheRecord()) {
1632 return;
1634 CookieChanged(content::Details<ChromeCookieDetails>(details).ptr());
1635 break;
1637 case chrome::NOTIFICATION_PROFILE_DESTROYED:
1638 DestroyAllContents(FINAL_STATUS_PROFILE_DESTROYED);
1639 on_close_web_contents_deleters_.clear();
1640 break;
1641 default:
1642 NOTREACHED() << "Unexpected notification sent.";
1643 break;
1647 void PrerenderManager::OnCreatingAudioStream(int render_process_id,
1648 int render_frame_id) {
1649 content::RenderFrameHost* render_frame_host =
1650 content::RenderFrameHost::FromID(render_process_id, render_frame_id);
1651 WebContents* tab = WebContents::FromRenderFrameHost(render_frame_host);
1652 if (!tab)
1653 return;
1655 PrerenderContents* prerender_contents = GetPrerenderContents(tab);
1656 if (!prerender_contents)
1657 return;
1659 prerender_contents->Destroy(prerender::FINAL_STATUS_CREATING_AUDIO_STREAM);
1662 void PrerenderManager::RecordLikelyLoginOnURL(const GURL& url) {
1663 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1664 if (!url.SchemeIsHTTPOrHTTPS())
1665 return;
1666 if (logged_in_predictor_table_.get()) {
1667 BrowserThread::PostTask(
1668 BrowserThread::DB,
1669 FROM_HERE,
1670 base::Bind(&LoggedInPredictorTable::AddDomainFromURL,
1671 logged_in_predictor_table_,
1672 url));
1674 std::string key = LoggedInPredictorTable::GetKey(url);
1675 if (!logged_in_state_.get())
1676 return;
1677 if (logged_in_state_->count(key))
1678 return;
1679 (*logged_in_state_)[key] = base::Time::Now().ToInternalValue();
1682 void PrerenderManager::CheckIfLikelyLoggedInOnURL(
1683 const GURL& url,
1684 bool* lookup_result,
1685 bool* database_was_present,
1686 const base::Closure& result_cb) {
1687 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1688 if (!logged_in_predictor_table_.get()) {
1689 *database_was_present = false;
1690 *lookup_result = false;
1691 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, result_cb);
1692 return;
1694 BrowserThread::PostTaskAndReply(
1695 BrowserThread::DB, FROM_HERE,
1696 base::Bind(&LoggedInPredictorTable::HasUserLoggedIn,
1697 logged_in_predictor_table_,
1698 url,
1699 lookup_result,
1700 database_was_present),
1701 result_cb);
1705 void PrerenderManager::CookieChanged(ChromeCookieDetails* details) {
1706 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1708 if (!logged_in_predictor_table_.get())
1709 return;
1711 // We only care when a cookie has been removed.
1712 if (!details->removed)
1713 return;
1715 std::string domain_key =
1716 LoggedInPredictorTable::GetKeyFromDomain(details->cookie->Domain());
1718 // If we have no record of this domain as a potentially logged in domain,
1719 // nothing to do here.
1720 if (logged_in_state_.get() && logged_in_state_->count(domain_key) < 1)
1721 return;
1723 net::URLRequestContextGetter* rq_context = profile_->GetRequestContext();
1724 if (!rq_context)
1725 return;
1727 BrowserThread::PostTask(
1728 BrowserThread::IO, FROM_HERE,
1729 base::Bind(&CheckIfCookiesExistForDomainOnIOThread,
1730 base::Unretained(rq_context),
1731 domain_key,
1732 base::Bind(
1733 &PrerenderManager::CookieChangedAnyCookiesLeftLookupResult,
1734 AsWeakPtr(),
1735 domain_key)
1739 void PrerenderManager::CookieChangedAnyCookiesLeftLookupResult(
1740 const std::string& domain_key,
1741 bool cookies_exist) {
1742 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1744 if (cookies_exist)
1745 return;
1747 if (logged_in_predictor_table_.get()) {
1748 BrowserThread::PostTask(BrowserThread::DB,
1749 FROM_HERE,
1750 base::Bind(&LoggedInPredictorTable::DeleteDomain,
1751 logged_in_predictor_table_,
1752 domain_key));
1755 if (logged_in_state_.get())
1756 logged_in_state_->erase(domain_key);
1759 void PrerenderManager::LoggedInPredictorDataReceived(
1760 scoped_ptr<LoggedInStateMap> new_map) {
1761 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1762 logged_in_state_.swap(new_map);
1765 void PrerenderManager::RecordEvent(PrerenderContents* contents,
1766 PrerenderEvent event) const {
1767 if (!contents)
1768 histograms_->RecordEvent(ORIGIN_NONE, kNoExperiment, event);
1769 else
1770 histograms_->RecordEvent(contents->origin(), contents->experiment_id(),
1771 event);
1774 // static
1775 void PrerenderManager::RecordCookieEvent(int process_id,
1776 int frame_id,
1777 const GURL& url,
1778 const GURL& frame_url,
1779 bool is_for_blocking_resource,
1780 PrerenderContents::CookieEvent event,
1781 const net::CookieList* cookie_list) {
1782 RenderFrameHost* rfh = RenderFrameHost::FromID(process_id, frame_id);
1783 WebContents* web_contents = WebContents::FromRenderFrameHost(rfh);
1784 if (!web_contents)
1785 return;
1787 bool is_main_frame = (rfh == web_contents->GetMainFrame());
1789 bool is_third_party_cookie =
1790 (!frame_url.is_empty() &&
1791 !net::registry_controlled_domains::SameDomainOrHost(
1792 url, frame_url,
1793 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES));
1795 PrerenderContents* prerender_contents =
1796 PrerenderContents::FromWebContents(web_contents);
1798 if (!prerender_contents)
1799 return;
1801 base::Time earliest_create_date;
1802 if (event == PrerenderContents::COOKIE_EVENT_SEND) {
1803 if (!cookie_list || cookie_list->empty())
1804 return;
1805 for (size_t i = 0; i < cookie_list->size(); i++) {
1806 if (earliest_create_date.is_null() ||
1807 (*cookie_list)[i].CreationDate() < earliest_create_date) {
1808 earliest_create_date = (*cookie_list)[i].CreationDate();
1813 prerender_contents->RecordCookieEvent(event,
1814 is_main_frame && url == frame_url,
1815 is_third_party_cookie,
1816 is_for_blocking_resource,
1817 earliest_create_date);
1820 void PrerenderManager::RecordCookieStatus(Origin origin,
1821 uint8 experiment_id,
1822 int cookie_status) const {
1823 histograms_->RecordCookieStatus(origin, experiment_id, cookie_status);
1826 void PrerenderManager::RecordCookieSendType(Origin origin,
1827 uint8 experiment_id,
1828 int cookie_send_type) const {
1829 histograms_->RecordCookieSendType(origin, experiment_id, cookie_send_type);
1832 void PrerenderManager::OnHistoryServiceDidQueryURL(
1833 Origin origin,
1834 uint8 experiment_id,
1835 bool success,
1836 const history::URLRow& url_row,
1837 const history::VisitVector& /*visits*/) {
1838 histograms_->RecordPrerenderPageVisitedStatus(origin, experiment_id, success);
1841 // static
1842 void PrerenderManager::HangSessionStorageMergesForTesting() {
1843 g_hang_session_storage_merges_for_testing = true;
1846 void PrerenderManager::RecordNetworkBytes(Origin origin,
1847 bool used,
1848 int64 prerender_bytes) {
1849 if (!ActuallyPrerendering())
1850 return;
1851 int64 recent_profile_bytes =
1852 profile_network_bytes_ - last_recorded_profile_network_bytes_;
1853 last_recorded_profile_network_bytes_ = profile_network_bytes_;
1854 DCHECK_GE(recent_profile_bytes, 0);
1855 histograms_->RecordNetworkBytes(
1856 origin, used, prerender_bytes, recent_profile_bytes);
1859 bool PrerenderManager::IsEnabled() const {
1860 DCHECK(CalledOnValidThread());
1862 return chrome_browser_net::CanPrefetchAndPrerenderUI(profile_->GetPrefs());
1865 void PrerenderManager::AddProfileNetworkBytesIfEnabled(int64 bytes) {
1866 DCHECK_GE(bytes, 0);
1867 if (IsEnabled() && ActuallyPrerendering())
1868 profile_network_bytes_ += bytes;
1871 void PrerenderManager::OnCookieStoreLoaded() {
1872 cookie_store_loaded_ = true;
1873 if (!on_cookie_store_loaded_cb_for_testing_.is_null())
1874 on_cookie_store_loaded_cb_for_testing_.Run();
1877 void PrerenderManager::AddPrerenderProcessHost(
1878 content::RenderProcessHost* process_host) {
1879 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1880 DCHECK(prerender_process_hosts_.find(process_host) ==
1881 prerender_process_hosts_.end());
1882 prerender_process_hosts_.insert(process_host);
1883 process_host->AddObserver(this);
1886 bool PrerenderManager::MayReuseProcessHost(
1887 content::RenderProcessHost* process_host) {
1888 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1889 // If prerender cookie stores are disabled, there is no need to require
1890 // isolated prerender processes.
1891 if (!IsPrerenderCookieStoreEnabled())
1892 return true;
1893 return (prerender_process_hosts_.find(process_host) ==
1894 prerender_process_hosts_.end());
1897 void PrerenderManager::RenderProcessHostDestroyed(
1898 content::RenderProcessHost* host) {
1899 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1900 prerender_process_hosts_.erase(host);
1901 BrowserThread::PostTask(
1902 BrowserThread::IO, FROM_HERE,
1903 base::Bind(&PrerenderTracker::RemovePrerenderCookieStoreOnIOThread,
1904 base::Unretained(prerender_tracker()), host->GetID(), false));
1907 } // namespace prerender