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.
5 #include "chrome/browser/net/predictor.h"
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/data_reduction_proxy_settings.h"
30 #include "chrome/browser/net/spdyproxy/proxy_advisor.h"
31 #include "chrome/browser/prefs/session_startup_pref.h"
32 #include "chrome/common/chrome_switches.h"
33 #include "chrome/common/pref_names.h"
34 #include "components/user_prefs/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/url_request/url_request_context_getter.h"
45 using base::TimeDelta
;
46 using content::BrowserThread
;
48 namespace chrome_browser_net
{
51 const int Predictor::kPredictorReferrerVersion
= 2;
52 const double Predictor::kPreconnectWorthyExpectedValue
= 0.8;
53 const double Predictor::kDNSPreresolutionWorthyExpectedValue
= 0.1;
54 const double Predictor::kDiscardableExpectedValue
= 0.05;
55 // The goal is of trimming is to to reduce the importance (number of expected
56 // subresources needed) by a factor of 2 after about 24 hours of uptime. We will
57 // trim roughly once-an-hour of uptime. The ratio to use in each trim operation
58 // is then the 24th root of 0.5. If a user only surfs for 4 hours a day, then
59 // after about 6 days they will have halved all their estimates of subresource
60 // connections. Once this falls below kDiscardableExpectedValue the referrer
62 // TODO(jar): Measure size of referrer lists in the field. Consider an adaptive
63 // system that uses a higher trim ratio when the list is large.
65 const double Predictor::kReferrerTrimRatio
= 0.97153;
66 const int64
Predictor::kDurationBetweenTrimmingsHours
= 1;
67 const int64
Predictor::kDurationBetweenTrimmingIncrementsSeconds
= 15;
68 const size_t Predictor::kUrlsTrimmedPerIncrement
= 5u;
69 const size_t Predictor::kMaxSpeculativeParallelResolves
= 3;
70 const int Predictor::kMaxUnusedSocketLifetimeSecondsWithoutAGet
= 10;
71 // To control our congestion avoidance system, which discards a queue when
72 // resolutions are "taking too long," we need an expected resolution time.
73 // Common average is in the range of 300-500ms.
74 const int kExpectedResolutionTimeMs
= 500;
75 const int Predictor::kTypicalSpeculativeGroupSize
= 8;
76 const int Predictor::kMaxSpeculativeResolveQueueDelayMs
=
77 (kExpectedResolutionTimeMs
* Predictor::kTypicalSpeculativeGroupSize
) /
78 Predictor::kMaxSpeculativeParallelResolves
;
80 static int g_max_queueing_delay_ms
=
81 Predictor::kMaxSpeculativeResolveQueueDelayMs
;
82 static size_t g_max_parallel_resolves
=
83 Predictor::kMaxSpeculativeParallelResolves
;
85 // A version number for prefs that are saved. This should be incremented when
86 // we change the format so that we discard old data.
87 static const int kPredictorStartupFormatVersion
= 1;
89 class Predictor::LookupRequest
{
91 LookupRequest(Predictor
* predictor
,
92 net::HostResolver
* host_resolver
,
94 : predictor_(predictor
),
96 resolver_(host_resolver
) {
99 // Return underlying network resolver status.
100 // net::OK ==> Host was found synchronously.
101 // net:ERR_IO_PENDING ==> Network will callback later with result.
102 // anything else ==> Host was not found synchronously.
104 net::HostResolver::RequestInfo
resolve_info(
105 net::HostPortPair::FromURL(url_
));
107 // Make a note that this is a speculative resolve request. This allows us
108 // to separate it from real navigations in the observer's callback, and
109 // lets the HostResolver know it can de-prioritize it.
110 resolve_info
.set_is_speculative(true);
111 return resolver_
.Resolve(
113 net::DEFAULT_PRIORITY
,
115 base::Bind(&LookupRequest::OnLookupFinished
, base::Unretained(this)),
120 void OnLookupFinished(int result
) {
121 predictor_
->OnLookupFinished(this, url_
, result
== net::OK
);
124 Predictor
* predictor_
; // The predictor which started us.
126 const GURL url_
; // Hostname to resolve.
127 net::SingleRequestHostResolver resolver_
;
128 net::AddressList addresses_
;
130 DISALLOW_COPY_AND_ASSIGN(LookupRequest
);
133 // This records UMAs for preconnect usage based on navigation URLs to
134 // gather precision/recall for user-event based preconnect triggers.
135 // Stats are gathered via a LRU cache that remembers all preconnect within the
137 // A preconnect trigger is considered as used iff a navigation including
138 // access to the preconnected host occurs within a time period specified by
139 // kMaxUnusedSocketLifetimeSecondsWithoutAGet.
140 class Predictor::PreconnectUsage
{
145 // Record a preconnect trigger to |url|.
146 void ObservePreconnect(const GURL
& url
);
148 // Record a user navigation with its redirect history, |url_chain|.
149 // We are uncertain if this is actually a link navigation.
150 void ObserveNavigationChain(const std::vector
<GURL
>& url_chain
,
151 bool is_subresource
);
153 // Record a user link navigation to |final_url|.
154 // We are certain that this is a user-triggered link navigation.
155 void ObserveLinkNavigation(const GURL
& final_url
);
158 // This tracks whether a preconnect was used in some navigation or not
159 class PreconnectPrecisionStat
{
161 PreconnectPrecisionStat()
162 : timestamp_(base::TimeTicks::Now()),
166 const base::TimeTicks
& timestamp() { return timestamp_
; }
168 void set_was_used() { was_used_
= true; }
169 bool was_used() const { return was_used_
; }
172 base::TimeTicks timestamp_
;
176 typedef base::MRUCache
<GURL
, PreconnectPrecisionStat
> MRUPreconnects
;
177 MRUPreconnects mru_preconnects_
;
179 // The longest time an entry can persist in mru_preconnect_
180 const base::TimeDelta max_duration_
;
182 std::vector
<GURL
> recent_navigation_chain_
;
184 DISALLOW_COPY_AND_ASSIGN(PreconnectUsage
);
187 Predictor::PreconnectUsage::PreconnectUsage()
188 : mru_preconnects_(MRUPreconnects::NO_AUTO_EVICT
),
189 max_duration_(base::TimeDelta::FromSeconds(
190 Predictor::kMaxUnusedSocketLifetimeSecondsWithoutAGet
)) {
193 Predictor::PreconnectUsage::~PreconnectUsage() {}
195 void Predictor::PreconnectUsage::ObservePreconnect(const GURL
& url
) {
196 // Evict any overly old entries and record stats.
197 base::TimeTicks now
= base::TimeTicks::Now();
199 MRUPreconnects::reverse_iterator eldest_preconnect
=
200 mru_preconnects_
.rbegin();
201 while (!mru_preconnects_
.empty()) {
202 DCHECK(eldest_preconnect
== mru_preconnects_
.rbegin());
203 if (now
- eldest_preconnect
->second
.timestamp() < max_duration_
)
206 UMA_HISTOGRAM_BOOLEAN("Net.PreconnectTriggerUsed",
207 eldest_preconnect
->second
.was_used());
208 eldest_preconnect
= mru_preconnects_
.Erase(eldest_preconnect
);
212 GURL
canonical_url(Predictor::CanonicalizeUrl(url
));
213 mru_preconnects_
.Put(canonical_url
, PreconnectPrecisionStat());
216 void Predictor::PreconnectUsage::ObserveNavigationChain(
217 const std::vector
<GURL
>& url_chain
,
218 bool is_subresource
) {
219 if (url_chain
.empty())
223 recent_navigation_chain_
= url_chain
;
225 GURL
canonical_url(Predictor::CanonicalizeUrl(url_chain
.back()));
227 MRUPreconnects::iterator itPreconnect
= mru_preconnects_
.Peek(canonical_url
);
228 bool was_preconnected
= (itPreconnect
!= mru_preconnects_
.end());
230 // This is an UMA which was named incorrectly. This actually measures the
231 // ratio of URLRequests which have used a preconnected session.
232 UMA_HISTOGRAM_BOOLEAN("Net.PreconnectedNavigation", was_preconnected
);
235 void Predictor::PreconnectUsage::ObserveLinkNavigation(const GURL
& url
) {
236 if (recent_navigation_chain_
.empty() ||
237 url
!= recent_navigation_chain_
.back()) {
238 // The navigation chain is not available for this navigation.
239 recent_navigation_chain_
.clear();
240 recent_navigation_chain_
.push_back(url
);
243 // See if the link navigation involved preconnected session.
244 bool did_use_preconnect
= false;
245 for (std::vector
<GURL
>::const_iterator it
= recent_navigation_chain_
.begin();
246 it
!= recent_navigation_chain_
.end();
248 GURL
canonical_url(Predictor::CanonicalizeUrl(*it
));
250 // Record the preconnect trigger for the url as used if exist
251 MRUPreconnects::iterator itPreconnect
=
252 mru_preconnects_
.Peek(canonical_url
);
253 bool was_preconnected
= (itPreconnect
!= mru_preconnects_
.end());
254 if (was_preconnected
) {
255 itPreconnect
->second
.set_was_used();
256 did_use_preconnect
= true;
260 UMA_HISTOGRAM_BOOLEAN("Net.PreconnectedLinkNavigations", did_use_preconnect
);
263 Predictor::Predictor(bool preconnect_enabled
)
264 : url_request_context_getter_(NULL
),
265 predictor_enabled_(true),
266 peak_pending_lookups_(0),
268 max_concurrent_dns_lookups_(g_max_parallel_resolves
),
269 max_dns_queue_delay_(
270 TimeDelta::FromMilliseconds(g_max_queueing_delay_ms
)),
271 host_resolver_(NULL
),
272 preconnect_enabled_(preconnect_enabled
),
273 consecutive_omnibox_preconnect_count_(0),
274 next_trim_time_(base::TimeTicks::Now() +
275 TimeDelta::FromHours(kDurationBetweenTrimmingsHours
)) {
276 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
279 Predictor::~Predictor() {
280 // TODO(rlp): Add DCHECK for CurrentlyOn(BrowserThread::IO) when the
281 // ProfileManagerTest has been updated with a mock profile.
286 Predictor
* Predictor::CreatePredictor(bool preconnect_enabled
,
287 bool simple_shutdown
) {
289 return new SimplePredictor(preconnect_enabled
);
290 return new Predictor(preconnect_enabled
);
293 void Predictor::RegisterProfilePrefs(
294 user_prefs::PrefRegistrySyncable
* registry
) {
295 registry
->RegisterListPref(prefs::kDnsPrefetchingStartupList
,
296 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
297 registry
->RegisterListPref(prefs::kDnsPrefetchingHostReferralList
,
298 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
301 // --------------------- Start UI methods. ------------------------------------
303 void Predictor::InitNetworkPredictor(PrefService
* user_prefs
,
304 PrefService
* local_state
,
306 net::URLRequestContextGetter
* getter
) {
307 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
309 bool predictor_enabled
=
310 user_prefs
->GetBoolean(prefs::kNetworkPredictionEnabled
);
312 url_request_context_getter_
= getter
;
314 // Gather the list of hostnames to prefetch on startup.
315 UrlList urls
= GetPredictedUrlListAtStartup(user_prefs
, local_state
);
317 base::ListValue
* referral_list
=
318 static_cast<base::ListValue
*>(user_prefs
->GetList(
319 prefs::kDnsPrefetchingHostReferralList
)->DeepCopy());
321 // Now that we have the statistics in memory, wipe them from the Preferences
322 // file. They will be serialized back on a clean shutdown. This way we only
323 // have to worry about clearing our in-memory state when Clearing Browsing
325 user_prefs
->ClearPref(prefs::kDnsPrefetchingStartupList
);
326 user_prefs
->ClearPref(prefs::kDnsPrefetchingHostReferralList
);
328 #if defined(OS_ANDROID) || defined(OS_IOS)
329 // TODO(marq): Once https://codereview.chromium.org/30883003/ lands, also
330 // condition this on DataReductionProxySettings::IsDataReductionProxyAllowed()
331 // Until then, we may create a proxy advisor when the proxy feature itself
332 // isn't available, and the advisor instance will never send advisory
333 // requests, which is slightly wasteful but not harmful.
334 if (DataReductionProxySettings::IsPreconnectHintingAllowed()) {
335 proxy_advisor_
.reset(new ProxyAdvisor(user_prefs
, getter
));
339 BrowserThread::PostTask(
343 &Predictor::FinalizeInitializationOnIOThread
,
344 base::Unretained(this),
346 io_thread
, predictor_enabled
));
349 void Predictor::AnticipateOmniboxUrl(const GURL
& url
, bool preconnectable
) {
350 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
351 if (!predictor_enabled_
)
353 if (!url
.is_valid() || !url
.has_host())
355 std::string host
= url
.HostNoBrackets();
356 bool is_new_host_request
= (host
!= last_omnibox_host_
);
357 last_omnibox_host_
= host
;
359 UrlInfo::ResolutionMotivation
motivation(UrlInfo::OMNIBOX_MOTIVATED
);
360 base::TimeTicks now
= base::TimeTicks::Now();
362 if (preconnect_enabled()) {
363 if (preconnectable
&& !is_new_host_request
) {
364 ++consecutive_omnibox_preconnect_count_
;
365 // The omnibox suggests a search URL (for which we can preconnect) after
366 // one or two characters are typed, even though such typing often (1 in
367 // 3?) becomes a real URL. This code waits till is has more evidence of a
368 // preconnectable URL (search URL) before forming a preconnection, so as
369 // to reduce the useless preconnect rate.
370 // Perchance this logic should be pushed back into the omnibox, where the
371 // actual characters typed, such as a space, can better forcast whether
372 // we need to search/preconnect or not. By waiting for at least 4
373 // characters in a row that have lead to a search proposal, we avoid
374 // preconnections for a prefix like "www." and we also wait until we have
375 // at least a 4 letter word to search for.
376 // Each character typed appears to induce 2 calls to
377 // AnticipateOmniboxUrl(), so we double 4 characters and limit at 8
379 // TODO(jar): Use an A/B test to optimize this.
380 const int kMinConsecutiveRequests
= 8;
381 if (consecutive_omnibox_preconnect_count_
>= kMinConsecutiveRequests
) {
382 // TODO(jar): Perhaps we should do a GET to leave the socket open in the
383 // pool. Currently, we just do a connect, which MAY be reset if we
384 // don't use it in 10 secondes!!! As a result, we may do more
385 // connections, and actually cost the server more than if we did a real
386 // get with a fake request (/gen_204 might be the good path on Google).
387 const int kMaxSearchKeepaliveSeconds(10);
388 if ((now
- last_omnibox_preconnect_
).InSeconds() <
389 kMaxSearchKeepaliveSeconds
)
390 return; // We've done a preconnect recently.
391 last_omnibox_preconnect_
= now
;
392 const int kConnectionsNeeded
= 1;
393 PreconnectUrl(CanonicalizeUrl(url
), GURL(), motivation
,
395 return; // Skip pre-resolution, since we'll open a connection.
398 consecutive_omnibox_preconnect_count_
= 0;
402 // Fall through and consider pre-resolution.
404 // Omnibox tends to call in pairs (just a few milliseconds apart), and we
405 // really don't need to keep resolving a name that often.
406 // TODO(jar): A/B tests could check for perf impact of the early returns.
407 if (!is_new_host_request
) {
408 const int kMinPreresolveSeconds(10);
409 if (kMinPreresolveSeconds
> (now
- last_omnibox_preresolve_
).InSeconds())
412 last_omnibox_preresolve_
= now
;
414 BrowserThread::PostTask(
417 base::Bind(&Predictor::Resolve
, base::Unretained(this),
418 CanonicalizeUrl(url
), motivation
));
421 void Predictor::PreconnectUrlAndSubresources(const GURL
& url
,
422 const GURL
& first_party_for_cookies
) {
423 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
) ||
424 BrowserThread::CurrentlyOn(BrowserThread::IO
));
425 if (!predictor_enabled_
|| !preconnect_enabled() ||
426 !url
.is_valid() || !url
.has_host())
429 UrlInfo::ResolutionMotivation
motivation(UrlInfo::EARLY_LOAD_MOTIVATED
);
430 const int kConnectionsNeeded
= 1;
431 PreconnectUrl(CanonicalizeUrl(url
), first_party_for_cookies
,
432 motivation
, kConnectionsNeeded
);
433 PredictFrameSubresources(url
.GetWithEmptyPath(), first_party_for_cookies
);
436 UrlList
Predictor::GetPredictedUrlListAtStartup(
437 PrefService
* user_prefs
,
438 PrefService
* local_state
) {
439 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
441 // Recall list of URLs we learned about during last session.
442 // This may catch secondary hostnames, pulled in by the homepages. It will
443 // also catch more of the "primary" home pages, since that was (presumably)
444 // rendered first (and will be rendered first this time too).
445 const base::ListValue
* startup_list
=
446 user_prefs
->GetList(prefs::kDnsPrefetchingStartupList
);
449 base::ListValue::const_iterator it
= startup_list
->begin();
450 int format_version
= -1;
451 if (it
!= startup_list
->end() &&
452 (*it
)->GetAsInteger(&format_version
) &&
453 format_version
== kPredictorStartupFormatVersion
) {
455 for (; it
!= startup_list
->end(); ++it
) {
456 std::string url_spec
;
457 if (!(*it
)->GetAsString(&url_spec
)) {
459 break; // Format incompatibility.
462 if (!url
.has_host() || !url
.has_scheme()) {
464 break; // Format incompatibility.
472 // Prepare for any static home page(s) the user has in prefs. The user may
473 // have a LOT of tab's specified, so we may as well try to warm them all.
474 SessionStartupPref tab_start_pref
=
475 SessionStartupPref::GetStartupPref(user_prefs
);
476 if (SessionStartupPref::URLS
== tab_start_pref
.type
) {
477 for (size_t i
= 0; i
< tab_start_pref
.urls
.size(); i
++) {
478 GURL gurl
= tab_start_pref
.urls
[i
];
479 if (!gurl
.is_valid() || gurl
.SchemeIsFile() || gurl
.host().empty())
481 if (gurl
.SchemeIsHTTPOrHTTPS())
482 urls
.push_back(gurl
.GetWithEmptyPath());
487 urls
.push_back(GURL("http://www.google.com:80"));
492 void Predictor::set_max_queueing_delay(int max_queueing_delay_ms
) {
493 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
494 g_max_queueing_delay_ms
= max_queueing_delay_ms
;
497 void Predictor::set_max_parallel_resolves(size_t max_parallel_resolves
) {
498 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
499 g_max_parallel_resolves
= max_parallel_resolves
;
502 void Predictor::ShutdownOnUIThread() {
503 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
504 BrowserThread::PostTask(
507 base::Bind(&Predictor::Shutdown
, base::Unretained(this)));
510 // ---------------------- End UI methods. -------------------------------------
512 // --------------------- Start IO methods. ------------------------------------
514 void Predictor::Shutdown() {
515 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
519 STLDeleteElements(&pending_lookups_
);
522 void Predictor::DiscardAllResults() {
523 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
524 // Delete anything listed so far in this session that shows in about:dns.
528 // Try to delete anything in our work queue.
529 while (!work_queue_
.IsEmpty()) {
530 // Emulate processing cycle as though host was not found.
531 GURL url
= work_queue_
.Pop();
532 UrlInfo
* info
= &results_
[url
];
533 DCHECK(info
->HasUrl(url
));
534 info
->SetAssignedState();
535 info
->SetNoSuchNameState();
537 // Now every result_ is either resolved, or is being resolved
538 // (see LookupRequest).
540 // Step through result_, recording names of all hosts that can't be erased.
541 // We can't erase anything being worked on.
543 for (Results::iterator it
= results_
.begin(); results_
.end() != it
; ++it
) {
545 UrlInfo
* info
= &it
->second
;
546 DCHECK(info
->HasUrl(url
));
547 if (info
->is_assigned()) {
548 info
->SetPendingDeleteState();
549 assignees
[url
] = *info
;
552 DCHECK_LE(assignees
.size(), max_concurrent_dns_lookups_
);
554 // Put back in the names being worked on.
555 for (Results::iterator it
= assignees
.begin(); assignees
.end() != it
; ++it
) {
556 DCHECK(it
->second
.is_marked_to_delete());
557 results_
[it
->first
] = it
->second
;
561 // Overloaded Resolve() to take a vector of names.
562 void Predictor::ResolveList(const UrlList
& urls
,
563 UrlInfo::ResolutionMotivation motivation
) {
564 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
566 for (UrlList::const_iterator it
= urls
.begin(); it
< urls
.end(); ++it
) {
567 AppendToResolutionQueue(*it
, motivation
);
571 // Basic Resolve() takes an invidual name, and adds it
573 void Predictor::Resolve(const GURL
& url
,
574 UrlInfo::ResolutionMotivation motivation
) {
575 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
578 AppendToResolutionQueue(url
, motivation
);
581 void Predictor::LearnFromNavigation(const GURL
& referring_url
,
582 const GURL
& target_url
) {
583 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
584 if (!predictor_enabled_
)
586 DCHECK_EQ(referring_url
, Predictor::CanonicalizeUrl(referring_url
));
587 DCHECK_NE(referring_url
, GURL::EmptyGURL());
588 DCHECK_EQ(target_url
, Predictor::CanonicalizeUrl(target_url
));
589 DCHECK_NE(target_url
, GURL::EmptyGURL());
591 referrers_
[referring_url
].SuggestHost(target_url
);
592 // Possibly do some referrer trimming.
596 //-----------------------------------------------------------------------------
597 // This section supports the about:dns page.
599 void Predictor::PredictorGetHtmlInfo(Predictor
* predictor
,
600 std::string
* output
) {
601 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
603 output
->append("<html><head><title>About DNS</title>"
604 // We'd like the following no-cache... but it doesn't work.
605 // "<META HTTP-EQUIV=\"Pragma\" CONTENT=\"no-cache\">"
607 if (predictor
&& predictor
->predictor_enabled()) {
608 predictor
->GetHtmlInfo(output
);
610 output
->append("DNS pre-resolution and TCP pre-connection is disabled.");
612 output
->append("</body></html>");
615 // Provide sort order so all .com's are together, etc.
616 struct RightToLeftStringSorter
{
617 bool operator()(const GURL
& left
, const GURL
& right
) const {
618 return ReverseComponents(left
) < ReverseComponents(right
);
622 // Transforms something like "http://www.google.com/xyz" to
623 // "http://com.google.www/xyz".
624 static std::string
ReverseComponents(const GURL
& url
) {
625 // Reverse the components in the hostname.
626 std::vector
<std::string
> parts
;
627 base::SplitString(url
.host(), '.', &parts
);
628 std::reverse(parts
.begin(), parts
.end());
629 std::string reversed_host
= JoinString(parts
, '.');
631 // Return the new URL.
632 GURL::Replacements url_components
;
633 url_components
.SetHostStr(reversed_host
);
634 return url
.ReplaceComponents(url_components
).spec();
638 void Predictor::GetHtmlReferrerLists(std::string
* output
) {
639 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
640 if (referrers_
.empty())
643 // TODO(jar): Remove any plausible JavaScript from names before displaying.
645 typedef std::set
<GURL
, struct RightToLeftStringSorter
>
647 SortedNames sorted_names
;
649 for (Referrers::iterator it
= referrers_
.begin();
650 referrers_
.end() != it
; ++it
)
651 sorted_names
.insert(it
->first
);
653 output
->append("<br><table border>");
655 "<tr><th>Host for Page</th>"
656 "<th>Page Load<br>Count</th>"
657 "<th>Subresource<br>Navigations</th>"
658 "<th>Subresource<br>PreConnects</th>"
659 "<th>Subresource<br>PreResolves</th>"
660 "<th>Expected<br>Connects</th>"
661 "<th>Subresource Spec</th></tr>");
663 for (SortedNames::iterator it
= sorted_names
.begin();
664 sorted_names
.end() != it
; ++it
) {
665 Referrer
* referrer
= &(referrers_
[*it
]);
666 bool first_set_of_futures
= true;
667 for (Referrer::iterator future_url
= referrer
->begin();
668 future_url
!= referrer
->end(); ++future_url
) {
669 output
->append("<tr align=right>");
670 if (first_set_of_futures
) {
671 base::StringAppendF(output
,
672 "<td rowspan=%d>%s</td><td rowspan=%d>%d</td>",
673 static_cast<int>(referrer
->size()),
675 static_cast<int>(referrer
->size()),
676 static_cast<int>(referrer
->use_count()));
678 first_set_of_futures
= false;
679 base::StringAppendF(output
,
680 "<td>%d</td><td>%d</td><td>%d</td><td>%2.3f</td><td>%s</td></tr>",
681 static_cast<int>(future_url
->second
.navigation_count()),
682 static_cast<int>(future_url
->second
.preconnection_count()),
683 static_cast<int>(future_url
->second
.preresolution_count()),
684 static_cast<double>(future_url
->second
.subresource_use_rate()),
685 future_url
->first
.spec().c_str());
688 output
->append("</table>");
691 void Predictor::GetHtmlInfo(std::string
* output
) {
692 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
693 if (initial_observer_
.get())
694 initial_observer_
->GetFirstResolutionsHtml(output
);
695 // Show list of subresource predictions and stats.
696 GetHtmlReferrerLists(output
);
698 // Local lists for calling UrlInfo
699 UrlInfo::UrlInfoTable name_not_found
;
700 UrlInfo::UrlInfoTable name_preresolved
;
702 // Get copies of all useful data.
703 typedef std::map
<GURL
, UrlInfo
, RightToLeftStringSorter
> SortedUrlInfo
;
704 SortedUrlInfo snapshot
;
705 // UrlInfo supports value semantics, so we can do a shallow copy.
706 for (Results::iterator
it(results_
.begin()); it
!= results_
.end(); it
++)
707 snapshot
[it
->first
] = it
->second
;
709 // Partition the UrlInfo's into categories.
710 for (SortedUrlInfo::iterator
it(snapshot
.begin());
711 it
!= snapshot
.end(); it
++) {
712 if (it
->second
.was_nonexistent()) {
713 name_not_found
.push_back(it
->second
);
716 if (!it
->second
.was_found())
717 continue; // Still being processed.
718 name_preresolved
.push_back(it
->second
);
726 // Call for display of each table, along with title.
727 UrlInfo::GetHtmlTable(name_preresolved
,
728 "Preresolution DNS records performed for ", brief
, output
);
729 UrlInfo::GetHtmlTable(name_not_found
,
730 "Preresolving DNS records revealed non-existence for ", brief
, output
);
733 void Predictor::TrimReferrersNow() {
734 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
735 // Just finish up work if an incremental trim is in progress.
736 if (urls_being_trimmed_
.empty())
737 LoadUrlsForTrimming();
738 IncrementalTrimReferrers(true); // Do everything now.
741 void Predictor::SerializeReferrers(base::ListValue
* referral_list
) {
742 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
743 referral_list
->Clear();
744 referral_list
->Append(new base::FundamentalValue(kPredictorReferrerVersion
));
745 for (Referrers::const_iterator it
= referrers_
.begin();
746 it
!= referrers_
.end(); ++it
) {
747 // Serialize the list of subresource names.
748 base::Value
* subresource_list(it
->second
.Serialize());
750 // Create a list for each referer.
751 base::ListValue
* motivator(new base::ListValue
);
752 motivator
->Append(new base::StringValue(it
->first
.spec()));
753 motivator
->Append(subresource_list
);
755 referral_list
->Append(motivator
);
759 void Predictor::DeserializeReferrers(const base::ListValue
& referral_list
) {
760 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
761 int format_version
= -1;
762 if (referral_list
.GetSize() > 0 &&
763 referral_list
.GetInteger(0, &format_version
) &&
764 format_version
== kPredictorReferrerVersion
) {
765 for (size_t i
= 1; i
< referral_list
.GetSize(); ++i
) {
766 const base::ListValue
* motivator
;
767 if (!referral_list
.GetList(i
, &motivator
)) {
771 std::string motivating_url_spec
;
772 if (!motivator
->GetString(0, &motivating_url_spec
)) {
777 const base::Value
* subresource_list
;
778 if (!motivator
->Get(1, &subresource_list
)) {
783 referrers_
[GURL(motivating_url_spec
)].Deserialize(*subresource_list
);
788 void Predictor::DeserializeReferrersThenDelete(
789 base::ListValue
* referral_list
) {
790 DeserializeReferrers(*referral_list
);
791 delete referral_list
;
794 void Predictor::DiscardInitialNavigationHistory() {
795 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
796 if (initial_observer_
.get())
797 initial_observer_
->DiscardInitialNavigationHistory();
800 void Predictor::FinalizeInitializationOnIOThread(
801 const UrlList
& startup_urls
,
802 base::ListValue
* referral_list
,
804 bool predictor_enabled
) {
805 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
807 predictor_enabled_
= predictor_enabled
;
808 initial_observer_
.reset(new InitialObserver());
809 host_resolver_
= io_thread
->globals()->host_resolver
.get();
810 preconnect_usage_
.reset(new PreconnectUsage());
812 // base::WeakPtrFactory instances need to be created and destroyed
813 // on the same thread. The predictor lives on the IO thread and will die
814 // from there so now that we're on the IO thread we need to properly
815 // initialize the base::WeakPtrFactory.
816 // TODO(groby): Check if WeakPtrFactory has the same constraint.
817 weak_factory_
.reset(new base::WeakPtrFactory
<Predictor
>(this));
819 // Prefetch these hostnames on startup.
820 DnsPrefetchMotivatedList(startup_urls
, UrlInfo::STARTUP_LIST_MOTIVATED
);
821 DeserializeReferrersThenDelete(referral_list
);
824 //-----------------------------------------------------------------------------
825 // This section intermingles prefetch results with actual browser HTTP
826 // network activity. It supports calculating of the benefit of a prefetch, as
827 // well as recording what prefetched hostname resolutions might be potentially
828 // helpful during the next chrome-startup.
829 //-----------------------------------------------------------------------------
831 void Predictor::LearnAboutInitialNavigation(const GURL
& url
) {
832 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
833 if (!predictor_enabled_
|| NULL
== initial_observer_
.get() )
835 initial_observer_
->Append(url
, this);
838 // This API is only used in the browser process.
839 // It is called from an IPC message originating in the renderer. It currently
840 // includes both Page-Scan, and Link-Hover prefetching.
841 // TODO(jar): Separate out link-hover prefetching, and page-scan results.
842 void Predictor::DnsPrefetchList(const NameList
& hostnames
) {
843 // TODO(jar): Push GURL transport further back into renderer, but this will
844 // require a Webkit change in the observer :-/.
846 for (NameList::const_iterator it
= hostnames
.begin();
847 it
< hostnames
.end();
849 urls
.push_back(GURL("http://" + *it
+ ":80"));
852 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
853 DnsPrefetchMotivatedList(urls
, UrlInfo::PAGE_SCAN_MOTIVATED
);
856 void Predictor::DnsPrefetchMotivatedList(
858 UrlInfo::ResolutionMotivation motivation
) {
859 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
) ||
860 BrowserThread::CurrentlyOn(BrowserThread::IO
));
861 if (!predictor_enabled_
)
864 if (BrowserThread::CurrentlyOn(BrowserThread::IO
)) {
865 ResolveList(urls
, motivation
);
867 BrowserThread::PostTask(
870 base::Bind(&Predictor::ResolveList
, base::Unretained(this),
875 //-----------------------------------------------------------------------------
876 // Functions to handle saving of hostnames from one session to the next, to
877 // expedite startup times.
879 static void SaveDnsPrefetchStateForNextStartupAndTrimOnIOThread(
880 base::ListValue
* startup_list
,
881 base::ListValue
* referral_list
,
882 base::WaitableEvent
* completion
,
883 Predictor
* predictor
) {
884 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
886 if (NULL
== predictor
) {
887 completion
->Signal();
890 predictor
->SaveDnsPrefetchStateForNextStartupAndTrim(
891 startup_list
, referral_list
, completion
);
894 void Predictor::SaveStateForNextStartupAndTrim(PrefService
* prefs
) {
895 if (!predictor_enabled_
)
898 base::WaitableEvent
completion(true, false);
900 ListPrefUpdate
update_startup_list(prefs
, prefs::kDnsPrefetchingStartupList
);
901 ListPrefUpdate
update_referral_list(prefs
,
902 prefs::kDnsPrefetchingHostReferralList
);
903 if (BrowserThread::CurrentlyOn(BrowserThread::IO
)) {
904 SaveDnsPrefetchStateForNextStartupAndTrimOnIOThread(
905 update_startup_list
.Get(),
906 update_referral_list
.Get(),
910 bool posted
= BrowserThread::PostTask(
914 &SaveDnsPrefetchStateForNextStartupAndTrimOnIOThread
,
915 update_startup_list
.Get(),
916 update_referral_list
.Get(),
920 // TODO(jar): Synchronous waiting for the IO thread is a potential source
921 // to deadlocks and should be investigated. See http://crbug.com/78451.
924 // http://crbug.com/124954
925 base::ThreadRestrictions::ScopedAllowWait allow_wait
;
931 void Predictor::SaveDnsPrefetchStateForNextStartupAndTrim(
932 base::ListValue
* startup_list
,
933 base::ListValue
* referral_list
,
934 base::WaitableEvent
* completion
) {
935 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
936 if (initial_observer_
.get())
937 initial_observer_
->GetInitialDnsResolutionList(startup_list
);
939 // Do at least one trim at shutdown, in case the user wasn't running long
940 // enough to do any regular trimming of referrers.
942 SerializeReferrers(referral_list
);
944 completion
->Signal();
947 void Predictor::EnablePredictor(bool enable
) {
948 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
) ||
949 BrowserThread::CurrentlyOn(BrowserThread::IO
));
951 if (BrowserThread::CurrentlyOn(BrowserThread::IO
)) {
952 EnablePredictorOnIOThread(enable
);
954 BrowserThread::PostTask(
957 base::Bind(&Predictor::EnablePredictorOnIOThread
,
958 base::Unretained(this), enable
));
962 void Predictor::EnablePredictorOnIOThread(bool enable
) {
963 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
964 predictor_enabled_
= enable
;
967 void Predictor::PreconnectUrl(const GURL
& url
,
968 const GURL
& first_party_for_cookies
,
969 UrlInfo::ResolutionMotivation motivation
,
971 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
) ||
972 BrowserThread::CurrentlyOn(BrowserThread::IO
));
974 if (BrowserThread::CurrentlyOn(BrowserThread::IO
)) {
975 PreconnectUrlOnIOThread(url
, first_party_for_cookies
, motivation
, count
);
977 BrowserThread::PostTask(
980 base::Bind(&Predictor::PreconnectUrlOnIOThread
,
981 base::Unretained(this), url
, first_party_for_cookies
,
986 void Predictor::PreconnectUrlOnIOThread(
988 const GURL
& first_party_for_cookies
,
989 UrlInfo::ResolutionMotivation motivation
,
991 if (motivation
== UrlInfo::MOUSE_OVER_MOTIVATED
)
992 RecordPreconnectTrigger(url
);
994 AdviseProxy(url
, motivation
, true /* is_preconnect */);
996 PreconnectOnIOThread(url
,
997 first_party_for_cookies
,
1000 url_request_context_getter_
.get());
1003 void Predictor::RecordPreconnectTrigger(const GURL
& url
) {
1004 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
1005 if (preconnect_usage_
)
1006 preconnect_usage_
->ObservePreconnect(url
);
1009 void Predictor::RecordPreconnectNavigationStat(
1010 const std::vector
<GURL
>& url_chain
,
1011 bool is_subresource
) {
1012 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
1014 if (preconnect_usage_
)
1015 preconnect_usage_
->ObserveNavigationChain(url_chain
, is_subresource
);
1018 void Predictor::RecordLinkNavigation(const GURL
& url
) {
1019 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
1020 if (preconnect_usage_
)
1021 preconnect_usage_
->ObserveLinkNavigation(url
);
1024 void Predictor::PredictFrameSubresources(const GURL
& url
,
1025 const GURL
& first_party_for_cookies
) {
1026 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
) ||
1027 BrowserThread::CurrentlyOn(BrowserThread::IO
));
1028 if (!predictor_enabled_
)
1030 DCHECK_EQ(url
.GetWithEmptyPath(), url
);
1031 // Add one pass through the message loop to allow current navigation to
1033 if (BrowserThread::CurrentlyOn(BrowserThread::IO
)) {
1034 PrepareFrameSubresources(url
, first_party_for_cookies
);
1036 BrowserThread::PostTask(
1039 base::Bind(&Predictor::PrepareFrameSubresources
,
1040 base::Unretained(this), url
, first_party_for_cookies
));
1044 void Predictor::AdviseProxy(const GURL
& url
,
1045 UrlInfo::ResolutionMotivation motivation
,
1046 bool is_preconnect
) {
1047 if (!proxy_advisor_
)
1050 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
) ||
1051 BrowserThread::CurrentlyOn(BrowserThread::IO
));
1053 if (BrowserThread::CurrentlyOn(BrowserThread::IO
)) {
1054 AdviseProxyOnIOThread(url
, motivation
, is_preconnect
);
1056 BrowserThread::PostTask(
1059 base::Bind(&Predictor::AdviseProxyOnIOThread
,
1060 base::Unretained(this), url
, motivation
, is_preconnect
));
1064 enum SubresourceValue
{
1068 SUBRESOURCE_VALUE_MAX
1071 void Predictor::PrepareFrameSubresources(const GURL
& url
,
1072 const GURL
& first_party_for_cookies
) {
1073 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
1074 DCHECK_EQ(url
.GetWithEmptyPath(), url
);
1075 Referrers::iterator it
= referrers_
.find(url
);
1076 if (referrers_
.end() == it
) {
1077 // Only when we don't know anything about this url, make 2 connections
1078 // available. We could do this completely via learning (by prepopulating
1079 // the referrer_ list with this expected value), but it would swell the
1080 // size of the list with all the "Leaf" nodes in the tree (nodes that don't
1081 // load any subresources). If we learn about this resource, we will instead
1082 // provide a more carefully estimated preconnection count.
1083 if (preconnect_enabled_
) {
1084 PreconnectUrlOnIOThread(url
, first_party_for_cookies
,
1085 UrlInfo::SELF_REFERAL_MOTIVATED
, 2);
1090 Referrer
* referrer
= &(it
->second
);
1091 referrer
->IncrementUseCount();
1092 const UrlInfo::ResolutionMotivation motivation
=
1093 UrlInfo::LEARNED_REFERAL_MOTIVATED
;
1094 for (Referrer::iterator future_url
= referrer
->begin();
1095 future_url
!= referrer
->end(); ++future_url
) {
1096 SubresourceValue
evalution(TOO_NEW
);
1097 double connection_expectation
= future_url
->second
.subresource_use_rate();
1098 UMA_HISTOGRAM_CUSTOM_COUNTS("Net.PreconnectSubresourceExpectation",
1099 static_cast<int>(connection_expectation
* 100),
1101 future_url
->second
.ReferrerWasObserved();
1102 if (preconnect_enabled_
&&
1103 connection_expectation
> kPreconnectWorthyExpectedValue
) {
1104 evalution
= PRECONNECTION
;
1105 future_url
->second
.IncrementPreconnectionCount();
1106 int count
= static_cast<int>(std::ceil(connection_expectation
));
1107 if (url
.host() == future_url
->first
.host())
1109 PreconnectUrlOnIOThread(future_url
->first
, first_party_for_cookies
,
1111 } else if (connection_expectation
> kDNSPreresolutionWorthyExpectedValue
) {
1112 evalution
= PRERESOLUTION
;
1113 future_url
->second
.preresolution_increment();
1114 UrlInfo
* queued_info
= AppendToResolutionQueue(future_url
->first
,
1117 queued_info
->SetReferringHostname(url
);
1119 UMA_HISTOGRAM_ENUMERATION("Net.PreconnectSubresourceEval", evalution
,
1120 SUBRESOURCE_VALUE_MAX
);
1124 void Predictor::OnLookupFinished(LookupRequest
* request
, const GURL
& url
,
1126 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
1128 LookupFinished(request
, url
, found
);
1129 pending_lookups_
.erase(request
);
1132 StartSomeQueuedResolutions();
1135 void Predictor::LookupFinished(LookupRequest
* request
, const GURL
& url
,
1137 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
1138 UrlInfo
* info
= &results_
[url
];
1139 DCHECK(info
->HasUrl(url
));
1140 if (info
->is_marked_to_delete()) {
1141 results_
.erase(url
);
1144 info
->SetFoundState();
1146 info
->SetNoSuchNameState();
1150 UrlInfo
* Predictor::AppendToResolutionQueue(
1152 UrlInfo::ResolutionMotivation motivation
) {
1153 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
1154 DCHECK(url
.has_host());
1159 UrlInfo
* info
= &results_
[url
];
1160 info
->SetUrl(url
); // Initialize or DCHECK.
1161 // TODO(jar): I need to discard names that have long since expired.
1162 // Currently we only add to the domain map :-/
1164 DCHECK(info
->HasUrl(url
));
1166 if (!info
->NeedsDnsUpdate()) {
1167 info
->DLogResultsStats("DNS PrefetchNotUpdated");
1171 AdviseProxy(url
, motivation
, false /* is_preconnect */);
1172 if (proxy_advisor_
&& proxy_advisor_
->WouldProxyURL(url
)) {
1173 info
->DLogResultsStats("DNS PrefetchForProxiedRequest");
1177 info
->SetQueuedState(motivation
);
1178 work_queue_
.Push(url
, motivation
);
1179 StartSomeQueuedResolutions();
1183 bool Predictor::CongestionControlPerformed(UrlInfo
* info
) {
1184 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
1185 // Note: queue_duration is ONLY valid after we go to assigned state.
1186 if (info
->queue_duration() < max_dns_queue_delay_
)
1188 // We need to discard all entries in our queue, as we're keeping them waiting
1189 // too long. By doing this, we'll have a chance to quickly service urgent
1190 // resolutions, and not have a bogged down system.
1192 info
->RemoveFromQueue();
1193 if (work_queue_
.IsEmpty())
1195 info
= &results_
[work_queue_
.Pop()];
1196 info
->SetAssignedState();
1201 void Predictor::StartSomeQueuedResolutions() {
1202 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
1204 while (!work_queue_
.IsEmpty() &&
1205 pending_lookups_
.size() < max_concurrent_dns_lookups_
) {
1206 const GURL
url(work_queue_
.Pop());
1207 UrlInfo
* info
= &results_
[url
];
1208 DCHECK(info
->HasUrl(url
));
1209 info
->SetAssignedState();
1211 if (CongestionControlPerformed(info
)) {
1212 DCHECK(work_queue_
.IsEmpty());
1216 LookupRequest
* request
= new LookupRequest(this, host_resolver_
, url
);
1217 int status
= request
->Start();
1218 if (status
== net::ERR_IO_PENDING
) {
1219 // Will complete asynchronously.
1220 pending_lookups_
.insert(request
);
1221 peak_pending_lookups_
= std::max(peak_pending_lookups_
,
1222 pending_lookups_
.size());
1224 // Completed synchronously (was already cached by HostResolver), or else
1225 // there was (equivalently) some network error that prevents us from
1226 // finding the name. Status net::OK means it was "found."
1227 LookupFinished(request
, url
, status
== net::OK
);
1233 void Predictor::TrimReferrers() {
1234 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
1235 if (!urls_being_trimmed_
.empty())
1236 return; // There is incremental trimming in progress already.
1238 // Check to see if it is time to trim yet.
1239 base::TimeTicks now
= base::TimeTicks::Now();
1240 if (now
< next_trim_time_
)
1242 next_trim_time_
= now
+ TimeDelta::FromHours(kDurationBetweenTrimmingsHours
);
1244 LoadUrlsForTrimming();
1245 PostIncrementalTrimTask();
1248 void Predictor::LoadUrlsForTrimming() {
1249 DCHECK(urls_being_trimmed_
.empty());
1250 for (Referrers::const_iterator it
= referrers_
.begin();
1251 it
!= referrers_
.end(); ++it
)
1252 urls_being_trimmed_
.push_back(it
->first
);
1253 UMA_HISTOGRAM_COUNTS("Net.PredictionTrimSize", urls_being_trimmed_
.size());
1256 void Predictor::PostIncrementalTrimTask() {
1257 if (urls_being_trimmed_
.empty())
1259 const TimeDelta kDurationBetweenTrimmingIncrements
=
1260 TimeDelta::FromSeconds(kDurationBetweenTrimmingIncrementsSeconds
);
1261 base::MessageLoop::current()->PostDelayedTask(
1263 base::Bind(&Predictor::IncrementalTrimReferrers
,
1264 weak_factory_
->GetWeakPtr(), false),
1265 kDurationBetweenTrimmingIncrements
);
1268 void Predictor::IncrementalTrimReferrers(bool trim_all_now
) {
1269 size_t trim_count
= urls_being_trimmed_
.size();
1271 trim_count
= std::min(trim_count
, kUrlsTrimmedPerIncrement
);
1272 while (trim_count
-- != 0) {
1273 Referrers::iterator it
= referrers_
.find(urls_being_trimmed_
.back());
1274 urls_being_trimmed_
.pop_back();
1275 if (it
== referrers_
.end())
1276 continue; // Defensive code: It got trimmed away already.
1277 if (!it
->second
.Trim(kReferrerTrimRatio
, kDiscardableExpectedValue
))
1278 referrers_
.erase(it
);
1280 PostIncrementalTrimTask();
1283 void Predictor::AdviseProxyOnIOThread(const GURL
& url
,
1284 UrlInfo::ResolutionMotivation motivation
,
1285 bool is_preconnect
) {
1286 if (!proxy_advisor_
)
1288 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
1289 proxy_advisor_
->Advise(url
, motivation
, is_preconnect
);
1292 // ---------------------- End IO methods. -------------------------------------
1294 //-----------------------------------------------------------------------------
1296 Predictor::HostNameQueue::HostNameQueue() {
1299 Predictor::HostNameQueue::~HostNameQueue() {
1302 void Predictor::HostNameQueue::Push(const GURL
& url
,
1303 UrlInfo::ResolutionMotivation motivation
) {
1304 switch (motivation
) {
1305 case UrlInfo::STATIC_REFERAL_MOTIVATED
:
1306 case UrlInfo::LEARNED_REFERAL_MOTIVATED
:
1307 case UrlInfo::MOUSE_OVER_MOTIVATED
:
1308 rush_queue_
.push(url
);
1312 background_queue_
.push(url
);
1317 bool Predictor::HostNameQueue::IsEmpty() const {
1318 return rush_queue_
.empty() && background_queue_
.empty();
1321 GURL
Predictor::HostNameQueue::Pop() {
1323 std::queue
<GURL
> *queue(rush_queue_
.empty() ? &background_queue_
1325 GURL
url(queue
->front());
1330 //-----------------------------------------------------------------------------
1331 // Member definitions for InitialObserver class.
1333 Predictor::InitialObserver::InitialObserver() {
1336 Predictor::InitialObserver::~InitialObserver() {
1339 void Predictor::InitialObserver::Append(const GURL
& url
,
1340 Predictor
* predictor
) {
1341 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
1343 // TODO(rlp): Do we really need the predictor check here?
1344 if (NULL
== predictor
)
1346 if (kStartupResolutionCount
<= first_navigations_
.size())
1349 DCHECK(url
.SchemeIsHTTPOrHTTPS());
1350 DCHECK_EQ(url
, Predictor::CanonicalizeUrl(url
));
1351 if (first_navigations_
.find(url
) == first_navigations_
.end())
1352 first_navigations_
[url
] = base::TimeTicks::Now();
1355 void Predictor::InitialObserver::GetInitialDnsResolutionList(
1356 base::ListValue
* startup_list
) {
1357 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
1358 DCHECK(startup_list
);
1359 startup_list
->Clear();
1360 DCHECK_EQ(0u, startup_list
->GetSize());
1361 startup_list
->Append(
1362 new base::FundamentalValue(kPredictorStartupFormatVersion
));
1363 for (FirstNavigations::iterator it
= first_navigations_
.begin();
1364 it
!= first_navigations_
.end();
1366 DCHECK(it
->first
== Predictor::CanonicalizeUrl(it
->first
));
1367 startup_list
->Append(new base::StringValue(it
->first
.spec()));
1371 void Predictor::InitialObserver::GetFirstResolutionsHtml(
1372 std::string
* output
) {
1373 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
1375 UrlInfo::UrlInfoTable resolution_list
;
1377 for (FirstNavigations::iterator
it(first_navigations_
.begin());
1378 it
!= first_navigations_
.end();
1381 info
.SetUrl(it
->first
);
1382 info
.set_time(it
->second
);
1383 resolution_list
.push_back(info
);
1386 UrlInfo::GetHtmlTable(resolution_list
,
1387 "Future startups will prefetch DNS records for ", false, output
);
1390 //-----------------------------------------------------------------------------
1392 //-----------------------------------------------------------------------------
1395 GURL
Predictor::CanonicalizeUrl(const GURL
& url
) {
1396 if (!url
.has_host())
1397 return GURL::EmptyGURL();
1400 if (url
.has_scheme()) {
1401 scheme
= url
.scheme();
1402 if (scheme
!= "http" && scheme
!= "https")
1403 return GURL::EmptyGURL();
1405 return url
.GetWithEmptyPath();
1410 // If we omit a port, it will default to 80 or 443 as appropriate.
1411 std::string colon_plus_port
;
1413 colon_plus_port
= ":" + url
.port();
1415 return GURL(scheme
+ "://" + url
.host() + colon_plus_port
);
1418 void SimplePredictor::InitNetworkPredictor(
1419 PrefService
* user_prefs
,
1420 PrefService
* local_state
,
1421 IOThread
* io_thread
,
1422 net::URLRequestContextGetter
* getter
) {
1423 // Empty function for unittests.
1426 void SimplePredictor::ShutdownOnUIThread() {
1430 } // namespace chrome_browser_net