Loading .eslintrc.js +0 −1 Original line number Diff line number Diff line Loading @@ -45,7 +45,6 @@ module.exports = { "overrides": [{ "files": [ "devtools/**", "testing/**", "toolkit/**", "tools/**", "uriloader/**", Loading .prettierignore +3 −1 Original line number Diff line number Diff line Loading @@ -40,7 +40,6 @@ toolkit/components/telemetry/datareporting-prefs.js toolkit/components/telemetry/healthreport-prefs.js # Ignore all top-level directories for now. testing/** toolkit/** tools/** uriloader/** Loading Loading @@ -81,6 +80,9 @@ devtools/server/** devtools/shared/** devtools/startup/** # Ignore testing pref files which aren't parsed normally. testing/profiles/**/user.js # Ignore CORS fixtures which require specific resource hashes. dom/security/test/sri/script* Loading testing/marionette/accessibility.js +46 −28 Original line number Diff line number Diff line Loading @@ -5,17 +5,22 @@ "use strict"; const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); const { XPCOMUtils } = ChromeUtils.import( "resource://gre/modules/XPCOMUtils.jsm" ); const {ElementNotAccessibleError} = ChromeUtils.import("chrome://marionette/content/error.js"); const { ElementNotAccessibleError } = ChromeUtils.import( "chrome://marionette/content/error.js" ); const { Log } = ChromeUtils.import("chrome://marionette/content/log.js"); XPCOMUtils.defineLazyGetter(this, "logger", Log.get); XPCOMUtils.defineLazyGetter(this, "service", () => { try { return Cc["@mozilla.org/accessibilityService;1"] .getService(Ci.nsIAccessibilityService); return Cc["@mozilla.org/accessibilityService;1"].getService( Ci.nsIAccessibilityService ); } catch (e) { logger.warn("Accessibility module is not present"); return undefined; Loading Loading @@ -81,7 +86,6 @@ accessibility.ActionableRoles = new Set([ "switch", ]); /** * Factory function that constructs a new {@code accessibility.Checks} * object with enforced strictness or not. Loading Loading @@ -131,8 +135,9 @@ accessibility.Checks = class { } // First, check if accessibility is ready. let docAcc = accessibility.service .getAccessibleFor(element.ownerDocument); let docAcc = accessibility.service.getAccessibleFor( element.ownerDocument ); let state = {}; docAcc.getState(state, {}); if ((state.value & Ci.nsIAccessibleStates.STATE_BUSY) == 0) { Loading Loading @@ -174,8 +179,9 @@ accessibility.Checks = class { }, }; Services.obs.addObserver(eventObserver, "accessible-event"); }).catch(() => this.error( "Element does not have an accessible object", element)); }).catch(() => this.error("Element does not have an accessible object", element) ); } /** Loading @@ -191,7 +197,8 @@ accessibility.Checks = class { */ isActionableRole(accessible) { return accessibility.ActionableRoles.has( accessibility.service.getStringRole(accessible.role)); accessibility.service.getStringRole(accessible.role) ); } /** Loading Loading @@ -302,10 +309,12 @@ accessibility.Checks = class { let message; if (visible && hiddenAccessibility) { message = "Element is not currently visible via the accessibility API " + message = "Element is not currently visible via the accessibility API " + "and may not be manipulated by it"; } else if (!visible && !hiddenAccessibility) { message = "Element is currently only visible via the accessibility API " + message = "Element is currently only visible via the accessibility API " + "and can be manipulated by it"; } this.error(message, element); Loading @@ -332,13 +341,17 @@ accessibility.Checks = class { let win = element.ownerGlobal; let disabledAccessibility = this.matchState( accessible, accessibility.State.Unavailable); let explorable = win.getComputedStyle(element) .getPropertyValue("pointer-events") !== "none"; accessible, accessibility.State.Unavailable ); let explorable = win.getComputedStyle(element).getPropertyValue("pointer-events") !== "none"; let message; if (!explorable && !disabledAccessibility) { message = "Element is enabled but is not explorable via the " + message = "Element is enabled but is not explorable via the " + "accessibility API"; } else if (enabled && disabledAccessibility) { message = "Element is enabled but disabled via the accessibility API"; Loading Loading @@ -369,7 +382,8 @@ accessibility.Checks = class { if (!this.hasActionCount(accessible)) { message = "Element does not support any accessible actions"; } else if (!this.isActionableRole(accessible)) { message = "Element does not have a correct accessibility role " + message = "Element does not have a correct accessibility role " + "and may not be manipulated via the accessibility API"; } else if (!this.hasValidName(accessible)) { message = "Element is missing an accessible name"; Loading Loading @@ -405,14 +419,18 @@ accessibility.Checks = class { return; } let selectedAccessibility = this.matchState(accessible, accessibility.State.Selected); let selectedAccessibility = this.matchState( accessible, accessibility.State.Selected ); let message; if (selected && !selectedAccessibility) { message = "Element is selected but not selected via the accessibility API"; message = "Element is selected but not selected via the accessibility API"; } else if (!selected && selectedAccessibility) { message = "Element is not selected but selected via the accessibility API"; message = "Element is not selected but selected via the accessibility API"; } this.error(message, element); } Loading testing/marionette/action.js +205 −151 Original line number Diff line number Diff line Loading @@ -10,7 +10,9 @@ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { assert } = ChromeUtils.import("chrome://marionette/content/assert.js"); const {element} = ChromeUtils.import("chrome://marionette/content/element.js"); const { element } = ChromeUtils.import( "chrome://marionette/content/element.js" ); const { InvalidArgumentError, MoveTargetOutOfBoundsError, Loading Loading @@ -54,10 +56,10 @@ const ACTIONS = { /** Map from normalized key value to UI Events modifier key name */ const MODIFIER_NAME_LOOKUP = { "Alt": "alt", "Shift": "shift", "Control": "ctrl", "Meta": "meta", Alt: "alt", Shift: "shift", Control: "ctrl", Meta: "meta", }; /** Map from raw key (codepoint) to normalized key value */ Loading Loading @@ -202,7 +204,7 @@ const KEY_CODE_LOOKUP = { "@": "Digit2", "#": "Digit3", "3": "Digit3", "$": "Digit4", $: "Digit4", "4": "Digit4", "%": "Digit5", "5": "Digit5", Loading Loading @@ -236,60 +238,60 @@ const KEY_CODE_LOOKUP = { "\uE016": "Insert", "<": "IntlBackslash", ">": "IntlBackslash", "A": "KeyA", "a": "KeyA", "B": "KeyB", "b": "KeyB", "C": "KeyC", "c": "KeyC", "D": "KeyD", "d": "KeyD", "E": "KeyE", "e": "KeyE", "F": "KeyF", "f": "KeyF", "G": "KeyG", "g": "KeyG", "H": "KeyH", "h": "KeyH", "I": "KeyI", "i": "KeyI", "J": "KeyJ", "j": "KeyJ", "K": "KeyK", "k": "KeyK", "L": "KeyL", "l": "KeyL", "M": "KeyM", "m": "KeyM", "N": "KeyN", "n": "KeyN", "O": "KeyO", "o": "KeyO", "P": "KeyP", "p": "KeyP", "Q": "KeyQ", "q": "KeyQ", "R": "KeyR", "r": "KeyR", "S": "KeyS", "s": "KeyS", "T": "KeyT", "t": "KeyT", "U": "KeyU", "u": "KeyU", "V": "KeyV", "v": "KeyV", "W": "KeyW", "w": "KeyW", "X": "KeyX", "x": "KeyX", "Y": "KeyY", "y": "KeyY", "Z": "KeyZ", "z": "KeyZ", A: "KeyA", a: "KeyA", B: "KeyB", b: "KeyB", C: "KeyC", c: "KeyC", D: "KeyD", d: "KeyD", E: "KeyE", e: "KeyE", F: "KeyF", f: "KeyF", G: "KeyG", g: "KeyG", H: "KeyH", h: "KeyH", I: "KeyI", i: "KeyI", J: "KeyJ", j: "KeyJ", K: "KeyK", k: "KeyK", L: "KeyL", l: "KeyL", M: "KeyM", m: "KeyM", N: "KeyN", n: "KeyN", O: "KeyO", o: "KeyO", P: "KeyP", p: "KeyP", Q: "KeyQ", q: "KeyQ", R: "KeyR", r: "KeyR", S: "KeyS", s: "KeyS", T: "KeyT", t: "KeyT", U: "KeyU", u: "KeyU", V: "KeyV", v: "KeyV", W: "KeyW", w: "KeyW", X: "KeyX", x: "KeyX", Y: "KeyY", y: "KeyY", Z: "KeyZ", z: "KeyZ", "-": "Minus", "_": "Minus", _: "Minus", "\uE01A": "Numpad0", "\uE05C": "Numpad0", "\uE01B": "Numpad1", Loading Loading @@ -323,7 +325,7 @@ const KEY_CODE_LOOKUP = { "\uE01F": "PageUp", ".": "Period", ">": "Period", "\"": "Quote", '"': "Quote", "'": "Quote", ":": "Semicolon", ";": "Semicolon", Loading Loading @@ -367,9 +369,11 @@ action.PointerOrigin.get = function(obj) { assert.in(name, this, pprint`Unknown pointer-move origin: ${obj}`); origin = this[name]; } else if (!element.isDOMElement(obj)) { throw new InvalidArgumentError("Expected 'origin' to be undefined, " + throw new InvalidArgumentError( "Expected 'origin' to be undefined, " + '"viewport", "pointer", ' + pprint`or an element, got: ${obj}`); pprint`or an element, got: ${obj}` ); } return origin; }; Loading Loading @@ -465,10 +469,13 @@ class InputState { assert.in(type, ACTIONS, pprint`Unknown action type: ${type}`); let name = type == "none" ? "Null" : capitalize(type); if (name == "Pointer") { if (!obj.pointerType && (!obj.parameters || !obj.parameters.pointerType)) { if ( !obj.pointerType && (!obj.parameters || !obj.parameters.pointerType) ) { throw new InvalidArgumentError( pprint`Expected obj to have pointerType, got ${obj}`); pprint`Expected obj to have pointerType, got ${obj}` ); } let pointerType = obj.pointerType || obj.parameters.pointerType; return new action.InputState[name](pointerType); Loading Loading @@ -511,7 +518,8 @@ action.InputState.Key = class Key extends InputState { throw new InvalidArgumentError( "Expected 'key' to be one of " + Object.keys(MODIFIER_NAME_LOOKUP) + pprint`, got ${key}`); pprint`, got ${key}` ); } } Loading Loading @@ -578,8 +586,10 @@ action.InputState.Pointer = class Pointer extends InputState { constructor(subtype) { super(); this.pressed = new Set(); assert.defined(subtype, pprint`Expected subtype to be defined, got ${subtype}`); assert.defined( subtype, pprint`Expected subtype to be defined, got ${subtype}` ); this.subtype = action.PointerType.get(subtype); this.x = 0; this.y = 0; Loading Loading @@ -687,13 +697,17 @@ action.Action = class { let subtype = actionItem.type; if (!subtypes.has(subtype)) { throw new InvalidArgumentError( `Unknown subtype for ${type} action: ${subtype}`); `Unknown subtype for ${type} action: ${subtype}` ); } let item = new action.Action(id, type, subtype); if (type === "pointer") { action.processPointerAction(id, action.PointerParameters.fromJSON(actionSequence.parameters), item); action.processPointerAction( id, action.PointerParameters.fromJSON(actionSequence.parameters), item ); } switch (item.subtype) { Loading @@ -703,35 +717,45 @@ action.Action = class { // TODO countGraphemes // TODO key.value could be a single code point like "\uE012" // (see rawKey) or "grapheme cluster" assert.string(key, assert.string( key, "Expected 'value' to be a string that represents single code point " + pprint`or grapheme cluster, got ${key}`); pprint`or grapheme cluster, got ${key}` ); item.value = key; break; case action.PointerDown: case action.PointerUp: assert.positiveInteger(actionItem.button, pprint`Expected 'button' (${actionItem.button}) to be >= 0`); assert.positiveInteger( actionItem.button, pprint`Expected 'button' (${actionItem.button}) to be >= 0` ); item.button = actionItem.button; break; case action.PointerMove: item.duration = actionItem.duration; if (typeof item.duration != "undefined") { assert.positiveInteger(item.duration, pprint`Expected 'duration' (${item.duration}) to be >= 0`); assert.positiveInteger( item.duration, pprint`Expected 'duration' (${item.duration}) to be >= 0` ); } item.origin = action.PointerOrigin.get(actionItem.origin); item.x = actionItem.x; if (typeof item.x != "undefined") { assert.integer(item.x, pprint`Expected 'x' (${item.x}) to be an Integer`); assert.integer( item.x, pprint`Expected 'x' (${item.x}) to be an Integer` ); } item.y = actionItem.y; if (typeof item.y != "undefined") { assert.integer(item.y, pprint`Expected 'y' (${item.y}) to be an Integer`); assert.integer( item.y, pprint`Expected 'y' (${item.y}) to be an Integer` ); } break; Loading @@ -743,7 +767,8 @@ action.Action = class { if (typeof item.duration != "undefined") { // eslint-disable-next-line assert.positiveInteger(item.duration, pprint`Expected 'duration' (${item.duration}) to be >= 0`); pprint`Expected 'duration' (${item.duration}) to be >= 0` ); } break; } Loading Loading @@ -773,8 +798,10 @@ action.Chain = class extends Array { * If <var>actions</var> is not an Array. */ static fromJSON(actions) { assert.array(actions, pprint`Expected 'actions' to be an array, got ${actions}`); assert.array( actions, pprint`Expected 'actions' to be an array, got ${actions}` ); let actionsByTick = new action.Chain(); for (let actionSequence of actions) { Loading @@ -783,7 +810,7 @@ action.Chain = class extends Array { let inputSourceActions = action.Sequence.fromJSON(actionSequence); for (let i = 0; i < inputSourceActions.length; i++) { // new tick if (actionsByTick.length < (i + 1)) { if (actionsByTick.length < i + 1) { actionsByTick.push([]); } actionsByTick[i].push(inputSourceActions[i]); Loading Loading @@ -825,14 +852,16 @@ action.Sequence = class extends Array { assert.array( actionItems, "Expected 'actionSequence.actions' to be an array, " + pprint`got ${actionSequence.actions}`); pprint`got ${actionSequence.actions}` ); if (!action.inputStateMap.has(id)) { action.inputStateMap.set(id, inputSourceState); } else if (!action.inputStateMap.get(id).is(inputSourceState)) { throw new InvalidArgumentError( `Expected ${id} to be mapped to ${inputSourceState}, ` + `got ${action.inputStateMap.get(id)}`); `got ${action.inputStateMap.get(id)}` ); } let actions = new action.Sequence(); Loading Loading @@ -893,20 +922,26 @@ action.PointerParameters = class { * <code>act.type</code> or <code>pointerParams.pointerType</code>. */ action.processPointerAction = function(id, pointerParams, act) { if (action.inputStateMap.has(id) && action.inputStateMap.get(id).type !== act.type) { if ( action.inputStateMap.has(id) && action.inputStateMap.get(id).type !== act.type ) { throw new InvalidArgumentError( `Expected 'id' ${id} to be mapped to InputState whose type is ` + action.inputStateMap.get(id).type + pprint` , got ${act.type}`); pprint` , got ${act.type}` ); } let pointerType = pointerParams.pointerType; if (action.inputStateMap.has(id) && action.inputStateMap.get(id).subtype !== pointerType) { if ( action.inputStateMap.has(id) && action.inputStateMap.get(id).subtype !== pointerType ) { throw new InvalidArgumentError( `Expected 'id' ${id} to be mapped to InputState whose subtype is ` + action.inputStateMap.get(id).subtype + pprint` , got ${pointerType}`); pprint` , got ${pointerType}` ); } act.pointerType = pointerParams.pointerType; }; Loading Loading @@ -993,7 +1028,8 @@ action.dispatch = function(chain, win, specCompatPointerOrigin = true) { await action.dispatchTickActions( tickActions, action.computeTickDuration(tickActions), win); win ); } })(); return chainEvents; Loading Loading @@ -1037,7 +1073,8 @@ action.dispatchTickActions = function(tickActions, tickDuration, win) { action.computeTickDuration = function(tickActions) { let max = 0; for (let a of tickActions) { let affectsWallClockTime = a.subtype == action.Pause || let affectsWallClockTime = a.subtype == action.Pause || (a.type == "pointer" && a.subtype == action.PointerMove); if (affectsWallClockTime && a.duration) { max = Math.max(a.duration, max); Loading @@ -1060,8 +1097,7 @@ action.computeTickDuration = function(tickActions) { * @return {Map.<string, number>} * x and y coordinates of pointer destination. */ action.computePointerDestination = function( a, inputState, center = undefined) { action.computePointerDestination = function(a, inputState, center = undefined) { let { x, y } = a; switch (a.origin) { case action.PointerOrigin.Viewport: Loading @@ -1078,7 +1114,7 @@ action.computePointerDestination = function( x += center.x; y += center.y; } return {"x": x, "y": y}; return { x, y }; }; /** Loading Loading @@ -1111,8 +1147,7 @@ function toEvents(tickDuration, win) { return dispatchPointerUp(a, inputState, win); case action.PointerMove: return dispatchPointerMove( a, inputState, tickDuration, win); return dispatchPointerMove(a, inputState, tickDuration, win); case action.PointerCancel: throw new UnsupportedOperationError(); Loading Loading @@ -1226,29 +1261,35 @@ function dispatchPointerDown(a, inputState, win) { event.DoubleClickTracker.resetClick(); } } else if (event.DoubleClickTracker.isClicked()) { mouseEvent = Object.assign({}, mouseEvent, {clickCount: 2}); mouseEvent = Object.assign({}, mouseEvent, { clickCount: 2 }); } event.synthesizeMouseAtPoint( inputState.x, inputState.y, mouseEvent, win); if (event.MouseButton.isSecondary(a.button) || mouseEvent.ctrlKey && Services.appinfo.OS !== "WINNT") { let contextMenuEvent = Object.assign({}, mouseEvent, {type: "contextmenu"}); win ); if ( event.MouseButton.isSecondary(a.button) || (mouseEvent.ctrlKey && Services.appinfo.OS !== "WINNT") ) { let contextMenuEvent = Object.assign({}, mouseEvent, { type: "contextmenu", }); event.synthesizeMouseAtPoint( inputState.x, inputState.y, contextMenuEvent, win); win ); } break; case action.PointerType.Pen: case action.PointerType.Touch: throw new UnsupportedOperationError("Only 'mouse' pointer type is supported"); throw new UnsupportedOperationError( "Only 'mouse' pointer type is supported" ); default: throw new TypeError(`Unknown pointer type: ${inputState.subtype}`); Loading Loading @@ -1286,16 +1327,21 @@ function dispatchPointerUp(a, inputState, win) { let mouseEvent = new action.Mouse("mouseup", a.button); mouseEvent.update(inputState); if (event.DoubleClickTracker.isClicked()) { mouseEvent = Object.assign({}, mouseEvent, {clickCount: 2}); mouseEvent = Object.assign({}, mouseEvent, { clickCount: 2 }); } event.synthesizeMouseAtPoint( inputState.x, inputState.y, mouseEvent, win); inputState.x, inputState.y, mouseEvent, win ); break; case action.PointerType.Pen: case action.PointerType.Touch: throw new UnsupportedOperationError("Only 'mouse' pointer type is supported"); throw new UnsupportedOperationError( "Only 'mouse' pointer type is supported" ); default: throw new TypeError(`Unknown pointer type: ${inputState.subtype}`); Loading Loading @@ -1342,10 +1388,12 @@ function dispatchPointerMove(a, inputState, tickDuration, win) { throw new MoveTargetOutOfBoundsError( `(${targetX}, ${targetY}) is out of bounds of viewport ` + `width (${win.innerWidth}) ` + `and height (${win.innerHeight})`); `and height (${win.innerHeight})` ); } const duration = typeof a.duration == "undefined" ? tickDuration : a.duration; const duration = typeof a.duration == "undefined" ? tickDuration : a.duration; if (duration === 0) { // move pointer to destination in one step performOnePointerMove(inputState, targetX, targetY, win); Loading @@ -1359,17 +1407,19 @@ function dispatchPointerMove(a, inputState, tickDuration, win) { let intermediatePointerEvents = (async () => { // wait |fps60| ms before performing first incremental pointer move await new Promise(resolveTimer => timer.initWithCallback(resolveTimer, fps60, ONE_SHOT)); timer.initWithCallback(resolveTimer, fps60, ONE_SHOT) ); let durationRatio = Math.floor(Date.now() - start) / duration; const epsilon = fps60 / duration / 10; while ((1 - durationRatio) > epsilon) { while (1 - durationRatio > epsilon) { let x = Math.floor(durationRatio * distanceX + startX); let y = Math.floor(durationRatio * distanceY + startY); performOnePointerMove(inputState, x, y, win); // wait |fps60| ms before performing next pointer move await new Promise(resolveTimer => timer.initWithCallback(resolveTimer, fps60, ONE_SHOT)); timer.initWithCallback(resolveTimer, fps60, ONE_SHOT) ); durationRatio = Math.floor(Date.now() - start) / duration; } Loading @@ -1377,10 +1427,12 @@ function dispatchPointerMove(a, inputState, tickDuration, win) { // perform last pointer move after all incremental moves are resolved and // durationRatio is close enough to 1 intermediatePointerEvents.then(() => { intermediatePointerEvents .then(() => { performOnePointerMove(inputState, targetX, targetY, win); resolve(); }).catch(err => { }) .catch(err => { reject(err); }); }); Loading @@ -1401,7 +1453,9 @@ function performOnePointerMove(inputState, targetX, targetY, win) { case action.PointerType.Pen: case action.PointerType.Touch: throw new UnsupportedOperationError("Only 'mouse' pointer type is supported"); throw new UnsupportedOperationError( "Only 'mouse' pointer type is supported" ); default: throw new TypeError(`Unknown pointer type: ${inputState.subtype}`); Loading testing/marionette/addon.js +18 −6 Original line number Diff line number Diff line Loading @@ -4,10 +4,16 @@ "use strict"; const {AddonManager} = ChromeUtils.import("resource://gre/modules/AddonManager.jsm"); const {FileUtils} = ChromeUtils.import("resource://gre/modules/FileUtils.jsm"); const { AddonManager } = ChromeUtils.import( "resource://gre/modules/AddonManager.jsm" ); const { FileUtils } = ChromeUtils.import( "resource://gre/modules/FileUtils.jsm" ); const {UnknownError} = ChromeUtils.import("chrome://marionette/content/error.js"); const { UnknownError } = ChromeUtils.import( "chrome://marionette/content/error.js" ); this.EXPORTED_SYMBOLS = ["Addon"]; Loading @@ -21,7 +27,9 @@ const ERRORS = { }; async function installAddon(file) { let install = await AddonManager.getInstallForFile(file, null, {source: "internal"}); let install = await AddonManager.getInstallForFile(file, null, { source: "internal", }); if (install.error) { throw new UnknownError(ERRORS[install.error]); Loading Loading @@ -76,7 +84,9 @@ class Addon { } } catch (e) { throw new UnknownError( `Could not install add-on: ${path}: ${e.message}`, e); `Could not install add-on: ${path}: ${e.message}`, e ); } return addon.id; Loading Loading @@ -104,7 +114,9 @@ class Addon { onOperationCancelled: addon => { if (addon.id === candidate.id) { AddonManager.removeAddonListener(listener); throw new UnknownError(`Uninstall of ${candidate.id} has been canceled`); throw new UnknownError( `Uninstall of ${candidate.id} has been canceled` ); } }, Loading Loading
.eslintrc.js +0 −1 Original line number Diff line number Diff line Loading @@ -45,7 +45,6 @@ module.exports = { "overrides": [{ "files": [ "devtools/**", "testing/**", "toolkit/**", "tools/**", "uriloader/**", Loading
.prettierignore +3 −1 Original line number Diff line number Diff line Loading @@ -40,7 +40,6 @@ toolkit/components/telemetry/datareporting-prefs.js toolkit/components/telemetry/healthreport-prefs.js # Ignore all top-level directories for now. testing/** toolkit/** tools/** uriloader/** Loading Loading @@ -81,6 +80,9 @@ devtools/server/** devtools/shared/** devtools/startup/** # Ignore testing pref files which aren't parsed normally. testing/profiles/**/user.js # Ignore CORS fixtures which require specific resource hashes. dom/security/test/sri/script* Loading
testing/marionette/accessibility.js +46 −28 Original line number Diff line number Diff line Loading @@ -5,17 +5,22 @@ "use strict"; const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); const { XPCOMUtils } = ChromeUtils.import( "resource://gre/modules/XPCOMUtils.jsm" ); const {ElementNotAccessibleError} = ChromeUtils.import("chrome://marionette/content/error.js"); const { ElementNotAccessibleError } = ChromeUtils.import( "chrome://marionette/content/error.js" ); const { Log } = ChromeUtils.import("chrome://marionette/content/log.js"); XPCOMUtils.defineLazyGetter(this, "logger", Log.get); XPCOMUtils.defineLazyGetter(this, "service", () => { try { return Cc["@mozilla.org/accessibilityService;1"] .getService(Ci.nsIAccessibilityService); return Cc["@mozilla.org/accessibilityService;1"].getService( Ci.nsIAccessibilityService ); } catch (e) { logger.warn("Accessibility module is not present"); return undefined; Loading Loading @@ -81,7 +86,6 @@ accessibility.ActionableRoles = new Set([ "switch", ]); /** * Factory function that constructs a new {@code accessibility.Checks} * object with enforced strictness or not. Loading Loading @@ -131,8 +135,9 @@ accessibility.Checks = class { } // First, check if accessibility is ready. let docAcc = accessibility.service .getAccessibleFor(element.ownerDocument); let docAcc = accessibility.service.getAccessibleFor( element.ownerDocument ); let state = {}; docAcc.getState(state, {}); if ((state.value & Ci.nsIAccessibleStates.STATE_BUSY) == 0) { Loading Loading @@ -174,8 +179,9 @@ accessibility.Checks = class { }, }; Services.obs.addObserver(eventObserver, "accessible-event"); }).catch(() => this.error( "Element does not have an accessible object", element)); }).catch(() => this.error("Element does not have an accessible object", element) ); } /** Loading @@ -191,7 +197,8 @@ accessibility.Checks = class { */ isActionableRole(accessible) { return accessibility.ActionableRoles.has( accessibility.service.getStringRole(accessible.role)); accessibility.service.getStringRole(accessible.role) ); } /** Loading Loading @@ -302,10 +309,12 @@ accessibility.Checks = class { let message; if (visible && hiddenAccessibility) { message = "Element is not currently visible via the accessibility API " + message = "Element is not currently visible via the accessibility API " + "and may not be manipulated by it"; } else if (!visible && !hiddenAccessibility) { message = "Element is currently only visible via the accessibility API " + message = "Element is currently only visible via the accessibility API " + "and can be manipulated by it"; } this.error(message, element); Loading @@ -332,13 +341,17 @@ accessibility.Checks = class { let win = element.ownerGlobal; let disabledAccessibility = this.matchState( accessible, accessibility.State.Unavailable); let explorable = win.getComputedStyle(element) .getPropertyValue("pointer-events") !== "none"; accessible, accessibility.State.Unavailable ); let explorable = win.getComputedStyle(element).getPropertyValue("pointer-events") !== "none"; let message; if (!explorable && !disabledAccessibility) { message = "Element is enabled but is not explorable via the " + message = "Element is enabled but is not explorable via the " + "accessibility API"; } else if (enabled && disabledAccessibility) { message = "Element is enabled but disabled via the accessibility API"; Loading Loading @@ -369,7 +382,8 @@ accessibility.Checks = class { if (!this.hasActionCount(accessible)) { message = "Element does not support any accessible actions"; } else if (!this.isActionableRole(accessible)) { message = "Element does not have a correct accessibility role " + message = "Element does not have a correct accessibility role " + "and may not be manipulated via the accessibility API"; } else if (!this.hasValidName(accessible)) { message = "Element is missing an accessible name"; Loading Loading @@ -405,14 +419,18 @@ accessibility.Checks = class { return; } let selectedAccessibility = this.matchState(accessible, accessibility.State.Selected); let selectedAccessibility = this.matchState( accessible, accessibility.State.Selected ); let message; if (selected && !selectedAccessibility) { message = "Element is selected but not selected via the accessibility API"; message = "Element is selected but not selected via the accessibility API"; } else if (!selected && selectedAccessibility) { message = "Element is not selected but selected via the accessibility API"; message = "Element is not selected but selected via the accessibility API"; } this.error(message, element); } Loading
testing/marionette/action.js +205 −151 Original line number Diff line number Diff line Loading @@ -10,7 +10,9 @@ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { assert } = ChromeUtils.import("chrome://marionette/content/assert.js"); const {element} = ChromeUtils.import("chrome://marionette/content/element.js"); const { element } = ChromeUtils.import( "chrome://marionette/content/element.js" ); const { InvalidArgumentError, MoveTargetOutOfBoundsError, Loading Loading @@ -54,10 +56,10 @@ const ACTIONS = { /** Map from normalized key value to UI Events modifier key name */ const MODIFIER_NAME_LOOKUP = { "Alt": "alt", "Shift": "shift", "Control": "ctrl", "Meta": "meta", Alt: "alt", Shift: "shift", Control: "ctrl", Meta: "meta", }; /** Map from raw key (codepoint) to normalized key value */ Loading Loading @@ -202,7 +204,7 @@ const KEY_CODE_LOOKUP = { "@": "Digit2", "#": "Digit3", "3": "Digit3", "$": "Digit4", $: "Digit4", "4": "Digit4", "%": "Digit5", "5": "Digit5", Loading Loading @@ -236,60 +238,60 @@ const KEY_CODE_LOOKUP = { "\uE016": "Insert", "<": "IntlBackslash", ">": "IntlBackslash", "A": "KeyA", "a": "KeyA", "B": "KeyB", "b": "KeyB", "C": "KeyC", "c": "KeyC", "D": "KeyD", "d": "KeyD", "E": "KeyE", "e": "KeyE", "F": "KeyF", "f": "KeyF", "G": "KeyG", "g": "KeyG", "H": "KeyH", "h": "KeyH", "I": "KeyI", "i": "KeyI", "J": "KeyJ", "j": "KeyJ", "K": "KeyK", "k": "KeyK", "L": "KeyL", "l": "KeyL", "M": "KeyM", "m": "KeyM", "N": "KeyN", "n": "KeyN", "O": "KeyO", "o": "KeyO", "P": "KeyP", "p": "KeyP", "Q": "KeyQ", "q": "KeyQ", "R": "KeyR", "r": "KeyR", "S": "KeyS", "s": "KeyS", "T": "KeyT", "t": "KeyT", "U": "KeyU", "u": "KeyU", "V": "KeyV", "v": "KeyV", "W": "KeyW", "w": "KeyW", "X": "KeyX", "x": "KeyX", "Y": "KeyY", "y": "KeyY", "Z": "KeyZ", "z": "KeyZ", A: "KeyA", a: "KeyA", B: "KeyB", b: "KeyB", C: "KeyC", c: "KeyC", D: "KeyD", d: "KeyD", E: "KeyE", e: "KeyE", F: "KeyF", f: "KeyF", G: "KeyG", g: "KeyG", H: "KeyH", h: "KeyH", I: "KeyI", i: "KeyI", J: "KeyJ", j: "KeyJ", K: "KeyK", k: "KeyK", L: "KeyL", l: "KeyL", M: "KeyM", m: "KeyM", N: "KeyN", n: "KeyN", O: "KeyO", o: "KeyO", P: "KeyP", p: "KeyP", Q: "KeyQ", q: "KeyQ", R: "KeyR", r: "KeyR", S: "KeyS", s: "KeyS", T: "KeyT", t: "KeyT", U: "KeyU", u: "KeyU", V: "KeyV", v: "KeyV", W: "KeyW", w: "KeyW", X: "KeyX", x: "KeyX", Y: "KeyY", y: "KeyY", Z: "KeyZ", z: "KeyZ", "-": "Minus", "_": "Minus", _: "Minus", "\uE01A": "Numpad0", "\uE05C": "Numpad0", "\uE01B": "Numpad1", Loading Loading @@ -323,7 +325,7 @@ const KEY_CODE_LOOKUP = { "\uE01F": "PageUp", ".": "Period", ">": "Period", "\"": "Quote", '"': "Quote", "'": "Quote", ":": "Semicolon", ";": "Semicolon", Loading Loading @@ -367,9 +369,11 @@ action.PointerOrigin.get = function(obj) { assert.in(name, this, pprint`Unknown pointer-move origin: ${obj}`); origin = this[name]; } else if (!element.isDOMElement(obj)) { throw new InvalidArgumentError("Expected 'origin' to be undefined, " + throw new InvalidArgumentError( "Expected 'origin' to be undefined, " + '"viewport", "pointer", ' + pprint`or an element, got: ${obj}`); pprint`or an element, got: ${obj}` ); } return origin; }; Loading Loading @@ -465,10 +469,13 @@ class InputState { assert.in(type, ACTIONS, pprint`Unknown action type: ${type}`); let name = type == "none" ? "Null" : capitalize(type); if (name == "Pointer") { if (!obj.pointerType && (!obj.parameters || !obj.parameters.pointerType)) { if ( !obj.pointerType && (!obj.parameters || !obj.parameters.pointerType) ) { throw new InvalidArgumentError( pprint`Expected obj to have pointerType, got ${obj}`); pprint`Expected obj to have pointerType, got ${obj}` ); } let pointerType = obj.pointerType || obj.parameters.pointerType; return new action.InputState[name](pointerType); Loading Loading @@ -511,7 +518,8 @@ action.InputState.Key = class Key extends InputState { throw new InvalidArgumentError( "Expected 'key' to be one of " + Object.keys(MODIFIER_NAME_LOOKUP) + pprint`, got ${key}`); pprint`, got ${key}` ); } } Loading Loading @@ -578,8 +586,10 @@ action.InputState.Pointer = class Pointer extends InputState { constructor(subtype) { super(); this.pressed = new Set(); assert.defined(subtype, pprint`Expected subtype to be defined, got ${subtype}`); assert.defined( subtype, pprint`Expected subtype to be defined, got ${subtype}` ); this.subtype = action.PointerType.get(subtype); this.x = 0; this.y = 0; Loading Loading @@ -687,13 +697,17 @@ action.Action = class { let subtype = actionItem.type; if (!subtypes.has(subtype)) { throw new InvalidArgumentError( `Unknown subtype for ${type} action: ${subtype}`); `Unknown subtype for ${type} action: ${subtype}` ); } let item = new action.Action(id, type, subtype); if (type === "pointer") { action.processPointerAction(id, action.PointerParameters.fromJSON(actionSequence.parameters), item); action.processPointerAction( id, action.PointerParameters.fromJSON(actionSequence.parameters), item ); } switch (item.subtype) { Loading @@ -703,35 +717,45 @@ action.Action = class { // TODO countGraphemes // TODO key.value could be a single code point like "\uE012" // (see rawKey) or "grapheme cluster" assert.string(key, assert.string( key, "Expected 'value' to be a string that represents single code point " + pprint`or grapheme cluster, got ${key}`); pprint`or grapheme cluster, got ${key}` ); item.value = key; break; case action.PointerDown: case action.PointerUp: assert.positiveInteger(actionItem.button, pprint`Expected 'button' (${actionItem.button}) to be >= 0`); assert.positiveInteger( actionItem.button, pprint`Expected 'button' (${actionItem.button}) to be >= 0` ); item.button = actionItem.button; break; case action.PointerMove: item.duration = actionItem.duration; if (typeof item.duration != "undefined") { assert.positiveInteger(item.duration, pprint`Expected 'duration' (${item.duration}) to be >= 0`); assert.positiveInteger( item.duration, pprint`Expected 'duration' (${item.duration}) to be >= 0` ); } item.origin = action.PointerOrigin.get(actionItem.origin); item.x = actionItem.x; if (typeof item.x != "undefined") { assert.integer(item.x, pprint`Expected 'x' (${item.x}) to be an Integer`); assert.integer( item.x, pprint`Expected 'x' (${item.x}) to be an Integer` ); } item.y = actionItem.y; if (typeof item.y != "undefined") { assert.integer(item.y, pprint`Expected 'y' (${item.y}) to be an Integer`); assert.integer( item.y, pprint`Expected 'y' (${item.y}) to be an Integer` ); } break; Loading @@ -743,7 +767,8 @@ action.Action = class { if (typeof item.duration != "undefined") { // eslint-disable-next-line assert.positiveInteger(item.duration, pprint`Expected 'duration' (${item.duration}) to be >= 0`); pprint`Expected 'duration' (${item.duration}) to be >= 0` ); } break; } Loading Loading @@ -773,8 +798,10 @@ action.Chain = class extends Array { * If <var>actions</var> is not an Array. */ static fromJSON(actions) { assert.array(actions, pprint`Expected 'actions' to be an array, got ${actions}`); assert.array( actions, pprint`Expected 'actions' to be an array, got ${actions}` ); let actionsByTick = new action.Chain(); for (let actionSequence of actions) { Loading @@ -783,7 +810,7 @@ action.Chain = class extends Array { let inputSourceActions = action.Sequence.fromJSON(actionSequence); for (let i = 0; i < inputSourceActions.length; i++) { // new tick if (actionsByTick.length < (i + 1)) { if (actionsByTick.length < i + 1) { actionsByTick.push([]); } actionsByTick[i].push(inputSourceActions[i]); Loading Loading @@ -825,14 +852,16 @@ action.Sequence = class extends Array { assert.array( actionItems, "Expected 'actionSequence.actions' to be an array, " + pprint`got ${actionSequence.actions}`); pprint`got ${actionSequence.actions}` ); if (!action.inputStateMap.has(id)) { action.inputStateMap.set(id, inputSourceState); } else if (!action.inputStateMap.get(id).is(inputSourceState)) { throw new InvalidArgumentError( `Expected ${id} to be mapped to ${inputSourceState}, ` + `got ${action.inputStateMap.get(id)}`); `got ${action.inputStateMap.get(id)}` ); } let actions = new action.Sequence(); Loading Loading @@ -893,20 +922,26 @@ action.PointerParameters = class { * <code>act.type</code> or <code>pointerParams.pointerType</code>. */ action.processPointerAction = function(id, pointerParams, act) { if (action.inputStateMap.has(id) && action.inputStateMap.get(id).type !== act.type) { if ( action.inputStateMap.has(id) && action.inputStateMap.get(id).type !== act.type ) { throw new InvalidArgumentError( `Expected 'id' ${id} to be mapped to InputState whose type is ` + action.inputStateMap.get(id).type + pprint` , got ${act.type}`); pprint` , got ${act.type}` ); } let pointerType = pointerParams.pointerType; if (action.inputStateMap.has(id) && action.inputStateMap.get(id).subtype !== pointerType) { if ( action.inputStateMap.has(id) && action.inputStateMap.get(id).subtype !== pointerType ) { throw new InvalidArgumentError( `Expected 'id' ${id} to be mapped to InputState whose subtype is ` + action.inputStateMap.get(id).subtype + pprint` , got ${pointerType}`); pprint` , got ${pointerType}` ); } act.pointerType = pointerParams.pointerType; }; Loading Loading @@ -993,7 +1028,8 @@ action.dispatch = function(chain, win, specCompatPointerOrigin = true) { await action.dispatchTickActions( tickActions, action.computeTickDuration(tickActions), win); win ); } })(); return chainEvents; Loading Loading @@ -1037,7 +1073,8 @@ action.dispatchTickActions = function(tickActions, tickDuration, win) { action.computeTickDuration = function(tickActions) { let max = 0; for (let a of tickActions) { let affectsWallClockTime = a.subtype == action.Pause || let affectsWallClockTime = a.subtype == action.Pause || (a.type == "pointer" && a.subtype == action.PointerMove); if (affectsWallClockTime && a.duration) { max = Math.max(a.duration, max); Loading @@ -1060,8 +1097,7 @@ action.computeTickDuration = function(tickActions) { * @return {Map.<string, number>} * x and y coordinates of pointer destination. */ action.computePointerDestination = function( a, inputState, center = undefined) { action.computePointerDestination = function(a, inputState, center = undefined) { let { x, y } = a; switch (a.origin) { case action.PointerOrigin.Viewport: Loading @@ -1078,7 +1114,7 @@ action.computePointerDestination = function( x += center.x; y += center.y; } return {"x": x, "y": y}; return { x, y }; }; /** Loading Loading @@ -1111,8 +1147,7 @@ function toEvents(tickDuration, win) { return dispatchPointerUp(a, inputState, win); case action.PointerMove: return dispatchPointerMove( a, inputState, tickDuration, win); return dispatchPointerMove(a, inputState, tickDuration, win); case action.PointerCancel: throw new UnsupportedOperationError(); Loading Loading @@ -1226,29 +1261,35 @@ function dispatchPointerDown(a, inputState, win) { event.DoubleClickTracker.resetClick(); } } else if (event.DoubleClickTracker.isClicked()) { mouseEvent = Object.assign({}, mouseEvent, {clickCount: 2}); mouseEvent = Object.assign({}, mouseEvent, { clickCount: 2 }); } event.synthesizeMouseAtPoint( inputState.x, inputState.y, mouseEvent, win); if (event.MouseButton.isSecondary(a.button) || mouseEvent.ctrlKey && Services.appinfo.OS !== "WINNT") { let contextMenuEvent = Object.assign({}, mouseEvent, {type: "contextmenu"}); win ); if ( event.MouseButton.isSecondary(a.button) || (mouseEvent.ctrlKey && Services.appinfo.OS !== "WINNT") ) { let contextMenuEvent = Object.assign({}, mouseEvent, { type: "contextmenu", }); event.synthesizeMouseAtPoint( inputState.x, inputState.y, contextMenuEvent, win); win ); } break; case action.PointerType.Pen: case action.PointerType.Touch: throw new UnsupportedOperationError("Only 'mouse' pointer type is supported"); throw new UnsupportedOperationError( "Only 'mouse' pointer type is supported" ); default: throw new TypeError(`Unknown pointer type: ${inputState.subtype}`); Loading Loading @@ -1286,16 +1327,21 @@ function dispatchPointerUp(a, inputState, win) { let mouseEvent = new action.Mouse("mouseup", a.button); mouseEvent.update(inputState); if (event.DoubleClickTracker.isClicked()) { mouseEvent = Object.assign({}, mouseEvent, {clickCount: 2}); mouseEvent = Object.assign({}, mouseEvent, { clickCount: 2 }); } event.synthesizeMouseAtPoint( inputState.x, inputState.y, mouseEvent, win); inputState.x, inputState.y, mouseEvent, win ); break; case action.PointerType.Pen: case action.PointerType.Touch: throw new UnsupportedOperationError("Only 'mouse' pointer type is supported"); throw new UnsupportedOperationError( "Only 'mouse' pointer type is supported" ); default: throw new TypeError(`Unknown pointer type: ${inputState.subtype}`); Loading Loading @@ -1342,10 +1388,12 @@ function dispatchPointerMove(a, inputState, tickDuration, win) { throw new MoveTargetOutOfBoundsError( `(${targetX}, ${targetY}) is out of bounds of viewport ` + `width (${win.innerWidth}) ` + `and height (${win.innerHeight})`); `and height (${win.innerHeight})` ); } const duration = typeof a.duration == "undefined" ? tickDuration : a.duration; const duration = typeof a.duration == "undefined" ? tickDuration : a.duration; if (duration === 0) { // move pointer to destination in one step performOnePointerMove(inputState, targetX, targetY, win); Loading @@ -1359,17 +1407,19 @@ function dispatchPointerMove(a, inputState, tickDuration, win) { let intermediatePointerEvents = (async () => { // wait |fps60| ms before performing first incremental pointer move await new Promise(resolveTimer => timer.initWithCallback(resolveTimer, fps60, ONE_SHOT)); timer.initWithCallback(resolveTimer, fps60, ONE_SHOT) ); let durationRatio = Math.floor(Date.now() - start) / duration; const epsilon = fps60 / duration / 10; while ((1 - durationRatio) > epsilon) { while (1 - durationRatio > epsilon) { let x = Math.floor(durationRatio * distanceX + startX); let y = Math.floor(durationRatio * distanceY + startY); performOnePointerMove(inputState, x, y, win); // wait |fps60| ms before performing next pointer move await new Promise(resolveTimer => timer.initWithCallback(resolveTimer, fps60, ONE_SHOT)); timer.initWithCallback(resolveTimer, fps60, ONE_SHOT) ); durationRatio = Math.floor(Date.now() - start) / duration; } Loading @@ -1377,10 +1427,12 @@ function dispatchPointerMove(a, inputState, tickDuration, win) { // perform last pointer move after all incremental moves are resolved and // durationRatio is close enough to 1 intermediatePointerEvents.then(() => { intermediatePointerEvents .then(() => { performOnePointerMove(inputState, targetX, targetY, win); resolve(); }).catch(err => { }) .catch(err => { reject(err); }); }); Loading @@ -1401,7 +1453,9 @@ function performOnePointerMove(inputState, targetX, targetY, win) { case action.PointerType.Pen: case action.PointerType.Touch: throw new UnsupportedOperationError("Only 'mouse' pointer type is supported"); throw new UnsupportedOperationError( "Only 'mouse' pointer type is supported" ); default: throw new TypeError(`Unknown pointer type: ${inputState.subtype}`); Loading
testing/marionette/addon.js +18 −6 Original line number Diff line number Diff line Loading @@ -4,10 +4,16 @@ "use strict"; const {AddonManager} = ChromeUtils.import("resource://gre/modules/AddonManager.jsm"); const {FileUtils} = ChromeUtils.import("resource://gre/modules/FileUtils.jsm"); const { AddonManager } = ChromeUtils.import( "resource://gre/modules/AddonManager.jsm" ); const { FileUtils } = ChromeUtils.import( "resource://gre/modules/FileUtils.jsm" ); const {UnknownError} = ChromeUtils.import("chrome://marionette/content/error.js"); const { UnknownError } = ChromeUtils.import( "chrome://marionette/content/error.js" ); this.EXPORTED_SYMBOLS = ["Addon"]; Loading @@ -21,7 +27,9 @@ const ERRORS = { }; async function installAddon(file) { let install = await AddonManager.getInstallForFile(file, null, {source: "internal"}); let install = await AddonManager.getInstallForFile(file, null, { source: "internal", }); if (install.error) { throw new UnknownError(ERRORS[install.error]); Loading Loading @@ -76,7 +84,9 @@ class Addon { } } catch (e) { throw new UnknownError( `Could not install add-on: ${path}: ${e.message}`, e); `Could not install add-on: ${path}: ${e.message}`, e ); } return addon.id; Loading Loading @@ -104,7 +114,9 @@ class Addon { onOperationCancelled: addon => { if (addon.id === candidate.id) { AddonManager.removeAddonListener(listener); throw new UnknownError(`Uninstall of ${candidate.id} has been canceled`); throw new UnknownError( `Uninstall of ${candidate.id} has been canceled` ); } }, Loading