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