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.
5 #include "chrome/browser/prerender/prerender_local_predictor.h"
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"
49 using base::DictionaryValue;
50 using base::ListValue;
52 using content::BrowserThread;
53 using content::PageTransition;
54 using content::SessionStorageNamespace;
55 using content::WebContents;
57 using net::URLFetcher;
58 using predictors::LoggedInPredictorTable;
66 static const size_t kURLHashSize = 5;
67 static const int kNumPrerenderCandidates = 5;
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
74 struct PrerenderLocalPredictor::LocalPredictorURLInfo {
77 bool url_lookup_success;
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;
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;
99 void MaybeAddCandidateURLFromLocalData(URLID id, double priority) {
100 LocalPredictorURLInfo info;
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);
109 void MaybeAddCandidateURLFromService(GURL url, double priority,
111 bool whitelist_lookup_ok) {
112 LocalPredictorURLInfo info;
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);
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)
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];
141 if (insert_pos < max_candidates)
142 candidate_urls_[insert_pos] = info;
148 #define TIMING_HISTOGRAM(name, value) \
149 UMA_HISTOGRAM_CUSTOM_TIMES(name, value, \
150 base::TimeDelta::FromMilliseconds(10), \
151 base::TimeDelta::FromSeconds(10), \
154 // Task to lookup the URL for a given URLID.
155 class GetURLForURLIDTask : public history::HistoryDBTask {
158 PrerenderLocalPredictor::CandidatePrerenderInfo* request,
159 const base::Closure& callback)
162 start_time_(base::Time::Now()) {
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]);
173 virtual void DoneRunOnMainThread() OVERRIDE {
175 TIMING_HISTOGRAM("Prerender.LocalPredictorURLLookupTime",
176 base::Time::Now() - start_time_);
180 virtual ~GetURLForURLIDTask() {}
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();
190 PrerenderLocalPredictor::CandidatePrerenderInfo* request_;
191 base::Closure callback_;
192 base::Time start_time_;
193 DISALLOW_COPY_AND_ASSIGN(GetURLForURLIDTask);
196 // Task to load history from the visit database on startup.
197 class GetVisitHistoryTask : public history::HistoryDBTask {
199 GetVisitHistoryTask(PrerenderLocalPredictor* local_predictor,
201 : local_predictor_(local_predictor),
202 max_visits_(max_visits),
203 visit_history_(new vector<history::BriefVisitInfo>) {
206 virtual bool RunOnDBThread(history::HistoryBackend* backend,
207 history::HistoryDatabase* db) OVERRIDE {
208 db->GetBriefVisitInfoOfMostRecentVisits(max_visits_, visit_history_.get());
212 virtual void DoneRunOnMainThread() OVERRIDE {
213 local_predictor_->OnGetInitialVisitHistory(visit_history_.Pass());
217 virtual ~GetVisitHistoryTask() {}
219 PrerenderLocalPredictor* local_predictor_;
221 scoped_ptr<vector<history::BriefVisitInfo> > visit_history_;
222 DISALLOW_COPY_AND_ASSIGN(GetVisitHistoryTask);
225 // Maximum visit history to retrieve from the visit database.
226 const int kMaxVisitHistory = 100 * 1000;
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;
232 const int kMinLocalPredictionTimeMs = 500;
234 int GetMaxLocalPredictionTimeMs() {
235 return GetLocalPredictorTTLSeconds() * 1000;
238 bool IsBackForward(PageTransition transition) {
239 return (transition & content::PAGE_TRANSITION_FORWARD_BACK) != 0;
242 bool IsHomePage(PageTransition transition) {
243 return (transition & content::PAGE_TRANSITION_HOME_PAGE) != 0;
246 bool IsIntermediateRedirect(PageTransition transition) {
247 return (transition & content::PAGE_TRANSITION_CHAIN_END) == 0;
250 bool IsFormSubmit(PageTransition transition) {
251 return PageTransitionCoreTypeIs(transition,
252 content::PAGE_TRANSITION_FORM_SUBMIT);
255 bool ShouldExcludeTransitionForPrediction(PageTransition transition) {
256 return IsBackForward(transition) || IsHomePage(transition) ||
257 IsIntermediateRedirect(transition);
260 base::Time GetCurrentTime() {
261 return base::Time::Now();
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;
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";
281 bool IsRootPageURL(const GURL& url) {
282 return (url.path() == "/" || url.path() == "" || IsExtendedRootURL(url)) &&
283 (!url.has_query()) && (!url.has_ref());
286 bool IsLogInURL(const GURL& url) {
287 return StringContainsIgnoringCase(url.spec().c_str(), "login") ||
288 StringContainsIgnoringCase(url.spec().c_str(), "signin");
291 bool IsLogOutURL(const GURL& url) {
292 return StringContainsIgnoringCase(url.spec().c_str(), "logout") ||
293 StringContainsIgnoringCase(url.spec().c_str(), "signout");
296 int64 URLHashToInt64(const unsigned char* data) {
297 COMPILE_ASSERT(kURLHashSize < sizeof(int64), url_hash_must_fit_in_int64);
299 memcpy(&value, data, kURLHashSize);
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);
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);
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);
333 info->logged_in_lookup_ok = false;
340 struct PrerenderLocalPredictor::PrerenderProperties {
341 PrerenderProperties(URLID url_id, const GURL& url, double priority,
342 base::Time start_time)
346 start_time(start_time),
347 would_have_matched(false) {
350 // Default constructor for dummy element
351 PrerenderProperties()
352 : priority(0.0), would_have_matched(false) {
355 double GetCurrentDecayedPriority() {
356 // If we are no longer prerendering, the priority is 0.
357 if (!prerender_handle || !prerender_handle->IsPrerendering())
359 int half_life_time_seconds =
360 GetLocalPredictorPrerenderPriorityHalfLifeTimeSeconds();
361 if (half_life_time_seconds < 1)
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;
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;
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),
399 &PrerenderLocalPredictor::Init);
400 RecordEvent(EVENT_INIT_SCHEDULED);
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);
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);
423 for (const unsigned char* p = front + kChecksumHashSize;
426 url_whitelist_.insert(URLHashToInt64(p));
428 RecordEvent(EVENT_URL_WHITELIST_OK);
431 PrerenderLocalPredictor::~PrerenderLocalPredictor() {
433 for (int i = 0; i < static_cast<int>(issued_prerenders_.size()); i++) {
434 PrerenderProperties* p = issued_prerenders_[i];
436 if (p->prerender_handle)
437 p->prerender_handle->OnCancel();
439 STLDeleteContainerPairPointers(
440 outstanding_prerender_service_requests_.begin(),
441 outstanding_prerender_service_requests_.end());
444 void PrerenderLocalPredictor::Shutdown() {
446 if (is_visit_database_observer_) {
447 HistoryService* history = GetHistoryIfExists();
449 history->RemoveVisitDatabaseObserver(this);
450 is_visit_database_observer_ = false;
454 void PrerenderLocalPredictor::OnAddVisit(const history::BriefVisitInfo& info) {
455 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
456 RecordEvent(EVENT_ADD_VISIT);
457 if (!visit_history_.get())
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);
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()),
474 last_swapped_in_prerender_.reset(current_prerender_.release());
475 RecordEvent(EVENT_ADD_VISIT_PRERENDER_IDENTIFIED);
477 if (ShouldExcludeTransitionForPrediction(info.transition))
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();
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);
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();
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++;
519 if (num_occurrences_of_current_visit > 1) {
520 RecordEvent(EVENT_ADD_VISIT_RELEVANT_TRANSITION_REPEAT_URL);
522 RecordEvent(EVENT_ADD_VISIT_RELEVANT_TRANSITION_NEW_URL);
525 for (std::map<URLID, int>::const_iterator it = next_urls_num_found.begin();
526 it != next_urls_num_found.end();
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 &&
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);
540 RecordEvent(EVENT_START_URL_LOOKUP);
541 HistoryService* history = GetHistoryIfExists();
543 RecordEvent(EVENT_GOT_HISTORY_ISSUING_LOOKUP);
544 CandidatePrerenderInfo* lookup_info_ptr = lookup_info.get();
545 history->ScheduleDBTask(
546 new GetURLForURLIDTask(
548 base::Bind(&PrerenderLocalPredictor::OnLookupURL,
549 base::Unretained(this),
550 base::Passed(&lookup_info))),
551 &history_db_consumer_);
555 void PrerenderLocalPredictor::OnLookupURL(
556 scoped_ptr<CandidatePrerenderInfo> info) {
557 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
559 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT);
561 if (!info->source_url_.url_lookup_success) {
562 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_FAILED);
566 if (info->candidate_urls_.size() > 0 &&
567 info->candidate_urls_[0].url_lookup_success) {
568 LogCandidateURLStats(info->candidate_urls_[0].url);
571 WebContents* source_web_contents = NULL;
572 bool multiple_source_web_contents_candidates = false;
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
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;
587 multiple_source_web_contents_candidates = true;
592 if (!source_web_contents) {
593 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_NO_SOURCE_WEBCONTENTS_FOUND);
597 if (multiple_source_web_contents_candidates)
598 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_MULTIPLE_SOURCE_WEBCONTENTS_FOUND);
600 info->session_storage_namespace_ =
601 source_web_contents->GetController().GetDefaultSessionStorageNamespace();
603 gfx::Rect container_bounds;
604 source_web_contents->GetView()->GetContainerBounds(&container_bounds);
605 info->size_.reset(new gfx::Size(container_bounds.size()));
607 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_SUCCESS);
609 DoPrerenderServiceCheck(info.Pass());
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());
621 Create a JSON request.
622 Here is a sample request:
623 { "prerender_request": {
628 { "url": "http://www.cnn.com/"
632 "candidate_check_request": {
634 { "url": "http://www.cnn.com/sports/"
636 { "url": "http://www.cnn.com/politics/"
643 base::DictionaryValue json_data;
644 base::DictionaryValue* req = new base::DictionaryValue();
645 req->SetInteger("version", 1);
646 req->SetInteger("behavior_id", GetPrerenderServiceBehaviorID());
647 if (ShouldQueryPrerenderServiceForCurrentURL() &&
648 info->source_url_.url_lookup_success) {
649 base::ListValue* browse_history = new base::ListValue();
650 base::DictionaryValue* browse_item = new base::DictionaryValue();
651 browse_item->SetString("url", info->source_url_.url.spec());
652 browse_history->Append(browse_item);
653 base::DictionaryValue* hint_request = new base::DictionaryValue();
654 hint_request->Set("browse_history", browse_history);
655 req->Set("hint_request", hint_request);
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++;
662 if (ShouldQueryPrerenderServiceForCandidateURLs() &&
663 num_candidate_urls > 0) {
664 base::ListValue* candidates = new base::ListValue();
665 base::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 base::DictionaryValue();
669 candidate->SetString("url", info->candidate_urls_[i].url.spec());
670 candidates->Append(candidate);
673 base::DictionaryValue* candidate_check_request =
674 new base::DictionaryValue();
675 candidate_check_request->Set("candidates", candidates);
676 req->Set("candidate_check_request", candidate_check_request);
678 json_data.Set("prerender_request", req);
679 string request_string;
680 base::JSONWriter::Write(&json_data, &request_string);
681 GURL fetch_url(GetPrerenderServiceURLPrefix() +
682 net::EscapeQueryParamValue(request_string, false));
683 net::URLFetcher* fetcher = net::URLFetcher::Create(
686 URLFetcher::GET, this);
687 fetcher->SetRequestContext(
688 prerender_manager_->profile()->GetRequestContext());
689 fetcher->SetLoadFlags(net::LOAD_DISABLE_CACHE |
690 net::LOAD_DO_NOT_SAVE_COOKIES |
691 net::LOAD_DO_NOT_SEND_COOKIES);
692 fetcher->AddExtraRequestHeader("Pragma: no-cache");
693 info->start_time_ = base::Time::Now();
694 outstanding_prerender_service_requests_.insert(
695 std::make_pair(fetcher, info.release()));
696 base::MessageLoop::current()->PostDelayedTask(
698 base::Bind(&PrerenderLocalPredictor::MaybeCancelURLFetcher,
699 weak_factory_.GetWeakPtr(), fetcher),
700 base::TimeDelta::FromMilliseconds(GetPrerenderServiceFetchTimeoutMs()));
701 RecordEvent(EVENT_PRERENDER_SERVICE_ISSUED_LOOKUP);
705 void PrerenderLocalPredictor::MaybeCancelURLFetcher(net::URLFetcher* fetcher) {
706 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
707 OutstandingFetchers::iterator it =
708 outstanding_prerender_service_requests_.find(fetcher);
709 if (it == outstanding_prerender_service_requests_.end())
712 scoped_ptr<CandidatePrerenderInfo> info(it->second);
713 outstanding_prerender_service_requests_.erase(it);
714 RecordEvent(EVENT_PRERENDER_SERVICE_LOOKUP_TIMED_OUT);
715 DoLoggedInLookup(info.Pass());
718 bool PrerenderLocalPredictor::ApplyParsedPrerenderServiceResponse(
719 base::DictionaryValue* dict,
720 CandidatePrerenderInfo* info,
721 bool* hinting_timed_out,
722 bool* hinting_url_lookup_timed_out,
723 bool* candidate_url_lookup_timed_out) {
725 Process the response to the request.
726 Here is a sample response to illustrate the format.
728 "prerender_response": {
731 "hinting_timed_out": 0,
733 { "url": "http://www.cnn.com/story-1",
736 "in_index_timed_out": 0
738 { "url": "http://www.cnn.com/story-2",
741 "in_index_timed_out": 0
745 "candidate_check_response": {
747 { "url": "http://www.cnn.com/sports/",
749 "in_index_timed_out": 0
751 { "url": "http://www.cnn.com/politics/",
753 "in_index_timed_out": "1"
760 base::ListValue* list = NULL;
762 if (!dict->GetInteger("prerender_response.behavior_id", &int_value) ||
763 int_value != GetPrerenderServiceBehaviorID()) {
766 if (!dict->GetList("prerender_response.candidate_check_response.candidates",
768 if (ShouldQueryPrerenderServiceForCandidateURLs()) {
769 for (int i = 0; i < static_cast<int>(info->candidate_urls_.size()); i++) {
770 if (info->candidate_urls_[i].url_lookup_success)
775 for (size_t i = 0; i < list->GetSize(); i++) {
776 base::DictionaryValue* d;
777 if (!list->GetDictionary(i, &d))
780 if (!d->GetString("url", &url_string) || !GURL(url_string).is_valid())
782 GURL url(url_string);
783 int in_index_timed_out = 0;
785 if ((!d->GetInteger("in_index_timed_out", &in_index_timed_out) ||
786 in_index_timed_out != 1) &&
787 !d->GetInteger("in_index", &in_index)) {
790 if (in_index < 0 || in_index > 1 ||
791 in_index_timed_out < 0 || in_index_timed_out > 1) {
794 if (in_index_timed_out == 1)
795 *candidate_url_lookup_timed_out = true;
796 for (size_t j = 0; j < info->candidate_urls_.size(); j++) {
797 if (info->candidate_urls_[j].url == url) {
798 info->candidate_urls_[j].service_whitelist_reported = true;
799 info->candidate_urls_[j].service_whitelist = (in_index == 1);
800 info->candidate_urls_[j].service_whitelist_lookup_ok =
801 ((1 - in_index_timed_out) == 1);
805 for (size_t i = 0; i < info->candidate_urls_.size(); i++) {
806 if (info->candidate_urls_[i].url_lookup_success &&
807 !info->candidate_urls_[i].service_whitelist_reported) {
813 if (ShouldQueryPrerenderServiceForCurrentURL() &&
814 info->source_url_.url_lookup_success) {
816 if (dict->GetInteger("prerender_response.hint_response.hinting_timed_out",
819 *hinting_timed_out = true;
820 } else if (!dict->GetList("prerender_response.hint_response.candidates",
824 for (int i = 0; i < static_cast<int>(list->GetSize()); i++) {
825 base::DictionaryValue* d;
826 if (!list->GetDictionary(i, &d))
830 if (!d->GetString("url", &url) || !d->GetDouble("likelihood", &priority)
831 || !GURL(url).is_valid()) {
834 int in_index_timed_out = 0;
836 if ((!d->GetInteger("in_index_timed_out", &in_index_timed_out) ||
837 in_index_timed_out != 1) &&
838 !d->GetInteger("in_index", &in_index)) {
841 if (priority < 0.0 || priority > 1.0 || in_index < 0 || in_index > 1 ||
842 in_index_timed_out < 0 || in_index_timed_out > 1) {
845 if (in_index_timed_out == 1)
846 *hinting_url_lookup_timed_out = true;
847 info->MaybeAddCandidateURLFromService(GURL(url),
850 (1 - in_index_timed_out) == 1);
852 if (list->GetSize() > 0)
853 RecordEvent(EVENT_PRERENDER_SERIVCE_RETURNED_HINTING_CANDIDATES);
860 void PrerenderLocalPredictor::OnURLFetchComplete(
861 const net::URLFetcher* source) {
862 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
863 RecordEvent(EVENT_PRERENDER_SERVICE_RECEIVED_RESULT);
864 net::URLFetcher* fetcher = const_cast<net::URLFetcher*>(source);
865 OutstandingFetchers::iterator it =
866 outstanding_prerender_service_requests_.find(fetcher);
867 if (it == outstanding_prerender_service_requests_.end()) {
868 RecordEvent(EVENT_PRERENDER_SERVICE_NO_RECORD_FOR_RESULT);
871 scoped_ptr<CandidatePrerenderInfo> info(it->second);
872 outstanding_prerender_service_requests_.erase(it);
873 TIMING_HISTOGRAM("Prerender.LocalPredictorServiceLookupTime",
874 base::Time::Now() - info->start_time_);
876 fetcher->GetResponseAsString(&result);
877 scoped_ptr<base::Value> root;
878 root.reset(base::JSONReader::Read(result));
879 bool hinting_timed_out = false;
880 bool hinting_url_lookup_timed_out = false;
881 bool candidate_url_lookup_timed_out = false;
882 if (!root.get() || !root->IsType(base::Value::TYPE_DICTIONARY)) {
883 RecordEvent(EVENT_PRERENDER_SERVICE_PARSE_ERROR_INCORRECT_JSON);
885 if (ApplyParsedPrerenderServiceResponse(
886 static_cast<base::DictionaryValue*>(root.get()),
889 &hinting_url_lookup_timed_out,
890 &candidate_url_lookup_timed_out)) {
891 // We finished parsing the result, and found no errors.
892 RecordEvent(EVENT_PRERENDER_SERVICE_PARSED_CORRECTLY);
893 if (hinting_timed_out)
894 RecordEvent(EVENT_PRERENDER_SERVICE_HINTING_TIMED_OUT);
895 if (hinting_url_lookup_timed_out)
896 RecordEvent(EVENT_PRERENDER_SERVICE_HINTING_URL_LOOKUP_TIMED_OUT);
897 if (candidate_url_lookup_timed_out)
898 RecordEvent(EVENT_PRERENDER_SERVICE_CANDIDATE_URL_LOOKUP_TIMED_OUT);
899 DoLoggedInLookup(info.Pass());
904 // If we did not return earlier, an error happened during parsing.
905 // Record this, and proceed.
906 RecordEvent(EVENT_PRERENDER_SERVICE_PARSE_ERROR);
907 DoLoggedInLookup(info.Pass());
910 void PrerenderLocalPredictor:: DoLoggedInLookup(
911 scoped_ptr<CandidatePrerenderInfo> info) {
912 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
913 scoped_refptr<LoggedInPredictorTable> logged_in_table =
914 prerender_manager_->logged_in_predictor_table();
916 if (!logged_in_table.get()) {
917 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_NO_LOGGED_IN_TABLE_FOUND);
921 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_ISSUING_LOGGED_IN_LOOKUP);
923 info->start_time_ = base::Time::Now();
925 CandidatePrerenderInfo* info_ptr = info.get();
926 BrowserThread::PostTaskAndReply(
927 BrowserThread::DB, FROM_HERE,
928 base::Bind(&LookupLoggedInStatesOnDBThread,
931 base::Bind(&PrerenderLocalPredictor::ContinuePrerenderCheck,
932 weak_factory_.GetWeakPtr(),
933 base::Passed(&info)));
936 void PrerenderLocalPredictor::LogCandidateURLStats(const GURL& url) const {
937 if (url_whitelist_.count(GetInt64URLHashForURL(url)) > 0) {
938 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_ON_WHITELIST);
939 if (IsRootPageURL(url))
940 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_ON_WHITELIST_ROOT_PAGE);
942 if (IsRootPageURL(url))
943 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_ROOT_PAGE);
944 if (IsExtendedRootURL(url))
945 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_EXTENDED_ROOT_PAGE);
946 if (IsRootPageURL(url) && url.SchemeIs("http"))
947 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_ROOT_PAGE_HTTP);
948 if (url.SchemeIs("http"))
949 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_IS_HTTP);
951 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_HAS_QUERY_STRING);
952 if (IsLogOutURL(url))
953 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_CONTAINS_LOGOUT);
955 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_CONTAINS_LOGIN);
958 void PrerenderLocalPredictor::OnGetInitialVisitHistory(
959 scoped_ptr<vector<history::BriefVisitInfo> > visit_history) {
960 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
961 DCHECK(!visit_history_.get());
962 RecordEvent(EVENT_INIT_SUCCEEDED);
963 // Since the visit history has descending timestamps, we must reverse it.
964 visit_history_.reset(new vector<history::BriefVisitInfo>(
965 visit_history->rbegin(), visit_history->rend()));
968 HistoryService* PrerenderLocalPredictor::GetHistoryIfExists() const {
969 Profile* profile = prerender_manager_->profile();
972 return HistoryServiceFactory::GetForProfileWithoutCreating(profile);
975 void PrerenderLocalPredictor::Init() {
976 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
977 RecordEvent(EVENT_INIT_STARTED);
978 Profile* profile = prerender_manager_->profile();
979 if (!profile || DisableLocalPredictorBasedOnSyncAndConfiguration(profile)) {
980 RecordEvent(EVENT_INIT_FAILED_UNENCRYPTED_SYNC_NOT_ENABLED);
983 HistoryService* history = GetHistoryIfExists();
985 CHECK(!is_visit_database_observer_);
986 history->ScheduleDBTask(
987 new GetVisitHistoryTask(this, kMaxVisitHistory),
988 &history_db_consumer_);
989 history->AddVisitDatabaseObserver(this);
990 is_visit_database_observer_ = true;
992 RecordEvent(EVENT_INIT_FAILED_NO_HISTORY);
996 void PrerenderLocalPredictor::OnPLTEventForURL(const GURL& url,
997 base::TimeDelta page_load_time) {
998 scoped_ptr<PrerenderProperties> prerender;
999 if (DoesPrerenderMatchPLTRecord(last_swapped_in_prerender_.get(),
1000 url, page_load_time)) {
1001 prerender.reset(last_swapped_in_prerender_.release());
1003 if (DoesPrerenderMatchPLTRecord(current_prerender_.get(),
1004 url, page_load_time)) {
1005 prerender.reset(current_prerender_.release());
1007 if (!prerender.get())
1009 if (IsPrerenderStillValid(prerender.get())) {
1010 UMA_HISTOGRAM_CUSTOM_TIMES("Prerender.SimulatedLocalBrowsingBaselinePLT",
1012 base::TimeDelta::FromMilliseconds(10),
1013 base::TimeDelta::FromSeconds(60),
1016 base::TimeDelta prerender_age = GetCurrentTime() - prerender->start_time;
1017 if (prerender_age > page_load_time) {
1018 base::TimeDelta new_plt;
1019 if (prerender_age < 2 * page_load_time)
1020 new_plt = 2 * page_load_time - prerender_age;
1021 UMA_HISTOGRAM_CUSTOM_TIMES("Prerender.SimulatedLocalBrowsingPLT",
1023 base::TimeDelta::FromMilliseconds(10),
1024 base::TimeDelta::FromSeconds(60),
1030 bool PrerenderLocalPredictor::IsPrerenderStillValid(
1031 PrerenderLocalPredictor::PrerenderProperties* prerender) const {
1032 return (prerender &&
1033 (prerender->start_time +
1034 base::TimeDelta::FromMilliseconds(GetMaxLocalPredictionTimeMs()))
1035 > GetCurrentTime());
1038 void PrerenderLocalPredictor::RecordEvent(
1039 PrerenderLocalPredictor::Event event) const {
1040 UMA_HISTOGRAM_ENUMERATION("Prerender.LocalPredictorEvent",
1041 event, PrerenderLocalPredictor::EVENT_MAX_VALUE);
1044 bool PrerenderLocalPredictor::DoesPrerenderMatchPLTRecord(
1045 PrerenderProperties* prerender,
1047 base::TimeDelta plt) const {
1048 if (prerender && prerender->start_time < GetCurrentTime() - plt) {
1049 if (prerender->url.is_empty())
1050 RecordEvent(EVENT_ERROR_NO_PRERENDER_URL_FOR_PLT);
1051 return (prerender->url == url);
1057 PrerenderLocalPredictor::PrerenderProperties*
1058 PrerenderLocalPredictor::GetIssuedPrerenderSlotForPriority(double priority) {
1059 int num_prerenders = GetLocalPredictorMaxConcurrentPrerenders();
1060 while (static_cast<int>(issued_prerenders_.size()) < num_prerenders)
1061 issued_prerenders_.push_back(new PrerenderProperties());
1062 PrerenderProperties* lowest_priority_prerender = NULL;
1063 for (int i = 0; i < static_cast<int>(issued_prerenders_.size()); i++) {
1064 PrerenderProperties* p = issued_prerenders_[i];
1066 if (!p->prerender_handle || !p->prerender_handle->IsPrerendering())
1068 double decayed_priority = p->GetCurrentDecayedPriority();
1069 if (decayed_priority > priority)
1071 if (lowest_priority_prerender == NULL ||
1072 lowest_priority_prerender->GetCurrentDecayedPriority() >
1074 lowest_priority_prerender = p;
1077 return lowest_priority_prerender;
1080 void PrerenderLocalPredictor::ContinuePrerenderCheck(
1081 scoped_ptr<CandidatePrerenderInfo> info) {
1082 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1083 TIMING_HISTOGRAM("Prerender.LocalPredictorLoggedInLookupTime",
1084 base::Time::Now() - info->start_time_);
1085 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_STARTED);
1086 if (info->candidate_urls_.size() == 0) {
1087 RecordEvent(EVENT_NO_PRERENDER_CANDIDATES);
1090 scoped_ptr<LocalPredictorURLInfo> url_info;
1091 #if defined(FULL_SAFE_BROWSING)
1092 scoped_refptr<SafeBrowsingDatabaseManager> sb_db_manager =
1093 g_browser_process->safe_browsing_service()->database_manager();
1095 PrerenderProperties* prerender_properties = NULL;
1097 for (int i = 0; i < static_cast<int>(info->candidate_urls_.size()); i++) {
1098 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_EXAMINE_NEXT_URL);
1099 url_info.reset(new LocalPredictorURLInfo(info->candidate_urls_[i]));
1100 if (url_info->local_history_based) {
1101 if (SkipLocalPredictorLocalCandidates()) {
1102 url_info.reset(NULL);
1105 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_EXAMINE_NEXT_URL_LOCAL);
1107 if (!url_info->local_history_based) {
1108 if (SkipLocalPredictorServiceCandidates()) {
1109 url_info.reset(NULL);
1112 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_EXAMINE_NEXT_URL_SERVICE);
1115 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_EXAMINE_NEXT_URL_NOT_SKIPPED);
1117 // We need to check whether we can issue a prerender for this URL.
1118 // We test a set of conditions. Each condition can either rule out
1119 // a prerender (in which case we reset url_info, so that it will not
1120 // be prerendered, and we continue, which means try the next candidate
1121 // URL), or it can be sufficient to issue the prerender without any
1122 // further checks (in which case we just break).
1123 // The order of the checks is critical, because it prescribes the logic
1124 // we use here to decide what to prerender.
1125 if (!url_info->url_lookup_success) {
1126 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_NO_URL);
1127 url_info.reset(NULL);
1130 prerender_properties =
1131 GetIssuedPrerenderSlotForPriority(url_info->priority);
1132 if (!prerender_properties) {
1133 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_PRIORITY_TOO_LOW);
1134 url_info.reset(NULL);
1137 if (!SkipLocalPredictorFragment() &&
1138 URLsIdenticalIgnoringFragments(info->source_url_.url,
1140 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_URLS_IDENTICAL_BUT_FRAGMENT);
1141 url_info.reset(NULL);
1144 if (!SkipLocalPredictorHTTPS() && url_info->url.SchemeIs("https")) {
1145 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_HTTPS);
1146 url_info.reset(NULL);
1149 if (IsRootPageURL(url_info->url)) {
1150 // For root pages, we assume that they are reasonably safe, and we
1151 // will just prerender them without any additional checks.
1152 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_ROOT_PAGE);
1155 if (IsLogOutURL(url_info->url)) {
1156 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_LOGOUT_URL);
1157 url_info.reset(NULL);
1160 if (IsLogInURL(url_info->url)) {
1161 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_LOGIN_URL);
1162 url_info.reset(NULL);
1165 #if defined(FULL_SAFE_BROWSING)
1166 if (!SkipLocalPredictorWhitelist() &&
1167 sb_db_manager->CheckSideEffectFreeWhitelistUrl(url_info->url)) {
1168 // If a page is on the side-effect free whitelist, we will just prerender
1169 // it without any additional checks.
1170 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_ON_SIDE_EFFECT_FREE_WHITELIST);
1174 if (!SkipLocalPredictorServiceWhitelist() &&
1175 url_info->service_whitelist && url_info->service_whitelist_lookup_ok) {
1176 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_ON_SERVICE_WHITELIST);
1179 if (!SkipLocalPredictorLoggedIn() &&
1180 !url_info->logged_in && url_info->logged_in_lookup_ok) {
1181 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_NOT_LOGGED_IN);
1184 if (!SkipLocalPredictorDefaultNoPrerender()) {
1185 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_FALLTHROUGH_NOT_PRERENDERING);
1186 url_info.reset(NULL);
1188 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_FALLTHROUGH_PRERENDERING);
1191 if (!url_info.get())
1193 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_ISSUING_PRERENDER);
1194 DCHECK(prerender_properties != NULL);
1195 if (IsLocalPredictorPrerenderLaunchEnabled()) {
1196 IssuePrerender(info.Pass(), url_info.Pass(), prerender_properties);
1200 void PrerenderLocalPredictor::IssuePrerender(
1201 scoped_ptr<CandidatePrerenderInfo> info,
1202 scoped_ptr<LocalPredictorURLInfo> url_info,
1203 PrerenderProperties* prerender_properties) {
1204 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1205 URLID url_id = url_info->id;
1206 const GURL& url = url_info->url;
1207 double priority = url_info->priority;
1208 base::Time current_time = GetCurrentTime();
1209 RecordEvent(EVENT_ISSUING_PRERENDER);
1211 // Issue the prerender and obtain a new handle.
1212 scoped_ptr<prerender::PrerenderHandle> new_prerender_handle(
1213 prerender_manager_->AddPrerenderFromLocalPredictor(
1214 url, info->session_storage_namespace_.get(), *(info->size_)));
1216 // Check if this is a duplicate of an existing prerender. If yes, clean up
1218 for (int i = 0; i < static_cast<int>(issued_prerenders_.size()); i++) {
1219 PrerenderProperties* p = issued_prerenders_[i];
1221 if (new_prerender_handle &&
1222 new_prerender_handle->RepresentingSamePrerenderAs(
1223 p->prerender_handle.get())) {
1224 new_prerender_handle->OnCancel();
1225 new_prerender_handle.reset(NULL);
1226 RecordEvent(EVENT_ISSUE_PRERENDER_ALREADY_PRERENDERING);
1231 if (new_prerender_handle.get()) {
1232 RecordEvent(EVENT_ISSUE_PRERENDER_NEW_PRERENDER);
1233 // The new prerender does not match any existing prerenders. Update
1234 // prerender_properties so that it reflects the new entry.
1235 prerender_properties->url_id = url_id;
1236 prerender_properties->url = url;
1237 prerender_properties->priority = priority;
1238 prerender_properties->start_time = current_time;
1239 prerender_properties->actual_start_time = current_time;
1240 prerender_properties->would_have_matched = false;
1241 prerender_properties->prerender_handle.swap(new_prerender_handle);
1242 // new_prerender_handle now represents the old previou prerender that we
1243 // are replacing. So we need to cancel it.
1244 if (new_prerender_handle) {
1245 new_prerender_handle->OnCancel();
1246 RecordEvent(EVENT_ISSUE_PRERENDER_CANCELLED_OLD_PRERENDER);
1250 RecordEvent(EVENT_ADD_VISIT_PRERENDERING);
1251 if (current_prerender_.get() && current_prerender_->url_id == url_id) {
1252 RecordEvent(EVENT_ADD_VISIT_PRERENDERING_EXTENDED);
1253 if (priority > current_prerender_->priority)
1254 current_prerender_->priority = priority;
1255 // If the prerender already existed, we want to extend it. However,
1256 // we do not want to set its start_time to the current time to
1257 // disadvantage PLT computations when the prerender is swapped in.
1258 // So we set the new start time to current_time - 10s (since the vast
1259 // majority of PLTs are < 10s), provided that is not before the actual
1260 // time the prerender was started (so as to not artificially advantage
1261 // the PLT computation).
1262 base::Time simulated_new_start_time =
1263 current_time - base::TimeDelta::FromSeconds(10);
1264 if (simulated_new_start_time > current_prerender_->start_time)
1265 current_prerender_->start_time = simulated_new_start_time;
1267 current_prerender_.reset(
1268 new PrerenderProperties(url_id, url, priority, current_time));
1270 current_prerender_->actual_start_time = current_time;
1273 void PrerenderLocalPredictor::OnTabHelperURLSeen(
1274 const GURL& url, WebContents* web_contents) {
1275 RecordEvent(EVENT_TAB_HELPER_URL_SEEN);
1277 bool browser_navigate_initiated = false;
1278 const content::NavigationEntry* entry =
1279 web_contents->GetController().GetPendingEntry();
1281 base::string16 result;
1282 browser_navigate_initiated =
1283 entry->GetExtraData(kChromeNavigateExtraDataKey, &result);
1286 // If the namespace matches and the URL matches, we might be able to swap
1287 // in. However, the actual code initating the swapin is in the renderer
1288 // and is checking for other criteria (such as POSTs). There may
1289 // also be conditions when a swapin should happen but does not. By recording
1290 // the two previous events, we can keep an eye on the magnitude of the
1293 PrerenderProperties* best_matched_prerender = NULL;
1294 bool session_storage_namespace_matches = false;
1295 SessionStorageNamespace* tab_session_storage_namespace =
1296 web_contents->GetController().GetDefaultSessionStorageNamespace();
1297 for (int i = 0; i < static_cast<int>(issued_prerenders_.size()); i++) {
1298 PrerenderProperties* p = issued_prerenders_[i];
1300 if (!p->prerender_handle.get() ||
1301 !p->prerender_handle->Matches(url, NULL) ||
1302 p->would_have_matched) {
1305 if (!best_matched_prerender || !session_storage_namespace_matches) {
1306 best_matched_prerender = p;
1307 session_storage_namespace_matches =
1308 p->prerender_handle->Matches(url, tab_session_storage_namespace);
1311 if (best_matched_prerender) {
1312 RecordEvent(EVENT_TAB_HELPER_URL_SEEN_MATCH);
1314 RecordEvent(EVENT_TAB_HELPER_URL_SEEN_MATCH_ENTRY);
1315 if (browser_navigate_initiated)
1316 RecordEvent(EVENT_TAB_HELPER_URL_SEEN_MATCH_BROWSER_NAVIGATE);
1317 best_matched_prerender->would_have_matched = true;
1318 if (session_storage_namespace_matches) {
1319 RecordEvent(EVENT_TAB_HELPER_URL_SEEN_NAMESPACE_MATCH);
1321 RecordEvent(EVENT_TAB_HELPER_URL_SEEN_NAMESPACE_MATCH_ENTRY);
1322 if (browser_navigate_initiated)
1323 RecordEvent(EVENT_TAB_HELPER_URL_SEEN_NAMESPACE_MATCH_BROWSER_NAVIGATE);
1325 SessionStorageNamespace* prerender_session_storage_namespace =
1326 best_matched_prerender->prerender_handle->
1327 GetSessionStorageNamespace();
1328 if (!prerender_session_storage_namespace) {
1329 RecordEvent(EVENT_TAB_HELPER_URL_SEEN_NAMESPACE_MISMATCH_NO_NAMESPACE);
1331 RecordEvent(EVENT_TAB_HELPER_URL_SEEN_NAMESPACE_MISMATCH_MERGE_ISSUED);
1332 prerender_session_storage_namespace->Merge(
1334 best_matched_prerender->prerender_handle->GetChildId(),
1335 tab_session_storage_namespace,
1336 base::Bind(&PrerenderLocalPredictor::ProcessNamespaceMergeResult,
1337 weak_factory_.GetWeakPtr()));
1343 void PrerenderLocalPredictor::ProcessNamespaceMergeResult(
1344 content::SessionStorageNamespace::MergeResult result) {
1345 RecordEvent(EVENT_NAMESPACE_MISMATCH_MERGE_RESULT_RECEIVED);
1347 case content::SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_FOUND:
1348 RecordEvent(EVENT_NAMESPACE_MISMATCH_MERGE_RESULT_NAMESPACE_NOT_FOUND);
1350 case content::SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_ALIAS:
1351 RecordEvent(EVENT_NAMESPACE_MISMATCH_MERGE_RESULT_NAMESPACE_NOT_ALIAS);
1353 case content::SessionStorageNamespace::MERGE_RESULT_NOT_LOGGING:
1354 RecordEvent(EVENT_NAMESPACE_MISMATCH_MERGE_RESULT_NOT_LOGGING);
1356 case content::SessionStorageNamespace::MERGE_RESULT_NO_TRANSACTIONS:
1357 RecordEvent(EVENT_NAMESPACE_MISMATCH_MERGE_RESULT_NO_TRANSACTIONS);
1359 case content::SessionStorageNamespace::MERGE_RESULT_TOO_MANY_TRANSACTIONS:
1360 RecordEvent(EVENT_NAMESPACE_MISMATCH_MERGE_RESULT_TOO_MANY_TRANSACTIONS);
1362 case content::SessionStorageNamespace::MERGE_RESULT_NOT_MERGEABLE:
1363 RecordEvent(EVENT_NAMESPACE_MISMATCH_MERGE_RESULT_NOT_MERGEABLE);
1365 case content::SessionStorageNamespace::MERGE_RESULT_MERGEABLE:
1366 RecordEvent(EVENT_NAMESPACE_MISMATCH_MERGE_RESULT_MERGEABLE);
1373 } // namespace prerender