From 2aa4a82499d4becd2284cdb482213d541b8804dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 16:29:10 +0200 Subject: Adding upstream version 86.0.1. Signed-off-by: Daniel Baumann --- remote/test/browser/input/browser.ini | 17 +++ .../test/browser/input/browser_dispatchKeyEvent.js | 169 +++++++++++++++++++++ .../input/browser_dispatchKeyEvent_events.js | 129 ++++++++++++++++ .../browser/input/browser_dispatchKeyEvent_race.js | 92 +++++++++++ .../browser/input/browser_dispatchMouseEvent.js | 60 ++++++++ .../browser/input/doc_dispatchKeyEvent_race.html | 27 ++++ remote/test/browser/input/doc_events.html | 148 ++++++++++++++++++ remote/test/browser/input/head.js | 123 +++++++++++++++ 8 files changed, 765 insertions(+) create mode 100644 remote/test/browser/input/browser.ini create mode 100644 remote/test/browser/input/browser_dispatchKeyEvent.js create mode 100644 remote/test/browser/input/browser_dispatchKeyEvent_events.js create mode 100644 remote/test/browser/input/browser_dispatchKeyEvent_race.js create mode 100644 remote/test/browser/input/browser_dispatchMouseEvent.js create mode 100644 remote/test/browser/input/doc_dispatchKeyEvent_race.html create mode 100644 remote/test/browser/input/doc_events.html create mode 100644 remote/test/browser/input/head.js (limited to 'remote/test/browser/input') diff --git a/remote/test/browser/input/browser.ini b/remote/test/browser/input/browser.ini new file mode 100644 index 0000000000..a2fb2f515e --- /dev/null +++ b/remote/test/browser/input/browser.ini @@ -0,0 +1,17 @@ +[DEFAULT] +tags = remote +subsuite = remote +prefs = + remote.enabled=true +support-files = + !/remote/test/browser/chrome-remote-interface.js + !/remote/test/browser/head.js + head.js + doc_events.html + doc_dispatchKeyEvent_race.html + +[browser_dispatchKeyEvent.js] +[browser_dispatchKeyEvent_events.js] +[browser_dispatchKeyEvent_race.js] +skip-if = socketprocess_networking +[browser_dispatchMouseEvent.js] diff --git a/remote/test/browser/input/browser_dispatchKeyEvent.js b/remote/test/browser/input/browser_dispatchKeyEvent.js new file mode 100644 index 0000000000..dc94bc57b3 --- /dev/null +++ b/remote/test/browser/input/browser_dispatchKeyEvent.js @@ -0,0 +1,169 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function testTypingPrintableCharacters({ client }) { + await setupForInput(toDataURL("")); + const { Input } = client; + + info("Write 'h'"); + await sendTextKey(Input, "h"); + await checkInputContent("h", 1); + + info("Write 'H'"); + await sendTextKey(Input, "H"); + await checkInputContent("hH", 2); + + info("Send char type event for char [’]"); + await Input.dispatchKeyEvent({ + type: "char", + modifiers: 0, + key: "’", + }); + await checkInputContent("hH’", 3); +}); + +add_task(async function testArrowKeys({ client }) { + await setupForInput(toDataURL("")); + const { Input } = client; + + await sendText(Input, "hH’"); + info("Send Left"); + await sendRawKey(Input, "ArrowLeft"); + await checkInputContent("hH’", 2); + + info("Write 'a'"); + await sendTextKey(Input, "a"); + await checkInputContent("hHa’", 3); + + info("Send Left"); + await sendRawKey(Input, "ArrowLeft"); + await checkInputContent("hHa’", 2); + + info("Send Left"); + await sendRawKey(Input, "ArrowLeft"); + await checkInputContent("hHa’", 1); + + info("Write 'a'"); + await sendTextKey(Input, "a"); + await checkInputContent("haHa’", 2); + + info("Send ALT/CONTROL + Right"); + let modCode = isMac ? alt : ctrl; + let modKey = isMac ? "Alt" : "Control"; + await dispatchKeyEvent(Input, modKey, "rawKeyDown", modCode); + await dispatchKeyEvent(Input, "ArrowRight", "rawKeyDown", modCode); + await dispatchKeyEvent(Input, "ArrowRight", "keyUp"); + await dispatchKeyEvent(Input, modKey, "keyUp"); + await checkInputContent("haHa’", 5); +}); + +add_task(async function testBackspace({ client }) { + await setupForInput(toDataURL("")); + const { Input } = client; + + await sendText(Input, "haHa’"); + + info("Delete every character in the input"); + await checkBackspace(Input, "haHa"); + await checkBackspace(Input, "haH"); + await checkBackspace(Input, "ha"); + await checkBackspace(Input, "h"); + await checkBackspace(Input, ""); +}); + +add_task(async function testShiftSelect({ client }) { + await setupForInput(toDataURL("")); + const { Input } = client; + await resetInput("word 2 word3"); + + info("Send Shift + Left (select one char to the left)"); + await dispatchKeyEvent(Input, "Shift", "rawKeyDown", shift); + await sendRawKey(Input, "ArrowLeft", shift); + await sendRawKey(Input, "ArrowLeft", shift); + await sendRawKey(Input, "ArrowLeft", shift); + info("(deleteContentBackward)"); + await checkBackspace(Input, "word 2 wo"); + await dispatchKeyEvent(Input, "Shift", "keyUp"); + + await resetInput("word 2 wo"); + info("Send Shift + Left (select one char to the left)"); + await dispatchKeyEvent(Input, "Shift", "rawKeyDown", shift); + await sendRawKey(Input, "ArrowLeft", shift); + await sendRawKey(Input, "ArrowLeft", shift); + await sendTextKey(Input, "H"); + await checkInputContent("word 2 H", 8); + await dispatchKeyEvent(Input, "Shift", "keyUp"); +}); + +add_task(async function testSelectWord({ client }) { + await setupForInput(toDataURL("")); + const { Input } = client; + await resetInput("word 2 word3"); + + info("Send Shift + Ctrl/Alt + Left (select one word to the left)"); + const { primary, primaryKey } = keyForPlatform(); + const combined = shift | primary; + await dispatchKeyEvent(Input, "Shift", "rawKeyDown", shift); + await dispatchKeyEvent(Input, primaryKey, "rawKeyDown", combined); + await sendRawKey(Input, "ArrowLeft", combined); + await sendRawKey(Input, "ArrowLeft", combined); + await dispatchKeyEvent(Input, "Shift", "keyUp", primary); + await dispatchKeyEvent(Input, primaryKey, "keyUp"); + info("(deleteContentBackward)"); + await checkBackspace(Input, "word "); +}); + +add_task(async function testSelectDelete({ client }) { + await setupForInput(toDataURL("")); + const { Input } = client; + await resetInput("word 2 word3"); + + info("Send Ctrl/Alt + Backspace (deleteWordBackward)"); + const { primary, primaryKey } = keyForPlatform(); + await dispatchKeyEvent(Input, primaryKey, "rawKeyDown", primary); + await checkBackspace(Input, "word 2 ", primary); + await dispatchKeyEvent(Input, primaryKey, "keyUp"); + + await resetInput("word 2 "); + await sendText(Input, "word4"); + await sendRawKey(Input, "ArrowLeft"); + await sendRawKey(Input, "ArrowLeft"); + await checkInputContent("word 2 word4", 10); + + if (isMac) { + info("Send Meta + Backspace (deleteSoftLineBackward)"); + await dispatchKeyEvent(Input, "Meta", "rawKeyDown", meta); + await sendRawKey(Input, "Backspace", meta); + await dispatchKeyEvent(Input, "Meta", "keyUp"); + await checkInputContent("d4", 0); + } +}); + +add_task(async function testCtrlShiftArrows({ client }) { + await loadURL( + toDataURL('') + ); + const { Input } = client; + + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function() { + const select = content.document.querySelector("select"); + select.selectedIndex = 0; + select.focus(); + }); + + const combined = shift | ctrl; + await dispatchKeyEvent(Input, "Control", "rawKeyDown", shift); + await dispatchKeyEvent(Input, "Shift", "rawKeyDown", combined); + await sendRawKey(Input, "ArrowDown", combined); + await dispatchKeyEvent(Input, "Control", "keyUp", shift); + await dispatchKeyEvent(Input, "Shift", "keyUp"); + + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function() { + const select = content.document.querySelector("select"); + ok(select[0].selected, "First option should be selected"); + ok(select[1].selected, "Second option should be selected"); + ok(!select[2].selected, "Third option should not be selected"); + }); +}); diff --git a/remote/test/browser/input/browser_dispatchKeyEvent_events.js b/remote/test/browser/input/browser_dispatchKeyEvent_events.js new file mode 100644 index 0000000000..24a5efdcb6 --- /dev/null +++ b/remote/test/browser/input/browser_dispatchKeyEvent_events.js @@ -0,0 +1,129 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const PAGE_URL = + "http://example.com/browser/remote/test/browser/input/doc_events.html"; + +add_task(async function testShiftEvents({ client }) { + await setupForInput(PAGE_URL); + const { Input } = client; + await resetEvents(); + + await withModifier(Input, "Shift", "shift", "A"); + await checkInputContent("A", 1); + let events = await getEvents(); + checkEvent(events[0], "keydown", "Shift", "shift", true); + checkEvent(events[1], "keydown", "A", "shift", true); + checkEvent(events[2], "keypress", "A", "shift", true); + checkProperties({ data: "A", inputType: "insertText" }, events[3]); + checkEvent(events[4], "keyup", "A", "shift", true); + checkEvent(events[5], "keyup", "Shift", "shift", false); + await resetEvents(); + + await withModifier(Input, "Shift", "shift", "Enter"); + events = await getEvents(); + checkEvent(events[2], "keypress", "Enter", "shift", true); + await resetEvents(); + + await withModifier(Input, "Shift", "shift", "Tab"); + events = await getEvents(); + checkEvent(events[1], "keydown", "Tab", "shift", true); + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function() { + const input = content.document.querySelector("input"); + isnot(input, content.document.activeElement, "input should lose focus"); + }); +}); + +add_task(async function testAltEvents({ client }) { + await setupForInput(PAGE_URL); + const { Input } = client; + + await withModifier(Input, "Alt", "alt", "a"); + if (isMac) { + await checkInputContent("a", 1); + } else { + await checkInputContent("", 0); + } + let events = await getEvents(); + checkEvent(events[1], "keydown", "a", "alt", true); + checkEvent(events[events.length - 1], "keyup", "Alt", "alt", false); +}); + +add_task(async function testControlEvents({ client }) { + await setupForInput(PAGE_URL); + const { Input } = client; + + await withModifier(Input, "Control", "ctrl", "`"); + let events = await getEvents(); + // no keypress or input event + checkEvent(events[1], "keydown", "`", "ctrl", true); + checkEvent(events[events.length - 1], "keyup", "Control", "ctrl", false); +}); + +add_task(async function testMetaEvents({ client }) { + if (!isMac) { + return; + } + await setupForInput(PAGE_URL); + const { Input } = client; + + await withModifier(Input, "Meta", "meta", "a"); + let events = await getEvents(); + // no keypress or input event + checkEvent(events[1], "keydown", "a", "meta", true); + checkEvent(events[events.length - 1], "keyup", "Meta", "meta", false); +}); + +add_task(async function testShiftClick({ client }) { + await loadURL(PAGE_URL); + const { Input } = client; + await resetEvents(); + + await dispatchKeyEvent(Input, "Shift", "rawKeyDown", shift); + info("Click the 'pointers' div."); + await Input.dispatchMouseEvent({ + type: "mousePressed", + x: 80, + y: 180, + modifiers: shift, + }); + await Input.dispatchMouseEvent({ + type: "mouseReleased", + x: 80, + y: 180, + modifiers: shift, + }); + await dispatchKeyEvent(Input, "Shift", "keyUp", shift); + let events = await getEvents(); + checkProperties({ type: "click", shiftKey: true, button: 0 }, events[2]); +}); + +async function getEvents() { + const events = await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { + return content.eval("allEvents"); + }); + info(`Events: ${JSON.stringify(events)}`); + return events; +} + +async function resetEvents() { + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { + content.eval("resetEvents()"); + const events = content.eval("allEvents"); + is(events.length, 0, "List of events should be empty"); + }); +} + +function checkEvent(event, type, key, property, expectedValue) { + let expected = { type, key }; + expected[property] = expectedValue; + checkProperties(expected, event, "Event"); +} + +function checkProperties(expectedObj, targetObj, message = "Compare objects") { + for (const prop in expectedObj) { + is(targetObj[prop], expectedObj[prop], message + `: check ${prop}`); + } +} diff --git a/remote/test/browser/input/browser_dispatchKeyEvent_race.js b/remote/test/browser/input/browser_dispatchKeyEvent_race.js new file mode 100644 index 0000000000..97daccf0ee --- /dev/null +++ b/remote/test/browser/input/browser_dispatchKeyEvent_race.js @@ -0,0 +1,92 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Here we test that the `dispatchKeyEvent` API resolves after all the synchronous event +// handlers from the content page have been flushed. +// +// Say the content page has an event handler such as: +// +// el.addEventListener("keyup", () => { +// doSomeVeryLongProcessing(); // <- takes a long time but is synchronous! +// window.myVariable = "newValue"; +// }); +// +// And imagine this is tested via: +// +// await Input.dispatchKeyEvent(...); +// const myVariable = await Runtime.evaluate({ expression: "window.myVariable" }); +// equals(myVariable, "newValue"); +// +// In order for this to work, we need to be sure that `await Input.dispatchKeyEvent` +// resolves only after the content page flushed the event handlers (and +// `window.myVariable = "newValue"` was executed). +// +// This can be racy because Input.dispatchKeyEvent and window.myVariable = "newValue" run +// in different processes. + +const PAGE_URL = + "http://example.com/browser/remote/test/browser/input/doc_dispatchKeyEvent_race.html"; + +add_task(async function({ client }) { + await loadURL(PAGE_URL); + + const { Input, Runtime } = client; + + // Need an enabled Runtime domain to run evaluate. + info("Enable the Runtime domain"); + await Runtime.enable(); + const { context } = await Runtime.executionContextCreated(); + + info("Focus the input on the page"); + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function() { + const input = content.document.querySelector("input"); + input.focus(); + is(input, content.document.activeElement, "Input should be focused"); + }); + + // See doc_input_dispatchKeyEvent_race.html + // The page listens to `input` events to update a property on window. + // We will check that the value is updated as soon dispatchKeyEvent has resolved. + await checkWindowTestValue("initial-value", context.id, Runtime); + + info("Write 'hhhhhh' ('h' times 6)"); + for (let i = 0; i < 6; i++) { + await dispatchKeyEvent(Input, "h", 72, "keyDown"); + await dispatchKeyEvent(Input, "h", 72, "keyUp"); + } + await checkWindowTestValue("hhhhhh", context.id, Runtime); + + info("Write 'aaaaaa' with 6 consecutive keydown and one keyup"); + await Promise.all([ + dispatchKeyEvent(Input, "a", 65, "keyDown"), + dispatchKeyEvent(Input, "a", 65, "keyDown"), + dispatchKeyEvent(Input, "a", 65, "keyDown"), + dispatchKeyEvent(Input, "a", 65, "keyDown"), + dispatchKeyEvent(Input, "a", 65, "keyDown"), + dispatchKeyEvent(Input, "a", 65, "keyDown"), + ]); + await dispatchKeyEvent(Input, "a", 65, "keyUp"); + await checkWindowTestValue("hhhhhhaaaaaa", context.id, Runtime); +}); + +function dispatchKeyEvent(Input, key, keyCode, type, modifiers = 0) { + info(`Send ${type} for key ${key}`); + return Input.dispatchKeyEvent({ + type, + modifiers, + windowsVirtualKeyCode: keyCode, + key, + }); +} + +async function checkWindowTestValue(expected, contextId, Runtime) { + info("Retrieve the value of `window.testValue` in the test page"); + const { result } = await Runtime.evaluate({ + contextId, + expression: "window.testValue", + }); + + is(result.value, expected, "Content window test value is correct"); +} diff --git a/remote/test/browser/input/browser_dispatchMouseEvent.js b/remote/test/browser/input/browser_dispatchMouseEvent.js new file mode 100644 index 0000000000..96adcd5a8a --- /dev/null +++ b/remote/test/browser/input/browser_dispatchMouseEvent.js @@ -0,0 +1,60 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function testDispatchMouseEvent({ client }) { + await loadURL(toDataURL("
foo
")); + + const { Input } = client; + + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function() { + const div = content.document.querySelector("div"); + content.mouseDownPromise = new Promise(resolve => { + div.addEventListener("mousedown", resolve, { once: true }); + }); + content.mouseUpPromise = new Promise(resolve => { + div.addEventListener("mouseup", resolve, { once: true }); + }); + content.clickPromise = new Promise(resolve => { + div.addEventListener("click", resolve, { once: true }); + }); + }); + + const { x, y } = await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [], + () => { + return content.document.querySelector("div").getBoundingClientRect(); + } + ); + + await Input.dispatchMouseEvent({ + type: "mousePressed", + x, + y, + }); + + info("Waiting for DOM mousedown event on the div"); + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function() { + await content.mouseDownPromise; + }); + + await Input.dispatchMouseEvent({ + type: "mouseReleased", + x, + y, + }); + + info("Waiting for DOM mouseup event on the div"); + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function() { + await content.mouseUpPromise; + }); + + info("Waiting for DOM click event on the div"); + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function() { + await content.clickPromise; + }); + + ok(true, "All events detected"); +}); diff --git a/remote/test/browser/input/doc_dispatchKeyEvent_race.html b/remote/test/browser/input/doc_dispatchKeyEvent_race.html new file mode 100644 index 0000000000..f8f45bae88 --- /dev/null +++ b/remote/test/browser/input/doc_dispatchKeyEvent_race.html @@ -0,0 +1,27 @@ + + + + Test page for dispatchKeyEvent race test + + + + + + diff --git a/remote/test/browser/input/doc_events.html b/remote/test/browser/input/doc_events.html new file mode 100644 index 0000000000..505c784c13 --- /dev/null +++ b/remote/test/browser/input/doc_events.html @@ -0,0 +1,148 @@ + + + + + Input Events + + + + +
+
+

KeyReporter

+ +
+
+

ClickReporter

+
+
+
+
+

Events

+
+
+ + diff --git a/remote/test/browser/input/head.js b/remote/test/browser/input/head.js new file mode 100644 index 0000000000..7bdc526857 --- /dev/null +++ b/remote/test/browser/input/head.js @@ -0,0 +1,123 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* import-globals-from ../head.js */ + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/remote/test/browser/head.js", + this +); + +const { Input: I } = ChromeUtils.import( + "chrome://remote/content/domains/parent/Input.jsm" +); + +const { alt, ctrl, meta, shift } = I.Modifier; + +const isMac = Services.appinfo.OS === "Darwin"; + +// Map of key codes used in Input tests. +const KEYCODES = { + a: KeyboardEvent.DOM_VK_A, + A: KeyboardEvent.DOM_VK_A, + b: KeyboardEvent.DOM_VK_B, + B: KeyboardEvent.DOM_VK_B, + c: KeyboardEvent.DOM_VK_C, + C: KeyboardEvent.DOM_VK_C, + h: KeyboardEvent.DOM_VK_H, + H: KeyboardEvent.DOM_VK_H, + Alt: KeyboardEvent.DOM_VK_ALT, + ArrowLeft: KeyboardEvent.DOM_VK_LEFT, + ArrowRight: KeyboardEvent.DOM_VK_RIGHT, + ArrowDown: KeyboardEvent.DOM_VK_DOWN, + Backspace: KeyboardEvent.DOM_VK_BACK_SPACE, + Control: KeyboardEvent.DOM_VK_CONTROL, + Meta: KeyboardEvent.DM_VK_META, + Shift: KeyboardEvent.DOM_VK_SHIFT, + Tab: KeyboardEvent.DOM_VK_TAB, +}; + +async function setupForInput(url) { + await loadURL(url); + info("Focus the input on the page"); + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function() { + const input = content.document.querySelector("input"); + input.focus(); + is(input, content.document.activeElement, "Input should be focused"); + is(input.value, "", "Check input content"); + is(input.selectionStart, 0, "Check position of input caret"); + }); +} + +async function withModifier(Input, modKey, mod, key) { + await dispatchKeyEvent(Input, modKey, "rawKeyDown", I.Modifier[mod]); + await dispatchKeyEvent(Input, key, "keyDown", I.Modifier[mod]); + await dispatchKeyEvent(Input, key, "keyUp", I.Modifier[mod]); + await dispatchKeyEvent(Input, modKey, "keyUp"); +} + +function dispatchKeyEvent(Input, key, type, modifiers = 0) { + info(`Send ${type} for key ${key}`); + return Input.dispatchKeyEvent({ + type, + modifiers, + windowsVirtualKeyCode: KEYCODES[key], + key, + }); +} + +function getInputContent() { + return SpecialPowers.spawn(gBrowser.selectedBrowser, [], function() { + const input = content.document.querySelector("input"); + return { value: input.value, caret: input.selectionStart }; + }); +} + +async function checkInputContent(expectedValue, expectedCaret) { + const { value, caret } = await getInputContent(); + is(value, expectedValue, "Check input content"); + is(caret, expectedCaret, "Check position of input caret"); +} + +function keyForPlatform() { + // TODO add cases for other key-combinations as the need arises + let primary = ctrl; + let primaryKey = "Control"; + if (isMac) { + primary = alt; + primaryKey = "Alt"; + } + return { primary, primaryKey }; +} + +async function sendTextKey(Input, key, modifiers = 0) { + await dispatchKeyEvent(Input, key, "keyDown", modifiers); + await dispatchKeyEvent(Input, key, "keyUp", modifiers); +} + +async function sendText(Input, text) { + for (const sym of text) { + await sendTextKey(Input, sym); + } +} + +async function sendRawKey(Input, key, modifiers = 0) { + await dispatchKeyEvent(Input, key, "rawKeyDown", modifiers); + await dispatchKeyEvent(Input, key, "keyUp", modifiers); +} + +async function checkBackspace(Input, expected, modifiers = 0) { + info("Send Backspace"); + await sendRawKey(Input, "Backspace", modifiers); + await checkInputContent(expected, expected.length); +} + +function resetInput(value = "") { + return SpecialPowers.spawn(gBrowser.selectedBrowser, [value], function(arg) { + const input = content.document.querySelector("input"); + input.value = arg; + input.focus(); + }); +} -- cgit v1.2.3