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 <limits>
      8 #include <set>
      9 #include <utility>
     10 
     11 #include "base/memory/scoped_ptr.h"
     12 #include "chrome/browser/prerender/prerender_contents.h"
     13 #include "chrome/browser/prerender/prerender_handle.h"
     14 #include "chrome/browser/prerender/prerender_manager.h"
     15 #include "chrome/browser/prerender/prerender_manager_factory.h"
     16 #include "chrome/browser/profiles/profile.h"
     17 #include "chrome/common/prerender_messages.h"
     18 #include "content/public/browser/render_process_host.h"
     19 #include "content/public/browser/render_view_host.h"
     20 #include "content/public/browser/session_storage_namespace.h"
     21 #include "content/public/common/referrer.h"
     22 #include "ui/gfx/size.h"
     23 #include "url/gurl.h"
     24 
     25 using base::TimeDelta;
     26 using base::TimeTicks;
     27 using content::RenderViewHost;
     28 using content::SessionStorageNamespace;
     29 
     30 namespace {
     31 
     32 void Send(int child_id, IPC::Message* raw_message) {
     33   using content::RenderProcessHost;
     34   scoped_ptr<IPC::Message> own_message(raw_message);
     35 
     36   RenderProcessHost* render_process_host = RenderProcessHost::FromID(child_id);
     37   if (!render_process_host)
     38     return;
     39   render_process_host->Send(own_message.release());
     40 }
     41 
     42 }  // namespace
     43 
     44 namespace prerender {
     45 
     46 PrerenderLinkManager::PrerenderLinkManager(PrerenderManager* manager)
     47     : has_shutdown_(false),
     48       manager_(manager) {
     49 }
     50 
     51 PrerenderLinkManager::~PrerenderLinkManager() {
     52   for (std::list<LinkPrerender>::iterator i = prerenders_.begin();
     53        i != prerenders_.end(); ++i) {
     54     if (i->handle) {
     55       DCHECK(!i->handle->IsPrerendering())
     56           << "All running prerenders should stop at the same time as the "
     57           << "PrerenderManager.";
     58       delete i->handle;
     59       i->handle = 0;
     60     }
     61   }
     62 }
     63 
     64 void PrerenderLinkManager::OnAddPrerender(int launcher_child_id,
     65                                           int prerender_id,
     66                                           const GURL& url,
     67                                           const content::Referrer& referrer,
     68                                           const gfx::Size& size,
     69                                           int render_view_route_id) {
     70   DCHECK_EQ(static_cast<LinkPrerender*>(NULL),
     71             FindByLauncherChildIdAndPrerenderId(launcher_child_id,
     72                                                 prerender_id));
     73   content::RenderProcessHost* rph =
     74       content::RenderProcessHost::FromID(launcher_child_id);
     75   // Guests inside <webview> do not support cross-process navigation and so we
     76   // do not allow guests to prerender content.
     77   if (rph && rph->IsGuest())
     78     return;
     79 
     80   LinkPrerender
     81       prerender(launcher_child_id, prerender_id, url, referrer, size,
     82                 render_view_route_id, manager_->GetCurrentTimeTicks());
     83   prerenders_.push_back(prerender);
     84   StartPrerenders();
     85 }
     86 
     87 void PrerenderLinkManager::OnCancelPrerender(int child_id, int prerender_id) {
     88   LinkPrerender* prerender = FindByLauncherChildIdAndPrerenderId(child_id,
     89                                                                  prerender_id);
     90   if (!prerender)
     91     return;
     92 
     93   CancelPrerender(prerender);
     94   StartPrerenders();
     95 }
     96 
     97 void PrerenderLinkManager::OnAbandonPrerender(int child_id, int prerender_id) {
     98   LinkPrerender* prerender = FindByLauncherChildIdAndPrerenderId(child_id,
     99                                                                  prerender_id);
    100   if (!prerender)
    101     return;
    102 
    103   if (!prerender->handle) {
    104     RemovePrerender(prerender);
    105     return;
    106   }
    107 
    108   prerender->has_been_abandoned = true;
    109   prerender->handle->OnNavigateAway();
    110   DCHECK(prerender->handle);
    111 
    112   // If the prerender is not running, remove it from the list so it does not
    113   // leak. If it is running, it will send a cancel event when it stops which
    114   // will remove it.
    115   if (!prerender->handle->IsPrerendering())
    116     RemovePrerender(prerender);
    117 }
    118 
    119 void PrerenderLinkManager::OnChannelClosing(int child_id) {
    120   std::list<LinkPrerender>::iterator next = prerenders_.begin();
    121   while (next != prerenders_.end()) {
    122     std::list<LinkPrerender>::iterator it = next;
    123     ++next;
    124 
    125     if (child_id != it->launcher_child_id)
    126       continue;
    127 
    128     const size_t running_prerender_count = CountRunningPrerenders();
    129     OnAbandonPrerender(child_id, it->prerender_id);
    130     DCHECK_EQ(running_prerender_count, CountRunningPrerenders());
    131   }
    132 }
    133 
    134 PrerenderLinkManager::LinkPrerender::LinkPrerender(
    135     int launcher_child_id,
    136     int prerender_id,
    137     const GURL& url,
    138     const content::Referrer& referrer,
    139     const gfx::Size& size,
    140     int render_view_route_id,
    141     TimeTicks creation_time) : launcher_child_id(launcher_child_id),
    142                                prerender_id(prerender_id),
    143                                url(url),
    144                                referrer(referrer),
    145                                size(size),
    146                                render_view_route_id(render_view_route_id),
    147                                creation_time(creation_time),
    148                                handle(NULL),
    149                                is_match_complete_replacement(false),
    150                                has_been_abandoned(false) {
    151 }
    152 
    153 PrerenderLinkManager::LinkPrerender::~LinkPrerender() {
    154   DCHECK_EQ(static_cast<PrerenderHandle*>(NULL), handle)
    155       << "The PrerenderHandle should be destroyed before its Prerender.";
    156 }
    157 
    158 bool PrerenderLinkManager::IsEmpty() const {
    159   return prerenders_.empty();
    160 }
    161 
    162 size_t PrerenderLinkManager::CountRunningPrerenders() const {
    163   size_t retval = 0;
    164   for (std::list<LinkPrerender>::const_iterator i = prerenders_.begin();
    165        i != prerenders_.end(); ++i) {
    166     if (i->handle && i->handle->IsPrerendering())
    167       ++retval;
    168   }
    169   return retval;
    170 }
    171 
    172 void PrerenderLinkManager::StartPrerenders() {
    173   if (has_shutdown_)
    174     return;
    175 
    176   size_t total_started_prerender_count = 0;
    177   std::list<LinkPrerender*> abandoned_prerenders;
    178   std::list<std::list<LinkPrerender>::iterator> pending_prerenders;
    179   std::multiset<std::pair<int, int> >
    180       running_launcher_and_render_view_routes;
    181 
    182   // Scan the list, counting how many prerenders have handles (and so were added
    183   // to the PrerenderManager). The count is done for the system as a whole, and
    184   // also per launcher.
    185   for (std::list<LinkPrerender>::iterator i = prerenders_.begin();
    186        i != prerenders_.end(); ++i) {
    187     if (!i->handle) {
    188       pending_prerenders.push_back(i);
    189     } else {
    190       ++total_started_prerender_count;
    191       if (i->has_been_abandoned) {
    192         abandoned_prerenders.push_back(&(*i));
    193       } else {
    194         // We do not count abandoned prerenders towards their launcher, since it
    195         // has already navigated on to another page.
    196         std::pair<int, int> launcher_and_render_view_route(
    197             i->launcher_child_id, i->render_view_route_id);
    198         running_launcher_and_render_view_routes.insert(
    199             launcher_and_render_view_route);
    200         DCHECK_GE(manager_->config().max_link_concurrency_per_launcher,
    201                   running_launcher_and_render_view_routes.count(
    202                       launcher_and_render_view_route));
    203       }
    204     }
    205 
    206     DCHECK_EQ(&(*i), FindByLauncherChildIdAndPrerenderId(i->launcher_child_id,
    207                                                          i->prerender_id));
    208   }
    209   DCHECK_LE(abandoned_prerenders.size(), total_started_prerender_count);
    210   DCHECK_GE(manager_->config().max_link_concurrency,
    211             total_started_prerender_count);
    212   DCHECK_LE(CountRunningPrerenders(), total_started_prerender_count);
    213 
    214   TimeTicks now = manager_->GetCurrentTimeTicks();
    215 
    216   // Scan the pending prerenders, starting prerenders as we can.
    217   for (std::list<std::list<LinkPrerender>::iterator>::const_iterator
    218            i = pending_prerenders.begin(), end = pending_prerenders.end();
    219        i != end; ++i) {
    220     TimeDelta prerender_age = now - (*i)->creation_time;
    221     if (prerender_age >= manager_->config().max_wait_to_launch) {
    222       // This prerender waited too long in the queue before launching.
    223       prerenders_.erase(*i);
    224       continue;
    225     }
    226 
    227     std::pair<int, int> launcher_and_render_view_route(
    228         (*i)->launcher_child_id, (*i)->render_view_route_id);
    229     if (manager_->config().max_link_concurrency_per_launcher <=
    230         running_launcher_and_render_view_routes.count(
    231             launcher_and_render_view_route)) {
    232       // This prerender's launcher is already at its limit.
    233       continue;
    234     }
    235 
    236     if (total_started_prerender_count >=
    237             manager_->config().max_link_concurrency ||
    238         total_started_prerender_count >= prerenders_.size()) {
    239       // The system is already at its prerender concurrency limit. Can we kill
    240       // an abandoned prerender to make room?
    241       if (!abandoned_prerenders.empty()) {
    242         CancelPrerender(abandoned_prerenders.front());
    243         --total_started_prerender_count;
    244         abandoned_prerenders.pop_front();
    245       } else {
    246         return;
    247       }
    248     }
    249 
    250     PrerenderHandle* handle = manager_->AddPrerenderFromLinkRelPrerender(
    251         (*i)->launcher_child_id, (*i)->render_view_route_id,
    252         (*i)->url, (*i)->referrer, (*i)->size);
    253     if (!handle) {
    254       // This prerender couldn't be launched, it's gone.
    255       prerenders_.erase(*i);
    256       continue;
    257     }
    258 
    259     // We have successfully started a new prerender.
    260     (*i)->handle = handle;
    261     ++total_started_prerender_count;
    262     handle->SetObserver(this);
    263     if (handle->IsPrerendering())
    264       OnPrerenderStart(handle);
    265 
    266     running_launcher_and_render_view_routes.insert(
    267         launcher_and_render_view_route);
    268   }
    269 }
    270 
    271 PrerenderLinkManager::LinkPrerender*
    272 PrerenderLinkManager::FindByLauncherChildIdAndPrerenderId(int launcher_child_id,
    273                                                           int prerender_id) {
    274   for (std::list<LinkPrerender>::iterator i = prerenders_.begin();
    275        i != prerenders_.end(); ++i) {
    276     if (launcher_child_id == i->launcher_child_id &&
    277         prerender_id == i->prerender_id) {
    278       return &(*i);
    279     }
    280   }
    281   return NULL;
    282 }
    283 
    284 PrerenderLinkManager::LinkPrerender*
    285 PrerenderLinkManager::FindByPrerenderHandle(PrerenderHandle* prerender_handle) {
    286   DCHECK(prerender_handle);
    287   for (std::list<LinkPrerender>::iterator i = prerenders_.begin();
    288        i != prerenders_.end(); ++i) {
    289     if (prerender_handle == i->handle)
    290       return &(*i);
    291   }
    292   return NULL;
    293 }
    294 
    295 void PrerenderLinkManager::RemovePrerender(LinkPrerender* prerender) {
    296   for (std::list<LinkPrerender>::iterator i = prerenders_.begin();
    297        i != prerenders_.end(); ++i) {
    298     if (&(*i) == prerender) {
    299       scoped_ptr<PrerenderHandle> own_handle(i->handle);
    300       i->handle = NULL;
    301       prerenders_.erase(i);
    302       return;
    303     }
    304   }
    305   NOTREACHED();
    306 }
    307 
    308 void PrerenderLinkManager::CancelPrerender(LinkPrerender* prerender) {
    309   for (std::list<LinkPrerender>::iterator i = prerenders_.begin();
    310        i != prerenders_.end(); ++i) {
    311     if (&(*i) == prerender) {
    312       scoped_ptr<PrerenderHandle> own_handle(i->handle);
    313       i->handle = NULL;
    314       prerenders_.erase(i);
    315       if (own_handle)
    316         own_handle->OnCancel();
    317       return;
    318     }
    319   }
    320   NOTREACHED();
    321 }
    322 
    323 void PrerenderLinkManager::Shutdown() {
    324   has_shutdown_ = true;
    325 }
    326 
    327 // In practice, this is always called from either
    328 // PrerenderLinkManager::OnAddPrerender in the regular case, or in the pending
    329 // prerender case, from PrerenderHandle::AdoptPrerenderDataFrom.
    330 void PrerenderLinkManager::OnPrerenderStart(
    331     PrerenderHandle* prerender_handle) {
    332   LinkPrerender* prerender = FindByPrerenderHandle(prerender_handle);
    333   if (!prerender)
    334     return;
    335   Send(prerender->launcher_child_id,
    336        new PrerenderMsg_OnPrerenderStart(prerender->prerender_id));
    337 }
    338 
    339 void PrerenderLinkManager::OnPrerenderStopLoading(
    340     PrerenderHandle* prerender_handle) {
    341   LinkPrerender* prerender = FindByPrerenderHandle(prerender_handle);
    342   if (!prerender)
    343     return;
    344 
    345   Send(prerender->launcher_child_id,
    346        new PrerenderMsg_OnPrerenderStopLoading(prerender->prerender_id));
    347 }
    348 
    349 void PrerenderLinkManager::OnPrerenderStop(
    350     PrerenderHandle* prerender_handle) {
    351   LinkPrerender* prerender = FindByPrerenderHandle(prerender_handle);
    352   if (!prerender)
    353     return;
    354 
    355   // If the prerender became a match complete replacement, the stop
    356   // message has already been sent.
    357   if (!prerender->is_match_complete_replacement) {
    358     Send(prerender->launcher_child_id,
    359          new PrerenderMsg_OnPrerenderStop(prerender->prerender_id));
    360   }
    361   RemovePrerender(prerender);
    362   StartPrerenders();
    363 }
    364 
    365 void PrerenderLinkManager::OnPrerenderCreatedMatchCompleteReplacement(
    366     PrerenderHandle* prerender_handle) {
    367   LinkPrerender* prerender = FindByPrerenderHandle(prerender_handle);
    368   if (!prerender)
    369     return;
    370 
    371   DCHECK(!prerender->is_match_complete_replacement);
    372   prerender->is_match_complete_replacement = true;
    373   Send(prerender->launcher_child_id,
    374        new PrerenderMsg_OnPrerenderStop(prerender->prerender_id));
    375   // Do not call RemovePrerender here. The replacement needs to stay connected
    376   // to the HTMLLinkElement in the renderer so it notices renderer-triggered
    377   // cancelations.
    378 }
    379 
    380 }  // namespace prerender
    381