summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/loading/early-hints
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/loading/early-hints
parentInitial commit. (diff)
downloadfirefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz
firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/loading/early-hints')
-rw-r--r--testing/web-platform/tests/loading/early-hints/404-with-early-hints.h2.window.js10
-rw-r--r--testing/web-platform/tests/loading/early-hints/arbitrary-header-in-early-hints.h2.window.js4
-rw-r--r--testing/web-platform/tests/loading/early-hints/coep-early-hints-none-final-require-corp.h2.window.js9
-rw-r--r--testing/web-platform/tests/loading/early-hints/coep-early-hints-require-corp-final-none.h2.window.js9
-rw-r--r--testing/web-platform/tests/loading/early-hints/csp-early-hints-absent-final-absent.h2.window.js8
-rw-r--r--testing/web-platform/tests/loading/early-hints/csp-early-hints-absent-final-allowed.h2.window.js8
-rw-r--r--testing/web-platform/tests/loading/early-hints/csp-early-hints-absent-final-disallowed.h2.window.js8
-rw-r--r--testing/web-platform/tests/loading/early-hints/csp-early-hints-allowed-final-absent.h2.window.js8
-rw-r--r--testing/web-platform/tests/loading/early-hints/csp-early-hints-allowed-final-allowed.h2.window.js8
-rw-r--r--testing/web-platform/tests/loading/early-hints/csp-early-hints-allowed-final-disallowed.h2.window.js8
-rw-r--r--testing/web-platform/tests/loading/early-hints/csp-early-hints-disallowed-final-absent.h2.window.js8
-rw-r--r--testing/web-platform/tests/loading/early-hints/csp-early-hints-disallowed-final-allowed.h2.window.js8
-rw-r--r--testing/web-platform/tests/loading/early-hints/csp-early-hints-disallowed-final-disallowed.h2.window.js8
-rw-r--r--testing/web-platform/tests/loading/early-hints/embed-object.h2.window.js44
-rw-r--r--testing/web-platform/tests/loading/early-hints/iframe-coep-disallow.h2.html35
-rw-r--r--testing/web-platform/tests/loading/early-hints/iframe-coep-disallow.h2.html.headers1
-rw-r--r--testing/web-platform/tests/loading/early-hints/iframe-pdf.h2.window.js31
-rw-r--r--testing/web-platform/tests/loading/early-hints/iframe-x-frame-options-deny.h2.window.js27
-rw-r--r--testing/web-platform/tests/loading/early-hints/invalid-headers-in-early-hints.h2.window.js22
-rw-r--r--testing/web-platform/tests/loading/early-hints/modulepreload-in-early-hints.h2.window.js10
-rw-r--r--testing/web-platform/tests/loading/early-hints/multiple-early-hints-responses.h2.window.js11
-rw-r--r--testing/web-platform/tests/loading/early-hints/preconnect-in-early-hints.h2.window.js12
-rw-r--r--testing/web-platform/tests/loading/early-hints/preload-finished-before-final-response.h2.window.js11
-rw-r--r--testing/web-platform/tests/loading/early-hints/preload-finished-while-receiving-final-response-body.h2.window.js11
-rw-r--r--testing/web-platform/tests/loading/early-hints/preload-in-flight-when-consumed.h2.window.js11
-rw-r--r--testing/web-platform/tests/loading/early-hints/preload-initiator-type.h2.window.js9
-rw-r--r--testing/web-platform/tests/loading/early-hints/preload-with-csp-document-disallow.h2.window.js7
-rw-r--r--testing/web-platform/tests/loading/early-hints/preload-with-invalid-as.h2.window.js14
-rw-r--r--testing/web-platform/tests/loading/early-hints/preload-without-as.h2.window.js13
-rw-r--r--testing/web-platform/tests/loading/early-hints/preload-without-csp-document-disallow.h2.window.js7
-rw-r--r--testing/web-platform/tests/loading/early-hints/redirect-cross-origin-between-early-hints.h2.window.js14
-rw-r--r--testing/web-platform/tests/loading/early-hints/redirect-cross-origin.h2.window.js10
-rw-r--r--testing/web-platform/tests/loading/early-hints/redirect-same-origin-between-early-hints.h2.window.js14
-rw-r--r--testing/web-platform/tests/loading/early-hints/redirect-same-origin.h2.window.js10
-rw-r--r--testing/web-platform/tests/loading/early-hints/referrer-policy-no-referrer.h2.window.js4
-rw-r--r--testing/web-platform/tests/loading/early-hints/referrer-policy-origin-when-cross-origin.h2.window.js4
-rw-r--r--testing/web-platform/tests/loading/early-hints/referrer-policy-origin.h2.window.js4
-rw-r--r--testing/web-platform/tests/loading/early-hints/referrer-policy-same-origin.h2.window.js4
-rw-r--r--testing/web-platform/tests/loading/early-hints/referrer-policy-unsafe-url.h2.window.js4
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/404-with-early-hints.h2.py24
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/404-with-early-hints.html15
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/arbitrary-header-in-early-hints.h2.py24
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/arbitrary-header-in-early-hints.html11
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/coep-mismatch.h2.py30
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/coep-mismatch.html24
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/csp-basic-loader.h2.py47
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/csp-basic.html37
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/csp-document-disallow-loader.h2.py39
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/csp-document-disallow.html26
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/delayed-js.h2.py16
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/early-hints-helpers.sub.js180
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/early-hints-test-loader.h2.py45
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/empty-corp-absent.js1
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/empty-corp-absent.js.headers1
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/empty.js1
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/empty.js.headers4
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/example.pdf50
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/fetch-and-record-js.h2.py16
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/get-fetch-timing-and-headers.h2.py12
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/html-with-early-hints.h2.py21
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/invalid-headers-in-early-hints.h2.py20
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/modulepreload-in-early-hints.h2.py24
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/modulepreload-in-early-hints.html26
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/multiple-early-hints-responses.h2.py39
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/multiple-early-hints-responses.html20
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/pdf-with-early-hints.h2.py26
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/png-with-early-hints.h2.py26
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/preconnect-in-early-hints.h2.py26
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/preconnect-in-early-hints.html22
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/preload-as-test.h2.py29
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/preload-as-test.html18
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/preload-finished-before-final-response.h2.py31
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/preload-finished-before-final-response.html15
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/preload-finished-while-receiving-final-response-body.h2.py31
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/preload-finished-while-receiving-final-response-body.html15
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/preload-in-flight-when-consumed.h2.py24
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/preload-in-flight-when-consumed.html19
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/preload-initiator-type.html22
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/redirect-between-early-hints.h2.py54
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/redirect-cross-origin-between-early-hints.html22
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/redirect-cross-origin.html15
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/redirect-same-origin-between-early-hints.html22
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/redirect-same-origin.html15
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/redirect-with-early-hints.h2.py20
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/referrer-policy-test-loader.h2.py39
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/referrer-policy-test.html63
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/resume-delayed-js.h2.py10
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/square.pngbin0 -> 18299 bytes
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/square.png.headers1
-rw-r--r--testing/web-platform/tests/loading/early-hints/resources/utils.py45
90 files changed, 1811 insertions, 0 deletions
diff --git a/testing/web-platform/tests/loading/early-hints/404-with-early-hints.h2.window.js b/testing/web-platform/tests/loading/early-hints/404-with-early-hints.h2.window.js
new file mode 100644
index 0000000000..f28208a30a
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/404-with-early-hints.h2.window.js
@@ -0,0 +1,10 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const params = new URLSearchParams();
+ params.set("resource-url",
+ SAME_ORIGIN_RESOURCES_URL + "/square.png?" + token());
+ const test_url = "resources/404-with-early-hints.h2.py?" + params.toString();
+ window.location.replace(new URL(test_url, window.location));
+});
diff --git a/testing/web-platform/tests/loading/early-hints/arbitrary-header-in-early-hints.h2.window.js b/testing/web-platform/tests/loading/early-hints/arbitrary-header-in-early-hints.h2.window.js
new file mode 100644
index 0000000000..f0e848ec10
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/arbitrary-header-in-early-hints.h2.window.js
@@ -0,0 +1,4 @@
+test(() => {
+ const test_url = "resources/arbitrary-header-in-early-hints.h2.py";
+ window.location.replace(new URL(test_url, window.location));
+});
diff --git a/testing/web-platform/tests/loading/early-hints/coep-early-hints-none-final-require-corp.h2.window.js b/testing/web-platform/tests/loading/early-hints/coep-early-hints-none-final-require-corp.h2.window.js
new file mode 100644
index 0000000000..ab135b5493
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/coep-early-hints-none-final-require-corp.h2.window.js
@@ -0,0 +1,9 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const early_hints_policy = "unsafe-none";
+ const final_policy = "require-corp";
+ navigateToCrossOriginEmbedderPolicyMismatchTest(early_hints_policy,
+ final_policy);
+});
diff --git a/testing/web-platform/tests/loading/early-hints/coep-early-hints-require-corp-final-none.h2.window.js b/testing/web-platform/tests/loading/early-hints/coep-early-hints-require-corp-final-none.h2.window.js
new file mode 100644
index 0000000000..68e20ba42f
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/coep-early-hints-require-corp-final-none.h2.window.js
@@ -0,0 +1,9 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const early_hints_policy = "require-corp";
+ const final_policy = "unsafe-none";
+ navigateToCrossOriginEmbedderPolicyMismatchTest(early_hints_policy,
+ final_policy);
+});
diff --git a/testing/web-platform/tests/loading/early-hints/csp-early-hints-absent-final-absent.h2.window.js b/testing/web-platform/tests/loading/early-hints/csp-early-hints-absent-final-absent.h2.window.js
new file mode 100644
index 0000000000..f61e268c8a
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/csp-early-hints-absent-final-absent.h2.window.js
@@ -0,0 +1,8 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const early_hints_policy = "absent";
+ const final_policy = "absent";
+ navigateToContentSecurityPolicyBasicTest(early_hints_policy, final_policy);
+});
diff --git a/testing/web-platform/tests/loading/early-hints/csp-early-hints-absent-final-allowed.h2.window.js b/testing/web-platform/tests/loading/early-hints/csp-early-hints-absent-final-allowed.h2.window.js
new file mode 100644
index 0000000000..0e1762a28c
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/csp-early-hints-absent-final-allowed.h2.window.js
@@ -0,0 +1,8 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const early_hints_policy = "absent";
+ const final_policy = "allowed";
+ navigateToContentSecurityPolicyBasicTest(early_hints_policy, final_policy);
+});
diff --git a/testing/web-platform/tests/loading/early-hints/csp-early-hints-absent-final-disallowed.h2.window.js b/testing/web-platform/tests/loading/early-hints/csp-early-hints-absent-final-disallowed.h2.window.js
new file mode 100644
index 0000000000..3fcd89c4cc
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/csp-early-hints-absent-final-disallowed.h2.window.js
@@ -0,0 +1,8 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const early_hints_policy = "absent";
+ const final_policy = "disallowed";
+ navigateToContentSecurityPolicyBasicTest(early_hints_policy, final_policy);
+});
diff --git a/testing/web-platform/tests/loading/early-hints/csp-early-hints-allowed-final-absent.h2.window.js b/testing/web-platform/tests/loading/early-hints/csp-early-hints-allowed-final-absent.h2.window.js
new file mode 100644
index 0000000000..15128ce5d9
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/csp-early-hints-allowed-final-absent.h2.window.js
@@ -0,0 +1,8 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const early_hints_policy = "allowed";
+ const final_policy = "absent";
+ navigateToContentSecurityPolicyBasicTest(early_hints_policy, final_policy);
+});
diff --git a/testing/web-platform/tests/loading/early-hints/csp-early-hints-allowed-final-allowed.h2.window.js b/testing/web-platform/tests/loading/early-hints/csp-early-hints-allowed-final-allowed.h2.window.js
new file mode 100644
index 0000000000..ee51e78bf1
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/csp-early-hints-allowed-final-allowed.h2.window.js
@@ -0,0 +1,8 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const early_hints_policy = "allowed";
+ const final_policy = "allowed";
+ navigateToContentSecurityPolicyBasicTest(early_hints_policy, final_policy);
+});
diff --git a/testing/web-platform/tests/loading/early-hints/csp-early-hints-allowed-final-disallowed.h2.window.js b/testing/web-platform/tests/loading/early-hints/csp-early-hints-allowed-final-disallowed.h2.window.js
new file mode 100644
index 0000000000..67b39333ad
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/csp-early-hints-allowed-final-disallowed.h2.window.js
@@ -0,0 +1,8 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const early_hints_policy = "allowed";
+ const final_policy = "disallowed";
+ navigateToContentSecurityPolicyBasicTest(early_hints_policy, final_policy);
+});
diff --git a/testing/web-platform/tests/loading/early-hints/csp-early-hints-disallowed-final-absent.h2.window.js b/testing/web-platform/tests/loading/early-hints/csp-early-hints-disallowed-final-absent.h2.window.js
new file mode 100644
index 0000000000..80b563dd88
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/csp-early-hints-disallowed-final-absent.h2.window.js
@@ -0,0 +1,8 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const early_hints_policy = "disallowed";
+ const final_policy = "absent";
+ navigateToContentSecurityPolicyBasicTest(early_hints_policy, final_policy);
+});
diff --git a/testing/web-platform/tests/loading/early-hints/csp-early-hints-disallowed-final-allowed.h2.window.js b/testing/web-platform/tests/loading/early-hints/csp-early-hints-disallowed-final-allowed.h2.window.js
new file mode 100644
index 0000000000..cfac9b0b98
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/csp-early-hints-disallowed-final-allowed.h2.window.js
@@ -0,0 +1,8 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const early_hints_policy = "disallowed";
+ const final_policy = "allowed";
+ navigateToContentSecurityPolicyBasicTest(early_hints_policy, final_policy);
+});
diff --git a/testing/web-platform/tests/loading/early-hints/csp-early-hints-disallowed-final-disallowed.h2.window.js b/testing/web-platform/tests/loading/early-hints/csp-early-hints-disallowed-final-disallowed.h2.window.js
new file mode 100644
index 0000000000..c8a7ca346f
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/csp-early-hints-disallowed-final-disallowed.h2.window.js
@@ -0,0 +1,8 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const early_hints_policy = "disallowed";
+ const final_policy = "disallowed";
+ navigateToContentSecurityPolicyBasicTest(early_hints_policy, final_policy);
+});
diff --git a/testing/web-platform/tests/loading/early-hints/embed-object.h2.window.js b/testing/web-platform/tests/loading/early-hints/embed-object.h2.window.js
new file mode 100644
index 0000000000..82365fafdd
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/embed-object.h2.window.js
@@ -0,0 +1,44 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+promise_test(async (t) => {
+ const resource_url = SAME_ORIGIN_RESOURCES_URL + "/empty.js?" + token();
+ const promise = new Promise((resolve) => {
+ const params = new URLSearchParams();
+ params.set("resource-url", resource_url);
+ params.set("token", token());
+ const embed_url = SAME_ORIGIN_RESOURCES_URL + "/png-with-early-hints.h2.py?" + params.toString();
+
+ const el = document.createElement("embed");
+ el.src = embed_url;
+ el.onload = resolve;
+ document.body.appendChild(el);
+ });
+ await promise;
+
+ await fetchScript(resource_url);
+ const entries = performance.getEntriesByName(resource_url);
+ assert_equals(entries.length, 1);
+ assert_not_equals(entries[0].transferSize, 0);
+}, "Early hints for an embed element should be ignored.");
+
+promise_test(async (t) => {
+ const resource_url = SAME_ORIGIN_RESOURCES_URL + "/empty.js?" + token();
+ const promise = new Promise((resolve) => {
+ const params = new URLSearchParams();
+ params.set("resource-url", resource_url);
+ params.set("token", token());
+ const object_url = SAME_ORIGIN_RESOURCES_URL + "/png-with-early-hints.h2.py?" + params.toString();
+
+ const el = document.createElement("object");
+ el.data = object_url;
+ el.onload = resolve;
+ document.body.appendChild(el);
+ });
+ await promise;
+
+ await fetchScript(resource_url);
+ const entries = performance.getEntriesByName(resource_url);
+ assert_equals(entries.length, 1);
+ assert_not_equals(entries[0].transferSize, 0);
+}, "Early hints for an object element should be ignored.");
diff --git a/testing/web-platform/tests/loading/early-hints/iframe-coep-disallow.h2.html b/testing/web-platform/tests/loading/early-hints/iframe-coep-disallow.h2.html
new file mode 100644
index 0000000000..d2efc0fefe
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/iframe-coep-disallow.h2.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<head>
+<meta charset="utf-8">
+<script src="/common/utils.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/early-hints-helpers.sub.js"></script>
+</head>
+<body>
+<script>
+promise_test(async (t) => {
+ const iframe = document.createElement("iframe");
+
+ const resource_url = CROSS_ORIGIN_RESOURCES_URL + "/empty.js?" + token();
+ const params = new URLSearchParams();
+ params.set("resource-url", resource_url);
+ params.set("token", token());
+ const iframe_url = CROSS_ORIGIN_RESOURCES_URL + "/html-with-early-hints.h2.py?" + params.toString();
+
+ iframe.src = iframe_url;
+ document.body.appendChild(iframe);
+ // Make sure the iframe didn't load. See https://github.com/whatwg/html/issues/125 for why a
+ // timeout is used here. Long term all network error handling should be similar and have a
+ // reliable event.
+ assert_equals(iframe.contentDocument.body.localName, "body");
+ await t.step_wait(() => iframe.contentDocument === null);
+
+ // Fetch the hinted resource and make sure it's not preloaded.
+ await fetchScript(resource_url);
+ const entries = performance.getEntriesByName(resource_url);
+ assert_equals(entries.length, 1);
+ assert_not_equals(entries[0].transferSize, 0);
+}, "Early hints for an iframe that violates Cross-Origin-Embedder-Policy should be ignored.");
+</script>
+</body>
diff --git a/testing/web-platform/tests/loading/early-hints/iframe-coep-disallow.h2.html.headers b/testing/web-platform/tests/loading/early-hints/iframe-coep-disallow.h2.html.headers
new file mode 100644
index 0000000000..6604450991
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/iframe-coep-disallow.h2.html.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/loading/early-hints/iframe-pdf.h2.window.js b/testing/web-platform/tests/loading/early-hints/iframe-pdf.h2.window.js
new file mode 100644
index 0000000000..11c1443c34
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/iframe-pdf.h2.window.js
@@ -0,0 +1,31 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+promise_test(async (t) => {
+ if (!navigator.pdfViewerEnabled) {
+ return;
+ }
+
+ const iframe = document.createElement("iframe");
+ const resource_url = SAME_ORIGIN_RESOURCES_URL + "/empty.js?" + token();
+ const promise = new Promise((resolve) => {
+ const params = new URLSearchParams();
+ params.set("resource-url", resource_url);
+ params.set("token", token());
+ const iframe_url = SAME_ORIGIN_RESOURCES_URL + "/pdf-with-early-hints.h2.py?" + params.toString();
+
+ iframe.src = iframe_url;
+ iframe.onload = resolve;
+ document.body.appendChild(iframe);
+ });
+ await promise;
+
+ // `iframe` should not preload the hinted resource.
+ const iframe_entries = iframe.contentWindow.performance.getEntriesByName(resource_url);
+ assert_equals(iframe_entries.length, 0);
+
+ await fetchScript(resource_url);
+ const entries = performance.getEntriesByName(resource_url);
+ assert_equals(entries.length, 1);
+ assert_not_equals(entries[0].transferSize, 0);
+}, "Early hints for an iframe of which content is pdf should be ignored.");
diff --git a/testing/web-platform/tests/loading/early-hints/iframe-x-frame-options-deny.h2.window.js b/testing/web-platform/tests/loading/early-hints/iframe-x-frame-options-deny.h2.window.js
new file mode 100644
index 0000000000..b3a114f7c6
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/iframe-x-frame-options-deny.h2.window.js
@@ -0,0 +1,27 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+promise_test(async (t) => {
+ const iframe = document.createElement("iframe");
+
+ const resource_url = SAME_ORIGIN_RESOURCES_URL + "/empty.js?" + token();
+ const params = new URLSearchParams();
+ params.set("resource-url", resource_url);
+ params.set("token", token());
+ params.set("x-frame-options", "DENY");
+ const iframe_url = SAME_ORIGIN_RESOURCES_URL + "/html-with-early-hints.h2.py?" + params.toString();
+
+ iframe.src = iframe_url;
+ document.body.appendChild(iframe);
+ // Make sure the iframe didn't load. See https://github.com/whatwg/html/issues/125 for why a
+ // timeout is used here. Long term all network error handling should be similar and have a
+ // reliable event.
+ assert_equals(iframe.contentDocument.body.localName, "body");
+ await t.step_wait(() => iframe.contentDocument === null);
+
+ // Fetch the hinted resource and make sure it's not preloaded.
+ await fetchScript(resource_url);
+ const entries = performance.getEntriesByName(resource_url);
+ assert_equals(entries.length, 1);
+ assert_not_equals(entries[0].transferSize, 0);
+}, "Early hints for an iframe that violates X-Frame-Options should be ignored.");
diff --git a/testing/web-platform/tests/loading/early-hints/invalid-headers-in-early-hints.h2.window.js b/testing/web-platform/tests/loading/early-hints/invalid-headers-in-early-hints.h2.window.js
new file mode 100644
index 0000000000..55181877df
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/invalid-headers-in-early-hints.h2.window.js
@@ -0,0 +1,22 @@
+async function testInvalidHeader(t, header_value) {
+ const params = new URLSearchParams();
+ params.set("header-value", header_value);
+ const test_url = "resources/invalid-headers-in-early-hints.h2.py?" + params.toString();
+ const opened_window = window.open(test_url, "invalid-header-in-early-hints");
+
+ // Use step_timeout() because neither "load" event nor postMessage() would
+ // work. Opening the test page should result in a network protocol error and
+ // accessing the document of the opened window should throw a SecurityError.
+ await new Promise(resolve => t.step_timeout(resolve, 1000));
+ assert_throws_dom("SecurityError", () => {
+ opened_window.document;
+ }, "window.open() should not load the test page successfully.");
+}
+
+promise_test(async (t) => {
+ await testInvalidHeader(t, "foo\r\nbar");
+}, "Early Hints contains invalid header: newline byte");
+
+promise_test(async (t) => {
+ await testInvalidHeader(t, "foo\x00bar");
+}, "Early Hints contains invalid header: nul byte");
diff --git a/testing/web-platform/tests/loading/early-hints/modulepreload-in-early-hints.h2.window.js b/testing/web-platform/tests/loading/early-hints/modulepreload-in-early-hints.h2.window.js
new file mode 100644
index 0000000000..5627f04a0f
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/modulepreload-in-early-hints.h2.window.js
@@ -0,0 +1,10 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const params = new URLSearchParams();
+ params.set("resource-url",
+ SAME_ORIGIN_RESOURCES_URL + "/empty.js?" + token());
+ const test_url = "resources/modulepreload-in-early-hints.h2.py?" + params.toString();
+ window.location.replace(new URL(test_url, window.location));
+});
diff --git a/testing/web-platform/tests/loading/early-hints/multiple-early-hints-responses.h2.window.js b/testing/web-platform/tests/loading/early-hints/multiple-early-hints-responses.h2.window.js
new file mode 100644
index 0000000000..2aba051ead
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/multiple-early-hints-responses.h2.window.js
@@ -0,0 +1,11 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const params = new URLSearchParams();
+ params.set("first-preload", SAME_ORIGIN_RESOURCES_URL + "/empty.js?" + token());
+ params.set("second-preload", CROSS_ORIGIN_RESOURCES_URL + "/empty.js?" + token());
+ params.set("second-preload-origin", CROSS_ORIGIN);
+ const test_url = "resources/multiple-early-hints-responses.h2.py?" + params.toString();
+ window.location.replace(new URL(test_url, window.location));
+}); \ No newline at end of file
diff --git a/testing/web-platform/tests/loading/early-hints/preconnect-in-early-hints.h2.window.js b/testing/web-platform/tests/loading/early-hints/preconnect-in-early-hints.h2.window.js
new file mode 100644
index 0000000000..821d0e40a8
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/preconnect-in-early-hints.h2.window.js
@@ -0,0 +1,12 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const resource_origin = CROSS_ORIGIN;
+ const resource_url = CROSS_ORIGIN + RESOURCES_PATH + "/empty.js?" + token();
+ const params = new URLSearchParams();
+ params.set("resource-origin", resource_origin);
+ params.set("resource-url", resource_url);
+ const test_url = "resources/preconnect-in-early-hints.h2.py?" + params.toString();
+ window.location.replace(new URL(test_url, window.location));
+});
diff --git a/testing/web-platform/tests/loading/early-hints/preload-finished-before-final-response.h2.window.js b/testing/web-platform/tests/loading/early-hints/preload-finished-before-final-response.h2.window.js
new file mode 100644
index 0000000000..c63239be1f
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/preload-finished-before-final-response.h2.window.js
@@ -0,0 +1,11 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const params = new URLSearchParams();
+ const id = token();
+ params.set("resource-url", SAME_ORIGIN_RESOURCES_URL + "/fetch-and-record-js.h2.py?id=" + id);
+ params.set("resource-id", id);
+ const test_url = "resources/preload-finished-before-final-response.h2.py?" + params.toString();
+ window.location.replace(new URL(test_url, window.location));
+}); \ No newline at end of file
diff --git a/testing/web-platform/tests/loading/early-hints/preload-finished-while-receiving-final-response-body.h2.window.js b/testing/web-platform/tests/loading/early-hints/preload-finished-while-receiving-final-response-body.h2.window.js
new file mode 100644
index 0000000000..bb03902bd1
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/preload-finished-while-receiving-final-response-body.h2.window.js
@@ -0,0 +1,11 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const params = new URLSearchParams();
+ const id = token();
+ params.set("resource-url", SAME_ORIGIN_RESOURCES_URL + "/fetch-and-record-js.h2.py?id=" + id);
+ params.set("resource-id", id);
+ const test_url = "resources/preload-finished-while-receiving-final-response-body.h2.py?" + params.toString();
+ window.location.replace(new URL(test_url, window.location));
+}); \ No newline at end of file
diff --git a/testing/web-platform/tests/loading/early-hints/preload-in-flight-when-consumed.h2.window.js b/testing/web-platform/tests/loading/early-hints/preload-in-flight-when-consumed.h2.window.js
new file mode 100644
index 0000000000..beaa45e2dd
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/preload-in-flight-when-consumed.h2.window.js
@@ -0,0 +1,11 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const params = new URLSearchParams();
+ const id = token();
+ params.set("resource-url", SAME_ORIGIN_RESOURCES_URL + "/delayed-js.h2.py?id=" + id);
+ params.set("resource-id", id);
+ const test_url = "resources/preload-in-flight-when-consumed.h2.py?" + params.toString();
+ window.location.replace(new URL(test_url, window.location));
+}); \ No newline at end of file
diff --git a/testing/web-platform/tests/loading/early-hints/preload-initiator-type.h2.window.js b/testing/web-platform/tests/loading/early-hints/preload-initiator-type.h2.window.js
new file mode 100644
index 0000000000..959aacef49
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/preload-initiator-type.h2.window.js
@@ -0,0 +1,9 @@
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const preloads = [{
+ "url": "empty.js?" + Date.now(),
+ "as_attr": "script",
+ }];
+ navigateToTestWithEarlyHints("resources/preload-initiator-type.html", preloads);
+}); \ No newline at end of file
diff --git a/testing/web-platform/tests/loading/early-hints/preload-with-csp-document-disallow.h2.window.js b/testing/web-platform/tests/loading/early-hints/preload-with-csp-document-disallow.h2.window.js
new file mode 100644
index 0000000000..87759b28a0
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/preload-with-csp-document-disallow.h2.window.js
@@ -0,0 +1,7 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const early_hints_policy = "allowed";
+ navigateToContentSecurityPolicyDocumentDisallowTest(early_hints_policy);
+});
diff --git a/testing/web-platform/tests/loading/early-hints/preload-with-invalid-as.h2.window.js b/testing/web-platform/tests/loading/early-hints/preload-with-invalid-as.h2.window.js
new file mode 100644
index 0000000000..bebd97887c
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/preload-with-invalid-as.h2.window.js
@@ -0,0 +1,14 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const params = new URLSearchParams();
+ params.set("description",
+ "An early hints preload with an invalid `as` attribute should be ignored.");
+ params.set("resource-url",
+ SAME_ORIGIN_RESOURCES_URL + "/empty.js?" + token());
+ params.set("as", "invalid");
+ params.set("should-preload", false);
+ const test_url = "resources/preload-as-test.h2.py?" + params.toString();
+ window.location.replace(new URL(test_url, window.location));
+});
diff --git a/testing/web-platform/tests/loading/early-hints/preload-without-as.h2.window.js b/testing/web-platform/tests/loading/early-hints/preload-without-as.h2.window.js
new file mode 100644
index 0000000000..9ac1621050
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/preload-without-as.h2.window.js
@@ -0,0 +1,13 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const params = new URLSearchParams();
+ params.set("description",
+ "An early hints preload without `as` attribute should be ignored.");
+ params.set("resource-url",
+ SAME_ORIGIN_RESOURCES_URL + "/empty.js?" + token());
+ params.set("should-preload", false);
+ const test_url = "resources/preload-as-test.h2.py?" + params.toString();
+ window.location.replace(new URL(test_url, window.location));
+});
diff --git a/testing/web-platform/tests/loading/early-hints/preload-without-csp-document-disallow.h2.window.js b/testing/web-platform/tests/loading/early-hints/preload-without-csp-document-disallow.h2.window.js
new file mode 100644
index 0000000000..3de55d04c3
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/preload-without-csp-document-disallow.h2.window.js
@@ -0,0 +1,7 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const early_hints_policy = "absent";
+ navigateToContentSecurityPolicyDocumentDisallowTest(early_hints_policy);
+});
diff --git a/testing/web-platform/tests/loading/early-hints/redirect-cross-origin-between-early-hints.h2.window.js b/testing/web-platform/tests/loading/early-hints/redirect-cross-origin-between-early-hints.h2.window.js
new file mode 100644
index 0000000000..2ca92cf82c
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/redirect-cross-origin-between-early-hints.h2.window.js
@@ -0,0 +1,14 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const params = new URLSearchParams();
+ params.set("preload-before-redirect", SAME_ORIGIN_RESOURCES_URL + "/empty.js?" + token());
+ params.set("preload-after-redirect", CROSS_ORIGIN_RESOURCES_URL + "/empty.js?" + token());
+ params.set("redirect-url", CROSS_ORIGIN_RESOURCES_URL + "/redirect-between-early-hints.h2.py");
+ params.set("final-test-page", "redirect-cross-origin-between-early-hints.html");
+
+ params.set("test-step", "redirect");
+ const test_url = "resources/redirect-between-early-hints.h2.py?" + params.toString();
+ window.location.replace(new URL(test_url, window.location));
+}); \ No newline at end of file
diff --git a/testing/web-platform/tests/loading/early-hints/redirect-cross-origin.h2.window.js b/testing/web-platform/tests/loading/early-hints/redirect-cross-origin.h2.window.js
new file mode 100644
index 0000000000..548759b19c
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/redirect-cross-origin.h2.window.js
@@ -0,0 +1,10 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const params = new URLSearchParams();
+ params.set("preload-url", SAME_ORIGIN_RESOURCES_URL + "/empty.js?" + token());
+ params.set("redirect-url", CROSS_ORIGIN_RESOURCES_URL + "/redirect-cross-origin.html");
+ const test_url = "resources/redirect-with-early-hints.h2.py?" + params.toString();
+ window.location.replace(new URL(test_url, window.location));
+});
diff --git a/testing/web-platform/tests/loading/early-hints/redirect-same-origin-between-early-hints.h2.window.js b/testing/web-platform/tests/loading/early-hints/redirect-same-origin-between-early-hints.h2.window.js
new file mode 100644
index 0000000000..276303608e
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/redirect-same-origin-between-early-hints.h2.window.js
@@ -0,0 +1,14 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const params = new URLSearchParams();
+ params.set("preload-before-redirect", SAME_ORIGIN_RESOURCES_URL + "/empty.js?" + token());
+ params.set("preload-after-redirect", SAME_ORIGIN_RESOURCES_URL + "/empty.js?" + token());
+ params.set("redirect-url", SAME_ORIGIN_RESOURCES_URL + "/redirect-between-early-hints.h2.py");
+ params.set("final-test-page", "redirect-same-origin-between-early-hints.html");
+
+ params.set("test-step", "redirect");
+ const test_url = "resources/redirect-between-early-hints.h2.py?" + params.toString();
+ window.location.replace(new URL(test_url, window.location));
+}); \ No newline at end of file
diff --git a/testing/web-platform/tests/loading/early-hints/redirect-same-origin.h2.window.js b/testing/web-platform/tests/loading/early-hints/redirect-same-origin.h2.window.js
new file mode 100644
index 0000000000..88d64f399a
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/redirect-same-origin.h2.window.js
@@ -0,0 +1,10 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => {
+ const params = new URLSearchParams();
+ params.set("preload-url", SAME_ORIGIN_RESOURCES_URL + "/empty.js?" + token());
+ params.set("redirect-url", SAME_ORIGIN_RESOURCES_URL + "/redirect-same-origin.html");
+ const test_url = "resources/redirect-with-early-hints.h2.py?" + params.toString();
+ window.location.replace(new URL(test_url, window.location));
+}); \ No newline at end of file
diff --git a/testing/web-platform/tests/loading/early-hints/referrer-policy-no-referrer.h2.window.js b/testing/web-platform/tests/loading/early-hints/referrer-policy-no-referrer.h2.window.js
new file mode 100644
index 0000000000..10f2c2c4bc
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/referrer-policy-no-referrer.h2.window.js
@@ -0,0 +1,4 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => testReferrerPolicy("no-referrer"));
diff --git a/testing/web-platform/tests/loading/early-hints/referrer-policy-origin-when-cross-origin.h2.window.js b/testing/web-platform/tests/loading/early-hints/referrer-policy-origin-when-cross-origin.h2.window.js
new file mode 100644
index 0000000000..250f350877
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/referrer-policy-origin-when-cross-origin.h2.window.js
@@ -0,0 +1,4 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => testReferrerPolicy("origin-when-cross-origin"));
diff --git a/testing/web-platform/tests/loading/early-hints/referrer-policy-origin.h2.window.js b/testing/web-platform/tests/loading/early-hints/referrer-policy-origin.h2.window.js
new file mode 100644
index 0000000000..3fbe9dff76
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/referrer-policy-origin.h2.window.js
@@ -0,0 +1,4 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => testReferrerPolicy("origin"));
diff --git a/testing/web-platform/tests/loading/early-hints/referrer-policy-same-origin.h2.window.js b/testing/web-platform/tests/loading/early-hints/referrer-policy-same-origin.h2.window.js
new file mode 100644
index 0000000000..4d24e1bf98
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/referrer-policy-same-origin.h2.window.js
@@ -0,0 +1,4 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => testReferrerPolicy("same-origin"));
diff --git a/testing/web-platform/tests/loading/early-hints/referrer-policy-unsafe-url.h2.window.js b/testing/web-platform/tests/loading/early-hints/referrer-policy-unsafe-url.h2.window.js
new file mode 100644
index 0000000000..a0c304cb10
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/referrer-policy-unsafe-url.h2.window.js
@@ -0,0 +1,4 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+test(() => testReferrerPolicy("unsafe-url"));
diff --git a/testing/web-platform/tests/loading/early-hints/resources/404-with-early-hints.h2.py b/testing/web-platform/tests/loading/early-hints/resources/404-with-early-hints.h2.py
new file mode 100644
index 0000000000..bb75bc0cbd
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/404-with-early-hints.h2.py
@@ -0,0 +1,24 @@
+import os
+
+
+def handle_headers(frame, request, response):
+ resource_url = request.GET.first(b"resource-url").decode()
+ link_header_value = "<{}>; rel=preload; as=image".format(resource_url)
+ early_hints = [
+ (b":status", b"103"),
+ (b"link", link_header_value),
+ ]
+ response.writer.write_raw_header_frame(headers=early_hints,
+ end_headers=True)
+
+ response.status = 404
+ response.headers[b"content-type"] = "text/html"
+ response.write_status_headers()
+
+
+def main(request, response):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ file_path = os.path.join(current_dir, "404-with-early-hints.html")
+ with open(file_path, "r") as f:
+ test_content = f.read()
+ response.writer.write_data(item=test_content, last=True)
diff --git a/testing/web-platform/tests/loading/early-hints/resources/404-with-early-hints.html b/testing/web-platform/tests/loading/early-hints/resources/404-with-early-hints.html
new file mode 100644
index 0000000000..2d351b08b8
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/404-with-early-hints.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="early-hints-helpers.sub.js"></script>
+<body>
+<script>
+promise_test(async (t) => {
+ const params = new URLSearchParams(window.location.search);
+ const resource_url = params.get("resource-url");
+ await fetchImage(resource_url);
+ assert_true(isPreloadedByEarlyHints(resource_url));
+}, "404 with an early hints preload.");
+</script>
+</body>
diff --git a/testing/web-platform/tests/loading/early-hints/resources/arbitrary-header-in-early-hints.h2.py b/testing/web-platform/tests/loading/early-hints/resources/arbitrary-header-in-early-hints.h2.py
new file mode 100644
index 0000000000..bb118c3200
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/arbitrary-header-in-early-hints.h2.py
@@ -0,0 +1,24 @@
+import os
+
+
+def handle_headers(frame, request, response):
+ # Send an early hints response with an unsupported header.
+ # User agents should ignore it.
+ early_hints = [
+ (b":status", b"103"),
+ (b"x-arbitrary-header", b"foobar"),
+ ]
+ response.writer.write_raw_header_frame(headers=early_hints,
+ end_headers=True)
+
+ response.status = 200
+ response.headers[b"content-type"] = "text/html"
+ response.write_status_headers()
+
+
+def main(request, response):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ file_path = os.path.join(current_dir, "arbitrary-header-in-early-hints.html")
+ with open(file_path, "r") as f:
+ test_content = f.read()
+ response.writer.write_data(item=test_content, last=True)
diff --git a/testing/web-platform/tests/loading/early-hints/resources/arbitrary-header-in-early-hints.html b/testing/web-platform/tests/loading/early-hints/resources/arbitrary-header-in-early-hints.html
new file mode 100644
index 0000000000..cc939f5ea6
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/arbitrary-header-in-early-hints.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+promise_test(async (t) => {
+ // Pass when the page is successfully loaded.
+}, "An Early hints response contains an arbitrary header and it should have no effect.");
+</script>
+</body>
diff --git a/testing/web-platform/tests/loading/early-hints/resources/coep-mismatch.h2.py b/testing/web-platform/tests/loading/early-hints/resources/coep-mismatch.h2.py
new file mode 100644
index 0000000000..240a804f57
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/coep-mismatch.h2.py
@@ -0,0 +1,30 @@
+import os
+
+
+def handle_headers(frame, request, response):
+ # Send a 103 response.
+ resource_url = request.GET.first(b"resource-url").decode()
+ link_header_value = "<{}>; rel=preload; as=script".format(resource_url)
+ coep_value = request.GET.first(b"early-hints-policy").decode()
+ early_hints = [
+ (b":status", b"103"),
+ (b"cross-origin-embedder-policy", coep_value),
+ (b"link", link_header_value),
+ ]
+ response.writer.write_raw_header_frame(headers=early_hints,
+ end_headers=True)
+
+ # Send the final response header.
+ coep_value = request.GET.first(b"final-policy").decode()
+ response.status = 200
+ response.headers["content-type"] = "text/html"
+ response.headers["cross-origin-embedder-policy"] = coep_value
+ response.write_status_headers()
+
+
+def main(request, response):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ file_path = os.path.join(current_dir, "coep-mismatch.html")
+ with open(file_path, "r") as f:
+ test_content = f.read()
+ response.writer.write_data(item=test_content, last=True)
diff --git a/testing/web-platform/tests/loading/early-hints/resources/coep-mismatch.html b/testing/web-platform/tests/loading/early-hints/resources/coep-mismatch.html
new file mode 100644
index 0000000000..1811bf5506
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/coep-mismatch.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="early-hints-helpers.sub.js"></script>
+<body>
+<script>
+const SEARCH_PARAMS = new URLSearchParams(window.location.search);
+const EARLY_HINTS_POLICY = SEARCH_PARAMS.get("early-hints-policy");
+const FINAL_POLICY = SEARCH_PARAMS.get("final-policy");
+
+promise_test(async (t) => {
+ const resource_url = SEARCH_PARAMS.get("resource-url");
+ if (FINAL_POLICY === "require-corp") {
+ assert_equals(EARLY_HINTS_POLICY, "unsafe-none");
+ await promise_rejects_js(t, Error, fetchScript(resource_url));
+ } else {
+ assert_equals(EARLY_HINTS_POLICY, "require-corp");
+ await fetchScript(resource_url);
+ assert_false(isPreloadedByEarlyHints(resource_url));
+ }
+}, `Early Hints COEP mismatch: Early Hints policy = ${EARLY_HINTS_POLICY}, final response policy = ${FINAL_POLICY}.`);
+</script>
+</body>
diff --git a/testing/web-platform/tests/loading/early-hints/resources/csp-basic-loader.h2.py b/testing/web-platform/tests/loading/early-hints/resources/csp-basic-loader.h2.py
new file mode 100644
index 0000000000..080901efe6
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/csp-basic-loader.h2.py
@@ -0,0 +1,47 @@
+import os
+
+
+def _calculate_csp_value(policy, resource_origin):
+ if policy == "absent":
+ return None
+ elif policy == "allowed":
+ return "script-src 'self' 'unsafe-inline' {}".format(resource_origin)
+ elif policy == "disallowed":
+ return "script-src 'self' 'unsafe-inline'"
+ else:
+ return None
+
+
+def handle_headers(frame, request, response):
+ resource_origin = request.GET.first(b"resource-origin").decode()
+
+ # Send a 103 response.
+ resource_url = request.GET.first(b"resource-url").decode()
+ link_header_value = "<{}>; rel=preload; as=script".format(resource_url)
+ early_hints = [
+ (b":status", b"103"),
+ (b"link", link_header_value),
+ ]
+ early_hints_csp = _calculate_csp_value(
+ request.GET.first(b"early-hints-policy").decode(), resource_origin)
+ if early_hints_csp:
+ early_hints.append((b"content-security-policy", early_hints_csp))
+ response.writer.write_raw_header_frame(headers=early_hints,
+ end_headers=True)
+
+ # Send the final response header.
+ response.status = 200
+ response.headers["content-type"] = "text/html"
+ final_csp = _calculate_csp_value(
+ request.GET.first(b"final-policy").decode(), resource_origin)
+ if final_csp:
+ response.headers["content-security-policy"] = final_csp
+ response.write_status_headers()
+
+
+def main(request, response):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ file_path = os.path.join(current_dir, "csp-basic.html")
+ with open(file_path, "r") as f:
+ test_content = f.read()
+ response.writer.write_data(item=test_content, last=True)
diff --git a/testing/web-platform/tests/loading/early-hints/resources/csp-basic.html b/testing/web-platform/tests/loading/early-hints/resources/csp-basic.html
new file mode 100644
index 0000000000..0086711fb7
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/csp-basic.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="early-hints-helpers.sub.js"></script>
+<body>
+<script>
+const SEARCH_PARAMS = new URLSearchParams(window.location.search);
+const EARLY_HINTS_POLICY = SEARCH_PARAMS.get("early-hints-policy");
+const FINAL_POLICY = SEARCH_PARAMS.get("final-policy");
+
+function isResourceAllowed() {
+ return FINAL_POLICY === "absent" || FINAL_POLICY === "allowed";
+}
+
+function shouldEarlyHintsPreloadResource() {
+ return EARLY_HINTS_POLICY === "absent" || EARLY_HINTS_POLICY == "allowed";
+}
+
+promise_test(async (t) => {
+ const resource_url = SEARCH_PARAMS.get("resource-url");
+ if (isResourceAllowed()) {
+ await fetchScript(resource_url);
+ const early_hints_preloaded = isPreloadedByEarlyHints(resource_url);
+ const should_early_hints_preload = shouldEarlyHintsPreloadResource();
+ const description = "Early Hints " +
+ (early_hints_preloaded ? "preloaded" : "didn't preload") +
+ " the resource, should " +
+ (should_early_hints_preload ? "" : "not ") + "preload.";
+ assert_equals(early_hints_preloaded, should_early_hints_preload,
+ description);
+ } else {
+ await promise_rejects_js(t, Error, fetchScript(resource_url));
+ }
+}, `Early Hints CSP: Early Hints policy = ${EARLY_HINTS_POLICY}, final response policy = ${FINAL_POLICY}.`);
+</script>
+</body>
diff --git a/testing/web-platform/tests/loading/early-hints/resources/csp-document-disallow-loader.h2.py b/testing/web-platform/tests/loading/early-hints/resources/csp-document-disallow-loader.h2.py
new file mode 100644
index 0000000000..bffa90c753
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/csp-document-disallow-loader.h2.py
@@ -0,0 +1,39 @@
+import os
+
+
+def handle_headers(frame, request, response):
+ # Send a 103 response.
+ resource_url = request.GET.first(b"resource-url").decode()
+ link_header_value = "<{}>; rel=preload; as=script".format(resource_url)
+ early_hints = [
+ (b":status", b"103"),
+ (b"link", link_header_value),
+ ]
+
+ early_hints_policy = request.GET.first(b"early-hints-policy").decode()
+ # In this test handler "allowed" or "absent" are only valid policies because
+ # csp-document-disallow.html always sets CSP to disallow the preload.
+ # "disallowed" makes no observable changes in the test. Note that
+ # csp-basic.html covers disallowing preloads in Early Hints.
+ assert early_hints_policy == "allowed" or early_hints_policy == "absent"
+
+ if early_hints_policy == "allowed":
+ resource_origin = request.GET.first(b"resource-origin").decode()
+ csp_value = "script-src 'self' 'unsafe-inline' {}".format(resource_origin)
+ early_hints.append((b"content-security-policy", csp_value))
+
+ response.writer.write_raw_header_frame(headers=early_hints,
+ end_headers=True)
+
+ # Send the final response header.
+ response.status = 200
+ response.headers["content-type"] = "text/html"
+ response.write_status_headers()
+
+
+def main(request, response):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ file_path = os.path.join(current_dir, "csp-document-disallow.html")
+ with open(file_path, "r") as f:
+ test_content = f.read()
+ response.writer.write_data(item=test_content, last=True)
diff --git a/testing/web-platform/tests/loading/early-hints/resources/csp-document-disallow.html b/testing/web-platform/tests/loading/early-hints/resources/csp-document-disallow.html
new file mode 100644
index 0000000000..53b6ee232d
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/csp-document-disallow.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="early-hints-helpers.sub.js"></script>
+<body>
+<script>
+const SEARCH_PARAMS = new URLSearchParams(window.location.search);
+const POLICY = SEARCH_PARAMS.get("early-hints-policy");
+
+promise_test(async (t) => {
+ const resource_url = SEARCH_PARAMS.get("resource-url");
+
+ // Resume the delayed preload.
+ const resume_url = SEARCH_PARAMS.get("resume-url");
+ await fetch(resume_url);
+
+ // Wait for the preload to finish.
+ await new Promise(resolve => t.step_timeout(resolve, 300));
+
+ // The preload should be denied by CSP.
+ await promise_rejects_js(t, Error, fetchScript(resource_url));
+}, `Early hints preload CSP = ${POLICY}, document disallowed the preload later.`);
+</script>
+</body>
diff --git a/testing/web-platform/tests/loading/early-hints/resources/delayed-js.h2.py b/testing/web-platform/tests/loading/early-hints/resources/delayed-js.h2.py
new file mode 100644
index 0000000000..93865b930d
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/delayed-js.h2.py
@@ -0,0 +1,16 @@
+import importlib
+
+utils = importlib.import_module("loading.early-hints.resources.utils")
+
+
+def main(request, response):
+ id = request.GET.first(b"id")
+ # Wait until the id is set via resume-delayed-js.h2.py.
+ utils.wait_for_preload_to_finish(request, id)
+
+ headers = [
+ ("Content-Type", "text/javascript"),
+ ("Cache-Control", "max-age=600"),
+ ]
+ body = "/*empty script*/"
+ return (200, "OK"), headers, body
diff --git a/testing/web-platform/tests/loading/early-hints/resources/early-hints-helpers.sub.js b/testing/web-platform/tests/loading/early-hints/resources/early-hints-helpers.sub.js
new file mode 100644
index 0000000000..3991e8fe9d
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/early-hints-helpers.sub.js
@@ -0,0 +1,180 @@
+"use strict";
+
+const SAME_ORIGIN = "https://{{host}}:{{ports[h2][0]}}";
+const CROSS_ORIGIN = "https://{{hosts[alt][www]}}:{{ports[h2][0]}}";
+
+const RESOURCES_PATH = "/loading/early-hints/resources";
+const SAME_ORIGIN_RESOURCES_URL = SAME_ORIGIN + RESOURCES_PATH;
+const CROSS_ORIGIN_RESOURCES_URL = CROSS_ORIGIN + RESOURCES_PATH;
+
+/**
+ * Navigate to a test page with an Early Hints response.
+ *
+ * @typedef {Object} Preload
+ * @property {string} url - A URL to preload. Note: This is relative to the
+ * `test_url` parameter of `navigateToTestWithEarlyHints()`.
+ * @property {string} as_attr - `as` attribute of this preload.
+ *
+ * @param {string} test_url - URL of a test after the Early Hints response.
+ * @param {Array<Preload>} preloads - Preloads included in the Early Hints response.
+ */
+function navigateToTestWithEarlyHints(test_url, preloads) {
+ const params = new URLSearchParams();
+ params.set("test_url", test_url);
+ for (const preload of preloads) {
+ params.append("preloads", JSON.stringify(preload));
+ }
+ const url = "resources/early-hints-test-loader.h2.py?" + params.toString();
+ window.location.replace(new URL(url, window.location));
+}
+
+/**
+ * Parses the query string of the current window location and returns preloads
+ * in the Early Hints response sent via `navigateToTestWithEarlyHints()`.
+ *
+ * @returns {Array<Preload>}
+ */
+function getPreloadsFromSearchParams() {
+ const params = new URLSearchParams(window.location.search);
+ const encoded_preloads = params.getAll("preloads");
+ const preloads = [];
+ for (const encoded of encoded_preloads) {
+ preloads.push(JSON.parse(encoded));
+ }
+ return preloads;
+}
+
+/**
+ * Fetches a script or an image.
+ *
+ * @param {string} element - "script" or "img".
+ * @param {string} url - URL of the resource.
+ */
+async function fetchResource(element, url) {
+ return new Promise((resolve, reject) => {
+ const el = document.createElement(element);
+ el.src = url;
+ el.onload = resolve;
+ el.onerror = _ => reject(new Error("Failed to fetch resource: " + url));
+ document.body.appendChild(el);
+ });
+}
+
+/**
+ * Fetches a script.
+ *
+ * @param {string} url
+ */
+async function fetchScript(url) {
+ return fetchResource("script", url);
+}
+
+/**
+ * Fetches an image.
+ *
+ * @param {string} url
+ */
+ async function fetchImage(url) {
+ return fetchResource("img", url);
+}
+
+/**
+ * Returns true when the resource is preloaded via Early Hints.
+ *
+ * @param {string} url
+ * @returns {boolean}
+ */
+function isPreloadedByEarlyHints(url) {
+ const entries = performance.getEntriesByName(url);
+ if (entries.length === 0) {
+ return false;
+ }
+ assert_equals(entries.length, 1);
+ return entries[0].initiatorType === "early-hints";
+}
+
+/**
+ * Navigate to the referrer policy test page.
+ *
+ * @param {string} referrer_policy - A value of Referrer-Policy to test.
+ */
+function testReferrerPolicy(referrer_policy) {
+ const params = new URLSearchParams();
+ params.set("referrer-policy", referrer_policy);
+ const same_origin_preload_url = SAME_ORIGIN_RESOURCES_URL + "/fetch-and-record-js.h2.py?id=" + token();
+ params.set("same-origin-preload-url", same_origin_preload_url);
+ const cross_origin_preload_url = CROSS_ORIGIN_RESOURCES_URL + "/fetch-and-record-js.h2.py?id=" + token();
+ params.set("cross-origin-preload-url", cross_origin_preload_url);
+
+ const path = "resources/referrer-policy-test-loader.h2.py?" + params.toString();
+ const url = new URL(path, window.location);
+ window.location.replace(url);
+}
+
+/**
+ * Navigate to the content security policy basic test. The test page sends an
+ * Early Hints response with a cross origin resource preload. CSP headers are
+ * configured based on the given policies. A policy should be one of the
+ * followings:
+ * "absent" - Do not send Content-Security-Policy header
+ * "allowed" - Set Content-Security-Policy to allow the cross origin preload
+ * "disallowed" - Set Content-Security-Policy to disallow the cross origin preload
+ *
+ * @param {string} early_hints_policy - The policy for the Early Hints response
+ * @param {string} final_policy - The policy for the final response
+ */
+function navigateToContentSecurityPolicyBasicTest(
+ early_hints_policy, final_policy) {
+ const params = new URLSearchParams();
+ params.set("resource-origin", CROSS_ORIGIN);
+ params.set("resource-url",
+ CROSS_ORIGIN_RESOURCES_URL + "/empty.js?" + token());
+ params.set("early-hints-policy", early_hints_policy);
+ params.set("final-policy", final_policy);
+
+ const url = "resources/csp-basic-loader.h2.py?" + params.toString();
+ window.location.replace(new URL(url, window.location));
+}
+
+/**
+ * Navigate to a test page which sends an Early Hints containing a cross origin
+ * preload link with/without Content-Security-Policy header. The CSP header is
+ * configured based on the given policy. The test page disallows the preload
+ * while the preload is in-flight. The policy should be one of the followings:
+ * "absent" - Do not send Content-Security-Policy header
+ * "allowed" - Set Content-Security-Policy to allow the cross origin preload
+ *
+ * @param {string} early_hints_policy
+ */
+function navigateToContentSecurityPolicyDocumentDisallowTest(early_hints_policy) {
+ const resource_id = token();
+ const params = new URLSearchParams();
+ params.set("resource-origin", CROSS_ORIGIN);
+ params.set("resource-url",
+ CROSS_ORIGIN_RESOURCES_URL + "/delayed-js.h2.py?id=" + resource_id);
+ params.set("resume-url",
+ CROSS_ORIGIN_RESOURCES_URL + "/resume-delayed-js.h2.py?id=" + resource_id);
+ params.set("early-hints-policy", early_hints_policy);
+
+ const url = "resources/csp-document-disallow-loader.h2.py?" + params.toString();
+ window.location.replace(new URL(url, window.location));
+}
+
+/**
+ * Navigate to a test page which sends different Cross-Origin-Embedder-Policy
+ * values in an Early Hints response and the final response.
+ *
+ * @param {string} early_hints_policy - The policy for the Early Hints response
+ * @param {string} final_policy - The policy for the final response
+ */
+function navigateToCrossOriginEmbedderPolicyMismatchTest(
+ early_hints_policy, final_policy) {
+ const params = new URLSearchParams();
+ params.set("resource-url",
+ CROSS_ORIGIN_RESOURCES_URL + "/empty-corp-absent.js?" + token());
+ params.set("early-hints-policy", early_hints_policy);
+ params.set("final-policy", final_policy);
+
+ const url = "resources/coep-mismatch.h2.py?" + params.toString();
+ window.location.replace(new URL(url, window.location));
+}
diff --git a/testing/web-platform/tests/loading/early-hints/resources/early-hints-test-loader.h2.py b/testing/web-platform/tests/loading/early-hints/resources/early-hints-test-loader.h2.py
new file mode 100644
index 0000000000..aa9188c6db
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/early-hints-test-loader.h2.py
@@ -0,0 +1,45 @@
+# An HTTP/2 handler for testing Early Hints. Used as an entry point of Early
+# Hints related tests to inject Early Hints response. See comments in
+# `early-hints-helpers.sub.js`.
+
+import json
+import os
+import time
+
+
+def _remove_relative_resources_prefix(path):
+ if path.startswith("resources/"):
+ return path[len("resources/"):]
+ return path
+
+
+def handle_headers(frame, request, response):
+ preload_headers = []
+ for encoded_preload in request.GET.get_list(b"preloads"):
+ preload = json.loads(encoded_preload.decode("utf-8"))
+ header = "<{}>; rel=preload; as={}".format(preload["url"], preload["as_attr"])
+ preload_headers.append(header.encode())
+
+ # Send a 103 response.
+ early_hints = [(b":status", b"103")]
+ for header in preload_headers:
+ early_hints.append((b"link", header))
+ response.writer.write_raw_header_frame(headers=early_hints,
+ end_headers=True)
+
+ # Simulate the response generation is taking time.
+ time.sleep(0.2)
+ response.status = 200
+ response.headers[b"content-type"] = "text/html"
+ for header in preload_headers:
+ response.headers.append(b"link", header)
+ response.write_status_headers()
+
+
+def main(request, response):
+ test_path = _remove_relative_resources_prefix(
+ request.GET[b"test_url"].decode("utf-8"))
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ file_path = os.path.join(current_dir, test_path)
+ test_content = open(file_path, "r").read()
+ response.writer.write_data(item=test_content, last=True)
diff --git a/testing/web-platform/tests/loading/early-hints/resources/empty-corp-absent.js b/testing/web-platform/tests/loading/early-hints/resources/empty-corp-absent.js
new file mode 100644
index 0000000000..b7965b64a1
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/empty-corp-absent.js
@@ -0,0 +1 @@
+// Empty script
diff --git a/testing/web-platform/tests/loading/early-hints/resources/empty-corp-absent.js.headers b/testing/web-platform/tests/loading/early-hints/resources/empty-corp-absent.js.headers
new file mode 100644
index 0000000000..175cdf8046
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/empty-corp-absent.js.headers
@@ -0,0 +1 @@
+cache-control: max-age=600
diff --git a/testing/web-platform/tests/loading/early-hints/resources/empty.js b/testing/web-platform/tests/loading/early-hints/resources/empty.js
new file mode 100644
index 0000000000..3e211cc8d2
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/empty.js
@@ -0,0 +1 @@
+// Empty script \ No newline at end of file
diff --git a/testing/web-platform/tests/loading/early-hints/resources/empty.js.headers b/testing/web-platform/tests/loading/early-hints/resources/empty.js.headers
new file mode 100644
index 0000000000..1738466bcb
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/empty.js.headers
@@ -0,0 +1,4 @@
+cache-control: max-age=600
+access-control-allow-origin: *
+timing-allow-origin: *
+cross-origin-resource-policy: cross-origin
diff --git a/testing/web-platform/tests/loading/early-hints/resources/example.pdf b/testing/web-platform/tests/loading/early-hints/resources/example.pdf
new file mode 100644
index 0000000000..7bad251ba7
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/example.pdf
@@ -0,0 +1,50 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+ /Type /Catalog
+ /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+ /Type /Pages
+ /MediaBox [ 0 0 200 300 ]
+ /Count 1
+ /Kids [ 3 0 R ]
+>>
+endobj
+3 0 obj <<
+ /Type /Page
+ /Parent 2 0 R
+ /Contents 4 0 R
+>>
+endobj
+4 0 obj <<
+>>
+stream
+q
+0 0 0 rg
+0 290 10 10 re B*
+10 150 50 30 re B*
+0 0 1 rg
+190 290 10 10 re B*
+70 232 50 30 re B*
+0 1 0 rg
+190 0 10 10 re B*
+130 150 50 30 re B*
+1 0 0 rg
+0 0 10 10 re B*
+70 67 50 30 re B*
+Q
+endstream
+endobj
+xref
+0 5
+0000000000 65535 f
+0000000015 00000 n
+0000000068 00000 n
+0000000161 00000 n
+0000000230 00000 n
+trailer<< /Root 1 0 R /Size 5 >>
+startxref
+456
+%%EOF
diff --git a/testing/web-platform/tests/loading/early-hints/resources/fetch-and-record-js.h2.py b/testing/web-platform/tests/loading/early-hints/resources/fetch-and-record-js.h2.py
new file mode 100644
index 0000000000..169f70c045
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/fetch-and-record-js.h2.py
@@ -0,0 +1,16 @@
+import importlib
+import time
+
+utils = importlib.import_module("loading.early-hints.resources.utils")
+
+
+def main(request, response):
+ utils.store_request_timing_and_headers(request)
+ headers = [
+ ("Content-Type", "text/javascript"),
+ ("Cache-Control", "max-age=600"),
+ ]
+ body = "/*empty script*/"
+ # Sleep to simulate loading time.
+ time.sleep(0.05)
+ return (200, "OK"), headers, body
diff --git a/testing/web-platform/tests/loading/early-hints/resources/get-fetch-timing-and-headers.h2.py b/testing/web-platform/tests/loading/early-hints/resources/get-fetch-timing-and-headers.h2.py
new file mode 100644
index 0000000000..59f67be3cf
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/get-fetch-timing-and-headers.h2.py
@@ -0,0 +1,12 @@
+import importlib
+
+utils = importlib.import_module("loading.early-hints.resources.utils")
+
+
+def main(request, response):
+ headers = [
+ ("Content-Type", "application/json"),
+ ("Access-Control-Allow-Origin", "*"),
+ ]
+ body = utils.get_request_timing_and_headers(request)
+ return (200, "OK"), headers, body
diff --git a/testing/web-platform/tests/loading/early-hints/resources/html-with-early-hints.h2.py b/testing/web-platform/tests/loading/early-hints/resources/html-with-early-hints.h2.py
new file mode 100644
index 0000000000..fc7ed0ffee
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/html-with-early-hints.h2.py
@@ -0,0 +1,21 @@
+def handle_headers(frame, request, response):
+ resource_url = request.GET.first(b"resource-url").decode()
+ link_header_value = "<{}>; rel=preload; as=script".format(resource_url)
+ early_hints = [
+ (b":status", b"103"),
+ (b"link", link_header_value),
+ ]
+ response.writer.write_raw_header_frame(headers=early_hints,
+ end_headers=True)
+
+ response.status = 200
+ response.headers[b"content-type"] = "text/html"
+ if b"x-frame-options" in request.GET:
+ x_frame_options = request.GET.first(b"x-frame-options").decode()
+ response.headers[b"x-frame-options"] = x_frame_options
+ response.write_status_headers()
+
+
+def main(request, response):
+ content = "<!-- empty -->"
+ response.writer.write_data(item=content, last=True)
diff --git a/testing/web-platform/tests/loading/early-hints/resources/invalid-headers-in-early-hints.h2.py b/testing/web-platform/tests/loading/early-hints/resources/invalid-headers-in-early-hints.h2.py
new file mode 100644
index 0000000000..438d1c79ac
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/invalid-headers-in-early-hints.h2.py
@@ -0,0 +1,20 @@
+import os
+
+
+def handle_headers(frame, request, response):
+ header_value = request.GET.first(b"header-value")
+ early_hints = [
+ (b":status", b"103"),
+ (b"invalid-header", header_value),
+ ]
+ response.writer.write_raw_header_frame(headers=early_hints,
+ end_headers=True)
+
+ response.status = 200
+ response.headers[b"content-type"] = "text/html"
+ response.write_status_headers()
+
+
+def main(request, response):
+ test_content = "<div>This page should not be loaded.</div>"
+ response.writer.write_data(item=test_content, last=True)
diff --git a/testing/web-platform/tests/loading/early-hints/resources/modulepreload-in-early-hints.h2.py b/testing/web-platform/tests/loading/early-hints/resources/modulepreload-in-early-hints.h2.py
new file mode 100644
index 0000000000..231b3bc69c
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/modulepreload-in-early-hints.h2.py
@@ -0,0 +1,24 @@
+import os
+
+
+def handle_headers(frame, request, response):
+ resource_url = request.GET.first(b"resource-url").decode()
+ link_header_value = "<{}>; rel=modulepreload".format(resource_url)
+ early_hints = [
+ (b":status", b"103"),
+ (b"link", link_header_value),
+ ]
+ response.writer.write_raw_header_frame(headers=early_hints,
+ end_headers=True)
+
+ response.status = 200
+ response.headers[b"content-type"] = "text/html"
+ response.write_status_headers()
+
+
+def main(request, response):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ file_path = os.path.join(current_dir, "modulepreload-in-early-hints.html")
+ with open(file_path, "r") as f:
+ test_content = f.read()
+ response.writer.write_data(item=test_content, last=True)
diff --git a/testing/web-platform/tests/loading/early-hints/resources/modulepreload-in-early-hints.html b/testing/web-platform/tests/loading/early-hints/resources/modulepreload-in-early-hints.html
new file mode 100644
index 0000000000..44aebf720a
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/modulepreload-in-early-hints.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="early-hints-helpers.sub.js"></script>
+<body>
+<script>
+async function fetchModuleScript(url) {
+ return new Promise((resolve, reject) => {
+ const el = document.createElement("script");
+ el.src = url;
+ el.type = "module";
+ el.onload = resolve;
+ el.onerror = _ => reject(new Error("Failed to fetch resource: " + url));
+ document.body.appendChild(el);
+ });
+}
+
+promise_test(async (t) => {
+ const params = new URLSearchParams(window.location.search);
+ const resource_url = params.get("resource-url");
+ await fetchModuleScript(resource_url);
+ assert_true(isPreloadedByEarlyHints(resource_url));
+}, "Modulepreload in an early hints.");
+</script>
+</body>
diff --git a/testing/web-platform/tests/loading/early-hints/resources/multiple-early-hints-responses.h2.py b/testing/web-platform/tests/loading/early-hints/resources/multiple-early-hints-responses.h2.py
new file mode 100644
index 0000000000..3cc221abf2
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/multiple-early-hints-responses.h2.py
@@ -0,0 +1,39 @@
+import os
+
+
+def handle_headers(frame, request, response):
+ # Send two Early Hints responses.
+
+ first_preload = request.GET.first(b"first-preload").decode()
+ link_header_value = "<{}>; rel=preload; as=script".format(first_preload)
+ early_hints = [
+ (b":status", b"103"),
+ (b"content-security-policy", "script-src 'self' 'unsafe-inline'"),
+ (b"link", link_header_value),
+ ]
+ response.writer.write_raw_header_frame(headers=early_hints,
+ end_headers=True)
+
+ second_preload = request.GET.first(b"second-preload").decode()
+ link_header_value = "<{}>; rel=preload; as=script".format(second_preload)
+ second_preload_origin = request.GET.first(b"second-preload-origin").decode()
+ csp_value = "script-src 'self' 'unsafe-inline' {}".format(second_preload_origin)
+ early_hints = [
+ (b":status", b"103"),
+ (b"content-security-policy", csp_value),
+ (b"link", link_header_value),
+ ]
+ response.writer.write_raw_header_frame(headers=early_hints,
+ end_headers=True)
+
+ response.status = 200
+ response.headers[b"content-type"] = "text/html"
+ response.write_status_headers()
+
+
+def main(request, response):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ file_path = os.path.join(current_dir, "multiple-early-hints-responses.html")
+ with open(file_path, "r") as f:
+ test_content = f.read()
+ response.writer.write_data(item=test_content, last=True)
diff --git a/testing/web-platform/tests/loading/early-hints/resources/multiple-early-hints-responses.html b/testing/web-platform/tests/loading/early-hints/resources/multiple-early-hints-responses.html
new file mode 100644
index 0000000000..3be2852348
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/multiple-early-hints-responses.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="early-hints-helpers.sub.js"></script>
+<body>
+<script>
+promise_test(async (t) => {
+ const params = new URLSearchParams(window.location.search);
+
+ const first_preload = params.get("first-preload");
+ await fetchScript(first_preload);
+ assert_true(isPreloadedByEarlyHints(first_preload));
+
+ const second_preload = params.get("second-preload");
+ await fetchScript(second_preload);
+ assert_false(isPreloadedByEarlyHints(second_preload));
+}, "Only the first early hints response is processed");
+</script>
+</body>
diff --git a/testing/web-platform/tests/loading/early-hints/resources/pdf-with-early-hints.h2.py b/testing/web-platform/tests/loading/early-hints/resources/pdf-with-early-hints.h2.py
new file mode 100644
index 0000000000..0d05f2a3c5
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/pdf-with-early-hints.h2.py
@@ -0,0 +1,26 @@
+import os
+import time
+
+def handle_headers(frame, request, response):
+ resource_url = request.GET.first(b"resource-url").decode()
+ link_header_value = "<{}>; rel=preload; as=script".format(resource_url)
+ early_hints = [
+ (b":status", b"103"),
+ (b"link", link_header_value),
+ ]
+ response.writer.write_raw_header_frame(headers=early_hints,
+ end_headers=True)
+
+ # Sleep to simulate a slow generation of the final response.
+ time.sleep(0.1)
+ response.status = 200
+ response.headers[b"content-type"] = "application/pdf"
+ response.write_status_headers()
+
+
+def main(request, response):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ file_path = os.path.join(current_dir, "example.pdf")
+ with open(file_path, "rb") as f:
+ content = f.read()
+ response.writer.write_data(item=content, last=True)
diff --git a/testing/web-platform/tests/loading/early-hints/resources/png-with-early-hints.h2.py b/testing/web-platform/tests/loading/early-hints/resources/png-with-early-hints.h2.py
new file mode 100644
index 0000000000..0785d512c1
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/png-with-early-hints.h2.py
@@ -0,0 +1,26 @@
+import os
+import time
+
+def handle_headers(frame, request, response):
+ resource_url = request.GET.first(b"resource-url").decode()
+ link_header_value = "<{}>; rel=preload; as=script".format(resource_url)
+ early_hints = [
+ (b":status", b"103"),
+ (b"link", link_header_value),
+ ]
+ response.writer.write_raw_header_frame(headers=early_hints,
+ end_headers=True)
+
+ # Sleep to simulate a slow generation of the final response.
+ time.sleep(0.1)
+ response.status = 200
+ response.headers[b"content-type"] = "image/png"
+ response.write_status_headers()
+
+
+def main(request, response):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ file_path = os.path.join(current_dir, "square.png")
+ with open(file_path, "rb") as f:
+ test_content = f.read()
+ response.writer.write_data(item=test_content, last=True)
diff --git a/testing/web-platform/tests/loading/early-hints/resources/preconnect-in-early-hints.h2.py b/testing/web-platform/tests/loading/early-hints/resources/preconnect-in-early-hints.h2.py
new file mode 100644
index 0000000000..e448fd0af7
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/preconnect-in-early-hints.h2.py
@@ -0,0 +1,26 @@
+import os
+
+
+def handle_headers(frame, request, response):
+ # Send a 103 response.
+ resource_origin = request.GET.first(b"resource-origin").decode()
+ link_header_value = "<{}>; rel=preconnect".format(resource_origin)
+ early_hints = [
+ (b":status", b"103"),
+ (b"link", link_header_value),
+ ]
+ response.writer.write_raw_header_frame(headers=early_hints,
+ end_headers=True)
+
+ # Send the final response header.
+ response.status = 200
+ response.headers["content-type"] = "text/html"
+ response.write_status_headers()
+
+
+def main(request, response):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ file_path = os.path.join(current_dir, "preconnect-in-early-hints.html")
+ with open(file_path, "r") as f:
+ test_content = f.read()
+ response.writer.write_data(item=test_content, last=True)
diff --git a/testing/web-platform/tests/loading/early-hints/resources/preconnect-in-early-hints.html b/testing/web-platform/tests/loading/early-hints/resources/preconnect-in-early-hints.html
new file mode 100644
index 0000000000..8ec8fde5e6
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/preconnect-in-early-hints.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="early-hints-helpers.sub.js"></script>
+<body>
+<script>
+promise_test(async (t) => {
+ const params = new URLSearchParams(window.location.search);
+ const resource_url = params.get("resource-url");
+ await fetchScript(resource_url);
+
+ // Check the resource's connect timing.
+ const entries = performance.getEntriesByName(resource_url);
+ assert_equals(entries.length, 1);
+ const connect_start = entries[0].connectStart;
+ const connect_end = entries[0].connectEnd;
+ assert_equals(connect_start, connect_end,
+ "Connection establishment should not take time for a resource from a preconnected origin.");
+}, "Preconnect in early hints.");
+</script>
+</body>
diff --git a/testing/web-platform/tests/loading/early-hints/resources/preload-as-test.h2.py b/testing/web-platform/tests/loading/early-hints/resources/preload-as-test.h2.py
new file mode 100644
index 0000000000..682edb56df
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/preload-as-test.h2.py
@@ -0,0 +1,29 @@
+import os
+
+
+def handle_headers(frame, request, response):
+ resource_url = request.GET.first(b"resource-url").decode()
+ as_value = request.GET.first(b"as", None)
+ if as_value:
+ link_header_value = "<{}>; rel=preload; as={}".format(
+ resource_url, as_value.decode())
+ else:
+ link_header_value = "<{}>; rel=preload".format(resource_url)
+ early_hints = [
+ (b":status", b"103"),
+ (b"link", link_header_value),
+ ]
+ response.writer.write_raw_header_frame(headers=early_hints,
+ end_headers=True)
+
+ response.status = 200
+ response.headers[b"content-type"] = "text/html"
+ response.write_status_headers()
+
+
+def main(request, response):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ file_path = os.path.join(current_dir, "preload-as-test.html")
+ with open(file_path, "r") as f:
+ test_content = f.read()
+ response.writer.write_data(item=test_content, last=True)
diff --git a/testing/web-platform/tests/loading/early-hints/resources/preload-as-test.html b/testing/web-platform/tests/loading/early-hints/resources/preload-as-test.html
new file mode 100644
index 0000000000..daea33160a
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/preload-as-test.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="early-hints-helpers.sub.js"></script>
+<body>
+<script>
+const params = new URLSearchParams(window.location.search);
+const description = params.get("description");
+
+promise_test(async (t) => {
+ const resource_url = params.get("resource-url");
+ const should_preload = params.get("should-preload") === "true";
+ await fetchScript(resource_url);
+ assert_equals(isPreloadedByEarlyHints(resource_url), should_preload);
+}, description);
+</script>
+</body>
diff --git a/testing/web-platform/tests/loading/early-hints/resources/preload-finished-before-final-response.h2.py b/testing/web-platform/tests/loading/early-hints/resources/preload-finished-before-final-response.h2.py
new file mode 100644
index 0000000000..d0b12408d9
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/preload-finished-before-final-response.h2.py
@@ -0,0 +1,31 @@
+import importlib
+import os
+
+utils = importlib.import_module("loading.early-hints.resources.utils")
+
+
+def handle_headers(frame, request, response):
+ resource_url = request.GET.first(b"resource-url").decode()
+ link_header_value = "<{}>; rel=preload; as=script".format(resource_url)
+ early_hints = [
+ (b":status", b"103"),
+ (b"link", link_header_value),
+ ]
+ response.writer.write_raw_header_frame(headers=early_hints,
+ end_headers=True)
+
+ # Wait for preload to finish before sending the final response headers.
+ resource_id = request.GET.first(b"resource-id").decode()
+ utils.wait_for_preload_to_finish(request, resource_id)
+
+ response.status = 200
+ response.headers[b"content-type"] = "text/html"
+ response.write_status_headers()
+
+
+def main(request, response):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ file_path = os.path.join(current_dir, "preload-finished-before-final-response.html")
+ with open(file_path, "r") as f:
+ test_content = f.read()
+ response.writer.write_data(item=test_content, last=True)
diff --git a/testing/web-platform/tests/loading/early-hints/resources/preload-finished-before-final-response.html b/testing/web-platform/tests/loading/early-hints/resources/preload-finished-before-final-response.html
new file mode 100644
index 0000000000..d965b40420
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/preload-finished-before-final-response.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="early-hints-helpers.sub.js"></script>
+<body>
+<script>
+promise_test(async (t) => {
+ const params = new URLSearchParams(window.location.search);
+ const resource_url = params.get("resource-url");
+ await fetchScript(resource_url);
+ assert_true(isPreloadedByEarlyHints(resource_url));
+}, "Early hints preload finished before the final response.");
+</script>
+</body>
diff --git a/testing/web-platform/tests/loading/early-hints/resources/preload-finished-while-receiving-final-response-body.h2.py b/testing/web-platform/tests/loading/early-hints/resources/preload-finished-while-receiving-final-response-body.h2.py
new file mode 100644
index 0000000000..1ba486002c
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/preload-finished-while-receiving-final-response-body.h2.py
@@ -0,0 +1,31 @@
+import importlib
+import os
+
+utils = importlib.import_module("loading.early-hints.resources.utils")
+
+
+def handle_headers(frame, request, response):
+ resource_url = request.GET.first(b"resource-url").decode()
+ link_header_value = "<{}>; rel=preload; as=script".format(resource_url)
+ early_hints = [
+ (b":status", b"103"),
+ (b"link", link_header_value),
+ ]
+ response.writer.write_raw_header_frame(headers=early_hints,
+ end_headers=True)
+
+ response.status = 200
+ response.headers[b"content-type"] = "text/html"
+ response.write_status_headers()
+
+
+def main(request, response):
+ # Wait for preload to finish before sending the response body.
+ resource_id = request.GET.first(b"resource-id").decode()
+ utils.wait_for_preload_to_finish(request, resource_id)
+
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ file_path = os.path.join(current_dir, "preload-finished-while-receiving-final-response-body.html")
+ with open(file_path, "r") as f:
+ test_content = f.read()
+ response.writer.write_data(item=test_content, last=True)
diff --git a/testing/web-platform/tests/loading/early-hints/resources/preload-finished-while-receiving-final-response-body.html b/testing/web-platform/tests/loading/early-hints/resources/preload-finished-while-receiving-final-response-body.html
new file mode 100644
index 0000000000..5a233612b2
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/preload-finished-while-receiving-final-response-body.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="early-hints-helpers.sub.js"></script>
+<body>
+<script>
+promise_test(async (t) => {
+ const params = new URLSearchParams(window.location.search);
+ const resource_url = params.get("resource-url");
+ await fetchScript(resource_url);
+ assert_true(isPreloadedByEarlyHints(resource_url));
+}, "Early hints preload finished while loading the final response body.");
+</script>
+</body>
diff --git a/testing/web-platform/tests/loading/early-hints/resources/preload-in-flight-when-consumed.h2.py b/testing/web-platform/tests/loading/early-hints/resources/preload-in-flight-when-consumed.h2.py
new file mode 100644
index 0000000000..c3d66160e3
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/preload-in-flight-when-consumed.h2.py
@@ -0,0 +1,24 @@
+import os
+
+
+def handle_headers(frame, request, response):
+ resource_url = request.GET.first(b"resource-url").decode()
+ link_header_value = "<{}>; rel=preload; as=script".format(resource_url)
+ early_hints = [
+ (b":status", b"103"),
+ (b"link", link_header_value),
+ ]
+ response.writer.write_raw_header_frame(headers=early_hints,
+ end_headers=True)
+
+ response.status = 200
+ response.headers[b"content-type"] = "text/html"
+ response.write_status_headers()
+
+
+def main(request, response):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ file_path = os.path.join(current_dir, "preload-in-flight-when-consumed.html")
+ with open(file_path, "r") as f:
+ test_content = f.read()
+ response.writer.write_data(item=test_content, last=True)
diff --git a/testing/web-platform/tests/loading/early-hints/resources/preload-in-flight-when-consumed.html b/testing/web-platform/tests/loading/early-hints/resources/preload-in-flight-when-consumed.html
new file mode 100644
index 0000000000..5075d92846
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/preload-in-flight-when-consumed.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="early-hints-helpers.sub.js"></script>
+<body>
+<script>
+promise_test(async (t) => {
+ const params = new URLSearchParams(window.location.search);
+ const resource_id = params.get("resource-id");
+ const resource_url = params.get("resource-url");
+
+ const promise = fetchScript(resource_url);
+ await fetch("resume-delayed-js.h2.py?id=" + resource_id);
+ await promise;
+ assert_true(isPreloadedByEarlyHints(resource_url));
+}, "Early hints preload is in-flight when consumed.");
+</script>
+</body>
diff --git a/testing/web-platform/tests/loading/early-hints/resources/preload-initiator-type.html b/testing/web-platform/tests/loading/early-hints/resources/preload-initiator-type.html
new file mode 100644
index 0000000000..0fdeb2b903
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/preload-initiator-type.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="early-hints-helpers.sub.js"></script>
+<body>
+<script>
+async_test((t) => {
+ const preloads = getPreloadsFromSearchParams();
+ assert_equals(preloads.length, 1);
+ const preload = preloads[0];
+
+ const el = document.createElement("script");
+ el.src = preload.url;
+ el.onload = t.step_func_done(() => {
+ const name = new URL(preload.url, window.location);
+ assert_true(isPreloadedByEarlyHints(name));
+ });
+ document.body.appendChild(el);
+}, "Ensure initiatorType is set to 'early-hints'");
+</script>
+</body>
diff --git a/testing/web-platform/tests/loading/early-hints/resources/redirect-between-early-hints.h2.py b/testing/web-platform/tests/loading/early-hints/resources/redirect-between-early-hints.h2.py
new file mode 100644
index 0000000000..a1b3f15ca4
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/redirect-between-early-hints.h2.py
@@ -0,0 +1,54 @@
+import os
+
+
+def _send_early_hints(preload, writer):
+ link_header_value = "<{}>; rel=preload; as=script".format(preload)
+ early_hints = [
+ (b":status", b"103"),
+ (b"link", link_header_value),
+ ]
+ writer.write_raw_header_frame(headers=early_hints, end_headers=True)
+
+
+def handle_headers(frame, request, response):
+ step = request.GET.first(b"test-step").decode()
+ if step == "redirect":
+ preload = request.GET.first(b"preload-before-redirect").decode()
+ _send_early_hints(preload, response.writer)
+
+ # Redirect to the final test page with parameters.
+ params = []
+ for key, values in request.GET.items():
+ if key == b"test-step":
+ params.append("test-step=final-response")
+ else:
+ params.append("{}={}".format(key.decode(), values[0].decode()))
+
+ redirect_url = request.GET.first(b"redirect-url").decode()
+ location = "{}?{}".format(redirect_url, "&".join(params))
+
+ response.status = 302
+ response.headers["location"] = location
+ response.write_status_headers()
+ elif step == "final-response":
+ preload = request.GET.first(b"preload-after-redirect").decode()
+ _send_early_hints(preload, response.writer)
+
+ response.status = 200
+ response.headers["content-type"] = "text/html"
+ response.write_status_headers()
+ else:
+ raise Exception("Invalid step: {}".format(step))
+
+
+def main(request, response):
+ step = request.GET.first(b"test-step").decode()
+ if step != "final-response":
+ return
+
+ final_test_page = request.GET.first(b"final-test-page").decode()
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ file_path = os.path.join(current_dir, final_test_page)
+ with open(file_path, "r") as f:
+ test_content = f.read()
+ response.writer.write_data(item=test_content, last=True)
diff --git a/testing/web-platform/tests/loading/early-hints/resources/redirect-cross-origin-between-early-hints.html b/testing/web-platform/tests/loading/early-hints/resources/redirect-cross-origin-between-early-hints.html
new file mode 100644
index 0000000000..46560bb2da
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/redirect-cross-origin-between-early-hints.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="early-hints-helpers.sub.js"></script>
+<body>
+<script>
+promise_test(async (t) => {
+ const params = new URLSearchParams(window.location.search);
+
+ const preload_before_redirect = params.get("preload-before-redirect");
+ await fetchScript(preload_before_redirect);
+ assert_false(isPreloadedByEarlyHints(preload_before_redirect),
+ "Early hints before cross origin redirect should not appear.");
+
+ const preload_after_redirect = params.get("preload-after-redirect");
+ await fetchScript(preload_after_redirect);
+ assert_true(isPreloadedByEarlyHints(preload_after_redirect),
+ "Early hints after cross origin redirect should preload.");
+}, "Early hints -> cross origin redirect -> early hints -> final response.");
+</script>
+</body>
diff --git a/testing/web-platform/tests/loading/early-hints/resources/redirect-cross-origin.html b/testing/web-platform/tests/loading/early-hints/resources/redirect-cross-origin.html
new file mode 100644
index 0000000000..39b37f8130
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/redirect-cross-origin.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="early-hints-helpers.sub.js"></script>
+<body>
+<script>
+promise_test(async (t) => {
+ const params = new URLSearchParams(window.location.search);
+ const preload_url = params.get("preload-url");
+ await fetchScript(preload_url);
+ assert_false(isPreloadedByEarlyHints(preload_url));
+}, "Redirect to a cross origin doesn't bring early hints preload");
+</script>
+</body>
diff --git a/testing/web-platform/tests/loading/early-hints/resources/redirect-same-origin-between-early-hints.html b/testing/web-platform/tests/loading/early-hints/resources/redirect-same-origin-between-early-hints.html
new file mode 100644
index 0000000000..395f7f1752
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/redirect-same-origin-between-early-hints.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="early-hints-helpers.sub.js"></script>
+<body>
+<script>
+promise_test(async (t) => {
+ const params = new URLSearchParams(window.location.search);
+
+ const preload_before_redirect = params.get("preload-before-redirect");
+ await fetchScript(preload_before_redirect);
+ assert_true(isPreloadedByEarlyHints(preload_before_redirect),
+ "Early hints before redirect should preload.");
+
+ const preload_after_redirect = params.get("preload-after-redirect");
+ await fetchScript(preload_after_redirect);
+ assert_false(isPreloadedByEarlyHints(preload_after_redirect),
+ "Early hints after same origin redirect should be ignored.");
+}, "Early hints -> same origin redirect -> early hints -> final response.");
+</script>
+</body>
diff --git a/testing/web-platform/tests/loading/early-hints/resources/redirect-same-origin.html b/testing/web-platform/tests/loading/early-hints/resources/redirect-same-origin.html
new file mode 100644
index 0000000000..6a2246a2ac
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/redirect-same-origin.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="early-hints-helpers.sub.js"></script>
+<body>
+<script>
+promise_test(async (t) => {
+ const params = new URLSearchParams(window.location.search);
+ const preload_url = params.get("preload-url");
+ await fetchScript(preload_url);
+ assert_true(isPreloadedByEarlyHints(preload_url));
+}, "Redirect to the same origin keeps early hints preload");
+</script>
+</body>
diff --git a/testing/web-platform/tests/loading/early-hints/resources/redirect-with-early-hints.h2.py b/testing/web-platform/tests/loading/early-hints/resources/redirect-with-early-hints.h2.py
new file mode 100644
index 0000000000..e501d85a6b
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/redirect-with-early-hints.h2.py
@@ -0,0 +1,20 @@
+def handle_headers(frame, request, response):
+ preload_url = request.GET.first(b"preload-url").decode()
+ link_header_value = "<{}>; rel=preload; as=script".format(preload_url)
+
+ early_hints = [
+ (b":status", b"103"),
+ (b"link", link_header_value),
+ ]
+ response.writer.write_raw_header_frame(headers=early_hints,
+ end_headers=True)
+
+ redirect_url = request.GET.first(b"redirect-url").decode()
+ location = "{}?preload-url={}".format(redirect_url, preload_url)
+ response.status = 302
+ response.headers["location"] = location
+ response.write_status_headers()
+
+
+def main(request, response):
+ pass
diff --git a/testing/web-platform/tests/loading/early-hints/resources/referrer-policy-test-loader.h2.py b/testing/web-platform/tests/loading/early-hints/resources/referrer-policy-test-loader.h2.py
new file mode 100644
index 0000000000..901d64a01f
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/referrer-policy-test-loader.h2.py
@@ -0,0 +1,39 @@
+import os
+import time
+
+
+def handle_headers(frame, request, response):
+ headers = []
+ referrer_policy = request.GET.first(b"referrer-policy")
+ headers.append((b"referrer-policy", referrer_policy))
+
+ preload_url = request.GET.first(b"same-origin-preload-url").decode()
+ link_header_value = "<{}>; rel=preload; as=script".format(preload_url)
+ headers.append((b"link", link_header_value))
+ preload_url = request.GET.first(b"cross-origin-preload-url").decode()
+ link_header_value = "<{}>; rel=preload; as=script".format(preload_url)
+ headers.append((b"link", link_header_value))
+
+ # Send a 103 response.
+ early_hints = [(b":status", b"103")]
+ for header in headers:
+ early_hints.append(header)
+ response.writer.write_raw_header_frame(headers=early_hints,
+ end_headers=True)
+
+ # Simulate the response generation is taking time.
+ time.sleep(0.2)
+
+ response.status = 200
+ response.headers["content-type"] = "text/html"
+ for (name, value) in headers:
+ response.headers[name] = value
+ response.write_status_headers()
+
+
+def main(request, response):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ file_path = os.path.join(current_dir, "referrer-policy-test.html")
+ with open(file_path, "r") as f:
+ test_content = f.read()
+ response.writer.write_data(item=test_content, last=True)
diff --git a/testing/web-platform/tests/loading/early-hints/resources/referrer-policy-test.html b/testing/web-platform/tests/loading/early-hints/resources/referrer-policy-test.html
new file mode 100644
index 0000000000..d0389c2e11
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/referrer-policy-test.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="early-hints-helpers.sub.js"></script>
+<body>
+<script>
+const SEARCH_PARAMS = new URLSearchParams(window.location.search);
+const REFERRER_POLICY = SEARCH_PARAMS.get("referrer-policy");
+
+async function get_fetch_timing_and_headers(url_string) {
+ const url = new URL(url_string);
+ const id = url.searchParams.get("id");
+ if (id === null) {
+ throw new Error(`"${url.href}" does not contain id parameter`);
+ }
+ const response = await fetch(`${url.origin}/loading/early-hints/resources/get-fetch-timing-and-headers.h2.py?id=${id}`);
+ const json = await response.json();
+ return json;
+}
+
+function get_expected_referrer(is_same_origin) {
+ const full = window.location.href;
+ const origin = self.origin + "/";
+ // There is no support for security level related policies such as
+ // "no-referrer-when-downgrade" since the test is available only on HTTP/2.
+ switch (REFERRER_POLICY) {
+ case "no-referrer":
+ return undefined;
+ case "origin":
+ return origin;
+ case "origin-when-cross-origin":
+ return is_same_origin ? full : origin;
+ case "same-origin":
+ return is_same_origin ? full : undefined;
+ case "unsafe-url":
+ return full;
+ default:
+ throw new Error(`Unsupported referrer policy: ${REFERRER_POLICY}`);
+ }
+}
+
+async function check_referrer(url, expected_referrer) {
+ await fetchScript(url);
+
+ const { headers } = await get_fetch_timing_and_headers(url);
+ assert_equals(headers["referer"], expected_referrer);
+
+ const name = new URL(url, window.location);
+ assert_true(isPreloadedByEarlyHints(name));
+}
+
+promise_test(async (t) => {
+ const same_origin_preload_url = SEARCH_PARAMS.get("same-origin-preload-url");
+ const same_origin_expected = get_expected_referrer(true);
+ await check_referrer(same_origin_preload_url, same_origin_expected);
+
+ const cross_origin_preload_url = SEARCH_PARAMS.get("cross-origin-preload-url");
+ const cross_origin_expected = get_expected_referrer(false);
+ await check_referrer(cross_origin_preload_url, cross_origin_expected);
+}, `Referrer policy: ${REFERRER_POLICY}`);
+</script>
+</body>
diff --git a/testing/web-platform/tests/loading/early-hints/resources/resume-delayed-js.h2.py b/testing/web-platform/tests/loading/early-hints/resources/resume-delayed-js.h2.py
new file mode 100644
index 0000000000..132f038ac9
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/resume-delayed-js.h2.py
@@ -0,0 +1,10 @@
+def main(request, response):
+ id = request.GET.first(b"id")
+ url_dir = u'/'.join(request.url_parts.path.split(u'/')[:-1]) + u'/'
+ request.server.stash.put(id, True, url_dir)
+ headers = [
+ ("Content-Type", "text/plain"),
+ ("Access-Control-Allow-Origin", "*"),
+ ]
+ body = "OK"
+ return (200, "OK"), headers, body
diff --git a/testing/web-platform/tests/loading/early-hints/resources/square.png b/testing/web-platform/tests/loading/early-hints/resources/square.png
new file mode 100644
index 0000000000..01c9666a8d
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/square.png
Binary files differ
diff --git a/testing/web-platform/tests/loading/early-hints/resources/square.png.headers b/testing/web-platform/tests/loading/early-hints/resources/square.png.headers
new file mode 100644
index 0000000000..175cdf8046
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/square.png.headers
@@ -0,0 +1 @@
+cache-control: max-age=600
diff --git a/testing/web-platform/tests/loading/early-hints/resources/utils.py b/testing/web-platform/tests/loading/early-hints/resources/utils.py
new file mode 100644
index 0000000000..f24638ab3a
--- /dev/null
+++ b/testing/web-platform/tests/loading/early-hints/resources/utils.py
@@ -0,0 +1,45 @@
+import datetime
+import json
+import time
+
+
+def _url_dir(request):
+ return u'/'.join(request.url_parts.path.split(u'/')[:-1]) + u'/'
+
+
+def store_request_timing_and_headers(request):
+ """Store the current timestamp and request's headers in the stash object of
+ the server. The request must a GET request and must have the "id" parameter.
+ """
+ id = request.GET.first(b"id")
+ timestamp = datetime.datetime.now().timestamp()
+
+ value = {
+ "timestamp": timestamp,
+ "headers": request.raw_headers,
+ }
+
+ url_dir = _url_dir(request)
+ request.server.stash.put(id, value, url_dir)
+
+
+def get_request_timing_and_headers(request, id=None):
+ """Get previously stored timestamp and request headers associated with the
+ given "id". When "id" is not given the id is retrieved from "request".
+ """
+ if id is None:
+ id = request.GET.first(b"id")
+ url_dir = _url_dir(request)
+ item = request.server.stash.take(id, url_dir)
+ if not item:
+ return None
+ return json.dumps(item)
+
+
+def wait_for_preload_to_finish(request, id):
+ """Wait until a preload associated with "id" is sent."""
+ while True:
+ if get_request_timing_and_headers(request, id):
+ break
+ time.sleep(0.1)
+ time.sleep(0.1)