summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/fetch/private-network-access
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
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')
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/META.yml7
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/README.md10
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/anchor.tentative.https.window.js191
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/anchor.tentative.window.js95
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/fenced-frame-no-preflight-required.tentative.https.window.js91
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/fenced-frame-subresource-fetch.tentative.https.window.js330
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/fenced-frame.tentative.https.window.js150
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/fetch-from-treat-as-public.tentative.https.window.js80
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/fetch.tentative.https.window.js271
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/fetch.tentative.window.js183
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/iframe.tentative.https.window.js267
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/iframe.tentative.window.js110
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/mixed-content-fetch.tentative.https.window.js278
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/nested-worker.tentative.https.window.js36
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/nested-worker.tentative.window.js36
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/preflight-cache.https.tentative.window.js88
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/redirect.tentative.https.window.js640
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/resources/anchor.html16
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/resources/executor.html9
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-fetcher.https.html25
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-fetcher.https.html.headers1
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-private-network-access-target.https.html8
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-private-network-access.https.html14
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-private-network-access.https.html.headers1
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/resources/fetcher.html21
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/resources/fetcher.js20
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/resources/iframed-no-preflight-received.html7
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/resources/iframed.html7
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/resources/iframer.html9
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/resources/no-preflight-received.html6
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/resources/open-to-existing-window.html12
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/resources/openee.html8
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/resources/opener.html11
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/resources/preflight.py191
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/resources/service-worker-bridge.html155
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/resources/service-worker.js18
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/resources/shared-fetcher.js23
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/resources/shared-worker-blob-fetcher.html50
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/resources/shared-worker-fetcher.html19
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/resources/socket-opener.html15
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/resources/support.sub.js857
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/resources/worker-blob-fetcher.html45
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/resources/worker-fetcher.html18
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/resources/worker-fetcher.js11
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/resources/xhr-sender.html33
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/service-worker-background-fetch.tentative.https.window.js143
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/service-worker-fetch.tentative.https.window.js235
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/service-worker-update.tentative.https.window.js106
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/service-worker.tentative.https.window.js84
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/shared-worker-blob-fetch.tentative.https.window.js168
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/shared-worker-blob-fetch.tentative.window.js173
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/shared-worker-fetch.tentative.https.window.js167
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/shared-worker-fetch.tentative.window.js154
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/shared-worker.tentative.https.window.js34
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/shared-worker.tentative.window.js34
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/websocket.tentative.https.window.js40
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/websocket.tentative.window.js40
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/window-open-existing.tentative.https.window.js209
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/window-open-existing.tentative.window.js95
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/window-open.tentative.https.window.js205
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/window-open.tentative.window.js95
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/worker-blob-fetch.tentative.window.js155
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/worker-fetch.tentative.https.window.js151
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/worker-fetch.tentative.window.js154
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/worker.tentative.https.window.js37
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/worker.tentative.window.js37
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/xhr-from-treat-as-public.tentative.https.window.js83
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/xhr.https.tentative.window.js142
-rw-r--r--testing/web-platform/tests/fetch/private-network-access/xhr.tentative.window.js195
69 files changed, 7409 insertions, 0 deletions
diff --git a/testing/web-platform/tests/fetch/private-network-access/META.yml b/testing/web-platform/tests/fetch/private-network-access/META.yml
new file mode 100644
index 0000000000..944ce6f14a
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/META.yml
@@ -0,0 +1,7 @@
+spec: https://wicg.github.io/private-network-access/
+suggested_reviewers:
+ - letitz
+ - lyf
+ - hemeryar
+ - camillelamy
+ - mikewest
diff --git a/testing/web-platform/tests/fetch/private-network-access/README.md b/testing/web-platform/tests/fetch/private-network-access/README.md
new file mode 100644
index 0000000000..a69aab4872
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/README.md
@@ -0,0 +1,10 @@
+# Private Network Access tests
+
+This directory contains tests for Private Network Access' integration with
+the Fetch specification.
+
+See also:
+
+* [The specification](https://wicg.github.io/private-network-access/)
+* [The repository](https://github.com/WICG/private-network-access/)
+* [Open issues](https://github.com/WICG/private-network-access/issues/)
diff --git a/testing/web-platform/tests/fetch/private-network-access/anchor.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/anchor.tentative.https.window.js
new file mode 100644
index 0000000000..4e860ad381
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/anchor.tentative.https.window.js
@@ -0,0 +1,191 @@
+// META: script=/common/subset-tests-by-key.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+// META: timeout=long
+// META: variant=?include=from-local
+// META: variant=?include=from-private
+// META: variant=?include=from-public
+// META: variant=?include=from-treat-as-public
+//
+// These tests verify that secure contexts can navigate to less-public address
+// spaces via an anchor link iff the target server responds affirmatively to
+// preflight requests.
+
+setup(() => {
+ assert_true(window.isSecureContext);
+});
+
+// Source: secure local context.
+//
+// All fetches unaffected by Private Network Access.
+
+subsetTestByKey("from-local", promise_test_parallel, t => anchorTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: { server: Server.HTTPS_LOCAL },
+ expected: NavigationTestResult.SUCCESS,
+}), "local to local: no preflight required.");
+
+subsetTestByKey("from-local", promise_test_parallel, t => anchorTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: { server: Server.HTTPS_PRIVATE },
+ expected: NavigationTestResult.SUCCESS,
+}), "local to private: no preflight required.");
+
+subsetTestByKey("from-local", promise_test_parallel, t => anchorTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: { server: Server.HTTPS_PUBLIC },
+ expected: NavigationTestResult.SUCCESS,
+}), "local to public: no preflight required.");
+
+// Generates tests of preflight behavior for a single (source, target) pair.
+//
+// Scenarios:
+//
+// - preflight response has non-2xx HTTP code
+// - preflight response is missing CORS headers
+// - preflight response is missing the PNA-specific `Access-Control` header
+// - success
+//
+function makePreflightTests({
+ key,
+ sourceName,
+ sourceServer,
+ sourceTreatAsPublic,
+ targetName,
+ targetServer,
+}) {
+ const prefix =
+ `${sourceName} to ${targetName}: `;
+
+ const source = {
+ server: sourceServer,
+ treatAsPublic: sourceTreatAsPublic,
+ };
+
+ promise_test_parallel(t => anchorTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: { preflight: PreflightBehavior.failure() },
+ },
+ expected: NavigationTestResult.FAILURE,
+ }), prefix + "failed preflight.");
+
+ promise_test_parallel(t => anchorTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: { preflight: PreflightBehavior.noCorsHeader(token()) },
+ },
+ expected: NavigationTestResult.FAILURE,
+ }), prefix + "missing CORS headers.");
+
+ promise_test_parallel(t => anchorTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: { preflight: PreflightBehavior.noPnaHeader(token()) },
+ },
+ expected: NavigationTestResult.FAILURE,
+ }), prefix + "missing PNA header.");
+
+ promise_test_parallel(t => anchorTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: { preflight: PreflightBehavior.navigation(token()) },
+ },
+ expected: NavigationTestResult.SUCCESS,
+ }), prefix + "success.");
+}
+
+// Source: private secure context.
+//
+// Fetches to the local address space require a successful preflight response
+// carrying a PNA-specific header.
+
+subsetTestByKey('from-private', makePreflightTests, {
+ sourceServer: Server.HTTPS_PRIVATE,
+ sourceName: 'private',
+ targetServer: Server.HTTPS_LOCAL,
+ targetName: 'local',
+});
+
+subsetTestByKey("from-private", promise_test_parallel, t => anchorTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: { server: Server.HTTPS_PRIVATE },
+ expected: NavigationTestResult.SUCCESS,
+}), "private to private: no preflight required.");
+
+subsetTestByKey("from-private", promise_test_parallel, t => anchorTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: { server: Server.HTTPS_PUBLIC },
+ expected: NavigationTestResult.SUCCESS,
+}), "private to public: no preflight required.");
+
+// Source: public secure context.
+//
+// Fetches to the local and private address spaces require a successful
+// preflight response carrying a PNA-specific header.
+
+subsetTestByKey('from-public', makePreflightTests, {
+ sourceServer: Server.HTTPS_PUBLIC,
+ sourceName: "public",
+ targetServer: Server.HTTPS_LOCAL,
+ targetName: "local",
+});
+
+subsetTestByKey('from-public', makePreflightTests, {
+ sourceServer: Server.HTTPS_PUBLIC,
+ sourceName: "public",
+ targetServer: Server.HTTPS_PRIVATE,
+ targetName: "private",
+});
+
+subsetTestByKey("from-public", promise_test_parallel, t => anchorTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: { server: Server.HTTPS_PUBLIC },
+ expected: NavigationTestResult.SUCCESS,
+}), "public to public: no preflight required.");
+
+// The following tests verify that `CSP: treat-as-public-address` makes
+// documents behave as if they had been served from a public IP address.
+
+subsetTestByKey('from-treat-as-public', makePreflightTests, {
+ sourceServer: Server.HTTPS_LOCAL,
+ sourceTreatAsPublic: true,
+ sourceName: "treat-as-public-address",
+ targetServer: Server.OTHER_HTTPS_LOCAL,
+ targetName: "local",
+});
+
+subsetTestByKey("from-treat-as-public", promise_test_parallel,
+ t => anchorTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {server: Server.HTTPS_LOCAL},
+ expected: NavigationTestResult.SUCCESS,
+ }),
+ 'treat-as-public-address to local (same-origin): no preflight required.');
+
+subsetTestByKey('from-treat-as-public', makePreflightTests, {
+ sourceServer: Server.HTTPS_LOCAL,
+ sourceTreatAsPublic: true,
+ sourceName: 'treat-as-public-address',
+ targetServer: Server.HTTPS_PRIVATE,
+ targetName: 'private',
+});
+
+subsetTestByKey("from-treat-as-public", promise_test_parallel,
+ t => anchorTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {server: Server.HTTPS_PUBLIC},
+ expected: NavigationTestResult.SUCCESS,
+ }),
+ 'treat-as-public-address to public: no preflight required.');
diff --git a/testing/web-platform/tests/fetch/private-network-access/anchor.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/anchor.tentative.window.js
new file mode 100644
index 0000000000..cb53865808
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/anchor.tentative.window.js
@@ -0,0 +1,95 @@
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+// META: timeout=long
+//
+// Spec: https://wicg.github.io/private-network-access/
+//
+// These tests verify that non-secure contexts cannot open a new window via an
+// anchor link to less-public address spaces.
+
+setup(() => {
+ // Making sure we are in a non secure context, as expected.
+ assert_false(window.isSecureContext);
+});
+
+promise_test_parallel(t => anchorTest(t, {
+ source: { server: Server.HTTP_LOCAL },
+ target: { server: Server.HTTP_LOCAL },
+ expected: NavigationTestResult.SUCCESS,
+}), "local to local: no preflight required.");
+
+promise_test_parallel(t => anchorTest(t, {
+ source: { server: Server.HTTP_LOCAL },
+ target: { server: Server.HTTP_PRIVATE },
+ expected: NavigationTestResult.SUCCESS,
+}), "local to private: no preflight required.");
+
+promise_test_parallel(t => anchorTest(t, {
+ source: { server: Server.HTTP_LOCAL },
+ target: { server: Server.HTTP_PUBLIC },
+ expected: NavigationTestResult.SUCCESS,
+}), "local to public: no preflight required.");
+
+promise_test_parallel(t => anchorTest(t, {
+ source: { server: Server.HTTP_PRIVATE },
+ target: { server: Server.HTTP_LOCAL },
+ expected: NavigationTestResult.FAILURE,
+}), "private to local: failure.");
+
+promise_test_parallel(t => anchorTest(t, {
+ source: { server: Server.HTTP_PRIVATE },
+ target: { server: Server.HTTP_PRIVATE },
+ expected: NavigationTestResult.SUCCESS,
+}), "private to private: no preflight required.");
+
+promise_test_parallel(t => anchorTest(t, {
+ source: { server: Server.HTTP_PRIVATE },
+ target: { server: Server.HTTP_PUBLIC },
+ expected: NavigationTestResult.SUCCESS,
+}), "private to public: no preflight required.");
+
+promise_test_parallel(t => anchorTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: { server: Server.HTTP_LOCAL },
+ expected: NavigationTestResult.FAILURE,
+}), "public to local: failure.");
+
+promise_test_parallel(t => anchorTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: { server: Server.HTTP_PRIVATE },
+ expected: NavigationTestResult.FAILURE,
+}), "public to private: failure.");
+
+promise_test_parallel(t => anchorTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: { server: Server.HTTP_PUBLIC },
+ expected: NavigationTestResult.SUCCESS,
+}), "public to public: no preflight required.");
+
+promise_test_parallel(t => anchorTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTP_LOCAL },
+ expected: NavigationTestResult.FAILURE,
+}), "treat-as-public-address to local: failure.");
+
+promise_test_parallel(t => anchorTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTP_PRIVATE },
+ expected: NavigationTestResult.FAILURE,
+}), "treat-as-public-address to private: failure.");
+
+promise_test_parallel(t => anchorTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTP_PUBLIC },
+ expected: NavigationTestResult.SUCCESS,
+}), "treat-as-public-address to public: no preflight required.");
diff --git a/testing/web-platform/tests/fetch/private-network-access/fenced-frame-no-preflight-required.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/fenced-frame-no-preflight-required.tentative.https.window.js
new file mode 100644
index 0000000000..33e94d57f1
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/fenced-frame-no-preflight-required.tentative.https.window.js
@@ -0,0 +1,91 @@
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+// META: script=/fenced-frame/resources/utils.js
+// META: timeout=long
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests verify that contexts can navigate fenced frames to more-public or
+// same address spaces without private network access preflight request header.
+
+setup(() => {
+ assert_true(window.isSecureContext);
+});
+
+// Source: secure local context.
+//
+// All fetches unaffected by Private Network Access.
+
+promise_test(
+ t => fencedFrameTest(t, {
+ source: {server: Server.HTTPS_LOCAL},
+ target: {server: Server.HTTPS_LOCAL},
+ expected: FrameTestResult.SUCCESS,
+ }),
+ 'local to local: no preflight required.');
+
+promise_test(
+ t => fencedFrameTest(t, {
+ source: {server: Server.HTTPS_LOCAL},
+ target: {server: Server.HTTPS_PRIVATE},
+ expected: FrameTestResult.SUCCESS,
+ }),
+ 'local to private: no preflight required.');
+
+promise_test(
+ t => fencedFrameTest(t, {
+ source: {server: Server.HTTPS_LOCAL},
+ target: {server: Server.HTTPS_PUBLIC},
+ expected: FrameTestResult.SUCCESS,
+ }),
+ 'local to public: no preflight required.');
+
+promise_test(
+ t => fencedFrameTest(t, {
+ source: {server: Server.HTTPS_PRIVATE},
+ target: {server: Server.HTTPS_PRIVATE},
+ expected: FrameTestResult.SUCCESS,
+ }),
+ 'private to private: no preflight required.');
+
+promise_test(
+ t => fencedFrameTest(t, {
+ source: {server: Server.HTTPS_PRIVATE},
+ target: {server: Server.HTTPS_PUBLIC},
+ expected: FrameTestResult.SUCCESS,
+ }),
+ 'private to public: no preflight required.');
+
+promise_test(
+ t => fencedFrameTest(t, {
+ source: {server: Server.HTTPS_PUBLIC},
+ target: {server: Server.HTTPS_PUBLIC},
+ expected: FrameTestResult.SUCCESS,
+ }),
+ 'public to public: no preflight required.');
+
+promise_test(
+ t => fencedFrameTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {server: Server.HTTPS_PUBLIC},
+ expected: FrameTestResult.SUCCESS,
+ }),
+ 'treat-as-public-address to public: no preflight required.');
+
+promise_test(
+ t => fencedFrameTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PUBLIC,
+ behavior: {preflight: PreflightBehavior.optionalSuccess(token())}
+ },
+ expected: FrameTestResult.SUCCESS,
+ }),
+ 'treat-as-public-address to local: optional preflight');
diff --git a/testing/web-platform/tests/fetch/private-network-access/fenced-frame-subresource-fetch.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/fenced-frame-subresource-fetch.tentative.https.window.js
new file mode 100644
index 0000000000..2dff325e3e
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/fenced-frame-subresource-fetch.tentative.https.window.js
@@ -0,0 +1,330 @@
+// META: script=/common/subset-tests-by-key.js
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+// META: script=/fenced-frame/resources/utils.js
+// META: variant=?include=baseline
+// META: variant=?include=from-local
+// META: variant=?include=from-private
+// META: variant=?include=from-public
+// META: timeout=long
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests verify that secure contexts can fetch subresources in fenced
+// frames from all address spaces, provided that the target server, if more
+// private than the initiator, respond affirmatively to preflight requests.
+//
+
+setup(() => {
+ // Making sure we are in a secure context, as expected.
+ assert_true(window.isSecureContext);
+});
+
+// Source: secure local context.
+//
+// All fetches unaffected by Private Network Access.
+
+subsetTestByKey(
+ 'from-local', promise_test, t => fencedFrameFetchTest(t, {
+ source: {server: Server.HTTPS_LOCAL},
+ target: {server: Server.HTTPS_LOCAL},
+ fetchOptions: {method: 'GET', mode: 'cors'},
+ expected: FetchTestResult.SUCCESS,
+ }),
+ 'local to local: no preflight required.');
+
+subsetTestByKey(
+ 'from-local', promise_test,
+ t => fencedFrameFetchTest(t, {
+ source: {server: Server.HTTPS_LOCAL},
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {response: ResponseBehavior.allowCrossOrigin()},
+ },
+ fetchOptions: {method: 'GET', mode: 'cors'},
+ expected: FetchTestResult.SUCCESS,
+ }),
+ 'local to private: no preflight required.');
+
+
+subsetTestByKey(
+ 'from-local', promise_test,
+ t => fencedFrameFetchTest(t, {
+ source: {server: Server.HTTPS_LOCAL},
+ target: {
+ server: Server.HTTPS_PUBLIC,
+ behavior: {response: ResponseBehavior.allowCrossOrigin()},
+ },
+ fetchOptions: {method: 'GET', mode: 'cors'},
+ expected: FetchTestResult.SUCCESS,
+ }),
+ 'local to public: no preflight required.');
+
+// Strictly speaking, the following two tests do not exercise PNA-specific
+// logic, but they serve as a baseline for comparison, ensuring that non-PNA
+// preflight requests are sent and handled as expected.
+
+subsetTestByKey(
+ 'baseline', promise_test,
+ t => fencedFrameFetchTest(t, {
+ source: {server: Server.HTTPS_LOCAL},
+ target: {
+ server: Server.HTTPS_PUBLIC,
+ behavior: {
+ preflight: PreflightBehavior.failure(),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ fetchOptions: {method: 'PUT', mode: 'cors'},
+ expected: FetchTestResult.FAILURE,
+ }),
+ 'local to public: PUT preflight failure.');
+
+subsetTestByKey(
+ 'baseline', promise_test,
+ t => fencedFrameFetchTest(t, {
+ source: {server: Server.HTTPS_LOCAL},
+ target: {
+ server: Server.HTTPS_PUBLIC,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ }
+ },
+ fetchOptions: {method: 'PUT', mode: 'cors'},
+ expected: FetchTestResult.SUCCESS,
+ }),
+ 'local to public: PUT preflight success.');
+
+// Generates tests of preflight behavior for a single (source, target) pair.
+//
+// Scenarios:
+//
+// - cors mode:
+// - preflight response has non-2xx HTTP code
+// - preflight response is missing CORS headers
+// - preflight response is missing the PNA-specific `Access-Control` header
+// - final response is missing CORS headers
+// - success
+// - success with PUT method (non-"simple" request)
+// - no-cors mode:
+// - preflight response has non-2xx HTTP code
+// - preflight response is missing CORS headers
+// - preflight response is missing the PNA-specific `Access-Control` header
+// - success
+//
+function makePreflightTests({
+ subsetKey,
+ source,
+ sourceDescription,
+ targetServer,
+ targetDescription,
+}) {
+ const prefix = `${sourceDescription} to ${targetDescription}: `;
+
+ subsetTestByKey(
+ subsetKey, promise_test,
+ t => fencedFrameFetchTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: {
+ preflight: PreflightBehavior.failure(),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ fetchOptions: {method: 'GET', mode: 'cors'},
+ expected: FetchTestResult.FAILURE,
+ }),
+ prefix + 'failed preflight.');
+
+ subsetTestByKey(
+ subsetKey, promise_test,
+ t => fencedFrameFetchTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: {
+ preflight: PreflightBehavior.noCorsHeader(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ fetchOptions: {method: 'GET', mode: 'cors'},
+ expected: FetchTestResult.FAILURE,
+ }),
+ prefix + 'missing CORS headers on preflight response.');
+
+ subsetTestByKey(
+ subsetKey, promise_test,
+ t => fencedFrameFetchTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: {
+ preflight: PreflightBehavior.noPnaHeader(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ fetchOptions: {method: 'GET', mode: 'cors'},
+ expected: FetchTestResult.FAILURE,
+ }),
+ prefix + 'missing PNA header on preflight response.');
+
+ subsetTestByKey(
+ subsetKey, promise_test,
+ t => fencedFrameFetchTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: {preflight: PreflightBehavior.success(token())},
+ },
+ fetchOptions: {method: 'GET', mode: 'cors'},
+ expected: FetchTestResult.FAILURE,
+ }),
+ prefix + 'missing CORS headers on final response.');
+
+ subsetTestByKey(
+ subsetKey, promise_test,
+ t => fencedFrameFetchTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ fetchOptions: {method: 'GET', mode: 'cors'},
+ expected: FetchTestResult.SUCCESS,
+ }),
+ prefix + 'success.');
+
+ subsetTestByKey(
+ subsetKey, promise_test,
+ t => fencedFrameFetchTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ fetchOptions: {method: 'PUT', mode: 'cors'},
+ expected: FetchTestResult.SUCCESS,
+ }),
+ prefix + 'PUT success.');
+
+ subsetTestByKey(
+ subsetKey, promise_test, t => fencedFrameFetchTest(t, {
+ source,
+ target: {server: targetServer},
+ fetchOptions: {method: 'GET', mode: 'no-cors'},
+ expected: FetchTestResult.FAILURE,
+ }),
+ prefix + 'no-CORS mode failed preflight.');
+
+ subsetTestByKey(
+ subsetKey, promise_test,
+ t => fencedFrameFetchTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: {preflight: PreflightBehavior.noCorsHeader(token())},
+ },
+ fetchOptions: {method: 'GET', mode: 'no-cors'},
+ expected: FetchTestResult.FAILURE,
+ }),
+ prefix + 'no-CORS mode missing CORS headers on preflight response.');
+
+ subsetTestByKey(
+ subsetKey, promise_test,
+ t => fencedFrameFetchTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: {preflight: PreflightBehavior.noPnaHeader(token())},
+ },
+ fetchOptions: {method: 'GET', mode: 'no-cors'},
+ expected: FetchTestResult.FAILURE,
+ }),
+ prefix + 'no-CORS mode missing PNA header on preflight response.');
+
+ subsetTestByKey(
+ subsetKey, promise_test,
+ t => fencedFrameFetchTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: {preflight: PreflightBehavior.success(token())},
+ },
+ fetchOptions: {method: 'GET', mode: 'no-cors'},
+ expected: FetchTestResult.OPAQUE,
+ }),
+ prefix + 'no-CORS mode success.');
+}
+
+// Source: private secure context.
+//
+// Fetches to the local address space require a successful preflight response
+// carrying a PNA-specific header.
+
+makePreflightTests({
+ subsetKey: 'from-private',
+ source: {server: Server.HTTPS_PRIVATE},
+ sourceDescription: 'private',
+ targetServer: Server.HTTPS_LOCAL,
+ targetDescription: 'local',
+});
+
+subsetTestByKey(
+ 'from-private', promise_test, t => fencedFrameFetchTest(t, {
+ source: {server: Server.HTTPS_PRIVATE},
+ target: {server: Server.HTTPS_PRIVATE},
+ fetchOptions: {method: 'GET', mode: 'cors'},
+ expected: FetchTestResult.SUCCESS,
+ }),
+ 'private to private: no preflight required.');
+
+subsetTestByKey(
+ 'from-private', promise_test,
+ t => fencedFrameFetchTest(t, {
+ source: {server: Server.HTTPS_PRIVATE},
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {response: ResponseBehavior.allowCrossOrigin()},
+ },
+ fetchOptions: {method: 'GET', mode: 'cors'},
+ expected: FetchTestResult.SUCCESS,
+ }),
+ 'private to public: no preflight required.');
+
+// Source: public secure context.
+//
+// Fetches to the local and private address spaces require a successful
+// preflight response carrying a PNA-specific header.
+
+makePreflightTests({
+ subsetKey: 'from-public',
+ source: {server: Server.HTTPS_PUBLIC},
+ sourceDescription: 'public',
+ targetServer: Server.HTTPS_LOCAL,
+ targetDescription: 'local',
+});
+
+makePreflightTests({
+ subsetKey: 'from-public',
+ source: {server: Server.HTTPS_PUBLIC},
+ sourceDescription: 'public',
+ targetServer: Server.HTTPS_PRIVATE,
+ targetDescription: 'private',
+});
+
+subsetTestByKey(
+ 'from-public', promise_test, t => fencedFrameFetchTest(t, {
+ source: {server: Server.HTTPS_PUBLIC},
+ target: {server: Server.HTTPS_PUBLIC},
+ fetchOptions: {method: 'GET', mode: 'cors'},
+ expected: FetchTestResult.SUCCESS,
+ }),
+ 'public to public: no preflight required.');
diff --git a/testing/web-platform/tests/fetch/private-network-access/fenced-frame.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/fenced-frame.tentative.https.window.js
new file mode 100644
index 0000000000..370cc9fbe9
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/fenced-frame.tentative.https.window.js
@@ -0,0 +1,150 @@
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+// META: script=/fenced-frame/resources/utils.js
+// META: timeout=long
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests verify that contexts can navigate fenced frames to less-public
+// address spaces iff the target server responds affirmatively to preflight
+// requests.
+
+setup(() => {
+ assert_true(window.isSecureContext);
+});
+
+// Generates tests of preflight behavior for a single (source, target) pair.
+//
+// Scenarios:
+//
+// - parent navigates child:
+// - preflight response has non-2xx HTTP code
+// - preflight response is missing CORS headers
+// - preflight response is missing the PNA-specific `Access-Control` header
+// - preflight response has the required PNA related headers, but still fails
+// because of the limitation of fenced frame that subjects to PNA checks.
+//
+function makePreflightTests({
+ sourceName,
+ sourceServer,
+ sourceTreatAsPublic,
+ targetName,
+ targetServer,
+}) {
+ const prefix = `${sourceName} to ${targetName}: `;
+
+ const source = {
+ server: sourceServer,
+ treatAsPublic: sourceTreatAsPublic,
+ };
+
+ promise_test_parallel(
+ t => fencedFrameTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: {preflight: PreflightBehavior.failure()},
+ },
+ expected: FrameTestResult.FAILURE,
+ }),
+ prefix + 'failed preflight.');
+
+ promise_test_parallel(
+ t => fencedFrameTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: {preflight: PreflightBehavior.noCorsHeader(token())},
+ },
+ expected: FrameTestResult.FAILURE,
+ }),
+ prefix + 'missing CORS headers.');
+
+ promise_test_parallel(
+ t => fencedFrameTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: {preflight: PreflightBehavior.noPnaHeader(token())},
+ },
+ expected: FrameTestResult.FAILURE,
+ }),
+ prefix + 'missing PNA header.');
+
+ promise_test_parallel(
+ t => fencedFrameTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin()
+ },
+ },
+ expected: FrameTestResult.FAILURE,
+ }),
+ prefix + 'failed because fenced frames are incompatible with PNA.');
+}
+
+// Source: private secure context.
+//
+// Fetches to the local address space require a successful preflight response
+// carrying a PNA-specific header.
+
+makePreflightTests({
+ sourceServer: Server.HTTPS_PRIVATE,
+ sourceName: 'private',
+ targetServer: Server.HTTPS_LOCAL,
+ targetName: 'local',
+});
+
+// Source: public secure context.
+//
+// Fetches to the local and private address spaces require a successful
+// preflight response carrying a PNA-specific header.
+
+makePreflightTests({
+ sourceServer: Server.HTTPS_PUBLIC,
+ sourceName: 'public',
+ targetServer: Server.HTTPS_LOCAL,
+ targetName: 'local',
+});
+
+makePreflightTests({
+ sourceServer: Server.HTTPS_PUBLIC,
+ sourceName: 'public',
+ targetServer: Server.HTTPS_PRIVATE,
+ targetName: 'private',
+});
+
+// The following tests verify that `CSP: treat-as-public-address` makes
+// documents behave as if they had been served from a public IP address.
+
+makePreflightTests({
+ sourceServer: Server.HTTPS_LOCAL,
+ sourceTreatAsPublic: true,
+ sourceName: 'treat-as-public-address',
+ targetServer: Server.OTHER_HTTPS_LOCAL,
+ targetName: 'local',
+});
+
+promise_test_parallel(
+ t => fencedFrameTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {server: Server.HTTPS_LOCAL},
+ expected: FrameTestResult.FAILURE,
+ }),
+ 'treat-as-public-address to local (same-origin): fenced frame embedder ' +
+ 'initiated navigation has opaque origin.');
+
+makePreflightTests({
+ sourceServer: Server.HTTPS_LOCAL,
+ sourceTreatAsPublic: true,
+ sourceName: 'treat-as-public-address',
+ targetServer: Server.HTTPS_PRIVATE,
+ targetName: 'private',
+});
diff --git a/testing/web-platform/tests/fetch/private-network-access/fetch-from-treat-as-public.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/fetch-from-treat-as-public.tentative.https.window.js
new file mode 100644
index 0000000000..084e03282f
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/fetch-from-treat-as-public.tentative.https.window.js
@@ -0,0 +1,80 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests verify that documents fetched from the `local` or `private`
+// address space yet carrying the `treat-as-public-address` CSP directive are
+// treated as if they had been fetched from the `public` address space.
+
+promise_test(t => fetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.OTHER_HTTPS_LOCAL,
+ preflight: PreflightBehavior.noPnaHeader(token()),
+ },
+ expected: FetchTestResult.FAILURE,
+}), "treat-as-public-address to local: failed preflight.");
+
+promise_test(t => fetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.OTHER_HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: FetchTestResult.SUCCESS,
+}), "treat-as-public-address to local: success.");
+
+promise_test(t => fetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTPS_LOCAL },
+ expected: FetchTestResult.SUCCESS,
+}), "treat-as-public-address to local (same-origin): no preflight required.");
+
+promise_test(t => fetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTPS_PRIVATE },
+ expected: FetchTestResult.FAILURE,
+}), "treat-as-public-address to private: failed preflight.");
+
+promise_test(t => fetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: FetchTestResult.SUCCESS,
+}), "treat-as-public-address to private: success.");
+
+promise_test(t => fetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PUBLIC,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: FetchTestResult.SUCCESS,
+}), "treat-as-public-address to public: no preflight required.");
diff --git a/testing/web-platform/tests/fetch/private-network-access/fetch.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/fetch.tentative.https.window.js
new file mode 100644
index 0000000000..606443dc14
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/fetch.tentative.https.window.js
@@ -0,0 +1,271 @@
+// META: script=/common/subset-tests-by-key.js
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+// META: variant=?include=baseline
+// META: variant=?include=from-local
+// META: variant=?include=from-private
+// META: variant=?include=from-public
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests verify that secure contexts can fetch subresources from all
+// address spaces, provided that the target server, if more private than the
+// initiator, respond affirmatively to preflight requests.
+//
+// This file covers only those tests that must execute in a secure context.
+// Other tests are defined in: fetch.window.js
+
+setup(() => {
+ // Making sure we are in a secure context, as expected.
+ assert_true(window.isSecureContext);
+});
+
+// Source: secure local context.
+//
+// All fetches unaffected by Private Network Access.
+
+subsetTestByKey("from-local", promise_test, t => fetchTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: { server: Server.HTTPS_LOCAL },
+ expected: FetchTestResult.SUCCESS,
+}), "local to local: no preflight required.");
+
+subsetTestByKey("from-local", promise_test, t => fetchTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: FetchTestResult.SUCCESS,
+}), "local to private: no preflight required.");
+
+
+subsetTestByKey("from-local", promise_test, t => fetchTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: {
+ server: Server.HTTPS_PUBLIC,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: FetchTestResult.SUCCESS,
+}), "local to public: no preflight required.");
+
+// Strictly speaking, the following two tests do not exercise PNA-specific
+// logic, but they serve as a baseline for comparison, ensuring that non-PNA
+// preflight requests are sent and handled as expected.
+
+subsetTestByKey("baseline", promise_test, t => fetchTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: {
+ server: Server.HTTPS_PUBLIC,
+ behavior: {
+ preflight: PreflightBehavior.failure(),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ fetchOptions: { method: "PUT" },
+ expected: FetchTestResult.FAILURE,
+}), "local to public: PUT preflight failure.");
+
+subsetTestByKey("baseline", promise_test, t => fetchTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: {
+ server: Server.HTTPS_PUBLIC,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ }
+ },
+ fetchOptions: { method: "PUT" },
+ expected: FetchTestResult.SUCCESS,
+}), "local to public: PUT preflight success.");
+
+// Generates tests of preflight behavior for a single (source, target) pair.
+//
+// Scenarios:
+//
+// - cors mode:
+// - preflight response has non-2xx HTTP code
+// - preflight response is missing CORS headers
+// - preflight response is missing the PNA-specific `Access-Control` header
+// - final response is missing CORS headers
+// - success
+// - success with PUT method (non-"simple" request)
+// - no-cors mode:
+// - preflight response has non-2xx HTTP code
+// - preflight response is missing CORS headers
+// - preflight response is missing the PNA-specific `Access-Control` header
+// - success
+//
+function makePreflightTests({
+ subsetKey,
+ source,
+ sourceDescription,
+ targetServer,
+ targetDescription,
+}) {
+ const prefix =
+ `${sourceDescription} to ${targetDescription}: `;
+
+ subsetTestByKey(subsetKey, promise_test, t => fetchTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: {
+ preflight: PreflightBehavior.failure(),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: FetchTestResult.FAILURE,
+ }), prefix + "failed preflight.");
+
+ subsetTestByKey(subsetKey, promise_test, t => fetchTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: {
+ preflight: PreflightBehavior.noCorsHeader(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: FetchTestResult.FAILURE,
+ }), prefix + "missing CORS headers on preflight response.");
+
+ subsetTestByKey(subsetKey, promise_test, t => fetchTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: {
+ preflight: PreflightBehavior.noPnaHeader(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: FetchTestResult.FAILURE,
+ }), prefix + "missing PNA header on preflight response.");
+
+ subsetTestByKey(subsetKey, promise_test, t => fetchTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: { preflight: PreflightBehavior.success(token()) },
+ },
+ expected: FetchTestResult.FAILURE,
+ }), prefix + "missing CORS headers on final response.");
+
+ subsetTestByKey(subsetKey, promise_test, t => fetchTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: FetchTestResult.SUCCESS,
+ }), prefix + "success.");
+
+ subsetTestByKey(subsetKey, promise_test, t => fetchTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ fetchOptions: { method: "PUT" },
+ expected: FetchTestResult.SUCCESS,
+ }), prefix + "PUT success.");
+
+ subsetTestByKey(subsetKey, promise_test, t => fetchTest(t, {
+ source,
+ target: { server: targetServer },
+ fetchOptions: { mode: "no-cors" },
+ expected: FetchTestResult.FAILURE,
+ }), prefix + "no-CORS mode failed preflight.");
+
+ subsetTestByKey(subsetKey, promise_test, t => fetchTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: { preflight: PreflightBehavior.noCorsHeader(token()) },
+ },
+ fetchOptions: { mode: "no-cors" },
+ expected: FetchTestResult.FAILURE,
+ }), prefix + "no-CORS mode missing CORS headers on preflight response.");
+
+ subsetTestByKey(subsetKey, promise_test, t => fetchTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: { preflight: PreflightBehavior.noPnaHeader(token()) },
+ },
+ fetchOptions: { mode: "no-cors" },
+ expected: FetchTestResult.FAILURE,
+ }), prefix + "no-CORS mode missing PNA header on preflight response.");
+
+ subsetTestByKey(subsetKey, promise_test, t => fetchTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: { preflight: PreflightBehavior.success(token()) },
+ },
+ fetchOptions: { mode: "no-cors" },
+ expected: FetchTestResult.OPAQUE,
+ }), prefix + "no-CORS mode success.");
+}
+
+// Source: private secure context.
+//
+// Fetches to the local address space require a successful preflight response
+// carrying a PNA-specific header.
+
+makePreflightTests({
+ subsetKey: "from-private",
+ source: { server: Server.HTTPS_PRIVATE },
+ sourceDescription: "private",
+ targetServer: Server.HTTPS_LOCAL,
+ targetDescription: "local",
+});
+
+subsetTestByKey("from-private", promise_test, t => fetchTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: { server: Server.HTTPS_PRIVATE },
+ expected: FetchTestResult.SUCCESS,
+}), "private to private: no preflight required.");
+
+subsetTestByKey("from-private", promise_test, t => fetchTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: {
+ server: Server.HTTPS_PUBLIC,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: FetchTestResult.SUCCESS,
+}), "private to public: no preflight required.");
+
+// Source: public secure context.
+//
+// Fetches to the local and private address spaces require a successful
+// preflight response carrying a PNA-specific header.
+
+makePreflightTests({
+ subsetKey: "from-public",
+ source: { server: Server.HTTPS_PUBLIC },
+ sourceDescription: "public",
+ targetServer: Server.HTTPS_LOCAL,
+ targetDescription: "local",
+});
+
+makePreflightTests({
+ subsetKey: "from-public",
+ source: { server: Server.HTTPS_PUBLIC },
+ sourceDescription: "public",
+ targetServer: Server.HTTPS_PRIVATE,
+ targetDescription: "private",
+});
+
+subsetTestByKey("from-public", promise_test, t => fetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: { server: Server.HTTPS_PUBLIC },
+ expected: FetchTestResult.SUCCESS,
+}), "public to public: no preflight required.");
+
diff --git a/testing/web-platform/tests/fetch/private-network-access/fetch.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/fetch.tentative.window.js
new file mode 100644
index 0000000000..8ee54c9056
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/fetch.tentative.window.js
@@ -0,0 +1,183 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests verify that non-secure contexts cannot fetch subresources from
+// less-public address spaces, and can fetch them otherwise.
+//
+// This file covers only those tests that must execute in a non secure context.
+// Other tests are defined in: fetch.https.window.js
+
+setup(() => {
+ // Making sure we are in a non secure context, as expected.
+ assert_false(window.isSecureContext);
+});
+
+promise_test(t => fetchTest(t, {
+ source: { server: Server.HTTP_LOCAL },
+ target: { server: Server.HTTP_LOCAL },
+ expected: FetchTestResult.SUCCESS,
+}), "local to local: no preflight required.");
+
+promise_test(t => fetchTest(t, {
+ source: { server: Server.HTTP_LOCAL },
+ target: {
+ server: Server.HTTP_PRIVATE,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: FetchTestResult.SUCCESS,
+}), "local to private: no preflight required.");
+
+promise_test(t => fetchTest(t, {
+ source: { server: Server.HTTP_LOCAL },
+ target: {
+ server: Server.HTTP_PUBLIC,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: FetchTestResult.SUCCESS,
+}), "local to public: no preflight required.");
+
+promise_test(t => fetchTest(t, {
+ source: { server: Server.HTTP_PRIVATE },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: FetchTestResult.FAILURE,
+}), "private to local: failure.");
+
+promise_test(t => fetchTest(t, {
+ source: { server: Server.HTTP_PRIVATE },
+ target: { server: Server.HTTP_PRIVATE },
+ expected: FetchTestResult.SUCCESS,
+}), "private to private: no preflight required.");
+
+promise_test(t => fetchTest(t, {
+ source: { server: Server.HTTP_PRIVATE },
+ target: {
+ server: Server.HTTP_PUBLIC,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: FetchTestResult.SUCCESS,
+}), "private to public: no preflight required.");
+
+promise_test(t => fetchTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: FetchTestResult.FAILURE,
+}), "public to local: failure.");
+
+promise_test(t => fetchTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: {
+ server: Server.HTTP_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: FetchTestResult.FAILURE,
+}), "public to private: failure.");
+
+promise_test(t => fetchTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: { server: Server.HTTP_PUBLIC },
+ expected: FetchTestResult.SUCCESS,
+}), "public to public: no preflight required.");
+
+// These tests verify that documents fetched from the `local` address space yet
+// carrying the `treat-as-public-address` CSP directive are treated as if they
+// had been fetched from the `public` address space.
+
+promise_test(t => fetchTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: FetchTestResult.FAILURE,
+}), "treat-as-public-address to local: failure.");
+
+promise_test(t => fetchTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTP_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: FetchTestResult.FAILURE,
+}), "treat-as-public-address to private: failure.");
+
+promise_test(t => fetchTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTP_PUBLIC,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: FetchTestResult.SUCCESS,
+}), "treat-as-public-address to public: no preflight required.");
+
+// These tests verify that HTTPS iframes embedded in an HTTP top-level document
+// cannot fetch subresources from less-public address spaces. Indeed, even
+// though the iframes have HTTPS origins, they are non-secure contexts because
+// their parent is a non-secure context.
+
+promise_test(t => fetchTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: FetchTestResult.FAILURE,
+}), "private https to local: failure.");
+
+promise_test(t => fetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: FetchTestResult.FAILURE,
+}), "public https to local: failure.");
+
+promise_test(t => fetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: FetchTestResult.FAILURE,
+}), "public https to private: failure.");
diff --git a/testing/web-platform/tests/fetch/private-network-access/iframe.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/iframe.tentative.https.window.js
new file mode 100644
index 0000000000..1e00c0af41
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/iframe.tentative.https.window.js
@@ -0,0 +1,267 @@
+// META: script=/common/subset-tests-by-key.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+// META: timeout=long
+// META: variant=?include=from-local
+// META: variant=?include=from-private
+// META: variant=?include=from-public
+// META: variant=?include=from-treat-as-public
+// META: variant=?include=grandparent
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests verify that secure contexts can navigate iframes to less-public
+// address spaces iff the target server responds affirmatively to preflight
+// requests.
+//
+// This file covers only those tests that must execute in a secure context.
+// Other tests are defined in: iframe.tentative.window.js
+
+setup(() => {
+ assert_true(window.isSecureContext);
+});
+
+// Source: secure local context.
+//
+// All fetches unaffected by Private Network Access.
+
+subsetTestByKey("from-local", promise_test_parallel, t => iframeTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: { server: Server.HTTPS_LOCAL },
+ expected: FrameTestResult.SUCCESS,
+}), "local to local: no preflight required.");
+
+subsetTestByKey("from-local", promise_test_parallel, t => iframeTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: { server: Server.HTTPS_PRIVATE },
+ expected: FrameTestResult.SUCCESS,
+}), "local to private: no preflight required.");
+
+subsetTestByKey("from-local", promise_test_parallel, t => iframeTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: { server: Server.HTTPS_PUBLIC },
+ expected: FrameTestResult.SUCCESS,
+}), "local to public: no preflight required.");
+
+// Generates tests of preflight behavior for a single (source, target) pair.
+//
+// Scenarios:
+//
+// - parent navigates child:
+// - preflight response has non-2xx HTTP code
+// - preflight response is missing CORS headers
+// - preflight response is missing the PNA-specific `Access-Control` header
+// - success
+//
+function makePreflightTests({
+ key,
+ sourceName,
+ sourceServer,
+ sourceTreatAsPublic,
+ targetName,
+ targetServer,
+}) {
+ const prefix =
+ `${sourceName} to ${targetName}: `;
+
+ const source = {
+ server: sourceServer,
+ treatAsPublic: sourceTreatAsPublic,
+ };
+
+ promise_test_parallel(t => iframeTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: { preflight: PreflightBehavior.failure() },
+ },
+ expected: FrameTestResult.FAILURE,
+ }), prefix + "failed preflight.");
+
+ promise_test_parallel(t => iframeTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: { preflight: PreflightBehavior.noCorsHeader(token()) },
+ },
+ expected: FrameTestResult.FAILURE,
+ }), prefix + "missing CORS headers.");
+
+ promise_test_parallel(t => iframeTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: { preflight: PreflightBehavior.noPnaHeader(token()) },
+ },
+ expected: FrameTestResult.FAILURE,
+ }), prefix + "missing PNA header.");
+
+ promise_test_parallel(t => iframeTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: { preflight: PreflightBehavior.navigation(token()) },
+ },
+ expected: FrameTestResult.SUCCESS,
+ }), prefix + "success.");
+}
+
+// Source: private secure context.
+//
+// Fetches to the local address space require a successful preflight response
+// carrying a PNA-specific header.
+
+subsetTestByKey('from-private', makePreflightTests, {
+ sourceServer: Server.HTTPS_PRIVATE,
+ sourceName: 'private',
+ targetServer: Server.HTTPS_LOCAL,
+ targetName: 'local',
+});
+
+subsetTestByKey("from-private", promise_test_parallel, t => iframeTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: { server: Server.HTTPS_PRIVATE },
+ expected: FrameTestResult.SUCCESS,
+}), "private to private: no preflight required.");
+
+subsetTestByKey("from-private", promise_test_parallel, t => iframeTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: { server: Server.HTTPS_PUBLIC },
+ expected: FrameTestResult.SUCCESS,
+}), "private to public: no preflight required.");
+
+// Source: public secure context.
+//
+// Fetches to the local and private address spaces require a successful
+// preflight response carrying a PNA-specific header.
+
+subsetTestByKey('from-public', makePreflightTests, {
+ sourceServer: Server.HTTPS_PUBLIC,
+ sourceName: "public",
+ targetServer: Server.HTTPS_LOCAL,
+ targetName: "local",
+});
+
+subsetTestByKey('from-public', makePreflightTests, {
+ sourceServer: Server.HTTPS_PUBLIC,
+ sourceName: "public",
+ targetServer: Server.HTTPS_PRIVATE,
+ targetName: "private",
+});
+
+subsetTestByKey("from-public", promise_test_parallel, t => iframeTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: { server: Server.HTTPS_PUBLIC },
+ expected: FrameTestResult.SUCCESS,
+}), "public to public: no preflight required.");
+
+// The following tests verify that `CSP: treat-as-public-address` makes
+// documents behave as if they had been served from a public IP address.
+
+subsetTestByKey('from-treat-as-public', makePreflightTests, {
+ sourceServer: Server.HTTPS_LOCAL,
+ sourceTreatAsPublic: true,
+ sourceName: "treat-as-public-address",
+ targetServer: Server.OTHER_HTTPS_LOCAL,
+ targetName: "local",
+});
+
+subsetTestByKey(
+ 'from-treat-as-public', promise_test_parallel,
+ t => iframeTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {server: Server.HTTPS_LOCAL},
+ expected: FrameTestResult.SUCCESS,
+ }),
+ 'treat-as-public-address to local (same-origin): no preflight required.'
+);
+
+subsetTestByKey('from-treat-as-public', makePreflightTests, {
+ sourceServer: Server.HTTPS_LOCAL,
+ sourceTreatAsPublic: true,
+ sourceName: "treat-as-public-address",
+ targetServer: Server.HTTPS_PRIVATE,
+ targetName: "private",
+});
+
+subsetTestByKey(
+ 'from-treat-as-public', promise_test_parallel,
+ t => iframeTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {server: Server.HTTPS_PUBLIC},
+ expected: FrameTestResult.SUCCESS,
+ }),
+ 'treat-as-public-address to public: no preflight required.'
+);
+
+subsetTestByKey(
+ 'from-treat-as-public', promise_test_parallel,
+ t => iframeTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PUBLIC,
+ behavior: {preflight: PreflightBehavior.optionalSuccess(token())}
+ },
+ expected: FrameTestResult.SUCCESS,
+ }),
+ 'treat-as-public-address to local: optional preflight'
+);
+
+// The following tests verify that when a grandparent frame navigates its
+// grandchild, the IP address space of the grandparent is compared against the
+// IP address space of the response. Indeed, the navigation initiator in this
+// case is the grandparent, not the parent.
+
+subsetTestByKey('grandparent', iframeGrandparentTest, {
+ name: 'local to local, grandparent navigates: no preflight required.',
+ grandparentServer: Server.HTTPS_LOCAL,
+ child: {server: Server.HTTPS_PUBLIC},
+ grandchild: {server: Server.OTHER_HTTPS_LOCAL},
+ expected: FrameTestResult.SUCCESS,
+});
+
+subsetTestByKey('grandparent', iframeGrandparentTest, {
+ name: "local to local (same-origin), grandparent navigates: no preflight required.",
+ grandparentServer: Server.HTTPS_LOCAL,
+ child: { server: Server.HTTPS_PUBLIC },
+ grandchild: { server: Server.HTTPS_LOCAL },
+ expected: FrameTestResult.SUCCESS,
+});
+
+subsetTestByKey('grandparent', iframeGrandparentTest, {
+ name: "public to local, grandparent navigates: failure.",
+ grandparentServer: Server.HTTPS_PUBLIC,
+ child: {
+ server: Server.HTTPS_LOCAL,
+ behavior: { preflight: PreflightBehavior.navigation(token()) },
+ },
+ grandchild: {
+ server: Server.HTTPS_LOCAL,
+ behavior: { preflight: PreflightBehavior.failure() },
+ },
+ expected: FrameTestResult.FAILURE,
+});
+
+subsetTestByKey('grandparent', iframeGrandparentTest, {
+ name: "public to local, grandparent navigates: success.",
+ grandparentServer: Server.HTTPS_PUBLIC,
+ child: {
+ server: Server.HTTPS_LOCAL,
+ behavior: { preflight: PreflightBehavior.navigation(token()) },
+ },
+ grandchild: {
+ server: Server.HTTPS_LOCAL,
+ behavior: { preflight: PreflightBehavior.navigation(token()) },
+ },
+ expected: FrameTestResult.SUCCESS,
+});
diff --git a/testing/web-platform/tests/fetch/private-network-access/iframe.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/iframe.tentative.window.js
new file mode 100644
index 0000000000..441e0884d2
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/iframe.tentative.window.js
@@ -0,0 +1,110 @@
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests verify that non-secure contexts cannot navigate iframes to
+// less-public address spaces, and can navigate them otherwise.
+//
+// This file covers only those tests that must execute in a non secure context.
+// Other tests are defined in: iframe.tentative.https.window.js
+
+setup(() => {
+ // Making sure we are in a non secure context, as expected.
+ assert_false(window.isSecureContext);
+});
+
+promise_test_parallel(t => iframeTest(t, {
+ source: { server: Server.HTTP_LOCAL },
+ target: { server: Server.HTTP_LOCAL },
+ expected: FrameTestResult.SUCCESS,
+}), "local to local: no preflight required.");
+
+promise_test_parallel(t => iframeTest(t, {
+ source: { server: Server.HTTP_LOCAL },
+ target: { server: Server.HTTP_PRIVATE },
+ expected: FrameTestResult.SUCCESS,
+}), "local to private: no preflight required.");
+
+promise_test_parallel(t => iframeTest(t, {
+ source: { server: Server.HTTP_LOCAL },
+ target: { server: Server.HTTP_PUBLIC },
+ expected: FrameTestResult.SUCCESS,
+}), "local to public: no preflight required.");
+
+promise_test_parallel(t => iframeTest(t, {
+ source: { server: Server.HTTP_PRIVATE },
+ target: { server: Server.HTTP_LOCAL },
+ expected: FrameTestResult.FAILURE,
+}), "private to local: failure.");
+
+promise_test_parallel(t => iframeTest(t, {
+ source: { server: Server.HTTP_PRIVATE },
+ target: { server: Server.HTTP_PRIVATE },
+ expected: FrameTestResult.SUCCESS,
+}), "private to private: no preflight required.");
+
+promise_test_parallel(t => iframeTest(t, {
+ source: { server: Server.HTTP_PRIVATE },
+ target: { server: Server.HTTP_PUBLIC },
+ expected: FrameTestResult.SUCCESS,
+}), "private to public: no preflight required.");
+
+promise_test_parallel(t => iframeTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: { server: Server.HTTP_LOCAL },
+ expected: FrameTestResult.FAILURE,
+}), "public to local: failure.");
+
+promise_test_parallel(t => iframeTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: { server: Server.HTTP_PRIVATE },
+ expected: FrameTestResult.FAILURE,
+}), "public to private: failure.");
+
+promise_test_parallel(t => iframeTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: { server: Server.HTTP_PUBLIC },
+ expected: FrameTestResult.SUCCESS,
+}), "public to public: no preflight required.");
+
+promise_test_parallel(t => iframeTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTP_LOCAL },
+ expected: FrameTestResult.FAILURE,
+}), "treat-as-public-address to local: failure.");
+
+promise_test_parallel(t => iframeTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTP_PRIVATE },
+ expected: FrameTestResult.FAILURE,
+}), "treat-as-public-address to private: failure.");
+
+promise_test_parallel(t => iframeTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTP_PUBLIC },
+ expected: FrameTestResult.SUCCESS,
+}), "treat-as-public-address to public: no preflight required.");
+
+// The following test verifies that when a grandparent frame navigates its
+// grandchild, the IP address space of the grandparent is compared against the
+// IP address space of the response. Indeed, the navigation initiator in this
+// case is the grandparent, not the parent.
+
+iframeGrandparentTest({
+ name: "local to local, grandparent navigates: success.",
+ grandparentServer: Server.HTTP_LOCAL,
+ child: { server: Server.HTTP_PUBLIC },
+ grandchild: { server: Server.HTTP_LOCAL },
+ expected: FrameTestResult.SUCCESS,
+});
diff --git a/testing/web-platform/tests/fetch/private-network-access/mixed-content-fetch.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/mixed-content-fetch.tentative.https.window.js
new file mode 100644
index 0000000000..dbae5193b5
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/mixed-content-fetch.tentative.https.window.js
@@ -0,0 +1,278 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+// META: timeout=long
+//
+// Spec: https://wicg.github.io/private-network-access
+//
+// These tests verify that secure contexts can fetch non-secure subresources
+// from more private address spaces, avoiding mixed context checks, as long as
+// they specify a valid `targetAddressSpace` fetch option that matches the
+// target server's address space.
+
+setup(() => {
+ // Making sure we are in a secure context, as expected.
+ assert_true(window.isSecureContext);
+});
+
+// Given `addressSpace`, returns the other three possible IP address spaces.
+function otherAddressSpaces(addressSpace) {
+ switch (addressSpace) {
+ case "local": return ["unknown", "private", "public"];
+ case "private": return ["unknown", "local", "public"];
+ case "public": return ["unknown", "local", "private"];
+ }
+}
+
+// Generates tests of `targetAddressSpace` for the given (source, target)
+// address space pair, expecting fetches to succeed iff `targetAddressSpace` is
+// correct.
+//
+// Scenarios exercised:
+//
+// - cors mode:
+// - missing targetAddressSpace option
+// - incorrect targetAddressSpace option (x3, see `otherAddressSpaces()`)
+// - failed preflight
+// - success
+// - success with PUT method (non-"simple" request)
+// - no-cors mode:
+// - success
+//
+function makeTests({ source, target }) {
+ const sourceServer = Server.get("https", source);
+ const targetServer = Server.get("http", target);
+
+ const makeTest = ({
+ fetchOptions,
+ targetBehavior,
+ name,
+ expected
+ }) => {
+ promise_test_parallel(t => fetchTest(t, {
+ source: { server: sourceServer },
+ target: {
+ server: targetServer,
+ behavior: targetBehavior,
+ },
+ fetchOptions,
+ expected,
+ }), `${sourceServer.name} to ${targetServer.name}: ${name}.`);
+ };
+
+ makeTest({
+ name: "missing targetAddressSpace",
+ targetBehavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ expected: FetchTestResult.FAILURE,
+ });
+
+ const correctAddressSpace = targetServer.addressSpace;
+
+ for (const targetAddressSpace of otherAddressSpaces(correctAddressSpace)) {
+ makeTest({
+ name: `wrong targetAddressSpace "${targetAddressSpace}"`,
+ targetBehavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ fetchOptions: { targetAddressSpace },
+ expected: FetchTestResult.FAILURE,
+ });
+ }
+
+ makeTest({
+ name: "failed preflight",
+ targetBehavior: {
+ preflight: PreflightBehavior.failure(),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ fetchOptions: { targetAddressSpace: correctAddressSpace },
+ expected: FetchTestResult.FAILURE,
+ });
+
+ makeTest({
+ name: "success",
+ targetBehavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ fetchOptions: { targetAddressSpace: correctAddressSpace },
+ expected: FetchTestResult.SUCCESS,
+ });
+
+ makeTest({
+ name: "PUT success",
+ targetBehavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ fetchOptions: {
+ targetAddressSpace: correctAddressSpace,
+ method: "PUT",
+ },
+ expected: FetchTestResult.SUCCESS,
+ });
+
+ makeTest({
+ name: "no-cors success",
+ targetBehavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ fetchOptions: {
+ targetAddressSpace: correctAddressSpace,
+ mode: "no-cors",
+ },
+ expected: FetchTestResult.OPAQUE,
+ });
+}
+
+// Generates tests for the given (source, target) address space pair expecting
+// that `targetAddressSpace` cannot be used to bypass mixed content.
+//
+// Scenarios exercised:
+//
+// - wrong `targetAddressSpace` (x3, see `otherAddressSpaces()`)
+// - correct `targetAddressSpace`
+//
+function makeNoBypassTests({ source, target }) {
+ const sourceServer = Server.get("https", source);
+ const targetServer = Server.get("http", target);
+
+ const prefix = `${sourceServer.name} to ${targetServer.name}: `;
+
+ const correctAddressSpace = targetServer.addressSpace;
+ for (const targetAddressSpace of otherAddressSpaces(correctAddressSpace)) {
+ promise_test_parallel(t => fetchTest(t, {
+ source: { server: sourceServer },
+ target: {
+ server: targetServer,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ fetchOptions: { targetAddressSpace },
+ expected: FetchTestResult.FAILURE,
+ }), prefix + `wrong targetAddressSpace "${targetAddressSpace}".`);
+ }
+
+ promise_test_parallel(t => fetchTest(t, {
+ source: { server: sourceServer },
+ target: {
+ server: targetServer,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ fetchOptions: { targetAddressSpace: correctAddressSpace },
+ expected: FetchTestResult.FAILURE,
+ }), prefix + 'not a private network request.');
+}
+
+// Source: local secure context.
+//
+// Fetches to the local and private address spaces cannot use
+// `targetAddressSpace` to bypass mixed content, as they are not otherwise
+// blocked by Private Network Access.
+
+makeNoBypassTests({ source: "local", target: "local" });
+makeNoBypassTests({ source: "local", target: "private" });
+makeNoBypassTests({ source: "local", target: "public" });
+
+// Source: private secure context.
+//
+// Fetches to the local address space requires the right `targetAddressSpace`
+// option, as well as a successful preflight response carrying a PNA-specific
+// header.
+//
+// Fetches to the private address space cannot use `targetAddressSpace` to
+// bypass mixed content, as they are not otherwise blocked by Private Network
+// Access.
+
+makeTests({ source: "private", target: "local" });
+
+makeNoBypassTests({ source: "private", target: "private" });
+makeNoBypassTests({ source: "private", target: "public" });
+
+// Source: public secure context.
+//
+// Fetches to the local and private address spaces require the right
+// `targetAddressSpace` option, as well as a successful preflight response
+// carrying a PNA-specific header.
+
+makeTests({ source: "public", target: "local" });
+makeTests({ source: "public", target: "private" });
+
+makeNoBypassTests({ source: "public", target: "public" });
+
+// These tests verify that documents fetched from the `local` address space yet
+// carrying the `treat-as-public-address` CSP directive are treated as if they
+// had been fetched from the `public` address space.
+
+promise_test_parallel(t => fetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ fetchOptions: { targetAddressSpace: "private" },
+ expected: FetchTestResult.FAILURE,
+}), 'https-treat-as-public to http-local: wrong targetAddressSpace "private".');
+
+promise_test_parallel(t => fetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ fetchOptions: { targetAddressSpace: "local" },
+ expected: FetchTestResult.SUCCESS,
+}), "https-treat-as-public to http-local: success.");
+
+promise_test_parallel(t => fetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTP_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ fetchOptions: { targetAddressSpace: "local" },
+ expected: FetchTestResult.FAILURE,
+}), 'https-treat-as-public to http-private: wrong targetAddressSpace "local".');
+
+promise_test_parallel(t => fetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTP_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ fetchOptions: { targetAddressSpace: "private" },
+ expected: FetchTestResult.SUCCESS,
+}), "https-treat-as-public to http-private: success.");
diff --git a/testing/web-platform/tests/fetch/private-network-access/nested-worker.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/nested-worker.tentative.https.window.js
new file mode 100644
index 0000000000..3eeb435bad
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/nested-worker.tentative.https.window.js
@@ -0,0 +1,36 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests check that initial `Worker` script fetches from within worker
+// scopes are subject to Private Network Access checks, just like a worker
+// script fetches from within document scopes (for non-nested workers). The
+// latter are tested in: worker.https.window.js
+//
+// This file covers only those tests that must execute in a secure context.
+// Other tests are defined in: nested-worker.window.js
+
+promise_test(t => nestedWorkerScriptTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTPS_LOCAL },
+ expected: WorkerScriptTestResult.FAILURE,
+}), "treat-as-public to local: failure.");
+
+promise_test(t => nestedWorkerScriptTest(t, {
+ source: {
+ server: Server.HTTPS_PRIVATE,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTPS_PRIVATE },
+ expected: WorkerScriptTestResult.FAILURE,
+}), "treat-as-public to private: failure.");
+
+promise_test(t => nestedWorkerScriptTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: { server: Server.HTTPS_PUBLIC },
+ expected: WorkerScriptTestResult.SUCCESS,
+}), "public to public: success.");
diff --git a/testing/web-platform/tests/fetch/private-network-access/nested-worker.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/nested-worker.tentative.window.js
new file mode 100644
index 0000000000..6d246e1c76
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/nested-worker.tentative.window.js
@@ -0,0 +1,36 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests check that initial `Worker` script fetches from within worker
+// scopes are subject to Private Network Access checks, just like a worker
+// script fetches from within document scopes (for non-nested workers). The
+// latter are tested in: worker.window.js
+//
+// This file covers only those tests that must execute in a non secure context.
+// Other tests are defined in: nested-worker.https.window.js
+
+promise_test(t => nestedWorkerScriptTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTP_LOCAL },
+ expected: WorkerScriptTestResult.FAILURE,
+}), "treat-as-public to local: failure.");
+
+promise_test(t => nestedWorkerScriptTest(t, {
+ source: {
+ server: Server.HTTP_PRIVATE,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTP_PRIVATE },
+ expected: WorkerScriptTestResult.FAILURE,
+}), "treat-as-public to private: failure.");
+
+promise_test(t => nestedWorkerScriptTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: { server: Server.HTTP_PUBLIC },
+ expected: WorkerScriptTestResult.SUCCESS,
+}), "public to public: success.");
diff --git a/testing/web-platform/tests/fetch/private-network-access/preflight-cache.https.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/preflight-cache.https.tentative.window.js
new file mode 100644
index 0000000000..87dbf501f6
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/preflight-cache.https.tentative.window.js
@@ -0,0 +1,88 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#cors-preflight
+//
+// These tests verify that PNA preflight responses are cached.
+//
+// TODO(https://crbug.com/1268312): We cannot currently test that cache
+// entries are keyed by target IP address space because that requires
+// loading the same URL from different IP address spaces, and the WPT
+// framework does not allow that.
+promise_test(async t => {
+ let uuid = token();
+ await fetchTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.singlePreflight(uuid),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: FetchTestResult.SUCCESS,
+ });
+ await fetchTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.singlePreflight(uuid),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: FetchTestResult.SUCCESS,
+ });
+}, "private to local: success.");
+
+promise_test(async t => {
+ let uuid = token();
+ await fetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.singlePreflight(uuid),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: FetchTestResult.SUCCESS,
+ });
+ await fetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.singlePreflight(uuid),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: FetchTestResult.SUCCESS,
+ });
+}, "public to local: success.");
+
+promise_test(async t => {
+ let uuid = token();
+ await fetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.singlePreflight(uuid),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: FetchTestResult.SUCCESS,
+ });
+ await fetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.singlePreflight(uuid),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: FetchTestResult.SUCCESS,
+ });
+}, "public to private: success."); \ No newline at end of file
diff --git a/testing/web-platform/tests/fetch/private-network-access/redirect.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/redirect.tentative.https.window.js
new file mode 100644
index 0000000000..efbd8f31f9
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/redirect.tentative.https.window.js
@@ -0,0 +1,640 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// This test verifies that Private Network Access checks are applied to all
+// the endpoints in a redirect chain, relative to the same client context.
+
+// local -> private -> public
+//
+// Request 1 (local -> private): no preflight.
+// Request 2 (local -> public): no preflight.
+
+promise_test(t => fetchTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ response: ResponseBehavior.allowCrossOrigin(),
+ redirect: preflightUrl({
+ server: Server.HTTPS_PUBLIC,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ }),
+ }
+ },
+ expected: FetchTestResult.SUCCESS,
+}), "local to private to public: success.");
+
+// local -> private -> local
+//
+// Request 1 (local -> private): no preflight.
+// Request 2 (local -> local): no preflight.
+//
+// This checks that the client for the second request is still the initial
+// context, not the redirector.
+
+promise_test(t => fetchTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ response: ResponseBehavior.allowCrossOrigin(),
+ redirect: preflightUrl({
+ server: Server.HTTPS_LOCAL,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ }),
+ }
+ },
+ expected: FetchTestResult.SUCCESS,
+}), "local to private to local: success.");
+
+// private -> private -> local
+//
+// Request 1 (private -> private): no preflight.
+// Request 2 (private -> local): preflight required.
+//
+// This verifies that PNA checks are applied after redirects.
+
+promise_test(t => fetchTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ redirect: preflightUrl({
+ server: Server.HTTPS_LOCAL,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ }),
+ }
+ },
+ expected: FetchTestResult.FAILURE,
+}), "private to private to local: failed preflight.");
+
+promise_test(t => fetchTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ redirect: preflightUrl({
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ }),
+ }
+ },
+ expected: FetchTestResult.SUCCESS,
+}), "private to private to local: success.");
+
+promise_test(t => fetchTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ redirect: preflightUrl({
+ server: Server.HTTPS_LOCAL,
+ behavior: { preflight: PreflightBehavior.success(token()) },
+ }),
+ }
+ },
+ fetchOptions: { mode: "no-cors" },
+ expected: FetchTestResult.OPAQUE,
+}), "private to private to local: no-cors success.");
+
+// private -> local -> private
+//
+// Request 1 (private -> local): preflight required.
+// Request 2 (private -> private): no preflight.
+//
+// This verifies that PNA checks are applied independently to every step in a
+// redirect chain.
+
+promise_test(t => fetchTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ response: ResponseBehavior.allowCrossOrigin(),
+ redirect: preflightUrl({
+ server: Server.HTTPS_PRIVATE,
+ }),
+ }
+ },
+ expected: FetchTestResult.FAILURE,
+}), "private to local to private: failed preflight.");
+
+promise_test(t => fetchTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ redirect: preflightUrl({
+ server: Server.HTTPS_PRIVATE,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ }),
+ }
+ },
+ expected: FetchTestResult.SUCCESS,
+}), "private to local to private: success.");
+
+promise_test(t => fetchTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ redirect: preflightUrl({ server: Server.HTTPS_PRIVATE }),
+ }
+ },
+ fetchOptions: { mode: "no-cors" },
+ expected: FetchTestResult.OPAQUE,
+}), "private to local to private: no-cors success.");
+
+// public -> private -> local
+//
+// Request 1 (public -> private): preflight required.
+// Request 2 (public -> local): preflight required.
+//
+// This verifies that PNA checks are applied to every step in a redirect chain.
+
+promise_test(t => fetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ response: ResponseBehavior.allowCrossOrigin(),
+ redirect: preflightUrl({
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ }),
+ }
+ },
+ expected: FetchTestResult.FAILURE,
+}), "public to private to local: failed first preflight.");
+
+promise_test(t => fetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ redirect: preflightUrl({
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ }),
+ }
+ },
+ expected: FetchTestResult.FAILURE,
+}), "public to private to local: failed second preflight.");
+
+promise_test(t => fetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ redirect: preflightUrl({
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ }),
+ }
+ },
+ expected: FetchTestResult.SUCCESS,
+}), "public to private to local: success.");
+
+promise_test(t => fetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ redirect: preflightUrl({
+ server: Server.HTTPS_LOCAL,
+ behavior: { preflight: PreflightBehavior.success(token()) },
+ }),
+ }
+ },
+ fetchOptions: { mode: "no-cors" },
+ expected: FetchTestResult.OPAQUE,
+}), "public to private to local: no-cors success.");
+
+// treat-as-public -> local -> private
+
+// Request 1 (treat-as-public -> local): preflight required.
+// Request 2 (treat-as-public -> private): preflight required.
+
+// This verifies that PNA checks are applied to every step in a redirect chain.
+
+promise_test(t => fetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.OTHER_HTTPS_LOCAL,
+ behavior: {
+ redirect: preflightUrl({
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ }),
+ response: ResponseBehavior.allowCrossOrigin(),
+ }
+ },
+ expected: FetchTestResult.FAILURE,
+}), "treat-as-public to local to private: failed first preflight.");
+
+promise_test(t => fetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.OTHER_HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ redirect: preflightUrl({
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.noPnaHeader(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ }),
+ response: ResponseBehavior.allowCrossOrigin(),
+ }
+ },
+ expected: FetchTestResult.FAILURE,
+}), "treat-as-public to local to private: failed second preflight.");
+
+promise_test(t => fetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.OTHER_HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ redirect: preflightUrl({
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ }),
+ response: ResponseBehavior.allowCrossOrigin(),
+ }
+ },
+ expected: FetchTestResult.SUCCESS,
+}), "treat-as-public to local to private: success.");
+
+promise_test(t => fetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.OTHER_HTTPS_LOCAL,
+ behavior: {
+ redirect: preflightUrl({
+ server: Server.HTTPS_PRIVATE,
+ behavior: { preflight: PreflightBehavior.success(token()) },
+ }),
+ }
+ },
+ fetchOptions: { mode: "no-cors" },
+ expected: FetchTestResult.FAILURE,
+}), "treat-as-public to local to private: no-cors failed first preflight.");
+
+promise_test(t => fetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.OTHER_HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ redirect: preflightUrl({ server: Server.HTTPS_PRIVATE }),
+ }
+ },
+ fetchOptions: { mode: "no-cors" },
+ expected: FetchTestResult.FAILURE,
+}), "treat-as-public to local to private: no-cors failed second preflight.");
+
+promise_test(t => fetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.OTHER_HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ redirect: preflightUrl({
+ server: Server.HTTPS_PRIVATE,
+ behavior: { preflight: PreflightBehavior.success(token()) },
+ }),
+ }
+ },
+ fetchOptions: { mode: "no-cors" },
+ expected: FetchTestResult.OPAQUE,
+}), "treat-as-public to local to private: no-cors success.");
+
+// treat-as-public -> local (same-origin) -> private
+
+// Request 1 (treat-as-public -> local (same-origin)): no preflight required.
+// Request 2 (treat-as-public -> private): preflight required.
+
+// This verifies that PNA checks are applied only to the second step in a
+// redirect chain if the first step is same-origin and the origin is potentially
+// trustworthy.
+
+promise_test(t => fetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ redirect: preflightUrl({
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.noPnaHeader(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ }),
+ }
+ },
+ expected: FetchTestResult.FAILURE,
+}), "treat-as-public to local (same-origin) to private: failed second preflight.");
+
+promise_test(t => fetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ redirect: preflightUrl({
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ }),
+ }
+ },
+ expected: FetchTestResult.SUCCESS,
+}), "treat-as-public to local (same-origin) to private: success.");
+
+promise_test(t => fetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ redirect: preflightUrl({ server: Server.HTTPS_PRIVATE }),
+ }
+ },
+ fetchOptions: { mode: "no-cors" },
+ expected: FetchTestResult.FAILURE,
+}), "treat-as-public to local (same-origin) to private: no-cors failed second preflight.");
+
+promise_test(t => fetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ redirect: preflightUrl({
+ server: Server.HTTPS_PRIVATE,
+ behavior: { preflight: PreflightBehavior.success(token()) },
+ }),
+ }
+ },
+ fetchOptions: { mode: "no-cors" },
+ expected: FetchTestResult.OPAQUE,
+}), "treat-as-public to local (same-origin) to private: no-cors success.");
+
+// treat-as-public -> private -> local
+
+// Request 1 (treat-as-public -> private): preflight required.
+// Request 2 (treat-as-public -> local): preflight required.
+
+// This verifies that PNA checks are applied to every step in a redirect chain.
+
+promise_test(t => fetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.noPnaHeader(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ redirect: preflightUrl({
+ server: Server.OTHER_HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ }),
+ }
+ },
+ expected: FetchTestResult.FAILURE,
+}), "treat-as-public to private to local: failed first preflight.");
+
+promise_test(t => fetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ redirect: preflightUrl({
+ server: Server.OTHER_HTTPS_LOCAL,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ }),
+ }
+ },
+ expected: FetchTestResult.FAILURE,
+}), "treat-as-public to private to local: failed second preflight.");
+
+promise_test(t => fetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ redirect: preflightUrl({
+ server: Server.OTHER_HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ }),
+ }
+ },
+ expected: FetchTestResult.SUCCESS,
+}), "treat-as-public to private to local: success.");
+
+promise_test(t => fetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ redirect: preflightUrl({
+ server: Server.OTHER_HTTPS_LOCAL,
+ behavior: { preflight: PreflightBehavior.success(token()) },
+ }),
+ }
+ },
+ fetchOptions: { mode: "no-cors" },
+ expected: FetchTestResult.FAILURE,
+}), "treat-as-public to private to local: no-cors failed first preflight.");
+
+promise_test(t => fetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ redirect: preflightUrl({ server: Server.OTHER_HTTPS_LOCAL }),
+ }
+ },
+ fetchOptions: { mode: "no-cors" },
+ expected: FetchTestResult.FAILURE,
+}), "treat-as-public to private to local: no-cors failed second preflight.");
+
+promise_test(t => fetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ redirect: preflightUrl({
+ server: Server.OTHER_HTTPS_LOCAL,
+ behavior: { preflight: PreflightBehavior.success(token()) },
+ }),
+ }
+ },
+ fetchOptions: { mode: "no-cors" },
+ expected: FetchTestResult.OPAQUE,
+}), "treat-as-public to private to local: no-cors success.");
+
+// treat-as-public -> private -> local (same-origin)
+
+// Request 1 (treat-as-public -> private): preflight required.
+// Request 2 (treat-as-public -> local (same-origin)): no preflight required.
+
+// This verifies that PNA checks are only applied to the first step in a
+// redirect chain if the second step is same-origin and the origin is
+// potentially trustworthy.
+
+promise_test(t => fetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.noPnaHeader(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ redirect: preflightUrl({
+ server: Server.HTTPS_LOCAL,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ }),
+ }
+ },
+ expected: FetchTestResult.FAILURE,
+}), "treat-as-public to private to local (same-origin): failed first preflight.");
+
+promise_test(t => fetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ redirect: preflightUrl({
+ server: Server.HTTPS_LOCAL,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ }),
+ }
+ },
+ expected: FetchTestResult.SUCCESS,
+}), "treat-as-public to private to local (same-origin): success.");
+
+promise_test(t => fetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ redirect: preflightUrl({ server: Server.HTTPS_LOCAL }),
+ }
+ },
+ fetchOptions: { mode: "no-cors" },
+ expected: FetchTestResult.FAILURE,
+}), "treat-as-public to private to local (same-origin): no-cors failed first preflight.");
+
+promise_test(t => fetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ redirect: preflightUrl({ server: Server.HTTPS_LOCAL }),
+ }
+ },
+ fetchOptions: { mode: "no-cors" },
+ expected: FetchTestResult.OPAQUE,
+}), "treat-as-public to private to local (same-origin): no-cors success.");
diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/anchor.html b/testing/web-platform/tests/fetch/private-network-access/resources/anchor.html
new file mode 100644
index 0000000000..0780b3fa50
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/resources/anchor.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Anchor</title>
+<body></body>
+<script>
+ window.onmessage = (event) => {
+ window.onmessage = (event) => parent.postMessage(event.data, "*");
+ const { url } = event.data;
+ const anchor = document.createElement('a');
+ anchor.href = url;
+ anchor.rel = 'opener';
+ anchor.target = '_blank';
+ document.body.appendChild(anchor);
+ anchor.click();
+ };
+</script>
diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/executor.html b/testing/web-platform/tests/fetch/private-network-access/resources/executor.html
new file mode 100644
index 0000000000..d71212951c
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/resources/executor.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Executor</title>
+<body></body>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script>
+ const uuid = new URL(window.location).searchParams.get("executor-uuid");
+ const executor = new Executor(uuid);
+</script>
diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-fetcher.https.html b/testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-fetcher.https.html
new file mode 100644
index 0000000000..b14601dba5
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-fetcher.https.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="../../../fenced-frame/resources/utils.js"></script>
+<title>Fetcher</title>
+<script>
+ const url = new URL(location.href).searchParams.get("url");
+ const mode = new URL(location.href).searchParams.get("mode");
+ const method = new URL(location.href).searchParams.get("method");
+ const [error_token, ok_token, body_token, type_token] = parseKeylist();
+
+ fetch(url, {mode: mode, method: method})
+ .then(async function(response) {
+ const body = await response.text();
+ writeValueToServer(ok_token, response.ok);
+ writeValueToServer(body_token, body);
+ writeValueToServer(type_token, response.type);
+ writeValueToServer(error_token, "");
+ })
+ .catch(error => {
+ writeValueToServer(ok_token, "");
+ writeValueToServer(body_token, "");
+ writeValueToServer(type_token, "");
+ writeValueToServer(error_token, error.toString());
+ });
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-fetcher.https.html.headers b/testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-fetcher.https.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-fetcher.https.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-private-network-access-target.https.html b/testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-private-network-access-target.https.html
new file mode 100644
index 0000000000..2b55e056f3
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-private-network-access-target.https.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="../../../fenced-frame/resources/utils.js"></script>
+<title>Fenced frame target</title>
+<script>
+ const [frame_loaded_key] = parseKeylist();
+ writeValueToServer(frame_loaded_key, 'loaded');
+</script>
diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-private-network-access.https.html b/testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-private-network-access.https.html
new file mode 100644
index 0000000000..98f118432e
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-private-network-access.https.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="../../../fenced-frame/resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+<title>Fenced frame</title>
+<body></body>
+<script>
+(async () => {
+ const target = new URL(location.href).searchParams.get("fenced_frame_url");
+ const urn = await runSelectURL(target);
+ attachFencedFrame(urn);
+})();
+</script>
diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-private-network-access.https.html.headers b/testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-private-network-access.https.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/resources/fenced-frame-private-network-access.https.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/fetcher.html b/testing/web-platform/tests/fetch/private-network-access/resources/fetcher.html
new file mode 100644
index 0000000000..000a5cc25b
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/resources/fetcher.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Fetcher</title>
+<script>
+ window.addEventListener("message", function (event) {
+ const { url, options } = event.data;
+ fetch(url, options)
+ .then(async function(response) {
+ const body = await response.text();
+ const message = {
+ ok: response.ok,
+ type: response.type,
+ body: body,
+ };
+ parent.postMessage(message, "*");
+ })
+ .catch(error => {
+ parent.postMessage({ error: error.toString() }, "*");
+ });
+ });
+</script>
diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/fetcher.js b/testing/web-platform/tests/fetch/private-network-access/resources/fetcher.js
new file mode 100644
index 0000000000..3a1859876d
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/resources/fetcher.js
@@ -0,0 +1,20 @@
+async function doFetch(url) {
+ const response = await fetch(url);
+ const body = await response.text();
+ return {
+ status: response.status,
+ body,
+ };
+}
+
+async function fetchAndPost(url) {
+ try {
+ const message = await doFetch(url);
+ self.postMessage(message);
+ } catch(e) {
+ self.postMessage({ error: e.name });
+ }
+}
+
+const url = new URL(self.location.href).searchParams.get("url");
+fetchAndPost(url);
diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/iframed-no-preflight-received.html b/testing/web-platform/tests/fetch/private-network-access/resources/iframed-no-preflight-received.html
new file mode 100644
index 0000000000..20b5150d44
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/resources/iframed-no-preflight-received.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Iframed</title>
+<script>
+ const uuid = new URL(window.location).searchParams.get("iframe-uuid");
+ top.postMessage({ uuid, message: "no preflight received" }, "*");
+</script>
diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/iframed.html b/testing/web-platform/tests/fetch/private-network-access/resources/iframed.html
new file mode 100644
index 0000000000..c889c2882a
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/resources/iframed.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Iframed</title>
+<script>
+ const uuid = new URL(window.location).searchParams.get("iframe-uuid");
+ top.postMessage({ uuid, message: "loaded" }, "*");
+</script>
diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/iframer.html b/testing/web-platform/tests/fetch/private-network-access/resources/iframer.html
new file mode 100644
index 0000000000..304cc54ae4
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/resources/iframer.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Iframer</title>
+<body></body>
+<script>
+ const child = document.createElement("iframe");
+ child.src = new URL(window.location).searchParams.get("url");
+ document.body.appendChild(child);
+</script>
diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/no-preflight-received.html b/testing/web-platform/tests/fetch/private-network-access/resources/no-preflight-received.html
new file mode 100644
index 0000000000..5ee533e182
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/resources/no-preflight-received.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>No preflight received</title>
+<script>
+ if (window.opener) window.opener.postMessage("no preflight received", "*");
+</script>
diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/open-to-existing-window.html b/testing/web-platform/tests/fetch/private-network-access/resources/open-to-existing-window.html
new file mode 100644
index 0000000000..6460024bc8
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/resources/open-to-existing-window.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Opener to an existing window</title>
+<body></body>
+<script>
+ window.onmessage = (event) => {
+ window.onmessage = (event) => parent.postMessage(event.data, "*");
+ const { url, token } = event.data;
+ window.open('', token);
+ window.open(url, token);
+ };
+</script>
diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/openee.html b/testing/web-platform/tests/fetch/private-network-access/resources/openee.html
new file mode 100644
index 0000000000..8f0a859cb3
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/resources/openee.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Openee</title>
+<script>
+ if (window.opener) {
+ window.opener.postMessage("success", "*");
+ }
+</script>
diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/opener.html b/testing/web-platform/tests/fetch/private-network-access/resources/opener.html
new file mode 100644
index 0000000000..78b66c6db7
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/resources/opener.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Opener</title>
+<body></body>
+<script>
+ window.onmessage = (event) => {
+ window.onmessage = (event) => parent.postMessage(event.data, "*");
+ const { url } = event.data;
+ window.open(url);
+ };
+</script>
diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/preflight.py b/testing/web-platform/tests/fetch/private-network-access/resources/preflight.py
new file mode 100644
index 0000000000..4467663239
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/resources/preflight.py
@@ -0,0 +1,191 @@
+# This endpoint responds to both preflight requests and the subsequent requests.
+#
+# Its behavior can be configured with various search/GET parameters, all of
+# which are optional:
+#
+# - treat-as-public-once: Must be a valid UUID if set.
+# If set, then this endpoint expects to receive a non-preflight request first,
+# for which it sets the `Content-Security-Policy: treat-as-public-address`
+# response header. This allows testing "DNS rebinding", where a URL first
+# resolves to the public IP address space, then a non-public IP address space.
+# - preflight-uuid: Must be a valid UUID if set, distinct from the value of the
+# `treat-as-public-once` parameter if both are set.
+# If set, then this endpoint expects to receive a preflight request first
+# followed by a regular request, as in the regular CORS protocol. If the
+# `treat-as-public-once` header is also set, it takes precedence: this
+# endpoint expects to receive a non-preflight request first, then a preflight
+# request, then finally a regular request.
+# If unset, then this endpoint expects to receive no preflight request, only
+# a regular (non-OPTIONS) request.
+# - preflight-headers: Valid values are:
+# - cors: this endpoint responds with valid CORS headers to preflights. These
+# should be sufficient for non-PNA preflight requests to succeed, but not
+# for PNA-specific preflight requests.
+# - cors+pna: this endpoint responds with valid CORS and PNA headers to
+# preflights. These should be sufficient for both non-PNA preflight
+# requests and PNA-specific preflight requests to succeed.
+# - cors+pna+sw: this endpoint responds with valid CORS and PNA headers and
+# "Access-Control-Allow-Headers: Service-Worker" to preflights. These should
+# be sufficient for both non-PNA preflight requests and PNA-specific
+# preflight requests to succeed. This allows the main request to fetch a
+# service worker script.
+# - unspecified, or any other value: this endpoint responds with no CORS or
+# PNA headers. Preflight requests should fail.
+# - final-headers: Valid values are:
+# - cors: this endpoint responds with valid CORS headers to CORS-enabled
+# non-preflight requests. These should be sufficient for non-preflighted
+# CORS-enabled requests to succeed.
+# - unspecified: this endpoint responds with no CORS headers to non-preflight
+# requests. This should fail CORS-enabled requests, but be sufficient for
+# no-CORS requests.
+#
+# The following parameters only affect non-preflight responses:
+#
+# - redirect: If set, the response code is set to 301 and the `Location`
+# response header is set to this value.
+# - mime-type: If set, the `Content-Type` response header is set to this value.
+# - file: Specifies a path (relative to this file's directory) to a file. If
+# set, the response body is copied from this file.
+# - random-js-prefix: If set to any value, the response body is prefixed with
+# a Javascript comment line containing a random value. This is useful in
+# service worker tests, since service workers are only updated if the new
+# script is not byte-for-byte identical with the old script.
+# - body: If set and `file` is not, the response body is set to this value.
+#
+
+import os
+import random
+
+from wptserve.utils import isomorphic_encode
+
+_ACAO = ("Access-Control-Allow-Origin", "*")
+_ACAPN = ("Access-Control-Allow-Private-Network", "true")
+_ACAH = ("Access-Control-Allow-Headers", "Service-Worker")
+
+def _get_response_headers(method, mode, origin):
+ acam = ("Access-Control-Allow-Methods", method)
+
+ if mode == b"cors":
+ return [acam, _ACAO]
+
+ if mode == b"cors+pna":
+ return [acam, _ACAO, _ACAPN]
+
+ if mode == b"cors+pna+sw":
+ return [acam, _ACAO, _ACAPN, _ACAH]
+
+ if mode == b"navigation":
+ return [
+ acam,
+ ("Access-Control-Allow-Origin", origin),
+ ("Access-Control-Allow-Credentials", "true"),
+ _ACAPN,
+ ]
+
+ return []
+
+def _get_expect_single_preflight(request):
+ return request.GET.get(b"expect-single-preflight")
+
+def _is_preflight_optional(request):
+ return request.GET.get(b"is-preflight-optional") or \
+ request.GET.get(b"file-if-no-preflight-received")
+
+def _get_preflight_uuid(request):
+ return request.GET.get(b"preflight-uuid")
+
+def _is_loaded_in_fenced_frame(request):
+ return request.GET.get(b"is-loaded-in-fenced-frame")
+
+def _should_treat_as_public_once(request):
+ uuid = request.GET.get(b"treat-as-public-once")
+ if uuid is None:
+ # If the search parameter is not given, never treat as public.
+ return False
+
+ # If the parameter is given, we treat the request as public only if the UUID
+ # has never been seen and stashed.
+ result = request.server.stash.take(uuid) is None
+ request.server.stash.put(uuid, "")
+ return result
+
+def _handle_preflight_request(request, response):
+ if _should_treat_as_public_once(request):
+ return (400, [], "received preflight for first treat-as-public request")
+
+ uuid = _get_preflight_uuid(request)
+ if uuid is None:
+ return (400, [], "missing `preflight-uuid` param from preflight URL")
+
+ value = request.server.stash.take(uuid)
+ request.server.stash.put(uuid, "preflight")
+ if _get_expect_single_preflight(request) and value is not None:
+ return (400, [], "received duplicated preflight")
+
+ method = request.headers.get("Access-Control-Request-Method")
+ mode = request.GET.get(b"preflight-headers")
+ origin = request.headers.get("Origin")
+ headers = _get_response_headers(method, mode, origin)
+
+ return (headers, "preflight")
+
+def _final_response_body(request, missing_preflight):
+ file_name = None
+ if missing_preflight and not request.GET.get(b"is-preflight-optional"):
+ file_name = request.GET.get(b"file-if-no-preflight-received")
+ if file_name is None:
+ file_name = request.GET.get(b"file")
+ if file_name is None:
+ return request.GET.get(b"body") or "success"
+
+ prefix = b""
+ if request.GET.get(b"random-js-prefix"):
+ value = random.randint(0, 1000000000)
+ prefix = isomorphic_encode("// Random value: {}\n\n".format(value))
+
+ path = os.path.join(os.path.dirname(isomorphic_encode(__file__)), file_name)
+ with open(path, 'rb') as f:
+ contents = f.read()
+
+ return prefix + contents
+
+def _handle_final_request(request, response):
+ missing_preflight = False
+ if _should_treat_as_public_once(request):
+ headers = [("Content-Security-Policy", "treat-as-public-address"),]
+ else:
+ uuid = _get_preflight_uuid(request)
+ if uuid is not None:
+ missing_preflight = request.server.stash.take(uuid) is None
+ if missing_preflight and not _is_preflight_optional(request):
+ return (405, [], "no preflight received")
+ request.server.stash.put(uuid, "final")
+
+ mode = request.GET.get(b"final-headers")
+ origin = request.headers.get("Origin")
+ headers = _get_response_headers(request.method, mode, origin)
+
+ redirect = request.GET.get(b"redirect")
+ if redirect is not None:
+ headers.append(("Location", redirect))
+ return (301, headers, b"")
+
+ mime_type = request.GET.get(b"mime-type")
+ if mime_type is not None:
+ headers.append(("Content-Type", mime_type),)
+
+ if _is_loaded_in_fenced_frame(request):
+ headers.append(("Supports-Loading-Mode", "fenced-frame"))
+
+ body = _final_response_body(request, missing_preflight)
+ return (headers, body)
+
+def main(request, response):
+ try:
+ if request.method == "OPTIONS":
+ return _handle_preflight_request(request, response)
+ else:
+ return _handle_final_request(request, response)
+ except BaseException as e:
+ # Surface exceptions to the client, where they show up as assertion errors.
+ return (500, [("X-exception", str(e))], "exception: {}".format(e))
diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/service-worker-bridge.html b/testing/web-platform/tests/fetch/private-network-access/resources/service-worker-bridge.html
new file mode 100644
index 0000000000..816de535fe
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/resources/service-worker-bridge.html
@@ -0,0 +1,155 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>ServiceWorker Bridge</title>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script>
+ // This bridge document exists to perform service worker commands on behalf
+ // of a test page. It lives within the same scope (including origin) as the
+ // service worker script, allowing it to be controlled by the service worker.
+
+ async function register({ url, options }) {
+ await navigator.serviceWorker.register(url, options);
+ return { loaded: true };
+ }
+
+ async function unregister({ scope }) {
+ const registration = await navigator.serviceWorker.getRegistration(scope);
+ if (!registration) {
+ return { unregistered: false, error: "no registration" };
+ }
+
+ const unregistered = await registration.unregister();
+ return { unregistered };
+ }
+
+ async function update({ scope }) {
+ const registration = await navigator.serviceWorker.getRegistration(scope);
+ if (!registration) {
+ return { updated: false, error: "no registration" };
+ }
+
+ const newRegistration = await registration.update();
+ return { updated: true };
+ }
+
+ // Total number of `controllerchange` events since document creation.
+ let totalNumControllerChanges = 0;
+ navigator.serviceWorker.addEventListener("controllerchange", () => {
+ totalNumControllerChanges++;
+ });
+
+ // Using `navigator.serviceWorker.ready` does not allow noticing new
+ // controllers after an update, so we count `controllerchange` events instead.
+ // This has the added benefit of ensuring that subsequent fetches are handled
+ // by the service worker, whereas `ready` does not guarantee that.
+ async function wait({ numControllerChanges }) {
+ if (totalNumControllerChanges >= numControllerChanges) {
+ return {
+ controlled: !!navigator.serviceWorker.controller,
+ numControllerChanges: totalNumControllerChanges,
+ };
+ }
+
+ let remaining = numControllerChanges - totalNumControllerChanges;
+ await new Promise((resolve) => {
+ navigator.serviceWorker.addEventListener("controllerchange", () => {
+ remaining--;
+ if (remaining == 0) {
+ resolve();
+ }
+ });
+ });
+
+ return {
+ controlled: !!navigator.serviceWorker.controller,
+ numControllerChanges,
+ };
+ }
+
+ async function doFetch({ url, options }) {
+ const response = await fetch(url, options);
+ const body = await response.text();
+ return {
+ ok: response.ok,
+ body,
+ };
+ }
+
+ async function setPermission({ name, state }) {
+ await test_driver.set_permission({ name }, state);
+
+ // Double-check, just to be sure.
+ // See the comment in `../service-worker-background-fetch.js`.
+ const permissionStatus = await navigator.permissions.query({ name });
+ return { state: permissionStatus.state };
+ }
+
+ async function backgroundFetch({ scope, url }) {
+ const registration = await navigator.serviceWorker.getRegistration(scope);
+ if (!registration) {
+ return { error: "no registration" };
+ }
+
+ const fetchRegistration =
+ await registration.backgroundFetch.fetch("test", url);
+ const resultReady = new Promise((resolve) => {
+ fetchRegistration.addEventListener("progress", () => {
+ if (fetchRegistration.result) {
+ resolve();
+ }
+ });
+ });
+
+ let ok;
+ let body;
+ const record = await fetchRegistration.match(url);
+ if (record) {
+ const response = await record.responseReady;
+ body = await response.text();
+ ok = response.ok;
+ }
+
+ // Wait for the result after getting the response. If the steps are
+ // inverted, then sometimes the response is not found due to an
+ // `UnknownError`.
+ await resultReady;
+
+ return {
+ result: fetchRegistration.result,
+ failureReason: fetchRegistration.failureReason,
+ ok,
+ body,
+ };
+ }
+
+ function getAction(action) {
+ switch (action) {
+ case "register":
+ return register;
+ case "unregister":
+ return unregister;
+ case "wait":
+ return wait;
+ case "update":
+ return update;
+ case "fetch":
+ return doFetch;
+ case "set-permission":
+ return setPermission;
+ case "background-fetch":
+ return backgroundFetch;
+ }
+ }
+
+ window.addEventListener("message", async (evt) => {
+ let message;
+ try {
+ const action = getAction(evt.data.action);
+ message = await action(evt.data);
+ } catch(e) {
+ message = { error: e.name };
+ }
+ parent.postMessage(message, "*");
+ });
+</script>
diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/service-worker.js b/testing/web-platform/tests/fetch/private-network-access/resources/service-worker.js
new file mode 100644
index 0000000000..bca71ad910
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/resources/service-worker.js
@@ -0,0 +1,18 @@
+self.addEventListener("install", () => {
+ // Skip waiting before replacing the previously-active service worker, if any.
+ // This allows the bridge script to notice the controller change and query
+ // the install time via fetch.
+ self.skipWaiting();
+});
+
+self.addEventListener("activate", (event) => {
+ // Claim all clients so that the bridge script notices the activation.
+ event.waitUntil(self.clients.claim());
+});
+
+self.addEventListener("fetch", (event) => {
+ const url = new URL(event.request.url).searchParams.get("proxied-url");
+ if (url) {
+ event.respondWith(fetch(url));
+ }
+});
diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/shared-fetcher.js b/testing/web-platform/tests/fetch/private-network-access/resources/shared-fetcher.js
new file mode 100644
index 0000000000..30bde1e054
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/resources/shared-fetcher.js
@@ -0,0 +1,23 @@
+async function doFetch(url) {
+ const response = await fetch(url);
+ const body = await response.text();
+ return {
+ status: response.status,
+ body,
+ };
+}
+
+async function fetchAndPost(url, port) {
+ try {
+ const message = await doFetch(url);
+ port.postMessage(message);
+ } catch(e) {
+ port.postMessage({ error: e.name });
+ }
+}
+
+const url = new URL(self.location.href).searchParams.get("url");
+
+self.addEventListener("connect", async (evt) => {
+ await fetchAndPost(url, evt.ports[0]);
+});
diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/shared-worker-blob-fetcher.html b/testing/web-platform/tests/fetch/private-network-access/resources/shared-worker-blob-fetcher.html
new file mode 100644
index 0000000000..a79869b2f9
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/resources/shared-worker-blob-fetcher.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>SharedWorker Blob Fetcher</title>
+<script>
+ window.addEventListener("message", function (evt) {
+ let { url } = evt.data;
+
+ const workerScriptContent = `
+ async function doFetch(url) {
+ const response = await fetch(url);
+ const body = await response.text();
+ return {
+ status: response.status,
+ body,
+ };
+ }
+
+ async function fetchAndPost(url, port) {
+ try {
+ const message = await doFetch(url);
+ port.postMessage(message);
+ } catch(e) {
+ port.postMessage({ error: e.name });
+ }
+ }
+
+ const url = "${url}";
+
+ self.addEventListener("connect", async (evt) => {
+ await fetchAndPost(url, evt.ports[0]);
+ });
+ `;
+ const blob =
+ new Blob([workerScriptContent], {type: 'application/javascript'});
+ const workerScriptUrl = URL.createObjectURL(blob);
+
+ const worker = new SharedWorker(workerScriptUrl);
+
+ URL.revokeObjectURL(workerScriptUrl);
+
+ worker.onerror = (evt) => {
+ parent.postMessage({ error: evt.message || "unknown error" }, "*");
+ };
+
+ worker.port.addEventListener("message", (evt) => {
+ parent.postMessage(evt.data, "*");
+ });
+ worker.port.start();
+ });
+</script>
diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/shared-worker-fetcher.html b/testing/web-platform/tests/fetch/private-network-access/resources/shared-worker-fetcher.html
new file mode 100644
index 0000000000..4af4b1f239
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/resources/shared-worker-fetcher.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>SharedWorker Fetcher</title>
+<script>
+ window.addEventListener("message", function (evt) {
+ let { url } = evt.data;
+
+ const worker = new SharedWorker(url);
+
+ worker.onerror = (evt) => {
+ parent.postMessage({ error: evt.message || "unknown error" }, "*");
+ };
+
+ worker.port.addEventListener("message", (evt) => {
+ parent.postMessage(evt.data, "*");
+ });
+ worker.port.start();
+ });
+</script>
diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/socket-opener.html b/testing/web-platform/tests/fetch/private-network-access/resources/socket-opener.html
new file mode 100644
index 0000000000..48d27216be
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/resources/socket-opener.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebSocket Opener</title>
+<script>
+ window.addEventListener("message", function (event) {
+ const socket = new WebSocket(event.data);
+
+ socket.onopen = () => {
+ parent.postMessage("open", "*");
+ };
+ socket.onclose = (evt) => {
+ parent.postMessage(`close: code ${evt.code}`, "*");
+ };
+ });
+</script>
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");
+}
diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/worker-blob-fetcher.html b/testing/web-platform/tests/fetch/private-network-access/resources/worker-blob-fetcher.html
new file mode 100644
index 0000000000..5a50271e11
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/resources/worker-blob-fetcher.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Worker Blob Fetcher</title>
+<script>
+ window.addEventListener("message", function (evt) {
+ const { url } = evt.data;
+
+ const workerScriptContent = `
+ async function doFetch(url) {
+ const response = await fetch(url);
+ const body = await response.text();
+ return {
+ status: response.status,
+ body,
+ };
+ }
+
+ async function fetchAndPost(url) {
+ try {
+ const message = await doFetch(url);
+ self.postMessage(message);
+ } catch(e) {
+ self.postMessage({ error: e.name });
+ }
+ }
+
+ fetchAndPost("${url}");
+ `;
+ const blob =
+ new Blob([workerScriptContent], {type: 'application/javascript'});
+ const workerScriptUrl = URL.createObjectURL(blob);
+
+ const worker = new Worker(workerScriptUrl);
+
+ URL.revokeObjectURL(workerScriptUrl);
+
+ worker.addEventListener("message", (evt) => {
+ parent.postMessage(evt.data, "*");
+ });
+
+ worker.addEventListener("error", (evt) => {
+ parent.postMessage({ error: evt.message || "unknown error" }, "*");
+ });
+ });
+</script>
diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/worker-fetcher.html b/testing/web-platform/tests/fetch/private-network-access/resources/worker-fetcher.html
new file mode 100644
index 0000000000..bd155a532b
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/resources/worker-fetcher.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Worker Fetcher</title>
+<script>
+ window.addEventListener("message", function (evt) {
+ let { url } = evt.data;
+
+ const worker = new Worker(url);
+
+ worker.addEventListener("message", (evt) => {
+ parent.postMessage(evt.data, "*");
+ });
+
+ worker.addEventListener("error", (evt) => {
+ parent.postMessage({ error: evt.message || "unknown error" }, "*");
+ });
+ });
+</script>
diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/worker-fetcher.js b/testing/web-platform/tests/fetch/private-network-access/resources/worker-fetcher.js
new file mode 100644
index 0000000000..aab49afe6f
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/resources/worker-fetcher.js
@@ -0,0 +1,11 @@
+const url = new URL(self.location).searchParams.get("url");
+const worker = new Worker(url);
+
+// Relay messages from the worker to the parent frame.
+worker.addEventListener("message", (evt) => {
+ self.postMessage(evt.data);
+});
+
+worker.addEventListener("error", (evt) => {
+ self.postMessage({ error: evt.message || "unknown error" });
+});
diff --git a/testing/web-platform/tests/fetch/private-network-access/resources/xhr-sender.html b/testing/web-platform/tests/fetch/private-network-access/resources/xhr-sender.html
new file mode 100644
index 0000000000..b131fa41f9
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/resources/xhr-sender.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>XHR Sender</title>
+<script>
+ window.addEventListener("message", function (event) {
+ let { url, method } = event.data;
+ if (!method) {
+ method = "GET";
+ }
+
+ const xhr = new XMLHttpRequest;
+
+ xhr.addEventListener("load", (evt) => {
+ const message = {
+ loaded: true,
+ status: xhr.status,
+ body: xhr.responseText,
+ };
+ parent.postMessage(message, "*");
+ });
+
+ xhr.addEventListener("error", (evt) => {
+ const message = {
+ loaded: false,
+ status: xhr.status,
+ };
+ parent.postMessage(message, "*");
+ });
+
+ xhr.open(method, url);
+ xhr.send();
+ });
+</script>
diff --git a/testing/web-platform/tests/fetch/private-network-access/service-worker-background-fetch.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/service-worker-background-fetch.tentative.https.window.js
new file mode 100644
index 0000000000..8d1028cc5e
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/service-worker-background-fetch.tentative.https.window.js
@@ -0,0 +1,143 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+// META: timeout=long
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+// Spec: https://wicg.github.io/background-fetch/
+//
+// These tests check that background fetches from within `ServiceWorker` scripts
+// are not subject to Private Network Access checks.
+
+// Results that may be expected in tests.
+const TestResult = {
+ SUCCESS: { ok: true, body: "success", result: "success", failureReason: "" },
+};
+
+async function makeTest(t, { source, target, expected }) {
+ const scriptUrl =
+ resolveUrl("resources/service-worker.js", sourceResolveOptions(source));
+
+ const bridgeUrl = new URL("service-worker-bridge.html", scriptUrl);
+
+ const targetUrl = preflightUrl(target);
+
+ const iframe = await appendIframe(t, document, bridgeUrl);
+
+ const request = (message) => {
+ const reply = futureMessage();
+ iframe.contentWindow.postMessage(message, "*");
+ return reply;
+ };
+
+ {
+ const { error, loaded } = await request({
+ action: "register",
+ url: scriptUrl.href,
+ });
+
+ assert_equals(error, undefined, "register error");
+ assert_true(loaded, "response loaded");
+ }
+
+ {
+ const { error, state } = await request({
+ action: "set-permission",
+ name: "background-fetch",
+ state: "granted",
+ });
+
+ assert_equals(error, undefined, "set permission error");
+ assert_equals(state, "granted", "permission state");
+ }
+
+ {
+ const { error, result, failureReason, ok, body } = await request({
+ action: "background-fetch",
+ url: targetUrl.href,
+ });
+
+ assert_equals(error, expected.error, "error");
+ assert_equals(failureReason, expected.failureReason, "fetch failure reason");
+ assert_equals(result, expected.result, "fetch result");
+ assert_equals(ok, expected.ok, "response ok");
+ assert_equals(body, expected.body, "response body");
+ }
+}
+
+promise_test(t => makeTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: { server: Server.HTTPS_LOCAL },
+ expected: TestResult.SUCCESS,
+}), "local to local: success.");
+
+promise_test(t => makeTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: TestResult.SUCCESS,
+}), "private to local: success.");
+
+promise_test(t => makeTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: { server: Server.HTTPS_PRIVATE },
+ expected: TestResult.SUCCESS,
+}), "private to private: success.");
+
+promise_test(t => makeTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: TestResult.SUCCESS,
+}), "public to local: success.");
+
+promise_test(t => makeTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: TestResult.SUCCESS,
+}), "public to private: success.");
+
+promise_test(t => makeTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: { server: Server.HTTPS_PUBLIC },
+ expected: TestResult.SUCCESS,
+}), "public to public: success.");
+
+promise_test(t => makeTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTPS_LOCAL },
+ expected: TestResult.SUCCESS,
+}), "treat-as-public to local: success.");
+
+promise_test(t => makeTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: TestResult.SUCCESS,
+}), "treat-as-public to private: success.");
+
+promise_test(t => makeTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PUBLIC,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: TestResult.SUCCESS,
+}), "treat-as-public to public: success.");
diff --git a/testing/web-platform/tests/fetch/private-network-access/service-worker-fetch.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/service-worker-fetch.tentative.https.window.js
new file mode 100644
index 0000000000..cb6d1f79b0
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/service-worker-fetch.tentative.https.window.js
@@ -0,0 +1,235 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+// META: script=/common/subset-tests.js
+// META: variant=?1-8
+// META: variant=?9-last
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests check that fetches from within `ServiceWorker` scripts are
+// subject to Private Network Access checks, just like fetches from within
+// documents.
+
+// Results that may be expected in tests.
+const TestResult = {
+ SUCCESS: { ok: true, body: "success" },
+ FAILURE: { error: "TypeError" },
+};
+
+async function makeTest(t, { source, target, expected }) {
+ const bridgeUrl = resolveUrl(
+ "resources/service-worker-bridge.html",
+ sourceResolveOptions({ server: source.server }));
+
+ const scriptUrl =
+ resolveUrl("resources/service-worker.js", sourceResolveOptions(source));
+
+ const realTargetUrl = preflightUrl(target);
+
+ // Fetch a URL within the service worker's scope, but tell it which URL to
+ // really fetch.
+ const targetUrl = new URL("service-worker-proxy", scriptUrl);
+ targetUrl.searchParams.append("proxied-url", realTargetUrl.href);
+
+ const iframe = await appendIframe(t, document, bridgeUrl);
+
+ const request = (message) => {
+ const reply = futureMessage();
+ iframe.contentWindow.postMessage(message, "*");
+ return reply;
+ };
+
+ {
+ const { error, loaded } = await request({
+ action: "register",
+ url: scriptUrl.href,
+ });
+
+ assert_equals(error, undefined, "register error");
+ assert_true(loaded, "response loaded");
+ }
+
+ try {
+ const { controlled, numControllerChanges } = await request({
+ action: "wait",
+ numControllerChanges: 1,
+ });
+
+ assert_equals(numControllerChanges, 1, "controller change");
+ assert_true(controlled, "bridge script is controlled");
+
+ const { error, ok, body } = await request({
+ action: "fetch",
+ url: targetUrl.href,
+ });
+
+ assert_equals(error, expected.error, "fetch error");
+ assert_equals(ok, expected.ok, "response ok");
+ assert_equals(body, expected.body, "response body");
+ } finally {
+ // Always unregister the service worker.
+ const { error, unregistered } = await request({
+ action: "unregister",
+ scope: new URL("./", scriptUrl).href,
+ });
+
+ assert_equals(error, undefined, "unregister error");
+ assert_true(unregistered, "unregistered");
+ }
+}
+
+subsetTest(promise_test, t => makeTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: { server: Server.HTTPS_LOCAL },
+ expected: TestResult.SUCCESS,
+}), "local to local: success.");
+
+subsetTest(promise_test, t => makeTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: TestResult.FAILURE,
+}), "private to local: failed preflight.");
+
+subsetTest(promise_test, t => makeTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: TestResult.SUCCESS,
+}), "private to local: success.");
+
+subsetTest(promise_test, t => makeTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: { server: Server.HTTPS_PRIVATE },
+ expected: TestResult.SUCCESS,
+}), "private to private: success.");
+
+subsetTest(promise_test, t => makeTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: TestResult.FAILURE,
+}), "public to local: failed preflight.");
+
+subsetTest(promise_test, t => makeTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: TestResult.SUCCESS,
+}), "public to local: success.");
+
+subsetTest(promise_test, t => makeTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: TestResult.FAILURE,
+}), "public to private: failed preflight.");
+
+subsetTest(promise_test, t => makeTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: TestResult.SUCCESS,
+}), "public to private: success.");
+
+subsetTest(promise_test, t => makeTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: { server: Server.HTTPS_PUBLIC },
+ expected: TestResult.SUCCESS,
+}), "public to public: success.");
+
+subsetTest(promise_test, t => makeTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.OTHER_HTTPS_LOCAL,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: TestResult.FAILURE,
+}), "treat-as-public to local: failed preflight.");
+
+subsetTest(promise_test, t => makeTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.OTHER_HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: TestResult.SUCCESS,
+}), "treat-as-public to local: success.");
+
+subsetTest(promise_test, t => makeTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTPS_LOCAL },
+ expected: TestResult.SUCCESS,
+}), "treat-as-public to local (same-origin): no preflight required.");
+
+subsetTest(promise_test, t => makeTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: TestResult.FAILURE,
+}), "treat-as-public to private: failed preflight.");
+
+subsetTest(promise_test, t => makeTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: TestResult.SUCCESS,
+}), "treat-as-public to private: success.");
+
+subsetTest(promise_test, t => makeTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PUBLIC,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: TestResult.SUCCESS,
+}), "treat-as-public to public: success.");
diff --git a/testing/web-platform/tests/fetch/private-network-access/service-worker-update.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/service-worker-update.tentative.https.window.js
new file mode 100644
index 0000000000..4882d235bb
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/service-worker-update.tentative.https.window.js
@@ -0,0 +1,106 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests check that `ServiceWorker` script update fetches are exempt from
+// Private Network Access checks because they are always same-origin and the
+// origin is potentially trustworthy. The client of the fetch, for PNA purposes,
+// is taken to be the previous script.
+//
+// The tests is carried out by instantiating a service worker from a resource
+// that carries the `Content-Security-Policy: treat-as-public-address` header,
+// such that the registration is placed in the public IP address space. When
+// the script is fetched for an update, the client is thus considered public,
+// yet the same-origin fetch observes that the server's IP endpoint is not
+// necessarily in the public IP address space.
+//
+// See also: worker.https.window.js
+
+// Results that may be expected in tests.
+const TestResult = {
+ SUCCESS: { updated: true },
+ FAILURE: { error: "TypeError" },
+};
+
+async function makeTest(t, { target, expected }) {
+ // The bridge must be same-origin with the service worker script.
+ const bridgeUrl = resolveUrl(
+ "resources/service-worker-bridge.html",
+ sourceResolveOptions({ server: target.server }));
+
+ const scriptUrl = preflightUrl(target);
+ scriptUrl.searchParams.append("treat-as-public-once", token());
+ scriptUrl.searchParams.append("mime-type", "application/javascript");
+ scriptUrl.searchParams.append("file", "service-worker.js");
+ scriptUrl.searchParams.append("random-js-prefix", true);
+
+ const iframe = await appendIframe(t, document, bridgeUrl);
+
+ const request = (message) => {
+ const reply = futureMessage();
+ iframe.contentWindow.postMessage(message, "*");
+ return reply;
+ };
+
+ {
+ const { error, loaded } = await request({
+ action: "register",
+ url: scriptUrl.href,
+ });
+
+ assert_equals(error, undefined, "register error");
+ assert_true(loaded, "response loaded");
+ }
+
+ try {
+ let { controlled, numControllerChanges } = await request({
+ action: "wait",
+ numControllerChanges: 1,
+ });
+
+ assert_equals(numControllerChanges, 1, "controller change");
+ assert_true(controlled, "bridge script is controlled");
+
+ const { error, updated } = await request({ action: "update" });
+
+ assert_equals(error, expected.error, "update error");
+ assert_equals(updated, expected.updated, "registration updated");
+
+ // Stop here if we do not expect the update to succeed.
+ if (!expected.updated) {
+ return;
+ }
+
+ ({ controlled, numControllerChanges } = await request({
+ action: "wait",
+ numControllerChanges: 2,
+ }));
+
+ assert_equals(numControllerChanges, 2, "controller change");
+ assert_true(controlled, "bridge script still controlled");
+ } finally {
+ const { error, unregistered } = await request({
+ action: "unregister",
+ scope: new URL("./", scriptUrl).href,
+ });
+
+ assert_equals(error, undefined, "unregister error");
+ assert_true(unregistered, "unregistered");
+ }
+}
+
+promise_test(t => makeTest(t, {
+ target: { server: Server.HTTPS_LOCAL },
+ expected: TestResult.SUCCESS,
+}), "update public to local: success.");
+
+promise_test(t => makeTest(t, {
+ target: { server: Server.HTTPS_PRIVATE },
+ expected: TestResult.SUCCESS,
+}), "update public to private: success.");
+
+promise_test(t => makeTest(t, {
+ target: { server: Server.HTTPS_PUBLIC },
+ expected: TestResult.SUCCESS,
+}), "update public to public: success.");
diff --git a/testing/web-platform/tests/fetch/private-network-access/service-worker.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/service-worker.tentative.https.window.js
new file mode 100644
index 0000000000..046f662a12
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/service-worker.tentative.https.window.js
@@ -0,0 +1,84 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests check that initial `ServiceWorker` script fetches are exempt from
+// Private Network Access checks because they are always same-origin and the
+// origin is potentially trustworthy.
+//
+// See also: worker.https.window.js
+
+// Results that may be expected in tests.
+const TestResult = {
+ SUCCESS: {
+ register: { loaded: true },
+ unregister: { unregistered: true },
+ },
+ FAILURE: {
+ register: { error: "TypeError" },
+ unregister: { unregistered: false, error: "no registration" },
+ },
+};
+
+async function makeTest(t, { source, target, expected }) {
+ const sourceUrl = resolveUrl("resources/service-worker-bridge.html",
+ sourceResolveOptions(source));
+
+ const targetUrl = preflightUrl(target);
+ targetUrl.searchParams.append("body", "undefined");
+ targetUrl.searchParams.append("mime-type", "application/javascript");
+
+ const scope = resolveUrl(`resources/${token()}`, {...target.server}).href;
+
+ const iframe = await appendIframe(t, document, sourceUrl);
+
+ {
+ const reply = futureMessage();
+ const message = {
+ action: "register",
+ url: targetUrl.href,
+ options: { scope },
+ };
+ iframe.contentWindow.postMessage(message, "*");
+
+ const { error, loaded } = await reply;
+
+ assert_equals(error, expected.register.error, "register error");
+ assert_equals(loaded, expected.register.loaded, "response loaded");
+ }
+
+ {
+ const reply = futureMessage();
+ iframe.contentWindow.postMessage({ action: "unregister", scope }, "*");
+
+ const { error, unregistered } = await reply;
+ assert_equals(error, expected.unregister.error, "unregister error");
+ assert_equals(
+ unregistered, expected.unregister.unregistered, "worker unregistered");
+ }
+}
+
+promise_test(t => makeTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTPS_LOCAL },
+ expected: TestResult.SUCCESS,
+}), "treat-as-public to local: success.");
+
+promise_test(t => makeTest(t, {
+ source: {
+ server: Server.HTTPS_PRIVATE,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTPS_PRIVATE },
+ expected: TestResult.SUCCESS,
+}), "treat-as-public to private: success.");
+
+promise_test(t => makeTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: { server: Server.HTTPS_PUBLIC },
+ expected: TestResult.SUCCESS,
+}), "public to public: success.");
diff --git a/testing/web-platform/tests/fetch/private-network-access/shared-worker-blob-fetch.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/shared-worker-blob-fetch.tentative.https.window.js
new file mode 100644
index 0000000000..269abb7edc
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/shared-worker-blob-fetch.tentative.https.window.js
@@ -0,0 +1,168 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests check that fetches from within `SharedWorker` scripts that are
+// loaded from blob URLs are subject to Private Network Access checks, just like
+// fetches from within documents.
+//
+// This file covers only those tests that must execute in a secure context.
+// Other tests are defined in: shared-worker-blob-fetch.window.js
+
+promise_test(t => sharedWorkerBlobFetchTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: { server: Server.HTTPS_LOCAL },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "local to local: success.");
+
+promise_test(t => sharedWorkerBlobFetchTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "private to local: failed preflight.");
+
+promise_test(t => sharedWorkerBlobFetchTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "private to local: success.");
+
+promise_test(t => sharedWorkerBlobFetchTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: { server: Server.HTTPS_PRIVATE },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "private to private: success.");
+
+promise_test(t => sharedWorkerBlobFetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "public to local: failed preflight.");
+
+promise_test(t => sharedWorkerBlobFetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "public to local: success.");
+
+promise_test(t => sharedWorkerBlobFetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "public to private: failed preflight.");
+
+promise_test(t => sharedWorkerBlobFetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "public to private: success.");
+
+promise_test(t => sharedWorkerBlobFetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: { server: Server.HTTPS_PUBLIC },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "public to public: success.");
+
+promise_test(t => sharedWorkerBlobFetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.OTHER_HTTPS_LOCAL,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "treat-as-public to local: failed preflight.");
+
+promise_test(t => sharedWorkerBlobFetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.OTHER_HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "treat-as-public to local: success.");
+
+promise_test(t => sharedWorkerBlobFetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTPS_LOCAL },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "treat-as-public to local (same-origin): no preflight required.");
+
+promise_test(t => sharedWorkerBlobFetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "treat-as-public to private: failed preflight.");
+
+promise_test(t => sharedWorkerBlobFetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "treat-as-public to private: success.");
+
+promise_test(t => sharedWorkerBlobFetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PUBLIC,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "treat-as-public to public: success.");
+
diff --git a/testing/web-platform/tests/fetch/private-network-access/shared-worker-blob-fetch.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/shared-worker-blob-fetch.tentative.window.js
new file mode 100644
index 0000000000..d430ea7383
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/shared-worker-blob-fetch.tentative.window.js
@@ -0,0 +1,173 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests check that fetches from within `SharedWorker` scripts that are
+// loaded from blob URLs are subject to Private Network Access checks, just like
+// fetches from within documents.
+//
+// This file covers only those tests that must execute in a non-secure context.
+// Other tests are defined in: shared-worker-blob-fetch.https.window.js
+
+promise_test(t => sharedWorkerBlobFetchTest(t, {
+ source: { server: Server.HTTP_LOCAL },
+ target: { server: Server.HTTP_LOCAL },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "local to local: success.");
+
+promise_test(t => sharedWorkerBlobFetchTest(t, {
+ source: { server: Server.HTTP_PRIVATE },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "private to local: failure.");
+
+promise_test(t => sharedWorkerBlobFetchTest(t, {
+ source: { server: Server.HTTP_PRIVATE },
+ target: { server: Server.HTTP_PRIVATE },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "private to private: success.");
+
+promise_test(t => sharedWorkerBlobFetchTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "public to local: failure.");
+
+promise_test(t => sharedWorkerBlobFetchTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: {
+ server: Server.HTTP_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "public to private: failure.");
+
+promise_test(t => sharedWorkerBlobFetchTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: { server: Server.HTTP_PUBLIC },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "public to public: success.");
+
+promise_test(t => sharedWorkerBlobFetchTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: { preflight: PreflightBehavior.optionalSuccess(token()) },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "treat-as-public to local: failure.");
+
+promise_test(t => sharedWorkerBlobFetchTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTP_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "treat-as-public to private: failure.");
+
+promise_test(t => sharedWorkerBlobFetchTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTP_PUBLIC,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "treat-as-public to public: success.");
+
+// The following tests verify that workers served over HTTPS are not allowed to
+// make private network requests because they are not secure contexts.
+
+promise_test(t => sharedWorkerBlobFetchTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "local https to local: success.");
+
+promise_test(t => sharedWorkerBlobFetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "private https to local: failure.");
+
+promise_test(t => sharedWorkerBlobFetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "public https to local: failure.");
+
+promise_test(t => sharedWorkerBlobFetchTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "local https to local https: success.");
+
+promise_test(t => sharedWorkerBlobFetchTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "private https to local https: failure.");
+
+promise_test(t => sharedWorkerBlobFetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "public https to local https: failure.");
diff --git a/testing/web-platform/tests/fetch/private-network-access/shared-worker-fetch.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/shared-worker-fetch.tentative.https.window.js
new file mode 100644
index 0000000000..e5f2b94920
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/shared-worker-fetch.tentative.https.window.js
@@ -0,0 +1,167 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests check that fetches from within `SharedWorker` scripts are subject
+// to Private Network Access checks, just like fetches from within documents.
+//
+// This file covers only those tests that must execute in a secure context.
+// Other tests are defined in: shared-worker-fetch.window.js
+
+promise_test(t => sharedWorkerFetchTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: { server: Server.HTTPS_LOCAL },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "local to local: success.");
+
+promise_test(t => sharedWorkerFetchTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "private to local: failure.");
+
+promise_test(t => sharedWorkerFetchTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "private to local: success.");
+
+promise_test(t => sharedWorkerFetchTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: { server: Server.HTTPS_PRIVATE },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "private to private: success.");
+
+promise_test(t => sharedWorkerFetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "public to local: failed preflight.");
+
+promise_test(t => sharedWorkerFetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "public to local: success.");
+
+promise_test(t => sharedWorkerFetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "public to private: failed preflight.");
+
+promise_test(t => sharedWorkerFetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "public to private: success.");
+
+promise_test(t => sharedWorkerFetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: { server: Server.HTTPS_PUBLIC },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "public to public: success.");
+
+promise_test(t => sharedWorkerFetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.OTHER_HTTPS_LOCAL,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "treat-as-public to local: failed preflight.");
+
+promise_test(t => sharedWorkerFetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.OTHER_HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "treat-as-public to local: success.");
+
+promise_test(t => sharedWorkerFetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTPS_LOCAL },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "treat-as-public to local (same-origin): no preflight required.");
+
+promise_test(t => sharedWorkerFetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "treat-as-public to private: failed preflight.");
+
+promise_test(t => sharedWorkerFetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "treat-as-public to private: success.");
+
+promise_test(t => sharedWorkerFetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PUBLIC,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "treat-as-public to public: success.");
+
diff --git a/testing/web-platform/tests/fetch/private-network-access/shared-worker-fetch.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/shared-worker-fetch.tentative.window.js
new file mode 100644
index 0000000000..9bc1a89bea
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/shared-worker-fetch.tentative.window.js
@@ -0,0 +1,154 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests check that fetches from within `SharedWorker` scripts are subject
+// to Private Network Access checks, just like fetches from within documents.
+//
+// This file covers only those tests that must execute in a non-secure context.
+// Other tests are defined in: shared-worker-fetch.https.window.js
+
+promise_test(t => sharedWorkerFetchTest(t, {
+ source: { server: Server.HTTP_LOCAL },
+ target: { server: Server.HTTP_LOCAL },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "local to local: success.");
+
+promise_test(t => sharedWorkerFetchTest(t, {
+ source: { server: Server.HTTP_PRIVATE },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "private to local: failure.");
+
+promise_test(t => sharedWorkerFetchTest(t, {
+ source: { server: Server.HTTP_PRIVATE },
+ target: { server: Server.HTTP_PRIVATE },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "private to private: success.");
+
+promise_test(t => sharedWorkerFetchTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "public to local: failure.");
+
+promise_test(t => sharedWorkerFetchTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: {
+ server: Server.HTTP_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "public to private: failure.");
+
+promise_test(t => sharedWorkerFetchTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: { server: Server.HTTP_PUBLIC },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "public to public: success.");
+
+promise_test(t => sharedWorkerFetchTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: { preflight: PreflightBehavior.optionalSuccess(token()) },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "treat-as-public to local: failure.");
+
+promise_test(t => sharedWorkerFetchTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTP_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "treat-as-public to private: failure.");
+
+promise_test(t => sharedWorkerFetchTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTP_PUBLIC,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "treat-as-public to public: success.");
+
+// The following tests verify that workers served over HTTPS are not allowed to
+// make private network requests because they are not secure contexts.
+
+promise_test(t => sharedWorkerFetchTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "local https to local: success.");
+
+promise_test(t => sharedWorkerFetchTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "private https to local: failure.");
+
+promise_test(t => sharedWorkerFetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "public https to local: failure.");
+
+promise_test(t => sharedWorkerFetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTP_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "public https to private: failure.");
diff --git a/testing/web-platform/tests/fetch/private-network-access/shared-worker.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/shared-worker.tentative.https.window.js
new file mode 100644
index 0000000000..24ae108782
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/shared-worker.tentative.https.window.js
@@ -0,0 +1,34 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests mirror `Worker` tests, except using `SharedWorker`.
+// See also: worker.https.window.js
+//
+// This file covers only those tests that must execute in a secure context.
+// Other tests are defined in: shared-worker.window.js
+
+promise_test(t => sharedWorkerScriptTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTPS_LOCAL },
+ expected: WorkerScriptTestResult.SUCCESS,
+}), "treat-as-public to local: success.");
+
+promise_test(t => sharedWorkerScriptTest(t, {
+ source: {
+ server: Server.HTTPS_PRIVATE,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTPS_PRIVATE },
+ expected: WorkerScriptTestResult.SUCCESS,
+}), "treat-as-public to private: success.");
+
+promise_test(t => sharedWorkerScriptTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: { server: Server.HTTPS_PUBLIC },
+ expected: WorkerScriptTestResult.SUCCESS,
+}), "public to public: success.");
diff --git a/testing/web-platform/tests/fetch/private-network-access/shared-worker.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/shared-worker.tentative.window.js
new file mode 100644
index 0000000000..ffa8a360c7
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/shared-worker.tentative.window.js
@@ -0,0 +1,34 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests mirror `Worker` tests, except using `SharedWorker`.
+// See also: shared-worker.window.js
+//
+// This file covers only those tests that must execute in a non secure context.
+// Other tests are defined in: shared-worker.https.window.js
+
+promise_test(t => sharedWorkerScriptTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTP_LOCAL },
+ expected: WorkerScriptTestResult.FAILURE,
+}), "treat-as-public to local: failure.");
+
+promise_test(t => sharedWorkerScriptTest(t, {
+ source: {
+ server: Server.HTTP_PRIVATE,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTP_PRIVATE },
+ expected: WorkerScriptTestResult.FAILURE,
+}), "treat-as-public to private: failure.");
+
+promise_test(t => sharedWorkerScriptTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: { server: Server.HTTP_PUBLIC },
+ expected: WorkerScriptTestResult.SUCCESS,
+}), "public to public: success.");
diff --git a/testing/web-platform/tests/fetch/private-network-access/websocket.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/websocket.tentative.https.window.js
new file mode 100644
index 0000000000..0731896098
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/websocket.tentative.https.window.js
@@ -0,0 +1,40 @@
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests verify that websocket connections behave similarly to fetches.
+//
+// This file covers only those tests that must execute in a secure context.
+// Other tests are defined in: websocket.https.window.js
+
+setup(() => {
+ // Making sure we are in a secure context, as expected.
+ assert_true(window.isSecureContext);
+});
+
+promise_test(t => websocketTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: { server: Server.WSS_LOCAL },
+ expected: WebsocketTestResult.SUCCESS,
+}), "local to local: websocket success.");
+
+promise_test(t => websocketTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: { server: Server.WSS_LOCAL },
+ expected: WebsocketTestResult.SUCCESS,
+}), "private to local: websocket success.");
+
+promise_test(t => websocketTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: { server: Server.WSS_LOCAL },
+ expected: WebsocketTestResult.SUCCESS,
+}), "public to local: websocket success.");
+
+promise_test(t => websocketTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: { server: Server.WSS_LOCAL },
+ expected: WebsocketTestResult.SUCCESS,
+}), "treat-as-public to local: websocket success.");
diff --git a/testing/web-platform/tests/fetch/private-network-access/websocket.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/websocket.tentative.window.js
new file mode 100644
index 0000000000..a44cfaedec
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/websocket.tentative.window.js
@@ -0,0 +1,40 @@
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+
+// These tests verify that websocket connections behave similarly to fetches.
+//
+// This file covers only those tests that must execute in a non secure context.
+// Other tests are defined in: websocket.https.window.js
+
+setup(() => {
+ // Making sure we are in a non secure context, as expected.
+ assert_false(window.isSecureContext);
+});
+
+promise_test(t => websocketTest(t, {
+ source: { server: Server.HTTP_LOCAL },
+ target: { server: Server.WS_LOCAL },
+ expected: WebsocketTestResult.SUCCESS,
+}), "local to local: websocket success.");
+
+promise_test(t => websocketTest(t, {
+ source: { server: Server.HTTP_PRIVATE },
+ target: { server: Server.WS_LOCAL },
+ expected: WebsocketTestResult.FAILURE,
+}), "private to local: websocket failure.");
+
+promise_test(t => websocketTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: { server: Server.WS_LOCAL },
+ expected: WebsocketTestResult.FAILURE,
+}), "public to local: websocket failure.");
+
+promise_test(t => websocketTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: { server: Server.WS_LOCAL },
+ expected: WebsocketTestResult.FAILURE,
+}), "treat-as-public to local: websocket failure.");
diff --git a/testing/web-platform/tests/fetch/private-network-access/window-open-existing.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/window-open-existing.tentative.https.window.js
new file mode 100644
index 0000000000..6a2a624fc8
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/window-open-existing.tentative.https.window.js
@@ -0,0 +1,209 @@
+// META: script=/common/subset-tests-by-key.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+// META: timeout=long
+// META: variant=?include=from-local
+// META: variant=?include=from-private
+// META: variant=?include=from-public
+// META: variant=?include=from-treat-as-public
+//
+// These tests verify that secure contexts can navigate to less-public address
+// spaces via window.open to an existing window iff the target server responds
+// affirmatively to preflight requests.
+
+setup(() => {
+ assert_true(window.isSecureContext);
+});
+
+// Source: secure local context.
+//
+// All fetches unaffected by Private Network Access.
+
+subsetTestByKey(
+ 'from-local', promise_test_parallel,
+ t => windowOpenExistingTest(t, {
+ source: {server: Server.HTTPS_LOCAL},
+ target: {server: Server.HTTPS_LOCAL},
+ expected: NavigationTestResult.SUCCESS,
+ }),
+ 'local to local: no preflight required.');
+
+subsetTestByKey(
+ 'from-local', promise_test_parallel,
+ t => windowOpenExistingTest(t, {
+ source: {server: Server.HTTPS_LOCAL},
+ target: {server: Server.HTTPS_PRIVATE},
+ expected: NavigationTestResult.SUCCESS,
+ }),
+ 'local to private: no preflight required.');
+
+subsetTestByKey(
+ 'from-local', promise_test_parallel,
+ t => windowOpenExistingTest(t, {
+ source: {server: Server.HTTPS_LOCAL},
+ target: {server: Server.HTTPS_PUBLIC},
+ expected: NavigationTestResult.SUCCESS,
+ }),
+ 'local to public: no preflight required.');
+
+// Generates tests of preflight behavior for a single (source, target) pair.
+//
+// Scenarios:
+//
+// - preflight response has non-2xx HTTP code
+// - preflight response is missing CORS headers
+// - preflight response is missing the PNA-specific `Access-Control` header
+// - success
+//
+function makePreflightTests({
+ key,
+ sourceName,
+ sourceServer,
+ sourceTreatAsPublic,
+ targetName,
+ targetServer,
+}) {
+ const prefix =
+ `${sourceName} to ${targetName}: `;
+
+ const source = {
+ server: sourceServer,
+ treatAsPublic: sourceTreatAsPublic,
+ };
+
+ promise_test_parallel(t => windowOpenExistingTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: { preflight: PreflightBehavior.failure() },
+ },
+ expected: NavigationTestResult.FAILURE,
+ }), prefix + "failed preflight.");
+
+ promise_test_parallel(t => windowOpenExistingTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: { preflight: PreflightBehavior.noCorsHeader(token()) },
+ },
+ expected: NavigationTestResult.FAILURE,
+ }), prefix + "missing CORS headers.");
+
+ promise_test_parallel(t => windowOpenExistingTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: { preflight: PreflightBehavior.noPnaHeader(token()) },
+ },
+ expected: NavigationTestResult.FAILURE,
+ }), prefix + "missing PNA header.");
+
+ promise_test_parallel(t => windowOpenExistingTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: { preflight: PreflightBehavior.navigation(token()) },
+ },
+ expected: NavigationTestResult.SUCCESS,
+ }), prefix + "success.");
+}
+
+// Source: private secure context.
+//
+// Navigating to the local address space require a successful preflight response
+// carrying a PNA-specific header.
+
+subsetTestByKey('from-private', makePreflightTests, {
+ sourceServer: Server.HTTPS_PRIVATE,
+ sourceName: 'private',
+ targetServer: Server.HTTPS_LOCAL,
+ targetName: 'local',
+});
+
+subsetTestByKey(
+ 'from-private', promise_test_parallel,
+ t => windowOpenExistingTest(t, {
+ source: {server: Server.HTTPS_PRIVATE},
+ target: {server: Server.HTTPS_PRIVATE},
+ expected: NavigationTestResult.SUCCESS,
+ }),
+ 'private to private: no preflight required.');
+
+subsetTestByKey(
+ 'from-private', promise_test_parallel,
+ t => windowOpenExistingTest(t, {
+ source: {server: Server.HTTPS_PRIVATE},
+ target: {server: Server.HTTPS_PUBLIC},
+ expected: NavigationTestResult.SUCCESS,
+ }),
+ 'private to public: no preflight required.');
+
+// Source: public secure context.
+//
+// Navigating to the local and private address spaces require a successful
+// preflight response carrying a PNA-specific header.
+
+subsetTestByKey('from-public', makePreflightTests, {
+ sourceServer: Server.HTTPS_PUBLIC,
+ sourceName: "public",
+ targetServer: Server.HTTPS_LOCAL,
+ targetName: "local",
+});
+
+subsetTestByKey('from-public', makePreflightTests, {
+ sourceServer: Server.HTTPS_PUBLIC,
+ sourceName: "public",
+ targetServer: Server.HTTPS_PRIVATE,
+ targetName: "private",
+});
+
+subsetTestByKey(
+ 'from-public', promise_test_parallel,
+ t => windowOpenExistingTest(t, {
+ source: {server: Server.HTTPS_PUBLIC},
+ target: {server: Server.HTTPS_PUBLIC},
+ expected: NavigationTestResult.SUCCESS,
+ }),
+ 'public to public: no preflight required.');
+
+// The following tests verify that `CSP: treat-as-public-address` makes
+// documents behave as if they had been served from a public IP address.
+
+subsetTestByKey('from-treat-as-public', makePreflightTests, {
+ sourceServer: Server.HTTPS_LOCAL,
+ sourceTreatAsPublic: true,
+ sourceName: "treat-as-public-address",
+ targetServer: Server.OTHER_HTTPS_LOCAL,
+ targetName: "local",
+});
+
+subsetTestByKey("from-treat-as-public", promise_test_parallel,
+ t => windowOpenExistingTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {server: Server.HTTPS_LOCAL},
+ expected: NavigationTestResult.SUCCESS,
+ }),
+ 'treat-as-public-address to local (same-origin): no preflight required.');
+
+subsetTestByKey('from-treat-as-public', makePreflightTests, {
+ sourceServer: Server.HTTPS_LOCAL,
+ sourceTreatAsPublic: true,
+ sourceName: 'treat-as-public-address',
+ targetServer: Server.HTTPS_PRIVATE,
+ targetName: 'private',
+});
+
+subsetTestByKey("from-treat-as-public", promise_test_parallel,
+ t => windowOpenExistingTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {server: Server.HTTPS_PUBLIC},
+ expected: NavigationTestResult.SUCCESS,
+ }),
+ 'treat-as-public-address to public: no preflight required.');
diff --git a/testing/web-platform/tests/fetch/private-network-access/window-open-existing.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/window-open-existing.tentative.window.js
new file mode 100644
index 0000000000..5a6cd4c5cf
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/window-open-existing.tentative.window.js
@@ -0,0 +1,95 @@
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+// META: timeout=long
+//
+// Spec: https://wicg.github.io/private-network-access/
+//
+// These tests verify that non-secure contexts cannot navigate to less-public
+// address spaces via window.open to an existing window.
+
+setup(() => {
+ // Making sure we are in a non secure context, as expected.
+ assert_false(window.isSecureContext);
+});
+
+promise_test_parallel(t => windowOpenExistingTest(t, {
+ source: { server: Server.HTTP_LOCAL },
+ target: { server: Server.HTTP_LOCAL },
+ expected: NavigationTestResult.SUCCESS,
+}), "local to local: no preflight required.");
+
+promise_test_parallel(t => windowOpenExistingTest(t, {
+ source: { server: Server.HTTP_LOCAL },
+ target: { server: Server.HTTP_PRIVATE },
+ expected: NavigationTestResult.SUCCESS,
+}), "local to private: no preflight required.");
+
+promise_test_parallel(t => windowOpenExistingTest(t, {
+ source: { server: Server.HTTP_LOCAL },
+ target: { server: Server.HTTP_PUBLIC },
+ expected: NavigationTestResult.SUCCESS,
+}), "local to public: no preflight required.");
+
+promise_test_parallel(t => windowOpenExistingTest(t, {
+ source: { server: Server.HTTP_PRIVATE },
+ target: { server: Server.HTTP_LOCAL },
+ expected: NavigationTestResult.FAILURE,
+}), "private to local: failure.");
+
+promise_test_parallel(t => windowOpenExistingTest(t, {
+ source: { server: Server.HTTP_PRIVATE },
+ target: { server: Server.HTTP_PRIVATE },
+ expected: NavigationTestResult.SUCCESS,
+}), "private to private: no preflight required.");
+
+promise_test_parallel(t => windowOpenExistingTest(t, {
+ source: { server: Server.HTTP_PRIVATE },
+ target: { server: Server.HTTP_PUBLIC },
+ expected: NavigationTestResult.SUCCESS,
+}), "private to public: no preflight required.");
+
+promise_test_parallel(t => windowOpenExistingTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: { server: Server.HTTP_LOCAL },
+ expected: NavigationTestResult.FAILURE,
+}), "public to local: failure.");
+
+promise_test_parallel(t => windowOpenExistingTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: { server: Server.HTTP_PRIVATE },
+ expected: NavigationTestResult.FAILURE,
+}), "public to private: failure.");
+
+promise_test_parallel(t => windowOpenExistingTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: { server: Server.HTTP_PUBLIC },
+ expected: NavigationTestResult.SUCCESS,
+}), "public to public: no preflight required.");
+
+promise_test_parallel(t => windowOpenExistingTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTP_LOCAL },
+ expected: NavigationTestResult.FAILURE,
+}), "treat-as-public-address to local: failure.");
+
+promise_test_parallel(t => windowOpenExistingTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTP_PRIVATE },
+ expected: NavigationTestResult.FAILURE,
+}), "treat-as-public-address to private: failure.");
+
+promise_test_parallel(t => windowOpenExistingTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTP_PUBLIC },
+ expected: NavigationTestResult.SUCCESS,
+}), "treat-as-public-address to public: no preflight required.");
diff --git a/testing/web-platform/tests/fetch/private-network-access/window-open.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/window-open.tentative.https.window.js
new file mode 100644
index 0000000000..6793d1f3b4
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/window-open.tentative.https.window.js
@@ -0,0 +1,205 @@
+// META: script=/common/subset-tests-by-key.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+// META: timeout=long
+// META: variant=?include=from-local
+// META: variant=?include=from-private
+// META: variant=?include=from-public
+// META: variant=?include=from-treat-as-public
+//
+// These tests verify that secure contexts can navigate to less-public address
+// spaces via window.open iff the target server responds affirmatively to
+// preflight requests.
+
+setup(() => {
+ assert_true(window.isSecureContext);
+});
+
+// Source: secure local context.
+//
+// All fetches unaffected by Private Network Access.
+
+subsetTestByKey("from-local", promise_test_parallel, t => windowOpenTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: { server: Server.HTTPS_LOCAL },
+ expected: NavigationTestResult.SUCCESS,
+}), "local to local: no preflight required.");
+
+subsetTestByKey("from-local", promise_test_parallel, t => windowOpenTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: { server: Server.HTTPS_PRIVATE },
+ expected: NavigationTestResult.SUCCESS,
+}), "local to private: no preflight required.");
+
+subsetTestByKey("from-local", promise_test_parallel, t => windowOpenTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: { server: Server.HTTPS_PUBLIC },
+ expected: NavigationTestResult.SUCCESS,
+}), "local to public: no preflight required.");
+
+// Generates tests of preflight behavior for a single (source, target) pair.
+//
+// Scenarios:
+//
+// - preflight response has non-2xx HTTP code
+// - preflight response is missing CORS headers
+// - preflight response is missing the PNA-specific `Access-Control` header
+// - success
+//
+function makePreflightTests({
+ key,
+ sourceName,
+ sourceServer,
+ sourceTreatAsPublic,
+ targetName,
+ targetServer,
+}) {
+ const prefix =
+ `${sourceName} to ${targetName}: `;
+
+ const source = {
+ server: sourceServer,
+ treatAsPublic: sourceTreatAsPublic,
+ };
+
+ promise_test_parallel(t => windowOpenTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: { preflight: PreflightBehavior.failure() },
+ },
+ expected: NavigationTestResult.FAILURE,
+ }), prefix + "failed preflight.");
+
+ promise_test_parallel(t => windowOpenTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: { preflight: PreflightBehavior.noCorsHeader(token()) },
+ },
+ expected: NavigationTestResult.FAILURE,
+ }), prefix + "missing CORS headers.");
+
+ promise_test_parallel(t => windowOpenTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: { preflight: PreflightBehavior.noPnaHeader(token()) },
+ },
+ expected: NavigationTestResult.FAILURE,
+ }), prefix + "missing PNA header.");
+
+ promise_test_parallel(t => windowOpenTest(t, {
+ source,
+ target: {
+ server: targetServer,
+ behavior: { preflight: PreflightBehavior.navigation(token()) },
+ },
+ expected: NavigationTestResult.SUCCESS,
+ }), prefix + "success.");
+}
+
+// Source: private secure context.
+//
+// Fetches to the local address space require a successful preflight response
+// carrying a PNA-specific header.
+
+subsetTestByKey('from-private', makePreflightTests, {
+ sourceServer: Server.HTTPS_PRIVATE,
+ sourceName: 'private',
+ targetServer: Server.HTTPS_LOCAL,
+ targetName: 'local',
+});
+
+subsetTestByKey("from-private", promise_test_parallel, t => windowOpenTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: { server: Server.HTTPS_PRIVATE },
+ expected: NavigationTestResult.SUCCESS,
+}), "private to private: no preflight required.");
+
+subsetTestByKey("from-private", promise_test_parallel, t => windowOpenTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: { server: Server.HTTPS_PUBLIC },
+ expected: NavigationTestResult.SUCCESS,
+}), "private to public: no preflight required.");
+
+// Source: public secure context.
+//
+// Fetches to the local and private address spaces require a successful
+// preflight response carrying a PNA-specific header.
+
+subsetTestByKey('from-public', makePreflightTests, {
+ sourceServer: Server.HTTPS_PUBLIC,
+ sourceName: "public",
+ targetServer: Server.HTTPS_LOCAL,
+ targetName: "local",
+});
+
+subsetTestByKey('from-public', makePreflightTests, {
+ sourceServer: Server.HTTPS_PUBLIC,
+ sourceName: "public",
+ targetServer: Server.HTTPS_PRIVATE,
+ targetName: "private",
+});
+
+subsetTestByKey("from-public", promise_test_parallel, t => windowOpenTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: { server: Server.HTTPS_PUBLIC },
+ expected: NavigationTestResult.SUCCESS,
+}), "public to public: no preflight required.");
+
+// The following tests verify that `CSP: treat-as-public-address` makes
+// documents behave as if they had been served from a public IP address.
+
+subsetTestByKey('from-treat-as-public', makePreflightTests, {
+ sourceServer: Server.HTTPS_LOCAL,
+ sourceTreatAsPublic: true,
+ sourceName: "treat-as-public-address",
+ targetServer: Server.OTHER_HTTPS_LOCAL,
+ targetName: "local",
+});
+
+subsetTestByKey("from-treat-as-public", promise_test_parallel,
+ t => windowOpenTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {server: Server.HTTPS_LOCAL},
+ expected: NavigationTestResult.SUCCESS,
+ }),
+ 'treat-as-public-address to local (same-origin): no preflight required.');
+
+subsetTestByKey('from-treat-as-public', makePreflightTests, {
+ sourceServer: Server.HTTPS_LOCAL,
+ sourceTreatAsPublic: true,
+ sourceName: 'treat-as-public-address',
+ targetServer: Server.HTTPS_PRIVATE,
+ targetName: 'private',
+});
+
+subsetTestByKey("from-treat-as-public", promise_test_parallel,
+ t => windowOpenTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {server: Server.HTTPS_PUBLIC},
+ expected: NavigationTestResult.SUCCESS,
+ }),
+ 'treat-as-public-address to public: no preflight required.');
+
+promise_test_parallel(
+ t => windowOpenTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PUBLIC,
+ behavior: {preflight: PreflightBehavior.optionalSuccess(token())}
+ },
+ expected: NavigationTestResult.SUCCESS,
+ }),
+ 'treat-as-public-address to local: optional preflight');
diff --git a/testing/web-platform/tests/fetch/private-network-access/window-open.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/window-open.tentative.window.js
new file mode 100644
index 0000000000..5e2313d60a
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/window-open.tentative.window.js
@@ -0,0 +1,95 @@
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+// META: timeout=long
+//
+// Spec: https://wicg.github.io/private-network-access/
+//
+// These tests verify that non-secure contexts cannot open a new window to
+// less-public address spaces.
+
+setup(() => {
+ // Making sure we are in a non secure context, as expected.
+ assert_false(window.isSecureContext);
+});
+
+promise_test_parallel(t => windowOpenTest(t, {
+ source: { server: Server.HTTP_LOCAL },
+ target: { server: Server.HTTP_LOCAL },
+ expected: NavigationTestResult.SUCCESS,
+}), "local to local: no preflight required.");
+
+promise_test_parallel(t => windowOpenTest(t, {
+ source: { server: Server.HTTP_LOCAL },
+ target: { server: Server.HTTP_PRIVATE },
+ expected: NavigationTestResult.SUCCESS,
+}), "local to private: no preflight required.");
+
+promise_test_parallel(t => windowOpenTest(t, {
+ source: { server: Server.HTTP_LOCAL },
+ target: { server: Server.HTTP_PUBLIC },
+ expected: NavigationTestResult.SUCCESS,
+}), "local to public: no preflight required.");
+
+promise_test_parallel(t => windowOpenTest(t, {
+ source: { server: Server.HTTP_PRIVATE },
+ target: { server: Server.HTTP_LOCAL },
+ expected: NavigationTestResult.FAILURE,
+}), "private to local: failure.");
+
+promise_test_parallel(t => windowOpenTest(t, {
+ source: { server: Server.HTTP_PRIVATE },
+ target: { server: Server.HTTP_PRIVATE },
+ expected: NavigationTestResult.SUCCESS,
+}), "private to private: no preflight required.");
+
+promise_test_parallel(t => windowOpenTest(t, {
+ source: { server: Server.HTTP_PRIVATE },
+ target: { server: Server.HTTP_PUBLIC },
+ expected: NavigationTestResult.SUCCESS,
+}), "private to public: no preflight required.");
+
+promise_test_parallel(t => windowOpenTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: { server: Server.HTTP_LOCAL },
+ expected: NavigationTestResult.FAILURE,
+}), "public to local: failure.");
+
+promise_test_parallel(t => windowOpenTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: { server: Server.HTTP_PRIVATE },
+ expected: NavigationTestResult.FAILURE,
+}), "public to private: failure.");
+
+promise_test_parallel(t => windowOpenTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: { server: Server.HTTP_PUBLIC },
+ expected: NavigationTestResult.SUCCESS,
+}), "public to public: no preflight required.");
+
+promise_test_parallel(t => windowOpenTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTP_LOCAL },
+ expected: NavigationTestResult.FAILURE,
+}), "treat-as-public-address to local: failure.");
+
+promise_test_parallel(t => windowOpenTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTP_PRIVATE },
+ expected: NavigationTestResult.FAILURE,
+}), "treat-as-public-address to private: failure.");
+
+promise_test_parallel(t => windowOpenTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTP_PUBLIC },
+ expected: NavigationTestResult.SUCCESS,
+}), "treat-as-public-address to public: no preflight required.");
diff --git a/testing/web-platform/tests/fetch/private-network-access/worker-blob-fetch.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/worker-blob-fetch.tentative.window.js
new file mode 100644
index 0000000000..e119746b8a
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/worker-blob-fetch.tentative.window.js
@@ -0,0 +1,155 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests check that fetches from within `Worker` scripts loaded from blob
+// URLs are subject to Private Network Access checks, just like fetches from
+// within documents.
+//
+// This file covers only those tests that must execute in a non-secure context.
+// Other tests are defined in: worker-blob-fetch.https.window.js
+
+promise_test(t => workerBlobFetchTest(t, {
+ source: { server: Server.HTTP_LOCAL },
+ target: { server: Server.HTTP_LOCAL },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "local to local: success.");
+
+promise_test(t => workerBlobFetchTest(t, {
+ source: { server: Server.HTTP_PRIVATE },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "private to local: failure.");
+
+promise_test(t => workerBlobFetchTest(t, {
+ source: { server: Server.HTTP_PRIVATE },
+ target: { server: Server.HTTP_PRIVATE },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "private to private: success.");
+
+promise_test(t => workerBlobFetchTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "public to local: failure.");
+
+promise_test(t => workerBlobFetchTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: {
+ server: Server.HTTP_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "public to private: failure.");
+
+promise_test(t => workerBlobFetchTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: { server: Server.HTTP_PUBLIC },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "public to public: success.");
+
+promise_test(t => workerBlobFetchTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: { preflight: PreflightBehavior.optionalSuccess(token()) },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "treat-as-public to local: failure.");
+
+promise_test(t => workerBlobFetchTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTP_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "treat-as-public to private: failure.");
+
+promise_test(t => workerBlobFetchTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTP_PUBLIC,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "treat-as-public to public: success.");
+
+// The following tests verify that workers served over HTTPS are not allowed to
+// make private network requests because they are not secure contexts.
+
+promise_test(t => workerBlobFetchTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "local https to local https: success.");
+
+promise_test(t => workerBlobFetchTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "private https to local https: failure.");
+
+promise_test(t => workerBlobFetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "public https to private https: failure.");
+
+promise_test(t => workerBlobFetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "public https to local https: failure.");
diff --git a/testing/web-platform/tests/fetch/private-network-access/worker-fetch.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/worker-fetch.tentative.https.window.js
new file mode 100644
index 0000000000..89e0c3cf1f
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/worker-fetch.tentative.https.window.js
@@ -0,0 +1,151 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests check that fetches from within `Worker` scripts are subject to
+// Private Network Access checks, just like fetches from within documents.
+//
+// This file covers only those tests that must execute in a secure context.
+// Other tests are defined in: worker-fetch.window.js
+
+promise_test(t => workerFetchTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: { server: Server.HTTPS_LOCAL },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "local to local: success.");
+
+promise_test(t => workerFetchTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "private to local: failed preflight.");
+
+promise_test(t => workerFetchTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "private to local: success.");
+
+promise_test(t => workerFetchTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: { server: Server.HTTPS_PRIVATE },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "private to private: success.");
+
+promise_test(t => workerFetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "public to local: failed preflight.");
+
+promise_test(t => workerFetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "public to local: success.");
+
+promise_test(t => workerFetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "public to private: failed preflight.");
+
+promise_test(t => workerFetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "public to private: success.");
+
+promise_test(t => workerFetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: { server: Server.HTTPS_PUBLIC },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "public to public: success.");
+
+promise_test(t => workerFetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTPS_LOCAL },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "treat-as-public to local: failed preflight.");
+
+promise_test(t => workerFetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: { preflight: PreflightBehavior.optionalSuccess(token()) },
+ },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "treat-as-public to local: success.");
+
+promise_test(t => workerFetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "treat-as-public to private: failed preflight.");
+
+promise_test(t => workerFetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "treat-as-public to private: success.");
+
+promise_test(t => workerFetchTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PUBLIC,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "treat-as-public to public: success.");
diff --git a/testing/web-platform/tests/fetch/private-network-access/worker-fetch.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/worker-fetch.tentative.window.js
new file mode 100644
index 0000000000..4d6b12f067
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/worker-fetch.tentative.window.js
@@ -0,0 +1,154 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests check that fetches from within `Worker` scripts are subject to
+// Private Network Access checks, just like fetches from within documents.
+//
+// This file covers only those tests that must execute in a non-secure context.
+// Other tests are defined in: worker-fetch.https.window.js
+
+promise_test(t => workerFetchTest(t, {
+ source: { server: Server.HTTP_LOCAL },
+ target: { server: Server.HTTP_LOCAL },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "local to local: success.");
+
+promise_test(t => workerFetchTest(t, {
+ source: { server: Server.HTTP_PRIVATE },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "private to local: failure.");
+
+promise_test(t => workerFetchTest(t, {
+ source: { server: Server.HTTP_PRIVATE },
+ target: { server: Server.HTTP_PRIVATE },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "private to private: success.");
+
+promise_test(t => workerFetchTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "public to local: failure.");
+
+promise_test(t => workerFetchTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: {
+ server: Server.HTTP_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "public to private: failure.");
+
+promise_test(t => workerFetchTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: { server: Server.HTTP_PUBLIC },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "public to public: success.");
+
+promise_test(t => workerFetchTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: { preflight: PreflightBehavior.optionalSuccess(token()) },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "treat-as-public to local: failure.");
+
+promise_test(t => workerFetchTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTP_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "treat-as-public to private: failure.");
+
+promise_test(t => workerFetchTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTP_PUBLIC,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "treat-as-public to public: success.");
+
+// The following tests verify that workers served over HTTPS are not allowed to
+// make private network requests because they are not secure contexts.
+
+promise_test(t => workerFetchTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.SUCCESS,
+}), "local https to local https: success.");
+
+promise_test(t => workerFetchTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "private https to local https: failure.");
+
+promise_test(t => workerFetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "public https to private https: failure.");
+
+promise_test(t => workerFetchTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: WorkerFetchTestResult.FAILURE,
+}), "public https to local https: failure.");
diff --git a/testing/web-platform/tests/fetch/private-network-access/worker.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/worker.tentative.https.window.js
new file mode 100644
index 0000000000..a0f19314ee
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/worker.tentative.https.window.js
@@ -0,0 +1,37 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests check that initial `Worker` script fetches in secure contexts are
+// exempt from Private Network Access checks because workers can only be fetched
+// same-origin and the origin is potentially trustworthy. The only way to test
+// this is using the `treat-as-public` CSP directive to artificially place the
+// parent document in the `public` IP address space.
+//
+// This file covers only those tests that must execute in a secure context.
+// Other tests are defined in: worker.window.js
+
+promise_test(t => workerScriptTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTPS_LOCAL },
+ expected: WorkerScriptTestResult.SUCCESS,
+}), "treat-as-public to local: success.");
+
+promise_test(t => workerScriptTest(t, {
+ source: {
+ server: Server.HTTPS_PRIVATE,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTPS_PRIVATE },
+ expected: WorkerScriptTestResult.SUCCESS,
+}), "treat-as-public to private: success.");
+
+promise_test(t => workerScriptTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: { server: Server.HTTPS_PUBLIC },
+ expected: WorkerScriptTestResult.SUCCESS,
+}), "public to public: success.");
diff --git a/testing/web-platform/tests/fetch/private-network-access/worker.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/worker.tentative.window.js
new file mode 100644
index 0000000000..118c099254
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/worker.tentative.window.js
@@ -0,0 +1,37 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests check that initial `Worker` script fetches are subject to Private
+// Network Access checks, just like a regular `fetch()`. The main difference is
+// that workers can only be fetched same-origin, so the only way to test this
+// is using the `treat-as-public` CSP directive to artificially place the parent
+// document in the `public` IP address space.
+//
+// This file covers only those tests that must execute in a non secure context.
+// Other tests are defined in: worker.https.window.js
+
+promise_test(t => workerScriptTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTP_LOCAL },
+ expected: WorkerScriptTestResult.FAILURE,
+}), "treat-as-public to local: failure.");
+
+promise_test(t => workerScriptTest(t, {
+ source: {
+ server: Server.HTTP_PRIVATE,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTP_PRIVATE },
+ expected: WorkerScriptTestResult.FAILURE,
+}), "treat-as-public to private: failure.");
+
+promise_test(t => workerScriptTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: { server: Server.HTTP_PUBLIC },
+ expected: WorkerScriptTestResult.SUCCESS,
+}), "public to public: success.");
diff --git a/testing/web-platform/tests/fetch/private-network-access/xhr-from-treat-as-public.tentative.https.window.js b/testing/web-platform/tests/fetch/private-network-access/xhr-from-treat-as-public.tentative.https.window.js
new file mode 100644
index 0000000000..3aae3050d9
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/xhr-from-treat-as-public.tentative.https.window.js
@@ -0,0 +1,83 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests verify that documents fetched from the `local` address space yet
+// carrying the `treat-as-public-address` CSP directive are treated as if they
+// had been fetched from the `public` address space.
+
+promise_test(t => xhrTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.OTHER_HTTPS_LOCAL,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: XhrTestResult.FAILURE,
+}), "treat-as-public to local: failed preflight.");
+
+promise_test(t => xhrTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.OTHER_HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: XhrTestResult.SUCCESS,
+}), "treat-as-public to local: success.");
+
+promise_test(t => xhrTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: { server: Server.HTTPS_LOCAL },
+ expected: XhrTestResult.SUCCESS,
+}), "treat-as-public to local (same-origin): no preflight required.");
+
+promise_test(t => xhrTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: XhrTestResult.FAILURE,
+}), "treat-as-public to private: failed preflight.");
+
+promise_test(t => xhrTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: XhrTestResult.SUCCESS,
+}), "treat-as-public to private: success.");
+
+promise_test(t => xhrTest(t, {
+ source: {
+ server: Server.HTTPS_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTPS_PUBLIC,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: XhrTestResult.SUCCESS,
+}), "treat-as-public to public: no preflight required.");
diff --git a/testing/web-platform/tests/fetch/private-network-access/xhr.https.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/xhr.https.tentative.window.js
new file mode 100644
index 0000000000..4dc5da9912
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/xhr.https.tentative.window.js
@@ -0,0 +1,142 @@
+// META: script=/common/subset-tests-by-key.js
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+// META: variant=?include=from-local
+// META: variant=?include=from-private
+// META: variant=?include=from-public
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests mirror fetch.https.window.js, but use `XmlHttpRequest` instead of
+// `fetch()` to perform subresource fetches. Preflights are tested less
+// extensively due to coverage being already provided by `fetch()`.
+//
+// This file covers only those tests that must execute in a secure context.
+// Other tests are defined in: xhr.window.js
+
+setup(() => {
+ // Making sure we are in a secure context, as expected.
+ assert_true(window.isSecureContext);
+});
+
+// Source: secure local context.
+//
+// All fetches unaffected by Private Network Access.
+
+subsetTestByKey("from-local", promise_test, t => xhrTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: { server: Server.HTTPS_LOCAL },
+ expected: XhrTestResult.SUCCESS,
+}), "local to local: no preflight required.");
+
+subsetTestByKey("from-local", promise_test, t => xhrTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: XhrTestResult.SUCCESS,
+}), "local to private: no preflight required.");
+
+subsetTestByKey("from-local", promise_test, t => xhrTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: {
+ server: Server.HTTPS_PUBLIC,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: XhrTestResult.SUCCESS,
+}), "local to public: no preflight required.");
+
+// Source: private secure context.
+//
+// Fetches to the local address space require a successful preflight response
+// carrying a PNA-specific header.
+
+subsetTestByKey("from-private", promise_test, t => xhrTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: XhrTestResult.FAILURE,
+}), "private to local: failed preflight.");
+
+subsetTestByKey("from-private", promise_test, t => xhrTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: XhrTestResult.SUCCESS,
+}), "private to local: success.");
+
+subsetTestByKey("from-private", promise_test, t => xhrTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: { server: Server.HTTPS_PRIVATE },
+ expected: XhrTestResult.SUCCESS,
+}), "private to private: no preflight required.");
+
+subsetTestByKey("from-private", promise_test, t => xhrTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: {
+ server: Server.HTTPS_PUBLIC,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: XhrTestResult.SUCCESS,
+}), "private to public: no preflight required.");
+
+// Source: public secure context.
+//
+// Fetches to the local and private address spaces require a successful
+// preflight response carrying a PNA-specific header.
+
+subsetTestByKey("from-public", promise_test, t => xhrTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: XhrTestResult.FAILURE,
+}), "public to local: failed preflight.");
+
+subsetTestByKey("from-public", promise_test, t => xhrTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: XhrTestResult.SUCCESS,
+}), "public to local: success.");
+
+subsetTestByKey("from-public", promise_test, t => xhrTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: XhrTestResult.FAILURE,
+}), "public to private: failed preflight.");
+
+subsetTestByKey("from-public", promise_test, t => xhrTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.success(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: XhrTestResult.SUCCESS,
+}), "public to private: success.");
+
+subsetTestByKey("from-public", promise_test, t => xhrTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: { server: Server.HTTPS_PUBLIC },
+ expected: XhrTestResult.SUCCESS,
+}), "public to public: no preflight required.");
diff --git a/testing/web-platform/tests/fetch/private-network-access/xhr.tentative.window.js b/testing/web-platform/tests/fetch/private-network-access/xhr.tentative.window.js
new file mode 100644
index 0000000000..fa307dc559
--- /dev/null
+++ b/testing/web-platform/tests/fetch/private-network-access/xhr.tentative.window.js
@@ -0,0 +1,195 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests mirror fetch.window.js, but use `XmlHttpRequest` instead of
+// `fetch()` to perform subresource fetches.
+//
+// This file covers only those tests that must execute in a non secure context.
+// Other tests are defined in: xhr.https.window.js
+
+setup(() => {
+ // Making sure we are in a non secure context, as expected.
+ assert_false(window.isSecureContext);
+});
+
+promise_test(t => xhrTest(t, {
+ source: { server: Server.HTTP_LOCAL },
+ target: { server: Server.HTTP_LOCAL },
+ expected: XhrTestResult.SUCCESS,
+}), "local to local: no preflight required.");
+
+promise_test(t => xhrTest(t, {
+ source: { server: Server.HTTP_LOCAL },
+ target: {
+ server: Server.HTTP_PRIVATE,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: XhrTestResult.SUCCESS,
+}), "local to private: no preflight required.");
+
+promise_test(t => xhrTest(t, {
+ source: { server: Server.HTTP_LOCAL },
+ target: {
+ server: Server.HTTP_PUBLIC,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: XhrTestResult.SUCCESS,
+}), "local to public: no preflight required.");
+
+promise_test(t => xhrTest(t, {
+ source: { server: Server.HTTP_PRIVATE },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: XhrTestResult.FAILURE,
+}), "private to local: failure.");
+
+promise_test(t => xhrTest(t, {
+ source: { server: Server.HTTP_PRIVATE },
+ target: { server: Server.HTTP_PRIVATE },
+ expected: XhrTestResult.SUCCESS,
+}), "private to private: no preflight required.");
+
+promise_test(t => xhrTest(t, {
+ source: { server: Server.HTTP_PRIVATE },
+ target: {
+ server: Server.HTTP_PUBLIC,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: XhrTestResult.SUCCESS,
+}), "private to public: no preflight required.");
+
+promise_test(t => xhrTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: XhrTestResult.FAILURE,
+}), "public to local: failure.");
+
+promise_test(t => xhrTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: {
+ server: Server.HTTP_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: XhrTestResult.FAILURE,
+}), "public to private: failure.");
+
+promise_test(t => xhrTest(t, {
+ source: { server: Server.HTTP_PUBLIC },
+ target: { server: Server.HTTP_PUBLIC },
+ expected: XhrTestResult.SUCCESS,
+}), "public to public: no preflight required.");
+
+// These tests verify that documents fetched from the `local` address space yet
+// carrying the `treat-as-public-address` CSP directive are treated as if they
+// had been fetched from the `public` address space.
+
+promise_test(t => xhrTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTP_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: XhrTestResult.FAILURE,
+}), "treat-as-public-address to local: failure.");
+
+promise_test(t => xhrTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTP_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: XhrTestResult.FAILURE,
+}), "treat-as-public-address to private: failure.");
+
+promise_test(t => xhrTest(t, {
+ source: {
+ server: Server.HTTP_LOCAL,
+ treatAsPublic: true,
+ },
+ target: {
+ server: Server.HTTP_PUBLIC,
+ behavior: { response: ResponseBehavior.allowCrossOrigin() },
+ },
+ expected: XhrTestResult.SUCCESS,
+}), "treat-as-public-address to public: no preflight required.");
+
+// These tests verify that HTTPS iframes embedded in an HTTP top-level document
+// cannot fetch subresources from less-public address spaces. Indeed, even
+// though the iframes have HTTPS origins, they are non-secure contexts because
+// their parent is a non-secure context.
+
+promise_test(t => xhrTest(t, {
+ source: { server: Server.HTTPS_LOCAL },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: XhrTestResult.SUCCESS,
+}), "local https to local: success.");
+
+promise_test(t => xhrTest(t, {
+ source: { server: Server.HTTPS_PRIVATE },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: XhrTestResult.FAILURE,
+}), "private https to local: failure.");
+
+promise_test(t => xhrTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_LOCAL,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: XhrTestResult.FAILURE,
+}), "public https to local: failure.");
+
+promise_test(t => xhrTest(t, {
+ source: { server: Server.HTTPS_PUBLIC },
+ target: {
+ server: Server.HTTPS_PRIVATE,
+ behavior: {
+ preflight: PreflightBehavior.optionalSuccess(token()),
+ response: ResponseBehavior.allowCrossOrigin(),
+ },
+ },
+ expected: XhrTestResult.FAILURE,
+}), "public https to private: failure.");