Bug 917227 - Part 2: network monitor server changes to add support for Firefox OS; r=ochameau
authorMihai Sucan <mihai.sucan@gmail.com>
Mon, 10 Mar 2014 20:57:27 +0200
changeset 191211 493f2ddd14ba57a7d832a8ae467df348d9251770
parent 191210 731be5361993d488ac76cc4d1e2e30c8aad57086
child 191212 15b0a825f153f04ab433f5fc1b99b91d98b877a1
push id474
push userasasaki@mozilla.com
push dateMon, 02 Jun 2014 21:01:02 +0000
treeherdermozilla-release@967f4cf1b31c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersochameau
bugs917227
milestone30.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 917227 - Part 2: network monitor server changes to add support for Firefox OS; r=ochameau
toolkit/devtools/client/dbg-client.jsm
toolkit/devtools/server/actors/childtab.js
toolkit/devtools/server/actors/root.js
toolkit/devtools/server/actors/webapps.js
toolkit/devtools/server/actors/webbrowser.js
toolkit/devtools/server/actors/webconsole.js
toolkit/devtools/webconsole/client.js
toolkit/devtools/webconsole/network-helper.js
toolkit/devtools/webconsole/network-monitor.js
toolkit/devtools/webconsole/utils.js
--- a/toolkit/devtools/client/dbg-client.jsm
+++ b/toolkit/devtools/client/dbg-client.jsm
@@ -444,17 +444,18 @@ DebuggerClient.prototype = {
    *        Called with the response packet and a TabClient
    *        (which will be undefined on error).
    */
   attachTab: function (aTabActor, aOnResponse) {
     if (this._tabClients.has(aTabActor)) {
       let cachedTab = this._tabClients.get(aTabActor);
       let cachedResponse = {
         cacheEnabled: cachedTab.cacheEnabled,
-        javascriptEnabled: cachedTab.javascriptEnabled
+        javascriptEnabled: cachedTab.javascriptEnabled,
+        traits: cachedTab.traits,
       };
       setTimeout(() => aOnResponse(cachedResponse, cachedTab), 0);
       return;
     }
 
     let packet = {
       to: aTabActor,
       type: "attach"
@@ -514,17 +515,17 @@ DebuggerClient.prototype = {
     };
 
     this.request(packet, (aResponse) => {
       let consoleClient;
       if (!aResponse.error) {
         if (this._consoleClients.has(aConsoleActor)) {
           consoleClient = this._consoleClients.get(aConsoleActor);
         } else {
-          consoleClient = new WebConsoleClient(this, aConsoleActor);
+          consoleClient = new WebConsoleClient(this, aResponse);
           this._consoleClients.set(aConsoleActor, consoleClient);
         }
       }
       aOnResponse(aResponse, consoleClient);
     });
   },
 
   /**
@@ -979,16 +980,17 @@ SSProto.translatePacket = function (aPac
 function TabClient(aClient, aForm) {
   this.client = aClient;
   this._actor = aForm.from;
   this._threadActor = aForm.threadActor;
   this.javascriptEnabled = aForm.javascriptEnabled;
   this.cacheEnabled = aForm.cacheEnabled;
   this.thread = null;
   this.request = this.client.request;
+  this.traits = aForm.traits || {};
 }
 
 TabClient.prototype = {
   get actor() { return this._actor },
   get _transport() { return this.client._transport; },
 
   /**
    * Attach to a thread actor.
--- a/toolkit/devtools/server/actors/childtab.js
+++ b/toolkit/devtools/server/actors/childtab.js
@@ -19,16 +19,17 @@
  *        The conection to the client.
  * @param chromeGlobal
  *        The content script global holding |content| and |docShell| properties for a tab.
  */
 function ContentActor(connection, chromeGlobal)
 {
   TabActor.call(this, connection, chromeGlobal);
   this._chromeGlobal = chromeGlobal;
+  this.traits.reconfigure = false;
 }
 
 ContentActor.prototype = Object.create(TabActor.prototype);
 
 ContentActor.prototype.constructor = ContentActor;
 
 Object.defineProperty(ContentActor.prototype, "docShell", {
   get: function() {
--- a/toolkit/devtools/server/actors/root.js
+++ b/toolkit/devtools/server/actors/root.js
@@ -178,17 +178,18 @@ RootActor.prototype = {
         sources: true,
         editOuterHTML: true,
         // Wether the server-side highlighter actor exists and can be used to
         // remotely highlight nodes (see server/actors/highlighter.js)
         highlightable: true,
         // Wether the inspector actor implements the getImageDataFromURL
         // method that returns data-uris for image URLs. This is used for image
         // tooltips for instance
-        urlToImageDataResolver: true
+        urlToImageDataResolver: true,
+        networkMonitor: true,
       }
     };
   },
 
   /**
    * This is true for the root actor only, used by some child actors
    */
   get isRootActor() true,
--- a/toolkit/devtools/server/actors/webapps.js
+++ b/toolkit/devtools/server/actors/webapps.js
@@ -5,16 +5,24 @@
 "use strict";
 
 let Cu = Components.utils;
 let Cc = Components.classes;
 let Ci = Components.interfaces;
 let CC = Components.Constructor;
 
 Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+
+XPCOMUtils.defineLazyGetter(this, "NetworkMonitorManager", () => {
+  return devtools.require("devtools/toolkit/webconsole/network-monitor")
+         .NetworkMonitorManager;
+});
 
 let promise;
 
 function debug(aMsg) {
   /*
   Cc["@mozilla.org/consoleservice;1"]
     .getService(Ci.nsIConsoleService)
     .logStringMessage("--*-- WebappsActor : " + aMsg);
@@ -817,23 +825,29 @@ WebappsActor.prototype = {
 
       // Only create a new actor, if we haven't already
       // instanciated one for this connection.
       let map = this._appActorsMap;
       let mm = appFrame.QueryInterface(Ci.nsIFrameLoaderOwner)
                        .frameLoader
                        .messageManager;
       let actor = map.get(mm);
+      let netMonitor = null;
       if (!actor) {
         let onConnect = actor => {
           map.set(mm, actor);
+          netMonitor = new NetworkMonitorManager(appFrame);
           return { actor: actor };
         };
         let onDisconnect = mm => {
           map.delete(mm);
+          if (netMonitor) {
+            netMonitor.destroy();
+            netMonitor = null;
+          }
         };
         return DebuggerServer.connectToChild(this.conn, mm, onDisconnect)
                              .then(onConnect);
       }
 
       return { actor: actor };
     });
   },
--- a/toolkit/devtools/server/actors/webbrowser.js
+++ b/toolkit/devtools/server/actors/webbrowser.js
@@ -489,22 +489,26 @@ function TabActor(aConnection, aChromeEv
 {
   this.conn = aConnection;
   this._chromeEventHandler = aChromeEventHandler;
   this._tabActorPool = null;
   // A map of actor names to actor instances provided by extensions.
   this._extraActors = {};
 
   this._onWindowCreated = this.onWindowCreated.bind(this);
+
+  this.traits = { reconfigure: true };
 }
 
 // XXX (bug 710213): TabActor attach/detach/exit/disconnect is a
 // *complete* mess, needs to be rethought asap.
 
 TabActor.prototype = {
+  traits: null,
+
   get exited() { return !this._chromeEventHandler; },
   get attached() { return !!this._attached; },
 
   _tabPool: null,
   get tabActorPool() { return this._tabPool; },
 
   _contextPool: null,
   get contextActorPool() { return this._contextPool; },
@@ -730,17 +734,18 @@ TabActor.prototype = {
     }
 
     this._attach();
 
     return {
       type: "tabAttached",
       threadActor: this.threadActor.actorID,
       cacheEnabled: this._getCacheEnabled(),
-      javascriptEnabled: this._getJavascriptEnabled()
+      javascriptEnabled: this._getJavascriptEnabled(),
+      traits: this.traits,
     };
   },
 
   onDetach: function BTA_onDetach(aRequest) {
     if (!this._detach()) {
       return { error: "wrongState" };
     }
 
--- a/toolkit/devtools/server/actors/webconsole.js
+++ b/toolkit/devtools/server/actors/webconsole.js
@@ -11,20 +11,31 @@ let Ci = Components.interfaces;
 let Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 let devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
 
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyGetter(this, "NetworkMonitor", () => {
+  return devtools.require("devtools/toolkit/webconsole/network-monitor")
+         .NetworkMonitor;
+});
+XPCOMUtils.defineLazyGetter(this, "NetworkMonitorChild", () => {
+  return devtools.require("devtools/toolkit/webconsole/network-monitor")
+         .NetworkMonitorChild;
+});
+XPCOMUtils.defineLazyGetter(this, "ConsoleProgressListener", () => {
+  return devtools.require("devtools/toolkit/webconsole/network-monitor")
+         .ConsoleProgressListener;
+});
 
 for (let name of ["WebConsoleUtils", "ConsoleServiceListener",
-                  "ConsoleAPIListener", "ConsoleProgressListener",
-                  "JSTermHelpers", "JSPropertyProvider", "NetworkMonitor",
+                  "ConsoleAPIListener", "JSTermHelpers", "JSPropertyProvider",
                   "ConsoleReflowListener"]) {
   Object.defineProperty(this, name, {
     get: function(prop) {
       if (prop == "WebConsoleUtils") {
         prop = "Utils";
       }
       return devtools.require("devtools/toolkit/webconsole/utils")[prop];
     }.bind(null, name),
@@ -60,16 +71,20 @@ function WebConsoleActor(aConnection, aP
   this._gripDepth = 0;
 
   this._onWillNavigate = this._onWillNavigate.bind(this);
   this._onObserverNotification = this._onObserverNotification.bind(this);
   if (this.parentActor.isRootActor) {
     Services.obs.addObserver(this._onObserverNotification,
                              "last-pb-context-exited", false);
   }
+
+  this.traits = {
+    customNetworkRequest: !this._parentIsContentActor,
+  };
 }
 
 WebConsoleActor.l10n = new WebConsoleUtils.l10n("chrome://global/locale/console.properties");
 
 WebConsoleActor.prototype =
 {
   /**
    * Debugger instance.
@@ -111,16 +126,33 @@ WebConsoleActor.prototype =
 
   /**
    * The debugger server connection instance.
    * @type object
    */
   conn: null,
 
   /**
+   * List of supported features by the console actor.
+   * @type object
+   */
+  traits: null,
+
+  /**
+   * Boolean getter that tells if the parent actor is a ContentActor.
+   *
+   * @private
+   * @type boolean
+   */
+  get _parentIsContentActor() {
+    return "ContentActor" in DebuggerServer &&
+            this.parentActor instanceof DebuggerServer.ContentActor;
+  },
+
+  /**
    * The window we work with.
    * @type nsIDOMWindow
    */
   get window() {
     if (this.parentActor.isRootActor) {
       return this._getWindowForBrowserConsole();
     }
     return this.parentActor.window;
@@ -249,23 +281,16 @@ WebConsoleActor.prototype =
 
   /**
    * The JSTerm Helpers names cache.
    * @private
    * @type array
    */
   _jstermHelpersCache: null,
 
-  /**
-   * Getter for the NetworkMonitor.saveRequestAndResponseBodies preference.
-   * @type boolean
-   */
-  get saveRequestAndResponseBodies()
-    this._prefs["NetworkMonitor.saveRequestAndResponseBodies"] || null,
-
   actorPrefix: "console",
 
   grip: function WCA_grip()
   {
     return { actor: this.actorID };
   },
 
   hasNativeConsoleAPI: function WCA_hasNativeConsoleAPI(aWindow) {
@@ -472,16 +497,24 @@ WebConsoleActor.prototype =
    *        The JSON request object received from the Web Console client.
    * @return object
    *         The response object which holds the startedListeners array.
    */
   onStartListeners: function WCA_onStartListeners(aRequest)
   {
     let startedListeners = [];
     let window = !this.parentActor.isRootActor ? this.window : null;
+    let appId = null;
+    let messageManager = null;
+
+    if (this._parentIsContentActor) {
+      // Filter network requests by appId on Firefox OS devices.
+      appId = this.parentActor.docShell.appId;
+      messageManager = this.parentActor._chromeGlobal;
+    }
 
     while (aRequest.listeners.length > 0) {
       let listener = aRequest.listeners.shift();
       switch (listener) {
         case "PageError":
           if (!this.consoleServiceListener) {
             this.consoleServiceListener =
               new ConsoleServiceListener(window, this);
@@ -494,18 +527,23 @@ WebConsoleActor.prototype =
             this.consoleAPIListener =
               new ConsoleAPIListener(window, this);
             this.consoleAPIListener.init();
           }
           startedListeners.push(listener);
           break;
         case "NetworkActivity":
           if (!this.networkMonitor) {
-            this.networkMonitor =
-              new NetworkMonitor(window, this);
+            if (appId && messageManager) {
+              this.networkMonitor =
+                new NetworkMonitorChild(appId, messageManager, this);
+            }
+            else {
+              this.networkMonitor = new NetworkMonitor({ window: window }, this);
+            }
             this.networkMonitor.init();
           }
           startedListeners.push(listener);
           break;
         case "FileActivity":
           if (!this.consoleProgressListener) {
             this.consoleProgressListener =
               new ConsoleProgressListener(this.window, this);
@@ -521,16 +559,17 @@ WebConsoleActor.prototype =
           }
           startedListeners.push(listener);
           break;
       }
     }
     return {
       startedListeners: startedListeners,
       nativeConsoleAPI: this.hasNativeConsoleAPI(this.window),
+      traits: this.traits,
     };
   },
 
   /**
    * Handler for the "stopListeners" request.
    *
    * @param object aRequest
    *        The JSON request object received from the Web Console client.
@@ -816,16 +855,21 @@ WebConsoleActor.prototype =
    *
    * @param object aRequest
    *        The request message - which preferences need to be updated.
    */
   onSetPreferences: function WCA_onSetPreferences(aRequest)
   {
     for (let key in aRequest.preferences) {
       this._prefs[key] = aRequest.preferences[key];
+
+      if (key == "NetworkMonitor.saveRequestAndResponseBodies" &&
+          this.networkMonitor) {
+        this.networkMonitor.saveRequestAndResponseBodies = this._prefs[key];
+      }
     }
     return { updated: Object.keys(aRequest.preferences) };
   },
 
   //////////////////
   // End of request handlers.
   //////////////////
 
@@ -1126,16 +1170,18 @@ WebConsoleActor.prototype =
    * Handler for network events. This method is invoked when a new network event
    * is about to be recorded.
    *
    * @see NetworkEventActor
    * @see NetworkMonitor from webconsole/utils.js
    *
    * @param object aEvent
    *        The initial network request event information.
+   * @param nsIHttpChannel aChannel
+   *        The network request nsIHttpChannel object.
    * @return object
    *         A new NetworkEventActor is returned. This is used for tracking the
    *         network request and response.
    */
   onNetworkEvent: function WCA_onNetworkEvent(aEvent, aChannel)
   {
     let actor = this.getNetworkEventActor(aChannel);
     actor.init(aEvent);
@@ -1150,18 +1196,20 @@ WebConsoleActor.prototype =
 
     return actor;
   },
 
   /**
    * Get the NetworkEventActor for a nsIChannel, if it exists,
    * otherwise create a new one.
    *
-   * @param object aChannel
+   * @param nsIHttpChannel aChannel
    *        The channel for the network event.
+   * @return object
+   *         The NetworkEventActor for the given channel.
    */
   getNetworkEventActor: function WCA_getNetworkEventActor(aChannel) {
     let actor = this._netEvents.get(aChannel);
     if (actor) {
       // delete from map as we should only need to do this check once
       this._netEvents.delete(aChannel);
       actor.channel = null;
       return actor;
--- a/toolkit/devtools/webconsole/client.js
+++ b/toolkit/devtools/webconsole/client.js
@@ -11,29 +11,32 @@ const {Cc, Ci, Cu} = require("chrome");
 loader.lazyImporter(this, "LongStringClient", "resource://gre/modules/devtools/dbg-client.jsm");
 
 /**
  * A WebConsoleClient is used as a front end for the WebConsoleActor that is
  * created on the server, hiding implementation details.
  *
  * @param object aDebuggerClient
  *        The DebuggerClient instance we live for.
- * @param string aActor
- *        The WebConsoleActor ID.
+ * @param object aResponse
+ *        The response packet received from the "startListeners" request sent to
+ *        the WebConsoleActor.
  */
-function WebConsoleClient(aDebuggerClient, aActor)
+function WebConsoleClient(aDebuggerClient, aResponse)
 {
-  this._actor = aActor;
+  this._actor = aResponse.from;
   this._client = aDebuggerClient;
   this._longStrings = {};
+  this.traits = aResponse.traits || {};
 }
 exports.WebConsoleClient = WebConsoleClient;
 
 WebConsoleClient.prototype = {
   _longStrings: null,
+  traits: null,
 
   get actor() { return this._actor; },
 
   /**
    * Retrieve the cached messages from the server.
    *
    * @see this.CACHED_MESSAGES
    * @param array aTypes
--- a/toolkit/devtools/webconsole/network-helper.js
+++ b/toolkit/devtools/webconsole/network-helper.js
@@ -184,16 +184,50 @@ let NetworkHelper = {
 
         return this.readAndConvertFromStream(descriptor, aCharset);
       }
     }
     return null;
   },
 
   /**
+   * Gets the web appId that is associated with aRequest.
+   *
+   * @param nsIHttpChannel aRequest
+   * @returns number|null
+   *          The appId for the given request, if available.
+   */
+  getAppIdForRequest: function NH_getAppIdForRequest(aRequest)
+  {
+    try {
+      return this.getRequestLoadContext(aRequest).appId;
+    } catch (ex) {
+      // request loadContent is not always available.
+    }
+    return null;
+  },
+
+  /**
+   * Gets the topFrameElement that is associated with aRequest.
+   *
+   * @param nsIHttpChannel aRequest
+   * @returns nsIDOMElement|null
+   *          The top frame element for the given request, if available.
+   */
+  getTopFrameForRequest: function NH_getTopFrameForRequest(aRequest)
+  {
+    try {
+      return this.getRequestLoadContext(aRequest).topFrameElement;
+    } catch (ex) {
+      // request loadContent is not always available.
+    }
+    return null;
+  },
+
+  /**
    * Gets the nsIDOMWindow that is associated with aRequest.
    *
    * @param nsIHttpChannel aRequest
    * @returns nsIDOMWindow or null
    */
   getWindowForRequest: function NH_getWindowForRequest(aRequest)
   {
     try {
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/webconsole/network-monitor.js
@@ -0,0 +1,1478 @@
+/* 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 {Cc, Ci, Cu} = require("chrome");
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+loader.lazyGetter(this, "NetworkHelper", () => require("devtools/toolkit/webconsole/network-helper"));
+loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
+loader.lazyImporter(this, "DevToolsUtils", "resource://gre/modules/devtools/DevToolsUtils.jsm");
+loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
+loader.lazyImporter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.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 maximum number of bytes a NetworkResponseListener can hold.
+const RESPONSE_BODY_LIMIT = 1048576; // 1 MB
+
+/**
+ * The network response listener implements the nsIStreamListener and
+ * nsIRequestObserver interfaces. This is used within the NetworkMonitor feature
+ * to get the response body of the request.
+ *
+ * The code is mostly based on code listings from:
+ *
+ *   http://www.softwareishard.com/blog/firebug/
+ *      nsitraceablechannel-intercept-http-traffic/
+ *
+ * @constructor
+ * @param object aOwner
+ *        The response listener owner. This object needs to hold the
+ *        |openResponses| object.
+ * @param object aHttpActivity
+ *        HttpActivity object associated with this request. See NetworkMonitor
+ *        for more information.
+ */
+function NetworkResponseListener(aOwner, aHttpActivity)
+{
+  this.owner = aOwner;
+  this.receivedData = "";
+  this.httpActivity = aHttpActivity;
+  this.bodySize = 0;
+}
+exports.NetworkResponseListener = NetworkResponseListener;
+
+NetworkResponseListener.prototype = {
+  QueryInterface:
+    XPCOMUtils.generateQI([Ci.nsIStreamListener, Ci.nsIInputStreamCallback,
+                           Ci.nsIRequestObserver, Ci.nsISupports]),
+
+  /**
+   * This NetworkResponseListener tracks the NetworkMonitor.openResponses object
+   * to find the associated uncached headers.
+   * @private
+   */
+  _foundOpenResponse: false,
+
+  /**
+   * The response listener owner.
+   */
+  owner: null,
+
+  /**
+   * The response will be written into the outputStream of this nsIPipe.
+   * Both ends of the pipe must be blocking.
+   */
+  sink: null,
+
+  /**
+   * The HttpActivity object associated with this response.
+   */
+  httpActivity: null,
+
+  /**
+   * Stores the received data as a string.
+   */
+  receivedData: null,
+
+  /**
+   * The network response body size.
+   */
+  bodySize: null,
+
+  /**
+   * The nsIRequest we are started for.
+   */
+  request: null,
+
+  /**
+   * Set the async listener for the given nsIAsyncInputStream. This allows us to
+   * wait asynchronously for any data coming from the stream.
+   *
+   * @param nsIAsyncInputStream aStream
+   *        The input stream from where we are waiting for data to come in.
+   * @param nsIInputStreamCallback aListener
+   *        The input stream callback you want. This is an object that must have
+   *        the onInputStreamReady() method. If the argument is null, then the
+   *        current callback is removed.
+   * @return void
+   */
+  setAsyncListener: function NRL_setAsyncListener(aStream, aListener)
+  {
+    // Asynchronously wait for the stream to be readable or closed.
+    aStream.asyncWait(aListener, 0, 0, Services.tm.mainThread);
+  },
+
+  /**
+   * Stores the received data, if request/response body logging is enabled. It
+   * also does limit the number of stored bytes, based on the
+   * RESPONSE_BODY_LIMIT constant.
+   *
+   * Learn more about nsIStreamListener at:
+   * https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIStreamListener
+   *
+   * @param nsIRequest aRequest
+   * @param nsISupports aContext
+   * @param nsIInputStream aInputStream
+   * @param unsigned long aOffset
+   * @param unsigned long aCount
+   */
+  onDataAvailable:
+  function NRL_onDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount)
+  {
+    this._findOpenResponse();
+    let data = NetUtil.readInputStreamToString(aInputStream, aCount);
+
+    this.bodySize += aCount;
+
+    if (!this.httpActivity.discardResponseBody &&
+        this.receivedData.length < RESPONSE_BODY_LIMIT) {
+      this.receivedData += NetworkHelper.
+                           convertToUnicode(data, aRequest.contentCharset);
+    }
+  },
+
+  /**
+   * See documentation at
+   * https://developer.mozilla.org/En/NsIRequestObserver
+   *
+   * @param nsIRequest aRequest
+   * @param nsISupports aContext
+   */
+  onStartRequest: function NRL_onStartRequest(aRequest)
+  {
+    this.request = aRequest;
+    this._findOpenResponse();
+    // Asynchronously wait for the data coming from the request.
+    this.setAsyncListener(this.sink.inputStream, this);
+  },
+
+  /**
+   * Handle the onStopRequest by closing the sink output stream.
+   *
+   * For more documentation about nsIRequestObserver go to:
+   * https://developer.mozilla.org/En/NsIRequestObserver
+   */
+  onStopRequest: function NRL_onStopRequest()
+  {
+    this._findOpenResponse();
+    this.sink.outputStream.close();
+  },
+
+  /**
+   * Find the open response object associated to the current request. The
+   * NetworkMonitor._httpResponseExaminer() method saves the response headers in
+   * NetworkMonitor.openResponses. This method takes the data from the open
+   * response object and puts it into the HTTP activity object, then sends it to
+   * the remote Web Console instance.
+   *
+   * @private
+   */
+  _findOpenResponse: function NRL__findOpenResponse()
+  {
+    if (!this.owner || this._foundOpenResponse) {
+      return;
+    }
+
+    let openResponse = null;
+
+    for each (let item in this.owner.openResponses) {
+      if (item.channel === this.httpActivity.channel) {
+        openResponse = item;
+        break;
+      }
+    }
+
+    if (!openResponse) {
+      return;
+    }
+    this._foundOpenResponse = true;
+
+    delete this.owner.openResponses[openResponse.id];
+
+    this.httpActivity.owner.addResponseHeaders(openResponse.headers);
+    this.httpActivity.owner.addResponseCookies(openResponse.cookies);
+  },
+
+  /**
+   * Clean up the response listener once the response input stream is closed.
+   * This is called from onStopRequest() or from onInputStreamReady() when the
+   * stream is closed.
+   * @return void
+   */
+  onStreamClose: function NRL_onStreamClose()
+  {
+    if (!this.httpActivity) {
+      return;
+    }
+    // Remove our listener from the request input stream.
+    this.setAsyncListener(this.sink.inputStream, null);
+
+    this._findOpenResponse();
+
+    if (!this.httpActivity.discardResponseBody && this.receivedData.length) {
+      this._onComplete(this.receivedData);
+    }
+    else if (!this.httpActivity.discardResponseBody &&
+             this.httpActivity.responseStatus == 304) {
+      // Response is cached, so we load it from cache.
+      let charset = this.request.contentCharset || this.httpActivity.charset;
+      NetworkHelper.loadFromCache(this.httpActivity.url, charset,
+                                  this._onComplete.bind(this));
+    }
+    else {
+      this._onComplete();
+    }
+  },
+
+  /**
+   * Handler for when the response completes. This function cleans up the
+   * response listener.
+   *
+   * @param string [aData]
+   *        Optional, the received data coming from the response listener or
+   *        from the cache.
+   */
+  _onComplete: function NRL__onComplete(aData)
+  {
+    let response = {
+      mimeType: "",
+      text: aData || "",
+    };
+
+    response.size = response.text.length;
+
+    try {
+      response.mimeType = this.request.contentType;
+    }
+    catch (ex) { }
+
+    if (!response.mimeType || !NetworkHelper.isTextMimeType(response.mimeType)) {
+      response.encoding = "base64";
+      response.text = btoa(response.text);
+    }
+
+    if (response.mimeType && this.request.contentCharset) {
+      response.mimeType += "; charset=" + this.request.contentCharset;
+    }
+
+    this.receivedData = "";
+
+    this.httpActivity.owner.
+      addResponseContent(response, this.httpActivity.discardResponseBody);
+
+    this.httpActivity.channel = null;
+    this.httpActivity.owner = null;
+    this.httpActivity = null;
+    this.sink = null;
+    this.inputStream = null;
+    this.request = null;
+    this.owner = null;
+  },
+
+  /**
+   * The nsIInputStreamCallback for when the request input stream is ready -
+   * either it has more data or it is closed.
+   *
+   * @param nsIAsyncInputStream aStream
+   *        The sink input stream from which data is coming.
+   * @returns void
+   */
+  onInputStreamReady: function NRL_onInputStreamReady(aStream)
+  {
+    if (!(aStream instanceof Ci.nsIAsyncInputStream) || !this.httpActivity) {
+      return;
+    }
+
+    let available = -1;
+    try {
+      // This may throw if the stream is closed normally or due to an error.
+      available = aStream.available();
+    }
+    catch (ex) { }
+
+    if (available != -1) {
+      if (available != 0) {
+        // Note that passing 0 as the offset here is wrong, but the
+        // onDataAvailable() method does not use the offset, so it does not
+        // matter.
+        this.onDataAvailable(this.request, null, aStream, 0, available);
+      }
+      this.setAsyncListener(aStream, this);
+    }
+    else {
+      this.onStreamClose();
+    }
+  },
+}; // NetworkResponseListener.prototype
+
+
+/**
+ * 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 aFilters
+ *        Object with the filters to use for network requests:
+ *        - window (nsIDOMWindow): filter network requests by the associated
+ *        window object.
+ *        - appId (number): filter requests by the appId.
+ *        - topFrame (nsIDOMElement): filter requests by their topFrameElement.
+ *        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 aOwner
+ *        The network monitor owner. This object needs to hold:
+ *        - onNetworkEvent(aRequestInfo, aChannel, aNetworkMonitor).
+ *        This method is invoked once for every new network request and it is
+ *        given the following arguments: the initial network request
+ *        information, and the channel. The third argument is the NetworkMonitor
+ *        instance.
+ *        onNetworkEvent() must return an object which holds several add*()
+ *        methods which are used to add further network request/response
+ *        information.
+ */
+function NetworkMonitor(aFilters, aOwner)
+{
+  if (aFilters) {
+    this.window = aFilters.window;
+    this.appId = aFilters.appId;
+    this.topFrame = aFilters.topFrame;
+  }
+  if (!this.window && !this.appId && !this.topFrame) {
+    this._logEverything = true;
+  }
+  this.owner = aOwner;
+  this.openRequests = {};
+  this.openResponses = {};
+  this._httpResponseExaminer =
+    DevToolsUtils.makeInfallible(this._httpResponseExaminer).bind(this);
+}
+exports.NetworkMonitor = NetworkMonitor;
+
+NetworkMonitor.prototype = {
+  _logEverything: false,
+  window: null,
+  appId: null,
+  topFrame: 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"
+  },
+
+  // 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. Disabled by
+   * default to save memory.
+   * @type boolean
+   */
+  saveRequestAndResponseBodies: false,
+
+  /**
+   * 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 NM_init()
+  {
+    this.responsePipeSegmentSize = Services.prefs
+                                   .getIntPref("network.buffer.cache.size");
+
+    gActivityDistributor.addObserver(this);
+
+    if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) {
+      Services.obs.addObserver(this._httpResponseExaminer,
+                               "http-on-examine-response", false);
+    }
+  },
+
+  /**
+   * Observe notifications for the http-on-examine-response topic, coming from
+   * the nsIObserverService.
+   *
+   * @private
+   * @param nsIHttpChannel aSubject
+   * @param string aTopic
+   * @returns void
+   */
+  _httpResponseExaminer: function NM__httpResponseExaminer(aSubject, aTopic)
+  {
+    // 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 || aTopic != "http-on-examine-response" ||
+        !(aSubject instanceof Ci.nsIHttpChannel)) {
+      return;
+    }
+
+    let channel = aSubject.QueryInterface(Ci.nsIHttpChannel);
+
+    if (!this._matchRequest(channel)) {
+      return;
+    }
+
+    let response = {
+      id: gSequenceId(),
+      channel: channel,
+      headers: [],
+      cookies: [],
+    };
+
+    let setCookieHeader = null;
+
+    channel.visitResponseHeaders({
+      visitHeader: function NM__visitHeader(aName, aValue) {
+        let lowerName = aName.toLowerCase();
+        if (lowerName == "set-cookie") {
+          setCookieHeader = aValue;
+        }
+        response.headers.push({ name: aName, value: aValue });
+      }
+    });
+
+    if (!response.headers.length) {
+      return; // No need to continue.
+    }
+
+    if (setCookieHeader) {
+      response.cookies = NetworkHelper.parseSetCookieHeader(setCookieHeader);
+    }
+
+    // Determine the HTTP version.
+    let httpVersionMaj = {};
+    let 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[response.id] = response;
+  },
+
+  /**
+   * Begin observing HTTP traffic that originates inside the current tab.
+   *
+   * @see https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIHttpActivityObserver
+   *
+   * @param nsIHttpChannel aChannel
+   * @param number aActivityType
+   * @param number aActivitySubtype
+   * @param number aTimestamp
+   * @param number aExtraSizeData
+   * @param string aExtraStringData
+   */
+  observeActivity: DevToolsUtils.makeInfallible(function NM_observeActivity(aChannel, aActivityType, aActivitySubtype, aTimestamp, aExtraSizeData, aExtraStringData)
+  {
+    if (!this.owner ||
+        aActivityType != gActivityDistributor.ACTIVITY_TYPE_HTTP_TRANSACTION &&
+        aActivityType != gActivityDistributor.ACTIVITY_TYPE_SOCKET_TRANSPORT) {
+      return;
+    }
+
+    if (!(aChannel instanceof Ci.nsIHttpChannel)) {
+      return;
+    }
+
+    aChannel = aChannel.QueryInterface(Ci.nsIHttpChannel);
+
+    if (aActivitySubtype ==
+        gActivityDistributor.ACTIVITY_SUBTYPE_REQUEST_HEADER) {
+      this._onRequestHeader(aChannel, aTimestamp, aExtraStringData);
+      return;
+    }
+
+    // Iterate over all currently ongoing requests. If aChannel can't
+    // be found within them, then exit this function.
+    let httpActivity = null;
+    for each (let item in this.openRequests) {
+      if (item.channel === aChannel) {
+        httpActivity = item;
+        break;
+      }
+    }
+
+    if (!httpActivity) {
+      return;
+    }
+
+    let transCodes = this.httpTransactionCodes;
+
+    // Store the time information for this activity subtype.
+    if (aActivitySubtype in transCodes) {
+      let stage = transCodes[aActivitySubtype];
+      if (stage in httpActivity.timings) {
+        httpActivity.timings[stage].last = aTimestamp;
+      }
+      else {
+        httpActivity.timings[stage] = {
+          first: aTimestamp,
+          last: aTimestamp,
+        };
+      }
+    }
+
+    switch (aActivitySubtype) {
+      case gActivityDistributor.ACTIVITY_SUBTYPE_REQUEST_BODY_SENT:
+        this._onRequestBodySent(httpActivity);
+        break;
+      case gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_HEADER:
+        this._onResponseHeader(httpActivity, aExtraStringData);
+        break;
+      case gActivityDistributor.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE:
+        this._onTransactionClose(httpActivity);
+        break;
+      default:
+        break;
+    }
+  }),
+
+  /**
+   * Check if a given network request should be logged by this network monitor
+   * instance based on the current filters.
+   *
+   * @private
+   * @param nsIHttpChannel aChannel
+   *        Request to check.
+   * @return boolean
+   *         True if the network request should be logged, false otherwise.
+   */
+  _matchRequest: function NM__matchRequest(aChannel)
+  {
+    if (this._logEverything) {
+      return true;
+    }
+
+    if (this.window) {
+      let win = NetworkHelper.getWindowForRequest(aChannel);
+      if (win && win.top === this.window) {
+        return true;
+      }
+    }
+
+    if (this.topFrame) {
+      let topFrame = NetworkHelper.getTopFrameForRequest(aChannel);
+      if (topFrame && topFrame === this.topFrame) {
+        return true;
+      }
+    }
+
+    if (this.appId) {
+      let appId = NetworkHelper.getAppIdForRequest(aChannel);
+      if (appId && appId == this.appId) {
+        return true;
+      }
+    }
+
+    return false;
+  },
+
+  /**
+   * 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 aChannel
+   * @param number aTimestamp
+   * @param string aExtraStringData
+   * @return void
+   */
+  _onRequestHeader:
+  function NM__onRequestHeader(aChannel, aTimestamp, aExtraStringData)
+  {
+    if (!this._matchRequest(aChannel)) {
+      return;
+    }
+
+    let win = NetworkHelper.getWindowForRequest(aChannel);
+    let httpActivity = this.createActivityObject(aChannel);
+
+    // see NM__onRequestBodySent()
+    httpActivity.charset = win ? win.document.characterSet : null;
+    httpActivity.private = win ? PrivateBrowsingUtils.isWindowPrivate(win) : false;
+
+    httpActivity.timings.REQUEST_HEADER = {
+      first: aTimestamp,
+      last: aTimestamp
+    };
+
+    let httpVersionMaj = {};
+    let httpVersionMin = {};
+    let event = {};
+    event.startedDateTime = new Date(Math.round(aTimestamp / 1000)).toISOString();
+    event.headersSize = aExtraStringData.length;
+    event.method = aChannel.requestMethod;
+    event.url = aChannel.URI.spec;
+    event.private = httpActivity.private;
+
+    // Determine if this is an XHR request.
+    try {
+      let callbacks = aChannel.notificationCallbacks;
+      let xhrRequest = callbacks ? callbacks.getInterface(Ci.nsIXMLHttpRequest) : null;
+      httpActivity.isXHR = event.isXHR = !!xhrRequest;
+    } catch (e) {
+      httpActivity.isXHR = event.isXHR = false;
+    }
+
+    // Determine the HTTP version.
+    aChannel.QueryInterface(Ci.nsIHttpChannelInternal);
+    aChannel.getRequestVersion(httpVersionMaj, httpVersionMin);
+
+    event.httpVersion = "HTTP/" + httpVersionMaj.value + "." +
+                                  httpVersionMin.value;
+
+    event.discardRequestBody = !this.saveRequestAndResponseBodies;
+    event.discardResponseBody = !this.saveRequestAndResponseBodies;
+
+    let headers = [];
+    let cookies = [];
+    let cookieHeader = null;
+
+    // Copy the request header data.
+    aChannel.visitRequestHeaders({
+      visitHeader: function NM__visitHeader(aName, aValue)
+      {
+        if (aName == "Cookie") {
+          cookieHeader = aValue;
+        }
+        headers.push({ name: aName, value: aValue });
+      }
+    });
+
+    if (cookieHeader) {
+      cookies = NetworkHelper.parseCookieHeader(cookieHeader);
+    }
+
+    httpActivity.owner = this.owner.onNetworkEvent(event, aChannel, this);
+
+    this._setupResponseListener(httpActivity);
+
+    this.openRequests[httpActivity.id] = httpActivity;
+
+    httpActivity.owner.addRequestHeaders(headers);
+    httpActivity.owner.addRequestCookies(cookies);
+  },
+
+  /**
+   * Create the empty HTTP activity object. 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.
+   *
+   * TODO: Bug 708717 - Add support for network log export to HAR
+   *
+   * @see http://www.softwareishard.com/blog/har-12-spec
+   * @param nsIHttpChannel aChannel
+   *        The HTTP channel for which the HTTP activity object is created.
+   * @return object
+   *         The new HTTP activity object.
+   */
+  createActivityObject: function NM_createActivityObject(aChannel)
+  {
+    return {
+      id: gSequenceId(),
+      channel: aChannel,
+      charset: null, // see NM__onRequestHeader()
+      url: aChannel.URI.spec,
+      discardRequestBody: !this.saveRequestAndResponseBodies,
+      discardResponseBody: !this.saveRequestAndResponseBodies,
+      timings: {}, // internal timing information, see NM_observeActivity()
+      responseStatus: null, // see NM__onResponseHeader()
+      owner: null, // the activity owner which is notified when changes happen
+    };
+  },
+
+  /**
+   * Setup the network response listener for the given HTTP activity. The
+   * NetworkResponseListener is responsible for storing the response body.
+   *
+   * @private
+   * @param object aHttpActivity
+   *        The HTTP activity object we are tracking.
+   */
+  _setupResponseListener: function NM__setupResponseListener(aHttpActivity)
+  {
+    let channel = aHttpActivity.channel;
+    channel.QueryInterface(Ci.nsITraceableChannel);
+
+    // 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.
+    let 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.
+    let newListener = new NetworkResponseListener(this, aHttpActivity);
+
+    // Remember the input stream, so it isn't released by GC.
+    newListener.inputStream = sink.inputStream;
+    newListener.sink = sink;
+
+    let tee = Cc["@mozilla.org/network/stream-listener-tee;1"].
+              createInstance(Ci.nsIStreamListenerTee);
+
+    let 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 aHttpActivity
+   *        The HTTP activity object we are working with.
+   */
+  _onRequestBodySent: function NM__onRequestBodySent(aHttpActivity)
+  {
+    if (aHttpActivity.discardRequestBody) {
+      return;
+    }
+
+    let sentBody = NetworkHelper.
+                   readPostTextFromRequest(aHttpActivity.channel,
+                                           aHttpActivity.charset);
+
+    if (!sentBody && this.window &&
+        aHttpActivity.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.
+      let webNav = this.window.QueryInterface(Ci.nsIInterfaceRequestor).
+                   getInterface(Ci.nsIWebNavigation);
+      sentBody = NetworkHelper.
+                 readPostTextFromPageViaWebNav(webNav, aHttpActivity.charset);
+    }
+
+    if (sentBody) {
+      aHttpActivity.owner.addRequestPostData({ text: sentBody });
+    }
+  },
+
+  /**
+   * Handler for ACTIVITY_SUBTYPE_RESPONSE_HEADER. This method stores
+   * information about the response headers.
+   *
+   * @private
+   * @param object aHttpActivity
+   *        The HTTP activity object we are working with.
+   * @param string aExtraStringData
+   *        The uncached response headers.
+   */
+  _onResponseHeader:
+  function NM__onResponseHeader(aHttpActivity, aExtraStringData)
+  {
+    // aExtraStringData 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() methood at this point sometimes causes an
+    // NS_ERROR_NOT_AVAILABLE exception.
+    //
+    // We could parse aExtraStringData 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().
+
+    let headers = aExtraStringData.split(/\r\n|\n|\r/);
+    let statusLine = headers.shift();
+    let statusLineArray = statusLine.split(" ");
+
+    let response = {};
+    response.httpVersion = statusLineArray.shift();
+    response.status = statusLineArray.shift();
+    response.statusText = statusLineArray.join(" ");
+    response.headersSize = aExtraStringData.length;
+
+    aHttpActivity.responseStatus = response.status;
+
+    // Discard the response body for known response statuses.
+    switch (parseInt(response.status)) {
+      case HTTP_MOVED_PERMANENTLY:
+      case HTTP_FOUND:
+      case HTTP_SEE_OTHER:
+      case HTTP_TEMPORARY_REDIRECT:
+        aHttpActivity.discardResponseBody = true;
+        break;
+    }
+
+    response.discardResponseBody = aHttpActivity.discardResponseBody;
+
+    aHttpActivity.owner.addResponseStart(response);
+  },
+
+  /**
+   * 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 aHttpActivity
+   *        The HTTP activity object we work with.
+   */
+  _onTransactionClose: function NM__onTransactionClose(aHttpActivity)
+  {
+    let result = this._setupHarTimings(aHttpActivity);
+    aHttpActivity.owner.addEventTimings(result.total, result.timings);
+    delete this.openRequests[aHttpActivity.id];
+  },
+
+  /**
+   * 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 aHttpActivity
+   *        The HTTP activity object we are working with.
+   * @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 NM__setupHarTimings(aHttpActivity)
+  {
+    let timings = aHttpActivity.timings;
+    let harTimings = {};
+
+    // Not clear how we can determine "blocked" time.
+    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 if (timings.STATUS_SENDING_TO) {
+      harTimings.connect = timings.STATUS_SENDING_TO.first -
+                           timings.REQUEST_HEADER.first;
+    }
+    else {
+      harTimings.connect = -1;
+    }
+
+    if ((timings.STATUS_WAITING_FOR || timings.STATUS_RECEIVING_FROM) &&
+        (timings.STATUS_CONNECTED_TO || timings.STATUS_SENDING_TO)) {
+      harTimings.send = (timings.STATUS_WAITING_FOR ||
+                         timings.STATUS_RECEIVING_FROM).first -
+                        (timings.STATUS_CONNECTED_TO ||
+                         timings.STATUS_SENDING_TO).last;
+    }
+    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;
+    }
+
+    let totalTime = 0;
+    for (let timing in harTimings) {
+      let time = Math.max(Math.round(harTimings[timing] / 1000), -1);
+      harTimings[timing] = time;
+      if (time > -1) {
+        totalTime += time;
+      }
+    }
+
+    return {
+      total: totalTime,
+      timings: harTimings,
+    };
+  },
+
+  /**
+   * Suspend Web Console activity. This is called when all Web Consoles are
+   * closed.
+   */
+  destroy: function NM_destroy()
+  {
+    if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) {
+      Services.obs.removeObserver(this._httpResponseExaminer,
+                                  "http-on-examine-response");
+    }
+
+    gActivityDistributor.removeObserver(this);
+
+    this.openRequests = {};
+    this.openResponses = {};
+    this.owner = null;
+    this.window = null;
+  },
+}; // NetworkMonitor.prototype
+
+
+/**
+ * The NetworkMonitorChild is used to proxy all of the network activity of the
+ * child app process from the main process. The child WebConsoleActor creates an
+ * instance of this object.
+ *
+ * Network requests for apps happen in the main process. As such,
+ * a NetworkMonitor instance is used by the WebappsActor in the main process to
+ * log the network requests for this child process.
+ *
+ * The main process creates NetworkEventActorProxy instances per request. These
+ * send the data to this object using the nsIMessageManager. Here we proxy the
+ * data to the WebConsoleActor or to a NetworkEventActor.
+ *
+ * @constructor
+ * @param number appId
+ *        The web appId of the child process.
+ * @param nsIMessageManager messageManager
+ *        The nsIMessageManager to use to communicate with the parent process.
+ * @param object owner
+ *        The WebConsoleActor that is listening for the network requests.
+ */
+function NetworkMonitorChild(appId, messageManager, owner) {
+  this.appId = appId;
+  this.owner = owner;
+  this._messageManager = messageManager;
+  this._onNewEvent = this._onNewEvent.bind(this);
+  this._onUpdateEvent = this._onUpdateEvent.bind(this);
+  this._netEvents = new Map();
+}
+exports.NetworkMonitorChild = NetworkMonitorChild;
+
+NetworkMonitorChild.prototype = {
+  appId: null,
+  owner: null,
+  _netEvents: null,
+  _saveRequestAndResponseBodies: false,
+
+  get saveRequestAndResponseBodies() {
+    return this._saveRequestAndResponseBodies;
+  },
+
+  set saveRequestAndResponseBodies(val) {
+    this._saveRequestAndResponseBodies = val;
+
+    this._messageManager.sendAsyncMessage("debug:netmonitor", {
+      appId: this.appId,
+      action: "setPreferences",
+      preferences: {
+        saveRequestAndResponseBodies: this._saveRequestAndResponseBodies,
+      },
+    });
+  },
+
+  init: function() {
+    this._messageManager.addMessageListener("debug:netmonitor:newEvent", this._onNewEvent);
+    this._messageManager.addMessageListener("debug:netmonitor:updateEvent", this._onUpdateEvent);
+    this._messageManager.sendAsyncMessage("debug:netmonitor", {
+      appId: this.appId,
+      action: "start",
+    });
+  },
+
+  _onNewEvent: DevToolsUtils.makeInfallible(function _onNewEvent(msg) {
+    let {id, event} = msg.data;
+    let actor = this.owner.onNetworkEvent(event);
+    this._netEvents.set(id, Cu.getWeakReference(actor));
+  }),
+
+  _onUpdateEvent: DevToolsUtils.makeInfallible(function _onUpdateEvent(msg) {
+    let {id, method, args} = msg.data;
+    let weakActor = this._netEvents.get(id);
+    let actor = weakActor ? weakActor.get() : null;
+    if (!actor) {
+      Cu.reportError("Received debug:netmonitor:updateEvent for unknown event ID: " + id);
+      return;
+    }
+    if (!(method in actor)) {
+      Cu.reportError("Received debug:netmonitor:updateEvent unsupported method: " + method);
+      return;
+    }
+    actor[method].apply(actor, args);
+  }),
+
+  destroy: function() {
+    this._messageManager.removeMessageListener("debug:netmonitor:newEvent", this._onNewEvent);
+    this._messageManager.removeMessageListener("debug:netmonitor:updateEvent", this._onUpdateEvent);
+    this._messageManager.sendAsyncMessage("debug:netmonitor", { action: "stop" });
+    this._netEvents.clear();
+    this._messageManager = null;
+    this.owner = null;
+  },
+}; // NetworkMonitorChild.prototype
+
+/**
+ * The NetworkEventActorProxy is used to send network request information from
+ * the main process to the child app process. One proxy is used per request.
+ * Similarly, one NetworkEventActor in the child app process is used per
+ * request. The client receives all network logs from the child actors.
+ *
+ * The child process has a NetworkMonitorChild instance that is listening for
+ * all network logging from the main process. The net monitor shim is used to
+ * proxy the data to the WebConsoleActor instance of the child process.
+ *
+ * @constructor
+ * @param nsIMessageManager messageManager
+ *        The message manager for the child app process. This is used for
+ *        communication with the NetworkMonitorChild instance of the process.
+ */
+function NetworkEventActorProxy(messageManager) {
+  this.id = gSequenceId();
+  this.messageManager = messageManager;
+}
+exports.NetworkEventActorProxy = NetworkEventActorProxy;
+
+NetworkEventActorProxy.methodFactory = function(method) {
+  return DevToolsUtils.makeInfallible(function() {
+    let args = Array.slice(arguments);
+    this.messageManager.sendAsyncMessage("debug:netmonitor:updateEvent", {
+      id: this.id,
+      method: method,
+      args: args,
+    });
+  }, "NetworkEventActorProxy." + method);
+};
+
+NetworkEventActorProxy.prototype = {
+  /**
+   * Initialize the network event. This method sends the network request event
+   * to the content process.
+   *
+   * @param object event
+   *        Object describing the network request.
+   * @return object
+   *         This object.
+   */
+  init: DevToolsUtils.makeInfallible(function(event)
+  {
+    this.messageManager.sendAsyncMessage("debug:netmonitor:newEvent", {
+      id: this.id,
+      event: event,
+    });
+    return this;
+  }),
+};
+
+(function() {
+  // Listeners for new network event data coming from the NetworkMonitor.
+  let methods = ["addRequestHeaders", "addRequestCookies", "addRequestPostData",
+                 "addResponseStart", "addResponseHeaders", "addResponseCookies",
+                 "addResponseContent", "addEventTimings"];
+  let factory = NetworkEventActorProxy.methodFactory;
+  for (let method of methods) {
+    NetworkEventActorProxy.prototype[method] = factory(method);
+  }
+})();
+
+
+/**
+ * The NetworkMonitor manager used by the Webapps actor in the main process.
+ * This object uses the message manager to listen for requests from the child
+ * process to start/stop the network monitor.
+ *
+ * @constructor
+ * @param nsIDOMElement frame
+ *        The browser frame to work with (mozbrowser).
+ */
+function NetworkMonitorManager(frame)
+{
+  let mm = frame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
+  this.messageManager = mm;
+  this.frame = frame;
+  this.onNetMonitorMessage = this.onNetMonitorMessage.bind(this);
+  this.onNetworkEvent = this.onNetworkEvent.bind(this);
+
+  mm.addMessageListener("debug:netmonitor", this.onNetMonitorMessage);
+}
+exports.NetworkMonitorManager = NetworkMonitorManager;
+
+NetworkMonitorManager.prototype = {
+  netMonitor: null,
+  frame: null,
+  messageManager: null,
+
+  /**
+   * Handler for "debug:monitor" messages received through the message manager
+   * from the content process.
+   *
+   * @param object msg
+   *        Message from the content.
+   */
+  onNetMonitorMessage: DevToolsUtils.makeInfallible(function _onNetMonitorMessage(msg) {
+    let { action, appId } = msg.json;
+
+    // Pipe network monitor data from parent to child via the message manager.
+    switch (action) {
+      case "start": {
+        if (!this.netMonitor) {
+          this.netMonitor = new NetworkMonitor({
+            topFrame: this.frame,
+            appId: appId,
+          }, this);
+          this.netMonitor.init();
+        }
+        break;
+      }
+
+      case "setPreferences": {
+        let {preferences} = msg.json;
+        for (let key of Object.keys(preferences)) {
+          if (key == "saveRequestAndResponseBodies" && this.netMonitor) {
+            this.netMonitor.saveRequestAndResponseBodies = preferences[key];
+          }
+        }
+        break;
+      }
+
+      case "stop": {
+        if (this.netMonitor) {
+          this.netMonitor.destroy();
+          this.netMonitor = null;
+        }
+        break;
+      }
+    }
+  }),
+
+  /**
+   * Handler for new network requests. This method is invoked by the current
+   * NetworkMonitor instance.
+   *
+   * @param object event
+   *        Object describing the network request.
+   * @return object
+   *         A NetworkEventActorProxy instance which is notified when further
+   *         data about the request is available.
+   */
+  onNetworkEvent: DevToolsUtils.makeInfallible(function _onNetworkEvent(event) {
+    return new NetworkEventActorProxy(this.messageManager).init(event);
+  }),
+
+  destroy: function()
+  {
+    this.messageManager.removeMessageListener("debug:netmonitor",
+                                              this.onNetMonitorMessage);
+    this.messageManager = null;
+    this.filters = null;
+
+    if (this.netMonitor) {
+      this.netMonitor.destroy();
+      this.netMonitor = null;
+    }
+  },
+}; // NetworkMonitorManager.prototype
+
+
+/**
+ * A WebProgressListener that listens for location changes.
+ *
+ * This progress listener is used to track file loads and other kinds of
+ * location changes.
+ *
+ * @constructor
+ * @param object aWindow
+ *        The window for which we need to track location changes.
+ * @param object aOwner
+ *        The listener owner which needs to implement two methods:
+ *        - onFileActivity(aFileURI)
+ *        - onLocationChange(aState, aTabURI, aPageTitle)
+ */
+function ConsoleProgressListener(aWindow, aOwner)
+{
+  this.window = aWindow;
+  this.owner = aOwner;
+}
+exports.ConsoleProgressListener = ConsoleProgressListener;
+
+ConsoleProgressListener.prototype = {
+  /**
+   * Constant used for startMonitor()/stopMonitor() that tells you want to
+   * monitor file loads.
+   */
+  MONITOR_FILE_ACTIVITY: 1,
+
+  /**
+   * Constant used for startMonitor()/stopMonitor() that tells you want to
+   * monitor page location changes.
+   */
+  MONITOR_LOCATION_CHANGE: 2,
+
+  /**
+   * Tells if you want to monitor file activity.
+   * @private
+   * @type boolean
+   */
+  _fileActivity: false,
+
+  /**
+   * Tells if you want to monitor location changes.
+   * @private
+   * @type boolean
+   */
+  _locationChange: false,
+
+  /**
+   * Tells if the console progress listener is initialized or not.
+   * @private
+   * @type boolean
+   */
+  _initialized: false,
+
+  _webProgress: null,
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+                                         Ci.nsISupportsWeakReference]),
+
+  /**
+   * Initialize the ConsoleProgressListener.
+   * @private
+   */
+  _init: function CPL__init()
+  {
+    if (this._initialized) {
+      return;
+    }
+
+    this._webProgress = this.window.QueryInterface(Ci.nsIInterfaceRequestor)
+                        .getInterface(Ci.nsIWebNavigation)
+                        .QueryInterface(Ci.nsIWebProgress);
+    this._webProgress.addProgressListener(this,
+                                          Ci.nsIWebProgress.NOTIFY_STATE_ALL);
+
+    this._initialized = true;
+  },
+
+  /**
+   * Start a monitor/tracker related to the current nsIWebProgressListener
+   * instance.
+   *
+   * @param number aMonitor
+   *        Tells what you want to track. Available constants:
+   *        - this.MONITOR_FILE_ACTIVITY
+   *          Track file loads.
+   *        - this.MONITOR_LOCATION_CHANGE
+   *          Track location changes for the top window.
+   */
+  startMonitor: function CPL_startMonitor(aMonitor)
+  {
+    switch (aMonitor) {
+      case this.MONITOR_FILE_ACTIVITY:
+        this._fileActivity = true;
+        break;
+      case this.MONITOR_LOCATION_CHANGE:
+        this._locationChange = true;
+        break;
+      default:
+        throw new Error("ConsoleProgressListener: unknown monitor type " +
+                        aMonitor + "!");
+    }
+    this._init();
+  },
+
+  /**
+   * Stop a monitor.
+   *
+   * @param number aMonitor
+   *        Tells what you want to stop tracking. See this.startMonitor() for
+   *        the list of constants.
+   */
+  stopMonitor: function CPL_stopMonitor(aMonitor)
+  {
+    switch (aMonitor) {
+      case this.MONITOR_FILE_ACTIVITY:
+        this._fileActivity = false;
+        break;
+      case this.MONITOR_LOCATION_CHANGE:
+        this._locationChange = false;
+        break;
+      default:
+        throw new Error("ConsoleProgressListener: unknown monitor type " +
+                        aMonitor + "!");
+    }
+
+    if (!this._fileActivity && !this._locationChange) {
+      this.destroy();
+    }
+  },
+
+  onStateChange:
+  function CPL_onStateChange(aProgress, aRequest, aState, aStatus)
+  {
+    if (!this.owner) {
+      return;
+    }
+
+    if (this._fileActivity) {
+      this._checkFileActivity(aProgress, aRequest, aState, aStatus);
+    }
+
+    if (this._locationChange) {
+      this._checkLocationChange(aProgress, aRequest, aState, aStatus);
+    }
+  },
+
+  /**
+   * Check if there is any file load, given the arguments of
+   * nsIWebProgressListener.onStateChange. If the state change tells that a file
+   * URI has been loaded, then the remote Web Console instance is notified.
+   * @private
+   */
+  _checkFileActivity:
+  function CPL__checkFileActivity(aProgress, aRequest, aState, aStatus)
+  {
+    if (!(aState & Ci.nsIWebProgressListener.STATE_START)) {
+      return;
+    }
+
+    let uri = null;
+    if (aRequest instanceof Ci.imgIRequest) {
+      let imgIRequest = aRequest.QueryInterface(Ci.imgIRequest);
+      uri = imgIRequest.URI;
+    }
+    else if (aRequest instanceof Ci.nsIChannel) {
+      let nsIChannel = aRequest.QueryInterface(Ci.nsIChannel);
+      uri = nsIChannel.URI;
+    }
+
+    if (!uri || !uri.schemeIs("file") && !uri.schemeIs("ftp")) {
+      return;
+    }
+
+    this.owner.onFileActivity(uri.spec);
+  },
+
+  /**
+   * Check if the current window.top location is changing, given the arguments
+   * of nsIWebProgressListener.onStateChange. If that is the case, the remote
+   * Web Console instance is notified.
+   * @private
+   */
+  _checkLocationChange:
+  function CPL__checkLocationChange(aProgress, aRequest, aState, aStatus)
+  {
+    let isStart = aState & Ci.nsIWebProgressListener.STATE_START;
+    let isStop = aState & Ci.nsIWebProgressListener.STATE_STOP;
+    let isNetwork = aState & Ci.nsIWebProgressListener.STATE_IS_NETWORK;
+    let isWindow = aState & Ci.nsIWebProgressListener.STATE_IS_WINDOW;
+
+    // Skip non-interesting states.
+    if (!isNetwork || !isWindow || aProgress.DOMWindow != this.window) {
+      return;
+    }
+
+    if (isStart && aRequest instanceof Ci.nsIChannel) {
+      this.owner.onLocationChange("start", aRequest.URI.spec, "");
+    }
+    else if (isStop) {
+      this.owner.onLocationChange("stop", this.window.location.href,
+                                  this.window.document.title);
+    }
+  },
+
+  onLocationChange: function() {},
+  onStatusChange: function() {},
+  onProgressChange: function() {},
+  onSecurityChange: function() {},
+
+  /**
+   * Destroy the ConsoleProgressListener.
+   */
+  destroy: function CPL_destroy()
+  {
+    if (!this._initialized) {
+      return;
+    }
+
+    this._initialized = false;
+    this._fileActivity = false;
+    this._locationChange = false;
+
+    try {
+      this._webProgress.removeProgressListener(this);
+    }
+    catch (ex) {
+      // This can throw during browser shutdown.
+    }
+
+    this._webProgress = null;
+    this.window = null;
+    this.owner = null;
+  },
+}; // ConsoleProgressListener.prototype
+
+function gSequenceId() { return gSequenceId.n++; }
+gSequenceId.n = 1;
--- a/toolkit/devtools/webconsole/utils.js
+++ b/toolkit/devtools/webconsole/utils.js
@@ -5,24 +5,18 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {Cc, Ci, Cu, components} = require("chrome");
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
-loader.lazyGetter(this, "NetworkHelper", () => require("devtools/toolkit/webconsole/network-helper"));
 loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
-loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
-loader.lazyImporter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm");
 loader.lazyImporter(this, "LayoutHelpers", "resource://gre/modules/devtools/LayoutHelpers.jsm");
-loader.lazyServiceGetter(this, "gActivityDistributor",
-                         "@mozilla.org/network/http-activity-distributor;1",
-                         "nsIHttpActivityDistributor");
 
 // TODO: Bug 842672 - toolkit/ imports modules from browser/.
 // Note that these are only used in JSTermHelpers, see $0 and pprint().
 loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
 loader.lazyImporter(this, "devtools", "resource://gre/modules/devtools/Loader.jsm");
 loader.lazyImporter(this, "VariablesView", "resource:///modules/devtools/VariablesView.jsm");
 loader.lazyImporter(this, "DevToolsUtils", "resource://gre/modules/devtools/DevToolsUtils.jsm");
 
@@ -1672,1167 +1666,16 @@ function JSTermHelpers(aOwner)
   aOwner.sandbox.print = function JSTH_print(aString)
   {
     aOwner.helperResult = { rawOutput: true };
     return String(aString);
   };
 }
 exports.JSTermHelpers = JSTermHelpers;
 
-(function(WCU) {
-///////////////////////////////////////////////////////////////////////////////
-// 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 maximum number of bytes a NetworkResponseListener can hold.
-const RESPONSE_BODY_LIMIT = 1048576; // 1 MB
-
-/**
- * The network response listener implements the nsIStreamListener and
- * nsIRequestObserver interfaces. This is used within the NetworkMonitor feature
- * to get the response body of the request.
- *
- * The code is mostly based on code listings from:
- *
- *   http://www.softwareishard.com/blog/firebug/
- *      nsitraceablechannel-intercept-http-traffic/
- *
- * @constructor
- * @param object aOwner
- *        The response listener owner. This object needs to hold the
- *        |openResponses| object.
- * @param object aHttpActivity
- *        HttpActivity object associated with this request. See NetworkMonitor
- *        for more information.
- */
-function NetworkResponseListener(aOwner, aHttpActivity)
-{
-  this.owner = aOwner;
-  this.receivedData = "";
-  this.httpActivity = aHttpActivity;
-  this.bodySize = 0;
-}
-
-NetworkResponseListener.prototype = {
-  QueryInterface:
-    XPCOMUtils.generateQI([Ci.nsIStreamListener, Ci.nsIInputStreamCallback,
-                           Ci.nsIRequestObserver, Ci.nsISupports]),
-
-  /**
-   * This NetworkResponseListener tracks the NetworkMonitor.openResponses object
-   * to find the associated uncached headers.
-   * @private
-   */
-  _foundOpenResponse: false,
-
-  /**
-   * The response listener owner.
-   */
-  owner: null,
-
-  /**
-   * The response will be written into the outputStream of this nsIPipe.
-   * Both ends of the pipe must be blocking.
-   */
-  sink: null,
-
-  /**
-   * The HttpActivity object associated with this response.
-   */
-  httpActivity: null,
-
-  /**
-   * Stores the received data as a string.
-   */
-  receivedData: null,
-
-  /**
-   * The network response body size.
-   */
-  bodySize: null,
-
-  /**
-   * The nsIRequest we are started for.
-   */
-  request: null,
-
-  /**
-   * Set the async listener for the given nsIAsyncInputStream. This allows us to
-   * wait asynchronously for any data coming from the stream.
-   *
-   * @param nsIAsyncInputStream aStream
-   *        The input stream from where we are waiting for data to come in.
-   * @param nsIInputStreamCallback aListener
-   *        The input stream callback you want. This is an object that must have
-   *        the onInputStreamReady() method. If the argument is null, then the
-   *        current callback is removed.
-   * @return void
-   */
-  setAsyncListener: function NRL_setAsyncListener(aStream, aListener)
-  {
-    // Asynchronously wait for the stream to be readable or closed.
-    aStream.asyncWait(aListener, 0, 0, Services.tm.mainThread);
-  },
-
-  /**
-   * Stores the received data, if request/response body logging is enabled. It
-   * also does limit the number of stored bytes, based on the
-   * RESPONSE_BODY_LIMIT constant.
-   *
-   * Learn more about nsIStreamListener at:
-   * https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIStreamListener
-   *
-   * @param nsIRequest aRequest
-   * @param nsISupports aContext
-   * @param nsIInputStream aInputStream
-   * @param unsigned long aOffset
-   * @param unsigned long aCount
-   */
-  onDataAvailable:
-  function NRL_onDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount)
-  {
-    this._findOpenResponse();
-    let data = NetUtil.readInputStreamToString(aInputStream, aCount);
-
-    this.bodySize += aCount;
-
-    if (!this.httpActivity.discardResponseBody &&
-        this.receivedData.length < RESPONSE_BODY_LIMIT) {
-      this.receivedData += NetworkHelper.
-                           convertToUnicode(data, aRequest.contentCharset);
-    }
-  },
-
-  /**
-   * See documentation at
-   * https://developer.mozilla.org/En/NsIRequestObserver
-   *
-   * @param nsIRequest aRequest
-   * @param nsISupports aContext
-   */
-  onStartRequest: function NRL_onStartRequest(aRequest)
-  {
-    this.request = aRequest;
-    this._findOpenResponse();
-    // Asynchronously wait for the data coming from the request.
-    this.setAsyncListener(this.sink.inputStream, this);
-  },
-
-  /**
-   * Handle the onStopRequest by closing the sink output stream.
-   *
-   * For more documentation about nsIRequestObserver go to:
-   * https://developer.mozilla.org/En/NsIRequestObserver
-   */
-  onStopRequest: function NRL_onStopRequest()
-  {
-    this._findOpenResponse();
-    this.sink.outputStream.close();
-  },
-
-  /**
-   * Find the open response object associated to the current request. The
-   * NetworkMonitor._httpResponseExaminer() method saves the response headers in
-   * NetworkMonitor.openResponses. This method takes the data from the open
-   * response object and puts it into the HTTP activity object, then sends it to
-   * the remote Web Console instance.
-   *
-   * @private
-   */
-  _findOpenResponse: function NRL__findOpenResponse()
-  {
-    if (!this.owner || this._foundOpenResponse) {
-      return;
-    }
-
-    let openResponse = null;
-
-    for each (let item in this.owner.openResponses) {
-      if (item.channel === this.httpActivity.channel) {
-        openResponse = item;
-        break;
-      }
-    }
-
-    if (!openResponse) {
-      return;
-    }
-    this._foundOpenResponse = true;
-
-    delete this.owner.openResponses[openResponse.id];
-
-    this.httpActivity.owner.addResponseHeaders(openResponse.headers);
-    this.httpActivity.owner.addResponseCookies(openResponse.cookies);
-  },
-
-  /**
-   * Clean up the response listener once the response input stream is closed.
-   * This is called from onStopRequest() or from onInputStreamReady() when the
-   * stream is closed.
-   * @return void
-   */
-  onStreamClose: function NRL_onStreamClose()
-  {
-    if (!this.httpActivity) {
-      return;
-    }
-    // Remove our listener from the request input stream.
-    this.setAsyncListener(this.sink.inputStream, null);
-
-    this._findOpenResponse();
-
-    if (!this.httpActivity.discardResponseBody && this.receivedData.length) {
-      this._onComplete(this.receivedData);
-    }
-    else if (!this.httpActivity.discardResponseBody &&
-             this.httpActivity.responseStatus == 304) {
-      // Response is cached, so we load it from cache.
-      let charset = this.request.contentCharset || this.httpActivity.charset;
-      NetworkHelper.loadFromCache(this.httpActivity.url, charset,
-                                  this._onComplete.bind(this));
-    }
-    else {
-      this._onComplete();
-    }
-  },
-
-  /**
-   * Handler for when the response completes. This function cleans up the
-   * response listener.
-   *
-   * @param string [aData]
-   *        Optional, the received data coming from the response listener or
-   *        from the cache.
-   */
-  _onComplete: function NRL__onComplete(aData)
-  {
-    let response = {
-      mimeType: "",
-      text: aData || "",
-    };
-
-    response.size = response.text.length;
-
-    try {
-      response.mimeType = this.request.contentType;
-    }
-    catch (ex) { }
-
-    if (!response.mimeType || !NetworkHelper.isTextMimeType(response.mimeType)) {
-      response.encoding = "base64";
-      response.text = btoa(response.text);
-    }
-
-    if (response.mimeType && this.request.contentCharset) {
-      response.mimeType += "; charset=" + this.request.contentCharset;
-    }
-
-    this.receivedData = "";
-
-    this.httpActivity.owner.
-      addResponseContent(response, this.httpActivity.discardResponseBody);
-
-    this.httpActivity.channel = null;
-    this.httpActivity.owner = null;
-    this.httpActivity = null;
-    this.sink = null;
-    this.inputStream = null;
-    this.request = null;
-    this.owner = null;
-  },
-
-  /**
-   * The nsIInputStreamCallback for when the request input stream is ready -
-   * either it has more data or it is closed.
-   *
-   * @param nsIAsyncInputStream aStream
-   *        The sink input stream from which data is coming.
-   * @returns void
-   */
-  onInputStreamReady: function NRL_onInputStreamReady(aStream)
-  {
-    if (!(aStream instanceof Ci.nsIAsyncInputStream) || !this.httpActivity) {
-      return;
-    }
-
-    let available = -1;
-    try {
-      // This may throw if the stream is closed normally or due to an error.
-      available = aStream.available();
-    }
-    catch (ex) { }
-
-    if (available != -1) {
-      if (available != 0) {
-        // Note that passing 0 as the offset here is wrong, but the
-        // onDataAvailable() method does not use the offset, so it does not
-        // matter.
-        this.onDataAvailable(this.request, null, aStream, 0, available);
-      }
-      this.setAsyncListener(aStream, this);
-    }
-    else {
-      this.onStreamClose();
-    }
-  },
-};
-
-/**
- * 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 nsIDOMWindow aWindow
- *        Optional, the window that we monitor network requests for. If no
- *        window is given, all browser network requests are logged.
- * @param object aOwner
- *        The network monitor owner. This object needs to hold:
- *        - onNetworkEvent(aRequestInfo, aChannel). This method is invoked once for
- *        every new network request and it is given two arguments: the initial network
- *        request information, and the channel. onNetworkEvent() must return an object
- *        which holds several add*() methods which are used to add further network
- *        request/response information.
- *        - saveRequestAndResponseBodies property which tells if you want to log
- *        request and response bodies.
- */
-function NetworkMonitor(aWindow, aOwner)
-{
-  this.window = aWindow;
-  this.owner = aOwner;
-  this.openRequests = {};
-  this.openResponses = {};
-  this._httpResponseExaminer = this._httpResponseExaminer.bind(this);
-}
-
-NetworkMonitor.prototype = {
-  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"
-  },
-
-  // 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. Disabled by
-   * default to save memory.
-   */
-  get saveRequestAndResponseBodies()
-    this.owner && this.owner.saveRequestAndResponseBodies,
-
-  /**
-   * 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 NM_init()
-  {
-    this.responsePipeSegmentSize = Services.prefs
-                                   .getIntPref("network.buffer.cache.size");
-
-    gActivityDistributor.addObserver(this);
-
-    if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) {
-      Services.obs.addObserver(this._httpResponseExaminer,
-                               "http-on-examine-response", false);
-    }
-  },
-
-  /**
-   * Observe notifications for the http-on-examine-response topic, coming from
-   * the nsIObserverService.
-   *
-   * @private
-   * @param nsIHttpChannel aSubject
-   * @param string aTopic
-   * @returns void
-   */
-  _httpResponseExaminer: function NM__httpResponseExaminer(aSubject, aTopic)
-  {
-    // 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 || aTopic != "http-on-examine-response" ||
-        !(aSubject instanceof Ci.nsIHttpChannel)) {
-      return;
-    }
-
-    let channel = aSubject.QueryInterface(Ci.nsIHttpChannel);
-
-    if (this.window) {
-      // Try to get the source window of the request.
-      let win = NetworkHelper.getWindowForRequest(channel);
-      if (!win || win.top !== this.window) {
-        return;
-      }
-    }
-
-    let response = {
-      id: gSequenceId(),
-      channel: channel,
-      headers: [],
-      cookies: [],
-    };
-
-    let setCookieHeader = null;
-
-    channel.visitResponseHeaders({
-      visitHeader: function NM__visitHeader(aName, aValue) {
-        let lowerName = aName.toLowerCase();
-        if (lowerName == "set-cookie") {
-          setCookieHeader = aValue;
-        }
-        response.headers.push({ name: aName, value: aValue });
-      }
-    });
-
-    if (!response.headers.length) {
-      return; // No need to continue.
-    }
-
-    if (setCookieHeader) {
-      response.cookies = NetworkHelper.parseSetCookieHeader(setCookieHeader);
-    }
-
-    // Determine the HTTP version.
-    let httpVersionMaj = {};
-    let 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[response.id] = response;
-  },
-
-  /**
-   * Begin observing HTTP traffic that originates inside the current tab.
-   *
-   * @see https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIHttpActivityObserver
-   *
-   * @param nsIHttpChannel aChannel
-   * @param number aActivityType
-   * @param number aActivitySubtype
-   * @param number aTimestamp
-   * @param number aExtraSizeData
-   * @param string aExtraStringData
-   */
-  observeActivity:
-  function NM_observeActivity(aChannel, aActivityType, aActivitySubtype,
-                              aTimestamp, aExtraSizeData, aExtraStringData)
-  {
-    if (!this.owner ||
-        aActivityType != gActivityDistributor.ACTIVITY_TYPE_HTTP_TRANSACTION &&
-        aActivityType != gActivityDistributor.ACTIVITY_TYPE_SOCKET_TRANSPORT) {
-      return;
-    }
-
-    if (!(aChannel instanceof Ci.nsIHttpChannel)) {
-      return;
-    }
-
-    aChannel = aChannel.QueryInterface(Ci.nsIHttpChannel);
-
-    if (aActivitySubtype ==
-        gActivityDistributor.ACTIVITY_SUBTYPE_REQUEST_HEADER) {
-      this._onRequestHeader(aChannel, aTimestamp, aExtraStringData);
-      return;
-    }
-
-    // Iterate over all currently ongoing requests. If aChannel can't
-    // be found within them, then exit this function.
-    let httpActivity = null;
-    for each (let item in this.openRequests) {
-      if (item.channel === aChannel) {
-        httpActivity = item;
-        break;
-      }
-    }
-
-    if (!httpActivity) {
-      return;
-    }
-
-    let transCodes = this.httpTransactionCodes;
-
-    // Store the time information for this activity subtype.
-    if (aActivitySubtype in transCodes) {
-      let stage = transCodes[aActivitySubtype];
-      if (stage in httpActivity.timings) {
-        httpActivity.timings[stage].last = aTimestamp;
-      }
-      else {
-        httpActivity.timings[stage] = {
-          first: aTimestamp,
-          last: aTimestamp,
-        };
-      }
-    }
-
-    switch (aActivitySubtype) {
-      case gActivityDistributor.ACTIVITY_SUBTYPE_REQUEST_BODY_SENT:
-        this._onRequestBodySent(httpActivity);
-        break;
-      case gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_HEADER:
-        this._onResponseHeader(httpActivity, aExtraStringData);
-        break;
-      case gActivityDistributor.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE:
-        this._onTransactionClose(httpActivity);
-        break;
-      default:
-        break;
-    }
-  },
-
-  /**
-   * 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 aChannel
-   * @param number aTimestamp
-   * @param string aExtraStringData
-   * @return void
-   */
-  _onRequestHeader:
-  function NM__onRequestHeader(aChannel, aTimestamp, aExtraStringData)
-  {
-    let win = NetworkHelper.getWindowForRequest(aChannel);
-
-    // Try to get the source window of the request.
-    if (this.window && (!win || win.top !== this.window)) {
-      return;
-    }
-
-    let httpActivity = this.createActivityObject(aChannel);
-
-    // see NM__onRequestBodySent()
-    httpActivity.charset = win ? win.document.characterSet : null;
-    httpActivity.private = win ? PrivateBrowsingUtils.isWindowPrivate(win) : false;
-
-    httpActivity.timings.REQUEST_HEADER = {
-      first: aTimestamp,
-      last: aTimestamp
-    };
-
-    let httpVersionMaj = {};
-    let httpVersionMin = {};
-    let event = {};
-    event.startedDateTime = new Date(Math.round(aTimestamp / 1000)).toISOString();
-    event.headersSize = aExtraStringData.length;
-    event.method = aChannel.requestMethod;
-    event.url = aChannel.URI.spec;
-    event.private = httpActivity.private;
-
-    // Determine if this is an XHR request.
-    try {
-      let callbacks = aChannel.notificationCallbacks;
-      let xhrRequest = callbacks ? callbacks.getInterface(Ci.nsIXMLHttpRequest) : null;
-      httpActivity.isXHR = event.isXHR = !!xhrRequest;
-    } catch (e) {
-      httpActivity.isXHR = event.isXHR = false;
-    }
-
-    // Determine the HTTP version.
-    aChannel.QueryInterface(Ci.nsIHttpChannelInternal);
-    aChannel.getRequestVersion(httpVersionMaj, httpVersionMin);
-
-    event.httpVersion = "HTTP/" + httpVersionMaj.value + "." +
-                                  httpVersionMin.value;
-
-    event.discardRequestBody = !this.saveRequestAndResponseBodies;
-    event.discardResponseBody = !this.saveRequestAndResponseBodies;
-
-    let headers = [];
-    let cookies = [];
-    let cookieHeader = null;
-
-    // Copy the request header data.
-    aChannel.visitRequestHeaders({
-      visitHeader: function NM__visitHeader(aName, aValue)
-      {
-        if (aName == "Cookie") {
-          cookieHeader = aValue;
-        }
-        headers.push({ name: aName, value: aValue });
-      }
-    });
-
-    if (cookieHeader) {
-      cookies = NetworkHelper.parseCookieHeader(cookieHeader);
-    }
-
-    httpActivity.owner = this.owner.onNetworkEvent(event, aChannel);
-
-    this._setupResponseListener(httpActivity);
-
-    this.openRequests[httpActivity.id] = httpActivity;
-
-    httpActivity.owner.addRequestHeaders(headers);
-    httpActivity.owner.addRequestCookies(cookies);
-  },
-
-  /**
-   * Create the empty HTTP activity object. 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.
-   *
-   * TODO: Bug 708717 - Add support for network log export to HAR
-   *
-   * @see http://www.softwareishard.com/blog/har-12-spec
-   * @param nsIHttpChannel aChannel
-   *        The HTTP channel for which the HTTP activity object is created.
-   * @return object
-   *         The new HTTP activity object.
-   */
-  createActivityObject: function NM_createActivityObject(aChannel)
-  {
-    return {
-      id: gSequenceId(),
-      channel: aChannel,
-      charset: null, // see NM__onRequestHeader()
-      url: aChannel.URI.spec,
-      discardRequestBody: !this.saveRequestAndResponseBodies,
-      discardResponseBody: !this.saveRequestAndResponseBodies,
-      timings: {}, // internal timing information, see NM_observeActivity()
-      responseStatus: null, // see NM__onResponseHeader()
-      owner: null, // the activity owner which is notified when changes happen
-    };
-  },
-
-  /**
-   * Setup the network response listener for the given HTTP activity. The
-   * NetworkResponseListener is responsible for storing the response body.
-   *
-   * @private
-   * @param object aHttpActivity
-   *        The HTTP activity object we are tracking.
-   */
-  _setupResponseListener: function NM__setupResponseListener(aHttpActivity)
-  {
-    let channel = aHttpActivity.channel;
-    channel.QueryInterface(Ci.nsITraceableChannel);
-
-    // 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.
-    let 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.
-    let newListener = new NetworkResponseListener(this, aHttpActivity);
-
-    // Remember the input stream, so it isn't released by GC.
-    newListener.inputStream = sink.inputStream;
-    newListener.sink = sink;
-
-    let tee = Cc["@mozilla.org/network/stream-listener-tee;1"].
-              createInstance(Ci.nsIStreamListenerTee);
-
-    let 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 aHttpActivity
-   *        The HTTP activity object we are working with.
-   */
-  _onRequestBodySent: function NM__onRequestBodySent(aHttpActivity)
-  {
-    if (aHttpActivity.discardRequestBody) {
-      return;
-    }
-
-    let sentBody = NetworkHelper.
-                   readPostTextFromRequest(aHttpActivity.channel,
-                                           aHttpActivity.charset);
-
-    if (!sentBody && this.window &&
-        aHttpActivity.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.
-      let webNav = this.window.QueryInterface(Ci.nsIInterfaceRequestor).
-                   getInterface(Ci.nsIWebNavigation);
-      sentBody = NetworkHelper.
-                 readPostTextFromPageViaWebNav(webNav, aHttpActivity.charset);
-    }
-
-    if (sentBody) {
-      aHttpActivity.owner.addRequestPostData({ text: sentBody });
-    }
-  },
-
-  /**
-   * Handler for ACTIVITY_SUBTYPE_RESPONSE_HEADER. This method stores
-   * information about the response headers.
-   *
-   * @private
-   * @param object aHttpActivity
-   *        The HTTP activity object we are working with.
-   * @param string aExtraStringData
-   *        The uncached response headers.
-   */
-  _onResponseHeader:
-  function NM__onResponseHeader(aHttpActivity, aExtraStringData)
-  {
-    // aExtraStringData 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() methood at this point sometimes causes an
-    // NS_ERROR_NOT_AVAILABLE exception.
-    //
-    // We could parse aExtraStringData 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().
-
-    let headers = aExtraStringData.split(/\r\n|\n|\r/);
-    let statusLine = headers.shift();
-    let statusLineArray = statusLine.split(" ");
-
-    let response = {};
-    response.httpVersion = statusLineArray.shift();
-    response.status = statusLineArray.shift();
-    response.statusText = statusLineArray.join(" ");
-    response.headersSize = aExtraStringData.length;
-
-    aHttpActivity.responseStatus = response.status;
-
-    // Discard the response body for known response statuses.
-    switch (parseInt(response.status)) {
-      case HTTP_MOVED_PERMANENTLY:
-      case HTTP_FOUND:
-      case HTTP_SEE_OTHER:
-      case HTTP_TEMPORARY_REDIRECT:
-        aHttpActivity.discardResponseBody = true;
-        break;
-    }
-
-    response.discardResponseBody = aHttpActivity.discardResponseBody;
-
-    aHttpActivity.owner.addResponseStart(response);
-  },
-
-  /**
-   * 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 aHttpActivity
-   *        The HTTP activity object we work with.
-   */
-  _onTransactionClose: function NM__onTransactionClose(aHttpActivity)
-  {
-    let result = this._setupHarTimings(aHttpActivity);
-    aHttpActivity.owner.addEventTimings(result.total, result.timings);
-    delete this.openRequests[aHttpActivity.id];
-  },
-
-  /**
-   * 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 aHttpActivity
-   *        The HTTP activity object we are working with.
-   * @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 NM__setupHarTimings(aHttpActivity)
-  {
-    let timings = aHttpActivity.timings;
-    let harTimings = {};
-
-    // Not clear how we can determine "blocked" time.
-    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 if (timings.STATUS_SENDING_TO) {
-      harTimings.connect = timings.STATUS_SENDING_TO.first -
-                           timings.REQUEST_HEADER.first;
-    }
-    else {
-      harTimings.connect = -1;
-    }
-
-    if ((timings.STATUS_WAITING_FOR || timings.STATUS_RECEIVING_FROM) &&
-        (timings.STATUS_CONNECTED_TO || timings.STATUS_SENDING_TO)) {
-      harTimings.send = (timings.STATUS_WAITING_FOR ||
-                         timings.STATUS_RECEIVING_FROM).first -
-                        (timings.STATUS_CONNECTED_TO ||
-                         timings.STATUS_SENDING_TO).last;
-    }
-    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;
-    }
-
-    let totalTime = 0;
-    for (let timing in harTimings) {
-      let time = Math.max(Math.round(harTimings[timing] / 1000), -1);
-      harTimings[timing] = time;
-      if (time > -1) {
-        totalTime += time;
-      }
-    }
-
-    return {
-      total: totalTime,
-      timings: harTimings,
-    };
-  },
-
-  /**
-   * Suspend Web Console activity. This is called when all Web Consoles are
-   * closed.
-   */
-  destroy: function NM_destroy()
-  {
-    if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) {
-      Services.obs.removeObserver(this._httpResponseExaminer,
-                                  "http-on-examine-response");
-    }
-
-    gActivityDistributor.removeObserver(this);
-
-    this.openRequests = {};
-    this.openResponses = {};
-    this.owner = null;
-    this.window = null;
-  },
-};
-
-exports.NetworkMonitor = NetworkMonitor;
-exports.NetworkResponseListener = NetworkResponseListener;
-})(WebConsoleUtils);
-
-/**
- * A WebProgressListener that listens for location changes.
- *
- * This progress listener is used to track file loads and other kinds of
- * location changes.
- *
- * @constructor
- * @param object aWindow
- *        The window for which we need to track location changes.
- * @param object aOwner
- *        The listener owner which needs to implement two methods:
- *        - onFileActivity(aFileURI)
- *        - onLocationChange(aState, aTabURI, aPageTitle)
- */
-function ConsoleProgressListener(aWindow, aOwner)
-{
-  this.window = aWindow;
-  this.owner = aOwner;
-}
-exports.ConsoleProgressListener = ConsoleProgressListener;
-
-ConsoleProgressListener.prototype = {
-  /**
-   * Constant used for startMonitor()/stopMonitor() that tells you want to
-   * monitor file loads.
-   */
-  MONITOR_FILE_ACTIVITY: 1,
-
-  /**
-   * Constant used for startMonitor()/stopMonitor() that tells you want to
-   * monitor page location changes.
-   */
-  MONITOR_LOCATION_CHANGE: 2,
-
-  /**
-   * Tells if you want to monitor file activity.
-   * @private
-   * @type boolean
-   */
-  _fileActivity: false,
-
-  /**
-   * Tells if you want to monitor location changes.
-   * @private
-   * @type boolean
-   */
-  _locationChange: false,
-
-  /**
-   * Tells if the console progress listener is initialized or not.
-   * @private
-   * @type boolean
-   */
-  _initialized: false,
-
-  _webProgress: null,
-
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
-                                         Ci.nsISupportsWeakReference]),
-
-  /**
-   * Initialize the ConsoleProgressListener.
-   * @private
-   */
-  _init: function CPL__init()
-  {
-    if (this._initialized) {
-      return;
-    }
-
-    this._webProgress = this.window.QueryInterface(Ci.nsIInterfaceRequestor)
-                        .getInterface(Ci.nsIWebNavigation)
-                        .QueryInterface(Ci.nsIWebProgress);
-    this._webProgress.addProgressListener(this,
-                                          Ci.nsIWebProgress.NOTIFY_STATE_ALL);
-
-    this._initialized = true;
-  },
-
-  /**
-   * Start a monitor/tracker related to the current nsIWebProgressListener
-   * instance.
-   *
-   * @param number aMonitor
-   *        Tells what you want to track. Available constants:
-   *        - this.MONITOR_FILE_ACTIVITY
-   *          Track file loads.
-   *        - this.MONITOR_LOCATION_CHANGE
-   *          Track location changes for the top window.
-   */
-  startMonitor: function CPL_startMonitor(aMonitor)
-  {
-    switch (aMonitor) {
-      case this.MONITOR_FILE_ACTIVITY:
-        this._fileActivity = true;
-        break;
-      case this.MONITOR_LOCATION_CHANGE:
-        this._locationChange = true;
-        break;
-      default:
-        throw new Error("ConsoleProgressListener: unknown monitor type " +
-                        aMonitor + "!");
-    }
-    this._init();
-  },
-
-  /**
-   * Stop a monitor.
-   *
-   * @param number aMonitor
-   *        Tells what you want to stop tracking. See this.startMonitor() for
-   *        the list of constants.
-   */
-  stopMonitor: function CPL_stopMonitor(aMonitor)
-  {
-    switch (aMonitor) {
-      case this.MONITOR_FILE_ACTIVITY:
-        this._fileActivity = false;
-        break;
-      case this.MONITOR_LOCATION_CHANGE:
-        this._locationChange = false;
-        break;
-      default:
-        throw new Error("ConsoleProgressListener: unknown monitor type " +
-                        aMonitor + "!");
-    }
-
-    if (!this._fileActivity && !this._locationChange) {
-      this.destroy();
-    }
-  },
-
-  onStateChange:
-  function CPL_onStateChange(aProgress, aRequest, aState, aStatus)
-  {
-    if (!this.owner) {
-      return;
-    }
-
-    if (this._fileActivity) {
-      this._checkFileActivity(aProgress, aRequest, aState, aStatus);
-    }
-
-    if (this._locationChange) {
-      this._checkLocationChange(aProgress, aRequest, aState, aStatus);
-    }
-  },
-
-  /**
-   * Check if there is any file load, given the arguments of
-   * nsIWebProgressListener.onStateChange. If the state change tells that a file
-   * URI has been loaded, then the remote Web Console instance is notified.
-   * @private
-   */
-  _checkFileActivity:
-  function CPL__checkFileActivity(aProgress, aRequest, aState, aStatus)
-  {
-    if (!(aState & Ci.nsIWebProgressListener.STATE_START)) {
-      return;
-    }
-
-    let uri = null;
-    if (aRequest instanceof Ci.imgIRequest) {
-      let imgIRequest = aRequest.QueryInterface(Ci.imgIRequest);
-      uri = imgIRequest.URI;
-    }
-    else if (aRequest instanceof Ci.nsIChannel) {
-      let nsIChannel = aRequest.QueryInterface(Ci.nsIChannel);
-      uri = nsIChannel.URI;
-    }
-
-    if (!uri || !uri.schemeIs("file") && !uri.schemeIs("ftp")) {
-      return;
-    }
-
-    this.owner.onFileActivity(uri.spec);
-  },
-
-  /**
-   * Check if the current window.top location is changing, given the arguments
-   * of nsIWebProgressListener.onStateChange. If that is the case, the remote
-   * Web Console instance is notified.
-   * @private
-   */
-  _checkLocationChange:
-  function CPL__checkLocationChange(aProgress, aRequest, aState, aStatus)
-  {
-    let isStart = aState & Ci.nsIWebProgressListener.STATE_START;
-    let isStop = aState & Ci.nsIWebProgressListener.STATE_STOP;
-    let isNetwork = aState & Ci.nsIWebProgressListener.STATE_IS_NETWORK;
-    let isWindow = aState & Ci.nsIWebProgressListener.STATE_IS_WINDOW;
-
-    // Skip non-interesting states.
-    if (!isNetwork || !isWindow || aProgress.DOMWindow != this.window) {
-      return;
-    }
-
-    if (isStart && aRequest instanceof Ci.nsIChannel) {
-      this.owner.onLocationChange("start", aRequest.URI.spec, "");
-    }
-    else if (isStop) {
-      this.owner.onLocationChange("stop", this.window.location.href,
-                                  this.window.document.title);
-    }
-  },
-
-  onLocationChange: function() {},
-  onStatusChange: function() {},
-  onProgressChange: function() {},
-  onSecurityChange: function() {},
-
-  /**
-   * Destroy the ConsoleProgressListener.
-   */
-  destroy: function CPL_destroy()
-  {
-    if (!this._initialized) {
-      return;
-    }
-
-    this._initialized = false;
-    this._fileActivity = false;
-    this._locationChange = false;
-
-    try {
-      this._webProgress.removeProgressListener(this);
-    }
-    catch (ex) {
-      // This can throw during browser shutdown.
-    }
-
-    this._webProgress = null;
-    this.window = null;
-    this.owner = null;
-  },
-};
-
 
 /**
  * A ReflowObserver that listens for reflow events from the page.
  * Implements nsIReflowObserver.
  *
  * @constructor
  * @param object aWindow
  *        The window for which we need to track reflow.