Add WPT for partitioned cookies/service worker interaction

This CL adds a WPT which exercises the interaction between partitioned service workers and partitioned cookies.

When 3P storage partitioning is enabled, partitioned cookies should
only be visible to partitioned workers whose StorageKey's equivalent CookiePartitionKey matches their own.

The CookieStoreManager API was partitioned in a separate CL.

This does NOT test that workers in nonced partitions do not have access to unpartitioned cookies.

Bug: 1225444,1246549,1427879
Change-Id: Id101b4fa48bed46605fe95f7ef2b1d2463f9301a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3674179
Commit-Queue: Dylan Cutler <dylancutler@google.com>
Reviewed-by: Steven Bingler <bingler@chromium.org>
Reviewed-by: Ayu Ishii <ayui@chromium.org>
Reviewed-by: Kent Tamura <tkent@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1131591}
diff --git a/service-workers/service-worker/partitioned-cookies.tentative.https.html b/service-workers/service-worker/partitioned-cookies.tentative.https.html
new file mode 100644
index 0000000..6744edc
--- /dev/null
+++ b/service-workers/service-worker/partitioned-cookies.tentative.https.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<head>
+<meta charset="utf-8"/>
+<meta name="timeout" content="long">
+<title>Service Worker: Partitioned Cookies</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+</head>
+
+<!--
+  This test exercises partitioned service workers' interaction with partitioned cookies.
+  Partitioned service workers should only be able to interact with partitioned cookies whose
+  partition key matches the worker's partition.
+-->
+
+<body>
+<script>
+
+promise_test(async t => {
+  const script = './resources/partitioned-cookies-sw.js'
+  const scope = './resources/partitioned-cookies-'
+  const absolute_scope = new URL(scope, window.location).href;
+
+  const reg = await service_worker_unregister_and_register(t, script, scope);
+  await wait_for_state(t, reg.installing, 'activated');
+  t.add_cleanup(() => reg.unregister());
+
+  // on_message will be reassigned below based on the expected reply from the service worker.
+  let on_message;
+  self.addEventListener('message', ev => on_message(ev));
+  navigator.serviceWorker.addEventListener('message', evt => {
+    self.postMessage(evt.data, '*');
+  });
+
+  const retrieved_registrations =
+        await navigator.serviceWorker.getRegistrations();
+  // It's possible that other tests have left behind other service workers.
+  // This steps filters those other SWs out.
+  const filtered_registrations =
+    retrieved_registrations.filter(reg => reg.scope == absolute_scope);
+
+  // First test that the worker script started correctly and message passing is enabed.
+  let resolve_wait_promise;
+  let wait_promise = new Promise(resolve => {
+    resolve_wait_promise = resolve;
+  });
+  let got;
+  on_message = ev => {
+    got = ev.data;
+    resolve_wait_promise();
+  };
+  filtered_registrations[0].active.postMessage({type: 'test_message'});
+  await wait_promise;
+  assert_true(got.ok, 'Message passing');
+
+  // Set a Partitioned cookie.
+  document.cookie = '__Host-partitioned=123; Secure; Path=/; SameSite=None; Partitioned;';
+  assert_true(document.cookie.includes('__Host-partitioned=123'));
+
+  // Test that the partitioned cookie is available to this worker.
+  wait_promise = new Promise(resolve => {
+    resolve_wait_promise = resolve;
+  });
+  on_message = ev => {
+    got = ev.data;
+    resolve_wait_promise();
+  };
+  filtered_registrations[0].active.postMessage({type: 'echo_cookies'});
+  await wait_promise;
+  assert_true(got.ok, 'Get cookies');
+  assert_true(got.cookies.includes('__Host-partitioned'), 'Can access partitioned cookie');
+
+  const popup = window.open(
+      new URL(
+          `./resources/partitioned-cookies-3p-window.html?origin=${
+              encodeURIComponent(self.location.origin)}`,
+          get_host_info().HTTPS_NOTSAMESITE_ORIGIN + self.location.pathname));
+  await fetch_tests_from_window(popup);
+});
+
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/service-workers/service-worker/resources/partitioned-cookies-3p-frame.html b/service-workers/service-worker/resources/partitioned-cookies-3p-frame.html
new file mode 100644
index 0000000..d3962d2
--- /dev/null
+++ b/service-workers/service-worker/resources/partitioned-cookies-3p-frame.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<head>
+<meta charset="utf-8"/>
+<meta name="timeout" content="long">
+<title>Service Worker: Partitioned Cookies 3P Iframe</title>
+<script src="/resources/testharness.js"></script>
+<script src="test-helpers.sub.js"></script>
+</head>
+
+<body>
+<script>
+
+promise_test(async t => {
+  const script = './partitioned-cookies-3p-sw.js';
+  const scope = './partitioned-cookies-3p-';
+  const absolute_scope = new URL(scope, window.location).href;
+
+  assert_false(document.cookie.includes('__Host-partitioned=123'), 'DOM cannot access partitioned cookie');
+
+  const reg = await service_worker_unregister_and_register(t, script, scope);
+  await wait_for_state(t, reg.installing, 'activated');
+
+  let retrieved_registrations =
+        await navigator.serviceWorker.getRegistrations();
+  let filtered_registrations =
+    retrieved_registrations.filter(reg => reg.scope == absolute_scope);
+
+  // on_message will be reassigned below based on the expected reply from the service worker.
+  let on_message;
+  self.addEventListener('message', ev => on_message(ev));
+  navigator.serviceWorker.addEventListener('message', evt => {
+    self.postMessage(evt.data, '*');
+  });
+
+  // First test that the worker script started correctly and message passing is enabled.
+  let resolve_wait_promise;
+  let wait_promise = new Promise(resolve => {
+    resolve_wait_promise = resolve;
+  });
+  let got;
+  on_message = ev => {
+    got = ev.data;
+    resolve_wait_promise();
+  };
+  filtered_registrations[0].active.postMessage({type: 'test_message'});
+  await wait_promise;
+  assert_true(got.ok, 'Message passing');
+
+  // Test that the partitioned cookie is not available to this worker.
+  wait_promise = new Promise(resolve => {
+    resolve_wait_promise = resolve;
+  });
+  on_message = ev => {
+    got = ev.data;
+    resolve_wait_promise();
+  };
+  filtered_registrations[0].active.postMessage({type: 'echo_cookies'});
+  await wait_promise;
+  assert_true(got.ok, 'Get cookies');
+  assert_false(
+      got.cookies.includes('__Host-partitioned'),
+      'Worker cannot access partitioned cookie');
+});
+
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/service-workers/service-worker/resources/partitioned-cookies-3p-sw.js b/service-workers/service-worker/resources/partitioned-cookies-3p-sw.js
new file mode 100644
index 0000000..2f54a98
--- /dev/null
+++ b/service-workers/service-worker/resources/partitioned-cookies-3p-sw.js
@@ -0,0 +1,30 @@
+self.addEventListener('message', ev => ev.waitUntil(onMessage(ev)));
+
+async function onMessage(event) {
+  if (!event.data)
+    return;
+  switch (event.data.type) {
+    case 'test_message':
+      return onTestMessage(event);
+    case 'echo_cookies':
+      return onEchoCookies(event);
+    default:
+      return;
+  }
+}
+
+// test_message just verifies that the message passing is working.
+async function onTestMessage(event) {
+  event.source.postMessage({ok: true});
+}
+
+// echo_cookies returns the names of all of the cookies available to the worker.
+async function onEchoCookies(event) {
+  try {
+    const cookie_objects = await self.cookieStore.getAll();
+    const cookies = cookie_objects.map(c => c.name);
+    event.source.postMessage({ok: true, cookies});
+  } catch (err) {
+    event.source.postMessage({ok: false});
+  }
+}
diff --git a/service-workers/service-worker/resources/partitioned-cookies-3p-window.html b/service-workers/service-worker/resources/partitioned-cookies-3p-window.html
new file mode 100644
index 0000000..d86a5eb
--- /dev/null
+++ b/service-workers/service-worker/resources/partitioned-cookies-3p-window.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<head>
+<meta charset="utf-8"/>
+<meta name="timeout" content="long">
+<title>Service Worker: Partitioned Cookies 3P Window</title>
+<script src="/resources/testharness.js"></script>
+</head>
+
+<body>
+<script>
+
+promise_test(async t => {
+  assert_true(
+      location.search.includes('origin='), 'First party origin passed');
+  const first_party_origin = decodeURIComponent(
+      location.search.split('origin=')[1]);
+  const iframe = document.createElement('iframe');
+  iframe.src = new URL(
+      './partitioned-cookies-3p-frame.html',
+      first_party_origin + location.pathname).href;
+  document.body.appendChild(iframe);
+  fetch_tests_from_window(iframe.contentWindow);
+});
+
+</script>
+</body>
+</html>
diff --git a/service-workers/service-worker/resources/partitioned-cookies-sw.js b/service-workers/service-worker/resources/partitioned-cookies-sw.js
new file mode 100644
index 0000000..2f54a98
--- /dev/null
+++ b/service-workers/service-worker/resources/partitioned-cookies-sw.js
@@ -0,0 +1,30 @@
+self.addEventListener('message', ev => ev.waitUntil(onMessage(ev)));
+
+async function onMessage(event) {
+  if (!event.data)
+    return;
+  switch (event.data.type) {
+    case 'test_message':
+      return onTestMessage(event);
+    case 'echo_cookies':
+      return onEchoCookies(event);
+    default:
+      return;
+  }
+}
+
+// test_message just verifies that the message passing is working.
+async function onTestMessage(event) {
+  event.source.postMessage({ok: true});
+}
+
+// echo_cookies returns the names of all of the cookies available to the worker.
+async function onEchoCookies(event) {
+  try {
+    const cookie_objects = await self.cookieStore.getAll();
+    const cookies = cookie_objects.map(c => c.name);
+    event.source.postMessage({ok: true, cookies});
+  } catch (err) {
+    event.source.postMessage({ok: false});
+  }
+}