summaryrefslogtreecommitdiffstats
path: root/dom/base/test/useractivation
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /dom/base/test/useractivation
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/base/test/useractivation')
-rw-r--r--dom/base/test/useractivation/file_clipboard_common.js505
-rw-r--r--dom/base/test/useractivation/file_empty.html0
-rw-r--r--dom/base/test/useractivation/file_iframe_check_user_activation.html22
-rw-r--r--dom/base/test/useractivation/file_iframe_consume_user_activation.html15
-rw-r--r--dom/base/test/useractivation/file_iframe_user_activated.html14
-rw-r--r--dom/base/test/useractivation/file_useractivation_sandbox_transient_popup.html20
-rw-r--r--dom/base/test/useractivation/mochitest.ini23
-rw-r--r--dom/base/test/useractivation/moz.build9
-rw-r--r--dom/base/test/useractivation/test_clipboard_editor.html31
-rw-r--r--dom/base/test/useractivation/test_clipboard_noeditor.html29
-rw-r--r--dom/base/test/useractivation/test_popup_blocker_async_callback.html83
-rw-r--r--dom/base/test/useractivation/test_popup_blocker_mouse_event.html98
-rw-r--r--dom/base/test/useractivation/test_popup_blocker_pointer_event.html122
-rw-r--r--dom/base/test/useractivation/test_useractivation_has_been_activated.html115
-rw-r--r--dom/base/test/useractivation/test_useractivation_key_events.html91
-rw-r--r--dom/base/test/useractivation/test_useractivation_sandbox_transient.html90
-rw-r--r--dom/base/test/useractivation/test_useractivation_scrollbar.html135
-rw-r--r--dom/base/test/useractivation/test_useractivation_transient.html155
-rw-r--r--dom/base/test/useractivation/test_useractivation_transient_consuming.html151
19 files changed, 1708 insertions, 0 deletions
diff --git a/dom/base/test/useractivation/file_clipboard_common.js b/dom/base/test/useractivation/file_clipboard_common.js
new file mode 100644
index 0000000000..fe172e52c8
--- /dev/null
+++ b/dom/base/test/useractivation/file_clipboard_common.js
@@ -0,0 +1,505 @@
+// This test is called from both test_clipboard_editor.html and test_clipboard_noeditor.html
+// This is to test that the code works both in the presence of a contentEditable node, and in the absense of one
+var WATCH_TIMEOUT = 300;
+
+// Some global variables to make the debug messages easier to track down
+var gTestN0 = 0,
+ gTestN1 = 0,
+ gTestN2 = 0;
+function testLoc() {
+ return " " + gTestN0 + " - " + gTestN1 + " - " + gTestN2;
+}
+
+// Listen for cut & copy events
+var gCopyCount = 0,
+ gCutCount = 0;
+document.addEventListener("copy", function () {
+ gCopyCount++;
+});
+document.addEventListener("cut", function () {
+ gCutCount++;
+});
+
+// Helper methods
+function selectNode(aSelector, aCb) {
+ var dn = document.querySelector(aSelector);
+ var range = document.createRange();
+ range.selectNodeContents(dn);
+ window.getSelection().removeAllRanges();
+ window.getSelection().addRange(range);
+ if (aCb) {
+ aCb();
+ }
+}
+
+function selectInputNode(aSelector, aCb) {
+ var dn = document.querySelector(aSelector);
+ synthesizeMouse(dn, 10, 10, {});
+ SimpleTest.executeSoon(function () {
+ synthesizeKey("A", { accelKey: true });
+ // Clear the user activation state which is set from synthesized mouse and
+ // key event.
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+ SimpleTest.executeSoon(aCb);
+ });
+}
+
+// Callback functions for attaching to the button
+function execCommand(aCommand, aShouldSucceed, aAsync = false) {
+ var cb = function (e) {
+ e.preventDefault();
+ document.removeEventListener("keydown", cb);
+
+ if (aAsync) {
+ setTimeout(() => {
+ is(
+ aShouldSucceed,
+ document.execCommand(aCommand),
+ "Keydown caused " + aCommand + " invocation" + testLoc()
+ );
+ }, 0);
+ } else {
+ is(
+ aShouldSucceed,
+ document.execCommand(aCommand),
+ "Keydown caused " + aCommand + " invocation" + testLoc()
+ );
+ }
+ };
+ return cb;
+}
+
+// The basic test set. Tries to cut/copy everything
+function cutCopyAll(
+ aDoCut,
+ aDoCopy,
+ aDone,
+ aNegate,
+ aClipOverride,
+ aJustClipboardNegate
+) {
+ var execCommandAlwaysSucceed = !!(aClipOverride || aJustClipboardNegate);
+
+ function waitForClipboard(aCond, aSetup, aNext, aNegateOne) {
+ if (aClipOverride) {
+ aCond = aClipOverride;
+ aNegateOne = false;
+ }
+ if (aNegate || aNegateOne || aJustClipboardNegate) {
+ SimpleTest.waitForClipboard(
+ null,
+ aSetup,
+ aNext,
+ aNext,
+ "text/plain",
+ WATCH_TIMEOUT,
+ true
+ );
+ } else {
+ SimpleTest.waitForClipboard(aCond, aSetup, aNext, aNext);
+ }
+ }
+
+ function validateCutCopy(aExpectedCut, aExpectedCopy) {
+ if (aNegate) {
+ aExpectedCut = aExpectedCopy = 0;
+ } // When we are negating - we always expect callbacks not to be run
+
+ is(
+ aExpectedCut,
+ gCutCount,
+ (aExpectedCut > 0
+ ? "Expect cut callback to run"
+ : "Expect cut callback not to run") + testLoc()
+ );
+ is(
+ aExpectedCopy,
+ gCopyCount,
+ (aExpectedCopy > 0
+ ? "Expect copy callback to run"
+ : "Expect copy callback not to run") + testLoc()
+ );
+ gCutCount = gCopyCount = 0;
+ }
+
+ function step(n) {
+ function nextStep() {
+ step(n + 1);
+ }
+
+ // Reset the user activation state before running next test.
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+
+ document.querySelector("span").textContent = "span text";
+ document.querySelector("input[type=text]").value = "text text";
+ document.querySelector("input[type=password]").value = "password text";
+ document.querySelector("textarea").value = "textarea text";
+
+ var contentEditableNode = document.querySelector(
+ "div[contentEditable=true]"
+ );
+ if (contentEditableNode) {
+ contentEditableNode.textContent = "contenteditable text";
+ }
+
+ gTestN2 = n;
+ switch (n) {
+ case 0:
+ // copy on readonly selection
+ selectNode("span");
+ waitForClipboard(
+ "span text",
+ function () {
+ aDoCopy(true);
+ },
+ nextStep
+ );
+ return;
+
+ case 1:
+ validateCutCopy(0, 1);
+
+ // cut on readonly selection
+ selectNode("span");
+
+ waitForClipboard(
+ "span text",
+ function () {
+ aDoCut(execCommandAlwaysSucceed);
+ },
+ nextStep,
+ true
+ );
+ return;
+
+ case 2:
+ validateCutCopy(1, 0);
+
+ // copy on textbox selection
+ selectInputNode("input[type=text]", nextStep);
+ return;
+
+ case 3:
+ waitForClipboard(
+ "text text",
+ function () {
+ selectInputNode("input[type=text]", function () {
+ aDoCopy(true);
+ });
+ },
+ nextStep
+ );
+ return;
+
+ case 4:
+ validateCutCopy(0, 1);
+
+ // cut on textbox selection
+ selectInputNode("input[type=text]", nextStep);
+ return;
+
+ case 5:
+ waitForClipboard(
+ "text text",
+ function () {
+ aDoCut(true);
+ },
+ nextStep
+ );
+ return;
+
+ case 6:
+ validateCutCopy(1, 0);
+
+ // copy on password selection
+ selectInputNode("input[type=password]", nextStep);
+ return;
+
+ case 7:
+ waitForClipboard(
+ null,
+ function () {
+ aDoCopy(execCommandAlwaysSucceed);
+ },
+ nextStep,
+ true
+ );
+ return;
+
+ case 8:
+ validateCutCopy(0, 1);
+
+ // cut on password selection
+ selectInputNode("input[type=password]", nextStep);
+ return;
+
+ case 9:
+ waitForClipboard(
+ null,
+ function () {
+ aDoCut(execCommandAlwaysSucceed);
+ },
+ nextStep,
+ true
+ );
+ return;
+
+ case 10:
+ validateCutCopy(1, 0);
+
+ // copy on textarea selection
+ selectInputNode("textarea", nextStep);
+ return;
+
+ case 11:
+ waitForClipboard(
+ "textarea text",
+ function () {
+ aDoCopy(true);
+ },
+ nextStep
+ );
+ return;
+
+ case 12:
+ validateCutCopy(0, 1);
+
+ // cut on password selection
+ selectInputNode("textarea", nextStep);
+ return;
+
+ case 13:
+ waitForClipboard(
+ "textarea text",
+ function () {
+ aDoCut(true);
+ },
+ nextStep
+ );
+ return;
+
+ case 14:
+ validateCutCopy(1, 0);
+
+ // copy on no selection
+ document.querySelector("textarea").blur();
+
+ waitForClipboard(
+ null,
+ function () {
+ aDoCopy(true);
+ },
+ nextStep,
+ true
+ );
+ return;
+
+ case 15:
+ validateCutCopy(0, 1);
+
+ // cut on no selection
+ waitForClipboard(
+ null,
+ function () {
+ aDoCut(execCommandAlwaysSucceed);
+ },
+ nextStep,
+ true
+ );
+ return;
+
+ case 16:
+ validateCutCopy(1, 0);
+
+ if (!document.querySelector("div[contentEditable=true]")) {
+ // We're done! (no contentEditable node!)
+ step(-1);
+ return;
+ }
+ break;
+
+ case 17:
+ // copy on contenteditable selection
+ waitForClipboard(
+ "contenteditable text",
+ function () {
+ selectNode("div[contentEditable=true]", function () {
+ aDoCopy(true);
+ });
+ },
+ nextStep
+ );
+ return;
+
+ case 18:
+ validateCutCopy(0, 1);
+ break;
+
+ case 19:
+ // cut on contenteditable selection
+ waitForClipboard(
+ "contenteditable text",
+ function () {
+ selectNode("div[contentEditable=true]", function () {
+ aDoCut(true);
+ });
+ },
+ nextStep
+ );
+ return;
+
+ case 20:
+ validateCutCopy(1, 0);
+ break;
+
+ default:
+ aDone();
+ return;
+ }
+
+ SimpleTest.executeSoon(function () {
+ step(n + 1);
+ });
+ }
+
+ step(0);
+}
+
+function allMechanisms(aCb, aClipOverride, aNegateAll) {
+ function testStep(n) {
+ gTestN1 = n;
+ switch (n) {
+ /** Test for Bug 1012662 **/
+ case 0:
+ // Keyboard issued
+ cutCopyAll(
+ function docut(aSucc) {
+ synthesizeKey("x", { accelKey: true });
+ },
+ function docopy(aSucc) {
+ synthesizeKey("c", { accelKey: true });
+ },
+ function done() {
+ testStep(n + 1);
+ },
+ false,
+ aClipOverride,
+ aNegateAll
+ );
+ return;
+
+ case 1:
+ // Button issued
+ cutCopyAll(
+ function docut(aSucc) {
+ document.addEventListener("keydown", execCommand("cut", aSucc));
+ sendString("Q");
+ },
+ function docopy(aSucc) {
+ document.addEventListener("keydown", execCommand("copy", aSucc));
+ sendString("Q");
+ },
+ function done() {
+ testStep(n + 1);
+ },
+ false,
+ aClipOverride,
+ aNegateAll
+ );
+ return;
+
+ case 2:
+ // Not triggered by user gesture
+ cutCopyAll(
+ function doCut(aSucc) {
+ is(
+ false,
+ document.execCommand("cut"),
+ "Can't directly execCommand not in a user callback"
+ );
+ },
+ function doCopy(aSucc) {
+ is(
+ false,
+ document.execCommand("copy"),
+ "Can't directly execCommand not in a user callback"
+ );
+ },
+ function done() {
+ testStep(n + 1);
+ },
+ true,
+ aClipOverride,
+ aNegateAll
+ );
+ return;
+
+ /** Test for Bug 1597857 **/
+ case 3:
+ // Button issued async
+ cutCopyAll(
+ function docut(aSucc) {
+ document.addEventListener(
+ "keydown",
+ execCommand("cut", aSucc, true)
+ );
+ sendString("Q");
+ },
+ function docopy(aSucc) {
+ document.addEventListener(
+ "keydown",
+ execCommand("copy", aSucc, true)
+ );
+ sendString("Q");
+ },
+ function done() {
+ testStep(n + 1);
+ },
+ false,
+ aClipOverride,
+ aNegateAll
+ );
+ return;
+
+ default:
+ aCb();
+ }
+ }
+ testStep(0);
+}
+
+// Run the tests
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(5); // On the emulator - this times out occasionally
+SimpleTest.waitForFocus(function () {
+ function justCancel(aEvent) {
+ aEvent.preventDefault();
+ }
+
+ function override(aEvent) {
+ aEvent.clipboardData.setData("text/plain", "overridden");
+ aEvent.preventDefault();
+ }
+
+ allMechanisms(function () {
+ gTestN0 = 1;
+ document.addEventListener("cut", override);
+ document.addEventListener("copy", override);
+
+ allMechanisms(function () {
+ gTestN0 = 2;
+ document.removeEventListener("cut", override);
+ document.removeEventListener("copy", override);
+ document.addEventListener("cut", justCancel);
+ document.addEventListener("copy", justCancel);
+
+ allMechanisms(
+ function () {
+ SimpleTest.finish();
+ },
+ null,
+ true
+ );
+ }, "overridden");
+ });
+});
diff --git a/dom/base/test/useractivation/file_empty.html b/dom/base/test/useractivation/file_empty.html
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/dom/base/test/useractivation/file_empty.html
diff --git a/dom/base/test/useractivation/file_iframe_check_user_activation.html b/dom/base/test/useractivation/file_iframe_check_user_activation.html
new file mode 100644
index 0000000000..0a3ec734a6
--- /dev/null
+++ b/dom/base/test/useractivation/file_iframe_check_user_activation.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>user activated iframe</title>
+</head>
+<body>
+<script>
+onload = function() {
+ parent.postMessage("loaded", "*");
+};
+onmessage = function(e) {
+ if (e.data === "get") {
+ parent.postMessage({
+ isActivated: SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ hasBeenActivated: SpecialPowers.wrap(document).hasBeenUserGestureActivated,
+ lastActivationTimestamp: SpecialPowers.wrap(document).lastUserGestureTimeStamp,
+ }, "*");
+ }
+};
+</script>
+</body>
+</html>
diff --git a/dom/base/test/useractivation/file_iframe_consume_user_activation.html b/dom/base/test/useractivation/file_iframe_consume_user_activation.html
new file mode 100644
index 0000000000..ad27453f1a
--- /dev/null
+++ b/dom/base/test/useractivation/file_iframe_consume_user_activation.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>user activated iframe</title>
+</head>
+<body>
+<script>
+onload = function() {
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ SpecialPowers.wrap(document).consumeTransientUserGestureActivation();
+ parent.postMessage("done", "*");
+}
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/base/test/useractivation/file_iframe_user_activated.html b/dom/base/test/useractivation/file_iframe_user_activated.html
new file mode 100644
index 0000000000..8d188001e8
--- /dev/null
+++ b/dom/base/test/useractivation/file_iframe_user_activated.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>user activated iframe</title>
+</head>
+<body>
+<script>
+onload = function() {
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ parent.postMessage("done", "*");
+}
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/base/test/useractivation/file_useractivation_sandbox_transient_popup.html b/dom/base/test/useractivation/file_useractivation_sandbox_transient_popup.html
new file mode 100644
index 0000000000..25bc2037c3
--- /dev/null
+++ b/dom/base/test/useractivation/file_useractivation_sandbox_transient_popup.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>User activation popup</title>
+</head>
+<body>
+<iframe sandbox="allow-top-navigation-by-user-activation allow-scripts allow-same-origin"></iframe>
+<script>
+ window.addEventListener("message", (e) => {
+ if (e.data === "triggerIframeLoad") {
+ // Load into the the iframe a script which notifies the opener of top level navigation or if it was prevented.
+ let script = `window.opener.postMessage("topNavigation");`;
+ frames[0].frameElement.src = `javascript:try{window.top.location='javascript:${encodeURIComponent(script)}';} catch (e) {window.top.opener.postMessage("topNavigationBlocked");}`;
+ } else {
+ window.opener.postMessage("unexpected");
+ }
+ });
+</script>
+</body>
+</html>
diff --git a/dom/base/test/useractivation/mochitest.ini b/dom/base/test/useractivation/mochitest.ini
new file mode 100644
index 0000000000..26c8594be8
--- /dev/null
+++ b/dom/base/test/useractivation/mochitest.ini
@@ -0,0 +1,23 @@
+[DEFAULT]
+support-files =
+ file_empty.html
+ file_iframe_user_activated.html
+ file_iframe_check_user_activation.html
+ file_iframe_consume_user_activation.html
+ file_clipboard_common.js
+prefs =
+ formhelper.autozoom.force-disable.test-only=true
+
+[test_useractivation_has_been_activated.html]
+[test_useractivation_key_events.html]
+[test_useractivation_transient.html]
+[test_useractivation_sandbox_transient.html]
+support-files = file_useractivation_sandbox_transient_popup.html
+[test_useractivation_scrollbar.html]
+skip-if = os == 'android' # scrollbar not showed on mobile
+[test_useractivation_transient_consuming.html]
+[test_clipboard_editor.html]
+[test_clipboard_noeditor.html]
+[test_popup_blocker_mouse_event.html]
+[test_popup_blocker_pointer_event.html]
+[test_popup_blocker_async_callback.html]
diff --git a/dom/base/test/useractivation/moz.build b/dom/base/test/useractivation/moz.build
new file mode 100644
index 0000000000..60b508c774
--- /dev/null
+++ b/dom/base/test/useractivation/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+MOCHITEST_MANIFESTS += [
+ "mochitest.ini",
+]
diff --git a/dom/base/test/useractivation/test_clipboard_editor.html b/dom/base/test/useractivation/test_clipboard_editor.html
new file mode 100644
index 0000000000..691e8e4a20
--- /dev/null
+++ b/dom/base/test/useractivation/test_clipboard_editor.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1012662
+https://bugzilla.mozilla.org/show_bug.cgi?id=1597857
+-->
+<head>
+ <title>Test for Clipboard</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1012662">Mozilla Bug 1012662</a><br>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1597857">Mozilla Bug 1597857</a>
+<p id="display"></p>
+
+<div id="content">
+ <span>span text</span>
+ <input type="text" value="text text">
+ <input type="password" value="password text">
+ <textarea>textarea text</textarea>
+ <div contentEditable="true">contenteditable text</div>
+</div>
+
+<pre id="test">
+<script type="application/javascript" src="file_clipboard_common.js"></script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/useractivation/test_clipboard_noeditor.html b/dom/base/test/useractivation/test_clipboard_noeditor.html
new file mode 100644
index 0000000000..f7f3d6cb99
--- /dev/null
+++ b/dom/base/test/useractivation/test_clipboard_noeditor.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1012662
+-->
+<head>
+ <title>Test for Clipboard</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1012662">Mozilla Bug 1012662</a><br>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1597857">Mozilla Bug 1597857</a>
+<p id="display"></p>
+
+<div id="content">
+ <span>span text</span>
+ <input type="text" value="text text">
+ <input type="password" value="password text">
+ <textarea>textarea text</textarea>
+</div>
+
+<pre id="test">
+<script type="application/javascript" src="file_clipboard_common.js"></script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/useractivation/test_popup_blocker_async_callback.html b/dom/base/test/useractivation/test_popup_blocker_async_callback.html
new file mode 100644
index 0000000000..dc53596531
--- /dev/null
+++ b/dom/base/test/useractivation/test_popup_blocker_async_callback.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for triggering popup by mouse events</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<div id="target" style="width: 50px; height: 50px; background: green"></div>
+<script>
+
+SimpleTest.requestFlakyTimeout("Need to test setTimeout");
+
+function startTest(aTestAsyncFun, aAllowPopup = true) {
+ return new Promise(aResolve => {
+ let target = document.getElementById("target");
+ target.addEventListener("click", (e) => {
+ aTestAsyncFun(() => {
+ let w = window.open("about:blank");
+ is(!!w, aAllowPopup, `Should ${aAllowPopup ? "allow" : "block"} popup`);
+ if (w) {
+ w.close();
+ }
+ aResolve();
+ });
+ }, {once: true});
+ synthesizeMouseAtCenter(target, {type: "mousedown"});
+ synthesizeMouseAtCenter(target, {type: "mouseup"});
+ });
+}
+
+add_setup(async function() {
+ await SpecialPowers.pushPrefEnv({"set": [
+ ["dom.disable_open_during_load", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]});
+});
+
+[
+ // setTimeout
+ function testSetTimout(aCallback) {
+ setTimeout(aCallback, 500);
+ },
+ // fetch
+ function testFetch(aCallback) {
+ fetch("../dummy.html").then(aCallback);
+ },
+ // requestStorageAccess
+ function testRequestStorageAccess(aCallback) {
+ document.requestStorageAccess().then(aCallback);
+ },
+ // serviceWorker.getRegistration
+ function testGetServiceWorkerRegistration(aCallback) {
+ navigator.serviceWorker.getRegistration("/app").then(aCallback);
+ },
+].forEach(testAsyncFun => {
+ add_task(async function() {
+ info(`start ${testAsyncFun.name}`);
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+ await startTest(testAsyncFun);
+ await new Promise(aResolve => SimpleTest.executeSoon(aResolve));
+ });
+});
+
+// Test popup should be blocked if user transient is timeout
+add_task(async function timeout() {
+ info(`start user transient timeout`);
+ await SpecialPowers.pushPrefEnv({"set": [
+ ["dom.user_activation.transient.timeout", 1000],
+ ]});
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+ await startTest((aCallback) => {
+ setTimeout(aCallback, 2000);
+ }, false);
+ await new Promise(aResolve => SimpleTest.executeSoon(aResolve));
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/base/test/useractivation/test_popup_blocker_mouse_event.html b/dom/base/test/useractivation/test_popup_blocker_mouse_event.html
new file mode 100644
index 0000000000..fd94150f1e
--- /dev/null
+++ b/dom/base/test/useractivation/test_popup_blocker_mouse_event.html
@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for triggering popup by mouse events</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<div id="target" style="width: 50px; height: 50px; background: green"></div>
+<script>
+
+function sendMouseEvent(element, eventName, button, listenEventName, handler) {
+ let needToCheckHandler = false;
+ let handlerIsCalled = false;
+ if (listenEventName && handler) {
+ needToCheckHandler = true;
+ element.addEventListener(listenEventName, (e) => {
+ handler(e);
+ handlerIsCalled = true;
+ }, {once: true});
+ }
+ synthesizeMouseAtCenter(element, {type: eventName, button});
+ if (needToCheckHandler) {
+ ok(handlerIsCalled, "Handler should be called");
+ }
+}
+
+function checkAllowOpenPopup(e) {
+ let w = window.open("about:blank");
+ ok(w, `Should allow popup in the ${e.type} listener with button=${e.button}`);
+ if (w) {
+ w.close();
+ }
+}
+
+function checkBlockOpenPopup(e) {
+ let w = window.open("about:blank");
+ ok(!w, `Should block popup in the ${e.type} listener with button=${e.button}`);
+ if (w) {
+ w.close();
+ }
+}
+
+add_setup(async function() {
+ const DENY_ACTION = SpecialPowers.Ci.nsIPermissionManager.DENY_ACTION;
+ let xorigin = SimpleTest.getTestFileURL("").replace(location.hostname, 'mochi.xorigin-test');
+ await SpecialPowers.pushPermissions([
+ {'type': 'popup', 'allow': DENY_ACTION,
+ 'context': document},
+ {'type': 'popup', 'allow': DENY_ACTION,
+ 'context': xorigin}
+ ]);
+
+ await new Promise(resolve => SimpleTest.waitForFocus(resolve));
+});
+
+const LEFT_BUTTON = 0;
+const MIDDLE_BUTTON = 1;
+const RIGHT_BUTTON = 2;
+let target = document.getElementById("target");
+
+add_task(function testMouseDownUpMove() {
+ // Left button
+ sendMouseEvent(target, "mousedown", LEFT_BUTTON, "mousedown", checkAllowOpenPopup);
+ sendMouseEvent(target, "mousemove", LEFT_BUTTON, "mousemove", checkBlockOpenPopup);
+ sendMouseEvent(target, "mouseup", LEFT_BUTTON, "mouseup", checkAllowOpenPopup);
+
+ // Middle button
+ sendMouseEvent(target, "mousedown", MIDDLE_BUTTON, "mousedown", checkAllowOpenPopup);
+ sendMouseEvent(target, "mousemove", MIDDLE_BUTTON, "mousemove", checkBlockOpenPopup);
+ sendMouseEvent(target, "mouseup", MIDDLE_BUTTON, "mouseup", checkAllowOpenPopup);
+
+ // Right button
+ sendMouseEvent(target, "mousedown", RIGHT_BUTTON, "mousedown", checkBlockOpenPopup);
+ sendMouseEvent(target, "mousemove", RIGHT_BUTTON, "mousemove", checkBlockOpenPopup);
+ sendMouseEvent(target, "mouseup", RIGHT_BUTTON, "mouseup", checkBlockOpenPopup);
+});
+
+add_task(function testMouseClick() {
+ // Left button
+ sendMouseEvent(target, "mousedown", LEFT_BUTTON);
+ sendMouseEvent(target, "mouseup", LEFT_BUTTON, "click", checkAllowOpenPopup);
+});
+
+add_task(function testMouseAuxclick() {
+ // Middle button
+ sendMouseEvent(target, "mousedown", MIDDLE_BUTTON);
+ sendMouseEvent(target, "mouseup", MIDDLE_BUTTON, "auxclick", checkAllowOpenPopup);
+
+ // Right button
+ sendMouseEvent(target, "mousedown", RIGHT_BUTTON);
+ sendMouseEvent(target, "mouseup", RIGHT_BUTTON, "auxclick", checkAllowOpenPopup);
+});
+</script>
+</body>
+</html>
diff --git a/dom/base/test/useractivation/test_popup_blocker_pointer_event.html b/dom/base/test/useractivation/test_popup_blocker_pointer_event.html
new file mode 100644
index 0000000000..8d0b2c8cd1
--- /dev/null
+++ b/dom/base/test/useractivation/test_popup_blocker_pointer_event.html
@@ -0,0 +1,122 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for triggering popup by pointer events</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <div id="target" style="width: 50px; height: 50px; background: green"></div>
+ <script>
+
+ function sendMouseEvent(element, eventName, button, listenEventName, handler) {
+ let needToCheckHandler = false;
+ let handlerIsCalled = false;
+ if (listenEventName && handler) {
+ needToCheckHandler = true;
+ element.addEventListener(listenEventName, (e) => {
+ handler(e);
+ handlerIsCalled = true;
+ }, {once: true});
+ }
+ synthesizeMouseAtCenter(element, {type: eventName, button});
+ if (needToCheckHandler) {
+ ok(handlerIsCalled, "Handler should be called");
+ }
+ }
+
+ function checkAllowOpenPopup(e) {
+ let w = window.open("about:blank");
+ ok(w, `Should allow popup in the ${e.type} listener with button=${e.button}`);
+ if (w) {
+ w.close();
+ }
+ }
+
+ function checkBlockOpenPopup(e) {
+ let w = window.open("about:blank");
+ ok(!w, `Should block popup in the ${e.type} listener with button=${e.button}`);
+ if (w) {
+ w.close();
+ }
+ }
+
+ add_setup(async function() {
+ const DENY_ACTION = SpecialPowers.Ci.nsIPermissionManager.DENY_ACTION;
+ let xorigin = SimpleTest.getTestFileURL("").replace(location.hostname, 'mochi.xorigin-test');
+ await SpecialPowers.pushPermissions([
+ {'type': 'popup', 'allow': DENY_ACTION,
+ 'context': document},
+ {'type': 'popup', 'allow': DENY_ACTION,
+ 'context': xorigin}
+ ]);
+ await new Promise(resolve => SimpleTest.waitForFocus(resolve));
+ });
+
+ const LEFT_BUTTON = 0;
+ const MIDDLE_BUTTON = 1;
+ const RIGHT_BUTTON = 2;
+ let target = document.getElementById("target");
+
+ add_task(function testPointerEventDefault() {
+ // By default, only allow opening popup in the pointerup listener.
+ // Left button
+ sendMouseEvent(target, "mousedown", LEFT_BUTTON, "pointerdown", checkAllowOpenPopup);
+ sendMouseEvent(target, "mousemove", LEFT_BUTTON, "pointermove", checkBlockOpenPopup);
+ sendMouseEvent(target, "mouseup", LEFT_BUTTON, "pointerup", checkAllowOpenPopup);
+
+ // Middle button
+ sendMouseEvent(target, "mousedown", MIDDLE_BUTTON, "pointerdown", checkAllowOpenPopup);
+ sendMouseEvent(target, "mousemove", MIDDLE_BUTTON, "pointermove", checkBlockOpenPopup);
+ sendMouseEvent(target, "mouseup", MIDDLE_BUTTON, "pointerup", checkAllowOpenPopup);
+
+ // Right button
+ sendMouseEvent(target, "mousedown", RIGHT_BUTTON, "pointerdown", checkBlockOpenPopup);
+ sendMouseEvent(target, "mousemove", RIGHT_BUTTON, "pointermove", checkBlockOpenPopup);
+ sendMouseEvent(target, "mouseup", RIGHT_BUTTON, "pointerup", checkBlockOpenPopup);
+ });
+
+ add_task(async function testPointerEventAddPointerDownToPref() {
+ // Adding pointerdown to preference
+ await SpecialPowers.pushPrefEnv({"set": [["dom.popup_allowed_events",
+ "pointerdown pointerup"]]});
+ // Left button
+ sendMouseEvent(target, "mousedown", LEFT_BUTTON, "pointerdown", checkAllowOpenPopup);
+ sendMouseEvent(target, "mousemove", LEFT_BUTTON, "pointermove", checkBlockOpenPopup);
+ sendMouseEvent(target, "mouseup", LEFT_BUTTON, "pointerup", checkAllowOpenPopup);
+
+ // Middle button
+ sendMouseEvent(target, "mousedown", MIDDLE_BUTTON, "pointerdown", checkAllowOpenPopup);
+ sendMouseEvent(target, "mousemove", MIDDLE_BUTTON, "pointermove", checkBlockOpenPopup);
+ sendMouseEvent(target, "mouseup", MIDDLE_BUTTON, "pointerup", checkAllowOpenPopup);
+
+ // Right button
+ sendMouseEvent(target, "mousedown", RIGHT_BUTTON, "pointerdown", checkBlockOpenPopup);
+ sendMouseEvent(target, "mousemove", RIGHT_BUTTON, "pointermove", checkBlockOpenPopup);
+ sendMouseEvent(target, "mouseup", RIGHT_BUTTON, "pointerup", checkBlockOpenPopup);
+ });
+
+ add_task(async function testPointerEventAddPointerMoveToPref() {
+ // Adding pointermove to preference should have no effect.
+ await SpecialPowers.pushPrefEnv({"set": [["dom.popup_allowed_events",
+ "pointerdown pointerup pointermove"]]});
+ // Left button
+ sendMouseEvent(target, "mousedown", LEFT_BUTTON, "pointerdown", checkAllowOpenPopup);
+ sendMouseEvent(target, "mousemove", LEFT_BUTTON, "pointermove", checkBlockOpenPopup);
+ sendMouseEvent(target, "mouseup", LEFT_BUTTON, "pointerup", checkAllowOpenPopup);
+
+ // Middle button
+ sendMouseEvent(target, "mousedown", MIDDLE_BUTTON, "pointerdown", checkAllowOpenPopup);
+ sendMouseEvent(target, "mousemove", MIDDLE_BUTTON, "pointermove", checkBlockOpenPopup);
+ sendMouseEvent(target, "mouseup", MIDDLE_BUTTON, "pointerup", checkAllowOpenPopup);
+
+ // Right button
+ sendMouseEvent(target, "mousedown", RIGHT_BUTTON, "pointerdown", checkBlockOpenPopup);
+ sendMouseEvent(target, "mousemove", RIGHT_BUTTON, "pointermove", checkBlockOpenPopup);
+ sendMouseEvent(target, "mouseup", RIGHT_BUTTON, "pointerup", checkBlockOpenPopup);
+ });
+ </script>
+</body>
+</html>
diff --git a/dom/base/test/useractivation/test_useractivation_has_been_activated.html b/dom/base/test/useractivation/test_useractivation_has_been_activated.html
new file mode 100644
index 0000000000..f46618915b
--- /dev/null
+++ b/dom/base/test/useractivation/test_useractivation_has_been_activated.html
@@ -0,0 +1,115 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>User activation test: has been user gesture activated</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<iframe></iframe>
+<script>
+
+function waitForEvent(aTarget, aEvent, aCallback) {
+ return new Promise((aResolve) => {
+ aTarget.addEventListener(aEvent, function listener(event) {
+ aCallback(event);
+ aResolve();
+ }, { once: true });
+ });
+}
+
+let [iframe0, iframe1] = document.querySelectorAll("iframe");
+
+function doCheck(aDocument, aName, aHasBeenUserGestureActivated) {
+ is(SpecialPowers.wrap(aDocument).hasBeenUserGestureActivated,
+ aHasBeenUserGestureActivated,
+ `check has-been-user-activated on the ${aName}`);
+ if (aHasBeenUserGestureActivated) {
+ ok(SpecialPowers.wrap(aDocument).lastUserGestureTimeStamp > 0,
+ `check last-user-gesture-timestamp on the ${aName}`);
+ } else {
+ is(SpecialPowers.wrap(aDocument).lastUserGestureTimeStamp, 0,
+ `check last-user-gesture-timestamp on the ${aName}`);
+ }
+}
+
+add_task(async function checkInitialStatus() {
+ doCheck(document, "top-level document", false);
+ doCheck(frames[0].document, "first iframe", false);
+ doCheck(frames[1].document, "second iframe", false);
+});
+
+add_task(async function triggerUserActivation() {
+ // Trigger user activation on the first iframe.
+ SpecialPowers.wrap(frames[0].document).notifyUserGestureActivation();
+ // We should also propagate to all the ancestors.
+ doCheck(document, "top-level document", true);
+ doCheck(frames[0].document, "first iframe", true);
+ doCheck(frames[1].document, "second iframe", false);
+});
+
+add_task(async function iframeNavigation() {
+ frames[0].frameElement.src = "file_empty.html";
+ await waitForEvent(frames[0].frameElement, "load", () => {});
+ // We should reset the flag on iframe that navigates away from current page,
+ // but the flag on its ancestor isn't changed.
+ doCheck(document, "top-level document", true);
+ doCheck(frames[0].document, "first iframe", false);
+ doCheck(frames[1].document, "second iframe", false);
+});
+
+add_task(async function triggerUserActivationOnCrossOriginFrame() {
+ // Reset the activation flag.
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+ doCheck(document, "top-level document", false);
+
+ // load cross-origin test page on iframe.
+ frames[0].frameElement.src = "https://example.com/tests/dom/base/test/useractivation/file_iframe_user_activated.html";
+ await waitForEvent(window, "message", (event) => {
+ if (event.data === "done") {
+ doCheck(document, "top-level document", true);
+ doCheck(frames[1].document, "second iframe", false);
+ } else {
+ ok(false, "receive unexpected message: " + event.data);
+ }
+ });
+});
+
+add_task(async function propagateToSameOriginConnectedSubframe() {
+ // Reset the activation flag.
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+
+ // load cross-origin test page on iframe.
+ iframe0.src = "https://example.com/tests/dom/base/test/useractivation/file_iframe_check_user_activation.html";
+ await waitForEvent(window, "message", (event) => {
+ if (event.data !== "loaded") {
+ ok(false, "receive unexpected message: " + event.data);
+ }
+ });
+
+ // Trigger user activation on top-level document.
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ doCheck(document, "top-level document", true);
+ doCheck(iframe1.contentDocument, "second iframe", true);
+
+ iframe0.contentWindow.postMessage("get", "*");
+ await waitForEvent(window, "message", (event) => {
+ if (typeof event.data === "object") {
+ ok(!event.data.hasBeenActivated, "check has-been-user-activated on the first iframe");
+ is(event.data.lastActivationTimestamp, 0, "check last-user-gesture-timestamp on the first iframe");
+ } else {
+ ok(false, "receive unexpected message: " + event.data);
+ }
+ });
+});
+
+add_task(async function endTests() {
+ // Reset the activation flag in order not to interfere following test in the
+ // verify mode which would run the test using same document couple times.
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+});
+
+</script>
+</body>
diff --git a/dom/base/test/useractivation/test_useractivation_key_events.html b/dom/base/test/useractivation/test_useractivation_key_events.html
new file mode 100644
index 0000000000..d97906c22b
--- /dev/null
+++ b/dom/base/test/useractivation/test_useractivation_key_events.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>User activation test: key events</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script>
+
+function synthesizeKeyAndWait(aKey, aEvent) {
+ let promise = new Promise(aResolve => {
+ document.addEventListener("keydown", function(e) {
+ e.preventDefault();
+ aResolve();
+ }, { once: true });
+ });
+ synthesizeKey(aKey, aEvent, window);
+ return promise;
+}
+
+add_task(async function TestPrintableKey() {
+ let tests = [ 'a', 'b', 'c', 'A', 'B', '1', '2', '3' ];
+
+ for (let key of tests) {
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+ await synthesizeKeyAndWait(key, {});
+ ok(SpecialPowers.wrap(document).hasBeenUserGestureActivated,
+ `check has-been-user-activated for ${key}`);
+ ok(SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ `check has-valid-transient-user-activation for ${key}`);
+ }
+});
+
+add_task(async function TestNonPrintableKey() {
+ let tests = [ [ "KEY_Alt", false],
+ [ "KEY_Backspace", false],
+ [ "KEY_Escape" , false ],
+ [ "KEY_Tab" , false ],
+ // Treat as user input
+ [ "KEY_Enter", true],
+ [ " ", true] ];
+
+ for (let [key, expectedResult] of tests) {
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+ await synthesizeKeyAndWait(key, {});
+ is(SpecialPowers.wrap(document).hasBeenUserGestureActivated, expectedResult,
+ `check has-been-user-activated for "${key}"`);
+ is(SpecialPowers.wrap(document).hasValidTransientUserGestureActivation, expectedResult,
+ `check has-valid-transient-user-activation for "${key}"`);
+ }
+});
+
+add_task(async function TestModifier() {
+ let tests = [ [ 'a', { altKey: true }, false],
+ [ 'a', { ctrlKey: true }, false],
+ [ 'a', { metaKey: true }, false],
+ [ 'a', { osKey: true }, false],
+ [ 'c', { altKey: true }, false ],
+ [ 'c', { osKey: true }, false ],
+ [ 'v', { altKey: true }, false ],
+ [ 'v', { osKey: true }, false ],
+ [ 'x', { altKey: true }, false ],
+ [ 'x', { osKey: true }, false ],
+ // Treat as user input
+ [ 'a', { altGraphKey: true }, true ],
+ [ 'a', { fnKey: true }, true ],
+ [ 'a', { shiftKey: true }, true ],
+ [ 'c', { accelKey: true }, true ],
+ [ 'v', { accelKey: true }, true ],
+ [ 'x', { accelKey: true }, true ] ];
+
+ for (let [key, event, expectedResult] of tests) {
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+ await synthesizeKeyAndWait(key, event);
+ is(SpecialPowers.wrap(document).hasBeenUserGestureActivated, expectedResult,
+ `check has-been-user-activated for ${key} with ${JSON.stringify(event)}`);
+ is(SpecialPowers.wrap(document).hasValidTransientUserGestureActivation, expectedResult,
+ `check has-valid-transient-user-activation for ${key} with ${JSON.stringify(event)}`);
+ }
+});
+
+add_task(async function endTests() {
+ // Reset the activation flag in order not to interfere following test in the
+ // verify mode which would run the test using same document couple times.
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+});
+
+</script>
+</body>
diff --git a/dom/base/test/useractivation/test_useractivation_sandbox_transient.html b/dom/base/test/useractivation/test_useractivation_sandbox_transient.html
new file mode 100644
index 0000000000..6b0fcb50f0
--- /dev/null
+++ b/dom/base/test/useractivation/test_useractivation_sandbox_transient.html
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>User activation test: transient flag</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script>
+SimpleTest.requestFlakyTimeout("Timeouts are needed to test transient user_activation");
+let timeout = SpecialPowers.getIntPref("dom.user_activation.transient.timeout") + 1000;
+
+function waitForEvent(aTarget, aEvent, aCallback) {
+ return new Promise((aResolve) => {
+ aTarget.addEventListener(aEvent, function listener(event) {
+ aCallback(event);
+ aResolve();
+ }, { once: true });
+ });
+}
+
+function checkPopupActivated(popup, activated) {
+ let state = activated ? "valid" : "invalid";
+ ok(activated === SpecialPowers.wrap(popup.document).hasValidTransientUserGestureActivation,
+ `check has-${state}-transient-user-activation on top-level document`);
+ ok(activated === SpecialPowers.wrap(popup.frames[0].document).hasValidTransientUserGestureActivation,
+ `check has-${state}-transient-user-activation on iframe`);
+}
+
+add_task(async function triggerUserActivation() {
+ let message = waitForEvent(window, "message", (e) => {
+ if (e.data === "topNavigation") {
+ ok(true, "Top navigation changed");
+ } else {
+ ok(false, "Unexpected message");
+ }
+ });
+ let popup = window.open("./file_useractivation_sandbox_transient_popup.html", "_blank");
+ await new Promise((r) => {
+ popup.addEventListener("load", r);
+ });
+ checkPopupActivated(popup, false);
+ // Create user gesture into iframe within popup just opened
+ SpecialPowers.wrap(popup.frames[0].frameElement.contentDocument).notifyUserGestureActivation();
+ checkPopupActivated(popup, true);
+ // Notify popup to load into the frame
+ popup.postMessage("triggerIframeLoad");
+ await message;
+ popup.close();
+});
+
+add_task(async function triggerUserActivationTimeout() {
+ let message = waitForEvent(window, "message", (e) => {
+ // Top navigation MUST be blocked
+ if (e.data === "topNavigationBlocked") {
+ ok(true, "Top navigation blocked");
+ } else {
+ ok(false, "Unexpected message");
+ }
+ });
+ let popup = window.open("./file_useractivation_sandbox_transient_popup.html", "_blank");
+ await new Promise((r) => {
+ popup.addEventListener("load", r);
+ });
+ checkPopupActivated(popup, false);
+ // Create user gesture into iframe within popup just opened
+ SpecialPowers.wrap(popup.frames[0].frameElement.contentDocument).notifyUserGestureActivation();
+ checkPopupActivated(popup, true);
+ // Ensure we timeout user gesture
+ await new Promise((aResolve) => {
+ setTimeout(() => {
+ checkPopupActivated(popup, false);
+ aResolve();
+ }, timeout);
+ });
+ // Notify popup to load into the frame
+ popup.postMessage("triggerIframeLoad");
+ await message;
+ popup.close();
+});
+
+add_task(async function endTests() {
+ // Reset the activation flag in order not to interfere following test in the
+ // verify mode which would run the test using same document couple times.
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+});
+
+</script>
+</body>
diff --git a/dom/base/test/useractivation/test_useractivation_scrollbar.html b/dom/base/test/useractivation/test_useractivation_scrollbar.html
new file mode 100644
index 0000000000..778d6d0898
--- /dev/null
+++ b/dom/base/test/useractivation/test_useractivation_scrollbar.html
@@ -0,0 +1,135 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>User activation test: consume transient flag</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<textarea style="height: 100px; resize: none">
+&nbsp
+&nbsp
+&nbsp
+&nbsp
+&nbsp
+&nbsp
+&nbsp
+&nbsp
+&nbsp
+&nbsp
+&nbsp
+&nbsp
+&nbsp
+</textarea>
+<div id="target" style="height: 100px; width: 200px; overflow: scroll">
+&nbsp<br>
+&nbsp<br>
+&nbsp<br>
+&nbsp<br>
+&nbsp<br>
+&nbsp<br>
+&nbsp<br>
+&nbsp<br>
+&nbsp<br>
+&nbsp<br>
+&nbsp<br>
+&nbsp<br>
+&nbsp<br>
+</div>
+<div style="height: 2000px; width: 500px; background-color: green;"></div>
+<script>
+
+// Open a new window to ensure scrollbar is always visible.
+if (!opener) {
+ add_task(async function init() {
+ // Turn on the prefs that force overlay scrollbars to always be visible.
+ await SpecialPowers.pushPrefEnv({
+ set: [["layout.testing.overlay-scrollbars.always-visible", true]],
+ });
+ });
+
+ async function testOnNewWindow(aPrefValue) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.user_activation.ignore_scrollbars", aPrefValue]],
+ });
+
+ let win = window.open(location);
+ // wait for message
+ await new Promise((aResolve) => {
+ window.addEventListener("message", function listener(event) {
+ if ("done" == event.data) {
+ window.removeEventListener("message", listener);
+ aResolve();
+ }
+ });
+ });
+ win.close();
+ }
+
+ add_task(async function test_pref_on() {
+ await testOnNewWindow(true);
+ });
+
+ add_task(async function test_pref_off() {
+ await testOnNewWindow(false);
+ });
+} else {
+ SimpleTest.waitForFocus(async function() {
+ function waitForEvent(aTarget, aEvent) {
+ return new Promise((aResolve) => {
+ aTarget.addEventListener(aEvent, function listener(event) {
+ aResolve();
+ }, { once: true });
+ });
+ }
+
+ let ignoreScrollbars = SpecialPowers.getBoolPref("dom.user_activation.ignore_scrollbars");
+
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+ let textarea = document.querySelector("textarea");
+ var rect = textarea.getBoundingClientRect();
+
+ // click scrollbar of textarea
+ let promise = waitForEvent(textarea, "scroll");
+ synthesizeMouse(textarea, rect.width - 5, rect.height / 2, {});
+ await promise;
+
+ opener.is(SpecialPowers.wrap(document).hasBeenUserGestureActivated,
+ !ignoreScrollbars, "check has-been-user-activated");
+ opener.is(SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ !ignoreScrollbars, "check has-valid-transient-user-activation");
+
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+ let div = document.querySelector("div[id=target]");
+ rect = div.getBoundingClientRect();
+
+ // click scrollbar of div
+ promise = waitForEvent(div, "scroll");
+ synthesizeMouse(div, rect.width - 5, rect.height / 2, {});
+ await promise;
+
+ opener.is(SpecialPowers.wrap(document).hasBeenUserGestureActivated,
+ !ignoreScrollbars, "check has-been-user-activated");
+ opener.is(SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ !ignoreScrollbars, "check has-valid-transient-user-activation");
+
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+ let body = document.querySelector("body");
+
+ // click scrollbar of page
+ promise = waitForEvent(document, "scroll");
+ synthesizeMouse(body, innerWidth - 10, innerHeight / 2, {});
+ await promise;
+
+ opener.is(SpecialPowers.wrap(document).hasBeenUserGestureActivated,
+ !ignoreScrollbars, "check has-been-user-activated");
+ opener.is(SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ !ignoreScrollbars, "check has-valid-transient-user-activation");
+
+ opener.postMessage("done", "*");
+ }, window);
+}
+
+</script>
+</body>
diff --git a/dom/base/test/useractivation/test_useractivation_transient.html b/dom/base/test/useractivation/test_useractivation_transient.html
new file mode 100644
index 0000000000..d3148e5d1d
--- /dev/null
+++ b/dom/base/test/useractivation/test_useractivation_transient.html
@@ -0,0 +1,155 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>User activation test: transient flag</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<iframe></iframe>
+<script>
+
+SimpleTest.requestFlakyTimeout("Timeouts are needed to test transient user_activation");
+
+let timeout = SpecialPowers.getIntPref("dom.user_activation.transient.timeout") + 1000;
+let [iframe0, iframe1] = document.querySelectorAll("iframe");
+
+function waitForEvent(aTarget, aEvent, aCallback) {
+ return new Promise((aResolve) => {
+ aTarget.addEventListener(aEvent, function listener(event) {
+ aCallback(event);
+ aResolve();
+ }, { once: true });
+ });
+}
+
+add_task(async function checkInitialStatus() {
+ ok(!SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on top-level document");
+ ok(!SpecialPowers.wrap(frames[0].document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on first iframe");
+ ok(!SpecialPowers.wrap(frames[1].document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on second iframe");
+});
+
+add_task(async function triggerUserActivation() {
+ // Trigger user activation on the first iframe.
+ SpecialPowers.wrap(frames[0].document).notifyUserGestureActivation();
+
+ // We should also propagate to all the ancestors.
+ ok(SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the top-level document");
+ ok(SpecialPowers.wrap(frames[0].document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the first iframe");
+ ok(!SpecialPowers.wrap(frames[1].document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the second iframe");
+});
+
+add_task(async function iframeNavigation() {
+ frames[0].frameElement.src = "file_empty.html";
+ await waitForEvent(frames[0].frameElement, "load", () => {});
+ // We should reset the flag on iframe that navigates away from current page,
+ // but the flag on its ancestor isn't changed.
+ ok(SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the top-level document");
+ ok(!SpecialPowers.wrap(frames[0].document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the first iframe");
+ ok(!SpecialPowers.wrap(frames[1].document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the second iframe");
+});
+
+add_task(async function triggerUserActivationTimeout() {
+ // Trigger user activation on the first iframe.
+ SpecialPowers.wrap(frames[0].document).notifyUserGestureActivation();
+
+ // hasValidTransientUserGestureActivation should return false after timeout.
+ await new Promise((aResolve) => {
+ setTimeout(() => {
+ ok(!SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the top-level document");
+ ok(!SpecialPowers.wrap(frames[0].document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the first iframe");
+ ok(!SpecialPowers.wrap(frames[1].document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the second iframe");
+ aResolve();
+ }, timeout);
+ });
+
+ // Trigger user activation again.
+ SpecialPowers.wrap(frames[0].document).notifyUserGestureActivation();
+ ok(SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the top-level document");
+ ok(SpecialPowers.wrap(frames[0].document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the first iframe");
+ ok(!SpecialPowers.wrap(frames[1].document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the second iframe");
+});
+
+add_task(async function triggerUserActivationOnCrossOriginFrame() {
+ // Reset the activation flag.
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+
+ // load cross-origin test page on iframe.
+ frames[0].frameElement.src = "https://example.com/tests/dom/base/test/useractivation/file_iframe_user_activated.html";
+ await waitForEvent(window, "message", (event) => {
+ if (event.data === "done") {
+ ok(SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the top-level document");
+ ok(!SpecialPowers.wrap(frames[1].document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the second iframe");
+ } else {
+ ok(false, "receive unexpected message: " + event.data);
+ }
+ });
+
+ // hasValidTransientUserGestureActivation should return false after timeout.
+ await new Promise((aResolve) => {
+ setTimeout(() => {
+ ok(!SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the top-level document");
+ ok(!SpecialPowers.wrap(frames[1].document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the second iframe");
+ aResolve();
+ }, timeout);
+ });
+});
+
+add_task(async function propagateToSameOriginConnectedSubframe() {
+ // Reset the activation flag.
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+
+ // load cross-origin test page on iframe.
+ iframe0.src = "https://example.com/tests/dom/base/test/useractivation/file_iframe_check_user_activation.html";
+ await waitForEvent(window, "message", (event) => {
+ if (event.data !== "loaded") {
+ ok(false, "receive unexpected message: " + event.data);
+ }
+ });
+
+ // Trigger user activation on top-level document.
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ ok(SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the top-level document");
+ ok(SpecialPowers.wrap(iframe1.contentDocument).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the second iframe");
+
+ iframe0.contentWindow.postMessage("get", "*");
+ await waitForEvent(window, "message", (event) => {
+ if (typeof event.data === "object") {
+ ok(!event.data.isActivated, "check has-valid-transient-user-activation on the first iframe");
+ } else {
+ ok(false, "receive unexpected message: " + event.data);
+ }
+ });
+});
+
+add_task(async function endTests() {
+ // Reset the activation flag in order not to interfere following test in the
+ // verify mode which would run the test using same document couple times.
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+});
+
+</script>
+</body>
diff --git a/dom/base/test/useractivation/test_useractivation_transient_consuming.html b/dom/base/test/useractivation/test_useractivation_transient_consuming.html
new file mode 100644
index 0000000000..4178d24477
--- /dev/null
+++ b/dom/base/test/useractivation/test_useractivation_transient_consuming.html
@@ -0,0 +1,151 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>User activation test: consume transient flag</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe></iframe>
+<iframe></iframe>
+<script>
+
+SimpleTest.requestFlakyTimeout("Timeouts are needed to test transient user_activation");
+
+let timeout = SpecialPowers.getIntPref("dom.user_activation.transient.timeout") + 1000;
+
+function waitForEvent(aTarget, aEvent, aCallback) {
+ return new Promise((aResolve) => {
+ aTarget.addEventListener(aEvent, function listener(event) {
+ aCallback(event);
+ aResolve();
+ }, { once: true });
+ });
+}
+
+function doCheck(aDocument, aName, aHasBeenUserGestureActivated,
+ aHasValidTransientUserGestureActivation,
+ aLastUserGestureTimeStamp) {
+ is(SpecialPowers.wrap(aDocument).hasBeenUserGestureActivated,
+ aHasBeenUserGestureActivated,
+ `check has-been-user-activated on the ${aName}`);
+ is(SpecialPowers.wrap(aDocument).hasValidTransientUserGestureActivation,
+ aHasValidTransientUserGestureActivation,
+ `check has-valid-transient-user-activation on the ${aName}`);
+ is(SpecialPowers.wrap(aDocument).lastUserGestureTimeStamp,
+ aLastUserGestureTimeStamp,
+ `check last-user-gesture-timestamp on the ${aName}`);
+}
+
+add_task(async function checkInitialStatus() {
+ doCheck(document, "top-level document", false, false, 0);
+ ok(!SpecialPowers.wrap(document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on top-level document");
+
+ doCheck(frames[0].document, "first iframe", false, false, 0);
+ ok(!SpecialPowers.wrap(frames[0].document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on first iframe");
+
+ doCheck(frames[1].document, "second iframe", false, false, 0);
+ ok(!SpecialPowers.wrap(frames[1].document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on second iframe");
+});
+
+add_task(async function consumeTransientUserActivation() {
+ // Trigger user activation on the first iframe.
+ SpecialPowers.wrap(frames[0].document).notifyUserGestureActivation();
+ let lastTimeStampTop = SpecialPowers.wrap(document).lastUserGestureTimeStamp;
+ let lastTimeStampFirst = SpecialPowers.wrap(frames[0].document).lastUserGestureTimeStamp;
+
+ // Try to consume transient user activation.
+ ok(!SpecialPowers.wrap(frames[1].document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on second iframe");
+ ok(SpecialPowers.wrap(frames[0].document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on first iframe");
+ // Consuming a transient-user-activation should affect all tree.
+ ok(!SpecialPowers.wrap(document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on top-level document");
+
+ // Check has-valid-transient-user-activation and should not affect
+ // has-been-user-activated
+ doCheck(document, "top-level document", true, false, lastTimeStampTop);
+ doCheck(frames[0].document, "first iframe", true, false, lastTimeStampFirst);
+ doCheck(frames[1].document, "second iframe", false, false, 0);
+});
+
+add_task(async function consumeTransientUserActivationTimeout() {
+ // Trigger user activation on the first iframe.
+ SpecialPowers.wrap(frames[0].document).notifyUserGestureActivation();
+
+ // Should not able to consume successfully after timeout.
+ await new Promise((aResolve) => {
+ setTimeout(() => {
+ ok(!SpecialPowers.wrap(document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on top-level document");
+ ok(!SpecialPowers.wrap(frames[0].document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on first iframe");
+ ok(!SpecialPowers.wrap(frames[1].document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on second iframe");
+ aResolve();
+ }, timeout);
+ });
+
+ // Trigger user activation again.
+ SpecialPowers.wrap(frames[0].document).notifyUserGestureActivation();
+
+ // Try to consume transient user activation.
+ ok(!SpecialPowers.wrap(frames[1].document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on second iframe");
+ ok(SpecialPowers.wrap(frames[0].document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on first iframe");
+ // Consuming a transient-user-activation should affect all tree.
+ ok(!SpecialPowers.wrap(document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on top-level document");
+});
+
+add_task(async function iframeNavigation() {
+ // Trigger user activation on the first iframe.
+ SpecialPowers.wrap(frames[0].document).notifyUserGestureActivation();
+
+ // Navigate away from current page.
+ frames[0].frameElement.src = "file_empty.html";
+ await waitForEvent(frames[0].frameElement, "load", () => {});
+
+ // Try to consume transient user activation.
+ ok(!SpecialPowers.wrap(frames[1].document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on second iframe");
+ ok(!SpecialPowers.wrap(frames[0].document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on first iframe");
+ ok(SpecialPowers.wrap(document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on top-level document");
+});
+
+add_task(async function triggerUserActivationOnCrossOriginFrame() {
+ // Reset the activation flag.
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+
+ // load cross-origin test page on iframe.
+ frames[0].frameElement.src = "https://example.com/tests/dom/base/test/useractivation/file_iframe_consume_user_activation.html";
+ await waitForEvent(window, "message", (event) => {
+ if (event.data === "done") {
+ ok(!SpecialPowers.wrap(document).consumeTransientUserGestureActivation(),
+ "consume transient-user-activation on top-level document");
+ ok(!SpecialPowers.wrap(document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the top-level document");
+ ok(!SpecialPowers.wrap(frames[1].document).hasValidTransientUserGestureActivation,
+ "check has-valid-transient-user-activation on the second iframe");
+ } else {
+ ok(false, "receive unexpected message: " + event.data);
+ }
+ });
+});
+
+add_task(async function endTests() {
+ // Reset the activation flag in order not to interfere following test in the
+ // verify mode which would run the test using same document couple times.
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+});
+
+</script>
+</body>