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"
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/pref_names.h"
48 #include "chrome/common/prerender_messages.h"
49 #include "chrome/common/prerender_types.h"
50 #include "content/public/browser/browser_thread.h"
51 #include "content/public/browser/devtools_agent_host.h"
52 #include "content/public/browser/navigation_controller.h"
53 #include "content/public/browser/notification_service.h"
54 #include "content/public/browser/notification_source.h"
55 #include "content/public/browser/render_frame_host.h"
56 #include "content/public/browser/render_process_host.h"
57 #include "content/public/browser/render_view_host.h"
58 #include "content/public/browser/resource_request_details.h"
59 #include "content/public/browser/session_storage_namespace.h"
60 #include "content/public/browser/site_instance.h"
61 #include "content/public/browser/storage_partition.h"
62 #include "content/public/browser/web_contents.h"
63 #include "content/public/browser/web_contents_delegate.h"
64 #include "content/public/common/url_constants.h"
65 #include "extensions/common/constants.h"
66 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
67 #include "net/url_request/url_request_context.h"
68 #include "net/url_request/url_request_context_getter.h"
70 using content::BrowserThread
;
71 using content::RenderViewHost
;
72 using content::RenderFrameHost
;
73 using content::SessionStorageNamespace
;
74 using content::WebContents
;
75 using predictors::LoggedInPredictorTable
;
81 // Time interval at which periodic cleanups are performed.
82 const int kPeriodicCleanupIntervalMs
= 1000;
84 // Valid HTTP methods for prerendering.
85 const char* const kValidHttpMethods
[] = {
93 // Length of prerender history, for display in chrome://net-internals
94 const int kHistoryLength
= 100;
96 // Timeout, in ms, for a session storage namespace merge.
97 const int kSessionStorageNamespaceMergeTimeoutMs
= 500;
99 // If true, all session storage merges hang indefinitely.
100 bool g_hang_session_storage_merges_for_testing
= false;
102 // Indicates whether a Prerender has been cancelled such that we need
103 // a dummy replacement for the purpose of recording the correct PPLT for
104 // the Match Complete case.
105 // Traditionally, "Match" means that a prerendered page was actually visited &
106 // the prerender was used. Our goal is to have "Match" cases line up in the
107 // control group & the experiment group, so that we can make meaningful
108 // comparisons of improvements. However, in the control group, since we don't
109 // actually perform prerenders, many of the cancellation reasons cannot be
110 // detected. Therefore, in the Prerender group, when we cancel for one of these
111 // reasons, we keep track of a dummy Prerender representing what we would
112 // have in the control group. If that dummy prerender in the prerender group
113 // would then be swapped in (but isn't actually b/c it's a dummy), we record
114 // this as a MatchComplete. This allows us to compare MatchComplete's
115 // across Prerender & Control group which ideally should be lining up.
116 // This ensures that there is no bias in terms of the page load times
117 // of the pages forming the difference between the two sets.
119 bool NeedMatchCompleteDummyForFinalStatus(FinalStatus final_status
) {
120 return final_status
!= FINAL_STATUS_USED
&&
121 final_status
!= FINAL_STATUS_TIMED_OUT
&&
122 final_status
!= FINAL_STATUS_MANAGER_SHUTDOWN
&&
123 final_status
!= FINAL_STATUS_PROFILE_DESTROYED
&&
124 final_status
!= FINAL_STATUS_APP_TERMINATING
&&
125 final_status
!= FINAL_STATUS_WINDOW_OPENER
&&
126 final_status
!= FINAL_STATUS_CACHE_OR_HISTORY_CLEARED
&&
127 final_status
!= FINAL_STATUS_CANCELLED
&&
128 final_status
!= FINAL_STATUS_DEVTOOLS_ATTACHED
&&
129 final_status
!= FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING
&&
130 final_status
!= FINAL_STATUS_PAGE_BEING_CAPTURED
&&
131 final_status
!= FINAL_STATUS_NAVIGATION_UNCOMMITTED
&&
132 final_status
!= FINAL_STATUS_NON_EMPTY_BROWSING_INSTANCE
;
135 void CheckIfCookiesExistForDomainResultOnUIThread(
136 const net::CookieMonster::HasCookiesForETLDP1Callback
& callback
,
137 bool cookies_exist
) {
138 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
139 callback
.Run(cookies_exist
);
142 void CheckIfCookiesExistForDomainResultOnIOThread(
143 const net::CookieMonster::HasCookiesForETLDP1Callback
& callback
,
144 bool cookies_exist
) {
145 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
146 BrowserThread::PostTask(
149 base::Bind(&CheckIfCookiesExistForDomainResultOnUIThread
,
154 void CheckIfCookiesExistForDomainOnIOThread(
155 net::URLRequestContextGetter
* rq_context
,
156 const std::string
& domain_key
,
157 const net::CookieMonster::HasCookiesForETLDP1Callback
& callback
) {
158 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
159 net::CookieStore
* cookie_store
=
160 rq_context
->GetURLRequestContext()->cookie_store();
161 cookie_store
->GetCookieMonster()->HasCookiesForETLDP1Async(
163 base::Bind(&CheckIfCookiesExistForDomainResultOnIOThread
, callback
));
168 class PrerenderManager::OnCloseWebContentsDeleter
169 : public content::WebContentsDelegate
,
170 public base::SupportsWeakPtr
<
171 PrerenderManager::OnCloseWebContentsDeleter
> {
173 OnCloseWebContentsDeleter(PrerenderManager
* manager
,
177 suppressed_dialog_(false) {
178 tab_
->SetDelegate(this);
179 base::MessageLoop::current()->PostDelayedTask(FROM_HERE
,
180 base::Bind(&OnCloseWebContentsDeleter::ScheduleWebContentsForDeletion
,
182 base::TimeDelta::FromSeconds(kDeleteWithExtremePrejudiceSeconds
));
185 virtual void CloseContents(WebContents
* source
) override
{
186 DCHECK_EQ(tab_
, source
);
187 ScheduleWebContentsForDeletion(false);
190 virtual void SwappedOut(WebContents
* source
) override
{
191 DCHECK_EQ(tab_
, source
);
192 ScheduleWebContentsForDeletion(false);
195 virtual bool ShouldSuppressDialogs() override
{
196 // Use this as a proxy for getting statistics on how often we fail to honor
197 // the beforeunload event.
198 suppressed_dialog_
= true;
203 static const int kDeleteWithExtremePrejudiceSeconds
= 3;
205 void ScheduleWebContentsForDeletion(bool timeout
) {
206 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterTimeout", timeout
);
207 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterSuppressedDialog",
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
);
222 int PrerenderManager::prerenders_per_session_count_
= 0;
225 PrerenderManager::PrerenderManagerMode
PrerenderManager::mode_
=
226 PRERENDER_MODE_ENABLED
;
228 struct PrerenderManager::NavigationRecord
{
229 NavigationRecord(const GURL
& url
, base::TimeTicks time
)
235 base::TimeTicks time
;
238 PrerenderManager::PrerenderManager(Profile
* profile
,
239 PrerenderTracker
* prerender_tracker
)
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
);
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_
,
270 base::Bind(&PrerenderManager::LoggedInPredictorDataReceived
,
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;
282 case PrerenderManager::PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP
:
283 config_
.time_to_live
= base::TimeDelta::FromMinutes(15);
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();
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();
325 DCHECK(active_prerenders_
.empty());
328 PrerenderHandle
* PrerenderManager::AddPrerenderFromLinkRelPrerender(
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
)
345 WebContents
* source_web_contents
=
346 WebContents::FromRenderViewHost(source_render_view_host
);
347 if (!source_web_contents
)
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(
365 SessionStorageNamespace
* session_storage_namespace
,
366 const gfx::Size
& size
) {
367 if (!IsOmniboxEnabled(profile_
))
369 return AddPrerender(ORIGIN_OMNIBOX
, -1, url
, content::Referrer(), size
,
370 session_storage_namespace
);
373 PrerenderHandle
* PrerenderManager::AddPrerenderFromLocalPredictor(
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(
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(
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())
420 to_delete_prerenders_
.clear();
422 // First, try to find prerender data with the correct session storage
424 // TODO(ajwong): This doesn't handle isolated apps correctly.
425 PrerenderData
* prerender_data
= FindPrerenderData(
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
432 if (!prerender_data
) {
433 prerender_data
= FindPrerenderData(url
, NULL
);
435 RecordEvent(prerender_data
->contents(),
436 PRERENDER_EVENT_SWAPIN_CANDIDATE_NAMESPACE_MATCHES
);
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())
448 // Abort any existing pending swap on the target contents.
449 PrerenderData
* pending_swap
=
450 FindPrerenderDataForTargetContents(web_contents
);
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
);
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
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
)
490 // Record the new target_contents for the callers.
491 params
->target_contents
= new_web_contents
;
495 WebContents
* PrerenderManager::SwapInternal(
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
);
511 PrerenderTabHelper
* target_tab_helper
=
512 PrerenderTabHelper::FromWebContents(web_contents
);
513 if (!target_tab_helper
) {
518 if (IsNoSwapInExperiment(prerender_data
->contents()->experiment_id()))
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
);
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) {
542 web_contents
->GetSiteInstance()->GetRelatedActiveContentsCount(), 1u);
543 prerender_data
->contents()->Destroy(
544 FINAL_STATUS_NON_EMPTY_BROWSING_INSTANCE
);
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
);
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
);
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
);
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
);
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
);
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
);
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(),
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() ?
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
,
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);
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());
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
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();
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.
712 void PrerenderManager::RecordPageLoadTimeNotSwappedIn(
714 base::TimeDelta page_load_time
,
716 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
717 histograms_
->RecordPageLoadTimeNotSwappedIn(origin
, page_load_time
, url
);
720 void PrerenderManager::RecordPerceivedPageLoadTime(
722 NavigationType navigation_type
,
723 base::TimeDelta perceived_page_load_time
,
724 double fraction_plt_elapsed_at_swap_in
,
726 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
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
);
743 PrerenderManager::PrerenderManagerMode
PrerenderManager::GetMode() {
748 void PrerenderManager::SetMode(PrerenderManagerMode mode
) {
753 const char* PrerenderManager::GetModeString() {
755 case PRERENDER_MODE_DISABLED
:
757 case PRERENDER_MODE_ENABLED
:
758 case PRERENDER_MODE_EXPERIMENT_PRERENDER_GROUP
:
760 case PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP
:
762 case PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP
:
764 case PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP
:
766 case PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP
:
768 case PRERENDER_MODE_EXPERIMENT_MATCH_COMPLETE_GROUP
:
769 return "_MatchComplete";
770 case PRERENDER_MODE_MAX
:
772 NOTREACHED() << "Invalid PrerenderManager mode.";
779 bool PrerenderManager::IsPrerenderingPossible() {
780 return GetMode() != PRERENDER_MODE_DISABLED
;
784 bool PrerenderManager::ActuallyPrerendering() {
785 return IsPrerenderingPossible() && !IsControlGroup(kNoExperiment
);
789 bool PrerenderManager::IsControlGroup(uint8 experiment_id
) {
790 return GetMode() == PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP
||
791 IsControlGroupExperiment(experiment_id
);
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
)) {
806 *origin
= prerender_contents
->origin();
812 bool PrerenderManager::HasPrerenderedUrl(
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
)) {
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();
856 PrerenderContents
* PrerenderManager::GetPrerenderContentsForRoute(
858 int route_id
) const {
859 content::WebContents
* web_contents
=
860 tab_util::GetWebContentsByID(child_id
, route_id
);
861 if (web_contents
== 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
);
881 bool PrerenderManager::HasRecentlyBeenNavigatedTo(Origin origin
,
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();
891 if (it
->url
== url
) {
892 base::TimeDelta delta
= GetCurrentTimeTicks() - it
->time
;
893 histograms_
->RecordTimeSinceLastRecentVisit(origin
, delta
);
902 bool PrerenderManager::IsValidHttpMethod(const std::string
& method
) {
903 // method has been canonicalized to upper case at this point so we can just
905 DCHECK_EQ(method
, StringToUpperASCII(method
));
906 for (size_t i
= 0; i
< arraysize(kValidHttpMethods
); ++i
) {
907 if (method
.compare(kValidHttpMethods
[i
]) == 0)
915 bool PrerenderManager::DoesURLHaveValidScheme(const GURL
& url
) {
916 return (url
.SchemeIsHTTPOrHTTPS() ||
917 url
.SchemeIs(extensions::kExtensionScheme
) ||
918 url
.SchemeIs("data"));
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) ";
938 enabled_note
+= "(No-use group: Not swapping in prerendered pages) ";
939 if (GetMode() == PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP
)
941 "(15 min TTL group: Extended prerender eviction to 15 mins) ";
942 dict_value
->SetString("enabled_note", enabled_note
);
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(
959 PrerenderContents::MatchCompleteStatus mc_status
,
960 FinalStatus final_status
) const {
961 histograms_
->RecordFinalStatus(origin
,
967 void PrerenderManager::RecordNavigation(const GURL
& url
) {
968 DCHECK(CalledOnValidThread());
970 navigations_
.push_back(NavigationRecord(url
, GetCurrentTimeTicks()));
971 CleanUpOldNavigations();
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
)
987 expiry_time_(expiry_time
) {
988 DCHECK_NE(static_cast<PrerenderContents
*>(NULL
), contents_
);
991 PrerenderManager::PrerenderData::~PrerenderData() {
994 void PrerenderManager::PrerenderData::MakeIntoMatchCompleteReplacement() {
996 contents_
->set_match_complete_status(
997 PrerenderContents::MATCH_COMPLETE_REPLACED
);
998 PrerenderData
* to_delete
= new PrerenderData(manager_
, contents_
.release(),
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_
);
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
,
1045 bool should_replace_current_entry
)
1046 : content::WebContentsObserver(target_contents
),
1048 prerender_data_(prerender_data
),
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
)
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(),
1074 base::Bind(&PrerenderManager::PendingSwap::OnMergeCompleted
,
1075 weak_factory_
.GetWeakPtr()));
1077 merge_timeout_
.Start(
1079 base::TimeDelta::FromMilliseconds(
1080 kSessionStorageNamespaceMergeTimeoutMs
),
1081 this, &PrerenderManager::PendingSwap::OnMergeTimeout
);
1084 void PrerenderManager::PendingSwap::AboutToNavigateRenderView(
1085 RenderViewHost
* render_view_host
) {
1086 if (seen_target_route_id_
) {
1087 // A second navigation began browser-side.
1088 prerender_data_
->ClearPendingSwap();
1092 seen_target_route_id_
= true;
1093 target_route_id_
= PrerenderTracker::ChildRouteIdPair(
1094 render_view_host
->GetMainFrame()->GetProcess()->GetID(),
1095 render_view_host
->GetMainFrame()->GetRoutingID());
1096 manager_
->prerender_tracker()->AddPrerenderPendingSwap(
1097 target_route_id_
, url_
);
1100 void PrerenderManager::PendingSwap::DidStartProvisionalLoadForFrame(
1101 content::RenderFrameHost
* render_frame_host
,
1102 const GURL
& validated_url
,
1104 bool is_iframe_srcdoc
) {
1105 if (render_frame_host
->GetParent())
1108 // We must only cancel the pending swap if the url navigated to is not
1109 // the URL being attempted to be swapped in. That's because in the normal
1110 // flow, a ProvisionalChangeToMainFrameUrl will happen for the URL attempted
1111 // to be swapped in immediately after the pending swap has issued its merge.
1112 if (validated_url
!= url_
)
1113 prerender_data_
->ClearPendingSwap();
1116 void PrerenderManager::PendingSwap::DidCommitProvisionalLoadForFrame(
1117 content::RenderFrameHost
* render_frame_host
,
1118 const GURL
& validated_url
,
1119 ui::PageTransition transition_type
) {
1120 if (render_frame_host
->GetParent())
1122 prerender_data_
->ClearPendingSwap();
1125 void PrerenderManager::PendingSwap::DidFailProvisionalLoad(
1126 content::RenderFrameHost
* render_frame_host
,
1127 const GURL
& validated_url
,
1129 const base::string16
& error_description
) {
1130 if (render_frame_host
->GetParent())
1132 prerender_data_
->ClearPendingSwap();
1135 void PrerenderManager::PendingSwap::WebContentsDestroyed() {
1136 prerender_data_
->ClearPendingSwap();
1139 void PrerenderManager::PendingSwap::RecordEvent(PrerenderEvent event
) const {
1140 manager_
->RecordEvent(prerender_data_
->contents(), event
);
1143 void PrerenderManager::PendingSwap::OnMergeCompleted(
1144 SessionStorageNamespace::MergeResult result
) {
1145 UMA_HISTOGRAM_TIMES("Prerender.SessionStorageNamespaceMergeTime",
1146 base::TimeTicks::Now() - start_time_
);
1147 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_MERGE_DONE
);
1149 // Log the exact merge result in a histogram.
1151 case SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_FOUND
:
1152 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NAMESPACE_NOT_FOUND
);
1154 case SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_ALIAS
:
1155 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NAMESPACE_NOT_ALIAS
);
1157 case SessionStorageNamespace::MERGE_RESULT_NOT_LOGGING
:
1158 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NOT_LOGGING
);
1160 case SessionStorageNamespace::MERGE_RESULT_NO_TRANSACTIONS
:
1161 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NO_TRANSACTIONS
);
1163 case SessionStorageNamespace::MERGE_RESULT_TOO_MANY_TRANSACTIONS
:
1164 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_TOO_MANY_TRANSACTIONS
);
1166 case SessionStorageNamespace::MERGE_RESULT_NOT_MERGEABLE
:
1167 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_NOT_MERGEABLE
);
1169 case SessionStorageNamespace::MERGE_RESULT_MERGEABLE
:
1170 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_RESULT_MERGEABLE
);
1176 if (result
!= SessionStorageNamespace::MERGE_RESULT_MERGEABLE
&&
1177 result
!= SessionStorageNamespace::MERGE_RESULT_NO_TRANSACTIONS
) {
1178 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_MERGE_FAILED
);
1179 prerender_data_
->ClearPendingSwap();
1183 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_SWAPPING_IN
);
1185 // Note that SwapInternal will, on success, delete |prerender_data_| and
1186 // |this|. It will also delete |this| in some failure cases. Pass in a new
1187 // GURL object rather than a reference to |url_|. Also hold on to |manager_|
1188 // and |prerender_data_|.
1190 // TODO(davidben): Can we make this less fragile?
1191 PrerenderManager
* manager
= manager_
;
1192 PrerenderData
* prerender_data
= prerender_data_
;
1193 WebContents
* new_web_contents
=
1194 manager_
->SwapInternal(GURL(url_
),
1197 should_replace_current_entry_
);
1198 if (!new_web_contents
) {
1199 manager
->RecordEvent(prerender_data
->contents(),
1200 PRERENDER_EVENT_MERGE_RESULT_SWAPIN_FAILED
);
1201 // Depending on whether SwapInternal called Destroy() or simply failed to
1202 // swap, |this| may or may not be deleted. Either way, if the swap failed,
1203 // |prerender_data| is deleted asynchronously, so this call is a no-op if
1204 // |this| is already gone.
1205 prerender_data
->ClearPendingSwap();
1209 void PrerenderManager::PendingSwap::OnMergeTimeout() {
1210 RecordEvent(PRERENDER_EVENT_MERGE_RESULT_TIMED_OUT
);
1211 prerender_data_
->ClearPendingSwap();
1214 void PrerenderManager::SetPrerenderContentsFactory(
1215 PrerenderContents::Factory
* prerender_contents_factory
) {
1216 DCHECK(CalledOnValidThread());
1217 prerender_contents_factory_
.reset(prerender_contents_factory
);
1220 void PrerenderManager::SourceNavigatedAway(PrerenderData
* prerender_data
) {
1221 // The expiry time of our prerender data will likely change because of
1222 // this navigation. This requires a resort of active_prerenders_.
1223 ScopedVector
<PrerenderData
>::iterator it
=
1224 std::find(active_prerenders_
.begin(), active_prerenders_
.end(),
1226 if (it
== active_prerenders_
.end())
1229 (*it
)->set_expiry_time(
1230 std::min((*it
)->expiry_time(),
1231 GetExpiryTimeForNavigatedAwayPrerender()));
1232 SortActivePrerenders();
1235 net::URLRequestContextGetter
* PrerenderManager::GetURLRequestContext() {
1236 return content::BrowserContext::GetDefaultStoragePartition(profile_
)->
1237 GetURLRequestContext();
1242 PrerenderHandle
* PrerenderManager::AddPrerender(
1245 const GURL
& url_arg
,
1246 const content::Referrer
& referrer
,
1247 const gfx::Size
& size
,
1248 SessionStorageNamespace
* session_storage_namespace
) {
1249 DCHECK(CalledOnValidThread());
1254 if ((origin
== ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN
||
1255 origin
== ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN
) &&
1256 IsGoogleSearchResultURL(referrer
.url
)) {
1257 origin
= ORIGIN_GWS_PRERENDER
;
1262 uint8 experiment
= GetQueryStringBasedExperiment(url_arg
);
1263 if (IsControlGroup(experiment
) &&
1264 MaybeGetQueryStringBasedAliasURL(url
, &alias_url
)) {
1268 // From here on, we will record a FinalStatus so we need to register with the
1269 // histogram tracking.
1270 histograms_
->RecordPrerender(origin
, url_arg
);
1272 if (PrerenderData
* preexisting_prerender_data
=
1273 FindPrerenderData(url
, session_storage_namespace
)) {
1274 RecordFinalStatusWithoutCreatingPrerenderContents(
1275 url
, origin
, experiment
, FINAL_STATUS_DUPLICATE
);
1276 return new PrerenderHandle(preexisting_prerender_data
);
1279 // Do not prerender if there are too many render processes, and we would
1280 // have to use an existing one. We do not want prerendering to happen in
1281 // a shared process, so that we can always reliably lower the CPU
1282 // priority for prerendering.
1283 // In single-process mode, ShouldTryToUseExistingProcessHost() always returns
1284 // true, so that case needs to be explicitly checked for.
1285 // TODO(tburkard): Figure out how to cancel prerendering in the opposite
1286 // case, when a new tab is added to a process used for prerendering.
1287 // TODO(ppi): Check whether there are usually enough render processes
1288 // available on Android. If not, kill an existing renderers so that we can
1289 // create a new one.
1290 if (content::RenderProcessHost::ShouldTryToUseExistingProcessHost(
1292 !content::RenderProcessHost::run_renderer_in_process()) {
1293 RecordFinalStatusWithoutCreatingPrerenderContents(
1294 url
, origin
, experiment
, FINAL_STATUS_TOO_MANY_PROCESSES
);
1298 // Check if enough time has passed since the last prerender.
1299 if (!DoesRateLimitAllowPrerender(origin
)) {
1300 // Cancel the prerender. We could add it to the pending prerender list but
1301 // this doesn't make sense as the next prerender request will be triggered
1302 // by a navigation and is unlikely to be the same site.
1303 RecordFinalStatusWithoutCreatingPrerenderContents(
1304 url
, origin
, experiment
, FINAL_STATUS_RATE_LIMIT_EXCEEDED
);
1308 if (IsPrerenderCookieStoreEnabled() && !cookie_store_loaded()) {
1309 // Only prerender if the cookie store for this profile has been loaded.
1310 // This is required by PrerenderCookieMonster.
1311 RecordFinalStatusWithoutCreatingPrerenderContents(
1312 url
, origin
, experiment
, FINAL_STATUS_COOKIE_STORE_NOT_LOADED
);
1316 PrerenderContents
* prerender_contents
= CreatePrerenderContents(
1317 url
, referrer
, origin
, experiment
);
1318 DCHECK(prerender_contents
);
1319 active_prerenders_
.push_back(
1320 new PrerenderData(this, prerender_contents
,
1321 GetExpiryTimeForNewPrerender(origin
)));
1322 if (!prerender_contents
->Init()) {
1323 DCHECK(active_prerenders_
.end() ==
1324 FindIteratorForPrerenderContents(prerender_contents
));
1328 histograms_
->RecordPrerenderStarted(origin
);
1329 DCHECK(!prerender_contents
->prerendering_has_started());
1331 PrerenderHandle
* prerender_handle
=
1332 new PrerenderHandle(active_prerenders_
.back());
1333 SortActivePrerenders();
1335 last_prerender_start_time_
= GetCurrentTimeTicks();
1337 gfx::Size contents_size
=
1338 size
.IsEmpty() ? config_
.default_tab_bounds
.size() : size
;
1340 net::URLRequestContextGetter
* request_context
=
1341 (IsPrerenderCookieStoreEnabled() ? GetURLRequestContext() : NULL
);
1343 prerender_contents
->StartPrerendering(process_id
, contents_size
,
1344 session_storage_namespace
,
1347 DCHECK(IsControlGroup(experiment
) ||
1348 prerender_contents
->prerendering_has_started() ||
1349 (origin
== ORIGIN_LOCAL_PREDICTOR
&&
1350 IsLocalPredictorPrerenderAlwaysControlEnabled()));
1352 if (GetMode() == PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP
)
1353 histograms_
->RecordConcurrency(active_prerenders_
.size());
1355 // Query the history to see if the URL being prerendered has ever been
1357 HistoryService
* history_service
= HistoryServiceFactory::GetForProfile(
1358 profile_
, Profile::EXPLICIT_ACCESS
);
1359 if (history_service
) {
1360 history_service
->QueryURL(
1363 base::Bind(&PrerenderManager::OnHistoryServiceDidQueryURL
,
1364 base::Unretained(this),
1367 &query_url_tracker_
);
1370 StartSchedulingPeriodicCleanups();
1371 return prerender_handle
;
1374 void PrerenderManager::StartSchedulingPeriodicCleanups() {
1375 DCHECK(CalledOnValidThread());
1376 if (repeating_timer_
.IsRunning())
1378 repeating_timer_
.Start(FROM_HERE
,
1379 base::TimeDelta::FromMilliseconds(kPeriodicCleanupIntervalMs
),
1381 &PrerenderManager::PeriodicCleanup
);
1384 void PrerenderManager::StopSchedulingPeriodicCleanups() {
1385 DCHECK(CalledOnValidThread());
1386 repeating_timer_
.Stop();
1389 void PrerenderManager::PeriodicCleanup() {
1390 DCHECK(CalledOnValidThread());
1392 base::ElapsedTimer resource_timer
;
1394 // Grab a copy of the current PrerenderContents pointers, so that we
1395 // will not interfere with potential deletions of the list.
1396 std::vector
<PrerenderContents
*>
1397 prerender_contents(active_prerenders_
.size());
1398 std::transform(active_prerenders_
.begin(), active_prerenders_
.end(),
1399 prerender_contents
.begin(),
1400 std::mem_fun(&PrerenderData::contents
));
1402 // And now check for prerenders using too much memory.
1403 std::for_each(prerender_contents
.begin(), prerender_contents
.end(),
1405 &PrerenderContents::DestroyWhenUsingTooManyResources
));
1407 // Measure how long the resource checks took. http://crbug.com/305419.
1408 UMA_HISTOGRAM_TIMES("Prerender.PeriodicCleanupResourceCheckTime",
1409 resource_timer
.Elapsed());
1411 base::ElapsedTimer cleanup_timer
;
1413 // Perform deferred cleanup work.
1414 DeleteOldWebContents();
1416 if (active_prerenders_
.empty())
1417 StopSchedulingPeriodicCleanups();
1419 to_delete_prerenders_
.clear();
1421 // Measure how long a the various cleanup tasks took. http://crbug.com/305419.
1422 UMA_HISTOGRAM_TIMES("Prerender.PeriodicCleanupDeleteContentsTime",
1423 cleanup_timer
.Elapsed());
1426 void PrerenderManager::PostCleanupTask() {
1427 DCHECK(CalledOnValidThread());
1428 base::MessageLoop::current()->PostTask(
1430 base::Bind(&PrerenderManager::PeriodicCleanup
, AsWeakPtr()));
1433 base::TimeTicks
PrerenderManager::GetExpiryTimeForNewPrerender(
1434 Origin origin
) const {
1435 base::TimeDelta ttl
= config_
.time_to_live
;
1436 if (origin
== ORIGIN_LOCAL_PREDICTOR
)
1437 ttl
= base::TimeDelta::FromSeconds(GetLocalPredictorTTLSeconds());
1438 return GetCurrentTimeTicks() + ttl
;
1441 base::TimeTicks
PrerenderManager::GetExpiryTimeForNavigatedAwayPrerender()
1443 return GetCurrentTimeTicks() + config_
.abandon_time_to_live
;
1446 void PrerenderManager::DeleteOldEntries() {
1447 DCHECK(CalledOnValidThread());
1448 while (!active_prerenders_
.empty()) {
1449 PrerenderData
* prerender_data
= active_prerenders_
.front();
1450 DCHECK(prerender_data
);
1451 DCHECK(prerender_data
->contents());
1453 if (prerender_data
->expiry_time() > GetCurrentTimeTicks())
1455 prerender_data
->contents()->Destroy(FINAL_STATUS_TIMED_OUT
);
1459 base::Time
PrerenderManager::GetCurrentTime() const {
1460 return base::Time::Now();
1463 base::TimeTicks
PrerenderManager::GetCurrentTimeTicks() const {
1464 return base::TimeTicks::Now();
1467 PrerenderContents
* PrerenderManager::CreatePrerenderContents(
1469 const content::Referrer
& referrer
,
1471 uint8 experiment_id
) {
1472 DCHECK(CalledOnValidThread());
1473 return prerender_contents_factory_
->CreatePrerenderContents(
1474 this, profile_
, url
, referrer
, origin
, experiment_id
);
1477 void PrerenderManager::SortActivePrerenders() {
1478 std::sort(active_prerenders_
.begin(), active_prerenders_
.end(),
1479 PrerenderData::OrderByExpiryTime());
1482 PrerenderManager::PrerenderData
* PrerenderManager::FindPrerenderData(
1484 const SessionStorageNamespace
* session_storage_namespace
) {
1485 for (ScopedVector
<PrerenderData
>::iterator it
= active_prerenders_
.begin();
1486 it
!= active_prerenders_
.end(); ++it
) {
1487 if ((*it
)->contents()->Matches(url
, session_storage_namespace
))
1493 PrerenderManager::PrerenderData
*
1494 PrerenderManager::FindPrerenderDataForTargetContents(
1495 WebContents
* target_contents
) {
1496 for (ScopedVector
<PrerenderData
>::iterator it
= active_prerenders_
.begin();
1497 it
!= active_prerenders_
.end(); ++it
) {
1498 if ((*it
)->pending_swap() &&
1499 (*it
)->pending_swap()->web_contents() == target_contents
)
1505 ScopedVector
<PrerenderManager::PrerenderData
>::iterator
1506 PrerenderManager::FindIteratorForPrerenderContents(
1507 PrerenderContents
* prerender_contents
) {
1508 for (ScopedVector
<PrerenderData
>::iterator it
= active_prerenders_
.begin();
1509 it
!= active_prerenders_
.end(); ++it
) {
1510 if (prerender_contents
== (*it
)->contents())
1513 return active_prerenders_
.end();
1516 bool PrerenderManager::DoesRateLimitAllowPrerender(Origin origin
) const {
1517 DCHECK(CalledOnValidThread());
1518 base::TimeDelta elapsed_time
=
1519 GetCurrentTimeTicks() - last_prerender_start_time_
;
1520 histograms_
->RecordTimeBetweenPrerenderRequests(origin
, elapsed_time
);
1521 if (!config_
.rate_limit_enabled
)
1523 // The LocalPredictor may issue multiple prerenders simultaneously (if so
1524 // configured), so no throttling.
1525 if (origin
== ORIGIN_LOCAL_PREDICTOR
)
1527 return elapsed_time
>=
1528 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs
);
1531 void PrerenderManager::DeleteOldWebContents() {
1532 while (!old_web_contents_list_
.empty()) {
1533 WebContents
* web_contents
= old_web_contents_list_
.front();
1534 old_web_contents_list_
.pop_front();
1535 // TODO(dominich): should we use Instant Unload Handler here?
1536 delete web_contents
;
1540 void PrerenderManager::CleanUpOldNavigations() {
1541 DCHECK(CalledOnValidThread());
1543 // Cutoff. Navigations before this cutoff can be discarded.
1544 base::TimeTicks cutoff
= GetCurrentTimeTicks() -
1545 base::TimeDelta::FromMilliseconds(kNavigationRecordWindowMs
);
1546 while (!navigations_
.empty()) {
1547 if (navigations_
.front().time
> cutoff
)
1549 navigations_
.pop_front();
1553 void PrerenderManager::ScheduleDeleteOldWebContents(
1555 OnCloseWebContentsDeleter
* deleter
) {
1556 old_web_contents_list_
.push_back(tab
);
1560 ScopedVector
<OnCloseWebContentsDeleter
>::iterator i
= std::find(
1561 on_close_web_contents_deleters_
.begin(),
1562 on_close_web_contents_deleters_
.end(),
1564 DCHECK(i
!= on_close_web_contents_deleters_
.end());
1565 on_close_web_contents_deleters_
.erase(i
);
1569 void PrerenderManager::AddToHistory(PrerenderContents
* contents
) {
1570 PrerenderHistory::Entry
entry(contents
->prerender_url(),
1571 contents
->final_status(),
1574 prerender_history_
->AddEntry(entry
);
1577 base::Value
* PrerenderManager::GetActivePrerendersAsValue() const {
1578 base::ListValue
* list_value
= new base::ListValue();
1579 for (ScopedVector
<PrerenderData
>::const_iterator it
=
1580 active_prerenders_
.begin();
1581 it
!= active_prerenders_
.end(); ++it
) {
1582 if (base::Value
* prerender_value
= (*it
)->contents()->GetAsValue())
1583 list_value
->Append(prerender_value
);
1588 void PrerenderManager::DestroyAllContents(FinalStatus final_status
) {
1589 DeleteOldWebContents();
1590 while (!active_prerenders_
.empty()) {
1591 PrerenderContents
* contents
= active_prerenders_
.front()->contents();
1592 contents
->Destroy(final_status
);
1594 to_delete_prerenders_
.clear();
1597 void PrerenderManager::DestroyAndMarkMatchCompleteAsUsed(
1598 PrerenderContents
* prerender_contents
,
1599 FinalStatus final_status
) {
1600 prerender_contents
->set_match_complete_status(
1601 PrerenderContents::MATCH_COMPLETE_REPLACED
);
1602 histograms_
->RecordFinalStatus(prerender_contents
->origin(),
1603 prerender_contents
->experiment_id(),
1604 PrerenderContents::MATCH_COMPLETE_REPLACEMENT
,
1605 FINAL_STATUS_WOULD_HAVE_BEEN_USED
);
1606 prerender_contents
->Destroy(final_status
);
1609 void PrerenderManager::RecordFinalStatusWithoutCreatingPrerenderContents(
1610 const GURL
& url
, Origin origin
, uint8 experiment_id
,
1611 FinalStatus final_status
) const {
1612 PrerenderHistory::Entry
entry(url
, final_status
, origin
, base::Time::Now());
1613 prerender_history_
->AddEntry(entry
);
1614 RecordFinalStatusWithMatchCompleteStatus(
1615 origin
, experiment_id
,
1616 PrerenderContents::MATCH_COMPLETE_DEFAULT
,
1620 void PrerenderManager::Observe(int type
,
1621 const content::NotificationSource
& source
,
1622 const content::NotificationDetails
& details
) {
1624 case chrome::NOTIFICATION_COOKIE_CHANGED
: {
1625 Profile
* profile
= content::Source
<Profile
>(source
).ptr();
1626 if (!profile
|| !profile_
->IsSameProfile(profile
) ||
1627 profile
->IsOffTheRecord()) {
1630 CookieChanged(content::Details
<ChromeCookieDetails
>(details
).ptr());
1633 case chrome::NOTIFICATION_PROFILE_DESTROYED
:
1634 DestroyAllContents(FINAL_STATUS_PROFILE_DESTROYED
);
1635 on_close_web_contents_deleters_
.clear();
1638 NOTREACHED() << "Unexpected notification sent.";
1643 void PrerenderManager::OnCreatingAudioStream(int render_process_id
,
1644 int render_frame_id
) {
1645 content::RenderFrameHost
* render_frame_host
=
1646 content::RenderFrameHost::FromID(render_process_id
, render_frame_id
);
1647 WebContents
* tab
= WebContents::FromRenderFrameHost(render_frame_host
);
1651 PrerenderContents
* prerender_contents
= GetPrerenderContents(tab
);
1652 if (!prerender_contents
)
1655 prerender_contents
->Destroy(prerender::FINAL_STATUS_CREATING_AUDIO_STREAM
);
1658 void PrerenderManager::RecordLikelyLoginOnURL(const GURL
& url
) {
1659 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1660 if (!url
.SchemeIsHTTPOrHTTPS())
1662 if (logged_in_predictor_table_
.get()) {
1663 BrowserThread::PostTask(
1666 base::Bind(&LoggedInPredictorTable::AddDomainFromURL
,
1667 logged_in_predictor_table_
,
1670 std::string key
= LoggedInPredictorTable::GetKey(url
);
1671 if (!logged_in_state_
.get())
1673 if (logged_in_state_
->count(key
))
1675 (*logged_in_state_
)[key
] = base::Time::Now().ToInternalValue();
1678 void PrerenderManager::CheckIfLikelyLoggedInOnURL(
1680 bool* lookup_result
,
1681 bool* database_was_present
,
1682 const base::Closure
& result_cb
) {
1683 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1684 if (!logged_in_predictor_table_
.get()) {
1685 *database_was_present
= false;
1686 *lookup_result
= false;
1687 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
, result_cb
);
1690 BrowserThread::PostTaskAndReply(
1691 BrowserThread::DB
, FROM_HERE
,
1692 base::Bind(&LoggedInPredictorTable::HasUserLoggedIn
,
1693 logged_in_predictor_table_
,
1696 database_was_present
),
1701 void PrerenderManager::CookieChanged(ChromeCookieDetails
* details
) {
1702 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1704 if (!logged_in_predictor_table_
.get())
1707 // We only care when a cookie has been removed.
1708 if (!details
->removed
)
1711 std::string domain_key
=
1712 LoggedInPredictorTable::GetKeyFromDomain(details
->cookie
->Domain());
1714 // If we have no record of this domain as a potentially logged in domain,
1715 // nothing to do here.
1716 if (logged_in_state_
.get() && logged_in_state_
->count(domain_key
) < 1)
1719 net::URLRequestContextGetter
* rq_context
= profile_
->GetRequestContext();
1723 BrowserThread::PostTask(
1724 BrowserThread::IO
, FROM_HERE
,
1725 base::Bind(&CheckIfCookiesExistForDomainOnIOThread
,
1726 base::Unretained(rq_context
),
1729 &PrerenderManager::CookieChangedAnyCookiesLeftLookupResult
,
1735 void PrerenderManager::CookieChangedAnyCookiesLeftLookupResult(
1736 const std::string
& domain_key
,
1737 bool cookies_exist
) {
1738 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1743 if (logged_in_predictor_table_
.get()) {
1744 BrowserThread::PostTask(BrowserThread::DB
,
1746 base::Bind(&LoggedInPredictorTable::DeleteDomain
,
1747 logged_in_predictor_table_
,
1751 if (logged_in_state_
.get())
1752 logged_in_state_
->erase(domain_key
);
1755 void PrerenderManager::LoggedInPredictorDataReceived(
1756 scoped_ptr
<LoggedInStateMap
> new_map
) {
1757 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1758 logged_in_state_
.swap(new_map
);
1761 void PrerenderManager::RecordEvent(PrerenderContents
* contents
,
1762 PrerenderEvent event
) const {
1764 histograms_
->RecordEvent(ORIGIN_NONE
, kNoExperiment
, event
);
1766 histograms_
->RecordEvent(contents
->origin(), contents
->experiment_id(),
1771 void PrerenderManager::RecordCookieEvent(int process_id
,
1774 const GURL
& frame_url
,
1775 bool is_for_blocking_resource
,
1776 PrerenderContents::CookieEvent event
,
1777 const net::CookieList
* cookie_list
) {
1778 RenderFrameHost
* rfh
= RenderFrameHost::FromID(process_id
, frame_id
);
1779 WebContents
* web_contents
= WebContents::FromRenderFrameHost(rfh
);
1783 bool is_main_frame
= (rfh
== web_contents
->GetMainFrame());
1785 bool is_third_party_cookie
=
1786 (!frame_url
.is_empty() &&
1787 !net::registry_controlled_domains::SameDomainOrHost(
1789 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES
));
1791 PrerenderContents
* prerender_contents
=
1792 PrerenderContents::FromWebContents(web_contents
);
1794 if (!prerender_contents
)
1797 base::Time earliest_create_date
;
1798 if (event
== PrerenderContents::COOKIE_EVENT_SEND
) {
1799 if (!cookie_list
|| cookie_list
->empty())
1801 for (size_t i
= 0; i
< cookie_list
->size(); i
++) {
1802 if (earliest_create_date
.is_null() ||
1803 (*cookie_list
)[i
].CreationDate() < earliest_create_date
) {
1804 earliest_create_date
= (*cookie_list
)[i
].CreationDate();
1809 prerender_contents
->RecordCookieEvent(event
,
1810 is_main_frame
&& url
== frame_url
,
1811 is_third_party_cookie
,
1812 is_for_blocking_resource
,
1813 earliest_create_date
);
1816 void PrerenderManager::RecordCookieStatus(Origin origin
,
1817 uint8 experiment_id
,
1818 int cookie_status
) const {
1819 histograms_
->RecordCookieStatus(origin
, experiment_id
, cookie_status
);
1822 void PrerenderManager::RecordCookieSendType(Origin origin
,
1823 uint8 experiment_id
,
1824 int cookie_send_type
) const {
1825 histograms_
->RecordCookieSendType(origin
, experiment_id
, cookie_send_type
);
1828 void PrerenderManager::OnHistoryServiceDidQueryURL(
1830 uint8 experiment_id
,
1832 const history::URLRow
& url_row
,
1833 const history::VisitVector
& /*visits*/) {
1834 histograms_
->RecordPrerenderPageVisitedStatus(origin
, experiment_id
, success
);
1838 void PrerenderManager::HangSessionStorageMergesForTesting() {
1839 g_hang_session_storage_merges_for_testing
= true;
1842 void PrerenderManager::RecordNetworkBytes(Origin origin
,
1844 int64 prerender_bytes
) {
1845 if (!ActuallyPrerendering())
1847 int64 recent_profile_bytes
=
1848 profile_network_bytes_
- last_recorded_profile_network_bytes_
;
1849 last_recorded_profile_network_bytes_
= profile_network_bytes_
;
1850 DCHECK_GE(recent_profile_bytes
, 0);
1851 histograms_
->RecordNetworkBytes(
1852 origin
, used
, prerender_bytes
, recent_profile_bytes
);
1855 bool PrerenderManager::IsEnabled() const {
1856 DCHECK(CalledOnValidThread());
1858 return chrome_browser_net::CanPrefetchAndPrerenderUI(profile_
->GetPrefs());
1861 void PrerenderManager::AddProfileNetworkBytesIfEnabled(int64 bytes
) {
1862 DCHECK_GE(bytes
, 0);
1863 if (IsEnabled() && ActuallyPrerendering())
1864 profile_network_bytes_
+= bytes
;
1867 void PrerenderManager::OnCookieStoreLoaded() {
1868 cookie_store_loaded_
= true;
1869 if (!on_cookie_store_loaded_cb_for_testing_
.is_null())
1870 on_cookie_store_loaded_cb_for_testing_
.Run();
1873 void PrerenderManager::AddPrerenderProcessHost(
1874 content::RenderProcessHost
* process_host
) {
1875 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1876 DCHECK(prerender_process_hosts_
.find(process_host
) ==
1877 prerender_process_hosts_
.end());
1878 prerender_process_hosts_
.insert(process_host
);
1879 process_host
->AddObserver(this);
1882 bool PrerenderManager::MayReuseProcessHost(
1883 content::RenderProcessHost
* process_host
) {
1884 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1885 // If prerender cookie stores are disabled, there is no need to require
1886 // isolated prerender processes.
1887 if (!IsPrerenderCookieStoreEnabled())
1889 return (prerender_process_hosts_
.find(process_host
) ==
1890 prerender_process_hosts_
.end());
1893 void PrerenderManager::RenderProcessHostDestroyed(
1894 content::RenderProcessHost
* host
) {
1895 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1896 prerender_process_hosts_
.erase(host
);
1897 BrowserThread::PostTask(
1898 BrowserThread::IO
, FROM_HERE
,
1899 base::Bind(&PrerenderTracker::RemovePrerenderCookieStoreOnIOThread
,
1900 base::Unretained(prerender_tracker()), host
->GetID(), false));
1903 } // namespace prerender