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 25using base::TimeDelta; 26using base::TimeTicks; 27using content::RenderViewHost; 28using content::SessionStorageNamespace; 29 30namespace { 31 32void 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 44namespace prerender { 45 46PrerenderLinkManager::PrerenderLinkManager(PrerenderManager* manager) 47 : has_shutdown_(false), 48 manager_(manager) { 49} 50 51PrerenderLinkManager::~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 64void 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 87void 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 106void 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 127void 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 142PrerenderLinkManager::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 159PrerenderLinkManager::LinkPrerender::~LinkPrerender() { 160 DCHECK_EQ(static_cast<PrerenderHandle*>(NULL), handle) 161 << "The PrerenderHandle should be destroyed before its Prerender."; 162} 163 164bool PrerenderLinkManager::IsEmpty() const { 165 return prerenders_.empty(); 166} 167 168size_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 178void 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 266PrerenderLinkManager::LinkPrerender* 267PrerenderLinkManager::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 279PrerenderLinkManager::LinkPrerender* 280PrerenderLinkManager::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 290void 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 303void 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. 310void 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 319void 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 329void 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