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