diff --git a/remote/shared/messagehandler/MessageHandler.jsm b/remote/shared/messagehandler/MessageHandler.jsm index 4bf3560a09f2..ffcdd44689b0 100644 --- a/remote/shared/messagehandler/MessageHandler.jsm +++ b/remote/shared/messagehandler/MessageHandler.jsm @@ -88,6 +88,28 @@ class MessageHandler extends EventEmitter { this.emit("message-handler-destroyed", this); } + /** + * Emit a message-handler-event. Such events should bubble up to the root of + * a MessageHandler network. + * + * @param {String} method + * A string literal of the form [module name].[event name]. This is the + * event name. + * @param {Object} params + * The event parameters. + */ + emitMessageHandlerEvent(method, params) { + this.emit("message-handler-event", { + // TODO: The messageHandlerInfo needs to be wrapped in the event so + // that consumers can check the type/context. Once MessageHandlerRegistry + // becomes context-specific (Bug 1722659), only the sessionId will be + // required. + messageHandlerInfo: this._messageHandlerInfo, + method, + params, + }); + } + /** * @typedef {Object} CommandDestination * @property {String} type - One of MessageHandler.type. diff --git a/remote/shared/messagehandler/MessageHandlerRegistry.jsm b/remote/shared/messagehandler/MessageHandlerRegistry.jsm index 31a384d2b1cd..4d8de1a459da 100644 --- a/remote/shared/messagehandler/MessageHandlerRegistry.jsm +++ b/remote/shared/messagehandler/MessageHandlerRegistry.jsm @@ -11,6 +11,8 @@ const { XPCOMUtils } = ChromeUtils.import( ); XPCOMUtils.defineLazyModuleGetters(this, { + EventEmitter: "resource://gre/modules/EventEmitter.jsm", + Log: "chrome://remote/content/shared/Log.jsm", MessageHandlerInfo: "chrome://remote/content/shared/messagehandler/MessageHandlerInfo.jsm", @@ -61,8 +63,10 @@ function getMessageHandlerClass(type) { * * Note: this is still created as a class, but exposed as a singleton. */ -class MessageHandlerRegistryClass { +class MessageHandlerRegistryClass extends EventEmitter { constructor() { + super(); + /** * Map of all message handlers registered in this process. * Keys are based on session id, message handler type and message handler @@ -75,6 +79,7 @@ class MessageHandlerRegistryClass { this._onMessageHandlerDestroyed = this._onMessageHandlerDestroyed.bind( this ); + this._onMessageHandlerEvent = this._onMessageHandlerEvent.bind(this); } /** @@ -154,6 +159,30 @@ class MessageHandlerRegistryClass { return messageHandler; } + /** + * Retrieve an already registered RootMessageHandler instance matching the + * provided sessionId. + * + * @param {String} sessionId + * ID of the session the handler is used for. + * @return {RootMessageHandler} + * A RootMessageHandler instance. + * @throws {Error} + * If no root MessageHandler can be found for the provided session id. + */ + getRootMessageHandler(sessionId) { + const rootMessageHandler = this.getExistingMessageHandler( + sessionId, + RootMessageHandler.type + ); + if (!rootMessageHandler) { + throw new Error( + `Unable to find a root MessageHandler for session id ${sessionId}` + ); + } + return rootMessageHandler; + } + toString() { return `[object ${this.constructor.name}]`; } @@ -182,6 +211,7 @@ class MessageHandlerRegistryClass { "message-handler-destroyed", this._onMessageHandlerDestroyed ); + messageHandler.on("message-handler-event", this._onMessageHandlerEvent); return messageHandler; } @@ -198,13 +228,20 @@ class MessageHandlerRegistryClass { // Event handlers - _onMessageHandlerDestroyed(evt, messageHandler) { + _onMessageHandlerDestroyed(eventName, messageHandler) { messageHandler.off( "message-handler-destroyed", this._onMessageHandlerDestroyed ); + messageHandler.off("message-handler-event", this._onMessageHandlerEvent); this._unregisterMessageHandler(messageHandler); } + + _onMessageHandlerEvent(eventName, messageHandlerEvent) { + // The registry simply re-emits MessageHandler events so that consumers + // don't have to attach listeners to individual MessageHandler instances. + this.emit("message-handler-registry-event", messageHandlerEvent); + } } const MessageHandlerRegistry = new MessageHandlerRegistryClass(); diff --git a/remote/shared/messagehandler/test/browser/broadcast/browser_with_frames.js b/remote/shared/messagehandler/test/browser/broadcast/browser_with_frames.js index 72479004fa0d..841bb412735d 100644 --- a/remote/shared/messagehandler/test/browser/broadcast/browser_with_frames.js +++ b/remote/shared/messagehandler/test/browser/broadcast/browser_with_frames.js @@ -6,37 +6,10 @@ add_task(async function test_broadcasting_with_frames() { info("Navigate the initial tab to the test URL"); const tab = gBrowser.selectedTab; - - // Create a test page with 2 iframes: - // - one with a different eTLD+1 (example.com) - // - one with a nested iframe on a different eTLD+1 (example.net) - // - // Overall the document structure should look like: - // - // html (example.org) - // iframe (example.org) - // iframe (example.net) - // iframe(example.com) - // - // Which means we should have 4 browsing contexts in total. - - // Create the markup for an example.net frame nested in an example.com frame. - const NESTED_FRAME_MARKUP = createFrameForUri( - `http://example.org/document-builder.sjs?html=${createFrame("example.net")}` - ); - - // Combine the nested frame markup created above with an example.com frame. - const TEST_URI_MARKUP = `${NESTED_FRAME_MARKUP}${createFrame("example.com")}`; - - // Create the test page URI on example.org. - const TEST_URI = `http://example.org/document-builder.sjs?html=${encodeURI( - TEST_URI_MARKUP - )}`; - - await loadURL(tab.linkedBrowser, TEST_URI); + await loadURL(tab.linkedBrowser, createTestMarkupWithFrames()); const contexts = tab.linkedBrowser.browsingContext.getAllBrowsingContextsInSubtree(); - is(contexts.length, 4, "Test tab has 3 children contexts"); + is(contexts.length, 4, "Test tab has 3 children contexts (4 in total)"); const rootMessageHandler = createRootMessageHandler( "session-id-broadcasting_with_frames" @@ -63,20 +36,3 @@ add_task(async function test_broadcasting_with_frames() { rootMessageHandler.destroy(); }); - -/** - * Create inline markup for a simple iframe that can be used with - * document-builder.sjs. The iframe will be served under the provided domain. - * - * @param {String} domain - * A domain (eg "example.com"), compatible with build/pgo/server-locations.txt - */ -function createFrame(domain) { - return createFrameForUri( - `http://${domain}/document-builder.sjs?html=frame-${domain}` - ); -} - -function createFrameForUri(uri) { - return ``; -} diff --git a/remote/shared/messagehandler/test/browser/browser.ini b/remote/shared/messagehandler/test/browser/browser.ini index 6b1abc19df67..9423daa793cc 100644 --- a/remote/shared/messagehandler/test/browser/browser.ini +++ b/remote/shared/messagehandler/test/browser/browser.ini @@ -7,6 +7,7 @@ support-files = prefs = remote.messagehandler.modulecache.useBrowserTestRoot=true +[browser_events.js] [browser_handle_command_errors.js] [browser_handle_simple_command.js] [browser_registry.js] diff --git a/remote/shared/messagehandler/test/browser/browser_events.js b/remote/shared/messagehandler/test/browser/browser_events.js new file mode 100644 index 000000000000..7901dadc5298 --- /dev/null +++ b/remote/shared/messagehandler/test/browser/browser_events.js @@ -0,0 +1,248 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { MessageHandlerRegistry } = ChromeUtils.import( + "chrome://remote/content/shared/messagehandler/MessageHandlerRegistry.jsm" +); +const { RootMessageHandler } = ChromeUtils.import( + "chrome://remote/content/shared/messagehandler/RootMessageHandler.jsm" +); +const { WindowGlobalMessageHandler } = ChromeUtils.import( + "chrome://remote/content/shared/messagehandler/WindowGlobalMessageHandler.jsm" +); + +/** + * Emit an event from a WindowGlobal module triggered by a specific command. + * Check that the event is emitted on the RootMessageHandler as well as on + * the parent process MessageHandlerRegistry. + */ +add_task(async function test_event() { + const tab = BrowserTestUtils.addTab( + gBrowser, + "http://example.com/document-builder.sjs?html=tab" + ); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + const browsingContext = tab.linkedBrowser.browsingContext; + + const rootMessageHandler = createRootMessageHandler("session-id-event"); + + const onTestEvent = rootMessageHandler.once("message-handler-event"); + // MessageHandlerRegistry should forward all the message-handler-events. + const onRegistryEvent = MessageHandlerRegistry.once( + "message-handler-registry-event" + ); + + callTestEmitEvent(rootMessageHandler, browsingContext.id); + + const messageHandlerEvent = await onTestEvent; + is( + messageHandlerEvent.method, + "event.testEvent", + "Received event.testEvent on the ROOT MessageHandler" + ); + is( + messageHandlerEvent.params.text, + `event from ${browsingContext.id}`, + "Received the expected data in testEvent" + ); + const registryEvent = await onRegistryEvent; + is( + registryEvent, + messageHandlerEvent, + "The event forwarded by the MessageHandlerRegistry is identical to the MessageHandler event" + ); + + rootMessageHandler.destroy(); + gBrowser.removeTab(tab); +}); + +/** + * Emit an event from a Root module triggered by a specific command. + * Check that the event is emitted on the RootMessageHandler. + */ +add_task(async function test_root_event() { + const rootMessageHandler = createRootMessageHandler("session-id-root_event"); + + const onTestEvent = rootMessageHandler.once("message-handler-event"); + rootMessageHandler.handleCommand({ + moduleName: "event", + commandName: "testEmitRootEvent", + destination: { + type: RootMessageHandler.type, + }, + }); + + const { method, params } = await onTestEvent; + is( + method, + "event.testRootEvent", + "Received event.testRootEvent on the ROOT MessageHandler" + ); + is( + params.text, + "event from root", + "Received the expected payload in testRootEvent" + ); + + rootMessageHandler.destroy(); +}); + +/** + * Emit an event from a windowglobal-in-root module triggered by a specific command. + * Check that the event is emitted on the RootMessageHandler. + */ +add_task(async function test_windowglobal_in_root_event() { + const tab = BrowserTestUtils.addTab( + gBrowser, + "http://example.com/document-builder.sjs?html=tab" + ); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + const browsingContext = tab.linkedBrowser.browsingContext; + + const rootMessageHandler = createRootMessageHandler( + "session-id-windowglobal_in_root_event" + ); + + const onTestEvent = rootMessageHandler.once("message-handler-event"); + rootMessageHandler.handleCommand({ + moduleName: "event", + commandName: "testEmitWindowGlobalInRootEvent", + destination: { + type: WindowGlobalMessageHandler.type, + id: browsingContext.id, + }, + }); + + const { method, params } = await onTestEvent; + is( + method, + "event.testWindowGlobalInRootEvent", + "Received event.testWindowGlobalInRoot on the ROOT MessageHandler" + ); + is( + params.text, + `windowglobal-in-root event for ${browsingContext.id}`, + "Received the expected payload in testWindowGlobalInRoot" + ); + + rootMessageHandler.destroy(); + gBrowser.removeTab(tab); +}); + +/** + * Emit an event from a windowglobal module, but from 2 different sessions. + * Check that the event is emitted by the corresponding RootMessageHandler as + * well as by the parent process MessageHandlerRegistry. + */ +add_task(async function test_event_multisession() { + const tab = BrowserTestUtils.addTab( + gBrowser, + "http://example.com/document-builder.sjs?html=tab" + ); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + const browsingContextId = tab.linkedBrowser.browsingContext.id; + + const root1 = createRootMessageHandler("session-id-event_multisession-1"); + let root1Events = 0; + const onRoot1Event = function(evtName, wrappedEvt) { + if (wrappedEvt.method === "event.testEvent") { + root1Events++; + } + }; + root1.on("message-handler-event", onRoot1Event); + + const root2 = createRootMessageHandler("session-id-event_multisession-2"); + let root2Events = 0; + const onRoot2Event = function(evtName, wrappedEvt) { + if (wrappedEvt.method === "event.testEvent") { + root2Events++; + } + }; + root2.on("message-handler-event", onRoot2Event); + + let registryEvents = 0; + const onRegistryEvent = function(evtName, wrappedEvt) { + if (wrappedEvt.method === "event.testEvent") { + registryEvents++; + } + }; + MessageHandlerRegistry.on("message-handler-registry-event", onRegistryEvent); + + callTestEmitEvent(root1, browsingContextId); + callTestEmitEvent(root2, browsingContextId); + + info("Wait for root1 event to be received"); + await TestUtils.waitForCondition(() => root1Events === 1); + info("Wait for root2 event to be received"); + await TestUtils.waitForCondition(() => root2Events === 1); + + await TestUtils.waitForTick(); + is(root1Events, 1, "Session 1 only received 1 event"); + is(root2Events, 1, "Session 2 only received 1 event"); + is( + registryEvents, + 2, + "MessageHandlerRegistry forwarded events from both sessions" + ); + + root1.off("message-handler-event", onRoot1Event); + root2.off("message-handler-event", onRoot2Event); + MessageHandlerRegistry.off("message-handler-registry-event", onRegistryEvent); + root1.destroy(); + root2.destroy(); + gBrowser.removeTab(tab); +}); + +/** + * Test that events can be emitted from individual frame contexts and that + * events going through a shared content process MessageHandlerRegistry are not + * duplicated. + */ +add_task(async function test_event_with_frames() { + info("Navigate the initial tab to the test URL"); + const tab = gBrowser.selectedTab; + await loadURL(tab.linkedBrowser, createTestMarkupWithFrames()); + + const contexts = tab.linkedBrowser.browsingContext.getAllBrowsingContextsInSubtree(); + is(contexts.length, 4, "Test tab has 3 children contexts (4 in total)"); + + const rootMessageHandler = createRootMessageHandler( + "session-id-event_with_frames" + ); + + let rootEvents = []; + const onRootEvent = function(evtName, wrappedEvt) { + if (wrappedEvt.method === "event.testEvent") { + rootEvents.push(wrappedEvt.params.text); + } + }; + rootMessageHandler.on("message-handler-event", onRootEvent); + + for (const context of contexts) { + callTestEmitEvent(rootMessageHandler, context.id); + info("Wait for root event to be received"); + await TestUtils.waitForCondition(() => + rootEvents.includes(`event from ${context.id}`) + ); + } + + info("Wait for a bit and check that we did not receive duplicated events"); + await TestUtils.waitForTick(); + is(rootEvents.length, 4, "Only received 4 events"); + + rootMessageHandler.off("message-handler-event", onRootEvent); + rootMessageHandler.destroy(); +}); + +function callTestEmitEvent(rootMessageHandler, browsingContextId) { + rootMessageHandler.handleCommand({ + moduleName: "event", + commandName: "testEmitEvent", + destination: { + type: WindowGlobalMessageHandler.type, + id: browsingContextId, + }, + }); +} diff --git a/remote/shared/messagehandler/test/browser/head.js b/remote/shared/messagehandler/test/browser/head.js index 0e6c3469c25e..0da8fb430fe9 100644 --- a/remote/shared/messagehandler/test/browser/head.js +++ b/remote/shared/messagehandler/test/browser/head.js @@ -46,3 +46,47 @@ async function addTab(url) { }); return tab; } + +/** + * Create inline markup for a simple iframe that can be used with + * document-builder.sjs. The iframe will be served under the provided domain. + * + * @param {String} domain + * A domain (eg "example.com"), compatible with build/pgo/server-locations.txt + */ +function createFrame(domain) { + return createFrameForUri( + `http://${domain}/document-builder.sjs?html=frame-${domain}` + ); +} + +function createFrameForUri(uri) { + return ``; +} + +// Create a test page with 2 iframes: +// - one with a different eTLD+1 (example.com) +// - one with a nested iframe on a different eTLD+1 (example.net) +// +// Overall the document structure should look like: +// +// html (example.org) +// iframe (example.org) +// iframe (example.net) +// iframe(example.com) +// +// Which means we should have 4 browsing contexts in total. +function createTestMarkupWithFrames() { + // Create the markup for an example.net frame nested in an example.com frame. + const NESTED_FRAME_MARKUP = createFrameForUri( + `http://example.org/document-builder.sjs?html=${createFrame("example.net")}` + ); + + // Combine the nested frame markup created above with an example.com frame. + const TEST_URI_MARKUP = `${NESTED_FRAME_MARKUP}${createFrame("example.com")}`; + + // Create the test page URI on example.org. + return `http://example.org/document-builder.sjs?html=${encodeURI( + TEST_URI_MARKUP + )}`; +} diff --git a/remote/shared/messagehandler/test/browser/resources/modules/root/event.jsm b/remote/shared/messagehandler/test/browser/resources/modules/root/event.jsm new file mode 100644 index 000000000000..8cb13790ea59 --- /dev/null +++ b/remote/shared/messagehandler/test/browser/resources/modules/root/event.jsm @@ -0,0 +1,27 @@ +/* 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 = ["event"]; + +class Event { + constructor(messageHandler) { + this.messageHandler = messageHandler; + } + + destroy() {} + + /** + * Commands + */ + + testEmitRootEvent() { + this.messageHandler.emitMessageHandlerEvent("event.testRootEvent", { + text: "event from root", + }); + } +} + +const event = Event; 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 new file mode 100644 index 000000000000..5bc4fb6e346a --- /dev/null +++ b/remote/shared/messagehandler/test/browser/resources/modules/windowglobal-in-root/event.jsm @@ -0,0 +1,28 @@ +/* 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 = ["event"]; + +class Event { + constructor(messageHandler) { + this.messageHandler = messageHandler; + } + + destroy() {} + + /** + * Commands + */ + + testEmitWindowGlobalInRootEvent(params, destination) { + this.messageHandler.emitMessageHandlerEvent( + "event.testWindowGlobalInRootEvent", + { text: `windowglobal-in-root event for ${destination.id}` } + ); + } +} + +const event = Event; diff --git a/remote/shared/messagehandler/test/browser/resources/modules/windowglobal/event.jsm b/remote/shared/messagehandler/test/browser/resources/modules/windowglobal/event.jsm new file mode 100644 index 000000000000..9acad9afc35e --- /dev/null +++ b/remote/shared/messagehandler/test/browser/resources/modules/windowglobal/event.jsm @@ -0,0 +1,28 @@ +/* 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 = ["event"]; + +class Event { + constructor(messageHandler) { + this.messageHandler = messageHandler; + } + + destroy() {} + + /** + * Commands + */ + + testEmitEvent() { + // Emit a payload including the contextId to check which context emitted + // a specific event. + const text = `event from ${this.messageHandler.contextId}`; + this.messageHandler.emitMessageHandlerEvent("event.testEvent", { text }); + } +} + +const event = Event; diff --git a/remote/shared/messagehandler/transports/js-window-actors/MessageHandlerFrameChild.jsm b/remote/shared/messagehandler/transports/js-window-actors/MessageHandlerFrameChild.jsm index 5cdf55bdff17..0b746ac67e4f 100644 --- a/remote/shared/messagehandler/transports/js-window-actors/MessageHandlerFrameChild.jsm +++ b/remote/shared/messagehandler/transports/js-window-actors/MessageHandlerFrameChild.jsm @@ -26,6 +26,17 @@ class MessageHandlerFrameChild extends JSWindowActorChild { actorCreated() { this.type = WindowGlobalMessageHandler.type; this.context = this.manager.browsingContext; + + this._onRegistryEvent = this._onRegistryEvent.bind(this); + + // MessageHandlerFrameChild is responsible for forwarding events from + // WindowGlobalMessageHandler to the parent process. + // Such events are re-emitted on the MessageHandlerRegistry to avoid + // setting up listeners on individual MessageHandler instances. + MessageHandlerRegistry.on( + "message-handler-registry-event", + this._onRegistryEvent + ); } receiveMessage(message) { @@ -53,7 +64,31 @@ class MessageHandlerFrameChild extends JSWindowActorChild { ); } + _onRegistryEvent(eventName, wrappedEvent) { + const { messageHandlerInfo, method, params } = wrappedEvent; + const { contextId, sessionId, type } = messageHandlerInfo; + + // TODO: With a single MessageHandlerRegistry per process, we might receive + // events intended for other contexts. Consequently we have to filter out + // unrelevant events. Once Registry becomes context-specific (Bug 1722659) + // this filtering should be removed. + if ( + type === this.type && + contextId === WindowGlobalMessageHandler.getIdFromContext(this.context) + ) { + this.sendAsyncMessage("MessageHandlerFrameChild:messageHandlerEvent", { + method, + params, + sessionId, + }); + } + } + didDestroy() { MessageHandlerRegistry.contextDestroyed(this.context, this.type); + MessageHandlerRegistry.off( + "message-handler-registry-event", + this._onRegistryEvent + ); } } diff --git a/remote/shared/messagehandler/transports/js-window-actors/MessageHandlerFrameParent.jsm b/remote/shared/messagehandler/transports/js-window-actors/MessageHandlerFrameParent.jsm index a96e80121c4e..36e8e87db397 100644 --- a/remote/shared/messagehandler/transports/js-window-actors/MessageHandlerFrameParent.jsm +++ b/remote/shared/messagehandler/transports/js-window-actors/MessageHandlerFrameParent.jsm @@ -6,12 +6,39 @@ var EXPORTED_SYMBOLS = ["MessageHandlerFrameParent"]; +const { XPCOMUtils } = ChromeUtils.import( + "resource://gre/modules/XPCOMUtils.jsm" +); + +XPCOMUtils.defineLazyModuleGetters(this, { + MessageHandlerRegistry: + "chrome://remote/content/shared/messagehandler/MessageHandlerRegistry.jsm", +}); + /** * Parent actor for the MessageHandlerFrame JSWindowActor. The * MessageHandlerFrame actor is used by FrameTransport to communicate between * ROOT MessageHandlers and WINDOW_GLOBAL MessageHandlers. */ class MessageHandlerFrameParent extends JSWindowActorParent { + receiveMessage(message) { + switch (message.name) { + case "MessageHandlerFrameChild:messageHandlerEvent": + const { method, params, sessionId } = message.data; + + const messageHandler = MessageHandlerRegistry.getRootMessageHandler( + sessionId + ); + + // Re-emit the event on the RootMessageHandler. + messageHandler.emitMessageHandlerEvent(method, params); + + break; + default: + throw new Error("Unsupported message:" + message.name); + } + } + /** * Send a command to the corresponding MessageHandlerFrameChild actor via a * JSWindowActor query. diff --git a/remote/shared/webdriver/Session.jsm b/remote/shared/webdriver/Session.jsm index b7c39d6579d8..d91be26cf9d0 100644 --- a/remote/shared/webdriver/Session.jsm +++ b/remote/shared/webdriver/Session.jsm @@ -211,7 +211,13 @@ class WebDriverSession { this._connections.clear(); // Destroy the dedicated MessageHandler instance if we created one. - this._messageHandler?.destroy(); + if (this._messageHandler) { + this._messageHandler.off( + "message-handler-event", + this._onMessageHandlerEvent + ); + this._messageHandler.destroy(); + } } execute(module, command, params) { @@ -242,6 +248,11 @@ class WebDriverSession { this.id, RootMessageHandler.type ); + this._onMessageHandlerEvent = this._onMessageHandlerEvent.bind(this); + this._messageHandler.on( + "message-handler-event", + this._onMessageHandlerEvent + ); } return this._messageHandler; @@ -296,6 +307,13 @@ class WebDriverSession { this._connections.add(conn); } + _onMessageHandlerEvent(eventName, messageHandlerEvent) { + const { method, params } = messageHandlerEvent; + this._connections.forEach(connection => + connection.sendEvent(method, params) + ); + } + // XPCOM get QueryInterface() { diff --git a/remote/webdriver-bidi/WebDriverBiDiConnection.jsm b/remote/webdriver-bidi/WebDriverBiDiConnection.jsm index 3a3670f9f8f9..9bd9f9f04231 100644 --- a/remote/webdriver-bidi/WebDriverBiDiConnection.jsm +++ b/remote/webdriver-bidi/WebDriverBiDiConnection.jsm @@ -93,7 +93,9 @@ class WebDriverBiDiConnection extends WebSocketConnection { * @param {Object} params * A JSON-serializable object, which is the payload of this event. */ - sendEvent(method, params) {} + sendEvent(method, params) { + this.send({ method, params }); + } /** * Send the result of a call to a module's method back to the