Bug 1485300 - Remove duplicated implementation of NetworkObserver. r=Honza
authorAlexandre Poirot <poirot.alex@gmail.com>
Wed, 22 Aug 2018 12:16:23 +0000
changeset 488098 6fe10cab33ca07a9b6ce8e9a0ce5e41a672945e5
parent 488097 3d27e4213ccc750e392a6b5717247886983e8d5a
child 488099 3c1d4d57f532e7a50de3e83c3aa2843396f4c8e6
push id9719
push userffxbld-merge
push dateFri, 24 Aug 2018 17:49:46 +0000
treeherdermozilla-beta@719ec98fba77 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersHonza
bugs1485300
milestone63.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 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";
-}