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