/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; const { RootMessageHandlerRegistry } = ChromeUtils.importESModule( "chrome://remote/content/shared/messagehandler/RootMessageHandlerRegistry.sys.mjs" ); const { RootMessageHandler } = ChromeUtils.importESModule( "chrome://remote/content/shared/messagehandler/RootMessageHandler.sys.mjs" ); /** * 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, "https://example.com/document-builder.sjs?html=tab" ); await BrowserTestUtils.browserLoaded(tab.linkedBrowser); const browsingContext = tab.linkedBrowser.browsingContext; const rootMessageHandler = createRootMessageHandler("session-id-event"); let messageHandlerEvent; let registryEvent; // Events are emitted both as generic message-handler-event events as well // as under their own name. We expect to receive the event for both. const _onMessageHandlerEvent = (eventName, eventData) => { if (eventData.name === "event-from-window-global") { messageHandlerEvent = eventData; } }; rootMessageHandler.on("message-handler-event", _onMessageHandlerEvent); const onNamedEvent = rootMessageHandler.once("event-from-window-global"); // MessageHandlerRegistry should forward all the message-handler-events. const _onMessageHandlerRegistryEvent = (eventName, eventData) => { if (eventData.name === "event-from-window-global") { registryEvent = eventData; } }; RootMessageHandlerRegistry.on( "message-handler-registry-event", _onMessageHandlerRegistryEvent ); callTestEmitEvent(rootMessageHandler, browsingContext.id); const namedEvent = await onNamedEvent; is( namedEvent.text, `event from ${browsingContext.id}`, "Received the expected payload" ); is( messageHandlerEvent.name, "event-from-window-global", "Received event on the ROOT MessageHandler" ); is( messageHandlerEvent.data.text, `event from ${browsingContext.id}`, "Received the expected payload" ); is( registryEvent, messageHandlerEvent, "The event forwarded by the MessageHandlerRegistry is identical to the MessageHandler event" ); rootMessageHandler.off("message-handler-event", _onMessageHandlerEvent); RootMessageHandlerRegistry.off( "message-handler-registry-event", _onMessageHandlerRegistryEvent ); 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"); // events are emitted both as generic message-handler-event events as // well as under their own name. We expect to receive the event for both. const onHandlerEvent = rootMessageHandler.once("message-handler-event"); const onNamedEvent = rootMessageHandler.once("event-from-root"); rootMessageHandler.handleCommand({ moduleName: "event", commandName: "testEmitRootEvent", destination: { type: RootMessageHandler.type, }, }); const { name, data } = await onHandlerEvent; is(name, "event-from-root", "Received event on the ROOT MessageHandler"); is(data.text, "event from root", "Received the expected payload"); const namedEvent = await onNamedEvent; is(namedEvent.text, "event from root", "Received the expected payload"); 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, "https://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" ); // events are emitted both as generic message-handler-event events as // well as under their own name. We expect to receive the event for both. const onHandlerEvent = rootMessageHandler.once("message-handler-event"); const onNamedEvent = rootMessageHandler.once( "event-from-window-global-in-root" ); rootMessageHandler.handleCommand({ moduleName: "event", commandName: "testEmitWindowGlobalInRootEvent", destination: { type: WindowGlobalMessageHandler.type, id: browsingContext.id, }, }); const { name, data } = await onHandlerEvent; is( name, "event-from-window-global-in-root", "Received event on the ROOT MessageHandler" ); is( data.text, `windowglobal-in-root event for ${browsingContext.id}`, "Received the expected payload" ); const namedEvent = await onNamedEvent; is( namedEvent.text, `windowglobal-in-root event for ${browsingContext.id}`, "Received the expected payload" ); 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, "https://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.name === "event-from-window-global") { 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.name === "event-from-window-global") { root2Events++; } }; root2.on("message-handler-event", onRoot2Event); let registryEvents = 0; const onRegistryEvent = function (evtName, wrappedEvt) { if (wrappedEvt.name === "event-from-window-global") { registryEvents++; } }; RootMessageHandlerRegistry.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); RootMessageHandlerRegistry.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" ); const rootEvents = []; const onRootEvent = function (evtName, wrappedEvt) { if (wrappedEvt.name === "event-from-window-global") { rootEvents.push(wrappedEvt.data.text); } }; rootMessageHandler.on("message-handler-event", onRootEvent); const namedEvents = []; const onNamedEvent = (name, event) => namedEvents.push(event.text); rootMessageHandler.on("event-from-window-global", onNamedEvent); for (const context of contexts) { callTestEmitEvent(rootMessageHandler, context.id); info("Wait for root event to be received in both event arrays"); await TestUtils.waitForCondition(() => [namedEvents, rootEvents].every(events => events.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.off("event-from-window-global", onNamedEvent); rootMessageHandler.destroy(); }); function callTestEmitEvent(rootMessageHandler, browsingContextId) { rootMessageHandler.handleCommand({ moduleName: "event", commandName: "testEmitEvent", destination: { type: WindowGlobalMessageHandler.type, id: browsingContextId, }, }); }