mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-17 06:09:19 +00:00
Bug 1930530 - [marionette] Correctly retry to dispatch actions when the browsing context is replaced. r=webdriver-reviewers,jdescottes
Differential Revision: https://phabricator.services.mozilla.com/D228658
This commit is contained in:
parent
7b29a2787f
commit
87f8b4d440
@ -119,36 +119,49 @@ export class MarionetteCommandsChild extends JSWindowActorChild {
|
||||
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`
|
||||
try {
|
||||
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`
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.message.includes("NS_ERROR_FAILURE")) {
|
||||
// Event dispatch failed. Re-throwing as AbortError to allow retrying
|
||||
// to dispatch the event.
|
||||
throw new DOMException(
|
||||
`Failed to dispatch event "${eventName}": ${e.message}`,
|
||||
"AbortError"
|
||||
);
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@ -683,8 +696,8 @@ export class MarionetteCommandsChild extends JSWindowActorChild {
|
||||
}
|
||||
browsingContext = childContexts[id];
|
||||
} else {
|
||||
const context = childContexts.find(context => {
|
||||
return context.embedderElement === id;
|
||||
const context = childContexts.find(childContext => {
|
||||
return childContext.embedderElement === id;
|
||||
});
|
||||
if (!context) {
|
||||
throw new lazy.error.NoSuchFrameError(
|
||||
|
@ -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,41 @@ 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) {
|
||||
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) {
|
||||
dispatchEvent(eventName, details) {
|
||||
return this.sendQuery("MarionetteCommandsParent:_dispatchEvent", {
|
||||
eventName,
|
||||
details,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalize an action command.
|
||||
*
|
||||
* @returns {Promise}
|
||||
* Promise that resolves when the finalization is done.
|
||||
*/
|
||||
#finalizeAction() {
|
||||
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) {
|
||||
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) {
|
||||
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,
|
||||
@ -378,34 +271,10 @@ export class MarionetteCommandsParent extends JSWindowActorParent {
|
||||
});
|
||||
}
|
||||
|
||||
async performActions(actions, asyncEventsEnabled) {
|
||||
if (!asyncEventsEnabled) {
|
||||
// Bug 1920959: Remove if we no longer need to dispatch in content.
|
||||
await this.sendQuery("MarionetteCommandsParent:performActions", {
|
||||
actions,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 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,
|
||||
performActions(actions) {
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -414,29 +283,8 @@ export class MarionetteCommandsParent extends JSWindowActorParent {
|
||||
* 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(asyncEventsEnabled) {
|
||||
if (!asyncEventsEnabled) {
|
||||
// Bug 1920959: Remove if we no longer need to dispatch in content.
|
||||
await this.sendQuery("MarionetteCommandsParent:releaseActions");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 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();
|
||||
releaseActions() {
|
||||
return this.sendQuery("MarionetteCommandsParent:releaseActions");
|
||||
}
|
||||
|
||||
async switchToFrame(id) {
|
||||
@ -528,16 +376,29 @@ export function getMarionetteCommandsActorProxy(browsingContextFn) {
|
||||
get(target, methodName) {
|
||||
return async (...args) => {
|
||||
let attempts = 0;
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
try {
|
||||
const browsingContext = browsingContextFn();
|
||||
if (!browsingContext) {
|
||||
throw new DOMException(
|
||||
"No BrowsingContext found",
|
||||
"NoBrowsingContext"
|
||||
);
|
||||
}
|
||||
let browsingContext = browsingContextFn();
|
||||
|
||||
// If a top-level browsing context was replaced and retrying is allowed,
|
||||
// retrieve the new one for the current browser.
|
||||
if (
|
||||
browsingContext?.isReplaced &&
|
||||
browsingContext.top === browsingContext &&
|
||||
!NO_RETRY_METHODS.includes(methodName)
|
||||
) {
|
||||
browsingContext = BrowsingContext.getCurrentTopByBrowserId(
|
||||
browsingContext.browserId
|
||||
);
|
||||
}
|
||||
|
||||
if (!browsingContext) {
|
||||
throw new lazy.error.UnknownError(
|
||||
`BrowsingContext does no longer exist`
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO: Scenarios where the window/tab got closed and
|
||||
// currentWindowGlobal is null will be handled in Bug 1662808.
|
||||
const actor =
|
||||
@ -555,25 +416,27 @@ export function getMarionetteCommandsActorProxy(browsingContextFn) {
|
||||
}
|
||||
|
||||
if (NO_RETRY_METHODS.includes(methodName)) {
|
||||
const browsingContextId = browsingContextFn()?.id;
|
||||
lazy.logger.trace(
|
||||
`[${browsingContextId}] Querying "${methodName}" failed with` +
|
||||
` ${e.name}, returning "null" as fallback`
|
||||
`[${browsingContext.id}] Querying "${methodName}"` +
|
||||
` failed with ${e.name}, returning "null" as fallback`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (++attempts > MAX_ATTEMPTS) {
|
||||
const browsingContextId = browsingContextFn()?.id;
|
||||
lazy.logger.trace(
|
||||
`[${browsingContextId}] Querying "${methodName} "` +
|
||||
`reached the limit of retry attempts (${MAX_ATTEMPTS})`
|
||||
`[${browsingContext.id}] Querying "${methodName}"` +
|
||||
` reached the limit of retry attempts (${MAX_ATTEMPTS})`
|
||||
);
|
||||
throw e;
|
||||
}
|
||||
|
||||
lazy.logger.trace(
|
||||
`Retrying "${methodName}", attempt: ${attempts}`
|
||||
`[${browsingContext.id}] Retrying "${methodName}"` +
|
||||
`, attempt: ${attempts}`
|
||||
);
|
||||
await new Promise(resolve =>
|
||||
Services.tm.dispatchToMainThread(resolve)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
action: "chrome://remote/content/shared/webdriver/Actions.sys.mjs",
|
||||
Addon: "chrome://remote/content/marionette/addon.sys.mjs",
|
||||
AppInfo: "chrome://remote/content/shared/AppInfo.sys.mjs",
|
||||
assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
|
||||
@ -102,6 +103,181 @@ const TOPIC_QUIT_APPLICATION_REQUESTED = "quit-application-requested";
|
||||
* @namespace driver
|
||||
*/
|
||||
|
||||
class ActionsHelper {
|
||||
#actionsOptions;
|
||||
#driver;
|
||||
|
||||
constructor(driver) {
|
||||
this.#driver = driver;
|
||||
|
||||
// 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),
|
||||
};
|
||||
}
|
||||
|
||||
get actionsOptions() {
|
||||
return this.#actionsOptions;
|
||||
}
|
||||
|
||||
#getActor(browsingContext) {
|
||||
return lazy.getMarionetteCommandsActorProxy(() => browsingContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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} browsingContext
|
||||
* 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, browsingContext) {
|
||||
return this.#getActor(browsingContext).assertInViewPort(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch an event.
|
||||
*
|
||||
* @param {string} eventName
|
||||
* Name of the event to be dispatched.
|
||||
* @param {BrowsingContext} browsingContext
|
||||
* 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, browsingContext, details) {
|
||||
return this.#getActor(browsingContext).dispatchEvent(eventName, details);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalize an action command.
|
||||
*
|
||||
* @param {BrowsingContext} browsingContext
|
||||
* The browsing context to dispatch the event to.
|
||||
*
|
||||
* @returns {Promise}
|
||||
* Promise that resolves when the finalization is done.
|
||||
*/
|
||||
finalizeAction(browsingContext) {
|
||||
this.#getActor(browsingContext).finalizeAction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the WebElement reference of the origin.
|
||||
*
|
||||
* @param {ElementOrigin} origin
|
||||
* Reference to the element origin of the action.
|
||||
* @param {BrowsingContext} _browsingContext
|
||||
* Not used by Marionette.
|
||||
*
|
||||
* @returns {WebElement}
|
||||
* The WebElement reference.
|
||||
*/
|
||||
getElementOrigin(origin, _browsingContext) {
|
||||
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} browsingContext
|
||||
* 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(element, browsingContext) {
|
||||
return this.#getActor(browsingContext).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} browsingContext
|
||||
* 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, browsingContext) {
|
||||
return this.#getActor(browsingContext).getInViewCentrePoint(rect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the action's input state.
|
||||
*
|
||||
* @param {BrowsingContext} browsingContext
|
||||
* The Browsing Context to retrieve the input state for.
|
||||
*
|
||||
* @returns {Actions.InputState}
|
||||
* The action's input state.
|
||||
*/
|
||||
getInputState(browsingContext) {
|
||||
// Bug 1821460: Fetch top-level browsing context.
|
||||
let inputState = this.#driver._inputStates.get(browsingContext);
|
||||
|
||||
if (inputState === undefined) {
|
||||
inputState = new lazy.action.State();
|
||||
this.#driver._inputStates.set(browsingContext, inputState);
|
||||
}
|
||||
|
||||
return inputState;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the action's input state.
|
||||
*
|
||||
* @param {BrowsingContext} browsingContext
|
||||
* The Browsing Context to reset the input state for.
|
||||
*/
|
||||
resetInputState(browsingContext) {
|
||||
// Bug 1821460: Fetch top-level browsing context.
|
||||
if (this.#driver._inputStates.has(browsingContext)) {
|
||||
this.#driver._inputStates.delete(browsingContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements (parts of) the W3C WebDriver protocol. GeckoDriver lives
|
||||
* in chrome space and mediates calls to the current browsing context's actor.
|
||||
@ -140,6 +316,12 @@ export function GeckoDriver(server) {
|
||||
// used for modal dialogs
|
||||
this.dialog = null;
|
||||
this.promptListener = null;
|
||||
|
||||
// Browsing context => input state.
|
||||
// Bug 1821460: Move to WebDriver Session and share with Remote Agent.
|
||||
this._inputStates = new WeakMap();
|
||||
|
||||
this._actionsHelper = new ActionsHelper(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1490,11 +1672,37 @@ GeckoDriver.prototype.setTimeouts = function (cmd) {
|
||||
* Not yet available in current context.
|
||||
*/
|
||||
GeckoDriver.prototype.performActions = async function (cmd) {
|
||||
lazy.assert.open(this.getBrowsingContext());
|
||||
const { actions } = cmd.parameters;
|
||||
|
||||
const browsingContext = lazy.assert.open(this.getBrowsingContext());
|
||||
await this._handleUserPrompts();
|
||||
|
||||
const actions = cmd.parameters.actions;
|
||||
await this.getActor().performActions(actions, lazy.prefAsyncEventsEnabled);
|
||||
if (!lazy.prefAsyncEventsEnabled) {
|
||||
// Bug 1920959: Remove if we no longer need to dispatch in content.
|
||||
await this.getActor().performActions(actions);
|
||||
return;
|
||||
}
|
||||
|
||||
// Bug 1821460: Fetch top-level browsing context.
|
||||
const inputState = this._actionsHelper.getInputState(browsingContext);
|
||||
const actionsOptions = {
|
||||
...this._actionsHelper.actionsOptions,
|
||||
context: browsingContext,
|
||||
};
|
||||
|
||||
const actionChain = await lazy.action.Chain.fromJSON(
|
||||
inputState,
|
||||
actions,
|
||||
actionsOptions
|
||||
);
|
||||
|
||||
// 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._actionsHelper.finalizeAction(browsingContext);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1508,10 +1716,32 @@ GeckoDriver.prototype.performActions = async function (cmd) {
|
||||
* Not available in current context.
|
||||
*/
|
||||
GeckoDriver.prototype.releaseActions = async function () {
|
||||
lazy.assert.open(this.getBrowsingContext());
|
||||
const browsingContext = lazy.assert.open(this.getBrowsingContext());
|
||||
await this._handleUserPrompts();
|
||||
|
||||
await this.getActor().releaseActions(lazy.prefAsyncEventsEnabled);
|
||||
if (!lazy.prefAsyncEventsEnabled) {
|
||||
// Bug 1920959: Remove if we no longer need to dispatch in content.
|
||||
await this.getActor().releaseActions();
|
||||
return;
|
||||
}
|
||||
|
||||
// Bug 1821460: Fetch top-level browsing context.
|
||||
const inputState = this._actionsHelper.getInputState(browsingContext);
|
||||
const actionsOptions = {
|
||||
...this._actionsHelper.actionsOptions,
|
||||
context: browsingContext,
|
||||
};
|
||||
|
||||
// Enqueue to serialize access to input state.
|
||||
await inputState.enqueueAction(() => {
|
||||
const undoActions = inputState.inputCancelList.reverse();
|
||||
return undoActions.dispatch(inputState, actionsOptions);
|
||||
});
|
||||
|
||||
this._actionsHelper.resetInputState(browsingContext);
|
||||
|
||||
// Process async follow-up tasks in content before the reply is sent.
|
||||
await this._actionsHelper.finalizeAction(browsingContext);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -34,10 +34,10 @@ add_task(async function test_commandsActor_getActorProxy_noBrowsingContext() {
|
||||
|
||||
try {
|
||||
await getMarionetteCommandsActorProxy(() => null).sendQuery("foo", "bar");
|
||||
ok(false, "Expected NoBrowsingContext error not raised");
|
||||
ok(false, "Expected error not raised");
|
||||
} catch (e) {
|
||||
ok(
|
||||
e.message.includes("No BrowsingContext found"),
|
||||
e.message.includes("BrowsingContext does no longer exist"),
|
||||
"Expected default error message found"
|
||||
);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user