Update broken references to image assets
[chromium-blink-merge.git] / chrome / browser / prerender / prerender_manager.cc
blobe5b76b79238961019e8020255db60659812c0034
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 "components/search/search.h"
43 #include "content/public/browser/browser_thread.h"
44 #include "content/public/browser/devtools_agent_host.h"
45 #include "content/public/browser/navigation_controller.h"
46 #include "content/public/browser/notification_service.h"
47 #include "content/public/browser/notification_source.h"
48 #include "content/public/browser/render_frame_host.h"
49 #include "content/public/browser/render_process_host.h"
50 #include "content/public/browser/render_view_host.h"
51 #include "content/public/browser/resource_request_details.h"
52 #include "content/public/browser/session_storage_namespace.h"
53 #include "content/public/browser/site_instance.h"
54 #include "content/public/browser/web_contents.h"
55 #include "content/public/browser/web_contents_delegate.h"
56 #include "content/public/common/url_constants.h"
57 #include "extensions/common/constants.h"
59 using content::BrowserThread;
60 using content::RenderViewHost;
61 using content::SessionStorageNamespace;
62 using content::WebContents;
64 namespace prerender {
66 namespace {
68 // Time interval at which periodic cleanups are performed.
69 const int kPeriodicCleanupIntervalMs = 1000;
71 // Valid HTTP methods for prerendering.
72 const char* const kValidHttpMethods[] = {
73 "GET",
74 "HEAD",
75 "OPTIONS",
76 "POST",
77 "TRACE",
80 // Length of prerender history, for display in chrome://net-internals
81 const int kHistoryLength = 100;
83 // Indicates whether a Prerender has been cancelled such that we need
84 // a dummy replacement for the purpose of recording the correct PPLT for
85 // the Match Complete case.
86 // Traditionally, "Match" means that a prerendered page was actually visited &
87 // the prerender was used. Our goal is to have "Match" cases line up in the
88 // control group & the experiment group, so that we can make meaningful
89 // comparisons of improvements. However, in the control group, since we don't
90 // actually perform prerenders, many of the cancellation reasons cannot be
91 // detected. Therefore, in the Prerender group, when we cancel for one of these
92 // reasons, we keep track of a dummy Prerender representing what we would
93 // have in the control group. If that dummy prerender in the prerender group
94 // would then be swapped in (but isn't actually b/c it's a dummy), we record
95 // this as a MatchComplete. This allows us to compare MatchComplete's
96 // across Prerender & Control group which ideally should be lining up.
97 // This ensures that there is no bias in terms of the page load times
98 // of the pages forming the difference between the two sets.
100 bool NeedMatchCompleteDummyForFinalStatus(FinalStatus final_status) {
101 return final_status != FINAL_STATUS_USED &&
102 final_status != FINAL_STATUS_TIMED_OUT &&
103 final_status != FINAL_STATUS_MANAGER_SHUTDOWN &&
104 final_status != FINAL_STATUS_PROFILE_DESTROYED &&
105 final_status != FINAL_STATUS_APP_TERMINATING &&
106 final_status != FINAL_STATUS_WINDOW_OPENER &&
107 final_status != FINAL_STATUS_CACHE_OR_HISTORY_CLEARED &&
108 final_status != FINAL_STATUS_CANCELLED &&
109 final_status != FINAL_STATUS_DEVTOOLS_ATTACHED &&
110 final_status != FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING &&
111 final_status != FINAL_STATUS_PAGE_BEING_CAPTURED &&
112 final_status != FINAL_STATUS_NAVIGATION_UNCOMMITTED &&
113 final_status != FINAL_STATUS_NON_EMPTY_BROWSING_INSTANCE;
116 } // namespace
118 class PrerenderManager::OnCloseWebContentsDeleter
119 : public content::WebContentsDelegate,
120 public base::SupportsWeakPtr<
121 PrerenderManager::OnCloseWebContentsDeleter> {
122 public:
123 OnCloseWebContentsDeleter(PrerenderManager* manager,
124 WebContents* tab)
125 : manager_(manager),
126 tab_(tab),
127 suppressed_dialog_(false) {
128 tab_->SetDelegate(this);
129 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
130 FROM_HERE,
131 base::Bind(&OnCloseWebContentsDeleter::ScheduleWebContentsForDeletion,
132 AsWeakPtr(), true),
133 base::TimeDelta::FromSeconds(kDeleteWithExtremePrejudiceSeconds));
136 void CloseContents(WebContents* source) override {
137 DCHECK_EQ(tab_, source);
138 ScheduleWebContentsForDeletion(false);
141 void SwappedOut(WebContents* source) override {
142 DCHECK_EQ(tab_, source);
143 ScheduleWebContentsForDeletion(false);
146 bool ShouldSuppressDialogs(WebContents* source) override {
147 // Use this as a proxy for getting statistics on how often we fail to honor
148 // the beforeunload event.
149 DCHECK_EQ(tab_, source);
150 suppressed_dialog_ = true;
151 return true;
154 private:
155 static const int kDeleteWithExtremePrejudiceSeconds = 3;
157 void ScheduleWebContentsForDeletion(bool timeout) {
158 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterTimeout", timeout);
159 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterSuppressedDialog",
160 suppressed_dialog_);
161 tab_->SetDelegate(NULL);
162 manager_->ScheduleDeleteOldWebContents(tab_.release(), this);
163 // |this| is deleted at this point.
166 PrerenderManager* manager_;
167 scoped_ptr<WebContents> tab_;
168 bool suppressed_dialog_;
170 DISALLOW_COPY_AND_ASSIGN(OnCloseWebContentsDeleter);
173 // static
174 int PrerenderManager::prerenders_per_session_count_ = 0;
176 // static
177 PrerenderManager::PrerenderManagerMode PrerenderManager::mode_ =
178 PRERENDER_MODE_ENABLED;
180 struct PrerenderManager::NavigationRecord {
181 NavigationRecord(const GURL& url, base::TimeTicks time)
182 : url(url),
183 time(time) {
186 GURL url;
187 base::TimeTicks time;
190 PrerenderManager::PrerenderManager(Profile* profile)
191 : profile_(profile),
192 prerender_contents_factory_(PrerenderContents::CreateFactory()),
193 last_prerender_start_time_(GetCurrentTimeTicks() -
194 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs)),
195 prerender_history_(new PrerenderHistory(kHistoryLength)),
196 histograms_(new PrerenderHistograms()),
197 profile_network_bytes_(0),
198 last_recorded_profile_network_bytes_(0) {
199 // There are some assumptions that the PrerenderManager is on the UI thread.
200 // Any other checks simply make sure that the PrerenderManager is accessed on
201 // the same thread that it was created on.
202 DCHECK_CURRENTLY_ON(BrowserThread::UI);
204 // Certain experiments override our default config_ values.
205 switch (PrerenderManager::GetMode()) {
206 case PrerenderManager::PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP:
207 config_.max_link_concurrency = 4;
208 config_.max_link_concurrency_per_launcher = 2;
209 break;
210 case PrerenderManager::PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP:
211 config_.time_to_live = base::TimeDelta::FromMinutes(15);
212 break;
213 default:
214 break;
217 notification_registrar_.Add(
218 this, chrome::NOTIFICATION_PROFILE_DESTROYED,
219 content::Source<Profile>(profile_));
221 MediaCaptureDevicesDispatcher::GetInstance()->AddObserver(this);
224 PrerenderManager::~PrerenderManager() {
225 MediaCaptureDevicesDispatcher::GetInstance()->RemoveObserver(this);
227 // The earlier call to KeyedService::Shutdown() should have
228 // emptied these vectors already.
229 DCHECK(active_prerenders_.empty());
230 DCHECK(to_delete_prerenders_.empty());
232 for (PrerenderProcessSet::const_iterator it =
233 prerender_process_hosts_.begin();
234 it != prerender_process_hosts_.end();
235 ++it) {
236 (*it)->RemoveObserver(this);
240 void PrerenderManager::Shutdown() {
241 DestroyAllContents(FINAL_STATUS_MANAGER_SHUTDOWN);
242 on_close_web_contents_deleters_.clear();
243 profile_ = NULL;
245 DCHECK(active_prerenders_.empty());
248 PrerenderHandle* PrerenderManager::AddPrerenderFromLinkRelPrerender(
249 int process_id,
250 int route_id,
251 const GURL& url,
252 const uint32 rel_types,
253 const content::Referrer& referrer,
254 const gfx::Size& size) {
255 Origin origin = rel_types & PrerenderRelTypePrerender ?
256 ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN :
257 ORIGIN_LINK_REL_NEXT;
258 SessionStorageNamespace* session_storage_namespace = NULL;
259 // Unit tests pass in a process_id == -1.
260 if (process_id != -1) {
261 RenderViewHost* source_render_view_host =
262 RenderViewHost::FromID(process_id, route_id);
263 if (!source_render_view_host)
264 return NULL;
265 WebContents* source_web_contents =
266 WebContents::FromRenderViewHost(source_render_view_host);
267 if (!source_web_contents)
268 return NULL;
269 if (origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN &&
270 source_web_contents->GetURL().host() == url.host()) {
271 origin = ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN;
273 // TODO(ajwong): This does not correctly handle storage for isolated apps.
274 session_storage_namespace =
275 source_web_contents->GetController()
276 .GetDefaultSessionStorageNamespace();
279 return AddPrerender(origin, url, referrer, size, session_storage_namespace);
282 PrerenderHandle* PrerenderManager::AddPrerenderFromOmnibox(
283 const GURL& url,
284 SessionStorageNamespace* session_storage_namespace,
285 const gfx::Size& size) {
286 if (!IsOmniboxEnabled(profile_))
287 return NULL;
288 return AddPrerender(ORIGIN_OMNIBOX, url, content::Referrer(), size,
289 session_storage_namespace);
292 PrerenderHandle* PrerenderManager::AddPrerenderFromExternalRequest(
293 const GURL& url,
294 const content::Referrer& referrer,
295 SessionStorageNamespace* session_storage_namespace,
296 const gfx::Size& size) {
297 return AddPrerender(ORIGIN_EXTERNAL_REQUEST, url, referrer, size,
298 session_storage_namespace);
301 PrerenderHandle* PrerenderManager::AddPrerenderForInstant(
302 const GURL& url,
303 content::SessionStorageNamespace* session_storage_namespace,
304 const gfx::Size& size) {
305 DCHECK(search::ShouldPrefetchSearchResults());
306 return AddPrerender(ORIGIN_INSTANT, url, content::Referrer(), size,
307 session_storage_namespace);
310 void PrerenderManager::CancelAllPrerenders() {
311 DCHECK(CalledOnValidThread());
312 while (!active_prerenders_.empty()) {
313 PrerenderContents* prerender_contents =
314 active_prerenders_.front()->contents();
315 prerender_contents->Destroy(FINAL_STATUS_CANCELLED);
319 bool PrerenderManager::MaybeUsePrerenderedPage(const GURL& url,
320 chrome::NavigateParams* params) {
321 DCHECK(CalledOnValidThread());
323 content::WebContents* web_contents = params->target_contents;
324 DCHECK(!IsWebContentsPrerendering(web_contents, NULL));
326 // Don't prerender if the navigation involves some special parameters.
327 if (params->uses_post || !params->extra_headers.empty())
328 return false;
330 DeleteOldEntries();
331 to_delete_prerenders_.clear();
333 // First, try to find prerender data with the correct session storage
334 // namespace.
335 // TODO(ajwong): This doesn't handle isolated apps correctly.
336 PrerenderData* prerender_data = FindPrerenderData(
337 url,
338 web_contents->GetController().GetDefaultSessionStorageNamespace());
339 if (!prerender_data)
340 return false;
341 DCHECK(prerender_data->contents());
343 WebContents* new_web_contents = SwapInternal(
344 url, web_contents, prerender_data,
345 params->should_replace_current_entry);
346 if (!new_web_contents)
347 return false;
349 // Record the new target_contents for the callers.
350 params->target_contents = new_web_contents;
351 return true;
354 WebContents* PrerenderManager::SwapInternal(
355 const GURL& url,
356 WebContents* web_contents,
357 PrerenderData* prerender_data,
358 bool should_replace_current_entry) {
359 DCHECK(CalledOnValidThread());
360 DCHECK(!IsWebContentsPrerendering(web_contents, NULL));
362 // Only swap if the target WebContents has a CoreTabHelper delegate to swap
363 // out of it. For a normal WebContents, this is if it is in a TabStripModel.
364 CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(web_contents);
365 if (!core_tab_helper || !core_tab_helper->delegate())
366 return NULL;
368 PrerenderTabHelper* target_tab_helper =
369 PrerenderTabHelper::FromWebContents(web_contents);
370 if (!target_tab_helper) {
371 NOTREACHED();
372 return NULL;
375 if (WebContents* new_web_contents =
376 prerender_data->contents()->prerender_contents()) {
377 if (web_contents == new_web_contents)
378 return NULL; // Do not swap in to ourself.
380 // We cannot swap in if there is no last committed entry, because we would
381 // show a blank page under an existing entry from the current tab. Even if
382 // there is a pending entry, it may not commit.
383 // TODO(creis): If there is a pending navigation and no last committed
384 // entry, we might be able to transfer the network request instead.
385 if (!new_web_contents->GetController().CanPruneAllButLastCommitted()) {
386 // Abort this prerender so it is not used later. http://crbug.com/292121
387 prerender_data->contents()->Destroy(FINAL_STATUS_NAVIGATION_UNCOMMITTED);
388 return NULL;
392 // Do not swap if the target WebContents is not the only WebContents in its
393 // current BrowsingInstance.
394 if (web_contents->GetSiteInstance()->GetRelatedActiveContentsCount() != 1u) {
395 DCHECK_GT(
396 web_contents->GetSiteInstance()->GetRelatedActiveContentsCount(), 1u);
397 prerender_data->contents()->Destroy(
398 FINAL_STATUS_NON_EMPTY_BROWSING_INSTANCE);
399 return NULL;
402 // Do not use the prerendered version if there is an opener object.
403 if (web_contents->HasOpener()) {
404 prerender_data->contents()->Destroy(FINAL_STATUS_WINDOW_OPENER);
405 return NULL;
408 // Do not swap in the prerender if the current WebContents is being captured.
409 if (web_contents->GetCapturerCount() > 0) {
410 prerender_data->contents()->Destroy(FINAL_STATUS_PAGE_BEING_CAPTURED);
411 return NULL;
414 // If we are just in the control group (which can be detected by noticing
415 // that prerendering hasn't even started yet), record that |web_contents| now
416 // would be showing a prerendered contents, but otherwise, don't do anything.
417 if (!prerender_data->contents()->prerendering_has_started()) {
418 target_tab_helper->WouldHavePrerenderedNextLoad(
419 prerender_data->contents()->origin());
420 prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED);
421 return NULL;
424 // Don't use prerendered pages if debugger is attached to the tab.
425 // See http://crbug.com/98541
426 if (content::DevToolsAgentHost::IsDebuggerAttached(web_contents)) {
427 DestroyAndMarkMatchCompleteAsUsed(prerender_data->contents(),
428 FINAL_STATUS_DEVTOOLS_ATTACHED);
429 return NULL;
432 // If the prerendered page is in the middle of a cross-site navigation,
433 // don't swap it in because there isn't a good way to merge histories.
434 if (prerender_data->contents()->IsCrossSiteNavigationPending()) {
435 DestroyAndMarkMatchCompleteAsUsed(
436 prerender_data->contents(),
437 FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING);
438 return NULL;
441 // For bookkeeping purposes, we need to mark this WebContents to
442 // reflect that it would have been prerendered.
443 if (GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP) {
444 target_tab_helper->WouldHavePrerenderedNextLoad(
445 prerender_data->contents()->origin());
446 prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED);
447 return NULL;
450 // At this point, we've determined that we will use the prerender.
451 content::RenderProcessHost* process_host =
452 prerender_data->contents()->GetRenderViewHost()->GetProcess();
453 process_host->RemoveObserver(this);
454 prerender_process_hosts_.erase(process_host);
455 if (!prerender_data->contents()->load_start_time().is_null()) {
456 histograms_->RecordTimeUntilUsed(
457 prerender_data->contents()->origin(),
458 GetCurrentTimeTicks() - prerender_data->contents()->load_start_time());
460 histograms_->RecordAbandonTimeUntilUsed(
461 prerender_data->contents()->origin(),
462 prerender_data->abandon_time().is_null() ?
463 base::TimeDelta() :
464 GetCurrentTimeTicks() - prerender_data->abandon_time());
466 histograms_->RecordPerSessionCount(prerender_data->contents()->origin(),
467 ++prerenders_per_session_count_);
468 histograms_->RecordUsedPrerender(prerender_data->contents()->origin());
470 ScopedVector<PrerenderData>::iterator to_erase =
471 FindIteratorForPrerenderContents(prerender_data->contents());
472 DCHECK(active_prerenders_.end() != to_erase);
473 DCHECK_EQ(prerender_data, *to_erase);
474 scoped_ptr<PrerenderContents>
475 prerender_contents(prerender_data->ReleaseContents());
476 active_prerenders_.erase(to_erase);
478 // Mark prerender as used.
479 prerender_contents->PrepareForUse();
481 WebContents* new_web_contents =
482 prerender_contents->ReleasePrerenderContents();
483 WebContents* old_web_contents = web_contents;
484 DCHECK(new_web_contents);
485 DCHECK(old_web_contents);
487 // Merge the browsing history.
488 new_web_contents->GetController().CopyStateFromAndPrune(
489 &old_web_contents->GetController(),
490 should_replace_current_entry);
491 CoreTabHelper::FromWebContents(old_web_contents)->delegate()->
492 SwapTabContents(old_web_contents,
493 new_web_contents,
494 true,
495 prerender_contents->has_finished_loading());
496 prerender_contents->CommitHistory(new_web_contents);
498 // Update PPLT metrics:
499 // If the tab has finished loading, record a PPLT of 0.
500 // If the tab is still loading, reset its start time to the current time.
501 PrerenderTabHelper* prerender_tab_helper =
502 PrerenderTabHelper::FromWebContents(new_web_contents);
503 DCHECK(prerender_tab_helper != NULL);
504 prerender_tab_helper->PrerenderSwappedIn();
506 if (old_web_contents->NeedToFireBeforeUnload()) {
507 // Schedule the delete to occur after the tab has run its unload handlers.
508 // TODO(davidben): Honor the beforeunload event. http://crbug.com/304932
509 on_close_web_contents_deleters_.push_back(
510 new OnCloseWebContentsDeleter(this, old_web_contents));
511 old_web_contents->DispatchBeforeUnload(false);
512 } else {
513 // No unload handler to run, so delete asap.
514 ScheduleDeleteOldWebContents(old_web_contents, NULL);
517 // TODO(cbentzel): Should prerender_contents move to the pending delete
518 // list, instead of deleting directly here?
519 AddToHistory(prerender_contents.get());
520 RecordNavigation(url);
521 return new_web_contents;
524 void PrerenderManager::MoveEntryToPendingDelete(PrerenderContents* entry,
525 FinalStatus final_status) {
526 DCHECK(CalledOnValidThread());
527 DCHECK(entry);
529 ScopedVector<PrerenderData>::iterator it =
530 FindIteratorForPrerenderContents(entry);
531 DCHECK(it != active_prerenders_.end());
533 // If this PrerenderContents is being deleted due to a cancellation any time
534 // after the prerender has started then we need to create a dummy replacement
535 // for PPLT accounting purposes for the Match Complete group. This is the case
536 // if the cancellation is for any reason that would not occur in the control
537 // group case.
538 if (entry->prerendering_has_started() &&
539 entry->match_complete_status() ==
540 PrerenderContents::MATCH_COMPLETE_DEFAULT &&
541 NeedMatchCompleteDummyForFinalStatus(final_status) &&
542 ActuallyPrerendering() &&
543 GetMode() == PRERENDER_MODE_EXPERIMENT_MATCH_COMPLETE_GROUP) {
544 // TODO(tburkard): I'd like to DCHECK that we are actually prerendering.
545 // However, what if new conditions are added and
546 // NeedMatchCompleteDummyForFinalStatus is not being updated. Not sure
547 // what's the best thing to do here. For now, I will just check whether
548 // we are actually prerendering.
549 (*it)->MakeIntoMatchCompleteReplacement();
550 } else {
551 to_delete_prerenders_.push_back(*it);
552 active_prerenders_.weak_erase(it);
555 // Destroy the old WebContents relatively promptly to reduce resource usage.
556 PostCleanupTask();
559 void PrerenderManager::RecordPageLoadTimeNotSwappedIn(
560 Origin origin,
561 base::TimeDelta page_load_time,
562 const GURL& url) {
563 DCHECK_CURRENTLY_ON(BrowserThread::UI);
564 histograms_->RecordPageLoadTimeNotSwappedIn(origin, page_load_time, url);
567 void PrerenderManager::RecordPerceivedPageLoadTime(
568 Origin origin,
569 NavigationType navigation_type,
570 base::TimeDelta perceived_page_load_time,
571 double fraction_plt_elapsed_at_swap_in,
572 const GURL& url) {
573 DCHECK_CURRENTLY_ON(BrowserThread::UI);
574 if (!IsEnabled())
575 return;
577 histograms_->RecordPerceivedPageLoadTime(
578 origin, perceived_page_load_time, navigation_type, url);
580 if (navigation_type == NAVIGATION_TYPE_PRERENDERED) {
581 histograms_->RecordPercentLoadDoneAtSwapin(
582 origin, fraction_plt_elapsed_at_swap_in);
586 // static
587 PrerenderManager::PrerenderManagerMode PrerenderManager::GetMode() {
588 return mode_;
591 // static
592 void PrerenderManager::SetMode(PrerenderManagerMode mode) {
593 mode_ = mode;
596 // static
597 const char* PrerenderManager::GetModeString() {
598 switch (mode_) {
599 case PRERENDER_MODE_DISABLED:
600 return "_Disabled";
601 case PRERENDER_MODE_ENABLED:
602 case PRERENDER_MODE_EXPERIMENT_PRERENDER_GROUP:
603 return "_Enabled";
604 case PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP:
605 return "_Control";
606 case PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP:
607 return "_Multi";
608 case PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP:
609 return "_15MinTTL";
610 case PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP:
611 return "_NoUse";
612 case PRERENDER_MODE_EXPERIMENT_MATCH_COMPLETE_GROUP:
613 return "_MatchComplete";
614 case PRERENDER_MODE_MAX:
615 default:
616 NOTREACHED() << "Invalid PrerenderManager mode.";
617 break;
619 return "";
622 // static
623 bool PrerenderManager::IsPrerenderingPossible() {
624 return GetMode() != PRERENDER_MODE_DISABLED;
627 // static
628 bool PrerenderManager::ActuallyPrerendering() {
629 return IsPrerenderingPossible() && !IsControlGroup();
632 // static
633 bool PrerenderManager::IsControlGroup() {
634 return GetMode() == PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP;
637 // static
638 bool PrerenderManager::IsNoUseGroup() {
639 return GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP;
642 bool PrerenderManager::IsWebContentsPrerendering(
643 const WebContents* web_contents,
644 Origin* origin) const {
645 DCHECK(CalledOnValidThread());
646 if (PrerenderContents* prerender_contents =
647 GetPrerenderContents(web_contents)) {
648 if (origin)
649 *origin = prerender_contents->origin();
650 return true;
652 return false;
655 bool PrerenderManager::HasPrerenderedUrl(
656 GURL url,
657 content::WebContents* web_contents) const {
658 content::SessionStorageNamespace* session_storage_namespace = web_contents->
659 GetController().GetDefaultSessionStorageNamespace();
661 for (ScopedVector<PrerenderData>::const_iterator it =
662 active_prerenders_.begin();
663 it != active_prerenders_.end(); ++it) {
664 PrerenderContents* prerender_contents = (*it)->contents();
665 if (prerender_contents->Matches(url, session_storage_namespace)) {
666 return true;
669 return false;
672 PrerenderContents* PrerenderManager::GetPrerenderContents(
673 const content::WebContents* web_contents) const {
674 DCHECK(CalledOnValidThread());
675 for (ScopedVector<PrerenderData>::const_iterator it =
676 active_prerenders_.begin();
677 it != active_prerenders_.end(); ++it) {
678 WebContents* prerender_web_contents =
679 (*it)->contents()->prerender_contents();
680 if (prerender_web_contents == web_contents) {
681 return (*it)->contents();
685 // Also check the pending-deletion list. If the prerender is in pending
686 // delete, anyone with a handle on the WebContents needs to know.
687 for (ScopedVector<PrerenderData>::const_iterator it =
688 to_delete_prerenders_.begin();
689 it != to_delete_prerenders_.end(); ++it) {
690 WebContents* prerender_web_contents =
691 (*it)->contents()->prerender_contents();
692 if (prerender_web_contents == web_contents) {
693 return (*it)->contents();
696 return NULL;
699 PrerenderContents* PrerenderManager::GetPrerenderContentsForRoute(
700 int child_id,
701 int route_id) const {
702 content::WebContents* web_contents =
703 tab_util::GetWebContentsByID(child_id, route_id);
704 if (web_contents == NULL)
705 return NULL;
706 return GetPrerenderContents(web_contents);
709 const std::vector<WebContents*>
710 PrerenderManager::GetAllPrerenderingContents() const {
711 DCHECK(CalledOnValidThread());
712 std::vector<WebContents*> result;
714 for (ScopedVector<PrerenderData>::const_iterator it =
715 active_prerenders_.begin();
716 it != active_prerenders_.end(); ++it) {
717 if (WebContents* contents = (*it)->contents()->prerender_contents())
718 result.push_back(contents);
721 return result;
724 bool PrerenderManager::HasRecentlyBeenNavigatedTo(Origin origin,
725 const GURL& url) {
726 DCHECK(CalledOnValidThread());
728 CleanUpOldNavigations();
729 std::list<NavigationRecord>::const_reverse_iterator end = navigations_.rend();
730 for (std::list<NavigationRecord>::const_reverse_iterator it =
731 navigations_.rbegin();
732 it != end;
733 ++it) {
734 if (it->url == url) {
735 base::TimeDelta delta = GetCurrentTimeTicks() - it->time;
736 histograms_->RecordTimeSinceLastRecentVisit(origin, delta);
737 return true;
741 return false;
744 // static
745 bool PrerenderManager::IsValidHttpMethod(const std::string& method) {
746 // method has been canonicalized to upper case at this point so we can just
747 // compare them.
748 DCHECK_EQ(method, base::ToUpperASCII(method));
749 for (size_t i = 0; i < arraysize(kValidHttpMethods); ++i) {
750 if (method.compare(kValidHttpMethods[i]) == 0)
751 return true;
754 return false;
757 // static
758 bool PrerenderManager::DoesURLHaveValidScheme(const GURL& url) {
759 return (url.SchemeIsHTTPOrHTTPS() ||
760 url.SchemeIs(extensions::kExtensionScheme) ||
761 url.SchemeIs("data"));
764 // static
765 bool PrerenderManager::DoesSubresourceURLHaveValidScheme(const GURL& url) {
766 return DoesURLHaveValidScheme(url) || url == GURL(url::kAboutBlankURL);
769 base::DictionaryValue* PrerenderManager::GetAsValue() const {
770 DCHECK(CalledOnValidThread());
771 base::DictionaryValue* dict_value = new base::DictionaryValue();
772 dict_value->Set("history", prerender_history_->GetEntriesAsValue());
773 dict_value->Set("active", GetActivePrerendersAsValue());
774 dict_value->SetBoolean("enabled", IsEnabled());
775 dict_value->SetBoolean("omnibox_enabled", IsOmniboxEnabled(profile_));
776 // If prerender is disabled via a flag this method is not even called.
777 std::string enabled_note;
778 if (IsControlGroup())
779 enabled_note += "(Control group: Not actually prerendering) ";
780 if (IsNoUseGroup())
781 enabled_note += "(No-use group: Not swapping in prerendered pages) ";
782 if (GetMode() == PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP)
783 enabled_note +=
784 "(15 min TTL group: Extended prerender eviction to 15 mins) ";
785 dict_value->SetString("enabled_note", enabled_note);
786 return dict_value;
789 void PrerenderManager::ClearData(int clear_flags) {
790 DCHECK_GE(clear_flags, 0);
791 DCHECK_LT(clear_flags, CLEAR_MAX);
792 if (clear_flags & CLEAR_PRERENDER_CONTENTS)
793 DestroyAllContents(FINAL_STATUS_CACHE_OR_HISTORY_CLEARED);
794 // This has to be second, since destroying prerenders can add to the history.
795 if (clear_flags & CLEAR_PRERENDER_HISTORY)
796 prerender_history_->Clear();
799 void PrerenderManager::RecordFinalStatusWithMatchCompleteStatus(
800 Origin origin,
801 PrerenderContents::MatchCompleteStatus mc_status,
802 FinalStatus final_status) const {
803 histograms_->RecordFinalStatus(origin, mc_status, final_status);
806 void PrerenderManager::RecordNavigation(const GURL& url) {
807 DCHECK(CalledOnValidThread());
809 navigations_.push_back(NavigationRecord(url, GetCurrentTimeTicks()));
810 CleanUpOldNavigations();
813 // protected
814 struct PrerenderManager::PrerenderData::OrderByExpiryTime {
815 bool operator()(const PrerenderData* a, const PrerenderData* b) const {
816 return a->expiry_time() < b->expiry_time();
820 PrerenderManager::PrerenderData::PrerenderData(PrerenderManager* manager,
821 PrerenderContents* contents,
822 base::TimeTicks expiry_time)
823 : manager_(manager),
824 contents_(contents),
825 handle_count_(0),
826 expiry_time_(expiry_time) {
827 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
830 PrerenderManager::PrerenderData::~PrerenderData() {
833 void PrerenderManager::PrerenderData::MakeIntoMatchCompleteReplacement() {
834 DCHECK(contents_);
835 contents_->set_match_complete_status(
836 PrerenderContents::MATCH_COMPLETE_REPLACED);
837 PrerenderData* to_delete = new PrerenderData(manager_, contents_.release(),
838 expiry_time_);
839 contents_.reset(to_delete->contents_->CreateMatchCompleteReplacement());
840 manager_->to_delete_prerenders_.push_back(to_delete);
843 void PrerenderManager::PrerenderData::OnHandleCreated(PrerenderHandle* handle) {
844 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
845 ++handle_count_;
846 contents_->AddObserver(handle);
849 void PrerenderManager::PrerenderData::OnHandleNavigatedAway(
850 PrerenderHandle* handle) {
851 DCHECK_LT(0, handle_count_);
852 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
853 if (abandon_time_.is_null())
854 abandon_time_ = base::TimeTicks::Now();
855 // We intentionally don't decrement the handle count here, so that the
856 // prerender won't be canceled until it times out.
857 manager_->SourceNavigatedAway(this);
860 void PrerenderManager::PrerenderData::OnHandleCanceled(
861 PrerenderHandle* handle) {
862 DCHECK_LT(0, handle_count_);
863 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_);
865 if (--handle_count_ == 0) {
866 // This will eventually remove this object from active_prerenders_.
867 contents_->Destroy(FINAL_STATUS_CANCELLED);
871 PrerenderContents* PrerenderManager::PrerenderData::ReleaseContents() {
872 return contents_.release();
875 void PrerenderManager::SetPrerenderContentsFactory(
876 PrerenderContents::Factory* prerender_contents_factory) {
877 DCHECK(CalledOnValidThread());
878 prerender_contents_factory_.reset(prerender_contents_factory);
881 void PrerenderManager::SourceNavigatedAway(PrerenderData* prerender_data) {
882 // The expiry time of our prerender data will likely change because of
883 // this navigation. This requires a resort of active_prerenders_.
884 ScopedVector<PrerenderData>::iterator it =
885 std::find(active_prerenders_.begin(), active_prerenders_.end(),
886 prerender_data);
887 if (it == active_prerenders_.end())
888 return;
890 (*it)->set_expiry_time(
891 std::min((*it)->expiry_time(),
892 GetExpiryTimeForNavigatedAwayPrerender()));
893 SortActivePrerenders();
896 // private
897 PrerenderHandle* PrerenderManager::AddPrerender(
898 Origin origin,
899 const GURL& url_arg,
900 const content::Referrer& referrer,
901 const gfx::Size& size,
902 SessionStorageNamespace* session_storage_namespace) {
903 DCHECK(CalledOnValidThread());
905 if (!IsEnabled())
906 return NULL;
908 if ((origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN ||
909 origin == ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN) &&
910 IsGoogleSearchResultURL(referrer.url)) {
911 origin = ORIGIN_GWS_PRERENDER;
914 GURL url = url_arg;
915 GURL alias_url;
916 if (IsControlGroup() && MaybeGetQueryStringBasedAliasURL(url, &alias_url))
917 url = alias_url;
919 // From here on, we will record a FinalStatus so we need to register with the
920 // histogram tracking.
921 histograms_->RecordPrerender(origin, url_arg);
923 if (PrerenderData* preexisting_prerender_data =
924 FindPrerenderData(url, session_storage_namespace)) {
925 RecordFinalStatusWithoutCreatingPrerenderContents(
926 url, origin, FINAL_STATUS_DUPLICATE);
927 return new PrerenderHandle(preexisting_prerender_data);
930 // Do not prerender if there are too many render processes, and we would
931 // have to use an existing one. We do not want prerendering to happen in
932 // a shared process, so that we can always reliably lower the CPU
933 // priority for prerendering.
934 // In single-process mode, ShouldTryToUseExistingProcessHost() always returns
935 // true, so that case needs to be explicitly checked for.
936 // TODO(tburkard): Figure out how to cancel prerendering in the opposite
937 // case, when a new tab is added to a process used for prerendering.
938 // TODO(ppi): Check whether there are usually enough render processes
939 // available on Android. If not, kill an existing renderers so that we can
940 // create a new one.
941 if (content::RenderProcessHost::ShouldTryToUseExistingProcessHost(
942 profile_, url) &&
943 !content::RenderProcessHost::run_renderer_in_process()) {
944 RecordFinalStatusWithoutCreatingPrerenderContents(
945 url, origin, FINAL_STATUS_TOO_MANY_PROCESSES);
946 return NULL;
949 // Check if enough time has passed since the last prerender.
950 if (!DoesRateLimitAllowPrerender(origin)) {
951 // Cancel the prerender. We could add it to the pending prerender list but
952 // this doesn't make sense as the next prerender request will be triggered
953 // by a navigation and is unlikely to be the same site.
954 RecordFinalStatusWithoutCreatingPrerenderContents(
955 url, origin, FINAL_STATUS_RATE_LIMIT_EXCEEDED);
956 return NULL;
959 PrerenderContents* prerender_contents = CreatePrerenderContents(url, referrer,
960 origin);
961 DCHECK(prerender_contents);
962 active_prerenders_.push_back(
963 new PrerenderData(this, prerender_contents,
964 GetExpiryTimeForNewPrerender(origin)));
965 if (!prerender_contents->Init()) {
966 DCHECK(active_prerenders_.end() ==
967 FindIteratorForPrerenderContents(prerender_contents));
968 return NULL;
971 histograms_->RecordPrerenderStarted(origin);
972 DCHECK(!prerender_contents->prerendering_has_started());
974 PrerenderHandle* prerender_handle =
975 new PrerenderHandle(active_prerenders_.back());
976 SortActivePrerenders();
978 last_prerender_start_time_ = GetCurrentTimeTicks();
980 gfx::Size contents_size =
981 size.IsEmpty() ? config_.default_tab_bounds.size() : size;
983 prerender_contents->StartPrerendering(contents_size,
984 session_storage_namespace);
986 DCHECK(IsControlGroup() || prerender_contents->prerendering_has_started());
988 if (GetMode() == PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP)
989 histograms_->RecordConcurrency(active_prerenders_.size());
991 StartSchedulingPeriodicCleanups();
992 return prerender_handle;
995 void PrerenderManager::StartSchedulingPeriodicCleanups() {
996 DCHECK(CalledOnValidThread());
997 if (repeating_timer_.IsRunning())
998 return;
999 repeating_timer_.Start(FROM_HERE,
1000 base::TimeDelta::FromMilliseconds(kPeriodicCleanupIntervalMs),
1001 this,
1002 &PrerenderManager::PeriodicCleanup);
1005 void PrerenderManager::StopSchedulingPeriodicCleanups() {
1006 DCHECK(CalledOnValidThread());
1007 repeating_timer_.Stop();
1010 void PrerenderManager::PeriodicCleanup() {
1011 DCHECK(CalledOnValidThread());
1013 base::ElapsedTimer resource_timer;
1015 // Grab a copy of the current PrerenderContents pointers, so that we
1016 // will not interfere with potential deletions of the list.
1017 std::vector<PrerenderContents*>
1018 prerender_contents(active_prerenders_.size());
1019 std::transform(active_prerenders_.begin(), active_prerenders_.end(),
1020 prerender_contents.begin(),
1021 std::mem_fun(&PrerenderData::contents));
1023 // And now check for prerenders using too much memory.
1024 std::for_each(prerender_contents.begin(), prerender_contents.end(),
1025 std::mem_fun(
1026 &PrerenderContents::DestroyWhenUsingTooManyResources));
1028 // Measure how long the resource checks took. http://crbug.com/305419.
1029 UMA_HISTOGRAM_TIMES("Prerender.PeriodicCleanupResourceCheckTime",
1030 resource_timer.Elapsed());
1032 base::ElapsedTimer cleanup_timer;
1034 // Perform deferred cleanup work.
1035 DeleteOldWebContents();
1036 DeleteOldEntries();
1037 if (active_prerenders_.empty())
1038 StopSchedulingPeriodicCleanups();
1040 to_delete_prerenders_.clear();
1042 // Measure how long a the various cleanup tasks took. http://crbug.com/305419.
1043 UMA_HISTOGRAM_TIMES("Prerender.PeriodicCleanupDeleteContentsTime",
1044 cleanup_timer.Elapsed());
1047 void PrerenderManager::PostCleanupTask() {
1048 DCHECK(CalledOnValidThread());
1049 base::ThreadTaskRunnerHandle::Get()->PostTask(
1050 FROM_HERE, base::Bind(&PrerenderManager::PeriodicCleanup, AsWeakPtr()));
1053 base::TimeTicks PrerenderManager::GetExpiryTimeForNewPrerender(
1054 Origin origin) const {
1055 return GetCurrentTimeTicks() + config_.time_to_live;
1058 base::TimeTicks PrerenderManager::GetExpiryTimeForNavigatedAwayPrerender()
1059 const {
1060 return GetCurrentTimeTicks() + config_.abandon_time_to_live;
1063 void PrerenderManager::DeleteOldEntries() {
1064 DCHECK(CalledOnValidThread());
1065 while (!active_prerenders_.empty()) {
1066 PrerenderData* prerender_data = active_prerenders_.front();
1067 DCHECK(prerender_data);
1068 DCHECK(prerender_data->contents());
1070 if (prerender_data->expiry_time() > GetCurrentTimeTicks())
1071 return;
1072 prerender_data->contents()->Destroy(FINAL_STATUS_TIMED_OUT);
1076 base::Time PrerenderManager::GetCurrentTime() const {
1077 return base::Time::Now();
1080 base::TimeTicks PrerenderManager::GetCurrentTimeTicks() const {
1081 return base::TimeTicks::Now();
1084 PrerenderContents* PrerenderManager::CreatePrerenderContents(
1085 const GURL& url,
1086 const content::Referrer& referrer,
1087 Origin origin) {
1088 DCHECK(CalledOnValidThread());
1089 return prerender_contents_factory_->CreatePrerenderContents(
1090 this, profile_, url, referrer, origin);
1093 void PrerenderManager::SortActivePrerenders() {
1094 std::sort(active_prerenders_.begin(), active_prerenders_.end(),
1095 PrerenderData::OrderByExpiryTime());
1098 PrerenderManager::PrerenderData* PrerenderManager::FindPrerenderData(
1099 const GURL& url,
1100 const SessionStorageNamespace* session_storage_namespace) {
1101 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1102 it != active_prerenders_.end(); ++it) {
1103 if ((*it)->contents()->Matches(url, session_storage_namespace))
1104 return *it;
1106 return NULL;
1109 ScopedVector<PrerenderManager::PrerenderData>::iterator
1110 PrerenderManager::FindIteratorForPrerenderContents(
1111 PrerenderContents* prerender_contents) {
1112 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin();
1113 it != active_prerenders_.end(); ++it) {
1114 if (prerender_contents == (*it)->contents())
1115 return it;
1117 return active_prerenders_.end();
1120 bool PrerenderManager::DoesRateLimitAllowPrerender(Origin origin) const {
1121 DCHECK(CalledOnValidThread());
1122 base::TimeDelta elapsed_time =
1123 GetCurrentTimeTicks() - last_prerender_start_time_;
1124 histograms_->RecordTimeBetweenPrerenderRequests(origin, elapsed_time);
1125 if (!config_.rate_limit_enabled)
1126 return true;
1127 return elapsed_time >=
1128 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs);
1131 void PrerenderManager::DeleteOldWebContents() {
1132 while (!old_web_contents_list_.empty()) {
1133 WebContents* web_contents = old_web_contents_list_.front();
1134 old_web_contents_list_.pop_front();
1135 // TODO(dominich): should we use Instant Unload Handler here?
1136 delete web_contents;
1140 void PrerenderManager::CleanUpOldNavigations() {
1141 DCHECK(CalledOnValidThread());
1143 // Cutoff. Navigations before this cutoff can be discarded.
1144 base::TimeTicks cutoff = GetCurrentTimeTicks() -
1145 base::TimeDelta::FromMilliseconds(kNavigationRecordWindowMs);
1146 while (!navigations_.empty()) {
1147 if (navigations_.front().time > cutoff)
1148 break;
1149 navigations_.pop_front();
1153 void PrerenderManager::ScheduleDeleteOldWebContents(
1154 WebContents* tab,
1155 OnCloseWebContentsDeleter* deleter) {
1156 old_web_contents_list_.push_back(tab);
1157 PostCleanupTask();
1159 if (deleter) {
1160 ScopedVector<OnCloseWebContentsDeleter>::iterator i = std::find(
1161 on_close_web_contents_deleters_.begin(),
1162 on_close_web_contents_deleters_.end(),
1163 deleter);
1164 DCHECK(i != on_close_web_contents_deleters_.end());
1165 on_close_web_contents_deleters_.erase(i);
1169 void PrerenderManager::AddToHistory(PrerenderContents* contents) {
1170 PrerenderHistory::Entry entry(contents->prerender_url(),
1171 contents->final_status(),
1172 contents->origin(),
1173 base::Time::Now());
1174 prerender_history_->AddEntry(entry);
1177 base::Value* PrerenderManager::GetActivePrerendersAsValue() const {
1178 base::ListValue* list_value = new base::ListValue();
1179 for (ScopedVector<PrerenderData>::const_iterator it =
1180 active_prerenders_.begin();
1181 it != active_prerenders_.end(); ++it) {
1182 if (base::Value* prerender_value = (*it)->contents()->GetAsValue())
1183 list_value->Append(prerender_value);
1185 return list_value;
1188 void PrerenderManager::DestroyAllContents(FinalStatus final_status) {
1189 DeleteOldWebContents();
1190 while (!active_prerenders_.empty()) {
1191 PrerenderContents* contents = active_prerenders_.front()->contents();
1192 contents->Destroy(final_status);
1194 to_delete_prerenders_.clear();
1197 void PrerenderManager::DestroyAndMarkMatchCompleteAsUsed(
1198 PrerenderContents* prerender_contents,
1199 FinalStatus final_status) {
1200 prerender_contents->set_match_complete_status(
1201 PrerenderContents::MATCH_COMPLETE_REPLACED);
1202 histograms_->RecordFinalStatus(prerender_contents->origin(),
1203 PrerenderContents::MATCH_COMPLETE_REPLACEMENT,
1204 FINAL_STATUS_WOULD_HAVE_BEEN_USED);
1205 prerender_contents->Destroy(final_status);
1208 void PrerenderManager::RecordFinalStatusWithoutCreatingPrerenderContents(
1209 const GURL& url, Origin origin, FinalStatus final_status) const {
1210 PrerenderHistory::Entry entry(url, final_status, origin, base::Time::Now());
1211 prerender_history_->AddEntry(entry);
1212 RecordFinalStatusWithMatchCompleteStatus(
1213 origin, PrerenderContents::MATCH_COMPLETE_DEFAULT, final_status);
1216 void PrerenderManager::Observe(int type,
1217 const content::NotificationSource& source,
1218 const content::NotificationDetails& details) {
1219 switch (type) {
1220 case chrome::NOTIFICATION_PROFILE_DESTROYED:
1221 DestroyAllContents(FINAL_STATUS_PROFILE_DESTROYED);
1222 on_close_web_contents_deleters_.clear();
1223 break;
1224 default:
1225 NOTREACHED() << "Unexpected notification sent.";
1226 break;
1230 void PrerenderManager::OnCreatingAudioStream(int render_process_id,
1231 int render_frame_id) {
1232 content::RenderFrameHost* render_frame_host =
1233 content::RenderFrameHost::FromID(render_process_id, render_frame_id);
1234 WebContents* tab = WebContents::FromRenderFrameHost(render_frame_host);
1235 if (!tab)
1236 return;
1238 PrerenderContents* prerender_contents = GetPrerenderContents(tab);
1239 if (!prerender_contents)
1240 return;
1242 prerender_contents->Destroy(prerender::FINAL_STATUS_CREATING_AUDIO_STREAM);
1245 void PrerenderManager::RecordNetworkBytes(Origin origin,
1246 bool used,
1247 int64 prerender_bytes) {
1248 if (!ActuallyPrerendering())
1249 return;
1250 int64 recent_profile_bytes =
1251 profile_network_bytes_ - last_recorded_profile_network_bytes_;
1252 last_recorded_profile_network_bytes_ = profile_network_bytes_;
1253 DCHECK_GE(recent_profile_bytes, 0);
1254 histograms_->RecordNetworkBytes(
1255 origin, used, prerender_bytes, recent_profile_bytes);
1258 bool PrerenderManager::IsEnabled() const {
1259 DCHECK(CalledOnValidThread());
1261 return chrome_browser_net::CanPrefetchAndPrerenderUI(profile_->GetPrefs());
1264 void PrerenderManager::AddProfileNetworkBytesIfEnabled(int64 bytes) {
1265 DCHECK_GE(bytes, 0);
1266 if (IsEnabled() && ActuallyPrerendering())
1267 profile_network_bytes_ += bytes;
1270 void PrerenderManager::AddPrerenderProcessHost(
1271 content::RenderProcessHost* process_host) {
1272 DCHECK_CURRENTLY_ON(BrowserThread::UI);
1273 DCHECK(prerender_process_hosts_.find(process_host) ==
1274 prerender_process_hosts_.end());
1275 prerender_process_hosts_.insert(process_host);
1276 process_host->AddObserver(this);
1279 bool PrerenderManager::MayReuseProcessHost(
1280 content::RenderProcessHost* process_host) {
1281 DCHECK_CURRENTLY_ON(BrowserThread::UI);
1282 // Isolate prerender processes to make the resource monitoring check more
1283 // accurate.
1284 return (prerender_process_hosts_.find(process_host) ==
1285 prerender_process_hosts_.end());
1288 void PrerenderManager::RenderProcessHostDestroyed(
1289 content::RenderProcessHost* host) {
1290 DCHECK_CURRENTLY_ON(BrowserThread::UI);
1291 size_t erased = prerender_process_hosts_.erase(host);
1292 DCHECK_EQ(1u, erased);
1295 } // namespace prerender