Fullscreen Capability Delegation: Refine activation consumption & tests

Consume source_frame transient user activation on delegation attempts.
Add same-origin subframe and same/cross-origin popup WPT coverage files.

Refactor the existing WPT so the new tests share and use similar code.
Move and rename WPTs to a new html/capability-delegation folder.

Bug: 1293083, 1326575
Change-Id: I0a232fb58e2b8c575e3afa8b6e76f3650a56b505
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3688764
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Reviewed-by: Mustaq Ahmed <mustaq@chromium.org>
Auto-Submit: Mike Wasserman <msw@chromium.org>
Commit-Queue: Mike Wasserman <msw@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1011876}
diff --git a/fullscreen/api/delegate-request.https.sub.tentative.html b/fullscreen/api/delegate-request.https.sub.tentative.html
deleted file mode 100644
index 37e0099..0000000
--- a/fullscreen/api/delegate-request.https.sub.tentative.html
+++ /dev/null
@@ -1,88 +0,0 @@
-<!DOCTYPE html>
-<!--
-   Tentative due to:
-     https://github.com/WICG/capability-delegation/issues/10
--->
-<title>Fullscreen request delegation test</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/resources/testdriver.js"></script>
-<script src="/resources/testdriver-actions.js"></script>
-<script src="/resources/testdriver-vendor.js"></script>
-
-<div>
-  Verifies that element.requestFullscreen() call from a cross-origin subframe without user
-  activation works if and only if the top frame has user activation and it delegates the capability
-  to the subframe.
-
-  https://wicg.github.io/capability-delegation/spec.html
-
-  See wpt/html/user-activation/propagation*.html for child->parent user activation visibility tests.
-  TODO: Check same-origin iframes, sibling frames, and popup<->opener delegation.
-</div>
-
-<iframe allow="fullscreen" width="300px" height="50px"
-        src="https://{{hosts[alt][www]}}:{{ports[https][0]}}/fullscreen/api/resources/delegate-request-subframe.html">
-</iframe>
-
-<script>
-  // Returns a |Promise| that gets resolved with |event.data| when |window|
-  // receives from |source| a "message" event whose |event.data.type| matches the string
-  // |message_data_type|.
-  function getMessageData(message_data_type, source) {
-      return new Promise(resolve => {
-          function waitAndRemove(e) {
-              if (e.source != source || !e.data || e.data.type != message_data_type)
-                  return;
-              window.removeEventListener("message", waitAndRemove);
-              resolve(e.data);
-          }
-          window.addEventListener("message", waitAndRemove);
-      });
-  }
-
-  promise_setup(async () => {
-      // Make sure the iframe has loaded.
-      await getMessageData("subframe-loaded", frames[0]);
-  });
-
-  const target_origin = "https://{{hosts[alt][www]}}:{{ports[https][0]}}";
-  const request = {"type": "make-fullscreen-request"};
-
-  promise_test(async () => {
-      let result_promise = getMessageData("result", frames[0]);
-      frames[0].postMessage(request, {targetOrigin: target_origin});
-      let data = await result_promise;
-
-      assert_equals(data.result, "failure");
-  }, "Fullscreen-request from a subframe fails without delegation when the top frame has no user activation");
-
-  promise_test(async () => {
-      let result_promise = getMessageData("result", frames[0]);
-      frames[0].postMessage(request, {targetOrigin: target_origin,
-                                      delegate: "fullscreen"});
-      let data = await result_promise;
-
-      assert_equals(data.result, "failure");
-  }, "Fullscreen-request from a subframe fails with delegation when the top frame has no user activation");
-
-  promise_test(async () => {
-      let result_promise = getMessageData("result", frames[0]);
-      await test_driver.bless();
-      frames[0].postMessage(request, {targetOrigin: target_origin});
-      let data = await result_promise;
-
-      assert_equals(data.result, "failure");
-  }, "Fullscreen-request from a subframe fails without delegation when the top frame has user activation");
-
-  promise_test(async () => {
-      let result_promise = getMessageData("result", frames[0]);
-      await test_driver.bless();
-      frames[0].postMessage(request, {targetOrigin: target_origin,
-                                      delegate: "fullscreen"});
-      let data = await result_promise;
-
-      assert_equals(data.result, "success");
-  }, "Fullscreen-request from a subframe succeeds with delegation when the top frame has user activation");
-
-</script>
diff --git a/fullscreen/api/resources/delegate-request-subframe.html b/fullscreen/api/resources/delegate-request-subframe.html
deleted file mode 100644
index 9bb9d78..0000000
--- a/fullscreen/api/resources/delegate-request-subframe.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<!DOCTYPE html>
-<title>Fullscreen request delegation test: subframe</title>
-<body>Subframe body</body>
-
-<script>
-  function reportResult(msg) {
-      window.top.postMessage({"type": "result", "result": msg}, "*");
-  }
-
-  document.addEventListener('fullscreenchange', () => {
-      if (document.fullscreenElement) {
-          reportResult("success");
-          document.exitFullscreen();
-      }
-  });
-
-  document.addEventListener('fullscreenerror', () => {
-      reportResult("failure");
-  });
-
-  window.addEventListener("message", e => {
-      if (e.data.type == "make-fullscreen-request") {
-          document.body.requestFullscreen();
-      }
-  });
-
-  window.top.postMessage({"type": "subframe-loaded"}, "*");
-</script>
diff --git a/html/capability-delegation/delegate-fullscreen-request-popup-cross-origin.https.sub.tentative.html b/html/capability-delegation/delegate-fullscreen-request-popup-cross-origin.https.sub.tentative.html
new file mode 100644
index 0000000..2d531a5
--- /dev/null
+++ b/html/capability-delegation/delegate-fullscreen-request-popup-cross-origin.https.sub.tentative.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<!--
+   Tentative due to:
+     https://github.com/WICG/capability-delegation/issues/10
+-->
+<title>Capability Delegation of Fullscreen Requests: Popup Cross-Origin</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/utils.js"></script>
+
+<div>
+  Verifies that element.requestFullscreen() calls from a cross-origin popup without user activation
+  work if and only if the opener has user activation and it delegates the capability.
+
+  https://wicg.github.io/capability-delegation/spec.html
+</div>
+
+<script>
+  let popup = null;
+
+  function testCrossOriginPopupFullscreenDelegation(capability, activate, expectation) {
+      const message = {"type": "make-fullscreen-request"};
+      const origin = "https://{{hosts[alt][www]}}:{{ports[https][0]}}";
+      const expectationType = expectation ? "succeeds" : "fails";
+      const delegationType = capability ? "with delegation" : "without delegation";
+      const activationType = activate ? "with user activation" : "with no user activation";
+
+      promise_test(async () => {
+          const data = await postCapabilityDelegationMessage(popup, message, origin, capability, activate);
+          assert_equals(data.result, expectation ? "success" : "failure");
+      }, `Fullscreen requests from a cross-origin popup ${expectationType} ${delegationType} from an opener ${activationType}`);
+  }
+
+  promise_setup(async () => {
+      // Make sure the recipient popup has loaded.
+      popup = window.open("https://{{hosts[alt][www]}}:{{ports[https][0]}}/html/capability-delegation/resources/delegate-fullscreen-request-recipient.html",
+                          "", "width=300,height=200");
+      return getMessageData("recipient-loaded", popup);
+  });
+
+  testCrossOriginPopupFullscreenDelegation(/*capability=*/"", /*activate=*/false, /*expectation=*/false);
+  testCrossOriginPopupFullscreenDelegation(/*capability=*/"fullscreen", /*activate=*/false, /*expectation=*/false);
+  testCrossOriginPopupFullscreenDelegation(/*capability=*/"", /*activate=*/true, /*expectation=*/false);
+  testCrossOriginPopupFullscreenDelegation(/*capability=*/"fullscreen", /*activate=*/true, /*expectation=*/true);
+</script>
diff --git a/html/capability-delegation/delegate-fullscreen-request-popup-same-origin.https.tentative.html b/html/capability-delegation/delegate-fullscreen-request-popup-same-origin.https.tentative.html
new file mode 100644
index 0000000..d429095
--- /dev/null
+++ b/html/capability-delegation/delegate-fullscreen-request-popup-same-origin.https.tentative.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<!--
+   Tentative due to:
+     https://github.com/WICG/capability-delegation/issues/10
+-->
+<title>Capability Delegation of Fullscreen Requests: Popup Same-Origin</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/utils.js"></script>
+
+<div>
+  Verifies that element.requestFullscreen() calls from a same-origin popup without user activation
+  work if and only if the opener has user activation and it delegates the capability.
+
+  https://wicg.github.io/capability-delegation/spec.html
+</div>
+
+<script>
+  let popup = null;
+
+  function testSameOriginPopupFullscreenDelegation(capability, activate, expectation) {
+      const message = {"type": "make-fullscreen-request"};
+      const expectationType = expectation ? "succeeds" : "fails";
+      const delegationType = capability ? "with delegation" : "without delegation";
+      const activationType = activate ? "with user activation" : "with no user activation";
+
+      promise_test(async () => {
+          const data = await postCapabilityDelegationMessage(popup, message, location.origin, capability, activate);
+          assert_equals(data.result, expectation ? "success" : "failure");
+      }, `Fullscreen requests from a same-origin popup ${expectationType} ${delegationType} from an opener ${activationType}`);
+  }
+
+  promise_setup(async () => {
+      // Make sure the recipient popup has loaded.
+      popup = window.open("./resources/delegate-fullscreen-request-recipient.html",
+                          "", "width=300,height=200");
+      return getMessageData("recipient-loaded", popup);
+  });
+
+  testSameOriginPopupFullscreenDelegation(/*capability=*/"", /*activate=*/false, /*expectation=*/false);
+  testSameOriginPopupFullscreenDelegation(/*capability=*/"fullscreen", /*activate=*/false, /*expectation=*/false);
+  testSameOriginPopupFullscreenDelegation(/*capability=*/"", /*activate=*/true, /*expectation=*/false);
+  testSameOriginPopupFullscreenDelegation(/*capability=*/"fullscreen", /*activate=*/true, /*expectation=*/true);
+</script>
diff --git a/html/capability-delegation/delegate-fullscreen-request-subframe-cross-origin.https.sub.tentative.html b/html/capability-delegation/delegate-fullscreen-request-subframe-cross-origin.https.sub.tentative.html
new file mode 100644
index 0000000..5f2d2f6
--- /dev/null
+++ b/html/capability-delegation/delegate-fullscreen-request-subframe-cross-origin.https.sub.tentative.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<!--
+   Tentative due to:
+     https://github.com/WICG/capability-delegation/issues/10
+-->
+<title>Capability Delegation of Fullscreen Requests: Subframe Cross-Origin</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/utils.js"></script>
+
+<div>
+  Verifies that element.requestFullscreen() calls from a cross-origin subframe without user
+  activation work if and only if the top frame has user activation and it delegates the capability.
+
+  https://wicg.github.io/capability-delegation/spec.html
+
+  See wpt/html/user-activation/propagation*.html for frame tree user activation visibility tests.
+</div>
+
+<iframe allow="fullscreen" width="300px" height="50px"
+        src="https://{{hosts[alt][www]}}:{{ports[https][0]}}/html/capability-delegation/resources/delegate-fullscreen-request-recipient.html">
+</iframe>
+
+<script>
+  function testCrossOriginSubframeFullscreenDelegation(capability, activate, expectation) {
+      const message = {"type": "make-fullscreen-request"};
+      const origin = "https://{{hosts[alt][www]}}:{{ports[https][0]}}";
+      const expectationType = expectation ? "succeeds" : "fails";
+      const delegationType = capability ? "with delegation" : "without delegation";
+      const activationType = activate ? "with user activation" : "with no user activation";
+
+      promise_test(async () => {
+          const data = await postCapabilityDelegationMessage(frames[0], message, origin, capability, activate);
+          assert_equals(data.result, expectation ? "success" : "failure");
+      }, `Fullscreen requests from a cross-origin subframe ${expectationType} ${delegationType} from an opener ${activationType}`);
+  }
+
+  promise_setup(async () => {
+      // Make sure the recipient iframe has loaded.
+      return getMessageData("recipient-loaded", frames[0]);
+  });
+
+  testCrossOriginSubframeFullscreenDelegation(/*capability=*/"", /*activate=*/false, /*expectation=*/false);
+  testCrossOriginSubframeFullscreenDelegation(/*capability=*/"fullscreen", /*activate=*/false, /*expectation=*/false);
+  testCrossOriginSubframeFullscreenDelegation(/*capability=*/"", /*activate=*/true, /*expectation=*/false);
+  testCrossOriginSubframeFullscreenDelegation(/*capability=*/"fullscreen", /*activate=*/true, /*expectation=*/true);
+</script>
diff --git a/html/capability-delegation/delegate-fullscreen-request-subframe-same-origin.https.tentative.html b/html/capability-delegation/delegate-fullscreen-request-subframe-same-origin.https.tentative.html
new file mode 100644
index 0000000..9e5482d
--- /dev/null
+++ b/html/capability-delegation/delegate-fullscreen-request-subframe-same-origin.https.tentative.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<!--
+   Tentative due to:
+     https://github.com/WICG/capability-delegation/issues/10
+-->
+<title>Capability Delegation of Fullscreen Requests: Subframe Same-Origin</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/utils.js"></script>
+
+<div>
+  Verifies that element.requestFullscreen() calls from a same-origin subframe without user
+  activation work if and only if the top frame has user activation, regardless of whether it
+  delegates the capability or not.
+
+  https://wicg.github.io/capability-delegation/spec.html
+
+  See wpt/html/user-activation/propagation*.html for frame tree user activation visibility tests.
+</div>
+
+<iframe allow="fullscreen" width="300px" height="50px"
+        src="./resources/delegate-fullscreen-request-recipient.html">
+</iframe>
+
+<script>
+  function testSameOriginSubframeFullscreenDelegation(capability, activate, expectation) {
+      const message = {"type": "make-fullscreen-request"};
+      const expectationType = expectation ? "succeeds" : "fails";
+      const delegationType = capability ? "with delegation" : "without delegation";
+      const activationType = activate ? "with user activation" : "with no user activation";
+
+      promise_test(async () => {
+          const data = await postCapabilityDelegationMessage(frames[0], message, window.location, capability, activate);
+          assert_equals(data.result, expectation ? "success" : "failure");
+      }, `Fullscreen requests from a same-origin subframe ${expectationType} ${delegationType} from an opener ${activationType}`);
+  }
+
+  promise_setup(async () => {
+      // Make sure the recipient iframe has loaded.
+      return getMessageData("recipient-loaded", frames[0]);
+  });
+
+  testSameOriginSubframeFullscreenDelegation(/*capability=*/"", /*activate=*/false, /*expectation=*/false);
+  testSameOriginSubframeFullscreenDelegation(/*capability=*/"fullscreen", /*activate=*/false, /*expectation=*/false);
+  testSameOriginSubframeFullscreenDelegation(/*capability=*/"", /*activate=*/true, /*expectation=*/true);
+  testSameOriginSubframeFullscreenDelegation(/*capability=*/"fullscreen", /*activate=*/true, /*expectation=*/true);
+</script>
diff --git a/html/capability-delegation/delegation-consumes-activation.https.tentative.html b/html/capability-delegation/delegation-consumes-activation.https.tentative.html
new file mode 100644
index 0000000..1a8805d
--- /dev/null
+++ b/html/capability-delegation/delegation-consumes-activation.https.tentative.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<!--
+   Tentative due to:
+     https://github.com/whatwg/html/issues/4008
+-->
+<title>Capability Delegation: Consumes User Activation</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="resources/utils.js"></script>
+
+<div>
+  Test that capability delegation consumes transient user activation.
+
+  https://wicg.github.io/capability-delegation/spec.html
+</div>
+
+<iframe allow="payment" width="300px" height="50px" src="about:blank"></iframe>
+
+<script>
+  promise_test(async () => {
+      assert_false(navigator.userActivation.isActive);
+      await test_driver.bless();
+      assert_true(navigator.userActivation.isActive);
+      frames[0].postMessage({"type": "none"}, {targetOrigin: location.origin, delegate: ""});
+      assert_true(navigator.userActivation.isActive);
+      frames[0].postMessage({"type": "none"}, {targetOrigin: location.origin, delegate: "payment"});
+      assert_false(navigator.userActivation.isActive);
+  }, `capability delegation consumes transient user activation`);
+</script>
diff --git a/html/capability-delegation/resources/delegate-fullscreen-request-recipient.html b/html/capability-delegation/resources/delegate-fullscreen-request-recipient.html
new file mode 100644
index 0000000..11daf73
--- /dev/null
+++ b/html/capability-delegation/resources/delegate-fullscreen-request-recipient.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<title>Capability Delegation of Fullscreen Requests test recipient</title>
+<body>Capability Delegation of Fullscreen Requests test recipient body</body>
+
+<script>
+  const initiator = window.opener ? window.opener : window.top;
+  initiator.postMessage({"type": "recipient-loaded"}, "*");
+
+  function reportResult(msg) {
+      initiator.postMessage({"type": "result", "result": msg}, "*");
+  }
+
+  document.addEventListener('fullscreenchange', async () => {
+      if (document.fullscreenElement) {
+          await document.exitFullscreen();
+          reportResult("success");
+      }
+  });
+
+  document.addEventListener('fullscreenerror', () => {
+      reportResult("failure");
+  });
+
+  window.addEventListener("message", e => {
+      if (e.data.type == "make-fullscreen-request") {
+          document.body.requestFullscreen();
+      }
+  });
+</script>
diff --git a/html/capability-delegation/resources/utils.js b/html/capability-delegation/resources/utils.js
new file mode 100644
index 0000000..3add3eb
--- /dev/null
+++ b/html/capability-delegation/resources/utils.js
@@ -0,0 +1,24 @@
+// Returns a Promise that gets resolved with `event.data` when `window` receives from `source` a
+// "message" event whose `event.data.type` matches the string `message_data_type`.
+function getMessageData(message_data_type, source) {
+    return new Promise(resolve => {
+        function waitAndRemove(e) {
+            if (e.source != source || !e.data || e.data.type != message_data_type)
+                return;
+            window.removeEventListener("message", waitAndRemove);
+            resolve(e.data);
+        }
+        window.addEventListener("message", waitAndRemove);
+    });
+}
+
+// A helper that simulates user activation on the current frame if `activate` is true, then posts
+// `message` to `frame` with the target `origin` and specified `capability` to delegate. This helper
+// awaits and returns the result message sent in reply from `frame`.
+async function postCapabilityDelegationMessage(frame, message, origin, capability, activate) {
+    let result_promise = getMessageData("result", frame);
+    if (activate)
+        await test_driver.bless();
+    frame.postMessage(message, {targetOrigin: origin, delegate: capability});
+    return await result_promise;
+}