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