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