summaryrefslogtreecommitdiffstats
path: root/devtools/server/actors/resources/server-sent-events.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/actors/resources/server-sent-events.js')
-rw-r--r--devtools/server/actors/resources/server-sent-events.js135
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;