1 // Copyright 2017 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/predictors/preconnect_manager.h"
6 
7 #include <utility>
8 
9 #include "base/bind.h"
10 #include "base/trace_event/trace_event.h"
11 #include "chrome/browser/predictors/predictors_features.h"
12 #include "chrome/browser/predictors/resource_prefetch_predictor.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "content/public/browser/browser_task_traits.h"
15 #include "content/public/browser/browser_thread.h"
16 #include "content/public/browser/storage_partition.h"
17 #include "services/network/public/mojom/network_context.mojom.h"
18 
19 namespace predictors {
20 
21 const bool kAllowCredentialsOnPreconnectByDefault = true;
22 
PreconnectedRequestStats(const url::Origin & origin,bool was_preconnected)23 PreconnectedRequestStats::PreconnectedRequestStats(const url::Origin& origin,
24                                                    bool was_preconnected)
25     : origin(origin), was_preconnected(was_preconnected) {}
26 
27 PreconnectedRequestStats::PreconnectedRequestStats(
28     const PreconnectedRequestStats& other) = default;
29 PreconnectedRequestStats::~PreconnectedRequestStats() = default;
30 
PreconnectStats(const GURL & url)31 PreconnectStats::PreconnectStats(const GURL& url)
32     : url(url), start_time(base::TimeTicks::Now()) {}
33 PreconnectStats::~PreconnectStats() = default;
34 
PreresolveInfo(const GURL & url,size_t count)35 PreresolveInfo::PreresolveInfo(const GURL& url, size_t count)
36     : url(url),
37       queued_count(count),
38       inflight_count(0),
39       was_canceled(false),
40       stats(std::make_unique<PreconnectStats>(url)) {}
41 
42 PreresolveInfo::~PreresolveInfo() = default;
43 
PreresolveJob(const GURL & url,int num_sockets,bool allow_credentials,net::NetworkIsolationKey network_isolation_key,PreresolveInfo * info)44 PreresolveJob::PreresolveJob(const GURL& url,
45                              int num_sockets,
46                              bool allow_credentials,
47                              net::NetworkIsolationKey network_isolation_key,
48                              PreresolveInfo* info)
49     : url(url),
50       num_sockets(num_sockets),
51       allow_credentials(allow_credentials),
52       network_isolation_key(std::move(network_isolation_key)),
53       info(info) {
54   DCHECK_GE(num_sockets, 0);
55 }
56 
PreresolveJob(PreconnectRequest preconnect_request,PreresolveInfo * info)57 PreresolveJob::PreresolveJob(PreconnectRequest preconnect_request,
58                              PreresolveInfo* info)
59     : url(preconnect_request.origin.GetURL()),
60       num_sockets(preconnect_request.num_sockets),
61       allow_credentials(preconnect_request.allow_credentials),
62       network_isolation_key(
63           std::move(preconnect_request.network_isolation_key)),
64       info(info) {
65   DCHECK_GE(num_sockets, 0);
66 }
67 
68 PreresolveJob::PreresolveJob(PreresolveJob&& other) = default;
69 PreresolveJob::~PreresolveJob() = default;
70 
PreconnectManager(base::WeakPtr<Delegate> delegate,Profile * profile)71 PreconnectManager::PreconnectManager(base::WeakPtr<Delegate> delegate,
72                                      Profile* profile)
73     : delegate_(std::move(delegate)),
74       profile_(profile),
75       inflight_preresolves_count_(0) {
76   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
77   DCHECK(profile_);
78 }
79 
80 PreconnectManager::~PreconnectManager() = default;
81 
Start(const GURL & url,std::vector<PreconnectRequest> requests)82 void PreconnectManager::Start(const GURL& url,
83                               std::vector<PreconnectRequest> requests) {
84   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
85   PreresolveInfo* info;
86   if (preresolve_info_.find(url) == preresolve_info_.end()) {
87     auto iterator_and_whether_inserted = preresolve_info_.emplace(
88         url, std::make_unique<PreresolveInfo>(url, requests.size()));
89     info = iterator_and_whether_inserted.first->second.get();
90   } else {
91     info = preresolve_info_.find(url)->second.get();
92     info->queued_count += requests.size();
93   }
94 
95   for (auto& request : requests) {
96     PreresolveJobId job_id = preresolve_jobs_.Add(
97         std::make_unique<PreresolveJob>(std::move(request), info));
98     queued_jobs_.push_back(job_id);
99   }
100 
101   TryToLaunchPreresolveJobs();
102 }
103 
StartPreresolveHost(const GURL & url,const net::NetworkIsolationKey & network_isolation_key)104 void PreconnectManager::StartPreresolveHost(
105     const GURL& url,
106     const net::NetworkIsolationKey& network_isolation_key) {
107   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
108   if (!url.SchemeIsHTTPOrHTTPS())
109     return;
110   PreresolveJobId job_id = preresolve_jobs_.Add(std::make_unique<PreresolveJob>(
111       url.GetOrigin(), 0, kAllowCredentialsOnPreconnectByDefault,
112       network_isolation_key, nullptr));
113   queued_jobs_.push_front(job_id);
114 
115   TryToLaunchPreresolveJobs();
116 }
117 
StartPreresolveHosts(const std::vector<std::string> & hostnames,const net::NetworkIsolationKey & network_isolation_key)118 void PreconnectManager::StartPreresolveHosts(
119     const std::vector<std::string>& hostnames,
120     const net::NetworkIsolationKey& network_isolation_key) {
121   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
122   // Push jobs in front of the queue due to higher priority.
123   for (auto it = hostnames.rbegin(); it != hostnames.rend(); ++it) {
124     PreresolveJobId job_id =
125         preresolve_jobs_.Add(std::make_unique<PreresolveJob>(
126             GURL("http://" + *it), 0, kAllowCredentialsOnPreconnectByDefault,
127             network_isolation_key, nullptr));
128     queued_jobs_.push_front(job_id);
129   }
130 
131   TryToLaunchPreresolveJobs();
132 }
133 
StartPreconnectUrl(const GURL & url,bool allow_credentials,net::NetworkIsolationKey network_isolation_key)134 void PreconnectManager::StartPreconnectUrl(
135     const GURL& url,
136     bool allow_credentials,
137     net::NetworkIsolationKey network_isolation_key) {
138   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
139   if (!url.SchemeIsHTTPOrHTTPS())
140     return;
141   PreresolveJobId job_id = preresolve_jobs_.Add(std::make_unique<PreresolveJob>(
142       url.GetOrigin(), 1, allow_credentials, std::move(network_isolation_key),
143       nullptr));
144   queued_jobs_.push_front(job_id);
145 
146   TryToLaunchPreresolveJobs();
147 }
148 
Stop(const GURL & url)149 void PreconnectManager::Stop(const GURL& url) {
150   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
151   auto it = preresolve_info_.find(url);
152   if (it == preresolve_info_.end()) {
153     return;
154   }
155 
156   it->second->was_canceled = true;
157 }
158 
PreconnectUrl(const GURL & url,int num_sockets,bool allow_credentials,const net::NetworkIsolationKey & network_isolation_key) const159 void PreconnectManager::PreconnectUrl(
160     const GURL& url,
161     int num_sockets,
162     bool allow_credentials,
163     const net::NetworkIsolationKey& network_isolation_key) const {
164   DCHECK(url.GetOrigin() == url);
165   DCHECK(url.SchemeIsHTTPOrHTTPS());
166   if (observer_)
167     observer_->OnPreconnectUrl(url, num_sockets, allow_credentials);
168 
169   auto* network_context = GetNetworkContext();
170   if (!network_context)
171     return;
172 
173   network_context->PreconnectSockets(num_sockets, url, allow_credentials,
174                                      network_isolation_key);
175 }
176 
PreresolveUrl(const GURL & url,const net::NetworkIsolationKey & network_isolation_key,ResolveHostCallback callback) const177 std::unique_ptr<ResolveHostClientImpl> PreconnectManager::PreresolveUrl(
178     const GURL& url,
179     const net::NetworkIsolationKey& network_isolation_key,
180     ResolveHostCallback callback) const {
181   DCHECK(url.GetOrigin() == url);
182   DCHECK(url.SchemeIsHTTPOrHTTPS());
183 
184   auto* network_context = GetNetworkContext();
185   if (!network_context) {
186     // Cannot invoke the callback right away because it would cause the
187     // use-after-free after returning from this function.
188     content::GetUIThreadTaskRunner({content::BrowserTaskType::kPreconnect})
189         ->PostTask(FROM_HERE, base::BindOnce(std::move(callback), false));
190     return nullptr;
191   }
192 
193   return std::make_unique<ResolveHostClientImpl>(
194       url, network_isolation_key, std::move(callback), network_context);
195 }
196 
LookupProxyForUrl(const GURL & url,const net::NetworkIsolationKey & network_isolation_key,ProxyLookupCallback callback) const197 std::unique_ptr<ProxyLookupClientImpl> PreconnectManager::LookupProxyForUrl(
198     const GURL& url,
199     const net::NetworkIsolationKey& network_isolation_key,
200     ProxyLookupCallback callback) const {
201   DCHECK(url.GetOrigin() == url);
202   DCHECK(url.SchemeIsHTTPOrHTTPS());
203 
204   auto* network_context = GetNetworkContext();
205   if (!network_context) {
206     std::move(callback).Run(false);
207     return nullptr;
208   }
209 
210   return std::make_unique<ProxyLookupClientImpl>(
211       url, network_isolation_key, std::move(callback), network_context);
212 }
213 
TryToLaunchPreresolveJobs()214 void PreconnectManager::TryToLaunchPreresolveJobs() {
215   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
216 
217   while (!queued_jobs_.empty() &&
218          inflight_preresolves_count_ < features::GetMaxInflightPreresolves()) {
219     auto job_id = queued_jobs_.front();
220     queued_jobs_.pop_front();
221     PreresolveJob* job = preresolve_jobs_.Lookup(job_id);
222     DCHECK(job);
223     PreresolveInfo* info = job->info;
224 
225     if (!(info && info->was_canceled)) {
226       // This is used to avoid issuing DNS requests when a fixed proxy
227       // configuration is in place, which improves efficiency, and is also
228       // important if the unproxied DNS may contain incorrect entries.
229       job->proxy_lookup_client = LookupProxyForUrl(
230           job->url, job->network_isolation_key,
231           base::BindOnce(&PreconnectManager::OnProxyLookupFinished,
232                          weak_factory_.GetWeakPtr(), job_id));
233       if (info) {
234         ++info->inflight_count;
235         if (delegate_)
236           delegate_->PreconnectInitiated(info->url, job->url);
237       }
238       ++inflight_preresolves_count_;
239     } else {
240       preresolve_jobs_.Remove(job_id);
241     }
242 
243     if (info) {
244       DCHECK_LE(1u, info->queued_count);
245       --info->queued_count;
246       if (info->is_done()) {
247         AllPreresolvesForUrlFinished(info);
248       }
249     }
250   }
251 }
252 
OnPreresolveFinished(PreresolveJobId job_id,bool success)253 void PreconnectManager::OnPreresolveFinished(PreresolveJobId job_id,
254                                              bool success) {
255   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
256   PreresolveJob* job = preresolve_jobs_.Lookup(job_id);
257   DCHECK(job);
258 
259   if (observer_)
260     observer_->OnPreresolveFinished(job->url, job->network_isolation_key,
261                                     success);
262 
263   job->resolve_host_client = nullptr;
264   FinishPreresolveJob(job_id, success);
265 }
266 
OnProxyLookupFinished(PreresolveJobId job_id,bool success)267 void PreconnectManager::OnProxyLookupFinished(PreresolveJobId job_id,
268                                               bool success) {
269   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
270   PreresolveJob* job = preresolve_jobs_.Lookup(job_id);
271   DCHECK(job);
272 
273   if (observer_) {
274     observer_->OnProxyLookupFinished(job->url, job->network_isolation_key,
275                                      success);
276   }
277 
278   job->proxy_lookup_client = nullptr;
279   if (success) {
280     FinishPreresolveJob(job_id, success);
281   } else {
282     job->resolve_host_client =
283         PreresolveUrl(job->url, job->network_isolation_key,
284                       base::BindOnce(&PreconnectManager::OnPreresolveFinished,
285                                      weak_factory_.GetWeakPtr(), job_id));
286   }
287 }
288 
FinishPreresolveJob(PreresolveJobId job_id,bool success)289 void PreconnectManager::FinishPreresolveJob(PreresolveJobId job_id,
290                                             bool success) {
291   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
292   PreresolveJob* job = preresolve_jobs_.Lookup(job_id);
293   DCHECK(job);
294 
295   bool need_preconnect = success && job->need_preconnect();
296   if (need_preconnect) {
297     PreconnectUrl(job->url, job->num_sockets, job->allow_credentials,
298                   job->network_isolation_key);
299   }
300 
301   PreresolveInfo* info = job->info;
302   if (info) {
303     info->stats->requests_stats.emplace_back(url::Origin::Create(job->url),
304                                              need_preconnect);
305   }
306   preresolve_jobs_.Remove(job_id);
307   --inflight_preresolves_count_;
308   if (info) {
309     DCHECK_LE(1u, info->inflight_count);
310     --info->inflight_count;
311   }
312   if (info && info->is_done())
313     AllPreresolvesForUrlFinished(info);
314   TryToLaunchPreresolveJobs();
315 }
316 
AllPreresolvesForUrlFinished(PreresolveInfo * info)317 void PreconnectManager::AllPreresolvesForUrlFinished(PreresolveInfo* info) {
318   DCHECK(info);
319   DCHECK(info->is_done());
320   auto it = preresolve_info_.find(info->url);
321   DCHECK(it != preresolve_info_.end());
322   DCHECK(info == it->second.get());
323   if (delegate_)
324     delegate_->PreconnectFinished(std::move(info->stats));
325   preresolve_info_.erase(it);
326 }
327 
GetNetworkContext() const328 network::mojom::NetworkContext* PreconnectManager::GetNetworkContext() const {
329   if (network_context_)
330     return network_context_;
331 
332   if (profile_->AsTestingProfile()) {
333     // We're testing and |network_context_| wasn't set. Return nullptr to avoid
334     // hitting the network.
335     return nullptr;
336   }
337 
338   return content::BrowserContext::GetDefaultStoragePartition(profile_)
339       ->GetNetworkContext();
340 }
341 
342 }  // namespace predictors
343