Backed out 6 changesets (bug 1915798, bug 1904665, bug 1918049) for causing wpt failures in input-events-get-target-ranges-joining-dl-elements.tentative.html CLOSED TREE

Backed out changeset 99755cd9107c (bug 1915798)
Backed out changeset 937789e1206d (bug 1915798)
Backed out changeset a204942669fe (bug 1904665)
Backed out changeset 56bcb0cc0d7f (bug 1904665)
Backed out changeset 148a13606d43 (bug 1904665)
Backed out changeset ae1bf54a9000 (bug 1918049)
This commit is contained in:
Cristian Tuns 2024-09-13 05:13:41 -04:00
Родитель c20f9e12cd
Коммит 58dba161ea
36 изменённых файлов: 826 добавлений и 2658 удалений

Просмотреть файл

@ -14,7 +14,6 @@ remote.jar:
# shared modules (all protocols)
content/shared/AppInfo.sys.mjs (shared/AppInfo.sys.mjs)
content/shared/AsyncQueue.sys.mjs (shared/AsyncQueue.sys.mjs)
content/shared/Browser.sys.mjs (shared/Browser.sys.mjs)
content/shared/Capture.sys.mjs (shared/Capture.sys.mjs)
content/shared/ChallengeHeaderParser.sys.mjs (shared/ChallengeHeaderParser.sys.mjs)

Просмотреть файл

@ -9,14 +9,11 @@ const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
accessibility:
"chrome://remote/content/shared/webdriver/Accessibility.sys.mjs",
AnimationFramePromise: "chrome://remote/content/shared/Sync.sys.mjs",
assertInViewPort: "chrome://remote/content/shared/webdriver/Actions.sys.mjs",
action: "chrome://remote/content/shared/webdriver/Actions.sys.mjs",
atom: "chrome://remote/content/marionette/atom.sys.mjs",
dom: "chrome://remote/content/shared/DOM.sys.mjs",
error: "chrome://remote/content/shared/webdriver/Errors.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",
json: "chrome://remote/content/marionette/json.sys.mjs",
Log: "chrome://remote/content/shared/Log.sys.mjs",
@ -40,6 +37,8 @@ export class MarionetteCommandsChild extends JSWindowActorChild {
// sandbox storage and name of the current sandbox
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() {
@ -60,65 +59,6 @@ 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) {
if (!this.contentWindow) {
throw new DOMException("Actor is no longer active", "InactiveActor");
@ -137,19 +77,6 @@ export class MarionetteCommandsChild extends JSWindowActorChild {
);
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":
this.clearElement(data);
waitForNextTick = true;
@ -213,6 +140,13 @@ export class MarionetteCommandsChild extends JSWindowActorChild {
case "MarionetteCommandsParent:isElementSelected":
result = await this.isElementSelected(data);
break;
case "MarionetteCommandsParent:performActions":
result = await this.performActions(data);
waitForNextTick = true;
break;
case "MarionetteCommandsParent:releaseActions":
result = await this.releaseActions();
break;
case "MarionetteCommandsParent:sendKeysToElement":
result = await this.sendKeysToElement(data);
waitForNextTick = true;
@ -230,7 +164,7 @@ export class MarionetteCommandsChild extends JSWindowActorChild {
// Inform the content process that the command has completed. It allows
// it to process async follow-up tasks before the reply is sent.
if (waitForNextTick) {
await new Promise(resolve => lazy.executeSoon(resolve));
await new Promise(resolve => Services.tm.dispatchToMainThread(resolve));
}
const { seenNodeIds, serializedValue, hasSerializedWindows } =
@ -538,6 +472,42 @@ 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.
*/

Просмотреть файл

@ -5,14 +5,12 @@
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
action: "chrome://remote/content/shared/webdriver/Actions.sys.mjs",
capture: "chrome://remote/content/shared/Capture.sys.mjs",
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
getSeenNodesForBrowsingContext:
"chrome://remote/content/shared/webdriver/Session.sys.mjs",
json: "chrome://remote/content/marionette/json.sys.mjs",
Log: "chrome://remote/content/shared/Log.sys.mjs",
WebElement: "chrome://remote/content/marionette/web-reference.sys.mjs",
});
ChromeUtils.defineLazyGetter(lazy, "logger", () =>
@ -24,146 +22,12 @@ ChromeUtils.defineLazyGetter(lazy, "logger", () =>
let webDriverSessionId = null;
export class MarionetteCommandsParent extends JSWindowActorParent {
#actionsOptions;
#actionState;
#deferredDialogOpened;
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;
}
/**
* 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) {
const seenNodes = lazy.getSeenNodesForBrowsingContext(
webDriverSessionId,
@ -379,48 +243,13 @@ export class MarionetteCommandsParent extends JSWindowActorParent {
}
async performActions(actions) {
// Bug 1821460: Use top-level browsing context.
if (this.#actionState === null) {
this.#actionState = new lazy.action.State();
}
const actionChain = await lazy.action.Chain.fromJSON(
this.#actionState,
return this.sendQuery("MarionetteCommandsParent:performActions", {
actions,
this.#actionsOptions
);
// Enqueue to serialize access to input state.
await this.#actionState.enqueueAction(() =>
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() {
// Bug 1821460: Use top-level browsing context.
if (this.#actionState === null) {
return;
}
// Enqueue to serialize access to input state.
await this.#actionState.enqueueAction(() => {
const undoActions = this.#actionState.inputCancelList.reverse();
undoActions.dispatch(this.#actionState, this.#actionsOptions);
});
this.#actionState = null;
// Process async follow-up tasks in content before the reply is sent.
await this.#finalizeAction();
return this.sendQuery("MarionetteCommandsParent:releaseActions");
}
async switchToFrame(id) {

Просмотреть файл

@ -8,7 +8,6 @@ const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
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",
});
@ -20,6 +19,20 @@ const { TYPE_ONE_SHOT, TYPE_REPEATING_SLACK } = Ci.nsITimer;
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
* through ``resolve`` or ``rejected`` callbacks. The function is
@ -310,7 +323,7 @@ export function MessageManagerDestroyedPromise(messageManager) {
*/
export function IdlePromise(win) {
const animationFramePromise = new Promise(resolve => {
lazy.executeSoon(() => {
executeSoon(() => {
win.requestAnimationFrame(resolve);
});
});

Просмотреть файл

@ -67,6 +67,24 @@ 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() {
for (let type of ["foo", 42, null, undefined, true, [], {}]) {
Assert.throws(() => new PollPromise(type), /TypeError/);

Просмотреть файл

@ -8,7 +8,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",
BulkPacket: "chrome://remote/content/marionette/packets.sys.mjs",
executeSoon: "chrome://remote/content/shared/Sync.sys.mjs",
executeSoon: "chrome://remote/content/marionette/sync.sys.mjs",
JSONPacket: "chrome://remote/content/marionette/packets.sys.mjs",
Packet: "chrome://remote/content/marionette/packets.sys.mjs",
StreamUtils: "chrome://remote/content/marionette/stream-utils.sys.mjs",

Просмотреть файл

@ -1,78 +0,0 @@
/* 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/. */
/**
* Manages a queue of asynchronous tasks, ensuring they are processed sequentially.
*/
export class AsyncQueue {
#processing;
#queue;
constructor() {
this.#queue = [];
this.#processing = false;
}
/**
* Dequeue a task.
*
* @returns {Promise}
* The wrapped task appearing as first item in the queue.
*/
#dequeue() {
return this.#queue.shift();
}
/**
* Dequeue and try to process all the queued tasks.
*
* @returns {Promise<undefined>}
* Promise that resolves when processing the queue is done.
*/
async #processQueue() {
// The queue is already processed or no tasks queued up.
if (this.#processing || this.#queue.length === 0) {
return;
}
this.#processing = true;
while (this.#queue.length) {
const wrappedTask = this.#dequeue();
await wrappedTask();
}
this.#processing = false;
}
/**
* Enqueue a task.
*
* @param {Function} task
* The task to queue.
*
* @returns {Promise<object>}
* Promise that resolves when the task is completed, with the resolved
* value being the result of the task.
*/
enqueue(task) {
const onTaskExecuted = new Promise((resolve, reject) => {
// Wrap the task in a function that will resolve or reject the Promise.
const wrappedTask = async () => {
try {
const result = await task();
resolve(result);
} catch (error) {
reject(error);
}
};
// Add the wrapped task to the queue
this.#queue.push(wrappedTask);
this.#processQueue();
});
return onTaskExecuted;
}
}

Просмотреть файл

@ -1,102 +0,0 @@
/* 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/. */
"use strict";
const { setTimeout } = ChromeUtils.importESModule(
"resource://gre/modules/Timer.sys.mjs"
);
const { AsyncQueue } = ChromeUtils.importESModule(
"chrome://remote/content/shared/AsyncQueue.sys.mjs"
);
function sleep(delay = 100) {
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
return new Promise(resolve => setTimeout(resolve, delay));
}
add_task(async function test_enqueueSyncTask() {
let value = "";
const queue = new AsyncQueue();
await Promise.all([
queue.enqueue(() => (value += "foo")),
queue.enqueue(() => (value += "bar")),
]);
equal(value, "foobar", "Tasks run in the correct order");
});
add_task(async function test_enqueueAsyncTask() {
let value = "";
const queue = new AsyncQueue();
await Promise.all([
queue.enqueue(async () => {
await sleep(100);
value += "foo";
}),
queue.enqueue(async () => {
await sleep(10);
value += "bar";
}),
]);
equal(value, "foobar", "Tasks run in the correct order");
});
add_task(async function test_enqueueAsyncTask() {
let value = "";
const queue = new AsyncQueue();
const promises = Promise.all([
queue.enqueue(async () => {
await sleep(100);
value += "foo";
}),
queue.enqueue(async () => {
await sleep(10);
value += "bar";
}),
]);
const promise = queue.enqueue(async () => (value += "42"));
await promise;
await promises;
equal(value, "foobar42", "Tasks run in the correct order");
});
add_task(async function test_returnValue() {
const queue = new AsyncQueue();
const results = await Promise.all([
queue.enqueue(() => "foo"),
queue.enqueue(() => 42),
]);
equal(results[0], "foo", "First task returned correct value");
equal(results[1], 42, "Second task returned correct value");
});
add_task(async function test_enqueueErroneousTasks() {
const queue = new AsyncQueue();
await Assert.rejects(
queue.enqueue(() => {
throw new Error("invalid");
}),
/Error: invalid/,
"Expected error was returned"
);
await Assert.rejects(
queue.enqueue(async () => {
throw new Error("invalid");
}),
/Error: invalid/,
"Expected error was returned"
);
});

Просмотреть файл

@ -3,8 +3,6 @@ head = "head.js"
["test_AppInfo.js"]
["test_AsyncQueue.js"]
["test_ChallengeHeaderParser.js"]
["test_DOM.js"]

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -84,7 +84,7 @@ event.synthesizeMouseAtPoint = function (left, top, opts, win) {
};
/**
* Synthesize a touch event at a point.
* 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.

Просмотреть файл

@ -35,15 +35,12 @@ add_task(function test_createInputState() {
}
});
add_task(async function test_defaultPointerParameters() {
add_task(function test_defaultPointerParameters() {
let state = new action.State();
const inputTickActions = [
{ type: "pointer", subtype: "pointerDown", button: 0 },
];
const chain = await action.Chain.fromJSON(
state,
chainForTick(inputTickActions)
);
const chain = action.Chain.fromJSON(state, chainForTick(inputTickActions));
const pointerAction = chain[0][0];
equal(
state.getInputSource(pointerAction.id).pointer.constructor.type,
@ -51,7 +48,7 @@ add_task(async function test_defaultPointerParameters() {
);
});
add_task(async function test_processPointerParameters() {
add_task(function test_processPointerParameters() {
for (let subtype of ["pointerDown", "pointerUp"]) {
for (let pointerType of [2, true, {}, []]) {
const inputTickActions = [
@ -63,7 +60,7 @@ add_task(async function test_processPointerParameters() {
},
];
let message = `Action sequence with parameters: {pointerType: ${pointerType} subtype: ${subtype}}`;
await checkFromJSONErrors(
checkFromJSONErrors(
inputTickActions,
/Expected "pointerType" to be a string/,
message
@ -80,7 +77,7 @@ add_task(async function test_processPointerParameters() {
},
];
let message = `Action sequence with parameters: {pointerType: ${pointerType} subtype: ${subtype}}`;
await checkFromJSONErrors(
checkFromJSONErrors(
inputTickActions,
/Expected "pointerType" to be one of/,
message
@ -98,10 +95,7 @@ add_task(async function test_processPointerParameters() {
button: 0,
},
];
const chain = await action.Chain.fromJSON(
state,
chainForTick(inputTickActions)
);
const chain = action.Chain.fromJSON(state, chainForTick(inputTickActions));
const pointerAction = chain[0][0];
equal(
state.getInputSource(pointerAction.id).pointer.constructor.type,
@ -110,12 +104,12 @@ add_task(async function test_processPointerParameters() {
}
});
add_task(async function test_processPointerDownAction() {
add_task(function test_processPointerDownAction() {
for (let button of [-1, "a"]) {
const inputTickActions = [
{ type: "pointer", subtype: "pointerDown", button },
];
await checkFromJSONErrors(
checkFromJSONErrors(
inputTickActions,
/Expected "button" to be a positive integer/,
`pointerDown with {button: ${button}}`
@ -125,21 +119,18 @@ add_task(async function test_processPointerDownAction() {
const inputTickActions = [
{ type: "pointer", subtype: "pointerDown", button: 5 },
];
const chain = await action.Chain.fromJSON(
state,
chainForTick(inputTickActions)
);
const chain = action.Chain.fromJSON(state, chainForTick(inputTickActions));
equal(chain[0][0].button, 5);
});
add_task(async function test_validateActionDurationAndCoordinates() {
add_task(function test_validateActionDurationAndCoordinates() {
for (let [type, subtype] of [
["none", "pause"],
["pointer", "pointerMove"],
]) {
for (let duration of [-1, "a"]) {
const inputTickActions = [{ type, subtype, duration }];
await checkFromJSONErrors(
checkFromJSONErrors(
inputTickActions,
/Expected "duration" to be a positive integer/,
`{subtype} with {duration: ${duration}}`
@ -153,7 +144,7 @@ add_task(async function test_validateActionDurationAndCoordinates() {
duration: 5000,
};
actionItem[name] = "a";
await checkFromJSONErrors(
checkFromJSONErrors(
[actionItem],
/Expected ".*" to be an integer/,
`${name}: "a", subtype: pointerMove`
@ -161,73 +152,54 @@ add_task(async function test_validateActionDurationAndCoordinates() {
}
});
add_task(async function test_processPointerMoveActionOriginStringValidation() {
for (let origin of ["", "viewports", "pointers"]) {
add_task(function test_processPointerMoveActionOriginValidation() {
for (let origin of [-1, { a: "blah" }, []]) {
const inputTickActions = [
{
type: "pointer",
x: 0,
y: 0,
duration: 5000,
subtype: "pointerMove",
origin,
},
{ type: "pointer", duration: 5000, subtype: "pointerMove", origin },
];
await checkFromJSONErrors(
checkFromJSONErrors(
inputTickActions,
/Expected "origin" to be undefined, "viewport", "pointer", or an element/,
`actionItem.origin: ${origin}`,
{ isElementOrigin: () => false }
`actionItem.origin: (${getTypeString(origin)})`
);
}
});
add_task(async function test_processPointerMoveActionOriginElementValidation() {
const element = { foo: "bar" };
add_task(function test_processPointerMoveActionOriginStringValidation() {
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 = [
{
type: "pointer",
x: 0,
y: 0,
duration: 5000,
subtype: "pointerMove",
origin: element,
origin: domEl,
x: 0,
y: 0,
},
];
// 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 });
const chain = action.Chain.fromJSON(state, chainForTick(inputTickActions));
deepEqual(chain[0][0].origin.element, domEl);
});
add_task(async function test_processPointerMoveActionDefaultOrigin() {
add_task(function test_processPointerMoveActionDefaultOrigin() {
let state = new action.State();
const inputTickActions = [
{ type: "pointer", x: 0, y: 0, duration: 5000, subtype: "pointerMove" },
{ type: "pointer", duration: 5000, subtype: "pointerMove", x: 0, y: 0 },
];
const chain = await action.Chain.fromJSON(
state,
chainForTick(inputTickActions),
{}
);
const chain = action.Chain.fromJSON(state, chainForTick(inputTickActions));
// 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), {
x: 0,
@ -235,7 +207,7 @@ add_task(async function test_processPointerMoveActionDefaultOrigin() {
});
});
add_task(async function test_processPointerMoveAction() {
add_task(function test_processPointerMoveAction() {
let state = new action.State();
const actionItems = [
{
@ -265,16 +237,7 @@ add_task(async function test_processPointerMoveAction() {
type: "pointer",
actions: actionItems,
};
let actionsOptions = {
isElementOrigin: elem => elem == domEl,
getElementOrigin: elem => elem,
};
let chain = await action.Chain.fromJSON(
state,
[actionSequence],
actionsOptions
);
let chain = action.Chain.fromJSON(state, [actionSequence]);
equal(chain.length, actionItems.length);
for (let i = 0; i < actionItems.length; i++) {
let actual = chain[i][0];
@ -295,7 +258,7 @@ add_task(async function test_processPointerMoveAction() {
}
});
add_task(async function test_computePointerDestinationViewport() {
add_task(function test_computePointerDestinationViewport() {
const state = new action.State();
const inputTickActions = [
{
@ -306,17 +269,13 @@ add_task(async function test_computePointerDestinationViewport() {
origin: "viewport",
},
];
const chain = await action.Chain.fromJSON(
state,
chainForTick(inputTickActions),
{}
);
const chain = action.Chain.fromJSON(state, chainForTick(inputTickActions));
const actionItem = chain[0][0];
const inputSource = state.getInputSource(actionItem.id);
// these values should not affect the outcome
inputSource.x = "99";
inputSource.y = "10";
const target = await actionItem.origin.getTargetCoordinates(
const target = actionItem.origin.getTargetCoordinates(
inputSource,
[actionItem.x, actionItem.y],
null
@ -325,7 +284,7 @@ add_task(async function test_computePointerDestinationViewport() {
equal(actionItem.y, target[1]);
});
add_task(async function test_computePointerDestinationPointer() {
add_task(function test_computePointerDestinationPointer() {
const state = new action.State();
const inputTickActions = [
{
@ -336,16 +295,12 @@ add_task(async function test_computePointerDestinationPointer() {
origin: "pointer",
},
];
const chain = await action.Chain.fromJSON(
state,
chainForTick(inputTickActions),
{}
);
const chain = action.Chain.fromJSON(state, chainForTick(inputTickActions));
const actionItem = chain[0][0];
const inputSource = state.getInputSource(actionItem.id);
inputSource.x = 10;
inputSource.y = 99;
const target = await actionItem.origin.getTargetCoordinates(
const target = actionItem.origin.getTargetCoordinates(
inputSource,
[actionItem.x, actionItem.y],
null
@ -354,7 +309,7 @@ add_task(async function test_computePointerDestinationPointer() {
equal(actionItem.y + inputSource.y, target[1]);
});
add_task(async function test_processPointerAction() {
add_task(function test_processPointerAction() {
for (let pointerType of ["mouse", "touch"]) {
const actionItems = [
{
@ -381,7 +336,7 @@ add_task(async function test_processPointerAction() {
actions: actionItems,
};
const state = new action.State();
const chain = await action.Chain.fromJSON(state, [actionSequence], {});
const chain = action.Chain.fromJSON(state, [actionSequence]);
equal(chain.length, actionItems.length);
for (let i = 0; i < actionItems.length; i++) {
const actual = chain[i][0];
@ -404,7 +359,7 @@ add_task(async function test_processPointerAction() {
}
});
add_task(async function test_processPauseAction() {
add_task(function test_processPauseAction() {
for (let type of ["none", "key", "pointer"]) {
const state = new action.State();
const actionSequence = {
@ -412,8 +367,7 @@ add_task(async function test_processPauseAction() {
id: "some_id",
actions: [{ type: "pause", duration: 5000 }],
};
const actions = await action.Chain.fromJSON(state, [actionSequence], {});
const actionItem = actions[0][0];
const actionItem = action.Chain.fromJSON(state, [actionSequence])[0][0];
equal(actionItem.type, "none");
equal(actionItem.subtype, "pause");
equal(actionItem.id, "some_id");
@ -425,16 +379,15 @@ add_task(async function test_processPauseAction() {
id: "some_id",
actions: [{ type: "pause" }],
};
const actions = await action.Chain.fromJSON(state, [actionSequence], {});
const actionItem = actions[0][0];
const actionItem = action.Chain.fromJSON(state, [actionSequence])[0][0];
equal(actionItem.duration, undefined);
});
add_task(async function test_processActionSubtypeValidation() {
add_task(function test_processActionSubtypeValidation() {
for (let type of ["none", "key", "pointer"]) {
const message = `type: ${type}, subtype: dancing`;
const inputTickActions = [{ type, subtype: "dancing" }];
await checkFromJSONErrors(
checkFromJSONErrors(
inputTickActions,
new RegExp(`Expected known subtype for type`),
message
@ -442,11 +395,11 @@ add_task(async function test_processActionSubtypeValidation() {
}
});
add_task(async function test_processKeyActionDown() {
add_task(function test_processKeyActionDown() {
for (let value of [-1, undefined, [], ["a"], { length: 1 }, null]) {
const inputTickActions = [{ type: "key", subtype: "keyDown", value }];
const message = `actionItem.value: (${getTypeString(value)})`;
await checkFromJSONErrors(
checkFromJSONErrors(
inputTickActions,
/Expected "value" to be a string that represents single code point/,
message
@ -459,8 +412,7 @@ add_task(async function test_processKeyActionDown() {
id: "keyboard",
actions: [{ type: "keyDown", value: "\uE004" }],
};
const actions = await action.Chain.fromJSON(state, [actionSequence], {});
const actionItem = actions[0][0];
const actionItem = action.Chain.fromJSON(state, [actionSequence])[0][0];
equal(actionItem.type, "key");
equal(actionItem.id, "keyboard");
@ -468,20 +420,20 @@ add_task(async function test_processKeyActionDown() {
equal(actionItem.value, "\ue004");
});
add_task(async function test_processInputSourceActionSequenceValidation() {
await checkFromJSONErrors(
add_task(function test_processInputSourceActionSequenceValidation() {
checkFromJSONErrors(
[{ type: "swim", subtype: "pause", id: "some id" }],
/Expected known action type/,
"actionSequence type: swim"
);
await checkFromJSONErrors(
checkFromJSONErrors(
[{ type: "none", subtype: "pause", id: -1 }],
/Expected "id" to be a string/,
"actionSequence id: -1"
);
await checkFromJSONErrors(
checkFromJSONErrors(
[{ type: "none", subtype: "pause", id: undefined }],
/Expected "id" to be a string/,
"actionSequence id: undefined"
@ -494,19 +446,19 @@ add_task(async function test_processInputSourceActionSequenceValidation() {
const errorRegex = /Expected "actionSequence.actions" to be an array/;
const message = "actionSequence actions: -1";
await Assert.rejects(
action.Chain.fromJSON(state, actionSequence, {}),
Assert.throws(
() => action.Chain.fromJSON(state, actionSequence),
/InvalidArgumentError/,
message
);
await Assert.rejects(
action.Chain.fromJSON(state, actionSequence, {}),
Assert.throws(
() => action.Chain.fromJSON(state, actionSequence),
errorRegex,
message
);
});
add_task(async function test_processInputSourceActionSequence() {
add_task(function test_processInputSourceActionSequence() {
const state = new action.State();
const actionItem = { type: "pause", duration: 5 };
const actionSequence = {
@ -514,7 +466,7 @@ add_task(async function test_processInputSourceActionSequence() {
id: "some id",
actions: [actionItem],
};
const chain = await action.Chain.fromJSON(state, [actionSequence], {});
const chain = action.Chain.fromJSON(state, [actionSequence]);
equal(chain.length, 1);
const tickActions = chain[0];
equal(tickActions.length, 1);
@ -524,7 +476,7 @@ add_task(async function test_processInputSourceActionSequence() {
equal(tickActions[0].id, "some id");
});
add_task(async function test_processInputSourceActionSequencePointer() {
add_task(function test_processInputSourceActionSequencePointer() {
const state = new action.State();
const actionItem = { type: "pointerDown", button: 1 };
const actionSequence = {
@ -535,7 +487,7 @@ add_task(async function test_processInputSourceActionSequencePointer() {
pointerType: "mouse", // TODO "pen"
},
};
const chain = await action.Chain.fromJSON(state, [actionSequence], {});
const chain = action.Chain.fromJSON(state, [actionSequence]);
equal(chain.length, 1);
const tickActions = chain[0];
equal(tickActions.length, 1);
@ -548,7 +500,7 @@ add_task(async function test_processInputSourceActionSequencePointer() {
equal(inputSource.pointer.constructor.type, "mouse");
});
add_task(async function test_processInputSourceActionSequenceKey() {
add_task(function test_processInputSourceActionSequenceKey() {
const state = new action.State();
const actionItem = { type: "keyUp", value: "a" };
const actionSequence = {
@ -556,7 +508,7 @@ add_task(async function test_processInputSourceActionSequenceKey() {
id: "9",
actions: [actionItem],
};
const chain = await action.Chain.fromJSON(state, [actionSequence], {});
const chain = action.Chain.fromJSON(state, [actionSequence]);
equal(chain.length, 1);
const tickActions = chain[0];
equal(tickActions.length, 1);
@ -566,7 +518,7 @@ add_task(async function test_processInputSourceActionSequenceKey() {
equal(tickActions[0].id, "9");
});
add_task(async function test_processInputSourceActionSequenceInputStateMap() {
add_task(function test_processInputSourceActionSequenceInputStateMap() {
const state = new action.State();
const id = "1";
const actionItem = { type: "pause", duration: 5000 };
@ -575,7 +527,7 @@ add_task(async function test_processInputSourceActionSequenceInputStateMap() {
id,
actions: [actionItem],
};
await action.Chain.fromJSON(state, [actionSequence], {});
action.Chain.fromJSON(state, [actionSequence]);
equal(state.inputStateMap.size, 1);
equal(state.inputStateMap.get(id).constructor.type, "key");
@ -587,7 +539,7 @@ add_task(async function test_processInputSourceActionSequenceInputStateMap() {
id,
actions: [actionItem1],
};
await action.Chain.fromJSON(state1, [actionSequence1], {});
action.Chain.fromJSON(state1, [actionSequence1]);
equal(state1.inputStateMap.size, 1);
// Overwrite the state in the initial map with one of a different type
@ -595,41 +547,41 @@ add_task(async function test_processInputSourceActionSequenceInputStateMap() {
equal(state.inputStateMap.get(id).constructor.type, "pointer");
const message = "Wrong state for input id type";
await Assert.rejects(
action.Chain.fromJSON(state, [actionSequence]),
Assert.throws(
() => action.Chain.fromJSON(state, [actionSequence]),
/InvalidArgumentError/,
message
);
await Assert.rejects(
action.Chain.fromJSON(state, [actionSequence]),
Assert.throws(
() => action.Chain.fromJSON(state, [actionSequence]),
/Expected input source \[object String\] "1" to be type pointer/,
message
);
});
add_task(async function test_extractActionChainValidation() {
add_task(function test_extractActionChainValidation() {
for (let actions of [-1, "a", undefined, null]) {
const state = new action.State();
let message = `actions: ${getTypeString(actions)}`;
await Assert.rejects(
action.Chain.fromJSON(state, actions),
Assert.throws(
() => action.Chain.fromJSON(state, actions),
/InvalidArgumentError/,
message
);
await Assert.rejects(
action.Chain.fromJSON(state, actions),
Assert.throws(
() => action.Chain.fromJSON(state, actions),
/Expected "actions" to be an array/,
message
);
}
});
add_task(async function test_extractActionChainEmpty() {
add_task(function test_extractActionChainEmpty() {
const state = new action.State();
deepEqual(await action.Chain.fromJSON(state, [], {}), []);
deepEqual(action.Chain.fromJSON(state, []), []);
});
add_task(async function test_extractActionChain_oneTickOneInput() {
add_task(function test_extractActionChain_oneTickOneInput() {
const state = new action.State();
const actionItem = { type: "pause", duration: 5000 };
const actionSequence = {
@ -637,11 +589,7 @@ add_task(async function test_extractActionChain_oneTickOneInput() {
id: "some id",
actions: [actionItem],
};
const actionsByTick = await action.Chain.fromJSON(
state,
[actionSequence],
{}
);
const actionsByTick = action.Chain.fromJSON(state, [actionSequence]);
equal(1, actionsByTick.length);
equal(1, actionsByTick[0].length);
equal(actionsByTick[0][0].id, actionSequence.id);
@ -650,7 +598,7 @@ add_task(async function test_extractActionChain_oneTickOneInput() {
equal(actionsByTick[0][0].duration, actionItem.duration);
});
add_task(async function test_extractActionChain_twoAndThreeTicks() {
add_task(function test_extractActionChain_twoAndThreeTicks() {
const state = new action.State();
const mouseActionItems = [
{
@ -689,11 +637,10 @@ add_task(async function test_extractActionChain_twoAndThreeTicks() {
id: "1",
actions: keyActionItems,
};
let actionsByTick = await action.Chain.fromJSON(
state,
[keyActionSequence, mouseActionSequence],
{}
);
let actionsByTick = action.Chain.fromJSON(state, [
keyActionSequence,
mouseActionSequence,
]);
// number of ticks is same as longest action sequence
equal(keyActionItems.length, actionsByTick.length);
equal(2, actionsByTick[0].length);
@ -705,7 +652,7 @@ add_task(async function test_extractActionChain_twoAndThreeTicks() {
equal(actionsByTick[2][0].subtype, "keyUp");
});
add_task(async function test_computeTickDuration() {
add_task(function test_computeTickDuration() {
const state = new action.State();
const expected = 8000;
const inputTickActions = [
@ -717,17 +664,13 @@ add_task(async function test_computeTickDuration() {
{ type: "pointer", subtype: "pause", duration: expected },
{ type: "pointer", subtype: "pointerUp", button: 0 },
];
const chain = await action.Chain.fromJSON(
state,
chainForTick(inputTickActions),
{}
);
const chain = action.Chain.fromJSON(state, chainForTick(inputTickActions));
equal(1, chain.length);
const tickActions = chain[0];
equal(expected, tickActions.getDuration());
});
add_task(async function test_computeTickDuration_noDurations() {
add_task(function test_computeTickDuration_noDurations() {
const state = new action.State();
const inputTickActions = [
// invalid because keyDown should not have duration, so duration should be ignored.
@ -738,11 +681,7 @@ add_task(async function test_computeTickDuration_noDurations() {
{ type: "pointer", subtype: "pointerDown", button: 0 },
{ type: "key", subtype: "keyUp", value: "a" },
];
const chain = await action.Chain.fromJSON(
state,
chainForTick(inputTickActions),
{}
);
const chain = action.Chain.fromJSON(state, chainForTick(inputTickActions));
equal(0, chain[0].getDuration());
});
@ -780,37 +719,19 @@ function getTypeString(obj) {
return Object.prototype.toString.call(obj);
}
async function checkFromJSONErrors(
inputTickActions,
regex,
message,
options = {}
) {
const { isElementOrigin = () => true, getElementOrigin = elem => elem } =
options;
function checkFromJSONErrors(inputTickActions, regex, message) {
const state = new action.State();
const actionsOptions = { isElementOrigin, getElementOrigin };
if (typeof message == "undefined") {
message = `fromJSON`;
}
await Assert.rejects(
action.Chain.fromJSON(
state,
chainForTick(inputTickActions),
actionsOptions
),
Assert.throws(
() => action.Chain.fromJSON(state, chainForTick(inputTickActions)),
/InvalidArgumentError/,
message
);
await Assert.rejects(
action.Chain.fromJSON(
state,
chainForTick(inputTickActions),
actionsOptions
),
Assert.throws(
() => action.Chain.fromJSON(state, chainForTick(inputTickActions)),
regex,
message
);

Просмотреть файл

@ -7,7 +7,6 @@ import { RootBiDiModule } from "chrome://remote/content/webdriver-bidi/modules/R
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
action: "chrome://remote/content/shared/webdriver/Actions.sys.mjs",
assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
pprint: "chrome://remote/content/shared/Format.sys.mjs",
@ -17,223 +16,8 @@ ChromeUtils.defineESModuleGetters(lazy, {
});
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() {}
/**
* 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 },
});
}
/**
* Retrieves the action's input state.
*
* @param {BrowsingContext} context
* The Browsing Context to retrieve the input state for.
*
* @returns {Actions.InputState}
* The action's input state.
*/
#getInputState(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);
}
return inputState;
}
/**
* 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"
);
}
/**
* Resets the action's input state.
*
* @param {BrowsingContext} context
* The Browsing Context to reset the input state for.
*/
#resetInputState(context) {
// Bug 1821460: Fetch top-level browsing context.
if (this.#inputStates.has(context)) {
this.#inputStates.delete(context);
}
}
async performActions(options = {}) {
const { actions, context: contextId } = options;
@ -249,22 +33,21 @@ class InputModule extends RootBiDiModule {
);
}
const inputState = this.#getInputState(context);
const actionsOptions = { ...this.#actionsOptions, context };
// Bug 1821460: Fetch top-level browsing context.
const actionChain = await lazy.action.Chain.fromJSON(
inputState,
actions,
actionsOptions
);
await this.messageHandler.forwardCommand({
moduleName: "input",
commandName: "performActions",
destination: {
type: lazy.WindowGlobalMessageHandler.type,
id: context.id,
},
params: {
actions,
},
});
// Enqueue to serialize access to input state.
await inputState.enqueueAction(() =>
actionChain.dispatch(inputState, actionsOptions)
);
// Process async follow-up tasks in content before the reply is sent.
await this.#finalizeAction(context);
return {};
}
/**
@ -294,19 +77,19 @@ class InputModule extends RootBiDiModule {
);
}
const inputState = this.#getInputState(context);
const actionsOptions = { ...this.#actionsOptions, context };
// Bug 1821460: Fetch top-level browsing context.
// Enqueue to serialize access to input state.
await inputState.enqueueAction(() => {
const undoActions = inputState.inputCancelList.reverse();
return undoActions.dispatch(inputState, actionsOptions);
await this.messageHandler.forwardCommand({
moduleName: "input",
commandName: "releaseActions",
destination: {
type: lazy.WindowGlobalMessageHandler.type,
id: context.id,
},
params: {},
});
this.#resetInputState(context);
// Process async follow-up tasks in content before the reply is sent.
this.#finalizeAction(context);
return {};
}
/**

Просмотреть файл

@ -7,96 +7,45 @@ import { WindowGlobalBiDiModule } from "chrome://remote/content/webdriver-bidi/m
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
AnimationFramePromise: "chrome://remote/content/shared/Sync.sys.mjs",
assertInViewPort: "chrome://remote/content/shared/webdriver/Actions.sys.mjs",
action: "chrome://remote/content/shared/webdriver/Actions.sys.mjs",
dom: "chrome://remote/content/shared/DOM.sys.mjs",
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
event: "chrome://remote/content/shared/webdriver/Event.sys.mjs",
});
class InputModule extends WindowGlobalBiDiModule {
#actionState;
constructor(messageHandler) {
super(messageHandler);
this.#actionState = null;
}
destroy() {}
_assertInViewPort(options = {}) {
const { target } = options;
return lazy.assertInViewPort(target, this.messageHandler.window);
}
async _dispatchEvent(options = {}) {
const { eventName, details } = options;
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`);
async performActions(options) {
const { actions } = options;
if (this.#actionState === null) {
this.#actionState = new lazy.action.State();
}
}
_finalizeAction() {
await this.#deserializeActionOrigins(actions);
const actionChain = lazy.action.Chain.fromJSON(this.#actionState, actions);
await actionChain.dispatch(this.#actionState, this.messageHandler.window);
// 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.
return lazy.AnimationFramePromise(this.messageHandler.window);
}
async _getClientRects(options = {}) {
const { element: reference } = options;
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 };
});
}
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 releaseActions() {
if (this.#actionState === null) {
return;
}
await this.#actionState.release(this.messageHandler.window);
this.#actionState = null;
}
async setFiles(options) {
@ -160,6 +109,50 @@ 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) {
if (typeof sharedReference?.sharedId !== "string") {
throw new lazy.error.InvalidArgumentError(

Просмотреть файл

@ -0,0 +1,2 @@
[input-events-arrow-key-on-number-input-delete-document.html]
expected: ERROR

Просмотреть файл

@ -1,4 +1,3 @@
[input-events-arrow-key-on-number-input-prevent-default.html]
[Number input should not fire input and change event if the beforeinput event is default prevented]
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1918254
expected: FAIL

Просмотреть файл

@ -1,5 +1,2 @@
[input-events-spin-button-click-on-number-input-delete-document.html]
[Number input should not crash and not fire subsequent events when event handler removes document]
expected:
if (os == "android"): FAIL
expected: ERROR

Просмотреть файл

@ -1,4 +1,3 @@
[input-events-spin-button-click-on-number-input-prevent-default.html]
[Number input should not fire input and change event if the beforeinput event is default prevented]
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1918254
expected: FAIL

Просмотреть файл

@ -1,5 +1,3 @@
[input-events-spin-button-click-on-number-input.html]
[Number input should fire beforeinput event before the input and change event]
expected:
if (os == "android"): FAIL
expected: FAIL

Просмотреть файл

@ -1,6 +1,7 @@
[pointer_pen.py]
[test_null_response_value]
expected: FAIL
expected:
ERROR
[test_pen_pointer_in_shadow_tree[outer-open\]]
expected: FAIL

Просмотреть файл

@ -9,39 +9,29 @@
<body>
<iframe id="iframe"></iframe>
<script>
const frame = document.getElementById("iframe");
promise_test(async function() {
const child = document.getElementById("iframe");
const childDocument = child.contentDocument;
const inputElement = childDocument.createElement('input');
inputElement.type = "number";
childDocument.body.appendChild(inputElement);
let events = [];
inputElement.addEventListener("beforeinput", () => {
child.remove();
events.push("beforeinput");
});
inputElement.addEventListener("input", () => {
evenst.push("input");
});
inputElement.addEventListener("change", () => {
events.push("change");
});
function loadIframe(doc) {
return new Promise((resolve) => {
frame.addEventListener("load", resolve);
frame.srcdoc = doc;
});
}
inputElement.focus();
promise_test(async function() {
await loadIframe("<input type='number'>");
const inputElement = frame.contentDocument.querySelector("input");
let events = [];
inputElement.addEventListener("beforeinput", () => {
events.push("beforeinput");
frame.remove();
});
inputElement.addEventListener("input", () => {
events.push("input");
});
inputElement.addEventListener("change", () => {
events.push("change");
});
inputElement.focus();
await test_driver.send_keys(inputElement, "\uE013");
assert_array_equals(events, ['beforeinput']);
assert_false(document.body.contains(frame));
}, "Number input should not crash and not fire subsequent events when event handler removes document");
await new test_driver.send_keys(inputElement, "\uE013");
assert_array_equals(events, ['beforeinput']);
}, "Number input should not crash and not fire subsequent events when event handler removes document");
</script>
</body>
</html>

Просмотреть файл

@ -4,57 +4,37 @@
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-actions.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
</head>
<body>
<iframe id="iframe"></iframe>
<script>
const frame = document.getElementById("iframe");
promise_test(async function() {
const child = document.getElementById("iframe");
const childDocument = child.contentDocument;
const inputElement = childDocument.createElement('input');
inputElement.type = "number";
childDocument.body.appendChild(inputElement);
let events = [];
inputElement.addEventListener("beforeinput", () => {
child.remove();
events.push("beforeinput");
});
inputElement.addEventListener("input", () => {
events.push("input");
});
inputElement.addEventListener("change", () => {
events.push("change");
});
function loadIframe(doc) {
return new Promise((resolve) => {
frame.addEventListener("load", resolve);
frame.srcdoc = doc;
});
}
promise_test(async function () {
await loadIframe(
"<input type='number' style='width: 100px; height: 20px'>"
);
const inputElement = frame.contentDocument.querySelector("input");
let events = [];
inputElement.addEventListener("beforeinput", () => {
frame.remove();
events.push("beforeinput");
});
inputElement.addEventListener("input", () => {
events.push("input");
});
inputElement.addEventListener("change", () => {
events.push("change");
});
// Roughly get the offset to the spin up arrow button's center point within
// the iframe's viewport. Note that this is fragile and might need specific
// coordinates for each browser and maybe platform.
const rect = inputElement.getBoundingClientRect();
const x = rect.x + rect.width - 10;
const y = rect.y + Math.round(rect.height / 4);
const actions = new test_driver.Actions()
.setContext(frame.contentWindow)
.pointerMove(x, y, { origin: "viewport" })
.pointerDown()
.pointerUp();
await actions.send();
assert_array_equals(events, ['beforeinput']);
assert_false(document.body.contains(frame));
}, "Number input should not crash and not fire subsequent events when event handler removes document");
inputElement.focus();
// Get the spin button up arrow key location and adjust with iframe offset to get absolute position
const x1 = (inputElement.offsetLeft + inputElement.offsetWidth - 10) + child.offsetLeft;
const y1 = (inputElement.offsetTop + inputElement.offsetHeight / 4) + child.offsetTop;
await test_driver_internal.click(inputElement,{x: x1, y: y1});
assert_array_equals(events, ['beforeinput']);
assert_false(document.body.contains(child));
}, "Number input should not crash and not fire subsequent events when event handler removes document");
</script>
</body>
</html>

Просмотреть файл

@ -4,43 +4,32 @@
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-actions.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
</head>
<body>
<input type="number" id="number_input">
<script>
promise_test(async function() {
const inputElement = document.getElementById("number_input");
promise_test(async function() {
const inputElement = document.getElementById("number_input");
let events = [];
inputElement.addEventListener("beforeinput", (e) => {
e.preventDefault();
events.push("beforeinput");
});
inputElement.addEventListener("input", () => {
events.push("input");
});
inputElement.addEventListener("change", () => {
events.push("change");
});
let events = [];
inputElement.addEventListener("beforeinput", (e) => {
e.preventDefault();
events.push("beforeinput");
});
inputElement.addEventListener("input", () => {
events.push("input");
});
inputElement.addEventListener("change", () => {
events.push("change");
});
// Roughly get the offset to the spin up arrow button's center point within
// the iframe's viewport. Note that this is fragile and might need specific
// coordinates for each browser and maybe platform.
const rect = inputElement.getBoundingClientRect();
const x = rect.x + rect.width - 10;
const y = rect.y + Math.round(rect.height / 4);
const actions = new test_driver.Actions()
.pointerMove(x, y, { origin: "viewport" })
.pointerDown()
.pointerUp();
await actions.send();
assert_array_equals(events, ['beforeinput']);
}, "Number input should not fire input and change event if the beforeinput event is default prevented");
inputElement.focus();
// Get the spin button up arrow key location
const x1 = (inputElement.offsetLeft + inputElement.offsetWidth - 10);
const y1 = (inputElement.offsetTop + inputElement.offsetHeight / 4);
await test_driver_internal.click(inputElement,{x: x1, y: y1});
assert_array_equals(events, ['beforeinput']);
}, "Number input should not fire input and change event if the beforeinput event is default prevented");
</script>
</body>
</html>

Просмотреть файл

@ -4,7 +4,6 @@
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-actions.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
</head>
<body>
@ -12,9 +11,7 @@
<script>
promise_test(async function() {
const inputElement = document.getElementById("number_input");
let events = [];
inputElement.addEventListener("beforeinput", () => {
events.push("beforeinput");
});
@ -25,19 +22,11 @@ promise_test(async function() {
events.push("change");
});
// Roughly get the offset to the spin up arrow button's center point within
// the iframe's viewport. Note that this is fragile and might need specific
// coordinates for each browser and maybe platform.
const rect = inputElement.getBoundingClientRect();
const x = rect.x + rect.width - 10;
const y = rect.y + Math.round(rect.height / 4);
const actions = new test_driver.Actions()
.pointerMove(x, y, { origin: "viewport" })
.pointerDown()
.pointerUp();
await actions.send();
inputElement.focus();
// Get the spin button up arrow key location
const x1 = (inputElement.offsetLeft + inputElement.offsetWidth - 10);
const y1 = (inputElement.offsetTop + inputElement.offsetHeight / 4);
await test_driver_internal.click(inputElement,{x: x1, y: y1});
assert_array_equals(events, ['beforeinput','input','change']);
}, "Number input should fire beforeinput event before the input and change event");
</script>

Просмотреть файл

@ -2,39 +2,6 @@ import json
from webdriver.bidi.modules.script import ContextTarget
async def add_mouse_listeners(bidi_session, context, include_mousemove=True):
result = await bidi_session.script.call_function(
function_declaration="""(include_mousemove) => {
window.allEvents = { events: []};
const events = ["auxclick", "click", "mousedown", "mouseup"];
if (include_mousemove) {
events.push("mousemove");
}
function handleEvent(event) {
window.allEvents.events.push({
type: event.type,
detail: event.detail,
clientX: event.clientX,
clientY: event.clientY,
isTrusted: event.isTrusted,
button: event.button,
buttons: event.buttons,
});
};
for (const event of events) {
document.addEventListener(event, handleEvent);
}
}""",
arguments=[{"type": "boolean", "value": include_mousemove}],
await_promise=False,
target=ContextTarget(context["context"]),
)
async def get_object_from_context(bidi_session, context, object_path):
"""Return a plain JS object from a given context, accessible at the given object_path"""
events_str = await bidi_session.script.evaluate(
@ -62,7 +29,6 @@ async def get_events(bidi_session, context):
# tests expect ''.
if "code" in e and e["code"] == "Unidentified":
e["code"] = ""
return events

Просмотреть файл

@ -1,4 +1,3 @@
# META: timeout=long
import pytest
import pytest_asyncio

Просмотреть файл

@ -54,26 +54,34 @@ async def test_click_at_coordinates(bidi_session, top_context, load_static_test_
assert expected == filtered_events[1:]
@pytest.mark.parametrize("origin", ["element", "pointer", "viewport"])
async def test_params_actions_origin_outside_viewport(
bidi_session, top_context, get_actions_origin_page, get_element, origin
):
if origin == "element":
url = get_actions_origin_page(
"""width: 100px; height: 50px; background: green;
position: relative; left: -200px; top: -100px;"""
)
await bidi_session.browsing_context.navigate(
context=top_context["context"],
url=url,
wait="complete",
@pytest.mark.parametrize("origin", ["pointer", "viewport"])
async def test_params_actions_origin_outside_viewport(bidi_session, top_context, origin):
actions = Actions()
actions.add_pointer().pointer_move(x=-50, y=-50, origin=origin)
with pytest.raises(MoveTargetOutOfBoundsException):
await bidi_session.input.perform_actions(
actions=actions, context=top_context["context"]
)
element = await get_element("#inner")
origin = get_element_origin(element)
async def test_params_actions_origin_element_outside_viewport(
bidi_session, top_context, get_actions_origin_page, get_element
):
url = get_actions_origin_page(
"""width: 100px; height: 50px; background: green;
position: relative; left: -200px; top: -100px;"""
)
await bidi_session.browsing_context.navigate(
context=top_context["context"],
url=url,
wait="complete",
)
elem = await get_element("#inner")
actions = Actions()
actions.add_pointer().pointer_move(x=-100, y=-100, origin=origin)
actions.add_pointer().pointer_move(x=0, y=0, origin=get_element_origin(elem))
with pytest.raises(MoveTargetOutOfBoundsException):
await bidi_session.input.perform_actions(

Просмотреть файл

@ -1,6 +1,5 @@
import pytest
from webdriver.bidi.error import MoveTargetOutOfBoundsException
from webdriver.bidi.modules.input import Actions, get_element_origin
from .. import get_events
@ -14,36 +13,6 @@ from . import (
pytestmark = pytest.mark.asyncio
@pytest.mark.parametrize("origin", ["element", "pointer", "viewport"])
async def test_params_actions_origin_outside_viewport(
bidi_session, get_actions_origin_page, top_context, get_element, origin
):
if origin == "element":
url = get_actions_origin_page(
"""width: 100px; height: 50px; background: green;
position: relative; left: -200px; top: -100px;"""
)
await bidi_session.browsing_context.navigate(
context=top_context["context"],
url=url,
wait="complete",
)
element = await get_element("#inner")
origin = get_element_origin(element)
actions = Actions()
(
actions.add_pointer(pointer_type="pen")
.pointer_move(x=-100, y=-100, origin=origin)
)
with pytest.raises(MoveTargetOutOfBoundsException):
await bidi_session.input.perform_actions(
actions=actions, context=top_context["context"]
)
@pytest.mark.parametrize("mode", ["open", "closed"])
@pytest.mark.parametrize("nested", [False, True], ids=["outer", "inner"])
async def test_pen_pointer_in_shadow_tree(

Просмотреть файл

@ -1,6 +1,5 @@
import pytest
from webdriver.bidi.error import MoveTargetOutOfBoundsException
from webdriver.bidi.modules.input import Actions, get_element_origin
from .. import get_events
@ -14,36 +13,6 @@ from . import (
pytestmark = pytest.mark.asyncio
@pytest.mark.parametrize("origin", ["element", "pointer", "viewport"])
async def test_params_actions_origin_outside_viewport(
bidi_session, get_actions_origin_page, top_context, get_element, origin
):
if origin == "element":
url = get_actions_origin_page(
"""width: 100px; height: 50px; background: green;
position: relative; left: -200px; top: -100px;"""
)
await bidi_session.browsing_context.navigate(
context=top_context["context"],
url=url,
wait="complete",
)
element = await get_element("#inner")
origin = get_element_origin(element)
actions = Actions()
(
actions.add_pointer(pointer_type="touch")
.pointer_move(x=-100, y=-100, origin=origin)
)
with pytest.raises(MoveTargetOutOfBoundsException):
await bidi_session.input.perform_actions(
actions=actions, context=top_context["context"]
)
@pytest.mark.parametrize("mode", ["open", "closed"])
@pytest.mark.parametrize("nested", [False, True], ids=["outer", "inner"])
async def test_touch_pointer_in_shadow_tree(

Просмотреть файл

@ -1,123 +0,0 @@
import asyncio
import pytest
from webdriver.bidi.modules.input import Actions
from tests.support.helpers import filter_supported_key_events
from tests.support.keys import Keys
from .. import add_mouse_listeners, get_events, get_keys_value
from ... import recursive_compare
pytestmark = pytest.mark.asyncio
async def test_parallel_key(bidi_session, top_context, setup_key_test):
actions_1 = Actions()
actions_1.add_key().send_keys("a").key_down(Keys.SHIFT)
actions_2 = Actions()
actions_2.add_key().send_keys("B").key_up(Keys.SHIFT)
# Run both actions in parallel to check that they are queued for
# sequential execution.
actions_performed = [
bidi_session.input.perform_actions(
actions=actions_1, context=top_context["context"]
),
bidi_session.input.perform_actions(
actions=actions_2, context=top_context["context"]
),
]
await asyncio.gather(*actions_performed)
expected = [
{"code": "KeyA", "key": "a", "type": "keydown"},
{"code": "KeyA", "key": "a", "type": "keypress"},
{"code": "KeyA", "key": "a", "type": "keyup"},
{"code": "ShiftLeft", "key": "Shift", "type": "keydown"},
{"code": "KeyB", "key": "B", "type": "keydown"},
{"code": "KeyB", "key": "B", "type": "keypress"},
{"code": "KeyB", "key": "B", "type": "keyup"},
{"code": "ShiftLeft", "key": "Shift", "type": "keyup"},
]
all_events = await get_events(bidi_session, top_context["context"])
(key_events, expected) = filter_supported_key_events(all_events, expected)
recursive_compare(expected, key_events)
keys_value = await get_keys_value(bidi_session, top_context["context"])
assert keys_value == "aB"
async def test_parallel_pointer(bidi_session, get_test_page, top_context):
url = get_test_page()
await bidi_session.browsing_context.navigate(
context=top_context["context"],
url=url,
wait="complete")
await add_mouse_listeners(bidi_session, top_context)
point_1 = {"x": 5, "y": 10}
point_2 = {"x": 10, "y": 20}
actions_1 = Actions()
(
actions_1.add_pointer()
.pointer_move(x=point_1["x"], y=point_1["y"])
.pointer_down(button=0)
.pointer_up(button=0)
)
actions_2 = Actions()
(
actions_2.add_pointer()
.pointer_move(x=point_2["x"], y=point_2["y"])
.pointer_down(button=0)
.pointer_up(button=0)
)
# Run both actions in parallel to check that they are queued for
# sequential execution.
actions_performed = [
bidi_session.input.perform_actions(
actions=actions_1, context=top_context["context"]
),
bidi_session.input.perform_actions(
actions=actions_2, context=top_context["context"]
),
]
await asyncio.gather(*actions_performed)
common_attributes = {
"button": 0,
"buttons": 0,
"detail": 1,
"isTrusted": True,
"clientX": point_1["x"],
"clientY": point_1["y"],
}
mouse_events = [
{"type": "mousemove"},
{"type": "mousedown", "buttons": 1},
{"type": "mouseup"},
{"type": "click"},
]
# Expected events for the first action.
expected_events_1 = [{**common_attributes, **event}
for event in mouse_events]
# Expected events for the second action.
common_attributes.update(
{"clientX": point_2["x"], "clientY": point_2["y"]})
expected_events_2 = [{**common_attributes, **event}
for event in mouse_events]
events = await get_events(bidi_session, top_context["context"])
assert events[:4] == expected_events_1
assert events[4:] == expected_events_2

Просмотреть файл

@ -1,6 +1,6 @@
import pytest
from webdriver.bidi.error import MoveTargetOutOfBoundsException, NoSuchFrameException
from webdriver.bidi.error import NoSuchFrameException
from webdriver.bidi.modules.input import Actions, get_element_origin
from webdriver.bidi.modules.script import ContextTarget
@ -20,23 +20,6 @@ async def test_invalid_browsing_context(bidi_session):
await bidi_session.input.perform_actions(actions=actions, context="foo")
@pytest.mark.parametrize("origin", ["element", "viewport"])
async def test_params_actions_origin_outside_viewport(
bidi_session, setup_wheel_test, top_context, get_element, origin
):
if origin == "element":
element = await get_element("#scrollable")
origin = get_element_origin(element)
actions = Actions()
actions.add_wheel().scroll(x=-100, y=-100, delta_x=10, delta_y=20, origin=origin)
with pytest.raises(MoveTargetOutOfBoundsException):
await bidi_session.input.perform_actions(
actions=actions, context=top_context["context"]
)
@pytest.mark.parametrize("delta_x, delta_y", [(0, 10), (5, 0), (5, 10)])
async def test_scroll_not_scrollable(
bidi_session, setup_wheel_test, top_context, get_element, delta_x, delta_y

Просмотреть файл

@ -1,126 +0,0 @@
import asyncio
import pytest
from webdriver.bidi.modules.input import Actions
from tests.support.helpers import filter_supported_key_events
from tests.support.keys import Keys
from .. import add_mouse_listeners, get_events, get_keys_value
from ... import recursive_compare
pytestmark = pytest.mark.asyncio
async def test_parallel_key(bidi_session, top_context, setup_key_test):
actions_1 = Actions()
actions_1.add_key().key_down("a").key_down(Keys.SHIFT)
actions_2 = Actions()
actions_2.add_key().key_down("b")
# Run the first release actions in-between to check that it is queued for
# sequential execution, and the state is reset before the 2nd action.
actions_performed = [
bidi_session.input.perform_actions(
actions=actions_1, context=top_context["context"]
),
bidi_session.input.release_actions(context=top_context["context"]),
bidi_session.input.perform_actions(
actions=actions_2, context=top_context["context"]
),
bidi_session.input.release_actions(context=top_context["context"]),
]
await asyncio.gather(*actions_performed)
expected = [
{"code": "KeyA", "key": "a", "type": "keydown"},
{"code": "KeyA", "key": "a", "type": "keypress"},
{"code": "ShiftLeft", "key": "Shift", "type": "keydown"},
{"code": "ShiftLeft", "key": "Shift", "type": "keyup"},
{"code": "KeyA", "key": "a", "type": "keyup"},
{"code": "KeyB", "key": "b", "type": "keydown"},
{"code": "KeyB", "key": "b", "type": "keypress"},
{"code": "KeyB", "key": "b", "type": "keyup"},
]
all_events = await get_events(bidi_session, top_context["context"])
(key_events, expected) = filter_supported_key_events(all_events, expected)
recursive_compare(expected, key_events)
keys_value = await get_keys_value(bidi_session, top_context["context"])
assert keys_value == "ab"
async def test_parallel_pointer(bidi_session, get_test_page, top_context):
url = get_test_page()
await bidi_session.browsing_context.navigate(
context=top_context["context"],
url=url,
wait="complete")
await add_mouse_listeners(bidi_session, top_context)
point_1 = {"x": 5, "y": 10}
point_2 = {"x": 10, "y": 20}
actions_1 = Actions()
(
actions_1.add_pointer()
.pointer_move(x=point_1["x"], y=point_1["y"])
.pointer_down(button=0)
)
actions_2 = Actions()
(
actions_2.add_pointer()
.pointer_move(x=point_2["x"], y=point_2["y"])
.pointer_down(button=0)
)
# Run the first release actions in-between to check that it is queued for
# sequential execution, and the state is reset before the 2nd action.
actions_performed = [
bidi_session.input.perform_actions(
actions=actions_1, context=top_context["context"]
),
bidi_session.input.release_actions(context=top_context["context"]),
bidi_session.input.perform_actions(
actions=actions_2, context=top_context["context"]
),
bidi_session.input.release_actions(context=top_context["context"]),
]
await asyncio.gather(*actions_performed)
common_attributes = {
"button": 0,
"buttons": 0,
"detail": 1,
"isTrusted": True,
"clientX": point_1["x"],
"clientY": point_1["y"],
}
mouse_events = [
{"type": "mousemove"},
{"type": "mousedown", "buttons": 1},
{"type": "mouseup"},
{"type": "click"},
]
# Expected events for the first action.
expected_events_1 = [{**common_attributes, **event}
for event in mouse_events]
# Expected events for the second action.
common_attributes.update(
{"clientX": point_2["x"], "clientY": point_2["y"]})
expected_events_2 = [{**common_attributes, **event}
for event in mouse_events]
events = await get_events(bidi_session, top_context["context"])
assert events[:4] == expected_events_1
assert events[4:] == expected_events_2

Просмотреть файл

@ -1,11 +1,6 @@
import pytest
from webdriver.error import (
InvalidArgumentException,
MoveTargetOutOfBoundsException,
NoSuchWindowException,
StaleElementReferenceException,
)
from webdriver.error import InvalidArgumentException, NoSuchWindowException, StaleElementReferenceException
from tests.classic.perform_actions.support.mouse import (
get_inview_center,
@ -42,15 +37,6 @@ def test_stale_element_reference(session, stale_element, mouse_chain, as_frame):
mouse_chain.click(element=element).perform()
@pytest.mark.parametrize("origin", ["element", "pointer", "viewport"])
def test_params_actions_origin_outside_viewport(session, test_actions_page, mouse_chain, origin):
if origin == "element":
origin = session.find.css("#outer", all=False)
with pytest.raises(MoveTargetOutOfBoundsException):
mouse_chain.pointer_move(-100, -100, origin=origin).perform()
def test_click_at_coordinates(session, test_actions_page, mouse_chain):
div_point = {
"x": 82,

Просмотреть файл

@ -1,10 +1,6 @@
import pytest
from webdriver.error import (
MoveTargetOutOfBoundsException,
NoSuchWindowException,
StaleElementReferenceException,
)
from webdriver.error import NoSuchWindowException, StaleElementReferenceException
from tests.classic.perform_actions.support.mouse import (
get_inview_center,
@ -38,15 +34,6 @@ def test_stale_element_reference(session, stale_element, pen_chain, as_frame):
pen_chain.click(element=element).perform()
@pytest.mark.parametrize("origin", ["element", "pointer", "viewport"])
def test_params_actions_origin_outside_viewport(session, test_actions_page, pen_chain, origin):
if origin == "element":
origin = session.find.css("#outer", all=False)
with pytest.raises(MoveTargetOutOfBoundsException):
pen_chain.pointer_move(-100, -100, origin=origin).perform()
@pytest.mark.parametrize("mode", ["open", "closed"])
@pytest.mark.parametrize("nested", [False, True], ids=["outer", "inner"])
def test_pen_pointer_in_shadow_tree(

Просмотреть файл

@ -1,10 +1,6 @@
import pytest
from webdriver.error import (
MoveTargetOutOfBoundsException,
NoSuchWindowException,
StaleElementReferenceException
)
from webdriver.error import NoSuchWindowException, StaleElementReferenceException
from tests.classic.perform_actions.support.mouse import (
get_inview_center,
get_viewport_rect,
@ -13,7 +9,6 @@ from tests.classic.perform_actions.support.refine import get_events
from . import assert_pointer_events, record_pointer_events
def test_null_response_value(session, touch_chain):
value = touch_chain.click().perform()
assert value is None
@ -37,15 +32,6 @@ def test_stale_element_reference(session, stale_element, touch_chain, as_frame):
touch_chain.click(element=element).perform()
@pytest.mark.parametrize("origin", ["element", "pointer", "viewport"])
def test_params_actions_origin_outside_viewport(session, test_actions_page, touch_chain, origin):
if origin == "element":
origin = session.find.css("#outer", all=False)
with pytest.raises(MoveTargetOutOfBoundsException):
touch_chain.pointer_move(-100, -100, origin=origin).perform()
@pytest.mark.parametrize("mode", ["open", "closed"])
@pytest.mark.parametrize("nested", [False, True], ids=["outer", "inner"])
def test_touch_pointer_in_shadow_tree(

Просмотреть файл

@ -1,6 +1,6 @@
import pytest
from webdriver.error import MoveTargetOutOfBoundsException, NoSuchWindowException
from webdriver.error import NoSuchWindowException
import time
from tests.classic.perform_actions.support.refine import get_events
@ -23,17 +23,6 @@ def test_no_browsing_context(session, closed_window, wheel_chain):
wheel_chain.scroll(0, 0, 0, 10).perform()
@pytest.mark.parametrize("origin", ["element", "viewport"])
def test_params_actions_origin_outside_viewport(
session, test_actions_scroll_page, wheel_chain, origin
):
if origin == "element":
origin = session.find.css("#scrollable", all=False)
with pytest.raises(MoveTargetOutOfBoundsException):
wheel_chain.scroll(-100, -100, 10, 20, origin="viewport").perform()
def test_scroll_not_scrollable(session, test_actions_scroll_page, wheel_chain):
target = session.find.css("#not-scrollable", all=False)