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   // Remove the handle from the PrerenderLinkManager before we cancel this
     94   // prerender, to avoid reentering the PrerenderLinkManager, sending events to
     95   // the underlying prerender and making a second erase.
     96   scoped_ptr<PrerenderHandle> own_prerender_handle(prerender->handle);
     97   prerender->handle = NULL;
     98   RemovePrerender(prerender);
     99 
    100   if (own_prerender_handle)
    101     own_prerender_handle->OnCancel();
    102 
    103   StartPrerenders();
    104 }
    105 
    106 void PrerenderLinkManager::OnAbandonPrerender(int child_id, int prerender_id) {
    107   LinkPrerender* prerender = FindByLauncherChildIdAndPrerenderId(child_id,
    108                                                                  prerender_id);
    109   if (!prerender)
    110     return;
    111 
    112   if (!prerender->handle) {
    113     RemovePrerender(prerender);
    114     return;
    115   }
    116 
    117   prerender->handle->OnNavigateAway();
    118   DCHECK(prerender->handle);
    119 
    120   // If the prerender is not running, remove it from the list so it does not
    121   // leak. If it is running, it will send a cancel event when it stops which
    122   // will remove it.
    123   if (!prerender->handle->IsPrerendering())
    124     RemovePrerender(prerender);
    125 }
    126 
    127 void PrerenderLinkManager::OnChannelClosing(int child_id) {
    128   std::list<LinkPrerender>::iterator next = prerenders_.begin();
    129   while (next != prerenders_.end()) {
    130     std::list<LinkPrerender>::iterator it = next;
    131     ++next;
    132 
    133     if (child_id != it->launcher_child_id)
    134       continue;
    135 
    136     const size_t running_prerender_count = CountRunningPrerenders();
    137     OnAbandonPrerender(child_id, it->prerender_id);
    138     DCHECK_EQ(running_prerender_count, CountRunningPrerenders());
    139   }
    140 }
    141 
    142 PrerenderLinkManager::LinkPrerender::LinkPrerender(
    143     int launcher_child_id,
    144     int prerender_id,
    145     const GURL& url,
    146     const content::Referrer& referrer,
    147     const gfx::Size& size,
    148     int render_view_route_id,
    149     TimeTicks creation_time) : launcher_child_id(launcher_child_id),
    150                                prerender_id(prerender_id),
    151                                url(url),
    152                                referrer(referrer),
    153                                size(size),
    154                                render_view_route_id(render_view_route_id),
    155                                creation_time(creation_time),
    156                                handle(NULL) {
    157 }
    158 
    159 PrerenderLinkManager::LinkPrerender::~LinkPrerender() {
    160   DCHECK_EQ(static_cast<PrerenderHandle*>(NULL), handle)
    161       << "The PrerenderHandle should be destroyed before its Prerender.";
    162 }
    163 
    164 bool PrerenderLinkManager::IsEmpty() const {
    165   return prerenders_.empty();
    166 }
    167 
    168 size_t PrerenderLinkManager::CountRunningPrerenders() const {
    169   size_t retval = 0;
    170   for (std::list<LinkPrerender>::const_iterator i = prerenders_.begin();
    171        i != prerenders_.end(); ++i) {
    172     if (i->handle && i->handle->IsPrerendering())
    173       ++retval;
    174   }
    175   return retval;
    176 }
    177 
    178 void PrerenderLinkManager::StartPrerenders() {
    179   if (has_shutdown_)
    180     return;
    181 
    182   size_t total_started_prerender_count = 0;
    183   std::multiset<std::pair<int, int> >
    184       running_launcher_and_render_view_routes;
    185 
    186   // Scan the list, counting how many prerenders have handles (and so were added
    187   // to the PrerenderManager). The count is done for the system as a whole, and
    188   // also per launcher.
    189   for (std::list<LinkPrerender>::iterator i = prerenders_.begin();
    190        i != prerenders_.end(); ++i) {
    191     if (i->handle) {
    192       ++total_started_prerender_count;
    193       std::pair<int, int> launcher_and_render_view_route(
    194           i->launcher_child_id, i->render_view_route_id);
    195       running_launcher_and_render_view_routes.insert(
    196           launcher_and_render_view_route);
    197       DCHECK_GE(manager_->config().max_link_concurrency_per_launcher,
    198                 running_launcher_and_render_view_routes.count(
    199                     launcher_and_render_view_route));
    200     }
    201 
    202     DCHECK_EQ(&(*i), FindByLauncherChildIdAndPrerenderId(i->launcher_child_id,
    203                                                          i->prerender_id));
    204   }
    205   DCHECK_GE(manager_->config().max_link_concurrency,
    206             total_started_prerender_count);
    207   DCHECK_LE(CountRunningPrerenders(), total_started_prerender_count);
    208 
    209   TimeTicks now = manager_->GetCurrentTimeTicks();
    210 
    211   // Scan the list again, starting prerenders as our counts allow.
    212   std::list<LinkPrerender>::iterator next = prerenders_.begin();
    213   while (next != prerenders_.end()) {
    214     std::list<LinkPrerender>::iterator i = next;
    215     ++next;
    216 
    217     if (total_started_prerender_count >=
    218             manager_->config().max_link_concurrency ||
    219         total_started_prerender_count >= prerenders_.size()) {
    220       // The system is already at its prerender concurrency limit.
    221       return;
    222     }
    223 
    224     if (i->handle) {
    225       // This prerender has already been added to the prerender manager.
    226       continue;
    227     }
    228 
    229     TimeDelta prerender_age = now - i->creation_time;
    230     if (prerender_age >= manager_->config().max_wait_to_launch) {
    231       // This prerender waited too long in the queue before launching.
    232       prerenders_.erase(i);
    233       continue;
    234     }
    235 
    236     std::pair<int, int> launcher_and_render_view_route(
    237         i->launcher_child_id, i->render_view_route_id);
    238     if (manager_->config().max_link_concurrency_per_launcher <=
    239         running_launcher_and_render_view_routes.count(
    240             launcher_and_render_view_route)) {
    241       // This prerender's launcher is already at its limit.
    242       continue;
    243     }
    244 
    245     PrerenderHandle* handle = manager_->AddPrerenderFromLinkRelPrerender(
    246         i->launcher_child_id, i->render_view_route_id,
    247         i->url, i->referrer, i->size);
    248     if (!handle) {
    249       // This prerender couldn't be launched, it's gone.
    250       prerenders_.erase(i);
    251       continue;
    252     }
    253 
    254     // We have successfully started a new prerender.
    255     i->handle = handle;
    256     ++total_started_prerender_count;
    257     handle->SetObserver(this);
    258     if (handle->IsPrerendering())
    259       OnPrerenderStart(handle);
    260 
    261     running_launcher_and_render_view_routes.insert(
    262         launcher_and_render_view_route);
    263   }
    264 }
    265 
    266 PrerenderLinkManager::LinkPrerender*
    267 PrerenderLinkManager::FindByLauncherChildIdAndPrerenderId(int launcher_child_id,
    268                                                           int prerender_id) {
    269   for (std::list<LinkPrerender>::iterator i = prerenders_.begin();
    270        i != prerenders_.end(); ++i) {
    271     if (launcher_child_id == i->launcher_child_id &&
    272         prerender_id == i->prerender_id) {
    273       return &(*i);
    274     }
    275   }
    276   return NULL;
    277 }
    278 
    279 PrerenderLinkManager::LinkPrerender*
    280 PrerenderLinkManager::FindByPrerenderHandle(PrerenderHandle* prerender_handle) {
    281   DCHECK(prerender_handle);
    282   for (std::list<LinkPrerender>::iterator i = prerenders_.begin();
    283        i != prerenders_.end(); ++i) {
    284     if (prerender_handle == i->handle)
    285       return &(*i);
    286   }
    287   return NULL;
    288 }
    289 
    290 void PrerenderLinkManager::RemovePrerender(LinkPrerender* prerender) {
    291   for (std::list<LinkPrerender>::iterator i = prerenders_.begin();
    292        i != prerenders_.end(); ++i) {
    293     if (&(*i) == prerender) {
    294       scoped_ptr<PrerenderHandle> own_handle(i->handle);
    295       i->handle = NULL;
    296       prerenders_.erase(i);
    297       return;
    298     }
    299   }
    300   NOTREACHED();
    301 }
    302 
    303 void PrerenderLinkManager::Shutdown() {
    304   has_shutdown_ = true;
    305 }
    306 
    307 // In practice, this is always called from either
    308 // PrerenderLinkManager::OnAddPrerender in the regular case, or in the pending
    309 // prerender case, from PrerenderHandle::AdoptPrerenderDataFrom.
    310 void PrerenderLinkManager::OnPrerenderStart(
    311     PrerenderHandle* prerender_handle) {
    312   LinkPrerender* prerender = FindByPrerenderHandle(prerender_handle);
    313   if (!prerender)
    314     return;
    315   Send(prerender->launcher_child_id,
    316        new PrerenderMsg_OnPrerenderStart(prerender->prerender_id));
    317 }
    318 
    319 void PrerenderLinkManager::OnPrerenderStopLoading(
    320     PrerenderHandle* prerender_handle) {
    321   LinkPrerender* prerender = FindByPrerenderHandle(prerender_handle);
    322   if (!prerender)
    323     return;
    324 
    325   Send(prerender->launcher_child_id,
    326        new PrerenderMsg_OnPrerenderStopLoading(prerender->prerender_id));
    327 }
    328 
    329 void PrerenderLinkManager::OnPrerenderStop(
    330     PrerenderHandle* prerender_handle) {
    331   LinkPrerender* prerender = FindByPrerenderHandle(prerender_handle);
    332   if (!prerender)
    333     return;
    334 
    335   Send(prerender->launcher_child_id,
    336        new PrerenderMsg_OnPrerenderStop(prerender->prerender_id));
    337   RemovePrerender(prerender);
    338   StartPrerenders();
    339 }
    340 
    341 }  // namespace prerender
    342