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