From 42f6a8c7093c294f012e51da039d526d8702341d Mon Sep 17 00:00:00 2001 From: Henrik Skupin Date: Wed, 11 Aug 2021 17:52:30 +0000 Subject: [PATCH] Bug 1694144 - [WebDriver BiDi] Add basic support for session.subscribe command. r=webdriver-reviewers,jdescottes Differential Revision: https://phabricator.services.mozilla.com/D122018 --- remote/jar.mn | 1 + .../shared/messagehandler/MessageHandler.jsm | 35 +++--- remote/shared/messagehandler/Module.jsm | 32 +++++ .../browser/browser_handle_simple_command.js | 5 +- .../resources/modules/root/command.jsm | 8 +- .../browser/resources/modules/root/event.jsm | 8 +- .../modules/windowglobal-in-root/command.jsm | 8 +- .../modules/windowglobal-in-root/event.jsm | 8 +- .../modules/windowglobal/command.jsm | 12 +- .../windowglobal/commandwindowglobalonly.jsm | 8 +- .../resources/modules/windowglobal/event.jsm | 8 +- remote/shared/webdriver/Session.jsm | 3 +- .../WebDriverBiDiConnection.jsm | 12 +- remote/webdriver-bidi/jar.mn | 3 + .../webdriver-bidi/modules/root/session.jsm | 111 ++++++++++++++++++ 15 files changed, 208 insertions(+), 54 deletions(-) create mode 100644 remote/shared/messagehandler/Module.jsm create mode 100644 remote/webdriver-bidi/modules/root/session.jsm diff --git a/remote/jar.mn b/remote/jar.mn index acd9c40daa12..05d7535aac6f 100644 --- a/remote/jar.mn +++ b/remote/jar.mn @@ -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) diff --git a/remote/shared/messagehandler/MessageHandler.jsm b/remote/shared/messagehandler/MessageHandler.jsm index ffcdd44689b0..addd58197e5f 100644 --- a/remote/shared/messagehandler/MessageHandler.jsm +++ b/remote/shared/messagehandler/MessageHandler.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"; } /** diff --git a/remote/shared/messagehandler/Module.jsm b/remote/shared/messagehandler/Module.jsm new file mode 100644 index 000000000000..c420bada3b81 --- /dev/null +++ b/remote/shared/messagehandler/Module.jsm @@ -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; + } +} diff --git a/remote/shared/messagehandler/test/browser/browser_handle_simple_command.js b/remote/shared/messagehandler/test/browser/browser_handle_simple_command.js index 9aa04924dc5b..0bd0af751345 100644 --- a/remote/shared/messagehandler/test/browser/browser_handle_simple_command.js +++ b/remote/shared/messagehandler/test/browser/browser_handle_simple_command.js @@ -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, diff --git a/remote/shared/messagehandler/test/browser/resources/modules/root/command.jsm b/remote/shared/messagehandler/test/browser/resources/modules/root/command.jsm index 1ce9082f823f..d7b0ba463821 100644 --- a/remote/shared/messagehandler/test/browser/resources/modules/root/command.jsm +++ b/remote/shared/messagehandler/test/browser/resources/modules/root/command.jsm @@ -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() {} /** diff --git a/remote/shared/messagehandler/test/browser/resources/modules/root/event.jsm b/remote/shared/messagehandler/test/browser/resources/modules/root/event.jsm index 8cb13790ea59..f1be715b77c3 100644 --- a/remote/shared/messagehandler/test/browser/resources/modules/root/event.jsm +++ b/remote/shared/messagehandler/test/browser/resources/modules/root/event.jsm @@ -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() {} /** diff --git a/remote/shared/messagehandler/test/browser/resources/modules/windowglobal-in-root/command.jsm b/remote/shared/messagehandler/test/browser/resources/modules/windowglobal-in-root/command.jsm index 4f9e1cc2923f..12d8922935aa 100644 --- a/remote/shared/messagehandler/test/browser/resources/modules/windowglobal-in-root/command.jsm +++ b/remote/shared/messagehandler/test/browser/resources/modules/windowglobal-in-root/command.jsm @@ -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() {} /** diff --git a/remote/shared/messagehandler/test/browser/resources/modules/windowglobal-in-root/event.jsm b/remote/shared/messagehandler/test/browser/resources/modules/windowglobal-in-root/event.jsm index 5bc4fb6e346a..7b1ed1413311 100644 --- a/remote/shared/messagehandler/test/browser/resources/modules/windowglobal-in-root/event.jsm +++ b/remote/shared/messagehandler/test/browser/resources/modules/windowglobal-in-root/event.jsm @@ -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() {} /** diff --git a/remote/shared/messagehandler/test/browser/resources/modules/windowglobal/command.jsm b/remote/shared/messagehandler/test/browser/resources/modules/windowglobal/command.jsm index 9732aaa57f26..e3bb87d2865e 100644 --- a/remote/shared/messagehandler/test/browser/resources/modules/windowglobal/command.jsm +++ b/remote/shared/messagehandler/test/browser/resources/modules/windowglobal/command.jsm @@ -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() { diff --git a/remote/shared/messagehandler/test/browser/resources/modules/windowglobal/commandwindowglobalonly.jsm b/remote/shared/messagehandler/test/browser/resources/modules/windowglobal/commandwindowglobalonly.jsm index a5430edfb9f3..22550002b736 100644 --- a/remote/shared/messagehandler/test/browser/resources/modules/windowglobal/commandwindowglobalonly.jsm +++ b/remote/shared/messagehandler/test/browser/resources/modules/windowglobal/commandwindowglobalonly.jsm @@ -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() {} /** diff --git a/remote/shared/messagehandler/test/browser/resources/modules/windowglobal/event.jsm b/remote/shared/messagehandler/test/browser/resources/modules/windowglobal/event.jsm index 9acad9afc35e..f6697750c4a1 100644 --- a/remote/shared/messagehandler/test/browser/resources/modules/windowglobal/event.jsm +++ b/remote/shared/messagehandler/test/browser/resources/modules/windowglobal/event.jsm @@ -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() {} /** diff --git a/remote/shared/webdriver/Session.jsm b/remote/shared/webdriver/Session.jsm index d91be26cf9d0..2fb0bb1732e3 100644 --- a/remote/shared/webdriver/Session.jsm +++ b/remote/shared/webdriver/Session.jsm @@ -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, }); } diff --git a/remote/webdriver-bidi/WebDriverBiDiConnection.jsm b/remote/webdriver-bidi/WebDriverBiDiConnection.jsm index 9bd9f9f04231..c24a8af5f492 100644 --- a/remote/webdriver-bidi/WebDriverBiDiConnection.jsm +++ b/remote/webdriver-bidi/WebDriverBiDiConnection.jsm @@ -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); diff --git a/remote/webdriver-bidi/jar.mn b/remote/webdriver-bidi/jar.mn index 819f4b0074b0..ff8a621f4e22 100644 --- a/remote/webdriver-bidi/jar.mn +++ b/remote/webdriver-bidi/jar.mn @@ -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) diff --git a/remote/webdriver-bidi/modules/root/session.jsm b/remote/webdriver-bidi/modules/root/session.jsm new file mode 100644 index 000000000000..5a84feb49d6f --- /dev/null +++ b/remote/webdriver-bidi/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} events + * List of events to subscribe to. + * @params {Array=} contexts + * Optional list of top-level browsing context ids + * to subscribe the events for. + * + * @throws {InvalidArgumentError} + * If events or contexts 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} + * A Set with the expanded events in the form of `.`. + * + * @throws {InvalidArgumentError} + * If event 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;