• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/net/predictor.h"
6 
7 #include <algorithm>
8 #include <cmath>
9 #include <set>
10 #include <sstream>
11 
12 #include "base/basictypes.h"
13 #include "base/bind.h"
14 #include "base/compiler_specific.h"
15 #include "base/containers/mru_cache.h"
16 #include "base/metrics/histogram.h"
17 #include "base/prefs/pref_service.h"
18 #include "base/prefs/scoped_user_pref_update.h"
19 #include "base/stl_util.h"
20 #include "base/strings/string_split.h"
21 #include "base/strings/string_util.h"
22 #include "base/strings/stringprintf.h"
23 #include "base/synchronization/waitable_event.h"
24 #include "base/threading/thread_restrictions.h"
25 #include "base/time/time.h"
26 #include "base/values.h"
27 #include "chrome/browser/io_thread.h"
28 #include "chrome/browser/net/preconnect.h"
29 #include "chrome/browser/net/spdyproxy/proxy_advisor.h"
30 #include "chrome/browser/prefs/session_startup_pref.h"
31 #include "chrome/common/chrome_switches.h"
32 #include "chrome/common/pref_names.h"
33 #include "components/data_reduction_proxy/browser/data_reduction_proxy_settings.h"
34 #include "components/pref_registry/pref_registry_syncable.h"
35 #include "content/public/browser/browser_thread.h"
36 #include "net/base/address_list.h"
37 #include "net/base/completion_callback.h"
38 #include "net/base/host_port_pair.h"
39 #include "net/base/net_errors.h"
40 #include "net/base/net_log.h"
41 #include "net/dns/host_resolver.h"
42 #include "net/dns/single_request_host_resolver.h"
43 #include "net/http/transport_security_state.h"
44 #include "net/ssl/ssl_config_service.h"
45 #include "net/url_request/url_request_context.h"
46 #include "net/url_request/url_request_context_getter.h"
47 
48 using base::TimeDelta;
49 using content::BrowserThread;
50 
51 namespace chrome_browser_net {
52 
53 // static
54 const int Predictor::kPredictorReferrerVersion = 2;
55 const double Predictor::kPreconnectWorthyExpectedValue = 0.8;
56 const double Predictor::kDNSPreresolutionWorthyExpectedValue = 0.1;
57 const double Predictor::kDiscardableExpectedValue = 0.05;
58 // The goal is of trimming is to to reduce the importance (number of expected
59 // subresources needed) by a factor of 2 after about 24 hours of uptime. We will
60 // trim roughly once-an-hour of uptime.  The ratio to use in each trim operation
61 // is then the 24th root of 0.5.  If a user only surfs for 4 hours a day, then
62 // after about 6 days they will have halved all their estimates of subresource
63 // connections.  Once this falls below kDiscardableExpectedValue the referrer
64 // will be discarded.
65 // TODO(jar): Measure size of referrer lists in the field.  Consider an adaptive
66 // system that uses a higher trim ratio when the list is large.
67 // static
68 const double Predictor::kReferrerTrimRatio = 0.97153;
69 const int64 Predictor::kDurationBetweenTrimmingsHours = 1;
70 const int64 Predictor::kDurationBetweenTrimmingIncrementsSeconds = 15;
71 const size_t Predictor::kUrlsTrimmedPerIncrement = 5u;
72 const size_t Predictor::kMaxSpeculativeParallelResolves = 3;
73 const int Predictor::kMaxUnusedSocketLifetimeSecondsWithoutAGet = 10;
74 // To control our congestion avoidance system, which discards a queue when
75 // resolutions are "taking too long," we need an expected resolution time.
76 // Common average is in the range of 300-500ms.
77 const int kExpectedResolutionTimeMs = 500;
78 const int Predictor::kTypicalSpeculativeGroupSize = 8;
79 const int Predictor::kMaxSpeculativeResolveQueueDelayMs =
80     (kExpectedResolutionTimeMs * Predictor::kTypicalSpeculativeGroupSize) /
81     Predictor::kMaxSpeculativeParallelResolves;
82 
83 static int g_max_queueing_delay_ms =
84     Predictor::kMaxSpeculativeResolveQueueDelayMs;
85 static size_t g_max_parallel_resolves =
86     Predictor::kMaxSpeculativeParallelResolves;
87 
88 // A version number for prefs that are saved. This should be incremented when
89 // we change the format so that we discard old data.
90 static const int kPredictorStartupFormatVersion = 1;
91 
92 class Predictor::LookupRequest {
93  public:
LookupRequest(Predictor * predictor,net::HostResolver * host_resolver,const GURL & url)94   LookupRequest(Predictor* predictor,
95                 net::HostResolver* host_resolver,
96                 const GURL& url)
97       : predictor_(predictor),
98         url_(url),
99         resolver_(host_resolver) {
100   }
101 
102   // Return underlying network resolver status.
103   // net::OK ==> Host was found synchronously.
104   // net:ERR_IO_PENDING ==> Network will callback later with result.
105   // anything else ==> Host was not found synchronously.
Start()106   int Start() {
107     net::HostResolver::RequestInfo resolve_info(
108         net::HostPortPair::FromURL(url_));
109 
110     // Make a note that this is a speculative resolve request. This allows us
111     // to separate it from real navigations in the observer's callback, and
112     // lets the HostResolver know it can de-prioritize it.
113     resolve_info.set_is_speculative(true);
114     return resolver_.Resolve(
115         resolve_info,
116         net::DEFAULT_PRIORITY,
117         &addresses_,
118         base::Bind(&LookupRequest::OnLookupFinished, base::Unretained(this)),
119         net::BoundNetLog());
120   }
121 
122  private:
OnLookupFinished(int result)123   void OnLookupFinished(int result) {
124     predictor_->OnLookupFinished(this, url_, result == net::OK);
125   }
126 
127   Predictor* predictor_;  // The predictor which started us.
128 
129   const GURL url_;  // Hostname to resolve.
130   net::SingleRequestHostResolver resolver_;
131   net::AddressList addresses_;
132 
133   DISALLOW_COPY_AND_ASSIGN(LookupRequest);
134 };
135 
136 // This records UMAs for preconnect usage based on navigation URLs to
137 // gather precision/recall for user-event based preconnect triggers.
138 // Stats are gathered via a LRU cache that remembers all preconnect within the
139 // last N seconds.
140 // A preconnect trigger is considered as used iff a navigation including
141 // access to the preconnected host occurs within a time period specified by
142 // kMaxUnusedSocketLifetimeSecondsWithoutAGet.
143 class Predictor::PreconnectUsage {
144  public:
145   PreconnectUsage();
146   ~PreconnectUsage();
147 
148   // Record a preconnect trigger to |url|.
149   void ObservePreconnect(const GURL& url);
150 
151   // Record a user navigation with its redirect history, |url_chain|.
152   // We are uncertain if this is actually a link navigation.
153   void ObserveNavigationChain(const std::vector<GURL>& url_chain,
154                               bool is_subresource);
155 
156   // Record a user link navigation to |final_url|.
157   // We are certain that this is a user-triggered link navigation.
158   void ObserveLinkNavigation(const GURL& final_url);
159 
160  private:
161   // This tracks whether a preconnect was used in some navigation or not
162   class PreconnectPrecisionStat {
163    public:
PreconnectPrecisionStat()164     PreconnectPrecisionStat()
165         : timestamp_(base::TimeTicks::Now()),
166           was_used_(false) {
167     }
168 
timestamp()169     const base::TimeTicks& timestamp() { return timestamp_; }
170 
set_was_used()171     void set_was_used() { was_used_ = true; }
was_used() const172     bool was_used() const { return was_used_; }
173 
174    private:
175     base::TimeTicks timestamp_;
176     bool was_used_;
177   };
178 
179   typedef base::MRUCache<GURL, PreconnectPrecisionStat> MRUPreconnects;
180   MRUPreconnects mru_preconnects_;
181 
182   // The longest time an entry can persist in mru_preconnect_
183   const base::TimeDelta max_duration_;
184 
185   std::vector<GURL> recent_navigation_chain_;
186 
187   DISALLOW_COPY_AND_ASSIGN(PreconnectUsage);
188 };
189 
PreconnectUsage()190 Predictor::PreconnectUsage::PreconnectUsage()
191     : mru_preconnects_(MRUPreconnects::NO_AUTO_EVICT),
192       max_duration_(base::TimeDelta::FromSeconds(
193           Predictor::kMaxUnusedSocketLifetimeSecondsWithoutAGet)) {
194 }
195 
~PreconnectUsage()196 Predictor::PreconnectUsage::~PreconnectUsage() {}
197 
ObservePreconnect(const GURL & url)198 void Predictor::PreconnectUsage::ObservePreconnect(const GURL& url) {
199   // Evict any overly old entries and record stats.
200   base::TimeTicks now = base::TimeTicks::Now();
201 
202   MRUPreconnects::reverse_iterator eldest_preconnect =
203       mru_preconnects_.rbegin();
204   while (!mru_preconnects_.empty()) {
205     DCHECK(eldest_preconnect == mru_preconnects_.rbegin());
206     if (now - eldest_preconnect->second.timestamp() < max_duration_)
207       break;
208 
209     UMA_HISTOGRAM_BOOLEAN("Net.PreconnectTriggerUsed",
210                           eldest_preconnect->second.was_used());
211     eldest_preconnect = mru_preconnects_.Erase(eldest_preconnect);
212   }
213 
214   // Add new entry.
215   GURL canonical_url(Predictor::CanonicalizeUrl(url));
216   mru_preconnects_.Put(canonical_url, PreconnectPrecisionStat());
217 }
218 
ObserveNavigationChain(const std::vector<GURL> & url_chain,bool is_subresource)219 void Predictor::PreconnectUsage::ObserveNavigationChain(
220     const std::vector<GURL>& url_chain,
221     bool is_subresource) {
222   if (url_chain.empty())
223     return;
224 
225   if (!is_subresource)
226     recent_navigation_chain_ = url_chain;
227 
228   GURL canonical_url(Predictor::CanonicalizeUrl(url_chain.back()));
229 
230   MRUPreconnects::iterator itPreconnect = mru_preconnects_.Peek(canonical_url);
231   bool was_preconnected = (itPreconnect != mru_preconnects_.end());
232 
233   // This is an UMA which was named incorrectly. This actually measures the
234   // ratio of URLRequests which have used a preconnected session.
235   UMA_HISTOGRAM_BOOLEAN("Net.PreconnectedNavigation", was_preconnected);
236 }
237 
ObserveLinkNavigation(const GURL & url)238 void Predictor::PreconnectUsage::ObserveLinkNavigation(const GURL& url) {
239   if (recent_navigation_chain_.empty() ||
240       url != recent_navigation_chain_.back()) {
241     // The navigation chain is not available for this navigation.
242     recent_navigation_chain_.clear();
243     recent_navigation_chain_.push_back(url);
244   }
245 
246   // See if the link navigation involved preconnected session.
247   bool did_use_preconnect = false;
248   for (std::vector<GURL>::const_iterator it = recent_navigation_chain_.begin();
249        it != recent_navigation_chain_.end();
250        ++it) {
251     GURL canonical_url(Predictor::CanonicalizeUrl(*it));
252 
253     // Record the preconnect trigger for the url as used if exist
254     MRUPreconnects::iterator itPreconnect =
255         mru_preconnects_.Peek(canonical_url);
256     bool was_preconnected = (itPreconnect != mru_preconnects_.end());
257     if (was_preconnected) {
258       itPreconnect->second.set_was_used();
259       did_use_preconnect = true;
260     }
261   }
262 
263   UMA_HISTOGRAM_BOOLEAN("Net.PreconnectedLinkNavigations", did_use_preconnect);
264 }
265 
Predictor(bool preconnect_enabled)266 Predictor::Predictor(bool preconnect_enabled)
267     : url_request_context_getter_(NULL),
268       predictor_enabled_(true),
269       peak_pending_lookups_(0),
270       shutdown_(false),
271       max_concurrent_dns_lookups_(g_max_parallel_resolves),
272       max_dns_queue_delay_(
273           TimeDelta::FromMilliseconds(g_max_queueing_delay_ms)),
274       host_resolver_(NULL),
275       transport_security_state_(NULL),
276       ssl_config_service_(NULL),
277       preconnect_enabled_(preconnect_enabled),
278       consecutive_omnibox_preconnect_count_(0),
279       next_trim_time_(base::TimeTicks::Now() +
280                       TimeDelta::FromHours(kDurationBetweenTrimmingsHours)),
281       observer_(NULL) {
282   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
283 }
284 
~Predictor()285 Predictor::~Predictor() {
286   // TODO(rlp): Add DCHECK for CurrentlyOn(BrowserThread::IO) when the
287   // ProfileManagerTest has been updated with a mock profile.
288   DCHECK(shutdown_);
289 }
290 
291 // static
CreatePredictor(bool preconnect_enabled,bool simple_shutdown)292 Predictor* Predictor::CreatePredictor(bool preconnect_enabled,
293                                       bool simple_shutdown) {
294   if (simple_shutdown)
295     return new SimplePredictor(preconnect_enabled);
296   return new Predictor(preconnect_enabled);
297 }
298 
RegisterProfilePrefs(user_prefs::PrefRegistrySyncable * registry)299 void Predictor::RegisterProfilePrefs(
300     user_prefs::PrefRegistrySyncable* registry) {
301   registry->RegisterListPref(prefs::kDnsPrefetchingStartupList,
302                              user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
303   registry->RegisterListPref(prefs::kDnsPrefetchingHostReferralList,
304                              user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
305 }
306 
307 // --------------------- Start UI methods. ------------------------------------
308 
InitNetworkPredictor(PrefService * user_prefs,PrefService * local_state,IOThread * io_thread,net::URLRequestContextGetter * getter)309 void Predictor::InitNetworkPredictor(PrefService* user_prefs,
310                                      PrefService* local_state,
311                                      IOThread* io_thread,
312                                      net::URLRequestContextGetter* getter) {
313   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
314 
315   bool predictor_enabled =
316       user_prefs->GetBoolean(prefs::kNetworkPredictionEnabled);
317 
318   url_request_context_getter_ = getter;
319 
320   // Gather the list of hostnames to prefetch on startup.
321   UrlList urls = GetPredictedUrlListAtStartup(user_prefs, local_state);
322 
323   base::ListValue* referral_list =
324       static_cast<base::ListValue*>(user_prefs->GetList(
325           prefs::kDnsPrefetchingHostReferralList)->DeepCopy());
326 
327   // Now that we have the statistics in memory, wipe them from the Preferences
328   // file. They will be serialized back on a clean shutdown. This way we only
329   // have to worry about clearing our in-memory state when Clearing Browsing
330   // Data.
331   user_prefs->ClearPref(prefs::kDnsPrefetchingStartupList);
332   user_prefs->ClearPref(prefs::kDnsPrefetchingHostReferralList);
333 
334 #if defined(OS_ANDROID) || defined(OS_IOS)
335   // TODO(marq): Once https://codereview.chromium.org/30883003/ lands, also
336   // condition this on DataReductionProxySettings::IsDataReductionProxyAllowed()
337   // Until then, we may create a proxy advisor when the proxy feature itself
338   // isn't available, and the advisor instance will never send advisory
339   // requests, which is slightly wasteful but not harmful.
340   if (data_reduction_proxy::DataReductionProxyParams::
341       IsIncludedInPreconnectHintingFieldTrial()) {
342     proxy_advisor_.reset(new ProxyAdvisor(user_prefs, getter));
343   }
344 #endif
345 
346   BrowserThread::PostTask(
347       BrowserThread::IO,
348       FROM_HERE,
349       base::Bind(
350           &Predictor::FinalizeInitializationOnIOThread,
351           base::Unretained(this),
352           urls, referral_list,
353           io_thread, predictor_enabled));
354 }
355 
AnticipateOmniboxUrl(const GURL & url,bool preconnectable)356 void Predictor::AnticipateOmniboxUrl(const GURL& url, bool preconnectable) {
357   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
358   if (!predictor_enabled_)
359     return;
360   if (!url.is_valid() || !url.has_host())
361     return;
362   std::string host = url.HostNoBrackets();
363   bool is_new_host_request = (host != last_omnibox_host_);
364   last_omnibox_host_ = host;
365 
366   UrlInfo::ResolutionMotivation motivation(UrlInfo::OMNIBOX_MOTIVATED);
367   base::TimeTicks now = base::TimeTicks::Now();
368 
369   if (preconnect_enabled()) {
370     if (preconnectable && !is_new_host_request) {
371       ++consecutive_omnibox_preconnect_count_;
372       // The omnibox suggests a search URL (for which we can preconnect) after
373       // one or two characters are typed, even though such typing often (1 in
374       // 3?) becomes a real URL.  This code waits till is has more evidence of a
375       // preconnectable URL (search URL) before forming a preconnection, so as
376       // to reduce the useless preconnect rate.
377       // Perchance this logic should be pushed back into the omnibox, where the
378       // actual characters typed, such as a space, can better forcast whether
379       // we need to search/preconnect or not.  By waiting for at least 4
380       // characters in a row that have lead to a search proposal, we avoid
381       // preconnections for a prefix like "www." and we also wait until we have
382       // at least a 4 letter word to search for.
383       // Each character typed appears to induce 2 calls to
384       // AnticipateOmniboxUrl(), so we double 4 characters and limit at 8
385       // requests.
386       // TODO(jar): Use an A/B test to optimize this.
387       const int kMinConsecutiveRequests = 8;
388       if (consecutive_omnibox_preconnect_count_ >= kMinConsecutiveRequests) {
389         // TODO(jar): Perhaps we should do a GET to leave the socket open in the
390         // pool.  Currently, we just do a connect, which MAY be reset if we
391         // don't use it in 10 secondes!!!  As a result, we may do more
392         // connections, and actually cost the server more than if we did a real
393         // get with a fake request (/gen_204 might be the good path on Google).
394         const int kMaxSearchKeepaliveSeconds(10);
395         if ((now - last_omnibox_preconnect_).InSeconds() <
396              kMaxSearchKeepaliveSeconds)
397           return;  // We've done a preconnect recently.
398         last_omnibox_preconnect_ = now;
399         const int kConnectionsNeeded = 1;
400         PreconnectUrl(CanonicalizeUrl(url), GURL(), motivation,
401                       kConnectionsNeeded);
402         return;  // Skip pre-resolution, since we'll open a connection.
403       }
404     } else {
405       consecutive_omnibox_preconnect_count_ = 0;
406     }
407   }
408 
409   // Fall through and consider pre-resolution.
410 
411   // Omnibox tends to call in pairs (just a few milliseconds apart), and we
412   // really don't need to keep resolving a name that often.
413   // TODO(jar): A/B tests could check for perf impact of the early returns.
414   if (!is_new_host_request) {
415     const int kMinPreresolveSeconds(10);
416     if (kMinPreresolveSeconds > (now - last_omnibox_preresolve_).InSeconds())
417       return;
418   }
419   last_omnibox_preresolve_ = now;
420 
421   BrowserThread::PostTask(
422       BrowserThread::IO,
423       FROM_HERE,
424       base::Bind(&Predictor::Resolve, base::Unretained(this),
425                  CanonicalizeUrl(url), motivation));
426 }
427 
PreconnectUrlAndSubresources(const GURL & url,const GURL & first_party_for_cookies)428 void Predictor::PreconnectUrlAndSubresources(const GURL& url,
429     const GURL& first_party_for_cookies) {
430   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
431          BrowserThread::CurrentlyOn(BrowserThread::IO));
432   if (!predictor_enabled_ || !preconnect_enabled() ||
433       !url.is_valid() || !url.has_host())
434     return;
435 
436   UrlInfo::ResolutionMotivation motivation(UrlInfo::EARLY_LOAD_MOTIVATED);
437   const int kConnectionsNeeded = 1;
438   PreconnectUrl(CanonicalizeUrl(url), first_party_for_cookies,
439                 motivation, kConnectionsNeeded);
440   PredictFrameSubresources(url.GetWithEmptyPath(), first_party_for_cookies);
441 }
442 
GetPredictedUrlListAtStartup(PrefService * user_prefs,PrefService * local_state)443 UrlList Predictor::GetPredictedUrlListAtStartup(
444     PrefService* user_prefs,
445     PrefService* local_state) {
446   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
447   UrlList urls;
448   // Recall list of URLs we learned about during last session.
449   // This may catch secondary hostnames, pulled in by the homepages.  It will
450   // also catch more of the "primary" home pages, since that was (presumably)
451   // rendered first (and will be rendered first this time too).
452   const base::ListValue* startup_list =
453       user_prefs->GetList(prefs::kDnsPrefetchingStartupList);
454 
455   if (startup_list) {
456     base::ListValue::const_iterator it = startup_list->begin();
457     int format_version = -1;
458     if (it != startup_list->end() &&
459         (*it)->GetAsInteger(&format_version) &&
460         format_version == kPredictorStartupFormatVersion) {
461       ++it;
462       for (; it != startup_list->end(); ++it) {
463         std::string url_spec;
464         if (!(*it)->GetAsString(&url_spec)) {
465           LOG(DFATAL);
466           break;  // Format incompatibility.
467         }
468         GURL url(url_spec);
469         if (!url.has_host() || !url.has_scheme()) {
470           LOG(DFATAL);
471           break;  // Format incompatibility.
472         }
473 
474         urls.push_back(url);
475       }
476     }
477   }
478 
479   // Prepare for any static home page(s) the user has in prefs.  The user may
480   // have a LOT of tab's specified, so we may as well try to warm them all.
481   SessionStartupPref tab_start_pref =
482       SessionStartupPref::GetStartupPref(user_prefs);
483   if (SessionStartupPref::URLS == tab_start_pref.type) {
484     for (size_t i = 0; i < tab_start_pref.urls.size(); i++) {
485       GURL gurl = tab_start_pref.urls[i];
486       if (!gurl.is_valid() || gurl.SchemeIsFile() || gurl.host().empty())
487         continue;
488       if (gurl.SchemeIsHTTPOrHTTPS())
489         urls.push_back(gurl.GetWithEmptyPath());
490     }
491   }
492 
493   if (urls.empty())
494     urls.push_back(GURL("http://www.google.com:80"));
495 
496   return urls;
497 }
498 
set_max_queueing_delay(int max_queueing_delay_ms)499 void Predictor::set_max_queueing_delay(int max_queueing_delay_ms) {
500   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
501   g_max_queueing_delay_ms = max_queueing_delay_ms;
502 }
503 
set_max_parallel_resolves(size_t max_parallel_resolves)504 void Predictor::set_max_parallel_resolves(size_t max_parallel_resolves) {
505   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
506   g_max_parallel_resolves = max_parallel_resolves;
507 }
508 
ShutdownOnUIThread()509 void Predictor::ShutdownOnUIThread() {
510   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
511   BrowserThread::PostTask(
512       BrowserThread::IO,
513       FROM_HERE,
514       base::Bind(&Predictor::Shutdown, base::Unretained(this)));
515 }
516 
517 // ---------------------- End UI methods. -------------------------------------
518 
519 // --------------------- Start IO methods. ------------------------------------
520 
Shutdown()521 void Predictor::Shutdown() {
522   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
523   DCHECK(!shutdown_);
524   shutdown_ = true;
525 
526   STLDeleteElements(&pending_lookups_);
527 }
528 
DiscardAllResults()529 void Predictor::DiscardAllResults() {
530   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
531   // Delete anything listed so far in this session that shows in about:dns.
532   referrers_.clear();
533 
534 
535   // Try to delete anything in our work queue.
536   while (!work_queue_.IsEmpty()) {
537     // Emulate processing cycle as though host was not found.
538     GURL url = work_queue_.Pop();
539     UrlInfo* info = &results_[url];
540     DCHECK(info->HasUrl(url));
541     info->SetAssignedState();
542     info->SetNoSuchNameState();
543   }
544   // Now every result_ is either resolved, or is being resolved
545   // (see LookupRequest).
546 
547   // Step through result_, recording names of all hosts that can't be erased.
548   // We can't erase anything being worked on.
549   Results assignees;
550   for (Results::iterator it = results_.begin(); results_.end() != it; ++it) {
551     GURL url(it->first);
552     UrlInfo* info = &it->second;
553     DCHECK(info->HasUrl(url));
554     if (info->is_assigned()) {
555       info->SetPendingDeleteState();
556       assignees[url] = *info;
557     }
558   }
559   DCHECK_LE(assignees.size(), max_concurrent_dns_lookups_);
560   results_.clear();
561   // Put back in the names being worked on.
562   for (Results::iterator it = assignees.begin(); assignees.end() != it; ++it) {
563     DCHECK(it->second.is_marked_to_delete());
564     results_[it->first] = it->second;
565   }
566 }
567 
568 // Overloaded Resolve() to take a vector of names.
ResolveList(const UrlList & urls,UrlInfo::ResolutionMotivation motivation)569 void Predictor::ResolveList(const UrlList& urls,
570                             UrlInfo::ResolutionMotivation motivation) {
571   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
572 
573   for (UrlList::const_iterator it = urls.begin(); it < urls.end(); ++it) {
574     AppendToResolutionQueue(*it, motivation);
575   }
576 }
577 
578 // Basic Resolve() takes an invidual name, and adds it
579 // to the queue.
Resolve(const GURL & url,UrlInfo::ResolutionMotivation motivation)580 void Predictor::Resolve(const GURL& url,
581                         UrlInfo::ResolutionMotivation motivation) {
582   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
583   if (!url.has_host())
584     return;
585   AppendToResolutionQueue(url, motivation);
586 }
587 
LearnFromNavigation(const GURL & referring_url,const GURL & target_url)588 void Predictor::LearnFromNavigation(const GURL& referring_url,
589                                     const GURL& target_url) {
590   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
591   if (!predictor_enabled_)
592     return;
593   DCHECK_EQ(referring_url, Predictor::CanonicalizeUrl(referring_url));
594   DCHECK_NE(referring_url, GURL::EmptyGURL());
595   DCHECK_EQ(target_url, Predictor::CanonicalizeUrl(target_url));
596   DCHECK_NE(target_url, GURL::EmptyGURL());
597 
598   referrers_[referring_url].SuggestHost(target_url);
599   // Possibly do some referrer trimming.
600   TrimReferrers();
601 }
602 
603 //-----------------------------------------------------------------------------
604 // This section supports the about:dns page.
605 
PredictorGetHtmlInfo(Predictor * predictor,std::string * output)606 void Predictor::PredictorGetHtmlInfo(Predictor* predictor,
607                                      std::string* output) {
608   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
609 
610   output->append("<html><head><title>About DNS</title>"
611                  // We'd like the following no-cache... but it doesn't work.
612                  // "<META HTTP-EQUIV=\"Pragma\" CONTENT=\"no-cache\">"
613                  "</head><body>");
614   if (predictor && predictor->predictor_enabled()) {
615     predictor->GetHtmlInfo(output);
616   } else {
617     output->append("DNS pre-resolution and TCP pre-connection is disabled.");
618   }
619   output->append("</body></html>");
620 }
621 
622 // Provide sort order so all .com's are together, etc.
623 struct RightToLeftStringSorter {
operator ()chrome_browser_net::RightToLeftStringSorter624   bool operator()(const GURL& left, const GURL& right) const {
625     return ReverseComponents(left) < ReverseComponents(right);
626   }
627 
628  private:
629   // Transforms something like "http://www.google.com/xyz" to
630   // "http://com.google.www/xyz".
ReverseComponentschrome_browser_net::RightToLeftStringSorter631   static std::string ReverseComponents(const GURL& url) {
632     // Reverse the components in the hostname.
633     std::vector<std::string> parts;
634     base::SplitString(url.host(), '.', &parts);
635     std::reverse(parts.begin(), parts.end());
636     std::string reversed_host = JoinString(parts, '.');
637 
638     // Return the new URL.
639     GURL::Replacements url_components;
640     url_components.SetHostStr(reversed_host);
641     return url.ReplaceComponents(url_components).spec();
642   }
643 };
644 
GetHtmlReferrerLists(std::string * output)645 void Predictor::GetHtmlReferrerLists(std::string* output) {
646   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
647   if (referrers_.empty())
648     return;
649 
650   // TODO(jar): Remove any plausible JavaScript from names before displaying.
651 
652   typedef std::set<GURL, struct RightToLeftStringSorter>
653       SortedNames;
654   SortedNames sorted_names;
655 
656   for (Referrers::iterator it = referrers_.begin();
657        referrers_.end() != it; ++it)
658     sorted_names.insert(it->first);
659 
660   output->append("<br><table border>");
661   output->append(
662       "<tr><th>Host for Page</th>"
663       "<th>Page Load<br>Count</th>"
664       "<th>Subresource<br>Navigations</th>"
665       "<th>Subresource<br>PreConnects</th>"
666       "<th>Subresource<br>PreResolves</th>"
667       "<th>Expected<br>Connects</th>"
668       "<th>Subresource Spec</th></tr>");
669 
670   for (SortedNames::iterator it = sorted_names.begin();
671        sorted_names.end() != it; ++it) {
672     Referrer* referrer = &(referrers_[*it]);
673     bool first_set_of_futures = true;
674     for (Referrer::iterator future_url = referrer->begin();
675          future_url != referrer->end(); ++future_url) {
676       output->append("<tr align=right>");
677       if (first_set_of_futures) {
678         base::StringAppendF(output,
679                             "<td rowspan=%d>%s</td><td rowspan=%d>%d</td>",
680                             static_cast<int>(referrer->size()),
681                             it->spec().c_str(),
682                             static_cast<int>(referrer->size()),
683                             static_cast<int>(referrer->use_count()));
684       }
685       first_set_of_futures = false;
686       base::StringAppendF(output,
687           "<td>%d</td><td>%d</td><td>%d</td><td>%2.3f</td><td>%s</td></tr>",
688           static_cast<int>(future_url->second.navigation_count()),
689           static_cast<int>(future_url->second.preconnection_count()),
690           static_cast<int>(future_url->second.preresolution_count()),
691           static_cast<double>(future_url->second.subresource_use_rate()),
692           future_url->first.spec().c_str());
693     }
694   }
695   output->append("</table>");
696 }
697 
GetHtmlInfo(std::string * output)698 void Predictor::GetHtmlInfo(std::string* output) {
699   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
700   if (initial_observer_.get())
701     initial_observer_->GetFirstResolutionsHtml(output);
702   // Show list of subresource predictions and stats.
703   GetHtmlReferrerLists(output);
704 
705   // Local lists for calling UrlInfo
706   UrlInfo::UrlInfoTable name_not_found;
707   UrlInfo::UrlInfoTable name_preresolved;
708 
709   // Get copies of all useful data.
710   typedef std::map<GURL, UrlInfo, RightToLeftStringSorter> SortedUrlInfo;
711   SortedUrlInfo snapshot;
712   // UrlInfo supports value semantics, so we can do a shallow copy.
713   for (Results::iterator it(results_.begin()); it != results_.end(); it++)
714     snapshot[it->first] = it->second;
715 
716   // Partition the UrlInfo's into categories.
717   for (SortedUrlInfo::iterator it(snapshot.begin());
718        it != snapshot.end(); it++) {
719     if (it->second.was_nonexistent()) {
720       name_not_found.push_back(it->second);
721       continue;
722     }
723     if (!it->second.was_found())
724       continue;  // Still being processed.
725     name_preresolved.push_back(it->second);
726   }
727 
728   bool brief = false;
729 #ifdef NDEBUG
730   brief = true;
731 #endif  // NDEBUG
732 
733   // Call for display of each table, along with title.
734   UrlInfo::GetHtmlTable(name_preresolved,
735       "Preresolution DNS records performed for ", brief, output);
736   UrlInfo::GetHtmlTable(name_not_found,
737       "Preresolving DNS records revealed non-existence for ", brief, output);
738 }
739 
TrimReferrersNow()740 void Predictor::TrimReferrersNow() {
741   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
742   // Just finish up work if an incremental trim is in progress.
743   if (urls_being_trimmed_.empty())
744     LoadUrlsForTrimming();
745   IncrementalTrimReferrers(true);  // Do everything now.
746 }
747 
SerializeReferrers(base::ListValue * referral_list)748 void Predictor::SerializeReferrers(base::ListValue* referral_list) {
749   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
750   referral_list->Clear();
751   referral_list->Append(new base::FundamentalValue(kPredictorReferrerVersion));
752   for (Referrers::const_iterator it = referrers_.begin();
753        it != referrers_.end(); ++it) {
754     // Serialize the list of subresource names.
755     base::Value* subresource_list(it->second.Serialize());
756 
757     // Create a list for each referer.
758     base::ListValue* motivator(new base::ListValue);
759     motivator->Append(new base::StringValue(it->first.spec()));
760     motivator->Append(subresource_list);
761 
762     referral_list->Append(motivator);
763   }
764 }
765 
DeserializeReferrers(const base::ListValue & referral_list)766 void Predictor::DeserializeReferrers(const base::ListValue& referral_list) {
767   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
768   int format_version = -1;
769   if (referral_list.GetSize() > 0 &&
770       referral_list.GetInteger(0, &format_version) &&
771       format_version == kPredictorReferrerVersion) {
772     for (size_t i = 1; i < referral_list.GetSize(); ++i) {
773       const base::ListValue* motivator;
774       if (!referral_list.GetList(i, &motivator)) {
775         NOTREACHED();
776         return;
777       }
778       std::string motivating_url_spec;
779       if (!motivator->GetString(0, &motivating_url_spec)) {
780         NOTREACHED();
781         return;
782       }
783 
784       const base::Value* subresource_list;
785       if (!motivator->Get(1, &subresource_list)) {
786         NOTREACHED();
787         return;
788       }
789 
790       referrers_[GURL(motivating_url_spec)].Deserialize(*subresource_list);
791     }
792   }
793 }
794 
DeserializeReferrersThenDelete(base::ListValue * referral_list)795 void Predictor::DeserializeReferrersThenDelete(
796     base::ListValue* referral_list) {
797   DeserializeReferrers(*referral_list);
798   delete referral_list;
799 }
800 
DiscardInitialNavigationHistory()801 void Predictor::DiscardInitialNavigationHistory() {
802   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
803   if (initial_observer_.get())
804     initial_observer_->DiscardInitialNavigationHistory();
805 }
806 
FinalizeInitializationOnIOThread(const UrlList & startup_urls,base::ListValue * referral_list,IOThread * io_thread,bool predictor_enabled)807 void Predictor::FinalizeInitializationOnIOThread(
808     const UrlList& startup_urls,
809     base::ListValue* referral_list,
810     IOThread* io_thread,
811     bool predictor_enabled) {
812   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
813 
814   predictor_enabled_ = predictor_enabled;
815   initial_observer_.reset(new InitialObserver());
816   host_resolver_ = io_thread->globals()->host_resolver.get();
817   preconnect_usage_.reset(new PreconnectUsage());
818 
819   net::URLRequestContext* context =
820       url_request_context_getter_->GetURLRequestContext();
821   transport_security_state_ = context->transport_security_state();
822   ssl_config_service_ = context->ssl_config_service();
823 
824   // base::WeakPtrFactory instances need to be created and destroyed
825   // on the same thread. The predictor lives on the IO thread and will die
826   // from there so now that we're on the IO thread we need to properly
827   // initialize the base::WeakPtrFactory.
828   // TODO(groby): Check if WeakPtrFactory has the same constraint.
829   weak_factory_.reset(new base::WeakPtrFactory<Predictor>(this));
830 
831   // Prefetch these hostnames on startup.
832   DnsPrefetchMotivatedList(startup_urls, UrlInfo::STARTUP_LIST_MOTIVATED);
833   DeserializeReferrersThenDelete(referral_list);
834 }
835 
836 //-----------------------------------------------------------------------------
837 // This section intermingles prefetch results with actual browser HTTP
838 // network activity.  It supports calculating of the benefit of a prefetch, as
839 // well as recording what prefetched hostname resolutions might be potentially
840 // helpful during the next chrome-startup.
841 //-----------------------------------------------------------------------------
842 
LearnAboutInitialNavigation(const GURL & url)843 void Predictor::LearnAboutInitialNavigation(const GURL& url) {
844   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
845   if (!predictor_enabled_ || NULL == initial_observer_.get() )
846     return;
847   initial_observer_->Append(url, this);
848 }
849 
850 // This API is only used in the browser process.
851 // It is called from an IPC message originating in the renderer.  It currently
852 // includes both Page-Scan, and Link-Hover prefetching.
853 // TODO(jar): Separate out link-hover prefetching, and page-scan results.
DnsPrefetchList(const NameList & hostnames)854 void Predictor::DnsPrefetchList(const NameList& hostnames) {
855   // TODO(jar): Push GURL transport further back into renderer, but this will
856   // require a Webkit change in the observer :-/.
857   UrlList urls;
858   for (NameList::const_iterator it = hostnames.begin();
859        it < hostnames.end();
860        ++it) {
861     urls.push_back(GURL("http://" + *it + ":80"));
862   }
863 
864   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
865   DnsPrefetchMotivatedList(urls, UrlInfo::PAGE_SCAN_MOTIVATED);
866 }
867 
DnsPrefetchMotivatedList(const UrlList & urls,UrlInfo::ResolutionMotivation motivation)868 void Predictor::DnsPrefetchMotivatedList(
869     const UrlList& urls,
870     UrlInfo::ResolutionMotivation motivation) {
871   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
872          BrowserThread::CurrentlyOn(BrowserThread::IO));
873   if (!predictor_enabled_)
874     return;
875 
876   if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
877     ResolveList(urls, motivation);
878   } else {
879     BrowserThread::PostTask(
880         BrowserThread::IO,
881         FROM_HERE,
882         base::Bind(&Predictor::ResolveList, base::Unretained(this),
883                    urls, motivation));
884   }
885 }
886 
887 //-----------------------------------------------------------------------------
888 // Functions to handle saving of hostnames from one session to the next, to
889 // expedite startup times.
890 
SaveDnsPrefetchStateForNextStartupAndTrimOnIOThread(base::ListValue * startup_list,base::ListValue * referral_list,base::WaitableEvent * completion,Predictor * predictor)891 static void SaveDnsPrefetchStateForNextStartupAndTrimOnIOThread(
892     base::ListValue* startup_list,
893     base::ListValue* referral_list,
894     base::WaitableEvent* completion,
895     Predictor* predictor) {
896   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
897 
898   if (NULL == predictor) {
899     completion->Signal();
900     return;
901   }
902   predictor->SaveDnsPrefetchStateForNextStartupAndTrim(
903       startup_list, referral_list, completion);
904 }
905 
SaveStateForNextStartupAndTrim(PrefService * prefs)906 void Predictor::SaveStateForNextStartupAndTrim(PrefService* prefs) {
907   if (!predictor_enabled_)
908     return;
909 
910   base::WaitableEvent completion(true, false);
911 
912   ListPrefUpdate update_startup_list(prefs, prefs::kDnsPrefetchingStartupList);
913   ListPrefUpdate update_referral_list(prefs,
914                                       prefs::kDnsPrefetchingHostReferralList);
915   if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
916     SaveDnsPrefetchStateForNextStartupAndTrimOnIOThread(
917         update_startup_list.Get(),
918         update_referral_list.Get(),
919         &completion,
920         this);
921   } else {
922     bool posted = BrowserThread::PostTask(
923         BrowserThread::IO,
924         FROM_HERE,
925         base::Bind(
926             &SaveDnsPrefetchStateForNextStartupAndTrimOnIOThread,
927             update_startup_list.Get(),
928             update_referral_list.Get(),
929             &completion,
930             this));
931 
932     // TODO(jar): Synchronous waiting for the IO thread is a potential source
933     // to deadlocks and should be investigated. See http://crbug.com/78451.
934     DCHECK(posted);
935     if (posted) {
936       // http://crbug.com/124954
937       base::ThreadRestrictions::ScopedAllowWait allow_wait;
938       completion.Wait();
939     }
940   }
941 }
942 
SaveDnsPrefetchStateForNextStartupAndTrim(base::ListValue * startup_list,base::ListValue * referral_list,base::WaitableEvent * completion)943 void Predictor::SaveDnsPrefetchStateForNextStartupAndTrim(
944     base::ListValue* startup_list,
945     base::ListValue* referral_list,
946     base::WaitableEvent* completion) {
947   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
948   if (initial_observer_.get())
949     initial_observer_->GetInitialDnsResolutionList(startup_list);
950 
951   // Do at least one trim at shutdown, in case the user wasn't running long
952   // enough to do any regular trimming of referrers.
953   TrimReferrersNow();
954   SerializeReferrers(referral_list);
955 
956   completion->Signal();
957 }
958 
EnablePredictor(bool enable)959 void Predictor::EnablePredictor(bool enable) {
960   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
961          BrowserThread::CurrentlyOn(BrowserThread::IO));
962 
963   if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
964     EnablePredictorOnIOThread(enable);
965   } else {
966     BrowserThread::PostTask(
967         BrowserThread::IO,
968         FROM_HERE,
969         base::Bind(&Predictor::EnablePredictorOnIOThread,
970                    base::Unretained(this), enable));
971   }
972 }
973 
EnablePredictorOnIOThread(bool enable)974 void Predictor::EnablePredictorOnIOThread(bool enable) {
975   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
976   predictor_enabled_ = enable;
977 }
978 
PreconnectUrl(const GURL & url,const GURL & first_party_for_cookies,UrlInfo::ResolutionMotivation motivation,int count)979 void Predictor::PreconnectUrl(const GURL& url,
980                               const GURL& first_party_for_cookies,
981                               UrlInfo::ResolutionMotivation motivation,
982                               int count) {
983   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
984          BrowserThread::CurrentlyOn(BrowserThread::IO));
985 
986   if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
987     PreconnectUrlOnIOThread(url, first_party_for_cookies, motivation, count);
988   } else {
989     BrowserThread::PostTask(
990         BrowserThread::IO,
991         FROM_HERE,
992         base::Bind(&Predictor::PreconnectUrlOnIOThread,
993                    base::Unretained(this), url, first_party_for_cookies,
994                    motivation, count));
995   }
996 }
997 
PreconnectUrlOnIOThread(const GURL & original_url,const GURL & first_party_for_cookies,UrlInfo::ResolutionMotivation motivation,int count)998 void Predictor::PreconnectUrlOnIOThread(
999     const GURL& original_url,
1000     const GURL& first_party_for_cookies,
1001     UrlInfo::ResolutionMotivation motivation,
1002     int count) {
1003   // Skip the HSTS redirect.
1004   GURL url = GetHSTSRedirectOnIOThread(original_url);
1005 
1006   if (motivation == UrlInfo::MOUSE_OVER_MOTIVATED)
1007     RecordPreconnectTrigger(url);
1008 
1009   AdviseProxy(url, motivation, true /* is_preconnect */);
1010 
1011   if (observer_) {
1012     observer_->OnPreconnectUrl(
1013         url, first_party_for_cookies, motivation, count);
1014   }
1015 
1016   PreconnectOnIOThread(url,
1017                        first_party_for_cookies,
1018                        motivation,
1019                        count,
1020                        url_request_context_getter_.get());
1021 }
1022 
RecordPreconnectTrigger(const GURL & url)1023 void Predictor::RecordPreconnectTrigger(const GURL& url) {
1024   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
1025   if (preconnect_usage_)
1026     preconnect_usage_->ObservePreconnect(url);
1027 }
1028 
RecordPreconnectNavigationStat(const std::vector<GURL> & url_chain,bool is_subresource)1029 void Predictor::RecordPreconnectNavigationStat(
1030     const std::vector<GURL>& url_chain,
1031     bool is_subresource) {
1032   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
1033 
1034   if (preconnect_usage_)
1035     preconnect_usage_->ObserveNavigationChain(url_chain, is_subresource);
1036 }
1037 
RecordLinkNavigation(const GURL & url)1038 void Predictor::RecordLinkNavigation(const GURL& url) {
1039   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
1040   if (preconnect_usage_)
1041     preconnect_usage_->ObserveLinkNavigation(url);
1042 }
1043 
PredictFrameSubresources(const GURL & url,const GURL & first_party_for_cookies)1044 void Predictor::PredictFrameSubresources(const GURL& url,
1045                                          const GURL& first_party_for_cookies) {
1046   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
1047          BrowserThread::CurrentlyOn(BrowserThread::IO));
1048   if (!predictor_enabled_)
1049     return;
1050   DCHECK_EQ(url.GetWithEmptyPath(), url);
1051   // Add one pass through the message loop to allow current navigation to
1052   // proceed.
1053   if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
1054     PrepareFrameSubresources(url, first_party_for_cookies);
1055   } else {
1056     BrowserThread::PostTask(
1057         BrowserThread::IO,
1058         FROM_HERE,
1059         base::Bind(&Predictor::PrepareFrameSubresources,
1060                    base::Unretained(this), url, first_party_for_cookies));
1061   }
1062 }
1063 
AdviseProxy(const GURL & url,UrlInfo::ResolutionMotivation motivation,bool is_preconnect)1064 void Predictor::AdviseProxy(const GURL& url,
1065                             UrlInfo::ResolutionMotivation motivation,
1066                             bool is_preconnect) {
1067   if (!proxy_advisor_)
1068     return;
1069 
1070   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) ||
1071          BrowserThread::CurrentlyOn(BrowserThread::IO));
1072 
1073   if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
1074     AdviseProxyOnIOThread(url, motivation, is_preconnect);
1075   } else {
1076     BrowserThread::PostTask(
1077         BrowserThread::IO,
1078         FROM_HERE,
1079         base::Bind(&Predictor::AdviseProxyOnIOThread,
1080                    base::Unretained(this), url, motivation, is_preconnect));
1081   }
1082 }
1083 
1084 enum SubresourceValue {
1085   PRECONNECTION,
1086   PRERESOLUTION,
1087   TOO_NEW,
1088   SUBRESOURCE_VALUE_MAX
1089 };
1090 
PrepareFrameSubresources(const GURL & original_url,const GURL & first_party_for_cookies)1091 void Predictor::PrepareFrameSubresources(const GURL& original_url,
1092                                          const GURL& first_party_for_cookies) {
1093   // Apply HSTS redirect early so it is taken into account when looking up
1094   // subresources.
1095   GURL url = GetHSTSRedirectOnIOThread(original_url);
1096 
1097   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
1098   DCHECK_EQ(url.GetWithEmptyPath(), url);
1099   Referrers::iterator it = referrers_.find(url);
1100   if (referrers_.end() == it) {
1101     // Only when we don't know anything about this url, make 2 connections
1102     // available.  We could do this completely via learning (by prepopulating
1103     // the referrer_ list with this expected value), but it would swell the
1104     // size of the list with all the "Leaf" nodes in the tree (nodes that don't
1105     // load any subresources).  If we learn about this resource, we will instead
1106     // provide a more carefully estimated preconnection count.
1107     if (preconnect_enabled_) {
1108       PreconnectUrlOnIOThread(url, first_party_for_cookies,
1109                               UrlInfo::SELF_REFERAL_MOTIVATED, 2);
1110     }
1111     return;
1112   }
1113 
1114   Referrer* referrer = &(it->second);
1115   referrer->IncrementUseCount();
1116   const UrlInfo::ResolutionMotivation motivation =
1117       UrlInfo::LEARNED_REFERAL_MOTIVATED;
1118   for (Referrer::iterator future_url = referrer->begin();
1119        future_url != referrer->end(); ++future_url) {
1120     SubresourceValue evalution(TOO_NEW);
1121     double connection_expectation = future_url->second.subresource_use_rate();
1122     UMA_HISTOGRAM_CUSTOM_COUNTS("Net.PreconnectSubresourceExpectation",
1123                                 static_cast<int>(connection_expectation * 100),
1124                                 10, 5000, 50);
1125     future_url->second.ReferrerWasObserved();
1126     if (preconnect_enabled_ &&
1127         connection_expectation > kPreconnectWorthyExpectedValue) {
1128       evalution = PRECONNECTION;
1129       future_url->second.IncrementPreconnectionCount();
1130       int count = static_cast<int>(std::ceil(connection_expectation));
1131       if (url.host() == future_url->first.host())
1132         ++count;
1133       PreconnectUrlOnIOThread(future_url->first, first_party_for_cookies,
1134                               motivation, count);
1135     } else if (connection_expectation > kDNSPreresolutionWorthyExpectedValue) {
1136       evalution = PRERESOLUTION;
1137       future_url->second.preresolution_increment();
1138       UrlInfo* queued_info = AppendToResolutionQueue(future_url->first,
1139                                                      motivation);
1140       if (queued_info)
1141         queued_info->SetReferringHostname(url);
1142     }
1143     UMA_HISTOGRAM_ENUMERATION("Net.PreconnectSubresourceEval", evalution,
1144                               SUBRESOURCE_VALUE_MAX);
1145   }
1146 }
1147 
OnLookupFinished(LookupRequest * request,const GURL & url,bool found)1148 void Predictor::OnLookupFinished(LookupRequest* request, const GURL& url,
1149                                  bool found) {
1150   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
1151 
1152   LookupFinished(request, url, found);
1153   pending_lookups_.erase(request);
1154   delete request;
1155 
1156   StartSomeQueuedResolutions();
1157 }
1158 
LookupFinished(LookupRequest * request,const GURL & url,bool found)1159 void Predictor::LookupFinished(LookupRequest* request, const GURL& url,
1160                                bool found) {
1161   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
1162   UrlInfo* info = &results_[url];
1163   DCHECK(info->HasUrl(url));
1164   if (info->is_marked_to_delete()) {
1165     results_.erase(url);
1166   } else {
1167     if (found)
1168       info->SetFoundState();
1169     else
1170       info->SetNoSuchNameState();
1171   }
1172 }
1173 
AppendToResolutionQueue(const GURL & url,UrlInfo::ResolutionMotivation motivation)1174 UrlInfo* Predictor::AppendToResolutionQueue(
1175     const GURL& url,
1176     UrlInfo::ResolutionMotivation motivation) {
1177   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
1178   DCHECK(url.has_host());
1179 
1180   if (shutdown_)
1181     return NULL;
1182 
1183   UrlInfo* info = &results_[url];
1184   info->SetUrl(url);  // Initialize or DCHECK.
1185   // TODO(jar):  I need to discard names that have long since expired.
1186   // Currently we only add to the domain map :-/
1187 
1188   DCHECK(info->HasUrl(url));
1189 
1190   if (!info->NeedsDnsUpdate()) {
1191     info->DLogResultsStats("DNS PrefetchNotUpdated");
1192     return NULL;
1193   }
1194 
1195   AdviseProxy(url, motivation, false /* is_preconnect */);
1196   if (proxy_advisor_ && proxy_advisor_->WouldProxyURL(url)) {
1197     info->DLogResultsStats("DNS PrefetchForProxiedRequest");
1198     return NULL;
1199   }
1200 
1201   info->SetQueuedState(motivation);
1202   work_queue_.Push(url, motivation);
1203   StartSomeQueuedResolutions();
1204   return info;
1205 }
1206 
CongestionControlPerformed(UrlInfo * info)1207 bool Predictor::CongestionControlPerformed(UrlInfo* info) {
1208   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
1209   // Note: queue_duration is ONLY valid after we go to assigned state.
1210   if (info->queue_duration() < max_dns_queue_delay_)
1211     return false;
1212   // We need to discard all entries in our queue, as we're keeping them waiting
1213   // too long.  By doing this, we'll have a chance to quickly service urgent
1214   // resolutions, and not have a bogged down system.
1215   while (true) {
1216     info->RemoveFromQueue();
1217     if (work_queue_.IsEmpty())
1218       break;
1219     info = &results_[work_queue_.Pop()];
1220     info->SetAssignedState();
1221   }
1222   return true;
1223 }
1224 
StartSomeQueuedResolutions()1225 void Predictor::StartSomeQueuedResolutions() {
1226   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
1227 
1228   while (!work_queue_.IsEmpty() &&
1229          pending_lookups_.size() < max_concurrent_dns_lookups_) {
1230     const GURL url(work_queue_.Pop());
1231     UrlInfo* info = &results_[url];
1232     DCHECK(info->HasUrl(url));
1233     info->SetAssignedState();
1234 
1235     if (CongestionControlPerformed(info)) {
1236       DCHECK(work_queue_.IsEmpty());
1237       return;
1238     }
1239 
1240     LookupRequest* request = new LookupRequest(this, host_resolver_, url);
1241     int status = request->Start();
1242     if (status == net::ERR_IO_PENDING) {
1243       // Will complete asynchronously.
1244       pending_lookups_.insert(request);
1245       peak_pending_lookups_ = std::max(peak_pending_lookups_,
1246                                        pending_lookups_.size());
1247     } else {
1248       // Completed synchronously (was already cached by HostResolver), or else
1249       // there was (equivalently) some network error that prevents us from
1250       // finding the name.  Status net::OK means it was "found."
1251       LookupFinished(request, url, status == net::OK);
1252       delete request;
1253     }
1254   }
1255 }
1256 
TrimReferrers()1257 void Predictor::TrimReferrers() {
1258   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
1259   if (!urls_being_trimmed_.empty())
1260     return;   // There is incremental trimming in progress already.
1261 
1262   // Check to see if it is time to trim yet.
1263   base::TimeTicks now = base::TimeTicks::Now();
1264   if (now < next_trim_time_)
1265     return;
1266   next_trim_time_ = now + TimeDelta::FromHours(kDurationBetweenTrimmingsHours);
1267 
1268   LoadUrlsForTrimming();
1269   PostIncrementalTrimTask();
1270 }
1271 
LoadUrlsForTrimming()1272 void Predictor::LoadUrlsForTrimming() {
1273   DCHECK(urls_being_trimmed_.empty());
1274   for (Referrers::const_iterator it = referrers_.begin();
1275        it != referrers_.end(); ++it)
1276     urls_being_trimmed_.push_back(it->first);
1277   UMA_HISTOGRAM_COUNTS("Net.PredictionTrimSize", urls_being_trimmed_.size());
1278 }
1279 
PostIncrementalTrimTask()1280 void Predictor::PostIncrementalTrimTask() {
1281   if (urls_being_trimmed_.empty())
1282     return;
1283   const TimeDelta kDurationBetweenTrimmingIncrements =
1284       TimeDelta::FromSeconds(kDurationBetweenTrimmingIncrementsSeconds);
1285   base::MessageLoop::current()->PostDelayedTask(
1286       FROM_HERE,
1287       base::Bind(&Predictor::IncrementalTrimReferrers,
1288                  weak_factory_->GetWeakPtr(), false),
1289       kDurationBetweenTrimmingIncrements);
1290 }
1291 
IncrementalTrimReferrers(bool trim_all_now)1292 void Predictor::IncrementalTrimReferrers(bool trim_all_now) {
1293   size_t trim_count = urls_being_trimmed_.size();
1294   if (!trim_all_now)
1295     trim_count = std::min(trim_count, kUrlsTrimmedPerIncrement);
1296   while (trim_count-- != 0) {
1297     Referrers::iterator it = referrers_.find(urls_being_trimmed_.back());
1298     urls_being_trimmed_.pop_back();
1299     if (it == referrers_.end())
1300       continue;  // Defensive code: It got trimmed away already.
1301     if (!it->second.Trim(kReferrerTrimRatio, kDiscardableExpectedValue))
1302       referrers_.erase(it);
1303   }
1304   PostIncrementalTrimTask();
1305 }
1306 
AdviseProxyOnIOThread(const GURL & url,UrlInfo::ResolutionMotivation motivation,bool is_preconnect)1307 void Predictor::AdviseProxyOnIOThread(const GURL& url,
1308                                       UrlInfo::ResolutionMotivation motivation,
1309                                       bool is_preconnect) {
1310   if (!proxy_advisor_)
1311     return;
1312   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
1313   proxy_advisor_->Advise(url, motivation, is_preconnect);
1314 }
1315 
GetHSTSRedirectOnIOThread(const GURL & url)1316 GURL Predictor::GetHSTSRedirectOnIOThread(const GURL& url) {
1317   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
1318 
1319   if (!transport_security_state_)
1320     return url;
1321   if (!url.SchemeIs("http"))
1322     return url;
1323   bool sni_available =
1324       net::SSLConfigService::IsSNIAvailable(ssl_config_service_);
1325   if (!transport_security_state_->ShouldUpgradeToSSL(url.host(), sni_available))
1326     return url;
1327 
1328   url::Replacements<char> replacements;
1329   const char kNewScheme[] = "https";
1330   replacements.SetScheme(kNewScheme, url::Component(0, strlen(kNewScheme)));
1331   return url.ReplaceComponents(replacements);
1332 }
1333 
1334 // ---------------------- End IO methods. -------------------------------------
1335 
1336 //-----------------------------------------------------------------------------
1337 
HostNameQueue()1338 Predictor::HostNameQueue::HostNameQueue() {
1339 }
1340 
~HostNameQueue()1341 Predictor::HostNameQueue::~HostNameQueue() {
1342 }
1343 
Push(const GURL & url,UrlInfo::ResolutionMotivation motivation)1344 void Predictor::HostNameQueue::Push(const GURL& url,
1345     UrlInfo::ResolutionMotivation motivation) {
1346   switch (motivation) {
1347     case UrlInfo::STATIC_REFERAL_MOTIVATED:
1348     case UrlInfo::LEARNED_REFERAL_MOTIVATED:
1349     case UrlInfo::MOUSE_OVER_MOTIVATED:
1350       rush_queue_.push(url);
1351       break;
1352 
1353     default:
1354       background_queue_.push(url);
1355       break;
1356   }
1357 }
1358 
IsEmpty() const1359 bool Predictor::HostNameQueue::IsEmpty() const {
1360   return rush_queue_.empty() && background_queue_.empty();
1361 }
1362 
Pop()1363 GURL Predictor::HostNameQueue::Pop() {
1364   DCHECK(!IsEmpty());
1365   std::queue<GURL> *queue(rush_queue_.empty() ? &background_queue_
1366                                               : &rush_queue_);
1367   GURL url(queue->front());
1368   queue->pop();
1369   return url;
1370 }
1371 
1372 //-----------------------------------------------------------------------------
1373 // Member definitions for InitialObserver class.
1374 
InitialObserver()1375 Predictor::InitialObserver::InitialObserver() {
1376 }
1377 
~InitialObserver()1378 Predictor::InitialObserver::~InitialObserver() {
1379 }
1380 
Append(const GURL & url,Predictor * predictor)1381 void Predictor::InitialObserver::Append(const GURL& url,
1382                                         Predictor* predictor) {
1383   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
1384 
1385   // TODO(rlp): Do we really need the predictor check here?
1386   if (NULL == predictor)
1387     return;
1388   if (kStartupResolutionCount <= first_navigations_.size())
1389     return;
1390 
1391   DCHECK(url.SchemeIsHTTPOrHTTPS());
1392   DCHECK_EQ(url, Predictor::CanonicalizeUrl(url));
1393   if (first_navigations_.find(url) == first_navigations_.end())
1394     first_navigations_[url] = base::TimeTicks::Now();
1395 }
1396 
GetInitialDnsResolutionList(base::ListValue * startup_list)1397 void Predictor::InitialObserver::GetInitialDnsResolutionList(
1398     base::ListValue* startup_list) {
1399   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
1400   DCHECK(startup_list);
1401   startup_list->Clear();
1402   DCHECK_EQ(0u, startup_list->GetSize());
1403   startup_list->Append(
1404       new base::FundamentalValue(kPredictorStartupFormatVersion));
1405   for (FirstNavigations::iterator it = first_navigations_.begin();
1406        it != first_navigations_.end();
1407        ++it) {
1408     DCHECK(it->first == Predictor::CanonicalizeUrl(it->first));
1409     startup_list->Append(new base::StringValue(it->first.spec()));
1410   }
1411 }
1412 
GetFirstResolutionsHtml(std::string * output)1413 void Predictor::InitialObserver::GetFirstResolutionsHtml(
1414     std::string* output) {
1415   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
1416 
1417   UrlInfo::UrlInfoTable resolution_list;
1418   {
1419     for (FirstNavigations::iterator it(first_navigations_.begin());
1420          it != first_navigations_.end();
1421          it++) {
1422       UrlInfo info;
1423       info.SetUrl(it->first);
1424       info.set_time(it->second);
1425       resolution_list.push_back(info);
1426     }
1427   }
1428   UrlInfo::GetHtmlTable(resolution_list,
1429       "Future startups will prefetch DNS records for ", false, output);
1430 }
1431 
1432 //-----------------------------------------------------------------------------
1433 // Helper functions
1434 //-----------------------------------------------------------------------------
1435 
1436 // static
CanonicalizeUrl(const GURL & url)1437 GURL Predictor::CanonicalizeUrl(const GURL& url) {
1438   if (!url.has_host())
1439      return GURL::EmptyGURL();
1440 
1441   std::string scheme;
1442   if (url.has_scheme()) {
1443     scheme = url.scheme();
1444     if (scheme != "http" && scheme != "https")
1445       return GURL::EmptyGURL();
1446     if (url.has_port())
1447       return url.GetWithEmptyPath();
1448   } else {
1449     scheme = "http";
1450   }
1451 
1452   // If we omit a port, it will default to 80 or 443 as appropriate.
1453   std::string colon_plus_port;
1454   if (url.has_port())
1455     colon_plus_port = ":" + url.port();
1456 
1457   return GURL(scheme + "://" + url.host() + colon_plus_port);
1458 }
1459 
InitNetworkPredictor(PrefService * user_prefs,PrefService * local_state,IOThread * io_thread,net::URLRequestContextGetter * getter)1460 void SimplePredictor::InitNetworkPredictor(
1461     PrefService* user_prefs,
1462     PrefService* local_state,
1463     IOThread* io_thread,
1464     net::URLRequestContextGetter* getter) {
1465   // Empty function for unittests.
1466 }
1467 
ShutdownOnUIThread()1468 void SimplePredictor::ShutdownOnUIThread() {
1469   SetShutdown(true);
1470 }
1471 
1472 }  // namespace chrome_browser_net
1473