This source file includes following definitions.
- resolver_
- Start
- OnLookupFinished
- was_used_
- timestamp
- set_was_used
- was_used
- max_duration_
- ObservePreconnect
- ObserveNavigationChain
- ObserveLinkNavigation
- observer_
- CreatePredictor
- RegisterProfilePrefs
- InitNetworkPredictor
- AnticipateOmniboxUrl
- PreconnectUrlAndSubresources
- GetPredictedUrlListAtStartup
- set_max_queueing_delay
- set_max_parallel_resolves
- ShutdownOnUIThread
- Shutdown
- DiscardAllResults
- ResolveList
- Resolve
- LearnFromNavigation
- PredictorGetHtmlInfo
- GetHtmlReferrerLists
- GetHtmlInfo
- TrimReferrersNow
- SerializeReferrers
- DeserializeReferrers
- DeserializeReferrersThenDelete
- DiscardInitialNavigationHistory
- FinalizeInitializationOnIOThread
- LearnAboutInitialNavigation
- DnsPrefetchList
- DnsPrefetchMotivatedList
- SaveDnsPrefetchStateForNextStartupAndTrimOnIOThread
- SaveStateForNextStartupAndTrim
- SaveDnsPrefetchStateForNextStartupAndTrim
- EnablePredictor
- EnablePredictorOnIOThread
- PreconnectUrl
- PreconnectUrlOnIOThread
- RecordPreconnectTrigger
- RecordPreconnectNavigationStat
- RecordLinkNavigation
- PredictFrameSubresources
- AdviseProxy
- PrepareFrameSubresources
- OnLookupFinished
- LookupFinished
- AppendToResolutionQueue
- CongestionControlPerformed
- StartSomeQueuedResolutions
- TrimReferrers
- LoadUrlsForTrimming
- PostIncrementalTrimTask
- IncrementalTrimReferrers
- AdviseProxyOnIOThread
- GetHSTSRedirectOnIOThread
- Push
- IsEmpty
- Pop
- Append
- GetInitialDnsResolutionList
- GetFirstResolutionsHtml
- CanonicalizeUrl
- InitNetworkPredictor
- ShutdownOnUIThread
#include "chrome/browser/net/predictor.h"
#include <algorithm>
#include <cmath>
#include <set>
#include <sstream>
#include "base/basictypes.h"
#include "base/bind.h"
#include "base/compiler_specific.h"
#include "base/containers/mru_cache.h"
#include "base/metrics/histogram.h"
#include "base/prefs/pref_service.h"
#include "base/prefs/scoped_user_pref_update.h"
#include "base/stl_util.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/io_thread.h"
#include "chrome/browser/net/preconnect.h"
#include "chrome/browser/net/spdyproxy/data_reduction_proxy_settings.h"
#include "chrome/browser/net/spdyproxy/proxy_advisor.h"
#include "chrome/browser/prefs/session_startup_pref.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "components/user_prefs/pref_registry_syncable.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/address_list.h"
#include "net/base/completion_callback.h"
#include "net/base/host_port_pair.h"
#include "net/base/net_errors.h"
#include "net/base/net_log.h"
#include "net/dns/host_resolver.h"
#include "net/dns/single_request_host_resolver.h"
#include "net/http/transport_security_state.h"
#include "net/ssl/ssl_config_service.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"
using base::TimeDelta;
using content::BrowserThread;
namespace chrome_browser_net {
const int Predictor::kPredictorReferrerVersion = 2;
const double Predictor::kPreconnectWorthyExpectedValue = 0.8;
const double Predictor::kDNSPreresolutionWorthyExpectedValue = 0.1;
const double Predictor::kDiscardableExpectedValue = 0.05;
const double Predictor::kReferrerTrimRatio = 0.97153;
const int64 Predictor::kDurationBetweenTrimmingsHours = 1;
const int64 Predictor::kDurationBetweenTrimmingIncrementsSeconds = 15;
const size_t Predictor::kUrlsTrimmedPerIncrement = 5u;
const size_t Predictor::kMaxSpeculativeParallelResolves = 3;
const int Predictor::kMaxUnusedSocketLifetimeSecondsWithoutAGet = 10;
const int kExpectedResolutionTimeMs = 500;
const int Predictor::kTypicalSpeculativeGroupSize = 8;
const int Predictor::kMaxSpeculativeResolveQueueDelayMs =
(kExpectedResolutionTimeMs * Predictor::kTypicalSpeculativeGroupSize) /
Predictor::kMaxSpeculativeParallelResolves;
static int g_max_queueing_delay_ms =
Predictor::kMaxSpeculativeResolveQueueDelayMs;
static size_t g_max_parallel_resolves =
Predictor::kMaxSpeculativeParallelResolves;
static const int kPredictorStartupFormatVersion = 1;
class Predictor::LookupRequest {
public:
LookupRequest(Predictor* predictor,
net::HostResolver* host_resolver,
const GURL& url)
: predictor_(predictor),
url_(url),
resolver_(host_resolver) {
}
int Start() {
net::HostResolver::RequestInfo resolve_info(
net::HostPortPair::FromURL(url_));
resolve_info.set_is_speculative(true);
return resolver_.Resolve(
resolve_info,
net::DEFAULT_PRIORITY,
&addresses_,
base::Bind(&LookupRequest::OnLookupFinished, base::Unretained(this)),
net::BoundNetLog());
}
private:
void OnLookupFinished(int result) {
predictor_->OnLookupFinished(this, url_, result == net::OK);
}
Predictor* predictor_;
const GURL url_;
net::SingleRequestHostResolver resolver_;
net::AddressList addresses_;
DISALLOW_COPY_AND_ASSIGN(LookupRequest);
};
class Predictor::PreconnectUsage {
public:
PreconnectUsage();
~PreconnectUsage();
void ObservePreconnect(const GURL& url);
void ObserveNavigationChain(const std::vector<GURL>& url_chain,
bool is_subresource);
void ObserveLinkNavigation(const GURL& final_url);
private:
class PreconnectPrecisionStat {
public:
PreconnectPrecisionStat()
: timestamp_(base::TimeTicks::Now()),
was_used_(false) {
}
const base::TimeTicks& timestamp() { return timestamp_; }
void set_was_used() { was_used_ = true; }
bool was_used() const { return was_used_; }
private:
base::TimeTicks timestamp_;
bool was_used_;
};
typedef base::MRUCache<GURL, PreconnectPrecisionStat> MRUPreconnects;
MRUPreconnects mru_preconnects_;
const base::TimeDelta max_duration_;
std::vector<GURL> recent_navigation_chain_;
DISALLOW_COPY_AND_ASSIGN(PreconnectUsage);
};
Predictor::PreconnectUsage::PreconnectUsage()
: mru_preconnects_(MRUPreconnects::NO_AUTO_EVICT),
max_duration_(base::TimeDelta::FromSeconds(
Predictor::kMaxUnusedSocketLifetimeSecondsWithoutAGet)) {
}
Predictor::PreconnectUsage::~PreconnectUsage() {}
void Predictor::PreconnectUsage::ObservePreconnect(const GURL& url) {
base::TimeTicks now = base::TimeTicks::Now();
MRUPreconnects::reverse_iterator eldest_preconnect =
mru_preconnects_.rbegin();
while (!mru_preconnects_.empty()) {
DCHECK(eldest_preconnect == mru_preconnects_.rbegin());
if (now - eldest_preconnect->second.timestamp() < max_duration_)
break;
UMA_HISTOGRAM_BOOLEAN("Net.PreconnectTriggerUsed",
eldest_preconnect->second.was_used());
eldest_preconnect = mru_preconnects_.Erase(eldest_preconnect);
}
GURL canonical_url(Predictor::CanonicalizeUrl(url));
mru_preconnects_.Put(canonical_url, PreconnectPrecisionStat());
}
void Predictor::PreconnectUsage::ObserveNavigationChain(
const std::vector<GURL>& url_chain,
bool is_subresource) {
if (url_chain.empty())
return;
if (!is_subresource)
recent_navigation_chain_ = url_chain;
GURL canonical_url(Predictor::CanonicalizeUrl(url_chain.back()));
MRUPreconnects::iterator itPreconnect = mru_preconnects_.Peek(canonical_url);
bool was_preconnected = (itPreconnect != mru_preconnects_.end());
UMA_HISTOGRAM_BOOLEAN("Net.PreconnectedNavigation", was_preconnected);
}
void Predictor::PreconnectUsage::ObserveLinkNavigation(const GURL& url) {
if (recent_navigation_chain_.empty() ||
url != recent_navigation_chain_.back()) {
recent_navigation_chain_.clear();
recent_navigation_chain_.push_back(url);
}
bool did_use_preconnect = false;
for (std::vector<GURL>::const_iterator it = recent_navigation_chain_.begin();
it != recent_navigation_chain_.end();
++it) {
GURL canonical_url(Predictor::CanonicalizeUrl(*it));
MRUPreconnects::iterator itPreconnect =
mru_preconnects_.Peek(canonical_url);
bool was_preconnected = (itPreconnect != mru_preconnects_.end());
if (was_preconnected) {
itPreconnect->second.set_was_used();
did_use_preconnect = true;
}
}
UMA_HISTOGRAM_BOOLEAN("Net.PreconnectedLinkNavigations", did_use_preconnect);
}
Predictor::Predictor(bool preconnect_enabled)
: url_request_context_getter_(NULL),
predictor_enabled_(true),
peak_pending_lookups_(0),
shutdown_(false),
max_concurrent_dns_lookups_(g_max_parallel_resolves),
max_dns_queue_delay_(
TimeDelta::FromMilliseconds(g_max_queueing_delay_ms)),
host_resolver_(NULL),
transport_security_state_(NULL),
ssl_config_service_(NULL),
preconnect_enabled_(preconnect_enabled),
consecutive_omnibox_preconnect_count_(0),
next_trim_time_(base::TimeTicks::Now() +
TimeDelta::FromHours(kDurationBetweenTrimmingsHours)),
observer_(NULL) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
}
Predictor::~Predictor() {
DCHECK(shutdown_);
}
Predictor* Predictor::CreatePredictor(bool preconnect_enabled,
bool simple_shutdown) {
if (simple_shutdown)
return new SimplePredictor(preconnect_enabled);
return new Predictor(preconnect_enabled);
}
void Predictor::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterListPref(prefs::kDnsPrefetchingStartupList,
user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
registry->RegisterListPref(prefs::kDnsPrefetchingHostReferralList,
user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
}
void Predictor::InitNetworkPredictor(PrefService* user_prefs,
PrefService* local_state,
IOThread* io_thread,
net::URLRequestContextGetter* getter) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
bool predictor_enabled =
user_prefs->GetBoolean(prefs::kNetworkPredictionEnabled);
url_request_context_getter_ = getter;
UrlList urls = GetPredictedUrlListAtStartup(user_prefs, local_state);
base::ListValue* referral_list =
static_cast<base::ListValue*>(user_prefs->GetList(
prefs::kDnsPrefetchingHostReferralList)->DeepCopy());
user_prefs->ClearPref(prefs::kDnsPrefetchingStartupList);
user_prefs->ClearPref(prefs::kDnsPrefetchingHostReferralList);
#if defined(OS_ANDROID) || defined(OS_IOS)
if (DataReductionProxySettings::IsPreconnectHintingAllowed()) {
proxy_advisor_.reset(new ProxyAdvisor(user_prefs, getter));
}
#endif
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(
&Predictor::FinalizeInitializationOnIOThread,
base::Unretained(this),
urls, referral_list,
io_thread, predictor_enabled));
}
void Predictor::AnticipateOmniboxUrl(const GURL& url, bool preconnectable) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!predictor_enabled_)
return;
if (!url.is_valid() || !url.has_host())
return;
std::string host = url.HostNoBrackets();
bool is_new_host_request = (host != last_omnibox_host_);
last_omnibox_host_ = host;
UrlInfo::ResolutionMotivation motivation(UrlInfo::OMNIBOX_MOTIVATED);
base::TimeTicks now = base::TimeTicks::Now();
if (preconnect_enabled()) {
if (preconnectable && !is_new_host_request) {
++consecutive_omnibox_preconnect_count_;
const int kMinConsecutiveRequests = 8;
if (consecutive_omnibox_preconnect_count_ >= kMinConsecutiveRequests) {
const int kMaxSearchKeepaliveSeconds(10);
if ((now - last_omnibox_preconnect_).InSeconds() <
kMaxSearchKeepaliveSeconds)
return;
last_omnibox_preconnect_ = now;
const int kConnectionsNeeded = 1;
PreconnectUrl(CanonicalizeUrl(url), GURL(), motivation,
kConnectionsNeeded);
return;
}
} else {
consecutive_omnibox_preconnect_count_ = 0;
}
}
if (!is_new_host_request) {
const int kMinPreresolveSeconds(10);
if (kMinPreresolveSeconds > (now - last_omnibox_preresolve_).InSeconds())
return;
}
last_omnibox_preresolve_ = now;
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(&Predictor::Resolve, base::Unretained(this),
CanonicalizeUrl(url), motivation));
}
void Predictor::PreconnectUrlAndSubresources(const GURL& url,
const GURL& first_party_for_cookies) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
BrowserThread::CurrentlyOn(BrowserThread::IO));
if (!predictor_enabled_ || !preconnect_enabled() ||
!url.is_valid() || !url.has_host())
return;
UrlInfo::ResolutionMotivation motivation(UrlInfo::EARLY_LOAD_MOTIVATED);
const int kConnectionsNeeded = 1;
PreconnectUrl(CanonicalizeUrl(url), first_party_for_cookies,
motivation, kConnectionsNeeded);
PredictFrameSubresources(url.GetWithEmptyPath(), first_party_for_cookies);
}
UrlList Predictor::GetPredictedUrlListAtStartup(
PrefService* user_prefs,
PrefService* local_state) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
UrlList urls;
const base::ListValue* startup_list =
user_prefs->GetList(prefs::kDnsPrefetchingStartupList);
if (startup_list) {
base::ListValue::const_iterator it = startup_list->begin();
int format_version = -1;
if (it != startup_list->end() &&
(*it)->GetAsInteger(&format_version) &&
format_version == kPredictorStartupFormatVersion) {
++it;
for (; it != startup_list->end(); ++it) {
std::string url_spec;
if (!(*it)->GetAsString(&url_spec)) {
LOG(DFATAL);
break;
}
GURL url(url_spec);
if (!url.has_host() || !url.has_scheme()) {
LOG(DFATAL);
break;
}
urls.push_back(url);
}
}
}
SessionStartupPref tab_start_pref =
SessionStartupPref::GetStartupPref(user_prefs);
if (SessionStartupPref::URLS == tab_start_pref.type) {
for (size_t i = 0; i < tab_start_pref.urls.size(); i++) {
GURL gurl = tab_start_pref.urls[i];
if (!gurl.is_valid() || gurl.SchemeIsFile() || gurl.host().empty())
continue;
if (gurl.SchemeIsHTTPOrHTTPS())
urls.push_back(gurl.GetWithEmptyPath());
}
}
if (urls.empty())
urls.push_back(GURL("http://www.google.com:80"));
return urls;
}
void Predictor::set_max_queueing_delay(int max_queueing_delay_ms) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
g_max_queueing_delay_ms = max_queueing_delay_ms;
}
void Predictor::set_max_parallel_resolves(size_t max_parallel_resolves) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
g_max_parallel_resolves = max_parallel_resolves;
}
void Predictor::ShutdownOnUIThread() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(&Predictor::Shutdown, base::Unretained(this)));
}
void Predictor::Shutdown() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK(!shutdown_);
shutdown_ = true;
STLDeleteElements(&pending_lookups_);
}
void Predictor::DiscardAllResults() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
referrers_.clear();
while (!work_queue_.IsEmpty()) {
GURL url = work_queue_.Pop();
UrlInfo* info = &results_[url];
DCHECK(info->HasUrl(url));
info->SetAssignedState();
info->SetNoSuchNameState();
}
Results assignees;
for (Results::iterator it = results_.begin(); results_.end() != it; ++it) {
GURL url(it->first);
UrlInfo* info = &it->second;
DCHECK(info->HasUrl(url));
if (info->is_assigned()) {
info->SetPendingDeleteState();
assignees[url] = *info;
}
}
DCHECK_LE(assignees.size(), max_concurrent_dns_lookups_);
results_.clear();
for (Results::iterator it = assignees.begin(); assignees.end() != it; ++it) {
DCHECK(it->second.is_marked_to_delete());
results_[it->first] = it->second;
}
}
void Predictor::ResolveList(const UrlList& urls,
UrlInfo::ResolutionMotivation motivation) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
for (UrlList::const_iterator it = urls.begin(); it < urls.end(); ++it) {
AppendToResolutionQueue(*it, motivation);
}
}
void Predictor::Resolve(const GURL& url,
UrlInfo::ResolutionMotivation motivation) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (!url.has_host())
return;
AppendToResolutionQueue(url, motivation);
}
void Predictor::LearnFromNavigation(const GURL& referring_url,
const GURL& target_url) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (!predictor_enabled_)
return;
DCHECK_EQ(referring_url, Predictor::CanonicalizeUrl(referring_url));
DCHECK_NE(referring_url, GURL::EmptyGURL());
DCHECK_EQ(target_url, Predictor::CanonicalizeUrl(target_url));
DCHECK_NE(target_url, GURL::EmptyGURL());
referrers_[referring_url].SuggestHost(target_url);
TrimReferrers();
}
void Predictor::PredictorGetHtmlInfo(Predictor* predictor,
std::string* output) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
output->append("<html><head><title>About DNS</title>"
"</head><body>");
if (predictor && predictor->predictor_enabled()) {
predictor->GetHtmlInfo(output);
} else {
output->append("DNS pre-resolution and TCP pre-connection is disabled.");
}
output->append("</body></html>");
}
struct RightToLeftStringSorter {
bool operator()(const GURL& left, const GURL& right) const {
return ReverseComponents(left) < ReverseComponents(right);
}
private:
static std::string ReverseComponents(const GURL& url) {
std::vector<std::string> parts;
base::SplitString(url.host(), '.', &parts);
std::reverse(parts.begin(), parts.end());
std::string reversed_host = JoinString(parts, '.');
GURL::Replacements url_components;
url_components.SetHostStr(reversed_host);
return url.ReplaceComponents(url_components).spec();
}
};
void Predictor::GetHtmlReferrerLists(std::string* output) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (referrers_.empty())
return;
typedef std::set<GURL, struct RightToLeftStringSorter>
SortedNames;
SortedNames sorted_names;
for (Referrers::iterator it = referrers_.begin();
referrers_.end() != it; ++it)
sorted_names.insert(it->first);
output->append("<br><table border>");
output->append(
"<tr><th>Host for Page</th>"
"<th>Page Load<br>Count</th>"
"<th>Subresource<br>Navigations</th>"
"<th>Subresource<br>PreConnects</th>"
"<th>Subresource<br>PreResolves</th>"
"<th>Expected<br>Connects</th>"
"<th>Subresource Spec</th></tr>");
for (SortedNames::iterator it = sorted_names.begin();
sorted_names.end() != it; ++it) {
Referrer* referrer = &(referrers_[*it]);
bool first_set_of_futures = true;
for (Referrer::iterator future_url = referrer->begin();
future_url != referrer->end(); ++future_url) {
output->append("<tr align=right>");
if (first_set_of_futures) {
base::StringAppendF(output,
"<td rowspan=%d>%s</td><td rowspan=%d>%d</td>",
static_cast<int>(referrer->size()),
it->spec().c_str(),
static_cast<int>(referrer->size()),
static_cast<int>(referrer->use_count()));
}
first_set_of_futures = false;
base::StringAppendF(output,
"<td>%d</td><td>%d</td><td>%d</td><td>%2.3f</td><td>%s</td></tr>",
static_cast<int>(future_url->second.navigation_count()),
static_cast<int>(future_url->second.preconnection_count()),
static_cast<int>(future_url->second.preresolution_count()),
static_cast<double>(future_url->second.subresource_use_rate()),
future_url->first.spec().c_str());
}
}
output->append("</table>");
}
void Predictor::GetHtmlInfo(std::string* output) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (initial_observer_.get())
initial_observer_->GetFirstResolutionsHtml(output);
GetHtmlReferrerLists(output);
UrlInfo::UrlInfoTable name_not_found;
UrlInfo::UrlInfoTable name_preresolved;
typedef std::map<GURL, UrlInfo, RightToLeftStringSorter> SortedUrlInfo;
SortedUrlInfo snapshot;
for (Results::iterator it(results_.begin()); it != results_.end(); it++)
snapshot[it->first] = it->second;
for (SortedUrlInfo::iterator it(snapshot.begin());
it != snapshot.end(); it++) {
if (it->second.was_nonexistent()) {
name_not_found.push_back(it->second);
continue;
}
if (!it->second.was_found())
continue;
name_preresolved.push_back(it->second);
}
bool brief = false;
#ifdef NDEBUG
brief = true;
#endif
UrlInfo::GetHtmlTable(name_preresolved,
"Preresolution DNS records performed for ", brief, output);
UrlInfo::GetHtmlTable(name_not_found,
"Preresolving DNS records revealed non-existence for ", brief, output);
}
void Predictor::TrimReferrersNow() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (urls_being_trimmed_.empty())
LoadUrlsForTrimming();
IncrementalTrimReferrers(true);
}
void Predictor::SerializeReferrers(base::ListValue* referral_list) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
referral_list->Clear();
referral_list->Append(new base::FundamentalValue(kPredictorReferrerVersion));
for (Referrers::const_iterator it = referrers_.begin();
it != referrers_.end(); ++it) {
base::Value* subresource_list(it->second.Serialize());
base::ListValue* motivator(new base::ListValue);
motivator->Append(new base::StringValue(it->first.spec()));
motivator->Append(subresource_list);
referral_list->Append(motivator);
}
}
void Predictor::DeserializeReferrers(const base::ListValue& referral_list) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
int format_version = -1;
if (referral_list.GetSize() > 0 &&
referral_list.GetInteger(0, &format_version) &&
format_version == kPredictorReferrerVersion) {
for (size_t i = 1; i < referral_list.GetSize(); ++i) {
const base::ListValue* motivator;
if (!referral_list.GetList(i, &motivator)) {
NOTREACHED();
return;
}
std::string motivating_url_spec;
if (!motivator->GetString(0, &motivating_url_spec)) {
NOTREACHED();
return;
}
const base::Value* subresource_list;
if (!motivator->Get(1, &subresource_list)) {
NOTREACHED();
return;
}
referrers_[GURL(motivating_url_spec)].Deserialize(*subresource_list);
}
}
}
void Predictor::DeserializeReferrersThenDelete(
base::ListValue* referral_list) {
DeserializeReferrers(*referral_list);
delete referral_list;
}
void Predictor::DiscardInitialNavigationHistory() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (initial_observer_.get())
initial_observer_->DiscardInitialNavigationHistory();
}
void Predictor::FinalizeInitializationOnIOThread(
const UrlList& startup_urls,
base::ListValue* referral_list,
IOThread* io_thread,
bool predictor_enabled) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
predictor_enabled_ = predictor_enabled;
initial_observer_.reset(new InitialObserver());
host_resolver_ = io_thread->globals()->host_resolver.get();
preconnect_usage_.reset(new PreconnectUsage());
net::URLRequestContext* context =
url_request_context_getter_->GetURLRequestContext();
transport_security_state_ = context->transport_security_state();
ssl_config_service_ = context->ssl_config_service();
weak_factory_.reset(new base::WeakPtrFactory<Predictor>(this));
DnsPrefetchMotivatedList(startup_urls, UrlInfo::STARTUP_LIST_MOTIVATED);
DeserializeReferrersThenDelete(referral_list);
}
void Predictor::LearnAboutInitialNavigation(const GURL& url) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (!predictor_enabled_ || NULL == initial_observer_.get() )
return;
initial_observer_->Append(url, this);
}
void Predictor::DnsPrefetchList(const NameList& hostnames) {
UrlList urls;
for (NameList::const_iterator it = hostnames.begin();
it < hostnames.end();
++it) {
urls.push_back(GURL("http://" + *it + ":80"));
}
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DnsPrefetchMotivatedList(urls, UrlInfo::PAGE_SCAN_MOTIVATED);
}
void Predictor::DnsPrefetchMotivatedList(
const UrlList& urls,
UrlInfo::ResolutionMotivation motivation) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
BrowserThread::CurrentlyOn(BrowserThread::IO));
if (!predictor_enabled_)
return;
if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
ResolveList(urls, motivation);
} else {
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(&Predictor::ResolveList, base::Unretained(this),
urls, motivation));
}
}
static void SaveDnsPrefetchStateForNextStartupAndTrimOnIOThread(
base::ListValue* startup_list,
base::ListValue* referral_list,
base::WaitableEvent* completion,
Predictor* predictor) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (NULL == predictor) {
completion->Signal();
return;
}
predictor->SaveDnsPrefetchStateForNextStartupAndTrim(
startup_list, referral_list, completion);
}
void Predictor::SaveStateForNextStartupAndTrim(PrefService* prefs) {
if (!predictor_enabled_)
return;
base::WaitableEvent completion(true, false);
ListPrefUpdate update_startup_list(prefs, prefs::kDnsPrefetchingStartupList);
ListPrefUpdate update_referral_list(prefs,
prefs::kDnsPrefetchingHostReferralList);
if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
SaveDnsPrefetchStateForNextStartupAndTrimOnIOThread(
update_startup_list.Get(),
update_referral_list.Get(),
&completion,
this);
} else {
bool posted = BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(
&SaveDnsPrefetchStateForNextStartupAndTrimOnIOThread,
update_startup_list.Get(),
update_referral_list.Get(),
&completion,
this));
DCHECK(posted);
if (posted) {
base::ThreadRestrictions::ScopedAllowWait allow_wait;
completion.Wait();
}
}
}
void Predictor::SaveDnsPrefetchStateForNextStartupAndTrim(
base::ListValue* startup_list,
base::ListValue* referral_list,
base::WaitableEvent* completion) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (initial_observer_.get())
initial_observer_->GetInitialDnsResolutionList(startup_list);
TrimReferrersNow();
SerializeReferrers(referral_list);
completion->Signal();
}
void Predictor::EnablePredictor(bool enable) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
BrowserThread::CurrentlyOn(BrowserThread::IO));
if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
EnablePredictorOnIOThread(enable);
} else {
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(&Predictor::EnablePredictorOnIOThread,
base::Unretained(this), enable));
}
}
void Predictor::EnablePredictorOnIOThread(bool enable) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
predictor_enabled_ = enable;
}
void Predictor::PreconnectUrl(const GURL& url,
const GURL& first_party_for_cookies,
UrlInfo::ResolutionMotivation motivation,
int count) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
BrowserThread::CurrentlyOn(BrowserThread::IO));
if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
PreconnectUrlOnIOThread(url, first_party_for_cookies, motivation, count);
} else {
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(&Predictor::PreconnectUrlOnIOThread,
base::Unretained(this), url, first_party_for_cookies,
motivation, count));
}
}
void Predictor::PreconnectUrlOnIOThread(
const GURL& original_url,
const GURL& first_party_for_cookies,
UrlInfo::ResolutionMotivation motivation,
int count) {
GURL url = GetHSTSRedirectOnIOThread(original_url);
if (motivation == UrlInfo::MOUSE_OVER_MOTIVATED)
RecordPreconnectTrigger(url);
AdviseProxy(url, motivation, true );
if (observer_) {
observer_->OnPreconnectUrl(
url, first_party_for_cookies, motivation, count);
}
PreconnectOnIOThread(url,
first_party_for_cookies,
motivation,
count,
url_request_context_getter_.get());
}
void Predictor::RecordPreconnectTrigger(const GURL& url) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (preconnect_usage_)
preconnect_usage_->ObservePreconnect(url);
}
void Predictor::RecordPreconnectNavigationStat(
const std::vector<GURL>& url_chain,
bool is_subresource) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (preconnect_usage_)
preconnect_usage_->ObserveNavigationChain(url_chain, is_subresource);
}
void Predictor::RecordLinkNavigation(const GURL& url) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (preconnect_usage_)
preconnect_usage_->ObserveLinkNavigation(url);
}
void Predictor::PredictFrameSubresources(const GURL& url,
const GURL& first_party_for_cookies) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
BrowserThread::CurrentlyOn(BrowserThread::IO));
if (!predictor_enabled_)
return;
DCHECK_EQ(url.GetWithEmptyPath(), url);
if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
PrepareFrameSubresources(url, first_party_for_cookies);
} else {
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(&Predictor::PrepareFrameSubresources,
base::Unretained(this), url, first_party_for_cookies));
}
}
void Predictor::AdviseProxy(const GURL& url,
UrlInfo::ResolutionMotivation motivation,
bool is_preconnect) {
if (!proxy_advisor_)
return;
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
BrowserThread::CurrentlyOn(BrowserThread::IO));
if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
AdviseProxyOnIOThread(url, motivation, is_preconnect);
} else {
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(&Predictor::AdviseProxyOnIOThread,
base::Unretained(this), url, motivation, is_preconnect));
}
}
enum SubresourceValue {
PRECONNECTION,
PRERESOLUTION,
TOO_NEW,
SUBRESOURCE_VALUE_MAX
};
void Predictor::PrepareFrameSubresources(const GURL& original_url,
const GURL& first_party_for_cookies) {
GURL url = GetHSTSRedirectOnIOThread(original_url);
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK_EQ(url.GetWithEmptyPath(), url);
Referrers::iterator it = referrers_.find(url);
if (referrers_.end() == it) {
if (preconnect_enabled_) {
PreconnectUrlOnIOThread(url, first_party_for_cookies,
UrlInfo::SELF_REFERAL_MOTIVATED, 2);
}
return;
}
Referrer* referrer = &(it->second);
referrer->IncrementUseCount();
const UrlInfo::ResolutionMotivation motivation =
UrlInfo::LEARNED_REFERAL_MOTIVATED;
for (Referrer::iterator future_url = referrer->begin();
future_url != referrer->end(); ++future_url) {
SubresourceValue evalution(TOO_NEW);
double connection_expectation = future_url->second.subresource_use_rate();
UMA_HISTOGRAM_CUSTOM_COUNTS("Net.PreconnectSubresourceExpectation",
static_cast<int>(connection_expectation * 100),
10, 5000, 50);
future_url->second.ReferrerWasObserved();
if (preconnect_enabled_ &&
connection_expectation > kPreconnectWorthyExpectedValue) {
evalution = PRECONNECTION;
future_url->second.IncrementPreconnectionCount();
int count = static_cast<int>(std::ceil(connection_expectation));
if (url.host() == future_url->first.host())
++count;
PreconnectUrlOnIOThread(future_url->first, first_party_for_cookies,
motivation, count);
} else if (connection_expectation > kDNSPreresolutionWorthyExpectedValue) {
evalution = PRERESOLUTION;
future_url->second.preresolution_increment();
UrlInfo* queued_info = AppendToResolutionQueue(future_url->first,
motivation);
if (queued_info)
queued_info->SetReferringHostname(url);
}
UMA_HISTOGRAM_ENUMERATION("Net.PreconnectSubresourceEval", evalution,
SUBRESOURCE_VALUE_MAX);
}
}
void Predictor::OnLookupFinished(LookupRequest* request, const GURL& url,
bool found) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
LookupFinished(request, url, found);
pending_lookups_.erase(request);
delete request;
StartSomeQueuedResolutions();
}
void Predictor::LookupFinished(LookupRequest* request, const GURL& url,
bool found) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
UrlInfo* info = &results_[url];
DCHECK(info->HasUrl(url));
if (info->is_marked_to_delete()) {
results_.erase(url);
} else {
if (found)
info->SetFoundState();
else
info->SetNoSuchNameState();
}
}
UrlInfo* Predictor::AppendToResolutionQueue(
const GURL& url,
UrlInfo::ResolutionMotivation motivation) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK(url.has_host());
if (shutdown_)
return NULL;
UrlInfo* info = &results_[url];
info->SetUrl(url);
DCHECK(info->HasUrl(url));
if (!info->NeedsDnsUpdate()) {
info->DLogResultsStats("DNS PrefetchNotUpdated");
return NULL;
}
AdviseProxy(url, motivation, false );
if (proxy_advisor_ && proxy_advisor_->WouldProxyURL(url)) {
info->DLogResultsStats("DNS PrefetchForProxiedRequest");
return NULL;
}
info->SetQueuedState(motivation);
work_queue_.Push(url, motivation);
StartSomeQueuedResolutions();
return info;
}
bool Predictor::CongestionControlPerformed(UrlInfo* info) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (info->queue_duration() < max_dns_queue_delay_)
return false;
while (true) {
info->RemoveFromQueue();
if (work_queue_.IsEmpty())
break;
info = &results_[work_queue_.Pop()];
info->SetAssignedState();
}
return true;
}
void Predictor::StartSomeQueuedResolutions() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
while (!work_queue_.IsEmpty() &&
pending_lookups_.size() < max_concurrent_dns_lookups_) {
const GURL url(work_queue_.Pop());
UrlInfo* info = &results_[url];
DCHECK(info->HasUrl(url));
info->SetAssignedState();
if (CongestionControlPerformed(info)) {
DCHECK(work_queue_.IsEmpty());
return;
}
LookupRequest* request = new LookupRequest(this, host_resolver_, url);
int status = request->Start();
if (status == net::ERR_IO_PENDING) {
pending_lookups_.insert(request);
peak_pending_lookups_ = std::max(peak_pending_lookups_,
pending_lookups_.size());
} else {
LookupFinished(request, url, status == net::OK);
delete request;
}
}
}
void Predictor::TrimReferrers() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (!urls_being_trimmed_.empty())
return;
base::TimeTicks now = base::TimeTicks::Now();
if (now < next_trim_time_)
return;
next_trim_time_ = now + TimeDelta::FromHours(kDurationBetweenTrimmingsHours);
LoadUrlsForTrimming();
PostIncrementalTrimTask();
}
void Predictor::LoadUrlsForTrimming() {
DCHECK(urls_being_trimmed_.empty());
for (Referrers::const_iterator it = referrers_.begin();
it != referrers_.end(); ++it)
urls_being_trimmed_.push_back(it->first);
UMA_HISTOGRAM_COUNTS("Net.PredictionTrimSize", urls_being_trimmed_.size());
}
void Predictor::PostIncrementalTrimTask() {
if (urls_being_trimmed_.empty())
return;
const TimeDelta kDurationBetweenTrimmingIncrements =
TimeDelta::FromSeconds(kDurationBetweenTrimmingIncrementsSeconds);
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&Predictor::IncrementalTrimReferrers,
weak_factory_->GetWeakPtr(), false),
kDurationBetweenTrimmingIncrements);
}
void Predictor::IncrementalTrimReferrers(bool trim_all_now) {
size_t trim_count = urls_being_trimmed_.size();
if (!trim_all_now)
trim_count = std::min(trim_count, kUrlsTrimmedPerIncrement);
while (trim_count-- != 0) {
Referrers::iterator it = referrers_.find(urls_being_trimmed_.back());
urls_being_trimmed_.pop_back();
if (it == referrers_.end())
continue;
if (!it->second.Trim(kReferrerTrimRatio, kDiscardableExpectedValue))
referrers_.erase(it);
}
PostIncrementalTrimTask();
}
void Predictor::AdviseProxyOnIOThread(const GURL& url,
UrlInfo::ResolutionMotivation motivation,
bool is_preconnect) {
if (!proxy_advisor_)
return;
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
proxy_advisor_->Advise(url, motivation, is_preconnect);
}
GURL Predictor::GetHSTSRedirectOnIOThread(const GURL& url) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (!transport_security_state_)
return url;
if (!url.SchemeIs("http"))
return url;
net::TransportSecurityState::DomainState domain_state;
if (!transport_security_state_->GetDomainState(
url.host(),
net::SSLConfigService::IsSNIAvailable(ssl_config_service_),
&domain_state)) {
return url;
}
if (!domain_state.ShouldUpgradeToSSL())
return url;
url_canon::Replacements<char> replacements;
const char kNewScheme[] = "https";
replacements.SetScheme(kNewScheme,
url_parse::Component(0, strlen(kNewScheme)));
return url.ReplaceComponents(replacements);
}
Predictor::HostNameQueue::HostNameQueue() {
}
Predictor::HostNameQueue::~HostNameQueue() {
}
void Predictor::HostNameQueue::Push(const GURL& url,
UrlInfo::ResolutionMotivation motivation) {
switch (motivation) {
case UrlInfo::STATIC_REFERAL_MOTIVATED:
case UrlInfo::LEARNED_REFERAL_MOTIVATED:
case UrlInfo::MOUSE_OVER_MOTIVATED:
rush_queue_.push(url);
break;
default:
background_queue_.push(url);
break;
}
}
bool Predictor::HostNameQueue::IsEmpty() const {
return rush_queue_.empty() && background_queue_.empty();
}
GURL Predictor::HostNameQueue::Pop() {
DCHECK(!IsEmpty());
std::queue<GURL> *queue(rush_queue_.empty() ? &background_queue_
: &rush_queue_);
GURL url(queue->front());
queue->pop();
return url;
}
Predictor::InitialObserver::InitialObserver() {
}
Predictor::InitialObserver::~InitialObserver() {
}
void Predictor::InitialObserver::Append(const GURL& url,
Predictor* predictor) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (NULL == predictor)
return;
if (kStartupResolutionCount <= first_navigations_.size())
return;
DCHECK(url.SchemeIsHTTPOrHTTPS());
DCHECK_EQ(url, Predictor::CanonicalizeUrl(url));
if (first_navigations_.find(url) == first_navigations_.end())
first_navigations_[url] = base::TimeTicks::Now();
}
void Predictor::InitialObserver::GetInitialDnsResolutionList(
base::ListValue* startup_list) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK(startup_list);
startup_list->Clear();
DCHECK_EQ(0u, startup_list->GetSize());
startup_list->Append(
new base::FundamentalValue(kPredictorStartupFormatVersion));
for (FirstNavigations::iterator it = first_navigations_.begin();
it != first_navigations_.end();
++it) {
DCHECK(it->first == Predictor::CanonicalizeUrl(it->first));
startup_list->Append(new base::StringValue(it->first.spec()));
}
}
void Predictor::InitialObserver::GetFirstResolutionsHtml(
std::string* output) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
UrlInfo::UrlInfoTable resolution_list;
{
for (FirstNavigations::iterator it(first_navigations_.begin());
it != first_navigations_.end();
it++) {
UrlInfo info;
info.SetUrl(it->first);
info.set_time(it->second);
resolution_list.push_back(info);
}
}
UrlInfo::GetHtmlTable(resolution_list,
"Future startups will prefetch DNS records for ", false, output);
}
GURL Predictor::CanonicalizeUrl(const GURL& url) {
if (!url.has_host())
return GURL::EmptyGURL();
std::string scheme;
if (url.has_scheme()) {
scheme = url.scheme();
if (scheme != "http" && scheme != "https")
return GURL::EmptyGURL();
if (url.has_port())
return url.GetWithEmptyPath();
} else {
scheme = "http";
}
std::string colon_plus_port;
if (url.has_port())
colon_plus_port = ":" + url.port();
return GURL(scheme + "://" + url.host() + colon_plus_port);
}
void SimplePredictor::InitNetworkPredictor(
PrefService* user_prefs,
PrefService* local_state,
IOThread* io_thread,
net::URLRequestContextGetter* getter) {
}
void SimplePredictor::ShutdownOnUIThread() {
SetShutdown(true);
}
}