Index: chrome/browser/net/predictor.cc |
=================================================================== |
--- chrome/browser/net/predictor.cc (revision 89645) |
+++ chrome/browser/net/predictor.cc (working copy) |
@@ -12,9 +12,15 @@ |
#include "base/compiler_specific.h" |
#include "base/metrics/histogram.h" |
#include "base/stringprintf.h" |
+#include "base/synchronization/waitable_event.h" |
#include "base/time.h" |
#include "base/values.h" |
#include "chrome/browser/net/preconnect.h" |
+#include "chrome/browser/prefs/pref_service.h" |
+#include "chrome/browser/prefs/scoped_user_pref_update.h" |
+#include "chrome/browser/prefs/session_startup_pref.h" |
+#include "chrome/browser/profiles/profile.h" |
+#include "chrome/common/pref_names.h" |
#include "content/browser/browser_thread.h" |
#include "net/base/address_list.h" |
#include "net/base/completion_callback.h" |
@@ -28,6 +34,8 @@ |
namespace chrome_browser_net { |
+static void DnsPrefetchMotivatedList(const UrlList& urls, |
+ UrlInfo::ResolutionMotivation motivation); |
// static |
const double Predictor::kPreconnectWorthyExpectedValue = 0.8; |
// static |
@@ -54,6 +62,10 @@ |
// static |
const size_t Predictor::kUrlsTrimmedPerIncrement = 5u; |
+// A version number for prefs that are saved. This should be incremented when |
+// we change the format so that we discard old data. |
+static const int kPredictorStartupFormatVersion = 1; |
+ |
class Predictor::LookupRequest { |
public: |
LookupRequest(Predictor* predictor, |
@@ -102,8 +114,12 @@ |
Predictor::Predictor(net::HostResolver* host_resolver, |
TimeDelta max_dns_queue_delay, |
size_t max_concurrent, |
- bool preconnect_enabled) |
- : peak_pending_lookups_(0), |
+ bool preconnect_enabled, |
+ bool predictor_enabled) |
+ : predictor_enabled_(true), |
+ off_the_record_windows_count_(0), |
+ on_the_record_(true), |
+ peak_pending_lookups_(0), |
shutdown_(false), |
max_concurrent_dns_lookups_(max_concurrent), |
max_dns_queue_delay_(max_dns_queue_delay), |
@@ -112,6 +128,7 @@ |
consecutive_omnibox_preconnect_count_(0), |
next_trim_time_(base::TimeTicks::Now() + kDurationBetweenTrimmings), |
ALLOW_THIS_IN_INITIALIZER_LIST(trim_task_factory_(this)) { |
+ initial_observer_.reset(new InitialObserver()); |
} |
Predictor::~Predictor() { |
@@ -151,6 +168,8 @@ |
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)); |
@@ -169,6 +188,11 @@ |
}; |
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; |
@@ -237,6 +261,10 @@ |
} |
void Predictor::PreconnectUrlAndSubresources(const GURL& url) { |
+ if (!predictor_enabled_) |
+ return; |
+ if (!url.is_valid() || !url.has_host()) |
+ return; |
if (preconnect_enabled()) { |
std::string host = url.HostNoBrackets(); |
UrlInfo::ResolutionMotivation motivation(UrlInfo::EARLY_LOAD_MOTIVATED); |
@@ -248,6 +276,8 @@ |
} |
void Predictor::PredictFrameSubresources(const GURL& url) { |
+ if (!predictor_enabled_) |
+ return; |
DCHECK_EQ(url.GetWithEmptyPath(), url); |
// Add one pass through the message loop to allow current navigation to |
// proceed. |
@@ -423,6 +453,11 @@ |
void Predictor::GetHtmlInfo(std::string* output) { |
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
+ if (initial_observer_.get()) |
+ initial_observer_->GetFirstResolutionsHtml(output); |
+ // Show list of subresource predictions and stats. |
+ GetHtmlReferrerLists(output); |
+ |
// Local lists for calling UrlInfo |
UrlInfo::UrlInfoTable name_not_found; |
UrlInfo::UrlInfoTable name_preresolved; |
@@ -703,8 +738,212 @@ |
PostIncrementalTrimTask(); |
} |
+void Predictor::DiscardInitialNavigationHistory() { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
+ if (initial_observer_.get()) |
+ initial_observer_->DiscardInitialNavigationHistory(); |
+} |
+ |
+void Predictor::FinalizeInitialization( |
+ const UrlList& startup_urls, |
+ ListValue* referral_list) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
+ |
+ // Prefetch these hostnames on startup. |
+ DnsPrefetchMotivatedList(startup_urls, UrlInfo::STARTUP_LIST_MOTIVATED); |
+ DeserializeReferrersThenDelete(referral_list); |
+} |
+ |
//------------------------------------------------------------------------------ |
+// 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. |
+//------------------------------------------------------------------------------ |
+void Predictor::LearnAboutInitialNavigation(const GURL& url) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
+ if (!predictor_enabled_ || NULL == initial_observer_.get() ) |
+ return; |
+ initial_observer_->Append(url, this); |
+} |
+ |
+// 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 Predictor::DnsPrefetchList(const NameList& hostnames) { |
+ // TODO(jar): Push GURL transport further back into renderer, but this will |
+ // require a Webkit change in the observer :-/. |
+ 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, |
+ NewRunnableMethod(this, &Predictor::ResolveList, urls, motivation)); |
+ } |
+} |
+ |
+//------------------------------------------------------------------------------ |
+// 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, |
+ Profile* profile) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
+ |
+ if (NULL == profile->GetPredictor()) { |
+ completion->Signal(); |
+ return; |
+ } |
+ profile->GetPredictor()->SaveDnsPrefetchStateForNextStartupAndTrim( |
+ startup_list, referral_list, completion); |
+} |
+ |
+void Predictor::SaveDnsPrefetchStateForNextStartupAndTrim( |
+ ListValue* startup_list, |
+ ListValue* referral_list, |
+ base::WaitableEvent* completion) { |
+ if (initial_observer_.get()) |
+ initial_observer_->GetInitialDnsResolutionList(startup_list); |
+ |
+ // Do at least one trim at shutdown, in case the user wasn't running long |
+ // enough to do any regular trimming of referrers. |
+ TrimReferrersNow(); |
+ SerializeReferrers(referral_list); |
+ |
+ completion->Signal(); |
+} |
+ |
+void Predictor::SaveStateForNextStartupAndTrim(PrefService* prefs, |
+ Profile* profile) { |
+ 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, |
+ profile); |
+ } else { |
+ bool posted = BrowserThread::PostTask( |
+ BrowserThread::IO, |
+ FROM_HERE, |
+ NewRunnableFunction( |
+ SaveDnsPrefetchStateForNextStartupAndTrimOnIOThread, |
+ update_startup_list.Get(), |
+ update_referral_list.Get(), |
+ &completion, |
+ profile)); |
+ |
+ // TODO(jar): Synchronous waiting for the IO thread is a potential source |
+ // to deadlocks and should be investigated. See http://crbug.com/78451. |
+ DCHECK(posted); |
+ if (posted) |
+ completion.Wait(); |
+ } |
+} |
+ |
+UrlList Predictor::GetPredictedUrlListAtStartup( |
+ PrefService* user_prefs, |
+ PrefService* local_state) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ UrlList urls; |
+ // Recall list of URLs 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). |
+ const ListValue* startup_list = |
+ user_prefs->GetList(prefs::kDnsPrefetchingStartupList); |
+ |
+ if (startup_list) { |
+ 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; // Format incompatibility. |
+ } |
+ GURL url(url_spec); |
+ if (!url.has_host() || !url.has_scheme()) { |
+ LOG(DFATAL); |
+ break; // Format incompatibility. |
+ } |
+ |
+ urls.push_back(url); |
+ } |
+ } |
+ } |
+ |
+ // 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.SchemeIsFile() || gurl.host().empty()) |
+ continue; |
+ if (gurl.SchemeIs("http") || gurl.SchemeIs("https")) |
+ urls.push_back(gurl.GetWithEmptyPath()); |
+ } |
+ } |
+ |
+ if (urls.empty()) |
+ urls.push_back(GURL("http://www.google.com:80")); |
+ |
+ return urls; |
+} |
+ |
+bool Predictor::HandleIncognitoBrowserClosed() { |
+ DCHECK_LT(0, off_the_record_windows_count_); |
+ if (0 >= off_the_record_windows_count_) // Defensive coding. |
+ return false; |
+ if (--off_the_record_windows_count_) |
+ return false; // Still some windows are incognito. |
+ return true; |
+} |
+ |
+void Predictor::HandleIncognitoBrowserOpened() { |
+ off_the_record_windows_count_++; |
+} |
+ |
+//------------------------------------------------------------------------------ |
+ |
Predictor::HostNameQueue::HostNameQueue() { |
} |
@@ -744,7 +983,59 @@ |
delete referral_list; |
} |
+//------------------------------------------------------------------------------ |
+// Member definitions for InitialObserver class. |
+void Predictor::InitialObserver::Append(const GURL& url, |
+ Predictor* predictor) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
+ |
+ // TODO(rlp): Do we really need the predictor check here? |
+ if (!predictor->on_the_record() || NULL == predictor) |
+ return; |
+ if (kStartupResolutionCount <= first_navigations_.size()) |
+ return; |
+ |
+ DCHECK(url.SchemeIs("http") || url.SchemeIs("https")); |
+ DCHECK_EQ(url, Predictor::CanonicalizeUrl(url)); |
+ if (first_navigations_.find(url) == first_navigations_.end()) |
+ first_navigations_[url] = base::TimeTicks::Now(); |
+} |
+ |
+void Predictor::InitialObserver::GetInitialDnsResolutionList( |
+ ListValue* startup_list) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
+ DCHECK(startup_list); |
+ startup_list->Clear(); |
+ DCHECK_EQ(0u, startup_list->GetSize()); |
+ startup_list->Append(new 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 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); |
+} |
+ |
//------------------------------------------------------------------------------ |
// Helper functions |
//------------------------------------------------------------------------------ |