summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/html/user-activation
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/html/user-activation')
-rw-r--r--testing/web-platform/tests/html/user-activation/META.yml3
-rw-r--r--testing/web-platform/tests/html/user-activation/activation-trigger-keyboard-enter.html47
-rw-r--r--testing/web-platform/tests/html/user-activation/activation-trigger-keyboard-escape.html41
-rw-r--r--testing/web-platform/tests/html/user-activation/activation-trigger-mouse-left.html45
-rw-r--r--testing/web-platform/tests/html/user-activation/activation-trigger-mouse-right.html60
-rw-r--r--testing/web-platform/tests/html/user-activation/activation-trigger-pointerevent.html64
-rw-r--r--testing/web-platform/tests/html/user-activation/chained-setTimeout.html63
-rw-r--r--testing/web-platform/tests/html/user-activation/detached-iframe.html47
-rw-r--r--testing/web-platform/tests/html/user-activation/message-event-activation-api-iframe-cross-origin.sub.tentative.html55
-rw-r--r--testing/web-platform/tests/html/user-activation/message-event-init.tentative.html19
-rw-r--r--testing/web-platform/tests/html/user-activation/navigation-state-reset-crossorigin.sub.html53
-rw-r--r--testing/web-platform/tests/html/user-activation/navigation-state-reset-sameorigin.html62
-rw-r--r--testing/web-platform/tests/html/user-activation/no-activation-thru-escape-key.html65
-rw-r--r--testing/web-platform/tests/html/user-activation/propagation-crossorigin.sub.html121
-rw-r--r--testing/web-platform/tests/html/user-activation/propagation-same-and-cross-origin.sub.html151
-rw-r--r--testing/web-platform/tests/html/user-activation/propagation-sameorigin.html120
-rw-r--r--testing/web-platform/tests/html/user-activation/resources/child-message-event-api.html24
-rw-r--r--testing/web-platform/tests/html/user-activation/resources/child-one.html29
-rw-r--r--testing/web-platform/tests/html/user-activation/resources/child-two.html29
-rw-r--r--testing/web-platform/tests/html/user-activation/resources/consumption-crossorigin-child.sub.html29
-rw-r--r--testing/web-platform/tests/html/user-activation/resources/consumption-sameorigin-child.html29
-rw-r--r--testing/web-platform/tests/html/user-activation/resources/propagation-crossorigin-child.sub.html27
-rw-r--r--testing/web-platform/tests/html/user-activation/resources/propagation-sameorigin-child.html27
-rw-r--r--testing/web-platform/tests/html/user-activation/resources/utils.js48
-rw-r--r--testing/web-platform/tests/html/user-activation/user-activation-interface.html32
25 files changed, 1290 insertions, 0 deletions
diff --git a/testing/web-platform/tests/html/user-activation/META.yml b/testing/web-platform/tests/html/user-activation/META.yml
new file mode 100644
index 0000000000..e50fcfc84f
--- /dev/null
+++ b/testing/web-platform/tests/html/user-activation/META.yml
@@ -0,0 +1,3 @@
+spec: https://html.spec.whatwg.org/multipage/interaction.html#tracking-user-activation
+suggested_reviewers:
+ - marcoscaceres
diff --git a/testing/web-platform/tests/html/user-activation/activation-trigger-keyboard-enter.html b/testing/web-platform/tests/html/user-activation/activation-trigger-keyboard-enter.html
new file mode 100644
index 0000000000..be32d999b1
--- /dev/null
+++ b/testing/web-platform/tests/html/user-activation/activation-trigger-keyboard-enter.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#activation-triggering-input-event">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="resources/utils.js"></script>
+</head>
+<body onload="runTests()">
+ <h1>Test for keyboard activation trigger for ENTER key</h1>
+ <p>Tests user activation from a ENTER keyboard event.</p>
+ <input type="text" autofocus />
+ <ol id="instructions">
+ <li>Press ENTER key.
+ </ol>
+ <script>
+ function runTests() {
+ promise_test(async () => {
+ const ENTER_KEY = '\uE007';
+
+ let keydown_event = getEvent('keydown');
+ let keypress_event = getEvent('keypress');
+ let keyup_event = getEvent('keyup');
+
+ await test_driver.send_keys(document.body, ENTER_KEY);
+
+ await keydown_event;
+ let consumed = await consumeTransientActivation();
+ assert_true(consumed,
+ "ENTER keydown event should result in activation");
+
+ await keypress_event;
+ consumed = await consumeTransientActivation();
+ assert_false(consumed,
+ "ENTER keypress should have no activation after keydown consumption");
+
+ await keyup_event;
+ consumed = await consumeTransientActivation();
+ assert_false(consumed,
+ "ENTER keyup should have no activation after keydown consumption");
+ }, "Activation through ENTER keyboard event");
+ }
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/user-activation/activation-trigger-keyboard-escape.html b/testing/web-platform/tests/html/user-activation/activation-trigger-keyboard-escape.html
new file mode 100644
index 0000000000..82c94d84c2
--- /dev/null
+++ b/testing/web-platform/tests/html/user-activation/activation-trigger-keyboard-escape.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#activation-triggering-input-event">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="resources/utils.js"></script>
+</head>
+<body onload="runTests()">
+ <h1>Test for keyboard activation trigger for ESCAPE key</h1>
+ <p>Tests missing user activation from a ESCAPE keyboard event.</p>
+ <input type="text" autofocus />
+ <ol id="instructions">
+ <li>Press ESCAPE key.
+ </ol>
+ <script>
+ function runTests() {
+ promise_test(async () => {
+ const ESCAPE_KEY = '\uE00C';
+
+ let keydown_event = getEvent('keydown');
+ let keyup_event = getEvent('keyup');
+
+ await test_driver.send_keys(document.body, ESCAPE_KEY);
+
+ await keydown_event;
+ let consumed = await consumeTransientActivation();
+ assert_false(consumed,
+ "ESCAPE keydown event should not result in activation");
+
+ await keyup_event;
+ consumed = await consumeTransientActivation();
+ assert_false(consumed,
+ "ESCAPE keyup should have no activation after keydown consumption");
+ }, "Activation through ESCAPE keyboard event");
+ }
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/user-activation/activation-trigger-mouse-left.html b/testing/web-platform/tests/html/user-activation/activation-trigger-mouse-left.html
new file mode 100644
index 0000000000..75a248c425
--- /dev/null
+++ b/testing/web-platform/tests/html/user-activation/activation-trigger-mouse-left.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#activation-triggering-input-event">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="resources/utils.js"></script>
+</head>
+<body onload="runTests()">
+ <h1>Test for click activation trigger</h1>
+ <p>Tests user activation from a mouse click.</p>
+ <ol id="instructions">
+ <li>Click anywhere in the document.
+ </ol>
+ <script>
+ function runTests() {
+ promise_test(async () => {
+
+ let mousedown_event = getEvent('mousedown');
+ let mouseup_event = getEvent('mouseup');
+ let click_event = getEvent('click');
+
+ await test_driver.click(document.body);
+
+ await mousedown_event;
+ let consumed = await consumeTransientActivation();
+ assert_true(consumed,
+ "mousedown event should result in activation");
+
+ await mouseup_event;
+ consumed = await consumeTransientActivation();
+ assert_false(consumed,
+ "mouseup should have no activation after mousedown consumption");
+
+ await click_event;
+ consumed = await consumeTransientActivation();
+ assert_false(consumed,
+ "click should have no activation after mousedown consumption");
+ }, "Activation through left-click mouse event");
+ }
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/user-activation/activation-trigger-mouse-right.html b/testing/web-platform/tests/html/user-activation/activation-trigger-mouse-right.html
new file mode 100644
index 0000000000..f98a54fe23
--- /dev/null
+++ b/testing/web-platform/tests/html/user-activation/activation-trigger-mouse-right.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#activation-triggering-input-event">
+ <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>
+</head>
+<body onload="runTests()">
+ <h1>Test for right-click activation trigger</h1>
+ <p>Tests user activation from a mouse right-click.</p>
+ <ol id="instructions">
+ <li>Right-click anywhere in the document.
+ </ol>
+ <script>
+ function runTests() {
+ promise_test(async () => {
+ var actions = new test_driver.Actions();
+ actions.pointerMove(0, 0, {origin: document.body})
+ .pointerDown({button: actions.ButtonType.RIGHT})
+ .pointerUp({button: actions.ButtonType.RIGHT})
+ .send();
+
+ // In most non-Windows platforms the right-click context-menu appears on mousedown, so
+ // mouseup and auxclick events are not received by the page if the menu is modal. We
+ // are suppressing the context-menu to guarantee receiving those later events.
+ document.body.addEventListener("contextmenu", e => e.preventDefault());
+
+ let mousedown_event = getEvent('mousedown');
+ let mouseup_event = getEvent('mouseup');
+ let auxclick_event = getEvent('auxclick');
+ let contextmenu_event = getEvent('contextmenu');
+
+ await mousedown_event;
+ let consumed = await consumeTransientActivation();
+ assert_true(consumed,
+ "mousedown event should result in activation");
+
+ await mouseup_event;
+ consumed = await consumeTransientActivation();
+ assert_false(consumed,
+ "mouseup should have no activation after mousedown consumption");
+
+ await auxclick_event;
+ consumed = await consumeTransientActivation();
+ assert_false(consumed,
+ "auxclick should have no activation after mousedown consumption");
+
+ await contextmenu_event;
+ consumed = await consumeTransientActivation();
+ assert_false(consumed,
+ "contextmenu should have no activation after mousedown consumption");
+ }, "Activation through right-click mouse event");
+ }
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/user-activation/activation-trigger-pointerevent.html b/testing/web-platform/tests/html/user-activation/activation-trigger-pointerevent.html
new file mode 100644
index 0000000000..e3042b718d
--- /dev/null
+++ b/testing/web-platform/tests/html/user-activation/activation-trigger-pointerevent.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#activation-triggering-input-event">
+ <meta name="variant" content="?mouse">
+ <meta name="variant" content="?pen">
+ <meta name="variant" content="?touch">
+ <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>
+</head>
+<body onload="runTests()">
+ <h1>Test for pointerevent click activation trigger</h1>
+ <p>Tests user activation from a pointer click.</p>
+ <ol id="instructions">
+ <li>Click anywhere in the document.
+ </ol>
+ <script>
+ function runTests() {
+ let pointer_type = location.search.substring(1);
+
+ promise_test(async () => {
+ const test_pointer = pointer_type + "TestPointer";
+
+ let pointerdown_event = getEvent('pointerdown');
+ let pointerup_event = getEvent('pointerup');
+ let click_event = getEvent('click');
+
+ await new test_driver.Actions().addPointer(test_pointer, pointer_type)
+ .pointerMove(0, 0, {origin:document.body, sourceName:test_pointer})
+ .pointerDown({sourceName:test_pointer})
+ .pointerUp({sourceName:test_pointer})
+ .send();
+
+ await pointerdown_event;
+ let consumed_pointerdown = await consumeTransientActivation();
+ await pointerup_event;
+ let consumed_pointerup = await consumeTransientActivation();
+ await click_event;
+ let consumed_click = await consumeTransientActivation();
+
+ if (pointer_type === "mouse") {
+ assert_true(consumed_pointerdown,
+ pointer_type + " pointerdown event should result in activation");
+ assert_false(consumed_pointerup,
+ pointer_type + " pointerup should have no activation after pointerdown consumption");
+ assert_false(consumed_click,
+ pointer_type + " click should have no activation after pointerdown consumption");
+ } else {
+ assert_false(consumed_pointerdown,
+ pointer_type + " pointerdown event should not result in activation");
+ assert_true(consumed_pointerup,
+ pointer_type + " pointerup event should result in activation");
+ assert_false(consumed_click,
+ pointer_type + " click should have no activation after pointerup consumption");
+ }
+ }, "Activation through " + pointer_type + " pointerevent click");
+ }
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/user-activation/chained-setTimeout.html b/testing/web-platform/tests/html/user-activation/chained-setTimeout.html
new file mode 100644
index 0000000000..a530837392
--- /dev/null
+++ b/testing/web-platform/tests/html/user-activation/chained-setTimeout.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script>
+ let chained_timeout_test = async_test("Chained setTimeout test");
+
+ const max_call_depth = 3;
+ const delay_ms = 10;
+
+ function testInitialStates(depth) {
+ assert_true(1 <= depth && depth <= max_call_depth);
+
+ chained_timeout_test.step_timeout(() => {
+ let test_name = "Call-depth=" + depth + ": initial activation states are false";
+ test(() => {
+ assert_false(navigator.userActivation.isActive);
+ assert_false(navigator.userActivation.hasBeenActive);
+ }, test_name);
+
+ if (depth < max_call_depth)
+ testInitialStates(depth+1);
+ else
+ test_driver.click(document.body);
+ }, delay_ms);
+ }
+
+ function testFinalStates(depth) {
+ assert_true(1 <= depth && depth <= max_call_depth);
+
+ chained_timeout_test.step_timeout(() => {
+ let test_name = "Call-depth=" + depth + ": after-click activation states are true";
+ test(() => {
+ assert_true(navigator.userActivation.isActive);
+ assert_true(navigator.userActivation.hasBeenActive);
+ }, test_name);
+
+ if (depth < max_call_depth)
+ testFinalStates(depth+1);
+ else
+ chained_timeout_test.done();
+ }, delay_ms)
+ }
+
+ function run() {
+ window.addEventListener("click", event => {
+ testFinalStates(1);
+ });
+
+ testInitialStates(1);
+ }
+ </script>
+</head>
+<body onload="run()">
+ <h1>User activation state in chained setTimeout calls</h1>
+ <p>Tests that user activation state is visible in arbitrary call depth of setTimeout.</p>
+ <ol id="instructions">
+ <li>Click anywhere in the document.
+ </ol></body>
+</html>
diff --git a/testing/web-platform/tests/html/user-activation/detached-iframe.html b/testing/web-platform/tests/html/user-activation/detached-iframe.html
new file mode 100644
index 0000000000..af3d23072b
--- /dev/null
+++ b/testing/web-platform/tests/html/user-activation/detached-iframe.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="resources/utils.js"></script>
+ </head>
+ <body></body>
+ <script>
+ async function attachIframe() {
+ const iframe = document.createElement("iframe");
+ iframe.src = "about:blank";
+ await new Promise((r) => {
+ iframe.addEventListener("load", r, { once: true });
+ document.body.append(iframe);
+ });
+ return iframe;
+ }
+
+ promise_test(async () => {
+ const iframe = await attachIframe();
+ const { userActivation } = iframe.contentWindow.navigator;
+
+ assert_false(
+ userActivation.isActive,
+ "No transient activation before click"
+ );
+ assert_false(
+ userActivation.hasBeenActive,
+ "No sticky activation before click"
+ );
+
+ // Confirm we have activation
+ await test_driver.bless("click", null, iframe.contentWindow);
+ assert_true(userActivation.isActive, "is active after click");
+ assert_true(userActivation.hasBeenActive, "has been active");
+
+ // Remove the context
+ iframe.remove();
+ assert_equals(iframe.contentWindow, null, "No more global");
+ assert_true(userActivation.isActive, "isActive");
+ assert_true(userActivation.hasBeenActive, "hasBeenActive");
+ }, "navigator.userActivation retains state even if global is removed");
+ </script>
+</html>
diff --git a/testing/web-platform/tests/html/user-activation/message-event-activation-api-iframe-cross-origin.sub.tentative.html b/testing/web-platform/tests/html/user-activation/message-event-activation-api-iframe-cross-origin.sub.tentative.html
new file mode 100644
index 0000000000..2a16f45496
--- /dev/null
+++ b/testing/web-platform/tests/html/user-activation/message-event-activation-api-iframe-cross-origin.sub.tentative.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<!--
+ Tentative due to:
+ https://github.com/whatwg/html/issues/1983
+-->
+<html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+</head>
+<body>
+ <h1>Clicking in iframe has activation state in child via MessageEvent</h1>
+ <ol id="instructions">
+ <li>Click inside the red area.
+ </ol>
+ <iframe id="child" width="200" height="200">
+ </iframe>
+ <script>
+ async_test(function(t) {
+ var child = document.getElementById("child");
+ child.src = "http://{{hosts[alt][]}}:{{ports[http][0]}}/html/user-activation/resources/child-message-event-api.html";
+ assert_false(navigator.userActivation.isActive);
+ assert_false(navigator.userActivation.hasBeenActive);
+ window.addEventListener("message", t.step_func(event => {
+ if (event.data == 'child-loaded') {
+ // values have false after load
+ assert_true(event.userActivation != null);
+ assert_false(event.userActivation.isActive);
+ assert_false(event.userActivation.hasBeenActive);
+ test_driver.click(child);
+ } else if (event.data == 'child-clicked') {
+ // values have activation state on click
+ assert_true(navigator.userActivation.hasBeenActive);
+ assert_true(event.userActivation != null);
+ assert_true(event.userActivation.isActive);
+ assert_true(event.userActivation.hasBeenActive);
+ child.contentWindow.postMessage('report', "*");
+ } else if (event.data == 'child-report') {
+ assert_false(navigator.userActivation.isActive);
+ assert_true(navigator.userActivation.hasBeenActive);
+ assert_true(event.userActivation != null);
+ assert_false(event.userActivation.isActive);
+ assert_true(event.userActivation.hasBeenActive);
+ child.contentWindow.postMessage('report-no-activation', "*");
+ } else if (event.data == 'child-report-no-activation') {
+ assert_equals(event.userActivation, null);
+ t.done();
+ }
+ }));
+ }, "Message propagates values on post");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/user-activation/message-event-init.tentative.html b/testing/web-platform/tests/html/user-activation/message-event-init.tentative.html
new file mode 100644
index 0000000000..1f3ef55170
--- /dev/null
+++ b/testing/web-platform/tests/html/user-activation/message-event-init.tentative.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<!--
+ Tentative due to:
+ https://github.com/whatwg/html/issues/1983
+-->
+<title>MessageEvent constructor</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(function() {
+ var ev = new MessageEvent("test", { userActivation: navigator.userActivation })
+ assert_equals(ev.userActivation, navigator.userActivation, "userActivation attribute")
+}, "MessageEventInit user activation set")
+test(function() {
+ var ev = new MessageEvent("test")
+ assert_equals(ev.userActivation, null, "userActivation attribute")
+}, "MessageEventInit user activation not set")
+
+</script>
diff --git a/testing/web-platform/tests/html/user-activation/navigation-state-reset-crossorigin.sub.html b/testing/web-platform/tests/html/user-activation/navigation-state-reset-crossorigin.sub.html
new file mode 100644
index 0000000000..5e16e9d46c
--- /dev/null
+++ b/testing/web-platform/tests/html/user-activation/navigation-state-reset-crossorigin.sub.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="resources/utils.js"></script>
+</head>
+<body>
+ <h1>Post-navigation activation state in child</h1>
+ <p>Tests that navigating a cross-origin child frame resets its activation states.</p>
+ <ol id="instructions">
+ <li>Click inside the yellow area.
+ </ol>
+ <iframe id="child" width="200" height="50">
+ </iframe>
+ <script>
+ async_test(function(t) {
+ var child = document.getElementById("child");
+ child.src = "http://{{hosts[alt][]}}:{{ports[http][0]}}/html/user-activation/resources/child-one.html";
+ window.addEventListener("message", t.step_func(event => {
+ // Test driver can send messages too...
+ if (typeof event.data !== "string") return;
+
+ var msg = JSON.parse(event.data);
+ if (msg.type == 'child-one-loaded') {
+ assert_false(navigator.userActivation.isActive);
+ assert_false(navigator.userActivation.hasBeenActive);
+ assert_false(msg.isActive);
+ assert_false(msg.hasBeenActive);
+
+ delayByFrames(() => test_driver.click(child), 5);
+ } else if (msg.type == 'child-one-clicked') {
+ assert_true(navigator.userActivation.isActive);
+ assert_true(navigator.userActivation.hasBeenActive);
+ assert_true(msg.isActive);
+ assert_true(msg.hasBeenActive);
+
+ child.src = "http://{{hosts[alt][]}}:{{ports[http][1]}}/html/user-activation/resources/child-two.html";
+ } else if (msg.type == 'child-two-loaded') {
+ assert_true(navigator.userActivation.isActive);
+ assert_true(navigator.userActivation.hasBeenActive);
+ assert_false(msg.isActive);
+ assert_false(msg.hasBeenActive);
+
+ t.done();
+ }
+ }));
+ }, "Post-navigation state reset.");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/user-activation/navigation-state-reset-sameorigin.html b/testing/web-platform/tests/html/user-activation/navigation-state-reset-sameorigin.html
new file mode 100644
index 0000000000..c240f96b2c
--- /dev/null
+++ b/testing/web-platform/tests/html/user-activation/navigation-state-reset-sameorigin.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ </head>
+ <body>
+ <h1>Post-navigation activation state in child</h1>
+ <p>
+ Tests that navigating a same-origin child frame resets its activation
+ states.
+ </p>
+ <ol id="instructions">
+ <li>Click inside the yellow area.</li>
+ </ol>
+
+ <iframe id="child" width="200" height="50"> </iframe>
+ <script>
+ function message(type) {
+ return new Promise((resolve) => {
+ window.addEventListener("message", function listener(event) {
+ const data = JSON.parse(event.data);
+ if (data.type === type) {
+ window.removeEventListener("message", listener);
+ resolve(data);
+ }
+ });
+ });
+ }
+ promise_test(async (t) => {
+ var child = document.getElementById("child");
+ child.src = "./resources/child-one.html";
+ const unclickeData = await message("child-one-loaded");
+ assert_false(navigator.userActivation.isActive);
+ assert_false(navigator.userActivation.hasBeenActive);
+ assert_false(unclickeData.isActive);
+ assert_false(unclickeData.hasBeenActive);
+
+ const [, child1Data] = await Promise.all([
+ test_driver.click(child),
+ message("child-one-clicked"),
+ ]);
+
+ assert_true(navigator.userActivation.isActive);
+ assert_true(navigator.userActivation.hasBeenActive);
+ assert_true(child1Data.isActive);
+ assert_true(child1Data.hasBeenActive);
+
+ child.src = "./resources/child-two.html";
+
+ const child2Data = await message("child-two-loaded");
+
+ assert_true(navigator.userActivation.isActive);
+ assert_true(navigator.userActivation.hasBeenActive);
+ assert_false(child2Data.isActive);
+ assert_false(child2Data.hasBeenActive);
+ }, "Post-navigation state reset.");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/user-activation/no-activation-thru-escape-key.html b/testing/web-platform/tests/html/user-activation/no-activation-thru-escape-key.html
new file mode 100644
index 0000000000..0045e20788
--- /dev/null
+++ b/testing/web-platform/tests/html/user-activation/no-activation-thru-escape-key.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>No user activation through 'Escape' key</title>
+ <meta name="timeout" content="long">
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
+ <link rel="author" title="Google" href="http://www.google.com "/>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/interaction.html#tracking-user-activation">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <style>
+ #target {
+ width: 40ex;
+ background-color: yellow;
+ }
+ </style>
+ <script type="text/javascript">
+ let keydown_event_fired = false;
+ let keyup_event_fired = false;
+
+ function run() {
+ let textbox_elem = document.getElementById("target");
+ let test_esc_key = async_test("'Escape' key doesn't activate a page.");
+
+ test_esc_key.step(() => {
+ assert_true(!!navigator.userActivation, "This test requires user activation query API");
+ });
+
+ textbox_elem.focus();
+
+ on_event(textbox_elem, "keydown", () => {
+ test_esc_key.step(() => {
+ keydown_event_fired = true;
+ assert_false(navigator.userActivation.isActive, "No user activation on keydown");
+ });
+ });
+
+ on_event(textbox_elem, "keyup", () => {
+ test_esc_key.step(() => {
+ if (keydown_event_fired)
+ keyup_event_fired = true;
+ assert_true(keydown_event_fired, "keydown event fired before keyup");
+ assert_false(navigator.userActivation.isActive, "No user activation on keyup");
+ });
+ });
+
+ // Inject mouse inputs.
+ const escape_key = "\uE00C";
+ test_driver
+ .send_keys(textbox_elem, escape_key)
+ .then(() => {
+ assert_true(keyup_event_fired, "keydown event fired before keyup");
+ test_esc_key.done();
+ });
+ }
+ </script>
+ </head>
+ <body onload="run()">
+ <h1>No user activation through 'Escape' key</h1>
+ <h4>Tests that pressing/releasing 'Escape' key is not treated as a user activation.</h4>
+ <input id="target" value="Press and release the 'Esc' key." />
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/user-activation/propagation-crossorigin.sub.html b/testing/web-platform/tests/html/user-activation/propagation-crossorigin.sub.html
new file mode 100644
index 0000000000..d317764036
--- /dev/null
+++ b/testing/web-platform/tests/html/user-activation/propagation-crossorigin.sub.html
@@ -0,0 +1,121 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="resources/utils.js"></script>
+ <script>
+ // Frame layout:
+ // top=origin0:this-file [
+ // child1=origin1:child-one.html,
+ // child-xo=origin2:propagation-crossorigin-child.html [
+ // gchild=origin3:child-two.html
+ // ]
+ // ]
+ let propagation_test = async_test("Propagation test");
+
+ let num_children_to_load = 3;
+ let num_children_to_report = 3;
+
+ function finishLoadPhase() {
+ assert_equals(num_children_to_load, 0);
+
+ test(() => {
+ assert_false(navigator.userActivation.isActive);
+ assert_false(navigator.userActivation.hasBeenActive);
+ }, "Parent frame initial state");
+
+ delayByFrames(() => test_driver.click(document.getElementById("child-xo")), 7);
+ }
+
+ function finishReportPhase() {
+ assert_equals(num_children_to_report, 0);
+
+ test(() => {
+ assert_true(navigator.userActivation.isActive);
+ assert_true(navigator.userActivation.hasBeenActive);
+ }, "Parent frame final state");
+
+ propagation_test.done();
+ }
+
+ window.addEventListener("message", event => {
+ // Test driver can send messages too...
+ if (typeof event.data !== "string") return;
+
+ var msg = JSON.parse(event.data);
+
+ if (msg.type == 'child-one-loaded') {
+ test(() => {
+ assert_false(msg.isActive);
+ assert_false(msg.hasBeenActive);
+ }, "Child1 frame initial state");
+ } else if (msg.type == 'child-crossorigin-loaded') {
+ test(() => {
+ assert_false(msg.isActive);
+ assert_false(msg.hasBeenActive);
+ }, "Child2 frame initial state");
+ } else if (msg.type == 'child-two-loaded') {
+ test(() => {
+ assert_false(msg.isActive);
+ assert_false(msg.hasBeenActive);
+ }, "Grandchild frame initial state");
+ } else if (msg.type == 'child-one-report') {
+ test(() => {
+ assert_false(msg.isActive);
+ assert_false(msg.hasBeenActive);
+ }, "Child1 frame final state");
+ } else if (msg.type == 'child-crossorigin-report') {
+ // This msg was triggered by a user click.
+ test(() => {
+ assert_true(msg.isActive);
+ assert_true(msg.hasBeenActive);
+ }, "Child2 frame final state");
+
+ // Ask remaining frames to report states.
+ let ask_report = JSON.stringify({"type": "report"});
+ frames[0].postMessage(ask_report, "*");
+ frames[1].frames[0].postMessage(ask_report, "*");
+ } else if (msg.type == 'child-two-report') {
+ test(() => {
+ assert_false(msg.isActive);
+ assert_false(msg.hasBeenActive);
+ }, "Grand child frame final state");
+ }
+
+ // Phase switching.
+ if (msg.type.endsWith("-loaded")) {
+ if (--num_children_to_load == 0)
+ finishLoadPhase();
+ } else if (msg.type.endsWith("-report")) {
+ if (--num_children_to_report == 0)
+ finishReportPhase();
+ }
+ });
+ async function createIframes() {
+ const child1 = document.createElement("iframe");
+ child1.src = "http://{{hosts[alt][]}}:{{ports[http][0]}}/html/user-activation/resources/child-one.html";
+ child1.id = "child1";
+ document.body.appendChild(child1);
+ await new Promise((resolve) => {
+ child1.onload = resolve;
+ document.body.appendChild(child1);
+ });
+ const childXO = document.createElement("iframe");
+ childXO.id = "child-xo";
+ childXO.src = "http://{{hosts[alt][]}}:{{ports[http][1]}}/html/user-activation/resources/propagation-crossorigin-child.sub.html";
+ document.body.appendChild(childXO);
+ }
+ </script>
+</head>
+<body onload="createIframes()">
+ <h1>User activation propagation across cross-origin frame boundary</h1>
+ <p>Tests that user activation does not propagate across cross-origin frame boundary.</p>
+ <ol id="instructions">
+ <li>Click anywhere on the green area (child frame).
+ </ol>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/user-activation/propagation-same-and-cross-origin.sub.html b/testing/web-platform/tests/html/user-activation/propagation-same-and-cross-origin.sub.html
new file mode 100644
index 0000000000..31a7222448
--- /dev/null
+++ b/testing/web-platform/tests/html/user-activation/propagation-same-and-cross-origin.sub.html
@@ -0,0 +1,151 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="resources/utils.js"></script>
+ </head>
+ <body>
+ <h1>Check that cross origin iframes don't get activated</h1>
+ <p>
+ Tests that activating a same-origin navigable doesn't activate a cross
+ origin navigable.
+ </p>
+ <ol id="instructions">
+ <li>Click inside the yellow area.</li>
+ </ol>
+ <h1>Same origin</h1>
+ <iframe id="so-child" width="200" height="50"></iframe>
+ <h1>Cross origin</h1>
+ <iframe id="xo-child" width="200" height="50"></iframe>
+ </body>
+ <script>
+ const soChild = document.getElementById("so-child");
+ const xoChild = document.getElementById("xo-child");
+
+ function requestXOReport() {
+ xoChild.contentWindow.postMessage(
+ JSON.stringify({ type: "report" }),
+ "*"
+ );
+ return receiveMessage(`child-two-report`);
+ }
+
+ promise_setup(() => {
+ soChild.src = "./resources/child-one.html";
+ xoChild.src =
+ "http://{{hosts[alt][]}}:{{ports[http][0]}}/html/user-activation/resources/child-two.html";
+ return Promise.all([
+ receiveMessage("child-one-loaded"),
+ receiveMessage("child-two-loaded"),
+ ]);
+ });
+
+ promise_test(async (t) => {
+ const unclickedCrossOrigin = await requestXOReport();
+ const soActivation = soChild.contentWindow.navigator.userActivation;
+ assert_false(
+ navigator.userActivation.isActive,
+ "top-frame navigator.userActivation.isActive must be false"
+ );
+ assert_false(
+ navigator.userActivation.hasBeenActive,
+ "top-frame navigator.userActivation.hasBeenActive must be false"
+ );
+
+ assert_false(soActivation.isActive, "child-one isActive must be false");
+ assert_false(
+ soActivation.hasBeenActive,
+ "child-one hasBeenActive must be false"
+ );
+ assert_false(
+ unclickedCrossOrigin.isActive,
+ "child-two isActive must be false"
+ );
+ assert_false(
+ unclickedCrossOrigin.hasBeenActive,
+ "child-two hasBeenActive must be false"
+ );
+ }, "Check Initial states of user activation are all false");
+
+ promise_test(async (t) => {
+ await test_driver.click(soChild);
+ const xoActivation = await requestXOReport();
+ const soActivation = soChild.contentWindow.navigator.userActivation;
+ assert_true(
+ navigator.userActivation.isActive,
+ "top-frame navigator.userActivation.isActive must be true"
+ );
+ assert_true(
+ navigator.userActivation.hasBeenActive,
+ "top-frame navigator.userActivation.hasBeenActive must be true"
+ );
+ assert_true(soActivation.isActive, "child-one isActive must be true");
+ assert_true(
+ soActivation.hasBeenActive,
+ "child-one hasBeenActive must be true"
+ );
+ assert_false(xoActivation.isActive, "child-two isActive must be false");
+ assert_false(
+ xoActivation.hasBeenActive,
+ "child-two hasBeenActive must be false"
+ );
+ }, "Check that activating a same-origin navigable doesn't activate a cross origin navigable");
+
+ promise_test(async (t) => {
+ await consumeTransientActivation();
+ const soActivation = soChild.contentWindow.navigator.userActivation;
+ // Before click...
+ assert_false(
+ navigator.userActivation.isActive,
+ "top-frame navigator.userActivation.isActive must be false"
+ );
+ assert_true(
+ navigator.userActivation.hasBeenActive,
+ "top-frame navigator.userActivation.hasBeenActive must be true"
+ );
+ assert_false(soActivation.isActive, "child-one isActive must be false");
+ assert_true(
+ soActivation.hasBeenActive,
+ "child-one hasBeenActive must be true"
+ );
+ const xoActivation = await requestXOReport();
+ assert_false(xoActivation.isActive, "child-two isActive must be false");
+ assert_false(
+ xoActivation.hasBeenActive,
+ "child-two hasBeenActive must be false"
+ );
+
+ // Click!
+ const [, xoActivationAfterClick] = await Promise.all([
+ test_driver.click(xoChild),
+ receiveMessage("child-two-clicked"),
+ ]);
+
+ // After click...
+ assert_true(
+ navigator.userActivation.isActive,
+ "top-frame navigator.userActivation.isActive must be true"
+ );
+ assert_true(
+ navigator.userActivation.hasBeenActive,
+ "top-frame navigator.userActivation.hasBeenActive must remain true"
+ );
+ assert_true(
+ xoActivationAfterClick.isActive,
+ "child-two isActive must be true"
+ );
+ assert_true(
+ xoActivationAfterClick.hasBeenActive,
+ "child-two hasBeenActive must be true"
+ );
+ assert_false(soActivation.isActive, "child-one isActive must be false");
+ assert_true(
+ soActivation.hasBeenActive,
+ "child-one hasBeenActive must remain true"
+ );
+ }, "Clicking on the cross-origin navigable activates parent navigable.");
+ </script>
+</html>
diff --git a/testing/web-platform/tests/html/user-activation/propagation-sameorigin.html b/testing/web-platform/tests/html/user-activation/propagation-sameorigin.html
new file mode 100644
index 0000000000..94d15c2d41
--- /dev/null
+++ b/testing/web-platform/tests/html/user-activation/propagation-sameorigin.html
@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script>
+ // Frame layout:
+ // top=this-file [
+ // child1=child-one.html,
+ // child-so=propagation-sameorigin-child.html [
+ // gchild=child-two.html
+ // ]
+ // ]
+ let propagation_test = async_test("Propagation test");
+
+ let num_children_to_load = 3;
+ let num_children_to_report = 3;
+
+ function finishLoadPhase() {
+ assert_equals(num_children_to_load, 0);
+
+ test(() => {
+ assert_false(navigator.userActivation.isActive);
+ assert_false(navigator.userActivation.hasBeenActive);
+ }, "Parent frame initial state");
+
+ test_driver.click(document.getElementById("child-so"));
+ }
+
+ function finishReportPhase() {
+ assert_equals(num_children_to_report, 0);
+
+ test(() => {
+ assert_true(navigator.userActivation.isActive);
+ assert_true(navigator.userActivation.hasBeenActive);
+ }, "Parent frame final state");
+
+ propagation_test.done();
+ }
+
+ window.addEventListener("message", event => {
+ // Test driver can send messages too...
+ if (typeof event.data !== "string") return;
+
+ var msg = JSON.parse(event.data);
+
+ if (msg.type == 'child-one-loaded') {
+ test(() => {
+ assert_false(msg.isActive);
+ assert_false(msg.hasBeenActive);
+ }, "Child1 frame initial state");
+ } else if (msg.type == 'child-sameorigin-loaded') {
+ test(() => {
+ assert_false(msg.isActive);
+ assert_false(msg.hasBeenActive);
+ }, "Child2 frame initial state");
+ } else if (msg.type == 'child-two-loaded') {
+ test(() => {
+ assert_false(msg.isActive);
+ assert_false(msg.hasBeenActive);
+ }, "Grandchild frame initial state");
+ } else if (msg.type == 'child-one-report') {
+ // Siblings (same or cross origin) should not be activated per spec
+ // Spec issue discussing: https://github.com/whatwg/html/issues/9831
+ test(() => {
+ assert_false(msg.isActive);
+ assert_false(msg.hasBeenActive);
+ }, "Child1 frame final state");
+ } else if (msg.type == 'child-sameorigin-report') {
+ // This msg was triggered by a user click.
+ test(() => {
+ assert_true(msg.isActive);
+ assert_true(msg.hasBeenActive);
+ }, "Child2 frame final state");
+
+ // Ask remaining frames to report states.
+ let ask_report = JSON.stringify({"type": "report"});
+ frames[0].postMessage(ask_report, "*");
+ frames[1].frames[0].postMessage(ask_report, "*");
+ } else if (msg.type == 'child-two-report') {
+ test(() => {
+ assert_true(msg.isActive);
+ assert_true(msg.hasBeenActive);
+ }, "Grand child frame final state");
+ }
+
+ // Phase switching.
+ if (msg.type.endsWith("-loaded")) {
+ if (--num_children_to_load == 0)
+ finishLoadPhase();
+ } else if (msg.type.endsWith("-report")) {
+ if (--num_children_to_report == 0)
+ finishReportPhase();
+ }
+ });
+ async function createIframes() {
+ const child1 = document.createElement("iframe");
+ child1.src = "resources/child-one.html";
+ child1.id = "child1";
+ await new Promise((resolve) => {
+ child1.onload = resolve;
+ document.body.appendChild(child1);
+ });
+ const childSO = document.createElement("iframe");
+ childSO.id = "child-so";
+ childSO.src = "resources/propagation-sameorigin-child.html";
+ document.body.appendChild(childSO);
+ }
+ </script>
+</head>
+<body onload="createIframes()">
+ <h1>User activation propagation across same-origin frame boundary</h1>
+ <p>Tests that user activation propagates across same-origin frame boundary.</p>
+ <ol id="instructions">
+ <li>Click anywhere on the green area (child frame).
+ </ol>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/user-activation/resources/child-message-event-api.html b/testing/web-platform/tests/html/user-activation/resources/child-message-event-api.html
new file mode 100644
index 0000000000..a0001633c2
--- /dev/null
+++ b/testing/web-platform/tests/html/user-activation/resources/child-message-event-api.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<body style="background: red;">
+ <script>
+ window.parent.postMessage("child-loaded",
+ {targetOrigin: "*", includeUserActivation: true});
+ window.addEventListener("click", event => {
+ window.parent.postMessage("child-clicked",
+ {targetOrigin: "*", includeUserActivation: true});
+ var win = window.open('404.html');
+ win.close();
+ });
+
+ window.addEventListener("message", event => {
+ if (event.data == "report") {
+ window.parent.postMessage("child-report",
+ {targetOrigin: "*", includeUserActivation: true});
+ }
+ if (event.data == "report-no-activation") {
+ window.parent.postMessage("child-report-no-activation",
+ {targetOrigin: "*", includeUserActivation: false});
+ }
+ });
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/user-activation/resources/child-one.html b/testing/web-platform/tests/html/user-activation/resources/child-one.html
new file mode 100644
index 0000000000..9668372620
--- /dev/null
+++ b/testing/web-platform/tests/html/user-activation/resources/child-one.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<body style="background: yellow;">
+ <script>
+ window.top.postMessage(JSON.stringify({
+ "type": "child-one-loaded",
+ "isActive": navigator.userActivation.isActive,
+ "hasBeenActive": navigator.userActivation.hasBeenActive
+ }), "*");
+
+ window.addEventListener("click", event => {
+ window.top.postMessage(JSON.stringify({
+ "type": "child-one-clicked",
+ "isActive": navigator.userActivation.isActive,
+ "hasBeenActive": navigator.userActivation.hasBeenActive
+ }), "*");
+ });
+
+ window.addEventListener("message", event => {
+ var msg = JSON.parse(event.data);
+ if (msg.type == "report") {
+ window.top.postMessage(JSON.stringify({
+ "type": "child-one-report",
+ "isActive": navigator.userActivation.isActive,
+ "hasBeenActive": navigator.userActivation.hasBeenActive
+ }), "*");
+ }
+ });
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/user-activation/resources/child-two.html b/testing/web-platform/tests/html/user-activation/resources/child-two.html
new file mode 100644
index 0000000000..9fb68dbd51
--- /dev/null
+++ b/testing/web-platform/tests/html/user-activation/resources/child-two.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<body style="background: lightgrey;">
+ <script>
+ window.top.postMessage(JSON.stringify({
+ "type": "child-two-loaded",
+ "isActive": navigator.userActivation.isActive,
+ "hasBeenActive": navigator.userActivation.hasBeenActive
+ }), "*");
+
+ window.addEventListener("click", event => {
+ window.top.postMessage(JSON.stringify({
+ "type": "child-two-clicked",
+ "isActive": navigator.userActivation.isActive,
+ "hasBeenActive": navigator.userActivation.hasBeenActive
+ }), "*");
+ });
+
+ window.addEventListener("message", event => {
+ var msg = JSON.parse(event.data);
+ if (msg.type == "report") {
+ window.top.postMessage(JSON.stringify({
+ "type": "child-two-report",
+ "isActive": navigator.userActivation.isActive,
+ "hasBeenActive": navigator.userActivation.hasBeenActive
+ }), "*");
+ }
+ });
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/user-activation/resources/consumption-crossorigin-child.sub.html b/testing/web-platform/tests/html/user-activation/resources/consumption-crossorigin-child.sub.html
new file mode 100644
index 0000000000..518e000d0b
--- /dev/null
+++ b/testing/web-platform/tests/html/user-activation/resources/consumption-crossorigin-child.sub.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script>
+ window.top.postMessage(JSON.stringify({
+ "type": "child-crossorigin-loaded",
+ "isActive": navigator.userActivation.isActive,
+ "hasBeenActive": navigator.userActivation.hasBeenActive
+ }), "*");
+
+ window.addEventListener("click", event => {
+ window.open().close();
+
+ window.top.postMessage(JSON.stringify({
+ "type": "child-crossorigin-report",
+ "isActive": navigator.userActivation.isActive,
+ "hasBeenActive": navigator.userActivation.hasBeenActive
+ }), "*");
+ });
+ </script>
+</head>
+<body style="background: lightgreen;">
+ <!-- The midpoint of this frame should be outside the grandchild frame. -->
+ <div style="height: 75px;">Cross-origin child frame</div>
+ <iframe id="child2" width="270px" height="30px"
+ src="http://{{hosts[][]}}:{{ports[http][1]}}/html/user-activation/resources/child-two.html">
+ </iframe>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/user-activation/resources/consumption-sameorigin-child.html b/testing/web-platform/tests/html/user-activation/resources/consumption-sameorigin-child.html
new file mode 100644
index 0000000000..9e421fc0f1
--- /dev/null
+++ b/testing/web-platform/tests/html/user-activation/resources/consumption-sameorigin-child.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script>
+ window.top.postMessage(JSON.stringify({
+ "type": "child-sameorigin-loaded",
+ "isActive": navigator.userActivation.isActive,
+ "hasBeenActive": navigator.userActivation.hasBeenActive
+ }), "*");
+
+ window.addEventListener("click", event => {
+ window.open().close();
+
+ window.top.postMessage(JSON.stringify({
+ "type": "child-sameorigin-report",
+ "isActive": navigator.userActivation.isActive,
+ "hasBeenActive": navigator.userActivation.hasBeenActive
+ }), "*");
+ });
+ </script>
+</head>
+<body style="background: lightgreen;">
+ <!-- The midpoint of this frame should be outside the grandchild frame. -->
+ <div style="height: 75px;">Same-origin child frame</div>
+ <iframe id="child2" width="270px" height="30px"
+ src="child-two.html">
+ </iframe>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/user-activation/resources/propagation-crossorigin-child.sub.html b/testing/web-platform/tests/html/user-activation/resources/propagation-crossorigin-child.sub.html
new file mode 100644
index 0000000000..e920566a21
--- /dev/null
+++ b/testing/web-platform/tests/html/user-activation/resources/propagation-crossorigin-child.sub.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script>
+ window.top.postMessage(JSON.stringify({
+ "type": "child-crossorigin-loaded",
+ "isActive": navigator.userActivation.isActive,
+ "hasBeenActive": navigator.userActivation.hasBeenActive
+ }), "*");
+
+ window.addEventListener("click", event => {
+ window.top.postMessage(JSON.stringify({
+ "type": "child-crossorigin-report",
+ "isActive": navigator.userActivation.isActive,
+ "hasBeenActive": navigator.userActivation.hasBeenActive
+ }), "*");
+ });
+ </script>
+</head>
+<body style="background: lightgreen;">
+ <!-- The midpoint of this frame should be outside the grandchild frame. -->
+ <div style="height: 75px;">Cross-origin child frame</div>
+ <iframe id="child2" width="270px" height="30px"
+ src="http://{{hosts[][]}}:{{ports[http][1]}}/html/user-activation/resources/child-two.html">
+ </iframe>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/user-activation/resources/propagation-sameorigin-child.html b/testing/web-platform/tests/html/user-activation/resources/propagation-sameorigin-child.html
new file mode 100644
index 0000000000..69ad50cb71
--- /dev/null
+++ b/testing/web-platform/tests/html/user-activation/resources/propagation-sameorigin-child.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script>
+ window.top.postMessage(JSON.stringify({
+ "type": "child-sameorigin-loaded",
+ "isActive": navigator.userActivation.isActive,
+ "hasBeenActive": navigator.userActivation.hasBeenActive
+ }), "*");
+
+ window.addEventListener("click", event => {
+ window.top.postMessage(JSON.stringify({
+ "type": "child-sameorigin-report",
+ "isActive": navigator.userActivation.isActive,
+ "hasBeenActive": navigator.userActivation.hasBeenActive
+ }), "*");
+ });
+ </script>
+</head>
+<body style="background: lightgreen;">
+ <!-- The midpoint of this frame should be outside the grandchild frame. -->
+ <div style="height: 75px;">Same-origin child frame</div>
+ <iframe id="child2" width="270px" height="30px"
+ src="child-two.html">
+ </iframe>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/user-activation/resources/utils.js b/testing/web-platform/tests/html/user-activation/resources/utils.js
new file mode 100644
index 0000000000..5d3302583f
--- /dev/null
+++ b/testing/web-platform/tests/html/user-activation/resources/utils.js
@@ -0,0 +1,48 @@
+function delayByFrames(f, num_frames) {
+ function recurse(depth) {
+ if (depth == 0)
+ f();
+ else
+ requestAnimationFrame(() => recurse(depth-1));
+ }
+ recurse(num_frames);
+}
+
+// Returns a Promise which is resolved with the event object when the event is
+// fired.
+function getEvent(eventType) {
+ return new Promise(resolve => {
+ document.body.addEventListener(eventType, e => resolve(e), {once: true});
+ });
+}
+
+
+// Returns a Promise which is resolved with a "true" iff transient activation
+// was available and successfully consumed.
+//
+// This function relies on Fullscreen API to check/consume user activation
+// state.
+async function consumeTransientActivation() {
+ try {
+ await document.body.requestFullscreen();
+ await document.exitFullscreen();
+ return true;
+ } catch(e) {
+ return false;
+ }
+}
+
+function receiveMessage(type) {
+ return new Promise((resolve) => {
+ window.addEventListener("message", function listener(event) {
+ if (typeof event.data !== "string") {
+ return;
+ }
+ const data = JSON.parse(event.data);
+ if (data.type === type) {
+ window.removeEventListener("message", listener);
+ resolve(data);
+ }
+ });
+ });
+}
diff --git a/testing/web-platform/tests/html/user-activation/user-activation-interface.html b/testing/web-platform/tests/html/user-activation/user-activation-interface.html
new file mode 100644
index 0000000000..8ece08f11d
--- /dev/null
+++ b/testing/web-platform/tests/html/user-activation/user-activation-interface.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="resources/utils.js"></script>
+</head>
+<body onload="runTests()">
+ <h1>Basic test for navigator.userActivation interface</h1>
+ <p>Tests that navigator.userActivation shows user activation states.</p>
+ <ol id="instructions">
+ <li>Click anywhere in the document.
+ </ol>
+ <script>
+ function runTests() {
+ promise_test(async () => {
+ assert_true(!!navigator.userActivation, "This test requires navigator.userActivation API");
+
+ assert_false(navigator.userActivation.hasBeenActive, "No sticky activation before click");
+ assert_false(navigator.userActivation.isActive, "No transient activation before click");
+
+ await test_driver.click(document.body);
+
+ assert_true(navigator.userActivation.hasBeenActive, "Has sticky activation after click");
+ assert_true(navigator.userActivation.isActive, "Has transient activation after click");
+ }, "navigator.userActivation shows correct states before/after a click");
+ }
+ </script>
+</body>
+</html>