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