diff options
Diffstat (limited to 'devtools/server/actors/resources/server-sent-events.js')
-rw-r--r-- | devtools/server/actors/resources/server-sent-events.js | 135 |
1 files changed, 135 insertions, 0 deletions
diff --git a/devtools/server/actors/resources/server-sent-events.js b/devtools/server/actors/resources/server-sent-events.js new file mode 100644 index 0000000000..5b16f8bb9f --- /dev/null +++ b/devtools/server/actors/resources/server-sent-events.js @@ -0,0 +1,135 @@ +/* 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 { + LongStringActor, +} = require("resource://devtools/server/actors/string.js"); + +const { + TYPES: { SERVER_SENT_EVENT }, +} = require("resource://devtools/server/actors/resources/index.js"); + +const eventSourceEventService = Cc[ + "@mozilla.org/eventsourceevent/service;1" +].getService(Ci.nsIEventSourceEventService); + +class ServerSentEventWatcher { + constructor() { + this.windowIds = new Set(); + // Register for backend events. + this.onWindowReady = this.onWindowReady.bind(this); + this.onWindowDestroy = this.onWindowDestroy.bind(this); + } + /** + * Start watching for all server sent events related to a given Target Actor. + * + * @param TargetActor targetActor + * The target actor on which we should observe server sent events. + * @param Object options + * Dictionary object with following attributes: + * - onAvailable: mandatory function + * This will be called for each resource. + */ + watch(targetActor, { onAvailable }) { + this.onAvailable = onAvailable; + this.targetActor = targetActor; + + for (const window of this.targetActor.windows) { + const { innerWindowId } = window.windowGlobalChild; + this.startListening(innerWindowId); + } + + // Listen for subsequent top-level-document reloads/navigations, + // new iframe additions or current iframe reloads/navigation. + this.targetActor.on("window-ready", this.onWindowReady); + this.targetActor.on("window-destroyed", this.onWindowDestroy); + } + + static createResource(messageType, eventParams) { + return { + resourceType: SERVER_SENT_EVENT, + messageType, + ...eventParams, + }; + } + + static prepareFramePayload(targetActor, frame) { + const payload = new LongStringActor(targetActor.conn, frame); + targetActor.manage(payload); + return payload.form(); + } + + onWindowReady({ window }) { + const { innerWindowId } = window.windowGlobalChild; + this.startListening(innerWindowId); + } + + onWindowDestroy({ id }) { + this.stopListening(id); + } + + startListening(innerWindowId) { + if (!this.windowIds.has(innerWindowId)) { + this.windowIds.add(innerWindowId); + eventSourceEventService.addListener(innerWindowId, this); + } + } + + stopListening(innerWindowId) { + if (this.windowIds.has(innerWindowId)) { + this.windowIds.delete(innerWindowId); + // The listener might have already been cleaned up on `window-destroy`. + if (!eventSourceEventService.hasListenerFor(innerWindowId)) { + console.warn( + "Already stopped listening to server sent events for this window." + ); + return; + } + eventSourceEventService.removeListener(innerWindowId, this); + } + } + + destroy() { + // cleanup any other listeners not removed on `window-destroy` + for (const id of this.windowIds) { + this.stopListening(id); + } + this.targetActor.off("window-ready", this.onWindowReady); + this.targetActor.off("window-destroyed", this.onWindowDestroy); + } + + // nsIEventSourceEventService specific functions + eventSourceConnectionOpened(httpChannelId) {} + + eventSourceConnectionClosed(httpChannelId) { + const resource = ServerSentEventWatcher.createResource( + "eventSourceConnectionClosed", + { httpChannelId } + ); + this.onAvailable([resource]); + } + + eventReceived(httpChannelId, eventName, lastEventId, data, retry, timeStamp) { + const payload = ServerSentEventWatcher.prepareFramePayload( + this.targetActor, + data + ); + const resource = ServerSentEventWatcher.createResource("eventReceived", { + httpChannelId, + data: { + payload, + eventName, + lastEventId, + retry, + timeStamp, + }, + }); + + this.onAvailable([resource]); + } +} + +module.exports = ServerSentEventWatcher; |