Bug 1636420 - Connect SSE actor to the Message panel. r=Honza,bomsy
authorFarooq AR <farooqbckk@gmail.com>
Wed, 20 May 2020 06:02:42 +0000
changeset 530957 9466e4962dba4c8f2fde8873c1853205a31f6962
parent 530956 0cb049134f60c2e794f0e31d5c1971a0aa766375
child 530958 ec7aebcb24cb7479ed9dddfa7f52abf38091660c
push id37435
push userapavel@mozilla.com
push dateWed, 20 May 2020 15:28:23 +0000
treeherdermozilla-central@5415da14ec9a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersHonza, bomsy
bugs1636420
milestone78.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1636420 - Connect SSE actor to the Message panel. r=Honza,bomsy Differential Revision: https://phabricator.services.mozilla.com/D75792
browser/app/profile/firefox.js
devtools/client/fronts/eventsource.js
devtools/client/fronts/moz.build
devtools/client/netmonitor/src/connector/firefox-connector.js
devtools/client/netmonitor/src/connector/firefox-data-provider.js
devtools/shared/specs/index.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -2142,16 +2142,19 @@ pref("devtools.netmonitor.har.jsonpCallb
 pref("devtools.netmonitor.har.includeResponseBodies", true);
 pref("devtools.netmonitor.har.compress", false);
 pref("devtools.netmonitor.har.forceExport", false);
 pref("devtools.netmonitor.har.pageLoadedTimeout", 1500);
 pref("devtools.netmonitor.har.enableAutoExportToFile", false);
 
 pref("devtools.netmonitor.features.webSockets", true);
 
+// Disable the EventSource Inspector.
+pref("devtools.netmonitor.features.serverSentEvents", false);
+
 // Enable the Storage Inspector
 pref("devtools.storage.enabled", true);
 
 // Enable the Style Editor.
 pref("devtools.styleeditor.enabled", true);
 pref("devtools.styleeditor.autocompletion-enabled", true);
 pref("devtools.styleeditor.showMediaSidebar", true);
 pref("devtools.styleeditor.mediaSidebarWidth", 238);
new file mode 100644
--- /dev/null
+++ b/devtools/client/fronts/eventsource.js
@@ -0,0 +1,93 @@
+/* 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 {
+  FrontClassWithSpec,
+  registerFront,
+} = require("devtools/shared/protocol");
+const { eventSourceSpec } = require("devtools/shared/specs/eventsource");
+
+/**
+ * A EventSourceFront is used as a front end for the EventSourceActor that is
+ * created on the server, hiding implementation details.
+ */
+class EventSourceFront extends FrontClassWithSpec(eventSourceSpec) {
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
+
+    this._onEventSourceConnectionOpened = this._onEventSourceConnectionOpened.bind(
+      this
+    );
+    this._onEventSourceConnectionClosed = this._onEventSourceConnectionClosed.bind(
+      this
+    );
+    this._onEventReceived = this._onEventReceived.bind(this);
+
+    // Attribute name from which to retrieve the actorID
+    // out of the target actor's form
+    this.formAttributeName = "eventSourceActor";
+
+    this.on(
+      "serverEventSourceConnectionOpened",
+      this._onEventSourceConnectionOpened
+    );
+    this.on(
+      "serverEventSourceConnectionClosed",
+      this._onEventSourceConnectionClosed
+    );
+    this.on("serverEventReceived", this._onEventReceived);
+  }
+
+  /**
+   * Close the EventSourceFront.
+   */
+  destroy() {
+    this.off("serverEventSourceConnectionOpened");
+    this.off("serverEventSourceConnectionClosed");
+    this.off("serverEventReceived");
+    return super.destroy();
+  }
+
+  /**
+   * The "eventSourceConnectionOpened" message type handler. We redirect any
+   * message to the UI for displaying.
+   *
+   * @private
+   * @param number httpChannelId
+   *        Channel ID of the eventsource connection.
+   */
+  async _onEventSourceConnectionOpened(httpChannelId) {
+    this.emit("eventSourceConnectionOpened", httpChannelId);
+  }
+
+  /**
+   * The "eventSourceConnectionClosed" message type handler. We redirect any message to
+   * the UI for displaying.
+   *
+   * @private
+   * @param number httpChannelId
+   */
+  async _onEventSourceConnectionClosed(httpChannelId) {
+    this.emit("eventSourceConnectionClosed", httpChannelId);
+  }
+
+  /**
+   * The "eventReceived" message type handler. We redirect any message to
+   * the UI for displaying.
+   *
+   * @private
+   * @param string httpChannelId
+   *        Channel ID of the eventSource connection.
+   * @param object data
+   *        The data received from the server.
+   */
+  async _onEventReceived(httpChannelId, data) {
+    this.emit("eventReceived", httpChannelId, data);
+  }
+}
+
+exports.EventSourceFront = EventSourceFront;
+registerFront(EventSourceFront);
--- a/devtools/client/fronts/moz.build
+++ b/devtools/client/fronts/moz.build
@@ -16,16 +16,17 @@ DevToolsModules(
     'accessibility.js',
     'animation.js',
     'array-buffer.js',
     'changes.js',
     'content-viewer.js',
     'css-properties.js',
     'device.js',
     'environment.js',
+    'eventsource.js',
     'frame.js',
     'framerate.js',
     'highlighters.js',
     'inspector.js',
     'layout.js',
     'manifest.js',
     'memory.js',
     'node.js',
--- a/devtools/client/netmonitor/src/connector/firefox-connector.js
+++ b/devtools/client/netmonitor/src/connector/firefox-connector.js
@@ -182,16 +182,36 @@ class FirefoxConnector {
           this.dataProvider.onWebSocketClosed
         );
         webSocketFront.on("frameReceived", this.dataProvider.onFrameReceived);
         webSocketFront.on("frameSent", this.dataProvider.onFrameSent);
       } catch (e) {
         // Support for FF68 or older
       }
     }
+
+    // Support for EventSource monitoring is currently hidden behind this pref.
+    if (
+      Services.prefs.getBoolPref(
+        "devtools.netmonitor.features.serverSentEvents"
+      )
+    ) {
+      const eventSourceFront = await this.currentTarget.getFront("eventSource");
+      eventSourceFront.startListening();
+
+      eventSourceFront.on(
+        "eventSourceConnectionOpened",
+        this.dataProvider.onEventSourceConnectionOpened
+      );
+      eventSourceFront.on(
+        "eventSourceConnectionClosed",
+        this.dataProvider.onEventSourceConnectionClosed
+      );
+      eventSourceFront.on("eventReceived", this.dataProvider.onEventReceived);
+    }
   }
 
   removeListeners() {
     const webSocketFront = this.currentTarget.getCachedFront("webSocket");
     if (webSocketFront) {
       webSocketFront.off(
         "webSocketOpened",
         this.dataProvider.onWebSocketOpened
@@ -209,16 +229,29 @@ class FirefoxConnector {
         "networkEvent",
         this.dataProvider.onNetworkEvent
       );
       this.webConsoleFront.off(
         "networkEventUpdate",
         this.dataProvider.onNetworkEventUpdate
       );
     }
+
+    const eventSourceFront = this.currentTarget.getCachedFront("eventSource");
+    if (eventSourceFront) {
+      eventSourceFront.off(
+        "eventSourceConnectionOpened",
+        this.dataProvider.onEventSourceConnectionOpened
+      );
+      eventSourceFront.off(
+        "eventSourceConnectionClosed",
+        this.dataProvider.onEventSourceConnectionClosed
+      );
+      eventSourceFront.off("eventReceived", this.dataProvider.onEventReceived);
+    }
   }
 
   enableActions(enable) {
     this.dataProvider.enableActions(enable);
   }
 
   willNavigate() {
     if (this.actions) {
--- a/devtools/client/netmonitor/src/connector/firefox-data-provider.js
+++ b/devtools/client/netmonitor/src/connector/firefox-data-provider.js
@@ -50,16 +50,24 @@ class FirefoxDataProvider {
     // Event handlers
     this.onNetworkEvent = this.onNetworkEvent.bind(this);
     this.onNetworkEventUpdate = this.onNetworkEventUpdate.bind(this);
 
     this.onWebSocketOpened = this.onWebSocketOpened.bind(this);
     this.onWebSocketClosed = this.onWebSocketClosed.bind(this);
     this.onFrameSent = this.onFrameSent.bind(this);
     this.onFrameReceived = this.onFrameReceived.bind(this);
+
+    this.onEventSourceConnectionOpened = this.onEventSourceConnectionOpened.bind(
+      this
+    );
+    this.onEventSourceConnectionClosed = this.onEventSourceConnectionClosed.bind(
+      this
+    );
+    this.onEventReceived = this.onEventReceived.bind(this);
   }
 
   /**
    * Enable/disable firing redux actions (enabled by default).
    *
    * @param {boolean} enable Set to true to fire actions.
    */
   enableActions(enable) {
@@ -784,16 +792,40 @@ class FirefoxDataProvider {
     const payload = await this.updateRequest(response.from, {
       stacktrace: response.stacktrace,
     });
     this.emitForTests(TEST_EVENTS.RECEIVED_EVENT_STACKTRACE, response.from);
     return payload.stacktrace;
   }
 
   /**
+   * Handle EventSource events.
+   */
+  async onEventSourceConnectionOpened(httpChannelId) {
+    // By default, an EventSource connection doesn't immediately get its mimeType, or
+    // any info which could help us identify a connection is an SSE channel.
+    // We can update the request's mimeType through this event.
+    if (this.actionsEnabled && this.actions.updateMimeType) {
+      // TODO: Implement action updateMimeType.
+      await this.actions.updateMimeType(
+        httpChannelId,
+        "text/event-stream",
+        true
+      );
+    }
+  }
+
+  async onEventSourceConnectionClosed(httpChannelId) {}
+
+  async onEventReceived(httpChannelId, data) {
+    // Dispatch the same action used by websocket inspector.
+    this.addFrame(httpChannelId, data);
+  }
+
+  /**
    * Fire events for the owner object.
    */
   emit(type, data) {
     if (this.owner) {
       this.owner.emit(type, data);
     }
   }
 
--- a/devtools/shared/specs/index.js
+++ b/devtools/shared/specs/index.js
@@ -83,16 +83,21 @@ const Types = (exports.__TypesForTests =
     front: "devtools/client/fronts/device",
   },
   {
     types: ["environment"],
     spec: "devtools/shared/specs/environment",
     front: "devtools/client/fronts/environment",
   },
   {
+    types: ["eventSource"],
+    spec: "devtools/shared/specs/eventsource",
+    front: "devtools/client/fronts/eventsource",
+  },
+  {
     types: ["frame"],
     spec: "devtools/shared/specs/frame",
     front: "devtools/client/fronts/frame",
   },
   {
     types: ["framerate"],
     spec: "devtools/shared/specs/framerate",
     front: "devtools/client/fronts/framerate",