// Copyright (c) 2011 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/net/predictor.h"
#include <algorithm>
#include <cmath>
#include <set>
#include <sstream>
#include "base/compiler_specific.h"
#include "base/metrics/histogram.h"
#include "base/string_util.h"
#include "base/time.h"
#include "base/values.h"
#include "chrome/browser/net/preconnect.h"
#include "content/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/host_resolver.h"
#include "net/base/net_errors.h"
#include "net/base/net_log.h"
using base::TimeDelta;
namespace chrome_browser_net {
// static
const double Predictor::kPreconnectWorthyExpectedValue = 0.8;
// static
const double Predictor::kDNSPreresolutionWorthyExpectedValue = 0.1;
// static
const double Predictor::kDiscardableExpectedValue = 0.05;
// The goal is of trimming is to to reduce the importance (number of expected
// subresources needed) by a factor of 2 after about 24 hours of uptime. We will
// trim roughly once-an-hour of uptime. The ratio to use in each trim operation
// is then the 24th root of 0.5. If a user only surfs for 4 hours a day, then
// after about 6 days they will have halved all their estimates of subresource
// connections. Once this falls below kDiscardableExpectedValue the referrer
// will be discarded.
// TODO(jar): Measure size of referrer lists in the field. Consider an adaptive
// system that uses a higher trim ratio when the list is large.
// static
const double Predictor::kReferrerTrimRatio = 0.97153;
// static
const TimeDelta Predictor::kDurationBetweenTrimmings = TimeDelta::FromHours(1);
// static
const TimeDelta Predictor::kDurationBetweenTrimmingIncrements =
TimeDelta::FromSeconds(15);
// static
const size_t Predictor::kUrlsTrimmedPerIncrement = 5u;
class Predictor::LookupRequest {
public:
LookupRequest(Predictor* predictor,
net::HostResolver* host_resolver,
const GURL& url)
: ALLOW_THIS_IN_INITIALIZER_LIST(
net_callback_(this, &LookupRequest::OnLookupFinished)),
predictor_(predictor),
url_(url),
resolver_(host_resolver) {
}
// Return underlying network resolver status.
// net::OK ==> Host was found synchronously.
// net:ERR_IO_PENDING ==> Network will callback later with result.
// anything else ==> Host was not found synchronously.
int Start() {
net::HostResolver::RequestInfo resolve_info(
net::HostPortPair::FromURL(url_));
// Make a note that this is a speculative resolve request. This allows us
// to separate it from real navigations in the observer's callback, and
// lets the HostResolver know it can de-prioritize it.
resolve_info.set_is_speculative(true);
return resolver_.Resolve(
resolve_info, &addresses_, &net_callback_, net::BoundNetLog());
}
private:
void OnLookupFinished(int result) {
predictor_->OnLookupFinished(this, url_, result == net::OK);
}
// HostResolver will call us using this callback when resolution is complete.
net::CompletionCallbackImpl<LookupRequest> net_callback_;
Predictor* predictor_; // The predictor which started us.
const GURL url_; // Hostname to resolve.
net::SingleRequestHostResolver resolver_;
net::AddressList addresses_;
DISALLOW_COPY_AND_ASSIGN(LookupRequest);
};
Predictor::Predictor(net::HostResolver* host_resolver,
TimeDelta max_dns_queue_delay,
size_t max_concurrent,
bool preconnect_enabled)
: peak_pending_lookups_(0),
shutdown_(false),
max_concurrent_dns_lookups_(max_concurrent),
max_dns_queue_delay_(max_dns_queue_delay),
host_resolver_(host_resolver),
preconnect_enabled_(preconnect_enabled),
consecutive_omnibox_preconnect_count_(0),
next_trim_time_(base::TimeTicks::Now() + kDurationBetweenTrimmings),
ALLOW_THIS_IN_INITIALIZER_LIST(trim_task_factory_(this)) {
}
Predictor::~Predictor() {
DCHECK(shutdown_);
}
void Predictor::Shutdown() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK(!shutdown_);
shutdown_ = true;
std::set<LookupRequest*>::iterator it;
for (it = pending_lookups_.begin(); it != pending_lookups_.end(); ++it)
delete *it;
}
// Overloaded Resolve() to take a vector of names.
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);
}
}
// Basic Resolve() takes an invidual name, and adds it
// to the queue.
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));
DCHECK(referring_url == referring_url.GetWithEmptyPath());
DCHECK(target_url == target_url.GetWithEmptyPath());
if (referring_url.has_host()) {
referrers_[referring_url].SuggestHost(target_url);
// Possibly do some referrer trimming.
TrimReferrers();
}
}
enum SubresourceValue {
PRECONNECTION,
PRERESOLUTION,
TOO_NEW,
SUBRESOURCE_VALUE_MAX
};
void Predictor::AnticipateOmniboxUrl(const GURL& url, bool preconnectable) {
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_;
// The omnibox suggests a search URL (for which we can preconnect) after
// one or two characters are typed, even though such typing often (1 in
// 3?) becomes a real URL. This code waits till is has more evidence of a
// preconnectable URL (search URL) before forming a preconnection, so as
// to reduce the useless preconnect rate.
// Perchance this logic should be pushed back into the omnibox, where the
// actual characters typed, such as a space, can better forcast whether
// we need to search/preconnect or not. By waiting for at least 4
// characters in a row that have lead to a search proposal, we avoid
// preconnections for a prefix like "www." and we also wait until we have
// at least a 4 letter word to search for.
// Each character typed appears to induce 2 calls to
// AnticipateOmniboxUrl(), so we double 4 characters and limit at 8
// requests.
// TODO(jar): Use an A/B test to optimize this.
const int kMinConsecutiveRequests = 8;
if (consecutive_omnibox_preconnect_count_ >= kMinConsecutiveRequests) {
// TODO(jar): The wild guess of 30 seconds could be tuned/tested, but it
// currently is just a guess that most sockets will remain open for at
// least 30 seconds. This avoids a lot of cross-thread posting, and
// exercise of the network stack in this common case.
const int kMaxSearchKeepaliveSeconds(30);
if ((now - last_omnibox_preconnect_).InSeconds() <
kMaxSearchKeepaliveSeconds)
return; // We've done a preconnect recently.
last_omnibox_preconnect_ = now;
const int kConnectionsNeeded = 1;
PreconnectOnUIThread(CanonicalizeUrl(url), motivation,
kConnectionsNeeded);
return; // Skip pre-resolution, since we'll open a connection.
}
} else {
consecutive_omnibox_preconnect_count_ = 0;
}
}
// Fall through and consider pre-resolution.
// Omnibox tends to call in pairs (just a few milliseconds apart), and we
// really don't need to keep resolving a name that often.
// TODO(jar): A/B tests could check for perf impact of the early returns.
if (!is_new_host_request) {
const int kMinPreresolveSeconds(10);
if (kMinPreresolveSeconds > (now - last_omnibox_preresolve_).InSeconds())
return;
}
last_omnibox_preresolve_ = now;
// Perform at least DNS pre-resolution.
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
NewRunnableMethod(this, &Predictor::Resolve, CanonicalizeUrl(url),
motivation));
}
void Predictor::PreconnectUrlAndSubresources(const GURL& url) {
if (preconnect_enabled()) {
std::string host = url.HostNoBrackets();
UrlInfo::ResolutionMotivation motivation(UrlInfo::EARLY_LOAD_MOTIVATED);
const int kConnectionsNeeded = 1;
PreconnectOnUIThread(CanonicalizeUrl(url), motivation,
kConnectionsNeeded);
PredictFrameSubresources(url.GetWithEmptyPath());
}
}
void Predictor::PredictFrameSubresources(const GURL& url) {
DCHECK(url.GetWithEmptyPath() == url);
// Add one pass through the message loop to allow current navigation to
// proceed.
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
NewRunnableMethod(this, &Predictor::PrepareFrameSubresources, url));
}
void Predictor::PrepareFrameSubresources(const GURL& url) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK(url.GetWithEmptyPath() == url);
Referrers::iterator it = referrers_.find(url);
if (referrers_.end() == it) {
// Only when we don't know anything about this url, make 2 connections
// available. We could do this completely via learning (by prepopulating
// the referrer_ list with this expected value), but it would swell the
// size of the list with all the "Leaf" nodes in the tree (nodes that don't
// load any subresources). If we learn about this resource, we will instead
// provide a more carefully estimated preconnection count.
if (preconnect_enabled_)
PreconnectOnIOThread(url, 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;
PreconnectOnIOThread(future_url->first, 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);
}
}
// Provide sort order so all .com's are together, etc.
struct RightToLeftStringSorter {
bool operator()(const GURL& left,
const GURL& right) const {
return string_compare(left.host(), right.host());
}
static bool string_compare(const std::string& left_host,
const std::string& right_host) {
if (left_host == right_host) return true;
size_t left_already_matched = left_host.size();
size_t right_already_matched = right_host.size();
// Ensure both strings have characters.
if (!left_already_matched) return true;
if (!right_already_matched) return false;
// Watch for trailing dot, so we'll always be safe to go one beyond dot.
if ('.' == left_host[left_already_matched - 1]) {
if ('.' != right_host[right_already_matched - 1])
return true;
// Both have dots at end of string.
--left_already_matched;
--right_already_matched;
} else {
if ('.' == right_host[right_already_matched - 1])
return false;
}
while (1) {
if (!left_already_matched) return true;
if (!right_already_matched) return false;
size_t left_length, right_length;
size_t left_start = left_host.find_last_of('.', left_already_matched - 1);
if (std::string::npos == left_start) {
left_length = left_already_matched;
left_already_matched = left_start = 0;
} else {
left_length = left_already_matched - left_start;
left_already_matched = left_start;
++left_start; // Don't compare the dot.
}
size_t right_start = right_host.find_last_of('.',
right_already_matched - 1);
if (std::string::npos == right_start) {
right_length = right_already_matched;
right_already_matched = right_start = 0;
} else {
right_length = right_already_matched - right_start;
right_already_matched = right_start;
++right_start; // Don't compare the dot.
}
int diff = left_host.compare(left_start, left_host.size(),
right_host, right_start, right_host.size());
if (diff > 0) return false;
if (diff < 0) return true;
}
}
};
void Predictor::GetHtmlReferrerLists(std::string* output) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (referrers_.empty())
return;
// TODO(jar): Remove any plausible JavaScript from names before displaying.
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));
// Local lists for calling UrlInfo
UrlInfo::UrlInfoTable name_not_found;
UrlInfo::UrlInfoTable name_preresolved;
// Get copies of all useful data.
typedef std::map<GURL, UrlInfo, RightToLeftStringSorter> SortedUrlInfo;
SortedUrlInfo snapshot;
// UrlInfo supports value semantics, so we can do a shallow copy.
for (Results::iterator it(results_.begin()); it != results_.end(); it++)
snapshot[it->first] = it->second;
// Partition the UrlInfo's into categories.
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; // Still being processed.
name_preresolved.push_back(it->second);
}
bool brief = false;
#ifdef NDEBUG
brief = true;
#endif // NDEBUG
// Call for display of each table, along with title.
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);
}
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); // Initialize or DCHECK.
// TODO(jar): I need to discard names that have long since expired.
// Currently we only add to the domain map :-/
DCHECK(info->HasUrl(url));
if (!info->NeedsDnsUpdate()) {
info->DLogResultsStats("DNS PrefetchNotUpdated");
return NULL;
}
info->SetQueuedState(motivation);
work_queue_.Push(url, motivation);
StartSomeQueuedResolutions();
return info;
}
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) {
// Will complete asynchronously.
pending_lookups_.insert(request);
peak_pending_lookups_ = std::max(peak_pending_lookups_,
pending_lookups_.size());
} else {
// Completed synchronously (was already cached by HostResolver), or else
// there was (equivalently) some network error that prevents us from
// finding the name. Status net::OK means it was "found."
LookupFinished(request, url, status == net::OK);
delete request;
}
}
}
bool Predictor::CongestionControlPerformed(UrlInfo* info) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
// Note: queue_duration is ONLY valid after we go to assigned state.
if (info->queue_duration() < max_dns_queue_delay_)
return false;
// We need to discard all entries in our queue, as we're keeping them waiting
// too long. By doing this, we'll have a chance to quickly service urgent
// resolutions, and not have a bogged down system.
while (true) {
info->RemoveFromQueue();
if (work_queue_.IsEmpty())
break;
info = &results_[work_queue_.Pop()];
info->SetAssignedState();
}
return true;
}
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();
}
}
void Predictor::DiscardAllResults() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
// Delete anything listed so far in this session that shows in about:dns.
referrers_.clear();
// Try to delete anything in our work queue.
while (!work_queue_.IsEmpty()) {
// Emulate processing cycle as though host was not found.
GURL url = work_queue_.Pop();
UrlInfo* info = &results_[url];
DCHECK(info->HasUrl(url));
info->SetAssignedState();
info->SetNoSuchNameState();
}
// Now every result_ is either resolved, or is being resolved
// (see LookupRequest).
// Step through result_, recording names of all hosts that can't be erased.
// We can't erase anything being worked on.
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(assignees.size() <= max_concurrent_dns_lookups_);
results_.clear();
// Put back in the names being worked on.
for (Results::iterator it = assignees.begin(); assignees.end() != it; ++it) {
DCHECK(it->second.is_marked_to_delete());
results_[it->first] = it->second;
}
}
void Predictor::TrimReferrersNow() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
// Just finish up work if an incremental trim is in progress.
if (urls_being_trimmed_.empty())
LoadUrlsForTrimming();
IncrementalTrimReferrers(true); // Do everything now.
}
void Predictor::SerializeReferrers(ListValue* referral_list) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
referral_list->Clear();
referral_list->Append(new FundamentalValue(PREDICTOR_REFERRER_VERSION));
for (Referrers::const_iterator it = referrers_.begin();
it != referrers_.end(); ++it) {
// Serialize the list of subresource names.
Value* subresource_list(it->second.Serialize());
// Create a list for each referer.
ListValue* motivator(new ListValue);
motivator->Append(new StringValue(it->first.spec()));
motivator->Append(subresource_list);
referral_list->Append(motivator);
}
}
void Predictor::DeserializeReferrers(const 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 == PREDICTOR_REFERRER_VERSION) {
for (size_t i = 1; i < referral_list.GetSize(); ++i) {
ListValue* motivator;
if (!referral_list.GetList(i, &motivator)) {
NOTREACHED();
return;
}
std::string motivating_url_spec;
if (!motivator->GetString(0, &motivating_url_spec)) {
NOTREACHED();
return;
}
Value* subresource_list;
if (!motivator->Get(1, &subresource_list)) {
NOTREACHED();
return;
}
referrers_[GURL(motivating_url_spec)].Deserialize(*subresource_list);
}
}
}
void Predictor::TrimReferrers() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (!urls_being_trimmed_.empty())
return; // There is incremental trimming in progress already.
// Check to see if it is time to trim yet.
base::TimeTicks now = base::TimeTicks::Now();
if (now < next_trim_time_)
return;
next_trim_time_ = now + kDurationBetweenTrimmings;
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;
MessageLoop::current()->PostDelayedTask(
FROM_HERE,
trim_task_factory_.NewRunnableMethod(&Predictor::IncrementalTrimReferrers,
false),
kDurationBetweenTrimmingIncrements.InMilliseconds());
}
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; // Defensive code: It got trimmed away already.
if (!it->second.Trim(kReferrerTrimRatio, kDiscardableExpectedValue))
referrers_.erase(it);
}
PostIncrementalTrimTask();
}
//------------------------------------------------------------------------------
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;
}
void Predictor::DeserializeReferrersThenDelete(ListValue* referral_list) {
DeserializeReferrers(*referral_list);
delete referral_list;
}
//------------------------------------------------------------------------------
// Helper functions
//------------------------------------------------------------------------------
// static
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";
}
// If we omit a port, it will default to 80 or 443 as appropriate.
std::string colon_plus_port;
if (url.has_port())
colon_plus_port = ":" + url.port();
return GURL(scheme + "://" + url.host() + colon_plus_port);
}
} // namespace chrome_browser_net