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