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