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