blob: 93525f626d6569766110d2e2ab183c361306e3e8 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/prerender/prerender_local_predictor.h"
#include <ctype.h>
#include <algorithm>
#include <map>
#include <set>
#include <string>
#include <utility>
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram.h"
#include "base/timer/timer.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/history/history_database.h"
#include "chrome/browser/history/history_db_task.h"
#include "chrome/browser/history/history_service.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/prerender/prerender_field_trial.h"
#include "chrome/browser/prerender/prerender_handle.h"
#include "chrome/browser/prerender/prerender_histograms.h"
#include "chrome/browser/prerender/prerender_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/safe_browsing/database_manager.h"
#include "chrome/browser/safe_browsing/safe_browsing_service.h"
#include "chrome/browser/ui/tab_contents/tab_contents_iterator.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/session_storage_namespace.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_view.h"
#include "content/public/common/page_transition_types.h"
#include "crypto/secure_hash.h"
#include "grit/browser_resources.h"
#include "ui/base/resource/resource_bundle.h"
#include "url/url_canon.h"
using content::BrowserThread;
using content::PageTransition;
using content::SessionStorageNamespace;
using content::WebContents;
using history::URLID;
using predictors::LoggedInPredictorTable;
using std::string;
using std::vector;
namespace prerender {
namespace {
static const size_t kURLHashSize = 5;
static const int kNumPrerenderCandidates = 5;
} // namespace
// When considering a candidate URL to be prerendered, we need to collect the
// data in this struct to make the determination whether we should issue the
// prerender or not.
struct PrerenderLocalPredictor::LocalPredictorURLInfo {
URLID id;
GURL url;
bool url_lookup_success;
bool logged_in;
bool logged_in_lookup_ok;
double priority;
};
// A struct consisting of everything needed for launching a potential prerender
// on a navigation: The navigation URL (source) triggering potential prerenders,
// and a set of candidate URLs.
struct PrerenderLocalPredictor::LocalPredictorURLLookupInfo {
LocalPredictorURLInfo source_url_;
vector<LocalPredictorURLInfo> candidate_urls_;
explicit LocalPredictorURLLookupInfo(URLID source_id) {
source_url_.id = source_id;
}
void MaybeAddCandidateURL(URLID id, double priority) {
// TODO(tburkard): clean up this code, potentially using a list or a heap
LocalPredictorURLInfo info;
info.id = id;
info.priority = priority;
int insert_pos = candidate_urls_.size();
if (insert_pos < kNumPrerenderCandidates)
candidate_urls_.push_back(info);
while (insert_pos > 0 &&
candidate_urls_[insert_pos - 1].priority < info.priority) {
if (insert_pos < kNumPrerenderCandidates)
candidate_urls_[insert_pos] = candidate_urls_[insert_pos - 1];
insert_pos--;
}
if (insert_pos < kNumPrerenderCandidates)
candidate_urls_[insert_pos] = info;
}
};
namespace {
// Task to lookup the URL for a given URLID.
class GetURLForURLIDTask : public history::HistoryDBTask {
public:
GetURLForURLIDTask(
PrerenderLocalPredictor::LocalPredictorURLLookupInfo* request,
const base::Closure& callback)
: request_(request),
callback_(callback),
start_time_(base::Time::Now()) {
}
virtual bool RunOnDBThread(history::HistoryBackend* backend,
history::HistoryDatabase* db) OVERRIDE {
DoURLLookup(db, &request_->source_url_);
for (int i = 0; i < static_cast<int>(request_->candidate_urls_.size()); i++)
DoURLLookup(db, &request_->candidate_urls_[i]);
return true;
}
virtual void DoneRunOnMainThread() OVERRIDE {
callback_.Run();
UMA_HISTOGRAM_CUSTOM_TIMES("Prerender.LocalPredictorURLLookupTime",
base::Time::Now() - start_time_,
base::TimeDelta::FromMilliseconds(10),
base::TimeDelta::FromSeconds(10),
50);
}
private:
virtual ~GetURLForURLIDTask() {}
void DoURLLookup(history::HistoryDatabase* db,
PrerenderLocalPredictor::LocalPredictorURLInfo* request) {
history::URLRow url_row;
request->url_lookup_success = db->GetURLRow(request->id, &url_row);
if (request->url_lookup_success)
request->url = url_row.url();
}
PrerenderLocalPredictor::LocalPredictorURLLookupInfo* request_;
base::Closure callback_;
base::Time start_time_;
DISALLOW_COPY_AND_ASSIGN(GetURLForURLIDTask);
};
// Task to load history from the visit database on startup.
class GetVisitHistoryTask : public history::HistoryDBTask {
public:
GetVisitHistoryTask(PrerenderLocalPredictor* local_predictor,
int max_visits)
: local_predictor_(local_predictor),
max_visits_(max_visits),
visit_history_(new vector<history::BriefVisitInfo>) {
}
virtual bool RunOnDBThread(history::HistoryBackend* backend,
history::HistoryDatabase* db) OVERRIDE {
db->GetBriefVisitInfoOfMostRecentVisits(max_visits_, visit_history_.get());
return true;
}
virtual void DoneRunOnMainThread() OVERRIDE {
local_predictor_->OnGetInitialVisitHistory(visit_history_.Pass());
}
private:
virtual ~GetVisitHistoryTask() {}
PrerenderLocalPredictor* local_predictor_;
int max_visits_;
scoped_ptr<vector<history::BriefVisitInfo> > visit_history_;
DISALLOW_COPY_AND_ASSIGN(GetVisitHistoryTask);
};
// Maximum visit history to retrieve from the visit database.
const int kMaxVisitHistory = 100 * 1000;
// Visit history size at which to trigger pruning, and number of items to prune.
const int kVisitHistoryPruneThreshold = 120 * 1000;
const int kVisitHistoryPruneAmount = 20 * 1000;
const int kMinLocalPredictionTimeMs = 500;
int GetMaxLocalPredictionTimeMs() {
return GetLocalPredictorTTLSeconds() * 1000;
}
bool IsBackForward(PageTransition transition) {
return (transition & content::PAGE_TRANSITION_FORWARD_BACK) != 0;
}
bool IsHomePage(PageTransition transition) {
return (transition & content::PAGE_TRANSITION_HOME_PAGE) != 0;
}
bool IsIntermediateRedirect(PageTransition transition) {
return (transition & content::PAGE_TRANSITION_CHAIN_END) == 0;
}
bool ShouldExcludeTransitionForPrediction(PageTransition transition) {
return IsBackForward(transition) || IsHomePage(transition) ||
IsIntermediateRedirect(transition);
}
base::Time GetCurrentTime() {
return base::Time::Now();
}
bool StringContainsIgnoringCase(string haystack, string needle) {
std::transform(haystack.begin(), haystack.end(), haystack.begin(), ::tolower);
std::transform(needle.begin(), needle.end(), needle.begin(), ::tolower);
return haystack.find(needle) != string::npos;
}
bool IsExtendedRootURL(const GURL& url) {
const string& path = url.path();
return path == "/index.html" || path == "/home.html" ||
path == "/main.html" ||
path == "/index.htm" || path == "/home.htm" || path == "/main.htm" ||
path == "/index.php" || path == "/home.php" || path == "/main.php" ||
path == "/index.asp" || path == "/home.asp" || path == "/main.asp" ||
path == "/index.py" || path == "/home.py" || path == "/main.py" ||
path == "/index.pl" || path == "/home.pl" || path == "/main.pl";
}
bool IsRootPageURL(const GURL& url) {
return (url.path() == "/" || url.path() == "" || IsExtendedRootURL(url)) &&
(!url.has_query()) && (!url.has_ref());
}
bool IsLogInURL(const GURL& url) {
return StringContainsIgnoringCase(url.spec().c_str(), "login") ||
StringContainsIgnoringCase(url.spec().c_str(), "signin");
}
bool IsLogOutURL(const GURL& url) {
return StringContainsIgnoringCase(url.spec().c_str(), "logout") ||
StringContainsIgnoringCase(url.spec().c_str(), "signout");
}
int64 URLHashToInt64(const unsigned char* data) {
COMPILE_ASSERT(kURLHashSize < sizeof(int64), url_hash_must_fit_in_int64);
int64 value = 0;
memcpy(&value, data, kURLHashSize);
return value;
}
int64 GetInt64URLHashForURL(const GURL& url) {
COMPILE_ASSERT(kURLHashSize < sizeof(int64), url_hash_must_fit_in_int64);
scoped_ptr<crypto::SecureHash> hash(
crypto::SecureHash::Create(crypto::SecureHash::SHA256));
int64 hash_value = 0;
const char* url_string = url.spec().c_str();
hash->Update(url_string, strlen(url_string));
hash->Finish(&hash_value, kURLHashSize);
return hash_value;
}
bool URLsIdenticalIgnoringFragments(const GURL& url1, const GURL& url2) {
url_canon::Replacements<char> replacement;
replacement.ClearRef();
GURL u1 = url1.ReplaceComponents(replacement);
GURL u2 = url2.ReplaceComponents(replacement);
return (u1 == u2);
}
void LookupLoggedInStatesOnDBThread(
scoped_refptr<LoggedInPredictorTable> logged_in_predictor_table,
PrerenderLocalPredictor::LocalPredictorURLLookupInfo* request_) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
for (int i = 0; i < static_cast<int>(request_->candidate_urls_.size()); i++) {
PrerenderLocalPredictor::LocalPredictorURLInfo* info =
&request_->candidate_urls_[i];
if (info->url_lookup_success) {
logged_in_predictor_table->HasUserLoggedIn(
info->url, &info->logged_in, &info->logged_in_lookup_ok);
} else {
info->logged_in_lookup_ok = false;
}
}
}
} // namespace
struct PrerenderLocalPredictor::PrerenderProperties {
PrerenderProperties(URLID url_id, const GURL& url, double priority,
base::Time start_time)
: url_id(url_id),
url(url),
priority(priority),
start_time(start_time) {
}
// Default constructor for dummy element
PrerenderProperties()
: priority(0.0) {
}
double GetCurrentDecayedPriority() {
// If we are no longer prerendering, the priority is 0.
if (!prerender_handle || !prerender_handle->IsPrerendering())
return 0.0;
int half_life_time_seconds =
GetLocalPredictorPrerenderPriorityHalfLifeTimeSeconds();
if (half_life_time_seconds < 1)
return priority;
double multiple_elapsed =
(GetCurrentTime() - actual_start_time).InMillisecondsF() /
base::TimeDelta::FromSeconds(half_life_time_seconds).InMillisecondsF();
// Decay factor: 2 ^ (-multiple_elapsed)
double decay_factor = exp(- multiple_elapsed * log(2.0));
return priority * decay_factor;
}
URLID url_id;
GURL url;
double priority;
// For expiration purposes, this is a synthetic start time consisting either
// of the actual start time, or of the last time the page was re-requested
// for prerendering - 10 seconds (unless the original request came after
// that). This is to emulate the effect of re-prerendering a page that is
// about to expire, because it was re-requested for prerendering a second
// time after the actual prerender being kept around.
base::Time start_time;
// The actual time this page was last requested for prerendering.
base::Time actual_start_time;
scoped_ptr<PrerenderHandle> prerender_handle;
// Indicates whether this prerender would have matched a URL navigated to,
// but was not swapped in for some reason.
bool would_have_matched;
};
PrerenderLocalPredictor::PrerenderLocalPredictor(
PrerenderManager* prerender_manager)
: prerender_manager_(prerender_manager),
is_visit_database_observer_(false),
weak_factory_(this) {
RecordEvent(EVENT_CONSTRUCTED);
if (base::MessageLoop::current()) {
timer_.Start(FROM_HERE,
base::TimeDelta::FromMilliseconds(kInitDelayMs),
this,
&PrerenderLocalPredictor::Init);
RecordEvent(EVENT_INIT_SCHEDULED);
}
static const size_t kChecksumHashSize = 32;
base::RefCountedStaticMemory* url_whitelist_data =
ResourceBundle::GetSharedInstance().LoadDataResourceBytes(
IDR_PRERENDER_URL_WHITELIST);
size_t size = url_whitelist_data->size();
const unsigned char* front = url_whitelist_data->front();
if (size < kChecksumHashSize ||
(size - kChecksumHashSize) % kURLHashSize != 0) {
RecordEvent(EVENT_URL_WHITELIST_ERROR);
return;
}
scoped_ptr<crypto::SecureHash> hash(
crypto::SecureHash::Create(crypto::SecureHash::SHA256));
hash->Update(front + kChecksumHashSize, size - kChecksumHashSize);
char hash_value[kChecksumHashSize];
hash->Finish(hash_value, kChecksumHashSize);
if (memcmp(hash_value, front, kChecksumHashSize)) {
RecordEvent(EVENT_URL_WHITELIST_ERROR);
return;
}
for (const unsigned char* p = front + kChecksumHashSize;
p < front + size;
p += kURLHashSize) {
url_whitelist_.insert(URLHashToInt64(p));
}
RecordEvent(EVENT_URL_WHITELIST_OK);
}
PrerenderLocalPredictor::~PrerenderLocalPredictor() {
Shutdown();
for (int i = 0; i < static_cast<int>(issued_prerenders_.size()); i++) {
PrerenderProperties* p = issued_prerenders_[i];
DCHECK(p != NULL);
if (p->prerender_handle)
p->prerender_handle->OnCancel();
}
}
void PrerenderLocalPredictor::Shutdown() {
timer_.Stop();
if (is_visit_database_observer_) {
HistoryService* history = GetHistoryIfExists();
CHECK(history);
history->RemoveVisitDatabaseObserver(this);
is_visit_database_observer_ = false;
}
}
void PrerenderLocalPredictor::OnAddVisit(const history::BriefVisitInfo& info) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
RecordEvent(EVENT_ADD_VISIT);
if (!visit_history_.get())
return;
visit_history_->push_back(info);
if (static_cast<int>(visit_history_->size()) > kVisitHistoryPruneThreshold) {
visit_history_->erase(visit_history_->begin(),
visit_history_->begin() + kVisitHistoryPruneAmount);
}
RecordEvent(EVENT_ADD_VISIT_INITIALIZED);
if (current_prerender_.get() &&
current_prerender_->url_id == info.url_id &&
IsPrerenderStillValid(current_prerender_.get())) {
UMA_HISTOGRAM_CUSTOM_TIMES(
"Prerender.LocalPredictorTimeUntilUsed",
GetCurrentTime() - current_prerender_->actual_start_time,
base::TimeDelta::FromMilliseconds(10),
base::TimeDelta::FromMilliseconds(GetMaxLocalPredictionTimeMs()),
50);
last_swapped_in_prerender_.reset(current_prerender_.release());
RecordEvent(EVENT_ADD_VISIT_PRERENDER_IDENTIFIED);
}
if (ShouldExcludeTransitionForPrediction(info.transition))
return;
RecordEvent(EVENT_ADD_VISIT_RELEVANT_TRANSITION);
base::TimeDelta max_age =
base::TimeDelta::FromMilliseconds(GetMaxLocalPredictionTimeMs());
base::TimeDelta min_age =
base::TimeDelta::FromMilliseconds(kMinLocalPredictionTimeMs);
std::set<URLID> next_urls_currently_found;
std::map<URLID, int> next_urls_num_found;
int num_occurrences_of_current_visit = 0;
base::Time last_visited;
scoped_ptr<LocalPredictorURLLookupInfo> lookup_info(
new LocalPredictorURLLookupInfo(info.url_id));
const vector<history::BriefVisitInfo>& visits = *(visit_history_.get());
for (int i = 0; i < static_cast<int>(visits.size()); i++) {
if (!ShouldExcludeTransitionForPrediction(visits[i].transition)) {
if (visits[i].url_id == info.url_id) {
last_visited = visits[i].time;
num_occurrences_of_current_visit++;
next_urls_currently_found.clear();
continue;
}
if (!last_visited.is_null() &&
last_visited > visits[i].time - max_age &&
last_visited < visits[i].time - min_age) {
next_urls_currently_found.insert(visits[i].url_id);
}
}
if (i == static_cast<int>(visits.size()) - 1 ||
visits[i+1].url_id == info.url_id) {
for (std::set<URLID>::iterator it = next_urls_currently_found.begin();
it != next_urls_currently_found.end();
++it) {
std::pair<std::map<URLID, int>::iterator, bool> insert_ret =
next_urls_num_found.insert(std::pair<URLID, int>(*it, 0));
std::map<URLID, int>::iterator num_found_it = insert_ret.first;
num_found_it->second++;
}
}
}
for (std::map<URLID, int>::const_iterator it = next_urls_num_found.begin();
it != next_urls_num_found.end();
++it) {
// Only consider a candidate next page for prerendering if it was viewed
// at least twice, and at least 10% of the time.
if (num_occurrences_of_current_visit > 0 &&
it->second > 1 &&
it->second * 10 >= num_occurrences_of_current_visit) {
RecordEvent(EVENT_ADD_VISIT_IDENTIFIED_PRERENDER_CANDIDATE);
double priority = static_cast<double>(it->second) /
static_cast<double>(num_occurrences_of_current_visit);
lookup_info->MaybeAddCandidateURL(it->first, priority);
}
}
if (lookup_info->candidate_urls_.size() == 0) {
RecordEvent(EVENT_NO_PRERENDER_CANDIDATES);
return;
}
RecordEvent(EVENT_START_URL_LOOKUP);
HistoryService* history = GetHistoryIfExists();
if (history) {
RecordEvent(EVENT_GOT_HISTORY_ISSUING_LOOKUP);
LocalPredictorURLLookupInfo* lookup_info_ptr = lookup_info.get();
history->ScheduleDBTask(
new GetURLForURLIDTask(
lookup_info_ptr,
base::Bind(&PrerenderLocalPredictor::OnLookupURL,
base::Unretained(this),
base::Passed(&lookup_info))),
&history_db_consumer_);
}
}
void PrerenderLocalPredictor::OnLookupURL(
scoped_ptr<LocalPredictorURLLookupInfo> info) {
RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT);
DCHECK_GE(static_cast<int>(info->candidate_urls_.size()), 1);
if (!info->source_url_.url_lookup_success) {
RecordEvent(EVENT_PRERENDER_URL_LOOKUP_FAILED);
return;
}
LogCandidateURLStats(info->candidate_urls_[0].url);
WebContents* source_web_contents = NULL;
bool multiple_source_web_contents_candidates = false;
#if !defined(OS_ANDROID)
// We need to figure out what tab launched the prerender. We do this by
// comparing URLs. This may not always work: the URL may occur in two
// tabs, and we pick the wrong one, or the tab we should have picked
// may have navigated elsewhere. Hopefully, this doesn't happen too often,
// so we ignore these cases for now.
// TODO(tburkard): Reconsider this, potentially measure it, and fix this
// in the future.
for (TabContentsIterator it; !it.done(); it.Next()) {
if (it->GetURL() == info->source_url_.url) {
if (!source_web_contents)
source_web_contents = *it;
else
multiple_source_web_contents_candidates = true;
}
}
#endif
if (!source_web_contents) {
RecordEvent(EVENT_PRERENDER_URL_LOOKUP_NO_SOURCE_WEBCONTENTS_FOUND);
return;
}
if (multiple_source_web_contents_candidates)
RecordEvent(EVENT_PRERENDER_URL_LOOKUP_MULTIPLE_SOURCE_WEBCONTENTS_FOUND);
scoped_refptr<SessionStorageNamespace> session_storage_namespace =
source_web_contents->GetController().GetDefaultSessionStorageNamespace();
gfx::Rect container_bounds;
source_web_contents->GetView()->GetContainerBounds(&container_bounds);
scoped_ptr<gfx::Size> size(new gfx::Size(container_bounds.size()));
scoped_refptr<LoggedInPredictorTable> logged_in_table =
prerender_manager_->logged_in_predictor_table();
if (!logged_in_table.get()) {
RecordEvent(EVENT_PRERENDER_URL_LOOKUP_NO_LOGGED_IN_TABLE_FOUND);
return;
}
RecordEvent(EVENT_PRERENDER_URL_LOOKUP_ISSUING_LOGGED_IN_LOOKUP);
LocalPredictorURLLookupInfo* info_ptr = info.get();
BrowserThread::PostTaskAndReply(
BrowserThread::DB, FROM_HERE,
base::Bind(&LookupLoggedInStatesOnDBThread,
logged_in_table,
info_ptr),
base::Bind(&PrerenderLocalPredictor::ContinuePrerenderCheck,
weak_factory_.GetWeakPtr(),
session_storage_namespace,
base::Passed(&size),
base::Passed(&info)));
}
void PrerenderLocalPredictor::LogCandidateURLStats(const GURL& url) const {
if (url_whitelist_.count(GetInt64URLHashForURL(url)) > 0) {
RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_ON_WHITELIST);
if (IsRootPageURL(url))
RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_ON_WHITELIST_ROOT_PAGE);
}
if (IsRootPageURL(url))
RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_ROOT_PAGE);
if (IsExtendedRootURL(url))
RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_EXTENDED_ROOT_PAGE);
if (IsRootPageURL(url) && url.SchemeIs("http"))
RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_ROOT_PAGE_HTTP);
if (url.SchemeIs("http"))
RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_IS_HTTP);
if (url.has_query())
RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_HAS_QUERY_STRING);
if (IsLogOutURL(url))
RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_CONTAINS_LOGOUT);
if (IsLogInURL(url))
RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_CONTAINS_LOGIN);
}
void PrerenderLocalPredictor::OnGetInitialVisitHistory(
scoped_ptr<vector<history::BriefVisitInfo> > visit_history) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(!visit_history_.get());
RecordEvent(EVENT_INIT_SUCCEEDED);
// Since the visit history has descending timestamps, we must reverse it.
visit_history_.reset(new vector<history::BriefVisitInfo>(
visit_history->rbegin(), visit_history->rend()));
}
HistoryService* PrerenderLocalPredictor::GetHistoryIfExists() const {
Profile* profile = prerender_manager_->profile();
if (!profile)
return NULL;
return HistoryServiceFactory::GetForProfileWithoutCreating(profile);
}
void PrerenderLocalPredictor::Init() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
RecordEvent(EVENT_INIT_STARTED);
HistoryService* history = GetHistoryIfExists();
if (history) {
CHECK(!is_visit_database_observer_);
history->ScheduleDBTask(
new GetVisitHistoryTask(this, kMaxVisitHistory),
&history_db_consumer_);
history->AddVisitDatabaseObserver(this);
is_visit_database_observer_ = true;
} else {
RecordEvent(EVENT_INIT_FAILED_NO_HISTORY);
}
}
void PrerenderLocalPredictor::OnPLTEventForURL(const GURL& url,
base::TimeDelta page_load_time) {
scoped_ptr<PrerenderProperties> prerender;
if (DoesPrerenderMatchPLTRecord(last_swapped_in_prerender_.get(),
url, page_load_time)) {
prerender.reset(last_swapped_in_prerender_.release());
}
if (DoesPrerenderMatchPLTRecord(current_prerender_.get(),
url, page_load_time)) {
prerender.reset(current_prerender_.release());
}
if (!prerender.get())
return;
if (IsPrerenderStillValid(prerender.get())) {
UMA_HISTOGRAM_CUSTOM_TIMES("Prerender.SimulatedLocalBrowsingBaselinePLT",
page_load_time,
base::TimeDelta::FromMilliseconds(10),
base::TimeDelta::FromSeconds(60),
100);
base::TimeDelta prerender_age = GetCurrentTime() - prerender->start_time;
if (prerender_age > page_load_time) {
base::TimeDelta new_plt;
if (prerender_age < 2 * page_load_time)
new_plt = 2 * page_load_time - prerender_age;
UMA_HISTOGRAM_CUSTOM_TIMES("Prerender.SimulatedLocalBrowsingPLT",
new_plt,
base::TimeDelta::FromMilliseconds(10),
base::TimeDelta::FromSeconds(60),
100);
}
}
}
bool PrerenderLocalPredictor::IsPrerenderStillValid(
PrerenderLocalPredictor::PrerenderProperties* prerender) const {
return (prerender &&
(prerender->start_time +
base::TimeDelta::FromMilliseconds(GetMaxLocalPredictionTimeMs()))
> GetCurrentTime());
}
void PrerenderLocalPredictor::RecordEvent(
PrerenderLocalPredictor::Event event) const {
UMA_HISTOGRAM_ENUMERATION("Prerender.LocalPredictorEvent",
event, PrerenderLocalPredictor::EVENT_MAX_VALUE);
}
bool PrerenderLocalPredictor::DoesPrerenderMatchPLTRecord(
PrerenderProperties* prerender,
const GURL& url,
base::TimeDelta plt) const {
if (prerender && prerender->start_time < GetCurrentTime() - plt) {
if (prerender->url.is_empty())
RecordEvent(EVENT_ERROR_NO_PRERENDER_URL_FOR_PLT);
return (prerender->url == url);
} else {
return false;
}
}
PrerenderLocalPredictor::PrerenderProperties*
PrerenderLocalPredictor::GetIssuedPrerenderSlotForPriority(double priority) {
int num_prerenders = GetLocalPredictorMaxConcurrentPrerenders();
while (static_cast<int>(issued_prerenders_.size()) < num_prerenders)
issued_prerenders_.push_back(new PrerenderProperties());
PrerenderProperties* lowest_priority_prerender = NULL;
for (int i = 0; i < static_cast<int>(issued_prerenders_.size()); i++) {
PrerenderProperties* p = issued_prerenders_[i];
DCHECK(p != NULL);
if (!p->prerender_handle || !p->prerender_handle->IsPrerendering())
return p;
double decayed_priority = p->GetCurrentDecayedPriority();
if (decayed_priority > priority)
continue;
if (lowest_priority_prerender == NULL ||
lowest_priority_prerender->GetCurrentDecayedPriority() >
decayed_priority) {
lowest_priority_prerender = p;
}
}
return lowest_priority_prerender;
}
void PrerenderLocalPredictor::ContinuePrerenderCheck(
scoped_refptr<SessionStorageNamespace> session_storage_namespace,
scoped_ptr<gfx::Size> size,
scoped_ptr<LocalPredictorURLLookupInfo> info) {
RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_STARTED);
scoped_ptr<LocalPredictorURLInfo> url_info;
scoped_refptr<SafeBrowsingDatabaseManager> sb_db_manager =
g_browser_process->safe_browsing_service()->database_manager();
PrerenderProperties* prerender_properties = NULL;
for (int i = 0; i < static_cast<int>(info->candidate_urls_.size()); i++) {
url_info.reset(new LocalPredictorURLInfo(info->candidate_urls_[i]));
// We need to check whether we can issue a prerender for this URL.
// We test a set of conditions. Each condition can either rule out
// a prerender (in which case we reset url_info, so that it will not
// be prerendered, and we continue, which means try the next candidate
// URL), or it can be sufficient to issue the prerender without any
// further checks (in which case we just break).
// The order of the checks is critical, because it prescribes the logic
// we use here to decide what to prerender.
if (!url_info->url_lookup_success) {
RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_NO_URL);
url_info.reset(NULL);
continue;
}
prerender_properties =
GetIssuedPrerenderSlotForPriority(url_info->priority);
if (!prerender_properties) {
RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_PRIORITY_TOO_LOW);
url_info.reset(NULL);
continue;
}
if (URLsIdenticalIgnoringFragments(info->source_url_.url,
url_info->url)) {
RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_URLS_IDENTICAL_BUT_FRAGMENT);
url_info.reset(NULL);
continue;
}
if (url_info->url.SchemeIs("https")) {
RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_HTTPS);
url_info.reset(NULL);
continue;
}
if (IsRootPageURL(url_info->url)) {
// For root pages, we assume that they are reasonably safe, and we
// will just prerender them without any additional checks.
RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_ROOT_PAGE);
break;
}
if (IsLogOutURL(url_info->url)) {
RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_LOGOUT_URL);
url_info.reset(NULL);
continue;
}
if (IsLogInURL(url_info->url)) {
RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_LOGIN_URL);
url_info.reset(NULL);
continue;
}
if (sb_db_manager->CheckSideEffectFreeWhitelistUrl(url_info->url)) {
// If a page is on the side-effect free whitelist, we will just prerender
// it without any additional checks.
RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_ON_SIDE_EFFECT_FREE_WHITELIST);
break;
}
if (!url_info->logged_in && url_info->logged_in_lookup_ok) {
RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_NOT_LOGGED_IN);
break;
}
RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_FALLTHROUGH_NOT_PRERENDERING);
url_info.reset(NULL);
}
if (!url_info.get())
return;
RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_ISSUING_PRERENDER);
DCHECK(prerender_properties != NULL);
if (IsLocalPredictorPrerenderLaunchEnabled()) {
IssuePrerender(session_storage_namespace, size.Pass(),
url_info.Pass(), prerender_properties);
}
}
void PrerenderLocalPredictor::IssuePrerender(
scoped_refptr<SessionStorageNamespace> session_storage_namespace,
scoped_ptr<gfx::Size> size,
scoped_ptr<LocalPredictorURLInfo> info,
PrerenderProperties* prerender_properties) {
URLID url_id = info->id;
const GURL& url = info->url;
double priority = info->priority;
base::Time current_time = GetCurrentTime();
RecordEvent(EVENT_ISSUING_PRERENDER);
// Issue the prerender and obtain a new handle.
scoped_ptr<prerender::PrerenderHandle> new_prerender_handle(
prerender_manager_->AddPrerenderFromLocalPredictor(
url, session_storage_namespace.get(), *size));
// Check if this is a duplicate of an existing prerender. If yes, clean up
// the new handle.
for (int i = 0; i < static_cast<int>(issued_prerenders_.size()); i++) {
PrerenderProperties* p = issued_prerenders_[i];
DCHECK(p != NULL);
if (new_prerender_handle &&
new_prerender_handle->RepresentingSamePrerenderAs(
p->prerender_handle.get())) {
new_prerender_handle->OnCancel();
new_prerender_handle.reset(NULL);
break;
}
}
if (new_prerender_handle.get()) {
// The new prerender does not match any existing prerenders. Update
// prerender_properties so that it reflects the new entry.
prerender_properties->url_id = url_id;
prerender_properties->url = url;
prerender_properties->priority = priority;
prerender_properties->start_time = current_time;
prerender_properties->actual_start_time = current_time;
prerender_properties->would_have_matched = false;
prerender_properties->prerender_handle.swap(new_prerender_handle);
// new_prerender_handle now represents the old previou prerender that we
// are replacing. So we need to cancel it.
if (new_prerender_handle)
new_prerender_handle->OnCancel();
}
RecordEvent(EVENT_ADD_VISIT_PRERENDERING);
if (current_prerender_.get() && current_prerender_->url_id == url_id) {
RecordEvent(EVENT_ADD_VISIT_PRERENDERING_EXTENDED);
if (priority > current_prerender_->priority)
current_prerender_->priority = priority;
// If the prerender already existed, we want to extend it. However,
// we do not want to set its start_time to the current time to
// disadvantage PLT computations when the prerender is swapped in.
// So we set the new start time to current_time - 10s (since the vast
// majority of PLTs are < 10s), provided that is not before the actual
// time the prerender was started (so as to not artificially advantage
// the PLT computation).
base::Time simulated_new_start_time =
current_time - base::TimeDelta::FromSeconds(10);
if (simulated_new_start_time > current_prerender_->start_time)
current_prerender_->start_time = simulated_new_start_time;
} else {
current_prerender_.reset(
new PrerenderProperties(url_id, url, priority, current_time));
}
current_prerender_->actual_start_time = current_time;
}
void PrerenderLocalPredictor::OnTabHelperURLSeen(
const GURL& url, WebContents* web_contents) {
RecordEvent(EVENT_TAB_HELPER_URL_SEEN);
// If the namespace matches and the URL matches, we might be able to swap
// in. However, the actual code initating the swapin is in the renderer
// and is checking for other criteria (such as POSTs). There may
// also be conditions when a swapin should happen but does not. By recording
// the two previous events, we can keep an eye on the magnitude of the
// discrepancy.
PrerenderProperties* best_matched_prerender = NULL;
bool session_storage_namespace_matches = false;
for (int i = 0; i < static_cast<int>(issued_prerenders_.size()); i++) {
PrerenderProperties* p = issued_prerenders_[i];
DCHECK(p != NULL);
if (!p->prerender_handle.get() ||
!p->prerender_handle->Matches(url, NULL) ||
p->would_have_matched) {
continue;
}
if (!best_matched_prerender || !session_storage_namespace_matches) {
best_matched_prerender = p;
session_storage_namespace_matches =
p->prerender_handle->Matches(
url,
web_contents->GetController().
GetDefaultSessionStorageNamespace());
}
}
if (best_matched_prerender) {
RecordEvent(EVENT_TAB_HELPER_URL_SEEN_MATCH);
best_matched_prerender->would_have_matched = true;
if (session_storage_namespace_matches)
RecordEvent(EVENT_TAB_HELPER_URL_SEEN_NAMESPACE_MATCH);
}
}
} // namespace prerender