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