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_manager.h" 6 7 #include <algorithm> 8 #include <functional> 9 #include <string> 10 #include <vector> 11 12 #include "base/bind.h" 13 #include "base/bind_helpers.h" 14 #include "base/logging.h" 15 #include "base/memory/weak_ptr.h" 16 #include "base/metrics/histogram.h" 17 #include "base/prefs/pref_service.h" 18 #include "base/stl_util.h" 19 #include "base/strings/utf_string_conversions.h" 20 #include "base/time/time.h" 21 #include "base/values.h" 22 #include "chrome/browser/browser_process.h" 23 #include "chrome/browser/chrome_notification_types.h" 24 #include "chrome/browser/common/cancelable_request.h" 25 #include "chrome/browser/favicon/favicon_tab_helper.h" 26 #include "chrome/browser/net/chrome_cookie_notification_details.h" 27 #include "chrome/browser/predictors/predictor_database.h" 28 #include "chrome/browser/predictors/predictor_database_factory.h" 29 #include "chrome/browser/prerender/prerender_condition.h" 30 #include "chrome/browser/prerender/prerender_contents.h" 31 #include "chrome/browser/prerender/prerender_field_trial.h" 32 #include "chrome/browser/prerender/prerender_final_status.h" 33 #include "chrome/browser/prerender/prerender_handle.h" 34 #include "chrome/browser/prerender/prerender_histograms.h" 35 #include "chrome/browser/prerender/prerender_history.h" 36 #include "chrome/browser/prerender/prerender_local_predictor.h" 37 #include "chrome/browser/prerender/prerender_manager_factory.h" 38 #include "chrome/browser/prerender/prerender_tab_helper.h" 39 #include "chrome/browser/prerender/prerender_tracker.h" 40 #include "chrome/browser/prerender/prerender_util.h" 41 #include "chrome/browser/profiles/profile.h" 42 #include "chrome/browser/tab_contents/tab_util.h" 43 #include "chrome/browser/ui/tab_contents/core_tab_helper.h" 44 #include "chrome/browser/ui/tab_contents/core_tab_helper_delegate.h" 45 #include "chrome/common/chrome_switches.h" 46 #include "chrome/common/pref_names.h" 47 #include "chrome/common/prerender_messages.h" 48 #include "content/public/browser/browser_thread.h" 49 #include "content/public/browser/devtools_agent_host.h" 50 #include "content/public/browser/navigation_controller.h" 51 #include "content/public/browser/notification_service.h" 52 #include "content/public/browser/notification_source.h" 53 #include "content/public/browser/render_process_host.h" 54 #include "content/public/browser/render_view_host.h" 55 #include "content/public/browser/session_storage_namespace.h" 56 #include "content/public/browser/web_contents.h" 57 #include "content/public/browser/web_contents_delegate.h" 58 #include "content/public/browser/web_contents_view.h" 59 #include "content/public/common/favicon_url.h" 60 #include "content/public/common/url_constants.h" 61 #include "extensions/common/constants.h" 62 #include "net/url_request/url_request_context.h" 63 #include "net/url_request/url_request_context_getter.h" 64 65 using content::BrowserThread; 66 using content::RenderViewHost; 67 using content::SessionStorageNamespace; 68 using content::WebContents; 69 using predictors::LoggedInPredictorTable; 70 71 namespace prerender { 72 73 namespace { 74 75 // Time interval at which periodic cleanups are performed. 76 const int kPeriodicCleanupIntervalMs = 1000; 77 78 // Valid HTTP methods for prerendering. 79 const char* const kValidHttpMethods[] = { 80 "GET", 81 "HEAD", 82 "OPTIONS", 83 "POST", 84 "TRACE", 85 }; 86 87 // Length of prerender history, for display in chrome://net-internals 88 const int kHistoryLength = 100; 89 90 // Indicates whether a Prerender has been cancelled such that we need 91 // a dummy replacement for the purpose of recording the correct PPLT for 92 // the Match Complete case. 93 // Traditionally, "Match" means that a prerendered page was actually visited & 94 // the prerender was used. Our goal is to have "Match" cases line up in the 95 // control group & the experiment group, so that we can make meaningful 96 // comparisons of improvements. However, in the control group, since we don't 97 // actually perform prerenders, many of the cancellation reasons cannot be 98 // detected. Therefore, in the Prerender group, when we cancel for one of these 99 // reasons, we keep track of a dummy Prerender representing what we would 100 // have in the control group. If that dummy prerender in the prerender group 101 // would then be swapped in (but isn't actually b/c it's a dummy), we record 102 // this as a MatchComplete. This allows us to compare MatchComplete's 103 // across Prerender & Control group which ideally should be lining up. 104 // This ensures that there is no bias in terms of the page load times 105 // of the pages forming the difference between the two sets. 106 107 bool NeedMatchCompleteDummyForFinalStatus(FinalStatus final_status) { 108 return final_status != FINAL_STATUS_USED && 109 final_status != FINAL_STATUS_TIMED_OUT && 110 final_status != FINAL_STATUS_MANAGER_SHUTDOWN && 111 final_status != FINAL_STATUS_APP_TERMINATING && 112 final_status != FINAL_STATUS_WINDOW_OPENER && 113 final_status != FINAL_STATUS_CACHE_OR_HISTORY_CLEARED && 114 final_status != FINAL_STATUS_CANCELLED && 115 final_status != FINAL_STATUS_DEVTOOLS_ATTACHED && 116 final_status != FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING && 117 final_status != FINAL_STATUS_PAGE_BEING_CAPTURED; 118 } 119 120 void CheckIfCookiesExistForDomainResultOnUIThread( 121 const net::CookieMonster::HasCookiesForETLDP1Callback& callback, 122 bool cookies_exist) { 123 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 124 callback.Run(cookies_exist); 125 } 126 127 void CheckIfCookiesExistForDomainResultOnIOThread( 128 const net::CookieMonster::HasCookiesForETLDP1Callback& callback, 129 bool cookies_exist) { 130 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 131 BrowserThread::PostTask( 132 BrowserThread::UI, 133 FROM_HERE, 134 base::Bind(&CheckIfCookiesExistForDomainResultOnUIThread, 135 callback, 136 cookies_exist)); 137 } 138 139 void CheckIfCookiesExistForDomainOnIOThread( 140 net::URLRequestContextGetter* rq_context, 141 const std::string& domain_key, 142 const net::CookieMonster::HasCookiesForETLDP1Callback& callback) { 143 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 144 net::CookieStore* cookie_store = 145 rq_context->GetURLRequestContext()->cookie_store(); 146 cookie_store->GetCookieMonster()->HasCookiesForETLDP1Async( 147 domain_key, 148 base::Bind(&CheckIfCookiesExistForDomainResultOnIOThread, callback)); 149 } 150 151 } // namespace 152 153 class PrerenderManager::OnCloseWebContentsDeleter 154 : public content::WebContentsDelegate, 155 public base::SupportsWeakPtr< 156 PrerenderManager::OnCloseWebContentsDeleter> { 157 public: 158 OnCloseWebContentsDeleter(PrerenderManager* manager, 159 WebContents* tab) 160 : manager_(manager), 161 tab_(tab) { 162 tab_->SetDelegate(this); 163 base::MessageLoop::current()->PostDelayedTask(FROM_HERE, 164 base::Bind(&OnCloseWebContentsDeleter::ScheduleWebContentsForDeletion, 165 AsWeakPtr(), true), 166 base::TimeDelta::FromSeconds(kDeleteWithExtremePrejudiceSeconds)); 167 } 168 169 virtual void CloseContents(WebContents* source) OVERRIDE { 170 DCHECK_EQ(tab_, source); 171 ScheduleWebContentsForDeletion(false); 172 } 173 174 virtual void SwappedOut(WebContents* source) OVERRIDE { 175 DCHECK_EQ(tab_, source); 176 ScheduleWebContentsForDeletion(false); 177 } 178 179 virtual bool ShouldSuppressDialogs() OVERRIDE { 180 return true; 181 } 182 183 private: 184 static const int kDeleteWithExtremePrejudiceSeconds = 3; 185 186 void ScheduleWebContentsForDeletion(bool timeout) { 187 tab_->SetDelegate(NULL); 188 manager_->ScheduleDeleteOldWebContents(tab_.release(), this); 189 UMA_HISTOGRAM_BOOLEAN("Prerender.TabContentsDeleterTimeout", timeout); 190 } 191 192 PrerenderManager* manager_; 193 scoped_ptr<WebContents> tab_; 194 195 DISALLOW_COPY_AND_ASSIGN(OnCloseWebContentsDeleter); 196 }; 197 198 // static 199 bool PrerenderManager::is_prefetch_enabled_ = false; 200 201 // static 202 int PrerenderManager::prerenders_per_session_count_ = 0; 203 204 // static 205 PrerenderManager::PrerenderManagerMode PrerenderManager::mode_ = 206 PRERENDER_MODE_ENABLED; 207 208 struct PrerenderManager::NavigationRecord { 209 NavigationRecord(const GURL& url, base::TimeTicks time) 210 : url(url), 211 time(time) { 212 } 213 214 GURL url; 215 base::TimeTicks time; 216 }; 217 218 PrerenderManager::PrerenderedWebContentsData:: 219 PrerenderedWebContentsData(Origin origin) : origin(origin) { 220 } 221 222 PrerenderManager::WouldBePrerenderedWebContentsData:: 223 WouldBePrerenderedWebContentsData(Origin origin) 224 : origin(origin), 225 state(WAITING_FOR_PROVISIONAL_LOAD) { 226 } 227 228 PrerenderManager::PrerenderManager(Profile* profile, 229 PrerenderTracker* prerender_tracker) 230 : enabled_(profile && profile->GetPrefs() && 231 profile->GetPrefs()->GetBoolean(prefs::kNetworkPredictionEnabled)), 232 profile_(profile), 233 prerender_tracker_(prerender_tracker), 234 prerender_contents_factory_(PrerenderContents::CreateFactory()), 235 last_prerender_start_time_(GetCurrentTimeTicks() - 236 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs)), 237 prerender_history_(new PrerenderHistory(kHistoryLength)), 238 histograms_(new PrerenderHistograms()) { 239 // There are some assumptions that the PrerenderManager is on the UI thread. 240 // Any other checks simply make sure that the PrerenderManager is accessed on 241 // the same thread that it was created on. 242 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 243 244 if (IsLocalPredictorEnabled()) 245 local_predictor_.reset(new PrerenderLocalPredictor(this)); 246 247 if (IsLoggedInPredictorEnabled() && !profile_->IsOffTheRecord()) { 248 predictors::PredictorDatabase* predictor_db = 249 predictors::PredictorDatabaseFactory::GetForProfile(profile); 250 if (predictor_db) { 251 logged_in_predictor_table_ = predictor_db->logged_in_table(); 252 scoped_ptr<LoggedInStateMap> new_state_map(new LoggedInStateMap); 253 LoggedInStateMap* new_state_map_ptr = new_state_map.get(); 254 BrowserThread::PostTaskAndReply( 255 BrowserThread::DB, FROM_HERE, 256 base::Bind(&LoggedInPredictorTable::GetAllData, 257 logged_in_predictor_table_, 258 new_state_map_ptr), 259 base::Bind(&PrerenderManager::LoggedInPredictorDataReceived, 260 AsWeakPtr(), 261 base::Passed(&new_state_map))); 262 } 263 } 264 265 // Certain experiments override our default config_ values. 266 switch (PrerenderManager::GetMode()) { 267 case PrerenderManager::PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP: 268 config_.max_link_concurrency = 4; 269 config_.max_link_concurrency_per_launcher = 2; 270 break; 271 case PrerenderManager::PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP: 272 config_.time_to_live = base::TimeDelta::FromMinutes(15); 273 break; 274 default: 275 break; 276 } 277 278 notification_registrar_.Add( 279 this, chrome::NOTIFICATION_COOKIE_CHANGED, 280 content::NotificationService::AllBrowserContextsAndSources()); 281 282 MediaCaptureDevicesDispatcher::GetInstance()->AddObserver(this); 283 } 284 285 PrerenderManager::~PrerenderManager() { 286 MediaCaptureDevicesDispatcher::GetInstance()->RemoveObserver(this); 287 288 // The earlier call to BrowserContextKeyedService::Shutdown() should have 289 // emptied these vectors already. 290 DCHECK(active_prerenders_.empty()); 291 DCHECK(to_delete_prerenders_.empty()); 292 } 293 294 void PrerenderManager::Shutdown() { 295 DestroyAllContents(FINAL_STATUS_MANAGER_SHUTDOWN); 296 STLDeleteElements(&prerender_conditions_); 297 on_close_web_contents_deleters_.clear(); 298 // Must happen before |profile_| is set to NULL as 299 // |local_predictor_| accesses it. 300 if (local_predictor_) 301 local_predictor_->Shutdown(); 302 profile_ = NULL; 303 304 DCHECK(active_prerenders_.empty()); 305 } 306 307 PrerenderHandle* PrerenderManager::AddPrerenderFromLinkRelPrerender( 308 int process_id, 309 int route_id, 310 const GURL& url, 311 const content::Referrer& referrer, 312 const gfx::Size& size) { 313 #if defined(OS_ANDROID) 314 // TODO(jcivelli): http://crbug.com/113322 We should have an option to disable 315 // link-prerender and enable omnibox-prerender only. 316 return NULL; 317 #else 318 DCHECK(!size.IsEmpty()); 319 Origin origin = ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN; 320 SessionStorageNamespace* session_storage_namespace = NULL; 321 // Unit tests pass in a process_id == -1. 322 if (process_id != -1) { 323 RenderViewHost* source_render_view_host = 324 RenderViewHost::FromID(process_id, route_id); 325 if (!source_render_view_host) 326 return NULL; 327 WebContents* source_web_contents = 328 WebContents::FromRenderViewHost(source_render_view_host); 329 if (!source_web_contents) 330 return NULL; 331 if (source_web_contents->GetURL().host() == url.host()) 332 origin = ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN; 333 // TODO(ajwong): This does not correctly handle storage for isolated apps. 334 session_storage_namespace = 335 source_web_contents->GetController() 336 .GetDefaultSessionStorageNamespace(); 337 } 338 339 // If the prerender request comes from a recently cancelled prerender that 340 // |this| still owns, then abort the prerender. 341 for (ScopedVector<PrerenderData>::iterator it = to_delete_prerenders_.begin(); 342 it != to_delete_prerenders_.end(); ++it) { 343 PrerenderContents* prerender_contents = (*it)->contents(); 344 int contents_child_id; 345 int contents_route_id; 346 if (prerender_contents->GetChildId(&contents_child_id) && 347 prerender_contents->GetRouteId(&contents_route_id)) { 348 if (contents_child_id == process_id && contents_route_id == route_id) 349 return NULL; 350 } 351 } 352 353 if (PrerenderData* parent_prerender_data = 354 FindPrerenderDataForChildAndRoute(process_id, route_id)) { 355 // Instead of prerendering from inside of a running prerender, we will defer 356 // this request until its launcher is made visible. 357 if (PrerenderContents* contents = parent_prerender_data->contents()) { 358 PrerenderHandle* prerender_handle = 359 new PrerenderHandle(static_cast<PrerenderData*>(NULL)); 360 scoped_ptr<PrerenderContents::PendingPrerenderInfo> 361 pending_prerender_info(new PrerenderContents::PendingPrerenderInfo( 362 prerender_handle->weak_ptr_factory_.GetWeakPtr(), 363 origin, url, referrer, size)); 364 365 contents->AddPendingPrerender(pending_prerender_info.Pass()); 366 return prerender_handle; 367 } 368 } 369 370 return AddPrerender(origin, process_id, url, referrer, size, 371 session_storage_namespace); 372 #endif 373 } 374 375 PrerenderHandle* PrerenderManager::AddPrerenderFromOmnibox( 376 const GURL& url, 377 SessionStorageNamespace* session_storage_namespace, 378 const gfx::Size& size) { 379 if (!IsOmniboxEnabled(profile_)) 380 return NULL; 381 return AddPrerender(ORIGIN_OMNIBOX, -1, url, content::Referrer(), size, 382 session_storage_namespace); 383 } 384 385 PrerenderHandle* PrerenderManager::AddPrerenderFromLocalPredictor( 386 const GURL& url, 387 SessionStorageNamespace* session_storage_namespace, 388 const gfx::Size& size) { 389 return AddPrerender(ORIGIN_LOCAL_PREDICTOR, -1, url, content::Referrer(), 390 size, session_storage_namespace); 391 } 392 393 void PrerenderManager::DestroyPrerenderForRenderView( 394 int process_id, int view_id, FinalStatus final_status) { 395 DCHECK(CalledOnValidThread()); 396 if (PrerenderData* prerender_data = 397 FindPrerenderDataForChildAndRoute(process_id, view_id)) { 398 prerender_data->contents()->Destroy(final_status); 399 } 400 } 401 402 void PrerenderManager::CancelAllPrerenders() { 403 DCHECK(CalledOnValidThread()); 404 while (!active_prerenders_.empty()) { 405 PrerenderContents* prerender_contents = 406 active_prerenders_.front()->contents(); 407 prerender_contents->Destroy(FINAL_STATUS_CANCELLED); 408 } 409 } 410 411 bool PrerenderManager::MaybeUsePrerenderedPage(WebContents* web_contents, 412 const GURL& url) { 413 DCHECK(CalledOnValidThread()); 414 DCHECK(!IsWebContentsPrerendering(web_contents, NULL)); 415 416 DeleteOldEntries(); 417 to_delete_prerenders_.clear(); 418 // TODO(ajwong): This doesn't handle isolated apps correctly. 419 PrerenderData* prerender_data = FindPrerenderData( 420 url, 421 web_contents->GetController().GetDefaultSessionStorageNamespace()); 422 if (!prerender_data) 423 return false; 424 DCHECK(prerender_data->contents()); 425 if (IsNoSwapInExperiment(prerender_data->contents()->experiment_id())) 426 return false; 427 428 if (WebContents* new_web_contents = 429 prerender_data->contents()->prerender_contents()) { 430 if (web_contents == new_web_contents) 431 return false; // Do not swap in to ourself. 432 433 // We cannot swap in if there is no last committed entry, because we would 434 // show a blank page under an existing entry from the current tab. Even if 435 // there is a pending entry, it may not commit. 436 // TODO(creis): If there is a pending navigation and no last committed 437 // entry, we might be able to transfer the network request instead. 438 if (!new_web_contents->GetController().CanPruneAllButVisible()) 439 return false; 440 } 441 442 // Do not use the prerendered version if there is an opener object. 443 if (web_contents->HasOpener()) { 444 prerender_data->contents()->Destroy(FINAL_STATUS_WINDOW_OPENER); 445 return false; 446 } 447 448 // Do not swap in the prerender if the current WebContents is being captured. 449 if (web_contents->GetCapturerCount() > 0) { 450 prerender_data->contents()->Destroy(FINAL_STATUS_PAGE_BEING_CAPTURED); 451 return false; 452 } 453 454 // If we are just in the control group (which can be detected by noticing 455 // that prerendering hasn't even started yet), record that |web_contents| now 456 // would be showing a prerendered contents, but otherwise, don't do anything. 457 if (!prerender_data->contents()->prerendering_has_started()) { 458 MarkWebContentsAsWouldBePrerendered(web_contents, 459 prerender_data->contents()->origin()); 460 prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED); 461 return false; 462 } 463 464 // Don't use prerendered pages if debugger is attached to the tab. 465 // See http://crbug.com/98541 466 if (content::DevToolsAgentHost::IsDebuggerAttached(web_contents)) { 467 DestroyAndMarkMatchCompleteAsUsed(prerender_data->contents(), 468 FINAL_STATUS_DEVTOOLS_ATTACHED); 469 return false; 470 } 471 472 // If the prerendered page is in the middle of a cross-site navigation, 473 // don't swap it in because there isn't a good way to merge histories. 474 if (prerender_data->contents()->IsCrossSiteNavigationPending()) { 475 DestroyAndMarkMatchCompleteAsUsed( 476 prerender_data->contents(), 477 FINAL_STATUS_CROSS_SITE_NAVIGATION_PENDING); 478 return false; 479 } 480 481 // For bookkeeping purposes, we need to mark this WebContents to 482 // reflect that it would have been prerendered. 483 if (GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP) { 484 MarkWebContentsAsWouldBePrerendered(web_contents, 485 prerender_data->contents()->origin()); 486 prerender_data->contents()->Destroy(FINAL_STATUS_WOULD_HAVE_BEEN_USED); 487 return false; 488 } 489 490 int child_id, route_id; 491 CHECK(prerender_data->contents()->GetChildId(&child_id)); 492 CHECK(prerender_data->contents()->GetRouteId(&route_id)); 493 494 // Try to set the prerendered page as used, so any subsequent attempts to 495 // cancel on other threads will fail. If this fails because the prerender 496 // was already cancelled, possibly on another thread, fail. 497 if (!prerender_tracker_->TryUse(child_id, route_id)) 498 return false; 499 500 // At this point, we've determined that we will use the prerender. 501 ScopedVector<PrerenderData>::iterator to_erase = 502 FindIteratorForPrerenderContents(prerender_data->contents()); 503 DCHECK(active_prerenders_.end() != to_erase); 504 DCHECK_EQ(prerender_data, *to_erase); 505 scoped_ptr<PrerenderContents> 506 prerender_contents(prerender_data->ReleaseContents()); 507 active_prerenders_.erase(to_erase); 508 509 if (!prerender_contents->load_start_time().is_null()) { 510 histograms_->RecordTimeUntilUsed( 511 prerender_contents->origin(), 512 GetCurrentTimeTicks() - prerender_contents->load_start_time()); 513 } 514 515 histograms_->RecordPerSessionCount(prerender_contents->origin(), 516 ++prerenders_per_session_count_); 517 histograms_->RecordUsedPrerender(prerender_contents->origin()); 518 prerender_contents->SetFinalStatus(FINAL_STATUS_USED); 519 520 RenderViewHost* new_render_view_host = 521 prerender_contents->prerender_contents()->GetRenderViewHost(); 522 new_render_view_host->Send( 523 new PrerenderMsg_SetIsPrerendering(new_render_view_host->GetRoutingID(), 524 false)); 525 526 // Start pending prerender requests from the PrerenderContents, if there are 527 // any. 528 prerender_contents->PrepareForUse(); 529 530 WebContents* new_web_contents = 531 prerender_contents->ReleasePrerenderContents(); 532 WebContents* old_web_contents = web_contents; 533 DCHECK(new_web_contents); 534 DCHECK(old_web_contents); 535 536 MarkWebContentsAsPrerendered(new_web_contents, prerender_contents->origin()); 537 538 // Merge the browsing history. 539 new_web_contents->GetController().CopyStateFromAndPrune( 540 &old_web_contents->GetController()); 541 CoreTabHelper::FromWebContents(old_web_contents)->delegate()-> 542 SwapTabContents(old_web_contents, new_web_contents); 543 prerender_contents->CommitHistory(new_web_contents); 544 545 GURL icon_url = prerender_contents->icon_url(); 546 547 if (!icon_url.is_empty()) { 548 #if defined(OS_ANDROID) 549 // Do the delayed icon fetch since we didn't download 550 // the favicon during prerendering on mobile devices. 551 FaviconTabHelper * favicon_tap_helper = 552 FaviconTabHelper::FromWebContents(new_web_contents); 553 favicon_tap_helper->set_should_fetch_icons(true); 554 favicon_tap_helper->FetchFavicon(icon_url); 555 #endif // defined(OS_ANDROID) 556 557 std::vector<content::FaviconURL> urls; 558 urls.push_back(content::FaviconURL(icon_url, content::FaviconURL::FAVICON)); 559 FaviconTabHelper::FromWebContents(new_web_contents)-> 560 DidUpdateFaviconURL(prerender_contents->page_id(), urls); 561 } 562 563 // Update PPLT metrics: 564 // If the tab has finished loading, record a PPLT of 0. 565 // If the tab is still loading, reset its start time to the current time. 566 PrerenderTabHelper* prerender_tab_helper = 567 PrerenderTabHelper::FromWebContents(new_web_contents); 568 DCHECK(prerender_tab_helper != NULL); 569 prerender_tab_helper->PrerenderSwappedIn(); 570 571 if (old_web_contents->NeedToFireBeforeUnload()) { 572 // Schedule the delete to occur after the tab has run its unload handlers. 573 on_close_web_contents_deleters_.push_back( 574 new OnCloseWebContentsDeleter(this, old_web_contents)); 575 old_web_contents->GetRenderViewHost()-> 576 FirePageBeforeUnload(false); 577 } else { 578 // No unload handler to run, so delete asap. 579 ScheduleDeleteOldWebContents(old_web_contents, NULL); 580 } 581 582 // TODO(cbentzel): Should prerender_contents move to the pending delete 583 // list, instead of deleting directly here? 584 AddToHistory(prerender_contents.get()); 585 RecordNavigation(url); 586 return true; 587 } 588 589 void PrerenderManager::MoveEntryToPendingDelete(PrerenderContents* entry, 590 FinalStatus final_status) { 591 DCHECK(CalledOnValidThread()); 592 DCHECK(entry); 593 594 ScopedVector<PrerenderData>::iterator it = 595 FindIteratorForPrerenderContents(entry); 596 DCHECK(it != active_prerenders_.end()); 597 598 // If this PrerenderContents is being deleted due to a cancellation any time 599 // after the prerender has started then we need to create a dummy replacement 600 // for PPLT accounting purposes for the Match Complete group. This is the case 601 // if the cancellation is for any reason that would not occur in the control 602 // group case. 603 if (entry->prerendering_has_started() && 604 entry->match_complete_status() == 605 PrerenderContents::MATCH_COMPLETE_DEFAULT && 606 NeedMatchCompleteDummyForFinalStatus(final_status) && 607 ActuallyPrerendering()) { 608 // TODO(tburkard): I'd like to DCHECK that we are actually prerendering. 609 // However, what if new conditions are added and 610 // NeedMatchCompleteDummyForFinalStatus is not being updated. Not sure 611 // what's the best thing to do here. For now, I will just check whether 612 // we are actually prerendering. 613 (*it)->MakeIntoMatchCompleteReplacement(); 614 } else { 615 to_delete_prerenders_.push_back(*it); 616 active_prerenders_.weak_erase(it); 617 } 618 619 // Destroy the old WebContents relatively promptly to reduce resource usage, 620 // and in the case of HTML5 media, reduce the chance of playing any sound. 621 PostCleanupTask(); 622 } 623 624 // static 625 void PrerenderManager::RecordPerceivedPageLoadTime( 626 base::TimeDelta perceived_page_load_time, 627 double fraction_plt_elapsed_at_swap_in, 628 WebContents* web_contents, 629 const GURL& url) { 630 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 631 PrerenderManager* prerender_manager = 632 PrerenderManagerFactory::GetForProfile( 633 Profile::FromBrowserContext(web_contents->GetBrowserContext())); 634 if (!prerender_manager) 635 return; 636 if (!prerender_manager->IsEnabled()) 637 return; 638 639 Origin prerender_origin = ORIGIN_NONE; 640 if (prerender_manager->IsWebContentsPrerendering(web_contents, 641 &prerender_origin)) { 642 prerender_manager->histograms_->RecordPageLoadTimeNotSwappedIn( 643 prerender_origin, perceived_page_load_time, url); 644 return; 645 } 646 647 bool was_prerender = prerender_manager->IsWebContentsPrerendered( 648 web_contents, &prerender_origin); 649 bool was_complete_prerender = was_prerender || 650 prerender_manager->WouldWebContentsBePrerendered(web_contents, 651 &prerender_origin); 652 prerender_manager->histograms_->RecordPerceivedPageLoadTime( 653 prerender_origin, perceived_page_load_time, was_prerender, 654 was_complete_prerender, url); 655 656 if (was_prerender) { 657 prerender_manager->histograms_->RecordPercentLoadDoneAtSwapin( 658 prerender_origin, fraction_plt_elapsed_at_swap_in); 659 } 660 if (prerender_manager->local_predictor_.get()) { 661 prerender_manager->local_predictor_-> 662 OnPLTEventForURL(url, perceived_page_load_time); 663 } 664 } 665 666 void PrerenderManager::RecordFractionPixelsFinalAtSwapin( 667 content::WebContents* web_contents, 668 double fraction) { 669 Origin origin = ORIGIN_NONE; 670 bool is_prerendered = IsWebContentsPrerendered(web_contents, &origin); 671 DCHECK(is_prerendered); 672 histograms_->RecordFractionPixelsFinalAtSwapin(origin, fraction); 673 } 674 675 void PrerenderManager::set_enabled(bool enabled) { 676 DCHECK(CalledOnValidThread()); 677 enabled_ = enabled; 678 } 679 680 // static 681 bool PrerenderManager::IsPrefetchEnabled() { 682 return is_prefetch_enabled_; 683 } 684 685 // static 686 void PrerenderManager::SetIsPrefetchEnabled(bool value) { 687 is_prefetch_enabled_ = value; 688 } 689 690 // static 691 PrerenderManager::PrerenderManagerMode PrerenderManager::GetMode() { 692 return mode_; 693 } 694 695 // static 696 void PrerenderManager::SetMode(PrerenderManagerMode mode) { 697 mode_ = mode; 698 } 699 700 // static 701 const char* PrerenderManager::GetModeString() { 702 switch (mode_) { 703 case PRERENDER_MODE_DISABLED: 704 return "_Disabled"; 705 case PRERENDER_MODE_ENABLED: 706 case PRERENDER_MODE_EXPERIMENT_PRERENDER_GROUP: 707 return "_Enabled"; 708 case PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP: 709 return "_Control"; 710 case PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP: 711 return "_Multi"; 712 case PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP: 713 return "_15MinTTL"; 714 case PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP: 715 return "_NoUse"; 716 case PRERENDER_MODE_MAX: 717 default: 718 NOTREACHED() << "Invalid PrerenderManager mode."; 719 break; 720 }; 721 return ""; 722 } 723 724 // static 725 bool PrerenderManager::IsPrerenderingPossible() { 726 return GetMode() != PRERENDER_MODE_DISABLED; 727 } 728 729 // static 730 bool PrerenderManager::ActuallyPrerendering() { 731 return IsPrerenderingPossible() && !IsControlGroup(kNoExperiment); 732 } 733 734 // static 735 bool PrerenderManager::IsControlGroup(uint8 experiment_id) { 736 return GetMode() == PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP || 737 IsControlGroupExperiment(experiment_id); 738 } 739 740 // static 741 bool PrerenderManager::IsNoUseGroup() { 742 return GetMode() == PRERENDER_MODE_EXPERIMENT_NO_USE_GROUP; 743 } 744 745 bool PrerenderManager::IsWebContentsPrerendering( 746 const WebContents* web_contents, 747 Origin* origin) const { 748 DCHECK(CalledOnValidThread()); 749 if (PrerenderContents* prerender_contents = 750 GetPrerenderContents(web_contents)) { 751 if (origin) 752 *origin = prerender_contents->origin(); 753 return true; 754 } 755 756 // Also look through the pending-deletion list. 757 for (ScopedVector<PrerenderData>::const_iterator it = 758 to_delete_prerenders_.begin(); 759 it != to_delete_prerenders_.end(); 760 ++it) { 761 if (PrerenderContents* prerender_contents = (*it)->contents()) { 762 WebContents* prerender_web_contents = 763 prerender_contents->prerender_contents(); 764 if (prerender_web_contents == web_contents) { 765 if (origin) 766 *origin = prerender_contents->origin(); 767 return true; 768 } 769 } 770 } 771 772 return false; 773 } 774 775 PrerenderContents* PrerenderManager::GetPrerenderContents( 776 const content::WebContents* web_contents) const { 777 DCHECK(CalledOnValidThread()); 778 for (ScopedVector<PrerenderData>::const_iterator it = 779 active_prerenders_.begin(); 780 it != active_prerenders_.end(); ++it) { 781 WebContents* prerender_web_contents = 782 (*it)->contents()->prerender_contents(); 783 if (prerender_web_contents == web_contents) { 784 return (*it)->contents(); 785 } 786 } 787 return NULL; 788 } 789 790 const std::vector<WebContents*> 791 PrerenderManager::GetAllPrerenderingContents() const { 792 DCHECK(CalledOnValidThread()); 793 std::vector<WebContents*> result; 794 795 for (ScopedVector<PrerenderData>::const_iterator it = 796 active_prerenders_.begin(); 797 it != active_prerenders_.end(); ++it) { 798 if (WebContents* contents = (*it)->contents()->prerender_contents()) 799 result.push_back(contents); 800 } 801 802 return result; 803 } 804 805 void PrerenderManager::MarkWebContentsAsPrerendered(WebContents* web_contents, 806 Origin origin) { 807 DCHECK(CalledOnValidThread()); 808 prerendered_web_contents_data_.insert( 809 base::hash_map<content::WebContents*, 810 PrerenderedWebContentsData>::value_type( 811 web_contents, PrerenderedWebContentsData(origin))); 812 } 813 814 void PrerenderManager::MarkWebContentsAsWouldBePrerendered( 815 WebContents* web_contents, 816 Origin origin) { 817 DCHECK(CalledOnValidThread()); 818 would_be_prerendered_map_.insert( 819 base::hash_map<content::WebContents*, 820 WouldBePrerenderedWebContentsData>::value_type( 821 web_contents, 822 WouldBePrerenderedWebContentsData(origin))); 823 } 824 825 void PrerenderManager::MarkWebContentsAsNotPrerendered( 826 WebContents* web_contents) { 827 DCHECK(CalledOnValidThread()); 828 prerendered_web_contents_data_.erase(web_contents); 829 base::hash_map<content::WebContents*, WouldBePrerenderedWebContentsData>:: 830 iterator it = would_be_prerendered_map_.find(web_contents); 831 if (it != would_be_prerendered_map_.end()) { 832 if (it->second.state == 833 WouldBePrerenderedWebContentsData::WAITING_FOR_PROVISIONAL_LOAD) { 834 it->second.state = 835 WouldBePrerenderedWebContentsData::SEEN_PROVISIONAL_LOAD; 836 } else { 837 would_be_prerendered_map_.erase(it); 838 } 839 } 840 } 841 842 bool PrerenderManager::IsWebContentsPrerendered( 843 content::WebContents* web_contents, 844 Origin* origin) const { 845 DCHECK(CalledOnValidThread()); 846 base::hash_map<content::WebContents*, PrerenderedWebContentsData>:: 847 const_iterator it = prerendered_web_contents_data_.find(web_contents); 848 if (it == prerendered_web_contents_data_.end()) 849 return false; 850 if (origin) 851 *origin = it->second.origin; 852 return true; 853 } 854 855 bool PrerenderManager::WouldWebContentsBePrerendered( 856 WebContents* web_contents, 857 Origin* origin) const { 858 DCHECK(CalledOnValidThread()); 859 base::hash_map<content::WebContents*, WouldBePrerenderedWebContentsData>:: 860 const_iterator it = would_be_prerendered_map_.find(web_contents); 861 if (it == would_be_prerendered_map_.end()) 862 return false; 863 if (origin) 864 *origin = it->second.origin; 865 return true; 866 } 867 868 bool PrerenderManager::HasRecentlyBeenNavigatedTo(Origin origin, 869 const GURL& url) { 870 DCHECK(CalledOnValidThread()); 871 872 CleanUpOldNavigations(); 873 std::list<NavigationRecord>::const_reverse_iterator end = navigations_.rend(); 874 for (std::list<NavigationRecord>::const_reverse_iterator it = 875 navigations_.rbegin(); 876 it != end; 877 ++it) { 878 if (it->url == url) { 879 base::TimeDelta delta = GetCurrentTimeTicks() - it->time; 880 histograms_->RecordTimeSinceLastRecentVisit(origin, delta); 881 return true; 882 } 883 } 884 885 return false; 886 } 887 888 // static 889 bool PrerenderManager::IsValidHttpMethod(const std::string& method) { 890 // method has been canonicalized to upper case at this point so we can just 891 // compare them. 892 DCHECK_EQ(method, StringToUpperASCII(method)); 893 for (size_t i = 0; i < arraysize(kValidHttpMethods); ++i) { 894 if (method.compare(kValidHttpMethods[i]) == 0) 895 return true; 896 } 897 898 return false; 899 } 900 901 // static 902 bool PrerenderManager::DoesURLHaveValidScheme(const GURL& url) { 903 return (IsWebURL(url) || 904 url.SchemeIs(extensions::kExtensionScheme) || 905 url.SchemeIs("data")); 906 } 907 908 // static 909 bool PrerenderManager::DoesSubresourceURLHaveValidScheme(const GURL& url) { 910 return DoesURLHaveValidScheme(url) || url == GURL(content::kAboutBlankURL); 911 } 912 913 DictionaryValue* PrerenderManager::GetAsValue() const { 914 DCHECK(CalledOnValidThread()); 915 DictionaryValue* dict_value = new DictionaryValue(); 916 dict_value->Set("history", prerender_history_->GetEntriesAsValue()); 917 dict_value->Set("active", GetActivePrerendersAsValue()); 918 dict_value->SetBoolean("enabled", enabled_); 919 dict_value->SetBoolean("omnibox_enabled", IsOmniboxEnabled(profile_)); 920 // If prerender is disabled via a flag this method is not even called. 921 std::string enabled_note; 922 if (IsControlGroup(kNoExperiment)) 923 enabled_note += "(Control group: Not actually prerendering) "; 924 if (IsNoUseGroup()) 925 enabled_note += "(No-use group: Not swapping in prerendered pages) "; 926 if (GetMode() == PRERENDER_MODE_EXPERIMENT_15MIN_TTL_GROUP) 927 enabled_note += 928 "(15 min TTL group: Extended prerender eviction to 15 mins) "; 929 dict_value->SetString("enabled_note", enabled_note); 930 return dict_value; 931 } 932 933 void PrerenderManager::ClearData(int clear_flags) { 934 DCHECK_GE(clear_flags, 0); 935 DCHECK_LT(clear_flags, CLEAR_MAX); 936 if (clear_flags & CLEAR_PRERENDER_CONTENTS) 937 DestroyAllContents(FINAL_STATUS_CACHE_OR_HISTORY_CLEARED); 938 // This has to be second, since destroying prerenders can add to the history. 939 if (clear_flags & CLEAR_PRERENDER_HISTORY) 940 prerender_history_->Clear(); 941 } 942 943 void PrerenderManager::RecordFinalStatusWithMatchCompleteStatus( 944 Origin origin, 945 uint8 experiment_id, 946 PrerenderContents::MatchCompleteStatus mc_status, 947 FinalStatus final_status) const { 948 histograms_->RecordFinalStatus(origin, 949 experiment_id, 950 mc_status, 951 final_status); 952 } 953 954 void PrerenderManager::AddCondition(const PrerenderCondition* condition) { 955 prerender_conditions_.push_back(condition); 956 } 957 958 void PrerenderManager::RecordNavigation(const GURL& url) { 959 DCHECK(CalledOnValidThread()); 960 961 navigations_.push_back(NavigationRecord(url, GetCurrentTimeTicks())); 962 CleanUpOldNavigations(); 963 } 964 965 // protected 966 struct PrerenderManager::PrerenderData::OrderByExpiryTime { 967 bool operator()(const PrerenderData* a, const PrerenderData* b) const { 968 return a->expiry_time() < b->expiry_time(); 969 } 970 }; 971 972 PrerenderManager::PrerenderData::PrerenderData(PrerenderManager* manager, 973 PrerenderContents* contents, 974 base::TimeTicks expiry_time) 975 : manager_(manager), 976 contents_(contents), 977 handle_count_(0), 978 expiry_time_(expiry_time) { 979 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_); 980 } 981 982 PrerenderManager::PrerenderData::~PrerenderData() { 983 } 984 985 void PrerenderManager::PrerenderData::MakeIntoMatchCompleteReplacement() { 986 DCHECK(contents_); 987 contents_->set_match_complete_status( 988 PrerenderContents::MATCH_COMPLETE_REPLACED); 989 PrerenderData* to_delete = new PrerenderData(manager_, contents_.release(), 990 expiry_time_); 991 contents_.reset(to_delete->contents_->CreateMatchCompleteReplacement()); 992 manager_->to_delete_prerenders_.push_back(to_delete); 993 } 994 995 void PrerenderManager::PrerenderData::OnHandleCreated(PrerenderHandle* handle) { 996 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_); 997 ++handle_count_; 998 contents_->AddObserver(handle); 999 } 1000 1001 void PrerenderManager::PrerenderData::OnHandleNavigatedAway( 1002 PrerenderHandle* handle) { 1003 DCHECK_LT(0, handle_count_); 1004 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_); 1005 // We intentionally don't decrement the handle count here, so that the 1006 // prerender won't be canceled until it times out. 1007 manager_->SourceNavigatedAway(this); 1008 } 1009 1010 void PrerenderManager::PrerenderData::OnHandleCanceled( 1011 PrerenderHandle* handle) { 1012 DCHECK_LT(0, handle_count_); 1013 DCHECK_NE(static_cast<PrerenderContents*>(NULL), contents_); 1014 1015 if (--handle_count_ == 0) { 1016 // This will eventually remove this object from active_prerenders_. 1017 contents_->Destroy(FINAL_STATUS_CANCELLED); 1018 } 1019 } 1020 1021 PrerenderContents* PrerenderManager::PrerenderData::ReleaseContents() { 1022 return contents_.release(); 1023 } 1024 1025 void PrerenderManager::SetPrerenderContentsFactory( 1026 PrerenderContents::Factory* prerender_contents_factory) { 1027 DCHECK(CalledOnValidThread()); 1028 prerender_contents_factory_.reset(prerender_contents_factory); 1029 } 1030 1031 void PrerenderManager::StartPendingPrerenders( 1032 const int process_id, 1033 ScopedVector<PrerenderContents::PendingPrerenderInfo>* pending_prerenders, 1034 content::SessionStorageNamespace* session_storage_namespace) { 1035 for (ScopedVector<PrerenderContents::PendingPrerenderInfo>::iterator 1036 it = pending_prerenders->begin(); 1037 it != pending_prerenders->end(); ++it) { 1038 PrerenderContents::PendingPrerenderInfo* info = *it; 1039 PrerenderHandle* existing_prerender_handle = 1040 info->weak_prerender_handle.get(); 1041 if (!existing_prerender_handle) 1042 continue; 1043 1044 DCHECK(!existing_prerender_handle->IsPrerendering()); 1045 DCHECK(process_id == -1 || session_storage_namespace); 1046 1047 scoped_ptr<PrerenderHandle> new_prerender_handle(AddPrerender( 1048 info->origin, process_id, 1049 info->url, info->referrer, info->size, 1050 session_storage_namespace)); 1051 if (new_prerender_handle) { 1052 // AddPrerender has returned a new prerender handle to us. We want to make 1053 // |existing_prerender_handle| active, so move the underlying 1054 // PrerenderData to our new handle. 1055 existing_prerender_handle->AdoptPrerenderDataFrom( 1056 new_prerender_handle.get()); 1057 continue; 1058 } 1059 } 1060 } 1061 1062 void PrerenderManager::SourceNavigatedAway(PrerenderData* prerender_data) { 1063 // The expiry time of our prerender data will likely change because of 1064 // this navigation. This requires a resort of active_prerenders_. 1065 ScopedVector<PrerenderData>::iterator it = 1066 std::find(active_prerenders_.begin(), active_prerenders_.end(), 1067 prerender_data); 1068 if (it == active_prerenders_.end()) 1069 return; 1070 1071 (*it)->set_expiry_time( 1072 std::min((*it)->expiry_time(), 1073 GetExpiryTimeForNavigatedAwayPrerender())); 1074 SortActivePrerenders(); 1075 } 1076 1077 // private 1078 PrerenderHandle* PrerenderManager::AddPrerender( 1079 Origin origin, 1080 int process_id, 1081 const GURL& url_arg, 1082 const content::Referrer& referrer, 1083 const gfx::Size& size, 1084 SessionStorageNamespace* session_storage_namespace) { 1085 DCHECK(CalledOnValidThread()); 1086 1087 if (!IsEnabled()) 1088 return NULL; 1089 1090 if ((origin == ORIGIN_LINK_REL_PRERENDER_CROSSDOMAIN || 1091 origin == ORIGIN_LINK_REL_PRERENDER_SAMEDOMAIN) && 1092 IsGoogleSearchResultURL(referrer.url)) { 1093 origin = ORIGIN_GWS_PRERENDER; 1094 } 1095 1096 GURL url = url_arg; 1097 GURL alias_url; 1098 uint8 experiment = GetQueryStringBasedExperiment(url_arg); 1099 if (IsControlGroup(experiment) && 1100 MaybeGetQueryStringBasedAliasURL(url, &alias_url)) { 1101 url = alias_url; 1102 } 1103 1104 // From here on, we will record a FinalStatus so we need to register with the 1105 // histogram tracking. 1106 histograms_->RecordPrerender(origin, url_arg); 1107 1108 if (PrerenderData* preexisting_prerender_data = 1109 FindPrerenderData(url, session_storage_namespace)) { 1110 RecordFinalStatus(origin, experiment, FINAL_STATUS_DUPLICATE); 1111 return new PrerenderHandle(preexisting_prerender_data); 1112 } 1113 1114 // Do not prerender if there are too many render processes, and we would 1115 // have to use an existing one. We do not want prerendering to happen in 1116 // a shared process, so that we can always reliably lower the CPU 1117 // priority for prerendering. 1118 // In single-process mode, ShouldTryToUseExistingProcessHost() always returns 1119 // true, so that case needs to be explicitly checked for. 1120 // TODO(tburkard): Figure out how to cancel prerendering in the opposite 1121 // case, when a new tab is added to a process used for prerendering. 1122 // On Android we do reuse processes as we have a limited number of them and we 1123 // still want the benefits of prerendering even when several tabs are open. 1124 #if !defined(OS_ANDROID) 1125 if (content::RenderProcessHost::ShouldTryToUseExistingProcessHost( 1126 profile_, url) && 1127 !content::RenderProcessHost::run_renderer_in_process()) { 1128 RecordFinalStatus(origin, experiment, FINAL_STATUS_TOO_MANY_PROCESSES); 1129 return NULL; 1130 } 1131 #endif 1132 1133 // Check if enough time has passed since the last prerender. 1134 if (!DoesRateLimitAllowPrerender(origin)) { 1135 // Cancel the prerender. We could add it to the pending prerender list but 1136 // this doesn't make sense as the next prerender request will be triggered 1137 // by a navigation and is unlikely to be the same site. 1138 RecordFinalStatus(origin, experiment, FINAL_STATUS_RATE_LIMIT_EXCEEDED); 1139 return NULL; 1140 } 1141 1142 PrerenderContents* prerender_contents = CreatePrerenderContents( 1143 url, referrer, origin, experiment); 1144 DCHECK(prerender_contents); 1145 active_prerenders_.push_back( 1146 new PrerenderData(this, prerender_contents, 1147 GetExpiryTimeForNewPrerender(origin))); 1148 if (!prerender_contents->Init()) { 1149 DCHECK(active_prerenders_.end() == 1150 FindIteratorForPrerenderContents(prerender_contents)); 1151 return NULL; 1152 } 1153 1154 histograms_->RecordPrerenderStarted(origin); 1155 DCHECK(!prerender_contents->prerendering_has_started()); 1156 1157 PrerenderHandle* prerender_handle = 1158 new PrerenderHandle(active_prerenders_.back()); 1159 SortActivePrerenders(); 1160 1161 last_prerender_start_time_ = GetCurrentTimeTicks(); 1162 1163 gfx::Size contents_size = 1164 size.IsEmpty() ? config_.default_tab_bounds.size() : size; 1165 1166 prerender_contents->StartPrerendering(process_id, contents_size, 1167 session_storage_namespace); 1168 1169 DCHECK(IsControlGroup(experiment) || 1170 prerender_contents->prerendering_has_started()); 1171 1172 if (GetMode() == PRERENDER_MODE_EXPERIMENT_MULTI_PRERENDER_GROUP) 1173 histograms_->RecordConcurrency(active_prerenders_.size()); 1174 1175 StartSchedulingPeriodicCleanups(); 1176 return prerender_handle; 1177 } 1178 1179 void PrerenderManager::StartSchedulingPeriodicCleanups() { 1180 DCHECK(CalledOnValidThread()); 1181 if (repeating_timer_.IsRunning()) 1182 return; 1183 repeating_timer_.Start(FROM_HERE, 1184 base::TimeDelta::FromMilliseconds(kPeriodicCleanupIntervalMs), 1185 this, 1186 &PrerenderManager::PeriodicCleanup); 1187 } 1188 1189 void PrerenderManager::StopSchedulingPeriodicCleanups() { 1190 DCHECK(CalledOnValidThread()); 1191 repeating_timer_.Stop(); 1192 } 1193 1194 void PrerenderManager::PeriodicCleanup() { 1195 DCHECK(CalledOnValidThread()); 1196 DeleteOldWebContents(); 1197 DeleteOldEntries(); 1198 if (active_prerenders_.empty()) 1199 StopSchedulingPeriodicCleanups(); 1200 1201 // Grab a copy of the current PrerenderContents pointers, so that we 1202 // will not interfere with potential deletions of the list. 1203 std::vector<PrerenderContents*> 1204 prerender_contents(active_prerenders_.size()); 1205 std::transform(active_prerenders_.begin(), active_prerenders_.end(), 1206 prerender_contents.begin(), 1207 std::mem_fun(&PrerenderData::contents)); 1208 1209 // And now check for prerenders using too much memory. 1210 std::for_each(prerender_contents.begin(), prerender_contents.end(), 1211 std::mem_fun( 1212 &PrerenderContents::DestroyWhenUsingTooManyResources)); 1213 1214 to_delete_prerenders_.clear(); 1215 } 1216 1217 void PrerenderManager::PostCleanupTask() { 1218 DCHECK(CalledOnValidThread()); 1219 base::MessageLoop::current()->PostTask( 1220 FROM_HERE, 1221 base::Bind(&PrerenderManager::PeriodicCleanup, AsWeakPtr())); 1222 } 1223 1224 base::TimeTicks PrerenderManager::GetExpiryTimeForNewPrerender( 1225 Origin origin) const { 1226 base::TimeDelta ttl = config_.time_to_live; 1227 if (origin == ORIGIN_LOCAL_PREDICTOR) 1228 ttl = base::TimeDelta::FromSeconds(GetLocalPredictorTTLSeconds()); 1229 return GetCurrentTimeTicks() + ttl; 1230 } 1231 1232 base::TimeTicks PrerenderManager::GetExpiryTimeForNavigatedAwayPrerender() 1233 const { 1234 return GetCurrentTimeTicks() + config_.abandon_time_to_live; 1235 } 1236 1237 void PrerenderManager::DeleteOldEntries() { 1238 DCHECK(CalledOnValidThread()); 1239 while (!active_prerenders_.empty()) { 1240 PrerenderData* prerender_data = active_prerenders_.front(); 1241 DCHECK(prerender_data); 1242 DCHECK(prerender_data->contents()); 1243 1244 if (prerender_data->expiry_time() > GetCurrentTimeTicks()) 1245 return; 1246 prerender_data->contents()->Destroy(FINAL_STATUS_TIMED_OUT); 1247 } 1248 } 1249 1250 base::Time PrerenderManager::GetCurrentTime() const { 1251 return base::Time::Now(); 1252 } 1253 1254 base::TimeTicks PrerenderManager::GetCurrentTimeTicks() const { 1255 return base::TimeTicks::Now(); 1256 } 1257 1258 PrerenderContents* PrerenderManager::CreatePrerenderContents( 1259 const GURL& url, 1260 const content::Referrer& referrer, 1261 Origin origin, 1262 uint8 experiment_id) { 1263 DCHECK(CalledOnValidThread()); 1264 return prerender_contents_factory_->CreatePrerenderContents( 1265 this, profile_, url, referrer, origin, experiment_id); 1266 } 1267 1268 void PrerenderManager::SortActivePrerenders() { 1269 std::sort(active_prerenders_.begin(), active_prerenders_.end(), 1270 PrerenderData::OrderByExpiryTime()); 1271 } 1272 1273 PrerenderManager::PrerenderData* PrerenderManager::FindPrerenderData( 1274 const GURL& url, 1275 const SessionStorageNamespace* session_storage_namespace) { 1276 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin(); 1277 it != active_prerenders_.end(); ++it) { 1278 if ((*it)->contents()->Matches(url, session_storage_namespace)) 1279 return *it; 1280 } 1281 return NULL; 1282 } 1283 1284 PrerenderManager::PrerenderData* 1285 PrerenderManager::FindPrerenderDataForChildAndRoute( 1286 const int child_id, const int route_id) { 1287 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin(); 1288 it != active_prerenders_.end(); ++it) { 1289 PrerenderContents* prerender_contents = (*it)->contents(); 1290 1291 int contents_child_id; 1292 if (!prerender_contents->GetChildId(&contents_child_id)) 1293 continue; 1294 int contents_route_id; 1295 if (!prerender_contents->GetRouteId(&contents_route_id)) 1296 continue; 1297 1298 if (contents_child_id == child_id && contents_route_id == route_id) 1299 return *it; 1300 } 1301 return NULL; 1302 } 1303 1304 ScopedVector<PrerenderManager::PrerenderData>::iterator 1305 PrerenderManager::FindIteratorForPrerenderContents( 1306 PrerenderContents* prerender_contents) { 1307 for (ScopedVector<PrerenderData>::iterator it = active_prerenders_.begin(); 1308 it != active_prerenders_.end(); ++it) { 1309 if (prerender_contents == (*it)->contents()) 1310 return it; 1311 } 1312 return active_prerenders_.end(); 1313 } 1314 1315 bool PrerenderManager::DoesRateLimitAllowPrerender(Origin origin) const { 1316 DCHECK(CalledOnValidThread()); 1317 base::TimeDelta elapsed_time = 1318 GetCurrentTimeTicks() - last_prerender_start_time_; 1319 histograms_->RecordTimeBetweenPrerenderRequests(origin, elapsed_time); 1320 if (!config_.rate_limit_enabled) 1321 return true; 1322 return elapsed_time >= 1323 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs); 1324 } 1325 1326 void PrerenderManager::DeleteOldWebContents() { 1327 while (!old_web_contents_list_.empty()) { 1328 WebContents* web_contents = old_web_contents_list_.front(); 1329 old_web_contents_list_.pop_front(); 1330 // TODO(dominich): should we use Instant Unload Handler here? 1331 delete web_contents; 1332 } 1333 } 1334 1335 void PrerenderManager::CleanUpOldNavigations() { 1336 DCHECK(CalledOnValidThread()); 1337 1338 // Cutoff. Navigations before this cutoff can be discarded. 1339 base::TimeTicks cutoff = GetCurrentTimeTicks() - 1340 base::TimeDelta::FromMilliseconds(kNavigationRecordWindowMs); 1341 while (!navigations_.empty()) { 1342 if (navigations_.front().time > cutoff) 1343 break; 1344 navigations_.pop_front(); 1345 } 1346 } 1347 1348 void PrerenderManager::ScheduleDeleteOldWebContents( 1349 WebContents* tab, 1350 OnCloseWebContentsDeleter* deleter) { 1351 old_web_contents_list_.push_back(tab); 1352 PostCleanupTask(); 1353 1354 if (deleter) { 1355 ScopedVector<OnCloseWebContentsDeleter>::iterator i = std::find( 1356 on_close_web_contents_deleters_.begin(), 1357 on_close_web_contents_deleters_.end(), 1358 deleter); 1359 DCHECK(i != on_close_web_contents_deleters_.end()); 1360 on_close_web_contents_deleters_.erase(i); 1361 } 1362 } 1363 1364 void PrerenderManager::AddToHistory(PrerenderContents* contents) { 1365 PrerenderHistory::Entry entry(contents->prerender_url(), 1366 contents->final_status(), 1367 contents->origin(), 1368 base::Time::Now()); 1369 prerender_history_->AddEntry(entry); 1370 } 1371 1372 Value* PrerenderManager::GetActivePrerendersAsValue() const { 1373 ListValue* list_value = new ListValue(); 1374 for (ScopedVector<PrerenderData>::const_iterator it = 1375 active_prerenders_.begin(); 1376 it != active_prerenders_.end(); ++it) { 1377 if (Value* prerender_value = (*it)->contents()->GetAsValue()) 1378 list_value->Append(prerender_value); 1379 } 1380 return list_value; 1381 } 1382 1383 void PrerenderManager::DestroyAllContents(FinalStatus final_status) { 1384 DeleteOldWebContents(); 1385 while (!active_prerenders_.empty()) { 1386 PrerenderContents* contents = active_prerenders_.front()->contents(); 1387 contents->Destroy(final_status); 1388 } 1389 to_delete_prerenders_.clear(); 1390 } 1391 1392 void PrerenderManager::DestroyAndMarkMatchCompleteAsUsed( 1393 PrerenderContents* prerender_contents, 1394 FinalStatus final_status) { 1395 prerender_contents->set_match_complete_status( 1396 PrerenderContents::MATCH_COMPLETE_REPLACED); 1397 histograms_->RecordFinalStatus(prerender_contents->origin(), 1398 prerender_contents->experiment_id(), 1399 PrerenderContents::MATCH_COMPLETE_REPLACEMENT, 1400 FINAL_STATUS_WOULD_HAVE_BEEN_USED); 1401 prerender_contents->Destroy(final_status); 1402 } 1403 1404 void PrerenderManager::RecordFinalStatus(Origin origin, 1405 uint8 experiment_id, 1406 FinalStatus final_status) const { 1407 RecordFinalStatusWithMatchCompleteStatus( 1408 origin, experiment_id, 1409 PrerenderContents::MATCH_COMPLETE_DEFAULT, 1410 final_status); 1411 } 1412 1413 bool PrerenderManager::IsEnabled() const { 1414 DCHECK(CalledOnValidThread()); 1415 if (!enabled_) 1416 return false; 1417 for (std::list<const PrerenderCondition*>::const_iterator it = 1418 prerender_conditions_.begin(); 1419 it != prerender_conditions_.end(); 1420 ++it) { 1421 const PrerenderCondition* condition = *it; 1422 if (!condition->CanPrerender()) 1423 return false; 1424 } 1425 return true; 1426 } 1427 1428 PrerenderManager* FindPrerenderManagerUsingRenderProcessId( 1429 int render_process_id) { 1430 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1431 content::RenderProcessHost* render_process_host = 1432 content::RenderProcessHost::FromID(render_process_id); 1433 // Each render process is guaranteed to only hold RenderViews owned by the 1434 // same BrowserContext. This is enforced by 1435 // RenderProcessHost::GetExistingProcessHost. 1436 if (!render_process_host || !render_process_host->GetBrowserContext()) 1437 return NULL; 1438 Profile* profile = Profile::FromBrowserContext( 1439 render_process_host->GetBrowserContext()); 1440 if (!profile) 1441 return NULL; 1442 return PrerenderManagerFactory::GetInstance()->GetForProfile(profile); 1443 } 1444 1445 void PrerenderManager::Observe(int type, 1446 const content::NotificationSource& source, 1447 const content::NotificationDetails& details) { 1448 Profile* profile = content::Source<Profile>(source).ptr(); 1449 if (!profile || !profile_->IsSameProfile(profile) || 1450 profile->IsOffTheRecord()) { 1451 return; 1452 } 1453 DCHECK(type == chrome::NOTIFICATION_COOKIE_CHANGED); 1454 CookieChanged(content::Details<ChromeCookieDetails>(details).ptr()); 1455 } 1456 1457 void PrerenderManager::OnCreatingAudioStream(int render_process_id, 1458 int render_view_id) { 1459 WebContents* tab = tab_util::GetWebContentsByID( 1460 render_process_id, render_view_id); 1461 if (!tab) 1462 return; 1463 1464 if (!IsWebContentsPrerendering(tab, NULL)) 1465 return; 1466 1467 prerender_tracker()->TryCancel( 1468 render_process_id, render_view_id, 1469 prerender::FINAL_STATUS_CREATING_AUDIO_STREAM); 1470 } 1471 1472 void PrerenderManager::RecordLikelyLoginOnURL(const GURL& url) { 1473 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1474 if (!IsWebURL(url)) 1475 return; 1476 if (logged_in_predictor_table_.get()) { 1477 BrowserThread::PostTask( 1478 BrowserThread::DB, 1479 FROM_HERE, 1480 base::Bind(&LoggedInPredictorTable::AddDomainFromURL, 1481 logged_in_predictor_table_, 1482 url)); 1483 } 1484 std::string key = LoggedInPredictorTable::GetKey(url); 1485 if (!logged_in_state_.get()) 1486 return; 1487 if (logged_in_state_->count(key)) 1488 return; 1489 (*logged_in_state_)[key] = base::Time::Now().ToInternalValue(); 1490 } 1491 1492 void PrerenderManager::CheckIfLikelyLoggedInOnURL( 1493 const GURL& url, 1494 bool* lookup_result, 1495 bool* database_was_present, 1496 const base::Closure& result_cb) { 1497 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1498 if (!logged_in_predictor_table_.get()) { 1499 *database_was_present = false; 1500 *lookup_result = false; 1501 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, result_cb); 1502 return; 1503 } 1504 BrowserThread::PostTaskAndReply( 1505 BrowserThread::DB, FROM_HERE, 1506 base::Bind(&LoggedInPredictorTable::HasUserLoggedIn, 1507 logged_in_predictor_table_, 1508 url, 1509 lookup_result, 1510 database_was_present), 1511 result_cb); 1512 } 1513 1514 1515 void PrerenderManager::CookieChanged(ChromeCookieDetails* details) { 1516 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1517 1518 if (!logged_in_predictor_table_.get()) 1519 return; 1520 1521 // We only care when a cookie has been removed. 1522 if (!details->removed) 1523 return; 1524 1525 std::string domain_key = 1526 LoggedInPredictorTable::GetKeyFromDomain(details->cookie->Domain()); 1527 1528 // If we have no record of this domain as a potentially logged in domain, 1529 // nothing to do here. 1530 if (logged_in_state_.get() && logged_in_state_->count(domain_key) < 1) 1531 return; 1532 1533 net::URLRequestContextGetter* rq_context = profile_->GetRequestContext(); 1534 if (!rq_context) 1535 return; 1536 1537 BrowserThread::PostTask( 1538 BrowserThread::IO, FROM_HERE, 1539 base::Bind(&CheckIfCookiesExistForDomainOnIOThread, 1540 base::Unretained(rq_context), 1541 domain_key, 1542 base::Bind( 1543 &PrerenderManager::CookieChangedAnyCookiesLeftLookupResult, 1544 AsWeakPtr(), 1545 domain_key) 1546 )); 1547 } 1548 1549 void PrerenderManager::CookieChangedAnyCookiesLeftLookupResult( 1550 const std::string& domain_key, 1551 bool cookies_exist) { 1552 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1553 1554 if (cookies_exist) 1555 return; 1556 1557 if (logged_in_predictor_table_.get()) { 1558 BrowserThread::PostTask(BrowserThread::DB, 1559 FROM_HERE, 1560 base::Bind(&LoggedInPredictorTable::DeleteDomain, 1561 logged_in_predictor_table_, 1562 domain_key)); 1563 } 1564 1565 if (logged_in_state_.get()) 1566 logged_in_state_->erase(domain_key); 1567 } 1568 1569 void PrerenderManager::LoggedInPredictorDataReceived( 1570 scoped_ptr<LoggedInStateMap> new_map) { 1571 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1572 logged_in_state_.swap(new_map); 1573 } 1574 1575 } // namespace prerender 1576