author | Maja Frydrychowicz <mjzffr@gmail.com> |
Fri, 18 Oct 2019 21:01:44 +0000 | |
changeset 498269 | 67c1068e06d4ca340a9563e521eae084f0410d38 |
parent 498268 | 29b831ba782de6ee59b707b829bf7cbd854ced74 |
child 498270 | 63cc54f123564df8124c048ce13b0fa5c5dcbdf0 |
push id | 36709 |
push user | aiakab@mozilla.com |
push date | Sat, 19 Oct 2019 09:53:21 +0000 |
treeherder | mozilla-central@b98968eb71d3 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | ato, remote-protocol-reviewers, jdescottes |
bugs | 1563206 |
milestone | 71.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/remote/doc/Testing.md +++ b/remote/doc/Testing.md @@ -27,17 +27,17 @@ Browser chrome tests -------------------- We also have a set of functional [browser chrome] tests located under _remote/test/browser_: % ./mach mochitest remote/test/browser/browser_cdp.js The functional tests will appear under the `M` (for _mochitest_) -category in the `bc` (_browser-chrome_) jobs on Treeherder. +category in the `remote` jobs on Treeherder. As the functional tests will sporadically pop up new Firefox application windows, a helpful tip is to run them in [headless mode]: % ./mach mochitest --headless remote/test/browser The `--headless` flag is equivalent to setting the `MOZ_HEADLESS`
--- a/remote/domains/parent/Input.jsm +++ b/remote/domains/parent/Input.jsm @@ -2,16 +2,17 @@ * 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"; var EXPORTED_SYMBOLS = ["Input"]; const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); + const { Domain } = ChromeUtils.import( "chrome://remote/content/domains/Domain.jsm" ); class Input extends Domain { // commands /** @@ -21,21 +22,26 @@ class Input extends Domain { * - autoRepeat (not supported) * - code (not supported) * - key * - isKeypad (not supported) * - location (not supported) * - modifiers * - text (not supported) * - type - * - unmodifiedTest (not supported) + * - unmodifiedText (not supported) * - windowsVirtualKeyCode + * - nativeVirtualKeyCode (not supported) + * - keyIdentifier (not supported) + * - isSystemKey (not supported) */ async dispatchKeyEvent(options) { + // missing code, text, unmodifiedText, autorepeat, location, iskeypad const { key, modifiers, type, windowsVirtualKeyCode } = options; + const { alt, ctrl, meta, shift } = Input.Modifier; let domType; if (type == "keyDown" || type == "rawKeyDown") { // 'rawKeyDown' is passed as type by puppeteer for all non-text keydown events: // See https://github.com/GoogleChrome/puppeteer/blob/2d99d85976dcb28cc6e3bad4b6a00cd61a67a2cf/lib/Input.js#L52 // For now we simply map rawKeyDown to keydown. domType = "keydown"; } else if (type == "keyUp" || type == "char") { @@ -57,75 +63,58 @@ class Input extends Domain { if (type == "char") { // type == "char" is used when doing `await page.keyboard.type( 'I’m a list' );` // the ’ character will be calling dispatchKeyEvent only once with type=char. EventUtils.synthesizeKey(key, {}, browserWindow); } else { // Non printable keys should be prefixed with `KEY_` const eventUtilsKey = key.length == 1 ? key : "KEY_" + key; - EventUtils.synthesizeKey( - eventUtilsKey, - { - keyCode: windowsVirtualKeyCode, - type: domType, - altKey: !!(modifiers & 1), - ctrlKey: !!(modifiers & 2), - metaKey: !!(modifiers & 4), - shiftKey: !!(modifiers & 8), - }, - browserWindow - ); + const eventInfo = { + keyCode: windowsVirtualKeyCode, + type: domType, + altKey: !!(modifiers & alt), + ctrlKey: !!(modifiers & ctrl), + metaKey: !!(modifiers & meta), + shiftKey: !!(modifiers & shift), + }; + EventUtils.synthesizeKey(eventUtilsKey, eventInfo, browserWindow); } await this.executeInChild("waitForContentEvent", eventId); } async dispatchMouseEvent({ type, button, x, y, modifiers, clickCount }) { + const { alt, ctrl, meta, shift } = Input.Modifier; + if (type == "mousePressed") { type = "mousedown"; } else if (type == "mouseReleased") { type = "mouseup"; } else if (type == "mouseMoved") { type = "mousemove"; } else { throw new Error(`Mouse type is not supported: ${type}`); } if (type === "mousedown" && button === "right") { type = "contextmenu"; } - if (button == undefined || button == "none" || button == "left") { - button = 0; - } else if (button == "middle") { - button = 1; - } else if (button == "right") { - button = 2; - } else if (button == "back") { - button = 3; - } else if (button == "forward") { - button = 4; - } else { - throw new Error(`Mouse button is not supported: ${button}`); - } - - // Gutenberg test packages/e2e-tests/specs/blocks/list.test.js: - // "can be created by converting multiple paragraphs" - // Works better with EventUtils, in order to make the Shift+Click to work + const buttonID = Input.Button[button] || Input.Button.left; const { browser } = this.session.target; const currentWindow = browser.ownerGlobal; const EventUtils = this._getEventUtils(currentWindow); EventUtils.synthesizeMouse(browser, x, y, { type, - button, + button: buttonID, clickCount: clickCount || 1, - altKey: !!(modifiers & 1), - ctrlKey: !!(modifiers & 2), - metaKey: !!(modifiers & 4), - shiftKey: !!(modifiers & 8), + altKey: !!(modifiers & alt), + ctrlKey: !!(modifiers & ctrl), + metaKey: !!(modifiers & meta), + shiftKey: !!(modifiers & shift), }); } /** * Memoized EventUtils getter. */ _getEventUtils(win) { if (!this._eventUtils) { @@ -138,8 +127,23 @@ class Input extends Domain { Services.scriptloader.loadSubScript( "chrome://remote/content/external/EventUtils.js", this._eventUtils ); } return this._eventUtils; } } + +Input.Button = { + left: 0, + middle: 1, + right: 2, + back: 3, + forward: 4, +}; + +Input.Modifier = { + alt: 1, + ctrl: 2, + meta: 4, + shift: 8, +};
--- a/remote/test/browser/browser.ini +++ b/remote/test/browser/browser.ini @@ -1,15 +1,16 @@ [DEFAULT] tags = remote subsuite = remote prefs = remote.enabled=true support-files = chrome-remote-interface.js doc_input_dispatchKeyEvent_race.html + doc_input_events.html doc_network_requestWillBeSent.html file_network_requestWillBeSent.js head.js [browser_cdp.js] [browser_input_dispatchKeyEvent.js] [browser_input_dispatchKeyEvent_race.js] [browser_input_dispatchMouseEvent.js]
--- a/remote/test/browser/browser_input_dispatchKeyEvent.js +++ b/remote/test/browser/browser_input_dispatchKeyEvent.js @@ -1,154 +1,433 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; -// Basic test for dispatch key event API with input type text +const { Input: I } = ChromeUtils.import( + "chrome://remote/content/domains/parent/Input.jsm" +); + +const { alt, ctrl, meta, shift } = I.Modifier; // Map of key codes used in this test. const KEYCODES = { - a: 65, - Backspace: 8, - h: 72, - H: 72, - AltLeft: 18, - ArrowLeft: 37, - ArrowRight: 39, + 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, }; -// Modifier for move forward shortcut is CTRL+RightArrow on Linux/Windows, ALT+RightArrow -// on Mac. const isMac = Services.appinfo.OS === "Darwin"; -const ALT_MODIFIER = 1; -const CTRL_MODIFIER = 2; -const ARROW_MODIFIER = isMac ? ALT_MODIFIER : CTRL_MODIFIER; -add_task(async function() { - // The selectionchange event was flagged behind dom.select_events.textcontrols.enabled, - // which is only true on Nightly and Local builds. Force the pref to true so that - // the test passes on all channels. See Bug 1309628 for more details. - info("Enable selectionchange events on input elements"); - await new Promise(resolve => { - const options = { - set: [["dom.select_events.textcontrols.enabled", true]], - }; - SpecialPowers.pushPrefEnv(options, resolve); - }); +const PAGE_URL = + "http://example.com/browser/remote/test/browser/doc_input_events.html"; - const { client, tab } = await setupForURL(toDataURL("<input>")); - is(gBrowser.selectedTab, tab, "Selected tab is the target tab"); - +add_task(async function testTypingPrintableCharacters() { + const { client } = await setupForInput(toDataURL("<input>")); const { Input } = client; - info("Focus the input on the page"); - await ContentTask.spawn(gBrowser.selectedBrowser, null, function() { - const input = content.document.querySelector("input"); - input.focus(); - is(input, content.document.activeElement, "Input should be focused"); - }); - await checkInputContent("", 0); - 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); + await teardown(client); +}); + +add_task(async function testArrowKeys() { + const { client } = await setupForInput(toDataURL("<input>")); + const { Input } = client; + + await sendText(Input, "hH’"); info("Send Left"); - await sendArrowKey(Input, "ArrowLeft"); + await sendRawKey(Input, "ArrowLeft"); await checkInputContent("hH’", 2); info("Write 'a'"); await sendTextKey(Input, "a"); await checkInputContent("hHa’", 3); info("Send Left"); - await sendArrowKey(Input, "ArrowLeft"); + await sendRawKey(Input, "ArrowLeft"); await checkInputContent("hHa’", 2); info("Send Left"); - await sendArrowKey(Input, "ArrowLeft"); + await sendRawKey(Input, "ArrowLeft"); await checkInputContent("hHa’", 1); info("Write 'a'"); await sendTextKey(Input, "a"); await checkInputContent("haHa’", 2); - info("Send ALT/CTRL + Right"); - await sendArrowKey(Input, "ArrowRight", ARROW_MODIFIER); + 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); + await teardown(client); +}); + +add_task(async function testBackspace() { + const { client } = await setupForInput(toDataURL("<input>")); + const { Input } = client; + + await sendText(Input, "haHa’"); + info("Delete every character in the input"); - await sendBackspace(Input, "haHa"); - await sendBackspace(Input, "haH"); - await sendBackspace(Input, "ha"); - await sendBackspace(Input, "h"); - await sendBackspace(Input, ""); + await checkBackspace(Input, "haHa"); + await checkBackspace(Input, "haH"); + await checkBackspace(Input, "ha"); + await checkBackspace(Input, "h"); + await checkBackspace(Input, ""); + + await teardown(client); +}); + +add_task(async function testShiftSelect() { + const { 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"); + + await teardown(client); +}); + +add_task(async function testSelectWord() { + const { 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 "); + + await teardown(client); +}); - await client.close(); - ok(true, "The client is closed"); +add_task(async function testSelectDelete() { + const { 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); + if (Services.appinfo.OS === "WINNT") { + 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); + const { value, caret } = await getInputContent(); + await dispatchKeyEvent(Input, "Meta", "keyUp"); + todo_is(value, "d4", "Meta + Backspace should delete line backward"); + todo_is(caret, 0, "Check position after Meta + Backspace"); + } - BrowserTestUtils.removeTab(tab); + await teardown(client); +}); + +add_task(async function testShiftEvents() { + const { client } = await setupForInput(PAGE_URL); + const { Input } = client; + await resetEvents(); - await RemoteAgent.close(); + 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 ContentTask.spawn(gBrowser.selectedBrowser, null, function() { + const input = content.document.querySelector("input"); + isnot(input, content.document.activeElement, "input should lose focus"); + }); + + await teardown(client); }); -async function sendTextKey(Input, key) { - await dispatchKeyEvent(Input, key, "keyDown"); - await dispatchKeyEvent(Input, key, "keyUp"); +add_task(async function testAltEvents() { + const { 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); + await teardown(client); +}); + +add_task(async function testControlEvents() { + const { 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); + await teardown(client); +}); + +add_task(async function testMetaEvents() { + if (!isMac) { + return; + } + const { client } = 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); + + await teardown(client); +}); + +add_task(async function testCtrlShiftArrows() { + const { client } = await setupForURL( + toDataURL('<select multiple size="3"><option>a<option>b<option>c</select>') + ); + const { Input } = client; + + await ContentTask.spawn(gBrowser.selectedBrowser, null, 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 ContentTask.spawn(gBrowser.selectedBrowser, null, 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"); + }); + await teardown(client); +}); + +add_task(async function testShiftClick() { + const { client } = await setupForURL(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]); + + await teardown(client); +}); + +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 sendArrowKey(Input, arrowKey, modifiers = 0) { - await dispatchKeyEvent(Input, arrowKey, "rawKeyDown", modifiers); - await dispatchKeyEvent(Input, arrowKey, "keyUp", modifiers); +async function setupForInput(url) { + const { client, tab } = await setupForURL(url); + info("Focus the input on the page"); + await ContentTask.spawn(gBrowser.selectedBrowser, null, function() { + const input = content.document.querySelector("input"); + input.focus(); + is(input, content.document.activeElement, "Input should be focused"); + }); + await checkInputContent("", 0); + return { client, tab }; +} + +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 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 checkInputContent(expectedValue, expectedCaret) { - const { value, caret } = await ContentTask.spawn( - gBrowser.selectedBrowser, - null, - function() { - const input = content.document.querySelector("input"); - return { value: input.value, caret: input.selectionStart }; - } - ); +function getInputContent() { + return ContentTask.spawn(gBrowser.selectedBrowser, null, function() { + const input = content.document.querySelector("input"); + return { value: input.value, caret: input.selectionStart }; + }); +} - is( - value, - expectedValue, - `The input value is correct ("${value}"="${expectedValue}")` - ); - is( - caret, - expectedCaret, - `The input caret has the correct index ("${caret}"="${expectedCaret}")` - ); +async function checkInputContent(expectedValue, expectedCaret) { + const { value, caret } = await getInputContent(); + is(value, expectedValue, "Check input content"); + is(caret, expectedCaret, "Check position of input caret"); +} + +// assuming doc_input_events.html +async function getEvents() { + const events = await ContentTask.spawn(gBrowser.selectedBrowser, null, () => { + return content.eval("allEvents"); + }); + info(`Events: ${JSON.stringify(events)}`); + return events; } -async function sendBackspace(Input, expected) { - info("Send Backspace"); - await dispatchKeyEvent(Input, "Backspace", "rawKeyDown"); - await dispatchKeyEvent(Input, "Backspace", "keyUp"); +// assuming doc_input_events.html +async function resetEvents() { + await ContentTask.spawn(gBrowser.selectedBrowser, null, () => { + content.eval("resetEvents()"); + const events = content.eval("allEvents"); + is(events.length, 0, "List of events should be empty"); + }); +} - await checkInputContent(expected, expected.length); +function resetInput(value = "") { + return ContentTask.spawn(gBrowser.selectedBrowser, value, function(arg) { + const input = content.document.querySelector("input"); + input.value = arg; + input.focus(); + }); } + +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}`); + } +}
--- a/remote/test/browser/browser_input_dispatchMouseEvent.js +++ b/remote/test/browser/browser_input_dispatchMouseEvent.js @@ -1,15 +1,15 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; add_task(async function testDispatchMouseEvent() { - const { client, tab } = await setupForURL(toDataURL("<div>foo</div>")); + const { client } = await setupForURL(toDataURL("<div>foo</div>")); const { Input } = client; await ContentTask.spawn(gBrowser.selectedBrowser, {}, function() { const div = content.document.querySelector("div"); this.mouseDownPromise = new Promise(resolve => { div.addEventListener("mousedown", resolve, { once: true }); }); @@ -47,15 +47,10 @@ add_task(async function testDispatchMous return this.mouseUpPromise; }); info("Waiting for DOM click event on the div"); await ContentTask.spawn(gBrowser.selectedBrowser, {}, function() { return this.clickPromise; }); - await client.close(); - ok(true, "The client is closed"); - - BrowserTestUtils.removeTab(tab); - - await RemoteAgent.close(); + await teardown(client); });
new file mode 100644 --- /dev/null +++ b/remote/test/browser/doc_input_events.html @@ -0,0 +1,148 @@ +<!doctype html> +<meta charset=utf-8> +<html> +<head> + <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>
--- a/remote/test/browser/head.js +++ b/remote/test/browser/head.js @@ -111,16 +111,17 @@ async function setup() { /** * Set up test environment by starting the remote agent, connecting * the CDP client over loopback, and creating a fresh tab to avoid * state bleedover from previous test. */ async function setupForURL(url) { const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + is(gBrowser.selectedTab, tab, "Selected tab is the target tab"); await RemoteAgent.listen(Services.io.newURI("http://localhost:9222")); const CDP = await getCDP(); const client = await CDP({ target(list) { // ensure we are debugging the right target, i.e. the requested URL return list.find(target => target.url == url); @@ -152,8 +153,20 @@ function toDataURL(src, doctype = "html" function getContentProperty(prop) { info(`Retrieve ${prop} on the content window`); return ContentTask.spawn( gBrowser.selectedBrowser, prop, _prop => content[_prop] ); } + +/** + * Close tabs, client, remote agent. + */ +async function teardown(client) { + await client.close(); + ok(true, "The client is closed"); + while (gBrowser.tabs.length > 1) { + gBrowser.removeCurrentTab(); + } + await RemoteAgent.close(); +}