summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/fetch/private-network-access/resources/support.sub.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/fetch/private-network-access/resources/support.sub.js
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/fetch/private-network-access/resources/support.sub.js')
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/resources/support.sub.js857
1 files changed, 857 insertions, 0 deletions
diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/support.sub.js b/testing/web-platform/tests/fetch/private-network-access/resources/support.sub.js
new file mode 100644
index 0000000000..46a9d9e076
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/resources/support.sub.js
@@ -0,0 +1,857 @@
+// Creates a new iframe in `doc`, calls `func` on it and appends it as a child
+// of `doc`.
+// Returns a promise that resolves to the iframe once loaded (successfully or
+// not).
+// The iframe is removed from `doc` once test `t` is done running.
+//
+// NOTE: There exists no interoperable way to check whether an iframe failed to
+// load, so this should only be used when the iframe is expected to load. It
+// also means we cannot wire the iframe's `error` event to a promise
+// rejection. See: https://github.com/whatwg/html/issues/125
+function appendIframeWith(t, doc, func) {
+ return new Promise(resolve => {
+ const child = doc.createElement("iframe");
+ t.add_cleanup(() => child.remove());
+
+ child.addEventListener("load", () => resolve(child), { once: true });
+ func(child);
+ doc.body.appendChild(child);
+ });
+}
+
+// Appends a child iframe to `doc` sourced from `src`.
+//
+// See `appendIframeWith()` for more details.
+function appendIframe(t, doc, src) {
+ return appendIframeWith(t, doc, child => { child.src = src; });
+}
+
+// Registers an event listener that will resolve this promise when this
+// window receives a message posted to it.
+//
+// `options` has the following shape:
+//
+// {
+// source: If specified, this function waits for the first message from the
+// given source only, ignoring other messages.
+//
+// filter: If specified, this function calls `filter` on each incoming
+// message, and resolves iff it returns true.
+// }
+//
+function futureMessage(options) {
+ return new Promise(resolve => {
+ window.addEventListener("message", (e) => {
+ if (options?.source && options.source !== e.source) {
+ return;
+ }
+
+ if (options?.filter && !options.filter(e.data)) {
+ return;
+ }
+
+ resolve(e.data);
+ });
+ });
+};
+
+// Like `promise_test()`, but executes tests in parallel like `async_test()`.
+//
+// Cribbed from COEP tests.
+function promise_test_parallel(promise, description) {
+ async_test(test => {
+ promise(test)
+ .then(() => test.done())
+ .catch(test.step_func(error => { throw error; }));
+ }, description);
+};
+
+async function postMessageAndAwaitReply(target, message) {
+ const reply = futureMessage({ source: target });
+ target.postMessage(message, "*");
+ return await reply;
+}
+
+// Maps protocol (without the trailing colon) and address space to port.
+const SERVER_PORTS = {
+ "http": {
+ "local": {{ports[http][0]}},
+ "private": {{ports[http-private][0]}},
+ "public": {{ports[http-public][0]}},
+ },
+ "https": {
+ "local": {{ports[https][0]}},
+ "other-local": {{ports[https][1]}},
+ "private": {{ports[https-private][0]}},
+ "public": {{ports[https-public][0]}},
+ },
+ "ws": {
+ "local": {{ports[ws][0]}},
+ },
+ "wss": {
+ "local": {{ports[wss][0]}},
+ },
+};
+
+// A `Server` is a web server accessible by tests. It has the following shape:
+//
+// {
+// addressSpace: the IP address space of the server ("local", "private" or
+// "public"),
+// name: a human-readable name for the server,
+// port: the port on which the server listens for connections,
+// protocol: the protocol (including trailing colon) spoken by the server,
+// }
+//
+// Constants below define the available servers, which can also be accessed
+// programmatically with `get()`.
+class Server {
+ // Maps the given `protocol` (without a trailing colon) and `addressSpace` to
+ // a server. Returns null if no such server exists.
+ static get(protocol, addressSpace) {
+ const ports = SERVER_PORTS[protocol];
+ if (ports === undefined) {
+ return null;
+ }
+
+ const port = ports[addressSpace];
+ if (port === undefined) {
+ return null;
+ }
+
+ return {
+ addressSpace,
+ name: `${protocol}-${addressSpace}`,
+ port,
+ protocol: protocol + ':',
+ };
+ }
+
+ static HTTP_LOCAL = Server.get("http", "local");
+ static HTTP_PRIVATE = Server.get("http", "private");
+ static HTTP_PUBLIC = Server.get("http", "public");
+ static HTTPS_LOCAL = Server.get("https", "local");
+ static OTHER_HTTPS_LOCAL = Server.get("https", "other-local");
+ static HTTPS_PRIVATE = Server.get("https", "private");
+ static HTTPS_PUBLIC = Server.get("https", "public");
+ static WS_LOCAL = Server.get("ws", "local");
+ static WSS_LOCAL = Server.get("wss", "local");
+};
+
+// Resolves a URL relative to the current location, returning an absolute URL.
+//
+// `url` specifies the relative URL, e.g. "foo.html" or "http://foo.example".
+// `options`, if defined, should have the following shape:
+//
+// {
+// // Optional. Overrides the protocol of the returned URL.
+// protocol,
+//
+// // Optional. Overrides the port of the returned URL.
+// port,
+//
+// // Extra headers.
+// headers,
+//
+// // Extra search params.
+// searchParams,
+// }
+//
+function resolveUrl(url, options) {
+ const result = new URL(url, window.location);
+ if (options === undefined) {
+ return result;
+ }
+
+ const { port, protocol, headers, searchParams } = options;
+ if (port !== undefined) {
+ result.port = port;
+ }
+ if (protocol !== undefined) {
+ result.protocol = protocol;
+ }
+ if (headers !== undefined) {
+ const pipes = [];
+ for (key in headers) {
+ pipes.push(`header(${key},${headers[key]})`);
+ }
+ result.searchParams.append("pipe", pipes.join("|"));
+ }
+ if (searchParams !== undefined) {
+ for (key in searchParams) {
+ result.searchParams.append(key, searchParams[key]);
+ }
+ }
+
+ return result;
+}
+
+// Computes options to pass to `resolveUrl()` for a source document's URL.
+//
+// `server` identifies the server from which to load the document.
+// `treatAsPublic`, if set to true, specifies that the source document should
+// be artificially placed in the `public` address space using CSP.
+function sourceResolveOptions({ server, treatAsPublic }) {
+ const options = {...server};
+ if (treatAsPublic) {
+ options.headers = { "Content-Security-Policy": "treat-as-public-address" };
+ }
+ return options;
+}
+
+// Computes the URL of a preflight handler configured with the given options.
+//
+// `server` identifies the server from which to load the resource.
+// `behavior` specifies the behavior of the target server. It may contain:
+// - `preflight`: The result of calling one of `PreflightBehavior`'s methods.
+// - `response`: The result of calling one of `ResponseBehavior`'s methods.
+// - `redirect`: A URL to which the target should redirect GET requests.
+function preflightUrl({ server, behavior }) {
+ assert_not_equals(server, undefined, 'server');
+ const options = {...server};
+ if (behavior) {
+ const { preflight, response, redirect } = behavior;
+ options.searchParams = {
+ ...preflight,
+ ...response,
+ };
+ if (redirect !== undefined) {
+ options.searchParams.redirect = redirect;
+ }
+ }
+
+ return resolveUrl("resources/preflight.py", options);
+}
+
+// Methods generate behavior specifications for how `resources/preflight.py`
+// should behave upon receiving a preflight request.
+const PreflightBehavior = {
+ // The preflight response should fail with a non-2xx code.
+ failure: () => ({}),
+
+ // The preflight response should be missing CORS headers.
+ // `uuid` should be a UUID that uniquely identifies the preflight request.
+ noCorsHeader: (uuid) => ({
+ "preflight-uuid": uuid,
+ }),
+
+ // The preflight response should be missing PNA headers.
+ // `uuid` should be a UUID that uniquely identifies the preflight request.
+ noPnaHeader: (uuid) => ({
+ "preflight-uuid": uuid,
+ "preflight-headers": "cors",
+ }),
+
+ // The preflight response should succeed.
+ // `uuid` should be a UUID that uniquely identifies the preflight request.
+ success: (uuid) => ({
+ "preflight-uuid": uuid,
+ "preflight-headers": "cors+pna",
+ }),
+
+ optionalSuccess: (uuid) => ({
+ "preflight-uuid": uuid,
+ "preflight-headers": "cors+pna",
+ "is-preflight-optional": true,
+ }),
+
+ // The preflight response should succeed and allow service-worker header.
+ // `uuid` should be a UUID that uniquely identifies the preflight request.
+ serviceWorkerSuccess: (uuid) => ({
+ "preflight-uuid": uuid,
+ "preflight-headers": "cors+pna+sw",
+ }),
+
+ // The preflight response should succeed only if it is the first preflight.
+ // `uuid` should be a UUID that uniquely identifies the preflight request.
+ singlePreflight: (uuid) => ({
+ "preflight-uuid": uuid,
+ "preflight-headers": "cors+pna",
+ "expect-single-preflight": true,
+ }),
+
+ // The preflight response should succeed and allow origins and headers for
+ // navigations.
+ navigation: (uuid) => ({
+ "preflight-uuid": uuid,
+ "preflight-headers": "navigation",
+ }),
+};
+
+// Methods generate behavior specifications for how `resources/preflight.py`
+// should behave upon receiving a regular (non-preflight) request.
+const ResponseBehavior = {
+ // The response should succeed without CORS headers.
+ default: () => ({}),
+
+ // The response should succeed with CORS headers.
+ allowCrossOrigin: () => ({ "final-headers": "cors" }),
+};
+
+const FetchTestResult = {
+ SUCCESS: {
+ ok: true,
+ body: "success",
+ },
+ OPAQUE: {
+ ok: false,
+ type: "opaque",
+ body: "",
+ },
+ FAILURE: {
+ error: "TypeError: Failed to fetch",
+ },
+};
+
+// Runs a fetch test. Tries to fetch a given subresource from a given document.
+//
+// Main argument shape:
+//
+// {
+// // Optional. Passed to `sourceResolveOptions()`.
+// source,
+//
+// // Optional. Passed to `preflightUrl()`.
+// target,
+//
+// // Optional. Passed to `fetch()`.
+// fetchOptions,
+//
+// // Required. One of the values in `FetchTestResult`.
+// expected,
+// }
+//
+async function fetchTest(t, { source, target, fetchOptions, expected }) {
+ const sourceUrl =
+ resolveUrl("resources/fetcher.html", sourceResolveOptions(source));
+
+ const targetUrl = preflightUrl(target);
+
+ const iframe = await appendIframe(t, document, sourceUrl);
+ const reply = futureMessage({ source: iframe.contentWindow });
+
+ const message = {
+ url: targetUrl.href,
+ options: fetchOptions,
+ };
+ iframe.contentWindow.postMessage(message, "*");
+
+ const { error, ok, type, body } = await reply;
+
+ assert_equals(error, expected.error, "error");
+
+ assert_equals(ok, expected.ok, "response ok");
+ assert_equals(body, expected.body, "response body");
+
+ if (expected.type !== undefined) {
+ assert_equals(type, expected.type, "response type");
+ }
+}
+
+// Similar to `fetchTest`, but replaced iframes with fenced frames.
+async function fencedFrameFetchTest(t, { source, target, fetchOptions, expected }) {
+ const fetcher_url =
+ resolveUrl("resources/fenced-frame-fetcher.https.html", sourceResolveOptions(source));
+
+ const target_url = preflightUrl(target);
+ target_url.searchParams.set("is-loaded-in-fenced-frame", true);
+
+ fetcher_url.searchParams.set("mode", fetchOptions.mode);
+ fetcher_url.searchParams.set("method", fetchOptions.method);
+ fetcher_url.searchParams.set("url", target_url);
+
+ const error_token = token();
+ const ok_token = token();
+ const body_token = token();
+ const type_token = token();
+ const source_url = generateURL(fetcher_url, [error_token, ok_token, body_token, type_token]);
+
+ const urn = await generateURNFromFledge(source_url, []);
+ attachFencedFrame(urn);
+
+ const error = await nextValueFromServer(error_token);
+ const ok = await nextValueFromServer(ok_token);
+ const body = await nextValueFromServer(body_token);
+ const type = await nextValueFromServer(type_token);
+
+ assert_equals(error, expected.error || "" , "error");
+ assert_equals(body, expected.body || "", "response body");
+ assert_equals(ok, expected.ok !== undefined ? expected.ok.toString() : "", "response ok");
+ if (expected.type !== undefined) {
+ assert_equals(type, expected.type, "response type");
+ }
+}
+
+const XhrTestResult = {
+ SUCCESS: {
+ loaded: true,
+ status: 200,
+ body: "success",
+ },
+ FAILURE: {
+ loaded: false,
+ status: 0,
+ },
+};
+
+// Runs an XHR test. Tries to fetch a given subresource from a given document.
+//
+// Main argument shape:
+//
+// {
+// // Optional. Passed to `sourceResolveOptions()`.
+// source,
+//
+// // Optional. Passed to `preflightUrl()`.
+// target,
+//
+// // Optional. Method to use when sending the request. Defaults to "GET".
+// method,
+//
+// // Required. One of the values in `XhrTestResult`.
+// expected,
+// }
+//
+async function xhrTest(t, { source, target, method, expected }) {
+ const sourceUrl =
+ resolveUrl("resources/xhr-sender.html", sourceResolveOptions(source));
+
+ const targetUrl = preflightUrl(target);
+
+ const iframe = await appendIframe(t, document, sourceUrl);
+ const reply = futureMessage();
+
+ const message = {
+ url: targetUrl.href,
+ method: method,
+ };
+ iframe.contentWindow.postMessage(message, "*");
+
+ const { loaded, status, body } = await reply;
+
+ assert_equals(loaded, expected.loaded, "response loaded");
+ assert_equals(status, expected.status, "response status");
+ assert_equals(body, expected.body, "response body");
+}
+
+const FrameTestResult = {
+ SUCCESS: "loaded",
+ FAILURE: "timeout",
+};
+
+async function iframeTest(t, { source, target, expected }) {
+ // Allows running tests in parallel.
+ const uuid = token();
+
+ const targetUrl = preflightUrl(target);
+ targetUrl.searchParams.set("file", "iframed.html");
+ targetUrl.searchParams.set("iframe-uuid", uuid);
+ targetUrl.searchParams.set(
+ "file-if-no-preflight-received",
+ "iframed-no-preflight-received.html",
+ );
+
+ const sourceUrl =
+ resolveUrl("resources/iframer.html", sourceResolveOptions(source));
+ sourceUrl.searchParams.set("url", targetUrl);
+
+ const messagePromise = futureMessage({
+ filter: (data) => data.uuid === uuid,
+ });
+ const iframe = await appendIframe(t, document, sourceUrl);
+
+ // The grandchild frame posts a message iff it loads successfully.
+ // There exists no interoperable way to check whether an iframe failed to
+ // load, so we use a timeout.
+ // See: https://github.com/whatwg/html/issues/125
+ const result = await Promise.race([
+ messagePromise.then((data) => data.message),
+ new Promise((resolve) => {
+ t.step_timeout(() => resolve("timeout"), 2000 /* ms */);
+ }),
+ ]);
+
+ assert_equals(result, expected);
+}
+
+const NavigationTestResult = {
+ SUCCESS: "success",
+ FAILURE: "timeout",
+};
+
+async function windowOpenTest(t, { source, target, expected }) {
+ const targetUrl = preflightUrl(target);
+ targetUrl.searchParams.set("file", "openee.html");
+ targetUrl.searchParams.set(
+ "file-if-no-preflight-received",
+ "no-preflight-received.html",
+ );
+
+ const sourceUrl =
+ resolveUrl("resources/opener.html", sourceResolveOptions(source));
+ sourceUrl.searchParams.set("url", targetUrl);
+
+ const iframe = await appendIframe(t, document, sourceUrl);
+ const reply = futureMessage({ source: iframe.contentWindow });
+
+ iframe.contentWindow.postMessage({ url: targetUrl.href }, "*");
+
+ const result = await Promise.race([
+ reply,
+ new Promise((resolve) => {
+ t.step_timeout(() => resolve("timeout"), 10000 /* ms */);
+ }),
+ ]);
+
+ assert_equals(result, expected);
+}
+
+async function windowOpenExistingTest(t, { source, target, expected }) {
+ const targetUrl = preflightUrl(target);
+ targetUrl.searchParams.set("file", "openee.html");
+ targetUrl.searchParams.set(
+ "file-if-no-preflight-received",
+ "no-preflight-received.html",
+ );
+
+ const sourceUrl = resolveUrl(
+ 'resources/open-to-existing-window.html', sourceResolveOptions(source));
+ sourceUrl.searchParams.set("url", targetUrl);
+ sourceUrl.searchParams.set("token", token());
+
+ const iframe = await appendIframe(t, document, sourceUrl);
+ const reply = futureMessage({ source: iframe.contentWindow });
+
+ iframe.contentWindow.postMessage({ url: targetUrl.href }, "*");
+
+ const result = await Promise.race([
+ reply,
+ new Promise((resolve) => {
+ t.step_timeout(() => resolve("timeout"), 10000 /* ms */);
+ }),
+ ]);
+
+ assert_equals(result, expected);
+}
+
+async function anchorTest(t, { source, target, expected }) {
+ const targetUrl = preflightUrl(target);
+ targetUrl.searchParams.set("file", "openee.html");
+ targetUrl.searchParams.set(
+ "file-if-no-preflight-received",
+ "no-preflight-received.html",
+ );
+
+ const sourceUrl =
+ resolveUrl("resources/anchor.html", sourceResolveOptions(source));
+ sourceUrl.searchParams.set("url", targetUrl);
+
+ const iframe = await appendIframe(t, document, sourceUrl);
+ const reply = futureMessage({ source: iframe.contentWindow });
+
+ iframe.contentWindow.postMessage({ url: targetUrl.href }, "*");
+
+ const result = await Promise.race([
+ reply,
+ new Promise((resolve) => {
+ t.step_timeout(() => resolve("timeout"), 10000 /* ms */);
+ }),
+ ]);
+
+ assert_equals(result, expected);
+}
+
+// Similar to `iframeTest`, but replaced iframes with fenced frames.
+async function fencedFrameTest(t, { source, target, expected }) {
+ // Allows running tests in parallel.
+ const target_url = preflightUrl(target);
+ target_url.searchParams.set("file", "fenced-frame-private-network-access-target.https.html");
+ target_url.searchParams.set("is-loaded-in-fenced-frame", true);
+
+ const frame_loaded_key = token();
+ const child_frame_target = generateURL(target_url, [frame_loaded_key]);
+
+ const source_url =
+ resolveUrl("resources/fenced-frame-private-network-access.https.html", sourceResolveOptions(source));
+ source_url.searchParams.set("fenced_frame_url", child_frame_target);
+
+ const urn = await generateURNFromFledge(source_url, []);
+ attachFencedFrame(urn);
+
+ // The grandchild fenced frame writes a value to the server iff it loads
+ // successfully.
+ const result = (expected == FrameTestResult.SUCCESS) ?
+ await nextValueFromServer(frame_loaded_key) :
+ await Promise.race([
+ nextValueFromServer(frame_loaded_key),
+ new Promise((resolve) => {
+ t.step_timeout(() => resolve("timeout"), 10000 /* ms */);
+ }),
+ ]);
+
+ assert_equals(result, expected);
+}
+
+const iframeGrandparentTest = ({
+ name,
+ grandparentServer,
+ child,
+ grandchild,
+ expected,
+}) => promise_test_parallel(async (t) => {
+ // Allows running tests in parallel.
+ const grandparentUuid = token();
+ const childUuid = token();
+ const grandchildUuid = token();
+
+ const grandparentUrl =
+ resolveUrl("resources/executor.html", grandparentServer);
+ grandparentUrl.searchParams.set("executor-uuid", grandparentUuid);
+
+ const childUrl = preflightUrl(child);
+ childUrl.searchParams.set("file", "executor.html");
+ childUrl.searchParams.set("executor-uuid", childUuid);
+
+ const grandchildUrl = preflightUrl(grandchild);
+ grandchildUrl.searchParams.set("file", "iframed.html");
+ grandchildUrl.searchParams.set("iframe-uuid", grandchildUuid);
+
+ const iframe = await appendIframe(t, document, grandparentUrl);
+
+ const addChild = (url) => new Promise((resolve) => {
+ const child = document.createElement("iframe");
+ child.src = url;
+ child.addEventListener("load", () => resolve(), { once: true });
+ document.body.appendChild(child);
+ });
+
+ const grandparentCtx = new RemoteContext(grandparentUuid);
+ await grandparentCtx.execute_script(addChild, [childUrl]);
+
+ // Add a blank grandchild frame inside the child.
+ // Apply a timeout to this step so that failures at this step do not block the
+ // execution of other tests.
+ const childCtx = new RemoteContext(childUuid);
+ await Promise.race([
+ childCtx.execute_script(addChild, ["about:blank"]),
+ new Promise((resolve, reject) => t.step_timeout(
+ () => reject("timeout adding grandchild"),
+ 2000 /* ms */
+ )),
+ ]);
+
+ const messagePromise = futureMessage({
+ filter: (data) => data.uuid === grandchildUuid,
+ });
+ await grandparentCtx.execute_script((url) => {
+ const child = window.frames[0];
+ const grandchild = child.frames[0];
+ grandchild.location = url;
+ }, [grandchildUrl]);
+
+ // The great-grandchild frame posts a message iff it loads successfully.
+ // There exists no interoperable way to check whether an iframe failed to
+ // load, so we use a timeout.
+ // See: https://github.com/whatwg/html/issues/125
+ const result = await Promise.race([
+ messagePromise.then((data) => data.message),
+ new Promise((resolve) => {
+ t.step_timeout(() => resolve("timeout"), 2000 /* ms */);
+ }),
+ ]);
+
+ assert_equals(result, expected);
+}, name);
+
+const WebsocketTestResult = {
+ SUCCESS: "open",
+
+ // The code is a best guess. It is not yet entirely specified, so it may need
+ // to be changed in the future based on implementation experience.
+ FAILURE: "close: code 1006",
+};
+
+// Runs a websocket test. Attempts to open a websocket from `source` (in an
+// iframe) to `target`, then checks that the result is as `expected`.
+//
+// Argument shape:
+//
+// {
+// // Required. Passed to `sourceResolveOptions()`.
+// source,
+//
+// // Required.
+// target: {
+// // Required. Target server.
+// server,
+// }
+//
+// // Required. Should be one of the values in `WebsocketTestResult`.
+// expected,
+// }
+//
+async function websocketTest(t, { source, target, expected }) {
+ const sourceUrl =
+ resolveUrl("resources/socket-opener.html", sourceResolveOptions(source));
+
+ const targetUrl = resolveUrl("/echo", target.server);
+
+ const iframe = await appendIframe(t, document, sourceUrl);
+
+ const reply = futureMessage();
+ iframe.contentWindow.postMessage(targetUrl.href, "*");
+
+ assert_equals(await reply, expected);
+}
+
+const WorkerScriptTestResult = {
+ SUCCESS: { loaded: true },
+ FAILURE: { error: "unknown error" },
+};
+
+function workerScriptUrl(target) {
+ const url = preflightUrl(target);
+
+ url.searchParams.append("body", "postMessage({ loaded: true })")
+ url.searchParams.append("mime-type", "application/javascript")
+
+ return url;
+}
+
+async function workerScriptTest(t, { source, target, expected }) {
+ const sourceUrl =
+ resolveUrl("resources/worker-fetcher.html", sourceResolveOptions(source));
+
+ const targetUrl = workerScriptUrl(target);
+
+ const iframe = await appendIframe(t, document, sourceUrl);
+ const reply = futureMessage();
+
+ iframe.contentWindow.postMessage({ url: targetUrl.href }, "*");
+
+ const { error, loaded } = await reply;
+
+ assert_equals(error, expected.error, "worker error");
+ assert_equals(loaded, expected.loaded, "response loaded");
+}
+
+async function nestedWorkerScriptTest(t, { source, target, expected }) {
+ const targetUrl = workerScriptUrl(target);
+
+ const sourceUrl = resolveUrl(
+ "resources/worker-fetcher.js", sourceResolveOptions(source));
+ sourceUrl.searchParams.append("url", targetUrl);
+
+ // Iframe must be same-origin with the parent worker.
+ const iframeUrl = new URL("worker-fetcher.html", sourceUrl);
+
+ const iframe = await appendIframe(t, document, iframeUrl);
+ const reply = futureMessage();
+
+ iframe.contentWindow.postMessage({ url: sourceUrl.href }, "*");
+
+ const { error, loaded } = await reply;
+
+ assert_equals(error, expected.error, "worker error");
+ assert_equals(loaded, expected.loaded, "response loaded");
+}
+
+async function sharedWorkerScriptTest(t, { source, target, expected }) {
+ const sourceUrl = resolveUrl("resources/shared-worker-fetcher.html",
+ sourceResolveOptions(source));
+ const targetUrl = preflightUrl(target);
+ targetUrl.searchParams.append(
+ "body", "onconnect = (e) => e.ports[0].postMessage({ loaded: true })")
+ targetUrl.searchParams.append("mime-type", "application/javascript")
+
+ const iframe = await appendIframe(t, document, sourceUrl);
+ const reply = futureMessage();
+
+ iframe.contentWindow.postMessage({ url: targetUrl.href }, "*");
+
+ const { error, loaded } = await reply;
+
+ assert_equals(error, expected.error, "worker error");
+ assert_equals(loaded, expected.loaded, "response loaded");
+}
+
+// Results that may be expected in tests.
+const WorkerFetchTestResult = {
+ SUCCESS: { status: 200, body: "success" },
+ FAILURE: { error: "TypeError" },
+};
+
+async function workerFetchTest(t, { source, target, expected }) {
+ const targetUrl = preflightUrl(target);
+
+ const sourceUrl =
+ resolveUrl("resources/fetcher.js", sourceResolveOptions(source));
+ sourceUrl.searchParams.append("url", targetUrl.href);
+
+ const fetcherUrl = new URL("worker-fetcher.html", sourceUrl);
+
+ const reply = futureMessage();
+ const iframe = await appendIframe(t, document, fetcherUrl);
+
+ iframe.contentWindow.postMessage({ url: sourceUrl.href }, "*");
+
+ const { error, status, body } = await reply;
+ assert_equals(error, expected.error, "fetch error");
+ assert_equals(status, expected.status, "response status");
+ assert_equals(body, expected.body, "response body");
+}
+
+async function workerBlobFetchTest(t, { source, target, expected }) {
+ const targetUrl = preflightUrl(target);
+
+ const fetcherUrl = resolveUrl(
+ 'resources/worker-blob-fetcher.html', sourceResolveOptions(source));
+
+ const reply = futureMessage();
+ const iframe = await appendIframe(t, document, fetcherUrl);
+
+ iframe.contentWindow.postMessage({ url: targetUrl.href }, "*");
+
+ const { error, status, body } = await reply;
+ assert_equals(error, expected.error, "fetch error");
+ assert_equals(status, expected.status, "response status");
+ assert_equals(body, expected.body, "response body");
+}
+
+async function sharedWorkerFetchTest(t, { source, target, expected }) {
+ const targetUrl = preflightUrl(target);
+
+ const sourceUrl =
+ resolveUrl("resources/shared-fetcher.js", sourceResolveOptions(source));
+ sourceUrl.searchParams.append("url", targetUrl.href);
+
+ const fetcherUrl = new URL("shared-worker-fetcher.html", sourceUrl);
+
+ const reply = futureMessage();
+ const iframe = await appendIframe(t, document, fetcherUrl);
+
+ iframe.contentWindow.postMessage({ url: sourceUrl.href }, "*");
+
+ const { error, status, body } = await reply;
+ assert_equals(error, expected.error, "fetch error");
+ assert_equals(status, expected.status, "response status");
+ assert_equals(body, expected.body, "response body");
+}
+
+async function sharedWorkerBlobFetchTest(t, { source, target, expected }) {
+ const targetUrl = preflightUrl(target);
+
+ const fetcherUrl = resolveUrl(
+ 'resources/shared-worker-blob-fetcher.html',
+ sourceResolveOptions(source));
+
+ const reply = futureMessage();
+ const iframe = await appendIframe(t, document, fetcherUrl);
+
+ iframe.contentWindow.postMessage({ url: targetUrl.href }, "*");
+
+ const { error, status, body } = await reply;
+ assert_equals(error, expected.error, "fetch error");
+ assert_equals(status, expected.status, "response status");
+ assert_equals(body, expected.body, "response body");
+}