Reland "Reject Web Serial requests with an opaque origin"

This reverts commit cd2f352906cbfa254100390271e51e9890ed2bbe.

Reason for revert: The fixed parent CL https://chromium-review.googlesource.com/c/chromium/src/+/4307819 is re-landed.

Original change's description:
> Revert "Reject Web Serial requests with an opaque origin"
>
> Revert submission 4112689
>
> Reason for revert: suspect for introducing test failures for
> DedicatedWorkerTest.TopLevelFrameSecurityOrigin, for example
> https://ci.chromium.org/ui/p/chromium/builders/ci/linux-ubsan-vptr/21391/overview
>
> Reverted changes: /q/submissionid:4112689
>
> Change-Id: I496ec21cc6a6e39dd3c2943b8fc7ce3d179a1f73
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4307114
> Owners-Override: Mikel Astiz <mastiz@google.com>
> Commit-Queue: Mikel Astiz <mastiz@chromium.org>
> Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
> Cr-Commit-Position: refs/heads/main@{#1112768}

Change-Id: Ic3f4fe58a946b53a23dfba57d30ee889ec3cc359
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4311852
Reviewed-by: Reilly Grant <reillyg@chromium.org>
Commit-Queue: Jack Hsieh <chengweih@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1113766}
diff --git a/serial/getPorts/reject_opaque_origin.https.html b/serial/getPorts/reject_opaque_origin.https.html
new file mode 100644
index 0000000..b2f630a
--- /dev/null
+++ b/serial/getPorts/reject_opaque_origin.https.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+  'use strict';
+
+  promise_test(async (t) => {
+    await promise_rejects_dom(
+        t, 'SecurityError', navigator.serial.getPorts(),
+        'getPorts() should throw a SecurityError DOMException when called ' +
+        'from a context where the top-level document has an opaque origin.');
+  }, 'Calls to Serial APIs from an origin with opaque top origin get blocked.');
+</script>
diff --git a/serial/getPorts/reject_opaque_origin.https.html.headers b/serial/getPorts/reject_opaque_origin.https.html.headers
new file mode 100644
index 0000000..1efcf8c
--- /dev/null
+++ b/serial/getPorts/reject_opaque_origin.https.html.headers
@@ -0,0 +1 @@
+Content-Security-Policy: sandbox allow-scripts
diff --git a/serial/getPorts/sandboxed_iframe.https.window.js b/serial/getPorts/sandboxed_iframe.https.window.js
new file mode 100644
index 0000000..8fae11c
--- /dev/null
+++ b/serial/getPorts/sandboxed_iframe.https.window.js
@@ -0,0 +1,22 @@
+'use strict';
+
+let iframe = document.createElement('iframe');
+
+promise_test(async () => {
+  await new Promise(resolve => {
+    iframe.src = '../resources/open-in-iframe.html';
+    iframe.sandbox.add('allow-scripts');
+    iframe.allow = 'serial';
+    document.body.appendChild(iframe);
+    iframe.addEventListener('load', resolve);
+  });
+
+  await new Promise(resolve => {
+    iframe.contentWindow.postMessage({type: 'GetPorts'}, '*');
+
+    window.addEventListener('message', (messageEvent) => {
+      assert_equals('Success', messageEvent.data);
+      resolve();
+    });
+  });
+}, 'GetPorts from a sandboxed iframe is valid.');
diff --git a/serial/requestPort/reject_opaque_origin.https.html b/serial/requestPort/reject_opaque_origin.https.html
new file mode 100644
index 0000000..ade8ae7
--- /dev/null
+++ b/serial/requestPort/reject_opaque_origin.https.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+  'use strict';
+
+  promise_test(async (t) => {
+    await promise_rejects_dom(
+        t, 'SecurityError', navigator.serial.requestPort(),
+        'requestPort() should throw a SecurityError DOMException when called ' +
+        'from a context where the top-level document has an opaque origin.');
+  }, 'Calls to Serial APIs from an origin with opaque top origin get blocked.');
+</script>
diff --git a/serial/requestPort/reject_opaque_origin.https.html.headers b/serial/requestPort/reject_opaque_origin.https.html.headers
new file mode 100644
index 0000000..1efcf8c
--- /dev/null
+++ b/serial/requestPort/reject_opaque_origin.https.html.headers
@@ -0,0 +1 @@
+Content-Security-Policy: sandbox allow-scripts
diff --git a/serial/requestPort/sandboxed_iframe.https.window.js b/serial/requestPort/sandboxed_iframe.https.window.js
new file mode 100644
index 0000000..6e16951
--- /dev/null
+++ b/serial/requestPort/sandboxed_iframe.https.window.js
@@ -0,0 +1,26 @@
+'use strict';
+
+let iframe = document.createElement('iframe');
+
+promise_test(async () => {
+  await new Promise(resolve => {
+    iframe.src = '../resources/open-in-iframe.html';
+    iframe.sandbox.add('allow-scripts');
+    iframe.allow = 'serial';
+    document.body.appendChild(iframe);
+    iframe.addEventListener('load', resolve);
+  });
+
+  await new Promise(resolve => {
+    iframe.contentWindow.postMessage({type: 'RequestPort'}, '*');
+
+    window.addEventListener('message', (messageEvent) => {
+      // The failure message of no device chosen is expected. The point here is
+      // to validate not failing because of a sandboxed iframe.
+      assert_equals(
+          'FAIL: NotFoundError: Failed to execute \'requestPort\' on \'Serial\': No port selected by the user.',
+          messageEvent.data);
+      resolve();
+    });
+  });
+}, 'RequestPort from a sandboxed iframe is valid.');
diff --git a/serial/resources/open-in-iframe.html b/serial/resources/open-in-iframe.html
new file mode 100644
index 0000000..9bf8beb
--- /dev/null
+++ b/serial/resources/open-in-iframe.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+  <button>Fake user gesture</button>
+</body>
+
+<script>
+  'use strict';
+
+  test_driver.set_test_context(parent);
+
+  window.onmessage = messageEvent => {
+    switch (messageEvent.data.type) {
+      case 'GetPorts':
+        navigator.serial.getPorts()
+            .then(ports => parent.postMessage('Success', '*'))
+            .catch(err => parent.postMessage(`FAIL: ${err}`, '*'));
+        break;
+      case 'RequestPort':
+        test_driver.click(document.getElementsByTagName('button')[0])
+            .then(() => navigator.serial.requestPort({filters: []}))
+            .then(port => parent.postMessage('Success', '*'))
+            .catch(err => parent.postMessage(`FAIL: ${err}`, '*'));
+        break;
+      default:
+        parent.postMessage(
+            `FAIL: Bad message type: ${messageEvent.data}`, '*');
+    };
+  };
+</script>