Loading remote/shared/messagehandler/MessageHandler.jsm +22 −0 Original line number Diff line number Diff line Loading @@ -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. Loading remote/shared/messagehandler/MessageHandlerRegistry.jsm +39 −2 Original line number Diff line number Diff line Loading @@ -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", Loading Loading @@ -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 Loading @@ -75,6 +79,7 @@ class MessageHandlerRegistryClass { this._onMessageHandlerDestroyed = this._onMessageHandlerDestroyed.bind( this ); this._onMessageHandlerEvent = this._onMessageHandlerEvent.bind(this); } /** Loading Loading @@ -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}]`; } Loading Loading @@ -182,6 +211,7 @@ class MessageHandlerRegistryClass { "message-handler-destroyed", this._onMessageHandlerDestroyed ); messageHandler.on("message-handler-event", this._onMessageHandlerEvent); return messageHandler; } Loading @@ -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(); remote/shared/messagehandler/test/browser/broadcast/browser_with_frames.js +2 −46 Original line number Diff line number Diff line Loading @@ -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" Loading @@ -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 `<iframe src="${encodeURI(uri)}"></iframe>`; } remote/shared/messagehandler/test/browser/browser.ini +1 −0 Original line number Diff line number Diff line Loading @@ -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] remote/shared/messagehandler/test/browser/browser_events.js 0 → 100644 +248 −0 Original line number Diff line number Diff line /* 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, }, }); } Loading
remote/shared/messagehandler/MessageHandler.jsm +22 −0 Original line number Diff line number Diff line Loading @@ -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. Loading
remote/shared/messagehandler/MessageHandlerRegistry.jsm +39 −2 Original line number Diff line number Diff line Loading @@ -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", Loading Loading @@ -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 Loading @@ -75,6 +79,7 @@ class MessageHandlerRegistryClass { this._onMessageHandlerDestroyed = this._onMessageHandlerDestroyed.bind( this ); this._onMessageHandlerEvent = this._onMessageHandlerEvent.bind(this); } /** Loading Loading @@ -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}]`; } Loading Loading @@ -182,6 +211,7 @@ class MessageHandlerRegistryClass { "message-handler-destroyed", this._onMessageHandlerDestroyed ); messageHandler.on("message-handler-event", this._onMessageHandlerEvent); return messageHandler; } Loading @@ -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();
remote/shared/messagehandler/test/browser/broadcast/browser_with_frames.js +2 −46 Original line number Diff line number Diff line Loading @@ -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" Loading @@ -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 `<iframe src="${encodeURI(uri)}"></iframe>`; }
remote/shared/messagehandler/test/browser/browser.ini +1 −0 Original line number Diff line number Diff line Loading @@ -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]
remote/shared/messagehandler/test/browser/browser_events.js 0 → 100644 +248 −0 Original line number Diff line number Diff line /* 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, }, }); }