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:
Henrik Skupin 2021-08-11 17:52:30 +00:00
Родитель 0ffd0582d4
Коммит 42f6a8c709
15 изменённых файлов: 208 добавлений и 54 удалений

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

@ -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;