diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /remote/cdp/test/browser/input | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'remote/cdp/test/browser/input')
-rw-r--r-- | remote/cdp/test/browser/input/browser.ini | 22 | ||||
-rw-r--r-- | remote/cdp/test/browser/input/browser_dispatchKeyEvent.js | 169 | ||||
-rw-r--r-- | remote/cdp/test/browser/input/browser_dispatchKeyEvent_events.js | 77 | ||||
-rw-r--r-- | remote/cdp/test/browser/input/browser_dispatchKeyEvent_race.js | 92 | ||||
-rw-r--r-- | remote/cdp/test/browser/input/browser_dispatchMouseEvent.js | 136 | ||||
-rw-r--r-- | remote/cdp/test/browser/input/doc_dispatchKeyEvent_race.html | 28 | ||||
-rw-r--r-- | remote/cdp/test/browser/input/doc_events.html | 148 | ||||
-rw-r--r-- | remote/cdp/test/browser/input/head.js | 150 |
8 files changed, 822 insertions, 0 deletions
diff --git a/remote/cdp/test/browser/input/browser.ini b/remote/cdp/test/browser/input/browser.ini new file mode 100644 index 0000000000..419d2ef8dc --- /dev/null +++ b/remote/cdp/test/browser/input/browser.ini @@ -0,0 +1,22 @@ +[DEFAULT] +tags = cdp +subsuite = remote +args = + --remote-debugging-port + --remote-allow-origins=null +prefs = # Bug 1600054: Make CDP Fission compatible + fission.bfcacheInParent=false + fission.webContentIsolationStrategy=0 +support-files = + !/remote/cdp/test/browser/chrome-remote-interface.js + !/remote/cdp/test/browser/head.js + head.js + doc_events.html + doc_dispatchKeyEvent_race.html + +[browser_dispatchKeyEvent.js] +[browser_dispatchKeyEvent_events.js] +https_first_disabled = true +[browser_dispatchKeyEvent_race.js] +https_first_disabled = true +[browser_dispatchMouseEvent.js] diff --git a/remote/cdp/test/browser/input/browser_dispatchKeyEvent.js b/remote/cdp/test/browser/input/browser_dispatchKeyEvent.js new file mode 100644 index 0000000000..e2b214503b --- /dev/null +++ b/remote/cdp/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("<input>")); + 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("<input>")); + 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"); + const modCode = AppInfo.isMac ? alt : ctrl; + const modKey = AppInfo.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("<input>")); + 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("<input>")); + 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("<input>")); + 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("<input>")); + 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 (AppInfo.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('<select multiple size="3"><option>a<option>b<option>c</select>') + ); + 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/cdp/test/browser/input/browser_dispatchKeyEvent_events.js b/remote/cdp/test/browser/input/browser_dispatchKeyEvent_events.js new file mode 100644 index 0000000000..195043019e --- /dev/null +++ b/remote/cdp/test/browser/input/browser_dispatchKeyEvent_events.js @@ -0,0 +1,77 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const PAGE_URL = + "https://example.com/browser/remote/cdp/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 (AppInfo.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 (!AppInfo.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); +}); diff --git a/remote/cdp/test/browser/input/browser_dispatchKeyEvent_race.js b/remote/cdp/test/browser/input/browser_dispatchKeyEvent_race.js new file mode 100644 index 0000000000..ee1cd5be39 --- /dev/null +++ b/remote/cdp/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 = + "https://example.com/browser/remote/cdp/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/cdp/test/browser/input/browser_dispatchMouseEvent.js b/remote/cdp/test/browser/input/browser_dispatchMouseEvent.js new file mode 100644 index 0000000000..9ab2b74a67 --- /dev/null +++ b/remote/cdp/test/browser/input/browser_dispatchMouseEvent.js @@ -0,0 +1,136 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const PAGE_URL = + "https://example.com/browser/remote/cdp/test/browser/input/doc_events.html"; + +add_task(async function testPressedReleasedAndClick({ client }) { + const { Input } = client; + + await loadURL(PAGE_URL); + await resetEvents(); + + info("Click the 'pointers' div."); + await Input.dispatchMouseEvent({ + type: "mousePressed", + x: 80, + y: 180, + }); + await Input.dispatchMouseEvent({ + type: "mouseReleased", + x: 80, + y: 180, + }); + + const events = await getEvents(); + ["mousedown", "mouseup", "click"].forEach((type, index) => { + info(`Checking properties for event ${type}`); + checkProperties({ type, button: 0 }, events[index]); + }); +}); + +add_task(async function testModifiers({ client }) { + const { Input } = client; + + await loadURL(PAGE_URL); + + const testable_modifiers = [ + [alt], + [ctrl], + [meta], + [shift], + [alt, meta], + [ctrl, shift], + [alt, ctrl, meta, shift], + ]; + + for (let modifiers of testable_modifiers) { + info(`MousePressed with modifier ${modifiers} on the 'pointers' div.`); + + await resetEvents(); + await Input.dispatchMouseEvent({ + type: "mousePressed", + x: 80, + y: 180, + modifiers: modifiers.reduce((previous, current) => previous | current), + }); + + const events = await getEvents(); + const expectedEvent = { + type: "mousedown", + button: 0, + altKey: modifiers.includes(alt), + ctrlKey: modifiers.includes(ctrl), + metaKey: modifiers.includes(meta), + shiftKey: modifiers.includes(shift), + }; + + checkProperties(expectedEvent, events[0]); + } +}); + +add_task(async function testClickCount({ client }) { + const { Input } = client; + + await loadURL(PAGE_URL); + + const testable_clickCounts = [ + { type: "click", clickCount: 1 }, + { type: "dblclick", clickCount: 2 }, + ]; + + for (const { clickCount, type } of testable_clickCounts) { + info(`MousePressed with clickCount ${clickCount} on the 'pointers' div.`); + + await resetEvents(); + await Input.dispatchMouseEvent({ + type: "mousePressed", + x: 80, + y: 180, + clickCount, + }); + await Input.dispatchMouseEvent({ + type: "mouseReleased", + x: 80, + y: 180, + clickCount, + }); + + const events = await getEvents(); + checkProperties({ type, button: 0 }, events[events.length - 1]); + } +}); + +add_task(async function testDispatchMouseEventAwaitClick({ client }) { + const { Input } = client; + + await setupForInput(PAGE_URL); + await loadURL( + toDataURL(` + <div onclick="setTimeout(() => result = 'clicked', 0)">foo</div> + `) + ); + + const { x, y } = await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [], + () => { + const div = content.document.querySelector("div"); + return div.getBoundingClientRect(); + } + ); + + await Input.dispatchMouseEvent({ type: "mousePressed", x, y }); + await Input.dispatchMouseEvent({ type: "mouseReleased", x, y }); + + const context = await enableRuntime(client); + const { result } = await evaluate( + client, + context.id, + () => globalThis.result + ); + + is(result.value, "clicked", "Awaited click event handler"); +}); diff --git a/remote/cdp/test/browser/input/doc_dispatchKeyEvent_race.html b/remote/cdp/test/browser/input/doc_dispatchKeyEvent_race.html new file mode 100644 index 0000000000..c761a224e6 --- /dev/null +++ b/remote/cdp/test/browser/input/doc_dispatchKeyEvent_race.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>Test page for dispatchKeyEvent race test</title> +</head> +<body> + <input type="text"> + <script type="text/javascript"> + window.testValue = "initial-value"; + + // Small helper to synchronously pause for a given delay, in ms. + function sleep(delay) { + const start = Date.now(); + while (Date.now() - start < delay) { + // Wait for the condition to fail. + } + } + + document.querySelector("input").addEventListener("input", () => { + // Block the execution synchronously for one second. + sleep(1000); + // Update the value that will be checked by the test. + window.testValue = document.querySelector("input").value; + }); + </script> +</body> +</html> diff --git a/remote/cdp/test/browser/input/doc_events.html b/remote/cdp/test/browser/input/doc_events.html new file mode 100644 index 0000000000..74df931233 --- /dev/null +++ b/remote/cdp/test/browser/input/doc_events.html @@ -0,0 +1,148 @@ +<!doctype html> +<html> +<head> + <meta charset="utf-8"> + <title>Input Events</title> + <style> + div { padding:0px; margin: 0px; } + #trackPointer { position: fixed; } + #resultContainer { width: 600px; height: 60px; } + .area { width: 100px; height: 50px; background-color: #ccc; } + .block { width: 5px; height: 5px; border: solid 1px red; } + #dragArea { position: relative; } + #dragTarget { position: absolute; top:22px; left:47px;} + </style> + <script> + "use strict"; + var allEvents = []; + function makeParagraph(message) { + let paragraph = document.createElement("p"); + paragraph.textContent = message; + return paragraph; + } + function displayMessage(message) { + let eventNode = document.getElementById("events"); + eventNode.innerHTML = "" + eventNode.appendChild(makeParagraph(message)); + } + + function appendMessage(message) { + document.getElementById("events").appendChild(makeParagraph(message)); + } + + /** + * Escape |key| if it's in a surrogate-half character range. + * + * Example: given "\ud83d" return "U+d83d". + * + * Otherwise JSON.stringify will convert it to U+FFFD (REPLACEMENT CHARACTER) + * when returning a value from executeScript, for example. + */ + function escapeSurrogateHalf(key) { + if (typeof key !== "undefined" && key.length === 1) { + var charCode = key.charCodeAt(0); + var highSurrogate = charCode >= 0xD800 && charCode <= 0xDBFF; + var surrogate = highSurrogate || (charCode >= 0xDC00 && charCode <= 0xDFFF); + if (surrogate) { + key = "U+" + charCode.toString(16); + } + } + return key; + } + + function recordKeyboardEvent(event) { + var key = escapeSurrogateHalf(event.key); + allEvents.push({ + "code": event.code, + "key": key, + "which": event.which, + "location": event.location, + "alt": event.altKey, + "ctrl": event.ctrlKey, + "meta": event.metaKey, + "shift": event.shiftKey, + "repeat": event.repeat, + "type": event.type + }); + appendMessage(event.type + " " + + "code: " + event.code + ", " + + "key: " + key + ", " + + "which: " + event.which + ", " + + "keyCode: " + event.keyCode); + } + function recordInputEvent(event) { + allEvents.push({ + "data": event.data, + "inputType": event.inputType, + "isComposing": event.isComposing, + }); + appendMessage("InputEvent " + + "data: " + event.data + ", " + + "inputType: " + event.inputType + ", " + + "isComposing: " + event.isComposing); + } + function recordPointerEvent(event) { + if (event.type === "contextmenu") { + event.preventDefault(); + } + allEvents.push({ + "type": event.type, + "button": event.button, + "buttons": event.buttons, + "pageX": event.pageX, + "pageY": event.pageY, + "ctrlKey": event.ctrlKey, + "metaKey": event.metaKey, + "altKey": event.altKey, + "shiftKey": event.shiftKey, + "target": event.target.id + }); + appendMessage(event.type + " " + + "pageX: " + event.pageX + ", " + + "pageY: " + event.pageY + ", " + + "button: " + event.button + ", " + + "buttons: " + event.buttons + ", " + + "ctrlKey: " + event.ctrlKey + ", " + + "altKey: " + event.altKey + ", " + + "metaKey: " + event.metaKey + ", " + + "shiftKey: " + event.shiftKey + ", " + + "target id: " + event.target.id); + } + function resetEvents() { + allEvents.length = 0; + displayMessage(""); + } + + document.addEventListener("DOMContentLoaded", function() { + let keyReporter = document.getElementById("keys"); + keyReporter.addEventListener("keyup", recordKeyboardEvent); + keyReporter.addEventListener("keypress", recordKeyboardEvent); + keyReporter.addEventListener("keydown", recordKeyboardEvent); + keyReporter.addEventListener("input", recordInputEvent); + + let mouseReporter = document.getElementById("pointers"); + mouseReporter.addEventListener("click", recordPointerEvent); + mouseReporter.addEventListener("dblclick", recordPointerEvent); + mouseReporter.addEventListener("mousedown", recordPointerEvent); + mouseReporter.addEventListener("mouseup", recordPointerEvent); + mouseReporter.addEventListener("contextmenu", recordPointerEvent); + }); + </script> +</head> +<body> + <div id="trackPointer" class="block"></div> + <div> + <h2>KeyReporter</h2> + <input type="text" id="keys" size="80"> + </div> + <div> + <h2>ClickReporter</h2> + <div id="pointers" class="area"> + </div> + </div> + <div id="resultContainer"> + <h2>Events</h2> + <div id="events"></div> + </div> +</body> +</html> diff --git a/remote/cdp/test/browser/input/head.js b/remote/cdp/test/browser/input/head.js new file mode 100644 index 0000000000..4076935802 --- /dev/null +++ b/remote/cdp/test/browser/input/head.js @@ -0,0 +1,150 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/remote/cdp/test/browser/head.js", + this +); + +const { Input: I } = ChromeUtils.importESModule( + "chrome://remote/content/cdp/domains/parent/Input.sys.mjs" +); +const { AppInfo } = ChromeUtils.importESModule( + "chrome://remote/content/shared/AppInfo.sys.mjs" +); + +const { alt, ctrl, meta, shift } = I.Modifier; + +// 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, + }); +} + +async function getEvents() { + const events = await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { + return content.wrappedJSObject.allEvents; + }); + info(`Events: ${JSON.stringify(events)}`); + return events; +} + +function getInputContent() { + return SpecialPowers.spawn(gBrowser.selectedBrowser, [], function () { + const input = content.document.querySelector("input"); + return { value: input.value, caret: input.selectionStart }; + }); +} + +function checkEvent(event, type, key, property, expectedValue) { + let expected = { type, key }; + expected[property] = expectedValue; + checkProperties(expected, event, "Event"); +} + +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 checkProperties(expectedObj, targetObj, message = "Compare objects") { + for (const prop in expectedObj) { + is(targetObj[prop], expectedObj[prop], message + `: check ${prop}`); + } +} + +function keyForPlatform() { + // TODO add cases for other key-combinations as the need arises + let primary = ctrl; + let primaryKey = "Control"; + if (AppInfo.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); +} + +async function resetEvents() { + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { + content.wrappedJSObject.resetEvents(); + const events = content.wrappedJSObject.allEvents; + is(events.length, 0, "List of events should be empty"); + }); +} + +function resetInput(value = "") { + return SpecialPowers.spawn(gBrowser.selectedBrowser, [value], function (arg) { + const input = content.document.querySelector("input"); + input.value = arg; + input.focus(); + }); +} |