1 // Copyright 2018 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/android/customtabs/detached_resource_request.h"
6
7 #include <cstdlib>
8 #include <utility>
9
10 #include "base/bind.h"
11 #include "base/location.h"
12 #include "base/metrics/histogram_functions.h"
13 #include "base/metrics/histogram_macros.h"
14 #include "content/public/browser/browser_context.h"
15 #include "content/public/browser/browser_thread.h"
16 #include "content/public/browser/storage_partition.h"
17 #include "content/public/common/referrer.h"
18 #include "net/traffic_annotation/network_traffic_annotation.h"
19 #include "net/url_request/url_request_job.h"
20 #include "services/network/public/cpp/resource_request.h"
21 #include "services/network/public/cpp/simple_url_loader.h"
22 #include "services/network/public/mojom/url_response_head.mojom.h"
23 #include "third_party/blink/public/mojom/loader/resource_load_info.mojom-shared.h"
24 #include "url/gurl.h"
25 #include "url/origin.h"
26
27 namespace customtabs {
28
29 namespace {
30
RecordParallelRequestHistograms(const std::string & suffix,int redirects,base::TimeDelta duration,int net_error)31 void RecordParallelRequestHistograms(const std::string& suffix,
32 int redirects,
33 base::TimeDelta duration,
34 int net_error) {
35 bool success = net_error == net::OK;
36 if (success) {
37 // Max 20 redirects, 21 would be a bug.
38 base::UmaHistogramCustomCounts(
39 "CustomTabs.DetachedResourceRequest.RedirectsCount.Success" + suffix,
40 redirects, 1, 21, 21);
41 base::UmaHistogramMediumTimes(
42 "CustomTabs.DetachedResourceRequest.Duration.Success" + suffix,
43 duration);
44 } else {
45 base::UmaHistogramCustomCounts(
46 "CustomTabs.DetachedResourceRequest.RedirectsCount.Failure" + suffix,
47 redirects, 1, 21, 21);
48 base::UmaHistogramMediumTimes(
49 "CustomTabs.DetachedResourceRequest.Duration.Failure" + suffix,
50 duration);
51 }
52
53 base::UmaHistogramSparse(
54 "CustomTabs.DetachedResourceRequest.FinalStatus" + suffix, net_error);
55 }
56
57 } // namespace
58
59 // static
CreateAndStart(content::BrowserContext * browser_context,const GURL & url,const GURL & site_for_cookies,const net::ReferrerPolicy referrer_policy,Motivation motivation,const std::string & package_name,DetachedResourceRequest::OnResultCallback cb)60 void DetachedResourceRequest::CreateAndStart(
61 content::BrowserContext* browser_context,
62 const GURL& url,
63 const GURL& site_for_cookies,
64 const net::ReferrerPolicy referrer_policy,
65 Motivation motivation,
66 const std::string& package_name,
67 DetachedResourceRequest::OnResultCallback cb) {
68 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
69 std::unique_ptr<DetachedResourceRequest> detached_request(
70 new DetachedResourceRequest(url, site_for_cookies, referrer_policy,
71 motivation, package_name, std::move(cb)));
72 Start(std::move(detached_request), browser_context);
73 }
74
75 DetachedResourceRequest::~DetachedResourceRequest() = default;
76
DetachedResourceRequest(const GURL & url,const GURL & site_for_cookies,net::ReferrerPolicy referrer_policy,Motivation motivation,const std::string & package_name,DetachedResourceRequest::OnResultCallback cb)77 DetachedResourceRequest::DetachedResourceRequest(
78 const GURL& url,
79 const GURL& site_for_cookies,
80 net::ReferrerPolicy referrer_policy,
81 Motivation motivation,
82 const std::string& package_name,
83 DetachedResourceRequest::OnResultCallback cb)
84 : url_(url),
85 site_for_cookies_(site_for_cookies),
86 motivation_(motivation),
87 cb_(std::move(cb)),
88 redirects_(0) {
89 is_from_aga_ = package_name == "com.google.android.googlequicksearchbox";
90 net::NetworkTrafficAnnotationTag traffic_annotation =
91 net::DefineNetworkTrafficAnnotation("customtabs_parallel_request",
92 R"(
93 semantics {
94 sender: "Custom Tabs"
95 description:
96 "When a URL is opened in Custom Tabs on Android, the calling "
97 "app can specify a single parallel request to be made while "
98 "the main URL is loading. This allows the calling app to "
99 "remove a redirect that would otherwise be needed, improving "
100 "performance."
101 trigger: "A page is loaded in a Custom Tabs."
102 data: "Same as a regular resource request."
103 destination: WEBSITE
104 }
105 policy {
106 cookies_allowed: YES
107 cookie_store: "user"
108 policy_exception_justification: "Identical to a resource fetch."
109 })");
110
111 auto resource_request = std::make_unique<network::ResourceRequest>();
112 resource_request->method = "GET";
113 resource_request->url = url_;
114 // The referrer is stripped if it's not set properly initially.
115 resource_request->referrer = net::URLRequestJob::ComputeReferrerForPolicy(
116 referrer_policy, site_for_cookies_, url_);
117 resource_request->referrer_policy = referrer_policy;
118 resource_request->site_for_cookies =
119 net::SiteForCookies::FromUrl(site_for_cookies_);
120
121 url::Origin site_for_cookies_origin = url::Origin::Create(site_for_cookies_);
122 resource_request->request_initiator = site_for_cookies_origin;
123
124 // Since |site_for_cookies_| has gone through digital asset links
125 // verification, it should be ok to use it to compute the network isolation
126 // key.
127 resource_request->trusted_params = network::ResourceRequest::TrustedParams();
128 resource_request->trusted_params->isolation_info = net::IsolationInfo::Create(
129 net::IsolationInfo::RequestType::kOther, site_for_cookies_origin,
130 site_for_cookies_origin,
131 net::SiteForCookies::FromOrigin(site_for_cookies_origin));
132
133 resource_request->resource_type =
134 static_cast<int>(blink::mojom::ResourceType::kSubResource);
135 resource_request->do_not_prompt_for_login = true;
136 resource_request->render_frame_id = -1;
137 resource_request->enable_load_timing = false;
138 resource_request->report_raw_headers = false;
139
140 url_loader_ = network::SimpleURLLoader::Create(std::move(resource_request),
141 traffic_annotation);
142 }
143
144 // static
Start(std::unique_ptr<DetachedResourceRequest> request,content::BrowserContext * browser_context)145 void DetachedResourceRequest::Start(
146 std::unique_ptr<DetachedResourceRequest> request,
147 content::BrowserContext* browser_context) {
148 request->start_time_ = base::TimeTicks::Now();
149 auto* storage_partition =
150 content::BrowserContext::GetStoragePartition(browser_context, nullptr);
151
152 request->url_loader_->SetOnRedirectCallback(
153 base::BindRepeating(&DetachedResourceRequest::OnRedirectCallback,
154 base::Unretained(request.get())));
155
156 // Retry for client-side transient failures: DNS resolution errors and network
157 // configuration changes. Server HTTP 5xx errors are not retried.
158 //
159 // This is due to seeing that network changes happen quite a bit in
160 // practice. This may be due to these requests happening early in Chrome's
161 // lifecycle, so perhaps when the network was otherwise idle before,
162 // potentially triggering a network change as a consequence. This is only an
163 // hypothesis, but happens in practice, and retrying does help lowering the
164 // failure rate.
165 //
166 // DNS errors are both independent and linked to this. They can happen for a
167 // number of reasons, including a network change. Starting with Chrome 81
168 // however, a network change happening during DNS resolution is reported as a
169 // DNS error, not a network configuration change. This is visible in
170 // metrics. As a consequence, retry the request on DNS errors as well. Note
171 // that this is harmless, since the request cannot have server-side
172 // side-effects if the DNS resolution failed. See crbug.com/1078350 for
173 // details.
174 int retry_mode = network::SimpleURLLoader::RETRY_ON_NETWORK_CHANGE |
175 network::SimpleURLLoader::RETRY_ON_NAME_NOT_RESOLVED;
176 request->url_loader_->SetRetryOptions(1 /* max_retries */, retry_mode);
177
178 // |url_loader_| is owned by the request, and must be kept alive to not cancel
179 // the request. Pass the ownership of the request to the response callback,
180 // ensuring that it stays alive, yet is freed upon completion or failure.
181 //
182 // This is also the reason for this function to be a static member function
183 // instead of a regular function.
184 request->url_loader_->DownloadToString(
185 storage_partition->GetURLLoaderFactoryForBrowserProcess().get(),
186 base::BindOnce(&DetachedResourceRequest::OnResponseCallback,
187 std::move(request)),
188 kMaxResponseSize);
189 }
190
OnRedirectCallback(const net::RedirectInfo & redirect_info,const network::mojom::URLResponseHead & response_head,std::vector<std::string> * to_be_removed_headers)191 void DetachedResourceRequest::OnRedirectCallback(
192 const net::RedirectInfo& redirect_info,
193 const network::mojom::URLResponseHead& response_head,
194 std::vector<std::string>* to_be_removed_headers) {
195 redirects_++;
196 }
197
OnResponseCallback(std::unique_ptr<std::string> response_body)198 void DetachedResourceRequest::OnResponseCallback(
199 std::unique_ptr<std::string> response_body) {
200 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
201 int net_error = url_loader_->NetError();
202 net_error = std::abs(net_error);
203 auto duration = base::TimeTicks::Now() - start_time_;
204
205 switch (motivation_) {
206 case Motivation::kParallelRequest: {
207 RecordParallelRequestHistograms("", redirects_, duration, net_error);
208 if (is_from_aga_) {
209 RecordParallelRequestHistograms(".FromAga", redirects_, duration,
210 net_error);
211 }
212 break;
213 }
214 case Motivation::kResourcePrefetch: {
215 if (net_error == net::OK) {
216 UMA_HISTOGRAM_MEDIUM_TIMES(
217 "CustomTabs.ResourcePrefetch.Duration.Success", duration);
218 } else {
219 UMA_HISTOGRAM_MEDIUM_TIMES(
220 "CustomTabs.ResourcePrefetch.Duration.Failure", duration);
221 }
222
223 base::UmaHistogramSparse("CustomTabs.ResourcePrefetch.FinalStatus",
224 net_error);
225 break;
226 }
227 }
228
229 std::move(cb_).Run(net_error);
230 }
231
232 } // namespace customtabs
233