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: 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. 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: 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: 164 PreconnectPrecisionStat() 165 : timestamp_(base::TimeTicks::Now()), 166 was_used_(false) { 167 } 168 169 const base::TimeTicks& timestamp() { return timestamp_; } 170 171 void set_was_used() { was_used_ = true; } 172 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 190 Predictor::PreconnectUsage::PreconnectUsage() 191 : mru_preconnects_(MRUPreconnects::NO_AUTO_EVICT), 192 max_duration_(base::TimeDelta::FromSeconds( 193 Predictor::kMaxUnusedSocketLifetimeSecondsWithoutAGet)) { 194 } 195 196 Predictor::PreconnectUsage::~PreconnectUsage() {} 197 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 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 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 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 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 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 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 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 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 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 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 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 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 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 521 void Predictor::Shutdown() { 522 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 523 DCHECK(!shutdown_); 524 shutdown_ = true; 525 526 STLDeleteElements(&pending_lookups_); 527 } 528 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. 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. 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 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 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 { 624 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". 631 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 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 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 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 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 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 795 void Predictor::DeserializeReferrersThenDelete( 796 base::ListValue* referral_list) { 797 DeserializeReferrers(*referral_list); 798 delete referral_list; 799 } 800 801 void Predictor::DiscardInitialNavigationHistory() { 802 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 803 if (initial_observer_.get()) 804 initial_observer_->DiscardInitialNavigationHistory(); 805 } 806 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 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. 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 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 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 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 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 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 974 void Predictor::EnablePredictorOnIOThread(bool enable) { 975 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 976 predictor_enabled_ = enable; 977 } 978 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 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 1023 void Predictor::RecordPreconnectTrigger(const GURL& url) { 1024 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 1025 if (preconnect_usage_) 1026 preconnect_usage_->ObservePreconnect(url); 1027 } 1028 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 1038 void Predictor::RecordLinkNavigation(const GURL& url) { 1039 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 1040 if (preconnect_usage_) 1041 preconnect_usage_->ObserveLinkNavigation(url); 1042 } 1043 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 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 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 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 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 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 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 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 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 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 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 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 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 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 1338 Predictor::HostNameQueue::HostNameQueue() { 1339 } 1340 1341 Predictor::HostNameQueue::~HostNameQueue() { 1342 } 1343 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 1359 bool Predictor::HostNameQueue::IsEmpty() const { 1360 return rush_queue_.empty() && background_queue_.empty(); 1361 } 1362 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 1375 Predictor::InitialObserver::InitialObserver() { 1376 } 1377 1378 Predictor::InitialObserver::~InitialObserver() { 1379 } 1380 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 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 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 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 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 1468 void SimplePredictor::ShutdownOnUIThread() { 1469 SetShutdown(true); 1470 } 1471 1472 } // namespace chrome_browser_net 1473