summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/fetch/connection-pool
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/fetch/connection-pool
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/fetch/connection-pool')
-rw-r--r--testing/web-platform/tests/fetch/connection-pool/network-partition-key.html264
-rw-r--r--testing/web-platform/tests/fetch/connection-pool/resources/network-partition-about-blank-checker.html35
-rw-r--r--testing/web-platform/tests/fetch/connection-pool/resources/network-partition-checker.html30
-rw-r--r--testing/web-platform/tests/fetch/connection-pool/resources/network-partition-iframe-checker.html22
-rw-r--r--testing/web-platform/tests/fetch/connection-pool/resources/network-partition-key.js47
-rw-r--r--testing/web-platform/tests/fetch/connection-pool/resources/network-partition-key.py130
-rw-r--r--testing/web-platform/tests/fetch/connection-pool/resources/network-partition-worker-checker.html24
-rw-r--r--testing/web-platform/tests/fetch/connection-pool/resources/network-partition-worker.js15
8 files changed, 567 insertions, 0 deletions
diff --git a/testing/web-platform/tests/fetch/connection-pool/network-partition-key.html b/testing/web-platform/tests/fetch/connection-pool/network-partition-key.html
new file mode 100644
index 0000000000..60a784cd84
--- /dev/null
+++ b/testing/web-platform/tests/fetch/connection-pool/network-partition-key.html
@@ -0,0 +1,264 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Connection partitioning by site</title>
+ <meta name="help" href="https://fetch.spec.whatwg.org/#network-partition-keys">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/utils.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+</head>
+<body>
+<!-- Used to open about:blank tabs from opaque origins -->
+<iframe id="iframe0" sandbox="allow-popups allow-scripts allow-popups-to-escape-sandbox"></iframe>
+<iframe id="iframe1" sandbox="allow-popups allow-scripts allow-popups-to-escape-sandbox"></iframe>
+<script>
+const host = get_host_info();
+
+// These two origins must correspond to different sites for this test to pass.
+const POPUP_ORIGINS = [
+ host.ORIGIN,
+ host.HTTP_NOTSAMESITE_ORIGIN
+];
+
+// This origin should ideally correspond to a different site from the two above, but the
+// tests will still pass if it matches the site of one of the other two origins.
+const OTHER_ORIGIN = host.REMOTE_ORIGIN;
+
+// Except for the csp_sandbox and about:blanks, each test opens up two windows, one at
+// POPUP_ORIGINS[0], one at POPUP_ORIGINS[1], and has them request subresources from
+// subresource_origin. All requests (HTML, JS, and fetch requests) for each window go
+// through network-partition-key.py and have a partition_id parameter, which is used
+// to check if any request for one window uses the same socket as a request for the
+// other window.
+//
+// Whenever requests from the two different popup windows use the same connection, the
+// fetch requests all start returning 400 errors, but other requests will continue to
+// succeed, to make for clearer errors.
+//
+// include_credentials indicates whether the fetch requests use credentials or not,
+// which is interesting as uncredentialed sockets have separate connection pools.
+const tests = [
+ {
+ name: 'With credentials',
+ subresource_origin: POPUP_ORIGINS[0],
+ include_credentials: true,
+ popup_params: [
+ {type: 'main_frame'},
+ {type: 'main_frame'}
+ ]
+ },
+ {
+ name: 'Without credentials',
+ subresource_origin: POPUP_ORIGINS[0],
+ include_credentials: false,
+ popup_params: [
+ {type: 'main_frame'},
+ {type: 'main_frame'}
+ ]
+ },
+ {
+ name: 'Cross-site resources with credentials',
+ subresource_origin: OTHER_ORIGIN,
+ include_credentials: true,
+ popup_params: [
+ {type: 'main_frame'},
+ {type: 'main_frame'}
+ ]
+ },
+ {
+ name: 'Cross-site resources without credentials',
+ subresource_origin: OTHER_ORIGIN,
+ include_credentials: false,
+ popup_params: [
+ {type: 'main_frame'},
+ {type: 'main_frame'}
+ ]
+ },
+ {
+ name: 'Iframes',
+ subresource_origin: OTHER_ORIGIN,
+ include_credentials: true,
+ popup_params: [
+ {
+ type: 'iframe',
+ iframe_origin: OTHER_ORIGIN
+ },
+ {
+ type: 'iframe',
+ iframe_origin: OTHER_ORIGIN
+ }
+ ]
+ },
+ {
+ name: 'Workers',
+ subresource_origin: POPUP_ORIGINS[0],
+ include_credentials: true,
+ popup_params: [
+ {type: 'worker'},
+ {type: 'worker'}
+ ]
+ },
+ {
+ name: 'Workers with cross-site resources',
+ subresource_origin: OTHER_ORIGIN,
+ include_credentials: true,
+ popup_params: [
+ {type: 'worker'},
+ {type: 'worker'}
+ ]
+ },
+ {
+ name: 'CSP sandbox',
+ subresource_origin: POPUP_ORIGINS[0],
+ include_credentials: true,
+ popup_params: [
+ {type: 'csp_sandbox'},
+ {type: 'csp_sandbox'}
+ ]
+ },
+ {
+ name: 'about:blank from opaque origin iframe',
+ subresource_origin: OTHER_ORIGIN,
+ include_credentials: true,
+ popup_params: [
+ {type: 'opaque_about_blank'},
+ {type: 'opaque_about_blank'}
+ ]
+ },
+];
+
+const BASE_PATH = window.location.pathname.replace(/\/[^\/]*$/, '/');
+
+function create_script_url(origin, uuid, partition_id, dispatch) {
+ return `${origin}${BASE_PATH}resources/network-partition-key.py?uuid=${uuid}&partition_id=${partition_id}&dispatch=${dispatch}`
+}
+
+function run_test(test) {
+ var uuid = token();
+
+ // Used to track the opened popup windows, so they can be closed at the end of the test.
+ // They could be closed immediately after use, but safest to keep them open, as browsers
+ // could use closing a window as a hint to close idle sockets that the window used.
+ var popup_windows = [];
+
+ // Creates a popup window at |url| and waits for a test result. Returns a promise.
+ function create_popup_and_wait_for_result(url) {
+ return new Promise(function(resolve, reject) {
+ popup_windows.push(window.open(url));
+ // Listen for the result
+ function message_listener(event) {
+ if (event.data.result === 'success') {
+ resolve();
+ } else if (event.data.result === 'error') {
+ reject(event.data.details);
+ } else {
+ reject('Unexpected message.');
+ }
+ }
+ window.addEventListener('message', message_listener, {once: 'true'});
+ });
+ }
+
+ // Navigates iframe to url and waits for a test result. Returns a promise.
+ function navigate_iframe_and_wait_for_result(iframe, url) {
+ return new Promise(function(resolve, reject) {
+ iframe.src = url;
+ // Listen for the result
+ function message_listener(event) {
+ if (event.data.result === 'success') {
+ resolve();
+ } else if (event.data.result === 'error') {
+ reject(event.data.details);
+ } else {
+ reject('Unexpected message.');
+ }
+ }
+ window.addEventListener('message', message_listener, {once: 'true'});
+ });
+ }
+
+ function make_test_function(test, index) {
+ var popup_params = test.popup_params[index];
+ return function() {
+ var popup_path;
+ var additional_url_params = '';
+ var origin = POPUP_ORIGINS[index];
+ var partition_id = POPUP_ORIGINS[index];
+ if (popup_params.type == 'main_frame') {
+ popup_path = 'resources/network-partition-checker.html';
+ } else if (popup_params.type == 'iframe') {
+ popup_path = 'resources/network-partition-iframe-checker.html';
+ additional_url_params = `&other_origin=${popup_params.iframe_origin}`;
+ } else if (popup_params.type == 'worker') {
+ popup_path = 'resources/network-partition-worker-checker.html';
+ // The origin of the dedicated worker must mutch the page that loads it.
+ additional_url_params = `&other_origin=${POPUP_ORIGINS[index]}`;
+ } else if (popup_params.type == 'csp_sandbox') {
+ // For the Content-Security-Policy sandbox test, all requests are from the same origin, but
+ // the origin should be treated as an opaque origin, so sockets should not be reused.
+ origin = test.subresource_origin;
+ partition_id = index;
+ popup_path = 'resources/network-partition-checker.html';
+ // Don't check partition of root document, since the document isn't sandboxed until the
+ // root document is fetched.
+ additional_url_params = '&sandbox=true&nocheck_partition=true'
+ } else if (popup_params.type=='opaque_about_blank') {
+ popup_path = 'resources/network-partition-about-blank-checker.html';
+ } else if (popup_params.type == 'iframe') {
+ throw 'Unrecognized popup_params.type.';
+ }
+ var url = create_script_url(origin, uuid, partition_id, 'fetch_file');
+ url += `&subresource_origin=${test.subresource_origin}`
+ url += `&include_credentials=${test.include_credentials}`
+ url += `&path=${BASE_PATH.substring(1)}${popup_path}`;
+ url += additional_url_params;
+
+ if (popup_params.type=='opaque_about_blank') {
+ return navigate_iframe_and_wait_for_result(iframe = document.getElementById('iframe' + index), url);
+ }
+
+ return create_popup_and_wait_for_result(url);
+ }
+ }
+
+ // Takes a Promise, and cleans up state when the promise has completed, successfully or not, re-throwing
+ // any exception from the passed in Promise.
+ async function clean_up_when_done(promise) {
+ var error;
+ try {
+ await promise;
+ } catch (e) {
+ error = e;
+ }
+
+ popup_windows.map(function (win) { win.close(); });
+
+ try {
+ var cleanup_url = create_script_url(host.ORIGIN, uuid, host.ORIGIN, 'clean_up');
+ var response = await fetch(cleanup_url, {credentials: 'omit', mode: 'cors'});
+ assert_equals(await response.text(), 'cleanup complete', `Sever state cleanup failed`);
+ } catch (e) {
+ // Prefer error from the passed in Promise over errors from the fetch request to clean up server state.
+ error = error || e;
+ }
+ if (error)
+ throw error;
+ }
+
+ return clean_up_when_done(
+ make_test_function(test, 0)()
+ .then(make_test_function(test, 1)));
+}
+
+tests.forEach(function (test) {
+ promise_test(
+ function() { return run_test(test); },
+ test.name);
+})
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fetch/connection-pool/resources/network-partition-about-blank-checker.html b/testing/web-platform/tests/fetch/connection-pool/resources/network-partition-about-blank-checker.html
new file mode 100644
index 0000000000..7a8b613237
--- /dev/null
+++ b/testing/web-platform/tests/fetch/connection-pool/resources/network-partition-about-blank-checker.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>about:blank Network Partition Checker</title>
+ <meta name="help" href="https://fetch.spec.whatwg.org/#network-partition-keys">
+ <meta name="timeout" content="normal">
+</head>
+<body>
+<script>
+ async function fetch_and_reply() {
+ // Load about:blank in a new tab, and inject the network partition checking code into it.
+ var win;
+ try {
+ win = window.open();
+ var url = 'SUBRESOURCE_PREFIX:&dispatch=fetch_file&path=fetch/connection-pool/resources/network-partition-checker.html&sandbox=true';
+ var response = await fetch(url, {credentials: 'omit', mode: 'cors'});
+ win.document.write(await response.text());
+ } catch (e) {
+ win.close();
+ window.parent.postMessage({result: 'error', details: e.message}, '*');
+ return;
+ }
+
+ // Listen for first message from the new window and pass it back to the parent.
+ function message_listener(event) {
+ window.parent.postMessage(event.data, '*');
+ win.close();
+ }
+ window.addEventListener('message', message_listener, {once: true});
+ }
+ fetch_and_reply();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fetch/connection-pool/resources/network-partition-checker.html b/testing/web-platform/tests/fetch/connection-pool/resources/network-partition-checker.html
new file mode 100644
index 0000000000..b058f61124
--- /dev/null
+++ b/testing/web-platform/tests/fetch/connection-pool/resources/network-partition-checker.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Network Partition Checker</title>
+ <meta name="help" href="https://fetch.spec.whatwg.org/#network-partition-keys">
+ <meta name="timeout" content="normal">
+ <script src="SUBRESOURCE_PREFIX:&dispatch=fetch_file&path=common/utils.js"></script>
+ <script src="SUBRESOURCE_PREFIX:&dispatch=fetch_file&path=resources/testharness.js"></script>
+ <script src="SUBRESOURCE_PREFIX:&dispatch=fetch_file&path=fetch/connection-pool/resources/network-partition-key.js"></script>
+</head>
+<body>
+<script>
+ async function fetch_and_reply() {
+ // If this is a top level window, report to the opener. Otherwise, this is an iframe,
+ // so report to the parent.
+ var report_to = window.opener;
+ if (!report_to)
+ report_to = window.parent;
+ try {
+ await check_partition_ids();
+ report_to.postMessage({result: 'success'}, '*');
+ } catch (e) {
+ report_to.postMessage({result: 'error', details: e.message}, '*');
+ }
+ }
+ fetch_and_reply();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fetch/connection-pool/resources/network-partition-iframe-checker.html b/testing/web-platform/tests/fetch/connection-pool/resources/network-partition-iframe-checker.html
new file mode 100644
index 0000000000..f76ed18447
--- /dev/null
+++ b/testing/web-platform/tests/fetch/connection-pool/resources/network-partition-iframe-checker.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Iframe Network Partition Checker</title>
+ <meta name="help" href="https://fetch.spec.whatwg.org/#network-partition-keys">
+ <meta name="timeout" content="normal">
+ <script src="SUBRESOURCE_PREFIX:&dispatch=fetch_file&path=common/utils.js"></script>
+ <script src="SUBRESOURCE_PREFIX:&dispatch=fetch_file&path=resources/testharness.js"></script>
+ <script src="SUBRESOURCE_PREFIX:&dispatch=fetch_file&path=fetch/connection-pool/resources/network-partition-key.js"></script>
+</head>
+<body>
+<script>
+ // Listen for first message from the iframe, and pass it back to the opener.
+ function message_listener(event) {
+ window.opener.postMessage(event.data, '*');
+ }
+ window.addEventListener('message', message_listener, {once: 'true'});
+</script>
+<iframe src="SUBRESOURCE_PREFIX:&dispatch=fetch_file&path=fetch/connection-pool/resources/network-partition-checker.html"></iframe>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fetch/connection-pool/resources/network-partition-key.js b/testing/web-platform/tests/fetch/connection-pool/resources/network-partition-key.js
new file mode 100644
index 0000000000..bd66109380
--- /dev/null
+++ b/testing/web-platform/tests/fetch/connection-pool/resources/network-partition-key.js
@@ -0,0 +1,47 @@
+// Runs multiple fetches that validate connections see only a single partition_id.
+// Requests are run in parallel so that they use multiple connections to maximize the
+// chance of exercising all matching connections in the connection pool. Only returns
+// once all requests have completed to make cleaning up server state non-racy.
+function check_partition_ids(location) {
+ const NUM_FETCHES = 20;
+
+ var base_url = 'SUBRESOURCE_PREFIX:&dispatch=check_partition';
+
+ // Not a perfect parse of the query string, but good enough for this test.
+ var include_credentials = base_url.search('include_credentials=true') != -1;
+ var exclude_credentials = base_url.search('include_credentials=false') != -1;
+ if (include_credentials != !exclude_credentials)
+ throw new Exception('Credentials mode not specified');
+
+
+ // Run NUM_FETCHES in parallel.
+ var fetches = [];
+ for (i = 0; i < NUM_FETCHES; ++i) {
+ var fetch_params = {
+ credentials: 'omit',
+ mode: 'cors',
+ headers: {
+ 'Header-To-Force-CORS': 'cors'
+ },
+ };
+
+ // Use a unique URL for each request, in case the caching layer serializes multiple
+ // requests for the same URL.
+ var url = `${base_url}&${token()}`;
+
+ fetches.push(fetch(url, fetch_params).then(
+ function (response) {
+ return response.text().then(function(text) {
+ assert_equals(text, 'ok', `Socket unexpectedly reused`);
+ });
+ }));
+ }
+
+ // Wait for all promises to complete.
+ return Promise.allSettled(fetches).then(function (results) {
+ results.forEach(function (result) {
+ if (result.status != 'fulfilled')
+ throw result.reason;
+ });
+ });
+}
diff --git a/testing/web-platform/tests/fetch/connection-pool/resources/network-partition-key.py b/testing/web-platform/tests/fetch/connection-pool/resources/network-partition-key.py
new file mode 100644
index 0000000000..32fe4999b7
--- /dev/null
+++ b/testing/web-platform/tests/fetch/connection-pool/resources/network-partition-key.py
@@ -0,0 +1,130 @@
+import mimetypes
+import os
+
+from wptserve.utils import isomorphic_decode, isomorphic_encode
+
+# Test server that tracks the last partition_id was used with each connection for each uuid, and
+# lets consumers query if multiple different partition_ids have been been used for any socket.
+#
+# Server assumes that ports aren't reused, so a client address and a server port uniquely identify
+# a connection. If that constraint is ever violated, the test will be flaky. No sockets being
+# closed for the duration of the test is sufficient to ensure that, though even if sockets are
+# closed, the OS should generally prefer to use new ports for new connections, if any are
+# available.
+def main(request, response):
+ response.headers.set(b"Cache-Control", b"no-store")
+ dispatch = request.GET.first(b"dispatch", None)
+ uuid = request.GET.first(b"uuid", None)
+ partition_id = request.GET.first(b"partition_id", None)
+
+ if not uuid or not dispatch or not partition_id:
+ return simple_response(request, response, 404, b"Not found", b"Invalid query parameters")
+
+ # Unless nocheck_partition is true, check partition_id against server_state, and update server_state.
+ stash = request.server.stash
+ test_failed = False
+ request_count = 0;
+ connection_count = 0;
+ if request.GET.first(b"nocheck_partition", None) != b"True":
+ # Need to grab the lock to access the Stash, since requests are made in parallel.
+ with stash.lock:
+ # Don't use server hostname here, since H2 allows multiple hosts to reuse a connection.
+ # Server IP is not currently available, unfortunately.
+ address_key = isomorphic_encode(str(request.client_address) + u"|" + str(request.url_parts.port))
+ server_state = stash.take(uuid) or {b"test_failed": False,
+ b"request_count": 0, b"connection_count": 0}
+ request_count = server_state[b"request_count"]
+ request_count += 1
+ server_state[b"request_count"] = request_count
+ if address_key in server_state:
+ if server_state[address_key] != partition_id:
+ server_state[b"test_failed"] = True
+ else:
+ connection_count = server_state[b"connection_count"]
+ connection_count += 1
+ server_state[b"connection_count"] = connection_count
+ server_state[address_key] = partition_id
+ test_failed = server_state[b"test_failed"]
+ stash.put(uuid, server_state)
+
+ origin = request.headers.get(b"Origin")
+ if origin:
+ response.headers.set(b"Access-Control-Allow-Origin", origin)
+ response.headers.set(b"Access-Control-Allow-Credentials", b"true")
+
+ if request.method == u"OPTIONS":
+ return handle_preflight(request, response)
+
+ if dispatch == b"fetch_file":
+ return handle_fetch_file(request, response, partition_id, uuid)
+
+ if dispatch == b"check_partition":
+ status = request.GET.first(b"status", 200)
+ if test_failed:
+ return simple_response(request, response, status, b"OK", b"Multiple partition IDs used on a socket")
+ body = b"ok"
+ if request.GET.first(b"addcounter", False):
+ body += (". Request was sent " + str(request_count) + " times. " +
+ str(connection_count) + " connections were created.").encode('utf-8')
+ return simple_response(request, response, status, b"OK", body)
+
+ if dispatch == b"clean_up":
+ stash.take(uuid)
+ if test_failed:
+ return simple_response(request, response, 200, b"OK", b"Test failed, but cleanup completed.")
+ return simple_response(request, response, 200, b"OK", b"cleanup complete")
+
+ return simple_response(request, response, 404, b"Not Found", b"Unrecognized dispatch parameter: " + dispatch)
+
+def handle_preflight(request, response):
+ response.status = (200, b"OK")
+ response.headers.set(b"Access-Control-Allow-Methods", b"GET")
+ response.headers.set(b"Access-Control-Allow-Headers", b"header-to-force-cors")
+ response.headers.set(b"Access-Control-Max-Age", b"86400")
+ return b"Preflight request"
+
+def simple_response(request, response, status_code, status_message, body, content_type=b"text/plain"):
+ response.status = (status_code, status_message)
+ response.headers.set(b"Content-Type", content_type)
+ return body
+
+def handle_fetch_file(request, response, partition_id, uuid):
+ subresource_origin = request.GET.first(b"subresource_origin", None)
+ rel_path = request.GET.first(b"path", None)
+
+ # This needs to be passed on to subresources so they all have access to it.
+ include_credentials = request.GET.first(b"include_credentials", None)
+ if not subresource_origin or not rel_path or not include_credentials:
+ return simple_response(request, response, 404, b"Not found", b"Invalid query parameters")
+
+ cur_path = os.path.realpath(isomorphic_decode(__file__))
+ base_path = os.path.abspath(os.path.join(os.path.dirname(cur_path), os.pardir, os.pardir, os.pardir))
+ path = os.path.abspath(os.path.join(base_path, isomorphic_decode(rel_path)))
+
+ # Basic security check.
+ if not path.startswith(base_path):
+ return simple_response(request, response, 404, b"Not found", b"Invalid path")
+
+ sandbox = request.GET.first(b"sandbox", None)
+ if sandbox == b"true":
+ response.headers.set(b"Content-Security-Policy", b"sandbox allow-scripts")
+
+ file = open(path, mode="rb")
+ body = file.read()
+ file.close()
+
+ subresource_path = b"/" + isomorphic_encode(os.path.relpath(isomorphic_decode(__file__), base_path)).replace(b'\\', b'/')
+ subresource_params = b"?partition_id=" + partition_id + b"&uuid=" + uuid + b"&subresource_origin=" + subresource_origin + b"&include_credentials=" + include_credentials
+ body = body.replace(b"SUBRESOURCE_PREFIX:", subresource_origin + subresource_path + subresource_params)
+
+ other_origin = request.GET.first(b"other_origin", None)
+ if other_origin:
+ body = body.replace(b"OTHER_PREFIX:", other_origin + subresource_path + subresource_params)
+
+ mimetypes.init()
+ mimetype_pair = mimetypes.guess_type(path)
+ mimetype = mimetype_pair[0]
+
+ if mimetype == None or mimetype_pair[1] != None:
+ return simple_response(request, response, 500, b"Server Error", b"Unknown MIME type")
+ return simple_response(request, response, 200, b"OK", body, mimetype)
diff --git a/testing/web-platform/tests/fetch/connection-pool/resources/network-partition-worker-checker.html b/testing/web-platform/tests/fetch/connection-pool/resources/network-partition-worker-checker.html
new file mode 100644
index 0000000000..e6b7ea7673
--- /dev/null
+++ b/testing/web-platform/tests/fetch/connection-pool/resources/network-partition-worker-checker.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Worker Network Partition Checker</title>
+ <meta name="help" href="https://fetch.spec.whatwg.org/#network-partition-keys">
+ <meta name="timeout" content="normal">
+ <script src="SUBRESOURCE_PREFIX:&dispatch=fetch_file&path=common/utils.js"></script>
+ <script src="SUBRESOURCE_PREFIX:&dispatch=fetch_file&path=resources/testharness.js"></script>
+ <script src="SUBRESOURCE_PREFIX:&dispatch=fetch_file&path=fetch/connection-pool/resources/network-partition-key.js"></script>
+</head>
+<body>
+<script>
+ // Workers must be same origin as the page loading them, but it's simpler to reuse the
+ // OTHER_PREFIX mechanism in the Python code than to craft the URL in Javascript here.
+ var worker = new Worker('OTHER_PREFIX:&dispatch=fetch_file&path=fetch/connection-pool/resources/network-partition-worker.js');
+ function message_listener(event) {
+ window.opener.postMessage(event.data, '*');
+ worker.terminate();
+ }
+ worker.addEventListener('message', message_listener);
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fetch/connection-pool/resources/network-partition-worker.js b/testing/web-platform/tests/fetch/connection-pool/resources/network-partition-worker.js
new file mode 100644
index 0000000000..1745edfacb
--- /dev/null
+++ b/testing/web-platform/tests/fetch/connection-pool/resources/network-partition-worker.js
@@ -0,0 +1,15 @@
+// This tests the partition key of fetches to subresouce_origin made by the worker and
+// imported scripts from subresource_origin.
+importScripts('SUBRESOURCE_PREFIX:&dispatch=fetch_file&path=common/utils.js');
+importScripts('SUBRESOURCE_PREFIX:&dispatch=fetch_file&path=resources/testharness.js');
+importScripts('SUBRESOURCE_PREFIX:&dispatch=fetch_file&path=fetch/connection-pool/resources/network-partition-key.js');
+
+async function fetch_and_reply() {
+ try {
+ await check_partition_ids();
+ self.postMessage({result: 'success'});
+ } catch (e) {
+ self.postMessage({result: 'error', details: e.message});
+ }
+}
+fetch_and_reply();