Home | History | Annotate | Download | only in prerender
      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.
      4 
      5 #include "chrome/browser/prerender/prerender_link_manager.h"
      6 
      7 #include <functional>
      8 #include <limits>
      9 #include <set>
     10 #include <string>
     11 #include <utility>
     12 
     13 #include "base/memory/scoped_ptr.h"
     14 #include "base/metrics/field_trial.h"
     15 #include "base/metrics/histogram.h"
     16 #include "chrome/browser/prerender/prerender_contents.h"
     17 #include "chrome/browser/prerender/prerender_handle.h"
     18 #include "chrome/browser/prerender/prerender_manager.h"
     19 #include "chrome/browser/prerender/prerender_manager_factory.h"
     20 #include "chrome/browser/profiles/profile.h"
     21 #include "chrome/common/prerender_messages.h"
     22 #include "chrome/common/prerender_types.h"
     23 #include "content/public/browser/render_process_host.h"
     24 #include "content/public/browser/render_view_host.h"
     25 #include "content/public/browser/session_storage_namespace.h"
     26 #include "content/public/common/referrer.h"
     27 #include "ui/gfx/size.h"
     28 #include "url/gurl.h"
     29 
     30 #if defined(ENABLE_EXTENSIONS)
     31 #include "extensions/browser/guest_view/guest_view_base.h"
     32 #endif
     33 
     34 using base::TimeDelta;
     35 using base::TimeTicks;
     36 using content::RenderViewHost;
     37 using content::SessionStorageNamespace;
     38 
     39 namespace prerender {
     40 
     41 namespace {
     42 
     43 bool ShouldStartRelNextPrerenders() {
     44   const std::string experiment_name =
     45       base::FieldTrialList::FindFullName("PrerenderRelNextTrial");
     46 
     47   return experiment_name.find("Yes") != std::string::npos;
     48 }
     49 
     50 bool ShouldStartPrerender(const uint32 rel_types) {
     51   const bool should_start_rel_next_prerenders =
     52       ShouldStartRelNextPrerenders();
     53 
     54   if (rel_types & PrerenderRelTypePrerender) {
     55     return true;
     56   } else if (should_start_rel_next_prerenders &&
     57              (rel_types & PrerenderRelTypeNext) == PrerenderRelTypeNext) {
     58     return true;
     59   }
     60   return false;
     61 }
     62 
     63 COMPILE_ASSERT(PrerenderRelTypePrerender == 0x1,
     64                RelTypeHistogramEnum_must_match_PrerenderRelType);
     65 COMPILE_ASSERT(PrerenderRelTypeNext == 0x2,
     66                RelTypeHistogramEnum_must_match_PrerenderRelType);
     67 enum RelTypeHistogramEnum {
     68   RelTypeHistogramEnumNone = 0,
     69   RelTypeHistogramEnumPrerender = PrerenderRelTypePrerender,
     70   RelTypeHistogramEnumNext = PrerenderRelTypeNext,
     71   RelTypeHistogramEnumPrerenderAndNext =
     72       PrerenderRelTypePrerender | PrerenderRelTypeNext,
     73   RelTypeHistogramEnumMax,
     74 };
     75 
     76 void RecordLinkManagerAdded(const uint32 rel_types) {
     77   const uint32 enum_value = rel_types & (RelTypeHistogramEnumMax - 1);
     78   UMA_HISTOGRAM_ENUMERATION("Prerender.RelTypesLinkAdded", enum_value,
     79                             RelTypeHistogramEnumMax);
     80 }
     81 
     82 void RecordLinkManagerStarting(const uint32 rel_types) {
     83   const uint32 enum_value = rel_types & (RelTypeHistogramEnumMax - 1);
     84   UMA_HISTOGRAM_ENUMERATION("Prerender.RelTypesLinkStarted", enum_value,
     85                             RelTypeHistogramEnumMax);
     86 }
     87 
     88 void Send(int child_id, IPC::Message* raw_message) {
     89   using content::RenderProcessHost;
     90   scoped_ptr<IPC::Message> own_message(raw_message);
     91 
     92   RenderProcessHost* render_process_host = RenderProcessHost::FromID(child_id);
     93   if (!render_process_host)
     94     return;
     95   render_process_host->Send(own_message.release());
     96 }
     97 
     98 }  // namespace
     99 
    100 // Helper class to implement PrerenderContents::Observer and watch prerenders
    101 // which launch other prerenders.
    102 class PrerenderLinkManager::PendingPrerenderManager
    103     : public PrerenderContents::Observer {
    104  public:
    105   explicit PendingPrerenderManager(PrerenderLinkManager* link_manager)
    106       : link_manager_(link_manager) {}
    107 
    108   virtual ~PendingPrerenderManager() {
    109     DCHECK(observed_launchers_.empty());
    110     for (std::set<PrerenderContents*>::iterator i = observed_launchers_.begin();
    111          i != observed_launchers_.end(); ++i) {
    112       (*i)->RemoveObserver(this);
    113     }
    114   }
    115 
    116   void ObserveLauncher(PrerenderContents* launcher) {
    117     DCHECK_EQ(FINAL_STATUS_MAX, launcher->final_status());
    118     if (observed_launchers_.find(launcher) != observed_launchers_.end())
    119       return;
    120     observed_launchers_.insert(launcher);
    121     launcher->AddObserver(this);
    122   }
    123 
    124   virtual void OnPrerenderStart(PrerenderContents* launcher) OVERRIDE {}
    125 
    126   virtual void OnPrerenderStop(PrerenderContents* launcher) OVERRIDE {
    127     observed_launchers_.erase(launcher);
    128     if (launcher->final_status() == FINAL_STATUS_USED) {
    129       link_manager_->StartPendingPrerendersForLauncher(launcher);
    130     } else {
    131       link_manager_->CancelPendingPrerendersForLauncher(launcher);
    132     }
    133   }
    134 
    135  private:
    136   // A pointer to the parent PrerenderLinkManager.
    137   PrerenderLinkManager* link_manager_;
    138 
    139   // The set of PrerenderContentses being observed. Lifetimes are managed by
    140   // OnPrerenderStop.
    141   std::set<PrerenderContents*> observed_launchers_;
    142 };
    143 
    144 PrerenderLinkManager::PrerenderLinkManager(PrerenderManager* manager)
    145     : has_shutdown_(false),
    146       manager_(manager),
    147       pending_prerender_manager_(new PendingPrerenderManager(this)) {}
    148 
    149 PrerenderLinkManager::~PrerenderLinkManager() {
    150   for (std::list<LinkPrerender>::iterator i = prerenders_.begin();
    151        i != prerenders_.end(); ++i) {
    152     if (i->handle) {
    153       DCHECK(!i->handle->IsPrerendering())
    154           << "All running prerenders should stop at the same time as the "
    155           << "PrerenderManager.";
    156       delete i->handle;
    157       i->handle = 0;
    158     }
    159   }
    160 }
    161 
    162 void PrerenderLinkManager::OnAddPrerender(int launcher_child_id,
    163                                           int prerender_id,
    164                                           const GURL& url,
    165                                           uint32 rel_types,
    166                                           const content::Referrer& referrer,
    167                                           const gfx::Size& size,
    168                                           int render_view_route_id) {
    169   DCHECK_EQ(static_cast<LinkPrerender*>(NULL),
    170             FindByLauncherChildIdAndPrerenderId(launcher_child_id,
    171                                                 prerender_id));
    172 
    173 #if defined(ENABLE_EXTENSIONS)
    174   content::RenderViewHost* rvh =
    175       content::RenderViewHost::FromID(launcher_child_id, render_view_route_id);
    176   content::WebContents* web_contents =
    177       rvh ? content::WebContents::FromRenderViewHost(rvh) : NULL;
    178   // Guests inside <webview> do not support cross-process navigation and so we
    179   // do not allow guests to prerender content.
    180   if (extensions::GuestViewBase::IsGuest(web_contents))
    181     return;
    182 #endif
    183 
    184   // Check if the launcher is itself an unswapped prerender.
    185   PrerenderContents* prerender_contents =
    186       manager_->GetPrerenderContentsForRoute(launcher_child_id,
    187                                              render_view_route_id);
    188   if (prerender_contents &&
    189       prerender_contents->final_status() != FINAL_STATUS_MAX) {
    190     // The launcher is a prerender about to be destroyed asynchronously, but
    191     // its AddLinkRelPrerender message raced with shutdown. Ignore it.
    192     DCHECK_NE(FINAL_STATUS_USED, prerender_contents->final_status());
    193     return;
    194   }
    195 
    196   LinkPrerender
    197       prerender(launcher_child_id, prerender_id, url, rel_types, referrer, size,
    198                 render_view_route_id, manager_->GetCurrentTimeTicks(),
    199                 prerender_contents);
    200   prerenders_.push_back(prerender);
    201   RecordLinkManagerAdded(rel_types);
    202   if (prerender_contents)
    203     pending_prerender_manager_->ObserveLauncher(prerender_contents);
    204   else
    205     StartPrerenders();
    206 }
    207 
    208 void PrerenderLinkManager::OnCancelPrerender(int child_id, int prerender_id) {
    209   LinkPrerender* prerender = FindByLauncherChildIdAndPrerenderId(child_id,
    210                                                                  prerender_id);
    211   if (!prerender)
    212     return;
    213 
    214   CancelPrerender(prerender);
    215   StartPrerenders();
    216 }
    217 
    218 void PrerenderLinkManager::OnAbandonPrerender(int child_id, int prerender_id) {
    219   LinkPrerender* prerender = FindByLauncherChildIdAndPrerenderId(child_id,
    220                                                                  prerender_id);
    221   if (!prerender)
    222     return;
    223 
    224   if (!prerender->handle) {
    225     RemovePrerender(prerender);
    226     return;
    227   }
    228 
    229   prerender->has_been_abandoned = true;
    230   prerender->handle->OnNavigateAway();
    231   DCHECK(prerender->handle);
    232 
    233   // If the prerender is not running, remove it from the list so it does not
    234   // leak. If it is running, it will send a cancel event when it stops which
    235   // will remove it.
    236   if (!prerender->handle->IsPrerendering())
    237     RemovePrerender(prerender);
    238 }
    239 
    240 void PrerenderLinkManager::OnChannelClosing(int child_id) {
    241   std::list<LinkPrerender>::iterator next = prerenders_.begin();
    242   while (next != prerenders_.end()) {
    243     std::list<LinkPrerender>::iterator it = next;
    244     ++next;
    245 
    246     if (child_id != it->launcher_child_id)
    247       continue;
    248 
    249     const size_t running_prerender_count = CountRunningPrerenders();
    250     OnAbandonPrerender(child_id, it->prerender_id);
    251     DCHECK_EQ(running_prerender_count, CountRunningPrerenders());
    252   }
    253 }
    254 
    255 PrerenderLinkManager::LinkPrerender::LinkPrerender(
    256     int launcher_child_id,
    257     int prerender_id,
    258     const GURL& url,
    259     uint32 rel_types,
    260     const content::Referrer& referrer,
    261     const gfx::Size& size,
    262     int render_view_route_id,
    263     TimeTicks creation_time,
    264     PrerenderContents* deferred_launcher)
    265     : launcher_child_id(launcher_child_id),
    266       prerender_id(prerender_id),
    267       url(url),
    268       rel_types(rel_types),
    269       referrer(referrer),
    270       size(size),
    271       render_view_route_id(render_view_route_id),
    272       creation_time(creation_time),
    273       deferred_launcher(deferred_launcher),
    274       handle(NULL),
    275       is_match_complete_replacement(false),
    276       has_been_abandoned(false) {
    277 }
    278 
    279 PrerenderLinkManager::LinkPrerender::~LinkPrerender() {
    280   DCHECK_EQ(static_cast<PrerenderHandle*>(NULL), handle)
    281       << "The PrerenderHandle should be destroyed before its Prerender.";
    282 }
    283 
    284 bool PrerenderLinkManager::IsEmpty() const {
    285   return prerenders_.empty();
    286 }
    287 
    288 size_t PrerenderLinkManager::CountRunningPrerenders() const {
    289   size_t retval = 0;
    290   for (std::list<LinkPrerender>::const_iterator i = prerenders_.begin();
    291        i != prerenders_.end(); ++i) {
    292     if (i->handle && i->handle->IsPrerendering())
    293       ++retval;
    294   }
    295   return retval;
    296 }
    297 
    298 void PrerenderLinkManager::StartPrerenders() {
    299   if (has_shutdown_)
    300     return;
    301 
    302   size_t total_started_prerender_count = 0;
    303   std::list<LinkPrerender*> abandoned_prerenders;
    304   std::list<std::list<LinkPrerender>::iterator> pending_prerenders;
    305   std::multiset<std::pair<int, int> >
    306       running_launcher_and_render_view_routes;
    307 
    308   // Scan the list, counting how many prerenders have handles (and so were added
    309   // to the PrerenderManager). The count is done for the system as a whole, and
    310   // also per launcher.
    311   for (std::list<LinkPrerender>::iterator i = prerenders_.begin();
    312        i != prerenders_.end(); ++i) {
    313     // Skip prerenders launched by a prerender.
    314     if (i->deferred_launcher)
    315       continue;
    316     if (!i->handle) {
    317       pending_prerenders.push_back(i);
    318     } else {
    319       ++total_started_prerender_count;
    320       if (i->has_been_abandoned) {
    321         abandoned_prerenders.push_back(&(*i));
    322       } else {
    323         // We do not count abandoned prerenders towards their launcher, since it
    324         // has already navigated on to another page.
    325         std::pair<int, int> launcher_and_render_view_route(
    326             i->launcher_child_id, i->render_view_route_id);
    327         running_launcher_and_render_view_routes.insert(
    328             launcher_and_render_view_route);
    329         DCHECK_GE(manager_->config().max_link_concurrency_per_launcher,
    330                   running_launcher_and_render_view_routes.count(
    331                       launcher_and_render_view_route));
    332       }
    333     }
    334 
    335     DCHECK_EQ(&(*i), FindByLauncherChildIdAndPrerenderId(i->launcher_child_id,
    336                                                          i->prerender_id));
    337   }
    338   DCHECK_LE(abandoned_prerenders.size(), total_started_prerender_count);
    339   DCHECK_GE(manager_->config().max_link_concurrency,
    340             total_started_prerender_count);
    341   DCHECK_LE(CountRunningPrerenders(), total_started_prerender_count);
    342 
    343   TimeTicks now = manager_->GetCurrentTimeTicks();
    344 
    345   // Scan the pending prerenders, starting prerenders as we can.
    346   for (std::list<std::list<LinkPrerender>::iterator>::const_iterator
    347            i = pending_prerenders.begin(), end = pending_prerenders.end();
    348        i != end; ++i) {
    349     TimeDelta prerender_age = now - (*i)->creation_time;
    350     if (prerender_age >= manager_->config().max_wait_to_launch) {
    351       // This prerender waited too long in the queue before launching.
    352       prerenders_.erase(*i);
    353       continue;
    354     }
    355 
    356     std::pair<int, int> launcher_and_render_view_route(
    357         (*i)->launcher_child_id, (*i)->render_view_route_id);
    358     if (manager_->config().max_link_concurrency_per_launcher <=
    359         running_launcher_and_render_view_routes.count(
    360             launcher_and_render_view_route)) {
    361       // This prerender's launcher is already at its limit.
    362       continue;
    363     }
    364 
    365     if (total_started_prerender_count >=
    366             manager_->config().max_link_concurrency ||
    367         total_started_prerender_count >= prerenders_.size()) {
    368       // The system is already at its prerender concurrency limit. Can we kill
    369       // an abandoned prerender to make room?
    370       if (!abandoned_prerenders.empty()) {
    371         CancelPrerender(abandoned_prerenders.front());
    372         --total_started_prerender_count;
    373         abandoned_prerenders.pop_front();
    374       } else {
    375         return;
    376       }
    377     }
    378 
    379     if (!ShouldStartPrerender((*i)->rel_types)) {
    380       prerenders_.erase(*i);
    381       continue;
    382     }
    383 
    384     PrerenderHandle* handle = manager_->AddPrerenderFromLinkRelPrerender(
    385         (*i)->launcher_child_id, (*i)->render_view_route_id,
    386         (*i)->url, (*i)->rel_types, (*i)->referrer, (*i)->size);
    387     if (!handle) {
    388       // This prerender couldn't be launched, it's gone.
    389       prerenders_.erase(*i);
    390       continue;
    391     }
    392 
    393     // We have successfully started a new prerender.
    394     (*i)->handle = handle;
    395     ++total_started_prerender_count;
    396     handle->SetObserver(this);
    397     if (handle->IsPrerendering())
    398       OnPrerenderStart(handle);
    399     RecordLinkManagerStarting((*i)->rel_types);
    400 
    401     running_launcher_and_render_view_routes.insert(
    402         launcher_and_render_view_route);
    403   }
    404 }
    405 
    406 PrerenderLinkManager::LinkPrerender*
    407 PrerenderLinkManager::FindByLauncherChildIdAndPrerenderId(int launcher_child_id,
    408                                                           int prerender_id) {
    409   for (std::list<LinkPrerender>::iterator i = prerenders_.begin();
    410        i != prerenders_.end(); ++i) {
    411     if (launcher_child_id == i->launcher_child_id &&
    412         prerender_id == i->prerender_id) {
    413       return &(*i);
    414     }
    415   }
    416   return NULL;
    417 }
    418 
    419 PrerenderLinkManager::LinkPrerender*
    420 PrerenderLinkManager::FindByPrerenderHandle(PrerenderHandle* prerender_handle) {
    421   DCHECK(prerender_handle);
    422   for (std::list<LinkPrerender>::iterator i = prerenders_.begin();
    423        i != prerenders_.end(); ++i) {
    424     if (prerender_handle == i->handle)
    425       return &(*i);
    426   }
    427   return NULL;
    428 }
    429 
    430 void PrerenderLinkManager::RemovePrerender(LinkPrerender* prerender) {
    431   for (std::list<LinkPrerender>::iterator i = prerenders_.begin();
    432        i != prerenders_.end(); ++i) {
    433     if (&(*i) == prerender) {
    434       scoped_ptr<PrerenderHandle> own_handle(i->handle);
    435       i->handle = NULL;
    436       prerenders_.erase(i);
    437       return;
    438     }
    439   }
    440   NOTREACHED();
    441 }
    442 
    443 void PrerenderLinkManager::CancelPrerender(LinkPrerender* prerender) {
    444   for (std::list<LinkPrerender>::iterator i = prerenders_.begin();
    445        i != prerenders_.end(); ++i) {
    446     if (&(*i) == prerender) {
    447       scoped_ptr<PrerenderHandle> own_handle(i->handle);
    448       i->handle = NULL;
    449       prerenders_.erase(i);
    450       if (own_handle)
    451         own_handle->OnCancel();
    452       return;
    453     }
    454   }
    455   NOTREACHED();
    456 }
    457 
    458 void PrerenderLinkManager::StartPendingPrerendersForLauncher(
    459     PrerenderContents* launcher) {
    460   for (std::list<LinkPrerender>::iterator i = prerenders_.begin();
    461        i != prerenders_.end(); ++i) {
    462     if (i->deferred_launcher == launcher)
    463       i->deferred_launcher = NULL;
    464   }
    465   StartPrerenders();
    466 }
    467 
    468 void PrerenderLinkManager::CancelPendingPrerendersForLauncher(
    469     PrerenderContents* launcher) {
    470   // Remove all pending prerenders for this launcher.
    471   std::vector<std::list<LinkPrerender>::iterator> to_erase;
    472   for (std::list<LinkPrerender>::iterator i = prerenders_.begin();
    473        i != prerenders_.end(); ++i) {
    474     if (i->deferred_launcher == launcher) {
    475       DCHECK(!i->handle);
    476       to_erase.push_back(i);
    477     }
    478   }
    479   std::for_each(to_erase.begin(), to_erase.end(),
    480                 std::bind1st(std::mem_fun(&std::list<LinkPrerender>::erase),
    481                              &prerenders_));
    482 }
    483 
    484 void PrerenderLinkManager::Shutdown() {
    485   has_shutdown_ = true;
    486 }
    487 
    488 // In practice, this is always called from PrerenderLinkManager::OnAddPrerender.
    489 void PrerenderLinkManager::OnPrerenderStart(
    490     PrerenderHandle* prerender_handle) {
    491   LinkPrerender* prerender = FindByPrerenderHandle(prerender_handle);
    492   if (!prerender)
    493     return;
    494   Send(prerender->launcher_child_id,
    495        new PrerenderMsg_OnPrerenderStart(prerender->prerender_id));
    496 }
    497 
    498 void PrerenderLinkManager::OnPrerenderStopLoading(
    499     PrerenderHandle* prerender_handle) {
    500   LinkPrerender* prerender = FindByPrerenderHandle(prerender_handle);
    501   if (!prerender)
    502     return;
    503 
    504   Send(prerender->launcher_child_id,
    505        new PrerenderMsg_OnPrerenderStopLoading(prerender->prerender_id));
    506 }
    507 
    508 void PrerenderLinkManager::OnPrerenderDomContentLoaded(
    509     PrerenderHandle* prerender_handle) {
    510   LinkPrerender* prerender = FindByPrerenderHandle(prerender_handle);
    511   if (!prerender)
    512     return;
    513 
    514   Send(prerender->launcher_child_id,
    515        new PrerenderMsg_OnPrerenderDomContentLoaded(prerender->prerender_id));
    516 }
    517 
    518 void PrerenderLinkManager::OnPrerenderStop(
    519     PrerenderHandle* prerender_handle) {
    520   LinkPrerender* prerender = FindByPrerenderHandle(prerender_handle);
    521   if (!prerender)
    522     return;
    523 
    524   // If the prerender became a match complete replacement, the stop
    525   // message has already been sent.
    526   if (!prerender->is_match_complete_replacement) {
    527     Send(prerender->launcher_child_id,
    528          new PrerenderMsg_OnPrerenderStop(prerender->prerender_id));
    529   }
    530   RemovePrerender(prerender);
    531   StartPrerenders();
    532 }
    533 
    534 void PrerenderLinkManager::OnPrerenderCreatedMatchCompleteReplacement(
    535     PrerenderHandle* prerender_handle) {
    536   LinkPrerender* prerender = FindByPrerenderHandle(prerender_handle);
    537   if (!prerender)
    538     return;
    539 
    540   DCHECK(!prerender->is_match_complete_replacement);
    541   prerender->is_match_complete_replacement = true;
    542   Send(prerender->launcher_child_id,
    543        new PrerenderMsg_OnPrerenderStop(prerender->prerender_id));
    544   // Do not call RemovePrerender here. The replacement needs to stay connected
    545   // to the HTMLLinkElement in the renderer so it notices renderer-triggered
    546   // cancelations.
    547 }
    548 
    549 }  // namespace prerender
    550