Backed out changeset 9c70da27ec28 (bug 768096)
authorMihai Sucan <mihai.sucan@gmail.com>
Fri, 28 Sep 2012 13:19:40 +0100
changeset 108510 b18888e94143f8f47d51f77830443c99e7e58521
parent 108509 3d2e44c2fd29e887780cdfe9afdc557ae1a472e0
child 108511 954d1a3b410b781460af7aa829d77261e226f911
push id82
push usershu@rfrn.org
push dateFri, 05 Oct 2012 13:20:22 +0000
bugs768096
milestone18.0a1
backs out9c70da27ec28f740cc2ae2f60394b17141ab452e
Backed out changeset 9c70da27ec28 (bug 768096)
browser/devtools/jar.mn
browser/devtools/shared/DeveloperToolbar.jsm
browser/devtools/shared/test/browser_toolbar_webconsole_errors_count.js
browser/devtools/webconsole/HUDService-content.js
browser/devtools/webconsole/HUDService.jsm
browser/devtools/webconsole/test/browser_console_log_inspectable_object.js
browser/devtools/webconsole/test/browser_webconsole_bug_595223_file_uri.js
browser/devtools/webconsole/test/browser_webconsole_bug_651501_document_body_autocomplete.js
browser/devtools/webconsole/test/browser_webconsole_bug_737873_mixedcontent.js
browser/devtools/webconsole/test/test-bug-737873-mixedcontent.html
browser/devtools/webconsole/webconsole.js
toolkit/devtools/webconsole/WebConsoleUtils.jsm
--- a/browser/devtools/jar.mn
+++ b/browser/devtools/jar.mn
@@ -2,16 +2,17 @@
 # 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/.
 
 browser.jar:
     content/browser/inspector.html                (highlighter/inspector.html)
     content/browser/devtools/markup-view.xhtml    (markupview/markup-view.xhtml)
     content/browser/devtools/markup-view.css      (markupview/markup-view.css)
     content/browser/NetworkPanel.xhtml            (webconsole/NetworkPanel.xhtml)
+    content/browser/devtools/HUDService-content.js (webconsole/HUDService-content.js)
     content/browser/devtools/webconsole.js        (webconsole/webconsole.js)
 *   content/browser/devtools/webconsole.xul       (webconsole/webconsole.xul)
 *   content/browser/scratchpad.xul                (scratchpad/scratchpad.xul)
     content/browser/scratchpad.js                 (scratchpad/scratchpad.js)
     content/browser/splitview.css                 (shared/splitview.css)
     content/browser/styleeditor.xul               (styleeditor/styleeditor.xul)
     content/browser/styleeditor.css               (styleeditor/styleeditor.css)
     content/browser/devtools/csshtmltree.xul      (styleinspector/csshtmltree.xul)
--- a/browser/devtools/shared/DeveloperToolbar.jsm
+++ b/browser/devtools/shared/DeveloperToolbar.jsm
@@ -3,34 +3,34 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const EXPORTED_SYMBOLS = [ "DeveloperToolbar" ];
 
 const NS_XHTML = "http://www.w3.org/1999/xhtml";
 
+const WEBCONSOLE_CONTENT_SCRIPT_URL =
+  "chrome://browser/content/devtools/HUDService-content.js";
+
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource:///modules/devtools/Commands.jsm");
 
 const Node = Components.interfaces.nsIDOMNode;
 
 XPCOMUtils.defineLazyModuleGetter(this, "console",
                                   "resource://gre/modules/devtools/Console.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "gcli",
                                   "resource:///modules/devtools/gcli.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "CmdCommands",
                                   "resource:///modules/devtools/CmdCmd.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "PageErrorListener",
-                                  "resource://gre/modules/devtools/WebConsoleUtils.jsm");
-
 /**
  * Due to a number of panel bugs we need a way to check if we are running on
  * Linux. See the comments for TooltipPanel and OutputPanel for further details.
  *
  * When bug 780102 is fixed all isLinux checks can be removed and we can revert
  * to using panels.
  */
 XPCOMUtils.defineLazyGetter(this, "isLinux", function () {
@@ -51,18 +51,17 @@ function DeveloperToolbar(aChromeWindow,
 
   this._element = aToolbarElement;
   this._element.hidden = true;
   this._doc = this._element.ownerDocument;
 
   this._lastState = NOTIFICATIONS.HIDE;
   this._pendingShowCallback = undefined;
   this._pendingHide = false;
-  this._errorsCount = Object.create(null);
-  this._errorListeners = Object.create(null);
+  this._errorsCount = {};
   this._webConsoleButton = this._doc
                            .getElementById("developer-toolbar-webconsole");
 
   try {
     CmdCommands.refreshAutoCommands(aChromeWindow);
   }
   catch (ex) {
     console.error(ex);
@@ -84,16 +83,19 @@ const NOTIFICATIONS = {
 };
 
 /**
  * Attach notification constants to the object prototype so tests etc can
  * use them without needing to import anything
  */
 DeveloperToolbar.prototype.NOTIFICATIONS = NOTIFICATIONS;
 
+DeveloperToolbar.prototype._contentMessageListeners =
+  ["WebConsole:CachedMessages", "WebConsole:PageError"];
+
 /**
  * Is the toolbar open?
  */
 Object.defineProperty(DeveloperToolbar.prototype, 'visible', {
   get: function DT_visible() {
     return !this._element.hidden;
   },
   enumerable: true
@@ -278,28 +280,31 @@ DeveloperToolbar.prototype._onload = fun
 DeveloperToolbar.prototype._initErrorsCount = function DT__initErrorsCount(aTab)
 {
   let tabId = aTab.linkedPanel;
   if (tabId in this._errorsCount) {
     this._updateErrorsCount();
     return;
   }
 
-  let window = aTab.linkedBrowser.contentWindow;
-  let listener = new PageErrorListener(window, {
-    onPageError: this._onPageError.bind(this, tabId),
-  });
-  listener.init();
+  let messageManager = aTab.linkedBrowser.messageManager;
+  messageManager.loadFrameScript(WEBCONSOLE_CONTENT_SCRIPT_URL, true);
 
-  this._errorListeners[tabId] = listener;
   this._errorsCount[tabId] = 0;
 
-  let messages = listener.getCachedMessages();
-  messages.forEach(this._onPageError.bind(this, tabId));
+  this._contentMessageListeners.forEach(function(aName) {
+    messageManager.addMessageListener(aName, this);
+  }, this);
 
+  let message = {
+    features: ["PageError"],
+    cachedMessages: ["PageError"],
+  };
+
+  this.sendMessageToTab(aTab, "WebConsole:Init", message);
   this._updateErrorsCount();
 };
 
 /**
  * Stop the listeners needed for tracking the number of errors for a given
  * tab.
  *
  * @private
@@ -309,20 +314,24 @@ DeveloperToolbar.prototype._initErrorsCo
 DeveloperToolbar.prototype._stopErrorsCount = function DT__stopErrorsCount(aTab)
 {
   let tabId = aTab.linkedPanel;
   if (!(tabId in this._errorsCount)) {
     this._updateErrorsCount();
     return;
   }
 
-  this._errorListeners[tabId].destroy();
-  delete this._errorListeners[tabId];
+  this.sendMessageToTab(aTab, "WebConsole:Destroy", {});
+
+  let messageManager = aTab.linkedBrowser.messageManager;
+  this._contentMessageListeners.forEach(function(aName) {
+    messageManager.removeMessageListener(aName, this);
+  }, this);
+
   delete this._errorsCount[tabId];
-
   this._updateErrorsCount();
 };
 
 /**
  * Hide the developer toolbar.
  */
 DeveloperToolbar.prototype.hide = function DT_hide()
 {
@@ -420,36 +429,83 @@ DeveloperToolbar.prototype.handleEvent =
     this._stopErrorsCount(aEvent.target);
   }
   else if (aEvent.type == "beforeunload") {
     this._onPageBeforeUnload(aEvent);
   }
 };
 
 /**
- * Count a page error received for the currently selected tab. This
- * method counts the JavaScript exceptions received and CSS errors/warnings.
+ * The handler of messages received from the nsIMessageManager.
+ *
+ * @param object aMessage the message received from the content process.
+ */
+DeveloperToolbar.prototype.receiveMessage = function DT_receiveMessage(aMessage)
+{
+  if (!aMessage.json || !(aMessage.json.hudId in this._errorsCount)) {
+    return;
+  }
+
+  let tabId = aMessage.json.hudId;
+  let errors = this._errorsCount[tabId];
+
+  switch (aMessage.name) {
+    case "WebConsole:PageError":
+      this._onPageError(tabId, aMessage.json.pageError);
+      break;
+    case "WebConsole:CachedMessages":
+      aMessage.json.messages.forEach(this._onPageError.bind(this, tabId));
+      break;
+  }
+
+  if (errors != this._errorsCount[tabId]) {
+    this._updateErrorsCount(tabId);
+  }
+};
+
+/**
+ * Send a message to the content process using the nsIMessageManager of the
+ * given tab.
+ *
+ * @param nsIDOMNode aTab the tab you want to send a message to.
+ * @param string aName the name of the message you want to send.
+ * @param object aMessage the message to send.
+ */
+DeveloperToolbar.prototype.sendMessageToTab =
+function DT_sendMessageToTab(aTab, aName, aMessage)
+{
+  let tabId = aTab.linkedPanel;
+  aMessage.hudId = tabId;
+  if (!("id" in aMessage)) {
+    aMessage.id = "DevToolbar-" + this.sequenceId;
+  }
+
+  aTab.linkedBrowser.messageManager.sendAsyncMessage(aName, aMessage);
+};
+
+/**
+ * Process a "WebConsole:PageError" message received from the given tab. This
+ * method counts the JavaScript exceptions received.
  *
  * @private
  * @param string aTabId the ID of the tab from where the page error comes.
- * @param object aPageError the page error object received from the
- * PageErrorListener.
+ * @param object aPageError the page error object received from the content
+ * process.
  */
 DeveloperToolbar.prototype._onPageError =
 function DT__onPageError(aTabId, aPageError)
 {
   if (aPageError.category == "CSS Parser" ||
       aPageError.category == "CSS Loader" ||
       (aPageError.flags & aPageError.warningFlag) ||
       (aPageError.flags & aPageError.strictFlag)) {
     return; // just a CSS or JS warning
   }
 
   this._errorsCount[aTabId]++;
-  this._updateErrorsCount(aTabId);
 };
 
 /**
  * The |beforeunload| event handler. This function resets the errors count when
  * a different page starts loading.
  *
  * @private
  * @param nsIDOMEvent aEvent the beforeunload DOM event.
--- a/browser/devtools/shared/test/browser_toolbar_webconsole_errors_count.js
+++ b/browser/devtools/shared/test/browser_toolbar_webconsole_errors_count.js
@@ -42,21 +42,18 @@ function test() {
       value: 3,
       success: addErrors,
       failure: finish,
     });
   }
 
   function addErrors() {
     expectUncaughtException();
-
-    waitForFocus(function() {
-      let button = content.document.querySelector("button");
-      EventUtils.synthesizeMouse(button, 2, 2, {}, content);
-    }, content);
+    let button = content.document.querySelector("button");
+    EventUtils.synthesizeMouse(button, 2, 2, {}, content);
 
     waitForValue({
       name: "button shows one more error after click in page",
       validator: getErrorsCount,
       value: 4,
       success: function() {
         ignoreAllUncaughtExceptions();
         addTab(TEST_URI, onOpenSecondTab);
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/HUDService-content.js
@@ -0,0 +1,2541 @@
+/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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";
+
+// This code is appended to the browser content script.
+(function _HUDServiceContent() {
+let Cc = Components.classes;
+let Ci = Components.interfaces;
+let Cu = Components.utils;
+const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
+
+let tempScope = {};
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", tempScope);
+Cu.import("resource://gre/modules/Services.jsm", tempScope);
+Cu.import("resource://gre/modules/ConsoleAPIStorage.jsm", tempScope);
+Cu.import("resource://gre/modules/devtools/WebConsoleUtils.jsm", tempScope);
+Cu.import("resource://gre/modules/devtools/NetworkHelper.jsm", tempScope);
+Cu.import("resource://gre/modules/NetUtil.jsm", tempScope);
+
+let XPCOMUtils = tempScope.XPCOMUtils;
+let Services = tempScope.Services;
+let gConsoleStorage = tempScope.ConsoleAPIStorage;
+let WebConsoleUtils = tempScope.WebConsoleUtils;
+let l10n = new WebConsoleUtils.l10n(STRINGS_URI);
+let JSPropertyProvider = tempScope.JSPropertyProvider;
+let NetworkHelper = tempScope.NetworkHelper;
+let NetUtil = tempScope.NetUtil;
+tempScope = null;
+
+let activityDistributor = Cc["@mozilla.org/network/http-activity-distributor;1"].getService(Ci.nsIHttpActivityDistributor);
+
+let _alive = true; // Track if this content script should still be alive.
+
+/**
+ * The Web Console content instance manager.
+ */
+let Manager = {
+  get window() content,
+  hudId: null,
+  _sequence: 0,
+  _messageListeners: ["WebConsole:Init", "WebConsole:EnableFeature",
+                      "WebConsole:DisableFeature", "WebConsole:SetPreferences",
+                      "WebConsole:GetPreferences", "WebConsole:Destroy"],
+  _messageHandlers: null,
+  _enabledFeatures: null,
+  _prefs: { },
+
+  /**
+   * Getter for a unique ID for the current Web Console content instance.
+   */
+  get sequenceId() "HUDContent-" + (++this._sequence),
+
+  /**
+   * Initialize the Web Console manager.
+   */
+  init: function Manager_init()
+  {
+    this._enabledFeatures = [];
+    this._messageHandlers = {};
+
+    this._messageListeners.forEach(function(aName) {
+      addMessageListener(aName, this);
+    }, this);
+
+    // Need to track the owner XUL window to listen to the unload and TabClose
+    // events, to avoid memory leaks.
+    let xulWindow = this._xulWindow();
+    xulWindow.addEventListener("unload", this._onXULWindowClose, false);
+
+    let tabContainer = xulWindow.gBrowser.tabContainer;
+    tabContainer.addEventListener("TabClose", this._onTabClose, false);
+
+    // Need to track private browsing change and quit application notifications,
+    // again to avoid memory leaks. The Web Console main process cannot notify
+    // this content script when the XUL window close, tab close, private
+    // browsing change and quit application events happen, so we must call
+    // Manager.destroy() on our own.
+    Services.obs.addObserver(this, "private-browsing-change-granted", false);
+    Services.obs.addObserver(this, "quit-application-granted", false);
+  },
+
+  /**
+   * The message handler. This method forwards all the remote messages to the
+   * appropriate code.
+   */
+  receiveMessage: function Manager_receiveMessage(aMessage)
+  {
+    if (!_alive || !aMessage.json) {
+      return;
+    }
+
+    if (aMessage.name == "WebConsole:Init" && !this.hudId) {
+      this._onInit(aMessage.json);
+      return;
+    }
+    if (aMessage.json.hudId != this.hudId) {
+      return;
+    }
+
+    switch (aMessage.name) {
+      case "WebConsole:EnableFeature":
+        this.enableFeature(aMessage.json.feature, aMessage.json);
+        break;
+      case "WebConsole:DisableFeature":
+        this.disableFeature(aMessage.json.feature);
+        break;
+      case "WebConsole:GetPreferences":
+        this.handleGetPreferences(aMessage.json);
+        break;
+      case "WebConsole:SetPreferences":
+        this.handleSetPreferences(aMessage.json);
+        break;
+      case "WebConsole:Destroy":
+        this.destroy();
+        break;
+      default: {
+        let handler = this._messageHandlers[aMessage.name];
+        handler && handler(aMessage.json);
+        break;
+      }
+    }
+  },
+
+  /**
+   * Observe notifications from the nsIObserverService.
+   *
+   * @param mixed aSubject
+   * @param string aTopic
+   * @param mixed aData
+   */
+  observe: function Manager_observe(aSubject, aTopic, aData)
+  {
+    if (_alive && (aTopic == "quit-application-granted" ||
+        (aTopic == "private-browsing-change-granted" &&
+         (aData == "enter" || aData == "exit")))) {
+      this.destroy();
+    }
+  },
+
+  /**
+   * The manager initialization code. This method is called when the Web Console
+   * remote process initializes the content process (this code!).
+   *
+   * @param object aMessage
+   *        The object received from the remote process. The WebConsole:Init
+   *        message properties:
+   *        - hudId - (required) the remote Web Console instance ID.
+   *        - features - (optional) array of features you want to enable from
+   *        the start. For each feature you enable you can pass feature-specific
+   *        options in a property on the JSON object you send with the same name
+   *        as the feature. See this.enableFeature() for the list of available
+   *        features.
+   *        - preferences - (optional) an object of preferences you want to set.
+   *        Use keys for preference names and values for preference values.
+   *        - cachedMessages - (optional) an array of cached messages you want
+   *        to receive. See this._sendCachedMessages() for the list of available
+   *        message types.
+   *
+   *        Example message:
+   *        {
+   *          hudId: "foo1",
+   *          features: ["JSTerm", "ConsoleAPI"],
+   *          ConsoleAPI: { ... }, // ConsoleAPI-specific options
+   *          cachedMessages: ["ConsoleAPI"],
+   *          preferences: {"foo.bar": true},
+   *        }
+   */
+  _onInit: function Manager_onInit(aMessage)
+  {
+    this.hudId = aMessage.hudId;
+
+    if (aMessage.preferences) {
+      this.handleSetPreferences({ preferences: aMessage.preferences });
+    }
+
+    if (aMessage.features) {
+      aMessage.features.forEach(function(aFeature) {
+        this.enableFeature(aFeature, aMessage[aFeature]);
+      }, this);
+    }
+
+    if (aMessage.cachedMessages) {
+      this._sendCachedMessages(aMessage.cachedMessages);
+    }
+
+    this.sendMessage("WebConsole:Initialized", {});
+  },
+
+  /**
+   * Add a remote message handler. This is used by other components of the Web
+   * Console content script.
+   *
+   * @param string aName
+   *        Message name to listen for.
+   * @param function aCallback
+   *        Function to execute when the message is received. This function is
+   *        given the JSON object that came from the remote Web Console
+   *        instance.
+   *        Only one callback per message name is allowed!
+   */
+  addMessageHandler: function Manager_addMessageHandler(aName, aCallback)
+  {
+    if (aName in this._messageHandlers) {
+      Cu.reportError("Web Console content script: addMessageHandler() called for an existing message handler: " + aName);
+      return;
+    }
+
+    this._messageHandlers[aName] = aCallback;
+    addMessageListener(aName, this);
+  },
+
+  /**
+   * Remove the message handler for the given name.
+   *
+   * @param string aName
+   *        Message name for the handler you want removed.
+   */
+  removeMessageHandler: function Manager_removeMessageHandler(aName)
+  {
+    if (!(aName in this._messageHandlers)) {
+      return;
+    }
+
+    delete this._messageHandlers[aName];
+    removeMessageListener(aName, this);
+  },
+
+  /**
+   * Send a message to the remote Web Console instance.
+   *
+   * @param string aName
+   *        The name of the message you want to send.
+   * @param object aMessage
+   *        The message object you want to send.
+   */
+  sendMessage: function Manager_sendMessage(aName, aMessage)
+  {
+    aMessage.hudId = this.hudId;
+    if (!("id" in aMessage)) {
+      aMessage.id = this.sequenceId;
+    }
+
+    sendAsyncMessage(aName, aMessage);
+  },
+
+  /**
+   * Enable a feature in the Web Console content script. A feature is generally
+   * a set of observers/listeners that are added in the content process. This
+   * content script exposes the data via the message manager for the features
+   * you enable.
+   *
+   * Supported features:
+   *    - JSTerm - a JavaScript "terminal" which allows code execution.
+   *    - ConsoleAPI - support for routing the window.console API to the remote
+   *    process.
+   *    - PageError - route all the nsIScriptErrors from the nsIConsoleService
+   *    to the remote process.
+   *    - NetworkMonitor - log all the network activity and send HAR-like
+   *    messages to the remote Web Console process.
+   *    - LocationChange - log page location changes. See
+   *    ConsoleProgressListener.
+   *
+   * @param string aFeature
+   *        One of the supported features.
+   * @param object [aMessage]
+   *        Optional JSON message object coming from the remote Web Console
+   *        instance. This can be used for feature-specific options.
+   */
+  enableFeature: function Manager_enableFeature(aFeature, aMessage)
+  {
+    if (this._enabledFeatures.indexOf(aFeature) != -1) {
+      return;
+    }
+
+    switch (aFeature) {
+      case "JSTerm":
+        JSTerm.init(aMessage);
+        break;
+      case "ConsoleAPI":
+        ConsoleAPIObserver.init(aMessage);
+        break;
+      case "PageError":
+        ConsoleListener.init(aMessage);
+        break;
+      case "NetworkMonitor":
+        NetworkMonitor.init(aMessage);
+        break;
+      case "LocationChange":
+        ConsoleProgressListener.startMonitor(ConsoleProgressListener
+                                             .MONITOR_LOCATION_CHANGE);
+        ConsoleProgressListener.sendLocation(this.window.location.href,
+                                             this.window.document.title);
+        break;
+      default:
+        Cu.reportError("Web Console content: unknown feature " + aFeature);
+        break;
+    }
+
+    this._enabledFeatures.push(aFeature);
+  },
+
+  /**
+   * Disable a Web Console content script feature.
+   *
+   * @see this.enableFeature
+   * @param string aFeature
+   *        One of the supported features - see this.enableFeature() for the
+   *        list of supported features.
+   */
+  disableFeature: function Manager_disableFeature(aFeature)
+  {
+    let index = this._enabledFeatures.indexOf(aFeature);
+    if (index == -1) {
+      return;
+    }
+    this._enabledFeatures.splice(index, 1);
+
+    switch (aFeature) {
+      case "JSTerm":
+        JSTerm.destroy();
+        break;
+      case "ConsoleAPI":
+        ConsoleAPIObserver.destroy();
+        break;
+      case "PageError":
+        ConsoleListener.destroy();
+        break;
+      case "NetworkMonitor":
+        NetworkMonitor.destroy();
+        break;
+      case "LocationChange":
+        ConsoleProgressListener.stopMonitor(ConsoleProgressListener
+                                            .MONITOR_LOCATION_CHANGE);
+        break;
+      default:
+        Cu.reportError("Web Console content: unknown feature " + aFeature);
+        break;
+    }
+  },
+
+  /**
+   * Handle the "WebConsole:GetPreferences" messages from the remote Web Console
+   * instance.
+   *
+   * @param object aMessage
+   *        The JSON object of the remote message. This object holds one
+   *        property: preferences. The |preferences| value must be an array of
+   *        preference names you want to retrieve the values for.
+   *        A "WebConsole:Preferences" message is sent back to the remote Web
+   *        Console instance. The message holds a |preferences| object which has
+   *        key names for preference names and values for each preference value.
+   */
+  handleGetPreferences: function Manager_handleGetPreferences(aMessage)
+  {
+    let prefs = {};
+    aMessage.preferences.forEach(function(aName) {
+      prefs[aName] = this.getPreference(aName);
+    }, this);
+
+    this.sendMessage("WebConsole:Preferences", {preferences: prefs});
+  },
+
+  /**
+   * Handle the "WebConsole:SetPreferences" messages from the remote Web Console
+   * instance.
+   *
+   * @param object aMessage
+   *        The JSON object of the remote message. This object holds one
+   *        property: preferences. The |preferences| value must be an object of
+   *        preference names as keys and preference values as object values, for
+   *        each preference you want to change.
+   */
+  handleSetPreferences: function Manager_handleSetPreferences(aMessage)
+  {
+    for (let key in aMessage.preferences) {
+      this.setPreference(key, aMessage.preferences[key]);
+    }
+  },
+
+  /**
+   * Retrieve a preference.
+   *
+   * @param string aName
+   *        Preference name.
+   * @return mixed|null
+   *         Preference value. Null is returned if the preference does not
+   *         exist.
+   */
+  getPreference: function Manager_getPreference(aName)
+  {
+    return aName in this._prefs ? this._prefs[aName] : null;
+  },
+
+  /**
+   * Set a preference to a new value.
+   *
+   * @param string aName
+   *        Preference name.
+   * @param mixed aValue
+   *        Preference value.
+   */
+  setPreference: function Manager_setPreference(aName, aValue)
+  {
+    this._prefs[aName] = aValue;
+  },
+
+  /**
+   * Send the cached messages to the remote Web Console instance.
+   *
+   * @private
+   * @param array aMessageTypes
+   *        An array that lists which kinds of messages you want. Supported
+   *        message types: "ConsoleAPI" and "PageError".
+   */
+  _sendCachedMessages: function Manager__sendCachedMessages(aMessageTypes)
+  {
+    let messages = [];
+
+    while (aMessageTypes.length > 0) {
+      switch (aMessageTypes.shift()) {
+        case "ConsoleAPI":
+          messages.push.apply(messages, ConsoleAPIObserver.getCachedMessages());
+          break;
+        case "PageError":
+          messages.push.apply(messages, ConsoleListener.getCachedMessages());
+          break;
+      }
+    }
+
+    messages.sort(function(a, b) { return a.timeStamp - b.timeStamp; });
+
+    this.sendMessage("WebConsole:CachedMessages", {messages: messages});
+  },
+
+  /**
+   * The XUL window "unload" event handler which destroys this content script
+   * instance.
+   * @private
+   */
+  _onXULWindowClose: function Manager__onXULWindowClose()
+  {
+    if (_alive) {
+      Manager.destroy();
+    }
+  },
+
+  /**
+   * The "TabClose" event handler which destroys this content script
+   * instance, if needed.
+   * @private
+   */
+  _onTabClose: function Manager__onTabClose(aEvent)
+  {
+    let tab = aEvent.target;
+    if (_alive && tab.linkedBrowser.contentWindow === Manager.window) {
+      Manager.destroy();
+    }
+  },
+
+  /**
+   * Find the XUL window that owns the content script.
+   * @private
+   * @return Window
+   *         The XUL window that owns the content script.
+   */
+  _xulWindow: function Manager__xulWindow()
+  {
+    return this.window.QueryInterface(Ci.nsIInterfaceRequestor)
+           .getInterface(Ci.nsIWebNavigation).QueryInterface(Ci.nsIDocShell)
+           .chromeEventHandler.ownerDocument.defaultView;
+  },
+
+  /**
+   * Destroy the Web Console content script instance.
+   */
+  destroy: function Manager_destroy()
+  {
+    Services.obs.removeObserver(this, "private-browsing-change-granted");
+    Services.obs.removeObserver(this, "quit-application-granted");
+
+    _alive = false;
+    let xulWindow = this._xulWindow();
+    xulWindow.removeEventListener("unload", this._onXULWindowClose, false);
+    let tabContainer = xulWindow.gBrowser.tabContainer;
+    tabContainer.removeEventListener("TabClose", this._onTabClose, false);
+
+    this._messageListeners.forEach(function(aName) {
+      removeMessageListener(aName, this);
+    }, this);
+
+    this._enabledFeatures.slice().forEach(this.disableFeature, this);
+
+    this.hudId = null;
+    this._messageHandlers = null;
+
+    Manager = ConsoleAPIObserver = JSTerm = ConsoleListener = NetworkMonitor =
+      NetworkResponseListener = ConsoleProgressListener = null;
+
+    XPCOMUtils = gConsoleStorage = WebConsoleUtils = l10n = JSPropertyProvider =
+      null;
+  },
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// JavaScript Terminal
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * JSTerm helper functions.
+ *
+ * Defines a set of functions ("helper functions") that are available from the
+ * Web Console but not from the web page.
+ *
+ * A list of helper functions used by Firebug can be found here:
+ *   http://getfirebug.com/wiki/index.php/Command_Line_API
+ */
+function JSTermHelper(aJSTerm)
+{
+  /**
+   * Find a node by ID.
+   *
+   * @param string aId
+   *        The ID of the element you want.
+   * @return nsIDOMNode or null
+   *         The result of calling document.querySelector(aSelector).
+   */
+  aJSTerm.sandbox.$ = function JSTH_$(aSelector)
+  {
+    return aJSTerm.window.document.querySelector(aSelector);
+  };
+
+  /**
+   * Find the nodes matching a CSS selector.
+   *
+   * @param string aSelector
+   *        A string that is passed to window.document.querySelectorAll.
+   * @return nsIDOMNodeList
+   *         Returns the result of document.querySelectorAll(aSelector).
+   */
+  aJSTerm.sandbox.$$ = function JSTH_$$(aSelector)
+  {
+    return aJSTerm.window.document.querySelectorAll(aSelector);
+  };
+
+  /**
+   * Runs an xPath query and returns all matched nodes.
+   *
+   * @param string aXPath
+   *        xPath search query to execute.
+   * @param [optional] nsIDOMNode aContext
+   *        Context to run the xPath query on. Uses window.document if not set.
+   * @returns array of nsIDOMNode
+   */
+  aJSTerm.sandbox.$x = function JSTH_$x(aXPath, aContext)
+  {
+    let nodes = [];
+    let doc = aJSTerm.window.document;
+    let aContext = aContext || doc;
+
+    try {
+      let results = doc.evaluate(aXPath, aContext, null,
+                                 Ci.nsIDOMXPathResult.ANY_TYPE, null);
+      let node;
+      while (node = results.iterateNext()) {
+        nodes.push(node);
+      }
+    }
+    catch (ex) {
+      aJSTerm.console.error(ex.message);
+    }
+
+    return nodes;
+  };
+
+  /**
+   * Returns the currently selected object in the highlighter.
+   *
+   * Warning: this implementation crosses the process boundaries! This is not
+   * usable within a remote browser. To implement this feature correctly we need
+   * support for remote inspection capabilities within the Inspector as well.
+   *
+   * @return nsIDOMElement|null
+   *         The DOM element currently selected in the highlighter.
+   */
+  Object.defineProperty(aJSTerm.sandbox, "$0", {
+    get: function() {
+      try {
+        return Manager._xulWindow().InspectorUI.selection;
+      }
+      catch (ex) {
+        aJSTerm.console.error(ex.message);
+      }
+    },
+    enumerable: true,
+    configurable: false
+  });
+
+  /**
+   * Clears the output of the JSTerm.
+   */
+  aJSTerm.sandbox.clear = function JSTH_clear()
+  {
+    aJSTerm.helperEvaluated = true;
+    Manager.sendMessage("JSTerm:ClearOutput", {});
+  };
+
+  /**
+   * Returns the result of Object.keys(aObject).
+   *
+   * @param object aObject
+   *        Object to return the property names from.
+   * @returns array of string
+   */
+  aJSTerm.sandbox.keys = function JSTH_keys(aObject)
+  {
+    return Object.keys(WebConsoleUtils.unwrap(aObject));
+  };
+
+  /**
+   * Returns the values of all properties on aObject.
+   *
+   * @param object aObject
+   *        Object to display the values from.
+   * @returns array of string
+   */
+  aJSTerm.sandbox.values = function JSTH_values(aObject)
+  {
+    let arrValues = [];
+    let obj = WebConsoleUtils.unwrap(aObject);
+
+    try {
+      for (let prop in obj) {
+        arrValues.push(obj[prop]);
+      }
+    }
+    catch (ex) {
+      aJSTerm.console.error(ex.message);
+    }
+    return arrValues;
+  };
+
+  /**
+   * Opens a help window in MDN.
+   */
+  aJSTerm.sandbox.help = function JSTH_help()
+  {
+    aJSTerm.helperEvaluated = true;
+    aJSTerm.window.open(
+        "https://developer.mozilla.org/docs/Tools/Web_Console/Helpers",
+        "help", "");
+  };
+
+  /**
+   * Inspects the passed aObject. This is done by opening the PropertyPanel.
+   *
+   * @param object aObject
+   *        Object to inspect.
+   */
+  aJSTerm.sandbox.inspect = function JSTH_inspect(aObject)
+  {
+    if (!WebConsoleUtils.isObjectInspectable(aObject)) {
+      return aObject;
+    }
+
+    aJSTerm.helperEvaluated = true;
+
+    let message = {
+      input: aJSTerm._evalInput,
+      objectCacheId: Manager.sequenceId,
+    };
+
+    message.resultObject =
+      aJSTerm.prepareObjectForRemote(WebConsoleUtils.unwrap(aObject),
+                                     message.objectCacheId);
+
+    Manager.sendMessage("JSTerm:InspectObject", message);
+  };
+
+  /**
+   * Prints aObject to the output.
+   *
+   * @param object aObject
+   *        Object to print to the output.
+   * @return string
+   */
+  aJSTerm.sandbox.pprint = function JSTH_pprint(aObject)
+  {
+    aJSTerm.helperEvaluated = true;
+    if (aObject === null || aObject === undefined || aObject === true ||
+        aObject === false) {
+      aJSTerm.console.error(l10n.getStr("helperFuncUnsupportedTypeError"));
+      return;
+    }
+    else if (typeof aObject == "function") {
+      aJSTerm.helperRawOutput = true;
+      return aObject + "\n";
+    }
+
+    aJSTerm.helperRawOutput = true;
+
+    let output = [];
+    let pairs = WebConsoleUtils.namesAndValuesOf(WebConsoleUtils.unwrap(aObject));
+    pairs.forEach(function(aPair) {
+      output.push(aPair.name + ": " + aPair.value);
+    });
+
+    return "  " + output.join("\n  ");
+  };
+
+  /**
+   * Print a string to the output, as-is.
+   *
+   * @param string aString
+   *        A string you want to output.
+   * @returns void
+   */
+  aJSTerm.sandbox.print = function JSTH_print(aString)
+  {
+    aJSTerm.helperEvaluated = true;
+    aJSTerm.helperRawOutput = true;
+    return String(aString);
+  };
+}
+
+/**
+ * The JavaScript terminal is meant to allow remote code execution for the Web
+ * Console.
+ */
+let JSTerm = {
+  get window() Manager.window,
+  get console() this.window.console,
+
+  /**
+   * The Cu.Sandbox() object where code is evaluated.
+   */
+  sandbox: null,
+
+  _sandboxLocation: null,
+  _messageHandlers: {},
+
+  /**
+   * Evaluation result objects are cached in this object. The chrome process can
+   * request any object based on its ID.
+   */
+  _objectCache: null,
+
+  /**
+   * Initialize the JavaScript terminal feature.
+   *
+   * @param object aMessage
+   *        Options for JSTerm sent from the remote Web Console instance. This
+   *        object holds the following properties:
+   *
+   *          - notifyNonNativeConsoleAPI - boolean that tells if you want to be
+   *          notified if the window.console API object in the page is not the
+   *          native one (if the page overrides it).
+   *          A "JSTerm:NonNativeConsoleAPI" message will be sent if this is the
+   *          case.
+   */
+  init: function JST_init(aMessage)
+  {
+    this._objectCache = {};
+    this._messageHandlers = {
+      "JSTerm:EvalRequest": this.handleEvalRequest,
+      "JSTerm:GetEvalObject": this.handleGetEvalObject,
+      "JSTerm:Autocomplete": this.handleAutocomplete,
+      "JSTerm:ClearObjectCache": this.handleClearObjectCache,
+    };
+
+    for (let name in this._messageHandlers) {
+      let handler = this._messageHandlers[name].bind(this);
+      Manager.addMessageHandler(name, handler);
+    }
+
+    if (aMessage && aMessage.notifyNonNativeConsoleAPI) {
+      let consoleObject = WebConsoleUtils.unwrap(this.window).console;
+      if (!("__mozillaConsole__" in consoleObject)) {
+        Manager.sendMessage("JSTerm:NonNativeConsoleAPI", {});
+      }
+    }
+  },
+
+  /**
+   * Handler for the "JSTerm:EvalRequest" remote message. This method evaluates
+   * user input in the JavaScript sandbox and sends the result back to the
+   * remote process. The "JSTerm:EvalResult" message includes the following
+   * data:
+   *   - id - the same ID as the EvalRequest (for tracking purposes).
+   *   - input - the JS string that was evaluated.
+   *   - resultString - the evaluation result converted to a string formatted
+   *   for display.
+   *   - timestamp - timestamp when evaluation occurred (Date.now(),
+   *   milliseconds since the UNIX epoch).
+   *   - inspectable - boolean that tells if the evaluation result object can be
+   *   inspected or not.
+   *   - error - the evaluation exception object (if any).
+   *   - errorMessage - the exception object converted to a string (if any error
+   *   occurred).
+   *   - helperResult - boolean that tells if a JSTerm helper was evaluated.
+   *   - helperRawOutput - boolean that tells if the helper evaluation result
+   *   should be displayed as raw output.
+   *
+   *   If the result object is inspectable then two additional properties are
+   *   included:
+   *     - childrenCacheId - tells where child objects are cached. This is the
+   *     same as aRequest.resultCacheId.
+   *     - resultObject - the result object prepared for the remote process. See
+   *     this.prepareObjectForRemote().
+   *
+   * @param object aRequest
+   *        The code evaluation request object:
+   *          - id - request ID.
+   *          - str - string to evaluate.
+   *          - resultCacheId - where to cache the evaluation child objects.
+   */
+  handleEvalRequest: function JST_handleEvalRequest(aRequest)
+  {
+    let id = aRequest.id;
+    let input = aRequest.str;
+    let result, error = null;
+    let timestamp;
+
+    this.helperEvaluated = false;
+    this.helperRawOutput = false;
+    this._evalInput = input;
+    try {
+      timestamp = Date.now();
+      result = this.evalInSandbox(input);
+    }
+    catch (ex) {
+      error = ex;
+    }
+    delete this._evalInput;
+
+    let inspectable = !error && WebConsoleUtils.isObjectInspectable(result);
+    let resultString = undefined;
+    if (!error) {
+      resultString = this.helperRawOutput ? result :
+                     WebConsoleUtils.formatResult(result);
+    }
+
+    let message = {
+      id: id,
+      input: input,
+      resultString: resultString,
+      timestamp: timestamp,
+      error: error,
+      errorMessage: error ? String(error) : null,
+      inspectable: inspectable,
+      helperResult: this.helperEvaluated,
+      helperRawOutput: this.helperRawOutput,
+    };
+
+    if (inspectable) {
+      message.childrenCacheId = aRequest.resultCacheId;
+      message.resultObject =
+        this.prepareObjectForRemote(result, message.childrenCacheId);
+    }
+
+    Manager.sendMessage("JSTerm:EvalResult", message);
+  },
+
+  /**
+   * Handler for the remote "JSTerm:GetEvalObject" message. This allows the
+   * remote Web Console instance to retrieve an object from the content process.
+   * The "JSTerm:EvalObject" message is sent back to the remote process:
+   *   - id - the request ID, used to trace back to the initial request.
+   *   - cacheId - the cache ID where the requested object is stored.
+   *   - objectId - the ID of the object being sent.
+   *   - object - the object representation prepared for remote inspection. See
+   *   this.prepareObjectForRemote().
+   *   - childrenCacheId - the cache ID where any child object of |object| are
+   *   stored.
+   *
+   * @param object aRequest
+   *        The message that requests the content object. Properties: cacheId,
+   *        objectId and resultCacheId.
+   *
+   *        Evaluated objects are stored in "buckets" (cache IDs). Each object
+   *        is assigned an ID (object ID). You can request a specific object
+   *        (objectId) from a specific cache (cacheId) and tell where the result
+   *        should be cached (resultCacheId). The requested object can have
+   *        further references to other objects - those references will be
+   *        cached in the "bucket" of your choice (based on resultCacheId). If
+   *        you do not provide any resultCacheId in the request message, then
+   *        cacheId will be used.
+   */
+  handleGetEvalObject: function JST_handleGetEvalObject(aRequest)
+  {
+    if (aRequest.cacheId in this._objectCache &&
+        aRequest.objectId in this._objectCache[aRequest.cacheId]) {
+      let object = this._objectCache[aRequest.cacheId][aRequest.objectId];
+      let resultCacheId = aRequest.resultCacheId || aRequest.cacheId;
+      let message = {
+        id: aRequest.id,
+        cacheId: aRequest.cacheId,
+        objectId: aRequest.objectId,
+        object: this.prepareObjectForRemote(object, resultCacheId),
+        childrenCacheId: resultCacheId,
+      };
+      Manager.sendMessage("JSTerm:EvalObject", message);
+    }
+    else {
+      Cu.reportError("JSTerm:GetEvalObject request " + aRequest.id +
+                     ": stale object.");
+    }
+  },
+
+  /**
+   * Handler for the remote "JSTerm:ClearObjectCache" message. This allows the
+   * remote Web Console instance to clear the cache of objects that it no longer
+   * uses.
+   *
+   * @param object aRequest
+   *        An object that holds one property: the cacheId you want cleared.
+   */
+  handleClearObjectCache: function JST_handleClearObjectCache(aRequest)
+  {
+    if (aRequest.cacheId in this._objectCache) {
+      delete this._objectCache[aRequest.cacheId];
+    }
+  },
+
+  /**
+   * Prepare an object to be sent to the remote Web Console instance.
+   *
+   * @param object aObject
+   *        The object you want to send to the remote Web Console instance.
+   * @param number aCacheId
+   *        Cache ID where you want object references to be stored into. The
+   *        given object may include references to other objects - those
+   *        references will be stored in the given cache ID so the remote
+   *        process can later retrieve them as well.
+   * @return array
+   *         An array that holds one element for each enumerable property and
+   *         method in aObject. Each element describes the property. For details
+   *         see WebConsoleUtils.namesAndValuesOf().
+   */
+  prepareObjectForRemote: function JST_prepareObjectForRemote(aObject, aCacheId)
+  {
+    // Cache the properties that have inspectable values.
+    let propCache = this._objectCache[aCacheId] || {};
+    let result = WebConsoleUtils.namesAndValuesOf(aObject, propCache);
+    if (!(aCacheId in this._objectCache) && Object.keys(propCache).length > 0) {
+      this._objectCache[aCacheId] = propCache;
+    }
+
+    return result;
+  },
+
+  /**
+   * Handler for the "JSTerm:Autocomplete" remote message. This handler provides
+   * completion results for user input. The "JSterm:AutocompleteProperties"
+   * message is sent to the remote process:
+   *   - id - the same as request ID.
+   *   - input - the user input (same as in the request message).
+   *   - matches - an array of matched properties (strings).
+   *   - matchProp - the part that was used from the user input for finding the
+   *   matches. For details see the JSPropertyProvider description and
+   *   implementation.
+   *
+   *
+   * @param object aRequest
+   *        The remote request object which holds two properties: an |id| and
+   *        the user |input|.
+   */
+  handleAutocomplete: function JST_handleAutocomplete(aRequest)
+  {
+    let result = JSPropertyProvider(this.window, aRequest.input) || {};
+    let message = {
+      id: aRequest.id,
+      input: aRequest.input,
+      matches: result.matches || [],
+      matchProp: result.matchProp,
+    };
+    Manager.sendMessage("JSTerm:AutocompleteProperties", message);
+  },
+
+  /**
+   * Create the JavaScript sandbox where user input is evaluated.
+   * @private
+   */
+  _createSandbox: function JST__createSandbox()
+  {
+    this._sandboxLocation = this.window.location;
+    this.sandbox = new Cu.Sandbox(this.window, {
+      sandboxPrototype: this.window,
+      wantXrays: false,
+    });
+
+    this.sandbox.console = this.console;
+
+    JSTermHelper(this);
+  },
+
+  /**
+   * Evaluates a string in the sandbox.
+   *
+   * @param string aString
+   *        String to evaluate in the sandbox.
+   * @return mixed
+   *         The result of the evaluation.
+   */
+  evalInSandbox: function JST_evalInSandbox(aString)
+  {
+    // If the user changed to a different location, we need to update the
+    // sandbox.
+    if (this._sandboxLocation !== this.window.location) {
+      this._createSandbox();
+    }
+
+    // The help function needs to be easy to guess, so we make the () optional
+    if (aString.trim() == "help" || aString.trim() == "?") {
+      aString = "help()";
+    }
+
+    let window = WebConsoleUtils.unwrap(this.sandbox.window);
+    let $ = null, $$ = null;
+
+    // We prefer to execute the page-provided implementations for the $() and
+    // $$() functions.
+    if (typeof window.$ == "function") {
+      $ = this.sandbox.$;
+      delete this.sandbox.$;
+    }
+    if (typeof window.$$ == "function") {
+      $$ = this.sandbox.$$;
+      delete this.sandbox.$$;
+    }
+
+    let result = Cu.evalInSandbox(aString, this.sandbox, "1.8",
+                                  "Web Console", 1);
+
+    if ($) {
+      this.sandbox.$ = $;
+    }
+    if ($$) {
+      this.sandbox.$$ = $$;
+    }
+
+    return result;
+  },
+
+  /**
+   * Destroy the JSTerm instance.
+   */
+  destroy: function JST_destroy()
+  {
+    for (let name in this._messageHandlers) {
+      Manager.removeMessageHandler(name);
+    }
+
+    delete this.sandbox;
+    delete this._sandboxLocation;
+    delete this._messageHandlers;
+    delete this._objectCache;
+  },
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// The window.console API observer
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The window.console API observer. This allows the window.console API messages
+ * to be sent to the remote Web Console instance.
+ */
+let ConsoleAPIObserver = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
+
+  /**
+   * Initialize the window.console API observer.
+   */
+  init: function CAO_init()
+  {
+    // Note that the observer is process-wide. We will filter the messages as
+    // needed, see CAO_observe().
+    Services.obs.addObserver(this, "console-api-log-event", false);
+
+    Manager.addMessageHandler("ConsoleAPI:ClearCache",
+                              this.handleClearCache.bind(this));
+  },
+
+  /**
+   * The console API message observer. When messages are received from the
+   * observer service we forward them to the remote Web Console instance.
+   *
+   * @param object aMessage
+   *        The message object receives from the observer service.
+   * @param string aTopic
+   *        The message topic received from the observer service.
+   */
+  observe: function CAO_observe(aMessage, aTopic)
+  {
+    if (!_alive || !aMessage || aTopic != "console-api-log-event") {
+      return;
+    }
+
+    let apiMessage = aMessage.wrappedJSObject;
+
+    let msgWindow =
+      WebConsoleUtils.getWindowByOuterId(apiMessage.ID, Manager.window);
+    if (!msgWindow || msgWindow.top != Manager.window) {
+      // Not the same window!
+      return;
+    }
+
+    let messageToChrome = {};
+    this._prepareApiMessageForRemote(apiMessage, messageToChrome);
+    Manager.sendMessage("WebConsole:ConsoleAPI", messageToChrome);
+  },
+
+  /**
+   * Prepare a message from the console APi to be sent to the remote Web Console
+   * instance.
+   *
+   * @param object aOriginalMessage
+   *        The original message received from console-api-log-event.
+   * @param object aRemoteMessage
+   *        The object you want to send to the remote Web Console. This object
+   *        is updated to hold information from the original message. New
+   *        properties added:
+   *        - timeStamp
+   *        Message timestamp (same as the aOriginalMessage.timeStamp property).
+   *        - apiMessage
+   *        An object that copies almost all the properties from
+   *        aOriginalMessage. Arguments might be skipped if it holds references
+   *        to objects that cannot be sent as they are to the remote Web Console
+   *        instance.
+   *        - argumentsToString
+   *        Optional: the aOriginalMessage.arguments object stringified.
+   *
+   *        The apiMessage.arguments property is set to hold data appropriate
+   *        to the message level. A similar approach is used for
+   *        argumentsToString.
+   */
+  _prepareApiMessageForRemote:
+  function CAO__prepareApiMessageForRemote(aOriginalMessage, aRemoteMessage)
+  {
+    aRemoteMessage.apiMessage =
+      WebConsoleUtils.cloneObject(aOriginalMessage, true,
+        function(aKey, aValue, aObject) {
+          // We need to skip the arguments property from the original object.
+          if (aKey == "wrappedJSObject" || aObject === aOriginalMessage &&
+              aKey == "arguments") {
+            return false;
+          }
+          return true;
+        });
+
+    aRemoteMessage.timeStamp = aOriginalMessage.timeStamp;
+
+    switch (aOriginalMessage.level) {
+      case "trace":
+      case "time":
+      case "timeEnd":
+      case "group":
+      case "groupCollapsed":
+        aRemoteMessage.apiMessage.arguments =
+          WebConsoleUtils.cloneObject(aOriginalMessage.arguments, true);
+        break;
+
+      case "groupEnd":
+        aRemoteMessage.argumentsToString =
+          Array.map(aOriginalMessage.arguments || [],
+                    this._formatObject.bind(this));
+        break;
+
+      case "log":
+      case "info":
+      case "warn":
+      case "error":
+      case "debug":
+      case "dir": {
+        aRemoteMessage.objectsCacheId = Manager.sequenceId;
+        aRemoteMessage.argumentsToString = [];
+        let mapFunction = function(aItem) {
+          let formattedObject = this._formatObject(aItem);
+          aRemoteMessage.argumentsToString.push(formattedObject);
+          if (WebConsoleUtils.isObjectInspectable(aItem)) {
+            return JSTerm.prepareObjectForRemote(aItem,
+                                                 aRemoteMessage.objectsCacheId);
+          }
+          return formattedObject;
+        }.bind(this);
+
+        aRemoteMessage.apiMessage.arguments =
+          Array.map(aOriginalMessage.arguments || [], mapFunction);
+        break;
+      }
+      default:
+        Cu.reportError("Unknown Console API log level: " +
+                       aOriginalMessage.level);
+        break;
+    }
+  },
+
+  /**
+   * Format an object's value to be displayed in the Web Console.
+   *
+   * @private
+   * @param object aObject
+   *        The object you want to display.
+   * @return string
+   *         The string you can display for the given object.
+   */
+  _formatObject: function CAO__formatObject(aObject)
+  {
+    return typeof aObject == "string" ?
+           aObject : WebConsoleUtils.formatResult(aObject);
+  },
+
+  /**
+   * Get the cached messages for the current inner window.
+   *
+   * @see this._prepareApiMessageForRemote()
+   * @return array
+   *         The array of cached messages. Each element is a Console API
+   *         prepared to be sent to the remote Web Console instance.
+   */
+  getCachedMessages: function CAO_getCachedMessages()
+  {
+    let innerWindowId = WebConsoleUtils.getInnerWindowId(Manager.window);
+    let messages = gConsoleStorage.getEvents(innerWindowId);
+
+    let result = messages.map(function(aMessage) {
+      let remoteMessage = { _type: "ConsoleAPI" };
+      this._prepareApiMessageForRemote(aMessage.wrappedJSObject, remoteMessage);
+      return remoteMessage;
+    }, this);
+
+    return result;
+  },
+
+  /**
+   * Handler for the "ConsoleAPI:ClearCache" message.
+   */
+  handleClearCache: function CAO_handleClearCache()
+  {
+    let windowId = WebConsoleUtils.getInnerWindowId(Manager.window);
+    gConsoleStorage.clearEvents(windowId);
+  },
+
+  /**
+   * Destroy the ConsoleAPIObserver listeners.
+   */
+  destroy: function CAO_destroy()
+  {
+    Manager.removeMessageHandler("ConsoleAPI:ClearCache");
+    Services.obs.removeObserver(this, "console-api-log-event");
+  },
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// The page errors listener
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The nsIConsoleService listener. This is used to send all the page errors
+ * (JavaScript, CSS and more) to the remote Web Console instance.
+ */
+let ConsoleListener = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleListener]),
+
+  /**
+   * Initialize the nsIConsoleService listener.
+   */
+  init: function CL_init()
+  {
+    Services.console.registerListener(this);
+  },
+
+  /**
+   * The nsIConsoleService observer. This method takes all the script error
+   * messages belonging to the current window and sends them to the remote Web
+   * Console instance.
+   *
+   * @param nsIScriptError aScriptError
+   *        The script error object coming from the nsIConsoleService.
+   */
+  observe: function CL_observe(aScriptError)
+  {
+    if (!_alive || !(aScriptError instanceof Ci.nsIScriptError) ||
+        !aScriptError.outerWindowID) {
+      return;
+    }
+
+    if (!this.isCategoryAllowed(aScriptError.category)) {
+      return;
+    }
+
+    let errorWindow =
+      WebConsoleUtils.getWindowByOuterId(aScriptError.outerWindowID,
+                                         Manager.window);
+    if (!errorWindow || errorWindow.top != Manager.window) {
+      return;
+    }
+
+    Manager.sendMessage("WebConsole:PageError", { pageError: aScriptError });
+  },
+
+
+  /**
+   * Check if the given script error category is allowed to be tracked or not.
+   * We ignore chrome-originating errors as we only care about content.
+   *
+   * @param string aCategory
+   *        The nsIScriptError category you want to check.
+   * @return boolean
+   *         True if the category is allowed to be logged, false otherwise.
+   */
+  isCategoryAllowed: function CL_isCategoryAllowed(aCategory)
+  {
+    switch (aCategory) {
+      case "XPConnect JavaScript":
+      case "component javascript":
+      case "chrome javascript":
+      case "chrome registration":
+      case "XBL":
+      case "XBL Prototype Handler":
+      case "XBL Content Sink":
+      case "xbl javascript":
+        return false;
+    }
+
+    return true;
+  },
+
+  /**
+   * Get the cached page errors for the current inner window.
+   *
+   * @return array
+   *         The array of cached messages. Each element is an nsIScriptError
+   *         with an added _type property so the remote Web Console instance can
+   *         tell the difference between various types of cached messages.
+   */
+  getCachedMessages: function CL_getCachedMessages()
+  {
+    let innerWindowId = WebConsoleUtils.getInnerWindowId(Manager.window);
+    let result = [];
+    let errors = {};
+    Services.console.getMessageArray(errors, {});
+
+    (errors.value || []).forEach(function(aError) {
+      if (!(aError instanceof Ci.nsIScriptError) ||
+          aError.innerWindowID != innerWindowId ||
+          !this.isCategoryAllowed(aError.category)) {
+        return;
+      }
+
+      let remoteMessage = WebConsoleUtils.cloneObject(aError);
+      remoteMessage._type = "PageError";
+      result.push(remoteMessage);
+    }, this);
+
+    return result;
+  },
+
+  /**
+   * Remove the nsIConsoleService listener.
+   */
+  destroy: function CL_destroy()
+  {
+    Services.console.unregisterListener(this);
+  },
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// 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 aHttpActivity
+ *        HttpActivity object associated with this request. Once the request is
+ *        complete the aHttpActivity object is updated to include the response
+ *        headers and body.
+ */
+function NetworkResponseListener(aHttpActivity) {
+  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 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.meta.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 (!_alive || this._foundOpenResponse) {
+      return;
+    }
+
+    let openResponse = null;
+
+    for each (let item in NetworkMonitor.openResponses) {
+      if (item.channel === this.httpActivity.channel) {
+        openResponse = item;
+        break;
+      }
+    }
+
+    if (!openResponse) {
+      return;
+    }
+    this._foundOpenResponse = true;
+
+    let logResponse = this.httpActivity.log.entries[0].response;
+    logResponse.headers = openResponse.headers;
+    logResponse.httpVersion = openResponse.httpVersion;
+    logResponse.status = openResponse.status;
+    logResponse.statusText = openResponse.statusText;
+    if (openResponse.cookies) {
+      logResponse.cookies = openResponse.cookies;
+    }
+
+    delete NetworkMonitor.openResponses[openResponse.id];
+
+    this.httpActivity.meta.stages.push("http-on-examine-response");
+    NetworkMonitor.sendActivity(this.httpActivity);
+  },
+
+  /**
+   * 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();
+
+    let meta = this.httpActivity.meta;
+    let entry = this.httpActivity.log.entries[0];
+    let request = entry.request;
+    let response = entry.response;
+
+    meta.stages.push("REQUEST_STOP");
+
+    if (!meta.discardResponseBody && this.receivedData.length) {
+      this._onComplete(this.receivedData);
+    }
+    else if (!meta.discardResponseBody && response.status == 304) {
+      // Response is cached, so we load it from cache.
+      let charset = this.request.contentCharset || this.httpActivity.charset;
+      NetworkHelper.loadFromCache(request.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 = this.httpActivity.log.entries[0].response;
+
+    try {
+      response.bodySize = response.status != 304 ? this.request.contentLength : 0;
+    }
+    catch (ex) {
+      response.bodySize = -1;
+    }
+
+    try {
+      response.content = { mimeType: this.request.contentType };
+    }
+    catch (ex) {
+      response.content = { mimeType: "" };
+    }
+
+    if (response.content.mimeType && this.request.contentCharset) {
+      response.content.mimeType += "; charset=" + this.request.contentCharset;
+    }
+
+    response.content.size = this.bodySize || (aData || "").length;
+
+    if (aData) {
+      response.content.text = aData;
+    }
+
+    this.receivedData = "";
+
+    if (_alive) {
+      NetworkMonitor.sendActivity(this.httpActivity);
+    }
+
+    this.httpActivity.channel = null;
+    this.httpActivity = null;
+    this.sink = null;
+    this.inputStream = null;
+    this.request = 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.
+ */
+let NetworkMonitor = {
+  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"
+  },
+
+  harCreator: {
+    name: Services.appinfo.name + " - Web Console",
+    version: Services.appinfo.version,
+  },
+
+  // Network response bodies are piped through a buffer of the given size (in
+  // bytes).
+  responsePipeSegmentSize: null,
+
+  /**
+   * Whether to save the bodies of network requests and responses. Disabled by
+   * default to save memory.
+   */
+  get saveRequestAndResponseBodies() {
+    return Manager.getPreference("NetworkMonitor.saveRequestAndResponseBodies");
+  },
+
+  openRequests: null,
+  openResponses: null,
+  progressListener: null,
+
+  /**
+   * The network monitor initializer.
+   *
+   * @param object aMessage
+   *        Initialization object sent by the remote Web Console instance. This
+   *        object can hold one property: monitorFileActivity - a boolean that
+   *        tells if monitoring of file:// requests should be enabled as well or
+   *        not.
+   */
+  init: function NM_init(aMessage)
+  {
+    this.responsePipeSegmentSize = Services.prefs
+                                   .getIntPref("network.buffer.cache.size");
+
+    this.openRequests = {};
+    this.openResponses = {};
+
+    activityDistributor.addObserver(this);
+
+    Services.obs.addObserver(this.httpResponseExaminer,
+                             "http-on-examine-response", false);
+
+    // Monitor file:// activity as well.
+    if (aMessage && aMessage.monitorFileActivity) {
+      ConsoleProgressListener.startMonitor(ConsoleProgressListener
+                                           .MONITOR_FILE_ACTIVITY);
+    }
+  },
+
+  /**
+   * Observe notifications for the http-on-examine-response topic, coming from
+   * the nsIObserverService.
+   *
+   * @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 (!_alive || aTopic != "http-on-examine-response" ||
+        !(aSubject instanceof Ci.nsIHttpChannel)) {
+      return;
+    }
+
+    let channel = aSubject.QueryInterface(Ci.nsIHttpChannel);
+    // Try to get the source window of the request.
+    let win = NetworkHelper.getWindowForRequest(channel);
+    if (!win || win.top !== Manager.window) {
+      return;
+    }
+
+    let response = {
+      id: Manager.sequenceId,
+      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;
+
+    NetworkMonitor.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 (!_alive ||
+        aActivityType != activityDistributor.ACTIVITY_TYPE_HTTP_TRANSACTION &&
+        aActivityType != activityDistributor.ACTIVITY_TYPE_SOCKET_TRANSPORT) {
+      return;
+    }
+
+    if (!(aChannel instanceof Ci.nsIHttpChannel)) {
+      return;
+    }
+
+    aChannel = aChannel.QueryInterface(Ci.nsIHttpChannel);
+
+    if (aActivitySubtype ==
+        activityDistributor.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.meta.stages.push(stage);
+        httpActivity.timings[stage] = {
+          first: aTimestamp,
+          last: aTimestamp,
+        };
+      }
+    }
+
+    switch (aActivitySubtype) {
+      case activityDistributor.ACTIVITY_SUBTYPE_REQUEST_BODY_SENT:
+        this._onRequestBodySent(httpActivity);
+        break;
+      case activityDistributor.ACTIVITY_SUBTYPE_RESPONSE_HEADER:
+        this._onResponseHeader(httpActivity, aExtraStringData);
+        break;
+      case activityDistributor.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)
+  {
+    // Try to get the source window of the request.
+    let win = NetworkHelper.getWindowForRequest(aChannel);
+    if (!win || win.top !== Manager.window) {
+      return;
+    }
+
+    let httpActivity = this.createActivityObject(aChannel);
+    httpActivity.charset = win.document.characterSet; // see NM__onRequestBodySent()
+    httpActivity.meta.stages.push("REQUEST_HEADER"); // activity stage (aActivitySubtype)
+
+    httpActivity.timings.REQUEST_HEADER = {
+      first: aTimestamp,
+      last: aTimestamp
+    };
+
+    let entry = httpActivity.log.entries[0];
+    entry.startedDateTime = new Date(Math.round(aTimestamp / 1000)).toISOString();
+
+    let request = httpActivity.log.entries[0].request;
+
+    let cookieHeader = null;
+
+    // Copy the request header data.
+    aChannel.visitRequestHeaders({
+      visitHeader: function NM__visitHeader(aName, aValue)
+      {
+        if (aName == "Cookie") {
+          cookieHeader = aValue;
+        }
+        request.headers.push({ name: aName, value: aValue });
+      }
+    });
+
+    if (cookieHeader) {
+      request.cookies = NetworkHelper.parseCookieHeader(cookieHeader);
+    }
+
+    // Determine the HTTP version.
+    let httpVersionMaj = {};
+    let httpVersionMin = {};
+
+    aChannel.QueryInterface(Ci.nsIHttpChannelInternal);
+    aChannel.getRequestVersion(httpVersionMaj, httpVersionMin);
+
+    request.httpVersion = "HTTP/" + httpVersionMaj.value + "." +
+                                    httpVersionMin.value;
+
+    request.headersSize = aExtraStringData.length;
+
+    this._setupResponseListener(httpActivity);
+
+    this.openRequests[httpActivity.id] = httpActivity;
+
+    this.sendActivity(httpActivity);
+  },
+
+  /**
+   * 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 {
+      hudId: Manager.hudId,
+      id: Manager.sequenceId,
+      channel: aChannel,
+      charset: null, // see NM__onRequestHeader()
+      meta: { // holds metadata about the activity object
+        stages: [], // activity stages (aActivitySubtype)
+        discardRequestBody: !this.saveRequestAndResponseBodies,
+        discardResponseBody: !this.saveRequestAndResponseBodies,
+      },
+      timings: {}, // internal timing information, see NM_observeActivity()
+      log: { // HAR-like object
+        version: "1.2",
+        creator: this.harCreator,
+        // missing |browser| and |pages|
+        entries: [{  // we only track one entry at a time
+          connection: Manager.sequenceId, // connection ID
+          startedDateTime: 0, // see NM__onRequestHeader()
+          time: 0, // see NM__setupHarTimings()
+          // missing |serverIPAddress| and |cache|
+          request: {
+            method: aChannel.requestMethod,
+            url: aChannel.URI.spec,
+            httpVersion: "", // see NM__onRequestHeader()
+            headers: [], // see NM__onRequestHeader()
+            cookies: [], // see NM__onRequestHeader()
+            queryString: [], // never set
+            headersSize: -1, // see NM__onRequestHeader()
+            bodySize: -1, // see NM__onRequestBodySent()
+            postData: null, // see NM__onRequestBodySent()
+          },
+          response: {
+            status: 0, // see NM__onResponseHeader()
+            statusText: "", // see NM__onResponseHeader()
+            httpVersion: "", // see NM__onResponseHeader()
+            headers: [], // see NM_httpResponseExaminer()
+            cookies: [], // see NM_httpResponseExaminer()
+            content: null, // see NRL_onStreamClose()
+            redirectURL: "", // never set
+            headersSize: -1, // see NM__onResponseHeader()
+            bodySize: -1, // see NRL_onStreamClose()
+          },
+          timings: {}, // see NM__setupHarTimings()
+        }],
+      },
+    };
+  },
+
+  /**
+   * 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(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);
+  },
+
+  /**
+   * Send an HTTP activity object to the remote Web Console instance.
+   * A WebConsole:NetworkActivity message is sent. The message holds two
+   * properties:
+   *   - meta - the |aHttpActivity.meta| object.
+   *   - log - the |aHttpActivity.log| object.
+   *
+   * @param object aHttpActivity
+   *        The HTTP activity object you want to send.
+   */
+  sendActivity: function NM_sendActivity(aHttpActivity)
+  {
+    Manager.sendMessage("WebConsole:NetworkActivity", {
+      meta: aHttpActivity.meta,
+      log: aHttpActivity.log,
+    });
+  },
+
+  /**
+   * 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.meta.discardRequestBody) {
+      return;
+    }
+
+    let request = aHttpActivity.log.entries[0].request;
+
+    let sentBody = NetworkHelper.
+                   readPostTextFromRequest(aHttpActivity.channel,
+                                           aHttpActivity.charset);
+
+    if (!sentBody && request.url == Manager.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.readPostTextFromPage()
+      // 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.
+      sentBody = NetworkHelper.readPostTextFromPage(docShell,
+                                                    aHttpActivity.charset);
+    }
+    if (!sentBody) {
+      return;
+    }
+
+    request.postData = {
+      mimeType: "", // never set
+      params: [],  // never set
+      text: sentBody,
+    };
+
+    request.bodySize = sentBody.length;
+
+    this.sendActivity(aHttpActivity);
+  },
+
+  /**
+   * 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 response = aHttpActivity.log.entries[0].response;
+
+    let headers = aExtraStringData.split(/\r\n|\n|\r/);
+    let statusLine = headers.shift();
+
+    let statusLineArray = statusLine.split(" ");
+    response.httpVersion = statusLineArray.shift();
+    response.status = statusLineArray.shift();
+    response.statusText = statusLineArray.join(" ");
+    response.headersSize = aExtraStringData.length;
+
+    // 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.meta.discardResponseBody = true;
+        break;
+    }
+
+    this.sendActivity(aHttpActivity);
+  },
+
+  /**
+   * 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)
+  {
+    this._setupHarTimings(aHttpActivity);
+    this.sendActivity(aHttpActivity);
+    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.
+   */
+  _setupHarTimings: function NM__setupHarTimings(aHttpActivity)
+  {
+    let timings = aHttpActivity.timings;
+    let entry = aHttpActivity.log.entries[0];
+    let harTimings = entry.timings;
+
+    // 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;
+    }
+
+    entry.time = 0;
+    for (let timing in harTimings) {
+      let time = Math.max(Math.round(harTimings[timing] / 1000), -1);
+      harTimings[timing] = time;
+      if (time > -1) {
+        entry.time += time;
+      }
+    }
+  },
+
+  /**
+   * Suspend Web Console activity. This is called when all Web Consoles are
+   * closed.
+   */
+  destroy: function NM_destroy()
+  {
+    Services.obs.removeObserver(this.httpResponseExaminer,
+                                "http-on-examine-response");
+
+    activityDistributor.removeObserver(this);
+
+    ConsoleProgressListener.stopMonitor(ConsoleProgressListener
+                                        .MONITOR_FILE_ACTIVITY);
+
+    delete this.openRequests;
+    delete this.openResponses;
+  },
+};
+
+/**
+ * A WebProgressListener that listens for location changes.
+ *
+ * This progress listener is used to track file loads and other kinds of
+ * location changes.
+ *
+ * When a file:// URI is loaded a "WebConsole:FileActivity" message is sent to
+ * the remote Web Console instance. The message JSON holds only one property:
+ * uri (the file URI).
+ *
+ * When the current page location changes a "WebConsole:LocationChange" message
+ * is sent. See ConsoleProgressListener.sendLocation() for details.
+ */
+let ConsoleProgressListener = {
+  /**
+   * 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,
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+                                         Ci.nsISupportsWeakReference]),
+
+  /**
+   * Initialize the ConsoleProgressListener.
+   * @private
+   */
+  _init: function CPL__init()
+  {
+    if (this._initialized) {
+      return;
+    }
+
+    this._initialized = true;
+    let webProgress = docShell.QueryInterface(Ci.nsIWebProgress);
+    webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_ALL);
+  },
+
+  /**
+   * 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("HUDService-content: unknown monitor type " +
+                        aMonitor + " for the ConsoleProgressListener!");
+    }
+    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("HUDService-content: unknown monitor type " +
+                        aMonitor + " for the ConsoleProgressListener!");
+    }
+
+    if (!this._fileActivity && !this._locationChange) {
+      this.destroy();
+    }
+  },
+
+  onStateChange:
+  function CPL_onStateChange(aProgress, aRequest, aState, aStatus)
+  {
+    if (!_alive) {
+      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;
+    }
+
+    Manager.sendMessage("WebConsole:FileActivity", {uri: 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 != Manager.window) {
+      return;
+    }
+
+    if (isStart && aRequest instanceof Ci.nsIChannel) {
+      this.sendLocation(aRequest.URI.spec, "");
+    }
+    else if (isStop) {
+      this.sendLocation(Manager.window.location.href,
+                        Manager.window.document.title);
+    }
+  },
+
+  onLocationChange: function() {},
+  onStatusChange: function() {},
+  onProgressChange: function() {},
+  onSecurityChange: function() {},
+
+  /**
+   * Send the location of the current top window to the remote Web Console.
+   * A "WebConsole:LocationChange" message is sent. The JSON object holds two
+   * properties: location and title.
+   *
+   * @param string aLocation
+   *        Current page address.
+   * @param string aTitle
+   *        Current page title.
+   */
+  sendLocation: function CPL_sendLocation(aLocation, aTitle)
+  {
+    let message = {
+      "location": aLocation,
+      "title": aTitle,
+    };
+    Manager.sendMessage("WebConsole:LocationChange", message);
+  },
+
+  /**
+   * Destroy the ConsoleProgressListener.
+   */
+  destroy: function CPL_destroy()
+  {
+    if (!this._initialized) {
+      return;
+    }
+
+    this._initialized = false;
+    this._fileActivity = false;
+    this._locationChange = false;
+    let webProgress = docShell.QueryInterface(Ci.nsIWebProgress);
+    webProgress.removeProgressListener(this);
+  },
+};
+
+Manager.init();
+})();
--- a/browser/devtools/webconsole/HUDService.jsm
+++ b/browser/devtools/webconsole/HUDService.jsm
@@ -52,16 +52,19 @@ const MINIMUM_CONSOLE_HEIGHT = 150;
 
 // Minimum page height, in pixels. This prevents the Web Console from
 // remembering a height that covers the whole page.
 const MINIMUM_PAGE_HEIGHT = 50;
 
 // The default console height, as a ratio from the content window inner height.
 const DEFAULT_CONSOLE_HEIGHT = 0.33;
 
+// This script is inserted into the content process.
+const CONTENT_SCRIPT_URL = "chrome://browser/content/devtools/HUDService-content.js";
+
 // points to the file to load in the Web Console iframe.
 const UI_IFRAME_URL = "chrome://browser/content/devtools/webconsole.xul";
 
 ///////////////////////////////////////////////////////////////////////////
 //// The HUD service
 
 function HUD_SERVICE()
 {
@@ -492,42 +495,46 @@ HUD_SERVICE.prototype =
  * This object only wraps the iframe that holds the Web Console UI.
  *
  * @param nsIDOMElement aTab
  *        The xul:tab for which you want the WebConsole object.
  */
 function WebConsole(aTab)
 {
   this.tab = aTab;
-  this.chromeDocument = this.tab.ownerDocument;
-  this.chromeWindow = this.chromeDocument.defaultView;
-  this.hudId = "hud_" + this.tab.linkedPanel;
   this._onIframeLoad = this._onIframeLoad.bind(this);
-  this._initUI();
+  this._asyncRequests = {};
+  this._init();
 }
 
 WebConsole.prototype = {
   /**
    * The xul:tab for which the current Web Console instance was created.
    * @type nsIDOMElement
    */
   tab: null,
 
-  chromeWindow: null,
-  chromeDocument: null,
-
   /**
    * Getter for HUDService.lastFinishedRequestCallback.
    *
    * @see HUDService.lastFinishedRequestCallback
    * @type function
    */
   get lastFinishedRequestCallback() HUDService.lastFinishedRequestCallback,
 
   /**
+   * Track callback functions registered for specific async requests sent to
+   * the content process.
+   *
+   * @private
+   * @type object
+   */
+  _asyncRequests: null,
+
+  /**
    * The xul:panel that holds the Web Console when it is positioned as a window.
    * @type nsIDOMElement
    */
   consolePanel: null,
 
   /**
    * Getter for the xul:popupset that holds any popups we open.
    * @type nsIDOMElement
@@ -544,16 +551,32 @@ WebConsole.prototype = {
   get outputNode()
   {
     return this.ui ? this.ui.outputNode : null;
   },
 
   get gViewSourceUtils() this.chromeWindow.gViewSourceUtils,
 
   /**
+   * Initialize the Web Console instance.
+   * @private
+   */
+  _init: function WC__init()
+  {
+    this.chromeDocument = this.tab.ownerDocument;
+    this.chromeWindow = this.chromeDocument.defaultView;
+    this.messageManager = this.tab.linkedBrowser.messageManager;
+    this.hudId = "hud_" + this.tab.linkedPanel;
+    this.notificationBox = this.chromeDocument
+                           .getElementById(this.tab.linkedPanel);
+
+    this._initUI();
+  },
+
+  /**
    * Initialize the Web Console UI. This method sets up the iframe.
    * @private
    */
   _initUI: function WC__initUI()
   {
     this.splitter = this.chromeDocument.createElement("splitter");
     this.splitter.className = "devtools-horizontal-splitter";
 
@@ -755,17 +778,17 @@ WebConsole.prototype = {
       this._createOwnWindowPanel();
       return;
     }
 
     let height = this.iframe.clientHeight;
 
     // get the node position index
     let nodeIdx = this.positions[aPosition];
-    let nBox = this.chromeDocument.getElementById(this.tab.linkedPanel);
+    let nBox = this.notificationBox;
     let node = nBox.childNodes[nodeIdx];
 
     // check to see if console is already positioned in aPosition
     if (node == this.iframe) {
       return;
     }
 
     let lastIndex = -1;
@@ -860,16 +883,102 @@ WebConsole.prototype = {
    * @private
    */
   _onClearButton: function WC__onClearButton()
   {
     this.chromeWindow.DeveloperToolbar.resetErrorsCount(this.tab);
   },
 
   /**
+   * Setup the message manager used to communicate with the Web Console content
+   * script. This method loads the content script, adds the message listeners
+   * and initializes the connection to the content script.
+   *
+   * @private
+   */
+  _setupMessageManager: function WC__setupMessageManager()
+  {
+    this.messageManager.loadFrameScript(CONTENT_SCRIPT_URL, true);
+
+    this._messageListeners.forEach(function(aName) {
+      this.messageManager.addMessageListener(aName, this.ui);
+    }, this);
+
+    let message = {
+      features: ["NetworkMonitor", "LocationChange"],
+      NetworkMonitor: { monitorFileActivity: true },
+      preferences: {
+        "NetworkMonitor.saveRequestAndResponseBodies":
+          this.ui.saveRequestAndResponseBodies,
+      },
+    };
+
+    this.sendMessageToContent("WebConsole:Init", message);
+  },
+
+  /**
+   * Handler for messages that have an associated callback function. The
+   * this.sendMessageToContent() allows one to provide a function to be invoked
+   * when the content script replies to the message previously sent. This is the
+   * method that invokes the callback.
+   *
+   * @see this.sendMessageToContent
+   * @private
+   * @param object aResponse
+   *        Message object received from the content script.
+   */
+  _receiveMessageWithCallback:
+  function WC__receiveMessageWithCallback(aResponse)
+  {
+    if (aResponse.id in this._asyncRequests) {
+      let request = this._asyncRequests[aResponse.id];
+      request.callback(aResponse, request.message);
+      delete this._asyncRequests[aResponse.id];
+    }
+    else {
+      Cu.reportError("receiveMessageWithCallback response for stale request " +
+                     "ID " + aResponse.id);
+    }
+  },
+
+  /**
+   * Send a message to the content script.
+   *
+   * @param string aName
+   *        The name of the message you want to send.
+   *
+   * @param object aMessage
+   *        The message object you want to send. This object needs to have no
+   *        cyclic references and it needs to be JSON-stringifiable.
+   *
+   * @param function [aCallback]
+   *        Optional function you want to have called when the content script
+   *        replies to your message. Your callback receives two arguments:
+   *        (1) the response object from the content script and (2) the message
+   *        you sent to the content script (which is aMessage here).
+   */
+  sendMessageToContent:
+  function WC_sendMessageToContent(aName, aMessage, aCallback)
+  {
+    aMessage.hudId = this.hudId;
+    if (!("id" in aMessage)) {
+      aMessage.id = "HUDChrome-" + HUDService.sequenceId();
+    }
+
+    if (aCallback) {
+      this._asyncRequests[aMessage.id] = {
+        name: aName,
+        message: aMessage,
+        callback: aCallback,
+      };
+    }
+    this.messageManager.sendAsyncMessage(aName, aMessage);
+  },
+
+  /**
    * Handler for page location changes. If the Web Console is
    * opened in a panel the panel title is updated.
    *
    * @param string aURI
    *        New page location.
    * @param string aTitle
    *        New page title.
    */
--- a/browser/devtools/webconsole/test/browser_console_log_inspectable_object.js
+++ b/browser/devtools/webconsole/test/browser_console_log_inspectable_object.js
@@ -3,17 +3,17 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test that objects given to console.log() are inspectable.
 
 function test()
 {
   waitForExplicitFinish();
 
-  addTab("data:text/html;charset=utf8,test for bug 676722 - inspectable objects for window.console");
+  addTab("data:text/html,test for bug 676722 - inspectable objects for window.console");
 
   gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
     gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
     openConsole(null, performTest);
   }, true);
 }
 
 function performTest(hud)
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_595223_file_uri.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_595223_file_uri.js
@@ -32,19 +32,19 @@ function test() {
   let jar = getJar(getRootDirectory(gTestPath));
   let dir = jar ?
             extractJarToTmp(jar) :
             getChromeDir(getResolvedURI(gTestPath));
   dir.append(TEST_FILE);
 
   let uri = Services.io.newFileURI(dir);
 
-  addTab("data:text/html;charset=utf8,<p>test file URI");
+  addTab(uri.spec);
   browser.addEventListener("load", function tabLoad() {
     browser.removeEventListener("load", tabLoad, true);
     openConsole(null, function(aHud) {
       hud = aHud;
       hud.jsterm.clearOutput();
       browser.addEventListener("load", tabReload, true);
-      content.location = uri.spec;
+      content.location.reload();
     });
   }, true);
 }
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_651501_document_body_autocomplete.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_651501_document_body_autocomplete.js
@@ -33,18 +33,17 @@ function consoleOpened(aHud) {
     popup._panel.removeEventListener("popupshown", onShown, false);
 
     ok(popup.isOpen, "popup is open");
 
     // |props| values, and the following properties:
     // __defineGetter__  __defineSetter__ __lookupGetter__ __lookupSetter__
     // constructor hasOwnProperty isPrototypeOf propertyIsEnumerable
     // toLocaleString toSource toString unwatch valueOf watch.
-    let props = WCU.inspectObject(content.wrappedJSObject.document.body,
-                                  function() { });
+    let props = WCU.namesAndValuesOf(content.wrappedJSObject.document.body);
     is(popup.itemCount, 14 + props.length, "popup.itemCount is correct");
 
     popup._panel.addEventListener("popuphidden", autocompletePopupHidden, false);
 
     EventUtils.synthesizeKey("VK_ESCAPE", {});
   }, false);
 
   jsterm.setInputValue("document.body");
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_737873_mixedcontent.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_737873_mixedcontent.js
@@ -8,17 +8,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 // Tests that the Web Console Mixed Content messages are displayed
 
 const TEST_HTTPS_URI = "https://example.com/browser/browser/devtools/webconsole/test/test-bug-737873-mixedcontent.html";
 
 function test() {
-  addTab("data:text/html;charset=utf8,Web Console mixed content test");
+  addTab("data:text/html,Web Console basic network logging test");
   browser.addEventListener("load", onLoad, true);
 }
 
 function onLoad(aEvent) {
   browser.removeEventListener("load", onLoad, true);
   openConsole(null, testMixedContent);
 }
 
@@ -63,19 +63,18 @@ function testSeverity(node) {
 }
 
 function testClickOpenNewTab(warningNode) {
   /* Invoke the click event and check if a new tab would open to the correct page */
   let linkOpened = false;
   let oldOpenUILinkIn = window.openUILinkIn;
 
   window.openUILinkIn = function(aLink) {
-    if (aLink == "https://developer.mozilla.org/en/Security/MixedContent") {
-      linkOpened = true;
-    }
+   if (aLink == "https://developer.mozilla.org/en/Security/MixedContent");
+     linkOpened = true;
   }
 
   EventUtils.synthesizeMouse(warningNode, 2, 2, {},
                              warningNode.ownerDocument.defaultView);
 
   ok(linkOpened, "Clicking the Mixed Content Warning node opens the desired page");
 
   window.openUILinkIn = oldOpenUILinkIn;
--- a/browser/devtools/webconsole/test/test-bug-737873-mixedcontent.html
+++ b/browser/devtools/webconsole/test/test-bug-737873-mixedcontent.html
@@ -1,11 +1,10 @@
 <!DOCTYPE HTML>
 <html dir="ltr" xml:lang="en-US" lang="en-US"><head>
-    <meta charset="utf8">
     <title>Mixed Content test - http on https</title>
     <script src="testscript.js"></script>
     <!--
        - Any copyright is dedicated to the Public Domain.
        - http://creativecommons.org/publicdomain/zero/1.0/
        -->
   </head>
   <body>
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -182,27 +182,24 @@ function WebConsoleFrame(aWebConsoleOwne
 
   this._cssNodes = {};
   this._outputQueue = [];
   this._pruneCategoriesQueue = {};
   this._networkRequests = {};
 
   this._toggleFilter = this._toggleFilter.bind(this);
   this._onPositionConsoleCommand = this._onPositionConsoleCommand.bind(this);
-  this._flushMessageQueue = this._flushMessageQueue.bind(this);
-
-  this._outputTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-  this._outputTimerInitialized = false;
 
   this._initDefaultFilterPrefs();
   this._commandController = new CommandController(this);
   this.positionConsole(aPosition, window);
 
   this.jsterm = new JSTerm(this);
   this.jsterm.inputNode.focus();
+
   this._initConnection();
 }
 
 WebConsoleFrame.prototype = {
   /**
    * The WebConsole instance that owns this frame.
    * @see HUDService.jsm::WebConsole
    * @type object
@@ -263,25 +260,16 @@ WebConsoleFrame.prototype = {
    * tests.
    *
    * @private
    * @type function
    */
   _flushCallback: null,
 
   /**
-   * Timer used for flushing the messages output queue.
-   *
-   * @private
-   * @type nsITimer
-   */
-  _outputTimer: null,
-  _outputTimerInitialized: null,
-
-  /**
    * Store for tracking repeated CSS nodes.
    * @private
    * @type object
    */
   _cssNodes: null,
 
   /**
    * Preferences for filtering messages by type.
@@ -1733,58 +1721,58 @@ WebConsoleFrame.prototype = {
     if (!this._outputQueue.length) {
       // If the queue is empty we consider that now was the last output flush.
       // This avoid an immediate output flush when the timer executes.
       this._lastOutputFlush = Date.now();
     }
 
     this._outputQueue.push([aCategory, aMethodOrNode, aArguments]);
 
-    if (!this._outputTimerInitialized) {
-      this._initOutputTimer();
+    if (!this._outputTimeout) {
+      this._outputTimeout =
+        this.window.setTimeout(this._flushMessageQueue.bind(this),
+                               OUTPUT_INTERVAL);
     }
   },
 
   /**
    * Try to flush the output message queue. This takes the messages in the
    * output queue and displays them. Outputting stops at MESSAGES_IN_INTERVAL.
    * Further output is queued to happen later - see OUTPUT_INTERVAL.
    *
    * @private
    */
   _flushMessageQueue: function WCF__flushMessageQueue()
   {
-    if (!this._outputTimer) {
-      return;
-    }
-
     let timeSinceFlush = Date.now() - this._lastOutputFlush;
     if (this._outputQueue.length > MESSAGES_IN_INTERVAL &&
         timeSinceFlush < THROTTLE_UPDATES) {
-      this._initOutputTimer();
+      this._outputTimeout =
+        this.window.setTimeout(this._flushMessageQueue.bind(this),
+                               OUTPUT_INTERVAL);
       return;
     }
 
     // Determine how many messages we can display now.
     let toDisplay = Math.min(this._outputQueue.length, MESSAGES_IN_INTERVAL);
     if (toDisplay < 1) {
-      this._outputTimerInitialized = false;
+      this._outputTimeout = null;
       return;
     }
 
     // Try to prune the message queue.
     let shouldPrune = false;
     if (this._outputQueue.length > toDisplay && this._pruneOutputQueue()) {
       toDisplay = Math.min(this._outputQueue.length, toDisplay);
       shouldPrune = true;
     }
 
     let batch = this._outputQueue.splice(0, toDisplay);
     if (!batch.length) {
-      this._outputTimerInitialized = false;
+      this._outputTimeout = null;
       return;
     }
 
     let outputNode = this.outputNode;
     let lastVisibleNode = null;
     let scrolledToBottom = Utils.isOutputScrolledToBottom(outputNode);
     let scrollBox = outputNode.scrollBoxObject.element;
 
@@ -1832,43 +1820,29 @@ WebConsoleFrame.prototype = {
              oldScrollHeight != scrollBox.scrollHeight) {
       // If there were pruned messages and if scroll is not at the bottom, then
       // we need to adjust the scroll location.
       scrollBox.scrollTop -= oldScrollHeight - scrollBox.scrollHeight;
     }
 
     // If the queue is not empty, schedule another flush.
     if (this._outputQueue.length > 0) {
-      this._initOutputTimer();
+      this._outputTimeout =
+        this.window.setTimeout(this._flushMessageQueue.bind(this),
+                               OUTPUT_INTERVAL);
     }
     else {
-      this._outputTimerInitialized = false;
+      this._outputTimeout = null;
       this._flushCallback && this._flushCallback();
     }
 
     this._lastOutputFlush = Date.now();
   },
 
   /**
-   * Initialize the output timer.
-   * @private
-   */
-  _initOutputTimer: function WCF__initOutputTimer()
-  {
-    if (!this._outputTimer) {
-      return;
-    }
-
-    this._outputTimerInitialized = true;
-    this._outputTimer.initWithCallback(this._flushMessageQueue,
-                                       OUTPUT_INTERVAL,
-                                       Ci.nsITimer.TYPE_ONE_SHOT);
-  },
-
-  /**
    * Output a message from the queue.
    *
    * @private
    * @param nsISupportsString aHudIdSupportsString
    *        The HUD ID as an nsISupportsString.
    * @param array aItem
    *        An item from the output queue - this item represents a message.
    * @return nsIDOMElement|undefined
@@ -2566,19 +2540,17 @@ WebConsoleFrame.prototype = {
    * Release an actor.
    *
    * @private
    * @param string aActor
    *        The actor ID you want to release.
    */
   _releaseObject: function WCF__releaseObject(aActor)
   {
-    if (this.proxy) {
-      this.proxy.releaseActor(aActor);
-    }
+    this.proxy.releaseActor(aActor);
   },
 
   /**
    * Open the selected item's URL in a new tab.
    */
   openSelectedItemInTab: function WCF_openSelectedItemInTab()
   {
     let item = this.outputNode.selectedItem;
@@ -2595,38 +2567,23 @@ WebConsoleFrame.prototype = {
    * Console is closed.
    *
    * @param function [aOnDestroy]
    *        Optional function to invoke when the Web Console instance is
    *        destroyed.
    */
   destroy: function WCF_destroy(aOnDestroy)
   {
-    this._cssNodes = {};
-    this._outputQueue = [];
-    this._pruneCategoriesQueue = {};
-    this._networkRequests = {};
-
-    if (this._outputTimerInitialized) {
-      this._outputTimerInitialized = false;
-      this._outputTimer.cancel();
-    }
-    this._outputTimer = null;
-
     if (this.proxy) {
       this.proxy.disconnect(aOnDestroy);
-      this.proxy = null;
     }
 
     if (this.jsterm) {
       this.jsterm.destroy();
-      this.jsterm = null;
     }
-
-    this._commandController = null;
   },
 };
 
 /**
  * Create a JSTerminal (a JavaScript command line). This is attached to an
  * existing HeadsUpDisplay (a Web Console instance). This code is responsible
  * with handling command line input, code evaluation and result output.
  *
@@ -2713,20 +2670,16 @@ JSTerm.prototype = {
    *        Optional function to invoke when the evaluation result is added to
    *        the output.
    * @param object aResponse
    *        The message received from the server.
    */
   _executeResultCallback:
   function JST__executeResultCallback(aAfterNode, aCallback, aResponse)
   {
-    if (!this.hud) {
-      return;
-    }
-
     let errorMessage = aResponse.errorMessage;
     let result = aResponse.result;
     let inspectable = result && typeof result == "object" && result.inspectable;
     let helperResult = aResponse.helperResult;
     let helperHasRawOutput = !!(helperResult || {}).rawOutput;
     let resultString =
       WebConsoleUtils.objectActorGripToString(result,
                                               !helperHasRawOutput);
@@ -3636,23 +3589,20 @@ JSTerm.prototype = {
    * Destroy the JSTerm object. Call this method to avoid memory leaks.
    */
   destroy: function JST_destroy()
   {
     this.clearCompletion();
     this.clearOutput();
 
     this.autocompletePopup.destroy();
-    this.autocompletePopup = null;
 
     this.inputNode.removeEventListener("keypress", this._keyPress, false);
     this.inputNode.removeEventListener("input", this._inputEventHandler, false);
     this.inputNode.removeEventListener("keyup", this._inputEventHandler, false);
-
-    this.hud = null;
   },
 };
 
 /**
  * Utils: a collection of globally used functions.
  */
 var Utils = {
   /**
@@ -3883,24 +3833,16 @@ WebConsoleConnectionProxy.prototype = {
    * The DebuggerClient object.
    *
    * @see DebuggerClient
    * @type object
    */
   client: null,
 
   /**
-   * The WebConsoleClient object.
-   *
-   * @see WebConsoleClient
-   * @type object
-   */
-  webConsoleClient: null,
-
-  /**
    * Tells if the connection is established.
    * @type boolean
    */
   connected: false,
 
   /**
    * The WebConsoleActor ID.
    *
@@ -4157,17 +4099,16 @@ WebConsoleConnectionProxy.prototype = {
     this.client.removeListener("networkEventUpdate", this._onNetworkEventUpdate);
     this.client.removeListener("fileActivity", this._onFileActivity);
     this.client.removeListener("locationChange", this._onLocationChange);
     this.client.close(aOnDisconnect);
 
     this.client = null;
     this.webConsoleClient = null;
     this.connected = false;
-    this.owner = null;
   },
 };
 
 function gSequenceId()
 {
   return gSequenceId.n++;
 }
 gSequenceId.n = 0;
--- a/toolkit/devtools/webconsole/WebConsoleUtils.jsm
+++ b/toolkit/devtools/webconsole/WebConsoleUtils.jsm
@@ -39,17 +39,31 @@ var EXPORTED_SYMBOLS = ["WebConsoleUtils
 // (function foobar(a, b) { ...
 // function foobar2(a) { ...
 // function() { ...
 const REGEX_MATCH_FUNCTION_NAME = /^\(?function\s+([^(\s]+)\s*\(/;
 
 // Match the function arguments from the result of toString() or toSource().
 const REGEX_MATCH_FUNCTION_ARGS = /^\(?function\s*[^\s(]*\s*\((.+?)\)/;
 
+const TYPES = { OBJECT: 0,
+                FUNCTION: 1,
+                ARRAY: 2,
+                OTHER: 3,
+                ITERATOR: 4,
+                GETTER: 5,
+                GENERATOR: 6,
+                STRING: 7
+              };
+
+var gObjectId = 0;
+
 var WebConsoleUtils = {
+  TYPES: TYPES,
+
   /**
    * Convenience function to unwrap a wrapped object.
    *
    * @param aObject the object to unwrap.
    * @return aObject unwrapped.
    */
   unwrap: function WCU_unwrap(aObject)
   {
@@ -336,16 +350,113 @@ var WebConsoleUtils = {
       // the Web Console. If the constructor.name is a getter that throws, or
       // something else bad happens.
     }
 
     return type.toLowerCase();
   },
 
   /**
+   * Figures out the type of aObject and the string to display as the object
+   * value.
+   *
+   * @see TYPES
+   * @param object aObject
+   *        The object to operate on.
+   * @return object
+   *         An object of the form:
+   *         {
+   *           type: TYPES.OBJECT || TYPES.FUNCTION || ...
+   *           display: string for displaying the object
+   *         }
+   */
+  presentableValueFor: function WCU_presentableValueFor(aObject)
+  {
+    let type = this.getResultType(aObject);
+    let presentable;
+
+    switch (type) {
+      case "undefined":
+      case "null":
+        return {
+          type: TYPES.OTHER,
+          display: type
+        };
+
+      case "array":
+        return {
+          type: TYPES.ARRAY,
+          display: "Array"
+        };
+
+      case "string":
+        return {
+          type: TYPES.STRING,
+          display: "\"" + aObject + "\""
+        };
+
+      case "date":
+      case "regexp":
+      case "number":
+      case "boolean":
+        return {
+          type: TYPES.OTHER,
+          display: aObject.toString()
+        };
+
+      case "iterator":
+        return {
+          type: TYPES.ITERATOR,
+          display: "Iterator"
+        };
+
+      case "function":
+        presentable = aObject.toString();
+        return {
+          type: TYPES.FUNCTION,
+          display: presentable.substring(0, presentable.indexOf(')') + 1)
+        };
+
+      default:
+        presentable = String(aObject);
+        let m = /^\[object (\S+)\]/.exec(presentable);
+
+        try {
+          if (typeof aObject == "object" && typeof aObject.next == "function" &&
+              m && m[1] == "Generator") {
+            return {
+              type: TYPES.GENERATOR,
+              display: m[1]
+            };
+          }
+        }
+        catch (ex) {
+          // window.history.next throws in the typeof check above.
+          return {
+            type: TYPES.OBJECT,
+            display: m ? m[1] : "Object"
+          };
+        }
+
+        if (typeof aObject == "object" &&
+            typeof aObject.__iterator__ == "function") {
+          return {
+            type: TYPES.ITERATOR,
+            display: "Iterator"
+          };
+        }
+
+        return {
+          type: TYPES.OBJECT,
+          display: m ? m[1] : "Object"
+        };
+    }
+  },
+
+  /**
    * Tells if the given function is native or not.
    *
    * @param function aFunction
    *        The function you want to check if it is native or not.
    * @return boolean
    *         True if the given function is native, false otherwise.
    */
   isNativeFunction: function WCU_isNativeFunction(aFunction)
@@ -404,16 +515,105 @@ var WebConsoleUtils = {
       catch (ex if (ex.name == "TypeError")) {
         return desc;
       }
     }
     return desc;
   },
 
   /**
+   * Get an array that describes the properties of the given object.
+   *
+   * @param object aObject
+   *        The object to get the properties from.
+   * @param object aObjectCache
+   *        Optional object cache where to store references to properties of
+   *        aObject that are inspectable. See this.isObjectInspectable().
+   * @return array
+   *         An array that describes each property from the given object. Each
+   *         array element is an object (a property descriptor). Each property
+   *         descriptor has the following properties:
+   *         - name - property name
+   *         - value - a presentable property value representation (see
+   *                   this.presentableValueFor())
+   *         - type - value type (see this.presentableValueFor())
+   *         - inspectable - tells if the property value holds further
+   *                         properties (see this.isObjectInspectable()).
+   *         - objectId - optional, available only if aObjectCache is given and
+   *         if |inspectable| is true. You can do
+   *         aObjectCache[propertyDescriptor.objectId] to get the actual object
+   *         referenced by the property of aObject.
+   */
+  namesAndValuesOf: function WCU_namesAndValuesOf(aObject, aObjectCache)
+  {
+    let pairs = [];
+    let value, presentable;
+
+    let isDOMDocument = aObject instanceof Ci.nsIDOMDocument;
+    let deprecated = ["width", "height", "inputEncoding"];
+
+    for (let propName in aObject) {
+      // See bug 632275: skip deprecated properties.
+      if (isDOMDocument && deprecated.indexOf(propName) > -1) {
+        continue;
+      }
+
+      // Also skip non-native getters.
+      if (this.isNonNativeGetter(aObject, propName)) {
+        value = "";
+        presentable = {type: TYPES.GETTER, display: "Getter"};
+      }
+      else {
+        try {
+          value = aObject[propName];
+          presentable = this.presentableValueFor(value);
+        }
+        catch (ex) {
+          continue;
+        }
+      }
+
+      let pair = {};
+      pair.name = propName;
+      pair.value = presentable.display;
+      pair.inspectable = false;
+      pair.type = presentable.type;
+
+      switch (presentable.type) {
+        case TYPES.GETTER:
+        case TYPES.ITERATOR:
+        case TYPES.GENERATOR:
+        case TYPES.STRING:
+          break;
+        default:
+          try {
+            for (let p in value) {
+              pair.inspectable = true;
+              break;
+            }
+          }
+          catch (ex) { }
+          break;
+      }
+
+      // Store the inspectable object.
+      if (pair.inspectable && aObjectCache) {
+        pair.objectId = ++gObjectId;
+        aObjectCache[pair.objectId] = value;
+      }
+
+      pairs.push(pair);
+    }
+
+    pairs.sort(this.propertiesSort);
+
+    return pairs;
+  },
+
+  /**
    * Sort function for object properties.
    *
    * @param object a
    *        Property descriptor.
    * @param object b
    *        Property descriptor.
    * @return integer
    *         -1 if a.name < b.name,
@@ -1163,27 +1363,27 @@ function getMatchedProps(aObj, aOptions 
 
   if (aObj == null) { return {}; }
   try {
     Object.getPrototypeOf(aObj);
   } catch(e) {
     aObj = aObj.constructor.prototype;
   }
   let c = MAX_COMPLETIONS;
-  let names = Object.create(null);   // Using an Object to avoid duplicates.
+  let names = {};   // Using an Object to avoid duplicates.
 
   // We need to go up the prototype chain.
   let ownNames = null;
   while (aObj !== null) {
     ownNames = Object.getOwnPropertyNames(aObj);
     for (let i = 0; i < ownNames.length; i++) {
       // Filtering happens here.
       // If we already have it in, no need to append it.
       if (ownNames[i].indexOf(aOptions.matchProp) != 0 ||
-          ownNames[i] in names) {
+          names[ownNames[i]] == true) {
         continue;
       }
       c--;
       if (c < 0) {
         return names;
       }
       // If it is an array index, we can't take it.
       // This uses a trick: converting a string to a number yields NaN if