blob: cf783573083dbe82c2d87925a8a0444773b18506 [file] [log] [blame]
// Copyright (c) 2006-2008 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/dns_global.h"
#include <map>
#include <string>
#include "base/singleton.h"
#include "base/stats_counters.h"
#include "base/string_util.h"
#include "base/thread.h"
#include "base/waitable_event.h"
#include "base/values.h"
#include "chrome/browser/browser.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_thread.h"
#include "chrome/browser/io_thread.h"
#include "chrome/browser/net/dns_host_info.h"
#include "chrome/browser/net/referrer.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/session_startup_pref.h"
#include "chrome/common/notification_registrar.h"
#include "chrome/common/notification_service.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/pref_service.h"
#include "net/base/host_resolver.h"
#include "net/base/host_resolver_impl.h"
using base::Time;
using base::TimeDelta;
namespace chrome_browser_net {
static void DnsMotivatedPrefetch(const std::string& hostname,
DnsHostInfo::ResolutionMotivation motivation);
static void DnsPrefetchMotivatedList(
const NameList& hostnames,
DnsHostInfo::ResolutionMotivation motivation);
static NameList GetDnsPrefetchHostNamesAtStartup(
PrefService* user_prefs, PrefService* local_state);
// static
const size_t DnsGlobalInit::kMaxPrefetchConcurrentLookups = 8;
// static
const int DnsGlobalInit::kMaxPrefetchQueueingDelayMs = 500;
//------------------------------------------------------------------------------
// This section contains all the globally accessable API entry points for the
// DNS Prefetching feature.
//------------------------------------------------------------------------------
// Status of prefetch feature, controlling whether any prefetching is done.
static bool dns_prefetch_enabled = true;
// Cached inverted copy of the off_the_record pref.
static bool on_the_record_switch = true;
// Enable/disable Dns prefetch activity (either via command line, or via pref).
void EnableDnsPrefetch(bool enable) {
// NOTE: this is invoked on the UI thread.
dns_prefetch_enabled = enable;
}
void OnTheRecord(bool enable) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
if (on_the_record_switch == enable)
return;
on_the_record_switch = enable;
if (on_the_record_switch)
g_browser_process->io_thread()->ChangedToOnTheRecord();
}
void RegisterPrefs(PrefService* local_state) {
local_state->RegisterListPref(prefs::kDnsStartupPrefetchList);
local_state->RegisterListPref(prefs::kDnsHostReferralList);
}
void RegisterUserPrefs(PrefService* user_prefs) {
user_prefs->RegisterBooleanPref(prefs::kDnsPrefetchingEnabled, true);
}
// When enabled, we use the following instance to service all requests in the
// browser process.
// TODO(willchan): Look at killing this.
static DnsMaster* dns_master = NULL;
// This API is only used in the browser process.
// It is called from an IPC message originating in the renderer. It currently
// includes both Page-Scan, and Link-Hover prefetching.
// TODO(jar): Separate out link-hover prefetching, and page-scan results.
void DnsPrefetchList(const NameList& hostnames) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
DnsPrefetchMotivatedList(hostnames, DnsHostInfo::PAGE_SCAN_MOTIVATED);
}
static void DnsPrefetchMotivatedList(
const NameList& hostnames,
DnsHostInfo::ResolutionMotivation motivation) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI) ||
ChromeThread::CurrentlyOn(ChromeThread::IO));
if (!dns_prefetch_enabled || NULL == dns_master)
return;
if (ChromeThread::CurrentlyOn(ChromeThread::IO)) {
dns_master->ResolveList(hostnames, motivation);
} else {
ChromeThread::PostTask(
ChromeThread::IO,
FROM_HERE,
NewRunnableMethod(dns_master,
&DnsMaster::ResolveList, hostnames, motivation));
}
}
// This API is used by the autocomplete popup box (where URLs are typed).
void DnsPrefetchUrl(const GURL& url) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
if (!dns_prefetch_enabled || NULL == dns_master)
return;
if (url.is_valid())
DnsMotivatedPrefetch(url.host(), DnsHostInfo::OMNIBOX_MOTIVATED);
}
static void DnsMotivatedPrefetch(const std::string& hostname,
DnsHostInfo::ResolutionMotivation motivation) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
if (!dns_prefetch_enabled || NULL == dns_master || !hostname.size())
return;
ChromeThread::PostTask(
ChromeThread::IO,
FROM_HERE,
NewRunnableMethod(dns_master,
&DnsMaster::Resolve, hostname, motivation));
}
//------------------------------------------------------------------------------
// This section intermingles prefetch results with actual browser HTTP
// network activity. It supports calculating of the benefit of a prefetch, as
// well as recording what prefetched hostname resolutions might be potentially
// helpful during the next chrome-startup.
//------------------------------------------------------------------------------
// This function determines if there was a saving by prefetching the hostname
// for which the navigation_info is supplied.
static bool AccruePrefetchBenefits(const GURL& referrer,
DnsHostInfo* navigation_info) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
if (!dns_prefetch_enabled || NULL == dns_master)
return false;
return dns_master->AccruePrefetchBenefits(referrer, navigation_info);
}
// When we navigate, we may know in advance some other domains that will need to
// be resolved. This function initiates those side effects.
static void NavigatingTo(const std::string& host_name) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
if (!dns_prefetch_enabled || NULL == dns_master)
return;
dns_master->NavigatingTo(host_name);
}
// The observer class needs to connect starts and finishes of HTTP network
// resolutions. We use the following type for that map.
typedef std::map<int, DnsHostInfo> ObservedResolutionMap;
// There will only be one instance ever created of the following Observer
// class. The PrefetchObserver lives on the IO thread, and intercepts DNS
// resolutions made by the network stack.
class PrefetchObserver : public net::HostResolver::Observer {
public:
// net::HostResolver::Observer implementation:
virtual void OnStartResolution(
int request_id,
const net::HostResolver::RequestInfo& request_info);
virtual void OnFinishResolutionWithStatus(
int request_id,
bool was_resolved,
const net::HostResolver::RequestInfo& request_info);
virtual void OnCancelResolution(
int request_id,
const net::HostResolver::RequestInfo& request_info);
void DnsGetFirstResolutionsHtml(std::string* output);
void GetInitialDnsResolutionList(ListValue* startup_list);
private:
void StartupListAppend(const DnsHostInfo& navigation_info);
// Map of pending resolutions seen by observer.
ObservedResolutionMap resolutions_;
// List of the first N hostname resolutions observed in this run.
Results first_resolutions_;
// The number of hostnames we'll save for prefetching at next startup.
static const size_t kStartupResolutionCount = 10;
};
// TODO(willchan): Look at killing this global.
static PrefetchObserver* g_prefetch_observer = NULL;
//------------------------------------------------------------------------------
// Member definitions for above Observer class.
void PrefetchObserver::OnStartResolution(
int request_id,
const net::HostResolver::RequestInfo& request_info) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
if (request_info.is_speculative())
return; // One of our own requests.
DCHECK_NE(0U, request_info.hostname().length());
DnsHostInfo navigation_info;
navigation_info.SetHostname(request_info.hostname());
navigation_info.SetStartedState();
// This entry will be deleted either by OnFinishResolutionWithStatus(), or
// by OnCancelResolution().
resolutions_[request_id] = navigation_info;
}
void PrefetchObserver::OnFinishResolutionWithStatus(
int request_id,
bool was_resolved,
const net::HostResolver::RequestInfo& request_info) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
if (request_info.is_speculative())
return; // One of our own requests.
DnsHostInfo navigation_info;
size_t startup_count = 0;
{
ObservedResolutionMap::iterator it = resolutions_.find(request_id);
if (resolutions_.end() == it) {
DCHECK(false);
return;
}
navigation_info = it->second;
resolutions_.erase(it);
startup_count = first_resolutions_.size();
}
navigation_info.SetFinishedState(was_resolved); // Get timing info
AccruePrefetchBenefits(request_info.referrer(), &navigation_info);
// Handle sub-resource resolutions now that the critical navigational
// resolution has completed. This prevents us from in any way delaying that
// navigational resolution.
NavigatingTo(request_info.hostname());
if (kStartupResolutionCount <= startup_count || !was_resolved)
return;
// TODO(jar): Don't add host to our list if it is a non-linked lookup, and
// instead rely on Referrers to pull this in automatically with the enclosing
// page load (once we start to persist elements of our referrer tree).
StartupListAppend(navigation_info);
}
void PrefetchObserver::OnCancelResolution(
int request_id,
const net::HostResolver::RequestInfo& request_info) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
if (request_info.is_speculative())
return; // One of our own requests.
// Remove the entry from |resolutions| that was added by OnStartResolution().
ObservedResolutionMap::iterator it = resolutions_.find(request_id);
if (resolutions_.end() == it) {
DCHECK(false);
return;
}
resolutions_.erase(it);
}
void PrefetchObserver::StartupListAppend(const DnsHostInfo& navigation_info) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
if (!on_the_record_switch || NULL == dns_master)
return;
if (kStartupResolutionCount <= first_resolutions_.size())
return; // Someone just added the last item.
std::string host_name = navigation_info.hostname();
if (first_resolutions_.find(host_name) != first_resolutions_.end())
return; // We already have this hostname listed.
first_resolutions_[host_name] = navigation_info;
}
void PrefetchObserver::GetInitialDnsResolutionList(ListValue* startup_list) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
DCHECK(startup_list);
startup_list->Clear();
DCHECK_EQ(0u, startup_list->GetSize());
for (Results::iterator it = first_resolutions_.begin();
it != first_resolutions_.end();
it++) {
const std::string hostname = it->first;
startup_list->Append(Value::CreateStringValue(hostname));
}
}
void PrefetchObserver::DnsGetFirstResolutionsHtml(std::string* output) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
DnsHostInfo::DnsInfoTable resolution_list;
{
for (Results::iterator it(first_resolutions_.begin());
it != first_resolutions_.end();
it++) {
resolution_list.push_back(it->second);
}
}
DnsHostInfo::GetHtmlTable(resolution_list,
"Future startups will prefetch DNS records for ", false, output);
}
//------------------------------------------------------------------------------
// Support observer to detect opening and closing of OffTheRecord windows.
// This object lives on the UI thread.
class OffTheRecordObserver : public NotificationObserver {
public:
void Register() {
// TODO(pkasting): This test should not be necessary. See crbug.com/12475.
if (registrar_.IsEmpty()) {
registrar_.Add(this, NotificationType::BROWSER_CLOSED,
NotificationService::AllSources());
registrar_.Add(this, NotificationType::BROWSER_OPENED,
NotificationService::AllSources());
}
}
void Observe(NotificationType type, const NotificationSource& source,
const NotificationDetails& details) {
switch (type.value) {
case NotificationType::BROWSER_OPENED:
if (!Source<Browser>(source)->profile()->IsOffTheRecord())
break;
++count_off_the_record_windows_;
OnTheRecord(false);
break;
case NotificationType::BROWSER_CLOSED:
if (!Source<Browser>(source)->profile()->IsOffTheRecord())
break; // Ignore ordinary windows.
DCHECK_LT(0, count_off_the_record_windows_);
if (0 >= count_off_the_record_windows_) // Defensive coding.
break;
if (--count_off_the_record_windows_)
break; // Still some windows are incognito.
OnTheRecord(true);
break;
default:
break;
}
}
private:
friend struct DefaultSingletonTraits<OffTheRecordObserver>;
OffTheRecordObserver() : count_off_the_record_windows_(0) {}
~OffTheRecordObserver() {}
NotificationRegistrar registrar_;
int count_off_the_record_windows_;
DISALLOW_COPY_AND_ASSIGN(OffTheRecordObserver);
};
//------------------------------------------------------------------------------
// This section supports the about:dns page.
//------------------------------------------------------------------------------
// Provide global support for the about:dns page.
void DnsPrefetchGetHtmlInfo(std::string* output) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
output->append("<html><head><title>About DNS</title>"
// We'd like the following no-cache... but it doesn't work.
// "<META HTTP-EQUIV=\"Pragma\" CONTENT=\"no-cache\">"
"</head><body>");
if (!dns_prefetch_enabled || NULL == dns_master) {
output->append("Dns Prefetching is disabled.");
} else {
if (!on_the_record_switch) {
output->append("Incognito mode is active in a window.");
} else {
dns_master->GetHtmlInfo(output);
g_prefetch_observer->DnsGetFirstResolutionsHtml(output);
dns_master->GetHtmlReferrerLists(output);
}
}
output->append("</body></html>");
}
//------------------------------------------------------------------------------
// This section intializes global DNS prefetch services.
//------------------------------------------------------------------------------
static void InitDnsPrefetch(TimeDelta max_queue_delay, size_t max_concurrent,
PrefService* user_prefs, PrefService* local_state) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
bool prefetching_enabled =
user_prefs->GetBoolean(prefs::kDnsPrefetchingEnabled);
// Gather the list of hostnames to prefetch on startup.
NameList hostnames =
GetDnsPrefetchHostNamesAtStartup(user_prefs, local_state);
ListValue* referral_list =
static_cast<ListValue*>(
local_state->GetMutableList(prefs::kDnsHostReferralList)->DeepCopy());
g_browser_process->io_thread()->InitDnsMaster(
prefetching_enabled, max_queue_delay, max_concurrent, hostnames,
referral_list);
}
void FinalizeDnsPrefetchInitialization(
DnsMaster* global_dns_master,
net::HostResolver::Observer* global_prefetch_observer,
const NameList& hostnames_to_prefetch,
ListValue* referral_list) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
dns_master = global_dns_master;
g_prefetch_observer =
static_cast<PrefetchObserver*>(global_prefetch_observer);
DLOG(INFO) << "DNS Prefetch service started";
// Prefetch these hostnames on startup.
DnsPrefetchMotivatedList(hostnames_to_prefetch,
DnsHostInfo::STARTUP_LIST_MOTIVATED);
dns_master->DeserializeReferrersThenDelete(referral_list);
}
void FreeDnsPrefetchResources() {
dns_master = NULL;
g_prefetch_observer = NULL;
}
//------------------------------------------------------------------------------
net::HostResolver::Observer* CreatePrefetchObserver() {
return new PrefetchObserver();
}
//------------------------------------------------------------------------------
// Functions to handle saving of hostnames from one session to the next, to
// expedite startup times.
static void SaveDnsPrefetchStateForNextStartupAndTrimOnIOThread(
ListValue* startup_list,
ListValue* referral_list,
base::WaitableEvent* completion) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
if (NULL == dns_master) {
completion->Signal();
return;
}
g_prefetch_observer->GetInitialDnsResolutionList(startup_list);
// TODO(jar): Trimming should be done more regularly, such as every 48 hours
// of physical time, or perhaps after 48 hours of running (excluding time
// between sessions possibly).
// For now, we'll just trim at shutdown.
dns_master->TrimReferrers();
dns_master->SerializeReferrers(referral_list);
completion->Signal();
}
void SaveDnsPrefetchStateForNextStartupAndTrim(PrefService* prefs) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
if (!dns_prefetch_enabled || dns_master == NULL)
return;
base::WaitableEvent completion(true, false);
bool posted = ChromeThread::PostTask(
ChromeThread::IO,
FROM_HERE,
NewRunnableFunction(SaveDnsPrefetchStateForNextStartupAndTrimOnIOThread,
prefs->GetMutableList(prefs::kDnsStartupPrefetchList),
prefs->GetMutableList(prefs::kDnsHostReferralList),
&completion));
DCHECK(posted);
if (posted)
completion.Wait();
}
static NameList GetDnsPrefetchHostNamesAtStartup(PrefService* user_prefs,
PrefService* local_state) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
NameList hostnames;
// Prefetch DNS for hostnames we learned about during last session.
// This may catch secondary hostnames, pulled in by the homepages. It will
// also catch more of the "primary" home pages, since that was (presumably)
// rendered first (and will be rendered first this time too).
ListValue* startup_list =
local_state->GetMutableList(prefs::kDnsStartupPrefetchList);
if (startup_list) {
for (ListValue::iterator it = startup_list->begin();
it != startup_list->end();
it++) {
std::string hostname;
(*it)->GetAsString(&hostname);
hostnames.push_back(hostname);
}
}
// Prepare for any static home page(s) the user has in prefs. The user may
// have a LOT of tab's specified, so we may as well try to warm them all.
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.host().empty())
hostnames.push_back(gurl.host());
}
}
if (hostnames.empty())
hostnames.push_back("www.google.com");
return hostnames;
}
//------------------------------------------------------------------------------
// Methods for the helper class that is used to startup and teardown the whole
// DNS prefetch system.
DnsGlobalInit::DnsGlobalInit(PrefService* user_prefs,
PrefService* local_state) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
// Set up a field trial to see what disabling DNS pre-resolution does to
// latency of page loads.
FieldTrial::Probability kDivisor = 100;
// For each option (i.e., non-default), we have a fixed probability.
FieldTrial::Probability kProbabilityPerGroup = 3; // 3% probability.
trial_ = new FieldTrial("DnsImpact", kDivisor);
// First option is to disable prefetching completely.
int disabled_prefetch = trial_->AppendGroup("_disabled_prefetch",
kProbabilityPerGroup);
// Set congestion detection at 250, 500, or 750ms, rather than the 1 second
// default.
int max_250ms_prefetch = trial_->AppendGroup("_max_250ms_queue_prefetch",
kProbabilityPerGroup);
int max_500ms_prefetch = trial_->AppendGroup("_max_500ms_queue_prefetch",
kProbabilityPerGroup);
int max_750ms_prefetch = trial_->AppendGroup("_max_750ms_queue_prefetch",
kProbabilityPerGroup);
// Set congestion detection at 2 seconds instead of the 1 second default.
int max_2s_prefetch = trial_->AppendGroup("_max_2s_queue_prefetch",
kProbabilityPerGroup);
trial_->AppendGroup("_default_enabled_prefetch",
FieldTrial::kAllRemainingProbability);
// We will register the incognito observer regardless of whether prefetching
// is enabled, as it is also used to clear the host cache.
Singleton<OffTheRecordObserver>::get()->Register();
if (trial_->group() != disabled_prefetch) {
// Initialize the DNS prefetch system.
size_t max_concurrent = kMaxPrefetchConcurrentLookups;
int max_queueing_delay_ms = kMaxPrefetchQueueingDelayMs;
if (trial_->group() == max_250ms_prefetch)
max_queueing_delay_ms = 250;
else if (trial_->group() == max_500ms_prefetch)
max_queueing_delay_ms = 500;
else if (trial_->group() == max_750ms_prefetch)
max_queueing_delay_ms = 750;
else if (trial_->group() == max_2s_prefetch)
max_queueing_delay_ms = 2000;
TimeDelta max_queueing_delay(
TimeDelta::FromMilliseconds(max_queueing_delay_ms));
DCHECK(!dns_master);
InitDnsPrefetch(max_queueing_delay, max_concurrent, user_prefs,
local_state);
}
}
} // namespace chrome_browser_net