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