summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/client-hints
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/client-hints')
-rw-r--r--testing/web-platform/tests/client-hints/META.yml5
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-change.https.html75
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-malformed-header.https.html24
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-malformed-header.https.html.headers1
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/README.md14
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-iframe-not-setting-other-origins.https.html19
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-iframe-not-setting-own-origin.https.html19
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-iframe-redirect-with-fp-delegation.https.html18
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-iframe-redirect-with-fp-delegation.https.html.headers2
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-navigation-redirect.https.html18
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-navigation-redirect.https.html.headers1
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-navigation.https.html19
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-subresource-redirect-with-fp-delegation.https.html17
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-subresource-redirect-with-fp-delegation.https.html.headers2
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-subresource-redirect.https.html17
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-subresource-redirect.https.html.headers1
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-subresource-with-feature-policy.https.html19
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-subresource.https.html19
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-syncxhr-redirect.https.html17
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-syncxhr-redirect.https.html.headers1
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/http-equiv-cross-origin-iframe-not-setting-other-origins.https.html19
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/http-equiv-cross-origin-iframe-not-setting-own-origin.https.html19
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/http-equiv-cross-origin-navigation.https.html19
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/http-equiv-cross-origin-subresource.https.html19
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/http-equiv-same-origin-iframe.https.html19
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/http-equiv-same-origin-navigation.https.html19
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/http-equiv-same-origin-subresource.https.html19
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-cross-origin-iframe-not-setting-other-origins.https.html19
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-cross-origin-iframe-not-setting-own-origin.https.html19
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-cross-origin-navigation.https.html19
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-cross-origin-subresource.https.html19
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-delegate-ch-cross-origin-iframe-with-hints.https.sub.html18
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-delegate-ch-cross-origin-iframe-without-hints.https.sub.html18
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-delegate-ch-cross-origin-subresource-with-hints.https.sub.html18
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-delegate-ch-cross-origin-subresource-without-hints.https.sub.html18
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-same-origin-iframe.https.html19
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-same-origin-navigation.https.html19
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-same-origin-subresource.https.html19
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/accept-ch-and-redir.py5
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/accept-ch-blank.html11
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/accept-ch-blank.html.headers2
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/accept-ch-test.js133
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/accept-ch.html16
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/accept-ch.html.headers2
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/clear-site-data.html0
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/clear-site-data.html.headers2
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/do-expect-received.py20
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/do-not-expect-client-hints-headers.html22
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/do-not-expect-received.py20
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/echo-client-hints-received.py14
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/expect-client-hints-headers.html22
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/expect-received.py28
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/feature-policy-with-cross-origin-subresource.html25
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/feature-policy-with-cross-origin-subresource.html.headers1
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/http-equiv-accept-ch.html8
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/http-equiv-accept-ch.html.headers2
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/meta-equiv-delegate-ch.html9
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/meta-equiv-delegate-ch.html.headers2
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/no-accept-ch.html11
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/no-accept-ch.html.headers1
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-iframe.https.html19
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-navigation-empty-accept-ch.https.html20
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-navigation-no-accept-ch.https.html20
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-navigation-redirect.https.html19
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-navigation.https.html19
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-subresource-redirect-opted-in.https.html17
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-subresource-redirect-opted-in.https.html.headers1
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-subresource-redirect.https.html18
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-subresource.https.html19
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch.wildcard.https.sub.html25
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch.wildcard.https.sub.html.sub.headers2
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch/__dir__.headers1
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch/answers.sub.https.html68
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch/cache-revalidation.https.html36
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch/feature-policy-navigation/__dir__.headers1
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch/feature-policy-navigation/feature-policy.https.html29
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch/feature-policy-navigation/feature-policy.https.html.headers1
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch/feature-policy-navigation/no-feature-policy.https.html36
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch/feature-policy.sub.https.html79
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch/feature-policy.sub.https.html.headers1
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch/meta/resource-in-markup-accept-ch.https.html14
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch/meta/resource-in-markup-delegate-ch.https.html14
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch/no-feature-policy.sub.https.html65
-rw-r--r--testing/web-platform/tests/client-hints/accept-ch/non-secure.http.html38
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-all.https.html37
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-all.https.html.headers1
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-cache.https.html37
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-cache.https.html.headers1
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-client-hints.https.html37
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-client-hints.https.html.headers1
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-cookies.https.html37
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-cookies.https.html.headers1
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-storage.https.html37
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-storage.https.html.headers1
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/resources/check-client-hints.py15
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/resources/clear-site-data.py12
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-all.https.html24
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-all.https.html.headers2
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-cache.https.html24
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-cache.https.html.headers2
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-client-hints.https.html24
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-client-hints.https.html.headers2
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-cookies.https.html24
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-cookies.https.html.headers2
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-storage.https.html24
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-storage.https.html.headers2
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-all.https.html24
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-all.https.html.headers3
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-cache.https.html24
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-cache.https.html.headers3
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-client-hints.https.html24
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-client-hints.https.html.headers3
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-cookies.https.html24
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-cookies.https.html.headers3
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-storage.https.html24
-rw-r--r--testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-storage.https.html.headers3
-rw-r--r--testing/web-platform/tests/client-hints/critical-ch/critical-ch.navigation-timing.no-restart.https.html13
-rw-r--r--testing/web-platform/tests/client-hints/critical-ch/critical-ch.navigation-timing.restart.https.html14
-rw-r--r--testing/web-platform/tests/client-hints/critical-ch/critical-ch.navigation-timing.restart.https.html.headers2
-rw-r--r--testing/web-platform/tests/client-hints/critical-ch/iframe.https.window.js15
-rw-r--r--testing/web-platform/tests/client-hints/critical-ch/mis-matched-count.https.window.js5
-rw-r--r--testing/web-platform/tests/client-hints/critical-ch/mis-matched.https.window.js3
-rw-r--r--testing/web-platform/tests/client-hints/critical-ch/mis-matched.multiple.https.window.js3
-rw-r--r--testing/web-platform/tests/client-hints/critical-ch/navigation.cross-origin.https.window.js4
-rw-r--r--testing/web-platform/tests/client-hints/critical-ch/navigation.cross-origin.multiple.https.window.js4
-rw-r--r--testing/web-platform/tests/client-hints/critical-ch/navigation.same-origin.https.window.js3
-rw-r--r--testing/web-platform/tests/client-hints/critical-ch/navigation.same-origin.multiple.https.window.js3
-rw-r--r--testing/web-platform/tests/client-hints/critical-ch/non-secure.http.window.js4
-rw-r--r--testing/web-platform/tests/client-hints/critical-ch/redirect.critical.cross-origin.https.window.js4
-rw-r--r--testing/web-platform/tests/client-hints/critical-ch/redirect.critical.cross-origin.multiple.https.window.js4
-rw-r--r--testing/web-platform/tests/client-hints/critical-ch/redirect.critical.same-origin.https.window.js3
-rw-r--r--testing/web-platform/tests/client-hints/critical-ch/redirect.critical.same-origin.multiple.https.window.js3
-rw-r--r--testing/web-platform/tests/client-hints/critical-ch/redirect.cross-origin.https.window.js4
-rw-r--r--testing/web-platform/tests/client-hints/critical-ch/redirect.cross-origin.multiple.https.window.js4
-rw-r--r--testing/web-platform/tests/client-hints/critical-ch/redirect.same-origin.https.window.js3
-rw-r--r--testing/web-platform/tests/client-hints/critical-ch/redirect.same-origin.multiple.https.window.js3
-rw-r--r--testing/web-platform/tests/client-hints/critical-ch/request-count.https.window.js4
-rw-r--r--testing/web-platform/tests/client-hints/critical-ch/request-count.multiple.https.window.js4
-rw-r--r--testing/web-platform/tests/client-hints/critical-ch/resources/echo-critical-hint.py52
-rw-r--r--testing/web-platform/tests/client-hints/critical-ch/resources/redirect-critical-hint.py14
-rw-r--r--testing/web-platform/tests/client-hints/critical-ch/resources/util.js16
-rw-r--r--testing/web-platform/tests/client-hints/critical-ch/subresource.https.window.js18
-rw-r--r--testing/web-platform/tests/client-hints/critical-ch/unsafe-method.https.window.js49
-rw-r--r--testing/web-platform/tests/client-hints/http-equiv-accept-ch-iframe.https.html26
-rw-r--r--testing/web-platform/tests/client-hints/http-equiv-accept-ch-malformed-header.https.html31
-rw-r--r--testing/web-platform/tests/client-hints/http-equiv-accept-ch-merge.https.html50
-rw-r--r--testing/web-platform/tests/client-hints/http-equiv-accept-ch-merge.https.html.headers2
-rw-r--r--testing/web-platform/tests/client-hints/http-equiv-accept-ch-non-secure.http.html39
-rw-r--r--testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_all.sub.https.html37
-rw-r--r--testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_all.sub.https.html.headers2
-rw-r--r--testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_none.sub.https.html36
-rw-r--r--testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_none.sub.https.html.headers2
-rw-r--r--testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_self.sub.https.html37
-rw-r--r--testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_self.sub.https.html.headers2
-rw-r--r--testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_some.sub.https.html37
-rw-r--r--testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_some.sub.https.html.sub.headers2
-rw-r--r--testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_src.sub.https.html37
-rw-r--r--testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_src.sub.https.html.headers2
-rw-r--r--testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_all_iframe.sub.https.html32
-rw-r--r--testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_all_iframe_iframe.sub.https.html23
-rw-r--r--testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_none_iframe.sub.https.html32
-rw-r--r--testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_none_iframe_iframe.sub.https.html23
-rw-r--r--testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_self_iframe.sub.https.html32
-rw-r--r--testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_self_iframe_iframe.sub.https.html23
-rw-r--r--testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_some_iframe.sub.https.html32
-rw-r--r--testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_some_iframe.sub.https.html.headers1
-rw-r--r--testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_some_iframe_iframe.sub.https.html23
-rw-r--r--testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_some_iframe_iframe.sub.https.html.sub.headers1
-rw-r--r--testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_src_iframe.sub.https.html33
-rw-r--r--testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_src_iframe_iframe.sub.https.html23
-rw-r--r--testing/web-platform/tests/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py11
-rw-r--r--testing/web-platform/tests/client-hints/meta-equiv-delegate-ch-iframe.https.html26
-rw-r--r--testing/web-platform/tests/client-hints/meta-equiv-delegate-ch-injection.https.html43
-rw-r--r--testing/web-platform/tests/client-hints/meta-equiv-delegate-ch-malformed-header.https.html32
-rw-r--r--testing/web-platform/tests/client-hints/meta-equiv-delegate-ch-merge.https.html51
-rw-r--r--testing/web-platform/tests/client-hints/meta-equiv-delegate-ch-merge.https.html.headers2
-rw-r--r--testing/web-platform/tests/client-hints/meta-equiv-delegate-ch-non-secure.http.html40
-rw-r--r--testing/web-platform/tests/client-hints/resources/accept-ch-different.html11
-rw-r--r--testing/web-platform/tests/client-hints/resources/accept-ch-different.html.headers2
-rw-r--r--testing/web-platform/tests/client-hints/resources/accept-ch-empty.html11
-rw-r--r--testing/web-platform/tests/client-hints/resources/accept-ch-empty.html.headers2
-rw-r--r--testing/web-platform/tests/client-hints/resources/accept-ch-malformed.html11
-rw-r--r--testing/web-platform/tests/client-hints/resources/accept-ch-malformed.html.headers2
-rw-r--r--testing/web-platform/tests/client-hints/resources/accept-ch-mixed.html10
-rw-r--r--testing/web-platform/tests/client-hints/resources/accept-ch-mixed.html.headers5
-rw-r--r--testing/web-platform/tests/client-hints/resources/accept-ch-split.html10
-rw-r--r--testing/web-platform/tests/client-hints/resources/accept-ch-split.html.headers3
-rw-r--r--testing/web-platform/tests/client-hints/resources/accept-ch.html10
-rw-r--r--testing/web-platform/tests/client-hints/resources/accept-ch.html.headers2
-rw-r--r--testing/web-platform/tests/client-hints/resources/accept-ch.wildcard.iframe.https.sub.html16
-rw-r--r--testing/web-platform/tests/client-hints/resources/accept-ch.wildcard.iframe.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/client-hints/resources/clienthintslist.py41
-rw-r--r--testing/web-platform/tests/client-hints/resources/echo-client-hints-received.py38
-rw-r--r--testing/web-platform/tests/client-hints/resources/echo-ua-client-hints-received.py23
-rw-r--r--testing/web-platform/tests/client-hints/resources/expect-client-hints-headers-iframe.py31
-rw-r--r--testing/web-platform/tests/client-hints/resources/expect-client-hints-headers.html22
-rw-r--r--testing/web-platform/tests/client-hints/resources/expect-different-client-hints-headers.html22
-rw-r--r--testing/web-platform/tests/client-hints/resources/expect-no-client-hints-headers.html24
-rw-r--r--testing/web-platform/tests/client-hints/resources/export.js50
-rw-r--r--testing/web-platform/tests/client-hints/resources/feature-policy-navigation.js72
-rw-r--r--testing/web-platform/tests/client-hints/resources/open-and-add-load-event.js23
-rw-r--r--testing/web-platform/tests/client-hints/resources/script-set-dpr-header.py4
-rw-r--r--testing/web-platform/tests/client-hints/resources/sec-ch-ua.py9
-rw-r--r--testing/web-platform/tests/client-hints/resources/stale-echo-client-hints.py50
-rw-r--r--testing/web-platform/tests/client-hints/resources/viewport-frame.py25
-rw-r--r--testing/web-platform/tests/client-hints/resources/viewport-measurement.html8
-rw-r--r--testing/web-platform/tests/client-hints/resources/viewport.py13
-rw-r--r--testing/web-platform/tests/client-hints/sandbox/__dir__.headers1
-rw-r--r--testing/web-platform/tests/client-hints/sandbox/iframe-csp-same-origin.https.html19
-rw-r--r--testing/web-platform/tests/client-hints/sandbox/iframe-csp-same-origin.https.html.headers1
-rw-r--r--testing/web-platform/tests/client-hints/sandbox/iframe-csp.https.html19
-rw-r--r--testing/web-platform/tests/client-hints/sandbox/iframe-csp.https.html.headers1
-rw-r--r--testing/web-platform/tests/client-hints/sandbox/iframe-iframe-popups.https.html19
-rw-r--r--testing/web-platform/tests/client-hints/sandbox/iframe-popups-escape-sandbox.https.html19
-rw-r--r--testing/web-platform/tests/client-hints/sandbox/iframe-popups.https.html19
-rw-r--r--testing/web-platform/tests/client-hints/sandbox/iframe-same-origin.https.html17
-rw-r--r--testing/web-platform/tests/client-hints/sandbox/iframe.https.html19
-rw-r--r--testing/web-platform/tests/client-hints/sandbox/resources/embedded-popup-expect-hints.html11
-rw-r--r--testing/web-platform/tests/client-hints/sandbox/resources/embedded-popup-expect-no-hints.html11
-rw-r--r--testing/web-platform/tests/client-hints/sandbox/resources/iframe-with-embedded-popup-expect-no-hints.html12
-rw-r--r--testing/web-platform/tests/client-hints/sandbox/resources/util.js25
-rw-r--r--testing/web-platform/tests/client-hints/sec-ch-quotes.https.html64
-rw-r--r--testing/web-platform/tests/client-hints/sec-ch-quotes.https.html.headers1
-rw-r--r--testing/web-platform/tests/client-hints/sec-ch-ua.http.html16
-rw-r--r--testing/web-platform/tests/client-hints/sec-ch-ua.https.html50
-rw-r--r--testing/web-platform/tests/client-hints/service-workers/critical-ch/echo-hint-in-html.py24
-rw-r--r--testing/web-platform/tests/client-hints/service-workers/critical-ch/foo.html1
-rw-r--r--testing/web-platform/tests/client-hints/service-workers/critical-ch/foo.html.headers2
-rw-r--r--testing/web-platform/tests/client-hints/service-workers/critical-ch/intercept-request.js6
-rw-r--r--testing/web-platform/tests/client-hints/service-workers/critical-ch/navigation-preload.js2
-rw-r--r--testing/web-platform/tests/client-hints/service-workers/critical-ch/new-request.js3
-rw-r--r--testing/web-platform/tests/client-hints/service-workers/critical-ch/passthrough-request.js1
-rw-r--r--testing/web-platform/tests/client-hints/service-workers/intercept-request-critical.https.window.js5
-rw-r--r--testing/web-platform/tests/client-hints/service-workers/intercept-request.https.html11
-rw-r--r--testing/web-platform/tests/client-hints/service-workers/intercept-request.https.html.headers1
-rw-r--r--testing/web-platform/tests/client-hints/service-workers/navigation-preload-critical.https.window.js5
-rw-r--r--testing/web-platform/tests/client-hints/service-workers/navigation-preload.https.html11
-rw-r--r--testing/web-platform/tests/client-hints/service-workers/navigation-preload.https.html.headers1
-rw-r--r--testing/web-platform/tests/client-hints/service-workers/new-request-critical.https.window.js5
-rw-r--r--testing/web-platform/tests/client-hints/service-workers/new-request.https.html11
-rw-r--r--testing/web-platform/tests/client-hints/service-workers/new-request.https.html.headers1
-rw-r--r--testing/web-platform/tests/client-hints/service-workers/passthrough-request-critical.https.window.js5
-rw-r--r--testing/web-platform/tests/client-hints/service-workers/passthrough-request.https.html11
-rw-r--r--testing/web-platform/tests/client-hints/service-workers/passthrough-request.https.html.headers1
-rw-r--r--testing/web-platform/tests/client-hints/service-workers/resources/echo-hint-in-html.py21
-rw-r--r--testing/web-platform/tests/client-hints/service-workers/resources/foo.html1
-rw-r--r--testing/web-platform/tests/client-hints/service-workers/resources/intercept-request.js6
-rw-r--r--testing/web-platform/tests/client-hints/service-workers/resources/navigation-preload.js2
-rw-r--r--testing/web-platform/tests/client-hints/service-workers/resources/new-request.js3
-rw-r--r--testing/web-platform/tests/client-hints/service-workers/resources/passthrough-request.js1
-rw-r--r--testing/web-platform/tests/client-hints/service-workers/resources/util.js23
-rw-r--r--testing/web-platform/tests/client-hints/viewport-size/viewport-size-iframe.https.html17
-rw-r--r--testing/web-platform/tests/client-hints/viewport-size/viewport-size-iframe.https.html.headers1
-rw-r--r--testing/web-platform/tests/client-hints/viewport-size/viewport-size-subresource.https.html16
-rw-r--r--testing/web-platform/tests/client-hints/viewport-size/viewport-size-subresource.https.html.headers1
-rw-r--r--testing/web-platform/tests/client-hints/viewport-size/viewport-size-window-different-dimensions.https.html17
-rw-r--r--testing/web-platform/tests/client-hints/viewport-size/viewport-size-window-different-dimensions.https.html.headers1
-rw-r--r--testing/web-platform/tests/client-hints/viewport-size/viewport-size-window.https.html17
-rw-r--r--testing/web-platform/tests/client-hints/viewport-size/viewport-size-window.https.html.headers1
259 files changed, 4239 insertions, 0 deletions
diff --git a/testing/web-platform/tests/client-hints/META.yml b/testing/web-platform/tests/client-hints/META.yml
new file mode 100644
index 0000000000..68cbc4d4fa
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/META.yml
@@ -0,0 +1,5 @@
+spec: https://wicg.github.io/client-hints-infrastructure/
+suggested_reviewers:
+ - igrigorik
+ - yoavweiss
+ - tarunban
diff --git a/testing/web-platform/tests/client-hints/accept-ch-change.https.html b/testing/web-platform/tests/client-hints/accept-ch-change.https.html
new file mode 100644
index 0000000000..826aca85a4
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-change.https.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<html>
+<title>Accept-CH test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/open-and-add-load-event.js"></script>
+
+<!--
+Apart from this webpage, the test opens two more html web page. One test is run
+in this web page, and two in the other web pages.
+-->
+
+<script>
+
+// This test fetches resources/accept_ch.html. The response headers to
+// that webpage contains only the Accept-CH header. These preferences should be
+// stored so that the next request to the same origin is sent with the
+// requested client hint headers.
+
+// Next, to verify that the origin preferences were persisted by the user
+// agent, this test fetches resources/expect_client_hints_headers.html in a new
+// window. Fetching of resources/expect_client_hints_headers.html verifies that
+// the user agent does send the client hints in the request headers.
+
+// After this, the same is done but with a differet Accept-CH header, to test
+// that the preferences change after receiving a different header.
+
+promise_test(async () => {
+ r = await fetch("resources/echo-client-hints-received.py");
+
+ assert_equals(r.status, 200);
+ // Verify that the browser did not include client hints in the request
+ // headers when fetching echo_client_hints_received.py.
+ assert_false(r.headers.has("device-memory-received"), "device-memory-received");
+ assert_false(r.headers.has("device-memory-deprecated-received"), "device-memory-deprecated-received");
+ assert_false(r.headers.has("viewport-width-received"), "viewport-width-received");
+ assert_false(r.headers.has("viewport-width-deprecated-received"), "viewport-width-deprecated-received");
+
+ // Fetching this webpage should cause user-agent to persist client hint
+ // preferences for the origin.
+ await open_and_add_load_event("resources/accept-ch.html");
+
+ // Open a new window. Verify that the user agent does attach the client hints.
+ await open_and_expect_headers("resources/expect-client-hints-headers.html");
+
+ // Use new window to overwrite client hint preferences, then verify new
+ // settings have been saved
+ await open_and_add_load_event("resources/accept-ch-different.html");
+
+ // Use new window to overwrite client hint preferences, then verify new
+ // settings have been saved
+ await open_and_expect_headers("resources/expect-different-client-hints-headers.html");
+
+ // Use new window to overwrite client hint preferences, then verify new
+ // settings have been saved
+ await open_and_add_load_event("resources/accept-ch-empty.html");
+
+ // Use new window to overwrite client hint preferences, then verify new
+ // settings have been saved
+ await open_and_expect_headers("resources/expect-no-client-hints-headers.html");
+
+ // Check that multiple headers will fill the cache.
+ await open_and_add_load_event("resources/accept-ch-split.html");
+ await open_and_expect_headers("resources/expect-client-hints-headers.html");
+
+ // Check that a mix of empty and full headers drops the cache.
+ await open_and_add_load_event("resources/accept-ch-mixed.html");
+ await open_and_expect_headers("resources/expect-no-client-hints-headers.html");
+
+}, "Accept-CH changes based on header");
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/accept-ch-malformed-header.https.html b/testing/web-platform/tests/client-hints/accept-ch-malformed-header.https.html
new file mode 100644
index 0000000000..649ecf9590
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-malformed-header.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/open-and-add-load-event.js"></script>
+
+<script>
+
+promise_test(async t => {
+ t.add_cleanup(() => {
+ return open_and_add_load_event("resources/accept-ch-empty.html");
+ });
+
+ await open_and_add_load_event("resources/accept-ch-malformed.html");
+ // Verify that the browser does not include client hints in the headers
+ // since Accept-CH is malformed (includes whitespace between attributes
+ // instead of comma).
+ await open_and_expect_headers("resources/expect-no-client-hints-headers.html");
+}, "Accept-CH malformed header test");
+
+</script>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/accept-ch-malformed-header.https.html.headers b/testing/web-platform/tests/client-hints/accept-ch-malformed-header.https.html.headers
new file mode 100644
index 0000000000..81396b1e41
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-malformed-header.https.html.headers
@@ -0,0 +1 @@
+Accept-CH: sec-ch-device-memory device-memory sec-ch-dpr dpr \ No newline at end of file
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/README.md b/testing/web-platform/tests/client-hints/accept-ch-stickiness/README.md
new file mode 100644
index 0000000000..7dd1c6ddaf
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/README.md
@@ -0,0 +1,14 @@
+These tests all follow the same format, calling the `run_test` function from
+`resources/accept_ch_test.js`. This function does the following:
+
+ * checks to make sure no client-hint preferences are saved for a particular origin
+ * loading a page with the response header `Accept-CH: device-memory` via a
+ particular method:
+ * Navigation (via window.open)
+ * Subresource (via fetch)
+ * iframe (added via js)
+ * Navigates to another page to check if the device-memory client hint was sent
+ with the next request
+
+Each test is in a separate file to ensure that the browser and it's state is
+properly reset between each test.
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-iframe-not-setting-other-origins.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-iframe-not-setting-other-origins.https.html
new file mode 100644
index 0000000000..5316c2874c
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-iframe-not-setting-other-origins.https.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Accept-CH Persistence test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+run_test({ name: "cross origin iframe not setting other origins",
+ initial_url: echo,
+ accept_url: host_info.HTTPS_REMOTE_ORIGIN + accept,
+ expect_url: do_not_expect,
+ type: "iframe" });
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-iframe-not-setting-own-origin.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-iframe-not-setting-own-origin.https.html
new file mode 100644
index 0000000000..f83aa6173e
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-iframe-not-setting-own-origin.https.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Accept-CH Persistence test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+run_test({ name: "cross origin iframe not setting own origin",
+ initial_url: host_info.HTTPS_REMOTE_ORIGIN + echo,
+ accept_url: host_info.HTTPS_REMOTE_ORIGIN + accept,
+ expect_url: host_info.HTTPS_REMOTE_ORIGIN + do_not_expect,
+ type: "iframe" });
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-iframe-redirect-with-fp-delegation.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-iframe-redirect-with-fp-delegation.https.html
new file mode 100644
index 0000000000..a2bc66fca1
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-iframe-redirect-with-fp-delegation.https.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Accept-CH Persistence test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+// Make sure that cross origin iframes that gets redirected, and has Feature Policy delegation maintain their Client Hints.
+const test_name = "Iframe redirect with Feature Policy delegation";
+verify_iframe_state("resources/accept-ch-and-redir.py?url=" + get_host_info()["HTTPS_REMOTE_ORIGIN"] + "/client-hints/accept-ch-stickiness/resources/expect-received.py", test_name);
+
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-iframe-redirect-with-fp-delegation.https.html.headers b/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-iframe-redirect-with-fp-delegation.https.html.headers
new file mode 100644
index 0000000000..28580b0a6f
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-iframe-redirect-with-fp-delegation.https.html.headers
@@ -0,0 +1,2 @@
+Accept-CH: Sec-CH-DPR, DPR, Sec-CH-Device-Memory, Device-Memory
+Permissions-Policy: ch-dpr=*, ch-device-memory=*
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-navigation-redirect.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-navigation-redirect.https.html
new file mode 100644
index 0000000000..14ba51ab5d
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-navigation-redirect.https.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Accept-CH Persistence test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+// Make sure a cross origin navigation that gets redirected doesn't keep the initial request's Client Hints.
+const test_name = "cross-origin redirect on navigation";
+verify_navigation_state("resources/accept-ch-and-redir.py?url=" + get_host_info()["HTTPS_REMOTE_ORIGIN"] + "/client-hints/accept-ch-stickiness/resources/do-not-expect-received.py", test_name);
+
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-navigation-redirect.https.html.headers b/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-navigation-redirect.https.html.headers
new file mode 100644
index 0000000000..af6945319d
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-navigation-redirect.https.html.headers
@@ -0,0 +1 @@
+Accept-CH: Sec-CH-DPR, DPR, Sec-CH-Device-Memory, Device-Memory
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-navigation.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-navigation.https.html
new file mode 100644
index 0000000000..e05c8e3ef5
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-navigation.https.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Accept-CH Persistence test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+run_test({ name: "cross origin navigation",
+ initial_url: echo,
+ accept_url: host_info.HTTPS_REMOTE_ORIGIN + accept,
+ expect_url: host_info.HTTPS_REMOTE_ORIGIN + expect,
+ type: "navigation" });
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-subresource-redirect-with-fp-delegation.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-subresource-redirect-with-fp-delegation.https.html
new file mode 100644
index 0000000000..dd7b9fab97
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-subresource-redirect-with-fp-delegation.https.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Accept-CH Persistence test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+// Make sure a cross origin subresource that gets redirected with Feature Policy delegation keeps the initial request's Client Hints.
+const test_name = "cross-origin subresource redirect with Feature Policy delegation";
+verify_subresource_state("resources/accept-ch-and-redir.py?url=" + get_host_info()["HTTPS_REMOTE_ORIGIN"] + "/client-hints/accept-ch-stickiness/resources/expect-received.py", test_name);
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-subresource-redirect-with-fp-delegation.https.html.headers b/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-subresource-redirect-with-fp-delegation.https.html.headers
new file mode 100644
index 0000000000..28580b0a6f
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-subresource-redirect-with-fp-delegation.https.html.headers
@@ -0,0 +1,2 @@
+Accept-CH: Sec-CH-DPR, DPR, Sec-CH-Device-Memory, Device-Memory
+Permissions-Policy: ch-dpr=*, ch-device-memory=*
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-subresource-redirect.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-subresource-redirect.https.html
new file mode 100644
index 0000000000..31334543cf
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-subresource-redirect.https.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Accept-CH Persistence test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+// Make sure a cross origin subresource that gets redirected doesn't keep the initial request's Client Hints.
+const test_name = "cross-origin subresource redirect";
+verify_subresource_state("resources/accept-ch-and-redir.py?url=" + get_host_info()["HTTPS_REMOTE_ORIGIN"] + "/client-hints/accept-ch-stickiness/resources/do-not-expect-received.py", test_name);
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-subresource-redirect.https.html.headers b/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-subresource-redirect.https.html.headers
new file mode 100644
index 0000000000..af6945319d
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-subresource-redirect.https.html.headers
@@ -0,0 +1 @@
+Accept-CH: Sec-CH-DPR, DPR, Sec-CH-Device-Memory, Device-Memory
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-subresource-with-feature-policy.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-subresource-with-feature-policy.https.html
new file mode 100644
index 0000000000..3108c23faa
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-subresource-with-feature-policy.https.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Accept-CH Persistence test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+run_test({ name: "cross origin subresources authorized by FP gets it own resources",
+ initial_url: echo,
+ accept_url: accept,
+ expect_url: "resources/feature-policy-with-cross-origin-subresource.html",
+ type: "navigation" });
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-subresource.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-subresource.https.html
new file mode 100644
index 0000000000..249ccb4a60
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-subresource.https.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Accept-CH Persistence test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+run_test({ name: "cross origin subresource",
+ initial_url: host_info.HTTPS_REMOTE_ORIGIN + echo,
+ accept_url: host_info.HTTPS_REMOTE_ORIGIN + accept,
+ expect_url: host_info.HTTPS_REMOTE_ORIGIN + do_not_expect,
+ type: "subresource" });
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-syncxhr-redirect.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-syncxhr-redirect.https.html
new file mode 100644
index 0000000000..1cce664d2c
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-syncxhr-redirect.https.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Accept-CH Persistence test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+// Make sure a cross origin syn XHR that gets redirected doesn't keep the initial request's Client Hints.
+const test_name = "cross-origin sync XHR redirect";
+verify_syncxhr_state("resources/accept-ch-and-redir.py?url=" + get_host_info()["HTTPS_REMOTE_ORIGIN"] + "/client-hints/accept-ch-stickiness/resources/do-not-expect-received.py", test_name);
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-syncxhr-redirect.https.html.headers b/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-syncxhr-redirect.https.html.headers
new file mode 100644
index 0000000000..af6945319d
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/cross-origin-syncxhr-redirect.https.html.headers
@@ -0,0 +1 @@
+Accept-CH: Sec-CH-DPR, DPR, Sec-CH-Device-Memory, Device-Memory
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/http-equiv-cross-origin-iframe-not-setting-other-origins.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/http-equiv-cross-origin-iframe-not-setting-other-origins.https.html
new file mode 100644
index 0000000000..226b3116fc
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/http-equiv-cross-origin-iframe-not-setting-other-origins.https.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Accept-CH Persistence test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+run_test({ name: "http-equiv cross origin iframe not setting other origins",
+ initial_url: echo,
+ accept_url: host_info.HTTPS_REMOTE_ORIGIN + httpequiv_accept,
+ expect_url: do_not_expect,
+ type: "iframe" });
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/http-equiv-cross-origin-iframe-not-setting-own-origin.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/http-equiv-cross-origin-iframe-not-setting-own-origin.https.html
new file mode 100644
index 0000000000..705b65a7bf
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/http-equiv-cross-origin-iframe-not-setting-own-origin.https.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Accept-CH Persistence test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+run_test({ name: "http-equiv cross origin iframe not setting own origin",
+ initial_url: host_info.HTTPS_REMOTE_ORIGIN + echo,
+ accept_url: host_info.HTTPS_REMOTE_ORIGIN + httpequiv_accept,
+ expect_url: host_info.HTTPS_REMOTE_ORIGIN + do_not_expect,
+ type: "iframe" });
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/http-equiv-cross-origin-navigation.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/http-equiv-cross-origin-navigation.https.html
new file mode 100644
index 0000000000..d2fa472015
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/http-equiv-cross-origin-navigation.https.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Accept-CH Persistence test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+run_test({ name: "http-equiv cross origin navigation",
+ initial_url: echo,
+ accept_url: host_info.HTTPS_REMOTE_ORIGIN + httpequiv_accept,
+ expect_url: host_info.HTTPS_REMOTE_ORIGIN + do_not_expect,
+ type: "navigation" });
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/http-equiv-cross-origin-subresource.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/http-equiv-cross-origin-subresource.https.html
new file mode 100644
index 0000000000..7bb5d1520c
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/http-equiv-cross-origin-subresource.https.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Accept-CH Persistence test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+run_test({ name: "http-equiv cross origin subresource",
+ initial_url: host_info.HTTPS_REMOTE_ORIGIN + echo,
+ accept_url: host_info.HTTPS_REMOTE_ORIGIN + httpequiv_accept,
+ expect_url: host_info.HTTPS_REMOTE_ORIGIN + do_not_expect,
+ type: "subresource" });
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/http-equiv-same-origin-iframe.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/http-equiv-same-origin-iframe.https.html
new file mode 100644
index 0000000000..93380ad23c
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/http-equiv-same-origin-iframe.https.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<!-- <meta name="timeout" content="long"> -->
+<title>Accept-CH Persistence test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+run_test({ name: "http-equiv same origin iframe",
+ initial_url: echo,
+ accept_url: httpequiv_accept,
+ expect_url: do_not_expect,
+ type: "iframe" });
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/http-equiv-same-origin-navigation.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/http-equiv-same-origin-navigation.https.html
new file mode 100644
index 0000000000..6efd7ccdf5
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/http-equiv-same-origin-navigation.https.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Accept-CH Persistence test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+run_test({ name: "http-equiv same origin navigation",
+ initial_url: echo,
+ accept_url: httpequiv_accept,
+ expect_url: do_not_expect,
+ type: "navigation" });
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/http-equiv-same-origin-subresource.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/http-equiv-same-origin-subresource.https.html
new file mode 100644
index 0000000000..793bf5e079
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/http-equiv-same-origin-subresource.https.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Accept-CH Persistence test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+run_test({ name: "http-equiv same origin subresource",
+ initial_url: echo,
+ accept_url: httpequiv_accept,
+ expect_url: do_not_expect,
+ type: "subresource" });
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-cross-origin-iframe-not-setting-other-origins.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-cross-origin-iframe-not-setting-other-origins.https.html
new file mode 100644
index 0000000000..9c4e9cf506
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-cross-origin-iframe-not-setting-other-origins.https.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Meta cross origin iframe not setting other origins</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+run_test({ name: "meta-equiv delegate-ch cross origin iframe not setting other origins",
+ initial_url: echo,
+ accept_url: host_info.HTTPS_REMOTE_ORIGIN + metaequiv_delegate,
+ expect_url: do_not_expect,
+ type: "iframe" });
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-cross-origin-iframe-not-setting-own-origin.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-cross-origin-iframe-not-setting-own-origin.https.html
new file mode 100644
index 0000000000..26e30a40e7
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-cross-origin-iframe-not-setting-own-origin.https.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Meta cross origin iframe not setting own origin</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+run_test({ name: "meta-equiv delegate-ch cross origin iframe not setting own origin",
+ initial_url: host_info.HTTPS_REMOTE_ORIGIN + echo,
+ accept_url: host_info.HTTPS_REMOTE_ORIGIN + metaequiv_delegate,
+ expect_url: host_info.HTTPS_REMOTE_ORIGIN + do_not_expect,
+ type: "iframe" });
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-cross-origin-navigation.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-cross-origin-navigation.https.html
new file mode 100644
index 0000000000..eb2292b22f
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-cross-origin-navigation.https.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Meta cross origin navigation</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+run_test({ name: "meta-equiv delegate-ch cross origin navigation",
+ initial_url: echo,
+ accept_url: host_info.HTTPS_REMOTE_ORIGIN + metaequiv_delegate,
+ expect_url: host_info.HTTPS_REMOTE_ORIGIN + do_not_expect,
+ type: "navigation" });
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-cross-origin-subresource.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-cross-origin-subresource.https.html
new file mode 100644
index 0000000000..31775cbea2
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-cross-origin-subresource.https.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Meta cross origin subresource</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+run_test({ name: "meta-equiv delegate-ch cross origin subresource",
+ initial_url: host_info.HTTPS_REMOTE_ORIGIN + echo,
+ accept_url: host_info.HTTPS_REMOTE_ORIGIN + metaequiv_delegate,
+ expect_url: host_info.HTTPS_REMOTE_ORIGIN + do_not_expect,
+ type: "subresource" });
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-delegate-ch-cross-origin-iframe-with-hints.https.sub.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-delegate-ch-cross-origin-iframe-with-hints.https.sub.html
new file mode 100644
index 0000000000..0a2c4897bc
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-delegate-ch-cross-origin-iframe-with-hints.https.sub.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<meta http-equiv="Delegate-CH" content="sec-ch-device-memory https://www1.{{host}}:{{ports[https][0]}}/; device-memory https://www1.{{host}}:{{ports[https][0]}}/">
+<title>Meta-equiv Delegate-CH cross origin iframe with hints</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+verify_iframe_state(
+ host_info.HTTPS_REMOTE_ORIGIN + "/client-hints/accept-ch-stickiness/resources/do-expect-received.py",
+ "meta-equiv cross origin iframe with hints");
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-delegate-ch-cross-origin-iframe-without-hints.https.sub.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-delegate-ch-cross-origin-iframe-without-hints.https.sub.html
new file mode 100644
index 0000000000..b79a941237
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-delegate-ch-cross-origin-iframe-without-hints.https.sub.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<meta http-equiv="Delegate-CH" content="sec-ch-device-memory https://{{host}}:{{ports[https][0]}}/; device-memory https://{{host}}:{{ports[https][0]}}/">
+<title>Meta-equiv Delegate-CH cross origin iframe without hints</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+verify_iframe_state(
+ host_info.HTTPS_REMOTE_ORIGIN + "/client-hints/accept-ch-stickiness/resources/do-not-expect-received.py",
+ "meta-equiv cross origin iframe without hints");
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-delegate-ch-cross-origin-subresource-with-hints.https.sub.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-delegate-ch-cross-origin-subresource-with-hints.https.sub.html
new file mode 100644
index 0000000000..bd39cbaff4
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-delegate-ch-cross-origin-subresource-with-hints.https.sub.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<meta http-equiv="Delegate-CH" content="sec-ch-device-memory https://www1.{{host}}:{{ports[https][0]}}/; device-memory https://www1.{{host}}:{{ports[https][0]}}/">
+<title>Meta-equiv Delegate-CH cross origin subresource with hints</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+verify_subresource_state(
+ host_info.HTTPS_REMOTE_ORIGIN + "/client-hints/accept-ch-stickiness/resources/do-expect-received.py",
+ "meta-equiv cross origin subresource with hints");
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-delegate-ch-cross-origin-subresource-without-hints.https.sub.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-delegate-ch-cross-origin-subresource-without-hints.https.sub.html
new file mode 100644
index 0000000000..20a3357fb6
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-delegate-ch-cross-origin-subresource-without-hints.https.sub.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<meta http-equiv="Delegate-CH" content="sec-ch-device-memory https://{{host}}:{{ports[https][0]}}/; device-memory https://{{host}}:{{ports[https][0]}}/">
+<title>Meta-equiv Delegate-CH cross origin subresource without hints</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+verify_subresource_state(
+ host_info.HTTPS_REMOTE_ORIGIN + "/client-hints/accept-ch-stickiness/resources/do-not-expect-received.py",
+ "meta-equiv cross origin subresource without hints");
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-same-origin-iframe.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-same-origin-iframe.https.html
new file mode 100644
index 0000000000..06a68ce3bc
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-same-origin-iframe.https.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<!-- <meta name="timeout" content="long"> -->
+<title>Meta same origin iframe</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+run_test({ name: "meta-equiv delegate-ch same origin iframe",
+ initial_url: echo,
+ accept_url: metaequiv_delegate,
+ expect_url: do_not_expect,
+ type: "iframe" });
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-same-origin-navigation.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-same-origin-navigation.https.html
new file mode 100644
index 0000000000..10fb120fc2
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-same-origin-navigation.https.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Meta same origin navigation</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+run_test({ name: "meta-equiv delegate-ch same origin navigation",
+ initial_url: echo,
+ accept_url: metaequiv_delegate,
+ expect_url: do_not_expect,
+ type: "navigation" });
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-same-origin-subresource.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-same-origin-subresource.https.html
new file mode 100644
index 0000000000..b4374476c6
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/meta-equiv-same-origin-subresource.https.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Meta same origin subresource</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+run_test({ name: "meta-equiv delegate-ch same origin subresource",
+ initial_url: echo,
+ accept_url: metaequiv_delegate,
+ expect_url: do_not_expect,
+ type: "subresource" });
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/accept-ch-and-redir.py b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/accept-ch-and-redir.py
new file mode 100644
index 0000000000..16ab11faa7
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/accept-ch-and-redir.py
@@ -0,0 +1,5 @@
+def main(request, response):
+ url = b''
+ if b'url' in request.GET:
+ url = request.GET[b'url']
+ return 301, [(b'Location', url),(b'Accept-CH', b'sec-ch-device-memory, device-memory, Sec-CH-DPR, DPR')], u''
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/accept-ch-blank.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/accept-ch-blank.html
new file mode 100644
index 0000000000..8a2e40ad90
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/accept-ch-blank.html
@@ -0,0 +1,11 @@
+<html>
+<body>
+
+<!-- Page with an empty accept-ch header, which disables client hints -->
+<script>
+ window.top.opener.postMessage('Loaded', '*');
+</script>
+
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/accept-ch-blank.html.headers b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/accept-ch-blank.html.headers
new file mode 100644
index 0000000000..25215abdf7
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/accept-ch-blank.html.headers
@@ -0,0 +1,2 @@
+Accept-CH:
+Access-Control-Allow-Origin: *
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/accept-ch-test.js b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/accept-ch-test.js
new file mode 100644
index 0000000000..b0bf39250c
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/accept-ch-test.js
@@ -0,0 +1,133 @@
+const echo = "/client-hints/accept-ch-stickiness/resources/echo-client-hints-received.py";
+const accept = "/client-hints/accept-ch-stickiness/resources/accept-ch.html";
+const accept_blank = "/client-hints/accept-ch-stickiness/resources/accept-ch-blank.html";
+const no_accept = "/client-hints/accept-ch-stickiness/resources/no-accept-ch.html";
+const httpequiv_accept = "/client-hints/accept-ch-stickiness/resources/http-equiv-accept-ch.html";
+const metaequiv_delegate = "/client-hints/accept-ch-stickiness/resources/meta-equiv-delegate-ch.html";
+const expect = "/client-hints/accept-ch-stickiness/resources/expect-client-hints-headers.html"
+const do_not_expect = "/client-hints/accept-ch-stickiness/resources/do-not-expect-client-hints-headers.html"
+
+const host_info = get_host_info();
+
+function verify_initial_state(initial_url, test_name) {
+ promise_test(t => {
+ return fetch(initial_url).then(r => {
+ assert_equals(r.status, 200)
+ // Verify that the browser did not include client hints in the request
+ // headers when fetching echo-client-hints-received.py.
+ assert_false(r.headers.has("device-memory-received"),
+ "device-memory-received");
+ assert_false(r.headers.has("device-memory-deprecated-received"),
+ "device-memory-deprecated-received");
+ });
+ }, test_name + " precondition: Test that the browser does not have client " +
+ "hints preferences cached");
+}
+
+function verify_iframe_state(expect_url, test_name) {
+ promise_test(t => {
+ return new Promise(resolve => {
+ window.addEventListener('message', t.step_func(function(e) {
+ assert_equals(e.data, "PASS", "message from opened frame");
+ fetch("/client-hints/accept-ch-stickiness/resources/clear-site-data.html").then(resolve);
+ }));
+ const iframe = document.createElement("iframe");
+ iframe.src = expect_url;
+ document.body.appendChild(iframe);
+ });
+ }, test_name + " got client hints according to expectations.");
+}
+
+function verify_navigation_state(expect_url, test_name) {
+ promise_test(t => {
+ return new Promise(resolve => {
+ let win;
+ window.addEventListener('message', t.step_func(function(e) {
+ win.close();
+ assert_equals(e.data, "PASS", "message from opened page");
+ fetch("/client-hints/accept-ch-stickiness/resources/clear-site-data.html").then(resolve);
+ }));
+ // Open a new window. Verify that the user agent attaches client hints.
+ win = window.open(expect_url);
+ assert_not_equals(win, null, "Popup windows not allowed?");
+ });
+ }, test_name + " got client hints according to expectations.");
+}
+
+function verify_subresource_state(expect_url, test_name) {
+ promise_test(t => {
+ return new Promise(resolve => {
+ fetch(expect_url).then(response => response.text()).then(t.step_func(text => {
+ assert_true(text.includes("PASS"));
+ fetch("/client-hints/accept-ch-stickiness/resources/clear-site-data.html").then(resolve);
+ }));
+ });
+ }, test_name + " got client hints according to expectations.");
+}
+
+function verify_syncxhr_state(expect_url, test_name) {
+ promise_test(t => {
+ return new Promise(resolve => {
+ const xhr = new XMLHttpRequest();
+ xhr.onreadystatechange = t.step_func(() => {
+ if (xhr.readyState != XMLHttpRequest.DONE) {
+ return;
+ }
+ assert_true(xhr.responseText.includes("PASS"));
+ fetch("/client-hints/accept-ch-stickiness/resources/clear-site-data.html").then(resolve);
+ });
+ xhr.open("GET", expect_url, false /* async */);
+ xhr.send();
+ });
+ }, test_name + " got client hints according to expectations.");
+}
+
+function attempt_set(test_type, accept_url, test_name, test_name_suffix) {
+ promise_test(t => {
+ return new Promise(resolve => {
+ if (test_type == "navigation") {
+ const win = window.open(accept_url);
+ assert_not_equals(win, null, "Popup windows not allowed?");
+ addEventListener('message', t.step_func(() => {
+ win.close();
+ resolve();
+ }), false);
+ } else if (test_type == "iframe") {
+ const iframe = document.createElement("iframe");
+ iframe.addEventListener('load', t.step_func(() => {
+ resolve();
+ }), false);
+ iframe.src = accept_url;
+ document.body.appendChild(iframe);
+ } else if (test_type == "subresource") {
+ fetch(accept_url).then(r => {
+ assert_equals(r.status, 200, "subresource response status")
+ // Verify that the browser did not include client hints in the request
+ // headers, just because we can..
+ assert_false(r.headers.has("device-memory-received"),
+ "device-memory-received",
+ "subresource request had no client hints");
+ assert_false(r.headers.has("device-memory-deprecated-received"),
+ "device-memory-deprecated-received",
+ "subresource request had no client hints");
+ resolve();
+ });
+ } else {
+ assert_unreached("unknown test type");
+ }
+ });
+ }, test_name + " set Accept-CH" + test_name_suffix);
+}
+
+const run_test = test => {
+ // First, verify the initial state to make sure that the browser does not have
+ // client hints preferences cached from a previous run of the test.
+ verify_initial_state(test.initial_url, test.name);
+
+ // Then, attempt to set Accept-CH
+ attempt_set(test.type, test.accept_url, test.name, "");
+
+ // Finally, verify that CH are actually sent (or not) on requests
+ verify_navigation_state(test.expect_url, test.name);
+};
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/accept-ch.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/accept-ch.html
new file mode 100644
index 0000000000..694c5b03bc
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/accept-ch.html
@@ -0,0 +1,16 @@
+<html>
+<body>
+
+<!-- An empty webpage whose response headers include Accept-CH
+header. Fetching this webpage should cause
+user-agent to persist origin preferences for the client hints
+specified in the Accept-CH header until a Clear-Site-Data header
+is sent or user action is take to clear the session or data about
+the origin.-->
+<script>
+ window.top.opener.postMessage('Loaded', '*');
+</script>
+
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/accept-ch.html.headers b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/accept-ch.html.headers
new file mode 100644
index 0000000000..e3ee616f2b
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/accept-ch.html.headers
@@ -0,0 +1,2 @@
+Accept-CH: sec-ch-device-memory, device-memory, Sec-CH-DPR, DPR
+Access-Control-Allow-Origin: *
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/clear-site-data.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/clear-site-data.html
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/clear-site-data.html
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/clear-site-data.html.headers b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/clear-site-data.html.headers
new file mode 100644
index 0000000000..955424f246
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/clear-site-data.html.headers
@@ -0,0 +1,2 @@
+Clear-Site-Data: "*"
+Access-Control-Allow-Origin: * \ No newline at end of file
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/do-expect-received.py b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/do-expect-received.py
new file mode 100644
index 0000000000..d2a544e46a
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/do-expect-received.py
@@ -0,0 +1,20 @@
+def main(request, response):
+ """
+ Check that headers sent to navigate here *do* contain the device-memory client
+ hint, and report success/failure in a way compatible with
+ verify_{subresource|iframe}_state() in accept-ch-test.js
+ """
+
+ if b"device-memory" in request.headers and b"sec-ch-device-memory" in request.headers:
+ result = u"PASS"
+ else:
+ result = u"FAIL"
+
+ content = u'''
+<script>
+ let messagee = window.opener || window.parent;
+ messagee.postMessage("%s" , "*");
+</script>
+''' % (result)
+ headers = [(b"Content-Type", b"text/html"), (b"Access-Control-Allow-Origin", b"*")]
+ return 200, headers, content
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/do-not-expect-client-hints-headers.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/do-not-expect-client-hints-headers.html
new file mode 100644
index 0000000000..2421eea18e
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/do-not-expect-client-hints-headers.html
@@ -0,0 +1,22 @@
+<html>
+<body>
+<script>
+
+// This test checks if browser attaches the device-memory client hint in the
+// HTTP request headers.
+
+// echo-client-hints-received.py sets the response headers depending on the set
+// of client hints it receives in the request headers.
+
+fetch("/client-hints/accept-ch-stickiness/resources/echo-client-hints-received.py").then(r => {
+ if(r.status == 200 && !r.headers.has("device-memory-received") && !r.headers.has("device-memory-deprecated-received")) {
+ window.top.opener.postMessage('PASS', '*');
+ }
+ else {
+ window.top.opener.postMessage('FAIL', '*');
+ }
+});
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/do-not-expect-received.py b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/do-not-expect-received.py
new file mode 100644
index 0000000000..48ebc21d14
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/do-not-expect-received.py
@@ -0,0 +1,20 @@
+def main(request, response):
+ """
+ Check that headers sent to navigate here *do not* contain the device-memory client
+ hint, and report success/failure in a way compatible with
+ verify_{subresource|iframe}_state() in accept-ch-test.js
+ """
+
+ if b"device-memory" in request.headers or b"sec-ch-device-memory" in request.headers:
+ result = u"FAIL"
+ else:
+ result = u"PASS"
+
+ content = u'''
+<script>
+ let messagee = window.opener || window.parent;
+ messagee.postMessage("%s" , "*");
+</script>
+''' % (result)
+ headers = [(b"Content-Type", b"text/html"), (b"Access-Control-Allow-Origin", b"*")]
+ return 200, headers, content
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/echo-client-hints-received.py b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/echo-client-hints-received.py
new file mode 100644
index 0000000000..3c61330671
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/echo-client-hints-received.py
@@ -0,0 +1,14 @@
+def main(request, response):
+ """
+ Simple handler that sets a response header based on which client hint
+ request headers were received.
+ """
+
+ response.headers.append(b"Access-Control-Allow-Origin", b"*")
+ response.headers.append(b"Access-Control-Allow-Headers", b"*")
+ response.headers.append(b"Access-Control-Expose-Headers", b"*")
+
+ if b"sec-ch-device-memory" in request.headers:
+ response.headers.set(b"device-memory-received", request.headers.get(b"sec-ch-device-memory"))
+ if b"device-memory" in request.headers:
+ response.headers.set(b"device-memory-deprecated-received", request.headers.get(b"device-memory"))
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/expect-client-hints-headers.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/expect-client-hints-headers.html
new file mode 100644
index 0000000000..1cde2ffd05
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/expect-client-hints-headers.html
@@ -0,0 +1,22 @@
+<html>
+<body>
+<script>
+
+// This test checks if browser attaches the device-memory client hint in the
+// HTTP request headers.
+
+// echo-client-hints-received.py sets the response headers depending on the set
+// of client hints it receives in the request headers.
+
+fetch("/client-hints/accept-ch-stickiness/resources/echo-client-hints-received.py").then(r => {
+ if(r.status == 200 && r.headers.has("device-memory-received") && r.headers.has("device-memory-deprecated-received")) {
+ window.top.opener.postMessage('PASS', '*');
+ }
+ else {
+ window.top.opener.postMessage('FAIL', '*');
+ }
+});
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/expect-received.py b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/expect-received.py
new file mode 100644
index 0000000000..876f025f3e
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/expect-received.py
@@ -0,0 +1,28 @@
+def main(request, response):
+ """
+ Check that headers sent to navigate here contain the device-memory client
+ hint, and report success/failure in a way compatible with
+ verify_navigation_state() in accept-ch-test.js
+ """
+
+ if b"sec-ch-device-memory" not in request.headers:
+ result = u"DEVICE-MEMORY"
+ elif b"device-memory" not in request.headers:
+ result = u"DEVICE-MEMORY-DEPRECATED"
+ elif b"sec-ch-ua" not in request.headers:
+ result = u"UA"
+ elif b"sec-ch-ua-mobile" not in request.headers:
+ result = u"MOBILE"
+ elif b"sec-ch-ua-platform" not in request.headers:
+ result = u"PLATFORM"
+ else:
+ result = u"PASS"
+
+ content = u'''
+<script>
+ let messagee = window.opener || window.parent;
+ messagee.postMessage("%s" , "*");
+</script>
+''' % (result)
+ headers = [(b"Content-Type", b"text/html"), (b"Access-Control-Allow-Origin", b"*")]
+ return 200, headers, content
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/feature-policy-with-cross-origin-subresource.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/feature-policy-with-cross-origin-subresource.html
new file mode 100644
index 0000000000..f7e1a767b4
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/feature-policy-with-cross-origin-subresource.html
@@ -0,0 +1,25 @@
+<html>
+<body>
+<script src="/common/get-host-info.sub.js"></script>
+<script>
+
+// This test checks if browser attaches the device-memory client hint in the
+// HTTP request headers --- while requesting it from 3P context after
+// settings feature policy to allow it; with Accept-CH coming from a sticky
+// source.
+
+// echo-client-hints-received.py sets the response headers depending on the set
+// of client hints it receives in the request headers.
+
+fetch(get_host_info()["HTTPS_REMOTE_ORIGIN"] + "/client-hints/accept-ch-stickiness/resources/echo-client-hints-received.py").then(r => {
+ if(r.status == 200 && r.headers.has("device-memory-received") && r.headers.has("device-memory-deprecated-received")) {
+ window.top.opener.postMessage('PASS', '*');
+ }
+ else {
+ window.top.opener.postMessage('FAIL', '*');
+ }
+});
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/feature-policy-with-cross-origin-subresource.html.headers b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/feature-policy-with-cross-origin-subresource.html.headers
new file mode 100644
index 0000000000..4738714fb4
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/feature-policy-with-cross-origin-subresource.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: ch-device-memory=*
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/http-equiv-accept-ch.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/http-equiv-accept-ch.html
new file mode 100644
index 0000000000..561cae49ca
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/http-equiv-accept-ch.html
@@ -0,0 +1,8 @@
+<html>
+<meta http-equiv="Accept-CH" content="sec-ch-device-memory,device-memory">
+<body>
+<script>
+ window.top.opener.postMessage('Loaded', '*');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/http-equiv-accept-ch.html.headers b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/http-equiv-accept-ch.html.headers
new file mode 100644
index 0000000000..27140bf36e
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/http-equiv-accept-ch.html.headers
@@ -0,0 +1,2 @@
+Access-Control-Allow-Origin: *
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/meta-equiv-delegate-ch.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/meta-equiv-delegate-ch.html
new file mode 100644
index 0000000000..7d7c4ccdf7
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/meta-equiv-delegate-ch.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<meta http-equiv="Delegate-CH" content="sec-ch-device-memory;device-memory">
+<body>
+<script>
+ window.top.opener.postMessage('Loaded', '*');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/meta-equiv-delegate-ch.html.headers b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/meta-equiv-delegate-ch.html.headers
new file mode 100644
index 0000000000..27140bf36e
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/meta-equiv-delegate-ch.html.headers
@@ -0,0 +1,2 @@
+Access-Control-Allow-Origin: *
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/no-accept-ch.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/no-accept-ch.html
new file mode 100644
index 0000000000..16ed6c1a7c
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/no-accept-ch.html
@@ -0,0 +1,11 @@
+<html>
+<body>
+
+<!-- Page with out an accept-ch header; client hints are unaffected -->
+<script>
+ window.top.opener.postMessage('Loaded', '*');
+</script>
+
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/no-accept-ch.html.headers b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/no-accept-ch.html.headers
new file mode 100644
index 0000000000..cb762eff80
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/resources/no-accept-ch.html.headers
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: *
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-iframe.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-iframe.https.html
new file mode 100644
index 0000000000..c768094544
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-iframe.https.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Accept-CH Persistence test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+run_test({ name: "same origin iframe can't set accept-ch",
+ initial_url: echo,
+ accept_url: accept,
+ expect_url: do_not_expect,
+ type: "iframe" });
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-navigation-empty-accept-ch.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-navigation-empty-accept-ch.https.html
new file mode 100644
index 0000000000..e57f1c3dc6
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-navigation-empty-accept-ch.https.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Accept-CH Persistence test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+// Tests that an empty accept-ch header disables client hints.
+const test_name = "empty-ch on navigation";
+verify_initial_state(echo, test_name);
+attempt_set("navigation", accept, test_name, " to non-empty first");
+attempt_set("navigation", accept_blank, test_name, " to empty second");
+verify_navigation_state(do_not_expect, test_name);
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-navigation-no-accept-ch.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-navigation-no-accept-ch.https.html
new file mode 100644
index 0000000000..ab59770176
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-navigation-no-accept-ch.https.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Accept-CH Persistence test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+// Tests that a non-existing accept-ch header doesn't affect client hints.
+const test_name = "empty-ch on navigation";
+verify_initial_state(echo, test_name);
+attempt_set("navigation", accept, test_name, " to non-empty first");
+attempt_set("navigation", no_accept, test_name, " w/o header second");
+verify_navigation_state(expect, test_name);
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-navigation-redirect.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-navigation-redirect.https.html
new file mode 100644
index 0000000000..69fc55e8f3
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-navigation-redirect.https.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Accept-CH Persistence test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+// This is similar to accept-ch-test.js tests, except setting and checking
+// header here are a single step, connected via redirect.
+const test_name = "redirect on navigation";
+verify_initial_state(echo, test_name);
+verify_navigation_state("resources/accept-ch-and-redir.py?url=expect-received.py", test_name);
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-navigation.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-navigation.https.html
new file mode 100644
index 0000000000..e35cbdcedd
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-navigation.https.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Accept-CH Persistence test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+run_test({ name: "same origin navigation",
+ initial_url: echo,
+ accept_url: accept,
+ expect_url: expect,
+ type: "navigation" });
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-subresource-redirect-opted-in.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-subresource-redirect-opted-in.https.html
new file mode 100644
index 0000000000..66c0e57497
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-subresource-redirect-opted-in.https.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Accept-CH Persistence test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+// Make sure a same origin subresource that gets redirected keeps the initial request's Client Hints.
+const test_name = "same-origin subresource redirect with opt-in";
+verify_subresource_state("resources/accept-ch-and-redir.py?url=expect-received.py", test_name);
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-subresource-redirect-opted-in.https.html.headers b/testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-subresource-redirect-opted-in.https.html.headers
new file mode 100644
index 0000000000..af6945319d
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-subresource-redirect-opted-in.https.html.headers
@@ -0,0 +1 @@
+Accept-CH: Sec-CH-DPR, DPR, Sec-CH-Device-Memory, Device-Memory
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-subresource-redirect.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-subresource-redirect.https.html
new file mode 100644
index 0000000000..8e687b58b1
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-subresource-redirect.https.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Accept-CH Persistence test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+// Make sure a same origin subresource without an opt-in that gets redirected doesn't contain Client Hints.
+const test_name = "same-origin subresource redirect with no opt-in";
+verify_initial_state(echo, test_name);
+verify_subresource_state("resources/accept-ch-and-redir.py?url=do-not-expect-received.py", test_name);
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-subresource.https.html b/testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-subresource.https.html
new file mode 100644
index 0000000000..0b7151a2cc
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch-stickiness/same-origin-subresource.https.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Accept-CH Persistence test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/accept-ch-test.js"></script>
+
+<script>
+run_test({ name: "same origin subresource",
+ initial_url: echo,
+ accept_url: accept,
+ expect_url: do_not_expect,
+ type: "subresource" });
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch.wildcard.https.sub.html b/testing/web-platform/tests/client-hints/accept-ch.wildcard.https.sub.html
new file mode 100644
index 0000000000..36eccd6a52
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch.wildcard.https.sub.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// Here's the set-up for this test:
+// Step 1 (top-frame) Embeds a cross-origin iframe.
+// Step 2 (sub-frame) Make a cross-origin POST request.
+// Step 3 (sub-frame) Checks if platform version header is present and alerts top frame.
+// Step 4 (top-frame) Asserts header was present and returns.
+async_test(t => {
+ // Step 4
+ window.addEventListener("message", t.step_func(e => {
+ assert_equals(e.data, "HadPlatformVersion");
+ t.done();
+ }));
+
+ // Step 1
+ let iframe = document.createElement("iframe");
+ iframe.src = "https://{{domains[www1]}}:{{ports[https][0]}}/client-hints/resources/accept-ch.wildcard.iframe.https.sub.html";
+ document.body.appendChild(iframe);
+}, "Accept-CH with wildcard policy and iframe subresource");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/accept-ch.wildcard.https.sub.html.sub.headers b/testing/web-platform/tests/client-hints/accept-ch.wildcard.https.sub.html.sub.headers
new file mode 100644
index 0000000000..66ad9243c7
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch.wildcard.https.sub.html.sub.headers
@@ -0,0 +1,2 @@
+Accept-CH: sec-ch-ua-platform-version
+Permissions-Policy: ch-ua-platform-version=("https://*.{{domains[]}}:{{ports[https][0]}}") \ No newline at end of file
diff --git a/testing/web-platform/tests/client-hints/accept-ch/__dir__.headers b/testing/web-platform/tests/client-hints/accept-ch/__dir__.headers
new file mode 100644
index 0000000000..4d4739028d
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch/__dir__.headers
@@ -0,0 +1 @@
+Accept-CH: device-memory, dpr, width, viewport-width, rtt, downlink, ect, sec-ch-ua, sec-ch-ua-arch, sec-ch-ua-platform, sec-ch-ua-model, sec-ch-ua-mobile, sec-ch-ua-full-version, sec-ch-ua-platform-version, sec-ch-prefers-color-scheme, sec-ch-prefers-reduced-motion, sec-ch-ua-bitness, sec-ch-viewport-height, sec-ch-device-memory, sec-ch-dpr, sec-ch-width, sec-ch-viewport-width, sec-ch-ua-full-version-list, sec-ch-ua-wow64, sec-ch-prefers-reduced-transparency
diff --git a/testing/web-platform/tests/client-hints/accept-ch/answers.sub.https.html b/testing/web-platform/tests/client-hints/accept-ch/answers.sub.https.html
new file mode 100644
index 0000000000..e49a515bf4
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch/answers.sub.https.html
@@ -0,0 +1,68 @@
+<html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+
+// If the response for the HTML file contains "Accept-CH" in the response
+// headers, then the browser should attach the specified client hints in the
+// HTTP request headers depending on whether the resource is being fetched from
+// the same origin or a different origin. Test this functionality by fetching
+// same-origin and cross-origin resources from this page. The response headers
+// for this page include "Accept-CH: device-memory, dpr, viewport-width, rtt, downlink, ect".
+//
+// resources/echo-client-hints-received.py sets the response headers depending on the set
+// of client hints it receives in the request headers.
+
+promise_test(t => {
+ return fetch("https://{{domains[]}}:{{ports[https][0]}}/client-hints/resources/echo-client-hints-received.py").then(r => {
+ assert_equals(r.status, 200)
+ // Verify that the browser includes client hints in the headers for a
+ // same-origin fetch.
+ assert_true(r.headers.has("device-memory-received"), "device-memory-received");
+ assert_true(r.headers.has("device-memory-deprecated-received"), "device-memory-deprecated-received");
+ assert_true(r.headers.has("dpr-received"), "dpr-received");
+ assert_true(r.headers.has("dpr-deprecated-received"), "dpr-deprecated-received");
+ assert_true(r.headers.has("viewport-width-received"), "viewport-width-received");
+ assert_true(r.headers.has("viewport-width-deprecated-received"), "viewport-width-deprecated-received");
+
+ assert_true(r.headers.has("rtt-received"), "rtt-received");
+ var rtt = parseInt(r.headers.get("rtt-received"));
+ assert_greater_than_equal(rtt, 0);
+ assert_less_than_equal(rtt, 3000);
+ assert_equals(rtt % 50, 0, 'rtt must be a multiple of 50 msec');
+
+ assert_true(r.headers.has("downlink-received"), "downlink-received");
+ var downlinkKbps = r.headers.get("downlink-received") * 1000;
+ assert_greater_than_equal(downlinkKbps, 0);
+ assert_less_than_equal(downlinkKbps, 10000);
+
+ assert_in_array(r.headers.get("ect-received"), ["slow-2g", "2g",
+ "3g", "4g"], 'ect-received is unexpected');
+ });
+}, "Accept-CH header test");
+
+promise_test(t => {
+ return fetch("https://{{domains[www]}}:{{ports[https][0]}}/client-hints/resources/echo-client-hints-received.py").then(r => {
+ assert_equals(r.status, 200)
+ // Verify that the browser does not include client hints in the headers
+ // for a cross-origin fetch.
+ assert_false(r.headers.has("device-memory-received"), "device-memory-received");
+ assert_false(r.headers.has("device-memory-deprecated-received"), "device-memory-deprecated-received");
+ assert_false(r.headers.has("dpr-received"), "dpr-received");
+ assert_false(r.headers.has("dpr-deprecated-received"), "dpr-deprecated-received");
+ assert_false(r.headers.has("viewport-width-received"), "viewport-width-received");
+ assert_false(r.headers.has("viewport-width-deprecated-received"), "viewport-width-deprecated-received");
+ assert_false(r.headers.has("rtt-received"), "rtt-received");
+ assert_false(r.headers.has("downlink-received"), "downlink-received");
+ assert_false(r.headers.has("ect-received"), "ect-received");
+ });
+}, "Cross-Origin Accept-CH header test");
+
+
+
+</script>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/accept-ch/cache-revalidation.https.html b/testing/web-platform/tests/client-hints/accept-ch/cache-revalidation.https.html
new file mode 100644
index 0000000000..5ed6f074a7
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch/cache-revalidation.https.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Tests Stale While Revalidate is not executed for fetch API</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/client-hints/resources/export.js"></script>
+<script src="/common/utils.js"></script>
+<script>
+function wait25ms(test) {
+ return new Promise(resolve => {
+ test.step_timeout(() => {
+ resolve();
+ }, 25);
+ });
+}
+
+promise_test(async (test) => {
+ var request_token = token();
+
+ const response = await fetch(`/client-hints/resources/stale-echo-client-hints.py?token=` + request_token);
+ const response2 = await fetch(`/client-hints/resources/stale-echo-client-hints.py?token=` + request_token);
+
+ assert_equals(response.headers.get('Unique-Id'), response2.headers.get('Unique-Id'));
+
+ while(true) {
+ const revalidation_check = await fetch(`/client-hints/resources/stale-echo-client-hints.py?query&token=` + request_token);
+ if (revalidation_check.headers.get('Count') == '2') {
+ client_hints_full_list.forEach(header => {
+ assert_equals(revalidation_check.headers.get(header+"-recieved"), revalidation_check.headers.get(header+"-previous"));
+ });
+ break;
+ }
+ await wait25ms(test);
+ }
+}, 'Same headers sent for revalidation request');
+</script>
diff --git a/testing/web-platform/tests/client-hints/accept-ch/feature-policy-navigation/__dir__.headers b/testing/web-platform/tests/client-hints/accept-ch/feature-policy-navigation/__dir__.headers
new file mode 100644
index 0000000000..4d4739028d
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch/feature-policy-navigation/__dir__.headers
@@ -0,0 +1 @@
+Accept-CH: device-memory, dpr, width, viewport-width, rtt, downlink, ect, sec-ch-ua, sec-ch-ua-arch, sec-ch-ua-platform, sec-ch-ua-model, sec-ch-ua-mobile, sec-ch-ua-full-version, sec-ch-ua-platform-version, sec-ch-prefers-color-scheme, sec-ch-prefers-reduced-motion, sec-ch-ua-bitness, sec-ch-viewport-height, sec-ch-device-memory, sec-ch-dpr, sec-ch-width, sec-ch-viewport-width, sec-ch-ua-full-version-list, sec-ch-ua-wow64, sec-ch-prefers-reduced-transparency
diff --git a/testing/web-platform/tests/client-hints/accept-ch/feature-policy-navigation/feature-policy.https.html b/testing/web-platform/tests/client-hints/accept-ch/feature-policy-navigation/feature-policy.https.html
new file mode 100644
index 0000000000..90a27280c1
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch/feature-policy-navigation/feature-policy.https.html
@@ -0,0 +1,29 @@
+<html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/client-hints/resources/export.js"></script>
+<script src="/client-hints/resources/feature-policy-navigation.js"></script>
+<script>
+(async () => {
+ await test_frame(
+ "HTTPS_REMOTE_ORIGIN",
+ cross_origin_client_hints,
+ "",
+ "Client hints loaded on cross-origin iframe request with feature policy.");
+ await test_frame(
+ "HTTPS_ORIGIN",
+ same_origin_client_hints,
+ "",
+ "Client hints loaded on same-origin iframe request with feature policy.");
+ await test_frame(
+ "HTTPS_REMOTE_ORIGIN",
+ cross_origin_client_hints,
+ "",
+ "Client hints loaded on cross-origin iframe request with feature policy after attempting to set independently.");
+})();
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/accept-ch/feature-policy-navigation/feature-policy.https.html.headers b/testing/web-platform/tests/client-hints/accept-ch/feature-policy-navigation/feature-policy.https.html.headers
new file mode 100644
index 0000000000..11bbceff0a
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch/feature-policy-navigation/feature-policy.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: ch-device-memory=*, ch-dpr=(), ch-viewport-width=(self), ch-ua=(self), ch-ua-mobile=()
diff --git a/testing/web-platform/tests/client-hints/accept-ch/feature-policy-navigation/no-feature-policy.https.html b/testing/web-platform/tests/client-hints/accept-ch/feature-policy-navigation/no-feature-policy.https.html
new file mode 100644
index 0000000000..02458f3116
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch/feature-policy-navigation/no-feature-policy.https.html
@@ -0,0 +1,36 @@
+<html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/client-hints/resources/export.js"></script>
+<script src="/client-hints/resources/feature-policy-navigation.js"></script>
+<script>
+(async () => {
+ await test_frame(
+ "HTTPS_REMOTE_ORIGIN",
+ expect_iframe_no_hints,
+ "",
+ "Client hints not loaded on cross-origin iframe request with no feature policy.");
+ await test_frame(
+ "HTTPS_ORIGIN",
+ expect_iframe_hints,
+ "",
+ "Client hints loaded on same-origin iframe request with no feature policy.");
+
+ let allow = "ch-device-memory *; ch-dpr 'none'; ch-viewport-width 'self'; ch-ua 'self'; ch-ua-mobile 'none';"; await test_frame(
+ "HTTPS_REMOTE_ORIGIN",
+ cross_origin_client_hints,
+ allow,
+ "Client hints loaded on cross-origin iframe request with allow list.");
+ await test_frame(
+ "HTTPS_ORIGIN",
+ same_origin_client_hints,
+ allow,
+ "Client hints loaded on same-origin iframe request with allow list.");
+})();
+
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/client-hints/accept-ch/feature-policy.sub.https.html b/testing/web-platform/tests/client-hints/accept-ch/feature-policy.sub.https.html
new file mode 100644
index 0000000000..8f9be9fde4
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch/feature-policy.sub.https.html
@@ -0,0 +1,79 @@
+<html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script>
+
+// If the response for the HTML file contains "Accept-CH" in the response
+// headers, then the browser should attach the specified client hints in the
+// HTTP request headers depending on whether the resource is being fetched from
+// the same origin or a different origin. Test this functionality by fetching
+// same-origin and cross-origin resources from this page. The response headers
+// for this page include "Accept-CH: device-memory, dpr, viewport-width, rtt, downlink, ect".
+//
+// resources/echo-client-hints-received.py sets the response headers depending on the set
+// of client hints it receives in the request headers.
+
+promise_test(t => {
+ return fetch(get_host_info()["HTTPS_ORIGIN"] + "/client-hints/resources/echo-client-hints-received.py").then(r => {
+ assert_equals(r.status, 200)
+ // Verify that the browser includes client hints in the headers for a
+ // same-origin fetch which not specifically excluded via Feature-Policy.
+ assert_true(r.headers.has("device-memory-received"), "device-memory-received");
+ assert_true(r.headers.has("device-memory-deprecated-received"), "device-memory-deprecated-received");
+ assert_false(r.headers.has("dpr-received"), "dpr-received");
+ assert_false(r.headers.has("dpr-deprecated-received"), "dpr-deprecated-received");
+ assert_true(r.headers.has("viewport-width-received"), "viewport-width-received");
+ assert_true(r.headers.has("viewport-width-deprecated-received"), "viewport-width-deprecated-received");
+
+ assert_true(r.headers.has("rtt-received"), "rtt-received");
+ var rtt = parseInt(r.headers.get("rtt-received"));
+ assert_greater_than_equal(rtt, 0);
+ assert_less_than_equal(rtt, 3000);
+ assert_equals(rtt % 50, 0, 'rtt must be a multiple of 50 msec');
+
+ assert_true(r.headers.has("downlink-received"), "downlink-received");
+ var downlinkKbps = r.headers.get("downlink-received") * 1000;
+ assert_greater_than_equal(downlinkKbps, 0);
+ assert_less_than_equal(downlinkKbps, 10000);
+
+ assert_in_array(r.headers.get("ect-received"), ["slow-2g", "2g",
+ "3g", "4g"], 'ect-received is unexpected');
+
+ assert_true(r.headers.has("mobile-received"));
+ assert_in_array(r.headers.get("mobile-received"), ["?0", "?1"], 'mobile is unexpected');
+ assert_false(r.headers.has("prefers-color-scheme-received"), "prefers-color-scheme-received");
+ assert_false(r.headers.has("prefers-reduced-motion-received"), "prefers-reduced-motion-received");
+ assert_false(r.headers.has("prefers-reduced-transparency-received"), "prefers-reduced-transparency-received");
+ assert_false(r.headers.has("viewport-height-received"), "viewport-height-received");
+ });
+}, "Accept-CH header test");
+
+promise_test(t => {
+ return fetch(get_host_info()["HTTPS_REMOTE_ORIGIN"] + "/client-hints/resources/echo-client-hints-received.py").then(r => {
+ assert_equals(r.status, 200)
+ // Verify that the browser includes client hints in the headers for a
+ // cross-origin fetch which are specifically requested via Feature-Policy.
+ assert_true(r.headers.has("device-memory-received"), "device-memory-received");
+ assert_true(r.headers.has("device-memory-deprecated-received"), "device-memory-deprecated-received");
+ assert_false(r.headers.has("dpr-received"), "dpr-received");
+ assert_false(r.headers.has("dpr-deprecated-received"), "dpr-deprecated-received");
+ assert_false(r.headers.has("viewport-width-received"), "viewport-width-received");
+ assert_false(r.headers.has("viewport-width-deprecated-received"), "viewport-width-deprecated-received");
+ assert_false(r.headers.has("rtt-received"), "rtt-received");
+ assert_false(r.headers.has("downlink-received"), "downlink-received");
+ assert_false(r.headers.has("ect-received"), "ect-received");
+ assert_false(r.headers.has("prefers-color-scheme-received"), "prefers-color-scheme-received");
+ assert_false(r.headers.has("prefers-reduced-motion-received"), "prefers-reduced-motion-received");
+ assert_false(r.headers.has("prefers-reduced-transparency-received"), "prefers-reduced-transparency-received");
+ assert_false(r.headers.has("viewport-height-received"), "viewport-height-received");
+ });
+}, "Cross-Origin Accept-CH header test");
+
+
+
+</script>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/accept-ch/feature-policy.sub.https.html.headers b/testing/web-platform/tests/client-hints/accept-ch/feature-policy.sub.https.html.headers
new file mode 100644
index 0000000000..6e6b89e923
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch/feature-policy.sub.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: ch-device-memory=*, ch-dpr=(), ch-viewport-width=(self), ch-mobile, ch-prefers-color-scheme=(), ch-prefers-reduced-motion=(), ch-viewport-height=(), ch-prefers-reduced-transparency=()
diff --git a/testing/web-platform/tests/client-hints/accept-ch/meta/resource-in-markup-accept-ch.https.html b/testing/web-platform/tests/client-hints/accept-ch/meta/resource-in-markup-accept-ch.https.html
new file mode 100644
index 0000000000..c9b98daccf
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch/meta/resource-in-markup-accept-ch.https.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+
+<meta http-equiv="Accept-CH" content="sec-ch-dpr">
+
+<script src="../../resources/script-set-dpr-header.py"></script>
+
+<script>
+test(() => {
+ assert_greater_than(dprHeader.length, 0,
+ "sec-ch-dpr header should have been received");
+}, "DPR is received in page with Accept-CH http-equiv meta tag");
+</script>
diff --git a/testing/web-platform/tests/client-hints/accept-ch/meta/resource-in-markup-delegate-ch.https.html b/testing/web-platform/tests/client-hints/accept-ch/meta/resource-in-markup-delegate-ch.https.html
new file mode 100644
index 0000000000..751f07f278
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch/meta/resource-in-markup-delegate-ch.https.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+
+<meta http-equiv="Delegate-CH" content="sec-ch-dpr">
+
+<script src="../../resources/script-set-dpr-header.py"></script>
+
+<script>
+test(() => {
+ assert_greater_than(dprHeader.length, 0,
+ "sec-ch-dpr header should have been received");
+}, "DPR is received in page with Accept-CH meta tag");
+</script>
diff --git a/testing/web-platform/tests/client-hints/accept-ch/no-feature-policy.sub.https.html b/testing/web-platform/tests/client-hints/accept-ch/no-feature-policy.sub.https.html
new file mode 100644
index 0000000000..8458bbed7b
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch/no-feature-policy.sub.https.html
@@ -0,0 +1,65 @@
+<html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script>
+
+// If the response for the HTML file contains "Accept-CH" in the response
+// headers, then the browser should attach the specified client hints in the
+// HTTP request headers depending on whether the resource is being fetched from
+// the same origin or a different origin. Test this functionality by fetching
+// same-origin and cross-origin resources from this page. The response headers
+// for this page include "Accept-CH: device-memory, dpr, viewport-width, rtt, downlink, ect".
+//
+// resources/echo-client-hints-received.py sets the response headers depending on the set
+// of client hints it receives in the request headers.
+
+promise_test(t => {
+ return fetch(get_host_info()["HTTPS_ORIGIN"] + "/client-hints/resources/echo-client-hints-received.py").then(r => {
+ assert_equals(r.status, 200)
+ // Verify that the browser includes client hints in the headers for a
+ // same-origin fetch with the default feature policy in place.
+ assert_true(r.headers.has("device-memory-received"), "device-memory-received");
+ assert_true(r.headers.has("device-memory-deprecated-received"), "device-memory-deprecated-received");
+ assert_true(r.headers.has("dpr-received"), "dpr-received");
+ assert_true(r.headers.has("dpr-deprecated-received"), "dpr-deprecated-received");
+ assert_true(r.headers.has("viewport-width-received"), "viewport-width-received");
+ assert_true(r.headers.has("viewport-width-deprecated-received"), "viewport-width-deprecated-received");
+
+ assert_true(r.headers.has("rtt-received"), "rtt-received");
+ var rtt = parseInt(r.headers.get("rtt-received"));
+ assert_greater_than_equal(rtt, 0);
+ assert_less_than_equal(rtt, 3000);
+ assert_equals(rtt % 50, 0, 'rtt must be a multiple of 50 msec');
+
+ assert_true(r.headers.has("downlink-received"), "downlink-received");
+ var downlinkKbps = r.headers.get("downlink-received") * 1000;
+ assert_greater_than_equal(downlinkKbps, 0);
+ assert_less_than_equal(downlinkKbps, 10000);
+
+ assert_in_array(r.headers.get("ect-received"), ["slow-2g", "2g",
+ "3g", "4g"], 'ect-received is unexpected');
+ });
+}, "Accept-CH header test");
+
+promise_test(t => {
+ return fetch(get_host_info()["HTTPS_REMOTE_ORIGIN"] + "/client-hints/resources/echo-client-hints-received.py").then(r => {
+ assert_equals(r.status, 200)
+ // Verify that the browser includes no client hints in the headers for a
+ // cross-origin fetch with the default feature policy in place.
+ assert_false(r.headers.has("device-memory-received"), "device-memory-received");
+ assert_false(r.headers.has("dpr-received"), "dpr-received");
+ assert_false(r.headers.has("viewport-width-received"), "viewport-width-received");
+ assert_false(r.headers.has("rtt-received"), "rtt-received");
+ assert_false(r.headers.has("downlink-received"), "downlink-received");
+ assert_false(r.headers.has("ect-received"), "ect-received");
+ });
+}, "Cross-Origin Accept-CH header test");
+
+
+
+</script>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/accept-ch/non-secure.http.html b/testing/web-platform/tests/client-hints/accept-ch/non-secure.http.html
new file mode 100644
index 0000000000..9115578b57
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/accept-ch/non-secure.http.html
@@ -0,0 +1,38 @@
+<html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+
+// When the response for the HTML file contains "Accept-CH" in the response
+// headers, then the browser should not attach the specified client hints in
+// the HTTP request headers if the response was delivered by an insecure HTTP
+// server. Test this functionality by fetching an XHR from this page hosted on
+// an insecure HTTP server. The response headers for this page include
+// "Accept-CH: device-memory, dpr, viewport-width, rtt, downlink, ect".
+//
+// resources/echo-client-hints-received.py sets the response headers depending on the set
+// of client hints it receives in the request headers.
+
+ promise_test(t => {
+ return fetch("/client-hints/resources/echo-client-hints-received.py").then(r => {
+ assert_equals(r.status, 200)
+ // Verify that the browser does not include client hints in the headers
+ // when fetching the XHR from an insecure HTTP server.
+ assert_false(r.headers.has("device-memory-received"), "device-memory-received");
+ assert_false(r.headers.has("device-memory-deprecated-received"), "device-memory-deprecated-received");
+ assert_false(r.headers.has("dpr-received"), "dpr-received");
+ assert_false(r.headers.has("dpr-deprecated-received"), "dpr-deprecated-received");
+ assert_false(r.headers.has("viewport-width-received"), "viewport-width-received");
+ assert_false(r.headers.has("viewport-width-deprecated-received"), "viewport-width-deprecated-received");
+ assert_false(r.headers.has("rtt-received"), "rtt-received");
+ assert_false(r.headers.has("downlink-received"), "downlink-received");
+ assert_false(r.headers.has("ect-received"), "ect-received");
+ });
+}, "Accept-CH header test");
+
+</script>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-all.https.html b/testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-all.https.html
new file mode 100644
index 0000000000..a371f07fed
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-all.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// Here's the set-up for this test:
+// Step 1 (first window) Open new window to verify that hints were set.
+// Step 2 (second window) Alert first window of client hints present.
+// Step 3 (first window) Asserts client hints were present.
+// Step 4 (first window) Navigate other window to page that clears all data.
+// Step 5 (second window) Navigate to a page that can check client hints still sent.
+// Step 6 (second window) Alert first window of client hints present.
+// Step 7 (first window) Asserts client hints were not present.
+async_test(t => {
+ var new_window;
+
+ // Step 3
+ window.addEventListener("message", t.step_func(e => {
+ assert_equals(e.data, "HadDeviceMemory");
+
+ // Step 7
+ window.addEventListener("message", t.step_func(e => {
+ assert_equals(e.data, "MissingDeviceMemory");
+ t.done();
+ }), {once: true});
+
+ // Step 4
+ new_window.location.href = "/client-hints/clear-site-data/resources/clear-site-data.py";
+ }), {once: true});
+
+ // Step 1
+ new_window = window.open("/client-hints/clear-site-data/resources/check-client-hints.py");
+ t.add_cleanup(() => new_window.close());
+}, "Clear-Site-Data for * should remove all client hints.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-all.https.html.headers b/testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-all.https.html.headers
new file mode 100644
index 0000000000..226bb1c3b1
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-all.https.html.headers
@@ -0,0 +1 @@
+Accept-CH: Sec-CH-Device-Memory
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-cache.https.html b/testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-cache.https.html
new file mode 100644
index 0000000000..1be71bf8b0
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-cache.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// Here's the set-up for this test:
+// Step 1 (first window) Open new window to verify that hints were set.
+// Step 2 (second window) Alert first window of client hints present.
+// Step 3 (first window) Asserts client hints were present.
+// Step 4 (first window) Navigate other window to page that clears cache data.
+// Step 5 (second window) Navigate to a page that can check client hints still sent.
+// Step 6 (second window) Alert first window of client hints present.
+// Step 7 (first window) Asserts client hints were not present.
+async_test(t => {
+ var new_window;
+
+ // Step 3
+ window.addEventListener("message", t.step_func(e => {
+ assert_equals(e.data, "HadDeviceMemory");
+
+ // Step 7
+ window.addEventListener("message", t.step_func(e => {
+ assert_equals(e.data, "MissingDeviceMemory");
+ t.done();
+ }), {once: true});
+
+ // Step 4
+ new_window.location.href = "/client-hints/clear-site-data/resources/clear-site-data.py?target=cache";
+ }), {once: true});
+
+ // Step 1
+ new_window = window.open("/client-hints/clear-site-data/resources/check-client-hints.py");
+ t.add_cleanup(() => new_window.close());
+}, "Clear-Site-Data for cache should remove all client hints.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-cache.https.html.headers b/testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-cache.https.html.headers
new file mode 100644
index 0000000000..226bb1c3b1
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-cache.https.html.headers
@@ -0,0 +1 @@
+Accept-CH: Sec-CH-Device-Memory
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-client-hints.https.html b/testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-client-hints.https.html
new file mode 100644
index 0000000000..d37f62af8f
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-client-hints.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// Here's the set-up for this test:
+// Step 1 (first window) Open new window to verify that hints were set.
+// Step 2 (second window) Alert first window of client hints present.
+// Step 3 (first window) Asserts client hints were present.
+// Step 4 (first window) Navigate other window to page that clears client hint data.
+// Step 5 (second window) Navigate to a page that can check client hints still sent.
+// Step 6 (second window) Alert first window of client hints present.
+// Step 7 (first window) Asserts client hints were not present.
+async_test(t => {
+ var new_window;
+
+ // Step 3
+ window.addEventListener("message", t.step_func(e => {
+ assert_equals(e.data, "HadDeviceMemory");
+
+ // Step 7
+ window.addEventListener("message", t.step_func(e => {
+ assert_equals(e.data, "MissingDeviceMemory");
+ t.done();
+ }), {once: true});
+
+ // Step 4
+ new_window.location.href = "/client-hints/clear-site-data/resources/clear-site-data.py?target=clientHints";
+ }), {once: true});
+
+ // Step 1
+ new_window = window.open("/client-hints/clear-site-data/resources/check-client-hints.py");
+ t.add_cleanup(() => new_window.close());
+}, "Clear-Site-Data for clientHints should remove all client hints.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-client-hints.https.html.headers b/testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-client-hints.https.html.headers
new file mode 100644
index 0000000000..226bb1c3b1
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-client-hints.https.html.headers
@@ -0,0 +1 @@
+Accept-CH: Sec-CH-Device-Memory
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-cookies.https.html b/testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-cookies.https.html
new file mode 100644
index 0000000000..e506b717e1
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-cookies.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// Here's the set-up for this test:
+// Step 1 (first window) Open new window to verify that hints were set.
+// Step 2 (second window) Alert first window of client hints present.
+// Step 3 (first window) Asserts client hints were present.
+// Step 4 (first window) Navigate other window to page that clears cookies.
+// Step 5 (second window) Navigate to a page that can check client hints still sent.
+// Step 6 (second window) Alert first window of client hints present.
+// Step 7 (first window) Asserts client hints were not present.
+async_test(t => {
+ var new_window;
+
+ // Step 3
+ window.addEventListener("message", t.step_func(e => {
+ assert_equals(e.data, "HadDeviceMemory");
+
+ // Step 7
+ window.addEventListener("message", t.step_func(e => {
+ assert_equals(e.data, "MissingDeviceMemory");
+ t.done();
+ }), {once: true});
+
+ // Step 4
+ new_window.location.href = "/client-hints/clear-site-data/resources/clear-site-data.py?target=cookies";
+ }), {once: true});
+
+ // Step 1
+ new_window = window.open("/client-hints/clear-site-data/resources/check-client-hints.py");
+ t.add_cleanup(() => new_window.close());
+}, "Clear-Site-Data for cookies should remove all client hints.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-cookies.https.html.headers b/testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-cookies.https.html.headers
new file mode 100644
index 0000000000..226bb1c3b1
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-cookies.https.html.headers
@@ -0,0 +1 @@
+Accept-CH: Sec-CH-Device-Memory
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-storage.https.html b/testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-storage.https.html
new file mode 100644
index 0000000000..19bce9e91e
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-storage.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// Here's the set-up for this test:
+// Step 1 (first window) Open new window to verify that hints were set.
+// Step 2 (second window) Alert first window of client hints present.
+// Step 3 (first window) Asserts client hints were present.
+// Step 4 (first window) Navigate other window to page that clears storage data.
+// Step 5 (second window) Navigate to a page that can check client hints still sent.
+// Step 6 (second window) Alert first window of client hints present.
+// Step 7 (first window) Asserts client hints were present.
+async_test(t => {
+ var new_window;
+
+ // Step 3
+ window.addEventListener("message", t.step_func(e => {
+ assert_equals(e.data, "HadDeviceMemory");
+
+ // Step 7
+ window.addEventListener("message", t.step_func(e => {
+ assert_equals(e.data, "HadDeviceMemory");
+ t.done();
+ }), {once: true});
+
+ // Step 4
+ new_window.location.href = "/client-hints/clear-site-data/resources/clear-site-data.py?target=storage";
+ }), {once: true});
+
+ // Step 1
+ new_window = window.open("/client-hints/clear-site-data/resources/check-client-hints.py");
+ t.add_cleanup(() => new_window.close());
+}, "Clear-Site-Data for storage should not remove all client hints.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-storage.https.html.headers b/testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-storage.https.html.headers
new file mode 100644
index 0000000000..226bb1c3b1
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/clear-site-data-storage.https.html.headers
@@ -0,0 +1 @@
+Accept-CH: Sec-CH-Device-Memory
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/resources/check-client-hints.py b/testing/web-platform/tests/client-hints/clear-site-data/resources/check-client-hints.py
new file mode 100644
index 0000000000..4fc4c331f2
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/resources/check-client-hints.py
@@ -0,0 +1,15 @@
+"""
+Step 2/6 (/client-hints/clear-site-data/clear-site-data-{}.https.html)
+Step 3/4 (/client-hints/clear-site-data/set-client-hints-{}-clear-{}.https.html)
+"""
+def main(request, response):
+ if b"sec-ch-device-memory" in request.headers:
+ result = u"HadDeviceMemory"
+ else:
+ result = u"MissingDeviceMemory"
+ content = u'''
+ <script>
+ window.opener.postMessage("%s" , "*");
+ </script>''' % (result)
+ headers = [(b"Content-Type", b"text/html")]
+ return 200, headers, content
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/resources/clear-site-data.py b/testing/web-platform/tests/client-hints/clear-site-data/resources/clear-site-data.py
new file mode 100644
index 0000000000..fd16be9fbc
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/resources/clear-site-data.py
@@ -0,0 +1,12 @@
+"""
+Step 5 (/client-hints/clear-site-data/clear-site-data-{}.https.html)
+"""
+def main(request, response):
+ content = u'''
+ <script>
+ window.onload = function() {
+ window.location.href = "/client-hints/clear-site-data/resources/check-client-hints.py";
+ };
+ </script>'''
+ headers = [(b"Content-Type", b"text/html"), (b"Clear-Site-Data", b'"%s"' % (request.GET.first(b"target", b"*")))]
+ return 200, headers, content
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-all.https.html b/testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-all.https.html
new file mode 100644
index 0000000000..22ee0f9861
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-all.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// Here's the set-up for this test:
+// Step 1 (first window) Set client hints in the same header that clears them.
+// Step 2 (first window) Open second window.
+// Step 3 (second window) Message first window about client hints sent.
+// Step 4 (first window) Asserts client hints not sent.
+async_test(t => {
+ // Step 4
+ window.addEventListener("message", t.step_func(e => {
+ assert_equals(e.data, "MissingDeviceMemory");
+ t.done();
+ }), {once: true});
+
+ // Step 2
+ new_window = window.open("/client-hints/clear-site-data/resources/check-client-hints.py");
+ t.add_cleanup(new_window.close);
+}, "Setting client hints after Clear-Site-Data for all shouldn't allow the client hints to take.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-all.https.html.headers b/testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-all.https.html.headers
new file mode 100644
index 0000000000..9768d865bc
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-all.https.html.headers
@@ -0,0 +1,2 @@
+Clear-Site-Data: "*"
+Accept-CH: Sec-CH-Device-Memory
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-cache.https.html b/testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-cache.https.html
new file mode 100644
index 0000000000..3436138b2c
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-cache.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// Here's the set-up for this test:
+// Step 1 (first window) Set client hints in the same header that clears them.
+// Step 2 (first window) Open second window.
+// Step 3 (second window) Message first window about client hints sent.
+// Step 4 (first window) Asserts client hints not sent.
+async_test(t => {
+ // Step 4
+ window.addEventListener("message", t.step_func(e => {
+ assert_equals(e.data, "MissingDeviceMemory");
+ t.done();
+ }), {once: true});
+
+ // Step 2
+ new_window = window.open("/client-hints/clear-site-data/resources/check-client-hints.py");
+ t.add_cleanup(new_window.close);
+}, "Setting client hints after Clear-Site-Data for cache shouldn't allow the client hints to take.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-cache.https.html.headers b/testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-cache.https.html.headers
new file mode 100644
index 0000000000..bb61032ede
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-cache.https.html.headers
@@ -0,0 +1,2 @@
+Clear-Site-Data: "cache"
+Accept-CH: Sec-CH-Device-Memory
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-client-hints.https.html b/testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-client-hints.https.html
new file mode 100644
index 0000000000..18aeab2f63
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-client-hints.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// Here's the set-up for this test:
+// Step 1 (first window) Set client hints in the same header that clears them.
+// Step 2 (first window) Open second window.
+// Step 3 (second window) Message first window about client hints sent.
+// Step 4 (first window) Asserts client hints not sent.
+async_test(t => {
+ // Step 4
+ window.addEventListener("message", t.step_func(e => {
+ assert_equals(e.data, "MissingDeviceMemory");
+ t.done();
+ }), {once: true});
+
+ // Step 2
+ new_window = window.open("/client-hints/clear-site-data/resources/check-client-hints.py");
+ t.add_cleanup(new_window.close);
+}, "Setting client hints after Clear-Site-Data for client hints shouldn't allow the client hints to take.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-client-hints.https.html.headers b/testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-client-hints.https.html.headers
new file mode 100644
index 0000000000..e815201076
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-client-hints.https.html.headers
@@ -0,0 +1,2 @@
+Clear-Site-Data: "clientHints"
+Accept-CH: Sec-CH-Device-Memory
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-cookies.https.html b/testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-cookies.https.html
new file mode 100644
index 0000000000..db780d3abd
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-cookies.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// Here's the set-up for this test:
+// Step 1 (first window) Set client hints in the same header that clears them.
+// Step 2 (first window) Open second window.
+// Step 3 (second window) Message first window about client hints sent.
+// Step 4 (first window) Asserts client hints not sent.
+async_test(t => {
+ // Step 4
+ window.addEventListener("message", t.step_func(e => {
+ assert_equals(e.data, "MissingDeviceMemory");
+ t.done();
+ }), {once: true});
+
+ // Step 2
+ new_window = window.open("/client-hints/clear-site-data/resources/check-client-hints.py");
+ t.add_cleanup(new_window.close);
+}, "Setting client hints after Clear-Site-Data for cookies shouldn't allow the client hints to take.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-cookies.https.html.headers b/testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-cookies.https.html.headers
new file mode 100644
index 0000000000..d0c1afae77
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-cookies.https.html.headers
@@ -0,0 +1,2 @@
+Clear-Site-Data: "cookies"
+Accept-CH: Sec-CH-Device-Memory
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-storage.https.html b/testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-storage.https.html
new file mode 100644
index 0000000000..30d541b6ed
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-storage.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// Here's the set-up for this test:
+// Step 1 (first window) Set client hints in the same header that clears them.
+// Step 2 (first window) Open second window.
+// Step 3 (second window) Message first window about client hints sent.
+// Step 4 (first window) Asserts client hints sent.
+async_test(t => {
+ // Step 4
+ window.addEventListener("message", t.step_func(e => {
+ assert_equals(e.data, "HadDeviceMemory");
+ t.done();
+ }), {once: true});
+
+ // Step 2
+ new_window = window.open("/client-hints/clear-site-data/resources/check-client-hints.py");
+ t.add_cleanup(new_window.close);
+}, "Setting client hints after Clear-Site-Data for storage should allow the client hints to take.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-storage.https.html.headers b/testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-storage.https.html.headers
new file mode 100644
index 0000000000..d898684cac
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/set-client-hints-after-clear-storage.https.html.headers
@@ -0,0 +1,2 @@
+Clear-Site-Data: "storage"
+Accept-CH: Sec-CH-Device-Memory
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-all.https.html b/testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-all.https.html
new file mode 100644
index 0000000000..062e72b544
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-all.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// Here's the set-up for this test:
+// Step 1 (first window) Set client hints in the same header that clears them.
+// Step 2 (first window) Open second window.
+// Step 3 (second window) Message first window about client hints sent.
+// Step 4 (first window) Asserts client hints not sent.
+async_test(t => {
+ // Step 4
+ window.addEventListener("message", t.step_func(e => {
+ assert_equals(e.data, "MissingDeviceMemory");
+ t.done();
+ }), {once: true});
+
+ // Step 2
+ new_window = window.open("/client-hints/clear-site-data/resources/check-client-hints.py");
+ t.add_cleanup(new_window.close);
+}, "Setting critical client hints after Clear-Site-Data for all shouldn't allow the client hints to take.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-all.https.html.headers b/testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-all.https.html.headers
new file mode 100644
index 0000000000..ebd7a88cc6
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-all.https.html.headers
@@ -0,0 +1,3 @@
+Clear-Site-Data: "*"
+Accept-CH: Sec-CH-Device-Memory
+Critical-CH: Sec-CH-Device-Memory
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-cache.https.html b/testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-cache.https.html
new file mode 100644
index 0000000000..ab97fa182b
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-cache.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// Here's the set-up for this test:
+// Step 1 (first window) Set client hints in the same header that clears them.
+// Step 2 (first window) Open second window.
+// Step 3 (second window) Message first window about client hints sent.
+// Step 4 (first window) Asserts client hints not sent.
+async_test(t => {
+ // Step 4
+ window.addEventListener("message", t.step_func(e => {
+ assert_equals(e.data, "MissingDeviceMemory");
+ t.done();
+ }), {once: true});
+
+ // Step 2
+ new_window = window.open("/client-hints/clear-site-data/resources/check-client-hints.py");
+ t.add_cleanup(new_window.close);
+}, "Setting critical client hints after Clear-Site-Data for cache shouldn't allow the client hints to take.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-cache.https.html.headers b/testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-cache.https.html.headers
new file mode 100644
index 0000000000..03d39b6fd7
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-cache.https.html.headers
@@ -0,0 +1,3 @@
+Clear-Site-Data: "cache"
+Accept-CH: Sec-CH-Device-Memory
+Critical-CH: Sec-CH-Device-Memory
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-client-hints.https.html b/testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-client-hints.https.html
new file mode 100644
index 0000000000..cdddb9ff35
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-client-hints.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// Here's the set-up for this test:
+// Step 1 (first window) Set client hints in the same header that clears them.
+// Step 2 (first window) Open second window.
+// Step 3 (second window) Message first window about client hints sent.
+// Step 4 (first window) Asserts client hints not sent.
+async_test(t => {
+ // Step 4
+ window.addEventListener("message", t.step_func(e => {
+ assert_equals(e.data, "MissingDeviceMemory");
+ t.done();
+ }), {once: true});
+
+ // Step 2
+ new_window = window.open("/client-hints/clear-site-data/resources/check-client-hints.py");
+ t.add_cleanup(new_window.close);
+}, "Setting critical client hints after Clear-Site-Data for client hints shouldn't allow the client hints to take.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-client-hints.https.html.headers b/testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-client-hints.https.html.headers
new file mode 100644
index 0000000000..f55913cb5b
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-client-hints.https.html.headers
@@ -0,0 +1,3 @@
+Clear-Site-Data: "clientHints"
+Accept-CH: Sec-CH-Device-Memory
+Critical-CH: Sec-CH-Device-Memory
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-cookies.https.html b/testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-cookies.https.html
new file mode 100644
index 0000000000..0060f3aaa0
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-cookies.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// Here's the set-up for this test:
+// Step 1 (first window) Set client hints in the same header that clears them.
+// Step 2 (first window) Open second window.
+// Step 3 (second window) Message first window about client hints sent.
+// Step 4 (first window) Asserts client hints not sent.
+async_test(t => {
+ // Step 4
+ window.addEventListener("message", t.step_func(e => {
+ assert_equals(e.data, "MissingDeviceMemory");
+ t.done();
+ }), {once: true});
+
+ // Step 2
+ new_window = window.open("/client-hints/clear-site-data/resources/check-client-hints.py");
+ t.add_cleanup(new_window.close);
+}, "Setting critical client hints after Clear-Site-Data for cookies shouldn't allow the client hints to take.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-cookies.https.html.headers b/testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-cookies.https.html.headers
new file mode 100644
index 0000000000..69c7e720ac
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-cookies.https.html.headers
@@ -0,0 +1,3 @@
+Clear-Site-Data: "cookies"
+Accept-CH: Sec-CH-Device-Memory
+Critical-CH: Sec-CH-Device-Memory
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-storage.https.html b/testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-storage.https.html
new file mode 100644
index 0000000000..9237e28ced
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-storage.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// Here's the set-up for this test:
+// Step 1 (first window) Set client hints in the same header that clears them.
+// Step 2 (first window) Open second window.
+// Step 3 (second window) Message first window about client hints sent.
+// Step 4 (first window) Asserts client hints sent.
+async_test(t => {
+ // Step 4
+ window.addEventListener("message", t.step_func(e => {
+ assert_equals(e.data, "HadDeviceMemory");
+ t.done();
+ }), {once: true});
+
+ // Step 2
+ new_window = window.open("/client-hints/clear-site-data/resources/check-client-hints.py");
+ t.add_cleanup(new_window.close);
+}, "Setting critical client hints after Clear-Site-Data for storage should allow the client hints to take.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-storage.https.html.headers b/testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-storage.https.html.headers
new file mode 100644
index 0000000000..0f18541cf7
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/clear-site-data/set-critical-client-hints-after-clear-storage.https.html.headers
@@ -0,0 +1,3 @@
+Clear-Site-Data: "storage"
+Accept-CH: Sec-CH-Device-Memory
+Critical-CH: Sec-CH-Device-Memory
diff --git a/testing/web-platform/tests/client-hints/critical-ch/critical-ch.navigation-timing.no-restart.https.html b/testing/web-platform/tests/client-hints/critical-ch/critical-ch.navigation-timing.no-restart.https.html
new file mode 100644
index 0000000000..a7c2ad705d
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/critical-ch/critical-ch.navigation-timing.no-restart.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+promise_test(async t => {
+ var navigationTiming = window.performance.getEntriesByType('navigation')[0];
+ assert_not_equals(navigationTiming, undefined);
+ assert_equals(navigationTiming.criticalCHRestart, 0, "This should be 0 as there was no restart.");
+}, "Critical-CH no-restart navigation timing test");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/critical-ch/critical-ch.navigation-timing.restart.https.html b/testing/web-platform/tests/client-hints/critical-ch/critical-ch.navigation-timing.restart.https.html
new file mode 100644
index 0000000000..d3a49a2e91
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/critical-ch/critical-ch.navigation-timing.restart.https.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+promise_test(async t => {
+ var navigationTiming = window.performance.getEntriesByType('navigation')[0];
+ assert_not_equals(navigationTiming, undefined);
+ assert_less_than(navigationTiming.startTime, navigationTiming.criticalCHRestart, "Restarts happen after the beginning of the navigation");
+ assert_less_than(navigationTiming.criticalCHRestart, navigationTiming.fetchStart, "Restarts happen before fetch");
+}, "Critical-CH restart navigation timing test");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/critical-ch/critical-ch.navigation-timing.restart.https.html.headers b/testing/web-platform/tests/client-hints/critical-ch/critical-ch.navigation-timing.restart.https.html.headers
new file mode 100644
index 0000000000..b221a32886
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/critical-ch/critical-ch.navigation-timing.restart.https.html.headers
@@ -0,0 +1,2 @@
+Accept-CH: sec-ch-device-memory
+Critical-CH: sec-ch-device-memory
diff --git a/testing/web-platform/tests/client-hints/critical-ch/iframe.https.window.js b/testing/web-platform/tests/client-hints/critical-ch/iframe.https.window.js
new file mode 100644
index 0000000000..cbf128149e
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/critical-ch/iframe.https.window.js
@@ -0,0 +1,15 @@
+// META: script=resources/util.js
+
+async_test((t) => {
+ window.addEventListener('message', message_listener(t, "FAIL"));
+ var iframe = document.createElement("iframe");
+ iframe.src = ECHO_URL;
+ document.body.appendChild(iframe);
+}, "Critical-CH iframe");
+
+async_test((t) => {
+ window.addEventListener('message', message_listener(t, "FAIL"));
+ var iframe = document.createElement("iframe");
+ iframe.src = ECHO_URL+"?multiple=true";
+ document.body.appendChild(iframe);
+}, "Critical-CH w/ multiple headers and iframe");
diff --git a/testing/web-platform/tests/client-hints/critical-ch/mis-matched-count.https.window.js b/testing/web-platform/tests/client-hints/critical-ch/mis-matched-count.https.window.js
new file mode 100644
index 0000000000..23d297837e
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/critical-ch/mis-matched-count.https.window.js
@@ -0,0 +1,5 @@
+// META: script=resources/util.js
+// META: script=/common/utils.js
+
+async_test(make_message_test(ECHO_URL+"?mismatch=true&token="+token(), "1"), "Critical-CH no restart on mismatched hints")
+async_test(make_message_test(ECHO_URL+"?multiple=true&mismatch=true&token="+token(), "1"), "Critical-CH w/ multiple headers and no restart on mismatched hints")
diff --git a/testing/web-platform/tests/client-hints/critical-ch/mis-matched.https.window.js b/testing/web-platform/tests/client-hints/critical-ch/mis-matched.https.window.js
new file mode 100644
index 0000000000..9476640b35
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/critical-ch/mis-matched.https.window.js
@@ -0,0 +1,3 @@
+// META: script=resources/util.js
+
+async_test(make_message_test(ECHO_URL+"?mismatch=true", "FAIL"), "Critical-CH Mis-matched hints")
diff --git a/testing/web-platform/tests/client-hints/critical-ch/mis-matched.multiple.https.window.js b/testing/web-platform/tests/client-hints/critical-ch/mis-matched.multiple.https.window.js
new file mode 100644
index 0000000000..97b94ccf71
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/critical-ch/mis-matched.multiple.https.window.js
@@ -0,0 +1,3 @@
+// META: script=resources/util.js
+
+async_test(make_message_test(ECHO_URL+"?multiple=true&mismatch=true", "FAIL"), "Critical-CH w/ multiple headers and Mis-matched hints")
diff --git a/testing/web-platform/tests/client-hints/critical-ch/navigation.cross-origin.https.window.js b/testing/web-platform/tests/client-hints/critical-ch/navigation.cross-origin.https.window.js
new file mode 100644
index 0000000000..6fdcee71d4
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/critical-ch/navigation.cross-origin.https.window.js
@@ -0,0 +1,4 @@
+// META: script=resources/util.js
+// META: script=/common/get-host-info.sub.js
+
+async_test(make_message_test(get_host_info().HTTPS_REMOTE_ORIGIN+"/client-hints/critical-ch/"+ECHO_URL, "PASS"), "Critical-CH cross-origin navigation")
diff --git a/testing/web-platform/tests/client-hints/critical-ch/navigation.cross-origin.multiple.https.window.js b/testing/web-platform/tests/client-hints/critical-ch/navigation.cross-origin.multiple.https.window.js
new file mode 100644
index 0000000000..91a57da3e4
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/critical-ch/navigation.cross-origin.multiple.https.window.js
@@ -0,0 +1,4 @@
+// META: script=resources/util.js
+// META: script=/common/get-host-info.sub.js
+
+async_test(make_message_test(get_host_info().HTTPS_REMOTE_ORIGIN+"/client-hints/critical-ch/"+ECHO_URL+"?multiple=true", "PASS"), "Critical-CH w/ multiple headers and cross-origin navigation")
diff --git a/testing/web-platform/tests/client-hints/critical-ch/navigation.same-origin.https.window.js b/testing/web-platform/tests/client-hints/critical-ch/navigation.same-origin.https.window.js
new file mode 100644
index 0000000000..549b321443
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/critical-ch/navigation.same-origin.https.window.js
@@ -0,0 +1,3 @@
+// META: script=resources/util.js
+
+async_test(make_message_test(ECHO_URL, "PASS"), "Critical-CH navigation")
diff --git a/testing/web-platform/tests/client-hints/critical-ch/navigation.same-origin.multiple.https.window.js b/testing/web-platform/tests/client-hints/critical-ch/navigation.same-origin.multiple.https.window.js
new file mode 100644
index 0000000000..ff9cc210fe
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/critical-ch/navigation.same-origin.multiple.https.window.js
@@ -0,0 +1,3 @@
+// META: script=resources/util.js
+
+async_test(make_message_test(ECHO_URL+"?multiple=true", "PASS"), "Critical-CH w/ multiple headers and navigation")
diff --git a/testing/web-platform/tests/client-hints/critical-ch/non-secure.http.window.js b/testing/web-platform/tests/client-hints/critical-ch/non-secure.http.window.js
new file mode 100644
index 0000000000..b634f98e55
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/critical-ch/non-secure.http.window.js
@@ -0,0 +1,4 @@
+// META: script=resources/util.js
+
+async_test(make_message_test(ECHO_URL, "FAIL"), "Critical-CH navigation non-secure")
+async_test(make_message_test(ECHO_URL+"?multiple=true", "FAIL"), "Critical-CH w/ multiple headers and navigation non-secure")
diff --git a/testing/web-platform/tests/client-hints/critical-ch/redirect.critical.cross-origin.https.window.js b/testing/web-platform/tests/client-hints/critical-ch/redirect.critical.cross-origin.https.window.js
new file mode 100644
index 0000000000..74d9a0cc3b
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/critical-ch/redirect.critical.cross-origin.https.window.js
@@ -0,0 +1,4 @@
+// META: script=resources/util.js
+// META: script=/common/get-host-info.sub.js
+
+async_test(make_message_test(REDIRECT_URL+"?critical=true&location="+get_host_info().HTTPS_REMOTE_ORIGIN+"/client-hints/critical-ch/"+ECHO_URL, "PASS"), "Critical-CH cross-origin critical redirect")
diff --git a/testing/web-platform/tests/client-hints/critical-ch/redirect.critical.cross-origin.multiple.https.window.js b/testing/web-platform/tests/client-hints/critical-ch/redirect.critical.cross-origin.multiple.https.window.js
new file mode 100644
index 0000000000..d1a3c67ecb
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/critical-ch/redirect.critical.cross-origin.multiple.https.window.js
@@ -0,0 +1,4 @@
+// META: script=resources/util.js
+// META: script=/common/get-host-info.sub.js
+
+async_test(make_message_test(REDIRECT_URL+"?critical=true&location="+get_host_info().HTTPS_REMOTE_ORIGIN+"/client-hints/critical-ch/"+ECHO_URL+"?multiple=true", "PASS"), "Critical-CH w/ multiple headers cross-origin critical redirect")
diff --git a/testing/web-platform/tests/client-hints/critical-ch/redirect.critical.same-origin.https.window.js b/testing/web-platform/tests/client-hints/critical-ch/redirect.critical.same-origin.https.window.js
new file mode 100644
index 0000000000..1c6511a007
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/critical-ch/redirect.critical.same-origin.https.window.js
@@ -0,0 +1,3 @@
+// META: script=resources/util.js
+
+async_test(make_message_test(REDIRECT_URL+"?critical=true&location=/client-hints/critical-ch/"+ECHO_URL, "FAIL"), "Critical-CH critical redirect")
diff --git a/testing/web-platform/tests/client-hints/critical-ch/redirect.critical.same-origin.multiple.https.window.js b/testing/web-platform/tests/client-hints/critical-ch/redirect.critical.same-origin.multiple.https.window.js
new file mode 100644
index 0000000000..5bf653afbc
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/critical-ch/redirect.critical.same-origin.multiple.https.window.js
@@ -0,0 +1,3 @@
+// META: script=resources/util.js
+
+async_test(make_message_test(REDIRECT_URL+"?critical=true&location=/client-hints/critical-ch/"+ECHO_URL+"?multiple=true", "FAIL"), "Critical-CH w/ multiple headers and critical redirect")
diff --git a/testing/web-platform/tests/client-hints/critical-ch/redirect.cross-origin.https.window.js b/testing/web-platform/tests/client-hints/critical-ch/redirect.cross-origin.https.window.js
new file mode 100644
index 0000000000..5d781bd082
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/critical-ch/redirect.cross-origin.https.window.js
@@ -0,0 +1,4 @@
+// META: script=resources/util.js
+// META: script=/common/get-host-info.sub.js
+
+async_test(make_message_test(REDIRECT_URL+"?location="+get_host_info().HTTPS_REMOTE_ORIGIN+"/client-hints/critical-ch/"+ECHO_URL, "PASS"), "Critical-CH cross-origin redirect")
diff --git a/testing/web-platform/tests/client-hints/critical-ch/redirect.cross-origin.multiple.https.window.js b/testing/web-platform/tests/client-hints/critical-ch/redirect.cross-origin.multiple.https.window.js
new file mode 100644
index 0000000000..5688c8459c
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/critical-ch/redirect.cross-origin.multiple.https.window.js
@@ -0,0 +1,4 @@
+// META: script=resources/util.js
+// META: script=/common/get-host-info.sub.js
+
+async_test(make_message_test(REDIRECT_URL+"?location="+get_host_info().HTTPS_REMOTE_ORIGIN+"/client-hints/critical-ch/"+ECHO_URL+"?multiple=true", "PASS"), "Critical-CH w/ multiple headers cross-origin redirect")
diff --git a/testing/web-platform/tests/client-hints/critical-ch/redirect.same-origin.https.window.js b/testing/web-platform/tests/client-hints/critical-ch/redirect.same-origin.https.window.js
new file mode 100644
index 0000000000..b2401025d9
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/critical-ch/redirect.same-origin.https.window.js
@@ -0,0 +1,3 @@
+// META: script=resources/util.js
+
+async_test(make_message_test(REDIRECT_URL+"?location=/client-hints/critical-ch/"+ECHO_URL, "PASS"), "Critical-CH redirect")
diff --git a/testing/web-platform/tests/client-hints/critical-ch/redirect.same-origin.multiple.https.window.js b/testing/web-platform/tests/client-hints/critical-ch/redirect.same-origin.multiple.https.window.js
new file mode 100644
index 0000000000..86305023f4
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/critical-ch/redirect.same-origin.multiple.https.window.js
@@ -0,0 +1,3 @@
+// META: script=resources/util.js
+
+async_test(make_message_test(REDIRECT_URL+"?location=/client-hints/critical-ch/"+ECHO_URL+"?multiple=true", "PASS"), "Critical-CH w/ multiple headers and redirect")
diff --git a/testing/web-platform/tests/client-hints/critical-ch/request-count.https.window.js b/testing/web-platform/tests/client-hints/critical-ch/request-count.https.window.js
new file mode 100644
index 0000000000..b337340509
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/critical-ch/request-count.https.window.js
@@ -0,0 +1,4 @@
+// META: script=resources/util.js
+// META: script=/common/utils.js
+
+async_test(make_message_test(ECHO_URL+"?token="+token(), "2"), "Critical-CH navigation restart")
diff --git a/testing/web-platform/tests/client-hints/critical-ch/request-count.multiple.https.window.js b/testing/web-platform/tests/client-hints/critical-ch/request-count.multiple.https.window.js
new file mode 100644
index 0000000000..602803b5d6
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/critical-ch/request-count.multiple.https.window.js
@@ -0,0 +1,4 @@
+// META: script=resources/util.js
+// META: script=/common/utils.js
+
+async_test(make_message_test(ECHO_URL+"?multiple=true&token="+token(), "2"), "Critical-CH w/ multiple headers and navigation restart")
diff --git a/testing/web-platform/tests/client-hints/critical-ch/resources/echo-critical-hint.py b/testing/web-platform/tests/client-hints/critical-ch/resources/echo-critical-hint.py
new file mode 100644
index 0000000000..a5c5160f75
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/critical-ch/resources/echo-critical-hint.py
@@ -0,0 +1,52 @@
+import sys
+
+def main(request, response):
+ """
+ Simple handler that sets a response header based on which client hint
+ request headers were received.
+ """
+
+ response.headers.append(b"Content-Type", b"text/html; charset=UTF-8")
+ response.headers.append(b"Access-Control-Allow-Origin", b"*")
+ response.headers.append(b"Access-Control-Allow-Headers", b"*")
+ response.headers.append(b"Access-Control-Expose-Headers", b"*")
+
+ accept = b"sec-ch-device-memory,device-memory"
+ if(request.GET.first(b"multiple", None) is not None):
+ for accept_part in accept.split(b","):
+ response.headers.append(b"Accept-CH", accept_part)
+ else:
+ response.headers.append(b"Accept-CH", accept)
+
+ critical = b"sec-ch-device-memory,device-memory"
+ if(request.GET.first(b"mismatch", None) is not None):
+ critical = b"sec-ch-viewport-width,viewport-width"
+
+ if(request.GET.first(b"multiple", None) is not None):
+ for critical_part in critical.split(b","):
+ response.headers.append(b"Critical-CH", critical_part)
+ else:
+ response.headers.append(b"Critical-CH", critical)
+
+ response.headers.append(b"Cache-Control", b"no-store")
+
+ result = "FAIL"
+
+ if b"sec-ch-device-memory" in request.headers and b"device-memory" in request.headers:
+ result = "PASS"
+
+ token = request.GET.first(b"token", None)
+ if(token is not None):
+ with request.server.stash.lock:
+ count = request.server.stash.take(token)
+ if(count == None):
+ count = 1
+ else:
+ count += 1
+ request.server.stash.put(token, count)
+ result = str(count)
+
+ if b"sec-ch-viewport-width" in request.headers and b"viewport-width" in request.headers:
+ result = "MISMATCH"
+
+ response.content = "<script>(window.opener || window.top).postMessage('{0}', '*')</script>".format(result)
diff --git a/testing/web-platform/tests/client-hints/critical-ch/resources/redirect-critical-hint.py b/testing/web-platform/tests/client-hints/critical-ch/resources/redirect-critical-hint.py
new file mode 100644
index 0000000000..77b8652e20
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/critical-ch/resources/redirect-critical-hint.py
@@ -0,0 +1,14 @@
+def main(request, response):
+ """
+ Simple handler that redirects to echo-critical-hint.py.
+ """
+
+ response.status = 302
+ location = request.GET.first(b"location")
+ response.headers.set(b"Location", location)
+ response.headers.set(b"Access-Control-Allow-Origin", b"*")
+ if(request.GET.first(b"critical", None) is not None):
+ hints = b"sec-ch-dpr,dpr"
+ response.headers.append(b"Accept-CH", hints)
+ response.headers.append(b"Critical-CH", hints)
+
diff --git a/testing/web-platform/tests/client-hints/critical-ch/resources/util.js b/testing/web-platform/tests/client-hints/critical-ch/resources/util.js
new file mode 100644
index 0000000000..36800f1e4f
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/critical-ch/resources/util.js
@@ -0,0 +1,16 @@
+ECHO_URL = "resources/echo-critical-hint.py"
+REDIRECT_URL = "resources/redirect-critical-hint.py"
+
+message_listener = (t, message) =>
+ (e) => {
+ t.step(()=>{assert_equals(e.data, message)});
+ t.done();
+ }
+
+make_message_test = (url, message) =>
+ (t) => {
+ popup_window = window.open("/common/blank.html");
+ assert_not_equals(popup_window, null, "Popup windows not allowed?");
+ window.addEventListener('message', message_listener(t, message));
+ popup_window.location = url;
+ }
diff --git a/testing/web-platform/tests/client-hints/critical-ch/subresource.https.window.js b/testing/web-platform/tests/client-hints/critical-ch/subresource.https.window.js
new file mode 100644
index 0000000000..81dfc303c6
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/critical-ch/subresource.https.window.js
@@ -0,0 +1,18 @@
+// META: script=resources/util.js
+// META: script=/common/utils.js
+
+promise_test(() =>
+ fetch(ECHO_URL)
+ .then((r) => r.text())
+ .then((r) => {
+ assert_true(r.includes("FAIL"));
+ })
+, "Critical-CH subresource fetch");
+
+promise_test(() =>
+ fetch(ECHO_URL+"?multiple=true")
+ .then((r) => r.text())
+ .then((r) => {
+ assert_true(r.includes("FAIL"));
+ })
+, "Critical-CH w/ multiple headers and subresource fetch");
diff --git a/testing/web-platform/tests/client-hints/critical-ch/unsafe-method.https.window.js b/testing/web-platform/tests/client-hints/critical-ch/unsafe-method.https.window.js
new file mode 100644
index 0000000000..61fca618f3
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/critical-ch/unsafe-method.https.window.js
@@ -0,0 +1,49 @@
+// META: script=resources/util.js
+
+async_test((t) => {
+ // This test requires a navigation with a non-safe (i.e. non-GET) HTTP
+ // response, which the Critical-CH spec says to ignore. The most
+ // "straight-forward" way to do this in JS is by making a form with an
+ // unsafe method (e.g. POST) method and submit it.
+
+ // Build the form DOM element
+ var form = document.createElement("form");
+ form.setAttribute("method", "post");
+ form.setAttribute("action", ECHO_URL);
+ form.setAttribute("target", "popup"); //don't navigate away from the page running the test...
+ document.body.appendChild(form);
+
+ window.addEventListener('message', (e) => {
+ t.step(()=>{assert_equals(e.data, "FAIL")});
+ t.done();
+ });
+
+ var popup_window = window.open("/common/blank.html", "popup");
+ assert_not_equals(popup_window, null, "Popup windows not allowed?");
+
+ form.submit();
+}, "Critical-CH unsafe method")
+
+async_test((t) => {
+ // This test requires a navigation with a non-safe (i.e. non-GET) HTTP
+ // response, which the Critical-CH spec says to ignore. The most
+ // "straight-forward" way to do this in JS is by making a form with an
+ // unsafe method (e.g. POST) method and submit it.
+
+ // Build the form DOM element
+ var form = document.createElement("form");
+ form.setAttribute("method", "post");
+ form.setAttribute("action", ECHO_URL+"?multiple=true");
+ form.setAttribute("target", "popup"); //don't navigate away from the page running the test...
+ document.body.appendChild(form);
+
+ window.addEventListener('message', (e) => {
+ t.step(()=>{assert_equals(e.data, "FAIL")});
+ t.done();
+ });
+
+ var popup_window = window.open("/common/blank.html", "popup");
+ assert_not_equals(popup_window, null, "Popup windows not allowed?");
+
+ form.submit();
+}, "Critical-CH w/ multiple headers and unsafe method")
diff --git a/testing/web-platform/tests/client-hints/http-equiv-accept-ch-iframe.https.html b/testing/web-platform/tests/client-hints/http-equiv-accept-ch-iframe.https.html
new file mode 100644
index 0000000000..4bde8ebc2d
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/http-equiv-accept-ch-iframe.https.html
@@ -0,0 +1,26 @@
+<html>
+<meta http-equiv="Accept-CH" content="Sec-CH-Device-Memory, Device-Memory, Sec-CH-DPR, DPR, Sec-CH-Viewport-Width, Viewport-Width">
+<title>Accept-CH http-equiv iframe test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/client-hints/resources/export.js"></script>
+<script src="resources/feature-policy-navigation.js"></script>
+<script>
+(async () => {
+ await test_frame(
+ "HTTPS_ORIGIN",
+ expect_iframe_no_hints,
+ "",
+ "Client hints loaded on same-origin iframe should include hints with a default permissions policy of" +
+ "self and *, but the http-equiv meta tag has a bug and it doesn't impact iframes.");
+ await test_frame(
+ "HTTPS_REMOTE_ORIGIN",
+ expect_iframe_no_hints,
+ "",
+ "Client hints loaded on cross-origin iframe only include hints with a default permissions policy of *.");
+})();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/http-equiv-accept-ch-malformed-header.https.html b/testing/web-platform/tests/client-hints/http-equiv-accept-ch-malformed-header.https.html
new file mode 100644
index 0000000000..3576dc9920
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/http-equiv-accept-ch-malformed-header.https.html
@@ -0,0 +1,31 @@
+<html>
+<meta http-equiv="Accept-CH" content="Sec-CH-DPR Sec-CH-Width">
+<title>Accept-CH malformed http-equiv test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+
+promise_test(t => {
+ return fetch("/client-hints/resources/echo-client-hints-received.py").then(r => {
+ assert_equals(r.status, 200)
+ // Verify that the browser does not include client hints in the headers
+ // since Accept-CH value in http-equiv is malformed (includes whitespace
+ // between attributes instead of comma).
+ assert_false(r.headers.has("device-memory-received"), "device-memory-received");
+ assert_false(r.headers.has("device-memory-deprecated-received"), "device-memory-deprecated-received");
+ assert_false(r.headers.has("dpr-received"), "dpr-received");
+ assert_false(r.headers.has("dpr-deprecated-received"), "dpr-deprecated-received");
+ assert_false(r.headers.has("viewport-width-received"), "viewport-width-received");
+ assert_false(r.headers.has("viewport-width-deprecated-received"), "viewport-width-deprecated-received");
+ assert_false(r.headers.has("rtt-received"), "rtt-received");
+ assert_false(r.headers.has("downlink-received"), "downlink-received");
+ assert_false(r.headers.has("ect-received"), "ect-received");
+ });
+}, "Accept-CH malformed http-equiv test");
+
+</script>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/http-equiv-accept-ch-merge.https.html b/testing/web-platform/tests/client-hints/http-equiv-accept-ch-merge.https.html
new file mode 100644
index 0000000000..bc578afb1e
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/http-equiv-accept-ch-merge.https.html
@@ -0,0 +1,50 @@
+<html>
+<head>
+<meta http-equiv="Accept-CH" content="sec-ch-viewport-width, viewport-width, rtt">
+<meta http-equiv="Accept-CH" content="downlink, ect, sec-ch-prefers-color-scheme">
+<meta http-equiv="Accept-CH" content="sec-ch-prefers-reduced-motion, sec-ch-prefers-reduced-transparency">
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script>
+
+// Test of merge of http-equiv headers on top of accept-ch provided ones.
+//
+// resources/echo-client-hints-received.py sets the response headers depending on the set
+// of client hints it receives in the request headers.
+
+promise_test(t => {
+ return fetch(get_host_info()["HTTPS_ORIGIN"] + "/client-hints/resources/echo-client-hints-received.py").then(r => {
+ assert_equals(r.status, 200)
+ // Verify that the browser includes client hints in the headers.
+ assert_true(r.headers.has("device-memory-received"), "device-memory-received");
+ assert_true(r.headers.has("device-memory-deprecated-received"), "device-memory-deprecated-received");
+ assert_true(r.headers.has("dpr-received"), "dpr-received");
+ assert_true(r.headers.has("dpr-deprecated-received"), "dpr-deprecated-received");
+ assert_true(r.headers.has("viewport-width-received"), "viewport-width-received");
+ assert_true(r.headers.has("viewport-width-deprecated-received"), "viewport-width-deprecated-received");
+
+ assert_true(r.headers.has("rtt-received"), "rtt-received");
+ var rtt = parseInt(r.headers.get("rtt-received"));
+ assert_greater_than_equal(rtt, 0);
+ assert_less_than_equal(rtt, 3000);
+ assert_equals(rtt % 50, 0, 'rtt must be a multiple of 50 msec');
+
+ assert_true(r.headers.has("downlink-received"), "downlink-received");
+ var downlinkKbps = r.headers.get("downlink-received") * 1000;
+ assert_greater_than_equal(downlinkKbps, 0);
+ assert_less_than_equal(downlinkKbps, 10000);
+
+ assert_in_array(r.headers.get("ect-received"), ["slow-2g", "2g",
+ "3g", "4g"], 'ect-received is unexpected');
+ assert_true(r.headers.has("prefers-color-scheme-received"), "prefers-color-scheme-received");
+ assert_true(r.headers.has("prefers-reduced-motion-received"), "prefers-reduced-motion-received");
+ assert_true(r.headers.has("prefers-reduced-transparency-received"), "prefers-reduced-transparency-received");
+ });
+}, "Accept-CH header test");
+
+</script>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/http-equiv-accept-ch-merge.https.html.headers b/testing/web-platform/tests/client-hints/http-equiv-accept-ch-merge.https.html.headers
new file mode 100644
index 0000000000..34edb7b82b
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/http-equiv-accept-ch-merge.https.html.headers
@@ -0,0 +1,2 @@
+Accept-CH: sec-ch-device-memory, device-memory, sec-ch-dpr, dpr
+
diff --git a/testing/web-platform/tests/client-hints/http-equiv-accept-ch-non-secure.http.html b/testing/web-platform/tests/client-hints/http-equiv-accept-ch-non-secure.http.html
new file mode 100644
index 0000000000..58a55d44c3
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/http-equiv-accept-ch-non-secure.http.html
@@ -0,0 +1,39 @@
+<html>
+<meta http-equiv="Accept-CH" content="Sec-CH-DPR, DPR, Sec-CH-Width, Width, Sec-CH-Viewport-Width, Viewport-Width, Sec-CH-Device-Memory, Device-Memory, rtt, downlink, ect">
+<title>Accept-CH http-equiv insecure transport test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+
+// Even though this HTML file contains "Accept-CH" http-equiv headers, the
+// browser should NOT attach the specified client hints in the HTTP request
+// headers since the page is being fetched over an insecure transport.
+// Test this functionality by fetching an XHR from this page hosted on
+// an insecure HTTP server.
+
+// resources/echo-client-hints-received.py sets the response headers depending on the set
+// of client hints it receives in the request headers.
+
+promise_test(t => {
+ return fetch("/client-hints/resources/echo-client-hints-received.py").then(r => {
+ assert_equals(r.status, 200)
+ // Verify that the browser does not include client hints in the headers
+ // when fetching the XHR from an insecure HTTP server.
+ assert_false(r.headers.has("device-memory-received"), "device-memory-received");
+ assert_false(r.headers.has("device-memory-deprecated-received"), "device-memory-deprecated-received");
+ assert_false(r.headers.has("dpr-received"), "dpr-received");
+ assert_false(r.headers.has("dpr-deprecated-received"), "dpr-deprecated-received");
+ assert_false(r.headers.has("viewport-width-received"), "viewport-width-received");
+ assert_false(r.headers.has("viewport-width-deprecated-received"), "viewport-width-deprecated-received");
+ assert_false(r.headers.has("rtt-received"), "rtt-received");
+ assert_false(r.headers.has("downlink-received"), "downlink-received");
+ assert_false(r.headers.has("ect-received"), "ect-received");
+ assert_false(r.headers.has("prefers-color-scheme-received"), "prefers-color-scheme-received");
+ });
+}, "Accept-CH http-equiv test over insecure transport");
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_all.sub.https.html b/testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_all.sub.https.html
new file mode 100644
index 0000000000..27ce76302d
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_all.sub.https.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Client Hints: Delegation of hints to cross-origin frames and resources for *</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+// Here's the set-up for this test:
+// Step 1. (Site 1 Window) verify ch-device-memory availability.
+// Step 2. (Site 1 Window) set up listener and embed Site 2 Frame.
+// Step 3. (Site 2 Frame) verify ch-device-memory availability.
+// Step 4. (Site 2 Frame) embeds Site 1 Frame.
+// Step 5. (Site 3 Frame) verify ch-device-memory availability.
+// Step 6. (Site 1 Window) exit.
+
+promise_test(async (t) => {
+ // Step 1
+ let r = await fetch("/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ assert_equals(r.status, 200, "Site 1 fetching Site 1 didn't recieve sec-ch-device-memory");
+ r = await fetch("https://{{hosts[alt][]}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ assert_equals(r.status, 200, "Site 1 fetching Site 2 didn't recieve sec-ch-device-memory");
+ r = await fetch("https://{{domains[www2]}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ assert_equals(r.status, 200, "Site 1 fetching Site 3 didn't recieve sec-ch-device-memory");
+ // Step 2
+ const site2Frame = document.createElement("iframe");
+ site2Frame.src = "https://{{hosts[alt][]}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/accept_ch_delegation_to_all_iframe.sub.https.html";
+ site2Frame.allow = "ch-device-memory *"
+ document.body.appendChild(site2Frame);
+ // Step 6
+ return new Promise(resolve => {
+ window.addEventListener("message", resolve);
+ }).then(e => {
+ assert_equals(e.data, "ch-device-memory is available as expected for all");
+ });
+}, "postMessage: First-Party to Third-Party, Cross-Partition, Same-Origin");
+</script>
+</body>
diff --git a/testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_all.sub.https.html.headers b/testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_all.sub.https.html.headers
new file mode 100644
index 0000000000..a8b1708b9d
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_all.sub.https.html.headers
@@ -0,0 +1,2 @@
+Permissions-Policy: ch-device-memory=(*)
+Accept-CH: Sec-CH-Device-Memory
diff --git a/testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_none.sub.https.html b/testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_none.sub.https.html
new file mode 100644
index 0000000000..1d4bfffe0a
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_none.sub.https.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Client Hints: Delegation of hints to cross-origin frames and resources for none</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+// Here's the set-up for this test:
+// Step 1. (Site 1 Window) verify ch-device-memory availability.
+// Step 2. (Site 1 Window) set up listener and embed Site 2 Frame.
+// Step 3. (Site 2 Frame) verify ch-device-memory availability.
+// Step 4. (Site 2 Frame) embeds Site 1 Frame.
+// Step 5. (Site 3 Frame) verify ch-device-memory availability.
+// Step 6. (Site 1 Window) exit.
+
+promise_test(async (t) => {
+ // Step 1
+ let r = await fetch("/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ assert_equals(r.status, 400, "Site 1 fetching Site 1 did recieve sec-ch-device-memory");
+ r = await fetch("https://{{hosts[alt][]}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ assert_equals(r.status, 400, "Site 1 fetching Site 2 did recieve sec-ch-device-memory");
+ r = await fetch("https://{{domains[www2]}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ assert_equals(r.status, 400, "Site 1 fetching Site 3 did recieve sec-ch-device-memory");
+ // Step 2
+ const site2Frame = document.createElement("iframe");
+ site2Frame.src = "https://{{hosts[alt][]}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/accept_ch_delegation_to_none_iframe.sub.https.html";
+ document.body.appendChild(site2Frame);
+ // Step 6
+ return new Promise(resolve => {
+ window.addEventListener("message", resolve);
+ }).then(e => {
+ assert_equals(e.data, "ch-device-memory is available as expected for none");
+ });
+}, "postMessage: First-Party to Third-Party, Cross-Partition, Same-Origin");
+</script>
+</body>
diff --git a/testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_none.sub.https.html.headers b/testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_none.sub.https.html.headers
new file mode 100644
index 0000000000..0b260a43f0
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_none.sub.https.html.headers
@@ -0,0 +1,2 @@
+Permissions-Policy: ch-device-memory=()
+Accept-CH: Sec-CH-Device-Memory
diff --git a/testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_self.sub.https.html b/testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_self.sub.https.html
new file mode 100644
index 0000000000..9647a8741a
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_self.sub.https.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Client Hints: Delegation of hints to cross-origin frames and resources for self</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+// Here's the set-up for this test:
+// Step 1. (Site 1 Window) verify ch-device-memory availability.
+// Step 2. (Site 1 Window) set up listener and embed Site 2 Frame.
+// Step 3. (Site 2 Frame) verify ch-device-memory availability.
+// Step 4. (Site 2 Frame) embeds Site 1 Frame.
+// Step 5. (Site 3 Frame) verify ch-device-memory availability.
+// Step 6. (Site 1 Window) exit.
+
+promise_test(async (t) => {
+ // Step 1
+ let r = await fetch("/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ assert_equals(r.status, 200, "Site 1 fetching Site 1 didn't recieve sec-ch-device-memory");
+ r = await fetch("https://{{hosts[alt][]}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ assert_equals(r.status, 400, "Site 1 fetching Site 2 did recieve sec-ch-device-memory");
+ r = await fetch("https://{{domains[www2]}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ assert_equals(r.status, 400, "Site 1 fetching Site 3 did recieve sec-ch-device-memory");
+ // Step 2
+ const site2Frame = document.createElement("iframe");
+ site2Frame.src = "https://{{hosts[alt][]}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/accept_ch_delegation_to_self_iframe.sub.https.html";
+ site2Frame.allow = "ch-device-memory self"
+ document.body.appendChild(site2Frame);
+ // Step 6
+ return new Promise(resolve => {
+ window.addEventListener("message", resolve);
+ }).then(e => {
+ assert_equals(e.data, "ch-device-memory is available as expected for self");
+ });
+}, "postMessage: First-Party to Third-Party, Cross-Partition, Same-Origin");
+</script>
+</body>
diff --git a/testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_self.sub.https.html.headers b/testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_self.sub.https.html.headers
new file mode 100644
index 0000000000..3b9de86bbd
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_self.sub.https.html.headers
@@ -0,0 +1,2 @@
+Permissions-Policy: ch-device-memory=(self)
+Accept-CH: Sec-CH-Device-Memory
diff --git a/testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_some.sub.https.html b/testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_some.sub.https.html
new file mode 100644
index 0000000000..9ea0049c39
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_some.sub.https.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Client Hints: Delegation of hints to cross-origin frames and resources for some</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+// Here's the set-up for this test:
+// Step 1. (Site 1 Window) verify ch-device-memory availability.
+// Step 2. (Site 1 Window) set up listener and embed Site 2 Frame.
+// Step 3. (Site 2 Frame) verify ch-device-memory availability.
+// Step 4. (Site 2 Frame) embeds Site 1 Frame.
+// Step 5. (Site 3 Frame) verify ch-device-memory availability.
+// Step 6. (Site 1 Window) exit.
+
+promise_test(async (t) => {
+ // Step 1
+ let r = await fetch("/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ assert_equals(r.status, 200, "Site 1 fetching Site 1 didn't recieve sec-ch-device-memory");
+ r = await fetch("https://{{hosts[alt][]}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ assert_equals(r.status, 200, "Site 1 fetching Site 2 didn't recieve sec-ch-device-memory");
+ r = await fetch("https://{{domains[www2]}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ assert_equals(r.status, 400, "Site 1 fetching Site 3 did recieve sec-ch-device-memory");
+ // Step 2
+ const site2Frame = document.createElement("iframe");
+ site2Frame.src = "https://{{hosts[alt][]}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/accept_ch_delegation_to_some_iframe.sub.https.html";
+ site2Frame.allow = "ch-device-memory https://{{hosts[alt][]}}:{{ports[https][0]}}"
+ document.body.appendChild(site2Frame);
+ // Step 6
+ return new Promise(resolve => {
+ window.addEventListener("message", resolve);
+ }).then(e => {
+ assert_equals(e.data, "ch-device-memory is available as expected for some");
+ });
+}, "postMessage: First-Party to Third-Party, Cross-Partition, Same-Origin");
+</script>
+</body>
diff --git a/testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_some.sub.https.html.sub.headers b/testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_some.sub.https.html.sub.headers
new file mode 100644
index 0000000000..24fe4e84c8
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_some.sub.https.html.sub.headers
@@ -0,0 +1,2 @@
+Permissions-Policy: ch-device-memory=("https://{{host}}:{{ports[https][0]}}" "https://{{hosts[alt][]}}:{{ports[https][0]}}")
+Accept-CH: Sec-CH-Device-Memory
diff --git a/testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_src.sub.https.html b/testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_src.sub.https.html
new file mode 100644
index 0000000000..e01853e5e9
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_src.sub.https.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Client Hints: Delegation of hints to cross-origin frames and resources for src</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+// Here's the set-up for this test:
+// Step 1. (Site 1 Window) verify ch-device-memory availability.
+// Step 2. (Site 1 Window) set up listener and embed Site 2 Frame.
+// Step 3. (Site 2 Frame) verify ch-device-memory availability.
+// Step 4. (Site 2 Frame) embeds Site 1 Frame.
+// Step 5. (Site 3 Frame) verify ch-device-memory availability.
+// Step 6. (Site 1 Window) exit.
+
+promise_test(async (t) => {
+ // Step 1
+ let r = await fetch("/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ assert_equals(r.status, 200, "Site 1 fetching Site 1 didn't recieve sec-ch-device-memory");
+ r = await fetch("https://{{hosts[alt][]}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ assert_equals(r.status, 400, "Site 1 fetching Site 2 did recieve sec-ch-device-memory");
+ r = await fetch("https://{{domains[www2]}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ assert_equals(r.status, 400, "Site 1 fetching Site 3 did recieve sec-ch-device-memory");
+ // Step 2
+ const site2Frame = document.createElement("iframe");
+ site2Frame.src = "https://{{hosts[alt][]}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/accept_ch_delegation_to_src_iframe.sub.https.html";
+ site2Frame.allow = "ch-device-memory src"
+ document.body.appendChild(site2Frame);
+ // Step 6
+ return new Promise(resolve => {
+ window.addEventListener("message", resolve);
+ }).then(e => {
+ assert_equals(e.data, "ch-device-memory is available as expected for src");
+ });
+}, "postMessage: First-Party to Third-Party, Cross-Partition, Same-Origin");
+</script>
+</body>
diff --git a/testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_src.sub.https.html.headers b/testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_src.sub.https.html.headers
new file mode 100644
index 0000000000..3b9de86bbd
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/inner-delegation/accept_ch_delegation_to_src.sub.https.html.headers
@@ -0,0 +1,2 @@
+Permissions-Policy: ch-device-memory=(self)
+Accept-CH: Sec-CH-Device-Memory
diff --git a/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_all_iframe.sub.https.html b/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_all_iframe.sub.https.html
new file mode 100644
index 0000000000..09df9ec46f
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_all_iframe.sub.https.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<meta charset=utf-8>
+<body>
+<script>
+async function test() {
+ // Step 3 (client-hints/inner-delegation/accept_ch_delegation_to_all.sub.https.html)
+ let r = await fetch("https://{{host}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ if (r.status == 200) {
+ return "Site 2 fetching Site 1 did recieve sec-ch-device-memory";
+ }
+ r = await fetch("/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ if (r.status != 200) {
+ return "Site 2 fetching Site 2 didn't recieve sec-ch-device-memory";
+ }
+ r = await fetch("https://{{domains[www2]}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ if (r.status == 200) {
+ return "Site 2 fetching Site 3 did recieve sec-ch-device-memory";
+ }
+ // Step 4 (client-hints/inner-delegation/accept_ch_delegation_to_all.sub.https.html)
+ const site3Frame = document.createElement("iframe");
+ site3Frame.src = "https://{{domains[www2]}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/accept_ch_delegation_to_all_iframe_iframe.sub.https.html";
+ site3Frame.allow = "ch-device-memory *"
+ document.body.appendChild(site3Frame);
+ return ""
+}
+test().then((message) => {
+ if (message) {
+ window.top.postMessage(message, "*");
+ }
+});
+</script>
+</body>
diff --git a/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_all_iframe_iframe.sub.https.html b/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_all_iframe_iframe.sub.https.html
new file mode 100644
index 0000000000..4291ae954c
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_all_iframe_iframe.sub.https.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset=utf-8>
+<body>
+<script>
+async function test() {
+ // Step 5 (client-hints/inner-delegation/accept_ch_delegation_to_all.sub.https.html)
+ let r = await fetch("https://{{host}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ if (r.status == 200) {
+ return "Site 3 fetching Site 1 did recieve sec-ch-device-memory";
+ }
+ r = await fetch("https://{{hosts[alt][]}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ if (r.status == 200) {
+ return "Site 3 fetching Site 2 did recieve sec-ch-device-memory";
+ }
+ r = await fetch("/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ if (r.status != 200) {
+ return "Site 3 fetching Site 3 didn't recieve sec-ch-device-memory";
+ }
+ return "ch-device-memory is available as expected for all"
+}
+test().then((message) => window.top.postMessage(message, "*"));
+</script>
+</body>
diff --git a/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_none_iframe.sub.https.html b/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_none_iframe.sub.https.html
new file mode 100644
index 0000000000..cd88fa54fc
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_none_iframe.sub.https.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<meta charset=utf-8>
+<body>
+<script>
+async function test() {
+ // Step 3 (client-hints/inner-delegation/accept_ch_delegation_to_none.sub.https.html)
+ let r = await fetch("https://{{host}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ if (r.status == 200) {
+ return "Site 2 fetching Site 1 did recieve sec-ch-device-memory";
+ }
+ r = await fetch("/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ if (r.status == 200) {
+ return "Site 2 fetching Site 2 did recieve sec-ch-device-memory";
+ }
+ r = await fetch("https://{{domains[www2]}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ if (r.status == 200) {
+ return "Site 2 fetching Site 3 did recieve sec-ch-device-memory";
+ }
+ // Step 4 (client-hints/inner-delegation/accept_ch_delegation_to_none.sub.https.html)
+ const site3Frame = document.createElement("iframe");
+ site3Frame.src = "https://{{domains[www2]}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/accept_ch_delegation_to_none_iframe_iframe.sub.https.html";
+ site3Frame.allow = "ch-device-memory none"
+ document.body.appendChild(site3Frame);
+ return ""
+}
+test().then((message) => {
+ if (message) {
+ window.top.postMessage(message, "*");
+ }
+});
+</script>
+</body>
diff --git a/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_none_iframe_iframe.sub.https.html b/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_none_iframe_iframe.sub.https.html
new file mode 100644
index 0000000000..5d661f4b15
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_none_iframe_iframe.sub.https.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset=utf-8>
+<body>
+<script>
+async function test() {
+ // Step 5 (client-hints/inner-delegation/accept_ch_delegation_to_none.sub.https.html)
+ let r = await fetch("https://{{host}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ if (r.status == 200) {
+ return "Site 3 fetching Site 1 did recieve sec-ch-device-memory";
+ }
+ r = await fetch("https://{{hosts[alt][]}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ if (r.status == 200) {
+ return "Site 3 fetching Site 2 did recieve sec-ch-device-memory";
+ }
+ r = await fetch("/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ if (r.status == 200) {
+ return "Site 3 fetching Site 3 did recieve sec-ch-device-memory";
+ }
+ return "ch-device-memory is available as expected for none"
+}
+test().then((message) => window.top.postMessage(message, "*"));
+</script>
+</body>
diff --git a/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_self_iframe.sub.https.html b/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_self_iframe.sub.https.html
new file mode 100644
index 0000000000..92157f4989
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_self_iframe.sub.https.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<meta charset=utf-8>
+<body>
+<script>
+async function test() {
+ // Step 3 (client-hints/inner-delegation/accept_ch_delegation_to_self.sub.https.html)
+ let r = await fetch("https://{{host}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ if (r.status == 200) {
+ return "Site 2 fetching Site 1 did recieve sec-ch-device-memory";
+ }
+ r = await fetch("/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ if (r.status == 200) {
+ return "Site 2 fetching Site 2 did recieve sec-ch-device-memory";
+ }
+ r = await fetch("https://{{domains[www2]}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ if (r.status == 200) {
+ return "Site 2 fetching Site 3 did recieve sec-ch-device-memory";
+ }
+ // Step 4 (client-hints/inner-delegation/accept_ch_delegation_to_self.sub.https.html)
+ const site3Frame = document.createElement("iframe");
+ site3Frame.src = "https://{{domains[www2]}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/accept_ch_delegation_to_self_iframe_iframe.sub.https.html";
+ site3Frame.allow = "ch-device-memory self"
+ document.body.appendChild(site3Frame);
+ return ""
+}
+test().then((message) => {
+ if (message) {
+ window.top.postMessage(message, "*");
+ }
+});
+</script>
+</body>
diff --git a/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_self_iframe_iframe.sub.https.html b/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_self_iframe_iframe.sub.https.html
new file mode 100644
index 0000000000..e12759e076
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_self_iframe_iframe.sub.https.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset=utf-8>
+<body>
+<script>
+async function test() {
+ // Step 5 (client-hints/inner-delegation/accept_ch_delegation_to_self.sub.https.html)
+ let r = await fetch("https://{{host}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ if (r.status == 200) {
+ return "Site 3 fetching Site 1 did recieve sec-ch-device-memory";
+ }
+ r = await fetch("https://{{hosts[alt][]}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ if (r.status == 200) {
+ return "Site 3 fetching Site 2 did recieve sec-ch-device-memory";
+ }
+ r = await fetch("/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ if (r.status == 200) {
+ return "Site 3 fetching Site 3 did recieve sec-ch-device-memory";
+ }
+ return "ch-device-memory is available as expected for self"
+}
+test().then((message) => window.top.postMessage(message, "*"));
+</script>
+</body>
diff --git a/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_some_iframe.sub.https.html b/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_some_iframe.sub.https.html
new file mode 100644
index 0000000000..39a4307230
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_some_iframe.sub.https.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<meta charset=utf-8>
+<body>
+<script>
+async function test() {
+ // Step 3 (client-hints/inner-delegation/accept_ch_delegation_to_some.sub.https.html)
+ let r = await fetch("https://{{host}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ if (r.status != 200) {
+ return "Site 2 fetching Site 1 didn't recieve sec-ch-device-memory";
+ }
+ r = await fetch("/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ if (r.status != 200) {
+ return "Site 2 fetching Site 2 didn't recieve sec-ch-device-memory";
+ }
+ r = await fetch("https://{{domains[www2]}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ if (r.status != 200) {
+ return "Site 2 fetching Site 3 didn't recieve sec-ch-device-memory";
+ }
+ // Step 4 (client-hints/inner-delegation/accept_ch_delegation_to_some.sub.https.html)
+ const site3Frame = document.createElement("iframe");
+ site3Frame.src = "https://{{domains[www2]}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/accept_ch_delegation_to_some_iframe_iframe.sub.https.html";
+ site3Frame.allow = "ch-device-memory https://{{domains[www2]}}:{{ports[https][0]}}"
+ document.body.appendChild(site3Frame);
+ return ""
+}
+test().then((message) => {
+ if (message) {
+ window.top.postMessage(message, "*");
+ }
+});
+</script>
+</body>
diff --git a/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_some_iframe.sub.https.html.headers b/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_some_iframe.sub.https.html.headers
new file mode 100644
index 0000000000..3e53702c6e
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_some_iframe.sub.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: ch-device-memory=(*)
diff --git a/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_some_iframe_iframe.sub.https.html b/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_some_iframe_iframe.sub.https.html
new file mode 100644
index 0000000000..999e3b6c99
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_some_iframe_iframe.sub.https.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset=utf-8>
+<body>
+<script>
+async function test() {
+ // Step 5 (client-hints/inner-delegation/accept_ch_delegation_to_some.sub.https.html)
+ let r = await fetch("https://{{host}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ if (r.status != 200) {
+ return "Site 3 fetching Site 1 didn't recieve sec-ch-device-memory";
+ }
+ r = await fetch("https://{{hosts[alt][]}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ if (r.status == 200) {
+ return "Site 3 fetching Site 2 did recieve sec-ch-device-memory";
+ }
+ r = await fetch("/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ if (r.status == 200) {
+ return "Site 3 fetching Site 3 did recieve sec-ch-device-memory";
+ }
+ return "ch-device-memory is available as expected for some"
+}
+test().then((message) => window.top.postMessage(message, "*"));
+</script>
+</body>
diff --git a/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_some_iframe_iframe.sub.https.html.sub.headers b/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_some_iframe_iframe.sub.https.html.sub.headers
new file mode 100644
index 0000000000..e8ccc92f51
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_some_iframe_iframe.sub.https.html.sub.headers
@@ -0,0 +1 @@
+Permissions-Policy: ch-device-memory=("https://{{host}}:{{ports[https][0]}}")
diff --git a/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_src_iframe.sub.https.html b/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_src_iframe.sub.https.html
new file mode 100644
index 0000000000..05d1db9fc9
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_src_iframe.sub.https.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<meta charset=utf-8>
+<body>
+<script>
+async function test() {
+ // Step 3 (client-hints/inner-delegation/accept_ch_delegation_to_src.sub.https.html)
+ const policy = JSON.stringify(document.featurePolicy.getAllowlistForFeature("ch-device-memory"));
+ let r = await fetch("https://{{host}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ if (r.status == 200) {
+ return "Site 2 fetching Site 1 did recieve sec-ch-device-memory";
+ }
+ r = await fetch("/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ if (r.status == 200) {
+ return "Site 2 fetching Site 2 did recieve sec-ch-device-memory";
+ }
+ r = await fetch("https://{{domains[www2]}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ if (r.status == 200) {
+ return "Site 2 fetching Site 3 did recieve sec-ch-device-memory";
+ }
+ // Step 4 (client-hints/inner-delegation/accept_ch_delegation_to_src.sub.https.html)
+ const site3Frame = document.createElement("iframe");
+ site3Frame.src = "https://{{domains[www2]}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/accept_ch_delegation_to_src_iframe_iframe.sub.https.html";
+ site3Frame.allow = "ch-device-memory src"
+ document.body.appendChild(site3Frame);
+ return ""
+}
+test().then((message) => {
+ if (message) {
+ window.top.postMessage(message, "*");
+ }
+});
+</script>
+</body>
diff --git a/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_src_iframe_iframe.sub.https.html b/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_src_iframe_iframe.sub.https.html
new file mode 100644
index 0000000000..497a19161f
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/inner-delegation/resources/accept_ch_delegation_to_src_iframe_iframe.sub.https.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<meta charset=utf-8>
+<body>
+<script>
+async function test() {
+ // Step 5 (client-hints/inner-delegation/accept_ch_delegation_to_src.sub.https.html)
+ let r = await fetch("https://{{host}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ if (r.status == 200) {
+ return "Site 3 fetching Site 1 did recieve sec-ch-device-memory";
+ }
+ r = await fetch("https://{{hosts[alt][]}}:{{ports[https][0]}}/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ if (r.status == 200) {
+ return "Site 3 fetching Site 2 did recieve sec-ch-device-memory";
+ }
+ r = await fetch("/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py");
+ if (r.status == 200) {
+ return "Site 3 fetching Site 3 did recieve sec-ch-device-memory";
+ }
+ return "ch-device-memory is available as expected for src"
+}
+test().then((message) => window.top.postMessage(message, "*"));
+</script>
+</body>
diff --git a/testing/web-platform/tests/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py b/testing/web-platform/tests/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py
new file mode 100644
index 0000000000..f57e121252
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/inner-delegation/resources/was-sec-ch-device-memory-received.py
@@ -0,0 +1,11 @@
+def main(request, response):
+ """
+ Simple handler that sets the status based on whether sec-ch-device-memory was received.
+ """
+ response.headers.append(b"Access-Control-Allow-Origin", b"*")
+ response.headers.append(b"Access-Control-Allow-Headers", b"*")
+ response.headers.append(b"Access-Control-Expose-Headers", b"*")
+ if b"sec-ch-device-memory" in request.headers:
+ response.status = 200
+ else:
+ response.status = 400 \ No newline at end of file
diff --git a/testing/web-platform/tests/client-hints/meta-equiv-delegate-ch-iframe.https.html b/testing/web-platform/tests/client-hints/meta-equiv-delegate-ch-iframe.https.html
new file mode 100644
index 0000000000..2ce9c63c18
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/meta-equiv-delegate-ch-iframe.https.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<meta http-equiv="Delegate-CH" content="Sec-CH-Device-Memory; Device-Memory; Sec-CH-DPR; DPR; Sec-CH-Viewport-Width; Viewport-Width">
+<title>Delegate-CH meta-equiv iframe test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/client-hints/resources/export.js"></script>
+<script src="resources/feature-policy-navigation.js"></script>
+<script>
+(async () => {
+ await test_frame(
+ "HTTPS_ORIGIN",
+ meta_name_client_hints,
+ "",
+ "Client hints loaded on same-origin iframe include hints with a default permissions policy of self and *.");
+ await test_frame(
+ "HTTPS_REMOTE_ORIGIN",
+ expect_iframe_no_hints,
+ "",
+ "Client hints loaded on cross-origin iframe only include hints with a default permissions policy of *.");
+})();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/meta-equiv-delegate-ch-injection.https.html b/testing/web-platform/tests/client-hints/meta-equiv-delegate-ch-injection.https.html
new file mode 100644
index 0000000000..79221efe95
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/meta-equiv-delegate-ch-injection.https.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Delegate-CH meta-equiv injection test</title>
+ <meta http-equiv="Delegate-CH" content="">
+</head>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+// Even though the next line injects an "Delegate-CH" meta-equiv header, the
+// browser should NOT attach the specified client hints in the request headers
+// because javascript injected delegate-ch meta-equiv headers are not trusted.
+document.getElementsByTagName('meta')[0].setAttribute("content", "dpr;sec-ch-dpr;device-memory;sec-ch-device-memory;viewport-width;sec-ch-viewport-width;rtt;downlink;ect");
+document.head.outerHTML += '<meta http-equiv="Delegate-CH" content="sec-ch-ua-arch;sec-ch-ua-platform;sec-ch-ua-model">';
+document.head.innerHTML += '<meta http-equiv="Delegate-CH" content="sec-ch-ua-full-version;sec-ch-ua-bitness;sec-ch-ua-full-version-list">';
+document.write('<meta http-equiv="Delegate-CH" content="sec-ch-ua-platform-version;sec-ch-prefers-color-scheme;sec-ch-prefers-reduced-motion;sec-ch-viewport-height;sec-ch-prefers-reduced-transparency">');
+
+// resources/echo-client-hints-received.py sets the response headers depending on the set
+// of client hints it receives in the request headers.
+promise_test(t => {
+ return fetch("/client-hints/resources/echo-client-hints-received.py").then(r => {
+ assert_equals(r.status, 200)
+ // Verify that the browser does not include client hints for javascript
+ // injected headers on the XHR.
+ assert_false(r.headers.has("device-memory-received"), "device-memory-received");
+ assert_false(r.headers.has("device-memory-deprecated-received"), "device-memory-deprecated-received");
+ assert_false(r.headers.has("dpr-received"), "dpr-received");
+ assert_false(r.headers.has("dpr-deprecated-received"), "dpr-deprecated-received");
+ assert_false(r.headers.has("viewport-width-received"), "viewport-width-received");
+ assert_false(r.headers.has("viewport-width-deprecated-received"), "viewport-width-deprecated-received");
+ assert_false(r.headers.has("rtt-received"), "rtt-received");
+ assert_false(r.headers.has("downlink-received"), "downlink-received");
+ assert_false(r.headers.has("ect-received"), "ect-received");
+ assert_false(r.headers.has("prefers-color-scheme-received"), "prefers-color-scheme-received");
+ assert_false(r.headers.has("prefers-reduced-transparency-received"), "prefers-reduced-transparency-received");
+ });
+}, "Delegate-CH meta-equiv injection test");
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/meta-equiv-delegate-ch-malformed-header.https.html b/testing/web-platform/tests/client-hints/meta-equiv-delegate-ch-malformed-header.https.html
new file mode 100644
index 0000000000..a4b91fd99b
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/meta-equiv-delegate-ch-malformed-header.https.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<meta http-equiv="Delegate-CH" content="Sec-CH-DPR= Sec-CH-Width">
+<title>Delegate-CH malformed meta-equiv test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+
+promise_test(t => {
+ return fetch("/client-hints/resources/echo-client-hints-received.py").then(r => {
+ assert_equals(r.status, 200)
+ // Verify that the browser does not include client hints in the headers
+ // since Delegate-CH value in meta-equiv is malformed (includes whitespace
+ // between attributes instead of comma).
+ assert_false(r.headers.has("device-memory-received"), "device-memory-received");
+ assert_false(r.headers.has("device-memory-deprecated-received"), "device-memory-deprecated-received");
+ assert_false(r.headers.has("dpr-received"), "dpr-received");
+ assert_false(r.headers.has("dpr-deprecated-received"), "dpr-deprecated-received");
+ assert_false(r.headers.has("viewport-width-received"), "viewport-width-received");
+ assert_false(r.headers.has("viewport-width-deprecated-received"), "viewport-width-deprecated-received");
+ assert_false(r.headers.has("rtt-received"), "rtt-received");
+ assert_false(r.headers.has("downlink-received"), "downlink-received");
+ assert_false(r.headers.has("ect-received"), "ect-received");
+ });
+}, "Delegate-CH malformed meta-equiv test");
+
+</script>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/meta-equiv-delegate-ch-merge.https.html b/testing/web-platform/tests/client-hints/meta-equiv-delegate-ch-merge.https.html
new file mode 100644
index 0000000000..1039be4dbe
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/meta-equiv-delegate-ch-merge.https.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Delegate-CH" content="sec-ch-viewport-width; viewport-width; rtt">
+<meta http-equiv="Delegate-CH" content="downlink; ect; sec-ch-prefers-color-scheme">
+<meta http-equiv="Delegate-CH" content="sec-ch-prefers-reduced-motion; sec-ch-prefers-reduced-transparency">
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script>
+
+// Test of merge of meta-equiv headers on top of delegate-ch provided ones.
+//
+// resources/echo-client-hints-received.py sets the response headers depending on the set
+// of client hints it receives in the request headers.
+
+promise_test(t => {
+ return fetch(get_host_info()["HTTPS_ORIGIN"] + "/client-hints/resources/echo-client-hints-received.py").then(r => {
+ assert_equals(r.status, 200)
+ // Verify that the browser includes client hints in the headers.
+ assert_true(r.headers.has("device-memory-received"), "device-memory-received");
+ assert_true(r.headers.has("device-memory-deprecated-received"), "device-memory-deprecated-received");
+ assert_true(r.headers.has("dpr-received"), "dpr-received");
+ assert_true(r.headers.has("dpr-deprecated-received"), "dpr-deprecated-received");
+ assert_true(r.headers.has("viewport-width-received"), "viewport-width-received");
+ assert_true(r.headers.has("viewport-width-deprecated-received"), "viewport-width-deprecated-received");
+
+ assert_true(r.headers.has("rtt-received"), "rtt-received");
+ var rtt = parseInt(r.headers.get("rtt-received"));
+ assert_greater_than_equal(rtt, 0);
+ assert_less_than_equal(rtt, 3000);
+ assert_equals(rtt % 50, 0, 'rtt must be a multiple of 50 msec');
+
+ assert_true(r.headers.has("downlink-received"), "downlink-received");
+ var downlinkKbps = r.headers.get("downlink-received") * 1000;
+ assert_greater_than_equal(downlinkKbps, 0);
+ assert_less_than_equal(downlinkKbps, 10000);
+
+ assert_in_array(r.headers.get("ect-received"), ["slow-2g", "2g",
+ "3g", "4g"], 'ect-received is unexpected');
+ assert_true(r.headers.has("prefers-color-scheme-received"), "prefers-color-scheme-received");
+ assert_true(r.headers.has("prefers-reduced-motion-received"), "prefers-reduced-motion-received");
+ assert_true(r.headers.has("prefers-reduced-transparency-received"), "prefers-reduced-transparency-received");
+ });
+}, "Delegate-CH header test");
+
+</script>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/meta-equiv-delegate-ch-merge.https.html.headers b/testing/web-platform/tests/client-hints/meta-equiv-delegate-ch-merge.https.html.headers
new file mode 100644
index 0000000000..34edb7b82b
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/meta-equiv-delegate-ch-merge.https.html.headers
@@ -0,0 +1,2 @@
+Accept-CH: sec-ch-device-memory, device-memory, sec-ch-dpr, dpr
+
diff --git a/testing/web-platform/tests/client-hints/meta-equiv-delegate-ch-non-secure.http.html b/testing/web-platform/tests/client-hints/meta-equiv-delegate-ch-non-secure.http.html
new file mode 100644
index 0000000000..1fbe62c281
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/meta-equiv-delegate-ch-non-secure.http.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+<meta http-equiv="Delegate-CH" content="Sec-CH-DPR; DPR; Sec-CH-Width; Width; Sec-CH-Viewport-Width; Viewport-Width; Sec-CH-Device-Memory; Device-Memory; rtt; downlink; ect">
+<title>Delegate-CH meta-equiv insecure transport test</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+
+// Even though this HTML file contains "Delegate-CH" meta-equiv headers, the
+// browser should NOT attach the specified client hints in the HTTP request
+// headers since the page is being fetched over an insecure transport.
+// Test this functionality by fetching an XHR from this page hosted on
+// an insecure HTTP server.
+
+// resources/echo-client-hints-received.py sets the response headers depending on the set
+// of client hints it receives in the request headers.
+
+promise_test(t => {
+ return fetch("/client-hints/resources/echo-client-hints-received.py").then(r => {
+ assert_equals(r.status, 200)
+ // Verify that the browser does not include client hints in the headers
+ // when fetching the XHR from an insecure HTTP server.
+ assert_false(r.headers.has("device-memory-received"), "device-memory-received");
+ assert_false(r.headers.has("device-memory-deprecated-received"), "device-memory-deprecated-received");
+ assert_false(r.headers.has("dpr-received"), "dpr-received");
+ assert_false(r.headers.has("dpr-deprecated-received"), "dpr-deprecated-received");
+ assert_false(r.headers.has("viewport-width-received"), "viewport-width-received");
+ assert_false(r.headers.has("viewport-width-deprecated-received"), "viewport-width-deprecated-received");
+ assert_false(r.headers.has("rtt-received"), "rtt-received");
+ assert_false(r.headers.has("downlink-received"), "downlink-received");
+ assert_false(r.headers.has("ect-received"), "ect-received");
+ assert_false(r.headers.has("prefers-color-scheme-received"), "prefers-color-scheme-received");
+ });
+}, "Delegate-CH meta-equiv test over insecure transport");
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/resources/accept-ch-different.html b/testing/web-platform/tests/client-hints/resources/accept-ch-different.html
new file mode 100644
index 0000000000..05cc0b61b0
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/resources/accept-ch-different.html
@@ -0,0 +1,11 @@
+<html>
+<body>
+
+<!-- An empty webpage whose response headers include only the
+Accept-CH header. Fetching this webpage not cause
+user-agent to persist origin preferences for the client hints
+specified in the Accept-CH header. (This test sends a viewport-width
+preference instead of the device-memory preference)-->
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/resources/accept-ch-different.html.headers b/testing/web-platform/tests/client-hints/resources/accept-ch-different.html.headers
new file mode 100644
index 0000000000..b428fda331
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/resources/accept-ch-different.html.headers
@@ -0,0 +1,2 @@
+Accept-CH: sec-ch-viewport-width,viewport-width
+Access-Control-Allow-Origin: *
diff --git a/testing/web-platform/tests/client-hints/resources/accept-ch-empty.html b/testing/web-platform/tests/client-hints/resources/accept-ch-empty.html
new file mode 100644
index 0000000000..27393e5a1a
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/resources/accept-ch-empty.html
@@ -0,0 +1,11 @@
+<html>
+<body>
+
+<!-- An empty webpage whose response headers include only the
+Accept-CH header. Fetching this webpage should cause
+user-agent to persist origin preferences for the client hints
+specified in the Accept-CH header until the origin data is
+cleared.-->
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/resources/accept-ch-empty.html.headers b/testing/web-platform/tests/client-hints/resources/accept-ch-empty.html.headers
new file mode 100644
index 0000000000..25215abdf7
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/resources/accept-ch-empty.html.headers
@@ -0,0 +1,2 @@
+Accept-CH:
+Access-Control-Allow-Origin: *
diff --git a/testing/web-platform/tests/client-hints/resources/accept-ch-malformed.html b/testing/web-platform/tests/client-hints/resources/accept-ch-malformed.html
new file mode 100644
index 0000000000..70c1c75713
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/resources/accept-ch-malformed.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<body>
+
+<!-- An empty webpage whose response headers include only the
+Accept-CH header. Fetching this webpage should cause
+user-agent to persist origin preferences for the client hints
+specified in the Accept-CH header.-->
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/resources/accept-ch-malformed.html.headers b/testing/web-platform/tests/client-hints/resources/accept-ch-malformed.html.headers
new file mode 100644
index 0000000000..83a6a05e54
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/resources/accept-ch-malformed.html.headers
@@ -0,0 +1,2 @@
+Accept-CH: device memory
+Access-Control-Allow-Origin: *
diff --git a/testing/web-platform/tests/client-hints/resources/accept-ch-mixed.html b/testing/web-platform/tests/client-hints/resources/accept-ch-mixed.html
new file mode 100644
index 0000000000..bf604dffee
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/resources/accept-ch-mixed.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<body>
+
+<!-- An empty webpage whose response headers include empty and full
+Accept-CH headers. Fetching this webpage should cause
+the hint cache to be dropped.-->
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/resources/accept-ch-mixed.html.headers b/testing/web-platform/tests/client-hints/resources/accept-ch-mixed.html.headers
new file mode 100644
index 0000000000..39d4e1935d
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/resources/accept-ch-mixed.html.headers
@@ -0,0 +1,5 @@
+Accept-CH:
+Accept-CH: sec-ch-device-memory
+Accept-CH: device-memory
+Accept-CH:
+Access-Control-Allow-Origin: *
diff --git a/testing/web-platform/tests/client-hints/resources/accept-ch-split.html b/testing/web-platform/tests/client-hints/resources/accept-ch-split.html
new file mode 100644
index 0000000000..5038b94013
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/resources/accept-ch-split.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<body>
+
+<!-- An empty webpage whose response headers include only
+non-empty Accept-CH headers. Fetching this webpage should cause
+hints to be added to the cache.-->
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/resources/accept-ch-split.html.headers b/testing/web-platform/tests/client-hints/resources/accept-ch-split.html.headers
new file mode 100644
index 0000000000..261e1e8ee6
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/resources/accept-ch-split.html.headers
@@ -0,0 +1,3 @@
+Accept-CH: sec-ch-device-memory
+Accept-CH: device-memory
+Access-Control-Allow-Origin: *
diff --git a/testing/web-platform/tests/client-hints/resources/accept-ch.html b/testing/web-platform/tests/client-hints/resources/accept-ch.html
new file mode 100644
index 0000000000..1f1da26ceb
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/resources/accept-ch.html
@@ -0,0 +1,10 @@
+<html>
+<body>
+
+<!-- An empty webpage whose response headers include only the
+Accept-CH header. Fetching this webpage should cause
+user-agent to persist origin preferences for the client hints
+specified in the Accept-CH header.-->
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/resources/accept-ch.html.headers b/testing/web-platform/tests/client-hints/resources/accept-ch.html.headers
new file mode 100644
index 0000000000..f5beb4c365
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/resources/accept-ch.html.headers
@@ -0,0 +1,2 @@
+Accept-CH: sec-ch-device-memory,device-memory
+Access-Control-Allow-Origin: *
diff --git a/testing/web-platform/tests/client-hints/resources/accept-ch.wildcard.iframe.https.sub.html b/testing/web-platform/tests/client-hints/resources/accept-ch.wildcard.iframe.https.sub.html
new file mode 100644
index 0000000000..250a9d9e22
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/resources/accept-ch.wildcard.iframe.https.sub.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<meta charset="utf-8">
+<script>
+// Step 2 (client-hints/accept-ch.wildcard.https.sub.html)
+const xhr = new XMLHttpRequest();
+xhr.open('POST', "https://{{domains[www2]}}:{{ports[https][0]}}/client-hints/resources/echo-ua-client-hints-received.py");
+xhr.onload = function() {
+ // Step 3 (client-hints/accept-ch.wildcard.https.sub.html)
+ if (xhr.getResponseHeader("sec-ch-ua-platform-received") != null) {
+ window.top.postMessage("HadPlatformVersion", "*");
+ } else {
+ window.top.postMessage("MissingPlatformVersion", "*");
+ }
+};
+xhr.send();
+</script>
diff --git a/testing/web-platform/tests/client-hints/resources/accept-ch.wildcard.iframe.https.sub.html.sub.headers b/testing/web-platform/tests/client-hints/resources/accept-ch.wildcard.iframe.https.sub.html.sub.headers
new file mode 100644
index 0000000000..c1c0fcd5c2
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/resources/accept-ch.wildcard.iframe.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Permissions-Policy: ch-ua-platform-version=("https://*.{{domains[]}}:{{ports[https][0]}}") \ No newline at end of file
diff --git a/testing/web-platform/tests/client-hints/resources/clienthintslist.py b/testing/web-platform/tests/client-hints/resources/clienthintslist.py
new file mode 100644
index 0000000000..badf716c02
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/resources/clienthintslist.py
@@ -0,0 +1,41 @@
+def client_hints_list():
+ return [b"device-memory",
+ b"dpr",
+ # b"width", (Only available for images)
+ b"viewport-width",
+ b"rtt",
+ b"downlink",
+ b"ect",
+ b"sec-ch-ua",
+ b"sec-ch-ua-arch",
+ b"sec-ch-ua-platform",
+ b"sec-ch-ua-model",
+ b"sec-ch-ua-mobile",
+ b"sec-ch-ua-full-version",
+ b"sec-ch-ua-platform-version",
+ b"sec-ch-prefers-color-scheme",
+ b"sec-ch-prefers-reduced-motion",
+ b"sec-ch-ua-bitness",
+ b"sec-ch-viewport-height",
+ b"sec-ch-device-memory",
+ b"sec-ch-dpr",
+ # b"sec-ch-width", (Only available for images)
+ b"sec-ch-viewport-width",
+ b"sec-ch-ua-full-version-list",
+ b"sec-ch-ua-wow64",
+ b"sec-ch-prefers-reduced-transparency",
+ ]
+
+def client_hints_full_list():
+ return client_hints_list() + [b"width", b"sec-ch-width"]
+
+def client_hints_ua_list():
+ return [b"sec-ch-ua",
+ b"sec-ch-ua-arch",
+ b"sec-ch-ua-platform",
+ b"sec-ch-ua-platform-version",
+ b"sec-ch-ua-model",
+ b"sec-ch-ua-full-version",
+ b"sec-ch-ua-full-version-list",
+ b"sec-ch-ua-wow64",
+ ] \ No newline at end of file
diff --git a/testing/web-platform/tests/client-hints/resources/echo-client-hints-received.py b/testing/web-platform/tests/client-hints/resources/echo-client-hints-received.py
new file mode 100644
index 0000000000..0aa5bfb25c
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/resources/echo-client-hints-received.py
@@ -0,0 +1,38 @@
+def main(request, response):
+ """
+ Simple handler that sets a response header based on which client hint
+ request headers were received.
+ """
+
+ response.headers.append(b"Access-Control-Allow-Origin", b"*")
+ response.headers.append(b"Access-Control-Allow-Headers", b"*")
+ response.headers.append(b"Access-Control-Expose-Headers", b"*")
+
+ if b"sec-ch-device-memory" in request.headers:
+ response.headers.set(b"device-memory-received", request.headers.get(b"sec-ch-device-memory"))
+ if b"device-memory" in request.headers:
+ response.headers.set(b"device-memory-deprecated-received", request.headers.get(b"device-memory"))
+ if b"sec-ch-dpr" in request.headers:
+ response.headers.set(b"dpr-received", request.headers.get(b"sec-ch-dpr"))
+ if b"dpr" in request.headers:
+ response.headers.set(b"dpr-deprecated-received", request.headers.get(b"dpr"))
+ if b"sec-ch-viewport-width" in request.headers:
+ response.headers.set(b"viewport-width-received", request.headers.get(b"sec-ch-viewport-width"))
+ if b"viewport-width" in request.headers:
+ response.headers.set(b"viewport-width-deprecated-received", request.headers.get(b"viewport-width"))
+ if b"sec-ch-viewport-height" in request.headers:
+ response.headers.set(b"viewport-height-received", request.headers.get(b"sec-ch-viewport-height"))
+ if b"rtt" in request.headers:
+ response.headers.set(b"rtt-received", request.headers.get(b"rtt"))
+ if b"downlink" in request.headers:
+ response.headers.set(b"downlink-received", request.headers.get(b"downlink"))
+ if b"ect" in request.headers:
+ response.headers.set(b"ect-received", request.headers.get(b"ect"))
+ if b"sec-ch-ua-mobile" in request.headers:
+ response.headers.set(b"mobile-received", request.headers.get(b"sec-ch-ua-mobile"))
+ if b"sec-ch-prefers-color-scheme" in request.headers:
+ response.headers.set(b"prefers-color-scheme-received", request.headers.get(b"sec-ch-prefers-color-scheme"))
+ if b"sec-ch-prefers-reduced-motion" in request.headers:
+ response.headers.set(b"prefers-reduced-motion-received", request.headers.get(b"sec-ch-prefers-reduced-motion"))
+ if b"sec-ch-prefers-reduced-transparency" in request.headers:
+ response.headers.set(b"prefers-reduced-transparency-received", request.headers.get(b"sec-ch-prefers-reduced-transparency"))
diff --git a/testing/web-platform/tests/client-hints/resources/echo-ua-client-hints-received.py b/testing/web-platform/tests/client-hints/resources/echo-ua-client-hints-received.py
new file mode 100644
index 0000000000..7982421d07
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/resources/echo-ua-client-hints-received.py
@@ -0,0 +1,23 @@
+import importlib
+client_hints_ua_list = importlib.import_module("client-hints.resources.clienthintslist").client_hints_ua_list
+
+def main(request, response):
+ """
+ Simple handler that sets a response header based on which client hint
+ request headers were received.
+ """
+
+ response.headers.append(b"Access-Control-Allow-Origin", b"*")
+ response.headers.append(b"Access-Control-Allow-Headers", b"*")
+ response.headers.append(b"Access-Control-Expose-Headers", b"*")
+
+ client_hint_headers = client_hints_ua_list()
+ request_client_hints = {i: request.headers.get(i) for i in client_hint_headers}
+
+ for header in client_hint_headers:
+ if request_client_hints[header] is not None:
+ response.headers.set(header + b"-received", request_client_hints[header])
+
+ headers = []
+ content = u""
+ return 200, headers, content
diff --git a/testing/web-platform/tests/client-hints/resources/expect-client-hints-headers-iframe.py b/testing/web-platform/tests/client-hints/resources/expect-client-hints-headers-iframe.py
new file mode 100644
index 0000000000..6a1218264e
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/resources/expect-client-hints-headers-iframe.py
@@ -0,0 +1,31 @@
+from wptserve.utils import isomorphic_decode
+
+import importlib
+client_hints_list = importlib.import_module("client-hints.resources.clienthintslist").client_hints_list
+
+def main(request, response):
+ """
+ Simple handler that returns an HTML response that passes when the required
+ Client Hints are received as request headers.
+ """
+
+ result = u"PASS"
+ log = u""
+ for value in client_hints_list():
+ should = (request.GET[value.lower()] == b"true")
+ present = request.headers.get(value.lower()) or request.headers.get(value)
+ if present:
+ log += isomorphic_decode(value) + u" " + str(should) + u" " + isomorphic_decode(present) + u", "
+ else:
+ log += isomorphic_decode(value) + u" " + str(should) + u" " + str(present) + u", "
+ if (should and not present) or (not should and present):
+ if present:
+ result = u"FAIL " + isomorphic_decode(value) + u" " + str(should) + u" " + isomorphic_decode(present)
+ else:
+ result = u"FAIL " + isomorphic_decode(value) + u" " + str(should) + u" " + str(present)
+ break
+
+ response.headers.append(b"Access-Control-Allow-Origin", b"*")
+ body = u"<script>console.log('" + log + u"'); window.parent.postMessage('" + result + u"', '*');</script>"
+
+ response.content = body
diff --git a/testing/web-platform/tests/client-hints/resources/expect-client-hints-headers.html b/testing/web-platform/tests/client-hints/resources/expect-client-hints-headers.html
new file mode 100644
index 0000000000..928a82db45
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/resources/expect-client-hints-headers.html
@@ -0,0 +1,22 @@
+<html>
+<body>
+<script>
+
+// This test checks if browser attaches the device-memory client hint in the
+// HTTP request headers.
+
+// resources/echo-client-hints-received.py sets the response headers depending on the set
+// of client hints it receives in the request headers.
+
+fetch("../resources/echo-client-hints-received.py").then(r => {
+ if(r.status == 200 && r.headers.has("device-memory-received") && r.headers.has("device-memory-deprecated-received")) {
+ window.top.opener.postMessage('PASS', '*');
+ }
+ else {
+ window.top.opener.postMessage('FAIL', '*');
+ }
+});
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/resources/expect-different-client-hints-headers.html b/testing/web-platform/tests/client-hints/resources/expect-different-client-hints-headers.html
new file mode 100644
index 0000000000..19ee394f46
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/resources/expect-different-client-hints-headers.html
@@ -0,0 +1,22 @@
+<html>
+<body>
+<script>
+
+// This test checks if browser attaches the viewport-width client hint in the
+// HTTP request headers.
+
+// resources/echo-client-hints-received.py sets the response headers depending on the set
+// of client hints it receives in the request headers.
+
+fetch("../resources/echo-client-hints-received.py").then(r => {
+ if(r.status == 200 && r.headers.has("viewport-width-received") && r.headers.has("viewport-width-deprecated-received")) {
+ window.top.opener.postMessage('PASS', '*');
+ }
+ else {
+ window.top.opener.postMessage('FAIL', '*');
+ }
+});
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/resources/expect-no-client-hints-headers.html b/testing/web-platform/tests/client-hints/resources/expect-no-client-hints-headers.html
new file mode 100644
index 0000000000..07eb0568d4
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/resources/expect-no-client-hints-headers.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script>
+
+// This test checks if browser attaches the viewport-width client hint in the
+// HTTP request headers.
+
+// resources/echo-client-hints-received.py sets the response headers depending on the set
+// of client hints it receives in the request headers.
+
+fetch("../resources/echo-client-hints-received.py").then(r => {
+ if(r.status == 200 &&
+ !r.headers.has("viewport-width-received") &&
+ !r.headers.has("viewport-width-received")) {
+ window.top.opener.postMessage('PASS', '*');
+ } else {
+ window.top.opener.postMessage('FAIL', '*');
+ }
+});
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/resources/export.js b/testing/web-platform/tests/client-hints/resources/export.js
new file mode 100644
index 0000000000..e2596e1c6f
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/resources/export.js
@@ -0,0 +1,50 @@
+const client_hints_list = [
+ "device-memory",
+ "dpr",
+ // "width", (only available for images)
+ "viewport-width",
+ "rtt",
+ "downlink",
+ "ect",
+ "sec-ch-ua",
+ "sec-ch-ua-arch",
+ "sec-ch-ua-platform",
+ "sec-ch-ua-model",
+ "sec-ch-ua-mobile",
+ "sec-ch-ua-full-version",
+ "sec-ch-ua-platform-version",
+ "sec-ch-prefers-color-scheme",
+ "sec-ch-prefers-reduced-motion",
+ "sec-ch-ua-bitness",
+ "sec-ch-viewport-height",
+ "sec-ch-device-memory",
+ "sec-ch-dpr",
+ // "sec-ch-width", (Only available for images)
+ "sec-ch-viewport-width",
+ "sec-ch-ua-full-version-list",
+ "sec-ch-ua-wow64",
+ "sec-ch-prefers-reduced-transparency",
+];
+
+const client_hints_full_list = client_hints_list.concat(["width", "sec-ch-width"])
+
+const default_on_client_hints = [
+ "sec-ch-ua",
+ "sec-ch-ua-mobile",
+ "sec-ch-ua-platform",
+];
+
+const iframe_src =
+ "/client-hints/resources/expect-client-hints-headers-iframe.py?";
+
+const expect_iframe_no_hints = iframe_src +
+ client_hints_list.map((e) => {
+ if(default_on_client_hints.includes(e)) {
+ return e+"=true";
+ } else {
+ return e+"=false";
+ }
+ }).join("&");
+
+const expect_iframe_hints = iframe_src +
+ client_hints_list.map(e => e+"=true").join("&");
diff --git a/testing/web-platform/tests/client-hints/resources/feature-policy-navigation.js b/testing/web-platform/tests/client-hints/resources/feature-policy-navigation.js
new file mode 100644
index 0000000000..23782fdfdb
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/resources/feature-policy-navigation.js
@@ -0,0 +1,72 @@
+const meta_name_enabled = [
+ "sec-ch-device-memory",
+ "device-memory",
+ "sec-ch-dpr",
+ "dpr",
+ "sec-ch-viewport-width",
+ "viewport-width",
+ "sec-ch-ua",
+ "sec-ch-ua-mobile",
+ "sec-ch-ua-platform",
+];
+
+const meta_name_client_hints = iframe_src +
+ client_hints_list.map((e) => {
+ if(meta_name_enabled.includes(e)) {
+ return e+"=true";
+ } else {
+ return e+"=false";
+ }
+ }).join("&");
+
+const cross_origin_enabled = [
+ "device-memory",
+ "sec-ch-device-memory",
+ "sec-ch-ua-platform",
+];
+
+const cross_origin_client_hints = iframe_src +
+ client_hints_list.map((e) => {
+ if(cross_origin_enabled.includes(e)) {
+ return e+"=true";
+ } else {
+ return e+"=false";
+ }
+ }).join("&");
+
+const same_origin_disabled = [
+ "dpr",
+ "sec-ch-dpr",
+ "sec-ch-ua-mobile",
+];
+
+const same_origin_client_hints = iframe_src +
+ client_hints_list.map((e) => {
+ if(same_origin_disabled.includes(e)) {
+ return e+"=false";
+ } else {
+ return e+"=true";
+ }
+ }).join("&");
+
+const test_frame = (origin, url, allow, message) => {
+ promise_test(() => {
+ return new Promise((resolve, reject) => {
+ let frame = document.createElement('iframe');
+ frame.allow = allow;
+ window.addEventListener('message', function(e) {
+ try {
+ assert_equals(typeof e.data, "string");
+ assert_equals(e.data, "PASS");
+ } catch {
+ reject(e.data);
+ }
+ resolve();
+ });
+ document.body.appendChild(frame);
+ // Writing to |frame.src| triggers the navigation, so
+ // everything else need to happen first.
+ frame.src = get_host_info()[origin] + url;
+ });
+ }, message);
+}
diff --git a/testing/web-platform/tests/client-hints/resources/open-and-add-load-event.js b/testing/web-platform/tests/client-hints/resources/open-and-add-load-event.js
new file mode 100644
index 0000000000..bd88fcc28e
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/resources/open-and-add-load-event.js
@@ -0,0 +1,23 @@
+function open_and_add_load_event(href) {
+ return new Promise((resolve) => {
+ // While not practically possible, opening "blank" first and setting the
+ // href after allows for the theoretical possibility of registering the event
+ // after the window is loaded.
+ let popup_window = window.open("/resources/blank.html");
+ assert_not_equals(popup_window, null, "Popup windows not allowed?");
+ popup_window.addEventListener('load', resolve, {once: true});
+ popup_window.location.href = href;
+ });
+}
+
+async function open_and_expect_headers(href) {
+ let e = await new Promise(resolve => {
+ let popup_window = window.open("/resources/blank.html");
+ assert_not_equals(popup_window, null, "Popup windows not allowed?");
+ window.addEventListener('message', resolve, false);
+ popup_window.location.href = href;
+ });
+
+ assert_equals(e.data, "PASS");
+ return e;
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/client-hints/resources/script-set-dpr-header.py b/testing/web-platform/tests/client-hints/resources/script-set-dpr-header.py
new file mode 100644
index 0000000000..9a65886ed8
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/resources/script-set-dpr-header.py
@@ -0,0 +1,4 @@
+def main(request, response):
+ headers = [(b"Content-Type", b"text/javascript")]
+ body = u'dprHeader = "%s";' % request.headers.get(b'sec-ch-dpr', '')
+ return 200, headers, body
diff --git a/testing/web-platform/tests/client-hints/resources/sec-ch-ua.py b/testing/web-platform/tests/client-hints/resources/sec-ch-ua.py
new file mode 100644
index 0000000000..ddeb582a19
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/resources/sec-ch-ua.py
@@ -0,0 +1,9 @@
+def main(request, response):
+ ua = request.headers.get(b'Sec-CH-UA', b'')
+ response.headers.set(b"Content-Type", b"text/html")
+ response.content = b'''
+<script>
+ window.opener.postMessage({ header: '%s' }, "*");
+</script>
+Sec-CH-UA: %s
+''' % (ua, ua)
diff --git a/testing/web-platform/tests/client-hints/resources/stale-echo-client-hints.py b/testing/web-platform/tests/client-hints/resources/stale-echo-client-hints.py
new file mode 100644
index 0000000000..e9832273b8
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/resources/stale-echo-client-hints.py
@@ -0,0 +1,50 @@
+import random
+import string
+
+from wptserve.utils import isomorphic_encode
+import importlib
+client_hints_full_list = importlib.import_module("client-hints.resources.clienthintslist").client_hints_full_list
+
+def id_token():
+ letters = string.ascii_lowercase
+ return u''.join(random.choice(letters) for i in range(20))
+
+def main(request, response):
+ client_hint_headers = client_hints_full_list()
+ client_hints_curr = {i:request.headers.get(i) for i in client_hint_headers}
+
+ token = request.GET.first(b"token", None)
+ is_query = request.GET.first(b"query", None) is not None
+ with request.server.stash.lock:
+ stash = request.server.stash.take(token)
+ if stash != None:
+ (value, client_hints_prev) = stash
+ count = int(value)
+ else:
+ count = 0
+ client_hints_prev = {}
+
+ if is_query:
+ if count < 2:
+ request.server.stash.put(token, (count, client_hints_curr))
+ else:
+ count = count + 1
+ request.server.stash.put(token, (count, client_hints_curr))
+
+ for header in client_hint_headers:
+ if client_hints_curr[header] is not None:
+ response.headers.set(header+b"-recieved", client_hints_curr[header])
+ if (header in client_hints_prev) and (client_hints_prev[header] is not None):
+ response.headers.set(header+b"-previous", client_hints_prev[header])
+
+ if is_query:
+ headers = [(b"Count", count)]
+ content = u""
+ return 200, headers, content
+ else:
+ unique_id = id_token()
+ headers = [(b"Content-Type", b"text/html"),
+ (b"Cache-Control", b"private, max-age=0, stale-while-revalidate=60"),
+ (b"Unique-Id", isomorphic_encode(unique_id))]
+ content = u"report('{}')".format(unique_id)
+ return 200, headers, content
diff --git a/testing/web-platform/tests/client-hints/resources/viewport-frame.py b/testing/web-platform/tests/client-hints/resources/viewport-frame.py
new file mode 100644
index 0000000000..67b592c71a
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/resources/viewport-frame.py
@@ -0,0 +1,25 @@
+def main(request, response):
+ """
+ postMessage with Viewport-Width and Sec-Ch-Viewport-Height headers
+ """
+
+ if b"sec-ch-viewport-width" in request.headers:
+ width = request.headers["sec-ch-viewport-width"]
+ else:
+ width = b"FAIL"
+
+ if b"sec-ch-viewport-height" in request.headers:
+ height = request.headers["sec-ch-viewport-height"]
+ else:
+ height = b"FAIL"
+
+ headers = [(b"Content-Type", b"text/html"),
+ (b"Access-Control-Allow-Origin", b"*")]
+ content = b'''
+<script>
+ let parentOrOpener = window.opener || window.parent;
+ parentOrOpener.postMessage({ viewportWidth: '%s', viewportHeight: '%s' }, "*");
+</script>
+''' % (width, height)
+
+ return 200, headers, content
diff --git a/testing/web-platform/tests/client-hints/resources/viewport-measurement.html b/testing/web-platform/tests/client-hints/resources/viewport-measurement.html
new file mode 100644
index 0000000000..2ac9043af7
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/resources/viewport-measurement.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<script>
+(async () => {
+ const response = await fetch("viewport.py");
+ const body = await response.text();
+ parent.postMessage(body, "*");
+})();
+</script>
diff --git a/testing/web-platform/tests/client-hints/resources/viewport.py b/testing/web-platform/tests/client-hints/resources/viewport.py
new file mode 100644
index 0000000000..d5ab6d4eee
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/resources/viewport.py
@@ -0,0 +1,13 @@
+def main(request, response):
+ """
+ Reflect Sec-Ch-Viewport-Width and Sec-Ch-Viewport-Height headers
+ """
+
+ if b"sec-ch-viewport-width" in request.headers and b"sec-ch-viewport-height" in request.headers:
+ result = request.headers["sec-ch-viewport-width"] + b"," + request.headers["sec-ch-viewport-height"]
+ else:
+ result = u"FAIL"
+
+ headers = [(b"Content-Type", b"text/html"),
+ (b"Access-Control-Allow-Origin", b"*")]
+ return 200, headers, result
diff --git a/testing/web-platform/tests/client-hints/sandbox/__dir__.headers b/testing/web-platform/tests/client-hints/sandbox/__dir__.headers
new file mode 100644
index 0000000000..4d4739028d
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/sandbox/__dir__.headers
@@ -0,0 +1 @@
+Accept-CH: device-memory, dpr, width, viewport-width, rtt, downlink, ect, sec-ch-ua, sec-ch-ua-arch, sec-ch-ua-platform, sec-ch-ua-model, sec-ch-ua-mobile, sec-ch-ua-full-version, sec-ch-ua-platform-version, sec-ch-prefers-color-scheme, sec-ch-prefers-reduced-motion, sec-ch-ua-bitness, sec-ch-viewport-height, sec-ch-device-memory, sec-ch-dpr, sec-ch-width, sec-ch-viewport-width, sec-ch-ua-full-version-list, sec-ch-ua-wow64, sec-ch-prefers-reduced-transparency
diff --git a/testing/web-platform/tests/client-hints/sandbox/iframe-csp-same-origin.https.html b/testing/web-platform/tests/client-hints/sandbox/iframe-csp-same-origin.https.html
new file mode 100644
index 0000000000..a5f094af9d
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/sandbox/iframe-csp-same-origin.https.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/client-hints/resources/export.js"></script>
+<script src="resources/util.js"></script>
+
+<script>
+
+sandbox_iframe_test("",
+ expect_iframe_hints,
+ "CSP sandboxed iframe with same-origin flag does send client hint headers");
+
+</script>
+
+</body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/client-hints/sandbox/iframe-csp-same-origin.https.html.headers b/testing/web-platform/tests/client-hints/sandbox/iframe-csp-same-origin.https.html.headers
new file mode 100644
index 0000000000..895eb51993
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/sandbox/iframe-csp-same-origin.https.html.headers
@@ -0,0 +1 @@
+Content-Security-Policy: sandbox allow-scripts allow-same-origin \ No newline at end of file
diff --git a/testing/web-platform/tests/client-hints/sandbox/iframe-csp.https.html b/testing/web-platform/tests/client-hints/sandbox/iframe-csp.https.html
new file mode 100644
index 0000000000..fb63c393fd
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/sandbox/iframe-csp.https.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/client-hints/resources/export.js"></script>
+<script src="resources/util.js"></script>
+
+<script>
+
+sandbox_iframe_test("",
+ expect_iframe_no_hints,
+ "CSP sandboxed iframe does not send client hint headers");
+
+</script>
+
+</body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/client-hints/sandbox/iframe-csp.https.html.headers b/testing/web-platform/tests/client-hints/sandbox/iframe-csp.https.html.headers
new file mode 100644
index 0000000000..c7e4e7cc5b
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/sandbox/iframe-csp.https.html.headers
@@ -0,0 +1 @@
+Content-Security-Policy: sandbox allow-scripts \ No newline at end of file
diff --git a/testing/web-platform/tests/client-hints/sandbox/iframe-iframe-popups.https.html b/testing/web-platform/tests/client-hints/sandbox/iframe-iframe-popups.https.html
new file mode 100644
index 0000000000..9cbf6006c8
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/sandbox/iframe-iframe-popups.https.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/client-hints/resources/export.js"></script>
+<script src="resources/util.js"></script>
+
+<script>
+
+sandbox_iframe_test("allow-scripts allow-popups",
+ "resources/iframe-with-embedded-popup-expect-no-hints.html",
+ "Popup from an iframe within a sandboxed iframe does not send hints");
+
+</script>
+
+</body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/client-hints/sandbox/iframe-popups-escape-sandbox.https.html b/testing/web-platform/tests/client-hints/sandbox/iframe-popups-escape-sandbox.https.html
new file mode 100644
index 0000000000..35a94b537d
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/sandbox/iframe-popups-escape-sandbox.https.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/client-hints/resources/export.js"></script>
+<script src="resources/util.js"></script>
+
+<script>
+
+sandbox_iframe_test("allow-scripts allow-popups allow-popups-to-escape-sandbox",
+ "resources/embedded-popup-expect-hints.html",
+ "popup from sandboxed iframe with allow-popups-to-escape-sandbox flag does send client hint headers");
+
+</script>
+
+</body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/client-hints/sandbox/iframe-popups.https.html b/testing/web-platform/tests/client-hints/sandbox/iframe-popups.https.html
new file mode 100644
index 0000000000..3d7bc6eb80
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/sandbox/iframe-popups.https.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/client-hints/resources/export.js"></script>
+<script src="resources/util.js"></script>
+
+<script>
+
+sandbox_iframe_test("allow-scripts allow-popups",
+ "resources/embedded-popup-expect-no-hints.html",
+ "popup from sandboxed iframe does not send client hint headers");
+
+</script>
+
+</body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/client-hints/sandbox/iframe-same-origin.https.html b/testing/web-platform/tests/client-hints/sandbox/iframe-same-origin.https.html
new file mode 100644
index 0000000000..0ea6a8ed53
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/sandbox/iframe-same-origin.https.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/client-hints/resources/export.js"></script>
+<script src="resources/util.js"></script>
+
+<script>
+sandbox_iframe_test("allow-scripts allow-same-origin",
+ expect_iframe_hints,
+ "Same origin sandboxed iframe with allow-same-origin flag does send client hint headers");
+</script>
+
+</body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/client-hints/sandbox/iframe.https.html b/testing/web-platform/tests/client-hints/sandbox/iframe.https.html
new file mode 100644
index 0000000000..f37b91011b
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/sandbox/iframe.https.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/client-hints/resources/export.js"></script>
+<script src="resources/util.js"></script>
+
+<script>
+
+sandbox_iframe_test("allow-scripts",
+ expect_iframe_no_hints,
+ "sandboxed iframe does not send client hint headers");
+
+</script>
+
+</body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/client-hints/sandbox/resources/embedded-popup-expect-hints.html b/testing/web-platform/tests/client-hints/sandbox/resources/embedded-popup-expect-hints.html
new file mode 100644
index 0000000000..6cd5f0a29e
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/sandbox/resources/embedded-popup-expect-hints.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="util.js"></script>
+<script>
+sandbox_popup_listener("/client-hints/resources/expect-client-hints-headers.html");
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/client-hints/sandbox/resources/embedded-popup-expect-no-hints.html b/testing/web-platform/tests/client-hints/sandbox/resources/embedded-popup-expect-no-hints.html
new file mode 100644
index 0000000000..46dbb7f236
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/sandbox/resources/embedded-popup-expect-no-hints.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="util.js"></script>
+<script>
+sandbox_popup_listener("/client-hints/resources/expect-no-client-hints-headers.html");
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/client-hints/sandbox/resources/iframe-with-embedded-popup-expect-no-hints.html b/testing/web-platform/tests/client-hints/sandbox/resources/iframe-with-embedded-popup-expect-no-hints.html
new file mode 100644
index 0000000000..96d2886f0e
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/sandbox/resources/iframe-with-embedded-popup-expect-no-hints.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script>
+ window.addEventListener('message', e => {
+ window.parent.postMessage(e.data, '*');
+ });
+
+</script>
+<iframe src="embedded-popup-expect-no-hints.html"></iframe>
+</body>
+</html>
diff --git a/testing/web-platform/tests/client-hints/sandbox/resources/util.js b/testing/web-platform/tests/client-hints/sandbox/resources/util.js
new file mode 100644
index 0000000000..8c25e5ad8b
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/sandbox/resources/util.js
@@ -0,0 +1,25 @@
+function sandbox_iframe_test(sandbox, src, title) {
+ return promise_test(async t => {
+ const iframe = document.createElement("iframe");
+ if (sandbox !== "")
+ iframe.sandbox = sandbox;
+ iframe.src = src;
+
+ let msg = await new Promise(resolve => {
+ window.addEventListener('message', resolve);
+ document.body.appendChild(iframe);
+ });
+
+ assert_equals(msg.data, "PASS", "message from opened frame");
+ await fetch("/client-hints/accept-ch-stickiness/resources/clear-site-data.html");
+ }, title);
+}
+
+function sandbox_popup_listener(src) {
+ window.addEventListener('message', e => {
+ window.parent.postMessage(e.data, '*');
+ });
+
+ let popup_window = window.open("/resources/blank.html");
+ popup_window.location.href = src;
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/client-hints/sec-ch-quotes.https.html b/testing/web-platform/tests/client-hints/sec-ch-quotes.https.html
new file mode 100644
index 0000000000..08f7c51af0
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/sec-ch-quotes.https.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Tests Stale While Revalidate is not executed for fetch API</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script>
+const verify_headers = (header_list, response, verification_func) => {
+ header_list.forEach(header => {
+ const value = response.headers.get(header+"-received");
+ if(value) {
+ verification_func(value);
+ }
+ });
+};
+
+promise_test(async (test) => {
+ const request_token = token();
+ const string_list_client_hint_headers = [
+ "sec-ch-ua",
+ ];
+ const string_client_hint_headers = [
+ "sec-ch-ua-arch",
+ "sec-ch-ua-platform",
+ "sec-ch-ua-platform-version",
+ "sec-ch-ua-model",
+ "sec-ch-ua-full-version",
+ "sec-ch-prefers-color-scheme",
+ "sec-ch-prefers-reduced-motion",
+ "sec-ch-ua-full-version-list",
+ "sec-ch-ua-wow64",
+ "sec-ch-prefers-reduced-transparency",
+ ];
+ const boolean_client_hint_headers = [
+ "sec-ch-mobile",
+ ];
+
+ const response = await fetch("resources/echo-ua-client-hints-received.py");
+ verify_headers(string_client_hint_headers, response, value => {
+ if(value) {
+ // Check that the hints have quotes
+ // TODO(yoav): this doesn't account for parameters. Need an SH parser, that verifies no parameters are present.
+ assert_equals(value.slice(0,1), "\"");
+ assert_equals(value.slice(-1), "\"");
+ }
+ });
+ verify_headers(string_list_client_hint_headers, response, value => {
+ // Check that the hints have quotes
+ // TODO(yoav): this doesn't account for list parsing or parameters. Need an SH parser, that verifies this is a list of strings with a "v" parameter present in at least one value.
+ assert_false((typeof value) == "undefined");
+ assert_equals(value.slice(0,1), "\"");
+ assert_equals(value.slice(-1), "\"");
+ });
+ verify_headers(boolean_client_hint_headers, response, value => {
+ if(value) {
+ // Check that the value is a valid boolean
+ assert_false((typeof value) == "undefined");
+ assert_equals(value.slice(0,1), "?");
+ const num = value.slice(-1);
+ assert_true(num == "0" || num == "1");
+ }
+ });
+}, 'User agent client hint header values are surrounded by quotes');
+</script>
diff --git a/testing/web-platform/tests/client-hints/sec-ch-quotes.https.html.headers b/testing/web-platform/tests/client-hints/sec-ch-quotes.https.html.headers
new file mode 100644
index 0000000000..1d6f8f26fb
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/sec-ch-quotes.https.html.headers
@@ -0,0 +1 @@
+Accept-CH: sec-ch-ua,sec-ch-ua-arch,sec-ch-ua-platform,sec-ch-ua-platform-version,sec-ch-ua-model,sec-ch-prefers-color-scheme,sec-ch-prefers-reduced-motion,sec-ch-prefers-reduced-transparency
diff --git a/testing/web-platform/tests/client-hints/sec-ch-ua.http.html b/testing/web-platform/tests/client-hints/sec-ch-ua.http.html
new file mode 100644
index 0000000000..e333c60530
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/sec-ch-ua.http.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ promise_test(t => {
+ return new Promise((resolve, reject) => {
+ var w;
+ window.onmessage = e => {
+ assert_equals(e.data.header, "", "The `Sec-CH-UA` header is not delivered.");
+ w.close();
+ resolve();
+ };
+ w = window.open("./resources/sec-ch-ua.py");
+ });
+ }, "Open HTTP window: no `Sec-CH-UA` header.")
+</script>
diff --git a/testing/web-platform/tests/client-hints/sec-ch-ua.https.html b/testing/web-platform/tests/client-hints/sec-ch-ua.https.html
new file mode 100644
index 0000000000..deb8169216
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/sec-ch-ua.https.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<head>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+ var sec_ch_ua_header = "";
+
+ function grabSECCHUAHeader(t) {
+ return new Promise((resolve, reject) => {
+ var w;
+ window.onmessage = e => {
+ try {
+ resolve(e.data.header)
+ } catch (ex) {
+ reject(ex);
+ }
+ };
+ w = window.open("./resources/sec-ch-ua.py");
+ t.add_cleanup(w.close);
+ });
+ }
+ promise_test(t => {
+ return grabSECCHUAHeader(t).then(header => {
+ sec_ch_ua_header = header;
+ assert_not_equals(sec_ch_ua_header, "", "`Sec-CH-UA` is sent.");
+ });
+ }, "Open HTTPS window: `Sec-CH-UA` header returned by default.");
+
+ promise_test(t => {
+ return grabSECCHUAHeader(t).then(header => {
+ assert_not_equals(header, "", "The `Sec-CH-UA` header is delivered.");
+ assert_equals(header, sec_ch_ua_header,
+ "The `Sec-CH-UA` header did not change between requests.");
+ });
+ }, "Open HTTPS window: `Sec-CH-UA` header is consistent across versions.");
+
+ promise_test(t => {
+ return grabSECCHUAHeader(t).then(header => {
+ assert_true(header.split(", ").every((brand) => {
+ let brandEnd = brand.indexOf(";v=");
+ assert_true(brandEnd !== -1,
+ "A well-formed Sec-CH-UA header must have version (v=) params");
+ /* 32 + 2, becuase of the extra quotes padding the brand,
+ e.g. '"lol";v=22"' */
+ return brandEnd < 34;
+ }));
+ });
+ }, "Open HTTPS window: No brand in `Sec-CH-UA` header is > than 32 chars.");
+</script>
+</head>
diff --git a/testing/web-platform/tests/client-hints/service-workers/critical-ch/echo-hint-in-html.py b/testing/web-platform/tests/client-hints/service-workers/critical-ch/echo-hint-in-html.py
new file mode 100644
index 0000000000..87a1afed7c
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/service-workers/critical-ch/echo-hint-in-html.py
@@ -0,0 +1,24 @@
+import sys
+
+def main(request, response):
+ """
+ Simple handler that sets a response header based on which client hint
+ request headers were received.
+ """
+
+ response.headers.append(b"Content-Type", b"text/html; charset=UTF-8")
+ response.headers.append(b"Access-Control-Allow-Origin", b"*")
+ response.headers.append(b"Access-Control-Allow-Headers", b"*")
+ response.headers.append(b"Access-Control-Expose-Headers", b"*")
+
+ response.headers.append(b"Cache-Control", b"no-store")
+
+ response.headers.append(b"Accept-CH", b"sec-ch-device-memory,device-memory");
+ response.headers.append(b"Critical-CH", b"sec-ch-device-memory,device-memory");
+
+ result = "FAIL"
+
+ if b"sec-ch-device-memory" in request.headers and b"device-memory" in request.headers:
+ result = "PASS"
+
+ response.content = result
diff --git a/testing/web-platform/tests/client-hints/service-workers/critical-ch/foo.html b/testing/web-platform/tests/client-hints/service-workers/critical-ch/foo.html
new file mode 100644
index 0000000000..ba578e48b1
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/service-workers/critical-ch/foo.html
@@ -0,0 +1 @@
+BAR
diff --git a/testing/web-platform/tests/client-hints/service-workers/critical-ch/foo.html.headers b/testing/web-platform/tests/client-hints/service-workers/critical-ch/foo.html.headers
new file mode 100644
index 0000000000..56b489178e
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/service-workers/critical-ch/foo.html.headers
@@ -0,0 +1,2 @@
+Accept-CH: sec-ch-device-memory, device-memory
+Critical-CH: sec-ch-device-memory, device-memory
diff --git a/testing/web-platform/tests/client-hints/service-workers/critical-ch/intercept-request.js b/testing/web-platform/tests/client-hints/service-workers/critical-ch/intercept-request.js
new file mode 100644
index 0000000000..4d27ceadcc
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/service-workers/critical-ch/intercept-request.js
@@ -0,0 +1,6 @@
+self.addEventListener('fetch', (event) => {
+ result="FAIL";
+ if(event.request.headers.has("sec-ch-device-memory") && event.request.headers.has("device-memory"))
+ result="PASS";
+ event.respondWith(new Response(result));
+});
diff --git a/testing/web-platform/tests/client-hints/service-workers/critical-ch/navigation-preload.js b/testing/web-platform/tests/client-hints/service-workers/critical-ch/navigation-preload.js
new file mode 100644
index 0000000000..d8a38ee7de
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/service-workers/critical-ch/navigation-preload.js
@@ -0,0 +1,2 @@
+self.addEventListener('activate', () => self.registration.navigationPreload.enable());
+self.addEventListener('fetch', (event) => event.respondWith(event.preloadResponse));
diff --git a/testing/web-platform/tests/client-hints/service-workers/critical-ch/new-request.js b/testing/web-platform/tests/client-hints/service-workers/critical-ch/new-request.js
new file mode 100644
index 0000000000..395c77183e
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/service-workers/critical-ch/new-request.js
@@ -0,0 +1,3 @@
+self.addEventListener('fetch', (event) => {
+ event.respondWith(fetch("/client-hints/service-workers/resources/echo-hint-in-html.py"))
+});
diff --git a/testing/web-platform/tests/client-hints/service-workers/critical-ch/passthrough-request.js b/testing/web-platform/tests/client-hints/service-workers/critical-ch/passthrough-request.js
new file mode 100644
index 0000000000..5541c5eb55
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/service-workers/critical-ch/passthrough-request.js
@@ -0,0 +1 @@
+self.addEventListener('fetch', (event) => fetch(event.request));
diff --git a/testing/web-platform/tests/client-hints/service-workers/intercept-request-critical.https.window.js b/testing/web-platform/tests/client-hints/service-workers/intercept-request-critical.https.window.js
new file mode 100644
index 0000000000..079b9fd9ba
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/service-workers/intercept-request-critical.https.window.js
@@ -0,0 +1,5 @@
+// META: script=/service-workers/service-worker/resources/test-helpers.sub.js
+// META: script=resources/util.js
+promise_test((t) =>
+ ch_sw_test(t, 'critical-ch/intercept-request.js', 'critical-ch/foo.html', 'FAIL'),
+ "Service workers succsefully receives hints from request");
diff --git a/testing/web-platform/tests/client-hints/service-workers/intercept-request.https.html b/testing/web-platform/tests/client-hints/service-workers/intercept-request.https.html
new file mode 100644
index 0000000000..00e024b7b6
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/service-workers/intercept-request.https.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<title>Client Hint/Service Worker interaction</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script src="resources/util.js"></script>
+<script>
+promise_test((t) =>
+ ch_sw_test(t, 'resources/intercept-request.js', 'resources/foo.html', 'PASS'),
+ "Service workers succsefully receives hints from request");
+</script>
diff --git a/testing/web-platform/tests/client-hints/service-workers/intercept-request.https.html.headers b/testing/web-platform/tests/client-hints/service-workers/intercept-request.https.html.headers
new file mode 100644
index 0000000000..546ac83d4b
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/service-workers/intercept-request.https.html.headers
@@ -0,0 +1 @@
+Accept-CH: sec-ch-device-memory, device-memory
diff --git a/testing/web-platform/tests/client-hints/service-workers/navigation-preload-critical.https.window.js b/testing/web-platform/tests/client-hints/service-workers/navigation-preload-critical.https.window.js
new file mode 100644
index 0000000000..5d05ab3c53
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/service-workers/navigation-preload-critical.https.window.js
@@ -0,0 +1,5 @@
+//META: script=/service-workers/service-worker/resources/test-helpers.sub.js
+//META: script=resources/util.js
+promise_test((t) =>
+ ch_sw_test(t, 'critical-ch/navigation-preload.js', 'critical-ch/echo-hint-in-html.py', 'PASS'),
+ "Service worker successfully passes hints through to new fetch");
diff --git a/testing/web-platform/tests/client-hints/service-workers/navigation-preload.https.html b/testing/web-platform/tests/client-hints/service-workers/navigation-preload.https.html
new file mode 100644
index 0000000000..a952e1e406
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/service-workers/navigation-preload.https.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<title>Client Hint/Service Worker interaction</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script src="resources/util.js"></script>
+<script>
+promise_test((t) =>
+ ch_sw_test(t, 'resources/navigation-preload.js', 'resources/echo-hint-in-html.py', 'PASS'),
+ "Service worker successfully passes hints through to new fetch");
+</script>
diff --git a/testing/web-platform/tests/client-hints/service-workers/navigation-preload.https.html.headers b/testing/web-platform/tests/client-hints/service-workers/navigation-preload.https.html.headers
new file mode 100644
index 0000000000..546ac83d4b
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/service-workers/navigation-preload.https.html.headers
@@ -0,0 +1 @@
+Accept-CH: sec-ch-device-memory, device-memory
diff --git a/testing/web-platform/tests/client-hints/service-workers/new-request-critical.https.window.js b/testing/web-platform/tests/client-hints/service-workers/new-request-critical.https.window.js
new file mode 100644
index 0000000000..9dee11a934
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/service-workers/new-request-critical.https.window.js
@@ -0,0 +1,5 @@
+//META: script=/service-workers/service-worker/resources/test-helpers.sub.js
+//META: script=resources/util.js
+promise_test((t) =>
+ ch_sw_test(t, 'critical-ch/new-request.js', 'critical-ch/foo.html', 'FAIL'),
+ "Service worker does NOT generate client hints in a new request");
diff --git a/testing/web-platform/tests/client-hints/service-workers/new-request.https.html b/testing/web-platform/tests/client-hints/service-workers/new-request.https.html
new file mode 100644
index 0000000000..006ec24e8b
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/service-workers/new-request.https.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<title>Client Hint/Service Worker interaction</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script src="resources/util.js"></script>
+<script>
+promise_test((t) =>
+ ch_sw_test(t, 'resources/new-request.js', 'resources/foo.html', 'FAIL'),
+ "Service worker does NOT generate client hints in a new request");
+</script>
diff --git a/testing/web-platform/tests/client-hints/service-workers/new-request.https.html.headers b/testing/web-platform/tests/client-hints/service-workers/new-request.https.html.headers
new file mode 100644
index 0000000000..546ac83d4b
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/service-workers/new-request.https.html.headers
@@ -0,0 +1 @@
+Accept-CH: sec-ch-device-memory, device-memory
diff --git a/testing/web-platform/tests/client-hints/service-workers/passthrough-request-critical.https.window.js b/testing/web-platform/tests/client-hints/service-workers/passthrough-request-critical.https.window.js
new file mode 100644
index 0000000000..4d59f5f478
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/service-workers/passthrough-request-critical.https.window.js
@@ -0,0 +1,5 @@
+//META: script=/service-workers/service-worker/resources/test-helpers.sub.js
+//META: script=resources/util.js
+promise_test((t) =>
+ ch_sw_test(t, 'critical-ch/passthrough-request.js', 'critical-ch/echo-hint-in-html.py', 'PASS'),
+ "Service worker successfully passes hints through to new fetch");
diff --git a/testing/web-platform/tests/client-hints/service-workers/passthrough-request.https.html b/testing/web-platform/tests/client-hints/service-workers/passthrough-request.https.html
new file mode 100644
index 0000000000..8dca424098
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/service-workers/passthrough-request.https.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<title>Client Hint/Service Worker interaction</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script src="resources/util.js"></script>
+<script>
+promise_test((t) =>
+ ch_sw_test(t, 'resources/passthrough-request.js', 'resources/echo-hint-in-html.py', 'PASS'),
+ "Service worker successfully passes hints through to new fetch");
+</script>
diff --git a/testing/web-platform/tests/client-hints/service-workers/passthrough-request.https.html.headers b/testing/web-platform/tests/client-hints/service-workers/passthrough-request.https.html.headers
new file mode 100644
index 0000000000..546ac83d4b
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/service-workers/passthrough-request.https.html.headers
@@ -0,0 +1 @@
+Accept-CH: sec-ch-device-memory, device-memory
diff --git a/testing/web-platform/tests/client-hints/service-workers/resources/echo-hint-in-html.py b/testing/web-platform/tests/client-hints/service-workers/resources/echo-hint-in-html.py
new file mode 100644
index 0000000000..e029b5433b
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/service-workers/resources/echo-hint-in-html.py
@@ -0,0 +1,21 @@
+import sys
+
+def main(request, response):
+ """
+ Simple handler that sets a response header based on which client hint
+ request headers were received.
+ """
+
+ response.headers.append(b"Content-Type", b"text/html; charset=UTF-8")
+ response.headers.append(b"Access-Control-Allow-Origin", b"*")
+ response.headers.append(b"Access-Control-Allow-Headers", b"*")
+ response.headers.append(b"Access-Control-Expose-Headers", b"*")
+
+ response.headers.append(b"Cache-Control", b"no-store")
+
+ result = "FAIL"
+
+ if b"sec-ch-device-memory" in request.headers and b"device-memory" in request.headers:
+ result = "PASS"
+
+ response.content = result
diff --git a/testing/web-platform/tests/client-hints/service-workers/resources/foo.html b/testing/web-platform/tests/client-hints/service-workers/resources/foo.html
new file mode 100644
index 0000000000..ba578e48b1
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/service-workers/resources/foo.html
@@ -0,0 +1 @@
+BAR
diff --git a/testing/web-platform/tests/client-hints/service-workers/resources/intercept-request.js b/testing/web-platform/tests/client-hints/service-workers/resources/intercept-request.js
new file mode 100644
index 0000000000..4d27ceadcc
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/service-workers/resources/intercept-request.js
@@ -0,0 +1,6 @@
+self.addEventListener('fetch', (event) => {
+ result="FAIL";
+ if(event.request.headers.has("sec-ch-device-memory") && event.request.headers.has("device-memory"))
+ result="PASS";
+ event.respondWith(new Response(result));
+});
diff --git a/testing/web-platform/tests/client-hints/service-workers/resources/navigation-preload.js b/testing/web-platform/tests/client-hints/service-workers/resources/navigation-preload.js
new file mode 100644
index 0000000000..d8a38ee7de
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/service-workers/resources/navigation-preload.js
@@ -0,0 +1,2 @@
+self.addEventListener('activate', () => self.registration.navigationPreload.enable());
+self.addEventListener('fetch', (event) => event.respondWith(event.preloadResponse));
diff --git a/testing/web-platform/tests/client-hints/service-workers/resources/new-request.js b/testing/web-platform/tests/client-hints/service-workers/resources/new-request.js
new file mode 100644
index 0000000000..395c77183e
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/service-workers/resources/new-request.js
@@ -0,0 +1,3 @@
+self.addEventListener('fetch', (event) => {
+ event.respondWith(fetch("/client-hints/service-workers/resources/echo-hint-in-html.py"))
+});
diff --git a/testing/web-platform/tests/client-hints/service-workers/resources/passthrough-request.js b/testing/web-platform/tests/client-hints/service-workers/resources/passthrough-request.js
new file mode 100644
index 0000000000..5541c5eb55
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/service-workers/resources/passthrough-request.js
@@ -0,0 +1 @@
+self.addEventListener('fetch', (event) => fetch(event.request));
diff --git a/testing/web-platform/tests/client-hints/service-workers/resources/util.js b/testing/web-platform/tests/client-hints/service-workers/resources/util.js
new file mode 100644
index 0000000000..d9b8f3f4d9
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/service-workers/resources/util.js
@@ -0,0 +1,23 @@
+async function ch_sw_test(t, worker, url, response) {
+ r = await service_worker_unregister_and_register(t, worker, url);
+ await wait_for_state(t, r.installing, 'activated')
+ var popup_window = window.open("/common/blank.html");
+ assert_not_equals(popup_window, null, "Popup windows not allowed?");
+
+ t.add_cleanup(async _=>{
+ popup_window.close();
+ await r.unregister();
+ });
+
+ popup_load = new Promise((resolve, reject) => {
+ popup_window.addEventListener('load', t.step_func((e) => {
+ if(popup_window.location.pathname != "/blank.html") {
+ assert_equals(popup_window.document.body.textContent, response);
+ resolve();
+ }
+ }))
+ });
+
+ popup_window.location = url;
+ await popup_load;
+}
diff --git a/testing/web-platform/tests/client-hints/viewport-size/viewport-size-iframe.https.html b/testing/web-platform/tests/client-hints/viewport-size/viewport-size-iframe.https.html
new file mode 100644
index 0000000000..6cb549d8f1
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/viewport-size/viewport-size-iframe.https.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+promise_test(t => {
+ return new Promise(resolve => {
+ window.addEventListener("message", t.step_func(e => {
+ assert_equals(e.data.viewportWidth, window.innerWidth.toString());
+ assert_equals(e.data.viewportHeight, window.innerHeight.toString());
+ resolve();
+ }));
+ });
+});
+</script>
+<iframe src="../resources/viewport-frame.py" width=503 height=614></iframe>
diff --git a/testing/web-platform/tests/client-hints/viewport-size/viewport-size-iframe.https.html.headers b/testing/web-platform/tests/client-hints/viewport-size/viewport-size-iframe.https.html.headers
new file mode 100644
index 0000000000..76c3cb886c
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/viewport-size/viewport-size-iframe.https.html.headers
@@ -0,0 +1 @@
+Accept-CH: Sec-Ch-Viewport-Width,Sec-Ch-Viewport-Height
diff --git a/testing/web-platform/tests/client-hints/viewport-size/viewport-size-subresource.https.html b/testing/web-platform/tests/client-hints/viewport-size/viewport-size-subresource.https.html
new file mode 100644
index 0000000000..ec22a4bdf3
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/viewport-size/viewport-size-subresource.https.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+promise_test(t => {
+ return new Promise(resolve => {
+ window.addEventListener("message", t.step_func(e => {
+ assert_equals(e.data, "503,614");
+ resolve();
+ }));
+ });
+});
+</script>
+<iframe src="../resources/viewport-measurement.html" width=503 height=614>
diff --git a/testing/web-platform/tests/client-hints/viewport-size/viewport-size-subresource.https.html.headers b/testing/web-platform/tests/client-hints/viewport-size/viewport-size-subresource.https.html.headers
new file mode 100644
index 0000000000..76c3cb886c
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/viewport-size/viewport-size-subresource.https.html.headers
@@ -0,0 +1 @@
+Accept-CH: Sec-Ch-Viewport-Width,Sec-Ch-Viewport-Height
diff --git a/testing/web-platform/tests/client-hints/viewport-size/viewport-size-window-different-dimensions.https.html b/testing/web-platform/tests/client-hints/viewport-size/viewport-size-window-different-dimensions.https.html
new file mode 100644
index 0000000000..242f68e3fa
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/viewport-size/viewport-size-window-different-dimensions.https.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+promise_test(t => {
+ return new Promise(resolve => {
+ window.addEventListener("message", t.step_func(e => {
+ assert_equals(e.data.viewportWidth, "503");
+ assert_equals(e.data.viewportHeight, "614");
+ resolve();
+ }));
+ });
+});
+window.open("../resources/viewport-frame.py", "", "width=503,height=614");
+</script>
diff --git a/testing/web-platform/tests/client-hints/viewport-size/viewport-size-window-different-dimensions.https.html.headers b/testing/web-platform/tests/client-hints/viewport-size/viewport-size-window-different-dimensions.https.html.headers
new file mode 100644
index 0000000000..76c3cb886c
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/viewport-size/viewport-size-window-different-dimensions.https.html.headers
@@ -0,0 +1 @@
+Accept-CH: Sec-Ch-Viewport-Width,Sec-Ch-Viewport-Height
diff --git a/testing/web-platform/tests/client-hints/viewport-size/viewport-size-window.https.html b/testing/web-platform/tests/client-hints/viewport-size/viewport-size-window.https.html
new file mode 100644
index 0000000000..0de61362b3
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/viewport-size/viewport-size-window.https.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+promise_test(t => {
+ return new Promise(resolve => {
+ window.addEventListener("message", t.step_func(e => {
+ assert_equals(e.data.viewportWidth, window.innerWidth.toString());
+ assert_equals(e.data.viewportHeight, window.innerHeight.toString());
+ resolve();
+ }));
+ });
+});
+window.open("../resources/viewport-frame.py", "");
+</script>
diff --git a/testing/web-platform/tests/client-hints/viewport-size/viewport-size-window.https.html.headers b/testing/web-platform/tests/client-hints/viewport-size/viewport-size-window.https.html.headers
new file mode 100644
index 0000000000..76c3cb886c
--- /dev/null
+++ b/testing/web-platform/tests/client-hints/viewport-size/viewport-size-window.https.html.headers
@@ -0,0 +1 @@
+Accept-CH: Sec-Ch-Viewport-Width,Sec-Ch-Viewport-Height