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_local_predictor.h" 6 7 #include <ctype.h> 8 9 #include <algorithm> 10 #include <map> 11 #include <set> 12 #include <string> 13 #include <utility> 14 15 #include "base/json/json_reader.h" 16 #include "base/json/json_writer.h" 17 #include "base/metrics/field_trial.h" 18 #include "base/metrics/histogram.h" 19 #include "base/stl_util.h" 20 #include "base/timer/timer.h" 21 #include "chrome/browser/browser_process.h" 22 #include "chrome/browser/history/history_database.h" 23 #include "chrome/browser/history/history_db_task.h" 24 #include "chrome/browser/history/history_service.h" 25 #include "chrome/browser/history/history_service_factory.h" 26 #include "chrome/browser/prerender/prerender_field_trial.h" 27 #include "chrome/browser/prerender/prerender_handle.h" 28 #include "chrome/browser/prerender/prerender_histograms.h" 29 #include "chrome/browser/prerender/prerender_manager.h" 30 #include "chrome/browser/prerender/prerender_util.h" 31 #include "chrome/browser/profiles/profile.h" 32 #include "chrome/browser/safe_browsing/database_manager.h" 33 #include "chrome/browser/safe_browsing/safe_browsing_service.h" 34 #include "chrome/browser/ui/tab_contents/tab_contents_iterator.h" 35 #include "chrome/common/prefetch_messages.h" 36 #include "content/public/browser/browser_thread.h" 37 #include "content/public/browser/navigation_controller.h" 38 #include "content/public/browser/navigation_entry.h" 39 #include "content/public/browser/render_frame_host.h" 40 #include "content/public/browser/render_process_host.h" 41 #include "content/public/browser/web_contents.h" 42 #include "crypto/secure_hash.h" 43 #include "grit/browser_resources.h" 44 #include "net/base/escape.h" 45 #include "net/base/load_flags.h" 46 #include "net/url_request/url_fetcher.h" 47 #include "ui/base/page_transition_types.h" 48 #include "ui/base/resource/resource_bundle.h" 49 #include "url/url_canon.h" 50 51 using base::DictionaryValue; 52 using base::ListValue; 53 using base::Value; 54 using content::BrowserThread; 55 using ui::PageTransition; 56 using content::RenderFrameHost; 57 using content::SessionStorageNamespace; 58 using content::WebContents; 59 using history::URLID; 60 using net::URLFetcher; 61 using predictors::LoggedInPredictorTable; 62 using std::string; 63 using std::vector; 64 65 namespace prerender { 66 67 namespace { 68 69 static const size_t kURLHashSize = 5; 70 static const int kNumPrerenderCandidates = 5; 71 static const int kInvalidProcessId = -1; 72 static const int kInvalidFrameId = -1; 73 static const int kMaxPrefetchItems = 100; 74 75 } // namespace 76 77 // When considering a candidate URL to be prerendered, we need to collect the 78 // data in this struct to make the determination whether we should issue the 79 // prerender or not. 80 struct PrerenderLocalPredictor::LocalPredictorURLInfo { 81 URLID id; 82 GURL url; 83 bool url_lookup_success; 84 bool logged_in; 85 bool logged_in_lookup_ok; 86 bool local_history_based; 87 bool service_whitelist; 88 bool service_whitelist_lookup_ok; 89 bool service_whitelist_reported; 90 double priority; 91 }; 92 93 // A struct consisting of everything needed for launching a potential prerender 94 // on a navigation: The navigation URL (source) triggering potential prerenders, 95 // and a set of candidate URLs. 96 struct PrerenderLocalPredictor::CandidatePrerenderInfo { 97 LocalPredictorURLInfo source_url_; 98 vector<LocalPredictorURLInfo> candidate_urls_; 99 scoped_refptr<SessionStorageNamespace> session_storage_namespace_; 100 // Render Process ID and Route ID of the page causing the prerender to be 101 // issued. Needed so that we can cause its renderer to issue prefetches within 102 // its context. 103 int render_process_id_; 104 int render_frame_id_; 105 scoped_ptr<gfx::Size> size_; 106 base::Time start_time_; // used for various time measurements 107 explicit CandidatePrerenderInfo(URLID source_id) 108 : render_process_id_(kInvalidProcessId), 109 render_frame_id_(kInvalidFrameId) { 110 source_url_.id = source_id; 111 } 112 void MaybeAddCandidateURLFromLocalData(URLID id, double priority) { 113 LocalPredictorURLInfo info; 114 info.id = id; 115 info.local_history_based = true; 116 info.service_whitelist = false; 117 info.service_whitelist_lookup_ok = false; 118 info.service_whitelist_reported = false; 119 info.priority = priority; 120 MaybeAddCandidateURLInternal(info); 121 } 122 void MaybeAddCandidateURLFromService(GURL url, double priority, 123 bool whitelist, 124 bool whitelist_lookup_ok) { 125 LocalPredictorURLInfo info; 126 info.id = kint64max; 127 info.url = url; 128 info.url_lookup_success = true; 129 info.local_history_based = false; 130 info.service_whitelist = whitelist; 131 info.service_whitelist_lookup_ok = whitelist_lookup_ok; 132 info.service_whitelist_reported = true; 133 info.priority = priority; 134 MaybeAddCandidateURLInternal(info); 135 } 136 void MaybeAddCandidateURLInternal(const LocalPredictorURLInfo& info) { 137 // TODO(tburkard): clean up this code, potentially using a list or a heap 138 int max_candidates = kNumPrerenderCandidates; 139 // We first insert local candidates, then service candidates. 140 // Since we want to keep kNumPrerenderCandidates for both local & service 141 // candidates, we need to double the maximum number of candidates once 142 // we start seeing service candidates. 143 if (!info.local_history_based) 144 max_candidates *= 2; 145 int insert_pos = candidate_urls_.size(); 146 if (insert_pos < max_candidates) 147 candidate_urls_.push_back(info); 148 while (insert_pos > 0 && 149 candidate_urls_[insert_pos - 1].priority < info.priority) { 150 if (insert_pos < max_candidates) 151 candidate_urls_[insert_pos] = candidate_urls_[insert_pos - 1]; 152 insert_pos--; 153 } 154 if (insert_pos < max_candidates) 155 candidate_urls_[insert_pos] = info; 156 } 157 }; 158 159 namespace { 160 161 #define TIMING_HISTOGRAM(name, value) \ 162 UMA_HISTOGRAM_CUSTOM_TIMES(name, value, \ 163 base::TimeDelta::FromMilliseconds(10), \ 164 base::TimeDelta::FromSeconds(10), \ 165 50); 166 167 // Task to lookup the URL for a given URLID. 168 class GetURLForURLIDTask : public history::HistoryDBTask { 169 public: 170 GetURLForURLIDTask( 171 PrerenderLocalPredictor::CandidatePrerenderInfo* request, 172 const base::Closure& callback) 173 : request_(request), 174 callback_(callback), 175 start_time_(base::Time::Now()) { 176 } 177 178 virtual bool RunOnDBThread(history::HistoryBackend* backend, 179 history::HistoryDatabase* db) OVERRIDE { 180 DoURLLookup(db, &request_->source_url_); 181 for (int i = 0; i < static_cast<int>(request_->candidate_urls_.size()); i++) 182 DoURLLookup(db, &request_->candidate_urls_[i]); 183 return true; 184 } 185 186 virtual void DoneRunOnMainThread() OVERRIDE { 187 callback_.Run(); 188 TIMING_HISTOGRAM("Prerender.LocalPredictorURLLookupTime", 189 base::Time::Now() - start_time_); 190 } 191 192 private: 193 virtual ~GetURLForURLIDTask() {} 194 195 void DoURLLookup(history::HistoryDatabase* db, 196 PrerenderLocalPredictor::LocalPredictorURLInfo* request) { 197 history::URLRow url_row; 198 request->url_lookup_success = db->GetURLRow(request->id, &url_row); 199 if (request->url_lookup_success) 200 request->url = url_row.url(); 201 } 202 203 PrerenderLocalPredictor::CandidatePrerenderInfo* request_; 204 base::Closure callback_; 205 base::Time start_time_; 206 DISALLOW_COPY_AND_ASSIGN(GetURLForURLIDTask); 207 }; 208 209 // Task to load history from the visit database on startup. 210 class GetVisitHistoryTask : public history::HistoryDBTask { 211 public: 212 GetVisitHistoryTask(PrerenderLocalPredictor* local_predictor, 213 int max_visits) 214 : local_predictor_(local_predictor), 215 max_visits_(max_visits), 216 visit_history_(new vector<history::BriefVisitInfo>) { 217 } 218 219 virtual bool RunOnDBThread(history::HistoryBackend* backend, 220 history::HistoryDatabase* db) OVERRIDE { 221 db->GetBriefVisitInfoOfMostRecentVisits(max_visits_, visit_history_.get()); 222 return true; 223 } 224 225 virtual void DoneRunOnMainThread() OVERRIDE { 226 local_predictor_->OnGetInitialVisitHistory(visit_history_.Pass()); 227 } 228 229 private: 230 virtual ~GetVisitHistoryTask() {} 231 232 PrerenderLocalPredictor* local_predictor_; 233 int max_visits_; 234 scoped_ptr<vector<history::BriefVisitInfo> > visit_history_; 235 DISALLOW_COPY_AND_ASSIGN(GetVisitHistoryTask); 236 }; 237 238 // Maximum visit history to retrieve from the visit database. 239 const int kMaxVisitHistory = 100 * 1000; 240 241 // Visit history size at which to trigger pruning, and number of items to prune. 242 const int kVisitHistoryPruneThreshold = 120 * 1000; 243 const int kVisitHistoryPruneAmount = 20 * 1000; 244 245 const int kMinLocalPredictionTimeMs = 500; 246 247 int GetMaxLocalPredictionTimeMs() { 248 return GetLocalPredictorTTLSeconds() * 1000; 249 } 250 251 bool IsBackForward(PageTransition transition) { 252 return (transition & ui::PAGE_TRANSITION_FORWARD_BACK) != 0; 253 } 254 255 bool IsHomePage(PageTransition transition) { 256 return (transition & ui::PAGE_TRANSITION_HOME_PAGE) != 0; 257 } 258 259 bool IsIntermediateRedirect(PageTransition transition) { 260 return (transition & ui::PAGE_TRANSITION_CHAIN_END) == 0; 261 } 262 263 bool IsFormSubmit(PageTransition transition) { 264 return ui::PageTransitionCoreTypeIs(transition, 265 ui::PAGE_TRANSITION_FORM_SUBMIT); 266 } 267 268 bool ShouldExcludeTransitionForPrediction(PageTransition transition) { 269 return IsBackForward(transition) || IsHomePage(transition) || 270 IsIntermediateRedirect(transition); 271 } 272 273 base::Time GetCurrentTime() { 274 return base::Time::Now(); 275 } 276 277 bool StringContainsIgnoringCase(string haystack, string needle) { 278 std::transform(haystack.begin(), haystack.end(), haystack.begin(), ::tolower); 279 std::transform(needle.begin(), needle.end(), needle.begin(), ::tolower); 280 return haystack.find(needle) != string::npos; 281 } 282 283 bool IsExtendedRootURL(const GURL& url) { 284 const string& path = url.path(); 285 return path == "/index.html" || path == "/home.html" || 286 path == "/main.html" || 287 path == "/index.htm" || path == "/home.htm" || path == "/main.htm" || 288 path == "/index.php" || path == "/home.php" || path == "/main.php" || 289 path == "/index.asp" || path == "/home.asp" || path == "/main.asp" || 290 path == "/index.py" || path == "/home.py" || path == "/main.py" || 291 path == "/index.pl" || path == "/home.pl" || path == "/main.pl"; 292 } 293 294 bool IsRootPageURL(const GURL& url) { 295 return (url.path() == "/" || url.path() == "" || IsExtendedRootURL(url)) && 296 (!url.has_query()) && (!url.has_ref()); 297 } 298 299 bool IsLogInURL(const GURL& url) { 300 return StringContainsIgnoringCase(url.spec().c_str(), "login") || 301 StringContainsIgnoringCase(url.spec().c_str(), "signin"); 302 } 303 304 bool IsLogOutURL(const GURL& url) { 305 return StringContainsIgnoringCase(url.spec().c_str(), "logout") || 306 StringContainsIgnoringCase(url.spec().c_str(), "signout"); 307 } 308 309 int64 URLHashToInt64(const unsigned char* data) { 310 COMPILE_ASSERT(kURLHashSize < sizeof(int64), url_hash_must_fit_in_int64); 311 int64 value = 0; 312 memcpy(&value, data, kURLHashSize); 313 return value; 314 } 315 316 int64 GetInt64URLHashForURL(const GURL& url) { 317 COMPILE_ASSERT(kURLHashSize < sizeof(int64), url_hash_must_fit_in_int64); 318 scoped_ptr<crypto::SecureHash> hash( 319 crypto::SecureHash::Create(crypto::SecureHash::SHA256)); 320 int64 hash_value = 0; 321 const char* url_string = url.spec().c_str(); 322 hash->Update(url_string, strlen(url_string)); 323 hash->Finish(&hash_value, kURLHashSize); 324 return hash_value; 325 } 326 327 bool URLsIdenticalIgnoringFragments(const GURL& url1, const GURL& url2) { 328 url::Replacements<char> replacement; 329 replacement.ClearRef(); 330 GURL u1 = url1.ReplaceComponents(replacement); 331 GURL u2 = url2.ReplaceComponents(replacement); 332 return (u1 == u2); 333 } 334 335 void LookupLoggedInStatesOnDBThread( 336 scoped_refptr<LoggedInPredictorTable> logged_in_predictor_table, 337 PrerenderLocalPredictor::CandidatePrerenderInfo* request) { 338 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 339 for (int i = 0; i < static_cast<int>(request->candidate_urls_.size()); i++) { 340 PrerenderLocalPredictor::LocalPredictorURLInfo* info = 341 &request->candidate_urls_[i]; 342 if (info->url_lookup_success) { 343 logged_in_predictor_table->HasUserLoggedIn( 344 info->url, &info->logged_in, &info->logged_in_lookup_ok); 345 } else { 346 info->logged_in_lookup_ok = false; 347 } 348 } 349 } 350 351 } // namespace 352 353 struct PrerenderLocalPredictor::PrerenderProperties { 354 PrerenderProperties(URLID url_id, const GURL& url, double priority, 355 base::Time start_time) 356 : url_id(url_id), 357 url(url), 358 priority(priority), 359 start_time(start_time), 360 would_have_matched(false) { 361 } 362 363 // Default constructor for dummy element 364 PrerenderProperties() 365 : priority(0.0), would_have_matched(false) { 366 } 367 368 double GetCurrentDecayedPriority() { 369 // If we are no longer prerendering, the priority is 0. 370 if (!prerender_handle || !prerender_handle->IsPrerendering()) 371 return 0.0; 372 int half_life_time_seconds = 373 GetLocalPredictorPrerenderPriorityHalfLifeTimeSeconds(); 374 if (half_life_time_seconds < 1) 375 return priority; 376 double multiple_elapsed = 377 (GetCurrentTime() - actual_start_time).InMillisecondsF() / 378 base::TimeDelta::FromSeconds(half_life_time_seconds).InMillisecondsF(); 379 // Decay factor: 2 ^ (-multiple_elapsed) 380 double decay_factor = exp(- multiple_elapsed * log(2.0)); 381 return priority * decay_factor; 382 } 383 384 URLID url_id; 385 GURL url; 386 double priority; 387 // For expiration purposes, this is a synthetic start time consisting either 388 // of the actual start time, or of the last time the page was re-requested 389 // for prerendering - 10 seconds (unless the original request came after 390 // that). This is to emulate the effect of re-prerendering a page that is 391 // about to expire, because it was re-requested for prerendering a second 392 // time after the actual prerender being kept around. 393 base::Time start_time; 394 // The actual time this page was last requested for prerendering. 395 base::Time actual_start_time; 396 scoped_ptr<PrerenderHandle> prerender_handle; 397 // Indicates whether this prerender would have matched a URL navigated to, 398 // but was not swapped in for some reason. 399 bool would_have_matched; 400 }; 401 402 // A class simulating a set of URLs prefetched, for statistical purposes. 403 class PrerenderLocalPredictor::PrefetchList { 404 public: 405 enum SeenType { 406 SEEN_TABCONTENTS_OBSERVER, 407 SEEN_HISTORY, 408 SEEN_MAX_VALUE 409 }; 410 411 PrefetchList() {} 412 ~PrefetchList() { 413 STLDeleteValues(&entries_); 414 } 415 416 // Adds a new URL being prefetched. If the URL is already in the list, 417 // nothing will happen. Returns whether a new prefetch was added. 418 bool AddURL(const GURL& url) { 419 ExpireOldItems(); 420 string url_string = url.spec().c_str(); 421 base::hash_map<string, ListEntry*>::iterator it = entries_.find(url_string); 422 if (it != entries_.end()) { 423 // If a prefetch previously existed, and has not been seen yet in either 424 // a tab contents or a history, we will not re-issue it. Otherwise, if it 425 // may have been consumed by either tab contents or history, we will 426 // permit re-issuing another one. 427 if (!it->second->seen_history_ && 428 !it->second->seen_tabcontents_) { 429 return false; 430 } 431 } 432 ListEntry* entry = new ListEntry(url_string); 433 entries_[entry->url_] = entry; 434 entry_list_.push_back(entry); 435 ExpireOldItems(); 436 return true; 437 } 438 439 // Marks the URL provided as seen in the context specified. Returns true 440 // iff the item is currently in the list and had not been seen before in 441 // the context specified, i.e. the marking was successful. 442 bool MarkURLSeen(const GURL& url, SeenType type) { 443 ExpireOldItems(); 444 bool return_value = false; 445 base::hash_map<string, ListEntry*>::iterator it = 446 entries_.find(url.spec().c_str()); 447 if (it == entries_.end()) 448 return return_value; 449 if (type == SEEN_TABCONTENTS_OBSERVER && !it->second->seen_tabcontents_) { 450 it->second->seen_tabcontents_ = true; 451 return_value = true; 452 } 453 if (type == SEEN_HISTORY && !it->second->seen_history_) { 454 it->second->seen_history_ = true; 455 return_value = true; 456 } 457 // If the item has been seen in both the history and in tab contents, 458 // and the page load time has been recorded, erase it from the map to 459 // make room for new prefetches. 460 if (it->second->seen_tabcontents_ && it->second->seen_history_ && 461 it->second->seen_plt_) { 462 entries_.erase(url.spec().c_str()); 463 } 464 return return_value; 465 } 466 467 // Marks the PLT for the provided UR as seen. Returns true 468 // iff the item is currently in the list and the PLT had not been seen 469 // before, i.e. the sighting was successful. 470 bool MarkPLTSeen(const GURL& url, base::TimeDelta plt) { 471 ExpireOldItems(); 472 base::hash_map<string, ListEntry*>::iterator it = 473 entries_.find(url.spec().c_str()); 474 if (it == entries_.end() || it->second->seen_plt_ || 475 it->second->add_time_ > GetCurrentTime() - plt) { 476 return false; 477 } 478 it->second->seen_plt_ = true; 479 // If the item has been seen in both the history and in tab contents, 480 // and the page load time has been recorded, erase it from the map to 481 // make room for new prefetches. 482 if (it->second->seen_tabcontents_ && it->second->seen_history_ && 483 it->second->seen_plt_) { 484 entries_.erase(url.spec().c_str()); 485 } 486 return true; 487 } 488 489 private: 490 struct ListEntry { 491 explicit ListEntry(const string& url) 492 : url_(url), 493 add_time_(GetCurrentTime()), 494 seen_tabcontents_(false), 495 seen_history_(false), 496 seen_plt_(false) { 497 } 498 std::string url_; 499 base::Time add_time_; 500 bool seen_tabcontents_; 501 bool seen_history_; 502 bool seen_plt_; 503 }; 504 505 void ExpireOldItems() { 506 base::Time expiry_cutoff = GetCurrentTime() - 507 base::TimeDelta::FromSeconds(GetPrerenderPrefetchListTimeoutSeconds()); 508 while (!entry_list_.empty() && 509 (entry_list_.front()->add_time_ < expiry_cutoff || 510 entries_.size() > kMaxPrefetchItems)) { 511 ListEntry* entry = entry_list_.front(); 512 entry_list_.pop_front(); 513 // If the entry to be deleted is still the one active in entries_, 514 // we must erase it from entries_. 515 base::hash_map<string, ListEntry*>::iterator it = 516 entries_.find(entry->url_); 517 if (it != entries_.end() && it->second == entry) 518 entries_.erase(entry->url_); 519 delete entry; 520 } 521 } 522 523 base::hash_map<string, ListEntry*> entries_; 524 std::list<ListEntry*> entry_list_; 525 DISALLOW_COPY_AND_ASSIGN(PrefetchList); 526 }; 527 528 PrerenderLocalPredictor::PrerenderLocalPredictor( 529 PrerenderManager* prerender_manager) 530 : prerender_manager_(prerender_manager), 531 is_visit_database_observer_(false), 532 weak_factory_(this), 533 prefetch_list_(new PrefetchList()) { 534 RecordEvent(EVENT_CONSTRUCTED); 535 if (base::MessageLoop::current()) { 536 timer_.Start(FROM_HERE, 537 base::TimeDelta::FromMilliseconds(kInitDelayMs), 538 this, 539 &PrerenderLocalPredictor::Init); 540 RecordEvent(EVENT_INIT_SCHEDULED); 541 } 542 543 static const size_t kChecksumHashSize = 32; 544 base::RefCountedStaticMemory* url_whitelist_data = 545 ResourceBundle::GetSharedInstance().LoadDataResourceBytes( 546 IDR_PRERENDER_URL_WHITELIST); 547 size_t size = url_whitelist_data->size(); 548 const unsigned char* front = url_whitelist_data->front(); 549 if (size < kChecksumHashSize || 550 (size - kChecksumHashSize) % kURLHashSize != 0) { 551 RecordEvent(EVENT_URL_WHITELIST_ERROR); 552 return; 553 } 554 scoped_ptr<crypto::SecureHash> hash( 555 crypto::SecureHash::Create(crypto::SecureHash::SHA256)); 556 hash->Update(front + kChecksumHashSize, size - kChecksumHashSize); 557 char hash_value[kChecksumHashSize]; 558 hash->Finish(hash_value, kChecksumHashSize); 559 if (memcmp(hash_value, front, kChecksumHashSize)) { 560 RecordEvent(EVENT_URL_WHITELIST_ERROR); 561 return; 562 } 563 for (const unsigned char* p = front + kChecksumHashSize; 564 p < front + size; 565 p += kURLHashSize) { 566 url_whitelist_.insert(URLHashToInt64(p)); 567 } 568 RecordEvent(EVENT_URL_WHITELIST_OK); 569 } 570 571 PrerenderLocalPredictor::~PrerenderLocalPredictor() { 572 Shutdown(); 573 for (int i = 0; i < static_cast<int>(issued_prerenders_.size()); i++) { 574 PrerenderProperties* p = issued_prerenders_[i]; 575 DCHECK(p != NULL); 576 if (p->prerender_handle) 577 p->prerender_handle->OnCancel(); 578 } 579 STLDeleteContainerPairPointers( 580 outstanding_prerender_service_requests_.begin(), 581 outstanding_prerender_service_requests_.end()); 582 } 583 584 void PrerenderLocalPredictor::Shutdown() { 585 timer_.Stop(); 586 if (is_visit_database_observer_) { 587 HistoryService* history = GetHistoryIfExists(); 588 CHECK(history); 589 history->RemoveVisitDatabaseObserver(this); 590 is_visit_database_observer_ = false; 591 } 592 } 593 594 void PrerenderLocalPredictor::OnAddVisit(const history::BriefVisitInfo& info) { 595 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 596 RecordEvent(EVENT_ADD_VISIT); 597 if (!visit_history_.get()) 598 return; 599 visit_history_->push_back(info); 600 if (static_cast<int>(visit_history_->size()) > kVisitHistoryPruneThreshold) { 601 visit_history_->erase(visit_history_->begin(), 602 visit_history_->begin() + kVisitHistoryPruneAmount); 603 } 604 RecordEvent(EVENT_ADD_VISIT_INITIALIZED); 605 if (current_prerender_.get() && 606 current_prerender_->url_id == info.url_id && 607 IsPrerenderStillValid(current_prerender_.get())) { 608 UMA_HISTOGRAM_CUSTOM_TIMES( 609 "Prerender.LocalPredictorTimeUntilUsed", 610 GetCurrentTime() - current_prerender_->actual_start_time, 611 base::TimeDelta::FromMilliseconds(10), 612 base::TimeDelta::FromMilliseconds(GetMaxLocalPredictionTimeMs()), 613 50); 614 last_swapped_in_prerender_.reset(current_prerender_.release()); 615 RecordEvent(EVENT_ADD_VISIT_PRERENDER_IDENTIFIED); 616 } 617 if (ShouldExcludeTransitionForPrediction(info.transition)) 618 return; 619 Profile* profile = prerender_manager_->profile(); 620 if (!profile || 621 ShouldDisableLocalPredictorDueToPreferencesAndNetwork(profile)) { 622 return; 623 } 624 RecordEvent(EVENT_ADD_VISIT_RELEVANT_TRANSITION); 625 base::TimeDelta max_age = 626 base::TimeDelta::FromMilliseconds(GetMaxLocalPredictionTimeMs()); 627 base::TimeDelta min_age = 628 base::TimeDelta::FromMilliseconds(kMinLocalPredictionTimeMs); 629 std::set<URLID> next_urls_currently_found; 630 std::map<URLID, int> next_urls_num_found; 631 int num_occurrences_of_current_visit = 0; 632 base::Time last_visited; 633 scoped_ptr<CandidatePrerenderInfo> lookup_info( 634 new CandidatePrerenderInfo(info.url_id)); 635 const vector<history::BriefVisitInfo>& visits = *(visit_history_.get()); 636 for (int i = 0; i < static_cast<int>(visits.size()); i++) { 637 if (!ShouldExcludeTransitionForPrediction(visits[i].transition)) { 638 if (visits[i].url_id == info.url_id) { 639 last_visited = visits[i].time; 640 num_occurrences_of_current_visit++; 641 next_urls_currently_found.clear(); 642 continue; 643 } 644 if (!last_visited.is_null() && 645 last_visited > visits[i].time - max_age && 646 last_visited < visits[i].time - min_age) { 647 if (!IsFormSubmit(visits[i].transition)) 648 next_urls_currently_found.insert(visits[i].url_id); 649 } 650 } 651 if (i == static_cast<int>(visits.size()) - 1 || 652 visits[i+1].url_id == info.url_id) { 653 for (std::set<URLID>::iterator it = next_urls_currently_found.begin(); 654 it != next_urls_currently_found.end(); 655 ++it) { 656 std::pair<std::map<URLID, int>::iterator, bool> insert_ret = 657 next_urls_num_found.insert(std::pair<URLID, int>(*it, 0)); 658 std::map<URLID, int>::iterator num_found_it = insert_ret.first; 659 num_found_it->second++; 660 } 661 } 662 } 663 664 if (num_occurrences_of_current_visit > 1) { 665 RecordEvent(EVENT_ADD_VISIT_RELEVANT_TRANSITION_REPEAT_URL); 666 } else { 667 RecordEvent(EVENT_ADD_VISIT_RELEVANT_TRANSITION_NEW_URL); 668 } 669 670 for (std::map<URLID, int>::const_iterator it = next_urls_num_found.begin(); 671 it != next_urls_num_found.end(); 672 ++it) { 673 // Only consider a candidate next page for prerendering if it was viewed 674 // at least twice, and at least 10% of the time. 675 if (num_occurrences_of_current_visit > 0 && 676 it->second > 1 && 677 it->second * 10 >= num_occurrences_of_current_visit) { 678 RecordEvent(EVENT_ADD_VISIT_IDENTIFIED_PRERENDER_CANDIDATE); 679 double priority = static_cast<double>(it->second) / 680 static_cast<double>(num_occurrences_of_current_visit); 681 lookup_info->MaybeAddCandidateURLFromLocalData(it->first, priority); 682 } 683 } 684 685 RecordEvent(EVENT_START_URL_LOOKUP); 686 HistoryService* history = GetHistoryIfExists(); 687 if (history) { 688 RecordEvent(EVENT_GOT_HISTORY_ISSUING_LOOKUP); 689 CandidatePrerenderInfo* lookup_info_ptr = lookup_info.get(); 690 history->ScheduleDBTask( 691 scoped_ptr<history::HistoryDBTask>( 692 new GetURLForURLIDTask( 693 lookup_info_ptr, 694 base::Bind(&PrerenderLocalPredictor::OnLookupURL, 695 base::Unretained(this), 696 base::Passed(&lookup_info)))), 697 &history_db_tracker_); 698 } 699 } 700 701 void PrerenderLocalPredictor::OnLookupURL( 702 scoped_ptr<CandidatePrerenderInfo> info) { 703 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 704 705 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT); 706 707 if (!info->source_url_.url_lookup_success) { 708 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_FAILED); 709 return; 710 } 711 712 if (prefetch_list_->MarkURLSeen(info->source_url_.url, 713 PrefetchList::SEEN_HISTORY)) { 714 RecordEvent(EVENT_PREFETCH_LIST_SEEN_HISTORY); 715 } 716 717 if (info->candidate_urls_.size() > 0 && 718 info->candidate_urls_[0].url_lookup_success) { 719 LogCandidateURLStats(info->candidate_urls_[0].url); 720 } 721 722 WebContents* source_web_contents = NULL; 723 bool multiple_source_web_contents_candidates = false; 724 725 #if !defined(OS_ANDROID) 726 // We need to figure out what tab launched the prerender. We do this by 727 // comparing URLs. This may not always work: the URL may occur in two 728 // tabs, and we pick the wrong one, or the tab we should have picked 729 // may have navigated elsewhere. Hopefully, this doesn't happen too often, 730 // so we ignore these cases for now. 731 // TODO(tburkard): Reconsider this, potentially measure it, and fix this 732 // in the future. 733 for (TabContentsIterator it; !it.done(); it.Next()) { 734 if (it->GetURL() == info->source_url_.url) { 735 if (!source_web_contents) 736 source_web_contents = *it; 737 else 738 multiple_source_web_contents_candidates = true; 739 } 740 } 741 #endif 742 743 if (!source_web_contents) { 744 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_NO_SOURCE_WEBCONTENTS_FOUND); 745 return; 746 } 747 748 if (multiple_source_web_contents_candidates) 749 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_MULTIPLE_SOURCE_WEBCONTENTS_FOUND); 750 751 info->session_storage_namespace_ = 752 source_web_contents->GetController().GetDefaultSessionStorageNamespace(); 753 RenderFrameHost* rfh = source_web_contents->GetMainFrame(); 754 info->render_process_id_ = rfh->GetProcess()->GetID(); 755 info->render_frame_id_ = rfh->GetRoutingID(); 756 757 gfx::Rect container_bounds = source_web_contents->GetContainerBounds(); 758 info->size_.reset(new gfx::Size(container_bounds.size())); 759 760 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_SUCCESS); 761 762 DoPrerenderServiceCheck(info.Pass()); 763 } 764 765 void PrerenderLocalPredictor::DoPrerenderServiceCheck( 766 scoped_ptr<CandidatePrerenderInfo> info) { 767 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 768 if (!ShouldQueryPrerenderService(prerender_manager_->profile())) { 769 RecordEvent(EVENT_PRERENDER_SERVICE_DISABLED); 770 DoLoggedInLookup(info.Pass()); 771 return; 772 } 773 /* 774 Create a JSON request. 775 Here is a sample request: 776 { "prerender_request": { 777 "version": 1, 778 "behavior_id": 6, 779 "hint_request": { 780 "browse_history": [ 781 { "url": "http://www.cnn.com/" 782 } 783 ] 784 }, 785 "candidate_check_request": { 786 "candidates": [ 787 { "url": "http://www.cnn.com/sports/" 788 }, 789 { "url": "http://www.cnn.com/politics/" 790 } 791 ] 792 } 793 } 794 } 795 */ 796 base::DictionaryValue json_data; 797 base::DictionaryValue* req = new base::DictionaryValue(); 798 req->SetInteger("version", 1); 799 req->SetInteger("behavior_id", GetPrerenderServiceBehaviorID()); 800 if (ShouldQueryPrerenderServiceForCurrentURL() && 801 info->source_url_.url_lookup_success) { 802 base::ListValue* browse_history = new base::ListValue(); 803 base::DictionaryValue* browse_item = new base::DictionaryValue(); 804 browse_item->SetString("url", info->source_url_.url.spec()); 805 browse_history->Append(browse_item); 806 base::DictionaryValue* hint_request = new base::DictionaryValue(); 807 hint_request->Set("browse_history", browse_history); 808 req->Set("hint_request", hint_request); 809 } 810 int num_candidate_urls = 0; 811 for (int i = 0; i < static_cast<int>(info->candidate_urls_.size()); i++) { 812 if (info->candidate_urls_[i].url_lookup_success) 813 num_candidate_urls++; 814 } 815 if (ShouldQueryPrerenderServiceForCandidateURLs() && 816 num_candidate_urls > 0) { 817 base::ListValue* candidates = new base::ListValue(); 818 base::DictionaryValue* candidate; 819 for (int i = 0; i < static_cast<int>(info->candidate_urls_.size()); i++) { 820 if (info->candidate_urls_[i].url_lookup_success) { 821 candidate = new base::DictionaryValue(); 822 candidate->SetString("url", info->candidate_urls_[i].url.spec()); 823 candidates->Append(candidate); 824 } 825 } 826 base::DictionaryValue* candidate_check_request = 827 new base::DictionaryValue(); 828 candidate_check_request->Set("candidates", candidates); 829 req->Set("candidate_check_request", candidate_check_request); 830 } 831 json_data.Set("prerender_request", req); 832 string request_string; 833 base::JSONWriter::Write(&json_data, &request_string); 834 GURL fetch_url(GetPrerenderServiceURLPrefix() + 835 net::EscapeQueryParamValue(request_string, false)); 836 net::URLFetcher* fetcher = net::URLFetcher::Create( 837 0, 838 fetch_url, 839 URLFetcher::GET, this); 840 fetcher->SetRequestContext( 841 prerender_manager_->profile()->GetRequestContext()); 842 fetcher->SetLoadFlags(net::LOAD_DISABLE_CACHE | 843 net::LOAD_DO_NOT_SAVE_COOKIES | 844 net::LOAD_DO_NOT_SEND_COOKIES); 845 fetcher->AddExtraRequestHeader("Pragma: no-cache"); 846 info->start_time_ = base::Time::Now(); 847 outstanding_prerender_service_requests_.insert( 848 std::make_pair(fetcher, info.release())); 849 base::MessageLoop::current()->PostDelayedTask( 850 FROM_HERE, 851 base::Bind(&PrerenderLocalPredictor::MaybeCancelURLFetcher, 852 weak_factory_.GetWeakPtr(), fetcher), 853 base::TimeDelta::FromMilliseconds(GetPrerenderServiceFetchTimeoutMs())); 854 RecordEvent(EVENT_PRERENDER_SERVICE_ISSUED_LOOKUP); 855 fetcher->Start(); 856 } 857 858 void PrerenderLocalPredictor::MaybeCancelURLFetcher(net::URLFetcher* fetcher) { 859 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 860 OutstandingFetchers::iterator it = 861 outstanding_prerender_service_requests_.find(fetcher); 862 if (it == outstanding_prerender_service_requests_.end()) 863 return; 864 delete it->first; 865 scoped_ptr<CandidatePrerenderInfo> info(it->second); 866 outstanding_prerender_service_requests_.erase(it); 867 RecordEvent(EVENT_PRERENDER_SERVICE_LOOKUP_TIMED_OUT); 868 DoLoggedInLookup(info.Pass()); 869 } 870 871 bool PrerenderLocalPredictor::ApplyParsedPrerenderServiceResponse( 872 base::DictionaryValue* dict, 873 CandidatePrerenderInfo* info, 874 bool* hinting_timed_out, 875 bool* hinting_url_lookup_timed_out, 876 bool* candidate_url_lookup_timed_out) { 877 /* 878 Process the response to the request. 879 Here is a sample response to illustrate the format. 880 { 881 "prerender_response": { 882 "behavior_id": 6, 883 "hint_response": { 884 "hinting_timed_out": 0, 885 "candidates": [ 886 { "url": "http://www.cnn.com/story-1", 887 "in_index": 1, 888 "likelihood": 0.60, 889 "in_index_timed_out": 0 890 }, 891 { "url": "http://www.cnn.com/story-2", 892 "in_index": 1, 893 "likelihood": 0.30, 894 "in_index_timed_out": 0 895 } 896 ] 897 }, 898 "candidate_check_response": { 899 "candidates": [ 900 { "url": "http://www.cnn.com/sports/", 901 "in_index": 1, 902 "in_index_timed_out": 0 903 }, 904 { "url": "http://www.cnn.com/politics/", 905 "in_index": 0, 906 "in_index_timed_out": "1" 907 } 908 ] 909 } 910 } 911 } 912 */ 913 base::ListValue* list = NULL; 914 int int_value; 915 if (!dict->GetInteger("prerender_response.behavior_id", &int_value) || 916 int_value != GetPrerenderServiceBehaviorID()) { 917 return false; 918 } 919 if (!dict->GetList("prerender_response.candidate_check_response.candidates", 920 &list)) { 921 if (ShouldQueryPrerenderServiceForCandidateURLs()) { 922 for (int i = 0; i < static_cast<int>(info->candidate_urls_.size()); i++) { 923 if (info->candidate_urls_[i].url_lookup_success) 924 return false; 925 } 926 } 927 } else { 928 for (size_t i = 0; i < list->GetSize(); i++) { 929 base::DictionaryValue* d; 930 if (!list->GetDictionary(i, &d)) 931 return false; 932 string url_string; 933 if (!d->GetString("url", &url_string) || !GURL(url_string).is_valid()) 934 return false; 935 GURL url(url_string); 936 int in_index_timed_out = 0; 937 int in_index = 0; 938 if ((!d->GetInteger("in_index_timed_out", &in_index_timed_out) || 939 in_index_timed_out != 1) && 940 !d->GetInteger("in_index", &in_index)) { 941 return false; 942 } 943 if (in_index < 0 || in_index > 1 || 944 in_index_timed_out < 0 || in_index_timed_out > 1) { 945 return false; 946 } 947 if (in_index_timed_out == 1) 948 *candidate_url_lookup_timed_out = true; 949 for (size_t j = 0; j < info->candidate_urls_.size(); j++) { 950 if (info->candidate_urls_[j].url == url) { 951 info->candidate_urls_[j].service_whitelist_reported = true; 952 info->candidate_urls_[j].service_whitelist = (in_index == 1); 953 info->candidate_urls_[j].service_whitelist_lookup_ok = 954 ((1 - in_index_timed_out) == 1); 955 } 956 } 957 } 958 for (size_t i = 0; i < info->candidate_urls_.size(); i++) { 959 if (info->candidate_urls_[i].url_lookup_success && 960 !info->candidate_urls_[i].service_whitelist_reported) { 961 return false; 962 } 963 } 964 } 965 966 if (ShouldQueryPrerenderServiceForCurrentURL() && 967 info->source_url_.url_lookup_success) { 968 list = NULL; 969 if (dict->GetInteger("prerender_response.hint_response.hinting_timed_out", 970 &int_value) && 971 int_value == 1) { 972 *hinting_timed_out = true; 973 } else if (!dict->GetList("prerender_response.hint_response.candidates", 974 &list)) { 975 return false; 976 } else { 977 for (int i = 0; i < static_cast<int>(list->GetSize()); i++) { 978 base::DictionaryValue* d; 979 if (!list->GetDictionary(i, &d)) 980 return false; 981 string url; 982 if (!d->GetString("url", &url) || !GURL(url).is_valid()) 983 return false; 984 double priority; 985 if (!d->GetDouble("likelihood", &priority) || priority < 0.0 || 986 priority > 1.0) { 987 return false; 988 } 989 int in_index_timed_out = 0; 990 int in_index = 0; 991 if ((!d->GetInteger("in_index_timed_out", &in_index_timed_out) || 992 in_index_timed_out != 1) && 993 !d->GetInteger("in_index", &in_index)) { 994 return false; 995 } 996 if (in_index < 0 || in_index > 1 || in_index_timed_out < 0 || 997 in_index_timed_out > 1) { 998 return false; 999 } 1000 if (in_index_timed_out == 1) 1001 *hinting_url_lookup_timed_out = true; 1002 info->MaybeAddCandidateURLFromService(GURL(url), 1003 priority, 1004 in_index == 1, 1005 !in_index_timed_out); 1006 } 1007 if (list->GetSize() > 0) 1008 RecordEvent(EVENT_PRERENDER_SERVICE_RETURNED_HINTING_CANDIDATES); 1009 } 1010 } 1011 1012 return true; 1013 } 1014 1015 void PrerenderLocalPredictor::OnURLFetchComplete( 1016 const net::URLFetcher* source) { 1017 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1018 RecordEvent(EVENT_PRERENDER_SERVICE_RECEIVED_RESULT); 1019 net::URLFetcher* fetcher = const_cast<net::URLFetcher*>(source); 1020 OutstandingFetchers::iterator it = 1021 outstanding_prerender_service_requests_.find(fetcher); 1022 if (it == outstanding_prerender_service_requests_.end()) { 1023 RecordEvent(EVENT_PRERENDER_SERVICE_NO_RECORD_FOR_RESULT); 1024 return; 1025 } 1026 scoped_ptr<CandidatePrerenderInfo> info(it->second); 1027 outstanding_prerender_service_requests_.erase(it); 1028 TIMING_HISTOGRAM("Prerender.LocalPredictorServiceLookupTime", 1029 base::Time::Now() - info->start_time_); 1030 string result; 1031 fetcher->GetResponseAsString(&result); 1032 scoped_ptr<base::Value> root; 1033 root.reset(base::JSONReader::Read(result)); 1034 bool hinting_timed_out = false; 1035 bool hinting_url_lookup_timed_out = false; 1036 bool candidate_url_lookup_timed_out = false; 1037 if (!root.get() || !root->IsType(base::Value::TYPE_DICTIONARY)) { 1038 RecordEvent(EVENT_PRERENDER_SERVICE_PARSE_ERROR_INCORRECT_JSON); 1039 } else { 1040 if (ApplyParsedPrerenderServiceResponse( 1041 static_cast<base::DictionaryValue*>(root.get()), 1042 info.get(), 1043 &hinting_timed_out, 1044 &hinting_url_lookup_timed_out, 1045 &candidate_url_lookup_timed_out)) { 1046 // We finished parsing the result, and found no errors. 1047 RecordEvent(EVENT_PRERENDER_SERVICE_PARSED_CORRECTLY); 1048 if (hinting_timed_out) 1049 RecordEvent(EVENT_PRERENDER_SERVICE_HINTING_TIMED_OUT); 1050 if (hinting_url_lookup_timed_out) 1051 RecordEvent(EVENT_PRERENDER_SERVICE_HINTING_URL_LOOKUP_TIMED_OUT); 1052 if (candidate_url_lookup_timed_out) 1053 RecordEvent(EVENT_PRERENDER_SERVICE_CANDIDATE_URL_LOOKUP_TIMED_OUT); 1054 DoLoggedInLookup(info.Pass()); 1055 return; 1056 } 1057 } 1058 1059 // If we did not return earlier, an error happened during parsing. 1060 // Record this, and proceed. 1061 RecordEvent(EVENT_PRERENDER_SERVICE_PARSE_ERROR); 1062 DoLoggedInLookup(info.Pass()); 1063 } 1064 1065 void PrerenderLocalPredictor:: DoLoggedInLookup( 1066 scoped_ptr<CandidatePrerenderInfo> info) { 1067 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1068 scoped_refptr<LoggedInPredictorTable> logged_in_table = 1069 prerender_manager_->logged_in_predictor_table(); 1070 1071 if (!logged_in_table.get()) { 1072 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_NO_LOGGED_IN_TABLE_FOUND); 1073 return; 1074 } 1075 1076 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_ISSUING_LOGGED_IN_LOOKUP); 1077 1078 info->start_time_ = base::Time::Now(); 1079 1080 CandidatePrerenderInfo* info_ptr = info.get(); 1081 BrowserThread::PostTaskAndReply( 1082 BrowserThread::DB, FROM_HERE, 1083 base::Bind(&LookupLoggedInStatesOnDBThread, 1084 logged_in_table, 1085 info_ptr), 1086 base::Bind(&PrerenderLocalPredictor::ContinuePrerenderCheck, 1087 weak_factory_.GetWeakPtr(), 1088 base::Passed(&info))); 1089 } 1090 1091 void PrerenderLocalPredictor::LogCandidateURLStats(const GURL& url) const { 1092 if (url_whitelist_.count(GetInt64URLHashForURL(url)) > 0) { 1093 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_ON_WHITELIST); 1094 if (IsRootPageURL(url)) 1095 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_ON_WHITELIST_ROOT_PAGE); 1096 } 1097 if (IsRootPageURL(url)) 1098 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_ROOT_PAGE); 1099 if (IsExtendedRootURL(url)) 1100 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_EXTENDED_ROOT_PAGE); 1101 if (IsRootPageURL(url) && url.SchemeIs("http")) 1102 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_ROOT_PAGE_HTTP); 1103 if (url.SchemeIs("http")) 1104 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_IS_HTTP); 1105 if (url.has_query()) 1106 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_HAS_QUERY_STRING); 1107 if (IsLogOutURL(url)) 1108 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_CONTAINS_LOGOUT); 1109 if (IsLogInURL(url)) 1110 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_CONTAINS_LOGIN); 1111 } 1112 1113 void PrerenderLocalPredictor::OnGetInitialVisitHistory( 1114 scoped_ptr<vector<history::BriefVisitInfo> > visit_history) { 1115 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1116 DCHECK(!visit_history_.get()); 1117 RecordEvent(EVENT_INIT_SUCCEEDED); 1118 // Since the visit history has descending timestamps, we must reverse it. 1119 visit_history_.reset(new vector<history::BriefVisitInfo>( 1120 visit_history->rbegin(), visit_history->rend())); 1121 } 1122 1123 HistoryService* PrerenderLocalPredictor::GetHistoryIfExists() const { 1124 Profile* profile = prerender_manager_->profile(); 1125 if (!profile) 1126 return NULL; 1127 return HistoryServiceFactory::GetForProfileWithoutCreating(profile); 1128 } 1129 1130 void PrerenderLocalPredictor::Init() { 1131 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1132 RecordEvent(EVENT_INIT_STARTED); 1133 Profile* profile = prerender_manager_->profile(); 1134 if (!profile || 1135 ShouldDisableLocalPredictorBasedOnSyncAndConfiguration(profile)) { 1136 RecordEvent(EVENT_INIT_FAILED_UNENCRYPTED_SYNC_NOT_ENABLED); 1137 return; 1138 } 1139 HistoryService* history = GetHistoryIfExists(); 1140 if (history) { 1141 CHECK(!is_visit_database_observer_); 1142 history->ScheduleDBTask( 1143 scoped_ptr<history::HistoryDBTask>( 1144 new GetVisitHistoryTask(this, kMaxVisitHistory)), 1145 &history_db_tracker_); 1146 history->AddVisitDatabaseObserver(this); 1147 is_visit_database_observer_ = true; 1148 } else { 1149 RecordEvent(EVENT_INIT_FAILED_NO_HISTORY); 1150 } 1151 } 1152 1153 void PrerenderLocalPredictor::OnPLTEventForURL(const GURL& url, 1154 base::TimeDelta page_load_time) { 1155 if (prefetch_list_->MarkPLTSeen(url, page_load_time)) { 1156 UMA_HISTOGRAM_CUSTOM_TIMES("Prerender.LocalPredictorPrefetchMatchPLT", 1157 page_load_time, 1158 base::TimeDelta::FromMilliseconds(10), 1159 base::TimeDelta::FromSeconds(60), 1160 100); 1161 } 1162 1163 scoped_ptr<PrerenderProperties> prerender; 1164 if (DoesPrerenderMatchPLTRecord(last_swapped_in_prerender_.get(), 1165 url, page_load_time)) { 1166 prerender.reset(last_swapped_in_prerender_.release()); 1167 } 1168 if (DoesPrerenderMatchPLTRecord(current_prerender_.get(), 1169 url, page_load_time)) { 1170 prerender.reset(current_prerender_.release()); 1171 } 1172 if (!prerender.get()) 1173 return; 1174 if (IsPrerenderStillValid(prerender.get())) { 1175 UMA_HISTOGRAM_CUSTOM_TIMES("Prerender.SimulatedLocalBrowsingBaselinePLT", 1176 page_load_time, 1177 base::TimeDelta::FromMilliseconds(10), 1178 base::TimeDelta::FromSeconds(60), 1179 100); 1180 1181 base::TimeDelta prerender_age = GetCurrentTime() - prerender->start_time; 1182 if (prerender_age > page_load_time) { 1183 base::TimeDelta new_plt; 1184 if (prerender_age < 2 * page_load_time) 1185 new_plt = 2 * page_load_time - prerender_age; 1186 UMA_HISTOGRAM_CUSTOM_TIMES("Prerender.SimulatedLocalBrowsingPLT", 1187 new_plt, 1188 base::TimeDelta::FromMilliseconds(10), 1189 base::TimeDelta::FromSeconds(60), 1190 100); 1191 } 1192 } 1193 } 1194 1195 bool PrerenderLocalPredictor::IsPrerenderStillValid( 1196 PrerenderLocalPredictor::PrerenderProperties* prerender) const { 1197 return (prerender && 1198 (prerender->start_time + 1199 base::TimeDelta::FromMilliseconds(GetMaxLocalPredictionTimeMs())) 1200 > GetCurrentTime()); 1201 } 1202 1203 void PrerenderLocalPredictor::RecordEvent( 1204 PrerenderLocalPredictor::Event event) const { 1205 UMA_HISTOGRAM_ENUMERATION("Prerender.LocalPredictorEvent", 1206 event, PrerenderLocalPredictor::EVENT_MAX_VALUE); 1207 } 1208 1209 bool PrerenderLocalPredictor::DoesPrerenderMatchPLTRecord( 1210 PrerenderProperties* prerender, 1211 const GURL& url, 1212 base::TimeDelta plt) const { 1213 if (prerender && prerender->start_time < GetCurrentTime() - plt) { 1214 if (prerender->url.is_empty()) 1215 RecordEvent(EVENT_ERROR_NO_PRERENDER_URL_FOR_PLT); 1216 return (prerender->url == url); 1217 } else { 1218 return false; 1219 } 1220 } 1221 1222 PrerenderLocalPredictor::PrerenderProperties* 1223 PrerenderLocalPredictor::GetIssuedPrerenderSlotForPriority(const GURL& url, 1224 double priority) { 1225 int num_prerenders = GetLocalPredictorMaxConcurrentPrerenders(); 1226 while (static_cast<int>(issued_prerenders_.size()) < num_prerenders) 1227 issued_prerenders_.push_back(new PrerenderProperties()); 1228 // First, check if we already have a prerender for the same URL issued. 1229 // If yes, we don't want to prerender this URL again, so we return NULL 1230 // (on matching slot found). 1231 for (int i = 0; i < static_cast<int>(issued_prerenders_.size()); i++) { 1232 PrerenderProperties* p = issued_prerenders_[i]; 1233 DCHECK(p != NULL); 1234 if (p->prerender_handle && p->prerender_handle->IsPrerendering() && 1235 p->prerender_handle->Matches(url, NULL)) { 1236 return NULL; 1237 } 1238 } 1239 // Otherwise, let's see if there are any empty slots. If yes, return the first 1240 // one we find. Otherwise, if the lowest priority prerender has a lower 1241 // priority than the page we want to prerender, use its slot. 1242 PrerenderProperties* lowest_priority_prerender = NULL; 1243 for (int i = 0; i < static_cast<int>(issued_prerenders_.size()); i++) { 1244 PrerenderProperties* p = issued_prerenders_[i]; 1245 DCHECK(p != NULL); 1246 if (!p->prerender_handle || !p->prerender_handle->IsPrerendering()) 1247 return p; 1248 double decayed_priority = p->GetCurrentDecayedPriority(); 1249 if (decayed_priority > priority) 1250 continue; 1251 if (lowest_priority_prerender == NULL || 1252 lowest_priority_prerender->GetCurrentDecayedPriority() > 1253 decayed_priority) { 1254 lowest_priority_prerender = p; 1255 } 1256 } 1257 return lowest_priority_prerender; 1258 } 1259 1260 void PrerenderLocalPredictor::ContinuePrerenderCheck( 1261 scoped_ptr<CandidatePrerenderInfo> info) { 1262 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1263 TIMING_HISTOGRAM("Prerender.LocalPredictorLoggedInLookupTime", 1264 base::Time::Now() - info->start_time_); 1265 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_STARTED); 1266 if (info->candidate_urls_.size() == 0) { 1267 RecordEvent(EVENT_NO_PRERENDER_CANDIDATES); 1268 return; 1269 } 1270 scoped_ptr<LocalPredictorURLInfo> url_info; 1271 #if defined(FULL_SAFE_BROWSING) 1272 scoped_refptr<SafeBrowsingDatabaseManager> sb_db_manager = 1273 g_browser_process->safe_browsing_service()->database_manager(); 1274 #endif 1275 int num_issued = 0; 1276 for (int i = 0; i < static_cast<int>(info->candidate_urls_.size()); i++) { 1277 if (num_issued >= GetLocalPredictorMaxLaunchPrerenders()) 1278 return; 1279 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_EXAMINE_NEXT_URL); 1280 url_info.reset(new LocalPredictorURLInfo(info->candidate_urls_[i])); 1281 if (url_info->local_history_based) { 1282 if (SkipLocalPredictorLocalCandidates()) { 1283 url_info.reset(NULL); 1284 continue; 1285 } 1286 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_EXAMINE_NEXT_URL_LOCAL); 1287 } 1288 if (!url_info->local_history_based) { 1289 if (SkipLocalPredictorServiceCandidates()) { 1290 url_info.reset(NULL); 1291 continue; 1292 } 1293 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_EXAMINE_NEXT_URL_SERVICE); 1294 } 1295 1296 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_EXAMINE_NEXT_URL_NOT_SKIPPED); 1297 1298 // We need to check whether we can issue a prerender for this URL. 1299 // We test a set of conditions. Each condition can either rule out 1300 // a prerender (in which case we reset url_info, so that it will not 1301 // be prerendered, and we continue, which means try the next candidate 1302 // URL), or it can be sufficient to issue the prerender without any 1303 // further checks (in which case we just break). 1304 // The order of the checks is critical, because it prescribes the logic 1305 // we use here to decide what to prerender. 1306 if (!url_info->url_lookup_success) { 1307 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_NO_URL); 1308 url_info.reset(NULL); 1309 continue; 1310 } 1311 if (!SkipLocalPredictorFragment() && 1312 URLsIdenticalIgnoringFragments(info->source_url_.url, 1313 url_info->url)) { 1314 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_URLS_IDENTICAL_BUT_FRAGMENT); 1315 url_info.reset(NULL); 1316 continue; 1317 } 1318 if (!SkipLocalPredictorHTTPS() && url_info->url.SchemeIs("https")) { 1319 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_HTTPS); 1320 url_info.reset(NULL); 1321 continue; 1322 } 1323 if (IsRootPageURL(url_info->url)) { 1324 // For root pages, we assume that they are reasonably safe, and we 1325 // will just prerender them without any additional checks. 1326 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_ROOT_PAGE); 1327 IssuePrerender(info.get(), url_info.get()); 1328 num_issued++; 1329 continue; 1330 } 1331 if (IsLogOutURL(url_info->url)) { 1332 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_LOGOUT_URL); 1333 url_info.reset(NULL); 1334 continue; 1335 } 1336 if (IsLogInURL(url_info->url)) { 1337 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_LOGIN_URL); 1338 url_info.reset(NULL); 1339 continue; 1340 } 1341 #if defined(FULL_SAFE_BROWSING) 1342 if (!SkipLocalPredictorWhitelist() && sb_db_manager.get() && 1343 sb_db_manager->CheckSideEffectFreeWhitelistUrl(url_info->url)) { 1344 // If a page is on the side-effect free whitelist, we will just prerender 1345 // it without any additional checks. 1346 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_ON_SIDE_EFFECT_FREE_WHITELIST); 1347 IssuePrerender(info.get(), url_info.get()); 1348 num_issued++; 1349 continue; 1350 } 1351 #endif 1352 if (!SkipLocalPredictorServiceWhitelist() && 1353 url_info->service_whitelist && url_info->service_whitelist_lookup_ok) { 1354 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_ON_SERVICE_WHITELIST); 1355 IssuePrerender(info.get(), url_info.get()); 1356 num_issued++; 1357 continue; 1358 } 1359 if (!SkipLocalPredictorLoggedIn() && 1360 !url_info->logged_in && url_info->logged_in_lookup_ok) { 1361 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_NOT_LOGGED_IN); 1362 IssuePrerender(info.get(), url_info.get()); 1363 num_issued++; 1364 continue; 1365 } 1366 if (!SkipLocalPredictorDefaultNoPrerender()) { 1367 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_FALLTHROUGH_NOT_PRERENDERING); 1368 url_info.reset(NULL); 1369 } else { 1370 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_FALLTHROUGH_PRERENDERING); 1371 IssuePrerender(info.get(), url_info.get()); 1372 num_issued++; 1373 continue; 1374 } 1375 } 1376 } 1377 1378 void PrerenderLocalPredictor::IssuePrerender( 1379 CandidatePrerenderInfo* info, 1380 LocalPredictorURLInfo* url_info) { 1381 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1382 RecordEvent(EVENT_ISSUE_PRERENDER_CALLED); 1383 if (prefetch_list_->AddURL(url_info->url)) { 1384 RecordEvent(EVENT_PREFETCH_LIST_ADDED); 1385 // If we are prefetching rather than prerendering, now is the time to launch 1386 // the prefetch. 1387 if (IsLocalPredictorPrerenderPrefetchEnabled()) { 1388 RecordEvent(EVENT_ISSUE_PRERENDER_PREFETCH_ENABLED); 1389 // Obtain the render frame host that caused this prefetch. 1390 RenderFrameHost* rfh = RenderFrameHost::FromID(info->render_process_id_, 1391 info->render_frame_id_); 1392 // If it is still alive, launch the prefresh. 1393 if (rfh) { 1394 rfh->Send(new PrefetchMsg_Prefetch(rfh->GetRoutingID(), url_info->url)); 1395 RecordEvent(EVENT_ISSUE_PRERENDER_PREFETCH_ISSUED); 1396 } 1397 } 1398 } 1399 PrerenderProperties* prerender_properties = 1400 GetIssuedPrerenderSlotForPriority(url_info->url, url_info->priority); 1401 if (!prerender_properties) { 1402 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_PRIORITY_TOO_LOW); 1403 return; 1404 } 1405 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_ISSUING_PRERENDER); 1406 DCHECK(prerender_properties != NULL); 1407 DCHECK(info != NULL); 1408 DCHECK(url_info != NULL); 1409 if (!IsLocalPredictorPrerenderLaunchEnabled()) 1410 return; 1411 URLID url_id = url_info->id; 1412 const GURL& url = url_info->url; 1413 double priority = url_info->priority; 1414 base::Time current_time = GetCurrentTime(); 1415 RecordEvent(EVENT_ISSUING_PRERENDER); 1416 1417 // Issue the prerender and obtain a new handle. 1418 scoped_ptr<prerender::PrerenderHandle> new_prerender_handle( 1419 prerender_manager_->AddPrerenderFromLocalPredictor( 1420 url, info->session_storage_namespace_.get(), *(info->size_))); 1421 1422 // Check if this is a duplicate of an existing prerender. If yes, clean up 1423 // the new handle. 1424 for (int i = 0; i < static_cast<int>(issued_prerenders_.size()); i++) { 1425 PrerenderProperties* p = issued_prerenders_[i]; 1426 DCHECK(p != NULL); 1427 if (new_prerender_handle && 1428 new_prerender_handle->RepresentingSamePrerenderAs( 1429 p->prerender_handle.get())) { 1430 new_prerender_handle->OnCancel(); 1431 new_prerender_handle.reset(NULL); 1432 RecordEvent(EVENT_ISSUE_PRERENDER_ALREADY_PRERENDERING); 1433 break; 1434 } 1435 } 1436 1437 if (new_prerender_handle.get()) { 1438 RecordEvent(EVENT_ISSUE_PRERENDER_NEW_PRERENDER); 1439 // The new prerender does not match any existing prerenders. Update 1440 // prerender_properties so that it reflects the new entry. 1441 prerender_properties->url_id = url_id; 1442 prerender_properties->url = url; 1443 prerender_properties->priority = priority; 1444 prerender_properties->start_time = current_time; 1445 prerender_properties->actual_start_time = current_time; 1446 prerender_properties->would_have_matched = false; 1447 prerender_properties->prerender_handle.swap(new_prerender_handle); 1448 // new_prerender_handle now represents the old previou prerender that we 1449 // are replacing. So we need to cancel it. 1450 if (new_prerender_handle) { 1451 new_prerender_handle->OnCancel(); 1452 RecordEvent(EVENT_ISSUE_PRERENDER_CANCELLED_OLD_PRERENDER); 1453 } 1454 } 1455 1456 RecordEvent(EVENT_ADD_VISIT_PRERENDERING); 1457 if (current_prerender_.get() && current_prerender_->url_id == url_id) { 1458 RecordEvent(EVENT_ADD_VISIT_PRERENDERING_EXTENDED); 1459 if (priority > current_prerender_->priority) 1460 current_prerender_->priority = priority; 1461 // If the prerender already existed, we want to extend it. However, 1462 // we do not want to set its start_time to the current time to 1463 // disadvantage PLT computations when the prerender is swapped in. 1464 // So we set the new start time to current_time - 10s (since the vast 1465 // majority of PLTs are < 10s), provided that is not before the actual 1466 // time the prerender was started (so as to not artificially advantage 1467 // the PLT computation). 1468 base::Time simulated_new_start_time = 1469 current_time - base::TimeDelta::FromSeconds(10); 1470 if (simulated_new_start_time > current_prerender_->start_time) 1471 current_prerender_->start_time = simulated_new_start_time; 1472 } else { 1473 current_prerender_.reset( 1474 new PrerenderProperties(url_id, url, priority, current_time)); 1475 } 1476 current_prerender_->actual_start_time = current_time; 1477 } 1478 1479 void PrerenderLocalPredictor::OnTabHelperURLSeen( 1480 const GURL& url, WebContents* web_contents) { 1481 RecordEvent(EVENT_TAB_HELPER_URL_SEEN); 1482 1483 if (prefetch_list_->MarkURLSeen(url, PrefetchList::SEEN_TABCONTENTS_OBSERVER)) 1484 RecordEvent(EVENT_PREFETCH_LIST_SEEN_TABCONTENTS); 1485 bool browser_navigate_initiated = false; 1486 const content::NavigationEntry* entry = 1487 web_contents->GetController().GetPendingEntry(); 1488 if (entry) { 1489 base::string16 result; 1490 browser_navigate_initiated = 1491 entry->GetExtraData(kChromeNavigateExtraDataKey, &result); 1492 } 1493 1494 // If the namespace matches and the URL matches, we might be able to swap 1495 // in. However, the actual code initating the swapin is in the renderer 1496 // and is checking for other criteria (such as POSTs). There may 1497 // also be conditions when a swapin should happen but does not. By recording 1498 // the two previous events, we can keep an eye on the magnitude of the 1499 // discrepancy. 1500 1501 PrerenderProperties* best_matched_prerender = NULL; 1502 bool session_storage_namespace_matches = false; 1503 SessionStorageNamespace* tab_session_storage_namespace = 1504 web_contents->GetController().GetDefaultSessionStorageNamespace(); 1505 for (int i = 0; i < static_cast<int>(issued_prerenders_.size()); i++) { 1506 PrerenderProperties* p = issued_prerenders_[i]; 1507 DCHECK(p != NULL); 1508 if (!p->prerender_handle.get() || 1509 !p->prerender_handle->Matches(url, NULL) || 1510 p->would_have_matched) { 1511 continue; 1512 } 1513 if (!best_matched_prerender || !session_storage_namespace_matches) { 1514 best_matched_prerender = p; 1515 session_storage_namespace_matches = 1516 p->prerender_handle->Matches(url, tab_session_storage_namespace); 1517 } 1518 } 1519 if (best_matched_prerender) { 1520 RecordEvent(EVENT_TAB_HELPER_URL_SEEN_MATCH); 1521 if (entry) 1522 RecordEvent(EVENT_TAB_HELPER_URL_SEEN_MATCH_ENTRY); 1523 if (browser_navigate_initiated) 1524 RecordEvent(EVENT_TAB_HELPER_URL_SEEN_MATCH_BROWSER_NAVIGATE); 1525 best_matched_prerender->would_have_matched = true; 1526 if (session_storage_namespace_matches) { 1527 RecordEvent(EVENT_TAB_HELPER_URL_SEEN_NAMESPACE_MATCH); 1528 if (entry) 1529 RecordEvent(EVENT_TAB_HELPER_URL_SEEN_NAMESPACE_MATCH_ENTRY); 1530 if (browser_navigate_initiated) 1531 RecordEvent(EVENT_TAB_HELPER_URL_SEEN_NAMESPACE_MATCH_BROWSER_NAVIGATE); 1532 } else { 1533 SessionStorageNamespace* prerender_session_storage_namespace = 1534 best_matched_prerender->prerender_handle-> 1535 GetSessionStorageNamespace(); 1536 if (!prerender_session_storage_namespace) { 1537 RecordEvent(EVENT_TAB_HELPER_URL_SEEN_NAMESPACE_MISMATCH_NO_NAMESPACE); 1538 } else { 1539 RecordEvent(EVENT_TAB_HELPER_URL_SEEN_NAMESPACE_MISMATCH_MERGE_ISSUED); 1540 prerender_session_storage_namespace->Merge( 1541 false, 1542 best_matched_prerender->prerender_handle->GetChildId(), 1543 tab_session_storage_namespace, 1544 base::Bind(&PrerenderLocalPredictor::ProcessNamespaceMergeResult, 1545 weak_factory_.GetWeakPtr())); 1546 } 1547 } 1548 } 1549 } 1550 1551 void PrerenderLocalPredictor::ProcessNamespaceMergeResult( 1552 content::SessionStorageNamespace::MergeResult result) { 1553 RecordEvent(EVENT_NAMESPACE_MISMATCH_MERGE_RESULT_RECEIVED); 1554 switch (result) { 1555 case content::SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_FOUND: 1556 RecordEvent(EVENT_NAMESPACE_MISMATCH_MERGE_RESULT_NAMESPACE_NOT_FOUND); 1557 break; 1558 case content::SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_ALIAS: 1559 RecordEvent(EVENT_NAMESPACE_MISMATCH_MERGE_RESULT_NAMESPACE_NOT_ALIAS); 1560 break; 1561 case content::SessionStorageNamespace::MERGE_RESULT_NOT_LOGGING: 1562 RecordEvent(EVENT_NAMESPACE_MISMATCH_MERGE_RESULT_NOT_LOGGING); 1563 break; 1564 case content::SessionStorageNamespace::MERGE_RESULT_NO_TRANSACTIONS: 1565 RecordEvent(EVENT_NAMESPACE_MISMATCH_MERGE_RESULT_NO_TRANSACTIONS); 1566 break; 1567 case content::SessionStorageNamespace::MERGE_RESULT_TOO_MANY_TRANSACTIONS: 1568 RecordEvent(EVENT_NAMESPACE_MISMATCH_MERGE_RESULT_TOO_MANY_TRANSACTIONS); 1569 break; 1570 case content::SessionStorageNamespace::MERGE_RESULT_NOT_MERGEABLE: 1571 RecordEvent(EVENT_NAMESPACE_MISMATCH_MERGE_RESULT_NOT_MERGEABLE); 1572 break; 1573 case content::SessionStorageNamespace::MERGE_RESULT_MERGEABLE: 1574 RecordEvent(EVENT_NAMESPACE_MISMATCH_MERGE_RESULT_MERGEABLE); 1575 break; 1576 default: 1577 NOTREACHED(); 1578 } 1579 } 1580 1581 } // namespace prerender 1582