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