1 // Copyright (c) 2011 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 "base/logging.h" 8 #include "base/metrics/field_trial.h" 9 #include "base/metrics/histogram.h" 10 #include "base/time.h" 11 #include "base/utf_string_conversions.h" 12 #include "chrome/browser/prerender/prerender_contents.h" 13 #include "chrome/browser/prerender/prerender_final_status.h" 14 #include "chrome/browser/profiles/profile.h" 15 #include "content/browser/browser_thread.h" 16 #include "content/browser/renderer_host/render_view_host.h" 17 #include "content/browser/renderer_host/render_process_host.h" 18 #include "content/browser/renderer_host/resource_dispatcher_host.h" 19 #include "content/browser/tab_contents/render_view_host_manager.h" 20 #include "content/browser/tab_contents/tab_contents.h" 21 #include "content/common/notification_service.h" 22 #include "content/common/view_messages.h" 23 #include "googleurl/src/url_parse.h" 24 #include "googleurl/src/url_canon.h" 25 #include "googleurl/src/url_util.h" 26 27 namespace prerender { 28 29 // static 30 int PrerenderManager::prerenders_per_session_count_ = 0; 31 32 // static 33 base::TimeTicks PrerenderManager::last_prefetch_seen_time_; 34 35 // static 36 PrerenderManager::PrerenderManagerMode PrerenderManager::mode_ = 37 PRERENDER_MODE_ENABLED; 38 39 // static 40 PrerenderManager::PrerenderManagerMode PrerenderManager::GetMode() { 41 return mode_; 42 } 43 44 // static 45 void PrerenderManager::SetMode(PrerenderManagerMode mode) { 46 mode_ = mode; 47 } 48 49 // static 50 bool PrerenderManager::IsPrerenderingPossible() { 51 return 52 GetMode() == PRERENDER_MODE_ENABLED || 53 GetMode() == PRERENDER_MODE_EXPERIMENT_PRERENDER_GROUP || 54 GetMode() == PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP; 55 } 56 57 // static 58 bool PrerenderManager::IsControlGroup() { 59 return GetMode() == PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP; 60 } 61 62 // static 63 bool PrerenderManager::MaybeGetQueryStringBasedAliasURL( 64 const GURL& url, GURL* alias_url) { 65 DCHECK(alias_url); 66 url_parse::Parsed parsed; 67 url_parse::ParseStandardURL(url.spec().c_str(), url.spec().length(), 68 &parsed); 69 url_parse::Component query = parsed.query; 70 url_parse::Component key, value; 71 while (url_parse::ExtractQueryKeyValue(url.spec().c_str(), &query, &key, 72 &value)) { 73 if (key.len != 3 || strncmp(url.spec().c_str() + key.begin, "url", key.len)) 74 continue; 75 // We found a url= query string component. 76 if (value.len < 1) 77 continue; 78 url_canon::RawCanonOutputW<1024> decoded_url; 79 url_util::DecodeURLEscapeSequences(url.spec().c_str() + value.begin, 80 value.len, &decoded_url); 81 GURL new_url(string16(decoded_url.data(), decoded_url.length())); 82 if (!new_url.is_empty() && new_url.is_valid()) { 83 *alias_url = new_url; 84 return true; 85 } 86 return false; 87 } 88 return false; 89 } 90 91 struct PrerenderManager::PrerenderContentsData { 92 PrerenderContents* contents_; 93 base::Time start_time_; 94 PrerenderContentsData(PrerenderContents* contents, base::Time start_time) 95 : contents_(contents), 96 start_time_(start_time) { 97 } 98 }; 99 100 struct PrerenderManager::PendingContentsData { 101 PendingContentsData(const GURL& url, const std::vector<GURL>& alias_urls, 102 const GURL& referrer) 103 : url_(url), alias_urls_(alias_urls), referrer_(referrer) { } 104 ~PendingContentsData() {} 105 GURL url_; 106 std::vector<GURL> alias_urls_; 107 GURL referrer_; 108 }; 109 110 111 PrerenderManager::PrerenderManager(Profile* profile) 112 : rate_limit_enabled_(true), 113 enabled_(true), 114 profile_(profile), 115 max_prerender_age_(base::TimeDelta::FromSeconds( 116 kDefaultMaxPrerenderAgeSeconds)), 117 max_elements_(kDefaultMaxPrerenderElements), 118 prerender_contents_factory_(PrerenderContents::CreateFactory()), 119 last_prerender_start_time_(GetCurrentTimeTicks() - 120 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs)) { 121 } 122 123 PrerenderManager::~PrerenderManager() { 124 while (!prerender_list_.empty()) { 125 PrerenderContentsData data = prerender_list_.front(); 126 prerender_list_.pop_front(); 127 data.contents_->set_final_status(FINAL_STATUS_MANAGER_SHUTDOWN); 128 delete data.contents_; 129 } 130 } 131 132 void PrerenderManager::SetPrerenderContentsFactory( 133 PrerenderContents::Factory* prerender_contents_factory) { 134 prerender_contents_factory_.reset(prerender_contents_factory); 135 } 136 137 bool PrerenderManager::AddPreload(const GURL& url, 138 const std::vector<GURL>& alias_urls, 139 const GURL& referrer) { 140 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 141 DeleteOldEntries(); 142 if (FindEntry(url)) 143 return false; 144 145 // Local copy, since we may have to add an additional entry to it. 146 std::vector<GURL> all_alias_urls = alias_urls; 147 148 GURL additional_alias_url; 149 if (IsControlGroup() && 150 PrerenderManager::MaybeGetQueryStringBasedAliasURL( 151 url, &additional_alias_url)) 152 all_alias_urls.push_back(additional_alias_url); 153 154 // Do not prerender if there are too many render processes, and we would 155 // have to use an existing one. We do not want prerendering to happen in 156 // a shared process, so that we can always reliably lower the CPU 157 // priority for prerendering. 158 // In single-process mode, ShouldTryToUseExistingProcessHost() always returns 159 // true, so that case needs to be explicitly checked for. 160 // TODO(tburkard): Figure out how to cancel prerendering in the opposite 161 // case, when a new tab is added to a process used for prerendering. 162 if (RenderProcessHost::ShouldTryToUseExistingProcessHost() && 163 !RenderProcessHost::run_renderer_in_process()) { 164 // Only record the status if we are not in the control group. 165 if (!IsControlGroup()) 166 RecordFinalStatus(FINAL_STATUS_TOO_MANY_PROCESSES); 167 return false; 168 } 169 170 // Check if enough time has passed since the last prerender. 171 if (!DoesRateLimitAllowPrerender()) { 172 // Cancel the prerender. We could add it to the pending prerender list but 173 // this doesn't make sense as the next prerender request will be triggered 174 // by a navigation and is unlikely to be the same site. 175 RecordFinalStatus(FINAL_STATUS_RATE_LIMIT_EXCEEDED); 176 177 return false; 178 } 179 180 // TODO(cbentzel): Move invalid checks here instead of PrerenderContents? 181 PrerenderContentsData data(CreatePrerenderContents(url, all_alias_urls, 182 referrer), 183 GetCurrentTime()); 184 185 prerender_list_.push_back(data); 186 if (IsControlGroup()) { 187 data.contents_->set_final_status(FINAL_STATUS_CONTROL_GROUP); 188 } else { 189 last_prerender_start_time_ = GetCurrentTimeTicks(); 190 data.contents_->StartPrerendering(); 191 } 192 while (prerender_list_.size() > max_elements_) { 193 data = prerender_list_.front(); 194 prerender_list_.pop_front(); 195 data.contents_->set_final_status(FINAL_STATUS_EVICTED); 196 delete data.contents_; 197 } 198 StartSchedulingPeriodicCleanups(); 199 return true; 200 } 201 202 void PrerenderManager::AddPendingPreload( 203 const std::pair<int,int>& child_route_id_pair, 204 const GURL& url, 205 const std::vector<GURL>& alias_urls, 206 const GURL& referrer) { 207 // Check if this is coming from a valid prerender rvh. 208 bool is_valid_prerender = false; 209 for (std::list<PrerenderContentsData>::iterator it = prerender_list_.begin(); 210 it != prerender_list_.end(); ++it) { 211 PrerenderContents* pc = it->contents_; 212 213 int child_id; 214 int route_id; 215 bool has_child_id = pc->GetChildId(&child_id); 216 bool has_route_id = has_child_id && pc->GetRouteId(&route_id); 217 218 if (has_child_id && has_route_id && 219 child_id == child_route_id_pair.first && 220 route_id == child_route_id_pair.second) { 221 is_valid_prerender = true; 222 break; 223 } 224 } 225 226 // If not, we could check to see if the RenderViewHost specified by the 227 // child_route_id_pair exists and if so just start prerendering, as this 228 // suggests that the link was clicked, though this might prerender something 229 // that the user has already navigated away from. For now, we'll be 230 // conservative and skip the prerender which will mean some prerender requests 231 // from prerendered pages will be missed if the user navigates quickly. 232 if (!is_valid_prerender) { 233 RecordFinalStatus(FINAL_STATUS_PENDING_SKIPPED); 234 return; 235 } 236 237 PendingPrerenderList::iterator it = 238 pending_prerender_list_.find(child_route_id_pair); 239 if (it == pending_prerender_list_.end()) { 240 PendingPrerenderList::value_type el = std::make_pair(child_route_id_pair, 241 std::vector<PendingContentsData>()); 242 it = pending_prerender_list_.insert(el).first; 243 } 244 245 it->second.push_back(PendingContentsData(url, alias_urls, referrer)); 246 } 247 248 void PrerenderManager::DeleteOldEntries() { 249 while (!prerender_list_.empty()) { 250 PrerenderContentsData data = prerender_list_.front(); 251 if (IsPrerenderElementFresh(data.start_time_)) 252 return; 253 prerender_list_.pop_front(); 254 data.contents_->set_final_status(FINAL_STATUS_TIMED_OUT); 255 delete data.contents_; 256 } 257 if (prerender_list_.empty()) 258 StopSchedulingPeriodicCleanups(); 259 } 260 261 PrerenderContents* PrerenderManager::GetEntry(const GURL& url) { 262 DeleteOldEntries(); 263 for (std::list<PrerenderContentsData>::iterator it = prerender_list_.begin(); 264 it != prerender_list_.end(); 265 ++it) { 266 PrerenderContents* pc = it->contents_; 267 if (pc->MatchesURL(url)) { 268 prerender_list_.erase(it); 269 return pc; 270 } 271 } 272 // Entry not found. 273 return NULL; 274 } 275 276 bool PrerenderManager::MaybeUsePreloadedPage(TabContents* tc, const GURL& url) { 277 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 278 scoped_ptr<PrerenderContents> pc(GetEntry(url)); 279 if (pc.get() == NULL) 280 return false; 281 282 // If we are just in the control group (which can be detected by noticing 283 // that prerendering hasn't even started yet), record that this TC now would 284 // be showing a prerendered contents, but otherwise, don't do anything. 285 if (!pc->prerendering_has_started()) { 286 MarkTabContentsAsWouldBePrerendered(tc); 287 return false; 288 } 289 290 if (!pc->load_start_time().is_null()) 291 RecordTimeUntilUsed(GetCurrentTimeTicks() - pc->load_start_time()); 292 293 UMA_HISTOGRAM_COUNTS("Prerender.PrerendersPerSessionCount", 294 ++prerenders_per_session_count_); 295 pc->set_final_status(FINAL_STATUS_USED); 296 297 int child_id; 298 int route_id; 299 CHECK(pc->GetChildId(&child_id)); 300 CHECK(pc->GetRouteId(&route_id)); 301 302 RenderViewHost* rvh = pc->render_view_host(); 303 // RenderViewHosts in PrerenderContents start out hidden. 304 // Since we are actually using it now, restore it. 305 rvh->WasRestored(); 306 pc->set_render_view_host(NULL); 307 rvh->Send(new ViewMsg_DisplayPrerenderedPage(rvh->routing_id())); 308 tc->SwapInRenderViewHost(rvh); 309 MarkTabContentsAsPrerendered(tc); 310 311 // See if we have any pending prerender requests for this routing id and start 312 // the preload if we do. 313 std::pair<int, int> child_route_pair = std::make_pair(child_id, route_id); 314 PendingPrerenderList::iterator pending_it = 315 pending_prerender_list_.find(child_route_pair); 316 if (pending_it != pending_prerender_list_.end()) { 317 for (std::vector<PendingContentsData>::iterator content_it = 318 pending_it->second.begin(); 319 content_it != pending_it->second.end(); ++content_it) { 320 AddPreload(content_it->url_, content_it->alias_urls_, 321 content_it->referrer_); 322 } 323 pending_prerender_list_.erase(pending_it); 324 } 325 326 NotificationService::current()->Notify( 327 NotificationType::PRERENDER_CONTENTS_USED, 328 Source<std::pair<int, int> >(&child_route_pair), 329 NotificationService::NoDetails()); 330 331 ViewHostMsg_FrameNavigate_Params* p = pc->navigate_params(); 332 if (p != NULL) 333 tc->DidNavigate(rvh, *p); 334 335 string16 title = pc->title(); 336 if (!title.empty()) 337 tc->UpdateTitle(rvh, pc->page_id(), UTF16ToWideHack(title)); 338 339 GURL icon_url = pc->icon_url(); 340 if (!icon_url.is_empty()) { 341 std::vector<FaviconURL> urls; 342 urls.push_back(FaviconURL(icon_url, FaviconURL::FAVICON)); 343 tc->favicon_helper().OnUpdateFaviconURL(pc->page_id(), urls); 344 } 345 346 if (pc->has_stopped_loading()) 347 tc->DidStopLoading(); 348 349 return true; 350 } 351 352 void PrerenderManager::RemoveEntry(PrerenderContents* entry) { 353 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 354 for (std::list<PrerenderContentsData>::iterator it = prerender_list_.begin(); 355 it != prerender_list_.end(); 356 ++it) { 357 if (it->contents_ == entry) { 358 RemovePendingPreload(entry); 359 prerender_list_.erase(it); 360 break; 361 } 362 } 363 DeleteOldEntries(); 364 } 365 366 base::Time PrerenderManager::GetCurrentTime() const { 367 return base::Time::Now(); 368 } 369 370 base::TimeTicks PrerenderManager::GetCurrentTimeTicks() const { 371 return base::TimeTicks::Now(); 372 } 373 374 bool PrerenderManager::IsPrerenderElementFresh(const base::Time start) const { 375 base::Time now = GetCurrentTime(); 376 return (now - start < max_prerender_age_); 377 } 378 379 PrerenderContents* PrerenderManager::CreatePrerenderContents( 380 const GURL& url, 381 const std::vector<GURL>& alias_urls, 382 const GURL& referrer) { 383 return prerender_contents_factory_->CreatePrerenderContents( 384 this, profile_, url, alias_urls, referrer); 385 } 386 387 // Helper macro for histograms. 388 #define RECORD_PLT(tag, perceived_page_load_time) { \ 389 UMA_HISTOGRAM_CUSTOM_TIMES( \ 390 base::FieldTrial::MakeName(std::string("Prerender.") + tag, \ 391 "Prefetch"), \ 392 perceived_page_load_time, \ 393 base::TimeDelta::FromMilliseconds(10), \ 394 base::TimeDelta::FromSeconds(60), \ 395 100); \ 396 } 397 398 // static 399 void PrerenderManager::RecordPerceivedPageLoadTime( 400 base::TimeDelta perceived_page_load_time, 401 TabContents* tab_contents) { 402 bool within_window = WithinWindow(); 403 PrerenderManager* prerender_manager = 404 tab_contents->profile()->GetPrerenderManager(); 405 if (!prerender_manager) 406 return; 407 if (!prerender_manager->is_enabled()) 408 return; 409 RECORD_PLT("PerceivedPLT", perceived_page_load_time); 410 if (within_window) 411 RECORD_PLT("PerceivedPLTWindowed", perceived_page_load_time); 412 if (prerender_manager && 413 ((mode_ == PRERENDER_MODE_EXPERIMENT_CONTROL_GROUP && 414 prerender_manager->WouldTabContentsBePrerendered(tab_contents)) || 415 (mode_ == PRERENDER_MODE_EXPERIMENT_PRERENDER_GROUP && 416 prerender_manager->IsTabContentsPrerendered(tab_contents)))) { 417 RECORD_PLT("PerceivedPLTMatched", perceived_page_load_time); 418 } else { 419 if (within_window) 420 RECORD_PLT("PerceivedPLTWindowNotMatched", perceived_page_load_time); 421 } 422 } 423 424 void PrerenderManager::RecordTimeUntilUsed(base::TimeDelta time_until_used) { 425 UMA_HISTOGRAM_CUSTOM_TIMES( 426 "Prerender.TimeUntilUsed", 427 time_until_used, 428 base::TimeDelta::FromMilliseconds(10), 429 base::TimeDelta::FromSeconds(kDefaultMaxPrerenderAgeSeconds), 430 50); 431 } 432 433 bool PrerenderManager::is_enabled() const { 434 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 435 return enabled_; 436 } 437 438 void PrerenderManager::set_enabled(bool enabled) { 439 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 440 enabled_ = enabled; 441 } 442 443 PrerenderContents* PrerenderManager::FindEntry(const GURL& url) { 444 for (std::list<PrerenderContentsData>::iterator it = prerender_list_.begin(); 445 it != prerender_list_.end(); 446 ++it) { 447 if (it->contents_->MatchesURL(url)) 448 return it->contents_; 449 } 450 // Entry not found. 451 return NULL; 452 } 453 454 PrerenderManager::PendingContentsData* 455 PrerenderManager::FindPendingEntry(const GURL& url) { 456 for (PendingPrerenderList::iterator map_it = pending_prerender_list_.begin(); 457 map_it != pending_prerender_list_.end(); 458 ++map_it) { 459 for (std::vector<PendingContentsData>::iterator content_it = 460 map_it->second.begin(); 461 content_it != map_it->second.end(); 462 ++content_it) { 463 if (content_it->url_ == url) { 464 return &(*content_it); 465 } 466 } 467 } 468 469 return NULL; 470 } 471 472 // static 473 void PrerenderManager::RecordPrefetchTagObserved() { 474 // Ensure that we are in the UI thread, and post to the UI thread if 475 // necessary. 476 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { 477 BrowserThread::PostTask( 478 BrowserThread::UI, 479 FROM_HERE, 480 NewRunnableFunction( 481 &PrerenderManager::RecordPrefetchTagObservedOnUIThread)); 482 } else { 483 RecordPrefetchTagObservedOnUIThread(); 484 } 485 } 486 487 // static 488 void PrerenderManager::RecordPrefetchTagObservedOnUIThread() { 489 // Once we get here, we have to be on the UI thread. 490 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 491 492 // If we observe multiple tags within the 30 second window, we will still 493 // reset the window to begin at the most recent occurrence, so that we will 494 // always be in a window in the 30 seconds from each occurrence. 495 last_prefetch_seen_time_ = base::TimeTicks::Now(); 496 } 497 498 void PrerenderManager::RemovePendingPreload(PrerenderContents* entry) { 499 int child_id; 500 int route_id; 501 bool has_child_id = entry->GetChildId(&child_id); 502 bool has_route_id = has_child_id && entry->GetRouteId(&route_id); 503 504 // If the entry doesn't have a RenderViewHost then it didn't start 505 // prerendering and there shouldn't be any pending preloads to remove. 506 if (has_child_id && has_route_id) { 507 std::pair<int, int> child_route_pair = std::make_pair(child_id, route_id); 508 pending_prerender_list_.erase(child_route_pair); 509 } 510 } 511 512 // static 513 bool PrerenderManager::WithinWindow() { 514 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 515 if (last_prefetch_seen_time_.is_null()) 516 return false; 517 base::TimeDelta elapsed_time = 518 base::TimeTicks::Now() - last_prefetch_seen_time_; 519 return elapsed_time <= base::TimeDelta::FromSeconds(kWindowDurationSeconds); 520 } 521 522 bool PrerenderManager::DoesRateLimitAllowPrerender() const { 523 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 524 base::TimeDelta elapsed_time = 525 GetCurrentTimeTicks() - last_prerender_start_time_; 526 UMA_HISTOGRAM_TIMES("Prerender.TimeBetweenPrerenderRequests", 527 elapsed_time); 528 if (!rate_limit_enabled_) 529 return true; 530 return elapsed_time > 531 base::TimeDelta::FromMilliseconds(kMinTimeBetweenPrerendersMs); 532 } 533 534 void PrerenderManager::StartSchedulingPeriodicCleanups() { 535 if (repeating_timer_.IsRunning()) 536 return; 537 repeating_timer_.Start( 538 base::TimeDelta::FromMilliseconds(kPeriodicCleanupIntervalMs), 539 this, 540 &PrerenderManager::PeriodicCleanup); 541 } 542 543 void PrerenderManager::StopSchedulingPeriodicCleanups() { 544 repeating_timer_.Stop(); 545 } 546 547 void PrerenderManager::PeriodicCleanup() { 548 DeleteOldEntries(); 549 // Grab a copy of the current PrerenderContents pointers, so that we 550 // will not interfere with potential deletions of the list. 551 std::vector<PrerenderContents*> prerender_contents; 552 for (std::list<PrerenderContentsData>::iterator it = prerender_list_.begin(); 553 it != prerender_list_.end(); 554 ++it) { 555 prerender_contents.push_back(it->contents_); 556 } 557 for (std::vector<PrerenderContents*>::iterator it = 558 prerender_contents.begin(); 559 it != prerender_contents.end(); 560 ++it) { 561 (*it)->DestroyWhenUsingTooManyResources(); 562 } 563 } 564 565 void PrerenderManager::MarkTabContentsAsPrerendered(TabContents* tc) { 566 prerendered_tc_set_.insert(tc); 567 } 568 569 void PrerenderManager::MarkTabContentsAsWouldBePrerendered(TabContents* tc) { 570 would_be_prerendered_tc_set_.insert(tc); 571 } 572 573 void PrerenderManager::MarkTabContentsAsNotPrerendered(TabContents* tc) { 574 prerendered_tc_set_.erase(tc); 575 would_be_prerendered_tc_set_.erase(tc); 576 } 577 578 bool PrerenderManager::IsTabContentsPrerendered(TabContents* tc) const { 579 return prerendered_tc_set_.count(tc) > 0; 580 } 581 582 bool PrerenderManager::WouldTabContentsBePrerendered(TabContents* tc) const { 583 return would_be_prerendered_tc_set_.count(tc) > 0; 584 } 585 586 } // namespace prerender 587