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