summaryrefslogtreecommitdiffstats
path: root/remote/cdp/test/browser/input
diff options
context:
space:
mode:
Diffstat (limited to 'remote/cdp/test/browser/input')
-rw-r--r--remote/cdp/test/browser/input/browser.toml31
-rw-r--r--remote/cdp/test/browser/input/browser_dispatchKeyEvent.js169
-rw-r--r--remote/cdp/test/browser/input/browser_dispatchKeyEvent_events.js77
-rw-r--r--remote/cdp/test/browser/input/browser_dispatchKeyEvent_race.js92
-rw-r--r--remote/cdp/test/browser/input/browser_dispatchMouseEvent.js136
-rw-r--r--remote/cdp/test/browser/input/doc_dispatchKeyEvent_race.html28
-rw-r--r--remote/cdp/test/browser/input/doc_events.html148
-rw-r--r--remote/cdp/test/browser/input/head.js150
8 files changed, 831 insertions, 0 deletions
diff --git a/remote/cdp/test/browser/input/browser.toml b/remote/cdp/test/browser/input/browser.toml
new file mode 100644
index 0000000000..b103734fc0
--- /dev/null
+++ b/remote/cdp/test/browser/input/browser.toml
@@ -0,0 +1,31 @@
+[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",
+]
+skip-if = [
+ "display == 'wayland'" # Bug 1861933: Timestamp unreliable due to worker setup
+]
+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();
+ });
+}