[Private Network Access] Add WPTs for window.open into an existing window
Like window-open tests but opening into an existing window.
Bug: 1431155
Change-Id: I3e835306f4bc3aec6187e9736592fa19c47c3fd4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5067914
Reviewed-by: Yifan Luo <lyf@chromium.org>
Commit-Queue: Jonathan Hao <phao@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1244799}
diff --git a/fetch/private-network-access/resources/open-to-existing-window.html b/fetch/private-network-access/resources/open-to-existing-window.html
new file mode 100644
index 0000000..6460024
--- /dev/null
+++ b/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/fetch/private-network-access/resources/support.sub.js b/fetch/private-network-access/resources/support.sub.js
index 69d8f50..46a9d9e 100644
--- a/fetch/private-network-access/resources/support.sub.js
+++ b/fetch/private-network-access/resources/support.sub.js
@@ -506,6 +506,34 @@
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");
diff --git a/fetch/private-network-access/window-open-existing.tentative.https.window.js b/fetch/private-network-access/window-open-existing.tentative.https.window.js
new file mode 100644
index 0000000..6a2a624
--- /dev/null
+++ b/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/fetch/private-network-access/window-open-existing.tentative.window.js b/fetch/private-network-access/window-open-existing.tentative.window.js
new file mode 100644
index 0000000..5a6cd4c
--- /dev/null
+++ b/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.");