From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- dom/events/test/clipboard/browser.ini | 21 +++ .../browser_navigator_clipboard_clickjacking.js | 69 +++++++ .../clipboard/browser_navigator_clipboard_read.js | 198 ++++++++++++++++++++ .../browser_navigator_clipboard_readText.js | 201 +++++++++++++++++++++ .../clipboard/browser_navigator_clipboard_touch.js | 114 ++++++++++++ dom/events/test/clipboard/chrome.ini | 3 + dom/events/test/clipboard/head.js | 169 +++++++++++++++++ .../simple_navigator_clipboard_keydown.html | 15 ++ .../clipboard/simple_navigator_clipboard_read.html | 65 +++++++ .../simple_navigator_clipboard_readText.html | 47 +++++ .../test/clipboard/test_async_clipboard.xhtml | 130 +++++++++++++ 11 files changed, 1032 insertions(+) create mode 100644 dom/events/test/clipboard/browser.ini create mode 100644 dom/events/test/clipboard/browser_navigator_clipboard_clickjacking.js create mode 100644 dom/events/test/clipboard/browser_navigator_clipboard_read.js create mode 100644 dom/events/test/clipboard/browser_navigator_clipboard_readText.js create mode 100644 dom/events/test/clipboard/browser_navigator_clipboard_touch.js create mode 100644 dom/events/test/clipboard/chrome.ini create mode 100644 dom/events/test/clipboard/head.js create mode 100644 dom/events/test/clipboard/simple_navigator_clipboard_keydown.html create mode 100644 dom/events/test/clipboard/simple_navigator_clipboard_read.html create mode 100644 dom/events/test/clipboard/simple_navigator_clipboard_readText.html create mode 100644 dom/events/test/clipboard/test_async_clipboard.xhtml (limited to 'dom/events/test/clipboard') diff --git a/dom/events/test/clipboard/browser.ini b/dom/events/test/clipboard/browser.ini new file mode 100644 index 0000000000..eb452c1443 --- /dev/null +++ b/dom/events/test/clipboard/browser.ini @@ -0,0 +1,21 @@ +[DEFAULT] +support-files = + head.js + !/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js + !/gfx/layers/apz/test/mochitest/apz_test_utils.js + +[browser_navigator_clipboard_readText.js] +support-files = + simple_navigator_clipboard_readText.html +[browser_navigator_clipboard_read.js] +support-files = + simple_navigator_clipboard_read.html +[browser_navigator_clipboard_touch.js] +support-files = + simple_navigator_clipboard_readText.html +[browser_navigator_clipboard_clickjacking.js] +skip-if = + os == "win" # The popupmenus dismiss when access keys for disabled items are pressed on windows + os == "mac" && verify +support-files = + simple_navigator_clipboard_keydown.html diff --git a/dom/events/test/clipboard/browser_navigator_clipboard_clickjacking.js b/dom/events/test/clipboard/browser_navigator_clipboard_clickjacking.js new file mode 100644 index 0000000000..cd3e97f274 --- /dev/null +++ b/dom/events/test/clipboard/browser_navigator_clipboard_clickjacking.js @@ -0,0 +1,69 @@ +/* -*- Mode: JavaScript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +"use strict"; + +const kBaseUrlForContent = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" +); + +const kContentFileName = "simple_navigator_clipboard_keydown.html"; + +const kContentFileUrl = kBaseUrlForContent + kContentFileName; + +const kApzTestNativeEventUtilsUrl = + "chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js"; + +Services.scriptloader.loadSubScript(kApzTestNativeEventUtilsUrl, this); + +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [["dom.events.asyncClipboard.readText", true]], + }); +}); + +add_task(async function test_paste_button_clickjacking() { + await BrowserTestUtils.withNewTab(kContentFileUrl, async function (browser) { + const pasteButtonIsShown = promisePasteButtonIsShown(); + + // synthesize key to trigger readText() to bring up paste popup. + EventUtils.synthesizeKey("p", {}, window); + await waitForPasteMenuPopupEvent("shown"); + + const pastePopup = document.getElementById(kPasteMenuPopupId); + const pasteButton = document.getElementById(kPasteMenuItemId); + ok( + pasteButton.disabled, + "Paste button should be shown with disabled by default" + ); + + let accesskey = pasteButton.getAttribute("accesskey"); + let delay = Services.prefs.getIntPref("security.dialog_enable_delay") * 3; + while (delay > 0) { + // There's no other way to allow some time to pass and ensure we're + // genuinely testing that these keypresses postpone the enabling of + // the paste button, so disable this check for this line: + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(r => setTimeout(r, 100)); + ok(pasteButton.disabled, "Paste button should still be disabled"); + EventUtils.synthesizeKey(accesskey, {}, window); + is(pastePopup.state, "open", "Paste popup should still be opened"); + delay = delay - 100; + } + + await BrowserTestUtils.waitForMutationCondition( + pasteButton, + { attributeFilter: ["disabled"] }, + () => !pasteButton.disabled, + "Wait for paste button enabled" + ); + + const pasteButtonIsHidden = promisePasteButtonIsHidden(); + EventUtils.synthesizeKey(accesskey, {}, window); + await pasteButtonIsHidden; + }); +}); diff --git a/dom/events/test/clipboard/browser_navigator_clipboard_read.js b/dom/events/test/clipboard/browser_navigator_clipboard_read.js new file mode 100644 index 0000000000..5b3af1d116 --- /dev/null +++ b/dom/events/test/clipboard/browser_navigator_clipboard_read.js @@ -0,0 +1,198 @@ +/* -*- Mode: JavaScript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +"use strict"; + +const kBaseUrlForContent = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" +); + +const kContentFileName = "simple_navigator_clipboard_read.html"; + +const kContentFileUrl = kBaseUrlForContent + kContentFileName; + +const kApzTestNativeEventUtilsUrl = + "chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js"; + +Services.scriptloader.loadSubScript(kApzTestNativeEventUtilsUrl, this); + +// @param aBrowser browser object of the content tab. +// @param aMultipleReadTextCalls if false, exactly one call is made, two +// otherwise. +function promiseClickContentToTriggerClipboardRead( + aBrowser, + aMultipleReadTextCalls +) { + return promiseClickContentElement( + aBrowser, + aMultipleReadTextCalls ? "invokeReadTwiceId" : "invokeReadOnceId" + ); +} + +// @param aBrowser browser object of the content tab. +function promiseMutatedReadResultFromContentElement(aBrowser) { + return promiseMutatedTextContentFromContentElement(aBrowser, "readResultId"); +} + +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.events.asyncClipboard.clipboardItem", true], + ["test.events.async.enabled", true], + ], + }); +}); + +add_task(async function test_paste_button_position() { + // Ensure there's text on the clipboard. + await promiseWritingRandomTextToClipboard(); + + await BrowserTestUtils.withNewTab(kContentFileUrl, async function (browser) { + const pasteButtonIsShown = promisePasteButtonIsShown(); + const coordsOfClickInContentRelativeToScreenInDevicePixels = + await promiseClickContentToTriggerClipboardRead(browser, false); + info( + "coordsOfClickInContentRelativeToScreenInDevicePixels: " + + coordsOfClickInContentRelativeToScreenInDevicePixels.x + + ", " + + coordsOfClickInContentRelativeToScreenInDevicePixels.y + ); + + const pasteButtonCoordsRelativeToScreenInDevicePixels = + await pasteButtonIsShown; + info( + "pasteButtonCoordsRelativeToScreenInDevicePixels: " + + pasteButtonCoordsRelativeToScreenInDevicePixels.x + + ", " + + pasteButtonCoordsRelativeToScreenInDevicePixels.y + ); + + const mouseCoordsRelativeToScreenInDevicePixels = + getMouseCoordsRelativeToScreenInDevicePixels(); + info( + "mouseCoordsRelativeToScreenInDevicePixels: " + + mouseCoordsRelativeToScreenInDevicePixels.x + + ", " + + mouseCoordsRelativeToScreenInDevicePixels.y + ); + + // Asserting not overlapping is important; otherwise, when the + // "Paste" button is shown via a `mousedown` event, the following + // `mouseup` event could accept the "Paste" button unnoticed by the + // user. + ok( + isCloselyLeftOnTopOf( + mouseCoordsRelativeToScreenInDevicePixels, + pasteButtonCoordsRelativeToScreenInDevicePixels + ), + "'Paste' button is closely left on top of the mouse pointer." + ); + ok( + isCloselyLeftOnTopOf( + coordsOfClickInContentRelativeToScreenInDevicePixels, + pasteButtonCoordsRelativeToScreenInDevicePixels + ), + "Coords of click in content are closely left on top of the 'Paste' button." + ); + + // To avoid disturbing subsequent tests. + const pasteButtonIsHidden = promisePasteButtonIsHidden(); + await promiseClickPasteButton(); + await pasteButtonIsHidden; + }); +}); + +add_task(async function test_accepting_paste_button() { + // Randomized text to avoid overlappings with other tests. + const clipboardText = await promiseWritingRandomTextToClipboard(); + + await BrowserTestUtils.withNewTab(kContentFileUrl, async function (browser) { + const pasteButtonIsShown = promisePasteButtonIsShown(); + await promiseClickContentToTriggerClipboardRead(browser, false); + await pasteButtonIsShown; + const pasteButtonIsHidden = promisePasteButtonIsHidden(); + const mutatedReadResultFromContentElement = + promiseMutatedReadResultFromContentElement(browser); + await promiseClickPasteButton(); + await pasteButtonIsHidden; + await mutatedReadResultFromContentElement.then(value => { + is( + value, + "Resolved: " + clipboardText, + "Text returned from `navigator.clipboard.read()` is as expected." + ); + }); + }); +}); + +add_task(async function test_dismissing_paste_button() { + await BrowserTestUtils.withNewTab(kContentFileUrl, async function (browser) { + const pasteButtonIsShown = promisePasteButtonIsShown(); + await promiseClickContentToTriggerClipboardRead(browser, false); + await pasteButtonIsShown; + const pasteButtonIsHidden = promisePasteButtonIsHidden(); + const mutatedReadResultFromContentElement = + promiseMutatedReadResultFromContentElement(browser); + await promiseDismissPasteButton(); + await pasteButtonIsHidden; + await mutatedReadResultFromContentElement.then(value => { + is( + value, + "Rejected: The user dismissed the 'Paste' button.", + "`navigator.clipboard.read()` rejected after dismissing the 'Paste' button" + ); + }); + }); +}); + +add_task( + async function test_multiple_read_invocations_for_same_user_activation() { + // Randomized text to avoid overlappings with other tests. + const clipboardText = await promiseWritingRandomTextToClipboard(); + + await BrowserTestUtils.withNewTab( + kContentFileUrl, + async function (browser) { + const pasteButtonIsShown = promisePasteButtonIsShown(); + await promiseClickContentToTriggerClipboardRead(browser, true); + await pasteButtonIsShown; + const mutatedReadResultFromContentElement = + promiseMutatedReadResultFromContentElement(browser); + const pasteButtonIsHidden = promisePasteButtonIsHidden(); + await promiseClickPasteButton(); + await mutatedReadResultFromContentElement.then(value => { + is( + value, + "Resolved 1: " + clipboardText + "; Resolved 2: " + clipboardText, + "Two calls of `navigator.clipboard.read()` both resolved with the expected text." + ); + }); + + // To avoid disturbing subsequent tests. + await pasteButtonIsHidden; + } + ); + } +); + +add_task(async function test_new_user_activation_shows_paste_button_again() { + await BrowserTestUtils.withNewTab(kContentFileUrl, async function (browser) { + // Ensure there's text on the clipboard. + await promiseWritingRandomTextToClipboard(); + + for (let i = 0; i < 2; ++i) { + const pasteButtonIsShown = promisePasteButtonIsShown(); + // A click initiates a new user activation. + await promiseClickContentToTriggerClipboardRead(browser, false); + await pasteButtonIsShown; + + const pasteButtonIsHidden = promisePasteButtonIsHidden(); + await promiseClickPasteButton(); + await pasteButtonIsHidden; + } + }); +}); diff --git a/dom/events/test/clipboard/browser_navigator_clipboard_readText.js b/dom/events/test/clipboard/browser_navigator_clipboard_readText.js new file mode 100644 index 0000000000..7563fa8a21 --- /dev/null +++ b/dom/events/test/clipboard/browser_navigator_clipboard_readText.js @@ -0,0 +1,201 @@ +/* -*- Mode: JavaScript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +"use strict"; + +const kBaseUrlForContent = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" +); + +const kContentFileName = "simple_navigator_clipboard_readText.html"; + +const kContentFileUrl = kBaseUrlForContent + kContentFileName; + +const kApzTestNativeEventUtilsUrl = + "chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js"; + +Services.scriptloader.loadSubScript(kApzTestNativeEventUtilsUrl, this); + +// @param aBrowser browser object of the content tab. +// @param aMultipleReadTextCalls if false, exactly one call is made, two +// otherwise. +function promiseClickContentToTriggerClipboardReadText( + aBrowser, + aMultipleReadTextCalls +) { + return promiseClickContentElement( + aBrowser, + aMultipleReadTextCalls ? "invokeReadTextTwiceId" : "invokeReadTextOnceId" + ); +} + +// @param aBrowser browser object of the content tab. +function promiseMutatedReadTextResultFromContentElement(aBrowser) { + return promiseMutatedTextContentFromContentElement( + aBrowser, + "readTextResultId" + ); +} + +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.events.asyncClipboard.readText", true], + ["test.events.async.enabled", true], + ], + }); +}); + +add_task(async function test_paste_button_position() { + // Ensure there's text on the clipboard. + await promiseWritingRandomTextToClipboard(); + + await BrowserTestUtils.withNewTab(kContentFileUrl, async function (browser) { + const pasteButtonIsShown = promisePasteButtonIsShown(); + const coordsOfClickInContentRelativeToScreenInDevicePixels = + await promiseClickContentToTriggerClipboardReadText(browser, false); + info( + "coordsOfClickInContentRelativeToScreenInDevicePixels: " + + coordsOfClickInContentRelativeToScreenInDevicePixels.x + + ", " + + coordsOfClickInContentRelativeToScreenInDevicePixels.y + ); + + const pasteButtonCoordsRelativeToScreenInDevicePixels = + await pasteButtonIsShown; + info( + "pasteButtonCoordsRelativeToScreenInDevicePixels: " + + pasteButtonCoordsRelativeToScreenInDevicePixels.x + + ", " + + pasteButtonCoordsRelativeToScreenInDevicePixels.y + ); + + const mouseCoordsRelativeToScreenInDevicePixels = + getMouseCoordsRelativeToScreenInDevicePixels(); + info( + "mouseCoordsRelativeToScreenInDevicePixels: " + + mouseCoordsRelativeToScreenInDevicePixels.x + + ", " + + mouseCoordsRelativeToScreenInDevicePixels.y + ); + + // Asserting not overlapping is important; otherwise, when the + // "Paste" button is shown via a `mousedown` event, the following + // `mouseup` event could accept the "Paste" button unnoticed by the + // user. + ok( + isCloselyLeftOnTopOf( + mouseCoordsRelativeToScreenInDevicePixels, + pasteButtonCoordsRelativeToScreenInDevicePixels + ), + "'Paste' button is closely left on top of the mouse pointer." + ); + ok( + isCloselyLeftOnTopOf( + coordsOfClickInContentRelativeToScreenInDevicePixels, + pasteButtonCoordsRelativeToScreenInDevicePixels + ), + "Coords of click in content are closely left on top of the 'Paste' button." + ); + + // To avoid disturbing subsequent tests. + const pasteButtonIsHidden = promisePasteButtonIsHidden(); + await promiseClickPasteButton(); + await pasteButtonIsHidden; + }); +}); + +add_task(async function test_accepting_paste_button() { + // Randomized text to avoid overlappings with other tests. + const clipboardText = await promiseWritingRandomTextToClipboard(); + + await BrowserTestUtils.withNewTab(kContentFileUrl, async function (browser) { + const pasteButtonIsShown = promisePasteButtonIsShown(); + await promiseClickContentToTriggerClipboardReadText(browser, false); + await pasteButtonIsShown; + const pasteButtonIsHidden = promisePasteButtonIsHidden(); + const mutatedReadTextResultFromContentElement = + promiseMutatedReadTextResultFromContentElement(browser); + await promiseClickPasteButton(); + await pasteButtonIsHidden; + await mutatedReadTextResultFromContentElement.then(value => { + is( + value, + "Resolved: " + clipboardText, + "Text returned from `navigator.clipboard.readText()` is as expected." + ); + }); + }); +}); + +add_task(async function test_dismissing_paste_button() { + await BrowserTestUtils.withNewTab(kContentFileUrl, async function (browser) { + const pasteButtonIsShown = promisePasteButtonIsShown(); + await promiseClickContentToTriggerClipboardReadText(browser, false); + await pasteButtonIsShown; + const pasteButtonIsHidden = promisePasteButtonIsHidden(); + const mutatedReadTextResultFromContentElement = + promiseMutatedReadTextResultFromContentElement(browser); + await promiseDismissPasteButton(); + await pasteButtonIsHidden; + await mutatedReadTextResultFromContentElement.then(value => { + is( + value, + "Rejected.", + "`navigator.clipboard.readText()` rejected after dismissing the 'Paste' button" + ); + }); + }); +}); + +add_task( + async function test_multiple_readText_invocations_for_same_user_activation() { + // Randomized text to avoid overlappings with other tests. + const clipboardText = await promiseWritingRandomTextToClipboard(); + + await BrowserTestUtils.withNewTab( + kContentFileUrl, + async function (browser) { + const pasteButtonIsShown = promisePasteButtonIsShown(); + await promiseClickContentToTriggerClipboardReadText(browser, true); + await pasteButtonIsShown; + const mutatedReadTextResultFromContentElement = + promiseMutatedReadTextResultFromContentElement(browser); + const pasteButtonIsHidden = promisePasteButtonIsHidden(); + await promiseClickPasteButton(); + await mutatedReadTextResultFromContentElement.then(value => { + is( + value, + "Resolved 1: " + clipboardText + "; Resolved 2: " + clipboardText, + "Two calls of `navigator.clipboard.read()` both resolved with the expected text." + ); + }); + + // To avoid disturbing subsequent tests. + await pasteButtonIsHidden; + } + ); + } +); + +add_task(async function test_new_user_activation_shows_paste_button_again() { + await BrowserTestUtils.withNewTab(kContentFileUrl, async function (browser) { + // Ensure there's text on the clipboard. + await promiseWritingRandomTextToClipboard(); + + for (let i = 0; i < 2; ++i) { + const pasteButtonIsShown = promisePasteButtonIsShown(); + // A click initiates a new user activation. + await promiseClickContentToTriggerClipboardReadText(browser, false); + await pasteButtonIsShown; + + const pasteButtonIsHidden = promisePasteButtonIsHidden(); + await promiseClickPasteButton(); + await pasteButtonIsHidden; + } + }); +}); diff --git a/dom/events/test/clipboard/browser_navigator_clipboard_touch.js b/dom/events/test/clipboard/browser_navigator_clipboard_touch.js new file mode 100644 index 0000000000..aeea9a612d --- /dev/null +++ b/dom/events/test/clipboard/browser_navigator_clipboard_touch.js @@ -0,0 +1,114 @@ +/* -*- Mode: JavaScript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +"use strict"; + +const kBaseUrlForContent = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" +); +const kContentFileUrl = + kBaseUrlForContent + "simple_navigator_clipboard_readText.html"; + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js", + this +); + +// @param aBrowser browser object of the content tab. +// @param aContentElementId the ID of the element to be tapped. +function promiseTouchTapContent(aBrowser, aContentElementId) { + return SpecialPowers.spawn( + aBrowser, + [aContentElementId], + async _contentElementId => { + await content.wrappedJSObject.waitUntilApzStable(); + + const contentElement = content.document.getElementById(_contentElementId); + let promise = new Promise(resolve => { + contentElement.addEventListener( + "click", + function (e) { + resolve({ x: e.screenX, y: e.screenY }); + }, + { once: true } + ); + }); + + EventUtils.synthesizeTouchAtCenter(contentElement, {}, content.window); + + return promise; + } + ); +} + +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.events.asyncClipboard.readText", true], + ["test.events.async.enabled", true], + ], + }); +}); + +add_task(async function test_paste_button_position_touch() { + // Ensure there's text on the clipboard. + await promiseWritingRandomTextToClipboard(); + + await BrowserTestUtils.withNewTab(kContentFileUrl, async function (browser) { + const pasteButtonIsShown = promisePasteButtonIsShown(); + const coordsOfClickInContentRelativeToScreenInDevicePixels = + await promiseTouchTapContent(browser, "invokeReadTextOnceId"); + info( + "coordsOfClickInContentRelativeToScreenInDevicePixels: " + + coordsOfClickInContentRelativeToScreenInDevicePixels.x + + ", " + + coordsOfClickInContentRelativeToScreenInDevicePixels.y + ); + + const pasteButtonCoordsRelativeToScreenInDevicePixels = + await pasteButtonIsShown; + info( + "pasteButtonCoordsRelativeToScreenInDevicePixels: " + + pasteButtonCoordsRelativeToScreenInDevicePixels.x + + ", " + + pasteButtonCoordsRelativeToScreenInDevicePixels.y + ); + + const mouseCoordsRelativeToScreenInDevicePixels = + getMouseCoordsRelativeToScreenInDevicePixels(); + info( + "mouseCoordsRelativeToScreenInDevicePixels: " + + mouseCoordsRelativeToScreenInDevicePixels.x + + ", " + + mouseCoordsRelativeToScreenInDevicePixels.y + ); + + // Asserting not overlapping is important; otherwise, when the + // "Paste" button is shown via a `mousedown` event, the following + // `mouseup` event could accept the "Paste" button unnoticed by the + // user. + ok( + isCloselyLeftOnTopOf( + mouseCoordsRelativeToScreenInDevicePixels, + pasteButtonCoordsRelativeToScreenInDevicePixels + ), + "'Paste' button is closely left on top of the mouse pointer." + ); + ok( + isCloselyLeftOnTopOf( + coordsOfClickInContentRelativeToScreenInDevicePixels, + pasteButtonCoordsRelativeToScreenInDevicePixels + ), + "Coords of click in content are closely left on top of the 'Paste' button." + ); + + // To avoid disturbing subsequent tests. + const pasteButtonIsHidden = promisePasteButtonIsHidden(); + await promiseClickPasteButton(); + await pasteButtonIsHidden; + }); +}); diff --git a/dom/events/test/clipboard/chrome.ini b/dom/events/test/clipboard/chrome.ini new file mode 100644 index 0000000000..ce646f98ef --- /dev/null +++ b/dom/events/test/clipboard/chrome.ini @@ -0,0 +1,3 @@ +[DEFAULT] + +[test_async_clipboard.xhtml] diff --git a/dom/events/test/clipboard/head.js b/dom/events/test/clipboard/head.js new file mode 100644 index 0000000000..0db1e0abf4 --- /dev/null +++ b/dom/events/test/clipboard/head.js @@ -0,0 +1,169 @@ +/* -*- Mode: JavaScript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const kPasteMenuPopupId = "clipboardReadPasteMenuPopup"; +const kPasteMenuItemId = "clipboardReadPasteMenuItem"; + +function promiseWritingRandomTextToClipboard() { + const clipboardText = "X" + Math.random(); + return navigator.clipboard.writeText(clipboardText).then(() => { + return clipboardText; + }); +} + +function promiseBrowserReflow() { + return new Promise(resolve => + requestAnimationFrame(() => requestAnimationFrame(resolve)) + ); +} + +function waitForPasteMenuPopupEvent(aEventSuffix) { + // The element with id `kPasteMenuPopupId` is inserted dynamically, hence + // calling `BrowserTestUtils.waitForEvent` instead of + // `BrowserTestUtils.waitForPopupEvent`. + return BrowserTestUtils.waitForEvent( + document, + "popup" + aEventSuffix, + false /* capture */, + e => { + return e.target.getAttribute("id") == kPasteMenuPopupId; + } + ); +} + +function promisePasteButtonIsShown() { + return waitForPasteMenuPopupEvent("shown").then(async () => { + ok(true, "Witnessed 'popupshown' event for 'Paste' button."); + + const pasteButton = document.getElementById(kPasteMenuItemId); + ok( + pasteButton.disabled, + "Paste button should be shown with disabled by default" + ); + await BrowserTestUtils.waitForMutationCondition( + pasteButton, + { attributeFilter: ["disabled"] }, + () => !pasteButton.disabled, + "Wait for paste button enabled" + ); + + return promiseBrowserReflow().then(() => { + return coordinatesRelativeToScreen({ + target: pasteButton, + offsetX: 0, + offsetY: 0, + }); + }); + }); +} + +function promisePasteButtonIsHidden() { + return waitForPasteMenuPopupEvent("hidden").then(() => { + ok(true, "Witnessed 'popuphidden' event for 'Paste' button."); + return promiseBrowserReflow(); + }); +} + +function promiseClickPasteButton() { + const pasteButton = document.getElementById(kPasteMenuItemId); + let promise = BrowserTestUtils.waitForEvent(pasteButton, "click"); + EventUtils.synthesizeMouseAtCenter(pasteButton, {}); + return promise; +} + +function getMouseCoordsRelativeToScreenInDevicePixels() { + let mouseXInCSSPixels = {}; + let mouseYInCSSPixels = {}; + window.windowUtils.getLastOverWindowPointerLocationInCSSPixels( + mouseXInCSSPixels, + mouseYInCSSPixels + ); + + return { + x: + (mouseXInCSSPixels.value + window.mozInnerScreenX) * + window.devicePixelRatio, + y: + (mouseYInCSSPixels.value + window.mozInnerScreenY) * + window.devicePixelRatio, + }; +} + +function isCloselyLeftOnTopOf(aCoordsP1, aCoordsP2, aDelta = 10) { + return ( + Math.abs(aCoordsP2.x - aCoordsP1.x) < aDelta && + Math.abs(aCoordsP2.y - aCoordsP1.y) < aDelta + ); +} + +function promiseDismissPasteButton() { + // nsXULPopupManager rollup is handled in widget code, so we have to + // synthesize native mouse events. + return EventUtils.promiseNativeMouseEvent({ + type: "click", + target: document.body, + // Relies on the assumption that the center of chrome document doesn't + // overlay with the paste button showed for clipboard readText request. + atCenter: true, + }); +} + +// @param aBrowser browser object of the content tab. +// @param aContentElementId the ID of the element to be clicked. +function promiseClickContentElement(aBrowser, aContentElementId) { + return SpecialPowers.spawn( + aBrowser, + [aContentElementId], + async _contentElementId => { + const contentElement = content.document.getElementById(_contentElementId); + let promise = new Promise(resolve => { + contentElement.addEventListener( + "click", + function (e) { + resolve({ x: e.screenX, y: e.screenY }); + }, + { once: true } + ); + }); + + EventUtils.synthesizeMouseAtCenter(contentElement, {}, content.window); + + return promise; + } + ); +} + +// @param aBrowser browser object of the content tab. +// @param aContentElementId the ID of the element to observe. +function promiseMutatedTextContentFromContentElement( + aBrowser, + aContentElementId +) { + return SpecialPowers.spawn( + aBrowser, + [aContentElementId], + async _contentElementId => { + const contentElement = content.document.getElementById(_contentElementId); + + const promiseTextContentResult = new Promise(resolve => { + const mutationObserver = new content.MutationObserver( + (aMutationRecord, aMutationObserver) => { + info("Observed mutation."); + aMutationObserver.disconnect(); + resolve(contentElement.textContent); + } + ); + + mutationObserver.observe(contentElement, { + childList: true, + }); + }); + + return await promiseTextContentResult; + } + ); +} diff --git a/dom/events/test/clipboard/simple_navigator_clipboard_keydown.html b/dom/events/test/clipboard/simple_navigator_clipboard_keydown.html new file mode 100644 index 0000000000..15fcdfba2c --- /dev/null +++ b/dom/events/test/clipboard/simple_navigator_clipboard_keydown.html @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/dom/events/test/clipboard/simple_navigator_clipboard_read.html b/dom/events/test/clipboard/simple_navigator_clipboard_read.html new file mode 100644 index 0000000000..89f38a3240 --- /dev/null +++ b/dom/events/test/clipboard/simple_navigator_clipboard_read.html @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + +
+ + diff --git a/dom/events/test/clipboard/simple_navigator_clipboard_readText.html b/dom/events/test/clipboard/simple_navigator_clipboard_readText.html new file mode 100644 index 0000000000..0b85371091 --- /dev/null +++ b/dom/events/test/clipboard/simple_navigator_clipboard_readText.html @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + +
+ + diff --git a/dom/events/test/clipboard/test_async_clipboard.xhtml b/dom/events/test/clipboard/test_async_clipboard.xhtml new file mode 100644 index 0000000000..787821efc3 --- /dev/null +++ b/dom/events/test/clipboard/test_async_clipboard.xhtml @@ -0,0 +1,130 @@ + + + + + + + + + +

+

+ +
+
+ + +
-- cgit v1.2.3