blob: 8d443bdcc6270290e147420b5becf19abbfad29e [file] [log] [blame]
Avi Drissman4e1b7bc32022-09-15 14:03:501// Copyright 2019 The Chromium Authors
Yoav Weiss603be262019-03-04 13:14:572// 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>
Arthur Sonzognic686e8f2024-01-11 08:36:378#include <optional>
Yoav Weiss603be262019-03-04 13:14:579#include <string>
10
Ari Chivukula3bba0d0e2023-05-12 17:26:3911#include "base/check_is_test.h"
Yoav Weiss603be262019-03-04 13:14:5712#include "base/command_line.h"
Ali Beyad49055ed2021-10-11 15:33:3313#include "base/containers/contains.h"
Ari Chivukula3bba0d0e2023-05-12 17:26:3914#include "base/dcheck_is_on.h"
Yoav Weiss603be262019-03-04 13:14:5715#include "base/feature_list.h"
16#include "base/metrics/field_trial_params.h"
Ali Beyad8765eda2022-01-24 22:32:1017#include "base/metrics/histogram_functions.h"
Peter Kastingf5d6dd92020-08-17 18:53:3018#include "base/numerics/safe_conversions.h"
Yoav Weiss603be262019-03-04 13:14:5719#include "base/rand_util.h"
20#include "base/strings/string_number_conversions.h"
Aaron Tagliaboschi579c632e2022-02-23 17:45:1221#include "base/strings/string_util.h"
Yoav Weiss603be262019-03-04 13:14:5722#include "base/time/time.h"
23#include "build/build_config.h"
Maks Orlovich85a6fd62020-05-05 23:02:1424#include "content/browser/devtools/devtools_instrumentation.h"
Lingqi Chi59b6b42a2022-08-23 02:25:3425#include "content/browser/preloading/prerender/prerender_host.h"
danakjc0270082020-09-15 22:28:0026#include "content/browser/renderer_host/frame_tree.h"
27#include "content/browser/renderer_host/frame_tree_node.h"
28#include "content/browser/renderer_host/navigator.h"
29#include "content/browser/renderer_host/navigator_delegate.h"
Scott Violet3cd8d362020-04-30 22:18:2430#include "content/browser/renderer_host/render_view_host_impl.h"
Ali Beyad49055ed2021-10-11 15:33:3331#include "content/public/browser/browser_context.h"
Yoav Weiss603be262019-03-04 13:14:5732#include "content/public/browser/browser_thread.h"
Ali Beyad49055ed2021-10-11 15:33:3333#include "content/public/browser/client_hints_controller_delegate.h"
Ari Chivukula116f0fc42022-04-13 23:26:1834#include "content/public/browser/content_browser_client.h"
Yoav Weiss603be262019-03-04 13:14:5735#include "content/public/browser/host_zoom_map.h"
Rakina Zata Amni347b70902020-07-22 10:49:0436#include "content/public/browser/web_contents.h"
Clark DuVall04894d7f2020-04-07 23:54:2137#include "content/public/common/content_client.h"
Yoav Weiss603be262019-03-04 13:14:5738#include "content/public/common/content_features.h"
39#include "content/public/common/content_switches.h"
Yoav Weiss603be262019-03-04 13:14:5740#include "net/base/url_util.h"
Ali Beyad7417fc22021-08-06 03:09:5841#include "net/http/http_request_headers.h"
42#include "net/http/http_response_headers.h"
Aaron Tagliaboschi9f01b682020-05-05 21:03:1743#include "net/http/structured_headers.h"
Yoav Weiss603be262019-03-04 13:14:5744#include "net/nqe/effective_connection_type.h"
45#include "net/nqe/network_quality_estimator_params.h"
Maks Orlovichbc5c59b2020-04-24 12:40:5946#include "services/network/public/cpp/client_hints.h"
Aaron Tagliaboschi579c632e2022-02-23 17:45:1247#include "services/network/public/cpp/is_potentially_trustworthy.h"
Yoav Weiss603be262019-03-04 13:14:5748#include "services/network/public/cpp/network_quality_tracker.h"
Sandor Major23d052552025-02-26 20:28:5949#include "services/network/public/cpp/permissions_policy/client_hints_permissions_policy_mapping.h"
Sandor Major07aa8b062025-02-28 17:24:3550#include "services/network/public/cpp/permissions_policy/permissions_policy.h"
Sandor Major878f8352025-02-18 20:16:0251#include "services/network/public/cpp/permissions_policy/permissions_policy_declaration.h"
52#include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom-shared.h"
Ali Beyad77dba952021-08-25 20:52:5853#include "services/network/public/mojom/web_client_hints_types.mojom-shared.h"
Yoav Weiss603be262019-03-04 13:14:5754#include "third_party/blink/public/common/client_hints/client_hints.h"
Ali Beyada6b0fb6f2021-07-28 00:13:2455#include "third_party/blink/public/common/client_hints/enabled_client_hints.h"
Yoav Weiss603be262019-03-04 13:14:5756#include "third_party/blink/public/common/device_memory/approximated_device_memory.h"
Ali Beyada6b0fb6f2021-07-28 00:13:2457#include "third_party/blink/public/common/features.h"
danakj938b37a62019-09-24 18:35:5458#include "third_party/blink/public/common/page/page_zoom.h"
Yoav Weiss603be262019-03-04 13:14:5759#include "third_party/blink/public/common/user_agent/user_agent_metadata.h"
Yoav Weiss603be262019-03-04 13:14:5760#include "ui/display/display.h"
61#include "ui/display/screen.h"
Ali Beyad49055ed2021-10-11 15:33:3362#include "url/origin.h"
Aaron Tagliaboschi579c632e2022-02-23 17:45:1263#include "url/url_constants.h"
Yoav Weiss603be262019-03-04 13:14:5764
Scott Violet3cd8d362020-04-30 22:18:2465namespace content {
66
Yoav Weiss603be262019-03-04 13:14:5767namespace {
Ali Beyad7417fc22021-08-06 03:09:5868using ::network::mojom::WebClientHintsType;
69
Yoav Weiss603be262019-03-04 13:14:5770uint8_t randomization_salt = 0;
71
72constexpr size_t kMaxRandomNumbers = 21;
73
74// Returns the randomization salt (weak and insecure) that should be used when
75// adding noise to the network quality metrics. This is known only to the
76// device, and is generated only once. This makes it possible to add the same
77// amount of noise for a given origin.
78uint8_t RandomizationSalt() {
79 if (randomization_salt == 0)
80 randomization_salt = base::RandInt(1, kMaxRandomNumbers);
81 DCHECK_LE(1, randomization_salt);
82 DCHECK_GE(kMaxRandomNumbers, randomization_salt);
83 return randomization_salt;
84}
85
86double GetRandomMultiplier(const std::string& host) {
87 // The random number should be a function of the hostname to reduce
88 // cross-origin fingerprinting. The random number should also be a function
89 // of randomized salt which is known only to the device. This prevents
90 // origin from removing noise from the estimates.
91 unsigned hash = std::hash<std::string>{}(host) + RandomizationSalt();
92 double random_multiplier =
93 0.9 + static_cast<double>((hash % kMaxRandomNumbers)) * 0.01;
94 DCHECK_LE(0.90, random_multiplier);
95 DCHECK_GE(1.10, random_multiplier);
96 return random_multiplier;
97}
98
99unsigned long RoundRtt(const std::string& host,
Arthur Sonzognic686e8f2024-01-11 08:36:37100 const std::optional<base::TimeDelta>& rtt) {
Yoav Weiss603be262019-03-04 13:14:57101 if (!rtt.has_value()) {
102 // RTT is unavailable. So, return the fastest value.
103 return 0;
104 }
105
Peter Kastingf5d6dd92020-08-17 18:53:30106 // Limit the maximum reported value and the granularity to reduce
107 // fingerprinting.
Peter Kastinge5a38ed2021-10-02 03:06:35108 constexpr base::TimeDelta kMaxRtt = base::Seconds(3);
109 constexpr base::TimeDelta kGranularity = base::Milliseconds(50);
Yoav Weiss603be262019-03-04 13:14:57110
Peter Kastingf5d6dd92020-08-17 18:53:30111 const base::TimeDelta modified_rtt =
112 std::min(rtt.value() * GetRandomMultiplier(host), kMaxRtt);
113 DCHECK_GE(modified_rtt, base::TimeDelta());
Peter Kastingfbdf4952020-08-14 03:07:06114 return modified_rtt.RoundToMultiple(kGranularity).InMilliseconds();
Yoav Weiss603be262019-03-04 13:14:57115}
116
117double RoundKbpsToMbps(const std::string& host,
Arthur Sonzognic686e8f2024-01-11 08:36:37118 const std::optional<int32_t>& downlink_kbps) {
Yoav Weiss603be262019-03-04 13:14:57119 // Limit the size of the buckets and the maximum reported value to reduce
120 // fingerprinting.
121 static const size_t kGranularityKbps = 50;
122 static const double kMaxDownlinkKbps = 10.0 * 1000;
123
124 // If downlink is unavailable, return the fastest value.
125 double randomized_downlink_kbps = downlink_kbps.value_or(kMaxDownlinkKbps);
126 randomized_downlink_kbps *= GetRandomMultiplier(host);
127
128 randomized_downlink_kbps =
129 std::min(randomized_downlink_kbps, kMaxDownlinkKbps);
130
131 DCHECK_LE(0, randomized_downlink_kbps);
132 DCHECK_GE(kMaxDownlinkKbps, randomized_downlink_kbps);
133 // Round down to the nearest kGranularityKbps kbps value.
134 double downlink_kbps_rounded =
135 std::round(randomized_downlink_kbps / kGranularityKbps) *
136 kGranularityKbps;
137
138 // Convert from Kbps to Mbps.
139 return downlink_kbps_rounded / 1000;
140}
141
142double GetDeviceScaleFactor() {
143 double device_scale_factor = 1.0;
144 if (display::Screen::GetScreen()) {
145 device_scale_factor =
146 display::Screen::GetScreen()->GetPrimaryDisplay().device_scale_factor();
147 }
148 DCHECK_LT(0.0, device_scale_factor);
149 return device_scale_factor;
150}
151
152// Returns the zoom factor for a given |url|.
Scott Violet3cd8d362020-04-30 22:18:24153double GetZoomFactor(BrowserContext* context, const GURL& url) {
Scott Violet3cd8d362020-04-30 22:18:24154 double zoom_level = HostZoomMap::GetDefaultForBrowserContext(context)
Yoav Weiss603be262019-03-04 13:14:57155 ->GetZoomLevelForHostAndScheme(
156 url.scheme(), net::GetHostOrSpecFromURL(url));
157
158 if (zoom_level == 0.0) {
159 // Get default zoom level.
Scott Violet3cd8d362020-04-30 22:18:24160 zoom_level = HostZoomMap::GetDefaultForBrowserContext(context)
Yoav Weiss603be262019-03-04 13:14:57161 ->GetDefaultZoomLevel();
162 }
163
Stefan Zager036a35c2024-06-13 20:53:34164 return blink::ZoomLevelToZoomFactor(zoom_level);
Yoav Weiss603be262019-03-04 13:14:57165}
166
167// Returns a string corresponding to |value|. The returned string satisfies
168// ABNF: 1*DIGIT [ "." 1*DIGIT ]
169std::string DoubleToSpecCompliantString(double value) {
170 DCHECK_LE(0.0, value);
171 std::string result = base::NumberToString(value);
172 DCHECK(!result.empty());
173 if (value >= 1.0)
174 return result;
175
176 DCHECK_LE(0.0, value);
177 DCHECK_GT(1.0, value);
178
179 // Check if there is at least one character before period.
180 if (result.at(0) != '.')
181 return result;
182
183 // '.' is the first character in |result|. Prefix one digit before the
184 // period to make it spec compliant.
185 return "0" + result;
186}
187
188// Return the effective connection type value overridden for web APIs.
189// If no override value has been set, a null value is returned.
Arthur Sonzognic686e8f2024-01-11 08:36:37190std::optional<net::EffectiveConnectionType>
Yoav Weiss603be262019-03-04 13:14:57191GetWebHoldbackEffectiveConnectionType() {
192 if (!base::FeatureList::IsEnabled(
193 features::kNetworkQualityEstimatorWebHoldback)) {
Arthur Sonzognic686e8f2024-01-11 08:36:37194 return std::nullopt;
Yoav Weiss603be262019-03-04 13:14:57195 }
196 std::string effective_connection_type_param =
197 base::GetFieldTrialParamValueByFeature(
198 features::kNetworkQualityEstimatorWebHoldback,
199 "web_effective_connection_type_override");
200
Arthur Sonzognic686e8f2024-01-11 08:36:37201 std::optional<net::EffectiveConnectionType> effective_connection_type =
Yoav Weiss603be262019-03-04 13:14:57202 net::GetEffectiveConnectionTypeForName(effective_connection_type_param);
203 DCHECK(effective_connection_type_param.empty() || effective_connection_type);
204
205 if (!effective_connection_type)
Arthur Sonzognic686e8f2024-01-11 08:36:37206 return std::nullopt;
Yoav Weiss603be262019-03-04 13:14:57207 DCHECK_NE(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN,
208 effective_connection_type.value());
209 return effective_connection_type;
210}
211
212void SetHeaderToDouble(net::HttpRequestHeaders* headers,
Ali Beyad7417fc22021-08-06 03:09:58213 WebClientHintsType client_hint_type,
Yoav Weiss603be262019-03-04 13:14:57214 double value) {
Ari Chivukula51baf9b2021-09-13 20:01:49215 headers->SetHeader(network::GetClientHintToNameMap().at(client_hint_type),
216 DoubleToSpecCompliantString(value));
Yoav Weiss603be262019-03-04 13:14:57217}
218
219void SetHeaderToInt(net::HttpRequestHeaders* headers,
Ali Beyad7417fc22021-08-06 03:09:58220 WebClientHintsType client_hint_type,
Yoav Weiss603be262019-03-04 13:14:57221 double value) {
Ari Chivukula51baf9b2021-09-13 20:01:49222 headers->SetHeader(network::GetClientHintToNameMap().at(client_hint_type),
223 base::NumberToString(std::round(value)));
Yoav Weiss603be262019-03-04 13:14:57224}
225
226void SetHeaderToString(net::HttpRequestHeaders* headers,
Ali Beyad7417fc22021-08-06 03:09:58227 WebClientHintsType client_hint_type,
Scott Violet3cd8d362020-04-30 22:18:24228 const std::string& value) {
Ari Chivukula51baf9b2021-09-13 20:01:49229 headers->SetHeader(network::GetClientHintToNameMap().at(client_hint_type),
230 value);
Yoav Weiss603be262019-03-04 13:14:57231}
232
Ali Beyad7417fc22021-08-06 03:09:58233void RemoveClientHintHeader(WebClientHintsType client_hint_type,
Scott Violet3cd8d362020-04-30 22:18:24234 net::HttpRequestHeaders* headers) {
Ari Chivukula51baf9b2021-09-13 20:01:49235 headers->RemoveHeader(network::GetClientHintToNameMap().at(client_hint_type));
Scott Violet3cd8d362020-04-30 22:18:24236}
237
Ari Chivukula64248112021-10-07 06:00:56238void AddDeviceMemoryHeader(net::HttpRequestHeaders* headers,
239 bool use_deprecated_version = false) {
Yoav Weiss603be262019-03-04 13:14:57240 DCHECK(headers);
241 blink::ApproximatedDeviceMemory::Initialize();
242 const float device_memory =
243 blink::ApproximatedDeviceMemory::GetApproximatedDeviceMemory();
244 DCHECK_LT(0.0, device_memory);
Ari Chivukula64248112021-10-07 06:00:56245 SetHeaderToDouble(headers,
246 use_deprecated_version
247 ? WebClientHintsType::kDeviceMemory_DEPRECATED
248 : WebClientHintsType::kDeviceMemory,
Ari Chivukula6692cc52021-09-09 09:30:11249 device_memory);
Yoav Weiss603be262019-03-04 13:14:57250}
251
252void AddDPRHeader(net::HttpRequestHeaders* headers,
Scott Violet3cd8d362020-04-30 22:18:24253 BrowserContext* context,
Ari Chivukula64248112021-10-07 06:00:56254 const GURL& url,
255 bool use_deprecated_version = false) {
Yoav Weiss603be262019-03-04 13:14:57256 DCHECK(headers);
257 DCHECK(context);
258 double device_scale_factor = GetDeviceScaleFactor();
259 double zoom_factor = GetZoomFactor(context, url);
Ari Chivukula64248112021-10-07 06:00:56260 SetHeaderToDouble(headers,
261 use_deprecated_version ? WebClientHintsType::kDpr_DEPRECATED
262 : WebClientHintsType::kDpr,
Yoav Weiss603be262019-03-04 13:14:57263 device_scale_factor * zoom_factor);
264}
265
Ari Chivukula116f0fc42022-04-13 23:26:18266void AddSaveDataHeader(net::HttpRequestHeaders* headers,
267 BrowserContext* context) {
268 DCHECK(headers);
269 DCHECK(context);
270 // Unlike other client hints, this one is only sent when it has a value.
271 if (GetContentClient()->browser()->IsDataSaverEnabled(context))
272 SetHeaderToString(headers, WebClientHintsType::kSaveData, "on");
273}
274
Max Curran2baf1f22022-08-26 22:01:07275RenderWidgetHostView* GetRenderWidgetHostViewFromFrameTreeNode(
276 FrameTreeNode* frame_tree_node) {
277 if (!frame_tree_node || !frame_tree_node->current_frame_host())
278 return nullptr;
279
Max Curran0d344cfe2023-05-25 18:31:18280 return frame_tree_node->current_frame_host()->GetMainFrame()->GetView();
Max Curran2baf1f22022-08-26 22:01:07281}
282
283gfx::Size GetViewportSize(FrameTreeNode* frame_tree_node,
284 ClientHintsControllerDelegate* delegate) {
Ari Chivukula3bba0d0e2023-05-12 17:26:39285#if DCHECK_IS_ON()
286 // In some tests we need to force an empty viewport size.
287 if (delegate->ShouldForceEmptyViewportSize()) {
288 CHECK_IS_TEST();
289 return gfx::Size();
290 }
291#endif
292
Max Curran2baf1f22022-08-26 22:01:07293 // If possible, return the current viewport size.
294 RenderWidgetHostView* view =
295 GetRenderWidgetHostViewFromFrameTreeNode(frame_tree_node);
296 if (view) {
297 return view->GetVisibleViewportSize();
298 }
299
300 // Otherwise, use the cached viewport size if it is valid (both dimensions are
301 // greater than zero).
302 gfx::Size cached_viewport_size =
303 delegate->GetMostRecentMainFrameViewportSize();
304 if (cached_viewport_size.width() > 0 && cached_viewport_size.height() > 0) {
305 return cached_viewport_size;
306 }
307
Ari Chivukula3bba0d0e2023-05-12 17:26:39308 // We used to return the display size here as a last resort if above methods
309 // didn't work, but this was so inaccurate as to be useless. Short of trying
310 // to build a more extensive caching method or restructuring the calculation
311 // path to make the estimated size available here, we simply return 0.
312 // Further context can be found in crbug.com/1430903.
Max Curranf445edad2022-11-29 23:39:43313 // viewport sizes already.
Ari Chivukula3bba0d0e2023-05-12 17:26:39314 return gfx::Size();
Max Curran2baf1f22022-08-26 22:01:07315}
316
317gfx::Size GetScaledViewportSize(BrowserContext* context,
318 const GURL& url,
319 FrameTreeNode* frame_tree_node,
320 ClientHintsControllerDelegate* delegate) {
321 gfx::Size viewport_size = GetViewportSize(frame_tree_node, delegate);
Finditc5fc7bc62020-11-12 20:48:33322
Xiaohan Wang1ecfd002022-01-19 22:33:10323#if BUILDFLAG(IS_ANDROID)
Max Curran2baf1f22022-08-26 22:01:07324 // On Android, the viewport is scaled so the width is 980. See
325 // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/css/viewportAndroid.css.
Alison Gale81f4f2c72024-04-22 19:33:31326 // TODO(crbug.com/40196453): Improve the usefulness of the viewport client
327 // hints for navigation requests.
Max Curran2baf1f22022-08-26 22:01:07328 if (viewport_size.width() > 0) {
329 viewport_size =
330 ScaleToRoundedSize(viewport_size, 980.0 / viewport_size.width());
331 }
Mark Schillaci1363e4a2021-10-04 19:10:09332#endif
333
Max Curranf445edad2022-11-29 23:39:43334 double zoom_factor = GetZoomFactor(context, url);
335 if (zoom_factor > 0) {
336 viewport_size = ScaleToRoundedSize(viewport_size, 1.0 / zoom_factor);
Max Curran2baf1f22022-08-26 22:01:07337 }
338 return viewport_size;
339}
340
341void AddViewportWidthHeader(net::HttpRequestHeaders* headers,
342 BrowserContext* context,
343 const GURL& url,
344 FrameTreeNode* frame_tree_node,
345 ClientHintsControllerDelegate* delegate,
346 bool use_deprecated_version = false) {
347 DCHECK(headers);
348 DCHECK(context);
349
350 gfx::Size viewport_size =
351 GetScaledViewportSize(context, url, frame_tree_node, delegate);
352
Ari Chivukula3bba0d0e2023-05-12 17:26:39353 // The width cannot be less than 0, but if it is zero that means we could not
354 // determine the width and should omit the header.
355 DCHECK_LE(0, viewport_size.width());
Max Curran2baf1f22022-08-26 22:01:07356 if (viewport_size.width() > 0) {
Ari Chivukula64248112021-10-07 06:00:56357 SetHeaderToInt(headers,
358 use_deprecated_version
359 ? WebClientHintsType::kViewportWidth_DEPRECATED
360 : WebClientHintsType::kViewportWidth,
Max Curran2baf1f22022-08-26 22:01:07361 viewport_size.width());
Avi Drissmanb5b854a82021-09-08 15:44:20362 }
Yoav Weiss603be262019-03-04 13:14:57363}
364
Max Curran99cbec1492021-08-16 19:54:26365void AddViewportHeightHeader(net::HttpRequestHeaders* headers,
366 BrowserContext* context,
Max Curran2baf1f22022-08-26 22:01:07367 const GURL& url,
368 FrameTreeNode* frame_tree_node,
369 ClientHintsControllerDelegate* delegate) {
Max Curran99cbec1492021-08-16 19:54:26370 DCHECK(headers);
371 DCHECK(context);
372
Max Curran2baf1f22022-08-26 22:01:07373 gfx::Size viewport_size =
374 GetScaledViewportSize(context, url, frame_tree_node, delegate);
Max Curran99cbec1492021-08-16 19:54:26375
Ari Chivukula3bba0d0e2023-05-12 17:26:39376 // The height cannot be less than 0, but if it is zero that means we could not
377 // determine the height and should omit the header.
378 DCHECK_LE(0, viewport_size.height());
379 if (viewport_size.height() > 0) {
380 SetHeaderToInt(headers, network::mojom::WebClientHintsType::kViewportHeight,
381 viewport_size.height());
382 }
Max Curran99cbec1492021-08-16 19:54:26383}
384
Yoav Weiss603be262019-03-04 13:14:57385void AddRttHeader(net::HttpRequestHeaders* headers,
386 network::NetworkQualityTracker* network_quality_tracker,
387 const GURL& url) {
388 DCHECK(headers);
389
Arthur Sonzognic686e8f2024-01-11 08:36:37390 std::optional<net::EffectiveConnectionType> web_holdback_ect =
Yoav Weiss603be262019-03-04 13:14:57391 GetWebHoldbackEffectiveConnectionType();
392
393 base::TimeDelta http_rtt;
394 if (web_holdback_ect.has_value()) {
395 http_rtt = net::NetworkQualityEstimatorParams::GetDefaultTypicalHttpRtt(
396 web_holdback_ect.value());
397 } else if (network_quality_tracker) {
398 http_rtt = network_quality_tracker->GetHttpRTT();
399 } else {
400 http_rtt = net::NetworkQualityEstimatorParams::GetDefaultTypicalHttpRtt(
401 net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN);
402 }
Ari Chivukula6692cc52021-09-09 09:30:11403 SetHeaderToInt(headers, WebClientHintsType::kRtt_DEPRECATED,
Yoav Weiss603be262019-03-04 13:14:57404 RoundRtt(url.host(), http_rtt));
405}
406
407void AddDownlinkHeader(net::HttpRequestHeaders* headers,
408 network::NetworkQualityTracker* network_quality_tracker,
409 const GURL& url) {
410 DCHECK(headers);
Arthur Sonzognic686e8f2024-01-11 08:36:37411 std::optional<net::EffectiveConnectionType> web_holdback_ect =
Yoav Weiss603be262019-03-04 13:14:57412 GetWebHoldbackEffectiveConnectionType();
413
414 int32_t downlink_throughput_kbps;
415
416 if (web_holdback_ect.has_value()) {
417 downlink_throughput_kbps =
418 net::NetworkQualityEstimatorParams::GetDefaultTypicalDownlinkKbps(
419 web_holdback_ect.value());
420 } else if (network_quality_tracker) {
421 downlink_throughput_kbps =
422 network_quality_tracker->GetDownstreamThroughputKbps();
423 } else {
424 downlink_throughput_kbps =
425 net::NetworkQualityEstimatorParams::GetDefaultTypicalDownlinkKbps(
426 net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN);
427 }
428
Ari Chivukula6692cc52021-09-09 09:30:11429 SetHeaderToDouble(headers, WebClientHintsType::kDownlink_DEPRECATED,
Yoav Weiss603be262019-03-04 13:14:57430 RoundKbpsToMbps(url.host(), downlink_throughput_kbps));
431}
432
433void AddEctHeader(net::HttpRequestHeaders* headers,
434 network::NetworkQualityTracker* network_quality_tracker,
435 const GURL& url) {
436 DCHECK(headers);
Yoav Weiss603be262019-03-04 13:14:57437
Arthur Sonzognic686e8f2024-01-11 08:36:37438 std::optional<net::EffectiveConnectionType> web_holdback_ect =
Yoav Weiss603be262019-03-04 13:14:57439 GetWebHoldbackEffectiveConnectionType();
440
441 int effective_connection_type;
442 if (web_holdback_ect.has_value()) {
443 effective_connection_type = web_holdback_ect.value();
444 } else if (network_quality_tracker) {
445 effective_connection_type =
446 static_cast<int>(network_quality_tracker->GetEffectiveConnectionType());
447 } else {
448 effective_connection_type =
449 static_cast<int>(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN);
450 }
451
452 SetHeaderToString(
Ari Chivukula6692cc52021-09-09 09:30:11453 headers, WebClientHintsType::kEct_DEPRECATED,
Ari Chivukula086d17262022-10-20 18:24:15454 network::kWebEffectiveConnectionTypeMapping[effective_connection_type]);
Yoav Weiss603be262019-03-04 13:14:57455}
456
François Beaufort1b51d062021-05-18 11:11:23457void AddPrefersColorSchemeHeader(net::HttpRequestHeaders* headers,
458 FrameTreeNode* frame_tree_node) {
François Beaufort34e299e2021-05-26 05:54:33459 if (!frame_tree_node)
460 return;
François Beaufort1b51d062021-05-18 11:11:23461 blink::mojom::PreferredColorScheme preferred_color_scheme =
François Beauforta957a4e2022-08-09 09:14:31462 WebContents::FromRenderFrameHost(frame_tree_node->current_frame_host())
463 ->GetOrCreateWebPreferences()
464 .preferred_color_scheme;
François Beaufort1b51d062021-05-18 11:11:23465 bool is_dark_mode =
466 preferred_color_scheme == blink::mojom::PreferredColorScheme::kDark;
Ali Beyad7417fc22021-08-06 03:09:58467 SetHeaderToString(headers, WebClientHintsType::kPrefersColorScheme,
François Beaufort378fffd2022-09-28 16:48:19468 is_dark_mode ? network::kPrefersColorSchemeDark
469 : network::kPrefersColorSchemeLight);
470}
471
472void AddPrefersReducedMotionHeader(net::HttpRequestHeaders* headers,
473 FrameTreeNode* frame_tree_node) {
474 if (!frame_tree_node)
475 return;
476 bool prefers_reduced_motion =
477 WebContents::FromRenderFrameHost(frame_tree_node->current_frame_host())
478 ->GetOrCreateWebPreferences()
479 .prefers_reduced_motion;
480 SetHeaderToString(headers, WebClientHintsType::kPrefersReducedMotion,
481 prefers_reduced_motion
482 ? network::kPrefersReducedMotionReduce
483 : network::kPrefersReducedMotionNoPreference);
François Beaufort1b51d062021-05-18 11:11:23484}
485
Luke Warlow86339292023-08-26 16:55:25486void AddPrefersReducedTransparencyHeader(net::HttpRequestHeaders* headers,
487 FrameTreeNode* frame_tree_node) {
488 if (!frame_tree_node) {
489 return;
490 }
491 bool prefers_reduced_transparency =
492 WebContents::FromRenderFrameHost(frame_tree_node->current_frame_host())
493 ->GetOrCreateWebPreferences()
494 .prefers_reduced_transparency;
495 SetHeaderToString(headers, WebClientHintsType::kPrefersReducedTransparency,
496 prefers_reduced_transparency
497 ? network::kPrefersReducedTransparencyReduce
498 : network::kPrefersReducedTransparencyNoPreference);
499}
500
Aaron Tagliaboschi579c632e2022-02-23 17:45:12501bool IsValidURLForClientHints(const url::Origin& origin) {
502 return network::IsOriginPotentiallyTrustworthy(origin);
Yoav Weiss603be262019-03-04 13:14:57503}
504
Yoav Weiss603be262019-03-04 13:14:57505void AddUAHeader(net::HttpRequestHeaders* headers,
Ali Beyad7417fc22021-08-06 03:09:58506 WebClientHintsType type,
Scott Violet3cd8d362020-04-30 22:18:24507 const std::string& value) {
Yoav Weiss603be262019-03-04 13:14:57508 SetHeaderToString(headers, type, value);
509}
510
Ali Beyad7417fc22021-08-06 03:09:58511// Creates a serialized string header value out of the input type, using
512// structured headers as described in
513// https://www.rfc-editor.org/rfc/rfc8941.html.
514template <typename T>
515const std::string SerializeHeaderString(const T& value) {
Aaron Tagliaboschi9f01b682020-05-05 21:03:17516 return net::structured_headers::SerializeItem(
Ali Beyad7417fc22021-08-06 03:09:58517 net::structured_headers::Item(value))
Aaron Tagliaboschi9f01b682020-05-05 21:03:17518 .value_or(std::string());
Yoav Weiss1aeefa82020-03-06 12:02:17519}
520
Scott Violet3cd8d362020-04-30 22:18:24521// Captures the state used in applying client hints.
522struct ClientHintsExtendedData {
Aaron Tagliaboschid0cda1e2022-03-09 19:30:37523 ClientHintsExtendedData(const url::Origin& origin,
Scott Violet3cd8d362020-04-30 22:18:24524 FrameTreeNode* frame_tree_node,
Aaron Tagliaboschid0cda1e2022-03-09 19:30:37525 ClientHintsControllerDelegate* delegate,
Arthur Sonzognic686e8f2024-01-11 08:36:37526 const std::optional<GURL>& maybe_request_url)
Aaron Tagliaboschi579c632e2022-02-23 17:45:12527 : resource_origin(origin) {
Ian Vollick25a9d032022-04-12 23:20:17528 // If the current frame is the outermost main frame, the URL wasn't
529 // committed yet, so in order to get the main frame URL, we should use the
530 // provided URL instead. Otherwise, the current frame is a subframe and the
531 // outermost main frame URL was committed, so we can safely get it from it.
532 // Similarly, an in-navigation outermost main frame doesn't yet have a
533 // permissions policy.
534 is_outermost_main_frame =
535 !frame_tree_node || frame_tree_node->IsOutermostMainFrame();
536 if (is_outermost_main_frame) {
Liam Bradycfb44102023-04-25 08:15:43537 main_frame_origin = resource_origin;
Ian Vollick25a9d032022-04-12 23:20:17538 } else if (frame_tree_node->IsInFencedFrameTree()) {
Alison Gale81f4f2c72024-04-22 19:33:31539 // TODO(crbug.com/40263100) Add WPT tests and specify the behavior
Adithya Srinivasan39c81912024-07-11 20:44:21540 // of client hints delegation for subframes inside FencedFrames.
Xiaochen Zhou86f2e712023-09-13 19:55:04541 // Test cases should cover this 3 layers nested frames case, from top to
542 // bottom:
543 // 1. Fenced frame.
544 // 2. Urn iframe.
545 // 3. Iframe.
546 // `GetFencedFrameProperties()` called from the iframe returns the
547 // fenced frame properties from the urn iframe because it does a bottom
548 // up traversal.
549 // See crbug.com/1470634.
Arthur Sonzognic686e8f2024-01-11 08:36:37550 const std::optional<FencedFrameProperties>& fenced_frame_properties =
Liam Brady95430592023-05-03 18:28:34551 frame_tree_node->GetFencedFrameProperties();
Sandor «Alex» Majore9545a72025-01-31 20:40:46552 base::span<const network::mojom::PermissionsPolicyFeature> permissions;
Liam Brady95430592023-05-03 18:28:34553 if (fenced_frame_properties) {
Garrett Tanzer0698070f2023-12-12 19:48:20554 permissions = fenced_frame_properties->effective_enabled_permissions();
Liam Brady95430592023-05-03 18:28:34555 }
Sandor Major07aa8b062025-02-28 17:24:35556 permissions_policy =
557 network::PermissionsPolicy::CreateFixedForFencedFrame(
558 resource_origin, /*header_policy=*/{}, permissions);
Scott Violet3cd8d362020-04-30 22:18:24559 } else {
Liam Bradycfb44102023-04-25 08:15:43560 RenderFrameHostImpl* main_frame =
561 frame_tree_node->frame_tree().GetMainFrame();
562 main_frame_origin = main_frame->GetLastCommittedOrigin();
Sandor Major07aa8b062025-02-28 17:24:35563 permissions_policy = network::PermissionsPolicy::CopyStateFrom(
Sandor Majord1bbf7752025-01-09 22:49:30564 main_frame->GetPermissionsPolicy());
Scott Violet3cd8d362020-04-30 22:18:24565 }
566
Ali Beyad8765eda2022-01-24 22:32:10567 const base::TimeTicks start_time = base::TimeTicks::Now();
Liam Bradycfb44102023-04-25 08:15:43568 delegate->GetAllowedClientHintsFromSource(main_frame_origin, &hints);
Victor Tanfcce6fa52023-01-19 23:16:05569 const base::TimeTicks pref_read_time = base::TimeTicks::Now();
Ali Beyad49055ed2021-10-11 15:33:33570
Lingqi Chi59b6b42a2022-08-23 02:25:34571 // If this is a prerender tree, also capture prerender local setting. The
572 // setting was given by navigation requests on the prerendering page, and
573 // has not been used as a global setting.
Hiroki Nakagawae2e4558d2023-06-07 07:54:48574 if (frame_tree_node) {
575 if (auto* host = PrerenderHost::GetFromFrameTreeNodeIfPrerendering(
576 *frame_tree_node)) {
Liam Bradycfb44102023-04-25 08:15:43577 host->GetAllowedClientHintsOnPage(main_frame_origin, &hints);
Hiroki Nakagawae2e4558d2023-06-07 07:54:48578 }
Lingqi Chi59b6b42a2022-08-23 02:25:34579 }
Ali Beyad8765eda2022-01-24 22:32:10580
581 // Record the time spent getting the client hints.
Victor Tanfcce6fa52023-01-19 23:16:05582 const base::TimeTicks end_time = base::TimeTicks::Now();
583 base::UmaHistogramMicrosecondsTimes("ClientHints.FetchLatency_PrefRead",
584 pref_read_time - start_time);
585 base::UmaHistogramMicrosecondsTimes(
Victor Tanb37a4c82023-06-08 16:18:28586 "ClientHints.FetchLatency_PrerenderHost", end_time - pref_read_time);
Victor Tanfcce6fa52023-01-19 23:16:05587 base::UmaHistogramMicrosecondsTimes("ClientHints.FetchLatency_Total",
588 end_time - start_time);
Scott Violet3cd8d362020-04-30 22:18:24589 }
590
Ali Beyada6b0fb6f2021-07-28 00:13:24591 blink::EnabledClientHints hints;
Scott Violet3cd8d362020-04-30 22:18:24592 url::Origin resource_origin;
Ian Vollick25a9d032022-04-12 23:20:17593 bool is_outermost_main_frame = false;
Liam Bradycfb44102023-04-25 08:15:43594 url::Origin main_frame_origin;
Sandor Major07aa8b062025-02-28 17:24:35595 std::unique_ptr<network::PermissionsPolicy> permissions_policy;
Scott Violet3cd8d362020-04-30 22:18:24596};
597
Ali Beyadf3128152022-01-21 20:36:36598bool IsClientHintEnabled(const ClientHintsExtendedData& data,
599 WebClientHintsType type) {
Victor Tanb37a4c82023-06-08 16:18:28600 return blink::IsClientHintSentByDefault(type) || data.hints.IsEnabled(type);
Ali Beyadf3128152022-01-21 20:36:36601}
602
Aaron Tagliaboschi41bed88e2020-10-20 16:29:42603bool IsClientHintAllowed(const ClientHintsExtendedData& data,
Ali Beyad7417fc22021-08-06 03:09:58604 WebClientHintsType type) {
Ian Vollick25a9d032022-04-12 23:20:17605 if (data.is_outermost_main_frame) {
Liam Bradyf5193992023-04-14 14:49:53606 return true;
Ali Beyadf3128152022-01-21 20:36:36607 }
Victor Tanb37a4c82023-06-08 16:18:28608 return (data.permissions_policy->IsFeatureEnabledForOrigin(
Sandor Major23d052552025-02-26 20:28:59609 network::GetClientHintToPolicyFeatureMap().at(type),
610 data.resource_origin));
Scott Violet3cd8d362020-04-30 22:18:24611}
612
Aaron Tagliaboschi41bed88e2020-10-20 16:29:42613bool ShouldAddClientHint(const ClientHintsExtendedData& data,
Ali Beyad7417fc22021-08-06 03:09:58614 WebClientHintsType type) {
Ali Beyadf3128152022-01-21 20:36:36615 return IsClientHintEnabled(data, type) && IsClientHintAllowed(data, type);
Aaron Tagliaboschi41bed88e2020-10-20 16:29:42616}
617
Scott Violet3cd8d362020-04-30 22:18:24618bool IsJavascriptEnabled(FrameTreeNode* frame_tree_node) {
Rakina Zata Amni347b70902020-07-22 10:49:04619 return WebContents::FromRenderFrameHost(frame_tree_node->current_frame_host())
620 ->GetOrCreateWebPreferences()
621 .javascript_enabled;
Scott Violet3cd8d362020-04-30 22:18:24622}
623
Ari Chivukula6a8b32f2021-12-16 18:32:57624// This modifies `data.permissions_policy` to reflect any changes to client hint
625// permissions which may have occurred via the named accept-ch meta tag.
626// The permissions policy the browser side has for the frame was set in stone
627// before HTML parsing began, so any updates must be sent via
628// `container_policy`.
Alison Gale53c77f62024-04-22 15:16:27629// TODO(crbug.com/40208054): Replace w/ generic HTML policy modification.
Ari Chivukula6a8b32f2021-12-16 18:32:57630void UpdateIFramePermissionsPolicyWithDelegationSupportForClientHints(
631 ClientHintsExtendedData& data,
Sandor Major878f8352025-02-18 20:16:02632 const network::ParsedPermissionsPolicy& container_policy) {
Ari Chivukula4ab383b2024-01-16 15:43:27633 if (container_policy.empty()) {
Ari Chivukula6a8b32f2021-12-16 18:32:57634 return;
635 }
636
637 // For client hints specifically, we need to allow the container policy
638 // to overwrite the parent policy so that permissions policies set in HTML
639 // via an accept-ch meta tag can be respected.
Sandor Major878f8352025-02-18 20:16:02640 network::ParsedPermissionsPolicy client_hints_container_policy;
Ari Chivukula6a8b32f2021-12-16 18:32:57641 for (const auto& container_policy_item : container_policy) {
Sandor Major23d052552025-02-26 20:28:59642 const auto& it = network::GetPolicyFeatureToClientHintMap().find(
Ari Chivukula6a8b32f2021-12-16 18:32:57643 container_policy_item.feature);
Sandor Major23d052552025-02-26 20:28:59644 if (it != network::GetPolicyFeatureToClientHintMap().end()) {
Ari Chivukula6a8b32f2021-12-16 18:32:57645 client_hints_container_policy.push_back(container_policy_item);
646
647 // We need to ensure `blink::EnabledClientHints` is updated where the
648 // main frame now has permission for the given client hints.
Ari Chivukulaaa0c11962022-09-28 19:19:47649 for (const auto& origin_with_possible_wildcards :
650 container_policy_item.allowed_origins) {
651 if (origin_with_possible_wildcards.DoesMatchOrigin(
Liam Bradycfb44102023-04-25 08:15:43652 data.main_frame_origin)) {
Ari Chivukulaaa0c11962022-09-28 19:19:47653 for (const auto& hint : it->second) {
654 data.hints.SetIsEnabled(hint, /*should_send*/ true);
655 }
656 break;
Ari Chivukula6a8b32f2021-12-16 18:32:57657 }
658 }
659 }
660 }
Fergal Dalya58cd7872024-05-08 00:23:31661 data.permissions_policy =
662 data.permissions_policy->WithClientHints(client_hints_container_policy);
Ari Chivukula6a8b32f2021-12-16 18:32:57663}
664
Scott Violet3cd8d362020-04-30 22:18:24665// Captures when UpdateNavigationRequestClientUaHeadersImpl() is being called.
666enum class ClientUaHeaderCallType {
667 // The call is happening during creation of the NavigationRequest.
668 kDuringCreation,
669
670 // The call is happening after creation of the NavigationRequest.
671 kAfterCreated,
672};
673
674// Implementation of UpdateNavigationRequestClientUaHeaders().
675void UpdateNavigationRequestClientUaHeadersImpl(
Scott Violet3cd8d362020-04-30 22:18:24676 ClientHintsControllerDelegate* delegate,
677 bool override_ua,
678 FrameTreeNode* frame_tree_node,
679 ClientUaHeaderCallType call_type,
Ari Chivukula6a8b32f2021-12-16 18:32:57680 net::HttpRequestHeaders* headers,
Sandor Major878f8352025-02-18 20:16:02681 const network::ParsedPermissionsPolicy& container_policy,
Arthur Sonzognic686e8f2024-01-11 08:36:37682 const std::optional<GURL>& request_url,
Patrick Nolandfec8dba2022-12-20 20:25:47683 const ClientHintsExtendedData& data) {
Arthur Sonzognic686e8f2024-01-11 08:36:37684 std::optional<blink::UserAgentMetadata> ua_metadata;
Scott Violet3cd8d362020-04-30 22:18:24685 bool disable_due_to_custom_ua = false;
686 if (override_ua) {
687 NavigatorDelegate* nav_delegate =
Ryan Sturm320fda4d2021-01-05 19:13:30688 frame_tree_node ? frame_tree_node->navigator().GetDelegate() : nullptr;
Scott Violet3cd8d362020-04-30 22:18:24689 ua_metadata =
Dave Tapuska94a6978a2024-11-22 14:37:29690 nav_delegate
691 ? nav_delegate->GetUserAgentOverride(frame_tree_node->frame_tree())
692 .ua_metadata_override
693 : std::nullopt;
Scott Violet3cd8d362020-04-30 22:18:24694 // If a custom UA override is set, but no value is provided for UA client
695 // hints, disable them.
696 disable_due_to_custom_ua = !ua_metadata.has_value();
697 }
698
Ryan Sturm320fda4d2021-01-05 19:13:30699 if (frame_tree_node &&
700 devtools_instrumentation::ApplyUserAgentMetadataOverrides(frame_tree_node,
Maks Orlovich85a6fd62020-05-05 23:02:14701 &ua_metadata)) {
702 // Likewise, if devtools says to override client hints but provides no
703 // value, disable them. This overwrites previous decision from UI.
704 disable_due_to_custom_ua = !ua_metadata.has_value();
705 }
706
Scott Violet3cd8d362020-04-30 22:18:24707 if (!disable_due_to_custom_ua) {
708 if (!ua_metadata.has_value())
709 ua_metadata = delegate->GetUserAgentMetadata();
710
711 // The `Sec-CH-UA` client hint is attached to all outgoing requests. This is
712 // (intentionally) different than other client hints.
Yoav Weiss8627cac2020-05-15 00:43:37713 // It's barred behind ShouldAddClientHints to make sure it's controlled by
Charlie Hu5130d25e2021-03-05 21:53:39714 // Permissions Policy.
Scott Violet3cd8d362020-04-30 22:18:24715 //
716 // https://wicg.github.io/client-hints-infrastructure/#abstract-opdef-append-client-hints-to-request
Ali Beyad7417fc22021-08-06 03:09:58717 if (ShouldAddClientHint(data, WebClientHintsType::kUA)) {
718 AddUAHeader(headers, WebClientHintsType::kUA,
Victor Tan1eb643c32021-11-11 18:34:02719 ua_metadata->SerializeBrandMajorVersionList());
Yoav Weiss8627cac2020-05-15 00:43:37720 }
Scott Violet3cd8d362020-04-30 22:18:24721 // The `Sec-CH-UA-Mobile client hint was also deemed "low entropy" and can
Yoav Weiss8627cac2020-05-15 00:43:37722 // safely be sent with every request. Similarly to UA, ShouldAddClientHints
Charlie Hu5130d25e2021-03-05 21:53:39723 // makes sure it's controlled by Permissions Policy.
Ali Beyad7417fc22021-08-06 03:09:58724 if (ShouldAddClientHint(data, WebClientHintsType::kUAMobile)) {
725 AddUAHeader(headers, WebClientHintsType::kUAMobile,
726 SerializeHeaderString(ua_metadata->mobile));
Yoav Weiss8627cac2020-05-15 00:43:37727 }
Scott Violet3cd8d362020-04-30 22:18:24728
Ali Beyad7417fc22021-08-06 03:09:58729 if (ShouldAddClientHint(data, WebClientHintsType::kUAFullVersion)) {
730 AddUAHeader(headers, WebClientHintsType::kUAFullVersion,
Aaron Tagliaboschi9f01b682020-05-05 21:03:17731 SerializeHeaderString(ua_metadata->full_version));
Scott Violet3cd8d362020-04-30 22:18:24732 }
733
Ali Beyad7417fc22021-08-06 03:09:58734 if (ShouldAddClientHint(data, WebClientHintsType::kUAArch)) {
735 AddUAHeader(headers, WebClientHintsType::kUAArch,
Aaron Tagliaboschi9f01b682020-05-05 21:03:17736 SerializeHeaderString(ua_metadata->architecture));
Scott Violet3cd8d362020-04-30 22:18:24737 }
738
Ali Beyad7417fc22021-08-06 03:09:58739 if (ShouldAddClientHint(data, WebClientHintsType::kUAPlatform)) {
740 AddUAHeader(headers, WebClientHintsType::kUAPlatform,
Aaron Tagliaboschi9f01b682020-05-05 21:03:17741 SerializeHeaderString(ua_metadata->platform));
Yoav Weissfd1d19f2020-05-05 09:23:03742 }
743
Ali Beyad7417fc22021-08-06 03:09:58744 if (ShouldAddClientHint(data, WebClientHintsType::kUAPlatformVersion)) {
745 AddUAHeader(headers, WebClientHintsType::kUAPlatformVersion,
Aaron Tagliaboschi9f01b682020-05-05 21:03:17746 SerializeHeaderString(ua_metadata->platform_version));
Scott Violet3cd8d362020-04-30 22:18:24747 }
748
Ali Beyad7417fc22021-08-06 03:09:58749 if (ShouldAddClientHint(data, WebClientHintsType::kUAModel)) {
750 AddUAHeader(headers, WebClientHintsType::kUAModel,
Aaron Tagliaboschi9f01b682020-05-05 21:03:17751 SerializeHeaderString(ua_metadata->model));
Scott Violet3cd8d362020-04-30 22:18:24752 }
Ali Beyad7417fc22021-08-06 03:09:58753 if (ShouldAddClientHint(data, WebClientHintsType::kUABitness)) {
754 AddUAHeader(headers, WebClientHintsType::kUABitness,
Aaron Tagliaboschi1649e102021-06-18 23:15:07755 SerializeHeaderString(ua_metadata->bitness));
756 }
Ali Beyadb235f342022-01-31 18:50:22757 if (ShouldAddClientHint(data, WebClientHintsType::kUAWoW64)) {
758 AddUAHeader(headers, WebClientHintsType::kUAWoW64,
759 SerializeHeaderString(ua_metadata->wow64));
760 }
Victor Tan1eb643c32021-11-11 18:34:02761 if (ShouldAddClientHint(data, WebClientHintsType::kUAFullVersionList)) {
762 AddUAHeader(headers, WebClientHintsType::kUAFullVersionList,
763 ua_metadata->SerializeBrandFullVersionList());
764 }
Dustin J. Mitchell5909d3b2024-03-13 01:03:24765 if (ShouldAddClientHint(data, WebClientHintsType::kUAFormFactors)) {
766 AddUAHeader(headers, WebClientHintsType::kUAFormFactors,
767 ua_metadata->SerializeFormFactors());
Dustin J. Mitchell434a18c2023-06-22 15:32:36768 }
Scott Violet3cd8d362020-04-30 22:18:24769 } else if (call_type == ClientUaHeaderCallType::kAfterCreated) {
Ali Beyad7417fc22021-08-06 03:09:58770 RemoveClientHintHeader(WebClientHintsType::kUA, headers);
771 RemoveClientHintHeader(WebClientHintsType::kUAMobile, headers);
772 RemoveClientHintHeader(WebClientHintsType::kUAFullVersion, headers);
773 RemoveClientHintHeader(WebClientHintsType::kUAArch, headers);
774 RemoveClientHintHeader(WebClientHintsType::kUAPlatform, headers);
775 RemoveClientHintHeader(WebClientHintsType::kUAPlatformVersion, headers);
776 RemoveClientHintHeader(WebClientHintsType::kUAModel, headers);
777 RemoveClientHintHeader(WebClientHintsType::kUABitness, headers);
Victor Tan1eb643c32021-11-11 18:34:02778 RemoveClientHintHeader(WebClientHintsType::kUAFullVersionList, headers);
Ali Beyadb235f342022-01-31 18:50:22779 RemoveClientHintHeader(WebClientHintsType::kUAWoW64, headers);
Dustin J. Mitchell5909d3b2024-03-13 01:03:24780 RemoveClientHintHeader(WebClientHintsType::kUAFormFactors, headers);
Scott Violet3cd8d362020-04-30 22:18:24781 }
Yoav Weiss8c9688d2019-11-07 17:21:04782}
783
Yoav Weiss603be262019-03-04 13:14:57784} // namespace
785
Aaron Tagliaboschi579c632e2022-02-23 17:45:12786bool ShouldAddClientHints(const url::Origin& origin,
Aaron Tagliaboschi41bed88e2020-10-20 16:29:42787 FrameTreeNode* frame_tree_node,
Aaron Tagliaboschid0cda1e2022-03-09 19:30:37788 ClientHintsControllerDelegate* delegate,
Arthur Sonzognic686e8f2024-01-11 08:36:37789 const std::optional<GURL> maybe_request_url) {
Aaron Tagliaboschid0cda1e2022-03-09 19:30:37790 url::Origin origin_to_check =
791 maybe_request_url ? url::Origin::Create(maybe_request_url.value())
792 : origin;
Aaron Tagliaboschi41bed88e2020-10-20 16:29:42793 // Client hints should only be enabled when JavaScript is enabled. Platforms
794 // which enable/disable JavaScript on a per-origin basis should implement
795 // IsJavaScriptAllowed to check a given origin. Other platforms (Android
796 // WebView) enable/disable JavaScript on a per-View basis, using the
797 // WebPreferences setting.
Aaron Tagliaboschid0cda1e2022-03-09 19:30:37798 return IsValidURLForClientHints(origin_to_check) &&
Adithya Srinivasanafe3e47c2022-04-21 22:54:02799 delegate->IsJavaScriptAllowed(
800 origin_to_check.GetURL(),
801 frame_tree_node ? frame_tree_node->GetParentOrOuterDocument()
802 : nullptr) &&
Ryan Sturm6e961a32020-11-30 20:37:55803 (!frame_tree_node || IsJavascriptEnabled(frame_tree_node));
Aaron Tagliaboschi41bed88e2020-10-20 16:29:42804}
805
Yoav Weiss603be262019-03-04 13:14:57806unsigned long RoundRttForTesting(const std::string& host,
Arthur Sonzognic686e8f2024-01-11 08:36:37807 const std::optional<base::TimeDelta>& rtt) {
Yoav Weiss603be262019-03-04 13:14:57808 return RoundRtt(host, rtt);
809}
810
811double RoundKbpsToMbpsForTesting(const std::string& host,
Arthur Sonzognic686e8f2024-01-11 08:36:37812 const std::optional<int32_t>& downlink_kbps) {
Yoav Weiss603be262019-03-04 13:14:57813 return RoundKbpsToMbps(host, downlink_kbps);
814}
815
Scott Violet3cd8d362020-04-30 22:18:24816void UpdateNavigationRequestClientUaHeaders(
Aaron Tagliaboschi579c632e2022-02-23 17:45:12817 const url::Origin& origin,
Scott Violet3cd8d362020-04-30 22:18:24818 ClientHintsControllerDelegate* delegate,
819 bool override_ua,
820 FrameTreeNode* frame_tree_node,
Aaron Tagliaboschid0cda1e2022-03-09 19:30:37821 net::HttpRequestHeaders* headers,
Arthur Sonzognic686e8f2024-01-11 08:36:37822 const std::optional<GURL>& request_url) {
Ryan Sturm6e961a32020-11-30 20:37:55823 DCHECK(frame_tree_node);
Victor Tan7cb768192024-01-19 22:53:24824 if (!ShouldAddClientHints(origin, frame_tree_node, delegate, request_url)) {
Scott Violet3cd8d362020-04-30 22:18:24825 return;
826 }
827
Patrick Nolandfec8dba2022-12-20 20:25:47828 ClientHintsExtendedData data(origin, frame_tree_node, delegate, request_url);
Scott Violet3cd8d362020-04-30 22:18:24829 UpdateNavigationRequestClientUaHeadersImpl(
Patrick Nolandfec8dba2022-12-20 20:25:47830 delegate, override_ua, frame_tree_node,
831 ClientUaHeaderCallType::kAfterCreated, headers, {}, request_url, data);
Scott Violet3cd8d362020-04-30 22:18:24832}
833
Ryan Sturm6e961a32020-11-30 20:37:55834namespace {
Yoav Weiss603be262019-03-04 13:14:57835
Aaron Tagliaboschieae7f4f2f2021-04-13 15:05:45836void AddRequestClientHintsHeaders(
Aaron Tagliaboschi579c632e2022-02-23 17:45:12837 const url::Origin& origin,
Aaron Tagliaboschieae7f4f2f2021-04-13 15:05:45838 net::HttpRequestHeaders* headers,
839 BrowserContext* context,
840 ClientHintsControllerDelegate* delegate,
841 bool is_ua_override_on,
842 FrameTreeNode* frame_tree_node,
Sandor Major878f8352025-02-18 20:16:02843 const network::ParsedPermissionsPolicy& container_policy,
Arthur Sonzognic686e8f2024-01-11 08:36:37844 const std::optional<GURL>& request_url) {
Aaron Tagliaboschid0cda1e2022-03-09 19:30:37845 ClientHintsExtendedData data(origin, frame_tree_node, delegate, request_url);
Ari Chivukula6a8b32f2021-12-16 18:32:57846 UpdateIFramePermissionsPolicyWithDelegationSupportForClientHints(
847 data, container_policy);
Yoav Weiss603be262019-03-04 13:14:57848
Aaron Tagliaboschi579c632e2022-02-23 17:45:12849 GURL url = origin.GetURL();
850
Yoav Weiss603be262019-03-04 13:14:57851 // Add Headers
Ari Chivukula6692cc52021-09-09 09:30:11852 if (ShouldAddClientHint(data, WebClientHintsType::kDeviceMemory_DEPRECATED)) {
Ari Chivukula64248112021-10-07 06:00:56853 AddDeviceMemoryHeader(headers, /*use_deprecated_version*/ true);
854 }
855 if (ShouldAddClientHint(data, WebClientHintsType::kDeviceMemory)) {
Yoav Weiss603be262019-03-04 13:14:57856 AddDeviceMemoryHeader(headers);
857 }
Ari Chivukula6692cc52021-09-09 09:30:11858 if (ShouldAddClientHint(data, WebClientHintsType::kDpr_DEPRECATED)) {
Ari Chivukula64248112021-10-07 06:00:56859 AddDPRHeader(headers, context, url, /*use_deprecated_version*/ true);
860 }
861 if (ShouldAddClientHint(data, WebClientHintsType::kDpr)) {
Yoav Weiss603be262019-03-04 13:14:57862 AddDPRHeader(headers, context, url);
863 }
Victor Tan1eb643c32021-11-11 18:34:02864 if (ShouldAddClientHint(data,
865 WebClientHintsType::kViewportWidth_DEPRECATED)) {
Max Curran2baf1f22022-08-26 22:01:07866 AddViewportWidthHeader(headers, context, url, frame_tree_node, delegate,
Ari Chivukula64248112021-10-07 06:00:56867 /*use_deprecated_version*/ true);
868 }
869 if (ShouldAddClientHint(data, WebClientHintsType::kViewportWidth)) {
Max Curran2baf1f22022-08-26 22:01:07870 AddViewportWidthHeader(headers, context, url, frame_tree_node, delegate);
Yoav Weiss603be262019-03-04 13:14:57871 }
Max Curran99cbec1492021-08-16 19:54:26872 if (ShouldAddClientHint(
873 data, network::mojom::WebClientHintsType::kViewportHeight)) {
Max Curran2baf1f22022-08-26 22:01:07874 AddViewportHeightHeader(headers, context, url, frame_tree_node, delegate);
Max Curran99cbec1492021-08-16 19:54:26875 }
Yoav Weiss603be262019-03-04 13:14:57876 network::NetworkQualityTracker* network_quality_tracker =
877 delegate->GetNetworkQualityTracker();
Ari Chivukula6692cc52021-09-09 09:30:11878 if (ShouldAddClientHint(data, WebClientHintsType::kRtt_DEPRECATED)) {
Yoav Weiss603be262019-03-04 13:14:57879 AddRttHeader(headers, network_quality_tracker, url);
880 }
Ari Chivukula6692cc52021-09-09 09:30:11881 if (ShouldAddClientHint(data, WebClientHintsType::kDownlink_DEPRECATED)) {
Yoav Weiss603be262019-03-04 13:14:57882 AddDownlinkHeader(headers, network_quality_tracker, url);
883 }
Ari Chivukula6692cc52021-09-09 09:30:11884 if (ShouldAddClientHint(data, WebClientHintsType::kEct_DEPRECATED)) {
Yoav Weiss603be262019-03-04 13:14:57885 AddEctHeader(headers, network_quality_tracker, url);
886 }
Yoav Weiss603be262019-03-04 13:14:57887
Victor Tan7cb768192024-01-19 22:53:24888 UpdateNavigationRequestClientUaHeadersImpl(
889 delegate, is_ua_override_on, frame_tree_node,
890 ClientUaHeaderCallType::kDuringCreation, headers, container_policy,
891 request_url, data);
Yoav Weiss603be262019-03-04 13:14:57892
Ali Beyad7417fc22021-08-06 03:09:58893 if (ShouldAddClientHint(data, WebClientHintsType::kPrefersColorScheme)) {
François Beaufort1b51d062021-05-18 11:11:23894 AddPrefersColorSchemeHeader(headers, frame_tree_node);
895 }
896
François Beaufort378fffd2022-09-28 16:48:19897 if (ShouldAddClientHint(data, WebClientHintsType::kPrefersReducedMotion)) {
898 AddPrefersReducedMotionHeader(headers, frame_tree_node);
899 }
900
Luke Warlow86339292023-08-26 16:55:25901 if (ShouldAddClientHint(data,
902 WebClientHintsType::kPrefersReducedTransparency)) {
903 AddPrefersReducedTransparencyHeader(headers, frame_tree_node);
904 }
905
Ari Chivukula116f0fc42022-04-13 23:26:18906 if (ShouldAddClientHint(data, WebClientHintsType::kSaveData))
907 AddSaveDataHeader(headers, context);
908
Yoav Weiss603be262019-03-04 13:14:57909 // Static assert that triggers if a new client hint header is added. If a
910 // new client hint header is added, the following assertion should be updated.
911 // If possible, logic should be added above so that the request headers for
912 // the newly added client hint can be added to the request.
913 static_assert(
Luke Warlow86339292023-08-26 16:55:25914 network::mojom::WebClientHintsType::kPrefersReducedTransparency ==
Max Curran99cbec1492021-08-16 19:54:26915 network::mojom::WebClientHintsType::kMaxValue,
Yoav Weiss603be262019-03-04 13:14:57916 "Consider adding client hint request headers from the browser process");
917
Alison Gale81f4f2c72024-04-22 19:33:31918 // TODO(crbug.com/40526905): If the request is redirected, the client hint
Yoav Weiss603be262019-03-04 13:14:57919 // headers stay attached to the redirected request. Consider removing/adding
920 // the client hints headers if the request is redirected with a change in
921 // scheme or a change in the origin.
922}
923
Ryan Sturm6e961a32020-11-30 20:37:55924} // namespace
925
926void AddPrefetchNavigationRequestClientHintsHeaders(
Aaron Tagliaboschi579c632e2022-02-23 17:45:12927 const url::Origin& origin,
Ryan Sturm6e961a32020-11-30 20:37:55928 net::HttpRequestHeaders* headers,
929 BrowserContext* context,
930 ClientHintsControllerDelegate* delegate,
Kouhei Ueno27f86cb2025-02-06 09:03:36931 bool is_ua_override_on) {
Ryan Sturm6e961a32020-11-30 20:37:55932 DCHECK_CURRENTLY_ON(BrowserThread::UI);
Ryan Sturm6e961a32020-11-30 20:37:55933 DCHECK(context);
934
Kouhei Ueno27f86cb2025-02-06 09:03:36935 if (!ShouldAddClientHints(origin, nullptr, delegate)) {
Ryan Sturm6e961a32020-11-30 20:37:55936 return;
937 }
938
Aaron Tagliaboschi579c632e2022-02-23 17:45:12939 AddRequestClientHintsHeaders(origin, headers, context, delegate,
Arthur Sonzognic686e8f2024-01-11 08:36:37940 is_ua_override_on, nullptr, {}, std::nullopt);
Ryan Sturm6e961a32020-11-30 20:37:55941}
942
943void AddNavigationRequestClientHintsHeaders(
Aaron Tagliaboschi579c632e2022-02-23 17:45:12944 const url::Origin& origin,
Ryan Sturm6e961a32020-11-30 20:37:55945 net::HttpRequestHeaders* headers,
946 BrowserContext* context,
947 ClientHintsControllerDelegate* delegate,
948 bool is_ua_override_on,
Aaron Tagliaboschieae7f4f2f2021-04-13 15:05:45949 FrameTreeNode* frame_tree_node,
Sandor Major878f8352025-02-18 20:16:02950 const network::ParsedPermissionsPolicy& container_policy,
Arthur Sonzognic686e8f2024-01-11 08:36:37951 const std::optional<GURL>& request_url) {
Ryan Sturm6e961a32020-11-30 20:37:55952 DCHECK(frame_tree_node);
953 DCHECK_CURRENTLY_ON(BrowserThread::UI);
Ryan Sturm6e961a32020-11-30 20:37:55954 DCHECK(context);
Aaron Tagliaboschid0cda1e2022-03-09 19:30:37955 if (!ShouldAddClientHints(origin, frame_tree_node, delegate, request_url)) {
Ryan Sturm6e961a32020-11-30 20:37:55956 return;
957 }
958
Aaron Tagliaboschi579c632e2022-02-23 17:45:12959 AddRequestClientHintsHeaders(origin, headers, context, delegate,
Aaron Tagliaboschieae7f4f2f2021-04-13 15:05:45960 is_ua_override_on, frame_tree_node,
Aaron Tagliaboschid0cda1e2022-03-09 19:30:37961 container_policy, request_url);
Ryan Sturm6e961a32020-11-30 20:37:55962}
963
Arthur Sonzognic686e8f2024-01-11 08:36:37964std::optional<std::vector<WebClientHintsType>>
Ali Beyadf690449a2021-07-13 07:49:53965ParseAndPersistAcceptCHForNavigation(
Aaron Tagliaboschi579c632e2022-02-23 17:45:12966 const url::Origin& origin,
Ali Beyad7417fc22021-08-06 03:09:58967 const network::mojom::ParsedHeadersPtr& parsed_headers,
968 const net::HttpResponseHeaders* response_headers,
Maks Orlovichbc5c59b2020-04-24 12:40:59969 BrowserContext* context,
Maks Orlovichbc5c59b2020-04-24 12:40:59970 ClientHintsControllerDelegate* delegate,
971 FrameTreeNode* frame_tree_node) {
972 DCHECK_CURRENTLY_ON(BrowserThread::UI);
973 DCHECK(context);
Ali Beyad7417fc22021-08-06 03:09:58974 DCHECK(parsed_headers);
Maks Orlovichbc5c59b2020-04-24 12:40:59975
Aaron Tagliaboschi579c632e2022-02-23 17:45:12976 if (!parsed_headers->accept_ch)
Arthur Sonzognic686e8f2024-01-11 08:36:37977 return std::nullopt;
Maks Orlovichbc5c59b2020-04-24 12:40:59978
Aaron Tagliaboschi579c632e2022-02-23 17:45:12979 if (!IsValidURLForClientHints(origin))
Arthur Sonzognic686e8f2024-01-11 08:36:37980 return std::nullopt;
Maks Orlovichbc5c59b2020-04-24 12:40:59981
982 // Client hints should only be enabled when JavaScript is enabled. Platforms
983 // which enable/disable JavaScript on a per-origin basis should implement
984 // IsJavaScriptAllowed to check a given origin. Other platforms (Android
985 // WebView) enable/disable JavaScript on a per-View basis, using the
986 // WebPreferences setting.
Adithya Srinivasanafe3e47c2022-04-21 22:54:02987 if (!delegate->IsJavaScriptAllowed(
988 origin.GetURL(), frame_tree_node->GetParentOrOuterDocument()) ||
Scott Violet3cd8d362020-04-30 22:18:24989 !IsJavascriptEnabled(frame_tree_node)) {
Arthur Sonzognic686e8f2024-01-11 08:36:37990 return std::nullopt;
Scott Violet3cd8d362020-04-30 22:18:24991 }
Maks Orlovichbc5c59b2020-04-24 12:40:59992
Victor Tanb37a4c82023-06-08 16:18:28993 // Only the main frame should parse accept-CH.
Ali Beyad49055ed2021-10-11 15:33:33994 if (!frame_tree_node->IsMainFrame()) {
Arthur Sonzognic686e8f2024-01-11 08:36:37995 return std::nullopt;
Ali Beyad49055ed2021-10-11 15:33:33996 }
Maks Orlovichbc5c59b2020-04-24 12:40:59997
Ali Beyad7417fc22021-08-06 03:09:58998 blink::EnabledClientHints enabled_hints;
Victor Tanb37a4c82023-06-08 16:18:28999 for (const WebClientHintsType type : parsed_headers->accept_ch.value()) {
1000 enabled_hints.SetIsEnabled(type, true);
Ali Beyad7417fc22021-08-06 03:09:581001 }
1002 const std::vector<WebClientHintsType> persisted_hints =
1003 enabled_hints.GetEnabledHints();
Lingqi Chi59b6b42a2022-08-23 02:25:341004 DCHECK(frame_tree_node);
1005 PersistAcceptCH(origin, *frame_tree_node, delegate, persisted_hints);
Ali Beyad7417fc22021-08-06 03:09:581006 return persisted_hints;
Maks Orlovichc66745a2020-06-30 17:40:021007}
1008
Aaron Tagliaboschi579c632e2022-02-23 17:45:121009void PersistAcceptCH(const url::Origin& origin,
Lingqi Chi59b6b42a2022-08-23 02:25:341010 FrameTreeNode& frame_tree_node,
Ali Beyad77dba952021-08-25 20:52:581011 ClientHintsControllerDelegate* delegate,
Ari Chivukula93a1f4f2021-12-01 23:27:181012 const std::vector<WebClientHintsType>& hints) {
Ali Beyad77dba952021-08-25 20:52:581013 DCHECK(delegate);
Lingqi Chi59b6b42a2022-08-23 02:25:341014
Hiroki Nakagawae2e4558d2023-06-07 07:54:481015 // For prerendering headers, it should not persist the client header until
1016 // activation, considering user has not visited the page and allowed it to
1017 // change content setting yet. The client hints should apply to navigations
1018 // in the prerendering page, and propagate to the global setting upon user
1019 // navigation.
1020 if (auto* host =
1021 PrerenderHost::GetFromFrameTreeNodeIfPrerendering(frame_tree_node)) {
1022 host->OnAcceptClientHintChanged(origin, hints);
Lingqi Chi59b6b42a2022-08-23 02:25:341023 return;
1024 }
1025
1026 delegate->PersistClientHints(
1027 origin, frame_tree_node.GetParentOrOuterDocument(), hints);
Ali Beyad77dba952021-08-25 20:52:581028}
1029
Lei Zhang8076d9f2021-11-16 00:33:271030std::vector<WebClientHintsType> LookupAcceptCHForCommit(
Aaron Tagliaboschi579c632e2022-02-23 17:45:121031 const url::Origin& origin,
Ali Beyad7417fc22021-08-06 03:09:581032 ClientHintsControllerDelegate* delegate,
Aaron Tagliaboschid0cda1e2022-03-09 19:30:371033 FrameTreeNode* frame_tree_node,
Arthur Sonzognic686e8f2024-01-11 08:36:371034 const std::optional<GURL>& request_url) {
Ali Beyad7417fc22021-08-06 03:09:581035 std::vector<WebClientHintsType> result;
Aaron Tagliaboschid0cda1e2022-03-09 19:30:371036 if (!ShouldAddClientHints(origin, frame_tree_node, delegate, request_url)) {
Maks Orlovichc66745a2020-06-30 17:40:021037 return result;
1038 }
1039
Aaron Tagliaboschid0cda1e2022-03-09 19:30:371040 const ClientHintsExtendedData data(origin, frame_tree_node, delegate,
1041 request_url);
Victor Tanb37a4c82023-06-08 16:18:281042 return data.hints.GetEnabledHints();
Maks Orlovichbc5c59b2020-04-24 12:40:591043}
1044
Aaron Tagliaboschi41bed88e2020-10-20 16:29:421045bool AreCriticalHintsMissing(
Aaron Tagliaboschi579c632e2022-02-23 17:45:121046 const url::Origin& origin,
Aaron Tagliaboschi41bed88e2020-10-20 16:29:421047 FrameTreeNode* frame_tree_node,
1048 ClientHintsControllerDelegate* delegate,
Ali Beyad7417fc22021-08-06 03:09:581049 const std::vector<WebClientHintsType>& critical_hints) {
Arthur Sonzognic686e8f2024-01-11 08:36:371050 ClientHintsExtendedData data(origin, frame_tree_node, delegate, std::nullopt);
Aaron Tagliaboschi41bed88e2020-10-20 16:29:421051
Charlie Hu5130d25e2021-03-05 21:53:391052 // Note: these only check for per-hint origin/permissions policy settings, not
Aaron Tagliaboschi41bed88e2020-10-20 16:29:421053 // origin-level or "browser-level" policies like disabiling JS or other
1054 // features.
1055 for (auto hint : critical_hints) {
Ali Beyadf3128152022-01-21 20:36:361056 if (IsClientHintAllowed(data, hint) && !IsClientHintEnabled(data, hint)) {
Aaron Tagliaboschi41bed88e2020-10-20 16:29:421057 return true;
Ali Beyadf3128152022-01-21 20:36:361058 }
Aaron Tagliaboschi41bed88e2020-10-20 16:29:421059 }
1060
1061 return false;
1062}
1063
Yoshisato Yanagisawab108f252025-07-24 23:49:041064network::ResourceRequest::TrustedParams::EnabledClientHints
1065GetEnabledClientHints(const url::Origin& origin,
1066 FrameTreeNode* frame_tree_node,
1067 ClientHintsControllerDelegate* delegate) {
Yoshisato Yanagisawa35b1e3632025-06-18 23:37:511068 ClientHintsExtendedData data(origin, frame_tree_node, delegate, std::nullopt);
1069
Yoshisato Yanagisawab108f252025-07-24 23:49:041070 network::ResourceRequest::TrustedParams::EnabledClientHints
1071 enabled_client_hints;
1072 enabled_client_hints.is_outermost_main_frame = data.is_outermost_main_frame;
Yoshisato Yanagisawa35b1e3632025-06-18 23:37:511073 const auto& client_hints_map = network::GetClientHintToNameMap();
1074 // Note: these only check for per-hint origin/permissions policy settings, not
1075 // origin-level or "browser-level" policies like disabiling JS or other
1076 // features.
1077 for (const auto& [hint, _] : client_hints_map) {
1078 if (ShouldAddClientHint(data, hint)) {
Yoshisato Yanagisawab108f252025-07-24 23:49:041079 enabled_client_hints.hints.push_back(hint);
Yoshisato Yanagisawa35b1e3632025-06-18 23:37:511080 }
1081 }
Yoshisato Yanagisawab108f252025-07-24 23:49:041082 enabled_client_hints.origin = data.main_frame_origin;
1083 return enabled_client_hints;
Yoshisato Yanagisawa35b1e3632025-06-18 23:37:511084}
1085
Yoav Weiss603be262019-03-04 13:14:571086} // namespace content