diff options
Diffstat (limited to 'testing/web-platform/tests/loading/early-hints/resources')
51 files changed, 1363 insertions, 0 deletions
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 Binary files differnew file mode 100644 index 0000000000..01c9666a8d --- /dev/null +++ b/testing/web-platform/tests/loading/early-hints/resources/square.png 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) |