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"
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
;
54 using content::BrowserThread
;
55 using ui::PageTransition
;
56 using content::RenderFrameHost
;
57 using content::SessionStorageNamespace
;
58 using content::WebContents
;
60 using net::URLFetcher
;
61 using predictors::LoggedInPredictorTable
;
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;
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
80 struct PrerenderLocalPredictor::LocalPredictorURLInfo
{
83 bool url_lookup_success
;
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
;
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
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
;
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
,
124 bool whitelist_lookup_ok
) {
125 LocalPredictorURLInfo info
;
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
)
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];
154 if (insert_pos
< max_candidates
)
155 candidate_urls_
[insert_pos
] = info
;
161 #define TIMING_HISTOGRAM(name, value) \
162 UMA_HISTOGRAM_CUSTOM_TIMES(name, value, \
163 base::TimeDelta::FromMilliseconds(10), \
164 base::TimeDelta::FromSeconds(10), \
167 // Task to lookup the URL for a given URLID.
168 class GetURLForURLIDTask
: public history::HistoryDBTask
{
171 PrerenderLocalPredictor::CandidatePrerenderInfo
* request
,
172 const base::Closure
& 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
]);
186 void DoneRunOnMainThread() override
{
188 TIMING_HISTOGRAM("Prerender.LocalPredictorURLLookupTime",
189 base::Time::Now() - start_time_
);
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
{
212 GetVisitHistoryTask(PrerenderLocalPredictor
* local_predictor
,
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());
225 void DoneRunOnMainThread() override
{
226 local_predictor_
->OnGetInitialVisitHistory(visit_history_
.Pass());
230 ~GetVisitHistoryTask() override
{}
232 PrerenderLocalPredictor
* local_predictor_
;
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");
312 memcpy(&value
, data
, kURLHashSize
);
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
);
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
);
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
);
346 info
->logged_in_lookup_ok
= false;
353 struct PrerenderLocalPredictor::PrerenderProperties
{
354 PrerenderProperties(URLID url_id
, const GURL
& url
, double priority
,
355 base::Time start_time
)
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())
372 int half_life_time_seconds
=
373 GetLocalPredictorPrerenderPriorityHalfLifeTimeSeconds();
374 if (half_life_time_seconds
< 1)
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
;
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
{
406 SEEN_TABCONTENTS_OBSERVER
,
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
) {
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_
) {
432 ListEntry
* entry
= new ListEntry(url_string
);
433 entries_
[entry
->url_
] = entry
;
434 entry_list_
.push_back(entry
);
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
) {
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())
449 if (type
== SEEN_TABCONTENTS_OBSERVER
&& !it
->second
->seen_tabcontents_
) {
450 it
->second
->seen_tabcontents_
= true;
453 if (type
== SEEN_HISTORY
&& !it
->second
->seen_history_
) {
454 it
->second
->seen_history_
= 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());
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
) {
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
) {
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());
491 explicit ListEntry(const string
& url
)
493 add_time_(GetCurrentTime()),
494 seen_tabcontents_(false),
495 seen_history_(false),
499 base::Time add_time_
;
500 bool seen_tabcontents_
;
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_
);
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
),
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
);
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
);
563 for (const unsigned char* p
= front
+ kChecksumHashSize
;
566 url_whitelist_
.insert(URLHashToInt64(p
));
568 RecordEvent(EVENT_URL_WHITELIST_OK
);
571 PrerenderLocalPredictor::~PrerenderLocalPredictor() {
573 for (PrerenderProperties
* p
: issued_prerenders_
) {
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() {
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())
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()),
610 last_swapped_in_prerender_
.reset(current_prerender_
.release());
611 RecordEvent(EVENT_ADD_VISIT_PRERENDER_IDENTIFIED
);
613 if (ShouldExcludeTransitionForPrediction(info
.transition
))
615 Profile
* profile
= prerender_manager_
->profile();
617 ShouldDisableLocalPredictorDueToPreferencesAndNetwork(profile
)) {
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();
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();
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
);
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();
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 &&
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();
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(
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
);
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
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
;
734 multiple_source_web_contents_candidates
= true;
739 if (!source_web_contents
) {
740 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_NO_SOURCE_WEBCONTENTS_FOUND
);
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());
770 Create a JSON request.
771 Here is a sample request:
772 { "prerender_request": {
777 { "url": "http://www.cnn.com/"
781 "candidate_check_request": {
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(
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(
847 base::Bind(&PrerenderLocalPredictor::MaybeCancelURLFetcher
,
848 weak_factory_
.GetWeakPtr(), fetcher
),
849 base::TimeDelta::FromMilliseconds(GetPrerenderServiceFetchTimeoutMs()));
850 RecordEvent(EVENT_PRERENDER_SERVICE_ISSUED_LOOKUP
);
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())
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": {
880 "hinting_timed_out": 0,
882 { "url": "http://www.cnn.com/story-1",
885 "in_index_timed_out": 0
887 { "url": "http://www.cnn.com/story-2",
890 "in_index_timed_out": 0
894 "candidate_check_response": {
896 { "url": "http://www.cnn.com/sports/",
898 "in_index_timed_out": 0
900 { "url": "http://www.cnn.com/politics/",
902 "in_index_timed_out": "1"
909 base::ListValue
* list
= NULL
;
911 if (!dict
->GetInteger("prerender_response.behavior_id", &int_value
) ||
912 int_value
!= GetPrerenderServiceBehaviorID()) {
915 if (!dict
->GetList("prerender_response.candidate_check_response.candidates",
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
)
924 for (size_t i
= 0; i
< list
->GetSize(); i
++) {
925 base::DictionaryValue
* d
;
926 if (!list
->GetDictionary(i
, &d
))
929 if (!d
->GetString("url", &url_string
) || !GURL(url_string
).is_valid())
931 GURL
url(url_string
);
932 int in_index_timed_out
= 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
)) {
939 if (in_index
< 0 || in_index
> 1 ||
940 in_index_timed_out
< 0 || in_index_timed_out
> 1) {
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
) {
962 if (ShouldQueryPrerenderServiceForCurrentURL() &&
963 info
->source_url_
.url_lookup_success
) {
965 if (dict
->GetInteger("prerender_response.hint_response.hinting_timed_out",
968 *hinting_timed_out
= true;
969 } else if (!dict
->GetList("prerender_response.hint_response.candidates",
973 for (int i
= 0; i
< static_cast<int>(list
->GetSize()); i
++) {
974 base::DictionaryValue
* d
;
975 if (!list
->GetDictionary(i
, &d
))
978 if (!d
->GetString("url", &url
) || !GURL(url
).is_valid())
981 if (!d
->GetDouble("likelihood", &priority
) || priority
< 0.0 ||
985 int in_index_timed_out
= 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
)) {
992 if (in_index
< 0 || in_index
> 1 || in_index_timed_out
< 0 ||
993 in_index_timed_out
> 1) {
996 if (in_index_timed_out
== 1)
997 *hinting_url_lookup_timed_out
= true;
998 info
->MaybeAddCandidateURLFromService(GURL(url
),
1001 !in_index_timed_out
);
1003 if (list
->GetSize() > 0)
1004 RecordEvent(EVENT_PRERENDER_SERVICE_RETURNED_HINTING_CANDIDATES
);
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
);
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_
);
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
);
1036 if (ApplyParsedPrerenderServiceResponse(
1037 static_cast<base::DictionaryValue
*>(root
.get()),
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());
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
);
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
,
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();
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();
1131 ShouldDisableLocalPredictorBasedOnSyncAndConfiguration(profile
)) {
1132 RecordEvent(EVENT_INIT_FAILED_UNENCRYPTED_SYNC_NOT_ENABLED
);
1135 history::HistoryService
* history
= GetHistoryIfExists();
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
);
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",
1153 base::TimeDelta::FromMilliseconds(10),
1154 base::TimeDelta::FromSeconds(60),
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())
1169 if (IsPrerenderStillValid(prerender
.get())) {
1170 UMA_HISTOGRAM_CUSTOM_TIMES("Prerender.SimulatedLocalBrowsingBaselinePLT",
1172 base::TimeDelta::FromMilliseconds(10),
1173 base::TimeDelta::FromSeconds(60),
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",
1183 base::TimeDelta::FromMilliseconds(10),
1184 base::TimeDelta::FromSeconds(60),
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
,
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
);
1217 PrerenderLocalPredictor::PrerenderProperties
*
1218 PrerenderLocalPredictor::GetIssuedPrerenderSlotForPriority(const GURL
& url
,
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
];
1229 if (p
->prerender_handle
&& p
->prerender_handle
->IsPrerendering() &&
1230 p
->prerender_handle
->Matches(url
, 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
];
1241 if (!p
->prerender_handle
|| !p
->prerender_handle
->IsPrerendering())
1243 double decayed_priority
= p
->GetCurrentDecayedPriority();
1244 if (decayed_priority
> priority
)
1246 if (lowest_priority_prerender
== NULL
||
1247 lowest_priority_prerender
->GetCurrentDecayedPriority() >
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
);
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();
1271 for (int i
= 0; i
< static_cast<int>(info
->candidate_urls_
.size()); i
++) {
1272 if (num_issued
>= GetLocalPredictorMaxLaunchPrerenders())
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
);
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
);
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
);
1306 if (!SkipLocalPredictorFragment() &&
1307 URLsIdenticalIgnoringFragments(info
->source_url_
.url
,
1309 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_URLS_IDENTICAL_BUT_FRAGMENT
);
1310 url_info
.reset(NULL
);
1313 if (!SkipLocalPredictorHTTPS() && url_info
->url
.SchemeIs("https")) {
1314 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_HTTPS
);
1315 url_info
.reset(NULL
);
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());
1326 if (IsLogOutURL(url_info
->url
)) {
1327 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_LOGOUT_URL
);
1328 url_info
.reset(NULL
);
1331 if (IsLogInURL(url_info
->url
)) {
1332 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_LOGIN_URL
);
1333 url_info
.reset(NULL
);
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());
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());
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());
1361 if (!SkipLocalPredictorDefaultNoPrerender()) {
1362 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_FALLTHROUGH_NOT_PRERENDERING
);
1363 url_info
.reset(NULL
);
1365 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_FALLTHROUGH_PRERENDERING
);
1366 IssuePrerender(info
.get(), url_info
.get());
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
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.
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
);
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())
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
1419 for (int i
= 0; i
< static_cast<int>(issued_prerenders_
.size()); i
++) {
1420 PrerenderProperties
* p
= issued_prerenders_
[i
];
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
);
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
;
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();
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
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
];
1503 if (!p
->prerender_handle
.get() ||
1504 !p
->prerender_handle
->Matches(url
, NULL
) ||
1505 p
->would_have_matched
) {
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
);
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
);
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