[Media Router] Add integration tests and e2e tests for media router and presentation...
[chromium-blink-merge.git] / chrome / browser / prerender / prerender_manager.cc
blobf06bb5b7a2df586c85c6c997dd96505d9b81c2f5
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/prerender/prerender_manager.h"
7 #include <algorithm>
8 #include <functional>
9 #include <string>
10 #include <vector>
12 #include "base/bind.h"
13 #include "base/bind_helpers.h"
14 #include "base/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;
63 namespace prerender {
65 namespace {
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[] = {
72 "GET",
73 "HEAD",
74 "OPTIONS",
75 "POST",
76 "TRACE",
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;
115 } // namespace
117 class PrerenderManager::OnCloseWebContentsDeleter
118 : public content::WebContentsDelegate,
119 public base::SupportsWeakPtr<
120 PrerenderManager::OnCloseWebContentsDeleter> {
121 public:
122 OnCloseWebContentsDeleter(PrerenderManager* manager,
123 WebContents* tab)
124 : manager_(manager),
125 tab_(tab),
126 suppressed_dialog_(false) {
127 tab_->SetDelegate(this);
128 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
129 FROM_HERE,
130 base::Bind(&OnCloseWebContentsDeleter::ScheduleWebContentsForDeletion,
131 AsWeakPtr(), true),
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;
150 return true;
153 private:
154 static const int kDeleteWithExtremePrejudiceSeconds = 3;
156 void ScheduleWebContentsForDeletion(bool timeout) {
157 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterTimeout", timeout);
158 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterSuppressedDialog",
159 suppressed_dialog_);
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);
172 // static
173 int PrerenderManager::prerenders_per_session_count_ = 0;
175 // static
176 PrerenderManager::PrerenderManagerMode PrerenderManager::mode_ =
177 PRERENDER_MODE_ENABLED;
179 struct PrerenderManager::NavigationRecord {
180 NavigationRecord(const GURL& url, base::TimeTicks time)
181 : url(url),
182 time(time) {
185 GURL url;
186 base::TimeTicks time;
189 PrerenderManager::PrerenderManager(Profile* profile)
190 : 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;
208 break;
209 case PrerenderManager::PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP:
210 config_.time_to_live = base::TimeDelta::FromMinutes(15);
211 break;
212 default:
213 break;
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();
234 ++it) {
235 (*it)->RemoveObserver(this);
239 void PrerenderManager::Shutdown() {
240 DestroyAllContents(FINAL_STATUS_MANAGER_SHUTDOWN);
241 on_close_web_contents_deleters_.clear();
242 profile_ = NULL;
244 DCHECK(active_prerenders_.empty());
247 PrerenderHandle* PrerenderManager::AddPrerenderFromLinkRelPrerender(
248 int process_id,
249 int route_id,
250 const GURL& url,
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)
263 return NULL;
264 WebContents* source_web_contents =
265 WebContents::FromRenderViewHost(source_render_view_host);
266 if (!source_web_contents)
267 return NULL;
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(
282 const GURL& url,
283 SessionStorageNamespace* session_storage_namespace,
284 const gfx::Size& size) {
285 if (!IsOmniboxEnabled(profile_))
286 return NULL;
287 return AddPrerender(ORIGIN_OMNIBOX, url, content::Referrer(), size,
288 session_storage_namespace);
291 PrerenderHandle* PrerenderManager::AddPrerenderFromExternalRequest(
292 const GURL& url,
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(
301 const GURL& url,
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())
327 return false;
329 DeleteOldEntries();
330 to_delete_prerenders_.clear();
332 // First, try to find prerender data with the correct session storage
333 // namespace.
334 // TODO(ajwong): This doesn't handle isolated apps correctly.
335 PrerenderData* prerender_data = FindPrerenderData(
336 url,
337 web_contents->GetController().GetDefaultSessionStorageNamespace());
338 if (!prerender_data)
339 return false;
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)
346 return false;
348 // Record the new target_contents for the callers.
349 params->target_contents = new_web_contents;
350 return true;
353 WebContents* PrerenderManager::SwapInternal(
354 const GURL& url,
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())
365 return NULL;
367 PrerenderTabHelper* target_tab_helper =
368 PrerenderTabHelper::FromWebContents(web_contents);
369 if (!target_tab_helper) {
370 NOTREACHED();
371 return NULL;
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);
387 return NULL;
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) {
394 DCHECK_GT(
395 web_contents->GetSiteInstance()->GetRelatedActiveContentsCount(), 1u);
396 prerender_data->contents()->Destroy(
397 FINAL_STATUS_NON_EMPTY_BROWSING_INSTANCE);
398 return NULL;
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);
404 return NULL;
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);
410 return NULL;
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);
420 return NULL;
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);
428 return NULL;
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);
437 return NULL;
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);
446 return NULL;
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() ?
462 base::TimeDelta() :
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,
492 new_web_contents,
493 true,
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);
511 } else {
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());
526 DCHECK(entry);
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
536 // group case.
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();
549 } else {
550 to_delete_prerenders_.push_back(*it);
551 active_prerenders_.weak_erase(it);
554 // Destroy the old WebContents relatively promptly to reduce resource usage.
555 PostCleanupTask();
558 void PrerenderManager::RecordPageLoadTimeNotSwappedIn(
559 Origin origin,
560 base::TimeDelta page_load_time,
561 const GURL& url) {
562 DCHECK_CURRENTLY_ON(BrowserThread::UI);
563 histograms_->RecordPageLoadTimeNotSwappedIn(origin, page_load_time, url);
566 void PrerenderManager::RecordPerceivedPageLoadTime(
567 Origin origin,
568 NavigationType navigation_type,
569 base::TimeDelta perceived_page_load_time,
570 double fraction_plt_elapsed_at_swap_in,
571 const GURL& url) {
572 DCHECK_CURRENTLY_ON(BrowserThread::UI);
573 if (!IsEnabled())
574 return;
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);
585 // static
586 PrerenderManager::PrerenderManagerMode PrerenderManager::GetMode() {
587 return mode_;
590 // static
591 void PrerenderManager::SetMode(PrerenderManagerMode mode) {
592 mode_ = mode;
595 // static
596 const char* PrerenderManager::GetModeString() {
597 switch (mode_) {
598 case PRERENDER_MODE_DISABLED:
599 return "_Disabled";
600 case PRERENDER_MODE_ENABLED:
601 case PRERENDER_MODE_EXPERIMENT_PRERENDER_GROUP:
602 return "_Enabled";
603 case PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP:
604 return "_Control";
605 case PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP:
606 return "_Multi";
607 case PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP:
608 return "_15MinTTL";
609 case PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP:
610 return "_NoUse";
611 case PRERENDER_MODE_EXPERIMENT_MATCH_COMPLETE_GROUP:
612 return "_MatchComplete";
613 case PRERENDER_MODE_MAX:
614 default:
615 NOTREACHED() << "Invalid PrerenderManager mode.";
616 break;
618 return "";
621 // static
622 bool PrerenderManager::IsPrerenderingPossible() {
623 return GetMode() != PRERENDER_MODE_DISABLED;
626 // static
627 bool PrerenderManager::ActuallyPrerendering() {
628 return IsPrerenderingPossible() && !IsControlGroup();
631 // static
632 bool PrerenderManager::IsControlGroup() {
633 return GetMode() == PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP;
636 // static
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)) {
647 if (origin)
648 *origin = prerender_contents->origin();
649 return true;
651 return false;
654 bool PrerenderManager::HasPrerenderedUrl(
655 GURL url,
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)) {
665 return true;
668 return false;
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();
695 return NULL;
698 PrerenderContents* PrerenderManager::GetPrerenderContentsForRoute(
699 int child_id,
700 int route_id) const {
701 content::WebContents* web_contents =
702 tab_util::GetWebContentsByID(child_id, route_id);
703 if (web_contents == NULL)
704 return 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);
720 return result;
723 bool PrerenderManager::HasRecentlyBeenNavigatedTo(Origin origin,
724 const GURL& url) {
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();
731 it != end;
732 ++it) {
733 if (it->url == url) {
734 base::TimeDelta delta = GetCurrentTimeTicks() - it->time;
735 histograms_->RecordTimeSinceLastRecentVisit(origin, delta);
736 return true;
740 return false;
743 // static
744 bool PrerenderManager::IsValidHttpMethod(const std::string& method) {
745 // method has been canonicalized to upper case at this point so we can just
746 // compare them.
747 DCHECK_EQ(method, base::StringToUpperASCII(method));
748 for (size_t i = 0; i < arraysize(kValidHttpMethods); ++i) {
749 if (method.compare(kValidHttpMethods[i]) == 0)
750 return true;
753 return false;
756 // static
757 bool PrerenderManager::DoesURLHaveValidScheme(const GURL& url) {
758 return (url.SchemeIsHTTPOrHTTPS() ||
759 url.SchemeIs(extensions::kExtensionScheme) ||
760 url.SchemeIs("data"));
763 // static
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) ";
779 if (IsNoUseGroup())
780 enabled_note += "(No-use group: Not swapping in prerendered pages) ";
781 if (GetMode() == PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP)
782 enabled_note +=
783 "(15 min TTL group: Extended prerender eviction to 15 mins) ";
784 dict_value->SetString("enabled_note", enabled_note);
785 return dict_value;
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(
799 Origin origin,
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();
812 // protected
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)
822 : manager_(manager),
823 contents_(contents),
824 handle_count_(0),
825 expiry_time_(expiry_time) {
826 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
829 PrerenderManager::PrerenderData::~PrerenderData() {
832 void PrerenderManager::PrerenderData::MakeIntoMatchCompleteReplacement() {
833 DCHECK(contents_);
834 contents_->set_match_complete_status(
835 PrerenderContents::MATCH_COMPLETE_REPLACED);
836 PrerenderData* to_delete = new PrerenderData(manager_, contents_.release(),
837 expiry_time_);
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_);
844 ++handle_count_;
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(),
885 prerender_data);
886 if (it == active_prerenders_.end())
887 return;
889 (*it)->set_expiry_time(
890 std::min((*it)->expiry_time(),
891 GetExpiryTimeForNavigatedAwayPrerender()));
892 SortActivePrerenders();
895 // private
896 PrerenderHandle* PrerenderManager::AddPrerender(
897 Origin origin,
898 const GURL& url_arg,
899 const content::Referrer& referrer,
900 const gfx::Size& size,
901 SessionStorageNamespace* session_storage_namespace) {
902 DCHECK(CalledOnValidThread());
904 if (!IsEnabled())
905 return NULL;
907 if ((origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN ||
908 origin == ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN) &&
909 IsGoogleSearchResultURL(referrer.url)) {
910 origin = ORIGIN_GWS_PRERENDER;
913 GURL url = url_arg;
914 GURL alias_url;
915 if (IsControlGroup() && MaybeGetQueryStringBasedAliasURL(url, &alias_url))
916 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
939 // create a new one.
940 if (content::RenderProcessHost::ShouldTryToUseExistingProcessHost(
941 profile_, url) &&
942 !content::RenderProcessHost::run_renderer_in_process()) {
943 RecordFinalStatusWithoutCreatingPrerenderContents(
944 url, origin, FINAL_STATUS_TOO_MANY_PROCESSES);
945 return NULL;
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);
955 return NULL;
958 PrerenderContents* prerender_contents = CreatePrerenderContents(url, referrer,
959 origin);
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));
967 return NULL;
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())
997 return;
998 repeating_timer_.Start(FROM_HERE,
999 base::TimeDelta::FromMilliseconds(kPeriodicCleanupIntervalMs),
1000 this,
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(),
1024 std::mem_fun(
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();
1035 DeleteOldEntries();
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()
1058 const {
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())
1070 return;
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(
1084 const GURL& url,
1085 const content::Referrer& referrer,
1086 Origin origin) {
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(
1098 const GURL& url,
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))
1103 return *it;
1105 return NULL;
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())
1114 return it;
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)
1125 return true;
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)
1147 break;
1148 navigations_.pop_front();
1152 void PrerenderManager::ScheduleDeleteOldWebContents(
1153 WebContents* tab,
1154 OnCloseWebContentsDeleter* deleter) {
1155 old_web_contents_list_.push_back(tab);
1156 PostCleanupTask();
1158 if (deleter) {
1159 ScopedVector<OnCloseWebContentsDeleter>::iterator i = std::find(
1160 on_close_web_contents_deleters_.begin(),
1161 on_close_web_contents_deleters_.end(),
1162 deleter);
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(),
1171 contents->origin(),
1172 base::Time::Now());
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);
1184 return list_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) {
1218 switch (type) {
1219 case chrome::NOTIFICATION_PROFILE_DESTROYED:
1220 DestroyAllContents(FINAL_STATUS_PROFILE_DESTROYED);
1221 on_close_web_contents_deleters_.clear();
1222 break;
1223 default:
1224 NOTREACHED() << "Unexpected notification sent.";
1225 break;
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);
1234 if (!tab)
1235 return;
1237 PrerenderContents* prerender_contents = GetPrerenderContents(tab);
1238 if (!prerender_contents)
1239 return;
1241 prerender_contents->Destroy(prerender::FINAL_STATUS_CREATING_AUDIO_STREAM);
1244 void PrerenderManager::RecordNetworkBytes(Origin origin,
1245 bool used,
1246 int64 prerender_bytes) {
1247 if (!ActuallyPrerendering())
1248 return;
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
1282 // accurate.
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