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/location.h"
15 #include "base/logging.h"
16 #include "base/metrics/histogram.h"
17 #include "base/single_thread_task_runner.h"
18 #include "base/thread_task_runner_handle.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/net/prediction_options.h"
25 #include "chrome/browser/prerender/prerender_contents.h"
26 #include "chrome/browser/prerender/prerender_field_trial.h"
27 #include "chrome/browser/prerender/prerender_final_status.h"
28 #include "chrome/browser/prerender/prerender_handle.h"
29 #include "chrome/browser/prerender/prerender_histograms.h"
30 #include "chrome/browser/prerender/prerender_history.h"
31 #include "chrome/browser/prerender/prerender_manager_factory.h"
32 #include "chrome/browser/prerender/prerender_tab_helper.h"
33 #include "chrome/browser/prerender/prerender_util.h"
34 #include "chrome/browser/profiles/profile.h"
35 #include "chrome/browser/search/search.h"
36 #include "chrome/browser/tab_contents/tab_util.h"
37 #include "chrome/browser/ui/browser_navigator.h"
38 #include "chrome/browser/ui/tab_contents/core_tab_helper.h"
39 #include "chrome/browser/ui/tab_contents/core_tab_helper_delegate.h"
40 #include "chrome/common/chrome_switches.h"
41 #include "chrome/common/prerender_types.h"
42 #include "content/public/browser/browser_thread.h"
43 #include "content/public/browser/devtools_agent_host.h"
44 #include "content/public/browser/navigation_controller.h"
45 #include "content/public/browser/notification_service.h"
46 #include "content/public/browser/notification_source.h"
47 #include "content/public/browser/render_frame_host.h"
48 #include "content/public/browser/render_process_host.h"
49 #include "content/public/browser/render_view_host.h"
50 #include "content/public/browser/resource_request_details.h"
51 #include "content/public/browser/session_storage_namespace.h"
52 #include "content/public/browser/site_instance.h"
53 #include "content/public/browser/web_contents.h"
54 #include "content/public/browser/web_contents_delegate.h"
55 #include "content/public/common/url_constants.h"
56 #include "extensions/common/constants.h"
58 using content::BrowserThread
;
59 using content::RenderViewHost
;
60 using content::SessionStorageNamespace
;
61 using content::WebContents
;
67 // Time interval at which periodic cleanups are performed.
68 const int kPeriodicCleanupIntervalMs
= 1000;
70 // Valid HTTP methods for prerendering.
71 const char* const kValidHttpMethods
[] = {
79 // Length of prerender history, for display in chrome://net-internals
80 const int kHistoryLength
= 100;
82 // Indicates whether a Prerender has been cancelled such that we need
83 // a dummy replacement for the purpose of recording the correct PPLT for
84 // the Match Complete case.
85 // Traditionally, "Match" means that a prerendered page was actually visited &
86 // the prerender was used. Our goal is to have "Match" cases line up in the
87 // control group & the experiment group, so that we can make meaningful
88 // comparisons of improvements. However, in the control group, since we don't
89 // actually perform prerenders, many of the cancellation reasons cannot be
90 // detected. Therefore, in the Prerender group, when we cancel for one of these
91 // reasons, we keep track of a dummy Prerender representing what we would
92 // have in the control group. If that dummy prerender in the prerender group
93 // would then be swapped in (but isn't actually b/c it's a dummy), we record
94 // this as a MatchComplete. This allows us to compare MatchComplete's
95 // across Prerender & Control group which ideally should be lining up.
96 // This ensures that there is no bias in terms of the page load times
97 // of the pages forming the difference between the two sets.
99 bool NeedMatchCompleteDummyForFinalStatus(FinalStatus final_status
) {
100 return final_status
!= FINAL_STATUS_USED
&&
101 final_status
!= FINAL_STATUS_TIMED_OUT
&&
102 final_status
!= FINAL_STATUS_MANAGER_SHUTDOWN
&&
103 final_status
!= FINAL_STATUS_PROFILE_DESTROYED
&&
104 final_status
!= FINAL_STATUS_APP_TERMINATING
&&
105 final_status
!= FINAL_STATUS_WINDOW_OPENER
&&
106 final_status
!= FINAL_STATUS_CACHE_OR_HISTORY_CLEARED
&&
107 final_status
!= FINAL_STATUS_CANCELLED
&&
108 final_status
!= FINAL_STATUS_DEVTOOLS_ATTACHED
&&
109 final_status
!= FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING
&&
110 final_status
!= FINAL_STATUS_PAGE_BEING_CAPTURED
&&
111 final_status
!= FINAL_STATUS_NAVIGATION_UNCOMMITTED
&&
112 final_status
!= FINAL_STATUS_NON_EMPTY_BROWSING_INSTANCE
;
117 class PrerenderManager::OnCloseWebContentsDeleter
118 : public content::WebContentsDelegate
,
119 public base::SupportsWeakPtr
<
120 PrerenderManager::OnCloseWebContentsDeleter
> {
122 OnCloseWebContentsDeleter(PrerenderManager
* manager
,
126 suppressed_dialog_(false) {
127 tab_
->SetDelegate(this);
128 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
130 base::Bind(&OnCloseWebContentsDeleter::ScheduleWebContentsForDeletion
,
132 base::TimeDelta::FromSeconds(kDeleteWithExtremePrejudiceSeconds
));
135 void CloseContents(WebContents
* source
) override
{
136 DCHECK_EQ(tab_
, source
);
137 ScheduleWebContentsForDeletion(false);
140 void SwappedOut(WebContents
* source
) override
{
141 DCHECK_EQ(tab_
, source
);
142 ScheduleWebContentsForDeletion(false);
145 bool ShouldSuppressDialogs(WebContents
* source
) override
{
146 // Use this as a proxy for getting statistics on how often we fail to honor
147 // the beforeunload event.
148 DCHECK_EQ(tab_
, source
);
149 suppressed_dialog_
= true;
154 static const int kDeleteWithExtremePrejudiceSeconds
= 3;
156 void ScheduleWebContentsForDeletion(bool timeout
) {
157 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterTimeout", timeout
);
158 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterSuppressedDialog",
160 tab_
->SetDelegate(NULL
);
161 manager_
->ScheduleDeleteOldWebContents(tab_
.release(), this);
162 // |this| is deleted at this point.
165 PrerenderManager
* manager_
;
166 scoped_ptr
<WebContents
> tab_
;
167 bool suppressed_dialog_
;
169 DISALLOW_COPY_AND_ASSIGN(OnCloseWebContentsDeleter
);
173 int PrerenderManager::prerenders_per_session_count_
= 0;
176 PrerenderManager::PrerenderManagerMode
PrerenderManager::mode_
=
177 PRERENDER_MODE_ENABLED
;
179 struct PrerenderManager::NavigationRecord
{
180 NavigationRecord(const GURL
& url
, base::TimeTicks time
)
186 base::TimeTicks time
;
189 PrerenderManager::PrerenderManager(Profile
* profile
)
191 prerender_contents_factory_(PrerenderContents::CreateFactory()),
192 last_prerender_start_time_(GetCurrentTimeTicks() -
193 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs
)),
194 prerender_history_(new PrerenderHistory(kHistoryLength
)),
195 histograms_(new PrerenderHistograms()),
196 profile_network_bytes_(0),
197 last_recorded_profile_network_bytes_(0) {
198 // There are some assumptions that the PrerenderManager is on the UI thread.
199 // Any other checks simply make sure that the PrerenderManager is accessed on
200 // the same thread that it was created on.
201 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
203 // Certain experiments override our default config_ values.
204 switch (PrerenderManager::GetMode()) {
205 case PrerenderManager::PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP
:
206 config_
.max_link_concurrency
= 4;
207 config_
.max_link_concurrency_per_launcher
= 2;
209 case PrerenderManager::PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP
:
210 config_
.time_to_live
= base::TimeDelta::FromMinutes(15);
216 notification_registrar_
.Add(
217 this, chrome::NOTIFICATION_PROFILE_DESTROYED
,
218 content::Source
<Profile
>(profile_
));
220 MediaCaptureDevicesDispatcher::GetInstance()->AddObserver(this);
223 PrerenderManager::~PrerenderManager() {
224 MediaCaptureDevicesDispatcher::GetInstance()->RemoveObserver(this);
226 // The earlier call to KeyedService::Shutdown() should have
227 // emptied these vectors already.
228 DCHECK(active_prerenders_
.empty());
229 DCHECK(to_delete_prerenders_
.empty());
231 for (PrerenderProcessSet::const_iterator it
=
232 prerender_process_hosts_
.begin();
233 it
!= prerender_process_hosts_
.end();
235 (*it
)->RemoveObserver(this);
239 void PrerenderManager::Shutdown() {
240 DestroyAllContents(FINAL_STATUS_MANAGER_SHUTDOWN
);
241 on_close_web_contents_deleters_
.clear();
244 DCHECK(active_prerenders_
.empty());
247 PrerenderHandle
* PrerenderManager::AddPrerenderFromLinkRelPrerender(
251 const uint32 rel_types
,
252 const content::Referrer
& referrer
,
253 const gfx::Size
& size
) {
254 Origin origin
= rel_types
& PrerenderRelTypePrerender
?
255 ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN
:
256 ORIGIN_LINK_REL_NEXT
;
257 SessionStorageNamespace
* session_storage_namespace
= NULL
;
258 // Unit tests pass in a process_id == -1.
259 if (process_id
!= -1) {
260 RenderViewHost
* source_render_view_host
=
261 RenderViewHost::FromID(process_id
, route_id
);
262 if (!source_render_view_host
)
264 WebContents
* source_web_contents
=
265 WebContents::FromRenderViewHost(source_render_view_host
);
266 if (!source_web_contents
)
268 if (origin
== ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN
&&
269 source_web_contents
->GetURL().host() == url
.host()) {
270 origin
= ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN
;
272 // TODO(ajwong): This does not correctly handle storage for isolated apps.
273 session_storage_namespace
=
274 source_web_contents
->GetController()
275 .GetDefaultSessionStorageNamespace();
278 return AddPrerender(origin
, url
, referrer
, size
, session_storage_namespace
);
281 PrerenderHandle
* PrerenderManager::AddPrerenderFromOmnibox(
283 SessionStorageNamespace
* session_storage_namespace
,
284 const gfx::Size
& size
) {
285 if (!IsOmniboxEnabled(profile_
))
287 return AddPrerender(ORIGIN_OMNIBOX
, url
, content::Referrer(), size
,
288 session_storage_namespace
);
291 PrerenderHandle
* PrerenderManager::AddPrerenderFromExternalRequest(
293 const content::Referrer
& referrer
,
294 SessionStorageNamespace
* session_storage_namespace
,
295 const gfx::Size
& size
) {
296 return AddPrerender(ORIGIN_EXTERNAL_REQUEST
, url
, referrer
, size
,
297 session_storage_namespace
);
300 PrerenderHandle
* PrerenderManager::AddPrerenderForInstant(
302 content::SessionStorageNamespace
* session_storage_namespace
,
303 const gfx::Size
& size
) {
304 DCHECK(chrome::ShouldPrefetchSearchResults());
305 return AddPrerender(ORIGIN_INSTANT
, url
, content::Referrer(), size
,
306 session_storage_namespace
);
309 void PrerenderManager::CancelAllPrerenders() {
310 DCHECK(CalledOnValidThread());
311 while (!active_prerenders_
.empty()) {
312 PrerenderContents
* prerender_contents
=
313 active_prerenders_
.front()->contents();
314 prerender_contents
->Destroy(FINAL_STATUS_CANCELLED
);
318 bool PrerenderManager::MaybeUsePrerenderedPage(const GURL
& url
,
319 chrome::NavigateParams
* params
) {
320 DCHECK(CalledOnValidThread());
322 content::WebContents
* web_contents
= params
->target_contents
;
323 DCHECK(!IsWebContentsPrerendering(web_contents
, NULL
));
325 // Don't prerender if the navigation involves some special parameters.
326 if (params
->uses_post
|| !params
->extra_headers
.empty())
330 to_delete_prerenders_
.clear();
332 // First, try to find prerender data with the correct session storage
334 // TODO(ajwong): This doesn't handle isolated apps correctly.
335 PrerenderData
* prerender_data
= FindPrerenderData(
337 web_contents
->GetController().GetDefaultSessionStorageNamespace());
340 DCHECK(prerender_data
->contents());
342 WebContents
* new_web_contents
= SwapInternal(
343 url
, web_contents
, prerender_data
,
344 params
->should_replace_current_entry
);
345 if (!new_web_contents
)
348 // Record the new target_contents for the callers.
349 params
->target_contents
= new_web_contents
;
353 WebContents
* PrerenderManager::SwapInternal(
355 WebContents
* web_contents
,
356 PrerenderData
* prerender_data
,
357 bool should_replace_current_entry
) {
358 DCHECK(CalledOnValidThread());
359 DCHECK(!IsWebContentsPrerendering(web_contents
, NULL
));
361 // Only swap if the target WebContents has a CoreTabHelper delegate to swap
362 // out of it. For a normal WebContents, this is if it is in a TabStripModel.
363 CoreTabHelper
* core_tab_helper
= CoreTabHelper::FromWebContents(web_contents
);
364 if (!core_tab_helper
|| !core_tab_helper
->delegate())
367 PrerenderTabHelper
* target_tab_helper
=
368 PrerenderTabHelper::FromWebContents(web_contents
);
369 if (!target_tab_helper
) {
374 if (WebContents
* new_web_contents
=
375 prerender_data
->contents()->prerender_contents()) {
376 if (web_contents
== new_web_contents
)
377 return NULL
; // Do not swap in to ourself.
379 // We cannot swap in if there is no last committed entry, because we would
380 // show a blank page under an existing entry from the current tab. Even if
381 // there is a pending entry, it may not commit.
382 // TODO(creis): If there is a pending navigation and no last committed
383 // entry, we might be able to transfer the network request instead.
384 if (!new_web_contents
->GetController().CanPruneAllButLastCommitted()) {
385 // Abort this prerender so it is not used later. http://crbug.com/292121
386 prerender_data
->contents()->Destroy(FINAL_STATUS_NAVIGATION_UNCOMMITTED
);
391 // Do not swap if the target WebContents is not the only WebContents in its
392 // current BrowsingInstance.
393 if (web_contents
->GetSiteInstance()->GetRelatedActiveContentsCount() != 1u) {
395 web_contents
->GetSiteInstance()->GetRelatedActiveContentsCount(), 1u);
396 prerender_data
->contents()->Destroy(
397 FINAL_STATUS_NON_EMPTY_BROWSING_INSTANCE
);
401 // Do not use the prerendered version if there is an opener object.
402 if (web_contents
->HasOpener()) {
403 prerender_data
->contents()->Destroy(FINAL_STATUS_WINDOW_OPENER
);
407 // Do not swap in the prerender if the current WebContents is being captured.
408 if (web_contents
->GetCapturerCount() > 0) {
409 prerender_data
->contents()->Destroy(FINAL_STATUS_PAGE_BEING_CAPTURED
);
413 // If we are just in the control group (which can be detected by noticing
414 // that prerendering hasn't even started yet), record that |web_contents| now
415 // would be showing a prerendered contents, but otherwise, don't do anything.
416 if (!prerender_data
->contents()->prerendering_has_started()) {
417 target_tab_helper
->WouldHavePrerenderedNextLoad(
418 prerender_data
->contents()->origin());
419 prerender_data
->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED
);
423 // Don't use prerendered pages if debugger is attached to the tab.
424 // See http://crbug.com/98541
425 if (content::DevToolsAgentHost::IsDebuggerAttached(web_contents
)) {
426 DestroyAndMarkMatchCompleteAsUsed(prerender_data
->contents(),
427 FINAL_STATUS_DEVTOOLS_ATTACHED
);
431 // If the prerendered page is in the middle of a cross-site navigation,
432 // don't swap it in because there isn't a good way to merge histories.
433 if (prerender_data
->contents()->IsCrossSiteNavigationPending()) {
434 DestroyAndMarkMatchCompleteAsUsed(
435 prerender_data
->contents(),
436 FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING
);
440 // For bookkeeping purposes, we need to mark this WebContents to
441 // reflect that it would have been prerendered.
442 if (GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP
) {
443 target_tab_helper
->WouldHavePrerenderedNextLoad(
444 prerender_data
->contents()->origin());
445 prerender_data
->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED
);
449 // At this point, we've determined that we will use the prerender.
450 content::RenderProcessHost
* process_host
=
451 prerender_data
->contents()->GetRenderViewHost()->GetProcess();
452 process_host
->RemoveObserver(this);
453 prerender_process_hosts_
.erase(process_host
);
454 if (!prerender_data
->contents()->load_start_time().is_null()) {
455 histograms_
->RecordTimeUntilUsed(
456 prerender_data
->contents()->origin(),
457 GetCurrentTimeTicks() - prerender_data
->contents()->load_start_time());
459 histograms_
->RecordAbandonTimeUntilUsed(
460 prerender_data
->contents()->origin(),
461 prerender_data
->abandon_time().is_null() ?
463 GetCurrentTimeTicks() - prerender_data
->abandon_time());
465 histograms_
->RecordPerSessionCount(prerender_data
->contents()->origin(),
466 ++prerenders_per_session_count_
);
467 histograms_
->RecordUsedPrerender(prerender_data
->contents()->origin());
469 ScopedVector
<PrerenderData
>::iterator to_erase
=
470 FindIteratorForPrerenderContents(prerender_data
->contents());
471 DCHECK(active_prerenders_
.end() != to_erase
);
472 DCHECK_EQ(prerender_data
, *to_erase
);
473 scoped_ptr
<PrerenderContents
>
474 prerender_contents(prerender_data
->ReleaseContents());
475 active_prerenders_
.erase(to_erase
);
477 // Mark prerender as used.
478 prerender_contents
->PrepareForUse();
480 WebContents
* new_web_contents
=
481 prerender_contents
->ReleasePrerenderContents();
482 WebContents
* old_web_contents
= web_contents
;
483 DCHECK(new_web_contents
);
484 DCHECK(old_web_contents
);
486 // Merge the browsing history.
487 new_web_contents
->GetController().CopyStateFromAndPrune(
488 &old_web_contents
->GetController(),
489 should_replace_current_entry
);
490 CoreTabHelper::FromWebContents(old_web_contents
)->delegate()->
491 SwapTabContents(old_web_contents
,
494 prerender_contents
->has_finished_loading());
495 prerender_contents
->CommitHistory(new_web_contents
);
497 // Update PPLT metrics:
498 // If the tab has finished loading, record a PPLT of 0.
499 // If the tab is still loading, reset its start time to the current time.
500 PrerenderTabHelper
* prerender_tab_helper
=
501 PrerenderTabHelper::FromWebContents(new_web_contents
);
502 DCHECK(prerender_tab_helper
!= NULL
);
503 prerender_tab_helper
->PrerenderSwappedIn();
505 if (old_web_contents
->NeedToFireBeforeUnload()) {
506 // Schedule the delete to occur after the tab has run its unload handlers.
507 // TODO(davidben): Honor the beforeunload event. http://crbug.com/304932
508 on_close_web_contents_deleters_
.push_back(
509 new OnCloseWebContentsDeleter(this, old_web_contents
));
510 old_web_contents
->DispatchBeforeUnload(false);
512 // No unload handler to run, so delete asap.
513 ScheduleDeleteOldWebContents(old_web_contents
, NULL
);
516 // TODO(cbentzel): Should prerender_contents move to the pending delete
517 // list, instead of deleting directly here?
518 AddToHistory(prerender_contents
.get());
519 RecordNavigation(url
);
520 return new_web_contents
;
523 void PrerenderManager::MoveEntryToPendingDelete(PrerenderContents
* entry
,
524 FinalStatus final_status
) {
525 DCHECK(CalledOnValidThread());
528 ScopedVector
<PrerenderData
>::iterator it
=
529 FindIteratorForPrerenderContents(entry
);
530 DCHECK(it
!= active_prerenders_
.end());
532 // If this PrerenderContents is being deleted due to a cancellation any time
533 // after the prerender has started then we need to create a dummy replacement
534 // for PPLT accounting purposes for the Match Complete group. This is the case
535 // if the cancellation is for any reason that would not occur in the control
537 if (entry
->prerendering_has_started() &&
538 entry
->match_complete_status() ==
539 PrerenderContents::MATCH_COMPLETE_DEFAULT
&&
540 NeedMatchCompleteDummyForFinalStatus(final_status
) &&
541 ActuallyPrerendering() &&
542 GetMode() == PRERENDER_MODE_EXPERIMENT_MATCH_COMPLETE_GROUP
) {
543 // TODO(tburkard): I'd like to DCHECK that we are actually prerendering.
544 // However, what if new conditions are added and
545 // NeedMatchCompleteDummyForFinalStatus is not being updated. Not sure
546 // what's the best thing to do here. For now, I will just check whether
547 // we are actually prerendering.
548 (*it
)->MakeIntoMatchCompleteReplacement();
550 to_delete_prerenders_
.push_back(*it
);
551 active_prerenders_
.weak_erase(it
);
554 // Destroy the old WebContents relatively promptly to reduce resource usage.
558 void PrerenderManager::RecordPageLoadTimeNotSwappedIn(
560 base::TimeDelta page_load_time
,
562 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
563 histograms_
->RecordPageLoadTimeNotSwappedIn(origin
, page_load_time
, url
);
566 void PrerenderManager::RecordPerceivedPageLoadTime(
568 NavigationType navigation_type
,
569 base::TimeDelta perceived_page_load_time
,
570 double fraction_plt_elapsed_at_swap_in
,
572 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
576 histograms_
->RecordPerceivedPageLoadTime(
577 origin
, perceived_page_load_time
, navigation_type
, url
);
579 if (navigation_type
== NAVIGATION_TYPE_PRERENDERED
) {
580 histograms_
->RecordPercentLoadDoneAtSwapin(
581 origin
, fraction_plt_elapsed_at_swap_in
);
586 PrerenderManager::PrerenderManagerMode
PrerenderManager::GetMode() {
591 void PrerenderManager::SetMode(PrerenderManagerMode mode
) {
596 const char* PrerenderManager::GetModeString() {
598 case PRERENDER_MODE_DISABLED
:
600 case PRERENDER_MODE_ENABLED
:
601 case PRERENDER_MODE_EXPERIMENT_PRERENDER_GROUP
:
603 case PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP
:
605 case PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP
:
607 case PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP
:
609 case PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP
:
611 case PRERENDER_MODE_EXPERIMENT_MATCH_COMPLETE_GROUP
:
612 return "_MatchComplete";
613 case PRERENDER_MODE_MAX
:
615 NOTREACHED() << "Invalid PrerenderManager mode.";
622 bool PrerenderManager::IsPrerenderingPossible() {
623 return GetMode() != PRERENDER_MODE_DISABLED
;
627 bool PrerenderManager::ActuallyPrerendering() {
628 return IsPrerenderingPossible() && !IsControlGroup();
632 bool PrerenderManager::IsControlGroup() {
633 return GetMode() == PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP
;
637 bool PrerenderManager::IsNoUseGroup() {
638 return GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP
;
641 bool PrerenderManager::IsWebContentsPrerendering(
642 const WebContents
* web_contents
,
643 Origin
* origin
) const {
644 DCHECK(CalledOnValidThread());
645 if (PrerenderContents
* prerender_contents
=
646 GetPrerenderContents(web_contents
)) {
648 *origin
= prerender_contents
->origin();
654 bool PrerenderManager::HasPrerenderedUrl(
656 content::WebContents
* web_contents
) const {
657 content::SessionStorageNamespace
* session_storage_namespace
= web_contents
->
658 GetController().GetDefaultSessionStorageNamespace();
660 for (ScopedVector
<PrerenderData
>::const_iterator it
=
661 active_prerenders_
.begin();
662 it
!= active_prerenders_
.end(); ++it
) {
663 PrerenderContents
* prerender_contents
= (*it
)->contents();
664 if (prerender_contents
->Matches(url
, session_storage_namespace
)) {
671 PrerenderContents
* PrerenderManager::GetPrerenderContents(
672 const content::WebContents
* web_contents
) const {
673 DCHECK(CalledOnValidThread());
674 for (ScopedVector
<PrerenderData
>::const_iterator it
=
675 active_prerenders_
.begin();
676 it
!= active_prerenders_
.end(); ++it
) {
677 WebContents
* prerender_web_contents
=
678 (*it
)->contents()->prerender_contents();
679 if (prerender_web_contents
== web_contents
) {
680 return (*it
)->contents();
684 // Also check the pending-deletion list. If the prerender is in pending
685 // delete, anyone with a handle on the WebContents needs to know.
686 for (ScopedVector
<PrerenderData
>::const_iterator it
=
687 to_delete_prerenders_
.begin();
688 it
!= to_delete_prerenders_
.end(); ++it
) {
689 WebContents
* prerender_web_contents
=
690 (*it
)->contents()->prerender_contents();
691 if (prerender_web_contents
== web_contents
) {
692 return (*it
)->contents();
698 PrerenderContents
* PrerenderManager::GetPrerenderContentsForRoute(
700 int route_id
) const {
701 content::WebContents
* web_contents
=
702 tab_util::GetWebContentsByID(child_id
, route_id
);
703 if (web_contents
== NULL
)
705 return GetPrerenderContents(web_contents
);
708 const std::vector
<WebContents
*>
709 PrerenderManager::GetAllPrerenderingContents() const {
710 DCHECK(CalledOnValidThread());
711 std::vector
<WebContents
*> result
;
713 for (ScopedVector
<PrerenderData
>::const_iterator it
=
714 active_prerenders_
.begin();
715 it
!= active_prerenders_
.end(); ++it
) {
716 if (WebContents
* contents
= (*it
)->contents()->prerender_contents())
717 result
.push_back(contents
);
723 bool PrerenderManager::HasRecentlyBeenNavigatedTo(Origin origin
,
725 DCHECK(CalledOnValidThread());
727 CleanUpOldNavigations();
728 std::list
<NavigationRecord
>::const_reverse_iterator end
= navigations_
.rend();
729 for (std::list
<NavigationRecord
>::const_reverse_iterator it
=
730 navigations_
.rbegin();
733 if (it
->url
== url
) {
734 base::TimeDelta delta
= GetCurrentTimeTicks() - it
->time
;
735 histograms_
->RecordTimeSinceLastRecentVisit(origin
, delta
);
744 bool PrerenderManager::IsValidHttpMethod(const std::string
& method
) {
745 // method has been canonicalized to upper case at this point so we can just
747 DCHECK_EQ(method
, base::StringToUpperASCII(method
));
748 for (size_t i
= 0; i
< arraysize(kValidHttpMethods
); ++i
) {
749 if (method
.compare(kValidHttpMethods
[i
]) == 0)
757 bool PrerenderManager::DoesURLHaveValidScheme(const GURL
& url
) {
758 return (url
.SchemeIsHTTPOrHTTPS() ||
759 url
.SchemeIs(extensions::kExtensionScheme
) ||
760 url
.SchemeIs("data"));
764 bool PrerenderManager::DoesSubresourceURLHaveValidScheme(const GURL
& url
) {
765 return DoesURLHaveValidScheme(url
) || url
== GURL(url::kAboutBlankURL
);
768 base::DictionaryValue
* PrerenderManager::GetAsValue() const {
769 DCHECK(CalledOnValidThread());
770 base::DictionaryValue
* dict_value
= new base::DictionaryValue();
771 dict_value
->Set("history", prerender_history_
->GetEntriesAsValue());
772 dict_value
->Set("active", GetActivePrerendersAsValue());
773 dict_value
->SetBoolean("enabled", IsEnabled());
774 dict_value
->SetBoolean("omnibox_enabled", IsOmniboxEnabled(profile_
));
775 // If prerender is disabled via a flag this method is not even called.
776 std::string enabled_note
;
777 if (IsControlGroup())
778 enabled_note
+= "(Control group: Not actually prerendering) ";
780 enabled_note
+= "(No-use group: Not swapping in prerendered pages) ";
781 if (GetMode() == PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP
)
783 "(15 min TTL group: Extended prerender eviction to 15 mins) ";
784 dict_value
->SetString("enabled_note", enabled_note
);
788 void PrerenderManager::ClearData(int clear_flags
) {
789 DCHECK_GE(clear_flags
, 0);
790 DCHECK_LT(clear_flags
, CLEAR_MAX
);
791 if (clear_flags
& CLEAR_PRERENDER_CONTENTS
)
792 DestroyAllContents(FINAL_STATUS_CACHE_OR_HISTORY_CLEARED
);
793 // This has to be second, since destroying prerenders can add to the history.
794 if (clear_flags
& CLEAR_PRERENDER_HISTORY
)
795 prerender_history_
->Clear();
798 void PrerenderManager::RecordFinalStatusWithMatchCompleteStatus(
800 PrerenderContents::MatchCompleteStatus mc_status
,
801 FinalStatus final_status
) const {
802 histograms_
->RecordFinalStatus(origin
, mc_status
, final_status
);
805 void PrerenderManager::RecordNavigation(const GURL
& url
) {
806 DCHECK(CalledOnValidThread());
808 navigations_
.push_back(NavigationRecord(url
, GetCurrentTimeTicks()));
809 CleanUpOldNavigations();
813 struct PrerenderManager::PrerenderData::OrderByExpiryTime
{
814 bool operator()(const PrerenderData
* a
, const PrerenderData
* b
) const {
815 return a
->expiry_time() < b
->expiry_time();
819 PrerenderManager::PrerenderData::PrerenderData(PrerenderManager
* manager
,
820 PrerenderContents
* contents
,
821 base::TimeTicks expiry_time
)
825 expiry_time_(expiry_time
) {
826 DCHECK_NE(static_cast<PrerenderContents
*>(NULL
), contents_
);
829 PrerenderManager::PrerenderData::~PrerenderData() {
832 void PrerenderManager::PrerenderData::MakeIntoMatchCompleteReplacement() {
834 contents_
->set_match_complete_status(
835 PrerenderContents::MATCH_COMPLETE_REPLACED
);
836 PrerenderData
* to_delete
= new PrerenderData(manager_
, contents_
.release(),
838 contents_
.reset(to_delete
->contents_
->CreateMatchCompleteReplacement());
839 manager_
->to_delete_prerenders_
.push_back(to_delete
);
842 void PrerenderManager::PrerenderData::OnHandleCreated(PrerenderHandle
* handle
) {
843 DCHECK_NE(static_cast<PrerenderContents
*>(NULL
), contents_
);
845 contents_
->AddObserver(handle
);
848 void PrerenderManager::PrerenderData::OnHandleNavigatedAway(
849 PrerenderHandle
* handle
) {
850 DCHECK_LT(0, handle_count_
);
851 DCHECK_NE(static_cast<PrerenderContents
*>(NULL
), contents_
);
852 if (abandon_time_
.is_null())
853 abandon_time_
= base::TimeTicks::Now();
854 // We intentionally don't decrement the handle count here, so that the
855 // prerender won't be canceled until it times out.
856 manager_
->SourceNavigatedAway(this);
859 void PrerenderManager::PrerenderData::OnHandleCanceled(
860 PrerenderHandle
* handle
) {
861 DCHECK_LT(0, handle_count_
);
862 DCHECK_NE(static_cast<PrerenderContents
*>(NULL
), contents_
);
864 if (--handle_count_
== 0) {
865 // This will eventually remove this object from active_prerenders_.
866 contents_
->Destroy(FINAL_STATUS_CANCELLED
);
870 PrerenderContents
* PrerenderManager::PrerenderData::ReleaseContents() {
871 return contents_
.release();
874 void PrerenderManager::SetPrerenderContentsFactory(
875 PrerenderContents::Factory
* prerender_contents_factory
) {
876 DCHECK(CalledOnValidThread());
877 prerender_contents_factory_
.reset(prerender_contents_factory
);
880 void PrerenderManager::SourceNavigatedAway(PrerenderData
* prerender_data
) {
881 // The expiry time of our prerender data will likely change because of
882 // this navigation. This requires a resort of active_prerenders_.
883 ScopedVector
<PrerenderData
>::iterator it
=
884 std::find(active_prerenders_
.begin(), active_prerenders_
.end(),
886 if (it
== active_prerenders_
.end())
889 (*it
)->set_expiry_time(
890 std::min((*it
)->expiry_time(),
891 GetExpiryTimeForNavigatedAwayPrerender()));
892 SortActivePrerenders();
896 PrerenderHandle
* PrerenderManager::AddPrerender(
899 const content::Referrer
& referrer
,
900 const gfx::Size
& size
,
901 SessionStorageNamespace
* session_storage_namespace
) {
902 DCHECK(CalledOnValidThread());
907 if ((origin
== ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN
||
908 origin
== ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN
) &&
909 IsGoogleSearchResultURL(referrer
.url
)) {
910 origin
= ORIGIN_GWS_PRERENDER
;
915 if (IsControlGroup() && MaybeGetQueryStringBasedAliasURL(url
, &alias_url
))
918 // From here on, we will record a FinalStatus so we need to register with the
919 // histogram tracking.
920 histograms_
->RecordPrerender(origin
, url_arg
);
922 if (PrerenderData
* preexisting_prerender_data
=
923 FindPrerenderData(url
, session_storage_namespace
)) {
924 RecordFinalStatusWithoutCreatingPrerenderContents(
925 url
, origin
, FINAL_STATUS_DUPLICATE
);
926 return new PrerenderHandle(preexisting_prerender_data
);
929 // Do not prerender if there are too many render processes, and we would
930 // have to use an existing one. We do not want prerendering to happen in
931 // a shared process, so that we can always reliably lower the CPU
932 // priority for prerendering.
933 // In single-process mode, ShouldTryToUseExistingProcessHost() always returns
934 // true, so that case needs to be explicitly checked for.
935 // TODO(tburkard): Figure out how to cancel prerendering in the opposite
936 // case, when a new tab is added to a process used for prerendering.
937 // TODO(ppi): Check whether there are usually enough render processes
938 // available on Android. If not, kill an existing renderers so that we can
940 if (content::RenderProcessHost::ShouldTryToUseExistingProcessHost(
942 !content::RenderProcessHost::run_renderer_in_process()) {
943 RecordFinalStatusWithoutCreatingPrerenderContents(
944 url
, origin
, FINAL_STATUS_TOO_MANY_PROCESSES
);
948 // Check if enough time has passed since the last prerender.
949 if (!DoesRateLimitAllowPrerender(origin
)) {
950 // Cancel the prerender. We could add it to the pending prerender list but
951 // this doesn't make sense as the next prerender request will be triggered
952 // by a navigation and is unlikely to be the same site.
953 RecordFinalStatusWithoutCreatingPrerenderContents(
954 url
, origin
, FINAL_STATUS_RATE_LIMIT_EXCEEDED
);
958 PrerenderContents
* prerender_contents
= CreatePrerenderContents(url
, referrer
,
960 DCHECK(prerender_contents
);
961 active_prerenders_
.push_back(
962 new PrerenderData(this, prerender_contents
,
963 GetExpiryTimeForNewPrerender(origin
)));
964 if (!prerender_contents
->Init()) {
965 DCHECK(active_prerenders_
.end() ==
966 FindIteratorForPrerenderContents(prerender_contents
));
970 histograms_
->RecordPrerenderStarted(origin
);
971 DCHECK(!prerender_contents
->prerendering_has_started());
973 PrerenderHandle
* prerender_handle
=
974 new PrerenderHandle(active_prerenders_
.back());
975 SortActivePrerenders();
977 last_prerender_start_time_
= GetCurrentTimeTicks();
979 gfx::Size contents_size
=
980 size
.IsEmpty() ? config_
.default_tab_bounds
.size() : size
;
982 prerender_contents
->StartPrerendering(contents_size
,
983 session_storage_namespace
);
985 DCHECK(IsControlGroup() || prerender_contents
->prerendering_has_started());
987 if (GetMode() == PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP
)
988 histograms_
->RecordConcurrency(active_prerenders_
.size());
990 StartSchedulingPeriodicCleanups();
991 return prerender_handle
;
994 void PrerenderManager::StartSchedulingPeriodicCleanups() {
995 DCHECK(CalledOnValidThread());
996 if (repeating_timer_
.IsRunning())
998 repeating_timer_
.Start(FROM_HERE
,
999 base::TimeDelta::FromMilliseconds(kPeriodicCleanupIntervalMs
),
1001 &PrerenderManager::PeriodicCleanup
);
1004 void PrerenderManager::StopSchedulingPeriodicCleanups() {
1005 DCHECK(CalledOnValidThread());
1006 repeating_timer_
.Stop();
1009 void PrerenderManager::PeriodicCleanup() {
1010 DCHECK(CalledOnValidThread());
1012 base::ElapsedTimer resource_timer
;
1014 // Grab a copy of the current PrerenderContents pointers, so that we
1015 // will not interfere with potential deletions of the list.
1016 std::vector
<PrerenderContents
*>
1017 prerender_contents(active_prerenders_
.size());
1018 std::transform(active_prerenders_
.begin(), active_prerenders_
.end(),
1019 prerender_contents
.begin(),
1020 std::mem_fun(&PrerenderData::contents
));
1022 // And now check for prerenders using too much memory.
1023 std::for_each(prerender_contents
.begin(), prerender_contents
.end(),
1025 &PrerenderContents::DestroyWhenUsingTooManyResources
));
1027 // Measure how long the resource checks took. http://crbug.com/305419.
1028 UMA_HISTOGRAM_TIMES("Prerender.PeriodicCleanupResourceCheckTime",
1029 resource_timer
.Elapsed());
1031 base::ElapsedTimer cleanup_timer
;
1033 // Perform deferred cleanup work.
1034 DeleteOldWebContents();
1036 if (active_prerenders_
.empty())
1037 StopSchedulingPeriodicCleanups();
1039 to_delete_prerenders_
.clear();
1041 // Measure how long a the various cleanup tasks took. http://crbug.com/305419.
1042 UMA_HISTOGRAM_TIMES("Prerender.PeriodicCleanupDeleteContentsTime",
1043 cleanup_timer
.Elapsed());
1046 void PrerenderManager::PostCleanupTask() {
1047 DCHECK(CalledOnValidThread());
1048 base::ThreadTaskRunnerHandle::Get()->PostTask(
1049 FROM_HERE
, base::Bind(&PrerenderManager::PeriodicCleanup
, AsWeakPtr()));
1052 base::TimeTicks
PrerenderManager::GetExpiryTimeForNewPrerender(
1053 Origin origin
) const {
1054 return GetCurrentTimeTicks() + config_
.time_to_live
;
1057 base::TimeTicks
PrerenderManager::GetExpiryTimeForNavigatedAwayPrerender()
1059 return GetCurrentTimeTicks() + config_
.abandon_time_to_live
;
1062 void PrerenderManager::DeleteOldEntries() {
1063 DCHECK(CalledOnValidThread());
1064 while (!active_prerenders_
.empty()) {
1065 PrerenderData
* prerender_data
= active_prerenders_
.front();
1066 DCHECK(prerender_data
);
1067 DCHECK(prerender_data
->contents());
1069 if (prerender_data
->expiry_time() > GetCurrentTimeTicks())
1071 prerender_data
->contents()->Destroy(FINAL_STATUS_TIMED_OUT
);
1075 base::Time
PrerenderManager::GetCurrentTime() const {
1076 return base::Time::Now();
1079 base::TimeTicks
PrerenderManager::GetCurrentTimeTicks() const {
1080 return base::TimeTicks::Now();
1083 PrerenderContents
* PrerenderManager::CreatePrerenderContents(
1085 const content::Referrer
& referrer
,
1087 DCHECK(CalledOnValidThread());
1088 return prerender_contents_factory_
->CreatePrerenderContents(
1089 this, profile_
, url
, referrer
, origin
);
1092 void PrerenderManager::SortActivePrerenders() {
1093 std::sort(active_prerenders_
.begin(), active_prerenders_
.end(),
1094 PrerenderData::OrderByExpiryTime());
1097 PrerenderManager::PrerenderData
* PrerenderManager::FindPrerenderData(
1099 const SessionStorageNamespace
* session_storage_namespace
) {
1100 for (ScopedVector
<PrerenderData
>::iterator it
= active_prerenders_
.begin();
1101 it
!= active_prerenders_
.end(); ++it
) {
1102 if ((*it
)->contents()->Matches(url
, session_storage_namespace
))
1108 ScopedVector
<PrerenderManager::PrerenderData
>::iterator
1109 PrerenderManager::FindIteratorForPrerenderContents(
1110 PrerenderContents
* prerender_contents
) {
1111 for (ScopedVector
<PrerenderData
>::iterator it
= active_prerenders_
.begin();
1112 it
!= active_prerenders_
.end(); ++it
) {
1113 if (prerender_contents
== (*it
)->contents())
1116 return active_prerenders_
.end();
1119 bool PrerenderManager::DoesRateLimitAllowPrerender(Origin origin
) const {
1120 DCHECK(CalledOnValidThread());
1121 base::TimeDelta elapsed_time
=
1122 GetCurrentTimeTicks() - last_prerender_start_time_
;
1123 histograms_
->RecordTimeBetweenPrerenderRequests(origin
, elapsed_time
);
1124 if (!config_
.rate_limit_enabled
)
1126 return elapsed_time
>=
1127 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs
);
1130 void PrerenderManager::DeleteOldWebContents() {
1131 while (!old_web_contents_list_
.empty()) {
1132 WebContents
* web_contents
= old_web_contents_list_
.front();
1133 old_web_contents_list_
.pop_front();
1134 // TODO(dominich): should we use Instant Unload Handler here?
1135 delete web_contents
;
1139 void PrerenderManager::CleanUpOldNavigations() {
1140 DCHECK(CalledOnValidThread());
1142 // Cutoff. Navigations before this cutoff can be discarded.
1143 base::TimeTicks cutoff
= GetCurrentTimeTicks() -
1144 base::TimeDelta::FromMilliseconds(kNavigationRecordWindowMs
);
1145 while (!navigations_
.empty()) {
1146 if (navigations_
.front().time
> cutoff
)
1148 navigations_
.pop_front();
1152 void PrerenderManager::ScheduleDeleteOldWebContents(
1154 OnCloseWebContentsDeleter
* deleter
) {
1155 old_web_contents_list_
.push_back(tab
);
1159 ScopedVector
<OnCloseWebContentsDeleter
>::iterator i
= std::find(
1160 on_close_web_contents_deleters_
.begin(),
1161 on_close_web_contents_deleters_
.end(),
1163 DCHECK(i
!= on_close_web_contents_deleters_
.end());
1164 on_close_web_contents_deleters_
.erase(i
);
1168 void PrerenderManager::AddToHistory(PrerenderContents
* contents
) {
1169 PrerenderHistory::Entry
entry(contents
->prerender_url(),
1170 contents
->final_status(),
1173 prerender_history_
->AddEntry(entry
);
1176 base::Value
* PrerenderManager::GetActivePrerendersAsValue() const {
1177 base::ListValue
* list_value
= new base::ListValue();
1178 for (ScopedVector
<PrerenderData
>::const_iterator it
=
1179 active_prerenders_
.begin();
1180 it
!= active_prerenders_
.end(); ++it
) {
1181 if (base::Value
* prerender_value
= (*it
)->contents()->GetAsValue())
1182 list_value
->Append(prerender_value
);
1187 void PrerenderManager::DestroyAllContents(FinalStatus final_status
) {
1188 DeleteOldWebContents();
1189 while (!active_prerenders_
.empty()) {
1190 PrerenderContents
* contents
= active_prerenders_
.front()->contents();
1191 contents
->Destroy(final_status
);
1193 to_delete_prerenders_
.clear();
1196 void PrerenderManager::DestroyAndMarkMatchCompleteAsUsed(
1197 PrerenderContents
* prerender_contents
,
1198 FinalStatus final_status
) {
1199 prerender_contents
->set_match_complete_status(
1200 PrerenderContents::MATCH_COMPLETE_REPLACED
);
1201 histograms_
->RecordFinalStatus(prerender_contents
->origin(),
1202 PrerenderContents::MATCH_COMPLETE_REPLACEMENT
,
1203 FINAL_STATUS_WOULD_HAVE_BEEN_USED
);
1204 prerender_contents
->Destroy(final_status
);
1207 void PrerenderManager::RecordFinalStatusWithoutCreatingPrerenderContents(
1208 const GURL
& url
, Origin origin
, FinalStatus final_status
) const {
1209 PrerenderHistory::Entry
entry(url
, final_status
, origin
, base::Time::Now());
1210 prerender_history_
->AddEntry(entry
);
1211 RecordFinalStatusWithMatchCompleteStatus(
1212 origin
, PrerenderContents::MATCH_COMPLETE_DEFAULT
, final_status
);
1215 void PrerenderManager::Observe(int type
,
1216 const content::NotificationSource
& source
,
1217 const content::NotificationDetails
& details
) {
1219 case chrome::NOTIFICATION_PROFILE_DESTROYED
:
1220 DestroyAllContents(FINAL_STATUS_PROFILE_DESTROYED
);
1221 on_close_web_contents_deleters_
.clear();
1224 NOTREACHED() << "Unexpected notification sent.";
1229 void PrerenderManager::OnCreatingAudioStream(int render_process_id
,
1230 int render_frame_id
) {
1231 content::RenderFrameHost
* render_frame_host
=
1232 content::RenderFrameHost::FromID(render_process_id
, render_frame_id
);
1233 WebContents
* tab
= WebContents::FromRenderFrameHost(render_frame_host
);
1237 PrerenderContents
* prerender_contents
= GetPrerenderContents(tab
);
1238 if (!prerender_contents
)
1241 prerender_contents
->Destroy(prerender::FINAL_STATUS_CREATING_AUDIO_STREAM
);
1244 void PrerenderManager::RecordNetworkBytes(Origin origin
,
1246 int64 prerender_bytes
) {
1247 if (!ActuallyPrerendering())
1249 int64 recent_profile_bytes
=
1250 profile_network_bytes_
- last_recorded_profile_network_bytes_
;
1251 last_recorded_profile_network_bytes_
= profile_network_bytes_
;
1252 DCHECK_GE(recent_profile_bytes
, 0);
1253 histograms_
->RecordNetworkBytes(
1254 origin
, used
, prerender_bytes
, recent_profile_bytes
);
1257 bool PrerenderManager::IsEnabled() const {
1258 DCHECK(CalledOnValidThread());
1260 return chrome_browser_net::CanPrefetchAndPrerenderUI(profile_
->GetPrefs());
1263 void PrerenderManager::AddProfileNetworkBytesIfEnabled(int64 bytes
) {
1264 DCHECK_GE(bytes
, 0);
1265 if (IsEnabled() && ActuallyPrerendering())
1266 profile_network_bytes_
+= bytes
;
1269 void PrerenderManager::AddPrerenderProcessHost(
1270 content::RenderProcessHost
* process_host
) {
1271 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
1272 DCHECK(prerender_process_hosts_
.find(process_host
) ==
1273 prerender_process_hosts_
.end());
1274 prerender_process_hosts_
.insert(process_host
);
1275 process_host
->AddObserver(this);
1278 bool PrerenderManager::MayReuseProcessHost(
1279 content::RenderProcessHost
* process_host
) {
1280 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
1281 // Isolate prerender processes to make the resource monitoring check more
1283 return (prerender_process_hosts_
.find(process_host
) ==
1284 prerender_process_hosts_
.end());
1287 void PrerenderManager::RenderProcessHostDestroyed(
1288 content::RenderProcessHost
* host
) {
1289 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
1290 size_t erased
= prerender_process_hosts_
.erase(host
);
1291 DCHECK_EQ(1u, erased
);
1294 } // namespace prerender