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