summaryrefslogtreecommitdiffstats
path: root/remote/shared/webdriver
diff options
context:
space:
mode:
Diffstat (limited to 'remote/shared/webdriver')
-rw-r--r--remote/shared/webdriver/Actions.sys.mjs57
-rw-r--r--remote/shared/webdriver/Capabilities.sys.mjs6
-rw-r--r--remote/shared/webdriver/Errors.sys.mjs17
-rw-r--r--remote/shared/webdriver/Event.sys.mjs295
-rw-r--r--remote/shared/webdriver/process-actors/WebDriverProcessDataChild.sys.mjs2
-rw-r--r--remote/shared/webdriver/test/xpcshell/test_Errors.js9
6 files changed, 340 insertions, 46 deletions
diff --git a/remote/shared/webdriver/Actions.sys.mjs b/remote/shared/webdriver/Actions.sys.mjs
index 4f5a41a421..2639c4dc9f 100644
--- a/remote/shared/webdriver/Actions.sys.mjs
+++ b/remote/shared/webdriver/Actions.sys.mjs
@@ -13,7 +13,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
clearTimeout: "resource://gre/modules/Timer.sys.mjs",
dom: "chrome://remote/content/shared/DOM.sys.mjs",
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
- event: "chrome://remote/content/marionette/event.sys.mjs",
+ event: "chrome://remote/content/shared/webdriver/Event.sys.mjs",
keyData: "chrome://remote/content/shared/webdriver/KeyData.sys.mjs",
Log: "chrome://remote/content/shared/Log.sys.mjs",
pprint: "chrome://remote/content/shared/Format.sys.mjs",
@@ -508,11 +508,8 @@ class Origin {
* Viewport coordinates of the origin of this coordinate system.
*
* This is overridden in subclasses to provide a class-specific origin.
- *
- * @param {InputSource} inputSource - State of current input device.
- * @param {WindowProxy} win - Current window global
*/
- getOriginCoordinates(inputSource, win) {
+ getOriginCoordinates() {
throw new Error(
`originCoordinates not defined for ${this.constructor.name}`
);
@@ -559,13 +556,13 @@ class Origin {
}
class ViewportOrigin extends Origin {
- getOriginCoordinates(inputSource, win) {
+ getOriginCoordinates() {
return { x: 0, y: 0 };
}
}
class PointerOrigin extends Origin {
- getOriginCoordinates(inputSource, win) {
+ getOriginCoordinates(inputSource) {
return { x: inputSource.x, y: inputSource.y };
}
}
@@ -624,13 +621,9 @@ class Action {
* This is overridden by subclasses to implement the type-specific
* dispatch of the action.
*
- * @param {State} state - Actions state.
- * @param {InputSource} inputSource - State of the current input device.
- * @param {number} tickDuration - Length of the current tick, in ms.
- * @param {WindowProxy} win - Current window global.
* @returns {Promise} - Promise that is resolved once the action is complete.
*/
- dispatch(state, inputSource, tickDuration, win) {
+ dispatch() {
throw new Error(
`Action subclass ${this.constructor.name} must override dispatch()`
);
@@ -708,10 +701,9 @@ class PauseAction extends NullAction {
* @param {State} state - Actions state.
* @param {InputSource} inputSource - State of the current input device.
* @param {number} tickDuration - Length of the current tick, in ms.
- * @param {WindowProxy} win - Current window global.
* @returns {Promise} - Promise that is resolved once the action is complete.
*/
- dispatch(state, inputSource, tickDuration, win) {
+ dispatch(state, inputSource, tickDuration) {
const ms = this.duration ?? tickDuration;
lazy.logger.trace(
@@ -1416,15 +1408,9 @@ class TouchActionGroup {
* This is overridden by subclasses to implement the type-specific
* dispatch of the action.
*
- * @param {State} state - Actions state.
- * @param {null} inputSource
- * This is always null; the argument only exists for compatibility
- * with {@link Action.dispatch}.
- * @param {number} tickDuration - Length of the current tick, in ms.
- * @param {WindowProxy} win - Current window global.
* @returns {Promise} - Promise that is resolved once the action is complete.
*/
- dispatch(state, inputSource, tickDuration, win) {
+ dispatch() {
throw new Error(
"TouchActionGroup subclass missing dispatch implementation"
);
@@ -1622,7 +1608,7 @@ class PointerMoveTouchActionGroup extends TouchActionGroup {
}
);
const reachedTarget = perPointerData.every(
- ([inputSource, action, target]) =>
+ ([inputSource, , target]) =>
target[0] === inputSource.x && target[1] === inputSource.y
);
@@ -1778,38 +1764,22 @@ class Pointer {
/**
* Implementation of depressing the pointer.
- *
- * @param {State} state - Actions state.
- * @param {InputSource} inputSource - State of the current input device.
- * @param {Action} action - The Action object invoking the pointer
- * @param {WindowProxy} win - Current window global.
*/
- pointerDown(state, inputSource, action, win) {
+ pointerDown() {
throw new Error(`Unimplemented pointerDown for pointerType ${this.type}`);
}
/**
* Implementation of releasing the pointer.
- *
- * @param {State} state - Actions state.
- * @param {InputSource} inputSource - State of the current input device.
- * @param {Action} action - The Action object invoking the pointer
- * @param {WindowProxy} win - Current window global.
*/
- pointerUp(state, inputSource, action, win) {
+ pointerUp() {
throw new Error(`Unimplemented pointerUp for pointerType ${this.type}`);
}
/**
* Implementation of moving the pointer.
- *
- * @param {State} state - Actions state.
- * @param {InputSource} inputSource - State of the current input device.
- * @param {number} targetX - Target X coordinate of the pointer move
- * @param {number} targetY - Target Y coordinate of the pointer move
- * @param {WindowProxy} win - Current window global.
*/
- pointerMove(state, inputSource, targetX, targetY, win) {
+ pointerMove() {
throw new Error(`Unimplemented pointerMove for pointerType ${this.type}`);
}
@@ -2138,11 +2108,8 @@ class InputEventData {
/**
* Update the input data based on global and input state
- *
- * @param {State} state - Actions state.
- * @param {InputSource} inputSource - State of the current input device.
*/
- update(state, inputSource) {}
+ update() {}
toString() {
return `${this.constructor.name} ${JSON.stringify(this)}`;
diff --git a/remote/shared/webdriver/Capabilities.sys.mjs b/remote/shared/webdriver/Capabilities.sys.mjs
index e3761315f2..3c30ea0789 100644
--- a/remote/shared/webdriver/Capabilities.sys.mjs
+++ b/remote/shared/webdriver/Capabilities.sys.mjs
@@ -445,6 +445,12 @@ export class Capabilities extends Map {
["timeouts", new Timeouts()],
["strictFileInteractability", false],
["unhandledPromptBehavior", UnhandledPromptBehavior.DismissAndNotify],
+ [
+ "userAgent",
+ Cc["@mozilla.org/network/protocol;1?name=http"].getService(
+ Ci.nsIHttpProtocolHandler
+ ).userAgent,
+ ],
["webSocketUrl", null],
// proprietary
diff --git a/remote/shared/webdriver/Errors.sys.mjs b/remote/shared/webdriver/Errors.sys.mjs
index 53b9d4426b..7060131075 100644
--- a/remote/shared/webdriver/Errors.sys.mjs
+++ b/remote/shared/webdriver/Errors.sys.mjs
@@ -41,6 +41,7 @@ const ERRORS = new Set([
"TimeoutError",
"UnableToCaptureScreen",
"UnableToSetCookieError",
+ "UnableToSetFileInputError",
"UnexpectedAlertOpenError",
"UnknownCommandError",
"UnknownError",
@@ -757,6 +758,21 @@ class UnableToSetCookieError extends WebDriverError {
}
/**
+ * A command to set a file could not be satisfied.
+ *
+ * @param {string=} message
+ * Optional string describing error situation.
+ * @param {object=} data
+ * Additional error data helpful in diagnosing the error.
+ */
+class UnableToSetFileInputError extends WebDriverError {
+ constructor(message, data = {}) {
+ super(message, data);
+ this.status = "unable to set file input";
+ }
+}
+
+/**
* A command to capture a screenshot could not be satisfied.
*
* @param {string=} message
@@ -865,6 +881,7 @@ const STATUSES = new Map([
["timeout", TimeoutError],
["unable to capture screen", UnableToCaptureScreen],
["unable to set cookie", UnableToSetCookieError],
+ ["unable to set file input", UnableToSetFileInputError],
["unexpected alert open", UnexpectedAlertOpenError],
["unknown command", UnknownCommandError],
["unknown error", UnknownError],
diff --git a/remote/shared/webdriver/Event.sys.mjs b/remote/shared/webdriver/Event.sys.mjs
new file mode 100644
index 0000000000..2240f1f2b2
--- /dev/null
+++ b/remote/shared/webdriver/Event.sys.mjs
@@ -0,0 +1,295 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+/* eslint-disable no-restricted-globals */
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ keyData: "chrome://remote/content/shared/webdriver/KeyData.sys.mjs",
+});
+
+/** Provides functionality for creating and sending DOM events. */
+export const event = {};
+
+const _eventUtils = new WeakMap();
+
+function _getEventUtils(win) {
+ if (!_eventUtils.has(win)) {
+ const eventUtilsObject = {
+ window: win,
+ parent: win,
+ _EU_Ci: Ci,
+ _EU_Cc: Cc,
+ };
+ Services.scriptloader.loadSubScript(
+ "chrome://remote/content/external/EventUtils.js",
+ eventUtilsObject
+ );
+ _eventUtils.set(win, eventUtilsObject);
+ }
+ return _eventUtils.get(win);
+}
+
+event.MouseEvents = {
+ click: 0,
+ dblclick: 1,
+ mousedown: 2,
+ mouseup: 3,
+ mouseover: 4,
+ mouseout: 5,
+};
+
+event.Modifiers = {
+ shiftKey: 0,
+ ctrlKey: 1,
+ altKey: 2,
+ metaKey: 3,
+};
+
+event.MouseButton = {
+ isPrimary(button) {
+ return button === 0;
+ },
+ isAuxiliary(button) {
+ return button === 1;
+ },
+ isSecondary(button) {
+ return button === 2;
+ },
+};
+
+/**
+ * Synthesise a mouse event at a point.
+ *
+ * If the type is specified in opts, an mouse event of that type is
+ * fired. Otherwise, a mousedown followed by a mouseup is performed.
+ *
+ * @param {number} left
+ * Offset from viewport left, in CSS pixels
+ * @param {number} top
+ * Offset from viewport top, in CSS pixels
+ * @param {object} opts
+ * Object which may contain the properties "shiftKey", "ctrlKey",
+ * "altKey", "metaKey", "accessKey", "clickCount", "button", and
+ * "type".
+ * @param {Window} win
+ * Window object.
+ *
+ * @returns {boolean} defaultPrevented
+ */
+event.synthesizeMouseAtPoint = function (left, top, opts, win) {
+ return _getEventUtils(win).synthesizeMouseAtPoint(left, top, opts, win);
+};
+
+/**
+ * Synthesise a touch event at a point.
+ *
+ * If the type is specified in opts, a touch event of that type is
+ * fired. Otherwise, a touchstart followed by a touchend is performed.
+ *
+ * @param {number} left
+ * Offset from viewport left, in CSS pixels
+ * @param {number} top
+ * Offset from viewport top, in CSS pixels
+ * @param {object} opts
+ * Object which may contain the properties "id", "rx", "ry", "angle",
+ * "force", "shiftKey", "ctrlKey", "altKey", "metaKey", "accessKey",
+ * "type".
+ * @param {Window} win
+ * Window object.
+ *
+ * @returns {boolean} defaultPrevented
+ */
+event.synthesizeTouchAtPoint = function (left, top, opts, win) {
+ return _getEventUtils(win).synthesizeTouchAtPoint(left, top, opts, win);
+};
+
+/**
+ * Synthesise a wheel scroll event at a point.
+ *
+ * @param {number} left
+ * Offset from viewport left, in CSS pixels
+ * @param {number} top
+ * Offset from viewport top, in CSS pixels
+ * @param {object} opts
+ * Object which may contain the properties "shiftKey", "ctrlKey",
+ * "altKey", "metaKey", "accessKey", "deltaX", "deltaY", "deltaZ",
+ * "deltaMode", "lineOrPageDeltaX", "lineOrPageDeltaY", "isMomentum",
+ * "isNoLineOrPageDelta", "isCustomizedByPrefs", "expectedOverflowDeltaX",
+ * "expectedOverflowDeltaY"
+ * @param {Window} win
+ * Window object.
+ */
+event.synthesizeWheelAtPoint = function (left, top, opts, win) {
+ const dpr = win.devicePixelRatio;
+
+ // All delta properties expect the value in device pixels while the
+ // WebDriver specification uses CSS pixels.
+ if (typeof opts.deltaX !== "undefined") {
+ opts.deltaX *= dpr;
+ }
+ if (typeof opts.deltaY !== "undefined") {
+ opts.deltaY *= dpr;
+ }
+ if (typeof opts.deltaZ !== "undefined") {
+ opts.deltaZ *= dpr;
+ }
+
+ return _getEventUtils(win).synthesizeWheelAtPoint(left, top, opts, win);
+};
+
+event.synthesizeMultiTouch = function (opts, win) {
+ const modifiers = _getEventUtils(win)._parseModifiers(opts);
+ win.windowUtils.sendTouchEvent(
+ opts.type,
+ opts.id,
+ opts.x,
+ opts.y,
+ opts.rx,
+ opts.ry,
+ opts.angle,
+ opts.force,
+ opts.tiltx,
+ opts.tilty,
+ opts.twist,
+ modifiers
+ );
+};
+
+/**
+ * Synthesize a keydown event for a single key.
+ *
+ * @param {object} key
+ * Key data as returned by keyData.getData
+ * @param {Window} win
+ * Window object.
+ */
+event.sendKeyDown = function (key, win) {
+ event.sendSingleKey(key, win, "keydown");
+};
+
+/**
+ * Synthesize a keyup event for a single key.
+ *
+ * @param {object} key
+ * Key data as returned by keyData.getData
+ * @param {Window} win
+ * Window object.
+ */
+event.sendKeyUp = function (key, win) {
+ event.sendSingleKey(key, win, "keyup");
+};
+
+/**
+ * Synthesize a key event for a single key.
+ *
+ * @param {object} key
+ * Key data as returned by keyData.getData
+ * @param {Window} win
+ * Window object.
+ * @param {string=} type
+ * Event to emit. By default the full keydown/keypressed/keyup event
+ * sequence is emitted.
+ */
+event.sendSingleKey = function (key, win, type = null) {
+ let keyValue = key.key;
+ if (!key.printable) {
+ keyValue = `KEY_${keyValue}`;
+ }
+ const event = {
+ code: key.code,
+ location: key.location,
+ altKey: key.altKey ?? false,
+ shiftKey: key.shiftKey ?? false,
+ ctrlKey: key.ctrlKey ?? false,
+ metaKey: key.metaKey ?? false,
+ repeat: key.repeat ?? false,
+ };
+ if (type) {
+ event.type = type;
+ }
+ _getEventUtils(win).synthesizeKey(keyValue, event, win);
+};
+
+/**
+ * Send a string as a series of keypresses.
+ *
+ * @param {string} keyString
+ * Sequence of characters to send as key presses
+ * @param {Window} win
+ * Window object
+ */
+event.sendKeys = function (keyString, win) {
+ const modifiers = {};
+ for (let modifier in event.Modifiers) {
+ modifiers[modifier] = false;
+ }
+
+ for (let keyValue of keyString) {
+ // keyValue will contain enough to represent the UTF-16 encoding of a single abstract character
+ // i.e. either a single scalar value, or a surrogate pair
+ if (modifiers.shiftKey) {
+ keyValue = lazy.keyData.getShiftedKey(keyValue);
+ }
+ const data = lazy.keyData.getData(keyValue);
+ const key = { ...data, ...modifiers };
+ if (data.modifier) {
+ // Negating the state of the modifier here is not spec compliant but
+ // makes us compatible to Chrome's behavior for now. That's fine unless
+ // we know the correct behavior.
+ //
+ // @see: https://github.com/w3c/webdriver/issues/1734
+ modifiers[data.modifier] = !modifiers[data.modifier];
+ }
+ event.sendSingleKey(key, win);
+ }
+};
+
+event.sendEvent = function (eventType, el, modifiers = {}, opts = {}) {
+ opts.canBubble = opts.canBubble || true;
+
+ let doc = el.ownerDocument || el.document;
+ let ev = doc.createEvent("Event");
+
+ ev.shiftKey = modifiers.shift;
+ ev.metaKey = modifiers.meta;
+ ev.altKey = modifiers.alt;
+ ev.ctrlKey = modifiers.ctrl;
+
+ ev.initEvent(eventType, opts.canBubble, true);
+ el.dispatchEvent(ev);
+};
+
+event.mouseover = function (el, modifiers = {}, opts = {}) {
+ return event.sendEvent("mouseover", el, modifiers, opts);
+};
+
+event.mousemove = function (el, modifiers = {}, opts = {}) {
+ return event.sendEvent("mousemove", el, modifiers, opts);
+};
+
+event.mousedown = function (el, modifiers = {}, opts = {}) {
+ return event.sendEvent("mousedown", el, modifiers, opts);
+};
+
+event.mouseup = function (el, modifiers = {}, opts = {}) {
+ return event.sendEvent("mouseup", el, modifiers, opts);
+};
+
+event.cancel = function (el, modifiers = {}, opts = {}) {
+ return event.sendEvent("cancel", el, modifiers, opts);
+};
+
+event.click = function (el, modifiers = {}, opts = {}) {
+ return event.sendEvent("click", el, modifiers, opts);
+};
+
+event.change = function (el, modifiers = {}, opts = {}) {
+ return event.sendEvent("change", el, modifiers, opts);
+};
+
+event.input = function (el, modifiers = {}, opts = {}) {
+ return event.sendEvent("input", el, modifiers, opts);
+};
diff --git a/remote/shared/webdriver/process-actors/WebDriverProcessDataChild.sys.mjs b/remote/shared/webdriver/process-actors/WebDriverProcessDataChild.sys.mjs
index 39db9d939e..8101240abe 100644
--- a/remote/shared/webdriver/process-actors/WebDriverProcessDataChild.sys.mjs
+++ b/remote/shared/webdriver/process-actors/WebDriverProcessDataChild.sys.mjs
@@ -17,7 +17,7 @@ class BrowsingContextObserver {
this.actor = actor;
}
- async observe(subject, topic, data) {
+ async observe(subject, topic) {
if (topic === "browsing-context-discarded") {
this.actor.cleanUp({ browsingContext: subject });
}
diff --git a/remote/shared/webdriver/test/xpcshell/test_Errors.js b/remote/shared/webdriver/test/xpcshell/test_Errors.js
index 22e3526039..d4803dee87 100644
--- a/remote/shared/webdriver/test/xpcshell/test_Errors.js
+++ b/remote/shared/webdriver/test/xpcshell/test_Errors.js
@@ -36,6 +36,7 @@ const errors = [
error.StaleElementReferenceError,
error.TimeoutError,
error.UnableToSetCookieError,
+ error.UnableToSetFileInputError,
error.UnexpectedAlertOpenError,
error.UnknownCommandError,
error.UnknownError,
@@ -510,6 +511,14 @@ add_task(function test_UnableToSetCookieError() {
ok(err instanceof error.WebDriverError);
});
+add_task(function test_UnableToSetFileInputError() {
+ let err = new error.UnableToSetFileInputError("foo");
+ equal("UnableToSetFileInputError", err.name);
+ equal("foo", err.message);
+ equal("unable to set file input", err.status);
+ ok(err instanceof error.WebDriverError);
+});
+
add_task(function test_UnexpectedAlertOpenError() {
let err = new error.UnexpectedAlertOpenError("foo");
equal("UnexpectedAlertOpenError", err.name);