Refine crossOriginIsolated implementation for workers (#25748)

- Fix WindowOrWorketGlobalScope.crossOriginIsolated behavior for
   workers.
   - For SharedWorkers and ServiceWorkers it always return false. See
     https://crbug.com/1131403 and https://crbug.com/1131404.
 - Rename ExecutionContext::IsCrossOriginIsolated to
   CrossOriginCapability. This is aligned with
   https://html.spec.whatwg.org/C/#concept-settings-object-cross-origin-isolated-capability.
 - Fix wpt/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/blob-data.https.html.
   I originally planned to do that in
   https://github.com/web-platform-tests/wpt/pull/24600 but I couldn't
   do that due to some flakiness.
 - Add more tests for workers in
   wpt/html/cross-origin-embedder-policy/cross-origin-isolated-permission.https.html.

Bug: 1115379, 1018680, 1131403, 1131404
Change-Id: I2afcb01403f67a11fd06aefde1238aba16b68f36
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2416428
Commit-Queue: Yutaka Hirano <yhirano@chromium.org>
Reviewed-by: Domenic Denicola <domenic@chromium.org>
Reviewed-by: Robert Flack <flackr@chromium.org>
Reviewed-by: Hiroki Nakagawa <nhiroki@chromium.org>
Cr-Commit-Position: refs/heads/master@{#810130}

Co-authored-by: Yutaka Hirano <yhirano@chromium.org>
diff --git a/html/cross-origin-embedder-policy/cross-origin-isolated-permission.https.html b/html/cross-origin-embedder-policy/cross-origin-isolated-permission.https.html
index aec1b85..2fc545d 100644
--- a/html/cross-origin-embedder-policy/cross-origin-isolated-permission.https.html
+++ b/html/cross-origin-embedder-policy/cross-origin-isolated-permission.https.html
@@ -3,24 +3,28 @@
 <script src=/resources/testharness.js></script>
 <script src=/resources/testharnessreport.js></script>
 <script src="/common/get-host-info.sub.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
 <body>
 <script>
 const {ORIGIN, HTTPS_REMOTE_ORIGIN} = get_host_info();
-const PATH =
+const FRAME_PATH =
   new URL('resources/cross-origin-isolated-frame.html', location).pathname;
+const WORKER_URL =
+  new URL('resources/cross-origin-isolated-worker.js', location).href;
 const PIPE =
   '?pipe=' +
   '|header(cross-origin-embedder-policy,require-corp)' +
   '|header(cross-origin-resource-policy,cross-origin)';
 
-async function getCrossOriginIsolatedFor(t, origin, value) {
+async function getCrossOriginIsolatedForFrame(t, origin, value) {
   const parentFrame = document.createElement('iframe');
   t.add_cleanup(() => parentFrame.remove());
   let pipe = PIPE;
   if (value !== undefined) {
     pipe += `|header(permissions-policy,cross-origin-isolated=${value})`;
   }
-  parentFrame.src = `${PATH}${pipe}`;
+  parentFrame.src = `${FRAME_PATH}${pipe}`;
   document.body.append(parentFrame);
 
   await new Promise((resolve) => {
@@ -28,7 +32,7 @@
   });
 
   const frame = parentFrame.contentDocument.createElement('iframe');
-  frame.src = `${origin}${PATH}${PIPE}`;
+  frame.src = `${origin}${FRAME_PATH}${PIPE}`;
   parentFrame.contentDocument.body.append(frame);
   frame.addEventListener('error', t.unreached_func('frame.error'));
   await new Promise((resolve) => {
@@ -37,35 +41,149 @@
 
   const mc = new MessageChannel();
   frame.contentWindow.postMessage({port: mc.port2}, '*', [mc.port2]);
-
-  const e = await new Promise((resolve) => {
-    mc.port1.onmessage = resolve;
-  });
-  assert_equals(typeof(e.data), 'boolean');
-  return e.data;
+  return (await new Promise(r => mc.port1.onmessage = r)).data;
 }
 
-function generateTest(origin, value, expectation) {
+async function getCrossOriginIsolatedForDedicatedWorker(t, scheme, value) {
+  const frame = document.createElement('iframe');
+  t.add_cleanup(() => frame.remove());
+  let pipe = PIPE;
+  if (value !== undefined) {
+    pipe += `|header(permissions-policy,cross-origin-isolated=${value})`
+  }
+  frame.src = `${FRAME_PATH}${pipe}`;
+  document.body.append(frame);
+
+  frame.addEventListener('error', t.unreached_func('frame.error'));
+  await new Promise((resolve) => {
+    frame.addEventListener('load', resolve);
+  });
+
+  let workerURL;
+  if (scheme === 'https') {
+    workerURL = `${WORKER_URL}${PIPE}`;
+  } else if (scheme === 'data') {
+    const res = await fetch(WORKER_URL);
+    const text = await res.text();
+
+    workerURL = `data:application/javascript;base64,${btoa(text)}`;
+  } else if (scheme === 'blob') {
+    const res = await fetch(WORKER_URL);
+    const blob = await res.blob();
+
+    workerURL = URL.createObjectURL(blob);
+  } else {
+    assert_unreached('scheme should be one of "https", "data" and "blob".');
+  }
+
+  const worker = new frame.contentWindow.Worker(workerURL);
+  const mc = new MessageChannel();
+  worker.postMessage({port: mc.port2}, [mc.port2]);
+  return (await new Promise(r => mc.port1.onmessage = r)).data;
+}
+
+async function getCrossOriginIsolatedForSharedWorker(t, withCoopCoep) {
+  const workerURL = `${WORKER_URL}${withCoopCoep ? PIPE : ''}`;
+  const worker = new SharedWorker(workerURL);
+  worker.addEventListener('error', t.unreached_func('worker.error'));
+
+  const mc = new MessageChannel();
+  worker.port.postMessage({port: mc.port2}, [mc.port2]);
+  return (await new Promise(r => mc.port1.onmessage = r)).data;
+}
+
+async function getCrossOriginIsolatedForServiceWorker(t, withCoopCoep) {
+  // As we don't want the service worker to control any page, generate a
+  // one-time scope.
+  const SCOPE = new URL(`resources/${token()}.html`, location).pathname;
+  const workerURL = `${WORKER_URL}${withCoopCoep ? PIPE : ''}`;
+  const reg =
+    await service_worker_unregister_and_register(t, workerURL, SCOPE);
+  t.add_cleanup(() => reg.unregister());
+  const worker = reg.installing;
+
+  const mc = new MessageChannel();
+  worker.postMessage({port: mc.port2}, [mc.port2]);
+  return (await new Promise(r => mc.port1.onmessage = r)).data;
+}
+
+function generateFrameTest(origin, value, expectation) {
   async function run(t) {
     assert_equals(
-      await getCrossOriginIsolatedFor(t, origin, value), expectation);
+      await getCrossOriginIsolatedForFrame(t, origin, value), expectation);
   }
   // We use async_test, not promise_test here to run tests in parallel.
   async_test((t) => {
     run(t).then(() => t.done(), (e) => t.step(() => {throw e;}));
-  }, `origin = ${origin}, value = ${value}`);
+  }, `frame: origin = ${origin}, value = ${value}`);
 }
 
-generateTest(ORIGIN, undefined, true);
-generateTest(ORIGIN, '*', true);
-generateTest(ORIGIN, "self", true);
+function generateDedicatedWorkerTest(scheme, value, expectation) {
+  async function run(t) {
+    assert_equals(
+      await getCrossOriginIsolatedForDedicatedWorker(t, scheme, value),
+      expectation);
+  }
+  // We use async_test, not promise_test here to run tests in parallel.
+  async_test((t) => {
+    run(t).then(() => t.done(), (e) => t.step(() => {throw e;}));
+  }, `dedicated worker: scheme = ${scheme}, value = ${value}`);
+}
+
+function generateSharedWorkerTest(withCoopCoep) {
+  async function run(t) {
+    assert_equals(
+      await getCrossOriginIsolatedForSharedWorker(t, withCoopCoep),
+      withCoopCoep);
+  }
+  // We use async_test, not promise_test here to run tests in parallel.
+  async_test((t) => {
+    run(t).then(() => t.done(), (e) => t.step(() => {throw e;}));
+  }, `shared worker: withCoopCoep = ${withCoopCoep}`);
+}
+
+function generateServiceWorkerTest(withCoopCoep) {
+  // Here we use promise_test as we want to use a cleanup callback that returns
+  // a promise.
+  promise_test(async (t) => {
+    assert_equals(
+      await getCrossOriginIsolatedForServiceWorker(t, withCoopCoep),
+      withCoopCoep);
+  }, `service worker: withCoopCoep = ${withCoopCoep}`);
+}
+
+generateFrameTest(ORIGIN, undefined, true);
+generateFrameTest(ORIGIN, '*', true);
+generateFrameTest(ORIGIN, 'self', true);
 // We need the backslash to escape the close parenthesis in a wpt pipe.
-generateTest(ORIGIN, "(\\)", false);
-generateTest(HTTPS_REMOTE_ORIGIN, undefined, false);
-generateTest(HTTPS_REMOTE_ORIGIN, '*', true);
-generateTest(HTTPS_REMOTE_ORIGIN, "self", false);
+generateFrameTest(ORIGIN, '(\\)', false);
+generateFrameTest(HTTPS_REMOTE_ORIGIN, undefined, false);
+generateFrameTest(HTTPS_REMOTE_ORIGIN, '*', true);
+generateFrameTest(HTTPS_REMOTE_ORIGIN, 'self', false);
+// We need the backslash to escape the close parenthesis in a  wpt pipe.
+generateFrameTest(HTTPS_REMOTE_ORIGIN, '(\\)', false);
+
+generateDedicatedWorkerTest('https', undefined, true);
+generateDedicatedWorkerTest('https', '*', true);
+generateDedicatedWorkerTest('https', 'self', true);
 // We need the backslash to escape the close parenthesis in a wpt pipe.
-generateTest(HTTPS_REMOTE_ORIGIN, "(\\)", false);
+generateDedicatedWorkerTest('https', '(\\)', false);
+generateDedicatedWorkerTest('data', undefined, false);
+generateDedicatedWorkerTest('data', '*', false);
+generateDedicatedWorkerTest('data', 'self', false);
+// We need the backslash to escape the close parenthesis in a wpt pipe.
+generateDedicatedWorkerTest('data', '(\\)', false);
+generateDedicatedWorkerTest('blob', undefined, true);
+generateDedicatedWorkerTest('blob', '*', true);
+generateDedicatedWorkerTest('blob', 'self', true);
+// We need the backslash to escape the close parenthesis in a wpt pipe.
+generateDedicatedWorkerTest('blob', '(\\)', false);
+
+generateSharedWorkerTest(false);
+generateSharedWorkerTest(true);
+
+generateServiceWorkerTest(false);
+generateServiceWorkerTest(true);
 </script>
 </body>
 </html>
diff --git a/html/cross-origin-embedder-policy/resources/cross-origin-isolated-worker.js b/html/cross-origin-embedder-policy/resources/cross-origin-isolated-worker.js
new file mode 100644
index 0000000..9f1e371
--- /dev/null
+++ b/html/cross-origin-embedder-policy/resources/cross-origin-isolated-worker.js
@@ -0,0 +1,11 @@
+// For DedicatedWorker and ServiceWorker
+self.addEventListener('message', (e) => {
+  e.data.port.postMessage(self.crossOriginIsolated);
+});
+
+// For SharedWorker
+self.addEventListener('connect', (e) => {
+  e.ports[0].onmessage = (ev) => {
+    ev.data.port.postMessage(self.crossOriginIsolated);
+  };
+});
\ No newline at end of file
diff --git a/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/blob-data.https.html b/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/blob-data.https.html
index dbc73e9..bfcc8b6 100644
--- a/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/blob-data.https.html
+++ b/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/blob-data.https.html
@@ -50,14 +50,14 @@
 `;
 }
 
-function propertyTests(name) {
+function propertyTests(name, crossOriginIsolated) {
   return `
 test(() => {
   assert_equals(self.origin, self.location.origin);
 }, "${name}: self.origin");
 
 test(() => {
-  assert_true(self.crossOriginIsolated);
+  assert_equals(self.crossOriginIsolated, ${crossOriginIsolated});
 }, "${name}: self.crossOriginIsolated");
 
 test(() => {
@@ -74,7 +74,7 @@
 
 ${blobWorkerIncrementerTest("blob worker", self.location.origin)}
 
-${propertyTests("blob worker")}
+${propertyTests("blob worker", true)}
 
 done();
 `;
@@ -90,7 +90,7 @@
 
 ${blobWorkerIncrementerTest("blob frame", self.location.origin)}
 
-${propertyTests("blob frame")}
+${propertyTests("blob frame", true)}
 <\/script>
 `;
 
@@ -106,7 +106,7 @@
 
 ${blobWorkerIncrementerTest("data worker")}
 
-${propertyTests("data worker")}
+${propertyTests("data worker", false)}
 
 done();
 `;
@@ -121,7 +121,7 @@
 
 ${blobWorkerIncrementerTest("data frame")}
 
-${propertyTests("data frame")}
+${propertyTests("data frame", true)}
 <\/script>
 `;