Support speculative pre-connection to search URLs

Implement several flavors of TCP/IP speculative preconnection
under a command line flag (not yet on by default).

The first area of preconnection takes place when a user types
a query into the omnibox, as we preconnect to the search service
when the omnibox suggests it is going to do a search.

The second area involves subresources, such as images.
When a navigation takes place, and we've seen navigations
to that domain/port before, and the history-based 
probabability that we'll need to make a connection to
a second site (host/port) is sufficiently large, then we
preconnect to that second site while we are still connecting
to the primary site (and before we've gotten content from
the primary site.

We also fall-back to mere DNS pre-resolution of subresource
hostnames when the probability of a connection to the
subresource is not high enough.

BUG=42694
r=pkasting,willchan,mbelshe
Review URL: http://codereview.chromium.org/1585029

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@47479 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/autocomplete/autocomplete_edit.cc b/chrome/browser/autocomplete/autocomplete_edit.cc
index 90601530..a650e1d 100644
--- a/chrome/browser/autocomplete/autocomplete_edit.cc
+++ b/chrome/browser/autocomplete/autocomplete_edit.cc
@@ -569,6 +569,25 @@
   return true;
 }
 
+// Return true if the suggestion type warrants a TCP/IP preconnection.
+// i.e., it is now highly likely that the user will select the related domain.
+static bool IsPreconnectable(AutocompleteMatch::Type type) {
+  UMA_HISTOGRAM_ENUMERATION("Autocomplete.MatchType", type,
+                            AutocompleteMatch::NUM_TYPES);
+  switch (type) {
+    // Matches using the user's default search engine.
+    case AutocompleteMatch::SEARCH_WHAT_YOU_TYPED:
+    case AutocompleteMatch::SEARCH_HISTORY:
+    case AutocompleteMatch::SEARCH_SUGGEST:
+    // A match that uses a non-default search engine (e.g. for tab-to-search).
+    case AutocompleteMatch::SEARCH_OTHER_ENGINE:
+      return true;
+
+    default:
+      return false;
+  }
+}
+
 void AutocompleteEditModel::Observe(NotificationType type,
                                     const NotificationSource& source,
                                     const NotificationDetails& details) {
@@ -588,7 +607,8 @@
           match->fill_into_edit.substr(match->inline_autocomplete_offset);
     }
     // Warm up DNS Prefetch Cache.
-    chrome_browser_net::DnsPrefetchUrl(match->destination_url);
+    chrome_browser_net::DnsPrefetchUrl(match->destination_url,
+                                       IsPreconnectable(match->type));
     // We could prefetch the alternate nav URL, if any, but because there
     // can be many of these as a user types an initial series of characters,
     // the OS DNS cache could suffer eviction problems for minimal gain.
diff --git a/chrome/browser/browser_main.cc b/chrome/browser/browser_main.cc
index 5d05685f..75d74c4 100644
--- a/chrome/browser/browser_main.cc
+++ b/chrome/browser/browser_main.cc
@@ -1077,7 +1077,10 @@
 
   // Initialize and maintain DNS prefetcher module. Also registers an observer
   // to clear the host cache when closing incognito mode.
-  chrome_browser_net::DnsGlobalInit dns_prefetch(user_prefs, local_state);
+  chrome_browser_net::DnsGlobalInit dns_prefetch(
+      user_prefs,
+      local_state,
+      parsed_command_line.HasSwitch(switches::kEnablePreconnect));
 
 #if defined(OS_WIN)
   win_util::ScopedCOMInitializer com_initializer;
diff --git a/chrome/browser/browser_process_impl.cc b/chrome/browser/browser_process_impl.cc
index 75a47c2..9c118bb4 100644
--- a/chrome/browser/browser_process_impl.cc
+++ b/chrome/browser/browser_process_impl.cc
@@ -211,7 +211,7 @@
 
 unsigned int BrowserProcessImpl::ReleaseModule() {
   DCHECK(CalledOnValidThread());
-  DCHECK(0 != module_ref_count_);
+  DCHECK_NE(0u, module_ref_count_);
   module_ref_count_--;
   if (0 == module_ref_count_) {
     MessageLoop::current()->PostTask(
@@ -459,7 +459,7 @@
 #if (defined(OS_WIN) || defined(OS_LINUX)) && !defined(OS_CHROMEOS)
 void BrowserProcessImpl::StartAutoupdateTimer() {
   autoupdate_timer_.Start(
-      TimeDelta::FromHours(kUpdateCheckIntervalHours),
+      base::TimeDelta::FromHours(kUpdateCheckIntervalHours),
       this,
       &BrowserProcessImpl::OnAutoupdateTimer);
 }
diff --git a/chrome/browser/io_thread.cc b/chrome/browser/io_thread.cc
index 74367504..ca5151b 100644
--- a/chrome/browser/io_thread.cc
+++ b/chrome/browser/io_thread.cc
@@ -110,7 +110,8 @@
     base::TimeDelta max_queue_delay,
     size_t max_concurrent,
     const chrome_common_net::NameList& hostnames_to_prefetch,
-    ListValue* referral_list) {
+    ListValue* referral_list,
+    bool preconnect_enabled) {
   DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
   message_loop()->PostTask(
       FROM_HERE,
@@ -118,7 +119,7 @@
           this,
           &IOThread::InitDnsMasterOnIOThread,
           prefetching_enabled, max_queue_delay, max_concurrent,
-          hostnames_to_prefetch, referral_list));
+          hostnames_to_prefetch, referral_list, preconnect_enabled));
 }
 
 void IOThread::ChangedToOnTheRecord() {
@@ -250,14 +251,18 @@
     base::TimeDelta max_queue_delay,
     size_t max_concurrent,
     chrome_common_net::NameList hostnames_to_prefetch,
-    ListValue* referral_list) {
+    ListValue* referral_list,
+    bool preconnect_enabled) {
   DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
   CHECK(!dns_master_);
 
   chrome_browser_net::EnableDnsPrefetch(prefetching_enabled);
 
   dns_master_ = new chrome_browser_net::DnsMaster(
-      globals_->host_resolver, max_queue_delay, max_concurrent);
+      globals_->host_resolver,
+      max_queue_delay,
+      max_concurrent,
+      preconnect_enabled);
   dns_master_->AddRef();
 
   DCHECK(!prefetch_observer_);
diff --git a/chrome/browser/io_thread.h b/chrome/browser/io_thread.h
index a2860ac..c624a99e 100644
--- a/chrome/browser/io_thread.h
+++ b/chrome/browser/io_thread.h
@@ -53,7 +53,8 @@
                      base::TimeDelta max_queue_delay,
                      size_t max_concurrent,
                      const chrome_common_net::NameList& hostnames_to_prefetch,
-                     ListValue* referral_list);
+                     ListValue* referral_list,
+                     bool preconnect_enabled);
 
   // Handles changing to On The Record mode.  Posts a task for this onto the
   // IOThread's message loop.
@@ -72,7 +73,8 @@
       base::TimeDelta max_queue_delay,
       size_t max_concurrent,
       chrome_common_net::NameList hostnames_to_prefetch,
-      ListValue* referral_list);
+      ListValue* referral_list,
+      bool preconnect_enabled);
 
   void ChangedToOnTheRecordOnIOThread();
 
diff --git a/chrome/browser/net/dns_global.cc b/chrome/browser/net/dns_global.cc
index 88da39e..f57e0293 100644
--- a/chrome/browser/net/dns_global.cc
+++ b/chrome/browser/net/dns_global.cc
@@ -9,6 +9,7 @@
 
 #include "base/singleton.h"
 #include "base/stats_counters.h"
+#include "base/stl_util-inl.h"
 #include "base/string_util.h"
 #include "base/thread.h"
 #include "base/waitable_event.h"
@@ -18,6 +19,7 @@
 #include "chrome/browser/chrome_thread.h"
 #include "chrome/browser/io_thread.h"
 #include "chrome/browser/net/dns_host_info.h"
+#include "chrome/browser/net/preconnect.h"
 #include "chrome/browser/net/referrer.h"
 #include "chrome/browser/pref_service.h"
 #include "chrome/browser/profile.h"
@@ -33,10 +35,10 @@
 
 namespace chrome_browser_net {
 
-static void DnsMotivatedPrefetch(const std::string& hostname,
-                                 DnsHostInfo::ResolutionMotivation motivation);
-static void DnsPrefetchMotivatedList(
-    const NameList& hostnames,
+static void DnsMotivatedPrefetch(const net::HostPortPair& hostport,
+    DnsHostInfo::ResolutionMotivation motivation);
+
+static void DnsPrefetchMotivatedList(const NameList& hostnames,
     DnsHostInfo::ResolutionMotivation motivation);
 
 static NameList GetDnsPrefetchHostNamesAtStartup(
@@ -48,6 +50,10 @@
 // static
 const int DnsGlobalInit::kMaxPrefetchQueueingDelayMs = 500;
 
+// 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 kDnsStartupFormatVersion = 0;
+
 //------------------------------------------------------------------------------
 // This section contains all the globally accessable API entry points for the
 // DNS Prefetching feature.
@@ -117,25 +123,62 @@
 }
 
 // This API is used by the autocomplete popup box (where URLs are typed).
-void DnsPrefetchUrl(const GURL& url) {
+void DnsPrefetchUrl(const GURL& url, bool preconnectable) {
   DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
   if (!dns_prefetch_enabled || NULL == dns_master)
     return;
-  if (url.is_valid())
-    DnsMotivatedPrefetch(url.host(), DnsHostInfo::OMNIBOX_MOTIVATED);
+  if (!url.is_valid())
+    return;
+
+  static std::string last_host;
+  std::string host = url.HostNoBrackets();
+  bool is_new_host_request = (host != last_host);
+  last_host = host;
+
+  // 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.
+  static base::TimeTicks last_prefetch_for_host;
+  base::TimeTicks now = base::TimeTicks::Now();
+  if (!is_new_host_request) {
+    const int kMinPreresolveSeconds(10);
+    if (kMinPreresolveSeconds > (now - last_prefetch_for_host).InSeconds())
+      return;
+  }
+  last_prefetch_for_host = now;
+
+  net::HostPortPair hostport(url.HostNoBrackets(), url.EffectiveIntPort());
+
+  if (dns_master->preconnect_enabled() && preconnectable) {
+    static base::TimeTicks last_keepalive;
+    // 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.
+    const int kMaxSearchKeepaliveSeconds(30);
+    if ((now - last_keepalive).InSeconds() < kMaxSearchKeepaliveSeconds)
+      return;
+    last_keepalive = now;
+
+    if (Preconnect::PreconnectOnUIThread(hostport))
+      return;  // Skip pre-resolution, since we'll open a connection.
+  }
+
+  // Perform at least DNS pre-resolution.
+  // TODO(jar): We could propogate a hostport here instead of a host.
+  DnsMotivatedPrefetch(hostport, DnsHostInfo::OMNIBOX_MOTIVATED);
 }
 
-static void DnsMotivatedPrefetch(const std::string& hostname,
+static void DnsMotivatedPrefetch(const net::HostPortPair& hostport,
                                  DnsHostInfo::ResolutionMotivation motivation) {
   DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
-  if (!dns_prefetch_enabled || NULL == dns_master || !hostname.size())
+  if (!dns_prefetch_enabled || NULL == dns_master || hostport.host.empty())
     return;
 
   ChromeThread::PostTask(
       ChromeThread::IO,
       FROM_HERE,
       NewRunnableMethod(dns_master,
-                        &DnsMaster::Resolve, hostname, motivation));
+                        &DnsMaster::Resolve, hostport, motivation));
 }
 
 //------------------------------------------------------------------------------
@@ -152,16 +195,18 @@
   DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
   if (!dns_prefetch_enabled || NULL == dns_master)
     return false;
-  return dns_master->AccruePrefetchBenefits(referrer, navigation_info);
+  return dns_master->AccruePrefetchBenefits(
+      net::HostPortPair(referrer.host(), referrer.EffectiveIntPort()),
+      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) {
+static void NavigatingTo(const net::HostPortPair& hostport) {
   DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
   if (!dns_prefetch_enabled || NULL == dns_master)
     return;
-  dns_master->NavigatingTo(host_name);
+  dns_master->NavigatingTo(hostport);
 }
 
 // The observer class needs to connect starts and finishes of HTTP network
@@ -173,6 +218,8 @@
 // resolutions made by the network stack.
 class PrefetchObserver : public net::HostResolver::Observer {
  public:
+  typedef std::map<net::HostPortPair, DnsHostInfo> FirstResolutionMap;
+
   // net::HostResolver::Observer implementation:
   virtual void OnStartResolution(
       int request_id,
@@ -194,7 +241,7 @@
   // Map of pending resolutions seen by observer.
   ObservedResolutionMap resolutions_;
   // List of the first N hostname resolutions observed in this run.
-  Results first_resolutions_;
+  FirstResolutionMap first_resolutions_;
   // The number of hostnames we'll save for prefetching at next startup.
   static const size_t kStartupResolutionCount = 10;
 };
@@ -212,8 +259,10 @@
   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.SetHostname(net::HostPortPair(request_info.hostname(),
+                                                request_info.port()));
   navigation_info.SetStartedState();
 
   // This entry will be deleted either by OnFinishResolutionWithStatus(), or
@@ -233,7 +282,7 @@
   {
     ObservedResolutionMap::iterator it = resolutions_.find(request_id);
     if (resolutions_.end() == it) {
-      DCHECK(false);
+      NOTREACHED();
       return;
     }
     navigation_info = it->second;
@@ -247,7 +296,7 @@
   // 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());
+  NavigatingTo(net::HostPortPair(request_info.hostname(), request_info.port()));
 
   if (kStartupResolutionCount <= startup_count || !was_resolved)
     return;
@@ -267,7 +316,7 @@
   // Remove the entry from |resolutions| that was added by OnStartResolution().
   ObservedResolutionMap::iterator it = resolutions_.find(request_id);
   if (resolutions_.end() == it) {
-    DCHECK(false);
+    NOTREACHED();
     return;
   }
   resolutions_.erase(it);
@@ -280,10 +329,9 @@
     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())
+  if (ContainsKey(first_resolutions_, navigation_info.hostport()))
     return;  // We already have this hostname listed.
-  first_resolutions_[host_name] = navigation_info;
+  first_resolutions_[navigation_info.hostport()] = navigation_info;
 }
 
 void PrefetchObserver::GetInitialDnsResolutionList(ListValue* startup_list) {
@@ -291,11 +339,12 @@
   DCHECK(startup_list);
   startup_list->Clear();
   DCHECK_EQ(0u, startup_list->GetSize());
-  for (Results::iterator it = first_resolutions_.begin();
+  startup_list->Append(new FundamentalValue(kDnsStartupFormatVersion));
+  for (FirstResolutionMap::iterator it = first_resolutions_.begin();
        it != first_resolutions_.end();
-       it++) {
-    const std::string hostname = it->first;
-    startup_list->Append(Value::CreateStringValue(hostname));
+       ++it) {
+    startup_list->Append(new StringValue(it->first.host));
+    startup_list->Append(new FundamentalValue(it->first.port));
   }
 }
 
@@ -304,7 +353,7 @@
 
   DnsHostInfo::DnsInfoTable resolution_list;
   {
-    for (Results::iterator it(first_resolutions_.begin());
+    for (FirstResolutionMap::iterator it(first_resolutions_.begin());
          it != first_resolutions_.end();
          it++) {
       resolution_list.push_back(it->second);
@@ -398,7 +447,8 @@
 //------------------------------------------------------------------------------
 
 static void InitDnsPrefetch(TimeDelta max_queue_delay, size_t max_concurrent,
-                            PrefService* user_prefs, PrefService* local_state) {
+    PrefService* user_prefs, PrefService* local_state,
+    bool preconnect_enabled) {
   DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
 
   bool prefetching_enabled =
@@ -414,7 +464,7 @@
 
   g_browser_process->io_thread()->InitDnsMaster(
       prefetching_enabled, max_queue_delay, max_concurrent, hostnames,
-      referral_list);
+      referral_list, preconnect_enabled);
 }
 
 void FinalizeDnsPrefetchInitialization(
@@ -505,12 +555,23 @@
   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);
+    ListValue::iterator it = startup_list->begin();
+    int format_version = -1;
+    if (it != startup_list->end() &&
+        (*it)->GetAsInteger(&format_version) &&
+        format_version == kDnsStartupFormatVersion) {
+      ++it;
+      for (; it != startup_list->end(); ++it) {
+        std::string hostname;
+        if (!(*it)->GetAsString(&hostname))
+          break;  // Format incompatibility.
+        int port;
+        if (!(*++it)->GetAsInteger(&port))
+          break;  // Format incompatibility.
+
+        // TODO(jar): We sohould accept hostport pairs.
+        hostnames.push_back(hostname);
+      }
     }
   }
 
@@ -522,7 +583,7 @@
     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());
+        hostnames.push_back(gurl.HostNoBrackets());
     }
   }
 
@@ -537,7 +598,8 @@
 // DNS prefetch system.
 
 DnsGlobalInit::DnsGlobalInit(PrefService* user_prefs,
-                             PrefService* local_state) {
+                             PrefService* local_state,
+                             bool preconnect_enabled) {
   DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
   // Set up a field trial to see what disabling DNS pre-resolution does to
   // latency of page loads.
@@ -616,8 +678,9 @@
 
     DCHECK(!dns_master);
     InitDnsPrefetch(max_queueing_delay, max_concurrent, user_prefs,
-                    local_state);
+                    local_state, preconnect_enabled);
   }
 }
 
+
 }  // namespace chrome_browser_net
diff --git a/chrome/browser/net/dns_global.h b/chrome/browser/net/dns_global.h
index bc44b79..639f61431 100644
--- a/chrome/browser/net/dns_global.h
+++ b/chrome/browser/net/dns_global.h
@@ -16,6 +16,7 @@
 
 #include "base/field_trial.h"
 #include "base/scoped_ptr.h"
+#include "chrome/browser/autocomplete/autocomplete.h"
 #include "chrome/browser/net/dns_master.h"
 #include "net/base/host_resolver.h"
 
@@ -42,10 +43,16 @@
 void EnableDnsPrefetch(bool enable);
 void RegisterPrefs(PrefService* local_state);
 void RegisterUserPrefs(PrefService* user_prefs);
+
 // Renderer bundles up list and sends to this browser API via IPC.
 void DnsPrefetchList(const NameList& hostnames);
+
 // This API is used by the autocomplete popup box (as user types).
-void DnsPrefetchUrl(const GURL& url);
+// This will either preresolve the domain name, or possibly preconnect creating
+// an open TCP/IP connection to the host.
+void DnsPrefetchUrl(const GURL& url, bool preconnectable);
+
+// When displaying info in about:dns, the following API is called.
 void DnsPrefetchGetHtmlInfo(std::string* output);
 
 //------------------------------------------------------------------------------
@@ -63,7 +70,8 @@
   // of that state.  The following is the suggested default time limit.
   static const int kMaxPrefetchQueueingDelayMs;
 
-  DnsGlobalInit(PrefService* user_prefs, PrefService* local_state);
+  DnsGlobalInit(PrefService* user_prefs, PrefService* local_state,
+                bool preconnect_enabled);
 
  private:
   // Maintain a field trial instance when we do A/B testing.
diff --git a/chrome/browser/net/dns_host_info.cc b/chrome/browser/net/dns_host_info.cc
index 45e088b..2fb626d 100644
--- a/chrome/browser/net/dns_host_info.cc
+++ b/chrome/browser/net/dns_host_info.cc
@@ -32,8 +32,7 @@
 int DnsHostInfo::sequence_counter = 1;
 
 
-bool DnsHostInfo::NeedsDnsUpdate(const std::string& hostname) {
-  DCHECK(hostname == hostname_);
+bool DnsHostInfo::NeedsDnsUpdate() {
   switch (state_) {
     case PENDING:  // Just now created info.
       return true;
@@ -48,7 +47,7 @@
       return !IsStillCached();  // See if DNS cache expired.
 
     default:
-      DCHECK(false);
+      NOTREACHED();
       return false;
   }
 }
@@ -171,11 +170,11 @@
   DLogResultsStats("DNS HTTP Finished");
 }
 
-void DnsHostInfo::SetHostname(const std::string& hostname) {
-  if (hostname != hostname_) {
-    DCHECK_EQ(hostname_.size(), 0u);  // Not yet initialized.
-    hostname_ = hostname;
-  }
+void DnsHostInfo::SetHostname(const net::HostPortPair& hostport) {
+  if (hostport_.host.empty())  // Not yet initialized.
+    hostport_ = hostport;
+  else
+    DCHECK(hostport_.Equals(hostport));
 }
 
 // IsStillCached() guesses if the DNS cache still has IP data,
@@ -201,7 +200,7 @@
 DnsBenefit DnsHostInfo::AccruePrefetchBenefits(DnsHostInfo* navigation_info) {
   DCHECK(FINISHED == navigation_info->state_ ||
          FINISHED_UNRESOLVED == navigation_info->state_);
-  DCHECK_EQ(navigation_info->hostname_, hostname_.data());
+  DCHECK(navigation_info->hostport().Equals(hostport_));
 
   if ((0 == benefits_remaining_.InMilliseconds()) ||
       (FOUND != state_ && NO_SUCH_NAME != state_)) {
@@ -221,7 +220,7 @@
   navigation_info->motivation_ = motivation_;
   if (LEARNED_REFERAL_MOTIVATED == motivation_ ||
       STATIC_REFERAL_MOTIVATED == motivation_)
-    navigation_info->referring_hostname_ = referring_hostname_;
+    navigation_info->referring_hostport_ = referring_hostport_;
 
   if (navigation_info->resolve_duration_ > kMaxNonNetworkDnsLookupDuration) {
     // Our precache effort didn't help since HTTP stack hit the network.
@@ -256,7 +255,7 @@
       << resolve_duration().InMilliseconds() << "ms\tp="
       << benefits_remaining_.InMilliseconds() << "ms\tseq="
       << sequence_number_
-      << "\t" << hostname_;
+      << "\t" << hostport_.ToString();
 }
 
 //------------------------------------------------------------------------------
@@ -270,7 +269,7 @@
   size_t length = output.length();
   for (size_t i = 0; i < length; i++) {
     char next = output[i];
-    if (isalnum(next) || isspace(next) || strchr(".-:", next) != NULL)
+    if (isalnum(next) || isspace(next) || strchr(".-:/", next) != NULL)
       continue;
     output[i] = '?';
   }
@@ -362,7 +361,7 @@
        it != host_infos.end(); it++) {
     queue.sample((it->queue_duration_.InMilliseconds()));
     StringAppendF(output, row_format,
-                  RemoveJs(it->hostname_).c_str(),
+                  RemoveJs(it->hostport_.ToString()).c_str(),
                   preresolve.sample((it->benefits_remaining_.InMilliseconds())),
                   resolve.sample((it->resolve_duration_.InMilliseconds())),
                   HoursMinutesSeconds(when.sample(
@@ -427,10 +426,10 @@
       return "n/a";
 
     case STATIC_REFERAL_MOTIVATED:
-      return RemoveJs(referring_hostname_) + "*";
+      return RemoveJs(referring_hostport_.ToString()) + "*";
 
     case LEARNED_REFERAL_MOTIVATED:
-      return RemoveJs(referring_hostname_);
+      return RemoveJs(referring_hostport_.ToString());
 
     default:
       return "";
diff --git a/chrome/browser/net/dns_host_info.h b/chrome/browser/net/dns_host_info.h
index 34160d5..73a0b2e13 100644
--- a/chrome/browser/net/dns_host_info.h
+++ b/chrome/browser/net/dns_host_info.h
@@ -15,6 +15,7 @@
 
 #include "base/time.h"
 #include "googleurl/src/gurl.h"
+#include "net/base/host_port_pair.h"
 
 namespace chrome_browser_net {
 
@@ -43,7 +44,7 @@
     NO_PREFETCH_MOTIVATION,  // Browser navigation info (not prefetch related).
 
     // The following involve predictive prefetching, triggered by a navigation.
-    // The referring_hostname_ is also set when these are used.
+    // The referring_hostport_ is also set when these are used.
     // TODO(jar): Support STATIC_REFERAL_MOTIVATED API and integration.
     STATIC_REFERAL_MOTIVATED,  // External database suggested this resolution.
     LEARNED_REFERAL_MOTIVATED,  // Prior navigation taught us this resolution.
@@ -89,7 +90,7 @@
   // if it would be valuable to attempt to update (prefectch)
   // DNS data for hostname.  This decision is based
   // on how recently we've done DNS prefetching for hostname.
-  bool NeedsDnsUpdate(const std::string& hostname);
+  bool NeedsDnsUpdate();
 
   static void set_cache_expiration(base::TimeDelta time);
 
@@ -105,13 +106,13 @@
   void SetFinishedState(bool was_resolved);
 
   // Finish initialization. Must only be called once.
-  void SetHostname(const std::string& hostname);
+  void SetHostname(const net::HostPortPair& hostport);
 
   bool was_linked() const { return was_linked_; }
 
-  std::string referring_hostname() const { return referring_hostname_; }
-  void SetReferringHostname(const std::string& hostname) {
-    referring_hostname_ = hostname;
+  net::HostPortPair referring_hostname() const { return referring_hostport_; }
+  void SetReferringHostname(const net::HostPortPair& hostport) {
+    referring_hostport_ = hostport;
   }
 
   bool was_found() const { return FOUND == state_; }
@@ -120,10 +121,10 @@
     return ASSIGNED == state_ || ASSIGNED_BUT_MARKED == state_;
   }
   bool is_marked_to_delete() const { return ASSIGNED_BUT_MARKED == state_; }
-  const std::string hostname() const { return hostname_; }
+  const net::HostPortPair hostport() const { return hostport_; }
 
-  bool HasHostname(const std::string& hostname) const {
-    return (hostname == hostname_);
+  bool HasHostname(const net::HostPortPair& hostport) const {
+    return (hostport.Equals(hostport_));
   }
 
   base::TimeDelta resolve_duration() const { return resolve_duration_;}
@@ -166,7 +167,7 @@
   // out of the queue.
   DnsProcessingState old_prequeue_state_;
 
-  std::string hostname_;  // Hostname for this info.
+  net::HostPortPair hostport_;  // Hostname for this info.
 
   // When was last state changed (usually lookup completed).
   base::TimeTicks time_;
@@ -189,7 +190,7 @@
   // If this instance holds data about a navigation, we store the referrer.
   // If this instance hold data about a prefetch, and the prefetch was
   // instigated by a referrer, we store it here (for use in about:dns).
-  std::string referring_hostname_;
+  net::HostPortPair referring_hostport_;
 
   // We put these objects into a std::map, and hence we
   // need some "evil" constructors.
diff --git a/chrome/browser/net/dns_host_info_unittest.cc b/chrome/browser/net/dns_host_info_unittest.cc
index 87f64bc..7c145a41 100644
--- a/chrome/browser/net/dns_host_info_unittest.cc
+++ b/chrome/browser/net/dns_host_info_unittest.cc
@@ -22,7 +22,7 @@
 
 TEST(DnsHostInfoTest, StateChangeTest) {
   DnsHostInfo info_practice, info;
-  std::string hostname1("domain1.com"), hostname2("domain2.com");
+  net::HostPortPair hostname1("domain1.com", 80), hostname2("domain2.com", 443);
 
   // First load DLL, so that their load time won't interfere with tests.
   // Some tests involve timing function performance, and DLL time can overwhelm
@@ -36,14 +36,14 @@
   // Complete the construction of real test object.
   info.SetHostname(hostname1);
 
-  EXPECT_TRUE(info.NeedsDnsUpdate(hostname1)) << "error in construction state";
+  EXPECT_TRUE(info.NeedsDnsUpdate()) << "error in construction state";
   info.SetQueuedState(DnsHostInfo::UNIT_TEST_MOTIVATED);
-  EXPECT_FALSE(info.NeedsDnsUpdate(hostname1))
+  EXPECT_FALSE(info.NeedsDnsUpdate())
     << "update needed after being queued";
   info.SetAssignedState();
-  EXPECT_FALSE(info.NeedsDnsUpdate(hostname1));
+  EXPECT_FALSE(info.NeedsDnsUpdate());
   info.SetFoundState();
-  EXPECT_FALSE(info.NeedsDnsUpdate(hostname1))
+  EXPECT_FALSE(info.NeedsDnsUpdate())
     << "default expiration time is TOOOOO short";
 
   // Note that time from ASSIGNED to FOUND was VERY short (probably 0ms), so the
@@ -55,31 +55,31 @@
     << "Non-net time is set too low";
 
   info.set_cache_expiration(TimeDelta::FromMilliseconds(300));
-  EXPECT_FALSE(info.NeedsDnsUpdate(hostname1)) << "expiration time not honored";
+  EXPECT_FALSE(info.NeedsDnsUpdate()) << "expiration time not honored";
   PlatformThread::Sleep(80);  // Not enough time to pass our 300ms mark.
-  EXPECT_FALSE(info.NeedsDnsUpdate(hostname1)) << "expiration time not honored";
+  EXPECT_FALSE(info.NeedsDnsUpdate()) << "expiration time not honored";
 
   // That was a nice life when the object was found.... but next time it won't
   // be found.  We'll sleep for a while, and then come back with not-found.
   info.SetQueuedState(DnsHostInfo::UNIT_TEST_MOTIVATED);
   info.SetAssignedState();
-  EXPECT_FALSE(info.NeedsDnsUpdate(hostname1));
+  EXPECT_FALSE(info.NeedsDnsUpdate());
   // Greater than minimal expected network latency on DNS lookup.
   PlatformThread::Sleep(25);
   info.SetNoSuchNameState();
-  EXPECT_FALSE(info.NeedsDnsUpdate(hostname1))
+  EXPECT_FALSE(info.NeedsDnsUpdate())
     << "default expiration time is TOOOOO short";
 
   // Note that now we'll actually utilize an expiration of 300ms,
   // since there was detected network activity time during lookup.
   // We're assuming the caching just started with our lookup.
   PlatformThread::Sleep(80);  // Not enough time to pass our 300ms mark.
-  EXPECT_FALSE(info.NeedsDnsUpdate(hostname1)) << "expiration time not honored";
+  EXPECT_FALSE(info.NeedsDnsUpdate()) << "expiration time not honored";
   // Still not past our 300ms mark (only about 4+2ms)
   PlatformThread::Sleep(80);
-  EXPECT_FALSE(info.NeedsDnsUpdate(hostname1)) << "expiration time not honored";
+  EXPECT_FALSE(info.NeedsDnsUpdate()) << "expiration time not honored";
   PlatformThread::Sleep(150);
-  EXPECT_TRUE(info.NeedsDnsUpdate(hostname1)) << "expiration time not honored";
+  EXPECT_TRUE(info.NeedsDnsUpdate()) << "expiration time not honored";
 }
 
 // When a system gets "congested" relative to DNS, it means it is doing too many
@@ -93,7 +93,7 @@
 // the state transitions used in such congestion handling.
 TEST(DnsHostInfoTest, CongestionResetStateTest) {
   DnsHostInfo info;
-  std::string hostname1("domain1.com");
+  net::HostPortPair hostname1("domain1.com", 80);
 
   info.SetHostname(hostname1);
   info.SetQueuedState(DnsHostInfo::UNIT_TEST_MOTIVATED);
diff --git a/chrome/browser/net/dns_master.cc b/chrome/browser/net/dns_master.cc
index 431842d..11f65d7 100644
--- a/chrome/browser/net/dns_master.cc
+++ b/chrome/browser/net/dns_master.cc
@@ -14,8 +14,10 @@
 #include "base/string_util.h"
 #include "base/time.h"
 #include "chrome/browser/chrome_thread.h"
+#include "chrome/browser/net/preconnect.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"
@@ -28,11 +30,11 @@
  public:
   LookupRequest(DnsMaster* master,
                 net::HostResolver* host_resolver,
-                const std::string& hostname)
+                const net::HostPortPair& hostport)
       : ALLOW_THIS_IN_INITIALIZER_LIST(
           net_callback_(this, &LookupRequest::OnLookupFinished)),
         master_(master),
-        hostname_(hostname),
+        hostport_(hostport),
         resolver_(host_resolver) {
   }
 
@@ -41,8 +43,7 @@
   // net:ERR_IO_PENDING ==> Network will callback later with result.
   // anything else ==> Host was not found synchronously.
   int Start() {
-    // Port doesn't really matter.
-    net::HostResolver::RequestInfo resolve_info(hostname_, 80);
+    net::HostResolver::RequestInfo resolve_info(hostport_.host, hostport_.port);
 
     // 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
@@ -54,7 +55,7 @@
 
  private:
   void OnLookupFinished(int result) {
-    master_->OnLookupFinished(this, hostname_, result == net::OK);
+    master_->OnLookupFinished(this, hostport_, result == net::OK);
   }
 
   // HostResolver will call us using this callback when resolution is complete.
@@ -62,7 +63,7 @@
 
   DnsMaster* master_;  // Master which started us.
 
-  const std::string hostname_;  // Hostname to resolve.
+  const net::HostPortPair hostport_;  // Hostname to resolve.
   net::SingleRequestHostResolver resolver_;
   net::AddressList addresses_;
 
@@ -70,13 +71,16 @@
 };
 
 DnsMaster::DnsMaster(net::HostResolver* host_resolver,
-                     TimeDelta max_queue_delay,
-                     size_t max_concurrent)
-  : peak_pending_lookups_(0),
-    shutdown_(false),
-    max_concurrent_lookups_(max_concurrent),
-    max_queue_delay_(max_queue_delay),
-    host_resolver_(host_resolver) {
+                     base::TimeDelta max_queue_delay,
+                     size_t max_concurrent,
+                     bool preconnect_enabled)
+    : peak_pending_lookups_(0),
+      shutdown_(false),
+      max_concurrent_lookups_(max_concurrent),
+      max_queue_delay_(max_queue_delay),
+      host_resolver_(host_resolver),
+      preconnect_enabled_(preconnect_enabled) {
+  Referrer::SetUsePreconnectValuations(preconnect_enabled);
 }
 
 DnsMaster::~DnsMaster() {
@@ -100,25 +104,25 @@
 
   NameList::const_iterator it;
   for (it = hostnames.begin(); it < hostnames.end(); ++it)
-    AppendToResolutionQueue(*it, motivation);
+    // TODO(jar): I should pass port all the way in from renderer.
+    AppendToResolutionQueue(net::HostPortPair(*it, 80), motivation);
 }
 
 // Basic Resolve() takes an invidual name, and adds it
 // to the queue.
-void DnsMaster::Resolve(const std::string& hostname,
+void DnsMaster::Resolve(const net::HostPortPair& hostport,
                         DnsHostInfo::ResolutionMotivation motivation) {
   DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
-  if (0 == hostname.length())
+  if (hostport.host.empty())
     return;
-  AppendToResolutionQueue(hostname, motivation);
+  AppendToResolutionQueue(hostport, motivation);
 }
 
-bool DnsMaster::AccruePrefetchBenefits(const GURL& referrer,
+bool DnsMaster::AccruePrefetchBenefits(const net::HostPortPair& referrer,
                                        DnsHostInfo* navigation_info) {
   DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
-  std::string hostname = navigation_info->hostname();
-
-  Results::iterator it = results_.find(hostname);
+  net::HostPortPair hostport = navigation_info->hostport();
+  Results::iterator it = results_.find(hostport);
   if (it == results_.end()) {
     // Use UMA histogram to quantify potential future gains here.
     UMA_HISTOGRAM_LONG_TIMES("DNS.UnexpectedResolutionL",
@@ -147,17 +151,15 @@
     case PREFETCH_NAME_NONEXISTANT:
       cache_hits_.push_back(*navigation_info);
       if (referrer_based_prefetch) {
-        std::string motivating_referrer(
-            prefetched_host_info.referring_hostname());
-        if (!motivating_referrer.empty()) {
-          referrers_[motivating_referrer].AccrueValue(
-              navigation_info->benefits_remaining(), hostname);
+        if (!referrer.host.empty()) {
+          referrers_[referrer].AccrueValue(
+              navigation_info->benefits_remaining(), hostport);
         }
       }
       return true;
 
     case PREFETCH_CACHE_EVICTION:
-      cache_eviction_map_[hostname] = *navigation_info;
+      cache_eviction_map_[hostport] = *navigation_info;
       return false;
 
     case PREFETCH_NO_BENEFIT:
@@ -165,58 +167,74 @@
       return false;
 
     default:
-      DCHECK(false);
+      NOTREACHED();
       return false;
   }
 }
 
-void DnsMaster::NonlinkNavigation(const GURL& referrer,
+void DnsMaster::NonlinkNavigation(const net::HostPortPair& referring_hostport,
                                   const DnsHostInfo* navigation_info) {
   DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
-  std::string referring_host = referrer.host();
-  if (referring_host.empty() || referring_host == navigation_info->hostname())
+  if (referring_hostport.host.empty() ||
+      referring_hostport.Equals(navigation_info->hostport()))
     return;
-
-  referrers_[referring_host].SuggestHost(navigation_info->hostname());
+  referrers_[referring_hostport].SuggestHost(navigation_info->hostport());
 }
 
-void DnsMaster::NavigatingTo(const std::string& host_name) {
+void DnsMaster::NavigatingTo(const net::HostPortPair& hostport) {
   DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
-
-  Referrers::iterator it = referrers_.find(host_name);
+  Referrers::iterator it = referrers_.find(hostport);
   if (referrers_.end() == it)
     return;
   Referrer* referrer = &(it->second);
-  for (Referrer::iterator future_host = referrer->begin();
-       future_host != referrer->end(); ++future_host) {
+  referrer->IncrementUseCount();
+  for (Referrer::iterator future_hostport = referrer->begin();
+       future_hostport != referrer->end(); ++future_hostport) {
+    if (preconnect_enabled_) {
+      if (future_hostport->second.IsPreconnectWorthDoing()) {
+        Preconnect::PreconnectOnIOThread(future_hostport->first);
+        continue;  // No need he pre-resolve DNS.
+      }
+      // Fall through and do DNS pre-resolution.
+    }
     DnsHostInfo* queued_info = AppendToResolutionQueue(
-        future_host->first,
+        future_hostport->first,
         DnsHostInfo::LEARNED_REFERAL_MOTIVATED);
     if (queued_info)
-      queued_info->SetReferringHostname(host_name);
+      queued_info->SetReferringHostname(hostport);
   }
 }
 
 // Provide sort order so all .com's are together, etc.
 struct RightToLeftStringSorter {
-  bool operator()(const std::string& left, const std::string& right) const {
-    if (left == right) return true;
-    size_t left_already_matched = left.size();
-    size_t right_already_matched = right.size();
+  bool operator()(const net::HostPortPair& left,
+                  const net::HostPortPair& right) const {
+    return string_compare(left.host, right.host);
+  }
+  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[left.size() - 1]) {
-      if ('.' != right[right.size() - 1])
+    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[right.size() - 1])
+      if ('.' == right_host[right_already_matched - 1])
         return false;
     }
 
@@ -225,7 +243,7 @@
       if (!right_already_matched) return false;
 
       size_t left_length, right_length;
-      size_t left_start = left.find_last_of('.', left_already_matched - 1);
+      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;
@@ -234,7 +252,8 @@
         left_already_matched = left_start;
         ++left_start;  // Don't compare the dot.
       }
-      size_t right_start = right.find_last_of('.', right_already_matched - 1);
+      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;
@@ -244,8 +263,8 @@
         ++right_start;  // Don't compare the dot.
       }
 
-      int diff = left.compare(left_start, left.size(),
-                              right, right_start, right.size());
+      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;
     }
@@ -259,7 +278,8 @@
 
   // TODO(jar): Remove any plausible JavaScript from names before displaying.
 
-  typedef std::set<std::string, struct RightToLeftStringSorter> SortedNames;
+  typedef std::set<net::HostPortPair, struct RightToLeftStringSorter>
+      SortedNames;
   SortedNames sorted_names;
 
   for (Referrers::iterator it = referrers_.begin();
@@ -267,22 +287,37 @@
     sorted_names.insert(it->first);
 
   output->append("<br><table border>");
-  StringAppendF(output,
-      "<tr><th>%s</th><th>%s</th></tr>",
-      "Host for Page", "Host(s) in Page<br>(benefits in ms)");
+  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>Expected<br>Connects</th>"
+      "<th>DNS<br>Savings</th>"
+      "<th>Subresource Spec</th></tr>");
 
   for (SortedNames::iterator it = sorted_names.begin();
        sorted_names.end() != it; ++it) {
     Referrer* referrer = &(referrers_[*it]);
-    StringAppendF(output, "<tr align=right><td>%s</td><td>", it->c_str());
-    output->append("<table>");
-    for (Referrer::iterator future_host = referrer->begin();
-         future_host != referrer->end(); ++future_host) {
-      StringAppendF(output, "<tr align=right><td>(%dms)</td><td>%s</td></tr>",
-          static_cast<int>(future_host->second.latency().InMilliseconds()),
-          future_host->first.c_str());
+    bool first_set_of_futures = true;
+    for (Referrer::iterator future_hostport = referrer->begin();
+         future_hostport != referrer->end(); ++future_hostport) {
+      output->append("<tr align=right>");
+      if (first_set_of_futures)
+        StringAppendF(output, "<td rowspan=%d>%s</td><td rowspan=%d>%d</td>",
+                      static_cast<int>(referrer->size()),
+                      it->ToString().c_str(),
+                      static_cast<int>(referrer->size()),
+                      static_cast<int>(referrer->use_count()));
+      first_set_of_futures = false;
+      StringAppendF(output,
+          "<td>%d</td><td>%d</td><td>%2.3f</td><td>%dms</td><td>%s</td></tr>",
+          static_cast<int>(future_hostport->second.navigation_count()),
+          static_cast<int>(future_hostport->second.preconnection_count()),
+          static_cast<double>(future_hostport->second.subresource_use_rate()),
+          static_cast<int>(future_hostport->second.latency().InMilliseconds()),
+          future_hostport->first.ToString().c_str());
     }
-    output->append("</table></td></tr>");
   }
   output->append("</table>");
 }
@@ -297,7 +332,8 @@
   DnsHostInfo::DnsInfoTable already_cached;
 
   // Get copies of all useful data.
-  typedef std::map<std::string, DnsHostInfo, RightToLeftStringSorter> Snapshot;
+  typedef std::map<net::HostPortPair, DnsHostInfo, RightToLeftStringSorter>
+      Snapshot;
   Snapshot snapshot;
   {
     // DnsHostInfo supports value semantics, so we can do a shallow copy.
@@ -357,28 +393,28 @@
 }
 
 DnsHostInfo* DnsMaster::AppendToResolutionQueue(
-    const std::string& hostname,
+    const net::HostPortPair& hostport,
     DnsHostInfo::ResolutionMotivation motivation) {
   DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
-  DCHECK_NE(0u, hostname.length());
+  DCHECK(!hostport.host.empty());
 
   if (shutdown_)
     return NULL;
 
-  DnsHostInfo* info = &results_[hostname];
-  info->SetHostname(hostname);  // Initialize or DCHECK.
+  DnsHostInfo* info = &results_[hostport];
+  info->SetHostname(hostport);  // 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->HasHostname(hostname));
+  DCHECK(info->HasHostname(hostport));
 
-  if (!info->NeedsDnsUpdate(hostname)) {
+  if (!info->NeedsDnsUpdate()) {
     info->DLogResultsStats("DNS PrefetchNotUpdated");
     return NULL;
   }
 
   info->SetQueuedState(motivation);
-  work_queue_.Push(hostname, motivation);
+  work_queue_.Push(hostport, motivation);
   StartSomeQueuedResolutions();
   return info;
 }
@@ -388,9 +424,9 @@
 
   while (!work_queue_.IsEmpty() &&
          pending_lookups_.size() < max_concurrent_lookups_) {
-    const std::string hostname(work_queue_.Pop());
-    DnsHostInfo* info = &results_[hostname];
-    DCHECK(info->HasHostname(hostname));
+    const net::HostPortPair hostport(work_queue_.Pop());
+    DnsHostInfo* info = &results_[hostport];
+    DCHECK(info->HasHostname(hostport));
     info->SetAssignedState();
 
     if (CongestionControlPerformed(info)) {
@@ -398,7 +434,7 @@
       return;
     }
 
-    LookupRequest* request = new LookupRequest(this, host_resolver_, hostname);
+    LookupRequest* request = new LookupRequest(this, host_resolver_, hostport);
     int status = request->Start();
     if (status == net::ERR_IO_PENDING) {
       // Will complete asynchronously.
@@ -409,7 +445,7 @@
       // 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, hostname, status == net::OK);
+      LookupFinished(request, hostport, status == net::OK);
       delete request;
     }
   }
@@ -434,10 +470,11 @@
 }
 
 void DnsMaster::OnLookupFinished(LookupRequest* request,
-                                 const std::string& hostname, bool found) {
+                                 const net::HostPortPair& hostport,
+                                 bool found) {
   DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
 
-  LookupFinished(request, hostname, found);
+  LookupFinished(request, hostport, found);
   pending_lookups_.erase(request);
   delete request;
 
@@ -445,13 +482,13 @@
 }
 
 void DnsMaster::LookupFinished(LookupRequest* request,
-                               const std::string& hostname,
+                               const net::HostPortPair& hostport,
                                bool found) {
   DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
-  DnsHostInfo* info = &results_[hostname];
-  DCHECK(info->HasHostname(hostname));
+  DnsHostInfo* info = &results_[hostport];
+  DCHECK(info->HasHostname(hostport));
   if (info->is_marked_to_delete()) {
-    results_.erase(hostname);
+    results_.erase(hostport);
   } else {
     if (found)
       info->SetFoundState();
@@ -471,9 +508,9 @@
   // Try to delete anything in our work queue.
   while (!work_queue_.IsEmpty()) {
     // Emulate processing cycle as though host was not found.
-    std::string hostname = work_queue_.Pop();
-    DnsHostInfo* info = &results_[hostname];
-    DCHECK(info->HasHostname(hostname));
+    net::HostPortPair hostport = work_queue_.Pop();
+    DnsHostInfo* info = &results_[hostport];
+    DCHECK(info->HasHostname(hostport));
     info->SetAssignedState();
     info->SetNoSuchNameState();
   }
@@ -484,12 +521,12 @@
   // We can't erase anything being worked on.
   Results assignees;
   for (Results::iterator it = results_.begin(); results_.end() != it; ++it) {
-    std::string hostname = it->first;
+    net::HostPortPair hostport(it->first);
     DnsHostInfo* info = &it->second;
-    DCHECK(info->HasHostname(hostname));
+    DCHECK(info->HasHostname(hostport));
     if (info->is_assigned()) {
       info->SetPendingDeleteState();
-      assignees[hostname] = *info;
+      assignees[hostport] = *info;
     }
   }
   DCHECK(assignees.size() <= max_concurrent_lookups_);
@@ -503,7 +540,7 @@
 
 void DnsMaster::TrimReferrers() {
   DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
-  std::vector<std::string> hosts;
+  std::vector<net::HostPortPair> hosts;
   for (Referrers::const_iterator it = referrers_.begin();
        it != referrers_.end(); ++it)
     hosts.push_back(it->first);
@@ -515,35 +552,57 @@
 void DnsMaster::SerializeReferrers(ListValue* referral_list) {
   DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
   referral_list->Clear();
+  referral_list->Append(new FundamentalValue(DNS_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* motivating_host(new ListValue);
-    motivating_host->Append(new StringValue(it->first));
-    motivating_host->Append(subresource_list);
+    ListValue* motivator(new ListValue);
+    motivator->Append(new FundamentalValue(it->first.port));
+    motivator->Append(new StringValue(it->first.host));
+    motivator->Append(subresource_list);
 
-    referral_list->Append(motivating_host);
+    referral_list->Append(motivator);
   }
 }
 
 void DnsMaster::DeserializeReferrers(const ListValue& referral_list) {
   DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
-  for (size_t i = 0; i < referral_list.GetSize(); ++i) {
-    ListValue* motivating_host;
-    if (!referral_list.GetList(i, &motivating_host))
-      continue;
-    std::string motivating_referrer;
-    if (!motivating_host->GetString(0, &motivating_referrer))
-      continue;
-    Value* subresource_list;
-    if (!motivating_host->Get(1, &subresource_list))
-      continue;
-    if (motivating_referrer.empty())
-      continue;
-    referrers_[motivating_referrer].Deserialize(*subresource_list);
+  int format_version = -1;
+  if (referral_list.GetSize() > 0 &&
+      referral_list.GetInteger(0, &format_version) &&
+      format_version == DNS_REFERRER_VERSION) {
+    for (size_t i = 1; i < referral_list.GetSize(); ++i) {
+      ListValue* motivator;
+      if (!referral_list.GetList(i, &motivator)) {
+        NOTREACHED();
+        continue;
+      }
+      int motivating_port;
+      if (!motivator->GetInteger(0, &motivating_port)) {
+        NOTREACHED();
+        continue;
+      }
+      std::string motivating_host;
+      if (!motivator->GetString(1, &motivating_host)) {
+        NOTREACHED();
+        continue;
+      }
+      if (motivating_host.empty()) {
+        NOTREACHED();
+        continue;
+      }
+
+      Value* subresource_list;
+      if (!motivator->Get(2, &subresource_list)) {
+        NOTREACHED();
+        continue;
+      }
+      net::HostPortPair motivating_hostport(motivating_host, motivating_port);
+      referrers_[motivating_hostport].Deserialize(*subresource_list);
+    }
   }
 }
 
@@ -556,17 +615,17 @@
 DnsMaster::HostNameQueue::~HostNameQueue() {
 }
 
-void DnsMaster::HostNameQueue::Push(const std::string& hostname,
+void DnsMaster::HostNameQueue::Push(const net::HostPortPair& hostport,
     DnsHostInfo::ResolutionMotivation motivation) {
   switch (motivation) {
     case DnsHostInfo::STATIC_REFERAL_MOTIVATED:
     case DnsHostInfo::LEARNED_REFERAL_MOTIVATED:
     case DnsHostInfo::MOUSE_OVER_MOTIVATED:
-      rush_queue_.push(hostname);
+      rush_queue_.push(hostport);
       break;
 
     default:
-      background_queue_.push(hostname);
+      background_queue_.push(hostport);
       break;
   }
 }
@@ -575,16 +634,16 @@
   return rush_queue_.empty() && background_queue_.empty();
 }
 
-std::string DnsMaster::HostNameQueue::Pop() {
+net::HostPortPair DnsMaster::HostNameQueue::Pop() {
   DCHECK(!IsEmpty());
   if (!rush_queue_.empty()) {
-    std::string hostname(rush_queue_.front());
+    net::HostPortPair hostport(rush_queue_.front());
     rush_queue_.pop();
-    return hostname;
+    return hostport;
   }
-  std::string hostname(background_queue_.front());
+  net::HostPortPair hostport(background_queue_.front());
   background_queue_.pop();
-  return hostname;
+  return hostport;
 }
 
 }  // namespace chrome_browser_net
diff --git a/chrome/browser/net/dns_master.h b/chrome/browser/net/dns_master.h
index b665358..84497317 100644
--- a/chrome/browser/net/dns_master.h
+++ b/chrome/browser/net/dns_master.h
@@ -23,10 +23,9 @@
 #include "chrome/browser/net/dns_host_info.h"
 #include "chrome/browser/net/referrer.h"
 #include "chrome/common/net/dns.h"
+#include "net/base/host_port_pair.h"
 #include "testing/gtest/include/gtest/gtest_prod.h"
 
-using base::TimeDelta;
-
 namespace net {
 class HostResolver;
 }  // namespace net
@@ -34,16 +33,21 @@
 namespace chrome_browser_net {
 
 typedef chrome_common_net::NameList NameList;
-typedef std::map<std::string, DnsHostInfo> Results;
+typedef std::map<net::HostPortPair, DnsHostInfo> Results;
 
 // Note that DNS master is not thread safe, and must only be called from
 // the IO thread. Failure to do so will result in a DCHECK at runtime.
 class DnsMaster : public base::RefCountedThreadSafe<DnsMaster> {
  public:
-  // |max_concurrent| specifies how many concurrent (parallel) prefetches will
+  // A version number for prefs that are saved. This should be incremented when
+  // we change the format so that we discard old data.
+  enum {DNS_REFERRER_VERSION = 0 };
+
+// |max_concurrent| specifies how many concurrent (parallel) prefetches will
   // be performed. Host lookups will be issued through |host_resolver|.
   DnsMaster(net::HostResolver* host_resolver,
-            TimeDelta max_queue_delay_ms, size_t max_concurrent);
+            base::TimeDelta max_queue_delay_ms, size_t max_concurrent,
+            bool preconnect_enabled);
 
   // Cancel pending requests and prevent new ones from being made.
   void Shutdown();
@@ -58,21 +62,21 @@
   // Add hostname(s) to the queue for processing.
   void ResolveList(const NameList& hostnames,
                    DnsHostInfo::ResolutionMotivation motivation);
-  void Resolve(const std::string& hostname,
+  void Resolve(const net::HostPortPair& hostport,
                DnsHostInfo::ResolutionMotivation motivation);
 
   // Get latency benefit of the prefetch that we are navigating to.
-  bool AccruePrefetchBenefits(const GURL& referrer,
+  bool AccruePrefetchBenefits(const net::HostPortPair& referrer,
                               DnsHostInfo* navigation_info);
 
   // Instigate prefetch of any domains we predict will be needed after this
   // navigation.
-  void NavigatingTo(const std::string& host_name);
+  void NavigatingTo(const net::HostPortPair& hostport);
 
   // Record details of a navigation so that we can preresolve the host name
   // ahead of time the next time the users navigates to the indicated host.
   // TODO(eroman): can this be a const& instead?
-  void NonlinkNavigation(const GURL& referrer,
+  void NonlinkNavigation(const net::HostPortPair& referrer,
                          const DnsHostInfo* navigation_info);
 
   // Dump HTML table containing list of referrers for about:dns.
@@ -106,6 +110,9 @@
   // For unit test code only.
   size_t max_concurrent_lookups() const { return max_concurrent_lookups_; }
 
+  // Flag setting to use preconnection instead of just DNS pre-fetching.
+  bool preconnect_enabled() const { return preconnect_enabled_; }
+
  private:
   friend class base::RefCountedThreadSafe<DnsMaster>;
   FRIEND_TEST(DnsMasterTest, BenefitLookupTest);
@@ -133,39 +140,41 @@
    public:
     HostNameQueue();
     ~HostNameQueue();
-    void Push(const std::string& hostname,
+    void Push(const net::HostPortPair& hostport,
               DnsHostInfo::ResolutionMotivation motivation);
     bool IsEmpty() const;
-    std::string Pop();
+    net::HostPortPair Pop();
 
   private:
     // The names in the queue that should be serviced (popped) ASAP.
-    std::queue<std::string> rush_queue_;
+    std::queue<net::HostPortPair> rush_queue_;
     // The names in the queue that should only be serviced when rush_queue is
     // empty.
-    std::queue<std::string> background_queue_;
+    std::queue<net::HostPortPair> background_queue_;
 
   DISALLOW_COPY_AND_ASSIGN(HostNameQueue);
   };
 
-  // A map that is keyed with the hostnames that we've learned were the cause
-  // of loading additional hostnames.  The list of additional hostnames in held
-  // in a Referrer instance, which is found in this type.
-  typedef std::map<std::string, Referrer> Referrers;
+  // A map that is keyed with the host/port that we've learned were the cause
+  // of loading additional URLs.  The list of additional targets is held
+  // in a Referrer instance, which is a value in this map.
+  typedef std::map<net::HostPortPair, Referrer> Referrers;
 
   // Only for testing. Returns true if hostname has been successfully resolved
   // (name found).
-  bool WasFound(const std::string& hostname) {
-    return (results_.find(hostname) != results_.end()) &&
-            results_[hostname].was_found();
+  bool WasFound(const net::HostPortPair& hostport) const {
+    Results::const_iterator it(results_.find(hostport));
+    return (it != results_.end()) &&
+            it->second.was_found();
   }
 
   // Only for testing. Return how long was the resolution
   // or DnsHostInfo::kNullDuration if it hasn't been resolved yet.
-  base::TimeDelta GetResolutionDuration(const std::string& hostname) {
-    if (results_.find(hostname) == results_.end())
+  base::TimeDelta GetResolutionDuration(const net::HostPortPair& hostport) {
+
+    if (results_.find(hostport) == results_.end())
       return DnsHostInfo::kNullDuration;
-    return results_[hostname].resolve_duration();
+    return results_[hostport].resolve_duration();
   }
 
   // Only for testing;
@@ -173,16 +182,15 @@
 
   // Access method for use by async lookup request to pass resolution result.
   void OnLookupFinished(LookupRequest* request,
-                        const std::string& hostname, bool found);
+                        const net::HostPortPair& hostport, bool found);
 
   // Underlying method for both async and synchronous lookup to update state.
   void LookupFinished(LookupRequest* request,
-                      const std::string& hostname,
-                      bool found);
+                      const net::HostPortPair& hostport, bool found);
 
   // Queue hostname for resolution.  If queueing was done, return the pointer
   // to the queued instance, otherwise return NULL.
-  DnsHostInfo* AppendToResolutionQueue(const std::string& hostname,
+  DnsHostInfo* AppendToResolutionQueue(const net::HostPortPair& hostport,
       DnsHostInfo::ResolutionMotivation motivation);
 
   // Check to see if too much queuing delay has been noted for the given info,
@@ -209,7 +217,7 @@
   // results_ contains information for existing/prior prefetches.
   Results results_;
 
-  // For each hostname that we might navigate to (that we've "learned about")
+  // For each URL that we might navigate to (that we've "learned about")
   // we have a Referrer list. Each Referrer list has all hostnames we need to
   // pre-resolve when there is a navigation to the orginial hostname.
   Referrers referrers_;
@@ -233,11 +241,15 @@
 
   // The maximum queueing delay that is acceptable before we enter congestion
   // reduction mode, and discard all queued (but not yet assigned) resolutions.
-  const TimeDelta max_queue_delay_;
+  const base::TimeDelta max_queue_delay_;
 
   // The host resovler we warm DNS entries for.
   scoped_refptr<net::HostResolver> host_resolver_;
 
+  // Are we currently using preconnection, rather than just DNS resolution, for
+  // subresources and omni-box search URLs.
+  bool preconnect_enabled_;
+
   DISALLOW_COPY_AND_ASSIGN(DnsMaster);
 };
 
diff --git a/chrome/browser/net/dns_master_unittest.cc b/chrome/browser/net/dns_master_unittest.cc
index 2f83d30..2699b1f 100644
--- a/chrome/browser/net/dns_master_unittest.cc
+++ b/chrome/browser/net/dns_master_unittest.cc
@@ -41,7 +41,8 @@
 
   void Run() {
     for (NameList::const_iterator i = hosts_.begin(); i != hosts_.end(); ++i)
-      if (master_->GetResolutionDuration(*i) == DnsHostInfo::kNullDuration)
+      if (master_->GetResolutionDuration(net::HostPortPair(*i, 80)) ==
+          DnsHostInfo::kNullDuration)
         return;  // We don't have resolution for that host.
 
     // When all hostnames have been resolved, exit the loop.
@@ -109,19 +110,22 @@
 TEST_F(DnsMasterTest, StartupShutdownTest) {
   scoped_refptr<DnsMaster> testing_master = new DnsMaster(host_resolver_,
       default_max_queueing_delay_,
-      DnsGlobalInit::kMaxPrefetchConcurrentLookups);
+      DnsGlobalInit::kMaxPrefetchConcurrentLookups,
+      false);
   testing_master->Shutdown();
 }
 
 TEST_F(DnsMasterTest, BenefitLookupTest) {
-  scoped_refptr<DnsMaster> testing_master = new DnsMaster(host_resolver_,
+  scoped_refptr<DnsMaster> testing_master = new DnsMaster(
+      host_resolver_,
       default_max_queueing_delay_,
-      DnsGlobalInit::kMaxPrefetchConcurrentLookups);
+      DnsGlobalInit::kMaxPrefetchConcurrentLookups,
+      false);
 
-  std::string goog("www.google.com"),
-    goog2("gmail.google.com.com"),
-    goog3("mail.google.com"),
-    goog4("gmail.com");
+  net::HostPortPair goog("www.google.com", 80),
+      goog2("gmail.google.com.com", 80),
+      goog3("mail.google.com", 80),
+      goog4("gmail.com", 80);
   DnsHostInfo goog_info, goog2_info, goog3_info, goog4_info;
 
   // Simulate getting similar names from a network observer
@@ -141,10 +145,10 @@
   goog4_info.SetFinishedState(true);
 
   NameList names;
-  names.insert(names.end(), goog);
-  names.insert(names.end(), goog2);
-  names.insert(names.end(), goog3);
-  names.insert(names.end(), goog4);
+  names.push_back(goog.host);
+  names.push_back(goog2.host);
+  names.push_back(goog3.host);
+  names.push_back(goog4.host);
 
   testing_master->ResolveList(names, DnsHostInfo::PAGE_SCAN_MOTIVATED);
 
@@ -158,18 +162,20 @@
   // With the mock DNS, each of these should have taken some time, and hence
   // shown a benefit (i.e., prefetch cost more than network access time).
 
+  net::HostPortPair referer;  // Null host.
+
   // Simulate actual navigation, and acrue the benefit for "helping" the DNS
   // part of the navigation.
-  EXPECT_TRUE(testing_master->AccruePrefetchBenefits(GURL(), &goog_info));
-  EXPECT_TRUE(testing_master->AccruePrefetchBenefits(GURL(), &goog2_info));
-  EXPECT_TRUE(testing_master->AccruePrefetchBenefits(GURL(), &goog3_info));
-  EXPECT_TRUE(testing_master->AccruePrefetchBenefits(GURL(), &goog4_info));
+  EXPECT_TRUE(testing_master->AccruePrefetchBenefits(referer, &goog_info));
+  EXPECT_TRUE(testing_master->AccruePrefetchBenefits(referer, &goog2_info));
+  EXPECT_TRUE(testing_master->AccruePrefetchBenefits(referer, &goog3_info));
+  EXPECT_TRUE(testing_master->AccruePrefetchBenefits(referer, &goog4_info));
 
   // Benefits can ONLY be reported once (for the first navigation).
-  EXPECT_FALSE(testing_master->AccruePrefetchBenefits(GURL(), &goog_info));
-  EXPECT_FALSE(testing_master->AccruePrefetchBenefits(GURL(), &goog2_info));
-  EXPECT_FALSE(testing_master->AccruePrefetchBenefits(GURL(), &goog3_info));
-  EXPECT_FALSE(testing_master->AccruePrefetchBenefits(GURL(), &goog4_info));
+  EXPECT_FALSE(testing_master->AccruePrefetchBenefits(referer, &goog_info));
+  EXPECT_FALSE(testing_master->AccruePrefetchBenefits(referer, &goog2_info));
+  EXPECT_FALSE(testing_master->AccruePrefetchBenefits(referer, &goog3_info));
+  EXPECT_FALSE(testing_master->AccruePrefetchBenefits(referer, &goog4_info));
 
   testing_master->Shutdown();
 }
@@ -181,11 +187,12 @@
 
   scoped_refptr<DnsMaster> testing_master = new DnsMaster(host_resolver_,
       default_max_queueing_delay_,
-      DnsGlobalInit::kMaxPrefetchConcurrentLookups);
+      DnsGlobalInit::kMaxPrefetchConcurrentLookups,
+      false);
 
-  std::string localhost("127.0.0.1");
+  net::HostPortPair localhost("127.0.0.1", 80);
   NameList names;
-  names.insert(names.end(), localhost);
+  names.push_back(localhost.host);
 
   testing_master->ResolveList(names, DnsHostInfo::PAGE_SCAN_MOTIVATED);
 
@@ -205,12 +212,13 @@
 TEST_F(DnsMasterTest, SingleLookupTest) {
   scoped_refptr<DnsMaster> testing_master = new DnsMaster(host_resolver_,
       default_max_queueing_delay_,
-      DnsGlobalInit::kMaxPrefetchConcurrentLookups);
+      DnsGlobalInit::kMaxPrefetchConcurrentLookups,
+      false);
 
-  std::string goog("www.google.com");
+  net::HostPortPair goog("www.google.com", 80);
 
   NameList names;
-  names.insert(names.end(), goog);
+  names.push_back(goog.host);
 
   // Try to flood the master with many concurrent requests.
   for (int i = 0; i < 10; i++)
@@ -235,23 +243,24 @@
 
   scoped_refptr<DnsMaster> testing_master = new DnsMaster(host_resolver_,
       default_max_queueing_delay_,
-      DnsGlobalInit::kMaxPrefetchConcurrentLookups);
+      DnsGlobalInit::kMaxPrefetchConcurrentLookups,
+      false);
 
-  std::string goog("www.google.com"),
-    goog2("gmail.google.com.com"),
-    goog3("mail.google.com"),
-    goog4("gmail.com");
-  std::string bad1("bad1.notfound"),
-    bad2("bad2.notfound");
+  net::HostPortPair goog("www.google.com", 80),
+      goog2("gmail.google.com.com", 80),
+      goog3("mail.google.com", 80),
+      goog4("gmail.com", 80);
+  net::HostPortPair bad1("bad1.notfound", 80),
+      bad2("bad2.notfound", 80);
 
   NameList names;
-  names.insert(names.end(), goog);
-  names.insert(names.end(), goog3);
-  names.insert(names.end(), bad1);
-  names.insert(names.end(), goog2);
-  names.insert(names.end(), bad2);
-  names.insert(names.end(), goog4);
-  names.insert(names.end(), goog);
+  names.push_back(goog.host);
+  names.push_back(goog3.host);
+  names.push_back(bad1.host);
+  names.push_back(goog2.host);
+  names.push_back(bad2.host);
+  names.push_back(goog4.host);
+  names.push_back(goog.host);
 
   // Try to flood the master with many concurrent requests.
   for (int i = 0; i < 10; i++)
@@ -282,9 +291,11 @@
 TEST_F(DnsMasterTest, MassiveConcurrentLookupTest) {
   host_resolver_->rules()->AddSimulatedFailure("*.notfound");
 
-  scoped_refptr<DnsMaster> testing_master = new DnsMaster(host_resolver_,
+  scoped_refptr<DnsMaster> testing_master = new DnsMaster(
+      host_resolver_,
       default_max_queueing_delay_,
-      DnsGlobalInit::kMaxPrefetchConcurrentLookups);
+      DnsGlobalInit::kMaxPrefetchConcurrentLookups,
+      false);
 
   NameList names;
   for (int i = 0; i < 100; i++)
@@ -311,25 +322,39 @@
 
 // Return a motivation_list if we can find one for the given motivating_host (or
 // NULL if a match is not found).
-static ListValue* FindSerializationMotivation(const std::string& motivation,
-                                              const ListValue& referral_list) {
+static ListValue* FindSerializationMotivation(
+    const net::HostPortPair& motivation, const ListValue& referral_list) {
+  CHECK_LT(0u, referral_list.GetSize());  // Room for version.
+  int format_version = -1;
+  CHECK(referral_list.GetInteger(0, &format_version));
+  CHECK_EQ(DnsMaster::DNS_REFERRER_VERSION, format_version);
   ListValue* motivation_list(NULL);
-  for (size_t i = 0; i < referral_list.GetSize(); ++i) {
+  for (size_t i = 1; i < referral_list.GetSize(); ++i) {
     referral_list.GetList(i, &motivation_list);
-    std::string existing_motivation;
-    EXPECT_TRUE(motivation_list->GetString(i, &existing_motivation));
-    if (existing_motivation == motivation)
-      break;
-    motivation_list = NULL;
+    std::string existing_host;
+    int existing_port;
+    EXPECT_TRUE(motivation_list->GetInteger(0, &existing_port));
+    EXPECT_TRUE(motivation_list->GetString(1, &existing_host));
+    if (motivation.host == existing_host && motivation.port == existing_port)
+      return motivation_list;
   }
-  return motivation_list;
+  return NULL;
+}
+
+// Create a new empty serialization list.
+static ListValue* NewEmptySerializationList() {
+  ListValue* list = new ListValue;
+  list->Append(new FundamentalValue(DnsMaster::DNS_REFERRER_VERSION));
+  return list;
 }
 
 // Add a motivating_host and a subresource_host to a serialized list, using
 // this given latency. This is a helper function for quickly building these
 // lists.
-static void AddToSerializedList(const std::string& motivation,
-                                const std::string& subresource, int latency,
+static void AddToSerializedList(const net::HostPortPair& motivation,
+                                const net::HostPortPair& subresource,
+                                int latency,
+                                double rate,
                                 ListValue* referral_list ) {
   // Find the motivation if it is already used.
   ListValue* motivation_list = FindSerializationMotivation(motivation,
@@ -337,7 +362,8 @@
   if (!motivation_list) {
     // This is the first mention of this motivation, so build a list.
     motivation_list = new ListValue;
-    motivation_list->Append(new StringValue(motivation));
+    motivation_list->Append(new FundamentalValue(motivation.port));
+    motivation_list->Append(new StringValue(motivation.host));
     // Provide empty subresource list.
     motivation_list->Append(new ListValue());
 
@@ -346,41 +372,48 @@
   }
 
   ListValue* subresource_list(NULL);
-  EXPECT_TRUE(motivation_list->GetList(1, &subresource_list));
+  // 0 == port; 1 == host; 2 == subresource_list.
+  EXPECT_TRUE(motivation_list->GetList(2, &subresource_list));
 
   // We won't bother to check for the subresource being there already.  Worst
   // case, during deserialization, the latency value we supply plus the
   // existing value(s) will be added to the referrer.
-  subresource_list->Append(new StringValue(subresource));
+
+  subresource_list->Append(new FundamentalValue(subresource.port));
+  subresource_list->Append(new StringValue(subresource.host));
   subresource_list->Append(new FundamentalValue(latency));
+  subresource_list->Append(new FundamentalValue(rate));
 }
 
 static const int kLatencyNotFound = -1;
 
-// For a given motivation_hostname, and subresource_hostname, find what latency
-// is currently listed.  This assume a well formed serialization, which has
-// at most one such entry for any pair of names.  If no such pair is found, then
-// return kLatencyNotFound.
-int GetLatencyFromSerialization(const std::string& motivation,
-                                const std::string& subresource,
-                                const ListValue& referral_list) {
+// For a given motivation, and subresource, find what latency is currently
+// listed.  This assume a well formed serialization, which has at most one such
+// entry for any pair of names.  If no such pair is found, then return false.
+// Data is written into rate and latency arguments.
+static bool GetDataFromSerialization(const net::HostPortPair& motivation,
+                                     const net::HostPortPair& subresource,
+                                     const ListValue& referral_list,
+                                     double* rate,
+                                     int* latency) {
   ListValue* motivation_list = FindSerializationMotivation(motivation,
                                                            referral_list);
   if (!motivation_list)
-    return kLatencyNotFound;
+    return false;
   ListValue* subresource_list;
-  EXPECT_TRUE(motivation_list->GetList(1, &subresource_list));
-  for (size_t i = 0; i < subresource_list->GetSize(); ++i) {
-    std::string subresource_name;
-    EXPECT_TRUE(subresource_list->GetString(i, &subresource_name));
-    if (subresource_name == subresource) {
-      int latency;
-      EXPECT_TRUE(subresource_list->GetInteger(i + 1, &latency));
-      return latency;
+  EXPECT_TRUE(motivation_list->GetList(2, &subresource_list));
+  for (size_t i = 0; i < subresource_list->GetSize();) {
+    std::string host;
+    int port;
+    EXPECT_TRUE(subresource_list->GetInteger(i++, &port));
+    EXPECT_TRUE(subresource_list->GetString(i++, &host));
+    EXPECT_TRUE(subresource_list->GetInteger(i++, latency));
+    EXPECT_TRUE(subresource_list->GetReal(i++, rate));
+    if (subresource.host == host  && subresource.port == port) {
+      return true;
     }
-    ++i;  // Skip latency value.
   }
-  return kLatencyNotFound;
+  return false;
 }
 
 //------------------------------------------------------------------------------
@@ -389,12 +422,14 @@
 TEST_F(DnsMasterTest, ReferrerSerializationNilTest) {
   scoped_refptr<DnsMaster> master = new DnsMaster(host_resolver_,
       default_max_queueing_delay_,
-      DnsGlobalInit::kMaxPrefetchConcurrentLookups);
-  ListValue referral_list;
-  master->SerializeReferrers(&referral_list);
-  EXPECT_EQ(0U, referral_list.GetSize());
-  EXPECT_EQ(kLatencyNotFound, GetLatencyFromSerialization("a.com", "b.com",
-                                                          referral_list));
+      DnsGlobalInit::kMaxPrefetchConcurrentLookups,
+      false);
+  scoped_ptr<ListValue> referral_list(NewEmptySerializationList());
+  master->SerializeReferrers(referral_list.get());
+  EXPECT_EQ(1U, referral_list->GetSize());
+  EXPECT_FALSE(GetDataFromSerialization(
+      net::HostPortPair("a.com", 79), net::HostPortPair("b.com", 78),
+      *referral_list.get(), NULL, NULL));
 
   master->Shutdown();
 }
@@ -405,23 +440,29 @@
 TEST_F(DnsMasterTest, ReferrerSerializationSingleReferrerTest) {
   scoped_refptr<DnsMaster> master = new DnsMaster(host_resolver_,
       default_max_queueing_delay_,
-      DnsGlobalInit::kMaxPrefetchConcurrentLookups);
-  std::string motivation_hostname = "www.google.com";
-  std::string subresource_hostname = "icons.google.com";
+      DnsGlobalInit::kMaxPrefetchConcurrentLookups,
+      false);
+  const net::HostPortPair motivation_hostport("www.google.com", 91);
+  const net::HostPortPair subresource_hostport("icons.google.com", 90);
   const int kLatency = 3;
-  ListValue referral_list;
+  const double kRate = 23.4;
+  scoped_ptr<ListValue> referral_list(NewEmptySerializationList());
 
-  AddToSerializedList(motivation_hostname, subresource_hostname, kLatency,
-                      &referral_list);
+  AddToSerializedList(motivation_hostport, subresource_hostport,
+      kLatency, kRate, referral_list.get());
 
-  master->DeserializeReferrers(referral_list);
+  master->DeserializeReferrers(*referral_list.get());
 
   ListValue recovered_referral_list;
   master->SerializeReferrers(&recovered_referral_list);
-  EXPECT_EQ(1U, recovered_referral_list.GetSize());
-  EXPECT_EQ(kLatency, GetLatencyFromSerialization(motivation_hostname,
-                                                  subresource_hostname,
-                                                  recovered_referral_list));
+  EXPECT_EQ(2U, recovered_referral_list.GetSize());
+  int latency;
+  double rate;
+  EXPECT_TRUE(GetDataFromSerialization(
+      motivation_hostport, subresource_hostport, recovered_referral_list, &rate,
+      &latency));
+  EXPECT_EQ(rate, kRate);
+  EXPECT_EQ(latency, kLatency);
 
   master->Shutdown();
 }
@@ -430,72 +471,104 @@
 TEST_F(DnsMasterTest, ReferrerSerializationTrimTest) {
   scoped_refptr<DnsMaster> master = new DnsMaster(host_resolver_,
       default_max_queueing_delay_,
-      DnsGlobalInit::kMaxPrefetchConcurrentLookups);
-  std::string motivation_hostname = "www.google.com";
-  std::string icon_subresource_hostname = "icons.google.com";
-  std::string img_subresource_hostname = "img.google.com";
-  ListValue referral_list;
+      DnsGlobalInit::kMaxPrefetchConcurrentLookups,
+      false);
+  net::HostPortPair motivation_hostport("www.google.com", 110);
 
-  AddToSerializedList(motivation_hostname, icon_subresource_hostname, 10,
-                      &referral_list);
-  AddToSerializedList(motivation_hostname, img_subresource_hostname, 3,
-                      &referral_list);
+  net::HostPortPair icon_subresource_hostport("icons.google.com", 111);
+  const int kLatencyIcon = 10;
+  const double kRateIcon = 0.;  // User low rate, so latency will dominate.
+  net::HostPortPair img_subresource_hostport("img.google.com", 118);
+  const int kLatencyImg = 3;
+  const double kRateImg = 0.;
 
-  master->DeserializeReferrers(referral_list);
+  scoped_ptr<ListValue> referral_list(NewEmptySerializationList());
+  AddToSerializedList(
+      motivation_hostport, icon_subresource_hostport,
+      kLatencyIcon, kRateIcon, referral_list.get());
+  AddToSerializedList(
+      motivation_hostport, img_subresource_hostport,
+      kLatencyImg, kRateImg, referral_list.get());
+
+  master->DeserializeReferrers(*referral_list.get());
 
   ListValue recovered_referral_list;
   master->SerializeReferrers(&recovered_referral_list);
-  EXPECT_EQ(1U, recovered_referral_list.GetSize());
-  EXPECT_EQ(10, GetLatencyFromSerialization(motivation_hostname,
-                                            icon_subresource_hostname,
-                                            recovered_referral_list));
-  EXPECT_EQ(3, GetLatencyFromSerialization(motivation_hostname,
-                                            img_subresource_hostname,
-                                            recovered_referral_list));
+  EXPECT_EQ(2U, recovered_referral_list.GetSize());
+  int latency;
+  double rate;
+  EXPECT_TRUE(GetDataFromSerialization(
+      motivation_hostport, icon_subresource_hostport, recovered_referral_list,
+      &rate, &latency));
+  EXPECT_EQ(latency, kLatencyIcon);
+  EXPECT_EQ(rate, kRateIcon);
+
+  EXPECT_TRUE(GetDataFromSerialization(
+      motivation_hostport, img_subresource_hostport, recovered_referral_list,
+      &rate, &latency));
+  EXPECT_EQ(latency, kLatencyImg);
+  EXPECT_EQ(rate, kRateImg);
 
   // Each time we Trim, the latency figures should reduce by a factor of two,
   // until they both are 0, an then a trim will delete the whole entry.
   master->TrimReferrers();
   master->SerializeReferrers(&recovered_referral_list);
-  EXPECT_EQ(1U, recovered_referral_list.GetSize());
-  EXPECT_EQ(5, GetLatencyFromSerialization(motivation_hostname,
-                                            icon_subresource_hostname,
-                                            recovered_referral_list));
-  EXPECT_EQ(1, GetLatencyFromSerialization(motivation_hostname,
-                                            img_subresource_hostname,
-                                            recovered_referral_list));
+  EXPECT_EQ(2U, recovered_referral_list.GetSize());
+  EXPECT_TRUE(GetDataFromSerialization(
+      motivation_hostport, icon_subresource_hostport, recovered_referral_list,
+      &rate, &latency));
+  EXPECT_EQ(latency, kLatencyIcon / 2);
+  EXPECT_EQ(rate, kRateIcon);
+
+  EXPECT_TRUE(GetDataFromSerialization(
+      motivation_hostport, img_subresource_hostport, recovered_referral_list,
+      &rate, &latency));
+  EXPECT_EQ(latency, kLatencyImg / 2);
+  EXPECT_EQ(rate, kRateImg);
 
   master->TrimReferrers();
   master->SerializeReferrers(&recovered_referral_list);
-  EXPECT_EQ(1U, recovered_referral_list.GetSize());
-  EXPECT_EQ(2, GetLatencyFromSerialization(motivation_hostname,
-                                            icon_subresource_hostname,
-                                            recovered_referral_list));
-  EXPECT_EQ(0, GetLatencyFromSerialization(motivation_hostname,
-                                            img_subresource_hostname,
-                                            recovered_referral_list));
+  EXPECT_EQ(2U, recovered_referral_list.GetSize());
+  EXPECT_TRUE(GetDataFromSerialization(
+      motivation_hostport, icon_subresource_hostport, recovered_referral_list,
+      &rate, &latency));
+  EXPECT_EQ(latency, kLatencyIcon / 4);
+  EXPECT_EQ(rate, kRateIcon);
+  // Img is down to zero, but we don't delete it yet.
+  EXPECT_TRUE(GetDataFromSerialization(
+      motivation_hostport, img_subresource_hostport, recovered_referral_list,
+      &rate, &latency));
+  EXPECT_EQ(kLatencyImg / 4, 0);
+  EXPECT_EQ(latency, kLatencyImg / 4);
+  EXPECT_EQ(rate, kRateImg);
 
   master->TrimReferrers();
   master->SerializeReferrers(&recovered_referral_list);
-  EXPECT_EQ(1U, recovered_referral_list.GetSize());
-  EXPECT_EQ(1, GetLatencyFromSerialization(motivation_hostname,
-                                           icon_subresource_hostname,
-                                           recovered_referral_list));
-  EXPECT_EQ(0, GetLatencyFromSerialization(motivation_hostname,
-                                           img_subresource_hostname,
-                                           recovered_referral_list));
+  EXPECT_EQ(2U, recovered_referral_list.GetSize());
+  EXPECT_TRUE(GetDataFromSerialization(
+      motivation_hostport, icon_subresource_hostport, recovered_referral_list,
+      &rate, &latency));
+  EXPECT_EQ(latency, kLatencyIcon / 8);
+  EXPECT_EQ(rate, kRateIcon);
+
+  // Img is down to zero, but we don't delete it yet.
+  EXPECT_TRUE(GetDataFromSerialization(
+      motivation_hostport, img_subresource_hostport, recovered_referral_list,
+      &rate, &latency));
+  EXPECT_EQ(kLatencyImg / 8, 0);
+  EXPECT_EQ(latency, kLatencyImg / 8);
+  EXPECT_EQ(rate, kRateImg);
 
   master->TrimReferrers();
   master->SerializeReferrers(&recovered_referral_list);
-  EXPECT_EQ(0U, recovered_referral_list.GetSize());
-  EXPECT_EQ(kLatencyNotFound,
-            GetLatencyFromSerialization(motivation_hostname,
-                                        icon_subresource_hostname,
-                                        recovered_referral_list));
-  EXPECT_EQ(kLatencyNotFound,
-            GetLatencyFromSerialization(motivation_hostname,
-                                        img_subresource_hostname,
-                                        recovered_referral_list));
+  // Icon is also trimmed away, so entire set gets discarded.
+  EXPECT_EQ(1U, recovered_referral_list.GetSize());
+  EXPECT_FALSE(GetDataFromSerialization(
+      motivation_hostport, icon_subresource_hostport, recovered_referral_list,
+      &rate, &latency));
+  EXPECT_FALSE(GetDataFromSerialization(
+      motivation_hostport, img_subresource_hostport, recovered_referral_list,
+      &rate, &latency));
 
   master->Shutdown();
 }
@@ -504,25 +577,27 @@
 TEST_F(DnsMasterTest, PriorityQueuePushPopTest) {
   DnsMaster::HostNameQueue queue;
 
+  net::HostPortPair first("first", 80), second("second", 90);
+
   // First check high priority queue FIFO functionality.
   EXPECT_TRUE(queue.IsEmpty());
-  queue.Push("a", DnsHostInfo::LEARNED_REFERAL_MOTIVATED);
+  queue.Push(first, DnsHostInfo::LEARNED_REFERAL_MOTIVATED);
   EXPECT_FALSE(queue.IsEmpty());
-  queue.Push("b", DnsHostInfo::MOUSE_OVER_MOTIVATED);
+  queue.Push(second, DnsHostInfo::MOUSE_OVER_MOTIVATED);
   EXPECT_FALSE(queue.IsEmpty());
-  EXPECT_EQ(queue.Pop(), "a");
+  EXPECT_EQ(queue.Pop().ToString(), first.ToString());
   EXPECT_FALSE(queue.IsEmpty());
-  EXPECT_EQ(queue.Pop(), "b");
+  EXPECT_EQ(queue.Pop().ToString(), second.ToString());
   EXPECT_TRUE(queue.IsEmpty());
 
   // Then check low priority queue FIFO functionality.
-  queue.Push("a", DnsHostInfo::PAGE_SCAN_MOTIVATED);
+  queue.Push(first, DnsHostInfo::PAGE_SCAN_MOTIVATED);
   EXPECT_FALSE(queue.IsEmpty());
-  queue.Push("b", DnsHostInfo::OMNIBOX_MOTIVATED);
+  queue.Push(second, DnsHostInfo::OMNIBOX_MOTIVATED);
   EXPECT_FALSE(queue.IsEmpty());
-  EXPECT_EQ(queue.Pop(), "a");
+  EXPECT_EQ(queue.Pop().ToString(), first.ToString());
   EXPECT_FALSE(queue.IsEmpty());
-  EXPECT_EQ(queue.Pop(), "b");
+  EXPECT_EQ(queue.Pop().ToString(), second.ToString());
   EXPECT_TRUE(queue.IsEmpty());
 }
 
@@ -530,31 +605,40 @@
   DnsMaster::HostNameQueue queue;
 
   // Push all the low priority items.
+  net::HostPortPair low1("low1", 80),
+      low2("low2", 80),
+      low3("low3", 443),
+      low4("low4", 80),
+      low5("low5", 80),
+      hi1("hi1", 80),
+      hi2("hi2", 80),
+      hi3("hi3", 80);
+
   EXPECT_TRUE(queue.IsEmpty());
-  queue.Push("scan", DnsHostInfo::PAGE_SCAN_MOTIVATED);
-  queue.Push("unit", DnsHostInfo::UNIT_TEST_MOTIVATED);
-  queue.Push("lmax", DnsHostInfo::LINKED_MAX_MOTIVATED);
-  queue.Push("omni", DnsHostInfo::OMNIBOX_MOTIVATED);
-  queue.Push("startup", DnsHostInfo::STARTUP_LIST_MOTIVATED);
-  queue.Push("omni", DnsHostInfo::OMNIBOX_MOTIVATED);
+  queue.Push(low1, DnsHostInfo::PAGE_SCAN_MOTIVATED);
+  queue.Push(low2, DnsHostInfo::UNIT_TEST_MOTIVATED);
+  queue.Push(low3, DnsHostInfo::LINKED_MAX_MOTIVATED);
+  queue.Push(low4, DnsHostInfo::OMNIBOX_MOTIVATED);
+  queue.Push(low5, DnsHostInfo::STARTUP_LIST_MOTIVATED);
+  queue.Push(low4, DnsHostInfo::OMNIBOX_MOTIVATED);
 
   // Push all the high prority items
-  queue.Push("learned", DnsHostInfo::LEARNED_REFERAL_MOTIVATED);
-  queue.Push("refer", DnsHostInfo::STATIC_REFERAL_MOTIVATED);
-  queue.Push("mouse", DnsHostInfo::MOUSE_OVER_MOTIVATED);
+  queue.Push(hi1, DnsHostInfo::LEARNED_REFERAL_MOTIVATED);
+  queue.Push(hi2, DnsHostInfo::STATIC_REFERAL_MOTIVATED);
+  queue.Push(hi3, DnsHostInfo::MOUSE_OVER_MOTIVATED);
 
   // Check that high priority stuff comes out first, and in FIFO order.
-  EXPECT_EQ(queue.Pop(), "learned");
-  EXPECT_EQ(queue.Pop(), "refer");
-  EXPECT_EQ(queue.Pop(), "mouse");
+  EXPECT_EQ(queue.Pop().ToString(), hi1.ToString());
+  EXPECT_EQ(queue.Pop().ToString(), hi2.ToString());
+  EXPECT_EQ(queue.Pop().ToString(), hi3.ToString());
 
   // ...and then low priority strings.
-  EXPECT_EQ(queue.Pop(), "scan");
-  EXPECT_EQ(queue.Pop(), "unit");
-  EXPECT_EQ(queue.Pop(), "lmax");
-  EXPECT_EQ(queue.Pop(), "omni");
-  EXPECT_EQ(queue.Pop(), "startup");
-  EXPECT_EQ(queue.Pop(), "omni");
+  EXPECT_EQ(queue.Pop().ToString(), low1.ToString());
+  EXPECT_EQ(queue.Pop().ToString(), low2.ToString());
+  EXPECT_EQ(queue.Pop().ToString(), low3.ToString());
+  EXPECT_EQ(queue.Pop().ToString(), low4.ToString());
+  EXPECT_EQ(queue.Pop().ToString(), low5.ToString());
+  EXPECT_EQ(queue.Pop().ToString(), low4.ToString());
 
   EXPECT_TRUE(queue.IsEmpty());
 }
diff --git a/chrome/browser/net/preconnect.cc b/chrome/browser/net/preconnect.cc
new file mode 100644
index 0000000..ff200de
--- /dev/null
+++ b/chrome/browser/net/preconnect.cc
@@ -0,0 +1,67 @@
+// 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/preconnect.h"
+
+#include "base/logging.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/chrome_thread.h"
+#include "chrome/common/net/url_request_context_getter.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_transaction_factory.h"
+#include "net/url_request/url_request_context.h"
+
+namespace chrome_browser_net {
+
+// We will deliberately leak this singular instance, which is used only for
+// callbacks.
+// static
+Preconnect* Preconnect::callback_instance_;
+
+// static
+bool Preconnect::PreconnectOnUIThread(const net::HostPortPair& hostport) {
+  // Try to do connection warming for this search provider.
+  URLRequestContextGetter* getter = Profile::GetDefaultRequestContext();
+  if (!getter)
+    return false;
+  // Prewarm connection to Search URL.
+  ChromeThread::PostTask(
+      ChromeThread::IO,
+      FROM_HERE,
+      NewRunnableFunction(Preconnect::PreconnectOnIOThread, hostport));
+  return true;
+}
+
+// static
+void Preconnect::PreconnectOnIOThread(const net::HostPortPair& hostport) {
+  URLRequestContextGetter* getter = Profile::GetDefaultRequestContext();
+  if (!getter)
+    return;
+  if (!ChromeThread::CurrentlyOn(ChromeThread::IO)) {
+    LOG(DFATAL) << "This must be run only on the IO thread.";
+    return;
+  }
+  URLRequestContext* context = getter->GetURLRequestContext();
+  net::HttpTransactionFactory* factory = context->http_transaction_factory();
+  net::HttpNetworkSession* session = factory->GetSession();
+  scoped_refptr<net::TCPClientSocketPool> pool = session->tcp_socket_pool();
+
+  net::TCPSocketParams params(hostport.host, hostport.port, net::LOW,
+                              GURL(), false);
+
+  net::ClientSocketHandle handle;
+  if (!callback_instance_)
+    callback_instance_ = new Preconnect;
+
+  // TODO(jar): This does not handle proxies currently.
+  handle.Init(hostport.ToString() , params, net::LOWEST,
+              callback_instance_, pool, net::BoundNetLog());
+  handle.Reset();
+}
+
+void Preconnect::RunWithParams(const Tuple1<int>& params) {
+  // This will rarely be called, as we reset the connection just after creating.
+  NOTREACHED();
+}
+}  // chrome_browser_net
diff --git a/chrome/browser/net/preconnect.h b/chrome/browser/net/preconnect.h
new file mode 100644
index 0000000..d4fb0dd5
--- /dev/null
+++ b/chrome/browser/net/preconnect.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2006-2010 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.
+
+// A Preconnect instance maintains state while a TCP/IP connection is made, and
+// and then released into the pool of available connections for future use.
+
+#ifndef CHROME_BROWSER_NET_PRECONNECT_H_
+#define CHROME_BROWSER_NET_PRECONNECT_H_
+
+#include "base/ref_counted.h"
+#include "net/base/completion_callback.h"
+#include "net/base/host_port_pair.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/tcp_client_socket_pool.h"
+#include "net/url_request/url_request_context.h"
+
+namespace chrome_browser_net {
+
+class Preconnect : public net::CompletionCallback {
+ public:
+  static bool PreconnectOnUIThread(const net::HostPortPair& hostport);
+
+  static void PreconnectOnIOThread(const net::HostPortPair& hostport);
+
+ private:
+  Preconnect() {}
+
+  // Supply an instance that could have been used in an IO callback, but will
+  // never actually be used (because we reset the connection so quickly).
+  static Preconnect* callback_instance_;
+
+  // IO Callback which whould be performed when the connection is established.
+  virtual void RunWithParams(const Tuple1<int>& params);
+
+  DISALLOW_COPY_AND_ASSIGN(Preconnect);
+};
+}  // chrome_browser_net
+
+#endif  // CHROME_BROWSER_NET_PRECONNECT_H_
diff --git a/chrome/browser/net/referrer.cc b/chrome/browser/net/referrer.cc
index bf171e5..736790a 100644
--- a/chrome/browser/net/referrer.cc
+++ b/chrome/browser/net/referrer.cc
@@ -4,69 +4,119 @@
 
 #include "chrome/browser/net/referrer.h"
 
+#include <limits.h>
+
 #include "base/logging.h"
 
 namespace chrome_browser_net {
 
-void Referrer::SuggestHost(const std::string& host) {
+//------------------------------------------------------------------------------
+// Smoothing parameter for updating subresource_use_rate_.
+
+// We always combine our old expected value, weighted by some factor, with the
+// new expected value Enew.  The new "expected value" is the number of actual
+// connections made due to the curernt navigations.
+// This means the formula (in a concise form) is:
+// Eupdated = Eold * W  + Enew * (1 - W)
+// That means that IF we end up needing to connect, we should apply the formula:
+// Pupdated = Pold * W  +  Enew * (1 - W)
+// If we visit the containing url, but don't end up needing a connection:
+// Pupdated = Pold * W
+// To achive the above upating algorithm, we end up doing the multiplication
+// by W every time we contemplate doing a preconneciton (i.e., when we navigate
+// to the containing URL, and consider doing a preconnection), and then IFF we
+// learn that we really needed a connection to the subresource, we complete the
+// above algorithm by adding the (1 - W) for each connection we make.
+
+// We weight the new expected value by a factor which is in the range of 0.0 to
+// 1.0.
+static const double kWeightingForOldExpectedValue = 0.66;
+
+// The expected value needed before we actually do a preconnection.
+static const double kPreconnectWorthyExpectedValue = 0.7;
+
+// The expected value that we'll need a preconnection when we first see the
+// subresource getting fetched.  Very conservative is 0.0, which will mean that
+// we have to wait for a while before using preconnection... but we do persist
+// results, so we'll have the learned answer in the long run.
+static const double kInitialExpectedValue = 0.0;
+
+// static
+bool Referrer::use_preconnect_valuations_ = false;
+
+void Referrer::SuggestHost(const net::HostPortPair& hostport) {
   // Limit how large our list can get, in case we make mistakes about what
   // hostnames are in sub-resources (example: Some advertisments have a link to
   // the ad agency, and then provide a "surprising" redirect to the advertised
   // entity, which then (mistakenly) appears to be a subresource on the page
   // hosting the ad).
   // TODO(jar): Do experiments to optimize the max count of suggestions.
-  static const size_t kMaxSuggestions = 8;
+  static const size_t kMaxSuggestions = 10;
 
-  if (host.empty())
+  if (hostport.host.empty())  // Is this really needed????
     return;
+  SubresourceMap::iterator it = find(hostport);
+  if (it != end()) {
+    it->second.SubresourceIsNeeded();
+    return;
+  }
+
   if (kMaxSuggestions <= size()) {
     DeleteLeastUseful();
     DCHECK(kMaxSuggestions > size());
   }
-  // Add in the new suggestion.
-  (*this)[host];
+  (*this)[hostport].SubresourceIsNeeded();
 }
 
 void Referrer::DeleteLeastUseful() {
-  std::string least_useful_name;
+  // Find the item with the lowest value.  Most important is preconnection_rate,
+  // next is latency savings, and last is lifetime (age).
+  net::HostPortPair least_useful_hostport;
+  double lowest_rate_seen = 0.0;
   // We use longs for durations because we will use multiplication on them.
-  int64 least_useful_latency = 0;  // Duration in milliseconds.
+  int64 lowest_latency_seen = 0;  // Duration in milliseconds.
   int64 least_useful_lifetime = 0;  // Duration in milliseconds.
 
   const base::Time kNow(base::Time::Now());  // Avoid multiple calls.
-  for (HostNameMap::iterator it = begin(); it != end(); ++it) {
+  for (SubresourceMap::iterator it = begin(); it != end(); ++it) {
     int64 lifetime = (kNow - it->second.birth_time()).InMilliseconds();
     int64 latency = it->second.latency().InMilliseconds();
-    if (!least_useful_name.empty()) {
-      if (!latency && !least_useful_latency) {
+    double rate = it->second.subresource_use_rate();
+    if (!least_useful_hostport.host.empty()) {
+      if (rate > lowest_rate_seen)
+        continue;
+      if (!latency && !lowest_latency_seen) {
         // Older name is less useful.
         if (lifetime <= least_useful_lifetime)
           continue;
       } else {
-        // Compare the ratios latency/lifetime vs.
-        // least_useful_latency/least_useful_lifetime by cross multiplying (to
-        // avoid integer division hassles).  Overflow's won't happen until
-        // both latency and lifetime pass about 49 days.
-        if (latency * least_useful_lifetime >=
-            least_useful_latency * lifetime) {
+        // Compare the ratios:
+        // latency/lifetime
+        // vs.
+        // lowest_latency_seen/least_useful_lifetime
+        // by cross multiplying (to avoid integer division hassles).  Overflow's
+        // won't happen until both latency and lifetime pass about 49 days.
+        if (latency * least_useful_lifetime >
+            lowest_latency_seen * lifetime) {
           continue;
         }
       }
     }
-    least_useful_name = it->first;
-    least_useful_latency = latency;
+    least_useful_hostport = it->first;
+    lowest_rate_seen = rate;
+    lowest_latency_seen = latency;
     least_useful_lifetime = lifetime;
   }
-  erase(least_useful_name);
-  // Note: there is a small chance that we will discard a least_useful_name
+  erase(least_useful_hostport);
+  // Note: there is a small chance that we will discard a least_useful_hostport
   // that is currently being prefetched because it *was* in this referer list.
   // In that case, when a benefit appears in AccrueValue() below, we are careful
   // to check before accessing the member.
 }
 
 void Referrer::AccrueValue(const base::TimeDelta& delta,
-                           const std::string& host) {
-  HostNameMap::iterator it = find(host);
+                           const net::HostPortPair& hostport) {
+  SubresourceMap::iterator it = find(hostport);
   // Be careful that we weren't evicted from this referrer in DeleteLeastUseful.
   if (it != end())
     it->second.AccrueValue(delta);
@@ -74,7 +124,7 @@
 
 bool Referrer::Trim() {
   bool has_some_latency_left = false;
-  for (HostNameMap::iterator it = begin(); it != end(); ++it)
+  for (SubresourceMap::iterator it = begin(); it != end(); ++it)
     if (it->second.Trim())
       has_some_latency_left = true;
   return has_some_latency_left;
@@ -83,7 +133,8 @@
 bool ReferrerValue::Trim() {
   int64 latency_ms = latency_.InMilliseconds() / 2;
   latency_ = base::TimeDelta::FromMilliseconds(latency_ms);
-  return latency_ms > 0;
+  return latency_ms > 0 ||
+      subresource_use_rate_ > kPreconnectWorthyExpectedValue / 2;
 }
 
 
@@ -91,42 +142,85 @@
   if (value.GetType() != Value::TYPE_LIST)
     return;
   const ListValue* subresource_list(static_cast<const ListValue*>(&value));
-  for (size_t index = 0; index + 1 < subresource_list->GetSize(); index += 2) {
+  size_t index = 0;  // Bounds checking is done by subresource_list->Get*().
+  while (true) {
+    int port;
+    if (!subresource_list->GetInteger(index++, &port))
+      return;
     std::string host;
-    if (!subresource_list->GetString(index, &host))
+    if (!subresource_list->GetString(index++, &host))
       return;
     int latency_ms;
-    if (!subresource_list->GetInteger(index + 1, &latency_ms))
+    if (!subresource_list->GetInteger(index++, &latency_ms))
       return;
+    double rate;
+    if (!subresource_list->GetReal(index++, &rate))
+      return;
+
+    net::HostPortPair hostport(host, port);
     base::TimeDelta latency = base::TimeDelta::FromMilliseconds(latency_ms);
     // TODO(jar): We could be more direct, and change birth date or similar to
     // show that this is a resurrected value we're adding in.  I'm not yet sure
     // of how best to optimize the learning and pruning (Trim) algorithm at this
     // level, so for now, we just suggest subresources, which leaves them all
     // with the same birth date (typically start of process).
-    SuggestHost(host);
-    AccrueValue(latency, host);
+    SuggestHost(hostport);
+    AccrueValue(latency, hostport);
+    (*this)[hostport].SetSubresourceUseRate(rate);
   }
 }
 
 Value* Referrer::Serialize() const {
   ListValue* subresource_list(new ListValue);
   for (const_iterator it = begin(); it != end(); ++it) {
-    StringValue* host(new StringValue(it->first));
+    FundamentalValue* port(new FundamentalValue(it->first.port));
+    StringValue* host(new StringValue(it->first.host));
     int latency_integer = static_cast<int>(it->second.latency().
                                            InMilliseconds());
     // Watch out for overflow in the above static_cast!  Check to see if we went
     // negative, and just use a "big" value.  The value seems unimportant once
     // we get to such high latencies.  Probable cause of high latency is a bug
     // in other code, so also do a DCHECK.
-    DCHECK(latency_integer >= 0);
+    DCHECK_GE(latency_integer, 0);
     if (latency_integer < 0)
       latency_integer = INT_MAX;
     FundamentalValue* latency(new FundamentalValue(latency_integer));
+    FundamentalValue* rate(new FundamentalValue(
+        it->second.subresource_use_rate()));
+
+    subresource_list->Append(port);
     subresource_list->Append(host);
     subresource_list->Append(latency);
+    subresource_list->Append(rate);
   }
   return subresource_list;
 }
 
+//------------------------------------------------------------------------------
+
+ReferrerValue::ReferrerValue()
+    : birth_time_(base::Time::Now()),
+      navigation_count_(0),
+      preconnection_count_(0),
+      subresource_use_rate_(kInitialExpectedValue) {
+}
+
+void ReferrerValue::SubresourceIsNeeded() {
+  DCHECK_GE(kWeightingForOldExpectedValue, 0);
+  DCHECK_LE(kWeightingForOldExpectedValue, 1.0);
+  ++navigation_count_;
+  subresource_use_rate_ += 1 - kWeightingForOldExpectedValue;
+}
+
+bool ReferrerValue::IsPreconnectWorthDoing() {
+  bool preconnecting = kPreconnectWorthyExpectedValue < subresource_use_rate_;
+  if (preconnecting)
+    ++preconnection_count_;
+  subresource_use_rate_ *= kWeightingForOldExpectedValue;
+  // Note: the use rate is temporarilly possibly incorect, as we need to find
+  // out if we really end up connecting.  This will happen in a few hundred
+  // milliseconds (when content arrives, etc.).
+  return preconnecting;
+}
+
 }  // namespace chrome_browser_net
diff --git a/chrome/browser/net/referrer.h b/chrome/browser/net/referrer.h
index 8c252b6..bef13a1b 100644
--- a/chrome/browser/net/referrer.h
+++ b/chrome/browser/net/referrer.h
@@ -20,36 +20,61 @@
 #include "base/basictypes.h"
 #include "base/time.h"
 #include "base/values.h"
-#include "googleurl/src/gurl.h"
+#include "net/base/host_port_pair.h"
 
 namespace chrome_browser_net {
 
 //------------------------------------------------------------------------------
 // For each hostname in a Referrer, we have a ReferrerValue.  It indicates
-// exactly how much value (re: latency reduction) has resulted from having this
-// entry.
+// exactly how much value (re: latency reduction, or connection use) has
+// resulted from having this entry.
 class ReferrerValue {
  public:
-  ReferrerValue() : birth_time_(base::Time::Now()) {}
+  ReferrerValue();
+
+  // Used during deserialization.
+  void SetSubresourceUseRate(double rate) { subresource_use_rate_ = rate; }
 
   base::TimeDelta latency() const { return latency_; }
   base::Time birth_time() const { return birth_time_; }
   void AccrueValue(const base::TimeDelta& delta) { latency_ += delta; }
 
+  // Record the fact that we navigated to the associated subresource URL.
+  void SubresourceIsNeeded();
+
+  // Evaluate if it is worth making this preconnection, and return true if it
+  // seems worthwhile.  As a side effect, we also tally the proconnection for
+  // statistical purposes only.
+  bool IsPreconnectWorthDoing();
+
+  int64 navigation_count() const { return navigation_count_; }
+  int64 preconnection_count() const { return preconnection_count_; }
+  double subresource_use_rate() const { return subresource_use_rate_; }
+
   // Reduce the latency figure by a factor of 2, and return true if any latency
   // remains.
   bool Trim();
 
  private:
-  base::TimeDelta latency_;  // Accumulated latency savings.
+  base::TimeDelta latency_;  // Accumulated DNS resolution latency savings.
   const base::Time birth_time_;
+
+  // The number of times this item was navigated to with the fixed referrer.
+  int64 navigation_count_;
+
+  // The number of times this item was preconnected as a consequence of its
+  // referrer.
+  int64 preconnection_count_;
+
+  // A smoothed estimate of the probability that a connection will be needed.
+  double subresource_use_rate_;
 };
 
 //------------------------------------------------------------------------------
 // A list of domain names to pre-resolve. The names are the keys to this map,
 // and the values indicate the amount of benefit derived from having each name
 // around.
-typedef std::map<std::string, ReferrerValue> HostNameMap;
+typedef std::map<net::HostPortPair, ReferrerValue> SubresourceMap;
 
 //------------------------------------------------------------------------------
 // There is one Referrer instance for each hostname that has acted as an HTTP
@@ -58,18 +83,23 @@
 // was probably needed as a subresource of a page, and was not otherwise
 // predictable until the content with the reference arrived).  Most typically,
 // an outer page was a page fetched by the user, and this instance lists names
-// in HostNameMap which are subresources and that were needed to complete the
+// in SubresourceMap which are subresources and that were needed to complete the
 // rendering of the outer page.
-class Referrer : public HostNameMap {
+class Referrer : public SubresourceMap {
  public:
-  // Add the indicated host to the list of hosts that are resolved via DNS when
-  // the user navigates to this referrer.  Note that if the list is long, an
-  // entry may be discarded to make room for this insertion.
-  void SuggestHost(const std::string& host);
+  Referrer() : use_count_(1) {}
+  void IncrementUseCount() { ++use_count_; }
+  int64 use_count() const { return use_count_; }
 
-  // Record additional usefulness of having this host name in the list.
+  // Add the indicated host/port to the list of hosts that are resolved via DNS
+  // when the user navigates to this referrer.  Note that if the list is long,
+  // an entry may be discarded to make room for this insertion.
+  void SuggestHost(const net::HostPortPair& hostport);
+
+  // Record additional usefulness of having this host/port name in the list.
   // Value is expressed as positive latency of amount delta.
-  void AccrueValue(const base::TimeDelta& delta, const std::string& host);
+  void AccrueValue(const base::TimeDelta& delta,
+                   const net::HostPortPair& hostport);
 
   // Trim the Referrer, by first diminishing (scaling down) the latency for each
   // ReferredValue.
@@ -80,6 +110,10 @@
   Value* Serialize() const;
   void Deserialize(const Value& referrers);
 
+  static void SetUsePreconnectValuations(bool dns) {
+      use_preconnect_valuations_ = dns;
+  }
+
  private:
   // Helper function for pruning list.  Metric for usefulness is "large accrued
   // value," in the form of latency_ savings associated with a host name.  We
@@ -89,6 +123,13 @@
   // accrue savings as quickly.
   void DeleteLeastUseful();
 
+  // The number of times this referer had its subresources scaned for possible
+  // preconnection or DNS preresolution.
+  int64 use_count_;
+
+  // Select between DNS prefetch latency savings, or preconnection valuations
+  // for a metric to decide which referers to save.
+  static bool use_preconnect_valuations_;
 
   // We put these into a std::map<>, so we need copy constructors.
   // DISALLOW_COPY_AND_ASSIGN(Referrer);
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 55a75ce7..def903d 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -1644,6 +1644,8 @@
         'browser/net/metadata_url_request.h',
         'browser/net/passive_log_collector.cc',
         'browser/net/passive_log_collector.h',
+        'browser/net/preconnect.cc',
+        'browser/net/preconnect.h',
         'browser/net/referrer.cc',
         'browser/net/referrer.h',
         'browser/net/resolve_proxy_msg_helper.cc',
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index c9028cfe..2b14989 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -321,6 +321,9 @@
 // Enable Native Web Worker support.
 const char kEnableNativeWebWorkers[]        = "enable-native-web-workers";
 
+// Enable speculative TCP/IP preconnection.
+const char kEnablePreconnect[]              = "enable-preconnect";
+
 // Enable Privacy Blacklists.
 const char kEnablePrivacyBlacklists[]       = "enable-privacy-blacklists";
 
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index bc82fd5..d9ac1e9fa 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -105,6 +105,7 @@
 extern const char kEnableMonitorProfile[];
 extern const char kEnableNaCl[];
 extern const char kEnableNativeWebWorkers[];
+extern const char kEnablePreconnect[];
 extern const char kEnablePrivacyBlacklists[];
 extern const char kEnableRendererAccessibility[];
 extern const char kEnableStatsTable[];
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index 4d73e6a..b7dc105 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -164,8 +164,8 @@
 const wchar_t kDnsStartupPrefetchList[] = L"StartupDNSPrefetchList";
 
 // A list of host names used to fetch web pages, and their commonly used
-// sub-resource hostnames (and expected latency benefits from pre-resolving such
-// sub-resource hostnames).
+// sub-resource hostnames (and expected latency benefits from pre-resolving, or
+// preconnecting to, such sub-resource hostnames).
 // This list is adaptively grown and pruned.
 const wchar_t kDnsHostReferralList[] = L"HostReferralList";