зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1694144 - [WebDriver BiDi] Add basic support for session.subscribe command. r=webdriver-reviewers,jdescottes
Differential Revision: https://phabricator.services.mozilla.com/D122018
This commit is contained in:
Родитель
0ffd0582d4
Коммит
42f6a8c709
|
@ -25,6 +25,7 @@ remote.jar:
|
|||
content/shared/messagehandler/MessageHandler.jsm (shared/messagehandler/MessageHandler.jsm)
|
||||
content/shared/messagehandler/MessageHandlerInfo.jsm (shared/messagehandler/MessageHandlerInfo.jsm)
|
||||
content/shared/messagehandler/MessageHandlerRegistry.jsm (shared/messagehandler/MessageHandlerRegistry.jsm)
|
||||
content/shared/messagehandler/Module.jsm (shared/messagehandler/Module.jsm)
|
||||
content/shared/messagehandler/ModuleCache.jsm (shared/messagehandler/ModuleCache.jsm)
|
||||
content/shared/messagehandler/RootMessageHandler.jsm (shared/messagehandler/RootMessageHandler.jsm)
|
||||
content/shared/messagehandler/WindowGlobalMessageHandler.jsm (shared/messagehandler/WindowGlobalMessageHandler.jsm)
|
||||
|
|
|
@ -112,18 +112,23 @@ class MessageHandler extends EventEmitter {
|
|||
|
||||
/**
|
||||
* @typedef {Object} CommandDestination
|
||||
* @property {String} type - One of MessageHandler.type.
|
||||
* @property {String} id - Unique context identifier, format depends on the
|
||||
* type. For WINDOW_GLOBAL destinations, this is a browsing context id.
|
||||
* @property {String} type
|
||||
* One of MessageHandler.type.
|
||||
* @property {String} id
|
||||
* Unique context identifier. The format depends on the type.
|
||||
* For WINDOW_GLOBAL destinations, this is a browsing context id.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Command
|
||||
* @property {String} commandName - The name of the command to execute.
|
||||
* @property {String} moduleName - The name of the module.
|
||||
* @property {CommandDestination} destination - The destination describing a
|
||||
* debuggable context.
|
||||
* @property {Object} params - Optional arguments.
|
||||
* @property {String} commandName
|
||||
* The name of the command to execute.
|
||||
* @property {String} moduleName
|
||||
* The name of the module.
|
||||
* @property {Object} params
|
||||
* Optional command parameters.
|
||||
* @property {CommandDestination} destination
|
||||
* The destination describing a debuggable context.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -137,14 +142,14 @@ class MessageHandler extends EventEmitter {
|
|||
* command once it has been executed.
|
||||
*/
|
||||
handleCommand(command) {
|
||||
const { moduleName, commandName, destination, params } = command;
|
||||
const { moduleName, commandName, params, destination } = command;
|
||||
logger.trace(
|
||||
`Received command ${moduleName}:${commandName} for destination ${destination.type}`
|
||||
`Received command ${moduleName}.${commandName} for destination ${destination.type}`
|
||||
);
|
||||
|
||||
const mod = this._moduleCache.getModuleInstance(moduleName, destination);
|
||||
if (this._isCommandSupportedByModule(commandName, mod)) {
|
||||
return mod[commandName](params, destination);
|
||||
const module = this._moduleCache.getModuleInstance(moduleName, destination);
|
||||
if (this._isCommandSupportedByModule(commandName, module)) {
|
||||
return module[commandName](params, destination);
|
||||
}
|
||||
|
||||
return this.forwardCommand(command);
|
||||
|
@ -154,12 +159,12 @@ class MessageHandler extends EventEmitter {
|
|||
return `[object ${this.constructor.name} ${this.key}]`;
|
||||
}
|
||||
|
||||
_isCommandSupportedByModule(commandName, mod) {
|
||||
_isCommandSupportedByModule(commandName, module) {
|
||||
// TODO: With the current implementation, all functions of a given module
|
||||
// are considered as valid commands.
|
||||
// This should probably be replaced by a more explicit declaration, via a
|
||||
// manifest for instance.
|
||||
return mod && typeof mod[commandName] === "function";
|
||||
return module && typeof module[commandName] === "function";
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/* 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 EXPORTED_SYMBOLS = ["Module"];
|
||||
|
||||
class Module {
|
||||
/**
|
||||
* Create a new module instance.
|
||||
*
|
||||
* @param {MessageHandler} messageHandler
|
||||
* The MessageHandler instance which owns this Module instance.
|
||||
*/
|
||||
constructor(messageHandler) {
|
||||
this._messageHandler = messageHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean-up the module instance.
|
||||
*
|
||||
* It's required to be implemented in the sub class.
|
||||
*/
|
||||
destroy() {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
get messageHandler() {
|
||||
return this._messageHandler;
|
||||
}
|
||||
}
|
|
@ -127,22 +127,22 @@ add_task(async function test_multisession() {
|
|||
await rootMessageHandler1.handleCommand({
|
||||
moduleName: "command",
|
||||
commandName: "testSetValue",
|
||||
params: { value: "session1-value" },
|
||||
destination: {
|
||||
type: WindowGlobalMessageHandler.type,
|
||||
id: browsingContextId,
|
||||
},
|
||||
params: "session1-value",
|
||||
});
|
||||
|
||||
info("Set value for session 2");
|
||||
await rootMessageHandler2.handleCommand({
|
||||
moduleName: "command",
|
||||
commandName: "testSetValue",
|
||||
params: { value: "session2-value" },
|
||||
destination: {
|
||||
type: WindowGlobalMessageHandler.type,
|
||||
id: browsingContextId,
|
||||
},
|
||||
params: "session2-value",
|
||||
});
|
||||
|
||||
const session1Value = await rootMessageHandler1.handleCommand({
|
||||
|
@ -189,6 +189,7 @@ add_task(async function test_forwarding_command() {
|
|||
const interceptAndForwardValue = await rootMessageHandler.handleCommand({
|
||||
moduleName: "command",
|
||||
commandName: "testInterceptAndForwardModule",
|
||||
params: { id: "value" },
|
||||
destination: {
|
||||
type: WindowGlobalMessageHandler.type,
|
||||
id: browsingContextId,
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
|
||||
const EXPORTED_SYMBOLS = ["command"];
|
||||
|
||||
class Command {
|
||||
constructor(messageHandler) {
|
||||
this.messageHandler = messageHandler;
|
||||
}
|
||||
const { Module } = ChromeUtils.import(
|
||||
"chrome://remote/content/shared/messagehandler/Module.jsm"
|
||||
);
|
||||
|
||||
class Command extends Module {
|
||||
destroy() {}
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
|
||||
const EXPORTED_SYMBOLS = ["event"];
|
||||
|
||||
class Event {
|
||||
constructor(messageHandler) {
|
||||
this.messageHandler = messageHandler;
|
||||
}
|
||||
const { Module } = ChromeUtils.import(
|
||||
"chrome://remote/content/shared/messagehandler/Module.jsm"
|
||||
);
|
||||
|
||||
class Event extends Module {
|
||||
destroy() {}
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
|
||||
const EXPORTED_SYMBOLS = ["command"];
|
||||
|
||||
class Command {
|
||||
constructor(messageHandler) {
|
||||
this.messageHandler = messageHandler;
|
||||
}
|
||||
const { Module } = ChromeUtils.import(
|
||||
"chrome://remote/content/shared/messagehandler/Module.jsm"
|
||||
);
|
||||
|
||||
class Command extends Module {
|
||||
destroy() {}
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
|
||||
const EXPORTED_SYMBOLS = ["event"];
|
||||
|
||||
class Event {
|
||||
constructor(messageHandler) {
|
||||
this.messageHandler = messageHandler;
|
||||
}
|
||||
const { Module } = ChromeUtils.import(
|
||||
"chrome://remote/content/shared/messagehandler/Module.jsm"
|
||||
);
|
||||
|
||||
class Event extends Module {
|
||||
destroy() {}
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
|
||||
const EXPORTED_SYMBOLS = ["command"];
|
||||
|
||||
class Command {
|
||||
constructor(messageHandler) {
|
||||
this.messageHandler = messageHandler;
|
||||
}
|
||||
const { Module } = ChromeUtils.import(
|
||||
"chrome://remote/content/shared/messagehandler/Module.jsm"
|
||||
);
|
||||
|
||||
class Command extends Module {
|
||||
destroy() {}
|
||||
|
||||
/**
|
||||
|
@ -22,7 +22,9 @@ class Command {
|
|||
}
|
||||
|
||||
testSetValue(params) {
|
||||
this._testValue = params;
|
||||
const { value } = params;
|
||||
|
||||
this._testValue = value;
|
||||
}
|
||||
|
||||
testGetValue() {
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
|
||||
const EXPORTED_SYMBOLS = ["commandwindowglobalonly"];
|
||||
|
||||
class CommandWindowGlobalOnly {
|
||||
constructor(messageHandler) {
|
||||
this.messageHandler = messageHandler;
|
||||
}
|
||||
const { Module } = ChromeUtils.import(
|
||||
"chrome://remote/content/shared/messagehandler/Module.jsm"
|
||||
);
|
||||
|
||||
class CommandWindowGlobalOnly extends Module {
|
||||
destroy() {}
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
|
||||
const EXPORTED_SYMBOLS = ["event"];
|
||||
|
||||
class Event {
|
||||
constructor(messageHandler) {
|
||||
this.messageHandler = messageHandler;
|
||||
}
|
||||
const { Module } = ChromeUtils.import(
|
||||
"chrome://remote/content/shared/messagehandler/Module.jsm"
|
||||
);
|
||||
|
||||
class Event extends Module {
|
||||
destroy() {}
|
||||
|
||||
/**
|
||||
|
|
|
@ -224,6 +224,8 @@ class WebDriverSession {
|
|||
return this.messageHandler.handleCommand({
|
||||
moduleName: module,
|
||||
commandName: command,
|
||||
params,
|
||||
|
||||
// XXX: At the moment, commands do not describe consistently their destination,
|
||||
// so we will need a translation step based on a specific command and its params
|
||||
// in order to extract a destination that can be understood by the MessageHandler.
|
||||
|
@ -234,7 +236,6 @@ class WebDriverSession {
|
|||
destination: {
|
||||
type: RootMessageHandler.type,
|
||||
},
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -107,6 +107,7 @@ class WebDriverBiDiConnection extends WebSocketConnection {
|
|||
* A JSON-serializable object, which is the actual result.
|
||||
*/
|
||||
sendResult(id, result) {
|
||||
result = typeof result !== "undefined" ? result : {};
|
||||
this.send({ id, result });
|
||||
}
|
||||
|
||||
|
@ -135,13 +136,10 @@ class WebDriverBiDiConnection extends WebSocketConnection {
|
|||
const { id, method, params } = packet;
|
||||
|
||||
try {
|
||||
// First check for mandatory field in the packets
|
||||
if (typeof id == "undefined") {
|
||||
throw new TypeError("Message missing 'id' field");
|
||||
}
|
||||
if (typeof method == "undefined") {
|
||||
throw new TypeError("Message missing 'method' field");
|
||||
}
|
||||
// First check for mandatory field in the command packet
|
||||
assert.positiveInteger(id, "id: unsigned integer value expected");
|
||||
assert.string(method, "method: string value expected");
|
||||
assert.object(params, "params: object value expected");
|
||||
|
||||
// Extract the module and the command name out of `method` attribute
|
||||
const { module, command } = splitMethod(method);
|
||||
|
|
|
@ -8,3 +8,6 @@ remote.jar:
|
|||
content/webdriver-bidi/NewSessionHandler.jsm (NewSessionHandler.jsm)
|
||||
content/webdriver-bidi/WebDriverBiDi.jsm (WebDriverBiDi.jsm)
|
||||
content/webdriver-bidi/WebDriverBiDiConnection.jsm (WebDriverBiDiConnection.jsm)
|
||||
|
||||
# WebDriver BiDi modules
|
||||
content/webdriver-bidi/modules/root/session.jsm (modules/root/session.jsm)
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
/* 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 EXPORTED_SYMBOLS = ["session"];
|
||||
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
assert: "chrome://remote/content/shared/webdriver/Assert.jsm",
|
||||
Module: "chrome://remote/content/shared/messagehandler/Module.jsm",
|
||||
RootMessageHandler:
|
||||
"chrome://remote/content/shared/messagehandler/RootMessageHandler.jsm",
|
||||
});
|
||||
|
||||
class Session extends Module {
|
||||
destroy() {}
|
||||
|
||||
/**
|
||||
* Commands
|
||||
*/
|
||||
|
||||
/**
|
||||
* Enable certain events either globally, or for a list of browsing contexts.
|
||||
*
|
||||
* @params {Object=} params
|
||||
* @params {Array<String>} events
|
||||
* List of events to subscribe to.
|
||||
* @params {Array<String>=} contexts
|
||||
* Optional list of top-level browsing context ids
|
||||
* to subscribe the events for.
|
||||
*
|
||||
* @throws {InvalidArgumentError}
|
||||
* If <var>events</var> or <var>contexts</var> are not valid types.
|
||||
*/
|
||||
async subscribe(params = {}) {
|
||||
const { events, contexts = [] } = params;
|
||||
|
||||
// Check input types until we run schema validation.
|
||||
assert.array(events, "events: array value expected");
|
||||
events.forEach(name => {
|
||||
assert.string(name, `${name}: string value expected`);
|
||||
});
|
||||
|
||||
assert.array(contexts, "contexts: array value expected");
|
||||
contexts.forEach(context => {
|
||||
assert.string(context, `${context}: string value expected`);
|
||||
});
|
||||
|
||||
// For now just subscribe the events to all available top-level
|
||||
// browsing contexts.
|
||||
const allEvents = events
|
||||
.map(event => Array.from(obtainEvents(event)))
|
||||
.flat();
|
||||
await Promise.allSettled(
|
||||
allEvents.map(event => {
|
||||
const [moduleName] = event.split(".");
|
||||
return this.messageHandler.handleCommand({
|
||||
moduleName,
|
||||
commandName: "_subscribeEvent",
|
||||
params: {
|
||||
event,
|
||||
},
|
||||
destination: {
|
||||
type: RootMessageHandler.type,
|
||||
},
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a set of events based on the given event name.
|
||||
*
|
||||
* Could contain a period for a
|
||||
* specific event, or just the module name for all events.
|
||||
*
|
||||
* @param {String} event
|
||||
* Name of the event to process.
|
||||
*
|
||||
* @returns {Set<String>}
|
||||
* A Set with the expanded events in the form of `<module>.<event>`.
|
||||
*
|
||||
* @throws {InvalidArgumentError}
|
||||
* If <var>event</var> does not reference a valid event.
|
||||
*/
|
||||
function obtainEvents(event) {
|
||||
const events = new Set();
|
||||
|
||||
// Check if a period is present that splits the event name into the module,
|
||||
// and the actual event. Hereby only care about the first found instance.
|
||||
const index = event.indexOf(".");
|
||||
if (index >= 0) {
|
||||
// TODO: Throw invalid argument error if event doesn't exist
|
||||
events.add(event);
|
||||
} else {
|
||||
// Interpret the name as module, and register all its available events
|
||||
// TODO: Throw invalid argument error if not a valid module name
|
||||
// TODO: Append all available events from the module
|
||||
}
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
// To export the class as lower-case
|
||||
const session = Session;
|
Загрузка…
Ссылка в новой задаче