1 // Copyright 2019 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 "content/browser/client_hints/client_hints.h"
6 
7 #include <algorithm>
8 #include <string>
9 
10 #include "base/command_line.h"
11 #include "base/feature_list.h"
12 #include "base/metrics/field_trial_params.h"
13 #include "base/optional.h"
14 #include "base/rand_util.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/stringprintf.h"
17 #include "base/time/time.h"
18 #include "build/build_config.h"
19 #include "content/browser/frame_host/frame_tree.h"
20 #include "content/browser/frame_host/frame_tree_node.h"
21 #include "content/browser/frame_host/navigator.h"
22 #include "content/public/browser/browser_thread.h"
23 #include "content/public/browser/host_zoom_map.h"
24 #include "content/public/common/content_features.h"
25 #include "content/public/common/content_switches.h"
26 #include "net/base/url_util.h"
27 #include "net/nqe/effective_connection_type.h"
28 #include "net/nqe/network_quality_estimator_params.h"
29 #include "services/network/public/cpp/network_quality_tracker.h"
30 #include "third_party/blink/public/common/client_hints/client_hints.h"
31 #include "third_party/blink/public/common/device_memory/approximated_device_memory.h"
32 #include "third_party/blink/public/common/feature_policy/feature_policy.h"
33 #include "third_party/blink/public/common/page/page_zoom.h"
34 #include "third_party/blink/public/common/user_agent/user_agent_metadata.h"
35 #include "third_party/blink/public/platform/web_client_hints_type.h"
36 #include "ui/display/display.h"
37 #include "ui/display/screen.h"
38 
39 namespace {
40 uint8_t randomization_salt = 0;
41 
42 constexpr size_t kMaxRandomNumbers = 21;
43 
44 // Returns the randomization salt (weak and insecure) that should be used when
45 // adding noise to the network quality metrics. This is known only to the
46 // device, and is generated only once. This makes it possible to add the same
47 // amount of noise for a given origin.
RandomizationSalt()48 uint8_t RandomizationSalt() {
49   if (randomization_salt == 0)
50     randomization_salt = base::RandInt(1, kMaxRandomNumbers);
51   DCHECK_LE(1, randomization_salt);
52   DCHECK_GE(kMaxRandomNumbers, randomization_salt);
53   return randomization_salt;
54 }
55 
GetRandomMultiplier(const std::string & host)56 double GetRandomMultiplier(const std::string& host) {
57   // The random number should be a function of the hostname to reduce
58   // cross-origin fingerprinting. The random number should also be a function
59   // of randomized salt which is known only to the device. This prevents
60   // origin from removing noise from the estimates.
61   unsigned hash = std::hash<std::string>{}(host) + RandomizationSalt();
62   double random_multiplier =
63       0.9 + static_cast<double>((hash % kMaxRandomNumbers)) * 0.01;
64   DCHECK_LE(0.90, random_multiplier);
65   DCHECK_GE(1.10, random_multiplier);
66   return random_multiplier;
67 }
68 
RoundRtt(const std::string & host,const base::Optional<base::TimeDelta> & rtt)69 unsigned long RoundRtt(const std::string& host,
70                        const base::Optional<base::TimeDelta>& rtt) {
71   // Limit the size of the buckets and the maximum reported value to reduce
72   // fingerprinting.
73   static const size_t kGranularityMsec = 50;
74   static const double kMaxRttMsec = 3.0 * 1000;
75 
76   if (!rtt.has_value()) {
77     // RTT is unavailable. So, return the fastest value.
78     return 0;
79   }
80 
81   double rtt_msec = static_cast<double>(rtt.value().InMilliseconds());
82   rtt_msec *= GetRandomMultiplier(host);
83   rtt_msec = std::min(rtt_msec, kMaxRttMsec);
84 
85   DCHECK_LE(0, rtt_msec);
86   DCHECK_GE(kMaxRttMsec, rtt_msec);
87 
88   // Round down to the nearest kBucketSize msec value.
89   return std::round(rtt_msec / kGranularityMsec) * kGranularityMsec;
90 }
91 
RoundKbpsToMbps(const std::string & host,const base::Optional<int32_t> & downlink_kbps)92 double RoundKbpsToMbps(const std::string& host,
93                        const base::Optional<int32_t>& downlink_kbps) {
94   // Limit the size of the buckets and the maximum reported value to reduce
95   // fingerprinting.
96   static const size_t kGranularityKbps = 50;
97   static const double kMaxDownlinkKbps = 10.0 * 1000;
98 
99   // If downlink is unavailable, return the fastest value.
100   double randomized_downlink_kbps = downlink_kbps.value_or(kMaxDownlinkKbps);
101   randomized_downlink_kbps *= GetRandomMultiplier(host);
102 
103   randomized_downlink_kbps =
104       std::min(randomized_downlink_kbps, kMaxDownlinkKbps);
105 
106   DCHECK_LE(0, randomized_downlink_kbps);
107   DCHECK_GE(kMaxDownlinkKbps, randomized_downlink_kbps);
108   // Round down to the nearest kGranularityKbps kbps value.
109   double downlink_kbps_rounded =
110       std::round(randomized_downlink_kbps / kGranularityKbps) *
111       kGranularityKbps;
112 
113   // Convert from Kbps to Mbps.
114   return downlink_kbps_rounded / 1000;
115 }
116 
GetDeviceScaleFactor()117 double GetDeviceScaleFactor() {
118   double device_scale_factor = 1.0;
119   if (display::Screen::GetScreen()) {
120     device_scale_factor =
121         display::Screen::GetScreen()->GetPrimaryDisplay().device_scale_factor();
122   }
123   DCHECK_LT(0.0, device_scale_factor);
124   return device_scale_factor;
125 }
126 
127 // Returns the zoom factor for a given |url|.
GetZoomFactor(content::BrowserContext * context,const GURL & url)128 double GetZoomFactor(content::BrowserContext* context, const GURL& url) {
129 // Android does not have the concept of zooming in like desktop.
130 #if defined(OS_ANDROID)
131   return 1.0;
132 #else
133 
134   double zoom_level = content::HostZoomMap::GetDefaultForBrowserContext(context)
135                           ->GetZoomLevelForHostAndScheme(
136                               url.scheme(), net::GetHostOrSpecFromURL(url));
137 
138   if (zoom_level == 0.0) {
139     // Get default zoom level.
140     zoom_level = content::HostZoomMap::GetDefaultForBrowserContext(context)
141                      ->GetDefaultZoomLevel();
142   }
143 
144   return blink::PageZoomLevelToZoomFactor(zoom_level);
145 #endif
146 }
147 
148 // Returns a string corresponding to |value|. The returned string satisfies
149 // ABNF: 1*DIGIT [ "." 1*DIGIT ]
DoubleToSpecCompliantString(double value)150 std::string DoubleToSpecCompliantString(double value) {
151   DCHECK_LE(0.0, value);
152   std::string result = base::NumberToString(value);
153   DCHECK(!result.empty());
154   if (value >= 1.0)
155     return result;
156 
157   DCHECK_LE(0.0, value);
158   DCHECK_GT(1.0, value);
159 
160   // Check if there is at least one character before period.
161   if (result.at(0) != '.')
162     return result;
163 
164   // '.' is the first character in |result|. Prefix one digit before the
165   // period to make it spec compliant.
166   return "0" + result;
167 }
168 
169 // Return the effective connection type value overridden for web APIs.
170 // If no override value has been set, a null value is returned.
171 base::Optional<net::EffectiveConnectionType>
GetWebHoldbackEffectiveConnectionType()172 GetWebHoldbackEffectiveConnectionType() {
173   if (!base::FeatureList::IsEnabled(
174           features::kNetworkQualityEstimatorWebHoldback)) {
175     return base::nullopt;
176   }
177   std::string effective_connection_type_param =
178       base::GetFieldTrialParamValueByFeature(
179           features::kNetworkQualityEstimatorWebHoldback,
180           "web_effective_connection_type_override");
181 
182   base::Optional<net::EffectiveConnectionType> effective_connection_type =
183       net::GetEffectiveConnectionTypeForName(effective_connection_type_param);
184   DCHECK(effective_connection_type_param.empty() || effective_connection_type);
185 
186   if (!effective_connection_type)
187     return base::nullopt;
188   DCHECK_NE(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN,
189             effective_connection_type.value());
190   return effective_connection_type;
191 }
192 
SetHeaderToDouble(net::HttpRequestHeaders * headers,blink::mojom::WebClientHintsType client_hint_type,double value)193 void SetHeaderToDouble(net::HttpRequestHeaders* headers,
194                        blink::mojom::WebClientHintsType client_hint_type,
195                        double value) {
196   headers->SetHeader(
197       blink::kClientHintsHeaderMapping[static_cast<int>(client_hint_type)],
198       DoubleToSpecCompliantString(value));
199 }
200 
SetHeaderToInt(net::HttpRequestHeaders * headers,blink::mojom::WebClientHintsType client_hint_type,double value)201 void SetHeaderToInt(net::HttpRequestHeaders* headers,
202                     blink::mojom::WebClientHintsType client_hint_type,
203                     double value) {
204   headers->SetHeader(
205       blink::kClientHintsHeaderMapping[static_cast<int>(client_hint_type)],
206       base::NumberToString(std::round(value)));
207 }
208 
SetHeaderToString(net::HttpRequestHeaders * headers,blink::mojom::WebClientHintsType client_hint_type,std::string value)209 void SetHeaderToString(net::HttpRequestHeaders* headers,
210                        blink::mojom::WebClientHintsType client_hint_type,
211                        std::string value) {
212   headers->SetHeader(
213       blink::kClientHintsHeaderMapping[static_cast<int>(client_hint_type)],
214       value);
215 }
216 
AddDeviceMemoryHeader(net::HttpRequestHeaders * headers)217 void AddDeviceMemoryHeader(net::HttpRequestHeaders* headers) {
218   DCHECK(headers);
219   blink::ApproximatedDeviceMemory::Initialize();
220   const float device_memory =
221       blink::ApproximatedDeviceMemory::GetApproximatedDeviceMemory();
222   DCHECK_LT(0.0, device_memory);
223   SetHeaderToDouble(headers, blink::mojom::WebClientHintsType::kDeviceMemory,
224                     device_memory);
225 }
226 
AddDPRHeader(net::HttpRequestHeaders * headers,content::BrowserContext * context,const GURL & url)227 void AddDPRHeader(net::HttpRequestHeaders* headers,
228                   content::BrowserContext* context,
229                   const GURL& url) {
230   DCHECK(headers);
231   DCHECK(context);
232   double device_scale_factor = GetDeviceScaleFactor();
233   double zoom_factor = GetZoomFactor(context, url);
234   SetHeaderToDouble(headers, blink::mojom::WebClientHintsType::kDpr,
235                     device_scale_factor * zoom_factor);
236 }
237 
AddViewportWidthHeader(net::HttpRequestHeaders * headers,content::BrowserContext * context,const GURL & url)238 void AddViewportWidthHeader(net::HttpRequestHeaders* headers,
239                             content::BrowserContext* context,
240                             const GURL& url) {
241   DCHECK(headers);
242   DCHECK(context);
243   // The default value on Android. See
244   // https://cs.chromium.org/chromium/src/third_party/WebKit/Source/core/css/viewportAndroid.css.
245   double viewport_width = 980;
246 
247 #if !defined(OS_ANDROID)
248   double device_scale_factor = GetDeviceScaleFactor();
249   viewport_width = (display::Screen::GetScreen()
250                         ->GetPrimaryDisplay()
251                         .GetSizeInPixel()
252                         .width()) /
253                    GetZoomFactor(context, url) / device_scale_factor;
254 #endif  // !OS_ANDROID
255   DCHECK_LT(0, viewport_width);
256   // TODO(yoav): Find out why this 0 check is needed...
257   if (viewport_width > 0) {
258     SetHeaderToInt(headers, blink::mojom::WebClientHintsType::kViewportWidth,
259                    viewport_width);
260   }
261 }
262 
AddRttHeader(net::HttpRequestHeaders * headers,network::NetworkQualityTracker * network_quality_tracker,const GURL & url)263 void AddRttHeader(net::HttpRequestHeaders* headers,
264                   network::NetworkQualityTracker* network_quality_tracker,
265                   const GURL& url) {
266   DCHECK(headers);
267 
268   base::Optional<net::EffectiveConnectionType> web_holdback_ect =
269       GetWebHoldbackEffectiveConnectionType();
270 
271   base::TimeDelta http_rtt;
272   if (web_holdback_ect.has_value()) {
273     http_rtt = net::NetworkQualityEstimatorParams::GetDefaultTypicalHttpRtt(
274         web_holdback_ect.value());
275   } else if (network_quality_tracker) {
276     http_rtt = network_quality_tracker->GetHttpRTT();
277   } else {
278     http_rtt = net::NetworkQualityEstimatorParams::GetDefaultTypicalHttpRtt(
279         net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN);
280   }
281   SetHeaderToInt(headers, blink::mojom::WebClientHintsType::kRtt,
282                  RoundRtt(url.host(), http_rtt));
283 }
284 
AddDownlinkHeader(net::HttpRequestHeaders * headers,network::NetworkQualityTracker * network_quality_tracker,const GURL & url)285 void AddDownlinkHeader(net::HttpRequestHeaders* headers,
286                        network::NetworkQualityTracker* network_quality_tracker,
287                        const GURL& url) {
288   DCHECK(headers);
289   base::Optional<net::EffectiveConnectionType> web_holdback_ect =
290       GetWebHoldbackEffectiveConnectionType();
291 
292   int32_t downlink_throughput_kbps;
293 
294   if (web_holdback_ect.has_value()) {
295     downlink_throughput_kbps =
296         net::NetworkQualityEstimatorParams::GetDefaultTypicalDownlinkKbps(
297             web_holdback_ect.value());
298   } else if (network_quality_tracker) {
299     downlink_throughput_kbps =
300         network_quality_tracker->GetDownstreamThroughputKbps();
301   } else {
302     downlink_throughput_kbps =
303         net::NetworkQualityEstimatorParams::GetDefaultTypicalDownlinkKbps(
304             net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN);
305   }
306 
307   SetHeaderToDouble(headers, blink::mojom::WebClientHintsType::kDownlink,
308                     RoundKbpsToMbps(url.host(), downlink_throughput_kbps));
309 }
310 
AddEctHeader(net::HttpRequestHeaders * headers,network::NetworkQualityTracker * network_quality_tracker,const GURL & url)311 void AddEctHeader(net::HttpRequestHeaders* headers,
312                   network::NetworkQualityTracker* network_quality_tracker,
313                   const GURL& url) {
314   DCHECK(headers);
315   DCHECK_EQ(blink::kWebEffectiveConnectionTypeMappingCount,
316             net::EFFECTIVE_CONNECTION_TYPE_4G + 1u);
317   DCHECK_EQ(blink::kWebEffectiveConnectionTypeMappingCount,
318             static_cast<size_t>(net::EFFECTIVE_CONNECTION_TYPE_LAST));
319 
320   base::Optional<net::EffectiveConnectionType> web_holdback_ect =
321       GetWebHoldbackEffectiveConnectionType();
322 
323   int effective_connection_type;
324   if (web_holdback_ect.has_value()) {
325     effective_connection_type = web_holdback_ect.value();
326   } else if (network_quality_tracker) {
327     effective_connection_type =
328         static_cast<int>(network_quality_tracker->GetEffectiveConnectionType());
329   } else {
330     effective_connection_type =
331         static_cast<int>(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN);
332   }
333 
334   SetHeaderToString(
335       headers, blink::mojom::WebClientHintsType::kEct,
336       blink::kWebEffectiveConnectionTypeMapping[effective_connection_type]);
337 }
338 
AddLangHeader(net::HttpRequestHeaders * headers,content::ClientHintsControllerDelegate * delegate)339 void AddLangHeader(net::HttpRequestHeaders* headers,
340                    content::ClientHintsControllerDelegate* delegate) {
341   SetHeaderToString(
342       headers, blink::mojom::WebClientHintsType::kLang,
343       blink::SerializeLangClientHint(delegate->GetAcceptLanguageString()));
344 }
345 
IsValidURLForClientHints(const GURL & url)346 bool IsValidURLForClientHints(const GURL& url) {
347   if (!url.is_valid() || !url.SchemeIsHTTPOrHTTPS() ||
348       (url.SchemeIs(url::kHttpScheme) && !net::IsLocalhost(url)))
349     return false;
350 
351   DCHECK(url.SchemeIs(url::kHttpsScheme) ||
352          (url.SchemeIs(url::kHttpScheme) && net::IsLocalhost(url)));
353   return true;
354 }
355 
UserAgentClientHintEnabled()356 bool UserAgentClientHintEnabled() {
357   return base::FeatureList::IsEnabled(features::kUserAgentClientHint);
358 }
359 
AddUAHeader(net::HttpRequestHeaders * headers,blink::mojom::WebClientHintsType type,std::string value)360 void AddUAHeader(net::HttpRequestHeaders* headers,
361                  blink::mojom::WebClientHintsType type,
362                  std::string value) {
363   SetHeaderToString(headers, type, value);
364 }
365 
AddQuotes(std::string str)366 std::string AddQuotes(std::string str) {
367   return base::StringPrintf("\"%s\"", str.c_str());
368 }
369 
AddBrandVersionQuotes(std::string brand,std::string version)370 std::string AddBrandVersionQuotes(std::string brand, std::string version) {
371   if (version.empty()) {
372     return AddQuotes(brand);
373   }
374   return base::StringPrintf("\"%s\"; v=\"%s\"", brand.c_str(), version.c_str());
375 }
376 
IsFeaturePolicyForClientHintsEnabled()377 bool IsFeaturePolicyForClientHintsEnabled() {
378   return base::FeatureList::IsEnabled(features::kFeaturePolicyForClientHints);
379 }
380 
ShouldAddClientHint(const blink::WebEnabledClientHints & main_frame_client_hints,bool is_main_frame,bool is_1p_origin,blink::FeaturePolicy * feature_policy,const url::Origin & resource_origin,blink::mojom::WebClientHintsType type,blink::mojom::FeaturePolicyFeature feature)381 bool ShouldAddClientHint(
382     const blink::WebEnabledClientHints& main_frame_client_hints,
383     bool is_main_frame,
384     bool is_1p_origin,
385     blink::FeaturePolicy* feature_policy,
386     const url::Origin& resource_origin,
387     blink::mojom::WebClientHintsType type,
388     blink::mojom::FeaturePolicyFeature feature) {
389   if (!main_frame_client_hints.IsEnabled(type))
390     return false;
391   if (!IsFeaturePolicyForClientHintsEnabled() || is_main_frame)
392     return is_1p_origin;
393   return feature_policy &&
394          feature_policy->IsFeatureEnabledForOrigin(feature, resource_origin);
395 }
396 
397 }  // namespace
398 
399 namespace content {
400 
RoundRttForTesting(const std::string & host,const base::Optional<base::TimeDelta> & rtt)401 unsigned long RoundRttForTesting(const std::string& host,
402                                  const base::Optional<base::TimeDelta>& rtt) {
403   return RoundRtt(host, rtt);
404 }
405 
RoundKbpsToMbpsForTesting(const std::string & host,const base::Optional<int32_t> & downlink_kbps)406 double RoundKbpsToMbpsForTesting(const std::string& host,
407                                  const base::Optional<int32_t>& downlink_kbps) {
408   return RoundKbpsToMbps(host, downlink_kbps);
409 }
410 
AddNavigationRequestClientHintsHeaders(const GURL & url,net::HttpRequestHeaders * headers,BrowserContext * context,bool javascript_enabled,ClientHintsControllerDelegate * delegate,bool is_ua_override_on,FrameTreeNode * frame_tree_node)411 void AddNavigationRequestClientHintsHeaders(
412     const GURL& url,
413     net::HttpRequestHeaders* headers,
414     BrowserContext* context,
415     bool javascript_enabled,
416     ClientHintsControllerDelegate* delegate,
417     bool is_ua_override_on,
418     FrameTreeNode* frame_tree_node) {
419   DCHECK_CURRENTLY_ON(BrowserThread::UI);
420   DCHECK_EQ(blink::kWebEffectiveConnectionTypeMappingCount,
421             net::EFFECTIVE_CONNECTION_TYPE_4G + 1u);
422   DCHECK_EQ(blink::kWebEffectiveConnectionTypeMappingCount,
423             static_cast<size_t>(net::EFFECTIVE_CONNECTION_TYPE_LAST));
424   DCHECK(context);
425   RenderFrameHostImpl* main_frame =
426       frame_tree_node->frame_tree()->GetMainFrame();
427 
428   if (!IsValidURLForClientHints(url))
429     return;
430 
431   // Client hints should only be enabled when JavaScript is enabled. Platforms
432   // which enable/disable JavaScript on a per-origin basis should implement
433   // IsJavaScriptAllowed to check a given origin. Other platforms (Android
434   // WebView) enable/disable JavaScript on a per-View basis, using the
435   // WebPreferences setting.
436   if (!delegate->IsJavaScriptAllowed(url) || !javascript_enabled)
437     return;
438 
439   blink::WebEnabledClientHints web_client_hints;
440   url::Origin resource_origin = url::Origin::Create(url);
441 
442   // If the current frame is the main frame, the URL wasn't committed yet, so in
443   // order to get the main frame URL, we should use the provided URL instead.
444   // Otherwise, the current frame is an iframe and the main frame URL was
445   // committed, so we can safely get it from it. Similarly, an in-navigation
446   // main frame doesn't yet have a feature policy.
447   bool is_main_frame = frame_tree_node->IsMainFrame();
448   GURL main_frame_url;
449   blink::FeaturePolicy* feature_policy;
450   bool is_1p_origin;
451   if (is_main_frame) {
452     main_frame_url = url;
453     feature_policy = nullptr;
454     is_1p_origin = true;
455   } else {
456     main_frame_url = main_frame->GetLastCommittedURL();
457     feature_policy = main_frame->feature_policy();
458     is_1p_origin =
459         resource_origin.IsSameOriginWith(main_frame->GetLastCommittedOrigin());
460   }
461 
462   delegate->GetAllowedClientHintsFromSource(main_frame_url, &web_client_hints);
463 
464 
465   // Add Headers
466   if (ShouldAddClientHint(
467           web_client_hints, is_main_frame, is_1p_origin, feature_policy,
468           resource_origin, blink::mojom::WebClientHintsType::kDeviceMemory,
469           blink::mojom::FeaturePolicyFeature::kClientHintDeviceMemory)) {
470     AddDeviceMemoryHeader(headers);
471   }
472   if (ShouldAddClientHint(web_client_hints, is_main_frame, is_1p_origin,
473                           feature_policy, resource_origin,
474                           blink::mojom::WebClientHintsType::kDpr,
475                           blink::mojom::FeaturePolicyFeature::kClientHintDPR)) {
476     AddDPRHeader(headers, context, url);
477   }
478   if (ShouldAddClientHint(
479           web_client_hints, is_main_frame, is_1p_origin, feature_policy,
480           resource_origin, blink::mojom::WebClientHintsType::kViewportWidth,
481           blink::mojom::FeaturePolicyFeature::kClientHintViewportWidth)) {
482     AddViewportWidthHeader(headers, context, url);
483   }
484   network::NetworkQualityTracker* network_quality_tracker =
485       delegate->GetNetworkQualityTracker();
486   if (ShouldAddClientHint(web_client_hints, is_1p_origin, is_main_frame,
487                           feature_policy, resource_origin,
488                           blink::mojom::WebClientHintsType::kRtt,
489                           blink::mojom::FeaturePolicyFeature::kClientHintRTT)) {
490     AddRttHeader(headers, network_quality_tracker, url);
491   }
492   if (ShouldAddClientHint(
493           web_client_hints, is_main_frame, is_1p_origin, feature_policy,
494           resource_origin, blink::mojom::WebClientHintsType::kDownlink,
495           blink::mojom::FeaturePolicyFeature::kClientHintDownlink)) {
496     AddDownlinkHeader(headers, network_quality_tracker, url);
497   }
498   if (ShouldAddClientHint(web_client_hints, is_main_frame, is_1p_origin,
499                           feature_policy, resource_origin,
500                           blink::mojom::WebClientHintsType::kEct,
501                           blink::mojom::FeaturePolicyFeature::kClientHintECT)) {
502     AddEctHeader(headers, network_quality_tracker, url);
503   }
504   if (ShouldAddClientHint(
505           web_client_hints, is_main_frame, is_1p_origin, feature_policy,
506           resource_origin, blink::mojom::WebClientHintsType::kLang,
507           blink::mojom::FeaturePolicyFeature::kClientHintLang)) {
508     AddLangHeader(headers, delegate);
509   }
510 
511   base::Optional<blink::UserAgentMetadata> ch_ua_override;
512   bool disable_ua_ch_due_to_custom_ua = false;
513   if (is_ua_override_on) {
514     NavigatorDelegate* nav_delegate =
515         frame_tree_node->navigator()->GetDelegate();
516     ch_ua_override =
517         nav_delegate ? nav_delegate->GetUserAgentOverride().ua_metadata_override
518                      : base::nullopt;
519     // If a custom UA override is set, but no value is provided for UA client
520     // hints, disable them.
521     disable_ua_ch_due_to_custom_ua = !ch_ua_override.has_value();
522   }
523 
524   if (UserAgentClientHintEnabled() && !disable_ua_ch_due_to_custom_ua) {
525     blink::UserAgentMetadata ua = ch_ua_override.has_value()
526                                       ? std::move(ch_ua_override.value())
527                                       : delegate->GetUserAgentMetadata();
528 
529     // The `Sec-CH-UA` client hint is attached to all outgoing requests. The
530     // opt-in controls the header's value, not its presence. This is
531     // (intentionally) different than other client hints.
532     //
533     // https://tools.ietf.org/html/draft-west-ua-client-hints-00#section-2.4
534     //
535     // TODO(morlovich): This should probably be using ShouldAddClientHint,
536     // to check FP?
537     AddUAHeader(headers, blink::mojom::WebClientHintsType::kUA,
538                 AddBrandVersionQuotes(ua.brand, ua.major_version));
539     // The `Sec-CH-UA-Mobile client hint was also deemed "low entropy" and can
540     // safely be sent with every request.
541     AddUAHeader(headers, blink::mojom::WebClientHintsType::kUAMobile,
542                 ua.mobile ? "?1" : "?0");
543 
544     if (ShouldAddClientHint(
545             web_client_hints, is_main_frame, is_1p_origin, feature_policy,
546             resource_origin, blink::mojom::WebClientHintsType::kUAFullVersion,
547             blink::mojom::FeaturePolicyFeature::kClientHintUAFullVersion)) {
548       AddUAHeader(headers, blink::mojom::WebClientHintsType::kUAFullVersion,
549                   AddQuotes(ua.full_version));
550     }
551 
552     if (ShouldAddClientHint(
553             web_client_hints, is_main_frame, is_1p_origin, feature_policy,
554             resource_origin, blink::mojom::WebClientHintsType::kUAArch,
555             blink::mojom::FeaturePolicyFeature::kClientHintUAArch)) {
556       AddUAHeader(headers, blink::mojom::WebClientHintsType::kUAArch,
557                   AddQuotes(ua.architecture));
558     }
559 
560     if (ShouldAddClientHint(
561             web_client_hints, is_main_frame, is_1p_origin, feature_policy,
562             resource_origin, blink::mojom::WebClientHintsType::kUAPlatform,
563             blink::mojom::FeaturePolicyFeature::kClientHintUAPlatform)) {
564       AddUAHeader(headers, blink::mojom::WebClientHintsType::kUAPlatform,
565                   AddBrandVersionQuotes(ua.platform, ua.platform_version));
566     }
567 
568     if (ShouldAddClientHint(
569             web_client_hints, is_main_frame, is_1p_origin, feature_policy,
570             resource_origin, blink::mojom::WebClientHintsType::kUAModel,
571             blink::mojom::FeaturePolicyFeature::kClientHintUAModel)) {
572       AddUAHeader(headers, blink::mojom::WebClientHintsType::kUAModel,
573                   AddQuotes(ua.model));
574     }
575   }
576 
577   // Static assert that triggers if a new client hint header is added. If a
578   // new client hint header is added, the following assertion should be updated.
579   // If possible, logic should be added above so that the request headers for
580   // the newly added client hint can be added to the request.
581   static_assert(
582       blink::mojom::WebClientHintsType::kUAFullVersion ==
583           blink::mojom::WebClientHintsType::kMaxValue,
584       "Consider adding client hint request headers from the browser process");
585 
586   // TODO(crbug.com/735518): If the request is redirected, the client hint
587   // headers stay attached to the redirected request. Consider removing/adding
588   // the client hints headers if the request is redirected with a change in
589   // scheme or a change in the origin.
590 }
591 
592 }  // namespace content
593