Reland "Add Sec-CH-UA-Reduced client hint header when Origin Trial token present"
This reverts commit f6370c35ee4f448247485c6e875d3c60679d0454.
Reason for revert: The original CL (crrev.com/c/3042810) started the EmbeddedTestServer on a specific port in the UaReducedOriginTrialBrowserTest, since the test used an Origin Trial token that is bound to a specific origin. This caused the test to fail on CI for linux-chromeos-chrome, due to being unable to start EmbeddedTestServer as the specific port was already in-use, and the original CL was reverted in crrev.com/c/3061119. This CL relands the original CL and fixes the problem by using URLLoaderInterceptor instead of EmbeddedTestServer in the UaReducedOriginTrialBrowserTest tests.
Original change's description:
> Revert "Add Sec-CH-UA-Reduced client hint header when Origin Trial token present"
>
> This reverts commit d9c057ea37355847f0c9530eada84aca335c96b4.
>
> Reason for revert: Browser tests is failing on linux-chromeos-chrome.
> See [1].
>
> [1] https://ci.chromium.org/ui/p/chrome/builders/ci/linux-chromeos-chrome/16242/overview
>
> Original change's description:
> > Add Sec-CH-UA-Reduced client hint header when Origin Trial token present
> >
> > In this CL, we add logic to parse and accept the Sec-CH-UA-Reduced
> > client hint in the Accept-CH header and and only send the
> > Sec-CH-UA-Reduced client hint only in the presence of a valid
> > UserAgentReduction Origin Trial token.
> >
> > If the Origin Trial token is present and valid, and the origin
> > contains Sec-CH-UA-Reduced in the Accept-CH cache, then on all
> > subsequent requests to the origin, the Sec-CH-UA-Reduced request
> > header will be set with a value of "?1" (sh-boolean). If the
> > Accept-CH cache does not contain Sec-CH-UA-Reduced, or the Origin
> > Trial token is not valid, then Sec-CH-UA-Reduced will not be sent
> > in the request headers.
> >
> > NB: A subsequent CL will change the User-Agent request header to
> > the reduced UA string if the Sec-CH-UA-Reduced header is sent.
> >
> > Chrome Platform Status: https://chromestatus.com/feature/5704553745874944
> > Design Doc: https://docs.google.com/document/d/1feIxK9S7oNgT2oGGebbxE9X0O-4wTKcsP_gRaY99tq4
> >
> > Bug: 1222742
> > Change-Id: If855d4bb393540d49de3be80aa9bc7c80f861c50
> > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3042810
> > Commit-Queue: Ali Beyad <[email protected]>
> > Reviewed-by: Aaron Tagliaboschi <[email protected]>
> > Reviewed-by: Robert Kroeger <[email protected]>
> > Reviewed-by: Mike West <[email protected]>
> > Cr-Commit-Position: refs/heads/master@{#906810}
>
> Bug: 1222742
> Change-Id: I0d8aab1d56d78f8323d4a3e3ed88b6eef370e1b9
> No-Presubmit: true
> No-Tree-Checks: true
> No-Try: true
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3061119
> Auto-Submit: Daniel Hosseinian <[email protected]>
> Commit-Queue: Rubber Stamper <[email protected]>
> Bot-Commit: Rubber Stamper <[email protected]>
> Owners-Override: Daniel Hosseinian <[email protected]>
> Cr-Commit-Position: refs/heads/master@{#906884}
Bug: 1222742
Change-Id: I3ed39697255116fe1f2ab5203331e8786e9d66a3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3064532
Commit-Queue: Ali Beyad <[email protected]>
Reviewed-by: Kinuko Yasuda <[email protected]>
Reviewed-by: Aaron Tagliaboschi <[email protected]>
Reviewed-by: Daniel Hosseinian <[email protected]>
Cr-Commit-Position: refs/heads/master@{#909169}
diff --git a/chrome/browser/client_hints/client_hints_browsertest.cc b/chrome/browser/client_hints/client_hints_browsertest.cc
index c44f7f87..5f3ef9fd 100644
--- a/chrome/browser/client_hints/client_hints_browsertest.cc
+++ b/chrome/browser/client_hints/client_hints_browsertest.cc
@@ -11,8 +11,10 @@
#include "base/containers/contains.h"
#include "base/metrics/field_trial_param_associator.h"
#include "base/run_loop.h"
+#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/synchronization/lock.h"
+#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "build/build_config.h"
#include "chrome/browser/content_settings/cookie_settings_factory.h"
@@ -28,17 +30,22 @@
#include "components/content_settings/core/browser/cookie_settings.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/pref_names.h"
+#include "components/embedder_support/switches.h"
#include "components/embedder_support/user_agent_utils.h"
#include "components/metrics/content/subprocess_metrics_provider.h"
#include "components/page_load_metrics/browser/page_load_metrics_test_waiter.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/web_contents_observer.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test.h"
+#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "content/public/test/url_loader_interceptor.h"
#include "net/dns/mock_host_resolver.h"
@@ -51,8 +58,11 @@
#include "services/network/public/cpp/cors/cors.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/network_switches.h"
+#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/mojom/web_client_hints_types.mojom-shared.h"
+#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gmock/include/gmock/gmock.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/common/client_hints/client_hints.h"
#include "third_party/blink/public/common/web_preferences/web_preferences.h"
@@ -692,8 +702,10 @@
continue;
}
- // TODO(crbug.com/1226193): Remove this block when support for
- // `Sec-CH-UA-Reduced` has been added.
+ // Skip over the `Sec-CH-UA-Reduced` client hint because it is only added
+ // in the presence of a valid "UserAgentReduction" Origin Trial token.
+ // `Sec-CH-UA-Reduced` is tested via UaReducedOriginTrialBrowserTest
+ // below.
if (std::string(blink::kClientHintsHeaderMapping[i]) ==
"sec-ch-ua-reduced") {
continue;
@@ -2475,3 +2487,184 @@
ui_test_utils::NavigateToURL(browser(), test_url());
EXPECT_EQ(prefers_color_scheme_observed(), "dark");
}
+
+// Tests that the Sec-CH-UA-Reduced client hint is sent if and only if the
+// UserAgentReduction Origin Trial token is present and valid in the response
+// headers.
+//
+// The test Origin Trial token found in the test files was generated by running
+// (in tools/origin_trials):
+// generate_token.py https://127.0.0.1:44444 UserAgentReduction
+// --expire-timestamp=2000000000
+//
+// The Origin Trial token expires in 2033. Generate a new token by then, or
+// find a better way to re-generate a test trial token.
+class UaReducedOriginTrialBrowserTest : public InProcessBrowserTest {
+ public:
+ UaReducedOriginTrialBrowserTest() = default;
+
+ // The URL that was used to register for the Origin Trial token.
+ static constexpr const char kOriginUrl[] = "https://127.0.0.1:44444";
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ // The public key for the default privatey key used by the
+ // tools/origin_trials/generate_token.py tool.
+ static constexpr char kOriginTrialTestPublicKey[] =
+ "dRCs+TocuKkocNKa0AtZ4awrt9XKH2SQCI6o4FY6BNA=";
+ command_line->AppendSwitchASCII(embedder_support::kOriginTrialPublicKey,
+ kOriginTrialTestPublicKey);
+ }
+
+ void SetUp() override {
+ std::unique_ptr<base::FeatureList> feature_list =
+ std::make_unique<base::FeatureList>();
+ feature_list->InitializeFromCommandLine("CriticalClientHint", "");
+ scoped_feature_list_.InitWithFeatureList(std::move(feature_list));
+
+ InProcessBrowserTest::SetUp();
+ }
+
+ void SetUpOnMainThread() override {
+ InProcessBrowserTest::SetUpOnMainThread();
+
+ // We use a URLLoaderInterceptor, rather than the EmbeddedTestServer, since
+ // the origin trial token in the response is associated with a fixed
+ // origin, whereas EmbeddedTestServer serves content on a random port.
+ url_loader_interceptor_ =
+ content::URLLoaderInterceptor::ServeFilesFromDirectoryAtOrigin(
+ "chrome/test/data/client_hints", GURL(kOriginUrl));
+ }
+
+ void TearDownOnMainThread() override {
+ url_loader_interceptor_.reset();
+ InProcessBrowserTest::TearDownOnMainThread();
+ }
+
+ GURL ua_reduced_with_valid_origin_trial_token_url() const {
+ return GURL(base::StrCat(
+ {kOriginUrl, "/accept_ch_ua_reduced_with_valid_origin_trial.html"}));
+ }
+
+ GURL ua_reduced_with_invalid_origin_trial_token_url() const {
+ return GURL(base::StrCat(
+ {kOriginUrl, "/accept_ch_ua_reduced_with_invalid_origin_trial.html"}));
+ }
+
+ GURL ua_reduced_with_no_origin_trial_token_url() const {
+ return GURL(base::StrCat(
+ {kOriginUrl, "/accept_ch_ua_reduced_with_no_origin_trial.html"}));
+ }
+
+ GURL ua_reduced_missing_with_valid_origin_trial_token_url() const {
+ return GURL(base::StrCat(
+ {kOriginUrl, "/accept_ch_ua_reduced_missing_valid_origin_trial.html"}));
+ }
+
+ GURL critical_ch_ua_reduced_with_valid_origin_trial_token_url() const {
+ return GURL(base::StrCat(
+ {kOriginUrl, "/critical_ch_ua_reduced_with_valid_origin_trial.html"}));
+ }
+
+ GURL critical_ch_ua_reduced_with_invalid_origin_trial_token_url() const {
+ return GURL(base::StrCat(
+ {kOriginUrl,
+ "/critical_ch_ua_reduced_with_invalid_origin_trial.html"}));
+ }
+
+ void NavigateAndCheckHeader(const GURL& url,
+ const bool ch_ua_reduced_expected) {
+ ui_test_utils::NavigateToURL(browser(), url);
+ base::RunLoop().RunUntilIdle();
+
+ std::string header_value;
+ const bool ch_ua_reduced =
+ url_loader_interceptor_->GetLastRequestHeaders().GetHeader(
+ "sec-ch-ua-reduced", &header_value);
+
+ EXPECT_EQ(ch_ua_reduced, ch_ua_reduced_expected);
+ if (ch_ua_reduced_expected) {
+ EXPECT_EQ(header_value, "?1");
+ }
+ }
+
+ void NavigateTwiceAndCheckHeader(const GURL& url,
+ const bool ch_ua_reduced_expected,
+ const bool critical_ch_ua_reduced_expected) {
+ // If Critical-CH is set, we expect Sec-CH-UA-Reduced in the first
+ // navigation request header. If Critical-CH is not set, we don't expect
+ // Sec-CH-UA-Reduced in the first navigation request.
+ NavigateAndCheckHeader(
+ url, critical_ch_ua_reduced_expected && ch_ua_reduced_expected);
+
+ // Regardless of the Critical-CH setting, we expect the Sec-CH-UA-Reduced
+ // client hint sent on the second request, if Sec-CH-UA-Reduced is set and
+ // the Origin Trial token is valid.
+ NavigateAndCheckHeader(url, ch_ua_reduced_expected);
+ }
+
+ private:
+ std::unique_ptr<content::URLLoaderInterceptor> url_loader_interceptor_;
+ base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+constexpr const char UaReducedOriginTrialBrowserTest::kOriginUrl[];
+
+IN_PROC_BROWSER_TEST_F(UaReducedOriginTrialBrowserTest,
+ AcceptChUaReducedWithValidOriginTrialToken) {
+ NavigateTwiceAndCheckHeader(ua_reduced_with_valid_origin_trial_token_url(),
+ /*ch_ua_reduced_expected=*/true,
+ /*critical_ch_ua_reduced_expected=*/false);
+}
+
+IN_PROC_BROWSER_TEST_F(UaReducedOriginTrialBrowserTest,
+ AcceptChUaReducedWithInvalidOriginTrialToken) {
+ // The response contained Sec-CH-UA-Reduced in the Accept-CH header, but the
+ // origin trial token is invalid.
+ NavigateTwiceAndCheckHeader(ua_reduced_with_invalid_origin_trial_token_url(),
+ /*ch_ua_reduced_expected=*/false,
+ /*critical_ch_ua_reduced_expected=*/false);
+}
+
+IN_PROC_BROWSER_TEST_F(UaReducedOriginTrialBrowserTest,
+ AcceptChUaReducedWithNoOriginTrialToken) {
+ // The response contained Sec-CH-UA-Reduced in the Accept-CH header, but the
+ // origin trial token is not present.
+ NavigateTwiceAndCheckHeader(ua_reduced_with_no_origin_trial_token_url(),
+ /*ch_ua_reduced_expected=*/false,
+ /*critical_ch_ua_reduced_expected=*/false);
+}
+
+IN_PROC_BROWSER_TEST_F(UaReducedOriginTrialBrowserTest,
+ NoAcceptChUaReducedWithValidOriginTrialToken) {
+ // The response contained a valid Origin Trial token, but no Sec-CH-UA-Reduced
+ // in the Accept-CH header.
+ NavigateTwiceAndCheckHeader(
+ ua_reduced_missing_with_valid_origin_trial_token_url(),
+ /*ch_ua_reduced_expected=*/false,
+ /*critical_ch_ua_reduced_expected=*/false);
+}
+
+IN_PROC_BROWSER_TEST_F(UaReducedOriginTrialBrowserTest,
+ CriticalChUaReducedWithValidOriginTrialToken) {
+ // The initial navigation also contains the Critical-CH header, so the
+ // Sec-CH-UA-Reduced header should be set after the first navigation.
+ NavigateTwiceAndCheckHeader(
+ critical_ch_ua_reduced_with_valid_origin_trial_token_url(),
+ /*ch_ua_reduced_expected=*/true,
+ /*critical_ch_ua_reduced_expected=*/true);
+}
+
+IN_PROC_BROWSER_TEST_F(UaReducedOriginTrialBrowserTest,
+ CriticalChUaReducedWithInvalidOriginTrialToken) {
+ // The Origin Trial token is invalid, so the Critical-CH should not have
+ // resulted in the Sec-CH-UA-Reduced header being sent.
+ NavigateTwiceAndCheckHeader(
+ critical_ch_ua_reduced_with_invalid_origin_trial_token_url(),
+ /*ch_ua_reduced_expected=*/false,
+ /*critical_ch_ua_reduced_expected=*/false);
+}
+
+IN_PROC_BROWSER_TEST_F(UaReducedOriginTrialBrowserTest,
+ ThirdPartyUaReducedWithValidOriginTrialToken) {
+ // TODO(crbug.com/1222742): Implement this test.
+}
diff --git a/chrome/test/data/client_hints/accept_ch_ua_reduced_missing_valid_origin_trial.html b/chrome/test/data/client_hints/accept_ch_ua_reduced_missing_valid_origin_trial.html
new file mode 100644
index 0000000..c340ceb
--- /dev/null
+++ b/chrome/test/data/client_hints/accept_ch_ua_reduced_missing_valid_origin_trial.html
@@ -0,0 +1,5 @@
+<html>
+<link rel="icon" href="data:;base64,=">
+<head></head>
+The Origin Trial token is valid, but the Sec-CH-UA-Reduced header is not present.
+</html>
diff --git a/chrome/test/data/client_hints/accept_ch_ua_reduced_missing_valid_origin_trial.html.mock-http-headers b/chrome/test/data/client_hints/accept_ch_ua_reduced_missing_valid_origin_trial.html.mock-http-headers
new file mode 100644
index 0000000..00c98741
--- /dev/null
+++ b/chrome/test/data/client_hints/accept_ch_ua_reduced_missing_valid_origin_trial.html.mock-http-headers
@@ -0,0 +1,4 @@
+HTTP/1.1 200 OK
+Content-Type: text/html
+Accept-CH: sec-ch-ua-full-version
+Origin-Trial: A93QtcQ0CRKf5ioPasUwNbweXQWgbI4ZEshiz+YS7dkQEWVfW9Ua2pTnA866sZwRzuElkPwsUdGdIaW0fRUP8AwAAABceyJvcmlnaW4iOiAiaHR0cHM6Ly8xMjcuMC4wLjE6NDQ0NDQiLCAiZmVhdHVyZSI6ICJVc2VyQWdlbnRSZWR1Y3Rpb24iLCAiZXhwaXJ5IjogMjAwMDAwMDAwMH0=
diff --git a/chrome/test/data/client_hints/accept_ch_ua_reduced_with_invalid_origin_trial.html b/chrome/test/data/client_hints/accept_ch_ua_reduced_with_invalid_origin_trial.html
new file mode 100644
index 0000000..d291bbe2
--- /dev/null
+++ b/chrome/test/data/client_hints/accept_ch_ua_reduced_with_invalid_origin_trial.html
@@ -0,0 +1,7 @@
+<html>
+<link rel="icon" href="data:;base64,=">
+<head></head>
+Empty file which uses link-rel to disable favicon fetches. The corresponding
+.mock-http-headers sets client hints. The Origin Trial token in the headers
+file is a corruption of the valid Origin Trial token.
+</html>
diff --git a/chrome/test/data/client_hints/accept_ch_ua_reduced_with_invalid_origin_trial.html.mock-http-headers b/chrome/test/data/client_hints/accept_ch_ua_reduced_with_invalid_origin_trial.html.mock-http-headers
new file mode 100644
index 0000000..f9543f9
--- /dev/null
+++ b/chrome/test/data/client_hints/accept_ch_ua_reduced_with_invalid_origin_trial.html.mock-http-headers
@@ -0,0 +1,4 @@
+HTTP/1.1 200 OK
+Content-Type: text/html
+Accept-CH: sec-ch-ua-full-version,sec-ch-ua-reduced
+Origin-Trial: A23QtcQ0CRKf5ioPasUwNbweXQWgbI4ZEshiz+YS7dkQEWVfW9Ua2pTnA866sZwRzuElkPwsUdGdIaW0fRUP8AwAAABceyJvcmlnbW4iOiAiaHR0cHM6Ly8xMjcuMC4wLjE6NDQ0NDQiLCAiZmVhdHVyZSI6ICJVc2VyQWdlbnRSZWR1Y3Rpb24iLCAiZXhwaXJ5IjogMjAwMDAwMDAwMH0=
diff --git a/chrome/test/data/client_hints/accept_ch_ua_reduced_with_no_origin_trial.html b/chrome/test/data/client_hints/accept_ch_ua_reduced_with_no_origin_trial.html
new file mode 100644
index 0000000..8988d58
--- /dev/null
+++ b/chrome/test/data/client_hints/accept_ch_ua_reduced_with_no_origin_trial.html
@@ -0,0 +1,8 @@
+<html>
+<link rel="icon" href="data:;base64,=">
+<head></head>
+Empty file which uses link-rel to disable favicon fetches. The corresponding
+.mock-http-headers sets client hints. The Origin Trial token in the headers
+file is missing, even though the Sec-CH-UA-Reduced client hint is present in
+the Accept-CH header.
+</html>
diff --git a/chrome/test/data/client_hints/accept_ch_ua_reduced_with_no_origin_trial.html.mock-http-headers b/chrome/test/data/client_hints/accept_ch_ua_reduced_with_no_origin_trial.html.mock-http-headers
new file mode 100644
index 0000000..ec93464
--- /dev/null
+++ b/chrome/test/data/client_hints/accept_ch_ua_reduced_with_no_origin_trial.html.mock-http-headers
@@ -0,0 +1,3 @@
+HTTP/1.1 200 OK
+Content-Type: text/html
+Accept-CH: sec-ch-ua-full-version,sec-ch-ua-reduced
diff --git a/chrome/test/data/client_hints/accept_ch_ua_reduced_with_valid_origin_trial.html b/chrome/test/data/client_hints/accept_ch_ua_reduced_with_valid_origin_trial.html
new file mode 100644
index 0000000..db1b984
--- /dev/null
+++ b/chrome/test/data/client_hints/accept_ch_ua_reduced_with_valid_origin_trial.html
@@ -0,0 +1,8 @@
+<html>
+<link rel="icon" href="data:;base64,=">
+<head></head>
+Empty file which uses link-rel to disable favicon fetches. The corresponding
+.mock-http-headers sets client hints. The Origin Trial token was generated by
+running (in tools/origin_trials):
+generate_token.py https://127.0.0.1:44444 UserAgentReduction --expire-timestamp=2000000000
+</html>
diff --git a/chrome/test/data/client_hints/accept_ch_ua_reduced_with_valid_origin_trial.html.mock-http-headers b/chrome/test/data/client_hints/accept_ch_ua_reduced_with_valid_origin_trial.html.mock-http-headers
new file mode 100644
index 0000000..1d3bf6e
--- /dev/null
+++ b/chrome/test/data/client_hints/accept_ch_ua_reduced_with_valid_origin_trial.html.mock-http-headers
@@ -0,0 +1,4 @@
+HTTP/1.1 200 OK
+Content-Type: text/html
+Accept-CH: sec-ch-ua-full-version,sec-ch-ua-reduced
+Origin-Trial: A93QtcQ0CRKf5ioPasUwNbweXQWgbI4ZEshiz+YS7dkQEWVfW9Ua2pTnA866sZwRzuElkPwsUdGdIaW0fRUP8AwAAABceyJvcmlnaW4iOiAiaHR0cHM6Ly8xMjcuMC4wLjE6NDQ0NDQiLCAiZmVhdHVyZSI6ICJVc2VyQWdlbnRSZWR1Y3Rpb24iLCAiZXhwaXJ5IjogMjAwMDAwMDAwMH0=
diff --git a/chrome/test/data/client_hints/critical_ch_ua_reduced_with_invalid_origin_trial.html b/chrome/test/data/client_hints/critical_ch_ua_reduced_with_invalid_origin_trial.html
new file mode 100644
index 0000000..9666ba7
--- /dev/null
+++ b/chrome/test/data/client_hints/critical_ch_ua_reduced_with_invalid_origin_trial.html
@@ -0,0 +1,7 @@
+<html>
+<link rel="icon" href="data:;base64,=">
+<head></head>
+Empty file which uses link-rel to disable favicon fetches. The corresponding
+.mock-http-headers sets client hints. The Origin Trial token is a corrupted
+version of the original.
+</html>
diff --git a/chrome/test/data/client_hints/critical_ch_ua_reduced_with_invalid_origin_trial.html.mock-http-headers b/chrome/test/data/client_hints/critical_ch_ua_reduced_with_invalid_origin_trial.html.mock-http-headers
new file mode 100644
index 0000000..8452ef6
--- /dev/null
+++ b/chrome/test/data/client_hints/critical_ch_ua_reduced_with_invalid_origin_trial.html.mock-http-headers
@@ -0,0 +1,5 @@
+HTTP/1.1 200 OK
+Content-Type: text/html
+Accept-CH: sec-ch-ua-full-version,sec-ch-ua-reduced
+Critical-CH: sec-ch-ua-reduced
+Origin-Trial: A23QtcQ0CRKf5ioPasUwNbweXQWgbI4ZEshiz+YS7dkQEWVfW9Ua2pTnA866sZwRzsElkPwsUdGdIaW0fRUP8AwAAABceyJvcmlnaW4iOiAiaHR0cHM6Ly8xMjcuMC4wLjE6NDQ0NDQiLCAiZmVhdHVyZSI6ICJVc2VyQWdlbnRSZWR1Y3Rpb24iLCAiZXhwaXJ5IjogMjAwMDAwMDAwMH0=
diff --git a/chrome/test/data/client_hints/critical_ch_ua_reduced_with_valid_origin_trial.html b/chrome/test/data/client_hints/critical_ch_ua_reduced_with_valid_origin_trial.html
new file mode 100644
index 0000000..db1b984
--- /dev/null
+++ b/chrome/test/data/client_hints/critical_ch_ua_reduced_with_valid_origin_trial.html
@@ -0,0 +1,8 @@
+<html>
+<link rel="icon" href="data:;base64,=">
+<head></head>
+Empty file which uses link-rel to disable favicon fetches. The corresponding
+.mock-http-headers sets client hints. The Origin Trial token was generated by
+running (in tools/origin_trials):
+generate_token.py https://127.0.0.1:44444 UserAgentReduction --expire-timestamp=2000000000
+</html>
diff --git a/chrome/test/data/client_hints/critical_ch_ua_reduced_with_valid_origin_trial.html.mock-http-headers b/chrome/test/data/client_hints/critical_ch_ua_reduced_with_valid_origin_trial.html.mock-http-headers
new file mode 100644
index 0000000..426c2fd
--- /dev/null
+++ b/chrome/test/data/client_hints/critical_ch_ua_reduced_with_valid_origin_trial.html.mock-http-headers
@@ -0,0 +1,5 @@
+HTTP/1.1 200 OK
+Content-Type: text/html
+Accept-CH: sec-ch-ua-full-version,sec-ch-ua-reduced
+Critical-CH: sec-ch-ua-reduced
+Origin-Trial: A93QtcQ0CRKf5ioPasUwNbweXQWgbI4ZEshiz+YS7dkQEWVfW9Ua2pTnA866sZwRzuElkPwsUdGdIaW0fRUP8AwAAABceyJvcmlnaW4iOiAiaHR0cHM6Ly8xMjcuMC4wLjE6NDQ0NDQiLCAiZmVhdHVyZSI6ICJVc2VyQWdlbnRSZWR1Y3Rpb24iLCAiZXhwaXJ5IjogMjAwMDAwMDAwMH0=
diff --git a/content/browser/client_hints/client_hints.cc b/content/browser/client_hints/client_hints.cc
index 4ed9891..876c026 100644
--- a/content/browser/client_hints/client_hints.cc
+++ b/content/browser/client_hints/client_hints.cc
@@ -18,6 +18,7 @@
#include "content/browser/devtools/devtools_instrumentation.h"
#include "content/browser/renderer_host/frame_tree.h"
#include "content/browser/renderer_host/frame_tree_node.h"
+#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/navigator.h"
#include "content/browser/renderer_host/navigator_delegate.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
@@ -28,6 +29,8 @@
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "net/base/url_util.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_response_headers.h"
#include "net/http/structured_headers.h"
#include "net/nqe/effective_connection_type.h"
#include "net/nqe/network_quality_estimator_params.h"
@@ -47,6 +50,8 @@
namespace content {
namespace {
+using ::network::mojom::WebClientHintsType;
+
uint8_t randomization_salt = 0;
constexpr size_t kMaxRandomNumbers = 21;
@@ -197,7 +202,7 @@
}
void SetHeaderToDouble(net::HttpRequestHeaders* headers,
- network::mojom::WebClientHintsType client_hint_type,
+ WebClientHintsType client_hint_type,
double value) {
headers->SetHeader(
blink::kClientHintsHeaderMapping[static_cast<int>(client_hint_type)],
@@ -205,7 +210,7 @@
}
void SetHeaderToInt(net::HttpRequestHeaders* headers,
- network::mojom::WebClientHintsType client_hint_type,
+ WebClientHintsType client_hint_type,
double value) {
headers->SetHeader(
blink::kClientHintsHeaderMapping[static_cast<int>(client_hint_type)],
@@ -213,14 +218,14 @@
}
void SetHeaderToString(net::HttpRequestHeaders* headers,
- network::mojom::WebClientHintsType client_hint_type,
+ WebClientHintsType client_hint_type,
const std::string& value) {
headers->SetHeader(
blink::kClientHintsHeaderMapping[static_cast<int>(client_hint_type)],
value);
}
-void RemoveClientHintHeader(network::mojom::WebClientHintsType client_hint_type,
+void RemoveClientHintHeader(WebClientHintsType client_hint_type,
net::HttpRequestHeaders* headers) {
headers->RemoveHeader(
blink::kClientHintsHeaderMapping[static_cast<int>(client_hint_type)]);
@@ -232,8 +237,7 @@
const float device_memory =
blink::ApproximatedDeviceMemory::GetApproximatedDeviceMemory();
DCHECK_LT(0.0, device_memory);
- SetHeaderToDouble(headers, network::mojom::WebClientHintsType::kDeviceMemory,
- device_memory);
+ SetHeaderToDouble(headers, WebClientHintsType::kDeviceMemory, device_memory);
}
void AddDPRHeader(net::HttpRequestHeaders* headers,
@@ -243,7 +247,7 @@
DCHECK(context);
double device_scale_factor = GetDeviceScaleFactor();
double zoom_factor = GetZoomFactor(context, url);
- SetHeaderToDouble(headers, network::mojom::WebClientHintsType::kDpr,
+ SetHeaderToDouble(headers, WebClientHintsType::kDpr,
device_scale_factor * zoom_factor);
}
@@ -267,8 +271,7 @@
DCHECK_LT(0, viewport_width);
// TODO(yoav): Find out why this 0 check is needed...
if (viewport_width > 0) {
- SetHeaderToInt(headers, network::mojom::WebClientHintsType::kViewportWidth,
- viewport_width);
+ SetHeaderToInt(headers, WebClientHintsType::kViewportWidth, viewport_width);
}
}
@@ -290,7 +293,7 @@
http_rtt = net::NetworkQualityEstimatorParams::GetDefaultTypicalHttpRtt(
net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN);
}
- SetHeaderToInt(headers, network::mojom::WebClientHintsType::kRtt,
+ SetHeaderToInt(headers, WebClientHintsType::kRtt,
RoundRtt(url.host(), http_rtt));
}
@@ -316,7 +319,7 @@
net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN);
}
- SetHeaderToDouble(headers, network::mojom::WebClientHintsType::kDownlink,
+ SetHeaderToDouble(headers, WebClientHintsType::kDownlink,
RoundKbpsToMbps(url.host(), downlink_throughput_kbps));
}
@@ -344,13 +347,13 @@
}
SetHeaderToString(
- headers, network::mojom::WebClientHintsType::kEct,
+ headers, WebClientHintsType::kEct,
blink::kWebEffectiveConnectionTypeMapping[effective_connection_type]);
}
void AddLangHeader(net::HttpRequestHeaders* headers, BrowserContext* context) {
SetHeaderToString(
- headers, network::mojom::WebClientHintsType::kLang,
+ headers, WebClientHintsType::kLang,
blink::SerializeLangClientHint(
GetContentClient()->browser()->GetAcceptLangs(context)));
}
@@ -363,8 +366,7 @@
frame_tree_node->current_frame_host()->GetPreferredColorScheme();
bool is_dark_mode =
preferred_color_scheme == blink::mojom::PreferredColorScheme::kDark;
- SetHeaderToString(headers,
- network::mojom::WebClientHintsType::kPrefersColorScheme,
+ SetHeaderToString(headers, WebClientHintsType::kPrefersColorScheme,
is_dark_mode ? "dark" : "light");
}
@@ -383,15 +385,18 @@
}
void AddUAHeader(net::HttpRequestHeaders* headers,
- network::mojom::WebClientHintsType type,
+ WebClientHintsType type,
const std::string& value) {
SetHeaderToString(headers, type, value);
}
-// Use structured headers to escape and quote headers
-std::string SerializeHeaderString(std::string str) {
+// Creates a serialized string header value out of the input type, using
+// structured headers as described in
+// https://www.rfc-editor.org/rfc/rfc8941.html.
+template <typename T>
+const std::string SerializeHeaderString(const T& value) {
return net::structured_headers::SerializeItem(
- net::structured_headers::Item(str))
+ net::structured_headers::Item(value))
.value_or(std::string());
}
@@ -436,7 +441,7 @@
};
bool IsClientHintAllowed(const ClientHintsExtendedData& data,
- network::mojom::WebClientHintsType type) {
+ WebClientHintsType type) {
if (!IsPermissionsPolicyForClientHintsEnabled() || data.is_main_frame)
return data.is_1p_origin;
return data.permissions_policy &&
@@ -447,7 +452,7 @@
}
bool ShouldAddClientHint(const ClientHintsExtendedData& data,
- network::mojom::WebClientHintsType type) {
+ WebClientHintsType type) {
if (!blink::IsClientHintSentByDefault(type) && !data.hints.IsEnabled(type))
return false;
return IsClientHintAllowed(data, type);
@@ -509,70 +514,60 @@
// Permissions Policy.
//
// https://wicg.github.io/client-hints-infrastructure/#abstract-opdef-append-client-hints-to-request
- if (ShouldAddClientHint(data, network::mojom::WebClientHintsType::kUA)) {
- AddUAHeader(headers, network::mojom::WebClientHintsType::kUA,
+ if (ShouldAddClientHint(data, WebClientHintsType::kUA)) {
+ AddUAHeader(headers, WebClientHintsType::kUA,
ua_metadata->SerializeBrandVersionList());
}
// The `Sec-CH-UA-Mobile client hint was also deemed "low entropy" and can
// safely be sent with every request. Similarly to UA, ShouldAddClientHints
// makes sure it's controlled by Permissions Policy.
- if (ShouldAddClientHint(data,
- network::mojom::WebClientHintsType::kUAMobile)) {
- AddUAHeader(headers, network::mojom::WebClientHintsType::kUAMobile,
- ua_metadata->mobile ? "?1" : "?0");
+ if (ShouldAddClientHint(data, WebClientHintsType::kUAMobile)) {
+ AddUAHeader(headers, WebClientHintsType::kUAMobile,
+ SerializeHeaderString(ua_metadata->mobile));
}
- if (ShouldAddClientHint(
- data, network::mojom::WebClientHintsType::kUAFullVersion)) {
- AddUAHeader(headers, network::mojom::WebClientHintsType::kUAFullVersion,
+ if (ShouldAddClientHint(data, WebClientHintsType::kUAFullVersion)) {
+ AddUAHeader(headers, WebClientHintsType::kUAFullVersion,
SerializeHeaderString(ua_metadata->full_version));
}
- if (ShouldAddClientHint(data,
- network::mojom::WebClientHintsType::kUAArch)) {
- AddUAHeader(headers, network::mojom::WebClientHintsType::kUAArch,
+ if (ShouldAddClientHint(data, WebClientHintsType::kUAArch)) {
+ AddUAHeader(headers, WebClientHintsType::kUAArch,
SerializeHeaderString(ua_metadata->architecture));
}
- if (ShouldAddClientHint(data,
- network::mojom::WebClientHintsType::kUAPlatform)) {
- AddUAHeader(headers, network::mojom::WebClientHintsType::kUAPlatform,
+ if (ShouldAddClientHint(data, WebClientHintsType::kUAPlatform)) {
+ AddUAHeader(headers, WebClientHintsType::kUAPlatform,
SerializeHeaderString(ua_metadata->platform));
}
- if (ShouldAddClientHint(
- data, network::mojom::WebClientHintsType::kUAPlatformVersion)) {
- AddUAHeader(headers,
- network::mojom::WebClientHintsType::kUAPlatformVersion,
+ if (ShouldAddClientHint(data, WebClientHintsType::kUAPlatformVersion)) {
+ AddUAHeader(headers, WebClientHintsType::kUAPlatformVersion,
SerializeHeaderString(ua_metadata->platform_version));
}
- if (ShouldAddClientHint(data,
- network::mojom::WebClientHintsType::kUAModel)) {
- AddUAHeader(headers, network::mojom::WebClientHintsType::kUAModel,
+ if (ShouldAddClientHint(data, WebClientHintsType::kUAModel)) {
+ AddUAHeader(headers, WebClientHintsType::kUAModel,
SerializeHeaderString(ua_metadata->model));
}
- if (ShouldAddClientHint(data,
- network::mojom::WebClientHintsType::kUABitness)) {
- AddUAHeader(headers, network::mojom::WebClientHintsType::kUABitness,
+ if (ShouldAddClientHint(data, WebClientHintsType::kUABitness)) {
+ AddUAHeader(headers, WebClientHintsType::kUABitness,
SerializeHeaderString(ua_metadata->bitness));
}
+ if (ShouldAddClientHint(data, WebClientHintsType::kUAReduced)) {
+ AddUAHeader(headers, WebClientHintsType::kUAReduced,
+ SerializeHeaderString(true));
+ }
} else if (call_type == ClientUaHeaderCallType::kAfterCreated) {
- RemoveClientHintHeader(network::mojom::WebClientHintsType::kUA, headers);
- RemoveClientHintHeader(network::mojom::WebClientHintsType::kUAMobile,
- headers);
- RemoveClientHintHeader(network::mojom::WebClientHintsType::kUAFullVersion,
- headers);
- RemoveClientHintHeader(network::mojom::WebClientHintsType::kUAArch,
- headers);
- RemoveClientHintHeader(network::mojom::WebClientHintsType::kUAPlatform,
- headers);
- RemoveClientHintHeader(
- network::mojom::WebClientHintsType::kUAPlatformVersion, headers);
- RemoveClientHintHeader(network::mojom::WebClientHintsType::kUAModel,
- headers);
- RemoveClientHintHeader(network::mojom::WebClientHintsType::kUABitness,
- headers);
+ RemoveClientHintHeader(WebClientHintsType::kUA, headers);
+ RemoveClientHintHeader(WebClientHintsType::kUAMobile, headers);
+ RemoveClientHintHeader(WebClientHintsType::kUAFullVersion, headers);
+ RemoveClientHintHeader(WebClientHintsType::kUAArch, headers);
+ RemoveClientHintHeader(WebClientHintsType::kUAPlatform, headers);
+ RemoveClientHintHeader(WebClientHintsType::kUAPlatformVersion, headers);
+ RemoveClientHintHeader(WebClientHintsType::kUAModel, headers);
+ RemoveClientHintHeader(WebClientHintsType::kUABitness, headers);
+ RemoveClientHintHeader(WebClientHintsType::kUAReduced, headers);
}
}
@@ -638,30 +633,27 @@
}
// Add Headers
- if (ShouldAddClientHint(data,
- network::mojom::WebClientHintsType::kDeviceMemory)) {
+ if (ShouldAddClientHint(data, WebClientHintsType::kDeviceMemory)) {
AddDeviceMemoryHeader(headers);
}
- if (ShouldAddClientHint(data, network::mojom::WebClientHintsType::kDpr)) {
+ if (ShouldAddClientHint(data, WebClientHintsType::kDpr)) {
AddDPRHeader(headers, context, url);
}
- if (ShouldAddClientHint(data,
- network::mojom::WebClientHintsType::kViewportWidth)) {
+ if (ShouldAddClientHint(data, WebClientHintsType::kViewportWidth)) {
AddViewportWidthHeader(headers, context, url);
}
network::NetworkQualityTracker* network_quality_tracker =
delegate->GetNetworkQualityTracker();
- if (ShouldAddClientHint(data, network::mojom::WebClientHintsType::kRtt)) {
+ if (ShouldAddClientHint(data, WebClientHintsType::kRtt)) {
AddRttHeader(headers, network_quality_tracker, url);
}
- if (ShouldAddClientHint(data,
- network::mojom::WebClientHintsType::kDownlink)) {
+ if (ShouldAddClientHint(data, WebClientHintsType::kDownlink)) {
AddDownlinkHeader(headers, network_quality_tracker, url);
}
- if (ShouldAddClientHint(data, network::mojom::WebClientHintsType::kEct)) {
+ if (ShouldAddClientHint(data, WebClientHintsType::kEct)) {
AddEctHeader(headers, network_quality_tracker, url);
}
- if (ShouldAddClientHint(data, network::mojom::WebClientHintsType::kLang)) {
+ if (ShouldAddClientHint(data, WebClientHintsType::kLang)) {
AddLangHeader(headers, context);
}
@@ -671,8 +663,7 @@
ClientUaHeaderCallType::kDuringCreation, headers);
}
- if (ShouldAddClientHint(
- data, network::mojom::WebClientHintsType::kPrefersColorScheme)) {
+ if (ShouldAddClientHint(data, WebClientHintsType::kPrefersColorScheme)) {
AddPrefersColorSchemeHeader(headers, frame_tree_node);
}
@@ -681,8 +672,7 @@
// If possible, logic should be added above so that the request headers for
// the newly added client hint can be added to the request.
static_assert(
- network::mojom::WebClientHintsType::kUAReduced ==
- network::mojom::WebClientHintsType::kMaxValue,
+ WebClientHintsType::kUAReduced == WebClientHintsType::kMaxValue,
"Consider adding client hint request headers from the browser process");
// TODO(crbug.com/735518): If the request is redirected, the client hint
@@ -743,18 +733,19 @@
container_policy);
}
-absl::optional<std::vector<network::mojom::WebClientHintsType>>
+absl::optional<std::vector<WebClientHintsType>>
ParseAndPersistAcceptCHForNavigation(
const GURL& url,
- const ::network::mojom::ParsedHeadersPtr& headers,
+ const network::mojom::ParsedHeadersPtr& parsed_headers,
+ const net::HttpResponseHeaders* response_headers,
BrowserContext* context,
ClientHintsControllerDelegate* delegate,
FrameTreeNode* frame_tree_node) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(context);
- DCHECK(headers);
+ DCHECK(parsed_headers);
- if (!headers->accept_ch)
+ if (!parsed_headers->accept_ch)
return absl::nullopt;
if (!IsValidURLForClientHints(url))
@@ -774,6 +765,13 @@
if (!frame_tree_node->IsMainFrame())
return absl::nullopt;
+ blink::EnabledClientHints enabled_hints;
+ for (const WebClientHintsType type : parsed_headers->accept_ch.value()) {
+ enabled_hints.SetIsEnabled(url, response_headers, type, true);
+ }
+ const std::vector<WebClientHintsType> persisted_hints =
+ enabled_hints.GetEnabledHints();
+
base::TimeDelta persist_duration;
if (IsPermissionsPolicyForClientHintsEnabled()) {
// JSON cannot store "non-finite" values (i.e. NaN or infinite) so
@@ -782,42 +780,35 @@
// large was chosen instead
persist_duration = base::TimeDelta::FromDays(1000000);
} else {
- persist_duration = headers->accept_ch_lifetime;
+ persist_duration = parsed_headers->accept_ch_lifetime;
if (persist_duration.is_zero())
- return headers->accept_ch;
+ return persisted_hints;
}
- delegate->PersistClientHints(url::Origin::Create(url),
- headers->accept_ch.value(), persist_duration);
+ delegate->PersistClientHints(url::Origin::Create(url), persisted_hints,
+ persist_duration);
- return headers->accept_ch;
+ return persisted_hints;
}
-CONTENT_EXPORT std::vector<::network::mojom::WebClientHintsType>
-LookupAcceptCHForCommit(const GURL& url,
- ClientHintsControllerDelegate* delegate,
- FrameTreeNode* frame_tree_node) {
- std::vector<::network::mojom::WebClientHintsType> result;
+CONTENT_EXPORT std::vector<WebClientHintsType> LookupAcceptCHForCommit(
+ const GURL& url,
+ ClientHintsControllerDelegate* delegate,
+ FrameTreeNode* frame_tree_node) {
+ std::vector<WebClientHintsType> result;
if (!ShouldAddClientHints(url, frame_tree_node, delegate)) {
return result;
}
const ClientHintsExtendedData data(url, frame_tree_node, delegate);
- for (int v = 0;
- v <= static_cast<int>(network::mojom::WebClientHintsType::kMaxValue);
- ++v) {
- auto hint = static_cast<network::mojom::WebClientHintsType>(v);
- if (data.hints.IsEnabled(hint))
- result.push_back(hint);
- }
- return result;
+ return data.hints.GetEnabledHints();
}
bool AreCriticalHintsMissing(
const GURL& url,
FrameTreeNode* frame_tree_node,
ClientHintsControllerDelegate* delegate,
- const std::vector<network::mojom::WebClientHintsType>& critical_hints) {
+ const std::vector<WebClientHintsType>& critical_hints) {
ClientHintsExtendedData data(url, frame_tree_node, delegate);
// Note: these only check for per-hint origin/permissions policy settings, not
diff --git a/content/browser/client_hints/client_hints.h b/content/browser/client_hints/client_hints.h
index 9c0bccd1..3c6d7389 100644
--- a/content/browser/client_hints/client_hints.h
+++ b/content/browser/client_hints/client_hints.h
@@ -13,6 +13,10 @@
#include "services/network/public/mojom/parsed_headers.mojom-forward.h"
#include "third_party/blink/public/common/permissions_policy/permissions_policy.h"
+namespace net {
+class HttpResponseHeaders;
+} // namespace net
+
namespace content {
class BrowserContext;
@@ -82,10 +86,17 @@
// persisted. The distinction is relevant in legacy case where permissions
// policy is off and there is no valid Accept-CH-Lifetime, where the header
// still applies locally within frame.
+//
+// The ParsedHeaders are used to retrieve the already parsed Accept-CH header
+// values. The HttpResponseHeaders are not meant to be used by non-sandboxed
+// processes, but here, we just pass the HttpRequestHeaders to the
+// TrialTokenValidator library. There is precedent for calling the
+// TrialTokenValidator from the browser process, see crrev.com/c/2142580.
CONTENT_EXPORT absl::optional<std::vector<network::mojom::WebClientHintsType>>
ParseAndPersistAcceptCHForNavigation(
const GURL& url,
- const ::network::mojom::ParsedHeadersPtr& headers,
+ const network::mojom::ParsedHeadersPtr& parsed_headers,
+ const net::HttpResponseHeaders* response_headers,
BrowserContext* context,
ClientHintsControllerDelegate* delegate,
FrameTreeNode*);
diff --git a/content/browser/client_hints/critical_client_hints_throttle.cc b/content/browser/client_hints/critical_client_hints_throttle.cc
index cbf620c..a173549 100644
--- a/content/browser/client_hints/critical_client_hints_throttle.cc
+++ b/content/browser/client_hints/critical_client_hints_throttle.cc
@@ -66,7 +66,7 @@
blink::EnabledClientHints hints;
for (const WebClientHintsType hint :
response_head.parsed_headers->accept_ch.value())
- hints.SetIsEnabled(hint, true);
+ hints.SetIsEnabled(response_url, response_head.headers.get(), hint, true);
std::vector<WebClientHintsType> critical_hints;
for (const WebClientHintsType hint :
@@ -85,8 +85,8 @@
if (ShouldRestartWithHints(response_url, critical_hints, modified_headers)) {
LogCriticalCHStatus(CriticalCHRestart::kNavigationRestarted);
ParseAndPersistAcceptCHForNavigation(
- response_url, response_head.parsed_headers, context_,
- client_hint_delegate_,
+ response_url, response_head.parsed_headers, response_head.headers.get(),
+ context_, client_hint_delegate_,
FrameTreeNode::GloballyFindByID(frame_tree_node_id_));
delegate_->RestartWithURLResetAndFlags(/*additional_load_flags=*/0);
}
diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
index 2e70ba9b..37e08af 100644
--- a/content/browser/renderer_host/navigation_request.cc
+++ b/content/browser/renderer_host/navigation_request.cc
@@ -3778,6 +3778,7 @@
ParseAndPersistAcceptCHForNavigation(
commit_params_->redirects.back(),
commit_params_->redirect_response.back()->parsed_headers,
+ commit_params_->redirect_response.back()->headers.get(),
browser_context, client_hints_delegate, frame_tree_node_);
AddNavigationRequestClientHintsHeaders(
common_params_->url, &client_hints_extra_headers, browser_context,
@@ -4169,8 +4170,9 @@
opt_in_hints_from_response;
if (response()) {
opt_in_hints_from_response = ParseAndPersistAcceptCHForNavigation(
- common_params_->url, response()->parsed_headers, browser_context,
- client_hints_delegate, frame_tree_node_);
+ common_params_->url, response()->parsed_headers,
+ response()->headers.get(), browser_context, client_hints_delegate,
+ frame_tree_node_);
}
commit_params_->enabled_client_hints = LookupAcceptCHForCommit(
common_params_->url, client_hints_delegate, frame_tree_node_);
diff --git a/content/public/test/url_loader_interceptor.cc b/content/public/test/url_loader_interceptor.cc
index 118f783..0855a5f 100644
--- a/content/public/test/url_loader_interceptor.cc
+++ b/content/public/test/url_loader_interceptor.cc
@@ -31,6 +31,7 @@
#include "net/http/http_util.h"
#include "net/test/embedded_test_server/request_handler_util.h"
#include "services/network/public/cpp/features.h"
+#include "services/network/public/cpp/parsed_headers.h"
#include "services/network/public/mojom/early_hints.mojom.h"
#include "services/network/public/mojom/url_loader.mojom.h"
@@ -511,6 +512,17 @@
}
}
+const net::HttpRequestHeaders& URLLoaderInterceptor::GetLastRequestHeaders() {
+ base::AutoLock lock(last_request_lock_);
+ return last_request_headers_;
+}
+
+void URLLoaderInterceptor::SetLastRequestHeaders(
+ const net::HttpRequestHeaders& headers) {
+ base::AutoLock lock(last_request_lock_);
+ last_request_headers_ = headers;
+}
+
// static
std::unique_ptr<URLLoaderInterceptor>
URLLoaderInterceptor::ServeFilesFromDirectoryAtOrigin(
@@ -538,8 +550,9 @@
return false;
callback.Run(params->url_request.url);
- content::URLLoaderInterceptor::WriteResponse(full_path,
- params->client.get());
+ content::URLLoaderInterceptor::WriteResponse(
+ full_path, params->client.get(), /*headers=*/nullptr,
+ /*ssl_info=*/absl::nullopt, /*url=*/params->url_request.url);
return true;
}));
}
@@ -548,13 +561,18 @@
base::StringPiece headers,
base::StringPiece body,
network::mojom::URLLoaderClient* client,
- absl::optional<net::SSLInfo> ssl_info) {
+ absl::optional<net::SSLInfo> ssl_info,
+ absl::optional<GURL> url) {
net::HttpResponseInfo info;
info.headers = base::MakeRefCounted<net::HttpResponseHeaders>(
net::HttpUtil::AssembleRawHeaders(headers));
auto response = network::mojom::URLResponseHead::New();
response->headers = info.headers;
response->headers->GetMimeType(&response->mime_type);
+ if (url.has_value()) {
+ response->parsed_headers =
+ network::PopulateParsedHeaders(response->headers.get(), *url);
+ }
response->ssl_info = std::move(ssl_info);
client->OnReceiveResponse(std::move(response));
@@ -565,16 +583,18 @@
const std::string& relative_path,
network::mojom::URLLoaderClient* client,
const std::string* headers,
- absl::optional<net::SSLInfo> ssl_info) {
+ absl::optional<net::SSLInfo> ssl_info,
+ absl::optional<GURL> url) {
return WriteResponse(GetDataFilePath(relative_path), client, headers,
- std::move(ssl_info));
+ std::move(ssl_info), std::move(url));
}
void URLLoaderInterceptor::WriteResponse(
const base::FilePath& file_path,
network::mojom::URLLoaderClient* client,
const std::string* headers,
- absl::optional<net::SSLInfo> ssl_info) {
+ absl::optional<net::SSLInfo> ssl_info,
+ absl::optional<GURL> url) {
base::ScopedAllowBlockingForTesting allow_io;
std::string headers_str;
if (headers) {
@@ -589,7 +609,8 @@
net::test_server::GetContentType(file_path) + "\n\n";
}
}
- WriteResponse(headers_str, ReadFile(file_path), client, std::move(ssl_info));
+ WriteResponse(headers_str, ReadFile(file_path), client, std::move(ssl_info),
+ std::move(url));
}
MojoResult URLLoaderInterceptor::WriteResponseBody(
@@ -670,8 +691,12 @@
}
bool URLLoaderInterceptor::Intercept(RequestParams* params) {
- if (callback_.Run(params))
+ if (callback_.Run(params)) {
+ // Only set the last request headers if the request was actually processed
+ // by the interceptor.
+ SetLastRequestHeaders(params->url_request.headers);
return true;
+ }
// mock.failed.request is a special request whereby the query indicates what
// error code to respond with.
diff --git a/content/public/test/url_loader_interceptor.h b/content/public/test/url_loader_interceptor.h
index 0d40094..4490aced 100644
--- a/content/public/test/url_loader_interceptor.h
+++ b/content/public/test/url_loader_interceptor.h
@@ -14,10 +14,12 @@
#include "base/macros.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/string_piece.h"
+#include "base/synchronization/lock.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/base/net_errors.h"
+#include "net/http/http_request_headers.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/mojom/url_loader.mojom.h"
@@ -110,12 +112,14 @@
base::RepeatingCallback<void(const GURL&)> callback = base::DoNothing());
// Helper methods for use when intercepting.
- // Writes the given response body, header, and SSL Info to |client|.
+ // Writes the given response body, header, and SSL Info to `client`.
+ // If `url` is present, also computes the ParsedHeaders for the response.
static void WriteResponse(
base::StringPiece headers,
base::StringPiece body,
network::mojom::URLLoaderClient* client,
- absl::optional<net::SSLInfo> ssl_info = absl::nullopt);
+ absl::optional<net::SSLInfo> ssl_info = absl::nullopt,
+ absl::optional<GURL> url = absl::nullopt);
// Reads the given path, relative to the root source directory, and writes it
// to |client|. For headers:
@@ -125,18 +129,21 @@
// 3) otherwise a simple 200 response will be used, with a Content-Type
// guessed from the file extension
// For SSL info, if |ssl_info| is specified, then it is added to the response.
+ // If `url` is present, also computes the ParsedHeaders for the response.
static void WriteResponse(
const std::string& relative_path,
network::mojom::URLLoaderClient* client,
const std::string* headers = nullptr,
- absl::optional<net::SSLInfo> ssl_info = absl::nullopt);
+ absl::optional<net::SSLInfo> ssl_info = absl::nullopt,
+ absl::optional<GURL> url = absl::nullopt);
// Like above, but uses an absolute file path.
static void WriteResponse(
const base::FilePath& file_path,
network::mojom::URLLoaderClient* client,
const std::string* headers = nullptr,
- absl::optional<net::SSLInfo> ssl_info = absl::nullopt);
+ absl::optional<net::SSLInfo> ssl_info = absl::nullopt,
+ absl::optional<GURL> url = absl::nullopt);
// Attempts to write |body| to |client| and complete the load with status OK.
// client->OnReceiveResponse() must have been called prior to this.
@@ -152,6 +159,15 @@
net::Error error,
base::OnceClosure ready_callback = {});
+ // Returns the request headers of the last request processed by this
+ // interceptor.
+ //
+ // Use this function instead of creating a WebContentsObserver to observe
+ // request headers, if you need the last request headers sent in the event of
+ // resends or redirects, as the NavigationHandle::GetRequestHeaders() function
+ // only returns the initial request's request headers.
+ const net::HttpRequestHeaders& GetLastRequestHeaders();
+
private:
class BrowserProcessWrapper;
class Interceptor;
@@ -185,6 +201,9 @@
// Called on IO thread at initialization and shutdown.
void InitializeOnIOThread(base::OnceClosure closure);
+ // Sets the request headers of the last request processed by this interceptor.
+ void SetLastRequestHeaders(const net::HttpRequestHeaders& headers);
+
bool use_runloop_;
base::OnceClosure ready_callback_;
InterceptCallback callback_;
@@ -197,6 +216,9 @@
std::set<std::unique_ptr<URLLoaderFactoryNavigationWrapper>>
navigation_wrappers_;
+ base::Lock last_request_lock_;
+ net::HttpRequestHeaders last_request_headers_ GUARDED_BY(last_request_lock_);
+
DISALLOW_COPY_AND_ASSIGN(URLLoaderInterceptor);
};
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index b557efb..a10085c 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -2468,6 +2468,7 @@
"//third_party/blink/public:blink",
"//third_party/blink/public:test_support",
"//third_party/blink/public/common:font_enumeration_table_proto",
+ "//third_party/blink/public/common:headers",
"//third_party/icu",
"//third_party/inspector_protocol:crdtp_test",
"//third_party/leveldatabase",
diff --git a/third_party/blink/common/client_hints/enabled_client_hints.cc b/third_party/blink/common/client_hints/enabled_client_hints.cc
index 59226fd..390470c 100644
--- a/third_party/blink/common/client_hints/enabled_client_hints.cc
+++ b/third_party/blink/common/client_hints/enabled_client_hints.cc
@@ -5,7 +5,11 @@
#include "third_party/blink/public/common/client_hints/enabled_client_hints.h"
#include "base/feature_list.h"
+#include "base/time/time.h"
+#include "net/http/http_response_headers.h"
#include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/common/origin_trials/trial_token_validator.h"
+#include "url/gurl.h"
namespace blink {
@@ -44,15 +48,37 @@
} // namespace
bool EnabledClientHints::IsEnabled(const WebClientHintsType type) const {
- if (IsDisabledByFeature(type)) {
- return false;
- }
return enabled_types_[static_cast<int>(type)];
}
void EnabledClientHints::SetIsEnabled(const WebClientHintsType type,
const bool should_send) {
- enabled_types_[static_cast<int>(type)] = should_send;
+ enabled_types_[static_cast<int>(type)] =
+ IsDisabledByFeature(type) ? false : should_send;
+}
+
+void EnabledClientHints::SetIsEnabled(
+ const GURL& url,
+ const net::HttpResponseHeaders* response_headers,
+ const network::mojom::WebClientHintsType type,
+ const bool should_send) {
+ bool enabled = should_send;
+ if (type == WebClientHintsType::kUAReduced) {
+ enabled &= blink::TrialTokenValidator().RequestEnablesFeature(
+ url, response_headers, "UserAgentReduction", base::Time::Now());
+ }
+ SetIsEnabled(type, enabled);
+}
+
+std::vector<WebClientHintsType> EnabledClientHints::GetEnabledHints() const {
+ std::vector<WebClientHintsType> hints;
+ for (int v = 0; v <= static_cast<int>(WebClientHintsType::kMaxValue); ++v) {
+ const auto hint = static_cast<WebClientHintsType>(v);
+ if (IsEnabled(hint)) {
+ hints.push_back(hint);
+ }
+ }
+ return hints;
}
} // namespace blink
diff --git a/third_party/blink/common/client_hints/enabled_client_hints_unittest.cc b/third_party/blink/common/client_hints/enabled_client_hints_unittest.cc
index 77be96a..b28a503 100644
--- a/third_party/blink/common/client_hints/enabled_client_hints_unittest.cc
+++ b/third_party/blink/common/client_hints/enabled_client_hints_unittest.cc
@@ -4,26 +4,93 @@
#include "third_party/blink/public/common/client_hints/enabled_client_hints.h"
+#include "base/memory/scoped_refptr.h"
#include "base/test/scoped_feature_list.h"
+#include "net/http/http_response_headers.h"
+#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/common/origin_trials/origin_trial_policy.h"
+#include "third_party/blink/public/common/origin_trials/origin_trial_public_key.h"
+#include "third_party/blink/public/common/origin_trials/trial_token_validator.h"
namespace blink {
using ::network::mojom::WebClientHintsType;
+using ::testing::ElementsAre;
+
+static constexpr char kOriginUrl[] = "https://127.0.0.1:44444";
+static const OriginTrialPublicKey kTestPublicKey = {
+ 0x75, 0x10, 0xac, 0xf9, 0x3a, 0x1c, 0xb8, 0xa9, 0x28, 0x70, 0xd2,
+ 0x9a, 0xd0, 0x0b, 0x59, 0xe1, 0xac, 0x2b, 0xb7, 0xd5, 0xca, 0x1f,
+ 0x64, 0x90, 0x08, 0x8e, 0xa8, 0xe0, 0x56, 0x3a, 0x04, 0xd0,
+};
+// Generated by running (in tools/origin_trials):
+// generate_token.py https://127.0.0.1:44444 UserAgentReduction
+// --expire-timestamp=2000000000
+//
+// The Origin Trial token expires in 2033. Generate a new token by then, or
+// find a better way to re-generate a test trial token.
+static constexpr char kValidOriginTrialToken[] =
+ "A93QtcQ0CRKf5ioPasUwNbweXQWgbI4ZEshiz+"
+ "YS7dkQEWVfW9Ua2pTnA866sZwRzuElkPwsUdGdIaW0fRUP8AwAAABceyJvcmlnaW4iOiAiaHR0"
+ "cHM6Ly8xMjcuMC4wLjE6NDQ0NDQiLCAiZmVhdHVyZSI6ICJVc2VyQWdlbnRSZWR1Y3Rpb24iLC"
+ "AiZXhwaXJ5IjogMjAwMDAwMDAwMH0=";
+// A slight corruption (changing a character) of kValidOriginTrialToken.
+static constexpr char kInvalidOriginTrialToken[] =
+ "A93QtcQ0CRKf5ioPasUwNbweXQWgbI4ZEshiz+"
+ "YS7dkQEWVfW9Ua2pTnA866sZwRzuElkPwsUdGdIaW0fRUP8AwAAABceyJvcmlnaW4iOiAiaHR0"
+ "cHM6Ly8xMjcuMC4wLjE6NDQ0NDQiLCAiZmVhdHVyzSI6ICJVc2VyQWdlbnRSZWR1Y3Rpb24iLC"
+ "AiZXhwaXJ5IjogMjAwMDAwMDAwMH0=";
class EnabledClientHintsTest : public testing::Test {
public:
- EnabledClientHintsTest() {
+ EnabledClientHintsTest()
+ : response_headers_(base::MakeRefCounted<net::HttpResponseHeaders>("")) {
// The UserAgentClientHint feature is enabled, and the LangClientHintHeader
// feature is disabled.
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{blink::features::kUserAgentClientHint},
/*disabled_features=*/{blink::features::kLangClientHintHeader});
+ TrialTokenValidator::SetOriginTrialPolicyGetter(
+ base::BindRepeating([](OriginTrialPolicy* policy) { return policy; },
+ base::Unretained(&policy_)));
+ policy_.SetPublicKeys({kTestPublicKey});
+ }
+
+ ~EnabledClientHintsTest() override {
+ TrialTokenValidator::ResetOriginTrialPolicyGetter();
+ }
+
+ const net::HttpResponseHeaders* response_headers() const {
+ return response_headers_.get();
+ }
+
+ void AddHeader(const std::string& header, const std::string& value) {
+ response_headers_->AddHeader(header, value);
}
private:
+ class TestOriginTrialPolicy : public OriginTrialPolicy {
+ public:
+ bool IsOriginTrialsSupported() const override { return true; }
+ bool IsOriginSecure(const GURL& url) const override {
+ return url.SchemeIs("https");
+ }
+ const std::vector<OriginTrialPublicKey>& GetPublicKeys() const override {
+ return keys_;
+ }
+ void SetPublicKeys(const std::vector<OriginTrialPublicKey>& keys) {
+ keys_ = keys;
+ }
+
+ private:
+ std::vector<OriginTrialPublicKey> keys_;
+ };
+
base::test::ScopedFeatureList scoped_feature_list_;
+ TestOriginTrialPolicy policy_;
+ scoped_refptr<net::HttpResponseHeaders> response_headers_;
};
TEST_F(EnabledClientHintsTest, EnabledClientHint) {
@@ -50,4 +117,31 @@
EXPECT_FALSE(hints.IsEnabled(WebClientHintsType::kLang));
}
+TEST_F(EnabledClientHintsTest,
+ EnabledUaReducedClientHintWithValidOriginTrialToken) {
+ AddHeader("Origin-Trial", kValidOriginTrialToken);
+ EnabledClientHints hints;
+ hints.SetIsEnabled(GURL(kOriginUrl), response_headers(),
+ WebClientHintsType::kUAReduced, true);
+ EXPECT_TRUE(hints.IsEnabled(WebClientHintsType::kUAReduced));
+}
+
+TEST_F(EnabledClientHintsTest,
+ EnabledUaReducedClientHintWithInvalidOriginTrialToken) {
+ AddHeader("Origin-Trial", kInvalidOriginTrialToken);
+ EnabledClientHints hints;
+ hints.SetIsEnabled(GURL(kOriginUrl), response_headers(),
+ WebClientHintsType::kUAReduced, true);
+ EXPECT_FALSE(hints.IsEnabled(WebClientHintsType::kUAReduced));
+}
+
+TEST_F(EnabledClientHintsTest, GetEnabledHints) {
+ EnabledClientHints hints;
+ hints.SetIsEnabled(WebClientHintsType::kUAFullVersion, true);
+ hints.SetIsEnabled(WebClientHintsType::kRtt, true);
+ EXPECT_THAT(hints.GetEnabledHints(),
+ ElementsAre(WebClientHintsType::kRtt,
+ WebClientHintsType::kUAFullVersion));
+}
+
} // namespace blink
diff --git a/third_party/blink/public/common/client_hints/enabled_client_hints.h b/third_party/blink/public/common/client_hints/enabled_client_hints.h
index 70fac17..f3b515f 100644
--- a/third_party/blink/public/common/client_hints/enabled_client_hints.h
+++ b/third_party/blink/public/common/client_hints/enabled_client_hints.h
@@ -8,6 +8,13 @@
#include "services/network/public/mojom/web_client_hints_types.mojom-shared.h"
#include "third_party/blink/public/common/common_export.h"
+// Forward declarations.
+class GURL;
+
+namespace net {
+class HttpResponseHeaders;
+} // namespace net
+
namespace blink {
// EnabledClientHints stores all the client hints along with whether the hint
@@ -27,13 +34,30 @@
// Sets the client hint as enabled for sending in an HTTP request header. Even
// if the client hint header is set to enabled, it is still possible that
- // other factors (such as feature toggles) could cause the client hint to not
+ // other factors (such as feature toggles) should cause the client hint to not
// be sent, and in that case, IsEnabled() would return false.
//
// If `type` is not a valid WebClientHintsType value, nothing is changed (no
// client hints get enabled).
void SetIsEnabled(network::mojom::WebClientHintsType type, bool should_send);
+ // Sets the client hint as enabled for sending in an HTTP request header.
+ //
+ // In addition to the client hint checks outlined in the SetIsEnabled()
+ // function above, this function also checks if the origin and/or the
+ // response headers indicate that the client hint should not be enabled (e.g.
+ // if the client hint should only be enabled in an Origin Trial).
+ //
+ // If `type` is not a valid WebClientHintsType value, nothing is changed (no
+ // client hints get enabled).
+ void SetIsEnabled(const GURL& url,
+ const net::HttpResponseHeaders* response_headers,
+ network::mojom::WebClientHintsType type,
+ bool should_send);
+
+ // Returns a list of the enabled client hints.
+ std::vector<network::mojom::WebClientHintsType> GetEnabledHints() const;
+
private:
std::array<bool,
static_cast<int>(network::mojom::WebClientHintsType::kMaxValue) +
diff --git a/third_party/blink/renderer/core/loader/base_fetch_context.cc b/third_party/blink/renderer/core/loader/base_fetch_context.cc
index 07ce40cf..0cda0e6 100644
--- a/third_party/blink/renderer/core/loader/base_fetch_context.cc
+++ b/third_party/blink/renderer/core/loader/base_fetch_context.cc
@@ -35,8 +35,10 @@
namespace {
-// Simple function to add quotes to make headers strings.
-const AtomicString SerializeHeaderString(std::string str) {
+// Creates a serialized AtomicString header value out of the input string, using
+// structured headers as described in
+// https://www.rfc-editor.org/rfc/rfc8941.html.
+const AtomicString SerializeStringHeader(std::string str) {
std::string output;
if (!str.empty()) {
output = net::structured_headers::SerializeItem(
@@ -47,6 +49,17 @@
return AtomicString(output.c_str());
}
+// Creates a serialized AtomicString header value out of the input boolean,
+// using structured headers as described in
+// https://www.rfc-editor.org/rfc/rfc8941.html.
+const AtomicString SerializeBoolHeader(const bool value) {
+ const std::string output = net::structured_headers::SerializeItem(
+ net::structured_headers::Item(value))
+ .value_or(std::string());
+
+ return AtomicString(output.c_str());
+}
+
} // namespace
namespace blink {
@@ -151,9 +164,7 @@
}
// We also send Sec-CH-UA-Mobile to all hints. It is a one-bit header
- // identifying if the browser has opted for a "mobile" experience
- // Formatted using the "sh-boolean" format from:
- // https://httpwg.org/http-extensions/draft-ietf-httpbis-header-structure.html#boolean
+ // identifying if the browser has opted for a "mobile" experience.
// ShouldSendClientHint is called to make sure it's controlled by
// PermissionsPolicy.
if (ShouldSendClientHint(
@@ -163,7 +174,7 @@
request.SetHttpHeaderField(
blink::kClientHintsHeaderMapping[static_cast<size_t>(
network::mojom::blink::WebClientHintsType::kUAMobile)],
- ua->mobile ? "?1" : "?0");
+ SerializeBoolHeader(ua->mobile));
}
}
@@ -293,7 +304,7 @@
request.SetHttpHeaderField(
blink::kClientHintsHeaderMapping[static_cast<size_t>(
network::mojom::blink::WebClientHintsType::kUAArch)],
- SerializeHeaderString(ua->architecture));
+ SerializeStringHeader(ua->architecture));
}
if (ShouldSendClientHint(
@@ -303,7 +314,7 @@
request.SetHttpHeaderField(
blink::kClientHintsHeaderMapping[static_cast<size_t>(
network::mojom::blink::WebClientHintsType::kUAPlatform)],
- SerializeHeaderString(ua->platform));
+ SerializeStringHeader(ua->platform));
}
if (ShouldSendClientHint(
@@ -313,7 +324,7 @@
request.SetHttpHeaderField(
blink::kClientHintsHeaderMapping[static_cast<size_t>(
network::mojom::blink::WebClientHintsType::kUAPlatformVersion)],
- SerializeHeaderString(ua->platform_version));
+ SerializeStringHeader(ua->platform_version));
}
if (ShouldSendClientHint(
@@ -323,7 +334,7 @@
request.SetHttpHeaderField(
blink::kClientHintsHeaderMapping[static_cast<size_t>(
network::mojom::blink::WebClientHintsType::kUAModel)],
- SerializeHeaderString(ua->model));
+ SerializeStringHeader(ua->model));
}
if (ShouldSendClientHint(
@@ -333,7 +344,7 @@
request.SetHttpHeaderField(
blink::kClientHintsHeaderMapping[static_cast<size_t>(
network::mojom::blink::WebClientHintsType::kUAFullVersion)],
- SerializeHeaderString(ua->full_version));
+ SerializeStringHeader(ua->full_version));
}
if (ShouldSendClientHint(
@@ -343,7 +354,20 @@
request.SetHttpHeaderField(
blink::kClientHintsHeaderMapping[static_cast<size_t>(
network::mojom::blink::WebClientHintsType::kUABitness)],
- SerializeHeaderString(ua->bitness));
+ SerializeStringHeader(ua->bitness));
+ }
+
+ if (ShouldSendClientHint(
+ ClientHintsMode::kStandard, policy, resource_origin, is_1p_origin,
+ network::mojom::blink::WebClientHintsType::kUAReduced,
+ hints_preferences)) {
+ // If the UA-Reduced client hint should be sent according to the hints
+ // preferences, it means the Origin Trial token for User-Agent Reduction
+ // has already been validated.
+ request.SetHttpHeaderField(
+ blink::kClientHintsHeaderMapping[static_cast<size_t>(
+ network::mojom::blink::WebClientHintsType::kUAReduced)],
+ SerializeBoolHeader(true));
}
}
diff --git a/third_party/blink/renderer/platform/loader/fetch/client_hints_preferences.cc b/third_party/blink/renderer/platform/loader/fetch/client_hints_preferences.cc
index 8ffd2aa..f1f127a 100644
--- a/third_party/blink/renderer/platform/loader/fetch/client_hints_preferences.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/client_hints_preferences.cc
@@ -64,6 +64,7 @@
// Note: .Ascii() would convert tab to ?, which is undesirable.
absl::optional<std::vector<network::mojom::WebClientHintsType>> parsed_ch =
network::ParseClientHintsHeader(header_value.Latin1());
+
if (!parsed_ch.has_value())
return;