[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.");