Home | History | Annotate | Download | only in prerender
      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
     40 PrerenderManager::PrerenderManagerMode PrerenderManager::GetMode() {
     41   return mode_;
     42 }
     43 
     44 // static
     45 void PrerenderManager::SetMode(PrerenderManagerMode mode) {
     46   mode_ = mode;
     47 }
     48 
     49 // static
     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
     58 bool PrerenderManager::IsControlGroup() {
     59   return GetMode() == PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP;
     60 }
     61 
     62 // static
     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_;
     94   PrerenderContentsData(PrerenderContents* contents, base::Time start_time)
     95       : contents_(contents),
     96         start_time_(start_time) {
     97   }
     98 };
     99 
    100 struct PrerenderManager::PendingContentsData {
    101   PendingContentsData(const GURL& url, const std::vector<GURL>& alias_urls,
    102                       const GURL& referrer)
    103       : url_(url), alias_urls_(alias_urls), referrer_(referrer) { }
    104   ~PendingContentsData() {}
    105   GURL url_;
    106   std::vector<GURL> alias_urls_;
    107   GURL referrer_;
    108 };
    109 
    110 
    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 
    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 
    132 void PrerenderManager::SetPrerenderContentsFactory(
    133     PrerenderContents::Factory* prerender_contents_factory) {
    134   prerender_contents_factory_.reset(prerender_contents_factory);
    135 }
    136 
    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 
    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 
    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 
    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 
    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 
    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 
    366 base::Time PrerenderManager::GetCurrentTime() const {
    367   return base::Time::Now();
    368 }
    369 
    370 base::TimeTicks PrerenderManager::GetCurrentTimeTicks() const {
    371   return base::TimeTicks::Now();
    372 }
    373 
    374 bool PrerenderManager::IsPrerenderElementFresh(const base::Time start) const {
    375   base::Time now = GetCurrentTime();
    376   return (now - start < max_prerender_age_);
    377 }
    378 
    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
    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 
    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 
    433 bool PrerenderManager::is_enabled() const {
    434   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    435   return enabled_;
    436 }
    437 
    438 void PrerenderManager::set_enabled(bool enabled) {
    439   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    440   enabled_ = enabled;
    441 }
    442 
    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*
    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
    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
    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 
    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
    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 
    522 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 
    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 
    543 void PrerenderManager::StopSchedulingPeriodicCleanups() {
    544   repeating_timer_.Stop();
    545 }
    546 
    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 
    565 void PrerenderManager::MarkTabContentsAsPrerendered(TabContents* tc) {
    566   prerendered_tc_set_.insert(tc);
    567 }
    568 
    569 void PrerenderManager::MarkTabContentsAsWouldBePrerendered(TabContents* tc) {
    570   would_be_prerendered_tc_set_.insert(tc);
    571 }
    572 
    573 void PrerenderManager::MarkTabContentsAsNotPrerendered(TabContents* tc) {
    574   prerendered_tc_set_.erase(tc);
    575   would_be_prerendered_tc_set_.erase(tc);
    576 }
    577 
    578 bool PrerenderManager::IsTabContentsPrerendered(TabContents* tc) const {
    579   return prerendered_tc_set_.count(tc) > 0;
    580 }
    581 
    582 bool PrerenderManager::WouldTabContentsBePrerendered(TabContents* tc) const {
    583   return would_be_prerendered_tc_set_.count(tc) > 0;
    584 }
    585 
    586 }  // namespace prerender
    587