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