зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1904665 - [remote] Prepare the Remote Agent code base for parent process event dispatching. r=webdriver-reviewers,jdescottes
Differential Revision: https://phabricator.services.mozilla.com/D220543
This commit is contained in:
Родитель
861fc2a11e
Коммит
dee4668b17
|
@ -9,11 +9,14 @@ const lazy = {};
|
||||||
ChromeUtils.defineESModuleGetters(lazy, {
|
ChromeUtils.defineESModuleGetters(lazy, {
|
||||||
accessibility:
|
accessibility:
|
||||||
"chrome://remote/content/shared/webdriver/Accessibility.sys.mjs",
|
"chrome://remote/content/shared/webdriver/Accessibility.sys.mjs",
|
||||||
action: "chrome://remote/content/shared/webdriver/Actions.sys.mjs",
|
AnimationFramePromise: "chrome://remote/content/shared/Sync.sys.mjs",
|
||||||
|
assertInViewPort: "chrome://remote/content/shared/webdriver/Actions.sys.mjs",
|
||||||
atom: "chrome://remote/content/marionette/atom.sys.mjs",
|
atom: "chrome://remote/content/marionette/atom.sys.mjs",
|
||||||
dom: "chrome://remote/content/shared/DOM.sys.mjs",
|
dom: "chrome://remote/content/shared/DOM.sys.mjs",
|
||||||
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
|
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
|
||||||
evaluate: "chrome://remote/content/marionette/evaluate.sys.mjs",
|
evaluate: "chrome://remote/content/marionette/evaluate.sys.mjs",
|
||||||
|
event: "chrome://remote/content/shared/webdriver/Event.sys.mjs",
|
||||||
|
executeSoon: "chrome://remote/content/shared/Sync.sys.mjs",
|
||||||
interaction: "chrome://remote/content/marionette/interaction.sys.mjs",
|
interaction: "chrome://remote/content/marionette/interaction.sys.mjs",
|
||||||
json: "chrome://remote/content/marionette/json.sys.mjs",
|
json: "chrome://remote/content/marionette/json.sys.mjs",
|
||||||
Log: "chrome://remote/content/shared/Log.sys.mjs",
|
Log: "chrome://remote/content/shared/Log.sys.mjs",
|
||||||
|
@ -37,8 +40,6 @@ export class MarionetteCommandsChild extends JSWindowActorChild {
|
||||||
|
|
||||||
// sandbox storage and name of the current sandbox
|
// sandbox storage and name of the current sandbox
|
||||||
this.sandboxes = new lazy.Sandboxes(() => this.document.defaultView);
|
this.sandboxes = new lazy.Sandboxes(() => this.document.defaultView);
|
||||||
// State of the input actions. This is specific to contexts and sessions
|
|
||||||
this.actionState = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get innerWindowId() {
|
get innerWindowId() {
|
||||||
|
@ -59,6 +60,65 @@ export class MarionetteCommandsChild extends JSWindowActorChild {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#assertInViewPort(options = {}) {
|
||||||
|
const { target } = options;
|
||||||
|
|
||||||
|
return lazy.assertInViewPort(target, this.contentWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
#dispatchEvent(options = {}) {
|
||||||
|
const { eventName, details } = options;
|
||||||
|
const win = this.contentWindow;
|
||||||
|
|
||||||
|
switch (eventName) {
|
||||||
|
case "synthesizeKeyDown":
|
||||||
|
lazy.event.sendKeyDown(details.eventData, win);
|
||||||
|
break;
|
||||||
|
case "synthesizeKeyUp":
|
||||||
|
lazy.event.sendKeyUp(details.eventData, win);
|
||||||
|
break;
|
||||||
|
case "synthesizeMouseAtPoint":
|
||||||
|
lazy.event.synthesizeMouseAtPoint(
|
||||||
|
details.x,
|
||||||
|
details.y,
|
||||||
|
details.eventData,
|
||||||
|
win
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "synthesizeMultiTouch":
|
||||||
|
lazy.event.synthesizeMultiTouch(details.eventData, win);
|
||||||
|
break;
|
||||||
|
case "synthesizeWheelAtPoint":
|
||||||
|
lazy.event.synthesizeWheelAtPoint(
|
||||||
|
details.x,
|
||||||
|
details.y,
|
||||||
|
details.eventData,
|
||||||
|
win
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
`${eventName} is not a supported event dispatch method`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async #finalizeAction() {
|
||||||
|
// Terminate the current wheel transaction if there is one. Wheel
|
||||||
|
// transactions should not live longer than a single action chain.
|
||||||
|
ChromeUtils.endWheelTransaction();
|
||||||
|
|
||||||
|
// Wait for the next animation frame to make sure the page's content
|
||||||
|
// was updated.
|
||||||
|
await lazy.AnimationFramePromise(this.contentWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
#getInViewCentrePoint(options) {
|
||||||
|
const { rect } = options;
|
||||||
|
|
||||||
|
return lazy.dom.getInViewCentrePoint(rect, this.contentWindow);
|
||||||
|
}
|
||||||
|
|
||||||
async receiveMessage(msg) {
|
async receiveMessage(msg) {
|
||||||
if (!this.contentWindow) {
|
if (!this.contentWindow) {
|
||||||
throw new DOMException("Actor is no longer active", "InactiveActor");
|
throw new DOMException("Actor is no longer active", "InactiveActor");
|
||||||
|
@ -77,6 +137,19 @@ export class MarionetteCommandsChild extends JSWindowActorChild {
|
||||||
);
|
);
|
||||||
|
|
||||||
switch (name) {
|
switch (name) {
|
||||||
|
case "MarionetteCommandsParent:_assertInViewPort":
|
||||||
|
result = this.#assertInViewPort(data);
|
||||||
|
break;
|
||||||
|
case "MarionetteCommandsParent:_dispatchEvent":
|
||||||
|
this.#dispatchEvent(data);
|
||||||
|
waitForNextTick = true;
|
||||||
|
break;
|
||||||
|
case "MarionetteCommandsParent:_getInViewCentrePoint":
|
||||||
|
result = this.#getInViewCentrePoint(data);
|
||||||
|
break;
|
||||||
|
case "MarionetteCommandsParent:_finalizeAction":
|
||||||
|
this.#finalizeAction();
|
||||||
|
break;
|
||||||
case "MarionetteCommandsParent:clearElement":
|
case "MarionetteCommandsParent:clearElement":
|
||||||
this.clearElement(data);
|
this.clearElement(data);
|
||||||
waitForNextTick = true;
|
waitForNextTick = true;
|
||||||
|
@ -140,13 +213,6 @@ export class MarionetteCommandsChild extends JSWindowActorChild {
|
||||||
case "MarionetteCommandsParent:isElementSelected":
|
case "MarionetteCommandsParent:isElementSelected":
|
||||||
result = await this.isElementSelected(data);
|
result = await this.isElementSelected(data);
|
||||||
break;
|
break;
|
||||||
case "MarionetteCommandsParent:performActions":
|
|
||||||
result = await this.performActions(data);
|
|
||||||
waitForNextTick = true;
|
|
||||||
break;
|
|
||||||
case "MarionetteCommandsParent:releaseActions":
|
|
||||||
result = await this.releaseActions();
|
|
||||||
break;
|
|
||||||
case "MarionetteCommandsParent:sendKeysToElement":
|
case "MarionetteCommandsParent:sendKeysToElement":
|
||||||
result = await this.sendKeysToElement(data);
|
result = await this.sendKeysToElement(data);
|
||||||
waitForNextTick = true;
|
waitForNextTick = true;
|
||||||
|
@ -164,7 +230,7 @@ export class MarionetteCommandsChild extends JSWindowActorChild {
|
||||||
// Inform the content process that the command has completed. It allows
|
// Inform the content process that the command has completed. It allows
|
||||||
// it to process async follow-up tasks before the reply is sent.
|
// it to process async follow-up tasks before the reply is sent.
|
||||||
if (waitForNextTick) {
|
if (waitForNextTick) {
|
||||||
await new Promise(resolve => Services.tm.dispatchToMainThread(resolve));
|
await new Promise(resolve => lazy.executeSoon(resolve));
|
||||||
}
|
}
|
||||||
|
|
||||||
const { seenNodeIds, serializedValue, hasSerializedWindows } =
|
const { seenNodeIds, serializedValue, hasSerializedWindows } =
|
||||||
|
@ -472,42 +538,6 @@ export class MarionetteCommandsChild extends JSWindowActorChild {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform a series of grouped actions at the specified points in time.
|
|
||||||
*
|
|
||||||
* @param {object} options
|
|
||||||
* @param {object} options.actions
|
|
||||||
* Array of objects with each representing an action sequence.
|
|
||||||
* @param {object} options.capabilities
|
|
||||||
* Object with a list of WebDriver session capabilities.
|
|
||||||
*/
|
|
||||||
async performActions(options = {}) {
|
|
||||||
const { actions } = options;
|
|
||||||
if (this.actionState === null) {
|
|
||||||
this.actionState = new lazy.action.State();
|
|
||||||
}
|
|
||||||
let actionChain = lazy.action.Chain.fromJSON(this.actionState, actions);
|
|
||||||
|
|
||||||
await actionChain.dispatch(this.actionState, this.document.defaultView);
|
|
||||||
// Terminate the current wheel transaction if there is one. Wheel
|
|
||||||
// transactions should not live longer than a single action chain.
|
|
||||||
ChromeUtils.endWheelTransaction();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The release actions command is used to release all the keys and pointer
|
|
||||||
* buttons that are currently depressed. This causes events to be fired
|
|
||||||
* as if the state was released by an explicit series of actions. It also
|
|
||||||
* clears all the internal state of the virtual devices.
|
|
||||||
*/
|
|
||||||
async releaseActions() {
|
|
||||||
if (this.actionState === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await this.actionState.release(this.document.defaultView);
|
|
||||||
this.actionState = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Send key presses to element after focusing on it.
|
* Send key presses to element after focusing on it.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -5,12 +5,14 @@
|
||||||
const lazy = {};
|
const lazy = {};
|
||||||
|
|
||||||
ChromeUtils.defineESModuleGetters(lazy, {
|
ChromeUtils.defineESModuleGetters(lazy, {
|
||||||
|
action: "chrome://remote/content/shared/webdriver/Actions.sys.mjs",
|
||||||
capture: "chrome://remote/content/shared/Capture.sys.mjs",
|
capture: "chrome://remote/content/shared/Capture.sys.mjs",
|
||||||
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
|
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
|
||||||
getSeenNodesForBrowsingContext:
|
getSeenNodesForBrowsingContext:
|
||||||
"chrome://remote/content/shared/webdriver/Session.sys.mjs",
|
"chrome://remote/content/shared/webdriver/Session.sys.mjs",
|
||||||
json: "chrome://remote/content/marionette/json.sys.mjs",
|
json: "chrome://remote/content/marionette/json.sys.mjs",
|
||||||
Log: "chrome://remote/content/shared/Log.sys.mjs",
|
Log: "chrome://remote/content/shared/Log.sys.mjs",
|
||||||
|
WebElement: "chrome://remote/content/marionette/web-reference.sys.mjs",
|
||||||
});
|
});
|
||||||
|
|
||||||
ChromeUtils.defineLazyGetter(lazy, "logger", () =>
|
ChromeUtils.defineLazyGetter(lazy, "logger", () =>
|
||||||
|
@ -22,12 +24,146 @@ ChromeUtils.defineLazyGetter(lazy, "logger", () =>
|
||||||
let webDriverSessionId = null;
|
let webDriverSessionId = null;
|
||||||
|
|
||||||
export class MarionetteCommandsParent extends JSWindowActorParent {
|
export class MarionetteCommandsParent extends JSWindowActorParent {
|
||||||
|
#actionsOptions;
|
||||||
|
#actionState;
|
||||||
#deferredDialogOpened;
|
#deferredDialogOpened;
|
||||||
|
|
||||||
actorCreated() {
|
actorCreated() {
|
||||||
|
// The {@link Actions.State} of the input actions.
|
||||||
|
this.#actionState = null;
|
||||||
|
|
||||||
|
// Options for actions to pass through performActions and releaseActions.
|
||||||
|
this.#actionsOptions = {
|
||||||
|
// Callbacks as defined in the WebDriver specification.
|
||||||
|
getElementOrigin: this.#getElementOrigin.bind(this),
|
||||||
|
isElementOrigin: this.#isElementOrigin.bind(this),
|
||||||
|
|
||||||
|
// Custom properties and callbacks
|
||||||
|
context: this.browsingContext,
|
||||||
|
|
||||||
|
assertInViewPort: this.#assertInViewPort.bind(this),
|
||||||
|
dispatchEvent: this.#dispatchEvent.bind(this),
|
||||||
|
getClientRects: this.#getClientRects.bind(this),
|
||||||
|
getInViewCentrePoint: this.#getInViewCentrePoint.bind(this),
|
||||||
|
};
|
||||||
|
|
||||||
this.#deferredDialogOpened = null;
|
this.#deferredDialogOpened = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert that the target coordinates are within the visible viewport.
|
||||||
|
*
|
||||||
|
* @param {Array.<number>} target
|
||||||
|
* Coordinates [x, y] of the target relative to the viewport.
|
||||||
|
* @param {BrowsingContext} _context
|
||||||
|
* Unused in Marionette.
|
||||||
|
*
|
||||||
|
* @returns {Promise<undefined>}
|
||||||
|
* Promise that rejects, if the coordinates are not within
|
||||||
|
* the visible viewport.
|
||||||
|
*
|
||||||
|
* @throws {MoveTargetOutOfBoundsError}
|
||||||
|
* If target is outside the viewport.
|
||||||
|
*/
|
||||||
|
#assertInViewPort(target, _context) {
|
||||||
|
return this.sendQuery("MarionetteCommandsParent:_assertInViewPort", {
|
||||||
|
target,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatch an event.
|
||||||
|
*
|
||||||
|
* @param {string} eventName
|
||||||
|
* Name of the event to be dispatched.
|
||||||
|
* @param {BrowsingContext} _context
|
||||||
|
* Unused in Marionette.
|
||||||
|
* @param {object} details
|
||||||
|
* Details of the event to be dispatched.
|
||||||
|
*
|
||||||
|
* @returns {Promise}
|
||||||
|
* Promise that resolves once the event is dispatched.
|
||||||
|
*/
|
||||||
|
#dispatchEvent(eventName, _context, details) {
|
||||||
|
return this.sendQuery("MarionetteCommandsParent:_dispatchEvent", {
|
||||||
|
eventName,
|
||||||
|
details,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finalize an action command.
|
||||||
|
*
|
||||||
|
* @returns {Promise}
|
||||||
|
* Promise that resolves when the finalization is done.
|
||||||
|
*/
|
||||||
|
#finalizeAction() {
|
||||||
|
return this.sendQuery("MarionetteCommandsParent:_finalizeAction");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the WebElement reference of the origin.
|
||||||
|
*
|
||||||
|
* @param {ElementOrigin} origin
|
||||||
|
* Reference to the element origin of the action.
|
||||||
|
* @param {BrowsingContext} _context
|
||||||
|
* Unused in Marionette.
|
||||||
|
*
|
||||||
|
* @returns {WebElement}
|
||||||
|
* The WebElement reference.
|
||||||
|
*/
|
||||||
|
#getElementOrigin(origin, _context) {
|
||||||
|
return origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the list of client rects for the element.
|
||||||
|
*
|
||||||
|
* @param {WebElement} element
|
||||||
|
* The web element reference to retrieve the rects from.
|
||||||
|
* @param {BrowsingContext} _context
|
||||||
|
* Unused in Marionette.
|
||||||
|
*
|
||||||
|
* @returns {Promise<Array<Map.<string, number>>>}
|
||||||
|
* Promise that resolves to a list of DOMRect-like objects.
|
||||||
|
*/
|
||||||
|
#getClientRects(element, _context) {
|
||||||
|
return this.executeScript("return arguments[0].getClientRects()", [
|
||||||
|
element,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the in-view center point for the rect and visible viewport.
|
||||||
|
*
|
||||||
|
* @param {DOMRect} rect
|
||||||
|
* Size and position of the rectangle to check.
|
||||||
|
* @param { BrowsingContext } _context
|
||||||
|
* Unused in Marionette.
|
||||||
|
*
|
||||||
|
* @returns {Promise<Map.<string, number>>}
|
||||||
|
* X and Y coordinates that denotes the in-view centre point of
|
||||||
|
* `rect`.
|
||||||
|
*/
|
||||||
|
#getInViewCentrePoint(rect, _context) {
|
||||||
|
return this.sendQuery("MarionetteCommandsParent:_getInViewCentrePoint", {
|
||||||
|
rect,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the given object is a valid element origin.
|
||||||
|
*
|
||||||
|
* @param {object} origin
|
||||||
|
* The object to check.
|
||||||
|
*
|
||||||
|
* @returns {boolean}
|
||||||
|
* True, if it's a WebElement.
|
||||||
|
*/
|
||||||
|
#isElementOrigin(origin) {
|
||||||
|
return lazy.WebElement.Identifier in origin;
|
||||||
|
}
|
||||||
|
|
||||||
async sendQuery(name, serializedValue) {
|
async sendQuery(name, serializedValue) {
|
||||||
const seenNodes = lazy.getSeenNodesForBrowsingContext(
|
const seenNodes = lazy.getSeenNodesForBrowsingContext(
|
||||||
webDriverSessionId,
|
webDriverSessionId,
|
||||||
|
@ -243,13 +379,39 @@ export class MarionetteCommandsParent extends JSWindowActorParent {
|
||||||
}
|
}
|
||||||
|
|
||||||
async performActions(actions) {
|
async performActions(actions) {
|
||||||
return this.sendQuery("MarionetteCommandsParent:performActions", {
|
// Bug 1821460: Use top-level browsing context.
|
||||||
actions,
|
if (this.#actionState === null) {
|
||||||
});
|
this.#actionState = new lazy.action.State();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const actionChain = await lazy.action.Chain.fromJSON(
|
||||||
|
this.#actionState,
|
||||||
|
actions,
|
||||||
|
this.#actionsOptions
|
||||||
|
);
|
||||||
|
await actionChain.dispatch(this.#actionState, this.#actionsOptions);
|
||||||
|
|
||||||
|
// Process async follow-up tasks in content before the reply is sent.
|
||||||
|
await this.#finalizeAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The release actions command is used to release all the keys and pointer
|
||||||
|
* buttons that are currently depressed. This causes events to be fired
|
||||||
|
* as if the state was released by an explicit series of actions. It also
|
||||||
|
* clears all the internal state of the virtual devices.
|
||||||
|
*/
|
||||||
async releaseActions() {
|
async releaseActions() {
|
||||||
return this.sendQuery("MarionetteCommandsParent:releaseActions");
|
// Bug 1821460: Use top-level browsing context.
|
||||||
|
if (this.#actionState === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.#actionState.release(this.#actionsOptions);
|
||||||
|
this.#actionState = null;
|
||||||
|
|
||||||
|
// Process async follow-up tasks in content before the reply is sent.
|
||||||
|
await this.#finalizeAction();
|
||||||
}
|
}
|
||||||
|
|
||||||
async switchToFrame(id) {
|
async switchToFrame(id) {
|
||||||
|
|
|
@ -8,6 +8,7 @@ const lazy = {};
|
||||||
|
|
||||||
ChromeUtils.defineESModuleGetters(lazy, {
|
ChromeUtils.defineESModuleGetters(lazy, {
|
||||||
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
|
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
|
||||||
|
executeSoon: "chrome://remote/content/shared/Sync.sys.mjs",
|
||||||
Log: "chrome://remote/content/shared/Log.sys.mjs",
|
Log: "chrome://remote/content/shared/Log.sys.mjs",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -19,20 +20,6 @@ const { TYPE_ONE_SHOT, TYPE_REPEATING_SLACK } = Ci.nsITimer;
|
||||||
|
|
||||||
const PROMISE_TIMEOUT = AppConstants.DEBUG ? 4500 : 1500;
|
const PROMISE_TIMEOUT = AppConstants.DEBUG ? 4500 : 1500;
|
||||||
|
|
||||||
/**
|
|
||||||
* Dispatch a function to be executed on the main thread.
|
|
||||||
*
|
|
||||||
* @param {Function} func
|
|
||||||
* Function to be executed.
|
|
||||||
*/
|
|
||||||
export function executeSoon(func) {
|
|
||||||
if (typeof func != "function") {
|
|
||||||
throw new TypeError();
|
|
||||||
}
|
|
||||||
|
|
||||||
Services.tm.dispatchToMainThread(func);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs a Promise-like function off the main thread until it is resolved
|
* Runs a Promise-like function off the main thread until it is resolved
|
||||||
* through ``resolve`` or ``rejected`` callbacks. The function is
|
* through ``resolve`` or ``rejected`` callbacks. The function is
|
||||||
|
@ -323,7 +310,7 @@ export function MessageManagerDestroyedPromise(messageManager) {
|
||||||
*/
|
*/
|
||||||
export function IdlePromise(win) {
|
export function IdlePromise(win) {
|
||||||
const animationFramePromise = new Promise(resolve => {
|
const animationFramePromise = new Promise(resolve => {
|
||||||
executeSoon(() => {
|
lazy.executeSoon(() => {
|
||||||
win.requestAnimationFrame(resolve);
|
win.requestAnimationFrame(resolve);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -67,24 +67,6 @@ class MockTimer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
add_task(function test_executeSoon_callback() {
|
|
||||||
// executeSoon() is already defined for xpcshell in head.js. As such import
|
|
||||||
// our implementation into a custom namespace.
|
|
||||||
let sync = ChromeUtils.importESModule(
|
|
||||||
"chrome://remote/content/marionette/sync.sys.mjs"
|
|
||||||
);
|
|
||||||
|
|
||||||
for (let func of ["foo", null, true, [], {}]) {
|
|
||||||
Assert.throws(() => sync.executeSoon(func), /TypeError/);
|
|
||||||
}
|
|
||||||
|
|
||||||
let a;
|
|
||||||
sync.executeSoon(() => {
|
|
||||||
a = 1;
|
|
||||||
});
|
|
||||||
executeSoon(() => equal(1, a));
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(function test_PollPromise_funcTypes() {
|
add_task(function test_PollPromise_funcTypes() {
|
||||||
for (let type of ["foo", 42, null, undefined, true, [], {}]) {
|
for (let type of ["foo", 42, null, undefined, true, [], {}]) {
|
||||||
Assert.throws(() => new PollPromise(type), /TypeError/);
|
Assert.throws(() => new PollPromise(type), /TypeError/);
|
||||||
|
|
|
@ -8,7 +8,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
||||||
EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",
|
EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",
|
||||||
|
|
||||||
BulkPacket: "chrome://remote/content/marionette/packets.sys.mjs",
|
BulkPacket: "chrome://remote/content/marionette/packets.sys.mjs",
|
||||||
executeSoon: "chrome://remote/content/marionette/sync.sys.mjs",
|
executeSoon: "chrome://remote/content/shared/Sync.sys.mjs",
|
||||||
JSONPacket: "chrome://remote/content/marionette/packets.sys.mjs",
|
JSONPacket: "chrome://remote/content/marionette/packets.sys.mjs",
|
||||||
Packet: "chrome://remote/content/marionette/packets.sys.mjs",
|
Packet: "chrome://remote/content/marionette/packets.sys.mjs",
|
||||||
StreamUtils: "chrome://remote/content/marionette/stream-utils.sys.mjs",
|
StreamUtils: "chrome://remote/content/marionette/stream-utils.sys.mjs",
|
||||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -84,7 +84,7 @@ event.synthesizeMouseAtPoint = function (left, top, opts, win) {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Synthesise a touch event at a point.
|
* Synthesize a touch event at a point.
|
||||||
*
|
*
|
||||||
* If the type is specified in opts, a touch event of that type is
|
* If the type is specified in opts, a touch event of that type is
|
||||||
* fired. Otherwise, a touchstart followed by a touchend is performed.
|
* fired. Otherwise, a touchstart followed by a touchend is performed.
|
||||||
|
|
|
@ -35,12 +35,15 @@ add_task(function test_createInputState() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_defaultPointerParameters() {
|
add_task(async function test_defaultPointerParameters() {
|
||||||
let state = new action.State();
|
let state = new action.State();
|
||||||
const inputTickActions = [
|
const inputTickActions = [
|
||||||
{ type: "pointer", subtype: "pointerDown", button: 0 },
|
{ type: "pointer", subtype: "pointerDown", button: 0 },
|
||||||
];
|
];
|
||||||
const chain = action.Chain.fromJSON(state, chainForTick(inputTickActions));
|
const chain = await action.Chain.fromJSON(
|
||||||
|
state,
|
||||||
|
chainForTick(inputTickActions)
|
||||||
|
);
|
||||||
const pointerAction = chain[0][0];
|
const pointerAction = chain[0][0];
|
||||||
equal(
|
equal(
|
||||||
state.getInputSource(pointerAction.id).pointer.constructor.type,
|
state.getInputSource(pointerAction.id).pointer.constructor.type,
|
||||||
|
@ -48,7 +51,7 @@ add_task(function test_defaultPointerParameters() {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_processPointerParameters() {
|
add_task(async function test_processPointerParameters() {
|
||||||
for (let subtype of ["pointerDown", "pointerUp"]) {
|
for (let subtype of ["pointerDown", "pointerUp"]) {
|
||||||
for (let pointerType of [2, true, {}, []]) {
|
for (let pointerType of [2, true, {}, []]) {
|
||||||
const inputTickActions = [
|
const inputTickActions = [
|
||||||
|
@ -60,7 +63,7 @@ add_task(function test_processPointerParameters() {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
let message = `Action sequence with parameters: {pointerType: ${pointerType} subtype: ${subtype}}`;
|
let message = `Action sequence with parameters: {pointerType: ${pointerType} subtype: ${subtype}}`;
|
||||||
checkFromJSONErrors(
|
await checkFromJSONErrors(
|
||||||
inputTickActions,
|
inputTickActions,
|
||||||
/Expected "pointerType" to be a string/,
|
/Expected "pointerType" to be a string/,
|
||||||
message
|
message
|
||||||
|
@ -77,7 +80,7 @@ add_task(function test_processPointerParameters() {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
let message = `Action sequence with parameters: {pointerType: ${pointerType} subtype: ${subtype}}`;
|
let message = `Action sequence with parameters: {pointerType: ${pointerType} subtype: ${subtype}}`;
|
||||||
checkFromJSONErrors(
|
await checkFromJSONErrors(
|
||||||
inputTickActions,
|
inputTickActions,
|
||||||
/Expected "pointerType" to be one of/,
|
/Expected "pointerType" to be one of/,
|
||||||
message
|
message
|
||||||
|
@ -95,7 +98,10 @@ add_task(function test_processPointerParameters() {
|
||||||
button: 0,
|
button: 0,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const chain = action.Chain.fromJSON(state, chainForTick(inputTickActions));
|
const chain = await action.Chain.fromJSON(
|
||||||
|
state,
|
||||||
|
chainForTick(inputTickActions)
|
||||||
|
);
|
||||||
const pointerAction = chain[0][0];
|
const pointerAction = chain[0][0];
|
||||||
equal(
|
equal(
|
||||||
state.getInputSource(pointerAction.id).pointer.constructor.type,
|
state.getInputSource(pointerAction.id).pointer.constructor.type,
|
||||||
|
@ -104,12 +110,12 @@ add_task(function test_processPointerParameters() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_processPointerDownAction() {
|
add_task(async function test_processPointerDownAction() {
|
||||||
for (let button of [-1, "a"]) {
|
for (let button of [-1, "a"]) {
|
||||||
const inputTickActions = [
|
const inputTickActions = [
|
||||||
{ type: "pointer", subtype: "pointerDown", button },
|
{ type: "pointer", subtype: "pointerDown", button },
|
||||||
];
|
];
|
||||||
checkFromJSONErrors(
|
await checkFromJSONErrors(
|
||||||
inputTickActions,
|
inputTickActions,
|
||||||
/Expected "button" to be a positive integer/,
|
/Expected "button" to be a positive integer/,
|
||||||
`pointerDown with {button: ${button}}`
|
`pointerDown with {button: ${button}}`
|
||||||
|
@ -119,18 +125,21 @@ add_task(function test_processPointerDownAction() {
|
||||||
const inputTickActions = [
|
const inputTickActions = [
|
||||||
{ type: "pointer", subtype: "pointerDown", button: 5 },
|
{ type: "pointer", subtype: "pointerDown", button: 5 },
|
||||||
];
|
];
|
||||||
const chain = action.Chain.fromJSON(state, chainForTick(inputTickActions));
|
const chain = await action.Chain.fromJSON(
|
||||||
|
state,
|
||||||
|
chainForTick(inputTickActions)
|
||||||
|
);
|
||||||
equal(chain[0][0].button, 5);
|
equal(chain[0][0].button, 5);
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_validateActionDurationAndCoordinates() {
|
add_task(async function test_validateActionDurationAndCoordinates() {
|
||||||
for (let [type, subtype] of [
|
for (let [type, subtype] of [
|
||||||
["none", "pause"],
|
["none", "pause"],
|
||||||
["pointer", "pointerMove"],
|
["pointer", "pointerMove"],
|
||||||
]) {
|
]) {
|
||||||
for (let duration of [-1, "a"]) {
|
for (let duration of [-1, "a"]) {
|
||||||
const inputTickActions = [{ type, subtype, duration }];
|
const inputTickActions = [{ type, subtype, duration }];
|
||||||
checkFromJSONErrors(
|
await checkFromJSONErrors(
|
||||||
inputTickActions,
|
inputTickActions,
|
||||||
/Expected "duration" to be a positive integer/,
|
/Expected "duration" to be a positive integer/,
|
||||||
`{subtype} with {duration: ${duration}}`
|
`{subtype} with {duration: ${duration}}`
|
||||||
|
@ -144,7 +153,7 @@ add_task(function test_validateActionDurationAndCoordinates() {
|
||||||
duration: 5000,
|
duration: 5000,
|
||||||
};
|
};
|
||||||
actionItem[name] = "a";
|
actionItem[name] = "a";
|
||||||
checkFromJSONErrors(
|
await checkFromJSONErrors(
|
||||||
[actionItem],
|
[actionItem],
|
||||||
/Expected ".*" to be an integer/,
|
/Expected ".*" to be an integer/,
|
||||||
`${name}: "a", subtype: pointerMove`
|
`${name}: "a", subtype: pointerMove`
|
||||||
|
@ -152,54 +161,73 @@ add_task(function test_validateActionDurationAndCoordinates() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_processPointerMoveActionOriginValidation() {
|
add_task(async function test_processPointerMoveActionOriginStringValidation() {
|
||||||
for (let origin of [-1, { a: "blah" }, []]) {
|
|
||||||
const inputTickActions = [
|
|
||||||
{ type: "pointer", duration: 5000, subtype: "pointerMove", origin },
|
|
||||||
];
|
|
||||||
checkFromJSONErrors(
|
|
||||||
inputTickActions,
|
|
||||||
/Expected "origin" to be undefined, "viewport", "pointer", or an element/,
|
|
||||||
`actionItem.origin: (${getTypeString(origin)})`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(function test_processPointerMoveActionOriginStringValidation() {
|
|
||||||
for (let origin of ["", "viewports", "pointers"]) {
|
for (let origin of ["", "viewports", "pointers"]) {
|
||||||
const inputTickActions = [
|
|
||||||
{ type: "pointer", duration: 5000, subtype: "pointerMove", origin },
|
|
||||||
];
|
|
||||||
checkFromJSONErrors(
|
|
||||||
inputTickActions,
|
|
||||||
/Expected "origin" to be undefined, "viewport", "pointer", or an element/,
|
|
||||||
`actionItem.origin: ${origin}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(function test_processPointerMoveActionElementOrigin() {
|
|
||||||
let state = new action.State();
|
|
||||||
const inputTickActions = [
|
const inputTickActions = [
|
||||||
{
|
{
|
||||||
type: "pointer",
|
type: "pointer",
|
||||||
duration: 5000,
|
|
||||||
subtype: "pointerMove",
|
|
||||||
origin: domEl,
|
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
|
duration: 5000,
|
||||||
|
subtype: "pointerMove",
|
||||||
|
origin,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const chain = action.Chain.fromJSON(state, chainForTick(inputTickActions));
|
await checkFromJSONErrors(
|
||||||
deepEqual(chain[0][0].origin.element, domEl);
|
inputTickActions,
|
||||||
|
/Expected "origin" to be undefined, "viewport", "pointer", or an element/,
|
||||||
|
`actionItem.origin: ${origin}`,
|
||||||
|
{ isElementOrigin: () => false }
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_processPointerMoveActionDefaultOrigin() {
|
add_task(async function test_processPointerMoveActionOriginElementValidation() {
|
||||||
|
const element = { foo: "bar" };
|
||||||
|
const inputTickActions = [
|
||||||
|
{
|
||||||
|
type: "pointer",
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
duration: 5000,
|
||||||
|
subtype: "pointerMove",
|
||||||
|
origin: element,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// invalid element origin
|
||||||
|
await checkFromJSONErrors(
|
||||||
|
inputTickActions,
|
||||||
|
/Expected "origin" to be undefined, "viewport", "pointer", or an element/,
|
||||||
|
`actionItem.origin: (${getTypeString(element)})`,
|
||||||
|
{ isElementOrigin: elem => "foo1" in elem }
|
||||||
|
);
|
||||||
|
|
||||||
|
let state = new action.State();
|
||||||
|
const actionsOptions = {
|
||||||
|
isElementOrigin: elem => "foo" in elem,
|
||||||
|
getElementOrigin: elem => elem,
|
||||||
|
};
|
||||||
|
|
||||||
|
// valid element origin
|
||||||
|
const chain = await action.Chain.fromJSON(
|
||||||
|
state,
|
||||||
|
chainForTick(inputTickActions),
|
||||||
|
actionsOptions
|
||||||
|
);
|
||||||
|
deepEqual(chain[0][0].origin, { element });
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_processPointerMoveActionDefaultOrigin() {
|
||||||
let state = new action.State();
|
let state = new action.State();
|
||||||
const inputTickActions = [
|
const inputTickActions = [
|
||||||
{ type: "pointer", duration: 5000, subtype: "pointerMove", x: 0, y: 0 },
|
{ type: "pointer", x: 0, y: 0, duration: 5000, subtype: "pointerMove" },
|
||||||
];
|
];
|
||||||
const chain = action.Chain.fromJSON(state, chainForTick(inputTickActions));
|
const chain = await action.Chain.fromJSON(
|
||||||
|
state,
|
||||||
|
chainForTick(inputTickActions),
|
||||||
|
{}
|
||||||
|
);
|
||||||
// The default is viewport coordinates which have an origin at [0,0] and don't depend on inputSource
|
// The default is viewport coordinates which have an origin at [0,0] and don't depend on inputSource
|
||||||
deepEqual(chain[0][0].origin.getOriginCoordinates(null, null), {
|
deepEqual(chain[0][0].origin.getOriginCoordinates(null, null), {
|
||||||
x: 0,
|
x: 0,
|
||||||
|
@ -207,7 +235,7 @@ add_task(function test_processPointerMoveActionDefaultOrigin() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_processPointerMoveAction() {
|
add_task(async function test_processPointerMoveAction() {
|
||||||
let state = new action.State();
|
let state = new action.State();
|
||||||
const actionItems = [
|
const actionItems = [
|
||||||
{
|
{
|
||||||
|
@ -237,7 +265,16 @@ add_task(function test_processPointerMoveAction() {
|
||||||
type: "pointer",
|
type: "pointer",
|
||||||
actions: actionItems,
|
actions: actionItems,
|
||||||
};
|
};
|
||||||
let chain = action.Chain.fromJSON(state, [actionSequence]);
|
let actionsOptions = {
|
||||||
|
isElementOrigin: elem => elem == domEl,
|
||||||
|
getElementOrigin: elem => elem,
|
||||||
|
};
|
||||||
|
|
||||||
|
let chain = await action.Chain.fromJSON(
|
||||||
|
state,
|
||||||
|
[actionSequence],
|
||||||
|
actionsOptions
|
||||||
|
);
|
||||||
equal(chain.length, actionItems.length);
|
equal(chain.length, actionItems.length);
|
||||||
for (let i = 0; i < actionItems.length; i++) {
|
for (let i = 0; i < actionItems.length; i++) {
|
||||||
let actual = chain[i][0];
|
let actual = chain[i][0];
|
||||||
|
@ -258,7 +295,7 @@ add_task(function test_processPointerMoveAction() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_computePointerDestinationViewport() {
|
add_task(async function test_computePointerDestinationViewport() {
|
||||||
const state = new action.State();
|
const state = new action.State();
|
||||||
const inputTickActions = [
|
const inputTickActions = [
|
||||||
{
|
{
|
||||||
|
@ -269,13 +306,17 @@ add_task(function test_computePointerDestinationViewport() {
|
||||||
origin: "viewport",
|
origin: "viewport",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const chain = action.Chain.fromJSON(state, chainForTick(inputTickActions));
|
const chain = await action.Chain.fromJSON(
|
||||||
|
state,
|
||||||
|
chainForTick(inputTickActions),
|
||||||
|
{}
|
||||||
|
);
|
||||||
const actionItem = chain[0][0];
|
const actionItem = chain[0][0];
|
||||||
const inputSource = state.getInputSource(actionItem.id);
|
const inputSource = state.getInputSource(actionItem.id);
|
||||||
// these values should not affect the outcome
|
// these values should not affect the outcome
|
||||||
inputSource.x = "99";
|
inputSource.x = "99";
|
||||||
inputSource.y = "10";
|
inputSource.y = "10";
|
||||||
const target = actionItem.origin.getTargetCoordinates(
|
const target = await actionItem.origin.getTargetCoordinates(
|
||||||
inputSource,
|
inputSource,
|
||||||
[actionItem.x, actionItem.y],
|
[actionItem.x, actionItem.y],
|
||||||
null
|
null
|
||||||
|
@ -284,7 +325,7 @@ add_task(function test_computePointerDestinationViewport() {
|
||||||
equal(actionItem.y, target[1]);
|
equal(actionItem.y, target[1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_computePointerDestinationPointer() {
|
add_task(async function test_computePointerDestinationPointer() {
|
||||||
const state = new action.State();
|
const state = new action.State();
|
||||||
const inputTickActions = [
|
const inputTickActions = [
|
||||||
{
|
{
|
||||||
|
@ -295,12 +336,16 @@ add_task(function test_computePointerDestinationPointer() {
|
||||||
origin: "pointer",
|
origin: "pointer",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const chain = action.Chain.fromJSON(state, chainForTick(inputTickActions));
|
const chain = await action.Chain.fromJSON(
|
||||||
|
state,
|
||||||
|
chainForTick(inputTickActions),
|
||||||
|
{}
|
||||||
|
);
|
||||||
const actionItem = chain[0][0];
|
const actionItem = chain[0][0];
|
||||||
const inputSource = state.getInputSource(actionItem.id);
|
const inputSource = state.getInputSource(actionItem.id);
|
||||||
inputSource.x = 10;
|
inputSource.x = 10;
|
||||||
inputSource.y = 99;
|
inputSource.y = 99;
|
||||||
const target = actionItem.origin.getTargetCoordinates(
|
const target = await actionItem.origin.getTargetCoordinates(
|
||||||
inputSource,
|
inputSource,
|
||||||
[actionItem.x, actionItem.y],
|
[actionItem.x, actionItem.y],
|
||||||
null
|
null
|
||||||
|
@ -309,7 +354,7 @@ add_task(function test_computePointerDestinationPointer() {
|
||||||
equal(actionItem.y + inputSource.y, target[1]);
|
equal(actionItem.y + inputSource.y, target[1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_processPointerAction() {
|
add_task(async function test_processPointerAction() {
|
||||||
for (let pointerType of ["mouse", "touch"]) {
|
for (let pointerType of ["mouse", "touch"]) {
|
||||||
const actionItems = [
|
const actionItems = [
|
||||||
{
|
{
|
||||||
|
@ -336,7 +381,7 @@ add_task(function test_processPointerAction() {
|
||||||
actions: actionItems,
|
actions: actionItems,
|
||||||
};
|
};
|
||||||
const state = new action.State();
|
const state = new action.State();
|
||||||
const chain = action.Chain.fromJSON(state, [actionSequence]);
|
const chain = await action.Chain.fromJSON(state, [actionSequence], {});
|
||||||
equal(chain.length, actionItems.length);
|
equal(chain.length, actionItems.length);
|
||||||
for (let i = 0; i < actionItems.length; i++) {
|
for (let i = 0; i < actionItems.length; i++) {
|
||||||
const actual = chain[i][0];
|
const actual = chain[i][0];
|
||||||
|
@ -359,7 +404,7 @@ add_task(function test_processPointerAction() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_processPauseAction() {
|
add_task(async function test_processPauseAction() {
|
||||||
for (let type of ["none", "key", "pointer"]) {
|
for (let type of ["none", "key", "pointer"]) {
|
||||||
const state = new action.State();
|
const state = new action.State();
|
||||||
const actionSequence = {
|
const actionSequence = {
|
||||||
|
@ -367,7 +412,8 @@ add_task(function test_processPauseAction() {
|
||||||
id: "some_id",
|
id: "some_id",
|
||||||
actions: [{ type: "pause", duration: 5000 }],
|
actions: [{ type: "pause", duration: 5000 }],
|
||||||
};
|
};
|
||||||
const actionItem = action.Chain.fromJSON(state, [actionSequence])[0][0];
|
const actions = await action.Chain.fromJSON(state, [actionSequence], {});
|
||||||
|
const actionItem = actions[0][0];
|
||||||
equal(actionItem.type, "none");
|
equal(actionItem.type, "none");
|
||||||
equal(actionItem.subtype, "pause");
|
equal(actionItem.subtype, "pause");
|
||||||
equal(actionItem.id, "some_id");
|
equal(actionItem.id, "some_id");
|
||||||
|
@ -379,15 +425,16 @@ add_task(function test_processPauseAction() {
|
||||||
id: "some_id",
|
id: "some_id",
|
||||||
actions: [{ type: "pause" }],
|
actions: [{ type: "pause" }],
|
||||||
};
|
};
|
||||||
const actionItem = action.Chain.fromJSON(state, [actionSequence])[0][0];
|
const actions = await action.Chain.fromJSON(state, [actionSequence], {});
|
||||||
|
const actionItem = actions[0][0];
|
||||||
equal(actionItem.duration, undefined);
|
equal(actionItem.duration, undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_processActionSubtypeValidation() {
|
add_task(async function test_processActionSubtypeValidation() {
|
||||||
for (let type of ["none", "key", "pointer"]) {
|
for (let type of ["none", "key", "pointer"]) {
|
||||||
const message = `type: ${type}, subtype: dancing`;
|
const message = `type: ${type}, subtype: dancing`;
|
||||||
const inputTickActions = [{ type, subtype: "dancing" }];
|
const inputTickActions = [{ type, subtype: "dancing" }];
|
||||||
checkFromJSONErrors(
|
await checkFromJSONErrors(
|
||||||
inputTickActions,
|
inputTickActions,
|
||||||
new RegExp(`Expected known subtype for type`),
|
new RegExp(`Expected known subtype for type`),
|
||||||
message
|
message
|
||||||
|
@ -395,11 +442,11 @@ add_task(function test_processActionSubtypeValidation() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_processKeyActionDown() {
|
add_task(async function test_processKeyActionDown() {
|
||||||
for (let value of [-1, undefined, [], ["a"], { length: 1 }, null]) {
|
for (let value of [-1, undefined, [], ["a"], { length: 1 }, null]) {
|
||||||
const inputTickActions = [{ type: "key", subtype: "keyDown", value }];
|
const inputTickActions = [{ type: "key", subtype: "keyDown", value }];
|
||||||
const message = `actionItem.value: (${getTypeString(value)})`;
|
const message = `actionItem.value: (${getTypeString(value)})`;
|
||||||
checkFromJSONErrors(
|
await checkFromJSONErrors(
|
||||||
inputTickActions,
|
inputTickActions,
|
||||||
/Expected "value" to be a string that represents single code point/,
|
/Expected "value" to be a string that represents single code point/,
|
||||||
message
|
message
|
||||||
|
@ -412,7 +459,8 @@ add_task(function test_processKeyActionDown() {
|
||||||
id: "keyboard",
|
id: "keyboard",
|
||||||
actions: [{ type: "keyDown", value: "\uE004" }],
|
actions: [{ type: "keyDown", value: "\uE004" }],
|
||||||
};
|
};
|
||||||
const actionItem = action.Chain.fromJSON(state, [actionSequence])[0][0];
|
const actions = await action.Chain.fromJSON(state, [actionSequence], {});
|
||||||
|
const actionItem = actions[0][0];
|
||||||
|
|
||||||
equal(actionItem.type, "key");
|
equal(actionItem.type, "key");
|
||||||
equal(actionItem.id, "keyboard");
|
equal(actionItem.id, "keyboard");
|
||||||
|
@ -420,20 +468,20 @@ add_task(function test_processKeyActionDown() {
|
||||||
equal(actionItem.value, "\ue004");
|
equal(actionItem.value, "\ue004");
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_processInputSourceActionSequenceValidation() {
|
add_task(async function test_processInputSourceActionSequenceValidation() {
|
||||||
checkFromJSONErrors(
|
await checkFromJSONErrors(
|
||||||
[{ type: "swim", subtype: "pause", id: "some id" }],
|
[{ type: "swim", subtype: "pause", id: "some id" }],
|
||||||
/Expected known action type/,
|
/Expected known action type/,
|
||||||
"actionSequence type: swim"
|
"actionSequence type: swim"
|
||||||
);
|
);
|
||||||
|
|
||||||
checkFromJSONErrors(
|
await checkFromJSONErrors(
|
||||||
[{ type: "none", subtype: "pause", id: -1 }],
|
[{ type: "none", subtype: "pause", id: -1 }],
|
||||||
/Expected "id" to be a string/,
|
/Expected "id" to be a string/,
|
||||||
"actionSequence id: -1"
|
"actionSequence id: -1"
|
||||||
);
|
);
|
||||||
|
|
||||||
checkFromJSONErrors(
|
await checkFromJSONErrors(
|
||||||
[{ type: "none", subtype: "pause", id: undefined }],
|
[{ type: "none", subtype: "pause", id: undefined }],
|
||||||
/Expected "id" to be a string/,
|
/Expected "id" to be a string/,
|
||||||
"actionSequence id: undefined"
|
"actionSequence id: undefined"
|
||||||
|
@ -446,19 +494,19 @@ add_task(function test_processInputSourceActionSequenceValidation() {
|
||||||
const errorRegex = /Expected "actionSequence.actions" to be an array/;
|
const errorRegex = /Expected "actionSequence.actions" to be an array/;
|
||||||
const message = "actionSequence actions: -1";
|
const message = "actionSequence actions: -1";
|
||||||
|
|
||||||
Assert.throws(
|
await Assert.rejects(
|
||||||
() => action.Chain.fromJSON(state, actionSequence),
|
action.Chain.fromJSON(state, actionSequence, {}),
|
||||||
/InvalidArgumentError/,
|
/InvalidArgumentError/,
|
||||||
message
|
message
|
||||||
);
|
);
|
||||||
Assert.throws(
|
await Assert.rejects(
|
||||||
() => action.Chain.fromJSON(state, actionSequence),
|
action.Chain.fromJSON(state, actionSequence, {}),
|
||||||
errorRegex,
|
errorRegex,
|
||||||
message
|
message
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_processInputSourceActionSequence() {
|
add_task(async function test_processInputSourceActionSequence() {
|
||||||
const state = new action.State();
|
const state = new action.State();
|
||||||
const actionItem = { type: "pause", duration: 5 };
|
const actionItem = { type: "pause", duration: 5 };
|
||||||
const actionSequence = {
|
const actionSequence = {
|
||||||
|
@ -466,7 +514,7 @@ add_task(function test_processInputSourceActionSequence() {
|
||||||
id: "some id",
|
id: "some id",
|
||||||
actions: [actionItem],
|
actions: [actionItem],
|
||||||
};
|
};
|
||||||
const chain = action.Chain.fromJSON(state, [actionSequence]);
|
const chain = await action.Chain.fromJSON(state, [actionSequence], {});
|
||||||
equal(chain.length, 1);
|
equal(chain.length, 1);
|
||||||
const tickActions = chain[0];
|
const tickActions = chain[0];
|
||||||
equal(tickActions.length, 1);
|
equal(tickActions.length, 1);
|
||||||
|
@ -476,7 +524,7 @@ add_task(function test_processInputSourceActionSequence() {
|
||||||
equal(tickActions[0].id, "some id");
|
equal(tickActions[0].id, "some id");
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_processInputSourceActionSequencePointer() {
|
add_task(async function test_processInputSourceActionSequencePointer() {
|
||||||
const state = new action.State();
|
const state = new action.State();
|
||||||
const actionItem = { type: "pointerDown", button: 1 };
|
const actionItem = { type: "pointerDown", button: 1 };
|
||||||
const actionSequence = {
|
const actionSequence = {
|
||||||
|
@ -487,7 +535,7 @@ add_task(function test_processInputSourceActionSequencePointer() {
|
||||||
pointerType: "mouse", // TODO "pen"
|
pointerType: "mouse", // TODO "pen"
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const chain = action.Chain.fromJSON(state, [actionSequence]);
|
const chain = await action.Chain.fromJSON(state, [actionSequence], {});
|
||||||
equal(chain.length, 1);
|
equal(chain.length, 1);
|
||||||
const tickActions = chain[0];
|
const tickActions = chain[0];
|
||||||
equal(tickActions.length, 1);
|
equal(tickActions.length, 1);
|
||||||
|
@ -500,7 +548,7 @@ add_task(function test_processInputSourceActionSequencePointer() {
|
||||||
equal(inputSource.pointer.constructor.type, "mouse");
|
equal(inputSource.pointer.constructor.type, "mouse");
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_processInputSourceActionSequenceKey() {
|
add_task(async function test_processInputSourceActionSequenceKey() {
|
||||||
const state = new action.State();
|
const state = new action.State();
|
||||||
const actionItem = { type: "keyUp", value: "a" };
|
const actionItem = { type: "keyUp", value: "a" };
|
||||||
const actionSequence = {
|
const actionSequence = {
|
||||||
|
@ -508,7 +556,7 @@ add_task(function test_processInputSourceActionSequenceKey() {
|
||||||
id: "9",
|
id: "9",
|
||||||
actions: [actionItem],
|
actions: [actionItem],
|
||||||
};
|
};
|
||||||
const chain = action.Chain.fromJSON(state, [actionSequence]);
|
const chain = await action.Chain.fromJSON(state, [actionSequence], {});
|
||||||
equal(chain.length, 1);
|
equal(chain.length, 1);
|
||||||
const tickActions = chain[0];
|
const tickActions = chain[0];
|
||||||
equal(tickActions.length, 1);
|
equal(tickActions.length, 1);
|
||||||
|
@ -518,7 +566,7 @@ add_task(function test_processInputSourceActionSequenceKey() {
|
||||||
equal(tickActions[0].id, "9");
|
equal(tickActions[0].id, "9");
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_processInputSourceActionSequenceInputStateMap() {
|
add_task(async function test_processInputSourceActionSequenceInputStateMap() {
|
||||||
const state = new action.State();
|
const state = new action.State();
|
||||||
const id = "1";
|
const id = "1";
|
||||||
const actionItem = { type: "pause", duration: 5000 };
|
const actionItem = { type: "pause", duration: 5000 };
|
||||||
|
@ -527,7 +575,7 @@ add_task(function test_processInputSourceActionSequenceInputStateMap() {
|
||||||
id,
|
id,
|
||||||
actions: [actionItem],
|
actions: [actionItem],
|
||||||
};
|
};
|
||||||
action.Chain.fromJSON(state, [actionSequence]);
|
await action.Chain.fromJSON(state, [actionSequence], {});
|
||||||
equal(state.inputStateMap.size, 1);
|
equal(state.inputStateMap.size, 1);
|
||||||
equal(state.inputStateMap.get(id).constructor.type, "key");
|
equal(state.inputStateMap.get(id).constructor.type, "key");
|
||||||
|
|
||||||
|
@ -539,7 +587,7 @@ add_task(function test_processInputSourceActionSequenceInputStateMap() {
|
||||||
id,
|
id,
|
||||||
actions: [actionItem1],
|
actions: [actionItem1],
|
||||||
};
|
};
|
||||||
action.Chain.fromJSON(state1, [actionSequence1]);
|
await action.Chain.fromJSON(state1, [actionSequence1], {});
|
||||||
equal(state1.inputStateMap.size, 1);
|
equal(state1.inputStateMap.size, 1);
|
||||||
|
|
||||||
// Overwrite the state in the initial map with one of a different type
|
// Overwrite the state in the initial map with one of a different type
|
||||||
|
@ -547,41 +595,41 @@ add_task(function test_processInputSourceActionSequenceInputStateMap() {
|
||||||
equal(state.inputStateMap.get(id).constructor.type, "pointer");
|
equal(state.inputStateMap.get(id).constructor.type, "pointer");
|
||||||
|
|
||||||
const message = "Wrong state for input id type";
|
const message = "Wrong state for input id type";
|
||||||
Assert.throws(
|
await Assert.rejects(
|
||||||
() => action.Chain.fromJSON(state, [actionSequence]),
|
action.Chain.fromJSON(state, [actionSequence]),
|
||||||
/InvalidArgumentError/,
|
/InvalidArgumentError/,
|
||||||
message
|
message
|
||||||
);
|
);
|
||||||
Assert.throws(
|
await Assert.rejects(
|
||||||
() => action.Chain.fromJSON(state, [actionSequence]),
|
action.Chain.fromJSON(state, [actionSequence]),
|
||||||
/Expected input source \[object String\] "1" to be type pointer/,
|
/Expected input source \[object String\] "1" to be type pointer/,
|
||||||
message
|
message
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_extractActionChainValidation() {
|
add_task(async function test_extractActionChainValidation() {
|
||||||
for (let actions of [-1, "a", undefined, null]) {
|
for (let actions of [-1, "a", undefined, null]) {
|
||||||
const state = new action.State();
|
const state = new action.State();
|
||||||
let message = `actions: ${getTypeString(actions)}`;
|
let message = `actions: ${getTypeString(actions)}`;
|
||||||
Assert.throws(
|
await Assert.rejects(
|
||||||
() => action.Chain.fromJSON(state, actions),
|
action.Chain.fromJSON(state, actions),
|
||||||
/InvalidArgumentError/,
|
/InvalidArgumentError/,
|
||||||
message
|
message
|
||||||
);
|
);
|
||||||
Assert.throws(
|
await Assert.rejects(
|
||||||
() => action.Chain.fromJSON(state, actions),
|
action.Chain.fromJSON(state, actions),
|
||||||
/Expected "actions" to be an array/,
|
/Expected "actions" to be an array/,
|
||||||
message
|
message
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_extractActionChainEmpty() {
|
add_task(async function test_extractActionChainEmpty() {
|
||||||
const state = new action.State();
|
const state = new action.State();
|
||||||
deepEqual(action.Chain.fromJSON(state, []), []);
|
deepEqual(await action.Chain.fromJSON(state, [], {}), []);
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_extractActionChain_oneTickOneInput() {
|
add_task(async function test_extractActionChain_oneTickOneInput() {
|
||||||
const state = new action.State();
|
const state = new action.State();
|
||||||
const actionItem = { type: "pause", duration: 5000 };
|
const actionItem = { type: "pause", duration: 5000 };
|
||||||
const actionSequence = {
|
const actionSequence = {
|
||||||
|
@ -589,7 +637,11 @@ add_task(function test_extractActionChain_oneTickOneInput() {
|
||||||
id: "some id",
|
id: "some id",
|
||||||
actions: [actionItem],
|
actions: [actionItem],
|
||||||
};
|
};
|
||||||
const actionsByTick = action.Chain.fromJSON(state, [actionSequence]);
|
const actionsByTick = await action.Chain.fromJSON(
|
||||||
|
state,
|
||||||
|
[actionSequence],
|
||||||
|
{}
|
||||||
|
);
|
||||||
equal(1, actionsByTick.length);
|
equal(1, actionsByTick.length);
|
||||||
equal(1, actionsByTick[0].length);
|
equal(1, actionsByTick[0].length);
|
||||||
equal(actionsByTick[0][0].id, actionSequence.id);
|
equal(actionsByTick[0][0].id, actionSequence.id);
|
||||||
|
@ -598,7 +650,7 @@ add_task(function test_extractActionChain_oneTickOneInput() {
|
||||||
equal(actionsByTick[0][0].duration, actionItem.duration);
|
equal(actionsByTick[0][0].duration, actionItem.duration);
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_extractActionChain_twoAndThreeTicks() {
|
add_task(async function test_extractActionChain_twoAndThreeTicks() {
|
||||||
const state = new action.State();
|
const state = new action.State();
|
||||||
const mouseActionItems = [
|
const mouseActionItems = [
|
||||||
{
|
{
|
||||||
|
@ -637,10 +689,11 @@ add_task(function test_extractActionChain_twoAndThreeTicks() {
|
||||||
id: "1",
|
id: "1",
|
||||||
actions: keyActionItems,
|
actions: keyActionItems,
|
||||||
};
|
};
|
||||||
let actionsByTick = action.Chain.fromJSON(state, [
|
let actionsByTick = await action.Chain.fromJSON(
|
||||||
keyActionSequence,
|
state,
|
||||||
mouseActionSequence,
|
[keyActionSequence, mouseActionSequence],
|
||||||
]);
|
{}
|
||||||
|
);
|
||||||
// number of ticks is same as longest action sequence
|
// number of ticks is same as longest action sequence
|
||||||
equal(keyActionItems.length, actionsByTick.length);
|
equal(keyActionItems.length, actionsByTick.length);
|
||||||
equal(2, actionsByTick[0].length);
|
equal(2, actionsByTick[0].length);
|
||||||
|
@ -652,7 +705,7 @@ add_task(function test_extractActionChain_twoAndThreeTicks() {
|
||||||
equal(actionsByTick[2][0].subtype, "keyUp");
|
equal(actionsByTick[2][0].subtype, "keyUp");
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_computeTickDuration() {
|
add_task(async function test_computeTickDuration() {
|
||||||
const state = new action.State();
|
const state = new action.State();
|
||||||
const expected = 8000;
|
const expected = 8000;
|
||||||
const inputTickActions = [
|
const inputTickActions = [
|
||||||
|
@ -664,13 +717,17 @@ add_task(function test_computeTickDuration() {
|
||||||
{ type: "pointer", subtype: "pause", duration: expected },
|
{ type: "pointer", subtype: "pause", duration: expected },
|
||||||
{ type: "pointer", subtype: "pointerUp", button: 0 },
|
{ type: "pointer", subtype: "pointerUp", button: 0 },
|
||||||
];
|
];
|
||||||
const chain = action.Chain.fromJSON(state, chainForTick(inputTickActions));
|
const chain = await action.Chain.fromJSON(
|
||||||
|
state,
|
||||||
|
chainForTick(inputTickActions),
|
||||||
|
{}
|
||||||
|
);
|
||||||
equal(1, chain.length);
|
equal(1, chain.length);
|
||||||
const tickActions = chain[0];
|
const tickActions = chain[0];
|
||||||
equal(expected, tickActions.getDuration());
|
equal(expected, tickActions.getDuration());
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(function test_computeTickDuration_noDurations() {
|
add_task(async function test_computeTickDuration_noDurations() {
|
||||||
const state = new action.State();
|
const state = new action.State();
|
||||||
const inputTickActions = [
|
const inputTickActions = [
|
||||||
// invalid because keyDown should not have duration, so duration should be ignored.
|
// invalid because keyDown should not have duration, so duration should be ignored.
|
||||||
|
@ -681,7 +738,11 @@ add_task(function test_computeTickDuration_noDurations() {
|
||||||
{ type: "pointer", subtype: "pointerDown", button: 0 },
|
{ type: "pointer", subtype: "pointerDown", button: 0 },
|
||||||
{ type: "key", subtype: "keyUp", value: "a" },
|
{ type: "key", subtype: "keyUp", value: "a" },
|
||||||
];
|
];
|
||||||
const chain = action.Chain.fromJSON(state, chainForTick(inputTickActions));
|
const chain = await action.Chain.fromJSON(
|
||||||
|
state,
|
||||||
|
chainForTick(inputTickActions),
|
||||||
|
{}
|
||||||
|
);
|
||||||
equal(0, chain[0].getDuration());
|
equal(0, chain[0].getDuration());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -719,19 +780,37 @@ function getTypeString(obj) {
|
||||||
return Object.prototype.toString.call(obj);
|
return Object.prototype.toString.call(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkFromJSONErrors(inputTickActions, regex, message) {
|
async function checkFromJSONErrors(
|
||||||
|
inputTickActions,
|
||||||
|
regex,
|
||||||
|
message,
|
||||||
|
options = {}
|
||||||
|
) {
|
||||||
|
const { isElementOrigin = () => true, getElementOrigin = elem => elem } =
|
||||||
|
options;
|
||||||
|
|
||||||
const state = new action.State();
|
const state = new action.State();
|
||||||
|
const actionsOptions = { isElementOrigin, getElementOrigin };
|
||||||
|
|
||||||
if (typeof message == "undefined") {
|
if (typeof message == "undefined") {
|
||||||
message = `fromJSON`;
|
message = `fromJSON`;
|
||||||
}
|
}
|
||||||
Assert.throws(
|
|
||||||
() => action.Chain.fromJSON(state, chainForTick(inputTickActions)),
|
await Assert.rejects(
|
||||||
|
action.Chain.fromJSON(
|
||||||
|
state,
|
||||||
|
chainForTick(inputTickActions),
|
||||||
|
actionsOptions
|
||||||
|
),
|
||||||
/InvalidArgumentError/,
|
/InvalidArgumentError/,
|
||||||
message
|
message
|
||||||
);
|
);
|
||||||
Assert.throws(
|
await Assert.rejects(
|
||||||
() => action.Chain.fromJSON(state, chainForTick(inputTickActions)),
|
action.Chain.fromJSON(
|
||||||
|
state,
|
||||||
|
chainForTick(inputTickActions),
|
||||||
|
actionsOptions
|
||||||
|
),
|
||||||
regex,
|
regex,
|
||||||
message
|
message
|
||||||
);
|
);
|
||||||
|
|
|
@ -453,6 +453,13 @@
|
||||||
"expectations": ["FAIL"],
|
"expectations": ["FAIL"],
|
||||||
"comment": "TODO: add a comment explaining why this expectation is required (include links to issues)"
|
"comment": "TODO: add a comment explaining why this expectation is required (include links to issues)"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"testIdPattern": "[mouse.spec] Mouse should not throw if clicking in parallel",
|
||||||
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
"parameters": ["firefox", "webDriverBiDi"],
|
||||||
|
"expectations": ["FAIL"],
|
||||||
|
"comment": "Needs support for action queue: https://bugzilla.mozilla.org/show_bug.cgi?id=1915798"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"testIdPattern": "[mouse.spec] Mouse should reset properly",
|
"testIdPattern": "[mouse.spec] Mouse should reset properly",
|
||||||
"platforms": ["darwin", "linux", "win32"],
|
"platforms": ["darwin", "linux", "win32"],
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { RootBiDiModule } from "chrome://remote/content/webdriver-bidi/modules/R
|
||||||
const lazy = {};
|
const lazy = {};
|
||||||
|
|
||||||
ChromeUtils.defineESModuleGetters(lazy, {
|
ChromeUtils.defineESModuleGetters(lazy, {
|
||||||
|
action: "chrome://remote/content/shared/webdriver/Actions.sys.mjs",
|
||||||
assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
|
assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
|
||||||
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
|
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
|
||||||
pprint: "chrome://remote/content/shared/Format.sys.mjs",
|
pprint: "chrome://remote/content/shared/Format.sys.mjs",
|
||||||
|
@ -16,8 +17,189 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
||||||
});
|
});
|
||||||
|
|
||||||
class InputModule extends RootBiDiModule {
|
class InputModule extends RootBiDiModule {
|
||||||
|
#actionsOptions;
|
||||||
|
#inputStates;
|
||||||
|
|
||||||
|
constructor(messageHandler) {
|
||||||
|
super(messageHandler);
|
||||||
|
|
||||||
|
// Browsing context => input state.
|
||||||
|
// Bug 1821460: Move to WebDriver Session and share with Marionette.
|
||||||
|
this.#inputStates = new WeakMap();
|
||||||
|
|
||||||
|
// Options for actions to pass through performActions and releaseActions.
|
||||||
|
this.#actionsOptions = {
|
||||||
|
// Callbacks as defined in the WebDriver specification.
|
||||||
|
getElementOrigin: this.#getElementOrigin.bind(this),
|
||||||
|
isElementOrigin: this.#isElementOrigin.bind(this),
|
||||||
|
|
||||||
|
// Custom callbacks.
|
||||||
|
assertInViewPort: this.#assertInViewPort.bind(this),
|
||||||
|
dispatchEvent: this.#dispatchEvent.bind(this),
|
||||||
|
getClientRects: this.#getClientRects.bind(this),
|
||||||
|
getInViewCentrePoint: this.#getInViewCentrePoint.bind(this),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
destroy() {}
|
destroy() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert that the target coordinates are within the visible viewport.
|
||||||
|
*
|
||||||
|
* @param {Array.<number>} target
|
||||||
|
* Coordinates [x, y] of the target relative to the viewport.
|
||||||
|
* @param {BrowsingContext} context
|
||||||
|
* The browsing context to dispatch the event to.
|
||||||
|
*
|
||||||
|
* @returns {Promise<undefined>}
|
||||||
|
* Promise that rejects, if the coordinates are not within
|
||||||
|
* the visible viewport.
|
||||||
|
*
|
||||||
|
* @throws {MoveTargetOutOfBoundsError}
|
||||||
|
* If target is outside the viewport.
|
||||||
|
*/
|
||||||
|
#assertInViewPort(target, context) {
|
||||||
|
return this.messageHandler.forwardCommand({
|
||||||
|
moduleName: "input",
|
||||||
|
commandName: "_assertInViewPort",
|
||||||
|
destination: {
|
||||||
|
type: lazy.WindowGlobalMessageHandler.type,
|
||||||
|
id: context.id,
|
||||||
|
},
|
||||||
|
params: { target },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatch an event.
|
||||||
|
*
|
||||||
|
* @param {string} eventName
|
||||||
|
* Name of the event to be dispatched.
|
||||||
|
* @param {BrowsingContext} context
|
||||||
|
* The browsing context to dispatch the event to.
|
||||||
|
* @param {object} details
|
||||||
|
* Details of the event to be dispatched.
|
||||||
|
*
|
||||||
|
* @returns {Promise}
|
||||||
|
* Promise that resolves once the event is dispatched.
|
||||||
|
*/
|
||||||
|
#dispatchEvent(eventName, context, details) {
|
||||||
|
return this.messageHandler.forwardCommand({
|
||||||
|
moduleName: "input",
|
||||||
|
commandName: "_dispatchEvent",
|
||||||
|
destination: {
|
||||||
|
type: lazy.WindowGlobalMessageHandler.type,
|
||||||
|
id: context.id,
|
||||||
|
},
|
||||||
|
params: { eventName, details },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finalize an action command.
|
||||||
|
*
|
||||||
|
* @param {BrowsingContext} context
|
||||||
|
* The browsing context to forward the command to.
|
||||||
|
*
|
||||||
|
* @returns {Promise}
|
||||||
|
* Promise that resolves when the finalization is done.
|
||||||
|
*/
|
||||||
|
#finalizeAction(context) {
|
||||||
|
return this.messageHandler.forwardCommand({
|
||||||
|
moduleName: "input",
|
||||||
|
commandName: "_finalizeAction",
|
||||||
|
destination: {
|
||||||
|
type: lazy.WindowGlobalMessageHandler.type,
|
||||||
|
id: context.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the list of client rects for the element.
|
||||||
|
*
|
||||||
|
* @param {Node} node
|
||||||
|
* The web element reference to retrieve the rects from.
|
||||||
|
* @param {BrowsingContext} context
|
||||||
|
* The browsing context to dispatch the event to.
|
||||||
|
*
|
||||||
|
* @returns {Promise<Array<Map.<string, number>>>}
|
||||||
|
* Promise that resolves to a list of DOMRect-like objects.
|
||||||
|
*/
|
||||||
|
#getClientRects(node, context) {
|
||||||
|
return this.messageHandler.forwardCommand({
|
||||||
|
moduleName: "input",
|
||||||
|
commandName: "_getClientRects",
|
||||||
|
destination: {
|
||||||
|
type: lazy.WindowGlobalMessageHandler.type,
|
||||||
|
id: context.id,
|
||||||
|
},
|
||||||
|
params: { element: node },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the Node reference of the origin.
|
||||||
|
*
|
||||||
|
* @param {ElementOrigin} origin
|
||||||
|
* Reference to the element origin of the action.
|
||||||
|
* @param {BrowsingContext} context
|
||||||
|
* The browsing context to dispatch the event to.
|
||||||
|
*
|
||||||
|
* @returns {Promise<SharedReference>}
|
||||||
|
* Promise that resolves to the shared reference.
|
||||||
|
*/
|
||||||
|
#getElementOrigin(origin, context) {
|
||||||
|
return this.messageHandler.forwardCommand({
|
||||||
|
moduleName: "input",
|
||||||
|
commandName: "_getElementOrigin",
|
||||||
|
destination: {
|
||||||
|
type: lazy.WindowGlobalMessageHandler.type,
|
||||||
|
id: context.id,
|
||||||
|
},
|
||||||
|
params: { origin },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the in-view center point for the rect and visible viewport.
|
||||||
|
*
|
||||||
|
* @param {DOMRect} rect
|
||||||
|
* Size and position of the rectangle to check.
|
||||||
|
* @param {BrowsingContext} context
|
||||||
|
* The browsing context to dispatch the event to.
|
||||||
|
*
|
||||||
|
* @returns {Promise<Map.<string, number>>}
|
||||||
|
* X and Y coordinates that denotes the in-view centre point of
|
||||||
|
* `rect`.
|
||||||
|
*/
|
||||||
|
#getInViewCentrePoint(rect, context) {
|
||||||
|
return this.messageHandler.forwardCommand({
|
||||||
|
moduleName: "input",
|
||||||
|
commandName: "_getInViewCentrePoint",
|
||||||
|
destination: {
|
||||||
|
type: lazy.WindowGlobalMessageHandler.type,
|
||||||
|
id: context.id,
|
||||||
|
},
|
||||||
|
params: { rect },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the given object is a valid element origin.
|
||||||
|
*
|
||||||
|
* @param {object} origin
|
||||||
|
* The object to check.
|
||||||
|
*
|
||||||
|
* @returns {boolean}
|
||||||
|
* True, if the object references a shared reference.
|
||||||
|
*/
|
||||||
|
#isElementOrigin(origin) {
|
||||||
|
return (
|
||||||
|
origin?.type === "element" && typeof origin.element?.sharedId === "string"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async performActions(options = {}) {
|
async performActions(options = {}) {
|
||||||
const { actions, context: contextId } = options;
|
const { actions, context: contextId } = options;
|
||||||
|
|
||||||
|
@ -34,20 +216,22 @@ class InputModule extends RootBiDiModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bug 1821460: Fetch top-level browsing context.
|
// Bug 1821460: Fetch top-level browsing context.
|
||||||
|
let inputState = this.#inputStates.get(context);
|
||||||
|
if (inputState === undefined) {
|
||||||
|
inputState = new lazy.action.State();
|
||||||
|
this.#inputStates.set(context, inputState);
|
||||||
|
}
|
||||||
|
|
||||||
await this.messageHandler.forwardCommand({
|
const actionsOptions = { ...this.#actionsOptions, context };
|
||||||
moduleName: "input",
|
const actionChain = await lazy.action.Chain.fromJSON(
|
||||||
commandName: "performActions",
|
inputState,
|
||||||
destination: {
|
|
||||||
type: lazy.WindowGlobalMessageHandler.type,
|
|
||||||
id: context.id,
|
|
||||||
},
|
|
||||||
params: {
|
|
||||||
actions,
|
actions,
|
||||||
},
|
actionsOptions
|
||||||
});
|
);
|
||||||
|
await actionChain.dispatch(inputState, actionsOptions);
|
||||||
|
|
||||||
return {};
|
// Process async follow-up tasks in content before the reply is sent.
|
||||||
|
await this.#finalizeAction(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -78,18 +262,17 @@ class InputModule extends RootBiDiModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bug 1821460: Fetch top-level browsing context.
|
// Bug 1821460: Fetch top-level browsing context.
|
||||||
|
let inputState = this.#inputStates.get(context);
|
||||||
|
if (inputState === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await this.messageHandler.forwardCommand({
|
const actionsOptions = { ...this.#actionsOptions, context };
|
||||||
moduleName: "input",
|
await inputState.release(actionsOptions);
|
||||||
commandName: "releaseActions",
|
this.#inputStates.delete(context);
|
||||||
destination: {
|
|
||||||
type: lazy.WindowGlobalMessageHandler.type,
|
|
||||||
id: context.id,
|
|
||||||
},
|
|
||||||
params: {},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {};
|
// Process async follow-up tasks in content before the reply is sent.
|
||||||
|
this.#finalizeAction(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -7,45 +7,96 @@ import { WindowGlobalBiDiModule } from "chrome://remote/content/webdriver-bidi/m
|
||||||
const lazy = {};
|
const lazy = {};
|
||||||
|
|
||||||
ChromeUtils.defineESModuleGetters(lazy, {
|
ChromeUtils.defineESModuleGetters(lazy, {
|
||||||
action: "chrome://remote/content/shared/webdriver/Actions.sys.mjs",
|
AnimationFramePromise: "chrome://remote/content/shared/Sync.sys.mjs",
|
||||||
|
assertInViewPort: "chrome://remote/content/shared/webdriver/Actions.sys.mjs",
|
||||||
dom: "chrome://remote/content/shared/DOM.sys.mjs",
|
dom: "chrome://remote/content/shared/DOM.sys.mjs",
|
||||||
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
|
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
|
||||||
event: "chrome://remote/content/shared/webdriver/Event.sys.mjs",
|
event: "chrome://remote/content/shared/webdriver/Event.sys.mjs",
|
||||||
});
|
});
|
||||||
|
|
||||||
class InputModule extends WindowGlobalBiDiModule {
|
class InputModule extends WindowGlobalBiDiModule {
|
||||||
#actionState;
|
|
||||||
|
|
||||||
constructor(messageHandler) {
|
|
||||||
super(messageHandler);
|
|
||||||
|
|
||||||
this.#actionState = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {}
|
destroy() {}
|
||||||
|
|
||||||
async performActions(options) {
|
_assertInViewPort(options = {}) {
|
||||||
const { actions } = options;
|
const { target } = options;
|
||||||
if (this.#actionState === null) {
|
|
||||||
this.#actionState = new lazy.action.State();
|
return lazy.assertInViewPort(target, this.messageHandler.window);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.#deserializeActionOrigins(actions);
|
async _dispatchEvent(options = {}) {
|
||||||
const actionChain = lazy.action.Chain.fromJSON(this.#actionState, actions);
|
const { eventName, details } = options;
|
||||||
|
|
||||||
await actionChain.dispatch(this.#actionState, this.messageHandler.window);
|
switch (eventName) {
|
||||||
|
case "synthesizeKeyDown":
|
||||||
|
lazy.event.sendKeyDown(details.eventData, this.messageHandler.window);
|
||||||
|
break;
|
||||||
|
case "synthesizeKeyUp":
|
||||||
|
lazy.event.sendKeyUp(details.eventData, this.messageHandler.window);
|
||||||
|
break;
|
||||||
|
case "synthesizeMouseAtPoint":
|
||||||
|
lazy.event.synthesizeMouseAtPoint(
|
||||||
|
details.x,
|
||||||
|
details.y,
|
||||||
|
details.eventData,
|
||||||
|
this.messageHandler.window
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "synthesizeMultiTouch":
|
||||||
|
lazy.event.synthesizeMultiTouch(
|
||||||
|
details.eventData,
|
||||||
|
this.messageHandler.window
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "synthesizeWheelAtPoint":
|
||||||
|
lazy.event.synthesizeWheelAtPoint(
|
||||||
|
details.x,
|
||||||
|
details.y,
|
||||||
|
details.eventData,
|
||||||
|
this.messageHandler.window
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`${eventName} is not a supported type for dispatching`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_finalizeAction() {
|
||||||
// Terminate the current wheel transaction if there is one. Wheel
|
// Terminate the current wheel transaction if there is one. Wheel
|
||||||
// transactions should not live longer than a single action chain.
|
// transactions should not live longer than a single action chain.
|
||||||
ChromeUtils.endWheelTransaction();
|
ChromeUtils.endWheelTransaction();
|
||||||
|
|
||||||
|
// Wait for the next animation frame to make sure the page's content
|
||||||
|
// was updated.
|
||||||
|
return lazy.AnimationFramePromise(this.messageHandler.window);
|
||||||
}
|
}
|
||||||
|
|
||||||
async releaseActions() {
|
async _getClientRects(options = {}) {
|
||||||
if (this.#actionState === null) {
|
const { element: reference } = options;
|
||||||
return;
|
|
||||||
|
const element = await this.#deserializeElementSharedReference(reference);
|
||||||
|
const rects = element.getClientRects();
|
||||||
|
|
||||||
|
// To avoid serialization and deserialization of DOMRect and DOMRectList
|
||||||
|
// convert to plain object and Array.
|
||||||
|
return [...rects].map(rect => {
|
||||||
|
const { x, y, width, height, top, right, bottom, left } = rect;
|
||||||
|
return { x, y, width, height, top, right, bottom, left };
|
||||||
|
});
|
||||||
}
|
}
|
||||||
await this.#actionState.release(this.messageHandler.window);
|
|
||||||
this.#actionState = null;
|
async _getElementOrigin(options) {
|
||||||
|
const { origin } = options;
|
||||||
|
|
||||||
|
const reference = origin.element;
|
||||||
|
this.#deserializeElementSharedReference(reference);
|
||||||
|
|
||||||
|
return reference;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getInViewCentrePoint(options = {}) {
|
||||||
|
const { rect } = options;
|
||||||
|
|
||||||
|
return lazy.dom.getInViewCentrePoint(rect, this.messageHandler.window);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setFiles(options) {
|
async setFiles(options) {
|
||||||
|
@ -109,50 +160,6 @@ class InputModule extends WindowGlobalBiDiModule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* In the provided array of input.SourceActions, replace all origins matching
|
|
||||||
* the input.ElementOrigin production with the Element corresponding to this
|
|
||||||
* origin.
|
|
||||||
*
|
|
||||||
* Note that this method replaces the content of the `actions` in place, and
|
|
||||||
* does not return a new array.
|
|
||||||
*
|
|
||||||
* @param {Array<input.SourceActions>} actions
|
|
||||||
* The array of SourceActions to deserialize.
|
|
||||||
* @returns {Promise}
|
|
||||||
* A promise which resolves when all ElementOrigin origins have been
|
|
||||||
* deserialized.
|
|
||||||
*/
|
|
||||||
async #deserializeActionOrigins(actions) {
|
|
||||||
const promises = [];
|
|
||||||
|
|
||||||
if (!Array.isArray(actions)) {
|
|
||||||
// Silently ignore invalid action chains because they are fully parsed later.
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const actionsByTick of actions) {
|
|
||||||
if (!Array.isArray(actionsByTick?.actions)) {
|
|
||||||
// Silently ignore invalid actions because they are fully parsed later.
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const action of actionsByTick.actions) {
|
|
||||||
if (action?.origin?.type === "element") {
|
|
||||||
promises.push(
|
|
||||||
(async () => {
|
|
||||||
action.origin = await this.#deserializeElementSharedReference(
|
|
||||||
action.origin.element
|
|
||||||
);
|
|
||||||
})()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.all(promises);
|
|
||||||
}
|
|
||||||
|
|
||||||
async #deserializeElementSharedReference(sharedReference) {
|
async #deserializeElementSharedReference(sharedReference) {
|
||||||
if (typeof sharedReference?.sharedId !== "string") {
|
if (typeof sharedReference?.sharedId !== "string") {
|
||||||
throw new lazy.error.InvalidArgumentError(
|
throw new lazy.error.InvalidArgumentError(
|
||||||
|
|
Загрузка…
Ссылка в новой задаче