1 // Copyright (c) 2011 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.
4
5 #include "chrome/browser/prerender/prerender_manager.h"
6
7 #include "base/logging.h"
8 #include "base/metrics/field_trial.h"
9 #include "base/metrics/histogram.h"
10 #include "base/time.h"
11 #include "base/utf_string_conversions.h"
12 #include "chrome/browser/prerender/prerender_contents.h"
13 #include "chrome/browser/prerender/prerender_final_status.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "content/browser/browser_thread.h"
16 #include "content/browser/renderer_host/render_view_host.h"
17 #include "content/browser/renderer_host/render_process_host.h"
18 #include "content/browser/renderer_host/resource_dispatcher_host.h"
19 #include "content/browser/tab_contents/render_view_host_manager.h"
20 #include "content/browser/tab_contents/tab_contents.h"
21 #include "content/common/notification_service.h"
22 #include "content/common/view_messages.h"
23 #include "googleurl/src/url_parse.h"
24 #include "googleurl/src/url_canon.h"
25 #include "googleurl/src/url_util.h"
26
27 namespace prerender {
28
29 // static
30 int PrerenderManager::prerenders_per_session_count_ = 0;
31
32 // static
33 base::TimeTicks PrerenderManager::last_prefetch_seen_time_;
34
35 // static
36 PrerenderManager::PrerenderManagerMode PrerenderManager::mode_ =
37 PRERENDER_MODE_ENABLED;
38
39 // static
GetMode()40 PrerenderManager::PrerenderManagerMode PrerenderManager::GetMode() {
41 return mode_;
42 }
43
44 // static
SetMode(PrerenderManagerMode mode)45 void PrerenderManager::SetMode(PrerenderManagerMode mode) {
46 mode_ = mode;
47 }
48
49 // static
IsPrerenderingPossible()50 bool PrerenderManager::IsPrerenderingPossible() {
51 return
52 GetMode() == PRERENDER_MODE_ENABLED ||
53 GetMode() == PRERENDER_MODE_EXPERIMENT_PRERENDER_GROUP ||
54 GetMode() == PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP;
55 }
56
57 // static
IsControlGroup()58 bool PrerenderManager::IsControlGroup() {
59 return GetMode() == PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP;
60 }
61
62 // static
MaybeGetQueryStringBasedAliasURL(const GURL & url,GURL * alias_url)63 bool PrerenderManager::MaybeGetQueryStringBasedAliasURL(
64 const GURL& url, GURL* alias_url) {
65 DCHECK(alias_url);
66 url_parse::Parsed parsed;
67 url_parse::ParseStandardURL(url.spec().c_str(), url.spec().length(),
68 &parsed);
69 url_parse::Component query = parsed.query;
70 url_parse::Component key, value;
71 while (url_parse::ExtractQueryKeyValue(url.spec().c_str(), &query, &key,
72 &value)) {
73 if (key.len != 3 || strncmp(url.spec().c_str() + key.begin, "url", key.len))
74 continue;
75 // We found a url= query string component.
76 if (value.len < 1)
77 continue;
78 url_canon::RawCanonOutputW<1024> decoded_url;
79 url_util::DecodeURLEscapeSequences(url.spec().c_str() + value.begin,
80 value.len, &decoded_url);
81 GURL new_url(string16(decoded_url.data(), decoded_url.length()));
82 if (!new_url.is_empty() && new_url.is_valid()) {
83 *alias_url = new_url;
84 return true;
85 }
86 return false;
87 }
88 return false;
89 }
90
91 struct PrerenderManager::PrerenderContentsData {
92 PrerenderContents* contents_;
93 base::Time start_time_;
PrerenderContentsDataprerender::PrerenderManager::PrerenderContentsData94 PrerenderContentsData(PrerenderContents* contents, base::Time start_time)
95 : contents_(contents),
96 start_time_(start_time) {
97 }
98 };
99
100 struct PrerenderManager::PendingContentsData {
PendingContentsDataprerender::PrerenderManager::PendingContentsData101 PendingContentsData(const GURL& url, const std::vector<GURL>& alias_urls,
102 const GURL& referrer)
103 : url_(url), alias_urls_(alias_urls), referrer_(referrer) { }
~PendingContentsDataprerender::PrerenderManager::PendingContentsData104 ~PendingContentsData() {}
105 GURL url_;
106 std::vector<GURL> alias_urls_;
107 GURL referrer_;
108 };
109
110
PrerenderManager(Profile * profile)111 PrerenderManager::PrerenderManager(Profile* profile)
112 : rate_limit_enabled_(true),
113 enabled_(true),
114 profile_(profile),
115 max_prerender_age_(base::TimeDelta::FromSeconds(
116 kDefaultMaxPrerenderAgeSeconds)),
117 max_elements_(kDefaultMaxPrerenderElements),
118 prerender_contents_factory_(PrerenderContents::CreateFactory()),
119 last_prerender_start_time_(GetCurrentTimeTicks() -
120 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs)) {
121 }
122
~PrerenderManager()123 PrerenderManager::~PrerenderManager() {
124 while (!prerender_list_.empty()) {
125 PrerenderContentsData data = prerender_list_.front();
126 prerender_list_.pop_front();
127 data.contents_->set_final_status(FINAL_STATUS_MANAGER_SHUTDOWN);
128 delete data.contents_;
129 }
130 }
131
SetPrerenderContentsFactory(PrerenderContents::Factory * prerender_contents_factory)132 void PrerenderManager::SetPrerenderContentsFactory(
133 PrerenderContents::Factory* prerender_contents_factory) {
134 prerender_contents_factory_.reset(prerender_contents_factory);
135 }
136
AddPreload(const GURL & url,const std::vector<GURL> & alias_urls,const GURL & referrer)137 bool PrerenderManager::AddPreload(const GURL& url,
138 const std::vector<GURL>& alias_urls,
139 const GURL& referrer) {
140 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
141 DeleteOldEntries();
142 if (FindEntry(url))
143 return false;
144
145 // Local copy, since we may have to add an additional entry to it.
146 std::vector<GURL> all_alias_urls = alias_urls;
147
148 GURL additional_alias_url;
149 if (IsControlGroup() &&
150 PrerenderManager::MaybeGetQueryStringBasedAliasURL(
151 url, &additional_alias_url))
152 all_alias_urls.push_back(additional_alias_url);
153
154 // Do not prerender if there are too many render processes, and we would
155 // have to use an existing one. We do not want prerendering to happen in
156 // a shared process, so that we can always reliably lower the CPU
157 // priority for prerendering.
158 // In single-process mode, ShouldTryToUseExistingProcessHost() always returns
159 // true, so that case needs to be explicitly checked for.
160 // TODO(tburkard): Figure out how to cancel prerendering in the opposite
161 // case, when a new tab is added to a process used for prerendering.
162 if (RenderProcessHost::ShouldTryToUseExistingProcessHost() &&
163 !RenderProcessHost::run_renderer_in_process()) {
164 // Only record the status if we are not in the control group.
165 if (!IsControlGroup())
166 RecordFinalStatus(FINAL_STATUS_TOO_MANY_PROCESSES);
167 return false;
168 }
169
170 // Check if enough time has passed since the last prerender.
171 if (!DoesRateLimitAllowPrerender()) {
172 // Cancel the prerender. We could add it to the pending prerender list but
173 // this doesn't make sense as the next prerender request will be triggered
174 // by a navigation and is unlikely to be the same site.
175 RecordFinalStatus(FINAL_STATUS_RATE_LIMIT_EXCEEDED);
176
177 return false;
178 }
179
180 // TODO(cbentzel): Move invalid checks here instead of PrerenderContents?
181 PrerenderContentsData data(CreatePrerenderContents(url, all_alias_urls,
182 referrer),
183 GetCurrentTime());
184
185 prerender_list_.push_back(data);
186 if (IsControlGroup()) {
187 data.contents_->set_final_status(FINAL_STATUS_CONTROL_GROUP);
188 } else {
189 last_prerender_start_time_ = GetCurrentTimeTicks();
190 data.contents_->StartPrerendering();
191 }
192 while (prerender_list_.size() > max_elements_) {
193 data = prerender_list_.front();
194 prerender_list_.pop_front();
195 data.contents_->set_final_status(FINAL_STATUS_EVICTED);
196 delete data.contents_;
197 }
198 StartSchedulingPeriodicCleanups();
199 return true;
200 }
201
AddPendingPreload(const std::pair<int,int> & child_route_id_pair,const GURL & url,const std::vector<GURL> & alias_urls,const GURL & referrer)202 void PrerenderManager::AddPendingPreload(
203 const std::pair<int,int>& child_route_id_pair,
204 const GURL& url,
205 const std::vector<GURL>& alias_urls,
206 const GURL& referrer) {
207 // Check if this is coming from a valid prerender rvh.
208 bool is_valid_prerender = false;
209 for (std::list<PrerenderContentsData>::iterator it = prerender_list_.begin();
210 it != prerender_list_.end(); ++it) {
211 PrerenderContents* pc = it->contents_;
212
213 int child_id;
214 int route_id;
215 bool has_child_id = pc->GetChildId(&child_id);
216 bool has_route_id = has_child_id && pc->GetRouteId(&route_id);
217
218 if (has_child_id && has_route_id &&
219 child_id == child_route_id_pair.first &&
220 route_id == child_route_id_pair.second) {
221 is_valid_prerender = true;
222 break;
223 }
224 }
225
226 // If not, we could check to see if the RenderViewHost specified by the
227 // child_route_id_pair exists and if so just start prerendering, as this
228 // suggests that the link was clicked, though this might prerender something
229 // that the user has already navigated away from. For now, we'll be
230 // conservative and skip the prerender which will mean some prerender requests
231 // from prerendered pages will be missed if the user navigates quickly.
232 if (!is_valid_prerender) {
233 RecordFinalStatus(FINAL_STATUS_PENDING_SKIPPED);
234 return;
235 }
236
237 PendingPrerenderList::iterator it =
238 pending_prerender_list_.find(child_route_id_pair);
239 if (it == pending_prerender_list_.end()) {
240 PendingPrerenderList::value_type el = std::make_pair(child_route_id_pair,
241 std::vector<PendingContentsData>());
242 it = pending_prerender_list_.insert(el).first;
243 }
244
245 it->second.push_back(PendingContentsData(url, alias_urls, referrer));
246 }
247
DeleteOldEntries()248 void PrerenderManager::DeleteOldEntries() {
249 while (!prerender_list_.empty()) {
250 PrerenderContentsData data = prerender_list_.front();
251 if (IsPrerenderElementFresh(data.start_time_))
252 return;
253 prerender_list_.pop_front();
254 data.contents_->set_final_status(FINAL_STATUS_TIMED_OUT);
255 delete data.contents_;
256 }
257 if (prerender_list_.empty())
258 StopSchedulingPeriodicCleanups();
259 }
260
GetEntry(const GURL & url)261 PrerenderContents* PrerenderManager::GetEntry(const GURL& url) {
262 DeleteOldEntries();
263 for (std::list<PrerenderContentsData>::iterator it = prerender_list_.begin();
264 it != prerender_list_.end();
265 ++it) {
266 PrerenderContents* pc = it->contents_;
267 if (pc->MatchesURL(url)) {
268 prerender_list_.erase(it);
269 return pc;
270 }
271 }
272 // Entry not found.
273 return NULL;
274 }
275
MaybeUsePreloadedPage(TabContents * tc,const GURL & url)276 bool PrerenderManager::MaybeUsePreloadedPage(TabContents* tc, const GURL& url) {
277 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
278 scoped_ptr<PrerenderContents> pc(GetEntry(url));
279 if (pc.get() == NULL)
280 return false;
281
282 // If we are just in the control group (which can be detected by noticing
283 // that prerendering hasn't even started yet), record that this TC now would
284 // be showing a prerendered contents, but otherwise, don't do anything.
285 if (!pc->prerendering_has_started()) {
286 MarkTabContentsAsWouldBePrerendered(tc);
287 return false;
288 }
289
290 if (!pc->load_start_time().is_null())
291 RecordTimeUntilUsed(GetCurrentTimeTicks() - pc->load_start_time());
292
293 UMA_HISTOGRAM_COUNTS("Prerender.PrerendersPerSessionCount",
294 ++prerenders_per_session_count_);
295 pc->set_final_status(FINAL_STATUS_USED);
296
297 int child_id;
298 int route_id;
299 CHECK(pc->GetChildId(&child_id));
300 CHECK(pc->GetRouteId(&route_id));
301
302 RenderViewHost* rvh = pc->render_view_host();
303 // RenderViewHosts in PrerenderContents start out hidden.
304 // Since we are actually using it now, restore it.
305 rvh->WasRestored();
306 pc->set_render_view_host(NULL);
307 rvh->Send(new ViewMsg_DisplayPrerenderedPage(rvh->routing_id()));
308 tc->SwapInRenderViewHost(rvh);
309 MarkTabContentsAsPrerendered(tc);
310
311 // See if we have any pending prerender requests for this routing id and start
312 // the preload if we do.
313 std::pair<int, int> child_route_pair = std::make_pair(child_id, route_id);
314 PendingPrerenderList::iterator pending_it =
315 pending_prerender_list_.find(child_route_pair);
316 if (pending_it != pending_prerender_list_.end()) {
317 for (std::vector<PendingContentsData>::iterator content_it =
318 pending_it->second.begin();
319 content_it != pending_it->second.end(); ++content_it) {
320 AddPreload(content_it->url_, content_it->alias_urls_,
321 content_it->referrer_);
322 }
323 pending_prerender_list_.erase(pending_it);
324 }
325
326 NotificationService::current()->Notify(
327 NotificationType::PRERENDER_CONTENTS_USED,
328 Source<std::pair<int, int> >(&child_route_pair),
329 NotificationService::NoDetails());
330
331 ViewHostMsg_FrameNavigate_Params* p = pc->navigate_params();
332 if (p != NULL)
333 tc->DidNavigate(rvh, *p);
334
335 string16 title = pc->title();
336 if (!title.empty())
337 tc->UpdateTitle(rvh, pc->page_id(), UTF16ToWideHack(title));
338
339 GURL icon_url = pc->icon_url();
340 if (!icon_url.is_empty()) {
341 std::vector<FaviconURL> urls;
342 urls.push_back(FaviconURL(icon_url, FaviconURL::FAVICON));
343 tc->favicon_helper().OnUpdateFaviconURL(pc->page_id(), urls);
344 }
345
346 if (pc->has_stopped_loading())
347 tc->DidStopLoading();
348
349 return true;
350 }
351
RemoveEntry(PrerenderContents * entry)352 void PrerenderManager::RemoveEntry(PrerenderContents* entry) {
353 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
354 for (std::list<PrerenderContentsData>::iterator it = prerender_list_.begin();
355 it != prerender_list_.end();
356 ++it) {
357 if (it->contents_ == entry) {
358 RemovePendingPreload(entry);
359 prerender_list_.erase(it);
360 break;
361 }
362 }
363 DeleteOldEntries();
364 }
365
GetCurrentTime() const366 base::Time PrerenderManager::GetCurrentTime() const {
367 return base::Time::Now();
368 }
369
GetCurrentTimeTicks() const370 base::TimeTicks PrerenderManager::GetCurrentTimeTicks() const {
371 return base::TimeTicks::Now();
372 }
373
IsPrerenderElementFresh(const base::Time start) const374 bool PrerenderManager::IsPrerenderElementFresh(const base::Time start) const {
375 base::Time now = GetCurrentTime();
376 return (now - start < max_prerender_age_);
377 }
378
CreatePrerenderContents(const GURL & url,const std::vector<GURL> & alias_urls,const GURL & referrer)379 PrerenderContents* PrerenderManager::CreatePrerenderContents(
380 const GURL& url,
381 const std::vector<GURL>& alias_urls,
382 const GURL& referrer) {
383 return prerender_contents_factory_->CreatePrerenderContents(
384 this, profile_, url, alias_urls, referrer);
385 }
386
387 // Helper macro for histograms.
388 #define RECORD_PLT(tag, perceived_page_load_time) { \
389 UMA_HISTOGRAM_CUSTOM_TIMES( \
390 base::FieldTrial::MakeName(std::string("Prerender.") + tag, \
391 "Prefetch"), \
392 perceived_page_load_time, \
393 base::TimeDelta::FromMilliseconds(10), \
394 base::TimeDelta::FromSeconds(60), \
395 100); \
396 }
397
398 // static
RecordPerceivedPageLoadTime(base::TimeDelta perceived_page_load_time,TabContents * tab_contents)399 void PrerenderManager::RecordPerceivedPageLoadTime(
400 base::TimeDelta perceived_page_load_time,
401 TabContents* tab_contents) {
402 bool within_window = WithinWindow();
403 PrerenderManager* prerender_manager =
404 tab_contents->profile()->GetPrerenderManager();
405 if (!prerender_manager)
406 return;
407 if (!prerender_manager->is_enabled())
408 return;
409 RECORD_PLT("PerceivedPLT", perceived_page_load_time);
410 if (within_window)
411 RECORD_PLT("PerceivedPLTWindowed", perceived_page_load_time);
412 if (prerender_manager &&
413 ((mode_ == PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP &&
414 prerender_manager->WouldTabContentsBePrerendered(tab_contents)) ||
415 (mode_ == PRERENDER_MODE_EXPERIMENT_PRERENDER_GROUP &&
416 prerender_manager->IsTabContentsPrerendered(tab_contents)))) {
417 RECORD_PLT("PerceivedPLTMatched", perceived_page_load_time);
418 } else {
419 if (within_window)
420 RECORD_PLT("PerceivedPLTWindowNotMatched", perceived_page_load_time);
421 }
422 }
423
RecordTimeUntilUsed(base::TimeDelta time_until_used)424 void PrerenderManager::RecordTimeUntilUsed(base::TimeDelta time_until_used) {
425 UMA_HISTOGRAM_CUSTOM_TIMES(
426 "Prerender.TimeUntilUsed",
427 time_until_used,
428 base::TimeDelta::FromMilliseconds(10),
429 base::TimeDelta::FromSeconds(kDefaultMaxPrerenderAgeSeconds),
430 50);
431 }
432
is_enabled() const433 bool PrerenderManager::is_enabled() const {
434 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
435 return enabled_;
436 }
437
set_enabled(bool enabled)438 void PrerenderManager::set_enabled(bool enabled) {
439 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
440 enabled_ = enabled;
441 }
442
FindEntry(const GURL & url)443 PrerenderContents* PrerenderManager::FindEntry(const GURL& url) {
444 for (std::list<PrerenderContentsData>::iterator it = prerender_list_.begin();
445 it != prerender_list_.end();
446 ++it) {
447 if (it->contents_->MatchesURL(url))
448 return it->contents_;
449 }
450 // Entry not found.
451 return NULL;
452 }
453
454 PrerenderManager::PendingContentsData*
FindPendingEntry(const GURL & url)455 PrerenderManager::FindPendingEntry(const GURL& url) {
456 for (PendingPrerenderList::iterator map_it = pending_prerender_list_.begin();
457 map_it != pending_prerender_list_.end();
458 ++map_it) {
459 for (std::vector<PendingContentsData>::iterator content_it =
460 map_it->second.begin();
461 content_it != map_it->second.end();
462 ++content_it) {
463 if (content_it->url_ == url) {
464 return &(*content_it);
465 }
466 }
467 }
468
469 return NULL;
470 }
471
472 // static
RecordPrefetchTagObserved()473 void PrerenderManager::RecordPrefetchTagObserved() {
474 // Ensure that we are in the UI thread, and post to the UI thread if
475 // necessary.
476 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
477 BrowserThread::PostTask(
478 BrowserThread::UI,
479 FROM_HERE,
480 NewRunnableFunction(
481 &PrerenderManager::RecordPrefetchTagObservedOnUIThread));
482 } else {
483 RecordPrefetchTagObservedOnUIThread();
484 }
485 }
486
487 // static
RecordPrefetchTagObservedOnUIThread()488 void PrerenderManager::RecordPrefetchTagObservedOnUIThread() {
489 // Once we get here, we have to be on the UI thread.
490 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
491
492 // If we observe multiple tags within the 30 second window, we will still
493 // reset the window to begin at the most recent occurrence, so that we will
494 // always be in a window in the 30 seconds from each occurrence.
495 last_prefetch_seen_time_ = base::TimeTicks::Now();
496 }
497
RemovePendingPreload(PrerenderContents * entry)498 void PrerenderManager::RemovePendingPreload(PrerenderContents* entry) {
499 int child_id;
500 int route_id;
501 bool has_child_id = entry->GetChildId(&child_id);
502 bool has_route_id = has_child_id && entry->GetRouteId(&route_id);
503
504 // If the entry doesn't have a RenderViewHost then it didn't start
505 // prerendering and there shouldn't be any pending preloads to remove.
506 if (has_child_id && has_route_id) {
507 std::pair<int, int> child_route_pair = std::make_pair(child_id, route_id);
508 pending_prerender_list_.erase(child_route_pair);
509 }
510 }
511
512 // static
WithinWindow()513 bool PrerenderManager::WithinWindow() {
514 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
515 if (last_prefetch_seen_time_.is_null())
516 return false;
517 base::TimeDelta elapsed_time =
518 base::TimeTicks::Now() - last_prefetch_seen_time_;
519 return elapsed_time <= base::TimeDelta::FromSeconds(kWindowDurationSeconds);
520 }
521
DoesRateLimitAllowPrerender() const522 bool PrerenderManager::DoesRateLimitAllowPrerender() const {
523 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
524 base::TimeDelta elapsed_time =
525 GetCurrentTimeTicks() - last_prerender_start_time_;
526 UMA_HISTOGRAM_TIMES("Prerender.TimeBetweenPrerenderRequests",
527 elapsed_time);
528 if (!rate_limit_enabled_)
529 return true;
530 return elapsed_time >
531 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs);
532 }
533
StartSchedulingPeriodicCleanups()534 void PrerenderManager::StartSchedulingPeriodicCleanups() {
535 if (repeating_timer_.IsRunning())
536 return;
537 repeating_timer_.Start(
538 base::TimeDelta::FromMilliseconds(kPeriodicCleanupIntervalMs),
539 this,
540 &PrerenderManager::PeriodicCleanup);
541 }
542
StopSchedulingPeriodicCleanups()543 void PrerenderManager::StopSchedulingPeriodicCleanups() {
544 repeating_timer_.Stop();
545 }
546
PeriodicCleanup()547 void PrerenderManager::PeriodicCleanup() {
548 DeleteOldEntries();
549 // Grab a copy of the current PrerenderContents pointers, so that we
550 // will not interfere with potential deletions of the list.
551 std::vector<PrerenderContents*> prerender_contents;
552 for (std::list<PrerenderContentsData>::iterator it = prerender_list_.begin();
553 it != prerender_list_.end();
554 ++it) {
555 prerender_contents.push_back(it->contents_);
556 }
557 for (std::vector<PrerenderContents*>::iterator it =
558 prerender_contents.begin();
559 it != prerender_contents.end();
560 ++it) {
561 (*it)->DestroyWhenUsingTooManyResources();
562 }
563 }
564
MarkTabContentsAsPrerendered(TabContents * tc)565 void PrerenderManager::MarkTabContentsAsPrerendered(TabContents* tc) {
566 prerendered_tc_set_.insert(tc);
567 }
568
MarkTabContentsAsWouldBePrerendered(TabContents * tc)569 void PrerenderManager::MarkTabContentsAsWouldBePrerendered(TabContents* tc) {
570 would_be_prerendered_tc_set_.insert(tc);
571 }
572
MarkTabContentsAsNotPrerendered(TabContents * tc)573 void PrerenderManager::MarkTabContentsAsNotPrerendered(TabContents* tc) {
574 prerendered_tc_set_.erase(tc);
575 would_be_prerendered_tc_set_.erase(tc);
576 }
577
IsTabContentsPrerendered(TabContents * tc) const578 bool PrerenderManager::IsTabContentsPrerendered(TabContents* tc) const {
579 return prerendered_tc_set_.count(tc) > 0;
580 }
581
WouldTabContentsBePrerendered(TabContents * tc) const582 bool PrerenderManager::WouldTabContentsBePrerendered(TabContents* tc) const {
583 return would_be_prerendered_tc_set_.count(tc) > 0;
584 }
585
586 } // namespace prerender
587