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_database.h"
23 #include "chrome/browser/history/history_db_task.h"
24 #include "chrome/browser/history/history_service.h"
25 #include "chrome/browser/history/history_service_factory.h"
26 #include "chrome/browser/prerender/prerender_field_trial.h"
27 #include "chrome/browser/prerender/prerender_handle.h"
28 #include "chrome/browser/prerender/prerender_histograms.h"
29 #include "chrome/browser/prerender/prerender_manager.h"
30 #include "chrome/browser/prerender/prerender_util.h"
31 #include "chrome/browser/profiles/profile.h"
32 #include "chrome/browser/safe_browsing/database_manager.h"
33 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
34 #include "chrome/browser/ui/tab_contents/tab_contents_iterator.h"
35 #include "chrome/common/prefetch_messages.h"
36 #include "content/public/browser/browser_thread.h"
37 #include "content/public/browser/navigation_controller.h"
38 #include "content/public/browser/navigation_entry.h"
39 #include "content/public/browser/render_frame_host.h"
40 #include "content/public/browser/render_process_host.h"
41 #include "content/public/browser/web_contents.h"
42 #include "content/public/common/page_transition_types.h"
43 #include "crypto/secure_hash.h"
44 #include "grit/browser_resources.h"
45 #include "net/base/escape.h"
46 #include "net/base/load_flags.h"
47 #include "net/url_request/url_fetcher.h"
48 #include "ui/base/resource/resource_bundle.h"
49 #include "url/url_canon.h"
51 using base::DictionaryValue
;
52 using base::ListValue
;
54 using content::BrowserThread
;
55 using content::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 virtual bool RunOnDBThread(history::HistoryBackend
* backend
,
179 history::HistoryDatabase
* db
) OVERRIDE
{
180 DoURLLookup(db
, &request_
->source_url_
);
181 for (int i
= 0; i
< static_cast<int>(request_
->candidate_urls_
.size()); i
++)
182 DoURLLookup(db
, &request_
->candidate_urls_
[i
]);
186 virtual void DoneRunOnMainThread() OVERRIDE
{
188 TIMING_HISTOGRAM("Prerender.LocalPredictorURLLookupTime",
189 base::Time::Now() - start_time_
);
193 virtual ~GetURLForURLIDTask() {}
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 virtual bool RunOnDBThread(history::HistoryBackend
* backend
,
220 history::HistoryDatabase
* db
) OVERRIDE
{
221 db
->GetBriefVisitInfoOfMostRecentVisits(max_visits_
, visit_history_
.get());
225 virtual void DoneRunOnMainThread() OVERRIDE
{
226 local_predictor_
->OnGetInitialVisitHistory(visit_history_
.Pass());
230 virtual ~GetVisitHistoryTask() {}
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
& content::PAGE_TRANSITION_FORWARD_BACK
) != 0;
255 bool IsHomePage(PageTransition transition
) {
256 return (transition
& content::PAGE_TRANSITION_HOME_PAGE
) != 0;
259 bool IsIntermediateRedirect(PageTransition transition
) {
260 return (transition
& content::PAGE_TRANSITION_CHAIN_END
) == 0;
263 bool IsFormSubmit(PageTransition transition
) {
264 return PageTransitionCoreTypeIs(transition
,
265 content::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() == "" || 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 COMPILE_ASSERT(kURLHashSize
< sizeof(int64
), url_hash_must_fit_in_int64
);
312 memcpy(&value
, data
, kURLHashSize
);
316 int64
GetInt64URLHashForURL(const GURL
& url
) {
317 COMPILE_ASSERT(kURLHashSize
< sizeof(int64
), url_hash_must_fit_in_int64
);
318 scoped_ptr
<crypto::SecureHash
> hash(
319 crypto::SecureHash::Create(crypto::SecureHash::SHA256
));
320 int64 hash_value
= 0;
321 const char* url_string
= url
.spec().c_str();
322 hash
->Update(url_string
, strlen(url_string
));
323 hash
->Finish(&hash_value
, kURLHashSize
);
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 is_visit_database_observer_(false),
533 prefetch_list_(new PrefetchList()) {
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 (int i
= 0; i
< static_cast<int>(issued_prerenders_
.size()); i
++) {
574 PrerenderProperties
* p
= issued_prerenders_
[i
];
576 if (p
->prerender_handle
)
577 p
->prerender_handle
->OnCancel();
579 STLDeleteContainerPairPointers(
580 outstanding_prerender_service_requests_
.begin(),
581 outstanding_prerender_service_requests_
.end());
584 void PrerenderLocalPredictor::Shutdown() {
586 if (is_visit_database_observer_
) {
587 HistoryService
* history
= GetHistoryIfExists();
589 history
->RemoveVisitDatabaseObserver(this);
590 is_visit_database_observer_
= false;
594 void PrerenderLocalPredictor::OnAddVisit(const history::BriefVisitInfo
& info
) {
595 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
596 RecordEvent(EVENT_ADD_VISIT
);
597 if (!visit_history_
.get())
599 visit_history_
->push_back(info
);
600 if (static_cast<int>(visit_history_
->size()) > kVisitHistoryPruneThreshold
) {
601 visit_history_
->erase(visit_history_
->begin(),
602 visit_history_
->begin() + kVisitHistoryPruneAmount
);
604 RecordEvent(EVENT_ADD_VISIT_INITIALIZED
);
605 if (current_prerender_
.get() &&
606 current_prerender_
->url_id
== info
.url_id
&&
607 IsPrerenderStillValid(current_prerender_
.get())) {
608 UMA_HISTOGRAM_CUSTOM_TIMES(
609 "Prerender.LocalPredictorTimeUntilUsed",
610 GetCurrentTime() - current_prerender_
->actual_start_time
,
611 base::TimeDelta::FromMilliseconds(10),
612 base::TimeDelta::FromMilliseconds(GetMaxLocalPredictionTimeMs()),
614 last_swapped_in_prerender_
.reset(current_prerender_
.release());
615 RecordEvent(EVENT_ADD_VISIT_PRERENDER_IDENTIFIED
);
617 if (ShouldExcludeTransitionForPrediction(info
.transition
))
619 RecordEvent(EVENT_ADD_VISIT_RELEVANT_TRANSITION
);
620 base::TimeDelta max_age
=
621 base::TimeDelta::FromMilliseconds(GetMaxLocalPredictionTimeMs());
622 base::TimeDelta min_age
=
623 base::TimeDelta::FromMilliseconds(kMinLocalPredictionTimeMs
);
624 std::set
<URLID
> next_urls_currently_found
;
625 std::map
<URLID
, int> next_urls_num_found
;
626 int num_occurrences_of_current_visit
= 0;
627 base::Time last_visited
;
628 scoped_ptr
<CandidatePrerenderInfo
> lookup_info(
629 new CandidatePrerenderInfo(info
.url_id
));
630 const vector
<history::BriefVisitInfo
>& visits
= *(visit_history_
.get());
631 for (int i
= 0; i
< static_cast<int>(visits
.size()); i
++) {
632 if (!ShouldExcludeTransitionForPrediction(visits
[i
].transition
)) {
633 if (visits
[i
].url_id
== info
.url_id
) {
634 last_visited
= visits
[i
].time
;
635 num_occurrences_of_current_visit
++;
636 next_urls_currently_found
.clear();
639 if (!last_visited
.is_null() &&
640 last_visited
> visits
[i
].time
- max_age
&&
641 last_visited
< visits
[i
].time
- min_age
) {
642 if (!IsFormSubmit(visits
[i
].transition
))
643 next_urls_currently_found
.insert(visits
[i
].url_id
);
646 if (i
== static_cast<int>(visits
.size()) - 1 ||
647 visits
[i
+1].url_id
== info
.url_id
) {
648 for (std::set
<URLID
>::iterator it
= next_urls_currently_found
.begin();
649 it
!= next_urls_currently_found
.end();
651 std::pair
<std::map
<URLID
, int>::iterator
, bool> insert_ret
=
652 next_urls_num_found
.insert(std::pair
<URLID
, int>(*it
, 0));
653 std::map
<URLID
, int>::iterator num_found_it
= insert_ret
.first
;
654 num_found_it
->second
++;
659 if (num_occurrences_of_current_visit
> 1) {
660 RecordEvent(EVENT_ADD_VISIT_RELEVANT_TRANSITION_REPEAT_URL
);
662 RecordEvent(EVENT_ADD_VISIT_RELEVANT_TRANSITION_NEW_URL
);
665 for (std::map
<URLID
, int>::const_iterator it
= next_urls_num_found
.begin();
666 it
!= next_urls_num_found
.end();
668 // Only consider a candidate next page for prerendering if it was viewed
669 // at least twice, and at least 10% of the time.
670 if (num_occurrences_of_current_visit
> 0 &&
672 it
->second
* 10 >= num_occurrences_of_current_visit
) {
673 RecordEvent(EVENT_ADD_VISIT_IDENTIFIED_PRERENDER_CANDIDATE
);
674 double priority
= static_cast<double>(it
->second
) /
675 static_cast<double>(num_occurrences_of_current_visit
);
676 lookup_info
->MaybeAddCandidateURLFromLocalData(it
->first
, priority
);
680 RecordEvent(EVENT_START_URL_LOOKUP
);
681 HistoryService
* history
= GetHistoryIfExists();
683 RecordEvent(EVENT_GOT_HISTORY_ISSUING_LOOKUP
);
684 CandidatePrerenderInfo
* lookup_info_ptr
= lookup_info
.get();
685 history
->ScheduleDBTask(
686 scoped_ptr
<history::HistoryDBTask
>(
687 new GetURLForURLIDTask(
689 base::Bind(&PrerenderLocalPredictor::OnLookupURL
,
690 base::Unretained(this),
691 base::Passed(&lookup_info
)))),
692 &history_db_tracker_
);
696 void PrerenderLocalPredictor::OnLookupURL(
697 scoped_ptr
<CandidatePrerenderInfo
> info
) {
698 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
700 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT
);
702 if (!info
->source_url_
.url_lookup_success
) {
703 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_FAILED
);
707 if (prefetch_list_
->MarkURLSeen(info
->source_url_
.url
,
708 PrefetchList::SEEN_HISTORY
)) {
709 RecordEvent(EVENT_PREFETCH_LIST_SEEN_HISTORY
);
712 if (info
->candidate_urls_
.size() > 0 &&
713 info
->candidate_urls_
[0].url_lookup_success
) {
714 LogCandidateURLStats(info
->candidate_urls_
[0].url
);
717 WebContents
* source_web_contents
= NULL
;
718 bool multiple_source_web_contents_candidates
= false;
720 #if !defined(OS_ANDROID)
721 // We need to figure out what tab launched the prerender. We do this by
722 // comparing URLs. This may not always work: the URL may occur in two
723 // tabs, and we pick the wrong one, or the tab we should have picked
724 // may have navigated elsewhere. Hopefully, this doesn't happen too often,
725 // so we ignore these cases for now.
726 // TODO(tburkard): Reconsider this, potentially measure it, and fix this
728 for (TabContentsIterator it
; !it
.done(); it
.Next()) {
729 if (it
->GetURL() == info
->source_url_
.url
) {
730 if (!source_web_contents
)
731 source_web_contents
= *it
;
733 multiple_source_web_contents_candidates
= true;
738 if (!source_web_contents
) {
739 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_NO_SOURCE_WEBCONTENTS_FOUND
);
743 if (multiple_source_web_contents_candidates
)
744 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_MULTIPLE_SOURCE_WEBCONTENTS_FOUND
);
746 info
->session_storage_namespace_
=
747 source_web_contents
->GetController().GetDefaultSessionStorageNamespace();
748 RenderFrameHost
* rfh
= source_web_contents
->GetMainFrame();
749 info
->render_process_id_
= rfh
->GetProcess()->GetID();
750 info
->render_frame_id_
= rfh
->GetRoutingID();
752 gfx::Rect container_bounds
= source_web_contents
->GetContainerBounds();
753 info
->size_
.reset(new gfx::Size(container_bounds
.size()));
755 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_SUCCESS
);
757 DoPrerenderServiceCheck(info
.Pass());
760 void PrerenderLocalPredictor::DoPrerenderServiceCheck(
761 scoped_ptr
<CandidatePrerenderInfo
> info
) {
762 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
763 if (!ShouldQueryPrerenderService(prerender_manager_
->profile())) {
764 RecordEvent(EVENT_PRERENDER_SERVICE_DISABLED
);
765 DoLoggedInLookup(info
.Pass());
769 Create a JSON request.
770 Here is a sample request:
771 { "prerender_request": {
776 { "url": "http://www.cnn.com/"
780 "candidate_check_request": {
782 { "url": "http://www.cnn.com/sports/"
784 { "url": "http://www.cnn.com/politics/"
791 base::DictionaryValue json_data
;
792 base::DictionaryValue
* req
= new base::DictionaryValue();
793 req
->SetInteger("version", 1);
794 req
->SetInteger("behavior_id", GetPrerenderServiceBehaviorID());
795 if (ShouldQueryPrerenderServiceForCurrentURL() &&
796 info
->source_url_
.url_lookup_success
) {
797 base::ListValue
* browse_history
= new base::ListValue();
798 base::DictionaryValue
* browse_item
= new base::DictionaryValue();
799 browse_item
->SetString("url", info
->source_url_
.url
.spec());
800 browse_history
->Append(browse_item
);
801 base::DictionaryValue
* hint_request
= new base::DictionaryValue();
802 hint_request
->Set("browse_history", browse_history
);
803 req
->Set("hint_request", hint_request
);
805 int num_candidate_urls
= 0;
806 for (int i
= 0; i
< static_cast<int>(info
->candidate_urls_
.size()); i
++) {
807 if (info
->candidate_urls_
[i
].url_lookup_success
)
808 num_candidate_urls
++;
810 if (ShouldQueryPrerenderServiceForCandidateURLs() &&
811 num_candidate_urls
> 0) {
812 base::ListValue
* candidates
= new base::ListValue();
813 base::DictionaryValue
* candidate
;
814 for (int i
= 0; i
< static_cast<int>(info
->candidate_urls_
.size()); i
++) {
815 if (info
->candidate_urls_
[i
].url_lookup_success
) {
816 candidate
= new base::DictionaryValue();
817 candidate
->SetString("url", info
->candidate_urls_
[i
].url
.spec());
818 candidates
->Append(candidate
);
821 base::DictionaryValue
* candidate_check_request
=
822 new base::DictionaryValue();
823 candidate_check_request
->Set("candidates", candidates
);
824 req
->Set("candidate_check_request", candidate_check_request
);
826 json_data
.Set("prerender_request", req
);
827 string request_string
;
828 base::JSONWriter::Write(&json_data
, &request_string
);
829 GURL
fetch_url(GetPrerenderServiceURLPrefix() +
830 net::EscapeQueryParamValue(request_string
, false));
831 net::URLFetcher
* fetcher
= net::URLFetcher::Create(
834 URLFetcher::GET
, this);
835 fetcher
->SetRequestContext(
836 prerender_manager_
->profile()->GetRequestContext());
837 fetcher
->SetLoadFlags(net::LOAD_DISABLE_CACHE
|
838 net::LOAD_DO_NOT_SAVE_COOKIES
|
839 net::LOAD_DO_NOT_SEND_COOKIES
);
840 fetcher
->AddExtraRequestHeader("Pragma: no-cache");
841 info
->start_time_
= base::Time::Now();
842 outstanding_prerender_service_requests_
.insert(
843 std::make_pair(fetcher
, info
.release()));
844 base::MessageLoop::current()->PostDelayedTask(
846 base::Bind(&PrerenderLocalPredictor::MaybeCancelURLFetcher
,
847 weak_factory_
.GetWeakPtr(), fetcher
),
848 base::TimeDelta::FromMilliseconds(GetPrerenderServiceFetchTimeoutMs()));
849 RecordEvent(EVENT_PRERENDER_SERVICE_ISSUED_LOOKUP
);
853 void PrerenderLocalPredictor::MaybeCancelURLFetcher(net::URLFetcher
* fetcher
) {
854 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
855 OutstandingFetchers::iterator it
=
856 outstanding_prerender_service_requests_
.find(fetcher
);
857 if (it
== outstanding_prerender_service_requests_
.end())
860 scoped_ptr
<CandidatePrerenderInfo
> info(it
->second
);
861 outstanding_prerender_service_requests_
.erase(it
);
862 RecordEvent(EVENT_PRERENDER_SERVICE_LOOKUP_TIMED_OUT
);
863 DoLoggedInLookup(info
.Pass());
866 bool PrerenderLocalPredictor::ApplyParsedPrerenderServiceResponse(
867 base::DictionaryValue
* dict
,
868 CandidatePrerenderInfo
* info
,
869 bool* hinting_timed_out
,
870 bool* hinting_url_lookup_timed_out
,
871 bool* candidate_url_lookup_timed_out
) {
873 Process the response to the request.
874 Here is a sample response to illustrate the format.
876 "prerender_response": {
879 "hinting_timed_out": 0,
881 { "url": "http://www.cnn.com/story-1",
884 "in_index_timed_out": 0
886 { "url": "http://www.cnn.com/story-2",
889 "in_index_timed_out": 0
893 "candidate_check_response": {
895 { "url": "http://www.cnn.com/sports/",
897 "in_index_timed_out": 0
899 { "url": "http://www.cnn.com/politics/",
901 "in_index_timed_out": "1"
908 base::ListValue
* list
= NULL
;
910 if (!dict
->GetInteger("prerender_response.behavior_id", &int_value
) ||
911 int_value
!= GetPrerenderServiceBehaviorID()) {
914 if (!dict
->GetList("prerender_response.candidate_check_response.candidates",
916 if (ShouldQueryPrerenderServiceForCandidateURLs()) {
917 for (int i
= 0; i
< static_cast<int>(info
->candidate_urls_
.size()); i
++) {
918 if (info
->candidate_urls_
[i
].url_lookup_success
)
923 for (size_t i
= 0; i
< list
->GetSize(); i
++) {
924 base::DictionaryValue
* d
;
925 if (!list
->GetDictionary(i
, &d
))
928 if (!d
->GetString("url", &url_string
) || !GURL(url_string
).is_valid())
930 GURL
url(url_string
);
931 int in_index_timed_out
= 0;
933 if ((!d
->GetInteger("in_index_timed_out", &in_index_timed_out
) ||
934 in_index_timed_out
!= 1) &&
935 !d
->GetInteger("in_index", &in_index
)) {
938 if (in_index
< 0 || in_index
> 1 ||
939 in_index_timed_out
< 0 || in_index_timed_out
> 1) {
942 if (in_index_timed_out
== 1)
943 *candidate_url_lookup_timed_out
= true;
944 for (size_t j
= 0; j
< info
->candidate_urls_
.size(); j
++) {
945 if (info
->candidate_urls_
[j
].url
== url
) {
946 info
->candidate_urls_
[j
].service_whitelist_reported
= true;
947 info
->candidate_urls_
[j
].service_whitelist
= (in_index
== 1);
948 info
->candidate_urls_
[j
].service_whitelist_lookup_ok
=
949 ((1 - in_index_timed_out
) == 1);
953 for (size_t i
= 0; i
< info
->candidate_urls_
.size(); i
++) {
954 if (info
->candidate_urls_
[i
].url_lookup_success
&&
955 !info
->candidate_urls_
[i
].service_whitelist_reported
) {
961 if (ShouldQueryPrerenderServiceForCurrentURL() &&
962 info
->source_url_
.url_lookup_success
) {
964 if (dict
->GetInteger("prerender_response.hint_response.hinting_timed_out",
967 *hinting_timed_out
= true;
968 } else if (!dict
->GetList("prerender_response.hint_response.candidates",
972 for (int i
= 0; i
< static_cast<int>(list
->GetSize()); i
++) {
973 base::DictionaryValue
* d
;
974 if (!list
->GetDictionary(i
, &d
))
977 if (!d
->GetString("url", &url
) || !GURL(url
).is_valid())
980 if (!d
->GetDouble("likelihood", &priority
) || priority
< 0.0 ||
984 int in_index_timed_out
= 0;
986 if ((!d
->GetInteger("in_index_timed_out", &in_index_timed_out
) ||
987 in_index_timed_out
!= 1) &&
988 !d
->GetInteger("in_index", &in_index
)) {
991 if (in_index
< 0 || in_index
> 1 || in_index_timed_out
< 0 ||
992 in_index_timed_out
> 1) {
995 if (in_index_timed_out
== 1)
996 *hinting_url_lookup_timed_out
= true;
997 info
->MaybeAddCandidateURLFromService(GURL(url
),
1000 !in_index_timed_out
);
1002 if (list
->GetSize() > 0)
1003 RecordEvent(EVENT_PRERENDER_SERVICE_RETURNED_HINTING_CANDIDATES
);
1010 void PrerenderLocalPredictor::OnURLFetchComplete(
1011 const net::URLFetcher
* source
) {
1012 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1013 RecordEvent(EVENT_PRERENDER_SERVICE_RECEIVED_RESULT
);
1014 net::URLFetcher
* fetcher
= const_cast<net::URLFetcher
*>(source
);
1015 OutstandingFetchers::iterator it
=
1016 outstanding_prerender_service_requests_
.find(fetcher
);
1017 if (it
== outstanding_prerender_service_requests_
.end()) {
1018 RecordEvent(EVENT_PRERENDER_SERVICE_NO_RECORD_FOR_RESULT
);
1021 scoped_ptr
<CandidatePrerenderInfo
> info(it
->second
);
1022 outstanding_prerender_service_requests_
.erase(it
);
1023 TIMING_HISTOGRAM("Prerender.LocalPredictorServiceLookupTime",
1024 base::Time::Now() - info
->start_time_
);
1026 fetcher
->GetResponseAsString(&result
);
1027 scoped_ptr
<base::Value
> root
;
1028 root
.reset(base::JSONReader::Read(result
));
1029 bool hinting_timed_out
= false;
1030 bool hinting_url_lookup_timed_out
= false;
1031 bool candidate_url_lookup_timed_out
= false;
1032 if (!root
.get() || !root
->IsType(base::Value::TYPE_DICTIONARY
)) {
1033 RecordEvent(EVENT_PRERENDER_SERVICE_PARSE_ERROR_INCORRECT_JSON
);
1035 if (ApplyParsedPrerenderServiceResponse(
1036 static_cast<base::DictionaryValue
*>(root
.get()),
1039 &hinting_url_lookup_timed_out
,
1040 &candidate_url_lookup_timed_out
)) {
1041 // We finished parsing the result, and found no errors.
1042 RecordEvent(EVENT_PRERENDER_SERVICE_PARSED_CORRECTLY
);
1043 if (hinting_timed_out
)
1044 RecordEvent(EVENT_PRERENDER_SERVICE_HINTING_TIMED_OUT
);
1045 if (hinting_url_lookup_timed_out
)
1046 RecordEvent(EVENT_PRERENDER_SERVICE_HINTING_URL_LOOKUP_TIMED_OUT
);
1047 if (candidate_url_lookup_timed_out
)
1048 RecordEvent(EVENT_PRERENDER_SERVICE_CANDIDATE_URL_LOOKUP_TIMED_OUT
);
1049 DoLoggedInLookup(info
.Pass());
1054 // If we did not return earlier, an error happened during parsing.
1055 // Record this, and proceed.
1056 RecordEvent(EVENT_PRERENDER_SERVICE_PARSE_ERROR
);
1057 DoLoggedInLookup(info
.Pass());
1060 void PrerenderLocalPredictor:: DoLoggedInLookup(
1061 scoped_ptr
<CandidatePrerenderInfo
> info
) {
1062 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1063 scoped_refptr
<LoggedInPredictorTable
> logged_in_table
=
1064 prerender_manager_
->logged_in_predictor_table();
1066 if (!logged_in_table
.get()) {
1067 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_NO_LOGGED_IN_TABLE_FOUND
);
1071 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_ISSUING_LOGGED_IN_LOOKUP
);
1073 info
->start_time_
= base::Time::Now();
1075 CandidatePrerenderInfo
* info_ptr
= info
.get();
1076 BrowserThread::PostTaskAndReply(
1077 BrowserThread::DB
, FROM_HERE
,
1078 base::Bind(&LookupLoggedInStatesOnDBThread
,
1081 base::Bind(&PrerenderLocalPredictor::ContinuePrerenderCheck
,
1082 weak_factory_
.GetWeakPtr(),
1083 base::Passed(&info
)));
1086 void PrerenderLocalPredictor::LogCandidateURLStats(const GURL
& url
) const {
1087 if (url_whitelist_
.count(GetInt64URLHashForURL(url
)) > 0) {
1088 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_ON_WHITELIST
);
1089 if (IsRootPageURL(url
))
1090 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_ON_WHITELIST_ROOT_PAGE
);
1092 if (IsRootPageURL(url
))
1093 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_ROOT_PAGE
);
1094 if (IsExtendedRootURL(url
))
1095 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_EXTENDED_ROOT_PAGE
);
1096 if (IsRootPageURL(url
) && url
.SchemeIs("http"))
1097 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_ROOT_PAGE_HTTP
);
1098 if (url
.SchemeIs("http"))
1099 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_IS_HTTP
);
1100 if (url
.has_query())
1101 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_HAS_QUERY_STRING
);
1102 if (IsLogOutURL(url
))
1103 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_CONTAINS_LOGOUT
);
1104 if (IsLogInURL(url
))
1105 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_CONTAINS_LOGIN
);
1108 void PrerenderLocalPredictor::OnGetInitialVisitHistory(
1109 scoped_ptr
<vector
<history::BriefVisitInfo
> > visit_history
) {
1110 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1111 DCHECK(!visit_history_
.get());
1112 RecordEvent(EVENT_INIT_SUCCEEDED
);
1113 // Since the visit history has descending timestamps, we must reverse it.
1114 visit_history_
.reset(new vector
<history::BriefVisitInfo
>(
1115 visit_history
->rbegin(), visit_history
->rend()));
1118 HistoryService
* PrerenderLocalPredictor::GetHistoryIfExists() const {
1119 Profile
* profile
= prerender_manager_
->profile();
1122 return HistoryServiceFactory::GetForProfileWithoutCreating(profile
);
1125 void PrerenderLocalPredictor::Init() {
1126 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1127 RecordEvent(EVENT_INIT_STARTED
);
1128 Profile
* profile
= prerender_manager_
->profile();
1129 if (!profile
|| DisableLocalPredictorBasedOnSyncAndConfiguration(profile
)) {
1130 RecordEvent(EVENT_INIT_FAILED_UNENCRYPTED_SYNC_NOT_ENABLED
);
1133 HistoryService
* history
= GetHistoryIfExists();
1135 CHECK(!is_visit_database_observer_
);
1136 history
->ScheduleDBTask(
1137 scoped_ptr
<history::HistoryDBTask
>(
1138 new GetVisitHistoryTask(this, kMaxVisitHistory
)),
1139 &history_db_tracker_
);
1140 history
->AddVisitDatabaseObserver(this);
1141 is_visit_database_observer_
= true;
1143 RecordEvent(EVENT_INIT_FAILED_NO_HISTORY
);
1147 void PrerenderLocalPredictor::OnPLTEventForURL(const GURL
& url
,
1148 base::TimeDelta page_load_time
) {
1149 if (prefetch_list_
->MarkPLTSeen(url
, page_load_time
)) {
1150 UMA_HISTOGRAM_CUSTOM_TIMES("Prerender.LocalPredictorPrefetchMatchPLT",
1152 base::TimeDelta::FromMilliseconds(10),
1153 base::TimeDelta::FromSeconds(60),
1157 scoped_ptr
<PrerenderProperties
> prerender
;
1158 if (DoesPrerenderMatchPLTRecord(last_swapped_in_prerender_
.get(),
1159 url
, page_load_time
)) {
1160 prerender
.reset(last_swapped_in_prerender_
.release());
1162 if (DoesPrerenderMatchPLTRecord(current_prerender_
.get(),
1163 url
, page_load_time
)) {
1164 prerender
.reset(current_prerender_
.release());
1166 if (!prerender
.get())
1168 if (IsPrerenderStillValid(prerender
.get())) {
1169 UMA_HISTOGRAM_CUSTOM_TIMES("Prerender.SimulatedLocalBrowsingBaselinePLT",
1171 base::TimeDelta::FromMilliseconds(10),
1172 base::TimeDelta::FromSeconds(60),
1175 base::TimeDelta prerender_age
= GetCurrentTime() - prerender
->start_time
;
1176 if (prerender_age
> page_load_time
) {
1177 base::TimeDelta new_plt
;
1178 if (prerender_age
< 2 * page_load_time
)
1179 new_plt
= 2 * page_load_time
- prerender_age
;
1180 UMA_HISTOGRAM_CUSTOM_TIMES("Prerender.SimulatedLocalBrowsingPLT",
1182 base::TimeDelta::FromMilliseconds(10),
1183 base::TimeDelta::FromSeconds(60),
1189 bool PrerenderLocalPredictor::IsPrerenderStillValid(
1190 PrerenderLocalPredictor::PrerenderProperties
* prerender
) const {
1191 return (prerender
&&
1192 (prerender
->start_time
+
1193 base::TimeDelta::FromMilliseconds(GetMaxLocalPredictionTimeMs()))
1194 > GetCurrentTime());
1197 void PrerenderLocalPredictor::RecordEvent(
1198 PrerenderLocalPredictor::Event event
) const {
1199 UMA_HISTOGRAM_ENUMERATION("Prerender.LocalPredictorEvent",
1200 event
, PrerenderLocalPredictor::EVENT_MAX_VALUE
);
1203 bool PrerenderLocalPredictor::DoesPrerenderMatchPLTRecord(
1204 PrerenderProperties
* prerender
,
1206 base::TimeDelta plt
) const {
1207 if (prerender
&& prerender
->start_time
< GetCurrentTime() - plt
) {
1208 if (prerender
->url
.is_empty())
1209 RecordEvent(EVENT_ERROR_NO_PRERENDER_URL_FOR_PLT
);
1210 return (prerender
->url
== url
);
1216 PrerenderLocalPredictor::PrerenderProperties
*
1217 PrerenderLocalPredictor::GetIssuedPrerenderSlotForPriority(const GURL
& url
,
1219 int num_prerenders
= GetLocalPredictorMaxConcurrentPrerenders();
1220 while (static_cast<int>(issued_prerenders_
.size()) < num_prerenders
)
1221 issued_prerenders_
.push_back(new PrerenderProperties());
1222 // First, check if we already have a prerender for the same URL issued.
1223 // If yes, we don't want to prerender this URL again, so we return NULL
1224 // (on matching slot found).
1225 for (int i
= 0; i
< static_cast<int>(issued_prerenders_
.size()); i
++) {
1226 PrerenderProperties
* p
= issued_prerenders_
[i
];
1228 if (p
->prerender_handle
&& p
->prerender_handle
->IsPrerendering() &&
1229 p
->prerender_handle
->Matches(url
, NULL
)) {
1233 // Otherwise, let's see if there are any empty slots. If yes, return the first
1234 // one we find. Otherwise, if the lowest priority prerender has a lower
1235 // priority than the page we want to prerender, use its slot.
1236 PrerenderProperties
* lowest_priority_prerender
= NULL
;
1237 for (int i
= 0; i
< static_cast<int>(issued_prerenders_
.size()); i
++) {
1238 PrerenderProperties
* p
= issued_prerenders_
[i
];
1240 if (!p
->prerender_handle
|| !p
->prerender_handle
->IsPrerendering())
1242 double decayed_priority
= p
->GetCurrentDecayedPriority();
1243 if (decayed_priority
> priority
)
1245 if (lowest_priority_prerender
== NULL
||
1246 lowest_priority_prerender
->GetCurrentDecayedPriority() >
1248 lowest_priority_prerender
= p
;
1251 return lowest_priority_prerender
;
1254 void PrerenderLocalPredictor::ContinuePrerenderCheck(
1255 scoped_ptr
<CandidatePrerenderInfo
> info
) {
1256 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1257 TIMING_HISTOGRAM("Prerender.LocalPredictorLoggedInLookupTime",
1258 base::Time::Now() - info
->start_time_
);
1259 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_STARTED
);
1260 if (info
->candidate_urls_
.size() == 0) {
1261 RecordEvent(EVENT_NO_PRERENDER_CANDIDATES
);
1264 scoped_ptr
<LocalPredictorURLInfo
> url_info
;
1265 #if defined(FULL_SAFE_BROWSING)
1266 scoped_refptr
<SafeBrowsingDatabaseManager
> sb_db_manager
=
1267 g_browser_process
->safe_browsing_service()->database_manager();
1270 for (int i
= 0; i
< static_cast<int>(info
->candidate_urls_
.size()); i
++) {
1271 if (num_issued
>= GetLocalPredictorMaxLaunchPrerenders())
1273 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_EXAMINE_NEXT_URL
);
1274 url_info
.reset(new LocalPredictorURLInfo(info
->candidate_urls_
[i
]));
1275 if (url_info
->local_history_based
) {
1276 if (SkipLocalPredictorLocalCandidates()) {
1277 url_info
.reset(NULL
);
1280 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_EXAMINE_NEXT_URL_LOCAL
);
1282 if (!url_info
->local_history_based
) {
1283 if (SkipLocalPredictorServiceCandidates()) {
1284 url_info
.reset(NULL
);
1287 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_EXAMINE_NEXT_URL_SERVICE
);
1290 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_EXAMINE_NEXT_URL_NOT_SKIPPED
);
1292 // We need to check whether we can issue a prerender for this URL.
1293 // We test a set of conditions. Each condition can either rule out
1294 // a prerender (in which case we reset url_info, so that it will not
1295 // be prerendered, and we continue, which means try the next candidate
1296 // URL), or it can be sufficient to issue the prerender without any
1297 // further checks (in which case we just break).
1298 // The order of the checks is critical, because it prescribes the logic
1299 // we use here to decide what to prerender.
1300 if (!url_info
->url_lookup_success
) {
1301 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_NO_URL
);
1302 url_info
.reset(NULL
);
1305 if (!SkipLocalPredictorFragment() &&
1306 URLsIdenticalIgnoringFragments(info
->source_url_
.url
,
1308 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_URLS_IDENTICAL_BUT_FRAGMENT
);
1309 url_info
.reset(NULL
);
1312 if (!SkipLocalPredictorHTTPS() && url_info
->url
.SchemeIs("https")) {
1313 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_HTTPS
);
1314 url_info
.reset(NULL
);
1317 if (IsRootPageURL(url_info
->url
)) {
1318 // For root pages, we assume that they are reasonably safe, and we
1319 // will just prerender them without any additional checks.
1320 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_ROOT_PAGE
);
1321 IssuePrerender(info
.get(), url_info
.get());
1325 if (IsLogOutURL(url_info
->url
)) {
1326 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_LOGOUT_URL
);
1327 url_info
.reset(NULL
);
1330 if (IsLogInURL(url_info
->url
)) {
1331 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_LOGIN_URL
);
1332 url_info
.reset(NULL
);
1335 #if defined(FULL_SAFE_BROWSING)
1336 if (!SkipLocalPredictorWhitelist() && sb_db_manager
.get() &&
1337 sb_db_manager
->CheckSideEffectFreeWhitelistUrl(url_info
->url
)) {
1338 // If a page is on the side-effect free whitelist, we will just prerender
1339 // it without any additional checks.
1340 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_ON_SIDE_EFFECT_FREE_WHITELIST
);
1341 IssuePrerender(info
.get(), url_info
.get());
1346 if (!SkipLocalPredictorServiceWhitelist() &&
1347 url_info
->service_whitelist
&& url_info
->service_whitelist_lookup_ok
) {
1348 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_ON_SERVICE_WHITELIST
);
1349 IssuePrerender(info
.get(), url_info
.get());
1353 if (!SkipLocalPredictorLoggedIn() &&
1354 !url_info
->logged_in
&& url_info
->logged_in_lookup_ok
) {
1355 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_NOT_LOGGED_IN
);
1356 IssuePrerender(info
.get(), url_info
.get());
1360 if (!SkipLocalPredictorDefaultNoPrerender()) {
1361 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_FALLTHROUGH_NOT_PRERENDERING
);
1362 url_info
.reset(NULL
);
1364 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_FALLTHROUGH_PRERENDERING
);
1365 IssuePrerender(info
.get(), url_info
.get());
1372 void PrerenderLocalPredictor::IssuePrerender(
1373 CandidatePrerenderInfo
* info
,
1374 LocalPredictorURLInfo
* url_info
) {
1375 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1376 RecordEvent(EVENT_ISSUE_PRERENDER_CALLED
);
1377 if (prefetch_list_
->AddURL(url_info
->url
)) {
1378 RecordEvent(EVENT_PREFETCH_LIST_ADDED
);
1379 // If we are prefetching rather than prerendering, now is the time to launch
1381 if (IsLocalPredictorPrerenderPrefetchEnabled()) {
1382 RecordEvent(EVENT_ISSUE_PRERENDER_PREFETCH_ENABLED
);
1383 // Obtain the render frame host that caused this prefetch.
1384 RenderFrameHost
* rfh
= RenderFrameHost::FromID(info
->render_process_id_
,
1385 info
->render_frame_id_
);
1386 // If it is still alive, launch the prefresh.
1388 rfh
->Send(new PrefetchMsg_Prefetch(rfh
->GetRoutingID(), url_info
->url
));
1389 RecordEvent(EVENT_ISSUE_PRERENDER_PREFETCH_ISSUED
);
1393 PrerenderProperties
* prerender_properties
=
1394 GetIssuedPrerenderSlotForPriority(url_info
->url
, url_info
->priority
);
1395 if (!prerender_properties
) {
1396 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_PRIORITY_TOO_LOW
);
1399 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_ISSUING_PRERENDER
);
1400 DCHECK(prerender_properties
!= NULL
);
1401 DCHECK(info
!= NULL
);
1402 DCHECK(url_info
!= NULL
);
1403 if (!IsLocalPredictorPrerenderLaunchEnabled())
1405 URLID url_id
= url_info
->id
;
1406 const GURL
& url
= url_info
->url
;
1407 double priority
= url_info
->priority
;
1408 base::Time current_time
= GetCurrentTime();
1409 RecordEvent(EVENT_ISSUING_PRERENDER
);
1411 // Issue the prerender and obtain a new handle.
1412 scoped_ptr
<prerender::PrerenderHandle
> new_prerender_handle(
1413 prerender_manager_
->AddPrerenderFromLocalPredictor(
1414 url
, info
->session_storage_namespace_
.get(), *(info
->size_
)));
1416 // Check if this is a duplicate of an existing prerender. If yes, clean up
1418 for (int i
= 0; i
< static_cast<int>(issued_prerenders_
.size()); i
++) {
1419 PrerenderProperties
* p
= issued_prerenders_
[i
];
1421 if (new_prerender_handle
&&
1422 new_prerender_handle
->RepresentingSamePrerenderAs(
1423 p
->prerender_handle
.get())) {
1424 new_prerender_handle
->OnCancel();
1425 new_prerender_handle
.reset(NULL
);
1426 RecordEvent(EVENT_ISSUE_PRERENDER_ALREADY_PRERENDERING
);
1431 if (new_prerender_handle
.get()) {
1432 RecordEvent(EVENT_ISSUE_PRERENDER_NEW_PRERENDER
);
1433 // The new prerender does not match any existing prerenders. Update
1434 // prerender_properties so that it reflects the new entry.
1435 prerender_properties
->url_id
= url_id
;
1436 prerender_properties
->url
= url
;
1437 prerender_properties
->priority
= priority
;
1438 prerender_properties
->start_time
= current_time
;
1439 prerender_properties
->actual_start_time
= current_time
;
1440 prerender_properties
->would_have_matched
= false;
1441 prerender_properties
->prerender_handle
.swap(new_prerender_handle
);
1442 // new_prerender_handle now represents the old previou prerender that we
1443 // are replacing. So we need to cancel it.
1444 if (new_prerender_handle
) {
1445 new_prerender_handle
->OnCancel();
1446 RecordEvent(EVENT_ISSUE_PRERENDER_CANCELLED_OLD_PRERENDER
);
1450 RecordEvent(EVENT_ADD_VISIT_PRERENDERING
);
1451 if (current_prerender_
.get() && current_prerender_
->url_id
== url_id
) {
1452 RecordEvent(EVENT_ADD_VISIT_PRERENDERING_EXTENDED
);
1453 if (priority
> current_prerender_
->priority
)
1454 current_prerender_
->priority
= priority
;
1455 // If the prerender already existed, we want to extend it. However,
1456 // we do not want to set its start_time to the current time to
1457 // disadvantage PLT computations when the prerender is swapped in.
1458 // So we set the new start time to current_time - 10s (since the vast
1459 // majority of PLTs are < 10s), provided that is not before the actual
1460 // time the prerender was started (so as to not artificially advantage
1461 // the PLT computation).
1462 base::Time simulated_new_start_time
=
1463 current_time
- base::TimeDelta::FromSeconds(10);
1464 if (simulated_new_start_time
> current_prerender_
->start_time
)
1465 current_prerender_
->start_time
= simulated_new_start_time
;
1467 current_prerender_
.reset(
1468 new PrerenderProperties(url_id
, url
, priority
, current_time
));
1470 current_prerender_
->actual_start_time
= current_time
;
1473 void PrerenderLocalPredictor::OnTabHelperURLSeen(
1474 const GURL
& url
, WebContents
* web_contents
) {
1475 RecordEvent(EVENT_TAB_HELPER_URL_SEEN
);
1477 if (prefetch_list_
->MarkURLSeen(url
, PrefetchList::SEEN_TABCONTENTS_OBSERVER
))
1478 RecordEvent(EVENT_PREFETCH_LIST_SEEN_TABCONTENTS
);
1479 bool browser_navigate_initiated
= false;
1480 const content::NavigationEntry
* entry
=
1481 web_contents
->GetController().GetPendingEntry();
1483 base::string16 result
;
1484 browser_navigate_initiated
=
1485 entry
->GetExtraData(kChromeNavigateExtraDataKey
, &result
);
1488 // If the namespace matches and the URL matches, we might be able to swap
1489 // in. However, the actual code initating the swapin is in the renderer
1490 // and is checking for other criteria (such as POSTs). There may
1491 // also be conditions when a swapin should happen but does not. By recording
1492 // the two previous events, we can keep an eye on the magnitude of the
1495 PrerenderProperties
* best_matched_prerender
= NULL
;
1496 bool session_storage_namespace_matches
= false;
1497 SessionStorageNamespace
* tab_session_storage_namespace
=
1498 web_contents
->GetController().GetDefaultSessionStorageNamespace();
1499 for (int i
= 0; i
< static_cast<int>(issued_prerenders_
.size()); i
++) {
1500 PrerenderProperties
* p
= issued_prerenders_
[i
];
1502 if (!p
->prerender_handle
.get() ||
1503 !p
->prerender_handle
->Matches(url
, NULL
) ||
1504 p
->would_have_matched
) {
1507 if (!best_matched_prerender
|| !session_storage_namespace_matches
) {
1508 best_matched_prerender
= p
;
1509 session_storage_namespace_matches
=
1510 p
->prerender_handle
->Matches(url
, tab_session_storage_namespace
);
1513 if (best_matched_prerender
) {
1514 RecordEvent(EVENT_TAB_HELPER_URL_SEEN_MATCH
);
1516 RecordEvent(EVENT_TAB_HELPER_URL_SEEN_MATCH_ENTRY
);
1517 if (browser_navigate_initiated
)
1518 RecordEvent(EVENT_TAB_HELPER_URL_SEEN_MATCH_BROWSER_NAVIGATE
);
1519 best_matched_prerender
->would_have_matched
= true;
1520 if (session_storage_namespace_matches
) {
1521 RecordEvent(EVENT_TAB_HELPER_URL_SEEN_NAMESPACE_MATCH
);
1523 RecordEvent(EVENT_TAB_HELPER_URL_SEEN_NAMESPACE_MATCH_ENTRY
);
1524 if (browser_navigate_initiated
)
1525 RecordEvent(EVENT_TAB_HELPER_URL_SEEN_NAMESPACE_MATCH_BROWSER_NAVIGATE
);
1527 SessionStorageNamespace
* prerender_session_storage_namespace
=
1528 best_matched_prerender
->prerender_handle
->
1529 GetSessionStorageNamespace();
1530 if (!prerender_session_storage_namespace
) {
1531 RecordEvent(EVENT_TAB_HELPER_URL_SEEN_NAMESPACE_MISMATCH_NO_NAMESPACE
);
1533 RecordEvent(EVENT_TAB_HELPER_URL_SEEN_NAMESPACE_MISMATCH_MERGE_ISSUED
);
1534 prerender_session_storage_namespace
->Merge(
1536 best_matched_prerender
->prerender_handle
->GetChildId(),
1537 tab_session_storage_namespace
,
1538 base::Bind(&PrerenderLocalPredictor::ProcessNamespaceMergeResult
,
1539 weak_factory_
.GetWeakPtr()));
1545 void PrerenderLocalPredictor::ProcessNamespaceMergeResult(
1546 content::SessionStorageNamespace::MergeResult result
) {
1547 RecordEvent(EVENT_NAMESPACE_MISMATCH_MERGE_RESULT_RECEIVED
);
1549 case content::SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_FOUND
:
1550 RecordEvent(EVENT_NAMESPACE_MISMATCH_MERGE_RESULT_NAMESPACE_NOT_FOUND
);
1552 case content::SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_ALIAS
:
1553 RecordEvent(EVENT_NAMESPACE_MISMATCH_MERGE_RESULT_NAMESPACE_NOT_ALIAS
);
1555 case content::SessionStorageNamespace::MERGE_RESULT_NOT_LOGGING
:
1556 RecordEvent(EVENT_NAMESPACE_MISMATCH_MERGE_RESULT_NOT_LOGGING
);
1558 case content::SessionStorageNamespace::MERGE_RESULT_NO_TRANSACTIONS
:
1559 RecordEvent(EVENT_NAMESPACE_MISMATCH_MERGE_RESULT_NO_TRANSACTIONS
);
1561 case content::SessionStorageNamespace::MERGE_RESULT_TOO_MANY_TRANSACTIONS
:
1562 RecordEvent(EVENT_NAMESPACE_MISMATCH_MERGE_RESULT_TOO_MANY_TRANSACTIONS
);
1564 case content::SessionStorageNamespace::MERGE_RESULT_NOT_MERGEABLE
:
1565 RecordEvent(EVENT_NAMESPACE_MISMATCH_MERGE_RESULT_NOT_MERGEABLE
);
1567 case content::SessionStorageNamespace::MERGE_RESULT_MERGEABLE
:
1568 RecordEvent(EVENT_NAMESPACE_MISMATCH_MERGE_RESULT_MERGEABLE
);
1575 } // namespace prerender