blob: fb36191b068ce354f09df7757ab6c6acad64f797 [file] [log] [blame]
Avi Drissman4e1b7bc32022-09-15 14:03:501// Copyright 2018 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
Peter Kasting1557e5f2025-01-28 01:14:087#include <algorithm>
8
Keishi Hattoric1b00232022-11-22 09:04:269#include "base/memory/raw_ptr.h"
Victor Tan3a676368e2022-11-04 20:52:2310#include "base/strings/strcat.h"
Yoav Weiss603be262019-03-04 13:14:5711#include "base/strings/string_number_conversions.h"
Victor Tan3a676368e2022-11-04 20:52:2312#include "base/strings/string_util.h"
Yoav Weiss603be262019-03-04 13:14:5713#include "base/time/time.h"
Victor Tan3a676368e2022-11-04 20:52:2314#include "content/public/test/mock_client_hints_controller_delegate.h"
15#include "content/public/test/test_browser_context.h"
16#include "content/test/test_render_frame_host.h"
17#include "content/test/test_render_view_host.h"
18#include "content/test/test_web_contents.h"
19#include "net/http/http_response_headers.h"
Antonio Sartori55369fff2022-11-30 09:17:5620#include "services/metrics/public/cpp/ukm_source_id.h"
Victor Tan3a676368e2022-11-04 20:52:2321#include "services/network/public/cpp/client_hints.h"
22#include "services/network/public/cpp/is_potentially_trustworthy.h"
Victor Tance1f6fb2025-08-22 01:11:0223#include "services/network/public/cpp/permissions_policy/permissions_policy_declaration.h"
Yoshisato Yanagisawa35b1e3632025-06-18 23:37:5124#include "testing/gmock/include/gmock/gmock-matchers.h"
25#include "third_party/blink/public/common/client_hints/client_hints.h"
Yoav Weiss603be262019-03-04 13:14:5726
27namespace content {
28
Victor Tan3a676368e2022-11-04 20:52:2329namespace {
30
31using ClientHintsVector = std::vector<network::mojom::WebClientHintsType>;
32using network::mojom::WebClientHintsType;
33
Victor Tan3a676368e2022-11-04 20:52:2334} // namespace
35
Victor Tan3a676368e2022-11-04 20:52:2336class ClientHintsTest : public RenderViewHostImplTestHarness {
37 public:
Victor Tanb37a4c82023-06-08 16:18:2838 ClientHintsTest() = default;
Victor Tan3a676368e2022-11-04 20:52:2339 ClientHintsTest(const ClientHintsTest&) = delete;
40 ClientHintsTest& operator=(const ClientHintsTest&) = delete;
41 ~ClientHintsTest() override {
42 blink::TrialTokenValidator::ResetOriginTrialPolicyGetter();
43 }
44
45 static constexpr char kOriginUrl[] = "https://example.com";
Victor Tan3a676368e2022-11-04 20:52:2346
47 void AddOneChildNode() {
48 main_test_rfh()->OnCreateChildFrame(
49 /*new_routing_id=*/14, TestRenderFrameHost::CreateStubFrameRemote(),
50 TestRenderFrameHost::CreateStubBrowserInterfaceBrokerReceiver(),
51 TestRenderFrameHost::CreateStubPolicyContainerBindParams(),
52 TestRenderFrameHost::CreateStubAssociatedInterfaceProviderReceiver(),
53 /*scope=*/blink::mojom::TreeScopeType::kDocument, /*frame_name=*/"",
54 /*frame_unique_name=*/"uniqueName0", /*is_created_by_script=*/false,
55 /*frame_token=*/blink::LocalFrameToken(),
56 /*devtools_frame_token=*/base::UnguessableToken::Create(),
57 /*document_token=*/blink::DocumentToken(),
58 /*frame_policy=*/blink::FramePolicy(),
59 /*frame_owner_properties=*/blink::mojom::FrameOwnerProperties(),
Antonio Sartori55369fff2022-11-30 09:17:5660 /*owner_type=*/blink::FrameOwnerElementType::kIframe,
61 /*document_ukm_source_id=*/ukm::kInvalidSourceId);
Victor Tan3a676368e2022-11-04 20:52:2362 }
63
Arthur Sonzognic686e8f2024-01-11 08:36:3764 std::optional<ClientHintsVector> ParseAndPersist(
Victor Tan3a676368e2022-11-04 20:52:2365 const GURL& url,
66 const net::HttpResponseHeaders* response_header,
67 const std::string& accept_ch_str,
68 FrameTreeNode* frame_tree_node,
69 MockClientHintsControllerDelegate* delegate) {
70 auto parsed_headers = network::mojom::ParsedHeaders::New();
71 parsed_headers->accept_ch = network::ParseClientHintsHeader(accept_ch_str);
72
73 return ParseAndPersistAcceptCHForNavigation(
74 url::Origin::Create(url), parsed_headers, response_header,
75 browser_context(), delegate, frame_tree_node);
76 }
77
Arthur Sonzognic686e8f2024-01-11 08:36:3778 std::string HintsToString(std::optional<ClientHintsVector> hints) {
Victor Tan3a676368e2022-11-04 20:52:2379 if (!hints)
80 return "";
81
82 std::vector<std::string> hints_list;
Peter Kasting1d48f352023-03-07 18:29:5783 const auto& map = network::GetClientHintToNameMap();
Peter Kasting1557e5f2025-01-28 01:14:0884 std::ranges::transform(hints.value(), std::back_inserter(hints_list),
85 [&map](network::mojom::WebClientHintsType hint) {
86 return map.at(hint);
87 });
Victor Tan3a676368e2022-11-04 20:52:2388
89 return base::JoinString(hints_list, ",");
90 }
91
Victor Tanb37a4c82023-06-08 16:18:2892 std::pair<std::string, ClientHintsVector> GetAllClientHints() {
Victor Tan3a676368e2022-11-04 20:52:2393 std::vector<std::string> accept_ch_tokens;
94 ClientHintsVector hints_list;
95 for (const auto& pair : network::GetClientHintToNameMap()) {
Victor Tan3a676368e2022-11-04 20:52:2396 hints_list.push_back(pair.first);
97 accept_ch_tokens.push_back(pair.second);
98 }
99 return {base::JoinString(accept_ch_tokens, ","), hints_list};
100 }
Victor Tan3a676368e2022-11-04 20:52:23101};
102
103TEST_F(ClientHintsTest, RttRoundedOff) {
Peter Kastinge5a38ed2021-10-02 03:06:35104 EXPECT_EQ(0u, RoundRttForTesting("", base::Milliseconds(1023)) % 50);
105 EXPECT_EQ(0u, RoundRttForTesting("", base::Milliseconds(6787)) % 50);
106 EXPECT_EQ(0u, RoundRttForTesting("", base::Milliseconds(12)) % 50);
107 EXPECT_EQ(0u, RoundRttForTesting("foo.com", base::Milliseconds(1023)) % 50);
108 EXPECT_EQ(0u, RoundRttForTesting("foo.com", base::Milliseconds(1193)) % 50);
109 EXPECT_EQ(0u, RoundRttForTesting("foo.com", base::Milliseconds(12)) % 50);
Yoav Weiss603be262019-03-04 13:14:57110}
111
Victor Tan3a676368e2022-11-04 20:52:23112TEST_F(ClientHintsTest, DownlinkRoundedOff) {
Yoav Weiss603be262019-03-04 13:14:57113 EXPECT_GE(1,
114 static_cast<int>(RoundKbpsToMbpsForTesting("", 102) * 1000) % 50);
115 EXPECT_GE(1, static_cast<int>(RoundKbpsToMbpsForTesting("", 12) * 1000) % 50);
116 EXPECT_GE(1,
117 static_cast<int>(RoundKbpsToMbpsForTesting("", 2102) * 1000) % 50);
118
119 EXPECT_GE(
120 1,
121 static_cast<int>(RoundKbpsToMbpsForTesting("foo.com", 102) * 1000) % 50);
122 EXPECT_GE(
123 1,
124 static_cast<int>(RoundKbpsToMbpsForTesting("foo.com", 12) * 1000) % 50);
125 EXPECT_GE(
126 1,
127 static_cast<int>(RoundKbpsToMbpsForTesting("foo.com", 2102) * 1000) % 50);
128 EXPECT_GE(
129 1, static_cast<int>(RoundKbpsToMbpsForTesting("foo.com", 12102) * 1000) %
130 50);
131}
132
133// Verify that the value of RTT after adding noise is within approximately 10%
134// of the original value. Note that the difference between the final value of
135// RTT and the original value may be slightly more than 10% due to rounding off.
136// To handle that, the maximum absolute difference allowed is set to a value
137// slightly larger than 10% of the original metric value.
Victor Tan3a676368e2022-11-04 20:52:23138TEST_F(ClientHintsTest, FinalRttWithin10PercentValue) {
Peter Kastinge5a38ed2021-10-02 03:06:35139 EXPECT_NEAR(98, RoundRttForTesting("", base::Milliseconds(98)), 100);
140 EXPECT_NEAR(1023, RoundRttForTesting("", base::Milliseconds(1023)), 200);
141 EXPECT_NEAR(1193, RoundRttForTesting("", base::Milliseconds(1193)), 200);
142 EXPECT_NEAR(2750, RoundRttForTesting("", base::Milliseconds(2750)), 400);
Yoav Weiss603be262019-03-04 13:14:57143}
144
145// Verify that the value of downlink after adding noise is within approximately
146// 10% of the original value. Note that the difference between the final value
147// of downlink and the original value may be slightly more than 10% due to
148// rounding off. To handle that, the maximum absolute difference allowed is set
149// to a value slightly larger than 10% of the original metric value.
Victor Tan3a676368e2022-11-04 20:52:23150TEST_F(ClientHintsTest, FinalDownlinkWithin10PercentValue) {
Yoav Weiss603be262019-03-04 13:14:57151 EXPECT_NEAR(0.098, RoundKbpsToMbpsForTesting("", 98), 0.1);
152 EXPECT_NEAR(1.023, RoundKbpsToMbpsForTesting("", 1023), 0.2);
153 EXPECT_NEAR(1.193, RoundKbpsToMbpsForTesting("", 1193), 0.2);
154 EXPECT_NEAR(7.523, RoundKbpsToMbpsForTesting("", 7523), 0.9);
155 EXPECT_NEAR(9.999, RoundKbpsToMbpsForTesting("", 9999), 1.2);
156}
157
Victor Tan3a676368e2022-11-04 20:52:23158TEST_F(ClientHintsTest, RttMaxValue) {
Peter Kastinge5a38ed2021-10-02 03:06:35159 EXPECT_GE(3000u, RoundRttForTesting("", base::Milliseconds(1023)));
160 EXPECT_GE(3000u, RoundRttForTesting("", base::Milliseconds(2789)));
161 EXPECT_GE(3000u, RoundRttForTesting("", base::Milliseconds(6023)));
162 EXPECT_EQ(0u, RoundRttForTesting("", base::Milliseconds(1023)) % 50);
163 EXPECT_EQ(0u, RoundRttForTesting("", base::Milliseconds(2789)) % 50);
164 EXPECT_EQ(0u, RoundRttForTesting("", base::Milliseconds(6023)) % 50);
Yoav Weiss603be262019-03-04 13:14:57165}
166
Victor Tan3a676368e2022-11-04 20:52:23167TEST_F(ClientHintsTest, DownlinkMaxValue) {
Yoav Weiss603be262019-03-04 13:14:57168 EXPECT_GE(10.0, RoundKbpsToMbpsForTesting("", 102));
169 EXPECT_GE(10.0, RoundKbpsToMbpsForTesting("", 2102));
170 EXPECT_GE(10.0, RoundKbpsToMbpsForTesting("", 100102));
171 EXPECT_GE(1,
172 static_cast<int>(RoundKbpsToMbpsForTesting("", 102) * 1000) % 50);
173 EXPECT_GE(1,
174 static_cast<int>(RoundKbpsToMbpsForTesting("", 2102) * 1000) % 50);
175 EXPECT_GE(
176 1, static_cast<int>(RoundKbpsToMbpsForTesting("", 100102) * 1000) % 50);
177}
178
Victor Tan3a676368e2022-11-04 20:52:23179TEST_F(ClientHintsTest, RttRandomized) {
Peter Kastinge5a38ed2021-10-02 03:06:35180 const int initial_value =
181 RoundRttForTesting("example.com", base::Milliseconds(1023));
Yoav Weiss603be262019-03-04 13:14:57182 bool network_quality_randomized_by_host = false;
183 // There is a 1/20 chance that the same random noise is selected for two
184 // different hosts. Run this test across 20 hosts to reduce the chances of
185 // test failing to (1/20)^20.
186 for (size_t i = 0; i < 20; ++i) {
Peter Kastinge5a38ed2021-10-02 03:06:35187 int value =
188 RoundRttForTesting(base::NumberToString(i), base::Milliseconds(1023));
Yoav Weiss603be262019-03-04 13:14:57189 // If |value| is different than |initial_value|, it implies that RTT is
190 // randomized by host. This verifies the behavior, and test can be ended.
191 if (value != initial_value)
192 network_quality_randomized_by_host = true;
193 }
194 EXPECT_TRUE(network_quality_randomized_by_host);
195
196 // Calling RoundRttForTesting for same host should return the same result.
197 for (size_t i = 0; i < 20; ++i) {
Peter Kastinge5a38ed2021-10-02 03:06:35198 int value = RoundRttForTesting("example.com", base::Milliseconds(1023));
Yoav Weiss603be262019-03-04 13:14:57199 EXPECT_EQ(initial_value, value);
200 }
201}
202
Victor Tan3a676368e2022-11-04 20:52:23203TEST_F(ClientHintsTest, DownlinkRandomized) {
Yoav Weiss603be262019-03-04 13:14:57204 const int initial_value = RoundKbpsToMbpsForTesting("example.com", 1023);
205 bool network_quality_randomized_by_host = false;
206 // There is a 1/20 chance that the same random noise is selected for two
207 // different hosts. Run this test across 20 hosts to reduce the chances of
208 // test failing to (1/20)^20.
209 for (size_t i = 0; i < 20; ++i) {
210 int value = RoundKbpsToMbpsForTesting(base::NumberToString(i), 1023);
211 // If |value| is different than |initial_value|, it implies that downlink is
212 // randomized by host. This verifies the behavior, and test can be ended.
213 if (value != initial_value)
214 network_quality_randomized_by_host = true;
215 }
216 EXPECT_TRUE(network_quality_randomized_by_host);
217
218 // Calling RoundMbps for same host should return the same result.
219 for (size_t i = 0; i < 20; ++i) {
220 int value = RoundKbpsToMbpsForTesting("example.com", 1023);
221 EXPECT_EQ(initial_value, value);
222 }
223}
224
Victor Tan3a676368e2022-11-04 20:52:23225TEST_F(ClientHintsTest, IntegrationTestsOnParseLookUp) {
Victor Tan3a676368e2022-11-04 20:52:23226 GURL url = GURL(ClientHintsTest::kOriginUrl);
227 contents()->NavigateAndCommit(url);
228 FrameTree& frame_tree = contents()->GetPrimaryFrameTree();
229 FrameTreeNode* main_frame_node = frame_tree.root();
230 AddOneChildNode();
231 FrameTreeNode* sub_frame_node = main_frame_node->child_at(0);
232
233 blink::UserAgentMetadata ua_metadata;
234 MockClientHintsControllerDelegate delegate(ua_metadata);
235
Victor Tanb37a4c82023-06-08 16:18:28236 const auto& all_non_origin_trial_hints_pair = GetAllClientHints();
Victor Tan3a676368e2022-11-04 20:52:23237 const struct {
238 std::string description;
Victor Tan3a676368e2022-11-04 20:52:23239 std::string accept_ch_str;
Keishi Hattoric1b00232022-11-22 09:04:26240 raw_ptr<FrameTreeNode> frame_tree_node;
Arthur Sonzognic686e8f2024-01-11 08:36:37241 std::optional<ClientHintsVector> expect_hints;
Victor Tan3a676368e2022-11-04 20:52:23242 ClientHintsVector expect_commit_hints;
243 } tests[] = {
Victor Tanb37a4c82023-06-08 16:18:28244 {"Persist hints for main frame", "sec-ch-ua-platform, sec-ch-ua-bitness",
245 main_frame_node,
Arthur Sonzognic686e8f2024-01-11 08:36:37246 std::make_optional(ClientHintsVector{WebClientHintsType::kUAPlatform,
247 WebClientHintsType::kUABitness}),
Victor Tan3a676368e2022-11-04 20:52:23248 ClientHintsVector{WebClientHintsType::kUAPlatform,
249 WebClientHintsType::kUABitness}},
Victor Tanb37a4c82023-06-08 16:18:28250 {"No persist hints for sub frame",
Arthur Sonzognic686e8f2024-01-11 08:36:37251 "sec-ch-ua-platform, sec-ch-ua-bitness", sub_frame_node, std::nullopt,
Victor Tan3a676368e2022-11-04 20:52:23252 ClientHintsVector{WebClientHintsType::kUAPlatform,
253 WebClientHintsType::kUABitness}},
Victor Tanb37a4c82023-06-08 16:18:28254 {"All client hints for main frame", all_non_origin_trial_hints_pair.first,
Victor Tan3a676368e2022-11-04 20:52:23255 main_frame_node,
Arthur Sonzognic686e8f2024-01-11 08:36:37256 std::make_optional(all_non_origin_trial_hints_pair.second),
Victor Tan3a676368e2022-11-04 20:52:23257 all_non_origin_trial_hints_pair.second},
Victor Tanb37a4c82023-06-08 16:18:28258 {"All client hints for sub frame", all_non_origin_trial_hints_pair.first,
Arthur Sonzognic686e8f2024-01-11 08:36:37259 sub_frame_node, std::nullopt, all_non_origin_trial_hints_pair.second},
Victor Tan3a676368e2022-11-04 20:52:23260 };
261
262 for (const auto& test : tests) {
263 auto response_headers =
264 base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK\n");
265
Victor Tan3a676368e2022-11-04 20:52:23266 auto actual_hints =
267 ParseAndPersist(url, response_headers.get(), test.accept_ch_str,
268 test.frame_tree_node, &delegate);
269 EXPECT_EQ(test.expect_hints, actual_hints)
270 << "Test case [" << test.description << "]: expected hints "
271 << HintsToString(test.expect_hints) << " but got "
272 << HintsToString(actual_hints) << ".";
273
274 // Verify commit hints.
275 ClientHintsVector actual_commit_hints = LookupAcceptCHForCommit(
276 url::Origin::Create(url), &delegate, test.frame_tree_node);
277 EXPECT_EQ(test.expect_commit_hints, actual_commit_hints)
278 << "Test case [" << test.description << "]: expected commit hints "
279 << HintsToString(test.expect_commit_hints) << " but got "
280 << HintsToString(actual_commit_hints) << ".";
281 }
282}
283
Victor Tanb37a4c82023-06-08 16:18:28284TEST_F(ClientHintsTest, SubFrame) {
Victor Tan969e1572023-05-19 17:49:28285 GURL url = GURL(ClientHintsTest::kOriginUrl);
286 contents()->NavigateAndCommit(url);
287 FrameTree& frame_tree = contents()->GetPrimaryFrameTree();
288 FrameTreeNode* main_frame_node = frame_tree.root();
289 AddOneChildNode();
290 FrameTreeNode* sub_frame_node = main_frame_node->child_at(0);
291
292 blink::UserAgentMetadata ua_metadata;
293 MockClientHintsControllerDelegate delegate(ua_metadata);
294
295 // Persist existing hint to accept-ch cache.
296 ClientHintsVector existing_hints = ClientHintsVector{
297 WebClientHintsType::kUAPlatform, WebClientHintsType::kUABitness};
298 delegate.PersistClientHints(url::Origin::Create(url),
299 main_frame_node->GetParentOrOuterDocument(),
300 existing_hints);
301 auto response_headers =
302 base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK\n");
Victor Tanb37a4c82023-06-08 16:18:28303 std::string accept_ch_str = "sec-ch-ua-platform-version";
Victor Tan969e1572023-05-19 17:49:28304
Victor Tanb37a4c82023-06-08 16:18:28305 // We shouldn't parse accept-ch in subframe, it should not overwrite existing
306 // hints.
Victor Tan969e1572023-05-19 17:49:28307 auto actual_updated_hints = ParseAndPersist(
308 url, response_headers.get(), accept_ch_str, sub_frame_node, &delegate);
309
Arthur Sonzognic686e8f2024-01-11 08:36:37310 EXPECT_EQ(std::nullopt, actual_updated_hints);
Victor Tan969e1572023-05-19 17:49:28311 blink::EnabledClientHints current_hints;
312 delegate.GetAllowedClientHintsFromSource(url::Origin::Create(url),
313 &current_hints);
314 EXPECT_EQ(existing_hints, current_hints.GetEnabledHints());
Victor Tan969e1572023-05-19 17:49:28315}
316
Yoshisato Yanagisawab108f252025-07-24 23:49:04317TEST_F(ClientHintsTest, GetEnabledClientHintsMainFrame) {
318 GURL main_url(kOriginUrl);
319 contents()->NavigateAndCommit(main_url);
320 url::Origin origin = url::Origin::Create(main_url);
Yoshisato Yanagisawa35b1e3632025-06-18 23:37:51321
322 FrameTree& frame_tree = contents()->GetPrimaryFrameTree();
323 FrameTreeNode* main_frame_node = frame_tree.root();
324
325 blink::UserAgentMetadata ua_metadata;
326 MockClientHintsControllerDelegate delegate(ua_metadata);
327
328 std::vector<network::mojom::WebClientHintsType> expected_types = {
329 network::mojom::WebClientHintsType::kUAArch,
330 network::mojom::WebClientHintsType::kUAWoW64,
331 };
332 delegate.SetAdditionalClientHints(expected_types);
333 // Add default ClientHints.
334 for (const auto& [hint, _] : network::GetClientHintToNameMap()) {
335 if (blink::IsClientHintSentByDefault(hint)) {
336 expected_types.push_back(hint);
337 }
338 }
Yoshisato Yanagisawab108f252025-07-24 23:49:04339
340 const auto& actual_hints =
Yoshisato Yanagisawa2b6d25a2025-07-08 04:23:39341 GetEnabledClientHints(origin, main_frame_node, &delegate);
Yoshisato Yanagisawa35b1e3632025-06-18 23:37:51342
Yoshisato Yanagisawab108f252025-07-24 23:49:04343 EXPECT_EQ(origin, actual_hints.origin);
344 EXPECT_TRUE(actual_hints.is_outermost_main_frame);
Yoshisato Yanagisawa35b1e3632025-06-18 23:37:51345 // We do not care the order of contents.
Yoshisato Yanagisawab108f252025-07-24 23:49:04346 EXPECT_THAT(actual_hints.hints,
347 testing::UnorderedElementsAreArray(expected_types));
348}
349
350TEST_F(ClientHintsTest, GetEnabledClientHintsSubframe) {
351 GURL main_url(kOriginUrl);
352 contents()->NavigateAndCommit(main_url);
353 url::Origin origin = url::Origin::Create(main_url);
354
355 FrameTree& frame_tree = contents()->GetPrimaryFrameTree();
356 FrameTreeNode* main_frame_node = frame_tree.root();
357 AddOneChildNode();
358 FrameTreeNode* sub_frame_node = main_frame_node->child_at(0);
359
360 blink::UserAgentMetadata ua_metadata;
361 MockClientHintsControllerDelegate delegate(ua_metadata);
362
363 // Add default ClientHints.
364 std::vector<network::mojom::WebClientHintsType> expected_types;
365 for (const auto& [hint, _] : network::GetClientHintToNameMap()) {
366 if (blink::IsClientHintSentByDefault(hint)) {
367 expected_types.push_back(hint);
368 }
369 }
370
371 // The origin passed to GetEnabledClientHints is the resource origin.
372 // The origin in the result is the main frame origin.
373 GURL sub_url("https://sub.example.com");
374 const auto& actual_hints = GetEnabledClientHints(url::Origin::Create(sub_url),
375 sub_frame_node, &delegate);
376
377 EXPECT_EQ(origin, actual_hints.origin);
378 EXPECT_FALSE(actual_hints.is_outermost_main_frame);
379 // Just check if the hints has the default low entropy client hints.
380 // We do not care the order of contents.
381 EXPECT_THAT(actual_hints.hints,
382 testing::UnorderedElementsAreArray(expected_types));
Yoshisato Yanagisawa35b1e3632025-06-18 23:37:51383}
384
Victor Tance1f6fb2025-08-22 01:11:02385TEST_F(ClientHintsTest, AddPrefetchNavigationRequestClientHintsHeaders) {
386 GURL url = GURL(ClientHintsTest::kOriginUrl);
387 url::Origin origin = url::Origin::Create(url);
388
389 blink::UserAgentMetadata ua_metadata;
390 ua_metadata.architecture = "x86";
391 MockClientHintsControllerDelegate delegate(ua_metadata);
392
393 // Enable a variety of hints.
394 delegate.SetAdditionalClientHints(
395 {WebClientHintsType::kDeviceMemory, WebClientHintsType::kDpr,
396 WebClientHintsType::kUAArch, WebClientHintsType::kPrefersColorScheme});
397
398 net::HttpRequestHeaders headers;
399 AddPrefetchNavigationRequestClientHintsHeaders(origin, &headers,
400 browser_context(), &delegate,
401 /*is_ua_override_on=*/false);
402
403 // Low-entropy UA hints are sent by default.
404 EXPECT_TRUE(headers.HasHeader("sec-ch-ua"));
405 EXPECT_TRUE(headers.HasHeader("sec-ch-ua-mobile"));
406
407 // High-entropy hints that were enabled.
408 std::optional<std::string> header_value = headers.GetHeader("sec-ch-ua-arch");
409 EXPECT_TRUE(header_value.has_value());
410 EXPECT_EQ("\"x86\"", header_value.value());
411
412 EXPECT_TRUE(headers.HasHeader("sec-ch-device-memory"));
413 EXPECT_TRUE(headers.HasHeader("sec-ch-dpr"));
414
415 // Prefers-color-scheme depends on a frame, which is null for prefetch.
416 EXPECT_FALSE(headers.HasHeader("sec-ch-prefers-color-scheme"));
417
418 // Verify hints not in the requested list.
419 EXPECT_FALSE(headers.HasHeader("sec-ch-ua-bitness"));
420}
421
422TEST_F(ClientHintsTest, AddNavigationRequestClientHintsHeaders_MainFrame) {
423 GURL main_url(kOriginUrl);
424 contents()->NavigateAndCommit(main_url);
425 url::Origin origin = url::Origin::Create(main_url);
426 FrameTreeNode* main_frame_node = contents()->GetPrimaryFrameTree().root();
427
428 blink::UserAgentMetadata ua_metadata;
429 ua_metadata.architecture = "x86";
430 MockClientHintsControllerDelegate delegate(ua_metadata);
431
432 // Enable a variety of hints.
433 delegate.SetAdditionalClientHints(
434 {WebClientHintsType::kDeviceMemory, WebClientHintsType::kDpr,
435 WebClientHintsType::kUAArch, WebClientHintsType::kPrefersColorScheme});
436
437 net::HttpRequestHeaders headers;
438 AddNavigationRequestClientHintsHeaders(
439 origin, &headers, browser_context(), &delegate,
440 /*is_ua_override_on=*/false, main_frame_node,
441 /*container_policy=*/{});
442
443 // Low-entropy UA hints are sent by default.
444 EXPECT_TRUE(headers.HasHeader("sec-ch-ua"));
445 EXPECT_TRUE(headers.HasHeader("sec-ch-ua-mobile"));
446
447 // High-entropy hints that were enabled.
448 std::optional<std::string> header_value = headers.GetHeader("sec-ch-ua-arch");
449 EXPECT_TRUE(header_value.has_value());
450 EXPECT_EQ("\"x86\"", header_value.value());
451
452 EXPECT_TRUE(headers.HasHeader("sec-ch-device-memory"));
453 EXPECT_TRUE(headers.HasHeader("sec-ch-dpr"));
454
455 // Prefers-color-scheme should have value for navigation request.
456 EXPECT_TRUE(headers.HasHeader("sec-ch-prefers-color-scheme"));
457
458 // Verify hints not in the requested list.
459 EXPECT_FALSE(headers.HasHeader("sec-ch-ua-bitness"));
460}
461
462TEST_F(ClientHintsTest, AddNavigationRequestClientHintsHeaders_Subframe) {
463 GURL url = GURL(ClientHintsTest::kOriginUrl);
464 contents()->NavigateAndCommit(url);
465 FrameTree& frame_tree = contents()->GetPrimaryFrameTree();
466 FrameTreeNode* main_frame_node = frame_tree.root();
467 AddOneChildNode();
468 FrameTreeNode* sub_frame_node = main_frame_node->child_at(0);
469
470 GURL subframe_url("https://sub.example.com");
471 url::Origin subframe_origin = url::Origin::Create(subframe_url);
472
473 blink::UserAgentMetadata ua_metadata;
474 MockClientHintsControllerDelegate delegate(ua_metadata);
475
476 // Enable a hint that requires delegation.
477 delegate.SetAdditionalClientHints({WebClientHintsType::kDeviceMemory});
478
479 net::HttpRequestHeaders headers;
480 // Case 1: No delegation policy. Hint should not be added.
481 AddNavigationRequestClientHintsHeaders(
482 subframe_origin, &headers, browser_context(), &delegate,
483 /*is_ua_override_on=*/false, sub_frame_node,
484 /*container_policy=*/{});
485 EXPECT_FALSE(headers.HasHeader("sec-ch-device-memory"));
486
487 // Case 2: With delegation policy. Hint should be added.
488 headers.Clear();
489 network::ParsedPermissionsPolicy container_policy;
490 network::ParsedPermissionsPolicyDeclaration decl(
491 network::mojom::PermissionsPolicyFeature::kClientHintDeviceMemory,
492 {*network::OriginWithPossibleWildcards::FromOrigin(subframe_origin)},
493 /*self_if_matches=*/std::nullopt,
494 /*matches_all_origins=*/false, /*matches_opaque_src=*/false);
495 container_policy.push_back(std::move(decl));
496
497 AddNavigationRequestClientHintsHeaders(
498 subframe_origin, &headers, browser_context(), &delegate,
499 /*is_ua_override_on=*/false, sub_frame_node, container_policy);
500
501 EXPECT_TRUE(headers.HasHeader("sec-ch-device-memory"));
502}
503
Yoshisato Yanagisawa56a1d2f2025-08-22 09:56:55504TEST_F(ClientHintsTest, GetCriticalHintsMissingStatus) {
505 const GURL main_url(kOriginUrl);
506 contents()->NavigateAndCommit(main_url);
507 const url::Origin origin = url::Origin::Create(main_url);
508
509 FrameTree& frame_tree = contents()->GetPrimaryFrameTree();
510 FrameTreeNode* main_frame_node = frame_tree.root();
511
512 blink::UserAgentMetadata ua_metadata;
513 MockClientHintsControllerDelegate delegate(ua_metadata);
514
515 const ClientHintsVector critical_hints = {WebClientHintsType::kDeviceMemory,
516 WebClientHintsType::kDpr};
517
518 // No hints persisted.
519 EXPECT_EQ(GetCriticalHintsMissingStatus(origin, main_frame_node, &delegate,
520 critical_hints),
521 CriticalHintsMissingStatus::kMissing);
522
523 // One hint persisted.
524 delegate.PersistClientHints(origin,
525 main_frame_node->GetParentOrOuterDocument(),
526 {WebClientHintsType::kDeviceMemory});
527 EXPECT_EQ(GetCriticalHintsMissingStatus(origin, main_frame_node, &delegate,
528 critical_hints),
529 CriticalHintsMissingStatus::kMissing);
530
531 // All hints persisted.
532 delegate.PersistClientHints(
533 origin, main_frame_node->GetParentOrOuterDocument(), critical_hints);
534 EXPECT_EQ(GetCriticalHintsMissingStatus(origin, main_frame_node, &delegate,
535 critical_hints),
536 CriticalHintsMissingStatus::kPresent);
537}
538
Yoav Weiss603be262019-03-04 13:14:57539} // namespace content