diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /dom/base/test/useractivation | |
parent | Initial commit. (diff) | |
download | thunderbird-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')
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"> +  +  +  +  +  +  +  +  +  +  +  +  +  +</textarea> +<div id="target" style="height: 100px; width: 200px; overflow: scroll"> + <br> + <br> + <br> + <br> + <br> + <br> + <br> + <br> + <br> + <br> + <br> + <br> + <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> |