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