Bug 1449162 - Refactor the NetworkEventActor to use protocol.js. r=jryans draft
authorAlexandre Poirot <poirot.alex@gmail.com>
Tue, 27 Mar 2018 06:33:11 -0700
changeset 800604 6809cc23b7a4398c5c35e6af027e7bc118e07724
parent 800491 a466172aed4bc2afc21169b749b8068a4b98c93f
child 800605 cb48b9869f94a634de30085537c1baf6bfe7d576
push id111423
push userbmo:poirot.alex@gmail.com
push dateMon, 28 May 2018 16:48:32 +0000
reviewersjryans
bugs1449162
milestone62.0a1
Bug 1449162 - Refactor the NetworkEventActor to use protocol.js. r=jryans MozReview-Commit-ID: 7HW8sO9dMuD
devtools/client/netmonitor/src/connector/firefox-data-provider.js
devtools/server/actors/network-event.js
devtools/server/actors/webconsole.js
devtools/shared/specs/index.js
devtools/shared/specs/moz.build
devtools/shared/specs/network-event.js
--- a/devtools/client/netmonitor/src/connector/firefox-data-provider.js
+++ b/devtools/client/netmonitor/src/connector/firefox-data-provider.js
@@ -488,17 +488,19 @@ class FirefoxDataProvider {
 
     let response = await new Promise((resolve, reject) => {
       // Do a RDP request to fetch data from the actor.
       if (typeof this.webConsoleClient[clientMethodName] === "function") {
         // Make sure we fetch the real actor data instead of cloned actor
         // e.g. CustomRequestPanel will clone a request with additional '-clone' actor id
         this.webConsoleClient[clientMethodName](actor.replace("-clone", ""), (res) => {
           if (res.error) {
-            reject(new Error(res.message));
+            reject(
+              new Error(`Error while calling method ${clientMethodName}: ${res.message}`)
+            );
           }
           resolve(res);
         });
       } else {
         reject(new Error(`Error: No such client method '${clientMethodName}'!`));
       }
     });
 
--- a/devtools/server/actors/network-event.js
+++ b/devtools/server/actors/network-event.js
@@ -1,61 +1,67 @@
 /* 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 protocol = require("devtools/shared/protocol");
+const { networkEventSpec } = require("devtools/shared/specs/network-event");
+
 /**
  * Creates an actor for a network event.
  *
  * @constructor
  * @param object webConsoleActor
  *        The parent WebConsoleActor instance for this object.
  */
-function NetworkEventActor(webConsoleActor) {
-  this.parent = webConsoleActor;
-  this.conn = this.parent.conn;
+const NetworkEventActor = protocol.ActorClassWithSpec(networkEventSpec, {
+  initialize(webConsoleActor) {
+    // Necessary to get the events to work
+    protocol.Actor.prototype.initialize.call(this, webConsoleActor.conn);
 
-  this._request = {
-    method: null,
-    url: null,
-    httpVersion: null,
-    headers: [],
-    cookies: [],
-    headersSize: null,
-    postData: {},
-  };
+    this.webConsoleActor = webConsoleActor;
+    this.conn = this.webConsoleActor.conn;
 
-  this._response = {
-    headers: [],
-    cookies: [],
-    content: {},
-  };
+    this._request = {
+      method: null,
+      url: null,
+      httpVersion: null,
+      headers: [],
+      cookies: [],
+      headersSize: null,
+      postData: {},
+    };
 
-  this._timings = {};
-  this._stackTrace = {};
+    this._response = {
+      headers: [],
+      cookies: [],
+      content: {},
+    };
 
-  // Keep track of LongStringActors owned by this NetworkEventActor.
-  this._longStringActors = new Set();
-}
+    this._timings = {};
+    this._stackTrace = {};
 
-NetworkEventActor.prototype =
-{
+    this._discardRequestBody = false;
+    this._discardResponseBody = false;
+
+    // Keep track of LongStringActors owned by this NetworkEventActor.
+    this._longStringActors = new Set();
+  },
+
   _request: null,
   _response: null,
   _timings: null,
   _longStringActors: null,
 
-  actorPrefix: "netEvent",
-
   /**
    * Returns a grip for this actor for returning in a protocol message.
    */
-  grip: function() {
+  form() {
     return {
       actor: this.actorID,
       startedDateTime: this._startedDateTime,
       timeStamp: Date.parse(this._startedDateTime),
       url: this._request.url,
       method: this._request.method,
       isXHR: this._isXHR,
       cause: this._cause,
@@ -63,50 +69,56 @@ NetworkEventActor.prototype =
       fromServiceWorker: this._fromServiceWorker,
       private: this._private,
     };
   },
 
   /**
    * Releases this actor from the pool.
    */
-  release: function() {
+  destroy(conn) {
     for (let grip of this._longStringActors) {
-      let actor = this.parent.getActorByID(grip.actor);
+      let actor = this.webConsoleActor.getActorByID(grip.actor);
       if (actor) {
-        this.parent.releaseActor(actor);
+        this.webConsoleActor.releaseActor(actor);
       }
     }
     this._longStringActors = new Set();
 
+    if (!this.webConsoleActor) {
+      return;
+    }
     if (this._request.url) {
-      this.parent._networkEventActorsByURL.delete(this._request.url);
+      this.webConsoleActor._networkEventActorsByURL.delete(this._request.url);
     }
     if (this.channel) {
-      this.parent._netEvents.delete(this.channel);
+      this.webConsoleActor._netEvents.delete(this.channel);
     }
-    this.parent.releaseActor(this);
+
+    // Nullify webConsoleActor before calling releaseActor as it will recall this method
+    // To be removed once WebConsoleActor switches to protocol.js
+    let actor = this.webConsoleActor;
+    this.webConsoleActor = null;
+    actor.releaseActor(this);
+
+    protocol.Actor.prototype.destroy.call(this, conn);
   },
 
-  /**
-   * Handle a protocol request to release a grip.
-   */
-  onRelease: function() {
-    this.release();
-    return {};
+  release() {
+    // Per spec, destroy is automatically going to be called after this request
   },
 
   /**
    * Set the properties of this actor based on it's corresponding
    * network event.
    *
    * @param object networkEvent
    *        The network event associated with this actor.
    */
-  init: function(networkEvent) {
+  init(networkEvent) {
     this._startedDateTime = networkEvent.startedDateTime;
     this._isXHR = networkEvent.isXHR;
     this._cause = networkEvent.cause;
     this._fromCache = networkEvent.fromCache;
     this._fromServiceWorker = networkEvent.fromServiceWorker;
 
     // Stack trace info isn't sent automatically. The client
     // needs to request it explicitly using getStackTrace
@@ -115,419 +127,347 @@ NetworkEventActor.prototype =
     delete networkEvent.cause.stacktrace;
     networkEvent.cause.stacktraceAvailable =
       !!(this._stackTrace && this._stackTrace.length);
 
     for (let prop of ["method", "url", "httpVersion", "headersSize"]) {
       this._request[prop] = networkEvent[prop];
     }
 
-    this._discardRequestBody = networkEvent.discardRequestBody;
-    this._discardResponseBody = networkEvent.discardResponseBody;
+    // Consider as not discarded if networkEvent.discard*Body is undefined
+    this._discardRequestBody = !!networkEvent.discardRequestBody;
+    this._discardResponseBody = !!networkEvent.discardResponseBody;
+
     this._truncated = false;
     this._private = networkEvent.private;
   },
 
   /**
    * The "getRequestHeaders" packet type handler.
    *
    * @return object
    *         The response packet - network request headers.
    */
-  onGetRequestHeaders: function() {
+  getRequestHeaders() {
     return {
-      from: this.actorID,
       headers: this._request.headers,
       headersSize: this._request.headersSize,
       rawHeaders: this._request.rawHeaders,
     };
   },
 
   /**
    * The "getRequestCookies" packet type handler.
    *
    * @return object
    *         The response packet - network request cookies.
    */
-  onGetRequestCookies: function() {
+  getRequestCookies() {
     return {
-      from: this.actorID,
       cookies: this._request.cookies,
     };
   },
 
   /**
    * The "getRequestPostData" packet type handler.
    *
    * @return object
    *         The response packet - network POST data.
    */
-  onGetRequestPostData: function() {
+  getRequestPostData() {
     return {
-      from: this.actorID,
       postData: this._request.postData,
       postDataDiscarded: this._discardRequestBody,
     };
   },
 
   /**
    * The "getSecurityInfo" packet type handler.
    *
    * @return object
    *         The response packet - connection security information.
    */
-  onGetSecurityInfo: function() {
+  getSecurityInfo() {
     return {
-      from: this.actorID,
       securityInfo: this._securityInfo,
     };
   },
 
   /**
    * The "getResponseHeaders" packet type handler.
    *
    * @return object
    *         The response packet - network response headers.
    */
-  onGetResponseHeaders: function() {
+  getResponseHeaders() {
     return {
-      from: this.actorID,
       headers: this._response.headers,
       headersSize: this._response.headersSize,
       rawHeaders: this._response.rawHeaders,
     };
   },
 
   /**
    * The "getResponseCache" packet type handler.
    *
    * @return object
    *         The cache packet - network cache information.
    */
-  onGetResponseCache: function() {
+  getResponseCache: function() {
     return {
-      from: this.actorID,
       cache: this._response.responseCache,
     };
   },
 
   /**
    * The "getResponseCookies" packet type handler.
    *
    * @return object
    *         The response packet - network response cookies.
    */
-  onGetResponseCookies: function() {
+  getResponseCookies() {
     return {
-      from: this.actorID,
       cookies: this._response.cookies,
     };
   },
 
   /**
    * The "getResponseContent" packet type handler.
    *
    * @return object
    *         The response packet - network response content.
    */
-  onGetResponseContent: function() {
+  getResponseContent() {
     return {
-      from: this.actorID,
       content: this._response.content,
       contentDiscarded: this._discardResponseBody,
     };
   },
 
   /**
    * The "getEventTimings" packet type handler.
    *
    * @return object
    *         The response packet - network event timings.
    */
-  onGetEventTimings: function() {
+  getEventTimings() {
     return {
-      from: this.actorID,
       timings: this._timings,
       totalTime: this._totalTime,
       offsets: this._offsets
     };
   },
 
   /**
    * The "getStackTrace" packet type handler.
    *
    * @return object
    *         The response packet - stack trace.
    */
-  onGetStackTrace: function() {
+  getStackTrace() {
     return {
-      from: this.actorID,
       stacktrace: this._stackTrace,
     };
   },
 
   /** ****************************************************************
    * Listeners for new network event data coming from NetworkMonitor.
    ******************************************************************/
 
   /**
    * Add network request headers.
    *
    * @param array headers
    *        The request headers array.
    * @param string rawHeaders
    *        The raw headers source.
    */
-  addRequestHeaders: function(headers, rawHeaders) {
+  addRequestHeaders(headers, rawHeaders) {
     this._request.headers = headers;
     this._prepareHeaders(headers);
 
-    rawHeaders = this.parent._createStringGrip(rawHeaders);
+    rawHeaders = this.webConsoleActor._createStringGrip(rawHeaders);
     if (typeof rawHeaders == "object") {
       this._longStringActors.add(rawHeaders);
     }
     this._request.rawHeaders = rawHeaders;
 
-    let packet = {
-      from: this.actorID,
-      type: "networkEventUpdate",
-      updateType: "requestHeaders",
+    this.emit("network-event-update:headers", "requestHeaders", {
       headers: headers.length,
       headersSize: this._request.headersSize,
-    };
-
-    this.conn.send(packet);
+    });
   },
 
   /**
    * Add network request cookies.
    *
    * @param array cookies
    *        The request cookies array.
    */
-  addRequestCookies: function(cookies) {
+  addRequestCookies(cookies) {
     this._request.cookies = cookies;
     this._prepareHeaders(cookies);
 
-    let packet = {
-      from: this.actorID,
-      type: "networkEventUpdate",
-      updateType: "requestCookies",
+    this.emit("network-event-update:cookies", "requestCookies", {
       cookies: cookies.length,
-    };
-
-    this.conn.send(packet);
+    });
   },
 
   /**
    * Add network request POST data.
    *
    * @param object postData
    *        The request POST data.
    */
-  addRequestPostData: function(postData) {
+  addRequestPostData(postData) {
     this._request.postData = postData;
-    postData.text = this.parent._createStringGrip(postData.text);
+    postData.text = this.webConsoleActor._createStringGrip(postData.text);
     if (typeof postData.text == "object") {
       this._longStringActors.add(postData.text);
     }
 
-    let packet = {
-      from: this.actorID,
-      type: "networkEventUpdate",
-      updateType: "requestPostData",
+    this.emit("network-event-update:post-data", "requestPostData", {
       dataSize: postData.text.length,
       discardRequestBody: this._discardRequestBody,
-    };
-
-    this.conn.send(packet);
+    });
   },
 
   /**
    * Add the initial network response information.
    *
    * @param object info
    *        The response information.
    * @param string rawHeaders
    *        The raw headers source.
    */
-  addResponseStart: function(info, rawHeaders) {
-    rawHeaders = this.parent._createStringGrip(rawHeaders);
+  addResponseStart(info, rawHeaders) {
+    rawHeaders = this.webConsoleActor._createStringGrip(rawHeaders);
     if (typeof rawHeaders == "object") {
       this._longStringActors.add(rawHeaders);
     }
     this._response.rawHeaders = rawHeaders;
 
     this._response.httpVersion = info.httpVersion;
     this._response.status = info.status;
     this._response.statusText = info.statusText;
     this._response.headersSize = info.headersSize;
-    this._discardResponseBody = info.discardResponseBody;
+    // Consider as not discarded if info.discardResponseBody is undefined
+    this._discardResponseBody = !!info.discardResponseBody;
 
-    let packet = {
-      from: this.actorID,
-      type: "networkEventUpdate",
-      updateType: "responseStart",
+    this.emit("network-event-update:response-start", "responseStart", {
       response: info
-    };
-
-    this.conn.send(packet);
+    });
   },
 
   /**
    * Add connection security information.
    *
    * @param object info
    *        The object containing security information.
    */
-  addSecurityInfo: function(info) {
+  addSecurityInfo(info) {
     this._securityInfo = info;
 
-    let packet = {
-      from: this.actorID,
-      type: "networkEventUpdate",
-      updateType: "securityInfo",
+    this.emit("network-event-update:security-info", "securityInfo", {
       state: info.state,
-    };
-
-    this.conn.send(packet);
+    });
   },
 
   /**
    * Add network response headers.
    *
    * @param array headers
    *        The response headers array.
    */
-  addResponseHeaders: function(headers) {
+  addResponseHeaders(headers) {
     this._response.headers = headers;
     this._prepareHeaders(headers);
 
-    let packet = {
-      from: this.actorID,
-      type: "networkEventUpdate",
-      updateType: "responseHeaders",
+    this.emit("network-event-update:headers", "responseHeaders", {
       headers: headers.length,
       headersSize: this._response.headersSize,
-    };
-
-    this.conn.send(packet);
+    });
   },
 
   /**
    * Add network response cookies.
    *
    * @param array cookies
    *        The response cookies array.
    */
-  addResponseCookies: function(cookies) {
+  addResponseCookies(cookies) {
     this._response.cookies = cookies;
     this._prepareHeaders(cookies);
 
-    let packet = {
-      from: this.actorID,
-      type: "networkEventUpdate",
-      updateType: "responseCookies",
+    this.emit("network-event-update:cookies", "responseCookies", {
       cookies: cookies.length,
-    };
-
-    this.conn.send(packet);
+    });
   },
 
   /**
    * Add network response content.
    *
    * @param object content
    *        The response content.
    * @param object
    *        - boolean discardedResponseBody
    *          Tells if the response content was recorded or not.
    *        - boolean truncated
    *          Tells if the some of the response content is missing.
    */
-  addResponseContent: function(content, {discardResponseBody, truncated}) {
+  addResponseContent(content, {discardResponseBody, truncated}) {
     this._truncated = truncated;
     this._response.content = content;
-    content.text = this.parent._createStringGrip(content.text);
+    content.text = this.webConsoleActor._createStringGrip(content.text);
     if (typeof content.text == "object") {
       this._longStringActors.add(content.text);
     }
 
-    let packet = {
-      from: this.actorID,
-      type: "networkEventUpdate",
-      updateType: "responseContent",
+    this.emit("network-event-update:response-content", "responseContent", {
       mimeType: content.mimeType,
       contentSize: content.size,
       encoding: content.encoding,
       transferredSize: content.transferredSize,
       discardResponseBody,
-    };
-
-    this.conn.send(packet);
+    });
   },
 
   addResponseCache: function(content) {
     this._response.responseCache = content.responseCache;
-    let packet = {
-      from: this.actorID,
-      type: "networkEventUpdate",
-      updateType: "responseCache",
-    };
-    this.conn.send(packet);
+    this.emit("network-event-update:response-cache", "responseCache");
   },
 
   /**
    * Add network event timing information.
    *
    * @param number total
    *        The total time of the network event.
    * @param object timings
    *        Timing details about the network event.
    */
-  addEventTimings: function(total, timings, offsets) {
+  addEventTimings(total, timings, offsets) {
     this._totalTime = total;
     this._timings = timings;
     this._offsets = offsets;
 
-    let packet = {
-      from: this.actorID,
-      type: "networkEventUpdate",
-      updateType: "eventTimings",
+    this.emit("network-event-update:event-timings", "eventTimings", {
       totalTime: total
-    };
-
-    this.conn.send(packet);
+    });
   },
 
   /**
    * Prepare the headers array to be sent to the client by using the
    * LongStringActor for the header values, when needed.
    *
    * @private
    * @param array aHeaders
    */
-  _prepareHeaders: function(headers) {
+  _prepareHeaders(headers) {
     for (let header of headers) {
-      header.value = this.parent._createStringGrip(header.value);
+      header.value = this.webConsoleActor._createStringGrip(header.value);
       if (typeof header.value == "object") {
         this._longStringActors.add(header.value);
       }
     }
   },
-};
-
-NetworkEventActor.prototype.requestTypes =
-{
-  "release": NetworkEventActor.prototype.onRelease,
-  "getRequestHeaders": NetworkEventActor.prototype.onGetRequestHeaders,
-  "getRequestCookies": NetworkEventActor.prototype.onGetRequestCookies,
-  "getRequestPostData": NetworkEventActor.prototype.onGetRequestPostData,
-  "getResponseHeaders": NetworkEventActor.prototype.onGetResponseHeaders,
-  "getResponseCookies": NetworkEventActor.prototype.onGetResponseCookies,
-  "getResponseCache": NetworkEventActor.prototype.onGetResponseCache,
-  "getResponseContent": NetworkEventActor.prototype.onGetResponseContent,
-  "getEventTimings": NetworkEventActor.prototype.onGetEventTimings,
-  "getSecurityInfo": NetworkEventActor.prototype.onGetSecurityInfo,
-  "getStackTrace": NetworkEventActor.prototype.onGetStackTrace,
-};
+});
 
 exports.NetworkEventActor = NetworkEventActor;
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -1638,17 +1638,17 @@ WebConsoleActor.prototype =
     let actor = this.getNetworkEventActor(event.channelId);
     actor.init(event);
 
     this._networkEventActorsByURL.set(actor._request.url, actor);
 
     let packet = {
       from: this.actorID,
       type: "networkEvent",
-      eventActor: actor.grip()
+      eventActor: actor.form()
     };
 
     this.conn.send(packet);
 
     return actor;
   },
 
   /**
@@ -1730,17 +1730,17 @@ WebConsoleActor.prototype =
 
     let actor = this.getNetworkEventActor(channel.channelId);
 
     // map channel to actor so we can associate future events with it
     this._netEvents.set(channel.channelId, actor);
 
     return {
       from: this.actorID,
-      eventActor: actor.grip()
+      eventActor: actor.form()
     };
   },
 
   /**
    * Handler for file activity. This method sends the file request information
    * to the remote Web Console client.
    *
    * @see ConsoleProgressListener
--- a/devtools/shared/specs/index.js
+++ b/devtools/shared/specs/index.js
@@ -111,16 +111,21 @@ const Types = exports.__TypesForTests = 
     spec: "devtools/shared/specs/layout",
     front: "devtools/shared/fronts/layout",
   },
   {
     types: ["memory"],
     spec: "devtools/shared/specs/memory",
     front: "devtools/shared/fronts/memory",
   },
+  {
+    types: ["netEvent"],
+    spec: "devtools/shared/specs/network-event",
+    front: null,
+  },
   /* imageData isn't an actor but just a DictType */
   {
     types: ["imageData", "disconnectedNode", "disconnectedNodeArray"],
     spec: "devtools/shared/specs/node",
     front: null,
   },
   {
     types: ["domnode", "domnodelist"],
--- a/devtools/shared/specs/moz.build
+++ b/devtools/shared/specs/moz.build
@@ -21,16 +21,17 @@ DevToolsModules(
     'framerate.js',
     'gcli.js',
     'heap-snapshot-file.js',
     'highlighters.js',
     'index.js',
     'inspector.js',
     'layout.js',
     'memory.js',
+    'network-event.js',
     'node.js',
     'perf.js',
     'performance-recording.js',
     'performance.js',
     'preference.js',
     'promises.js',
     'property-iterator.js',
     'reflow.js',
new file mode 100644
--- /dev/null
+++ b/devtools/shared/specs/network-event.js
@@ -0,0 +1,208 @@
+/* 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 {
+  Arg,
+  Option,
+  RetVal,
+  generateActorSpec,
+  types
+} = require("devtools/shared/protocol");
+
+types.addDictType("netevent.headers-cookies", {
+  name: "string",
+  value: "longstring",
+});
+
+types.addDictType("netevent.headers", {
+  headers: "array:netevent.headers-cookies",
+  headersSize: "number",
+  rawHeaders: "nullable:longstring",
+});
+
+types.addDictType("netevent.cookies", {
+  cookies: "array:netevent.headers-cookies",
+});
+
+types.addDictType("netevent.postdata.text", {
+  text: "longstring",
+});
+
+types.addDictType("netevent.postdata", {
+  postData: "netevent.postdata.text",
+  postDataDiscarded: "boolean",
+});
+
+types.addDictType("netevent.cache", {
+  content: "json",
+});
+
+types.addDictType("netevent.content.content", {
+  text: "longstring",
+});
+
+types.addDictType("netevent.content", {
+  content: "netevent.content.content",
+  contentDiscarded: "boolean",
+});
+
+types.addDictType("netevent.timings.data", {
+  blocked: "number",
+  dns: "number",
+  ssl: "number",
+  connect: "number",
+  send: "number",
+  wait: "number",
+  receive: "number",
+});
+
+types.addDictType("netevent.timings", {
+  timings: "netevent.timings.data",
+  totalTime: "number",
+  offsets: "netevent.timings.data",
+});
+
+// See NetworkHelper.parseCertificateInfo for more details
+types.addDictType("netevent.cert", {
+  subject: "json",
+  issuer: "json",
+  validity: "json",
+  fingerprint: "json",
+});
+
+types.addDictType("netevent.secinfo", {
+  state: "string",
+  weaknessReasons: "array:string",
+  cipherSuite: "string",
+  keaGroupName: "string",
+  signatureSchemeName: "string",
+  protocolVersion: "string",
+  cert: "nullable:netevent.cert",
+  certificateTransparency: "nullable:string",
+  hsts: "boolean",
+  hpkp: "boolean",
+  errorMessage: "nullable:string",
+});
+
+const networkEventSpec = generateActorSpec({
+  typeName: "netEvent",
+
+  events: {
+    // All these events end up emitting a `networkEventUpdate` RDP message
+    // `updateType` attribute allows to identify which kind of event is emitted.
+    // We use individual event at protocol.js level to workaround performance issue
+    // with `Option` types. (See bug 1449162)
+    "network-event-update:headers": {
+      type: "networkEventUpdate",
+      updateType: Arg(0, "string"),
+
+      headers: Option(1, "number"),
+      headersSize: Option(1, "number"),
+    },
+
+    "network-event-update:cookies": {
+      type: "networkEventUpdate",
+      updateType: Arg(0, "string"),
+
+      cookies: Option(1, "number"),
+    },
+
+    "network-event-update:post-data": {
+      type: "networkEventUpdate",
+      updateType: Arg(0, "string"),
+
+      dataSize: Option(1, "number"),
+      discardRequestBody: Option(1, "boolean"),
+    },
+
+    "network-event-update:response-start": {
+      type: "networkEventUpdate",
+      updateType: Arg(0, "string"),
+
+      response: Option(1, "json"),
+    },
+
+    "network-event-update:security-info": {
+      type: "networkEventUpdate",
+      updateType: Arg(0, "string"),
+
+      state: Option(1, "string"),
+    },
+
+    "network-event-update:response-content": {
+      type: "networkEventUpdate",
+      updateType: Arg(0, "string"),
+
+      mimeType: Option(1, "string"),
+      contentSize: Option(1, "number"),
+      encoding: Option(1, "string"),
+      transferredSize: Option(1, "number"),
+      discardResponseBody: Option(1, "boolean"),
+    },
+
+    "network-event-update:event-timings": {
+      type: "networkEventUpdate",
+      updateType: Arg(0, "string"),
+
+      totalTime: Option(1, "number"),
+    },
+
+    "network-event-update:response-cache": {
+      type: "networkEventUpdate",
+      updateType: Arg(0, "string"),
+    },
+  },
+
+  methods: {
+    release: {
+      // This makes protocol.js call destroy method
+      release: true
+    },
+    getRequestHeaders: {
+      request: {},
+      response: RetVal("netevent.headers")
+    },
+    getRequestCookies: {
+      request: {},
+      response: RetVal("netevent.cookies")
+    },
+    getRequestPostData: {
+      request: {},
+      response: RetVal("netevent.postdata")
+    },
+    getResponseHeaders: {
+      request: {},
+      response: RetVal("netevent.headers")
+    },
+    getResponseCookies: {
+      request: {},
+      response: RetVal("netevent.cookies")
+    },
+    getResponseCache: {
+      request: {},
+      response: RetVal("netevent.cache")
+    },
+    getResponseContent: {
+      request: {},
+      response: RetVal("netevent.content")
+    },
+    getEventTimings: {
+      request: {},
+      response: RetVal("netevent.timings")
+    },
+    getSecurityInfo: {
+      request: {},
+      response: RetVal("netevent.secinfo")
+    },
+    getStackTrace: {
+      request: {},
+      // stacktrace is an "array:string", but not always.
+      response: RetVal("json")
+    },
+  }
+});
+
+exports.networkEventSpec = networkEventSpec;