Bug 1485300 - Remove duplicated implementation of NetworkObserver. r=Honza
authorAlexandre Poirot <poirot.alex@gmail.com>
Wed, 22 Aug 2018 12:16:23 +0000
changeset 481450 6fe10cab33ca07a9b6ce8e9a0ce5e41a672945e5
parent 481449 3d27e4213ccc750e392a6b5717247886983e8d5a
child 481451 3c1d4d57f532e7a50de3e83c3aa2843396f4c8e6
push id232
push userfmarier@mozilla.com
push dateWed, 05 Sep 2018 20:45:54 +0000
reviewersHonza
bugs1485300
milestone63.0a1
Bug 1485300 - Remove duplicated implementation of NetworkObserver. r=Honza MozReview-Commit-ID: F3yDYfmsv59 Differential Revision: https://phabricator.services.mozilla.com/D3970
devtools/server/actors/network-monitor/network-response-listener.js
--- a/devtools/server/actors/network-monitor/network-response-listener.js
+++ b/devtools/server/actors/network-monitor/network-response-listener.js
@@ -9,38 +9,22 @@
 const {Cc, Ci, Cr} = require("chrome");
 const ChromeUtils = require("ChromeUtils");
 const Services = require("Services");
 
 loader.lazyRequireGetter(this, "NetworkHelper",
                          "devtools/shared/webconsole/network-helper");
 loader.lazyRequireGetter(this, "DevToolsUtils",
                          "devtools/shared/DevToolsUtils");
-loader.lazyRequireGetter(this, "NetworkThrottleManager",
-                         "devtools/shared/webconsole/throttle", true);
 loader.lazyRequireGetter(this, "CacheEntry",
                          "devtools/shared/platform/cache-entry", true);
-loader.lazyRequireGetter(this, "matchRequest",
-                         "devtools/server/actors/network-monitor/network-observer", true);
 loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
-loader.lazyServiceGetter(this, "gActivityDistributor",
-                         "@mozilla.org/network/http-activity-distributor;1",
-                         "nsIHttpActivityDistributor");
 
 // Network logging
 
-// The maximum uint32 value.
-const PR_UINT32_MAX = 4294967295;
-
-// HTTP status codes.
-const HTTP_MOVED_PERMANENTLY = 301;
-const HTTP_FOUND = 302;
-const HTTP_SEE_OTHER = 303;
-const HTTP_TEMPORARY_REDIRECT = 307;
-
 /**
  * The network response listener implements the nsIStreamListener and
  * nsIRequestObserver interfaces. This is used within the NetworkObserver feature
  * to get the response body of the request.
  *
  * The code is mostly based on code listings from:
  *
  *   http://www.softwareishard.com/blog/firebug/
@@ -516,1040 +500,8 @@ NetworkResponseListener.prototype = {
       this.offset += available;
       this.setAsyncListener(stream, this);
     } else {
       this.onStreamClose();
       this.offset = 0;
     }
   },
 };
-
-/**
- * The network monitor uses the nsIHttpActivityDistributor to monitor network
- * requests. The nsIObserverService is also used for monitoring
- * http-on-examine-response notifications. All network request information is
- * routed to the remote Web Console.
- *
- * @constructor
- * @param object filters
- *        Object with the filters to use for network requests:
- *        - window (nsIDOMWindow): filter network requests by the associated
- *          window object.
- *        - outerWindowID (number): filter requests by their top frame's outerWindowID.
- *        Filters are optional. If any of these filters match the request is
- *        logged (OR is applied). If no filter is provided then all requests are
- *        logged.
- * @param object owner
- *        The network monitor owner. This object needs to hold:
- *        - onNetworkEvent(requestInfo)
- *          This method is invoked once for every new network request and it is
- *          given the initial network request information as an argument.
- *          onNetworkEvent() must return an object which holds several add*()
- *          methods which are used to add further network request/response information.
- */
-function NetworkObserver(filters, owner) {
-  this.filters = filters;
-  this.owner = owner;
-  this.openRequests = new Map();
-  this.openResponses = new Map();
-  this._httpResponseExaminer =
-    DevToolsUtils.makeInfallible(this._httpResponseExaminer).bind(this);
-  this._httpModifyExaminer =
-    DevToolsUtils.makeInfallible(this._httpModifyExaminer).bind(this);
-  this._serviceWorkerRequest = this._serviceWorkerRequest.bind(this);
-  this._throttleData = null;
-  this._throttler = null;
-}
-
-exports.NetworkObserver = NetworkObserver;
-
-NetworkObserver.prototype = {
-  filters: null,
-
-  httpTransactionCodes: {
-    0x5001: "REQUEST_HEADER",
-    0x5002: "REQUEST_BODY_SENT",
-    0x5003: "RESPONSE_START",
-    0x5004: "RESPONSE_HEADER",
-    0x5005: "RESPONSE_COMPLETE",
-    0x5006: "TRANSACTION_CLOSE",
-
-    0x804b0003: "STATUS_RESOLVING",
-    0x804b000b: "STATUS_RESOLVED",
-    0x804b0007: "STATUS_CONNECTING_TO",
-    0x804b0004: "STATUS_CONNECTED_TO",
-    0x804b0005: "STATUS_SENDING_TO",
-    0x804b000a: "STATUS_WAITING_FOR",
-    0x804b0006: "STATUS_RECEIVING_FROM",
-    0x804b000c: "STATUS_TLS_STARTING",
-    0x804b000d: "STATUS_TLS_ENDING"
-  },
-
-  httpDownloadActivities: [
-    gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_START,
-    gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_HEADER,
-    gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_COMPLETE,
-    gActivityDistributor.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE
-  ],
-
-  // Network response bodies are piped through a buffer of the given size (in
-  // bytes).
-  responsePipeSegmentSize: null,
-
-  owner: null,
-
-  /**
-   * Whether to save the bodies of network requests and responses.
-   * @type boolean
-   */
-  saveRequestAndResponseBodies: true,
-
-  /**
-   * Object that holds the HTTP activity objects for ongoing requests.
-   */
-  openRequests: null,
-
-  /**
-   * Object that holds response headers coming from this._httpResponseExaminer.
-   */
-  openResponses: null,
-
-  /**
-   * The network monitor initializer.
-   */
-  init: function() {
-    this.responsePipeSegmentSize = Services.prefs
-                                   .getIntPref("network.buffer.cache.size");
-    this.interceptedChannels = new Set();
-
-    if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) {
-      gActivityDistributor.addObserver(this);
-      Services.obs.addObserver(this._httpResponseExaminer,
-                               "http-on-examine-response");
-      Services.obs.addObserver(this._httpResponseExaminer,
-                               "http-on-examine-cached-response");
-      Services.obs.addObserver(this._httpModifyExaminer,
-                               "http-on-modify-request");
-    }
-    // In child processes, only watch for service worker requests
-    // everything else only happens in the parent process
-    Services.obs.addObserver(this._serviceWorkerRequest,
-                             "service-worker-synthesized-response");
-  },
-
-  get throttleData() {
-    return this._throttleData;
-  },
-
-  set throttleData(value) {
-    this._throttleData = value;
-    // Clear out any existing throttlers
-    this._throttler = null;
-  },
-
-  _getThrottler: function() {
-    if (this.throttleData !== null && this._throttler === null) {
-      this._throttler = new NetworkThrottleManager(this.throttleData);
-    }
-    return this._throttler;
-  },
-
-  _serviceWorkerRequest: function(subject, topic, data) {
-    const channel = subject.QueryInterface(Ci.nsIHttpChannel);
-
-    if (!matchRequest(channel, this.filters)) {
-      return;
-    }
-
-    this.interceptedChannels.add(subject);
-
-    // Service workers never fire http-on-examine-cached-response, so fake one.
-    this._httpResponseExaminer(channel, "http-on-examine-cached-response");
-  },
-
-  /**
-   * Observe notifications for the http-on-examine-response topic, coming from
-   * the nsIObserverService.
-   *
-   * @private
-   * @param nsIHttpChannel subject
-   * @param string topic
-   * @returns void
-   */
-  _httpResponseExaminer: function(subject, topic) {
-    // The httpResponseExaminer is used to retrieve the uncached response
-    // headers. The data retrieved is stored in openResponses. The
-    // NetworkResponseListener is responsible with updating the httpActivity
-    // object with the data from the new object in openResponses.
-
-    if (!this.owner ||
-        (topic != "http-on-examine-response" &&
-         topic != "http-on-examine-cached-response") ||
-        !(subject instanceof Ci.nsIHttpChannel)) {
-      return;
-    }
-
-    const channel = subject.QueryInterface(Ci.nsIHttpChannel);
-
-    if (!matchRequest(channel, this.filters)) {
-      return;
-    }
-
-    const response = {
-      id: gSequenceId(),
-      channel: channel,
-      headers: [],
-      cookies: [],
-    };
-
-    const setCookieHeaders = [];
-
-    channel.visitOriginalResponseHeaders({
-      visitHeader: function(name, value) {
-        const lowerName = name.toLowerCase();
-        if (lowerName == "set-cookie") {
-          setCookieHeaders.push(value);
-        }
-        response.headers.push({ name: name, value: value });
-      }
-    });
-
-    if (!response.headers.length) {
-      // No need to continue.
-      return;
-    }
-
-    if (setCookieHeaders.length) {
-      response.cookies = setCookieHeaders.reduce((result, header) => {
-        const cookies = NetworkHelper.parseSetCookieHeader(header);
-        return result.concat(cookies);
-      }, []);
-    }
-
-    // Determine the HTTP version.
-    const httpVersionMaj = {};
-    const httpVersionMin = {};
-
-    channel.QueryInterface(Ci.nsIHttpChannelInternal);
-    channel.getResponseVersion(httpVersionMaj, httpVersionMin);
-
-    response.status = channel.responseStatus;
-    response.statusText = channel.responseStatusText;
-    response.httpVersion = "HTTP/" + httpVersionMaj.value + "." +
-                                     httpVersionMin.value;
-
-    this.openResponses.set(channel, response);
-
-    if (topic === "http-on-examine-cached-response") {
-      // Service worker requests emits cached-reponse notification on non-e10s,
-      // and we fake one on e10s.
-      const fromServiceWorker = this.interceptedChannels.has(channel);
-      this.interceptedChannels.delete(channel);
-
-      // If this is a cached response, there never was a request event
-      // so we need to construct one here so the frontend gets all the
-      // expected events.
-      const httpActivity = this._createNetworkEvent(channel, {
-        fromCache: !fromServiceWorker,
-        fromServiceWorker: fromServiceWorker
-      });
-      httpActivity.owner.addResponseStart({
-        httpVersion: response.httpVersion,
-        remoteAddress: "",
-        remotePort: "",
-        status: response.status,
-        statusText: response.statusText,
-        headersSize: 0,
-      }, "", true);
-
-      // There also is never any timing events, so we can fire this
-      // event with zeroed out values.
-      const timings = this._setupHarTimings(httpActivity, true);
-      httpActivity.owner.addEventTimings(timings.total, timings.timings,
-                                         timings.offsets);
-    }
-  },
-
-  /**
-   * Observe notifications for the http-on-modify-request topic, coming from
-   * the nsIObserverService.
-   *
-   * @private
-   * @param nsIHttpChannel aSubject
-   * @returns void
-   */
-  _httpModifyExaminer: function(subject) {
-    const throttler = this._getThrottler();
-    if (throttler) {
-      const channel = subject.QueryInterface(Ci.nsIHttpChannel);
-      if (matchRequest(channel, this.filters)) {
-        // Read any request body here, before it is throttled.
-        const httpActivity = this.createOrGetActivityObject(channel);
-        this._onRequestBodySent(httpActivity);
-        throttler.manageUpload(channel);
-      }
-    }
-  },
-
-  /**
-   * A helper function for observeActivity.  This does whatever work
-   * is required by a particular http activity event.  Arguments are
-   * the same as for observeActivity.
-   */
-  _dispatchActivity: function(httpActivity, channel, activityType,
-                               activitySubtype, timestamp, extraSizeData,
-                               extraStringData) {
-    const transCodes = this.httpTransactionCodes;
-
-    // Store the time information for this activity subtype.
-    if (activitySubtype in transCodes) {
-      const stage = transCodes[activitySubtype];
-      if (stage in httpActivity.timings) {
-        httpActivity.timings[stage].last = timestamp;
-      } else {
-        httpActivity.timings[stage] = {
-          first: timestamp,
-          last: timestamp,
-        };
-      }
-    }
-
-    switch (activitySubtype) {
-      case gActivityDistributor.ACTIVITY_SUBTYPE_REQUEST_BODY_SENT:
-        this._onRequestBodySent(httpActivity);
-        if (httpActivity.sentBody !== null) {
-          httpActivity.owner.addRequestPostData({ text: httpActivity.sentBody });
-          httpActivity.sentBody = null;
-        }
-        break;
-      case gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_HEADER:
-        this._onResponseHeader(httpActivity, extraStringData);
-        break;
-      case gActivityDistributor.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE:
-        this._onTransactionClose(httpActivity);
-        break;
-      default:
-        break;
-    }
-  },
-
-  /**
-   * Begin observing HTTP traffic that originates inside the current tab.
-   *
-   * @see https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIHttpActivityObserver
-   *
-   * @param nsIHttpChannel channel
-   * @param number activityType
-   * @param number activitySubtype
-   * @param number timestamp
-   * @param number extraSizeData
-   * @param string extraStringData
-   */
-  observeActivity:
-  DevToolsUtils.makeInfallible(function(channel, activityType, activitySubtype,
-                                        timestamp, extraSizeData,
-                                        extraStringData) {
-    if (!this.owner ||
-        activityType != gActivityDistributor.ACTIVITY_TYPE_HTTP_TRANSACTION &&
-        activityType != gActivityDistributor.ACTIVITY_TYPE_SOCKET_TRANSPORT) {
-      return;
-    }
-
-    if (!(channel instanceof Ci.nsIHttpChannel)) {
-      return;
-    }
-
-    channel = channel.QueryInterface(Ci.nsIHttpChannel);
-
-    if (activitySubtype ==
-        gActivityDistributor.ACTIVITY_SUBTYPE_REQUEST_HEADER) {
-      this._onRequestHeader(channel, timestamp, extraStringData);
-      return;
-    }
-
-    // Iterate over all currently ongoing requests. If channel can't
-    // be found within them, then exit this function.
-    const httpActivity = this._findActivityObject(channel);
-    if (!httpActivity) {
-      return;
-    }
-
-    // If we're throttling, we must not report events as they arrive
-    // from platform, but instead let the throttler emit the events
-    // after some time has elapsed.
-    if (httpActivity.downloadThrottle &&
-        this.httpDownloadActivities.includes(activitySubtype)) {
-      const callback = this._dispatchActivity.bind(this);
-      httpActivity.downloadThrottle
-        .addActivityCallback(callback, httpActivity, channel, activityType,
-                             activitySubtype, timestamp, extraSizeData,
-                             extraStringData);
-    } else {
-      this._dispatchActivity(httpActivity, channel, activityType,
-                             activitySubtype, timestamp, extraSizeData,
-                             extraStringData);
-    }
-  }),
-
-  /**
-   *
-   */
-  _createNetworkEvent: function(channel, { timestamp, extraStringData,
-                                           fromCache, fromServiceWorker }) {
-    const httpActivity = this.createOrGetActivityObject(channel);
-
-    channel.QueryInterface(Ci.nsIPrivateBrowsingChannel);
-    httpActivity.private = channel.isChannelPrivate;
-
-    if (timestamp) {
-      httpActivity.timings.REQUEST_HEADER = {
-        first: timestamp,
-        last: timestamp
-      };
-    }
-
-    const event = {};
-    event.method = channel.requestMethod;
-    event.channelId = channel.channelId;
-    event.url = channel.URI.spec;
-    event.private = httpActivity.private;
-    event.headersSize = 0;
-    event.startedDateTime =
-      (timestamp ? new Date(Math.round(timestamp / 1000)) : new Date())
-      .toISOString();
-    event.fromCache = fromCache;
-    event.fromServiceWorker = fromServiceWorker;
-    httpActivity.fromServiceWorker = fromServiceWorker;
-
-    if (extraStringData) {
-      event.headersSize = extraStringData.length;
-    }
-
-    // Determine the cause and if this is an XHR request.
-    let causeType = Ci.nsIContentPolicy.TYPE_OTHER;
-    let causeUri = null;
-    let stacktrace;
-
-    if (channel.loadInfo) {
-      causeType = channel.loadInfo.externalContentPolicyType;
-      const { loadingPrincipal } = channel.loadInfo;
-      if (loadingPrincipal && loadingPrincipal.URI) {
-        causeUri = loadingPrincipal.URI.spec;
-      }
-    }
-
-    event.cause = {
-      type: causeTypeToString(causeType),
-      loadingDocumentUri: causeUri,
-      stacktrace
-    };
-
-    httpActivity.isXHR = event.isXHR =
-        (causeType === Ci.nsIContentPolicy.TYPE_XMLHTTPREQUEST ||
-         causeType === Ci.nsIContentPolicy.TYPE_FETCH);
-
-    // Determine the HTTP version.
-    const httpVersionMaj = {};
-    const httpVersionMin = {};
-    channel.QueryInterface(Ci.nsIHttpChannelInternal);
-    channel.getRequestVersion(httpVersionMaj, httpVersionMin);
-
-    event.httpVersion = "HTTP/" + httpVersionMaj.value + "." +
-                                  httpVersionMin.value;
-
-    event.discardRequestBody = !this.saveRequestAndResponseBodies;
-    event.discardResponseBody = !this.saveRequestAndResponseBodies;
-
-    const headers = [];
-    let cookies = [];
-    let cookieHeader = null;
-
-    // Copy the request header data.
-    channel.visitRequestHeaders({
-      visitHeader: function(name, value) {
-        if (name == "Cookie") {
-          cookieHeader = value;
-        }
-        headers.push({ name: name, value: value });
-      }
-    });
-
-    if (cookieHeader) {
-      cookies = NetworkHelper.parseCookieHeader(cookieHeader);
-    }
-
-    httpActivity.owner = this.owner.onNetworkEvent(event);
-
-    this._setupResponseListener(httpActivity, fromCache);
-
-    httpActivity.owner.addRequestHeaders(headers, extraStringData);
-    httpActivity.owner.addRequestCookies(cookies);
-
-    return httpActivity;
-  },
-
-  /**
-   * Handler for ACTIVITY_SUBTYPE_REQUEST_HEADER. When a request starts the
-   * headers are sent to the server. This method creates the |httpActivity|
-   * object where we store the request and response information that is
-   * collected through its lifetime.
-   *
-   * @private
-   * @param nsIHttpChannel channel
-   * @param number timestamp
-   * @param string extraStringData
-   * @return void
-   */
-  _onRequestHeader: function(channel, timestamp, extraStringData) {
-    if (!matchRequest(channel, this.filters)) {
-      return;
-    }
-
-    this._createNetworkEvent(channel, { timestamp, extraStringData });
-  },
-
-  /**
-   * Find an HTTP activity object for the channel.
-   *
-   * @param nsIHttpChannel channel
-   *        The HTTP channel whose activity object we want to find.
-   * @return object
-   *        The HTTP activity object, or null if it is not found.
-   */
-  _findActivityObject: function(channel) {
-    return this.openRequests.get(channel) || null;
-  },
-
-  /**
-   * Find an existing HTTP activity object, or create a new one. This
-   * object is used for storing all the request and response
-   * information.
-   *
-   * This is a HAR-like object. Conformance to the spec is not guaranteed at
-   * this point.
-   *
-   * @see http://www.softwareishard.com/blog/har-12-spec
-   * @param nsIHttpChannel channel
-   *        The HTTP channel for which the HTTP activity object is created.
-   * @return object
-   *         The new HTTP activity object.
-   */
-  createOrGetActivityObject: function(channel) {
-    let httpActivity = this._findActivityObject(channel);
-    if (!httpActivity) {
-      const win = NetworkHelper.getWindowForRequest(channel);
-      const charset = win ? win.document.characterSet : null;
-
-      httpActivity = {
-        id: gSequenceId(),
-        channel: channel,
-        // see _onRequestBodySent()
-        charset: charset,
-        sentBody: null,
-        url: channel.URI.spec,
-        headersSize: null,
-        // needed for host specific security info
-        hostname: channel.URI.host,
-        discardRequestBody: !this.saveRequestAndResponseBodies,
-        discardResponseBody: !this.saveRequestAndResponseBodies,
-        // internal timing information, see observeActivity()
-        timings: {},
-        // see _onResponseHeader()
-        responseStatus: null,
-        // the activity owner which is notified when changes happen
-        owner: null,
-      };
-
-      this.openRequests.set(channel, httpActivity);
-    }
-
-    return httpActivity;
-  },
-
-  /**
-   * Setup the network response listener for the given HTTP activity. The
-   * NetworkResponseListener is responsible for storing the response body.
-   *
-   * @private
-   * @param object httpActivity
-   *        The HTTP activity object we are tracking.
-   */
-  _setupResponseListener: function(httpActivity, fromCache) {
-    const channel = httpActivity.channel;
-    channel.QueryInterface(Ci.nsITraceableChannel);
-
-    if (!fromCache) {
-      const throttler = this._getThrottler();
-      if (throttler) {
-        httpActivity.downloadThrottle = throttler.manage(channel);
-      }
-    }
-
-    // The response will be written into the outputStream of this pipe.
-    // This allows us to buffer the data we are receiving and read it
-    // asynchronously.
-    // Both ends of the pipe must be blocking.
-    const sink = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
-
-    // The streams need to be blocking because this is required by the
-    // stream tee.
-    sink.init(false, false, this.responsePipeSegmentSize, PR_UINT32_MAX, null);
-
-    // Add listener for the response body.
-    const newListener = new NetworkResponseListener(this, httpActivity);
-
-    // Remember the input stream, so it isn't released by GC.
-    newListener.inputStream = sink.inputStream;
-    newListener.sink = sink;
-
-    const tee = Cc["@mozilla.org/network/stream-listener-tee;1"]
-              .createInstance(Ci.nsIStreamListenerTee);
-
-    const originalListener = channel.setNewListener(tee);
-
-    tee.init(originalListener, sink.outputStream, newListener);
-  },
-
-  /**
-   * Handler for ACTIVITY_SUBTYPE_REQUEST_BODY_SENT. The request body is logged
-   * here.
-   *
-   * @private
-   * @param object httpActivity
-   *        The HTTP activity object we are working with.
-   */
-  _onRequestBodySent: function(httpActivity) {
-    // Return early if we don't need the request body, or if we've
-    // already found it.
-    if (httpActivity.discardRequestBody || httpActivity.sentBody !== null) {
-      return;
-    }
-
-    let sentBody = NetworkHelper.readPostTextFromRequest(httpActivity.channel,
-                                                         httpActivity.charset);
-
-    if (sentBody !== null && this.window &&
-        httpActivity.url == this.window.location.href) {
-      // If the request URL is the same as the current page URL, then
-      // we can try to get the posted text from the page directly.
-      // This check is necessary as otherwise the
-      //   NetworkHelper.readPostTextFromPageViaWebNav()
-      // function is called for image requests as well but these
-      // are not web pages and as such don't store the posted text
-      // in the cache of the webpage.
-      const webNav = this.window.docShell.QueryInterface(Ci.nsIWebNavigation);
-      sentBody = NetworkHelper
-                 .readPostTextFromPageViaWebNav(webNav, httpActivity.charset);
-    }
-
-    if (sentBody !== null) {
-      httpActivity.sentBody = sentBody;
-    }
-  },
-
-  /**
-   * Handler for ACTIVITY_SUBTYPE_RESPONSE_HEADER. This method stores
-   * information about the response headers.
-   *
-   * @private
-   * @param object httpActivity
-   *        The HTTP activity object we are working with.
-   * @param string extraStringData
-   *        The uncached response headers.
-   */
-  _onResponseHeader: function(httpActivity, extraStringData) {
-    // extraStringData contains the uncached response headers. The first line
-    // contains the response status (e.g. HTTP/1.1 200 OK).
-    //
-    // Note: The response header is not saved here. Calling the
-    // channel.visitResponseHeaders() method at this point sometimes causes an
-    // NS_ERROR_NOT_AVAILABLE exception.
-    //
-    // We could parse extraStringData to get the headers and their values, but
-    // that is not trivial to do in an accurate manner. Hence, we save the
-    // response headers in this._httpResponseExaminer().
-
-    const headers = extraStringData.split(/\r\n|\n|\r/);
-    const statusLine = headers.shift();
-    const statusLineArray = statusLine.split(" ");
-
-    const response = {};
-    response.httpVersion = statusLineArray.shift();
-    response.remoteAddress = httpActivity.channel.remoteAddress;
-    response.remotePort = httpActivity.channel.remotePort;
-    response.status = statusLineArray.shift();
-    response.statusText = statusLineArray.join(" ");
-    response.headersSize = extraStringData.length;
-
-    httpActivity.responseStatus = response.status;
-    httpActivity.headersSize = response.headersSize;
-
-    // Discard the response body for known response statuses.
-    switch (parseInt(response.status, 10)) {
-      case HTTP_MOVED_PERMANENTLY:
-      case HTTP_FOUND:
-      case HTTP_SEE_OTHER:
-      case HTTP_TEMPORARY_REDIRECT:
-        httpActivity.discardResponseBody = true;
-        break;
-    }
-
-    response.discardResponseBody = httpActivity.discardResponseBody;
-
-    httpActivity.owner.addResponseStart(response, extraStringData);
-  },
-
-  /**
-   * Handler for ACTIVITY_SUBTYPE_TRANSACTION_CLOSE. This method updates the HAR
-   * timing information on the HTTP activity object and clears the request
-   * from the list of known open requests.
-   *
-   * @private
-   * @param object httpActivity
-   *        The HTTP activity object we work with.
-   */
-  _onTransactionClose: function(httpActivity) {
-    const result = this._setupHarTimings(httpActivity);
-    httpActivity.owner.addEventTimings(result.total, result.timings,
-                                       result.offsets);
-    this.openRequests.delete(httpActivity.channel);
-  },
-
-  /**
-   * Update the HTTP activity object to include timing information as in the HAR
-   * spec. The HTTP activity object holds the raw timing information in
-   * |timings| - these are timings stored for each activity notification. The
-   * HAR timing information is constructed based on these lower level
-   * data.
-   *
-   * @param object httpActivity
-   *        The HTTP activity object we are working with.
-   * @param boolean fromCache
-   *        Indicates that the result was returned from the browser cache
-   * @return object
-   *         This object holds two properties:
-   *         - total - the total time for all of the request and response.
-   *         - timings - the HAR timings object.
-   */
-  _setupHarTimings: function(httpActivity, fromCache) {
-    if (fromCache) {
-      // If it came from the browser cache, we have no timing
-      // information and these should all be 0
-      return {
-        total: 0,
-        timings: {
-          blocked: 0,
-          dns: 0,
-          ssl: 0,
-          connect: 0,
-          send: 0,
-          wait: 0,
-          receive: 0
-        },
-        offsets: {
-          blocked: 0,
-          dns: 0,
-          ssl: 0,
-          connect: 0,
-          send: 0,
-          wait: 0,
-          receive: 0
-        }
-      };
-    }
-
-    const timings = httpActivity.timings;
-    const harTimings = {};
-    // If the TCP Fast Open option or tls1.3 0RTT is used tls and data can
-    // be dispatched in SYN packet and not after tcp socket is connected.
-    // To demostrate this properly we will calculated TLS and send start time
-    // relative to CONNECTING_TO.
-    // Similary if 0RTT is used, data can be sent as soon as a TLS handshake
-    // starts.
-    let secureConnectionStartTime = 0;
-    let secureConnectionStartTimeRelative = false;
-    let startSendingTime = 0;
-    let startSendingTimeRelative = false;
-
-    if (timings.STATUS_RESOLVING && timings.STATUS_CONNECTING_TO) {
-      harTimings.blocked = timings.STATUS_RESOLVING.first -
-                           timings.REQUEST_HEADER.first;
-    } else if (timings.STATUS_SENDING_TO) {
-      harTimings.blocked = timings.STATUS_SENDING_TO.first -
-                           timings.REQUEST_HEADER.first;
-    } else {
-      harTimings.blocked = -1;
-    }
-
-    // DNS timing information is available only in when the DNS record is not
-    // cached.
-    harTimings.dns = timings.STATUS_RESOLVING && timings.STATUS_RESOLVED ?
-                     timings.STATUS_RESOLVED.last -
-                     timings.STATUS_RESOLVING.first : -1;
-
-    if (timings.STATUS_CONNECTING_TO && timings.STATUS_CONNECTED_TO) {
-      harTimings.connect = timings.STATUS_CONNECTED_TO.last -
-                           timings.STATUS_CONNECTING_TO.first;
-    } else {
-      harTimings.connect = -1;
-    }
-
-    if (timings.STATUS_TLS_STARTING && timings.STATUS_TLS_ENDING) {
-      harTimings.ssl = timings.STATUS_TLS_ENDING.last -
-                           timings.STATUS_TLS_STARTING.first;
-      if (timings.STATUS_CONNECTING_TO) {
-        secureConnectionStartTime =
-          timings.STATUS_TLS_STARTING.first - timings.STATUS_CONNECTING_TO.first;
-      }
-      if (secureConnectionStartTime < 0) {
-        secureConnectionStartTime = 0;
-      }
-      secureConnectionStartTimeRelative = true;
-    } else {
-      harTimings.ssl = -1;
-    }
-
-    // sometimes the connection information events are attached to a speculative
-    // channel instead of this one, but necko might glue them back together in the
-    // nsITimedChannel interface used by Resource and Navigation Timing
-    const timedChannel = httpActivity.channel.QueryInterface(Ci.nsITimedChannel);
-
-    let tcTcpConnectEndTime = 0;
-    let tcConnectStartTime = 0;
-    let tcConnectEndTime = 0;
-    let tcSecureConnectionStartTime = 0;
-    let tcDomainLookupEndTime = 0;
-    let tcDomainLookupStartTime = 0;
-
-    if (timedChannel) {
-      tcTcpConnectEndTime = timedChannel.tcpConnectEndTime;
-      tcConnectStartTime = timedChannel.connectStartTime;
-      tcConnectEndTime = timedChannel.connectEndTime;
-      tcSecureConnectionStartTime = timedChannel.secureConnectionStartTime;
-      tcDomainLookupEndTime = timedChannel.domainLookupEndTime;
-      tcDomainLookupStartTime = timedChannel.domainLookupStartTime;
-    }
-
-    // Make sure the above values are at least timedChannel.asyncOpenTime.
-    if (timedChannel && timedChannel.asyncOpenTime) {
-      if ((tcTcpConnectEndTime != 0) &&
-          (tcTcpConnectEndTime < timedChannel.asyncOpenTime)) {
-        tcTcpConnectEndTime = 0;
-      }
-      if ((tcConnectStartTime != 0) &&
-          (tcConnectStartTime < timedChannel.asyncOpenTime)) {
-        tcConnectStartTime = 0;
-      }
-      if ((tcConnectEndTime != 0) &&
-          (tcConnectEndTime < timedChannel.asyncOpenTime)) {
-        tcConnectEndTime = 0;
-      }
-      if ((tcSecureConnectionStartTime != 0) &&
-          (tcSecureConnectionStartTime < timedChannel.asyncOpenTime)) {
-        tcSecureConnectionStartTime = 0;
-      }
-      if ((tcDomainLookupEndTime != 0) &&
-          (tcDomainLookupEndTime < timedChannel.asyncOpenTime)) {
-        tcDomainLookupEndTime = 0;
-      }
-      if ((tcDomainLookupStartTime != 0) &&
-          (tcDomainLookupStartTime < timedChannel.asyncOpenTime)) {
-        tcDomainLookupStartTime = 0;
-      }
-    }
-
-    if ((harTimings.connect <= 0) && timedChannel &&
-        (tcTcpConnectEndTime != 0) && (tcConnectStartTime != 0)) {
-      harTimings.connect = tcTcpConnectEndTime - tcConnectStartTime;
-      if (tcSecureConnectionStartTime != 0) {
-        harTimings.ssl = tcConnectEndTime - tcSecureConnectionStartTime;
-        secureConnectionStartTime =
-          tcSecureConnectionStartTime - tcConnectStartTime;
-        secureConnectionStartTimeRelative = true;
-      } else {
-        harTimings.ssl = -1;
-      }
-    } else if (timedChannel && timings.STATUS_TLS_STARTING &&
-               (tcSecureConnectionStartTime != 0)) {
-      // It can happen that TCP Fast Open actually have not sent any data and
-      // timings.STATUS_TLS_STARTING.first value will be corrected in
-      // timedChannel.secureConnectionStartTime
-      if (tcSecureConnectionStartTime > timings.STATUS_TLS_STARTING.first) {
-        // TCP Fast Open actually did not sent any data.
-        harTimings.ssl =
-          tcConnectEndTime - tcSecureConnectionStartTime;
-        secureConnectionStartTimeRelative = false;
-      }
-    }
-
-    if ((harTimings.dns <= 0) && timedChannel &&
-        (tcDomainLookupEndTime != 0) && (tcDomainLookupStartTime != 0)) {
-      harTimings.dns = tcDomainLookupEndTime - tcDomainLookupStartTime;
-    }
-
-    if (timings.STATUS_SENDING_TO) {
-      harTimings.send =
-        timings.STATUS_SENDING_TO.last - timings.STATUS_SENDING_TO.first;
-      if (timings.STATUS_CONNECTING_TO) {
-        startSendingTime =
-          timings.STATUS_SENDING_TO.first - timings.STATUS_CONNECTING_TO.first;
-        startSendingTimeRelative = true;
-      } else if (tcConnectStartTime != 0) {
-        startSendingTime = timings.STATUS_SENDING_TO.first - tcConnectStartTime;
-        startSendingTimeRelative = true;
-      }
-      if (startSendingTime < 0) {
-        startSendingTime = 0;
-      }
-    } else if (timings.REQUEST_HEADER && timings.REQUEST_BODY_SENT) {
-      harTimings.send = timings.REQUEST_BODY_SENT.last - timings.REQUEST_HEADER.first;
-    } else {
-      harTimings.send = -1;
-    }
-
-    if (timings.RESPONSE_START) {
-      harTimings.wait = timings.RESPONSE_START.first -
-                        (timings.REQUEST_BODY_SENT ||
-                         timings.STATUS_SENDING_TO).last;
-    } else {
-      harTimings.wait = -1;
-    }
-
-    if (timings.RESPONSE_START && timings.RESPONSE_COMPLETE) {
-      harTimings.receive = timings.RESPONSE_COMPLETE.last -
-                           timings.RESPONSE_START.first;
-    } else {
-      harTimings.receive = -1;
-    }
-
-    if (secureConnectionStartTimeRelative) {
-      const time = Math.max(Math.round(secureConnectionStartTime / 1000), -1);
-      secureConnectionStartTime = time;
-    }
-    if (startSendingTimeRelative) {
-      const time = Math.max(Math.round(startSendingTime / 1000), -1);
-      startSendingTime = time;
-    }
-
-    const ot = this._calculateOffsetAndTotalTime(harTimings,
-                                               secureConnectionStartTime,
-                                               startSendingTimeRelative,
-                                               secureConnectionStartTimeRelative,
-                                               startSendingTime);
-    return {
-      total: ot.total,
-      timings: harTimings,
-      offsets: ot.offsets
-    };
-  },
-
-  _calculateOffsetAndTotalTime: function(harTimings,
-                                          secureConnectionStartTime,
-                                          startSendingTimeRelative,
-                                          secureConnectionStartTimeRelative,
-                                          startSendingTime) {
-    let totalTime = 0;
-    for (const timing in harTimings) {
-      const time = Math.max(Math.round(harTimings[timing] / 1000), -1);
-      harTimings[timing] = time;
-      if ((time > -1) && (timing != "connect") && (timing != "ssl")) {
-        totalTime += time;
-      }
-    }
-
-    // connect, ssl and send times can be overlapped.
-    if (startSendingTimeRelative) {
-      totalTime += startSendingTime;
-    } else if (secureConnectionStartTimeRelative) {
-      totalTime += secureConnectionStartTime;
-      totalTime += harTimings.ssl;
-    }
-
-    const offsets = {};
-    offsets.blocked = 0;
-    offsets.dns = harTimings.blocked;
-    offsets.connect = offsets.dns + harTimings.dns;
-    if (secureConnectionStartTimeRelative) {
-      offsets.ssl = offsets.connect + secureConnectionStartTime;
-    } else {
-      offsets.ssl = offsets.connect + harTimings.connect;
-    }
-    if (startSendingTimeRelative) {
-      offsets.send = offsets.connect + startSendingTime;
-      if (!secureConnectionStartTimeRelative) {
-        offsets.ssl = offsets.send - harTimings.ssl;
-      }
-    } else {
-      offsets.send = offsets.ssl + harTimings.ssl;
-    }
-    offsets.wait = offsets.send + harTimings.send;
-    offsets.receive = offsets.wait + harTimings.wait;
-
-    return {
-      total: totalTime,
-      offsets: offsets
-    };
-  },
-
-  /**
-   * Suspend Web Console activity. This is called when all Web Consoles are
-   * closed.
-   */
-  destroy: function() {
-    if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) {
-      gActivityDistributor.removeObserver(this);
-      Services.obs.removeObserver(this._httpResponseExaminer,
-                                  "http-on-examine-response");
-      Services.obs.removeObserver(this._httpResponseExaminer,
-                                  "http-on-examine-cached-response");
-      Services.obs.removeObserver(this._httpModifyExaminer,
-                                  "http-on-modify-request");
-    }
-
-    Services.obs.removeObserver(this._serviceWorkerRequest,
-                                "service-worker-synthesized-response");
-
-    this.interceptedChannels.clear();
-    this.openRequests.clear();
-    this.openResponses.clear();
-    this.owner = null;
-    this.filters = null;
-    this._throttler = null;
-  },
-};
-
-function gSequenceId() {
-  return gSequenceId.n++;
-}
-gSequenceId.n = 1;
-
-/**
- * Convert a nsIContentPolicy constant to a display string
- */
-const LOAD_CAUSE_STRINGS = {
-  [Ci.nsIContentPolicy.TYPE_INVALID]: "invalid",
-  [Ci.nsIContentPolicy.TYPE_OTHER]: "other",
-  [Ci.nsIContentPolicy.TYPE_SCRIPT]: "script",
-  [Ci.nsIContentPolicy.TYPE_IMAGE]: "img",
-  [Ci.nsIContentPolicy.TYPE_STYLESHEET]: "stylesheet",
-  [Ci.nsIContentPolicy.TYPE_OBJECT]: "object",
-  [Ci.nsIContentPolicy.TYPE_DOCUMENT]: "document",
-  [Ci.nsIContentPolicy.TYPE_SUBDOCUMENT]: "subdocument",
-  [Ci.nsIContentPolicy.TYPE_REFRESH]: "refresh",
-  [Ci.nsIContentPolicy.TYPE_XBL]: "xbl",
-  [Ci.nsIContentPolicy.TYPE_PING]: "ping",
-  [Ci.nsIContentPolicy.TYPE_XMLHTTPREQUEST]: "xhr",
-  [Ci.nsIContentPolicy.TYPE_OBJECT_SUBREQUEST]: "objectSubdoc",
-  [Ci.nsIContentPolicy.TYPE_DTD]: "dtd",
-  [Ci.nsIContentPolicy.TYPE_FONT]: "font",
-  [Ci.nsIContentPolicy.TYPE_MEDIA]: "media",
-  [Ci.nsIContentPolicy.TYPE_WEBSOCKET]: "websocket",
-  [Ci.nsIContentPolicy.TYPE_CSP_REPORT]: "csp",
-  [Ci.nsIContentPolicy.TYPE_XSLT]: "xslt",
-  [Ci.nsIContentPolicy.TYPE_BEACON]: "beacon",
-  [Ci.nsIContentPolicy.TYPE_FETCH]: "fetch",
-  [Ci.nsIContentPolicy.TYPE_IMAGESET]: "imageset",
-  [Ci.nsIContentPolicy.TYPE_WEB_MANIFEST]: "webManifest"
-};
-
-function causeTypeToString(causeType) {
-  return LOAD_CAUSE_STRINGS[causeType] || "unknown";
-}