Merge m-c to inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Tue, 09 Apr 2013 15:29:13 -0400
changeset 139985 9db46ddfb517f302ba8039560247f4abd26581d5
parent 139984 974726290de1761f5df6d3fda7ff79dacd0e4979 (current diff)
parent 139904 9d5f05a6d497f968fd1580f6af8f84486323b0c3 (diff)
child 139986 52a3f612300983c750c889b4c883133ede1c8b28
push id350
push userbbajaj@mozilla.com
push dateMon, 29 Jul 2013 23:00:49 +0000
treeherdermozilla-release@064965b37dbd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone23.0a1
first release with
nightly linux32
9db46ddfb517 / 23.0a1 / 20130410031044 / files
nightly linux64
9db46ddfb517 / 23.0a1 / 20130410031044 / files
nightly mac
9db46ddfb517 / 23.0a1 / 20130410031044 / files
nightly win32
9db46ddfb517 / 23.0a1 / 20130410031044 / files
nightly win64
9db46ddfb517 / 23.0a1 / 20130410031044 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to inbound.
browser/base/content/browser.js
browser/devtools/debugger/test/browser_dbg_debugger-tab-switch-window.js
browser/devtools/debugger/test/browser_dbg_debugger-tab-switch.js
browser/devtools/webconsole/test/browser_webconsole_property_panel.js
js/src/vm/Debugger.cpp
--- a/browser/base/content/browser-appmenu.inc
+++ b/browser/base/content/browser-appmenu.inc
@@ -144,16 +144,18 @@
         <menupopup id="appmenu_webDeveloper_popup">
           <menuitem id="appmenu_devToolbox"
                     observes="devtoolsMenuBroadcaster_DevToolbox"/>
           <menuseparator id="appmenu_devtools_separator"/>
           <menuitem id="appmenu_devToolbar"
                     observes="devtoolsMenuBroadcaster_DevToolbar"/>
           <menuitem id="appmenu_chromeDebugger"
                     observes="devtoolsMenuBroadcaster_ChromeDebugger"/>
+          <menuitem id="appmenu_browserConsole"
+                    observes="devtoolsMenuBroadcaster_BrowserConsole"/>
           <menuitem id="appmenu_responsiveUI"
                     observes="devtoolsMenuBroadcaster_ResponsiveUI"/>
           <menuitem id="appmenu_scratchpad"
                     observes="devtoolsMenuBroadcaster_Scratchpad"/>
           <menuitem id="appmenu_pageSource"
                     observes="devtoolsMenuBroadcaster_PageSource"/>
           <menuitem id="appmenu_errorConsole"
                     observes="devtoolsMenuBroadcaster_ErrorConsole"/>
--- a/browser/base/content/browser-menubar.inc
+++ b/browser/base/content/browser-menubar.inc
@@ -571,16 +571,18 @@
                             observes="devtoolsMenuBroadcaster_DevToolbox"
                             accesskey="&devToolboxMenuItem.accesskey;"/>
                   <menuseparator id="menu_devtools_separator"/>
                   <menuitem id="menu_devToolbar"
                             observes="devtoolsMenuBroadcaster_DevToolbar"
                             accesskey="&devToolbarMenu.accesskey;"/>
                   <menuitem id="menu_chromeDebugger"
                             observes="devtoolsMenuBroadcaster_ChromeDebugger"/>
+                  <menuitem id="menu_browserConsole"
+                            observes="devtoolsMenuBroadcaster_BrowserConsole"/>
                   <menuitem id="menu_responsiveUI"
                             observes="devtoolsMenuBroadcaster_ResponsiveUI"
                             accesskey="&responsiveDesignTool.accesskey;"/>
                   <menuitem id="menu_scratchpad"
                             observes="devtoolsMenuBroadcaster_Scratchpad"
                             accesskey="&scratchpad.accesskey;"/>
                   <menuitem id="menu_pageSource"
                             observes="devtoolsMenuBroadcaster_PageSource"
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -91,16 +91,17 @@
     <command id="Browser:RestoreLastSession" oncommand="restoreLastSession();" disabled="true"/>
 
     <command id="Tools:Search" oncommand="BrowserSearch.webSearch();"/>
     <command id="Tools:Downloads" oncommand="BrowserDownloadsUI();"/>
     <command id="Tools:DevToolbox" oncommand="gDevToolsBrowser.toggleToolboxCommand(gBrowser);"/>
     <command id="Tools:DevToolbar" oncommand="DeveloperToolbar.toggle();" disabled="true" hidden="true"/>
     <command id="Tools:DevToolbarFocus" oncommand="DeveloperToolbar.focusToggle();" disabled="true"/>
     <command id="Tools:ChromeDebugger" oncommand="DebuggerUI.toggleChromeDebugger();" disabled="true" hidden="true"/>
+    <command id="Tools:BrowserConsole" oncommand="HUDConsoleUI.toggleBrowserConsole();" disabled="true" hidden="true"/>
     <command id="Tools:Scratchpad" oncommand="Scratchpad.openScratchpad();" disabled="true" hidden="true"/>
     <command id="Tools:ResponsiveUI" oncommand="ResponsiveUI.toggle();" disabled="true" hidden="true"/>
     <command id="Tools:Addons" oncommand="BrowserOpenAddonsMgr();"/>
     <command id="Tools:ErrorConsole" oncommand="toJavaScriptConsole()" disabled="true" hidden="true"/>
     <command id="Tools:DevToolsConnect" oncommand="gDevToolsBrowser.openConnectScreen(gBrowser)" disabled="true" hidden="true"/>
     <command id="Tools:Sanitize"
      oncommand="Cc['@mozilla.org/browser/browserglue;1'].getService(Ci.nsIBrowserGlue).sanitize(window);"/>
     <command id="Tools:PrivateBrowsing"
@@ -179,16 +180,19 @@
     <broadcaster id="devtoolsMenuBroadcaster_DevToolbar"
                  label="&devToolbarMenu.label;"
                  type="checkbox" autocheck="false"
                  command="Tools:DevToolbar"
                  key="key_devToolbar"/>
     <broadcaster id="devtoolsMenuBroadcaster_ChromeDebugger"
                  label="&chromeDebuggerMenu.label;"
                  command="Tools:ChromeDebugger"/>
+    <broadcaster id="devtoolsMenuBroadcaster_BrowserConsole"
+                 label="&browserConsoleCmd.label;"
+                 command="Tools:BrowserConsole"/>
     <broadcaster id="devtoolsMenuBroadcaster_Scratchpad"
                  label="&scratchpad.label;"
                  command="Tools:Scratchpad"
                  key="key_scratchpad"/>
     <broadcaster id="devtoolsMenuBroadcaster_ResponsiveUI"
                  label="&responsiveDesignTool.label;"
                  type="checkbox" autocheck="false"
                  command="Tools:ResponsiveUI"
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1566,29 +1566,37 @@ var gBrowserInit = {
 
       // Show the toolbar if it was previously visible
       if (gPrefService.getBoolPref("devtools.toolbar.visible")) {
         DeveloperToolbar.show(false);
       }
     }
 
     // Enable Chrome Debugger?
-    let enabled = gPrefService.getBoolPref("devtools.chrome.enabled") &&
-                  gPrefService.getBoolPref("devtools.debugger.chrome-enabled") &&
-                  gPrefService.getBoolPref("devtools.debugger.remote-enabled");
-    if (enabled) {
+    let chromeEnabled = gPrefService.getBoolPref("devtools.chrome.enabled");
+    let remoteEnabled = chromeEnabled &&
+                        gPrefService.getBoolPref("devtools.debugger.chrome-enabled") &&
+                        gPrefService.getBoolPref("devtools.debugger.remote-enabled");
+    if (remoteEnabled) {
       let cmd = document.getElementById("Tools:ChromeDebugger");
       cmd.removeAttribute("disabled");
       cmd.removeAttribute("hidden");
     }
 
+    // Enable the Browser Console?
+    if (chromeEnabled) {
+      let cmd = document.getElementById("Tools:BrowserConsole");
+      cmd.removeAttribute("disabled");
+      cmd.removeAttribute("hidden");
+    }
+
     // Enable Error Console?
     // Temporarily enabled. See bug 798925.
     let consoleEnabled = true || gPrefService.getBoolPref("devtools.errorconsole.enabled") ||
-                         gPrefService.getBoolPref("devtools.chrome.enabled");
+                         chromeEnabled;
     if (consoleEnabled) {
       let cmd = document.getElementById("Tools:ErrorConsole");
       cmd.removeAttribute("disabled");
       cmd.removeAttribute("hidden");
     }
 
     // Enable Scratchpad in the UI, if the preference allows this.
     let scratchpadEnabled = gPrefService.getBoolPref(Scratchpad.prefEnabledName);
--- a/browser/devtools/debugger/DebuggerPanel.jsm
+++ b/browser/devtools/debugger/DebuggerPanel.jsm
@@ -81,15 +81,9 @@ DebuggerPanel.prototype = {
 
   getBreakpoint: function() {
     return this._bkp.getBreakpoint.apply(this._bkp, arguments);
   },
 
   getAllBreakpoints: function() {
     return this._bkp.store;
   },
-
-  // Private
-
-  _ensureOnlyOneRunningDebugger: function() {
-    // FIXME
-  },
 };
--- a/browser/devtools/debugger/DebuggerUI.jsm
+++ b/browser/devtools/debugger/DebuggerUI.jsm
@@ -7,17 +7,16 @@
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 const DBG_XUL = "chrome://browser/content/debugger.xul";
 const DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties";
 const CHROME_DEBUGGER_PROFILE_NAME = "-chrome-debugger";
-const TAB_SWITCH_NOTIFICATION = "debugger-tab-switch";
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this,
   "DebuggerServer", "resource://gre/modules/devtools/dbg-server.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this,
   "Services", "resource://gre/modules/Services.jsm");
@@ -78,20 +77,16 @@ DebuggerUI.prototype = {
    * @return DebuggerPane | null
    *         The script debugger instance if it's started, null if stopped.
    */
   toggleDebugger: function DUI_toggleDebugger() {
     let scriptDebugger = this.findDebugger();
     let selectedTab = this.chromeWindow.gBrowser.selectedTab;
 
     if (scriptDebugger) {
-      if (scriptDebugger.ownerTab !== selectedTab) {
-        this.showTabSwitchNotification();
-        return scriptDebugger;
-      }
       scriptDebugger.close();
       return null;
     }
     return new DebuggerPane(this, selectedTab);
   },
 
   /**
    * Starts a remote debugger in a new window, or stops it if already started.
@@ -166,73 +161,16 @@ DebuggerUI.prototype = {
   /**
    * Get the chrome debugger for the current firefox instance.
    *
    * @return ChromeDebuggerProcess | null
    *         The chrome debugger instance if it exists, null otherwise.
    */
   getChromeDebugger: function DUI_getChromeDebugger() {
     return '_chromeDebugger' in this ? this._chromeDebugger : null;
-  },
-
-  /**
-   * Currently, there can only be one debugger per tab.
-   * Show an asynchronous notification which asks the user to switch the
-   * script debugger to the current tab if it's already open in another one.
-   */
-  showTabSwitchNotification: function DUI_showTabSwitchNotification() {
-    let gBrowser = this.chromeWindow.gBrowser;
-    let selectedBrowser = gBrowser.selectedBrowser;
-
-    let nbox = gBrowser.getNotificationBox(selectedBrowser);
-    let notification = nbox.getNotificationWithValue(TAB_SWITCH_NOTIFICATION);
-    if (notification) {
-      nbox.removeNotification(notification);
-      return;
-    }
-    let self = this;
-
-    let buttons = [{
-      id: "debugger.confirmTabSwitch.buttonSwitch",
-      label: L10N.getStr("confirmTabSwitch.buttonSwitch"),
-      accessKey: L10N.getStr("confirmTabSwitch.buttonSwitch.accessKey"),
-      callback: function DUI_notificationButtonSwitch() {
-        let scriptDebugger = self.findDebugger();
-        let targetWindow = scriptDebugger.globalUI.chromeWindow;
-        targetWindow.gBrowser.selectedTab = scriptDebugger.ownerTab;
-        targetWindow.focus();
-      }
-    }, {
-      id: "debugger.confirmTabSwitch.buttonOpen",
-      label: L10N.getStr("confirmTabSwitch.buttonOpen"),
-      accessKey: L10N.getStr("confirmTabSwitch.buttonOpen.accessKey"),
-      callback: function DUI_notificationButtonOpen() {
-        let scriptDebugger = self.findDebugger();
-        let targetWindow = scriptDebugger.globalUI.chromeWindow;
-        scriptDebugger.close();
-
-        targetWindow.addEventListener("Debugger:Shutdown", function onShutdown() {
-          targetWindow.removeEventListener("Debugger:Shutdown", onShutdown, false);
-          Services.tm.currentThread.dispatch({ run: function() {
-            self.toggleDebugger();
-          }}, 0);
-        }, false);
-      }
-    }];
-
-    let message = L10N.getStr("confirmTabSwitch.message");
-    let imageURL = "chrome://browser/skin/Info.png";
-
-    notification = nbox.appendNotification(
-      message, TAB_SWITCH_NOTIFICATION,
-      imageURL, nbox.PRIORITY_WARNING_HIGH, buttons, null);
-
-    // Make sure this is not a transient notification, to avoid the automatic
-    // transient notification removal.
-    notification.persistence = -1;
   }
 };
 
 /**
  * Creates a pane that will host the debugger.
  *
  * @param DebuggerUI aDebuggerUI
  *        The parent instance creating the new debugger.
--- a/browser/devtools/debugger/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -10,27 +10,16 @@ const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 const DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties";
 const NEW_SOURCE_IGNORED_URLS = ["debugger eval code", "self-hosted"];
 const NEW_SOURCE_DISPLAY_DELAY = 200; // ms
 const FETCH_SOURCE_RESPONSE_DELAY = 50; // ms
 const FRAME_STEP_CLEAR_DELAY = 100; // ms
 const CALL_STACK_PAGE_SIZE = 25; // frames
-const VARIABLES_VIEW_NON_SORTABLE = [
-  "Array",
-  "Int8Array",
-  "Uint8Array",
-  "Int16Array",
-  "Uint16Array",
-  "Int32Array",
-  "Uint32Array",
-  "Float32Array",
-  "Float64Array"
-];
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
 Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
 Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
 Cu.import("resource:///modules/source-editor.jsm");
 Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
@@ -262,26 +251,35 @@ let DebuggerController = {
           Cu.reportError("Couldn't attach to thread: " + aResponse.error);
           return;
         }
         this.activeThread = aThreadClient;
 
         this.ThreadState.connect();
         this.StackFrames.connect();
         this.SourceScripts.connect();
-        aThreadClient.resume();
+        aThreadClient.resume(this._ensureResumptionOrder);
 
         if (aCallback) {
           aCallback();
         }
       });
     });
   },
 
   /**
+   * Warn if resuming execution produced a wrongOrder error.
+   */
+  _ensureResumptionOrder: function DC__ensureResumptionOrder(aResponse) {
+    if (aResponse.error == "wrongOrder") {
+      DebuggerView.Toolbar.showResumeWarning(aResponse.lastPausedUrl);
+    }
+  },
+
+  /**
    * Sets up a chrome debugging session.
    *
    * @param DebuggerClient aClient
    *        The debugger client.
    * @param object aChromeDebugger
    *        The remote protocol grip of the chrome debugger.
    * @param function aCallback
    *        A function to invoke once the client attached to the active thread.
@@ -298,17 +296,17 @@ let DebuggerController = {
         Cu.reportError("Couldn't attach to thread: " + aResponse.error);
         return;
       }
       this.activeThread = aThreadClient;
 
       this.ThreadState.connect();
       this.StackFrames.connect();
       this.SourceScripts.connect();
-      aThreadClient.resume();
+      aThreadClient.resume(this._ensureResumptionOrder);
 
       if (aCallback) {
         aCallback();
       }
     });
   },
 
   /**
@@ -523,17 +521,17 @@ StackFrames.prototype = {
       }
     }
     // Got our evaluation of the current breakpoint's conditional expression.
     if (this._isConditionalBreakpointEvaluation) {
       this._isConditionalBreakpointEvaluation = false;
       // If the breakpoint's conditional expression evaluation is falsy,
       // automatically resume execution.
       if (VariablesView.isFalsy({ value: this.currentEvaluation.return })) {
-        this.activeThread.resume();
+        this.activeThread.resume(DebuggerController._ensureResumptionOrder);
         return;
       }
     }
 
 
     // Watch expressions are evaluated in the context of the topmost frame,
     // and the results and displayed in the variables view.
     if (this.currentWatchExpressions) {
@@ -888,17 +886,17 @@ StackFrames.prototype = {
     if (aVar._fetched) {
       return;
     }
     aVar._fetched = true;
     let grip = aVar._sourceGrip;
 
     this.activeThread.pauseGrip(grip).getPrototypeAndProperties(function(aResponse) {
       let { ownProperties, prototype } = aResponse;
-      let sortable = VARIABLES_VIEW_NON_SORTABLE.indexOf(grip.class) == -1;
+      let sortable = VariablesView.NON_SORTABLE_CLASSES.indexOf(grip.class) == -1;
 
       // Add all the variable properties.
       if (ownProperties) {
         aVar.addProperties(ownProperties, {
           // Not all variables need to force sorted properties.
           sorted: sortable,
           // Expansion handlers must be set after the properties are added.
           callback: this._addVarExpander
--- a/browser/devtools/debugger/debugger-toolbar.js
+++ b/browser/devtools/debugger/debugger-toolbar.js
@@ -22,16 +22,17 @@ function ToolbarView() {
 ToolbarView.prototype = {
   /**
    * Initialization function, called when the debugger is started.
    */
   initialize: function DVT_initialize() {
     dumpn("Initializing the ToolbarView");
 
     this._instrumentsPaneToggleButton = document.getElementById("instruments-pane-toggle");
+    this._resumeOrderPanel = document.getElementById("resumption-order-panel");
     this._resumeButton = document.getElementById("resume");
     this._stepOverButton = document.getElementById("step-over");
     this._stepInButton = document.getElementById("step-in");
     this._stepOutButton = document.getElementById("step-out");
     this._chromeGlobals = document.getElementById("chrome-globals");
 
     let resumeKey = LayoutHelpers.prettyKey(document.getElementById("resumeKey"), true);
     let stepOverKey = LayoutHelpers.prettyKey(document.getElementById("stepOverKey"), true);
@@ -85,16 +86,29 @@ ToolbarView.prototype = {
     // If we're attached, do the opposite.
     else if (aState == "attached") {
       this._resumeButton.removeAttribute("checked");
       this._resumeButton.setAttribute("tooltiptext", this._pauseTooltip);
     }
   },
 
   /**
+   * Display a warning when trying to resume a debuggee while another is paused.
+   * Debuggees must be unpaused in a Last-In-First-Out order.
+   *
+   * @param string aPausedUrl
+   *        The URL of the last paused debuggee.
+   */
+  showResumeWarning: function DVT_showResumeWarning(aPausedUrl) {
+    let label = L10N.getFormatStr("resumptionOrderPanelTitle", [aPausedUrl]);
+    document.getElementById("resumption-panel-desc").textContent = label;
+    this._resumeOrderPanel.openPopup(this._resumeButton);
+  },
+
+  /**
    * Sets the chrome globals container hidden or visible. It's hidden by default.
    *
    * @param boolean aVisibleFlag
    *        Specifies the intended visibility.
    */
   toggleChromeGlobalsContainer: function DVT_toggleChromeGlobalsContainer(aVisibleFlag) {
     this._chromeGlobals.setAttribute("hidden", !aVisibleFlag);
   },
@@ -110,17 +124,18 @@ ToolbarView.prototype = {
     });
   },
 
   /**
    * Listener handling the pause/resume button click event.
    */
   _onResumePressed: function DVT__onResumePressed() {
     if (DebuggerController.activeThread.paused) {
-      DebuggerController.activeThread.resume();
+      let warn = DebuggerController._ensureResumptionOrder;
+      DebuggerController.activeThread.resume(warn);
     } else {
       DebuggerController.activeThread.interrupt();
     }
   },
 
   /**
    * Listener handling the step over button click event.
    */
@@ -144,16 +159,17 @@ ToolbarView.prototype = {
    */
   _onStepOutPressed: function DVT__onStepOutPressed() {
     if (DebuggerController.activeThread.paused) {
       DebuggerController.activeThread.stepOut();
     }
   },
 
   _instrumentsPaneToggleButton: null,
+  _resumeOrderPanel: null,
   _resumeButton: null,
   _stepOverButton: null,
   _stepInButton: null,
   _stepOutButton: null,
   _chromeGlobals: null,
   _resumeTooltip: "",
   _pauseTooltip: "",
   _stepOverTooltip: "",
--- a/browser/devtools/debugger/debugger.xul
+++ b/browser/devtools/debugger/debugger.xul
@@ -335,9 +335,19 @@
          noautofocus="true">
     <vbox>
       <label id="conditional-breakpoint-panel-description"
              value="&debuggerUI.condBreakPanelTitle;"/>
       <textbox id="conditional-breakpoint-panel-textbox"/>
     </vbox>
   </panel>
 
+  <panel id="resumption-order-panel"
+         type="arrow"
+         noautofocus="true"
+         position="before_start">
+    <hbox align="start">
+      <image class="alert-icon"/>
+      <label id="resumption-panel-desc" class="description"/>
+    </hbox>
+  </panel>
+
 </window>
--- a/browser/devtools/debugger/test/Makefile.in
+++ b/browser/devtools/debugger/test/Makefile.in
@@ -12,18 +12,16 @@ include $(DEPTH)/config/autoconf.mk
 
 MOCHITEST_BROWSER_TESTS = \
 	browser_dbg_aaa_run_first_leaktest.js \
 	browser_dbg_clean-exit.js \
 	browser_dbg_cmd.js \
 	$(browser_dbg_cmd_break.js disabled until bug 722727 is fixed) \
 	browser_dbg_createChrome.js \
 	$(browser_dbg_createRemote.js disabled for intermittent failures, bug 753225) \
-	$(browser_dbg_debugger-tab-switch.js disabled until issues 106, 40 are fixed) \
-	$(browser_dbg_debugger-tab-switch-window.js disabled until issues 106, 40 are fixed) \
 	browser_dbg_debuggerstatement.js \
 	browser_dbg_listtabs.js \
 	browser_dbg_tabactor-01.js \
 	browser_dbg_tabactor-02.js \
 	browser_dbg_globalactor-01.js \
 	browser_dbg_nav-01.js \
 	browser_dbg_propertyview-01.js \
 	browser_dbg_propertyview-02.js \
deleted file mode 100644
--- a/browser/devtools/debugger/test/browser_dbg_debugger-tab-switch-window.js
+++ /dev/null
@@ -1,244 +0,0 @@
-/* vim:set ts=2 sw=2 sts=2 et: */
-/*
- * Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
-let gInitialTab, gTab1, gTab2, gTab3, gTab4;
-let gInitialWindow, gSecondWindow;
-let gPane1, gPane2;
-let gNbox;
-
-/**
- * Tests that a debugger instance can't be opened in multiple windows at once,
- * and that on such an attempt a notification is shown, which can either switch
- * to the old debugger instance, or close the old instance to open a new one.
- */
-
-function test() {
-  gInitialWindow = window;
-  gInitialTab = window.gBrowser.selectedTab;
-  gNbox = gInitialWindow.gBrowser.getNotificationBox(gInitialWindow.gBrowser.selectedBrowser);
-
-  testTab1_initialWindow(function() {
-    testTab2_secondWindow(function() {
-      testTab3_secondWindow(function() {
-        testTab4_secondWindow(function() {
-          lastTest(function() {
-            cleanup(function() {
-              finish();
-            });
-          });
-        });
-      });
-    });
-  });
-}
-
-function testTab1_initialWindow(callback) {
-  gTab1 = addTab(TAB1_URL, function() {
-    gInitialWindow.gBrowser.selectedTab = gTab1;
-    gNbox = gInitialWindow.gBrowser.getNotificationBox(gInitialWindow.gBrowser.selectedBrowser);
-
-    is(gNbox.getNotificationWithValue("debugger-tab-switch"), null,
-      "Shouldn't have a tab switch notification.");
-    ok(!gInitialWindow.DebuggerUI.getDebugger(),
-      "Shouldn't have a debugger pane for this tab yet.");
-
-    info("Toggling a debugger (1).");
-
-    gPane1 = gInitialWindow.DebuggerUI.toggleDebugger();
-    ok(gPane1, "toggleDebugger() should return a pane.");
-    is(gPane1.ownerTab, gTab1, "Incorrect tab owner.");
-
-    is(gInitialWindow.DebuggerUI.getDebugger(), gPane1,
-      "getDebugger() should return the same pane as toggleDebugger().");
-
-    wait_for_connect_and_resume(function dbgLoaded() {
-      info("First debugger has finished loading correctly.");
-      executeSoon(function() {
-        callback();
-      });
-    }, gInitialWindow);
-  }, gInitialWindow);
-}
-
-function testTab2_secondWindow(callback) {
-  gSecondWindow = addWindow();
-
-  gTab2 = addTab(TAB1_URL, function() {
-    gSecondWindow.gBrowser.selectedTab = gTab2;
-    gNbox = gSecondWindow.gBrowser.getNotificationBox(gSecondWindow.gBrowser.selectedBrowser);
-
-    is(gNbox.getNotificationWithValue("debugger-tab-switch"), null,
-      "Shouldn't have a tab switch notification yet.");
-    ok(gSecondWindow.DebuggerUI.findDebugger(),
-      "Should already have a debugger pane for another tab.");
-
-    gNbox.addEventListener("AlertActive", function active() {
-      gNbox.removeEventListener("AlertActive", active, true);
-      executeSoon(function() {
-        ok(gPane2, "toggleDebugger() should always return a pane.");
-        is(gPane2.ownerTab, gTab1, "Incorrect tab owner.");
-
-        is(gSecondWindow.DebuggerUI.findDebugger(), gPane1,
-          "findDebugger() should return the same pane as the first call to toggleDebugger().");
-        is(gSecondWindow.DebuggerUI.findDebugger(), gPane2,
-          "findDebugger() should return the same pane as the second call to toggleDebugger().");
-
-        info("Second debugger has not loaded.");
-
-        let notification = gNbox.getNotificationWithValue("debugger-tab-switch");
-        ok(gNbox.currentNotification, "Should have a tab switch notification.");
-        is(gNbox.currentNotification, notification, "Incorrect current notification.");
-
-        info("Notification will be simply closed.");
-        notification.close();
-
-        executeSoon(function() {
-          callback();
-        });
-      });
-    }, true);
-
-    info("Toggling a debugger (2).");
-
-    gPane2 = gSecondWindow.DebuggerUI.toggleDebugger();
-  }, gSecondWindow);
-}
-
-function testTab3_secondWindow(callback) {
-  gTab3 = addTab(TAB1_URL, function() {
-    gSecondWindow.gBrowser.selectedTab = gTab3;
-    gNbox = gSecondWindow.gBrowser.getNotificationBox(gSecondWindow.gBrowser.selectedBrowser);
-
-    is(gNbox.getNotificationWithValue("debugger-tab-switch"), null,
-      "Shouldn't have a tab switch notification.");
-    ok(gSecondWindow.DebuggerUI.findDebugger(),
-      "Should already have a debugger pane for another tab.");
-
-    gNbox.addEventListener("AlertActive", function active() {
-      gNbox.removeEventListener("AlertActive", active, true);
-      executeSoon(function() {
-        ok(gPane2, "toggleDebugger() should always return a pane.");
-        is(gPane2.ownerTab, gTab1, "Incorrect tab owner.");
-
-        is(gSecondWindow.DebuggerUI.findDebugger(), gPane1,
-          "findDebugger() should return the same pane as the first call to toggleDebugger().");
-        is(gSecondWindow.DebuggerUI.findDebugger(), gPane2,
-          "findDebugger() should return the same pane as the second call to toggleDebugger().");
-
-        info("Second debugger has not loaded.");
-
-        let notification = gNbox.getNotificationWithValue("debugger-tab-switch");
-        ok(gNbox.currentNotification, "Should have a tab switch notification.");
-        is(gNbox.currentNotification, notification, "Incorrect current notification.");
-
-        gInitialWindow.gBrowser.selectedTab = gInitialTab;
-        gInitialWindow.gBrowser.tabContainer.addEventListener("TabSelect", function tabSelect() {
-          gInitialWindow.gBrowser.tabContainer.removeEventListener("TabSelect", tabSelect, true);
-          executeSoon(function() {
-            callback();
-          });
-        }, true);
-
-        let buttonSwitch = notification.querySelectorAll("button")[0];
-        buttonSwitch.focus();
-        EventUtils.sendKey("SPACE", gSecondWindow);
-        info("The switch button on the notification was pressed.");
-      });
-    }, true);
-
-    info("Toggling a debugger (3).");
-
-    gPane2 = gSecondWindow.DebuggerUI.toggleDebugger();
-  }, gSecondWindow);
-}
-
-function testTab4_secondWindow(callback) {
-  is(gInitialWindow.gBrowser.selectedTab, gTab1,
-    "Should've switched to the first debugged tab.");
-
-  gTab4 = addTab(TAB1_URL, function() {
-    gSecondWindow.gBrowser.selectedTab = gTab4;
-    gNbox = gSecondWindow.gBrowser.getNotificationBox(gSecondWindow.gBrowser.selectedBrowser);
-
-    is(gNbox.getNotificationWithValue("debugger-tab-switch"), null,
-      "Shouldn't have a tab switch notification.");
-    ok(gSecondWindow.DebuggerUI.findDebugger(),
-      "Should already have a debugger pane for another tab.");
-
-    gNbox.addEventListener("AlertActive", function active() {
-      gNbox.removeEventListener("AlertActive", active, true);
-      executeSoon(function() {
-        ok(gPane2, "toggleDebugger() should always return a pane.");
-        is(gPane2.ownerTab, gTab1, "Incorrect tab owner.");
-
-        is(gSecondWindow.DebuggerUI.findDebugger(), gPane1,
-          "findDebugger() should return the same pane as the first call to toggleDebugger().");
-        is(gSecondWindow.DebuggerUI.findDebugger(), gPane2,
-          "findDebugger() should return the same pane as the second call to toggleDebugger().");
-
-        info("Second debugger has not loaded.");
-
-        let notification = gNbox.getNotificationWithValue("debugger-tab-switch");
-        ok(gNbox.currentNotification, "Should have a tab switch notification.");
-        is(gNbox.currentNotification, notification, "Incorrect current notification.");
-
-        let buttonOpen = notification.querySelectorAll("button")[1];
-        buttonOpen.focus();
-        EventUtils.sendKey("SPACE", gSecondWindow);
-        info("The open button on the notification was pressed.");
-
-        wait_for_connect_and_resume(function() {
-          callback();
-        }, gSecondWindow);
-      });
-    }, true);
-
-    info("Toggling a debugger (4).");
-
-    gPane2 = gSecondWindow.DebuggerUI.toggleDebugger();
-  }, gSecondWindow);
-}
-
-function lastTest(callback) {
-  is(gInitialWindow.gBrowser.selectedTab, gTab1,
-    "The initial window should continue having selected the first debugged tab.");
-  is(gSecondWindow.gBrowser.selectedTab, gTab4,
-    "Should currently be in the fourth tab.");
-  is(gSecondWindow.DebuggerUI.findDebugger().ownerTab, gTab4,
-    "The debugger should be open for the fourth tab.");
-
-  is(gNbox.getNotificationWithValue("debugger-tab-switch"), null,
-    "Shouldn't have a tab switch notification.");
-
-  info("Second debugger has loaded.");
-
-  executeSoon(function() {
-    callback();
-  });
-}
-
-function cleanup(callback)
-{
-  gPane1 = null;
-  gPane2 = null;
-  gNbox = null;
-
-  closeDebuggerAndFinish(false, function() {
-    removeTab(gTab1, gInitialWindow);
-    removeTab(gTab2, gSecondWindow);
-    removeTab(gTab3, gSecondWindow);
-    removeTab(gTab4, gSecondWindow);
-    gSecondWindow.close();
-    gTab1 = null;
-    gTab2 = null;
-    gTab3 = null;
-    gTab4 = null;
-    gInitialWindow = null;
-    gSecondWindow = null;
-
-    callback();
-  }, gSecondWindow);
-}
deleted file mode 100644
--- a/browser/devtools/debugger/test/browser_dbg_debugger-tab-switch.js
+++ /dev/null
@@ -1,235 +0,0 @@
-/* vim:set ts=2 sw=2 sts=2 et: */
-/*
- * Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
-let gTab1, gTab2, gTab3, gTab4;
-let gPane1, gPane2;
-let gNbox;
-
-/**
- * Tests that a debugger instance can't be opened in multiple tabs at once,
- * and that on such an attempt a notification is shown, which can either switch
- * to the old debugger instance, or close the old instance to open a new one.
- */
-
-function test() {
-  gNbox = gBrowser.getNotificationBox(gBrowser.selectedBrowser);
-
-  testTab1(function() {
-    testTab2(function() {
-      testTab3(function() {
-        testTab4(function() {
-          lastTest(function() {
-            cleanup(function() {
-              finish();
-            });
-          });
-        });
-      });
-    });
-  });
-}
-
-function testTab1(callback) {
-  gTab1 = addTab(TAB1_URL, function() {
-    gBrowser.selectedTab = gTab1;
-    gNbox = gBrowser.getNotificationBox(gBrowser.selectedBrowser);
-
-    is(gNbox.getNotificationWithValue("debugger-tab-switch"), null,
-      "Shouldn't have a tab switch notification.");
-    ok(!DebuggerUI.getDebugger(),
-      "Shouldn't have a debugger pane for this tab yet.");
-
-    info("Toggling a debugger (1).");
-
-    gPane1 = DebuggerUI.toggleDebugger();
-    ok(gPane1, "toggleDebugger() should return a pane.");
-    is(gPane1.ownerTab, gTab1, "Incorrect tab owner.");
-
-    is(DebuggerUI.getDebugger(), gPane1,
-      "getDebugger() should return the same pane as toggleDebugger().");
-
-    wait_for_connect_and_resume(function dbgLoaded() {
-      info("First debugger has finished loading correctly.");
-      executeSoon(function() {
-        callback();
-      });
-    });
-  });
-}
-
-function testTab2(callback) {
-  gTab2 = addTab(TAB1_URL, function() {
-    gBrowser.selectedTab = gTab2;
-    gNbox = gBrowser.getNotificationBox(gBrowser.selectedBrowser);
-
-    is(gNbox.getNotificationWithValue("debugger-tab-switch"), null,
-      "Shouldn't have a tab switch notification yet.");
-    ok(DebuggerUI.getDebugger(),
-      "Should already have a debugger pane for another tab.");
-
-    gNbox.addEventListener("AlertActive", function active() {
-      gNbox.removeEventListener("AlertActive", active, true);
-      executeSoon(function() {
-        ok(gPane2, "toggleDebugger() should always return a pane.");
-        is(gPane2.ownerTab, gTab1, "Incorrect tab owner.");
-
-        is(DebuggerUI.getDebugger(), gPane1,
-          "getDebugger() should return the same pane as the first call to toggleDebugger().");
-        is(DebuggerUI.getDebugger(), gPane2,
-          "getDebugger() should return the same pane as the second call to toggleDebugger().");
-
-        info("Second debugger has not loaded.");
-
-        let notification = gNbox.getNotificationWithValue("debugger-tab-switch");
-        ok(gNbox.currentNotification, "Should have a tab switch notification.");
-        is(gNbox.currentNotification, notification, "Incorrect current notification.");
-
-        info("Notification will be simply closed.");
-        notification.close();
-
-        executeSoon(function() {
-          callback();
-        });
-      });
-    }, true);
-
-    info("Toggling a debugger (2).");
-
-    gPane2 = DebuggerUI.toggleDebugger();
-  });
-}
-
-function testTab3(callback) {
-  gTab3 = addTab(TAB1_URL, function() {
-    gBrowser.selectedTab = gTab3;
-    gNbox = gBrowser.getNotificationBox(gBrowser.selectedBrowser);
-
-    is(gNbox.getNotificationWithValue("debugger-tab-switch"), null,
-      "Shouldn't have a tab switch notification.");
-    ok(DebuggerUI.getDebugger(),
-      "Should already have a debugger pane for another tab.");
-
-    gNbox.addEventListener("AlertActive", function active() {
-      gNbox.removeEventListener("AlertActive", active, true);
-      executeSoon(function() {
-        ok(gPane2, "toggleDebugger() should always return a pane.");
-        is(gPane2.ownerTab, gTab1, "Incorrect tab owner.");
-
-        is(DebuggerUI.getDebugger(), gPane1,
-          "getDebugger() should return the same pane as the first call to toggleDebugger().");
-        is(DebuggerUI.getDebugger(), gPane2,
-          "getDebugger() should return the same pane as the second call to toggleDebugger().");
-
-        info("Second debugger has not loaded.");
-
-        let notification = gNbox.getNotificationWithValue("debugger-tab-switch");
-        ok(gNbox.currentNotification, "Should have a tab switch notification.");
-        is(gNbox.currentNotification, notification, "Incorrect current notification.");
-
-        gBrowser.tabContainer.addEventListener("TabSelect", function tabSelect() {
-          gBrowser.tabContainer.removeEventListener("TabSelect", tabSelect, true);
-          executeSoon(function() {
-            callback();
-          });
-        }, true);
-
-        let buttonSwitch = notification.querySelectorAll("button")[0];
-        buttonSwitch.focus();
-        EventUtils.sendKey("SPACE");
-        info("The switch button on the notification was pressed.");
-      });
-    }, true);
-
-    info("Toggling a debugger (3).");
-
-    gPane2 = DebuggerUI.toggleDebugger();
-  });
-}
-
-function testTab4(callback) {
-  is(gBrowser.selectedTab, gTab1,
-    "Should've switched to the first debugged tab.");
-
-  gTab4 = addTab(TAB1_URL, function() {
-    gBrowser.selectedTab = gTab4;
-    gNbox = gBrowser.getNotificationBox(gBrowser.selectedBrowser);
-
-    is(gNbox.getNotificationWithValue("debugger-tab-switch"), null,
-      "Shouldn't have a tab switch notification.");
-    ok(DebuggerUI.getDebugger(),
-      "Should already have a debugger pane for another tab.");
-
-    gNbox.addEventListener("AlertActive", function active() {
-      gNbox.removeEventListener("AlertActive", active, true);
-      executeSoon(function() {
-        ok(gPane2, "toggleDebugger() should always return a pane.");
-        is(gPane2.ownerTab, gTab1, "Incorrect tab owner.");
-
-        is(DebuggerUI.getDebugger(), gPane1,
-          "getDebugger() should return the same pane as the first call to toggleDebugger().");
-        is(DebuggerUI.getDebugger(), gPane2,
-          "getDebugger() should return the same pane as the second call to toggleDebugger().");
-
-        info("Second debugger has not loaded.");
-
-        let notification = gNbox.getNotificationWithValue("debugger-tab-switch");
-        ok(gNbox.currentNotification, "Should have a tab switch notification.");
-        is(gNbox.currentNotification, notification, "Incorrect current notification.");
-
-        let buttonOpen = notification.querySelectorAll("button")[1];
-        buttonOpen.focus();
-        EventUtils.sendKey("SPACE");
-        info("The open button on the notification was pressed.");
-
-        wait_for_connect_and_resume(function() {
-          callback();
-        });
-      });
-    }, true);
-
-    info("Toggling a debugger (4).");
-
-    gPane2 = DebuggerUI.toggleDebugger();
-  });
-}
-
-function lastTest(callback) {
-  isnot(gBrowser.selectedTab, gTab1,
-    "Shouldn't have switched to the first debugged tab.");
-  is(gBrowser.selectedTab, gTab4,
-    "Should currently be in the fourth tab.");
-  is(DebuggerUI.getDebugger().ownerTab, gTab4,
-    "The debugger should be open for the fourth tab.");
-
-  is(gNbox.getNotificationWithValue("debugger-tab-switch"), null,
-    "Shouldn't have a tab switch notification.");
-
-  info("Second debugger has loaded.");
-
-  executeSoon(function() {
-    callback();
-  });
-}
-
-function cleanup(callback)
-{
-  gPane1 = null;
-  gPane2 = null;
-  gNbox = null;
-
-  closeDebuggerAndFinish(false, function() {
-    removeTab(gTab1);
-    removeTab(gTab2);
-    removeTab(gTab3);
-    removeTab(gTab4);
-    gTab1 = null;
-    gTab2 = null;
-    gTab3 = null;
-    gTab4 = null;
-
-    callback();
-  });
-}
--- a/browser/devtools/framework/Sidebar.jsm
+++ b/browser/devtools/framework/Sidebar.jsm
@@ -56,24 +56,24 @@ ToolSidebar.prototype = {
     iframe.setAttribute("src", url);
     iframe.tooltip = "aHTMLTooltip";
 
     let tab = this._tabbox.tabs.appendItem();
     tab.setAttribute("label", ""); // Avoid showing "undefined" while the tab is loading
 
     let onIFrameLoaded = function() {
       tab.setAttribute("label", iframe.contentDocument.title);
-      iframe.removeEventListener("DOMContentLoaded", onIFrameLoaded, true);
+      iframe.removeEventListener("load", onIFrameLoaded, true);
       if ("setPanel" in iframe.contentWindow) {
         iframe.contentWindow.setPanel(this._toolPanel, iframe);
       }
       this.emit(id + "-ready");
     }.bind(this);
 
-    iframe.addEventListener("DOMContentLoaded", onIFrameLoaded, true);
+    iframe.addEventListener("load", onIFrameLoaded, true);
 
     let tabpanel = this._panelDoc.createElementNS(XULNS, "tabpanel");
     tabpanel.setAttribute("id", "sidebar-panel-" + id);
     tabpanel.appendChild(iframe);
     this._tabbox.tabpanels.appendChild(tabpanel);
 
     this._tooltip = this._panelDoc.createElementNS(XULNS, "tooltip");
     this._tooltip.id = "aHTMLTooltip";
@@ -86,17 +86,17 @@ ToolSidebar.prototype = {
     this._tabs.set(id, tab);
 
     if (selected) {
       // For some reason I don't understand, if we call this.select in this
       // event loop (after inserting the tab), the tab will never get the
       // the "selected" attribute set to true.
       this._panelDoc.defaultView.setTimeout(function() {
         this.select(id);
-      }.bind(this), 0);
+      }.bind(this), 10);
     }
 
     this.emit("new-tab-registered", id);
   },
 
   /**
    * Select a specific tab.
    */
--- a/browser/devtools/framework/test/browser_toolbox_sidebar.js
+++ b/browser/devtools/framework/test/browser_toolbox_sidebar.js
@@ -17,17 +17,17 @@ function test() {
                   "</hbox>" +
                   "</window>";
 
   const tab1URL = "data:text/html;charset=utf8,<title>1</title><p>1</p>";
   const tab2URL = "data:text/html;charset=utf8,<title>2</title><p>2</p>";
   const tab3URL = "data:text/html;charset=utf8,<title>3</title><p>3</p>";
 
   let panelDoc;
-
+  let tab1Selected = false;
   let registeredTabs = {};
   let readyTabs = {};
 
   let toolDefinition = {
     id: "fakeTool4242",
     killswitch: "devtools.fakeTool4242.enabled",
     url: toolURL,
     label: "FAKE TOOL!!!",
@@ -58,45 +58,52 @@ function test() {
       let tabbox = panel.panelDoc.getElementById("sidebar");
       panel.sidebar = new ToolSidebar(tabbox, panel, true);
 
       panel.sidebar.on("new-tab-registered", function(event, id) {
         registeredTabs[id] = true;
       });
 
       panel.sidebar.once("tab1-ready", function(event) {
+        info(event);
         readyTabs.tab1 = true;
-        if (readyTabs.tab1 && readyTabs.tab2 && readyTabs.tab3) {
-          allTabsReady(panel);
-        }
+        allTabsReady(panel);
       });
 
       panel.sidebar.once("tab2-ready", function(event) {
+        info(event);
         readyTabs.tab2 = true;
-        if (readyTabs.tab1 && readyTabs.tab2 && readyTabs.tab3) {
-          allTabsReady(panel);
-        }
+        allTabsReady(panel);
       });
 
       panel.sidebar.once("tab3-ready", function(event) {
+        info(event);
         readyTabs.tab3 = true;
-        if (readyTabs.tab1 && readyTabs.tab2 && readyTabs.tab3) {
-          allTabsReady(panel);
-        }
+        allTabsReady(panel);
+      });
+
+      panel.sidebar.once("tab1-selected", function(event) {
+        info(event);
+        tab1Selected = true;
+        allTabsReady(panel);
       });
 
       panel.sidebar.addTab("tab1", tab1URL, true);
       panel.sidebar.addTab("tab2", tab2URL);
       panel.sidebar.addTab("tab3", tab3URL);
 
       panel.sidebar.show();
     }).then(null, console.error);
   });
 
   function allTabsReady(panel) {
+    if (!tab1Selected || !readyTabs.tab1 || !readyTabs.tab2 || !readyTabs.tab3) {
+      return;
+    }
+
     ok(registeredTabs.tab1, "tab1 registered");
     ok(registeredTabs.tab2, "tab2 registered");
     ok(registeredTabs.tab3, "tab3 registered");
     ok(readyTabs.tab1, "tab1 ready");
     ok(readyTabs.tab2, "tab2 ready");
     ok(readyTabs.tab3, "tab3 ready");
 
     let tabs = panel.sidebar._tabbox.querySelectorAll("tab");
--- a/browser/devtools/jar.mn
+++ b/browser/devtools/jar.mn
@@ -1,14 +1,15 @@
 # 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/.
 
 browser.jar:
     content/browser/devtools/widgets.css          (shared/widgets/widgets.css)
+    content/browser/devtools/widgets/VariablesView.xul (shared/widgets/VariablesView.xul)
     content/browser/devtools/markup-view.xhtml    (markupview/markup-view.xhtml)
     content/browser/devtools/markup-view.css      (markupview/markup-view.css)
     content/browser/devtools/netmonitor.xul           (netmonitor/netmonitor.xul)
     content/browser/devtools/netmonitor.css           (netmonitor/netmonitor.css)
     content/browser/devtools/netmonitor-controller.js (netmonitor/netmonitor-controller.js)
     content/browser/devtools/netmonitor-view.js       (netmonitor/netmonitor-view.js)
     content/browser/NetworkPanel.xhtml            (webconsole/NetworkPanel.xhtml)
     content/browser/devtools/webconsole.js        (webconsole/webconsole.js)
--- a/browser/devtools/netmonitor/netmonitor-view.js
+++ b/browser/devtools/netmonitor/netmonitor-view.js
@@ -487,28 +487,28 @@ create({ constructor: RequestsMenuView, 
       case "status": {
         let node = $(".requests-menu-status", aItem.target);
         node.setAttribute("code", aValue);
         break;
       }
       case "contentSize": {
         let size = (aValue / 1024).toFixed(CONTENT_SIZE_DECIMALS);
         let node = $(".requests-menu-size", aItem.target);
-        node.setAttribute("value", L10N.getFormatStr("networkMenu.size", size));
+        node.setAttribute("value", L10N.getFormatStr("networkMenu.sizeKB", size));
         break;
       }
       case "mimeType": {
         let type = aValue.split(";")[0].split("/")[1] || "?";
         let node = $(".requests-menu-type", aItem.target);
         node.setAttribute("value", CONTENT_MIME_TYPE_ABBREVIATIONS[type] || type);
         break;
       }
       case "totalTime": {
         let node = $(".requests-menu-timings-total", aItem.target);
-        node.setAttribute("value", L10N.getFormatStr("networkMenu.total", aValue));
+        node.setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", aValue));
         break;
       }
     }
   },
 
   /**
    * Creates a waterfall representing timing information in a network request item view.
    *
@@ -853,17 +853,17 @@ create({ constructor: NetworkDetailsView
    *
    * @param string aName
    *        The type of headers to populate (request or response).
    * @param object aResponse
    *        The message received from the server.
    */
   _addHeaders: function NVND__addHeaders(aName, aResponse) {
     let kb = (aResponse.headersSize / 1024).toFixed(HEADERS_SIZE_DECIMALS);
-    let size = L10N.getFormatStr("networkMenu.size", kb);
+    let size = L10N.getFormatStr("networkMenu.sizeKB", kb);
     let headersScope = this._headers.addScope(aName + " (" + size + ")");
     headersScope.expanded = true;
 
     for (let header of aResponse.headers) {
       let headerVar = headersScope.addVar(header.name, { null: true }, true);
       headerVar.setGrip(header.value);
     }
   },
@@ -1078,42 +1078,42 @@ create({ constructor: NetworkDetailsView
 
     let tabboxWidth = $("#details-pane").getAttribute("width");
     let availableWidth = tabboxWidth / 2; // Other nodes also take some space.
     let scale = Math.max(availableWidth / aResponse.totalTime, 0);
 
     $("#timings-summary-blocked .requests-menu-timings-box")
       .setAttribute("width", blocked * scale);
     $("#timings-summary-blocked .requests-menu-timings-total")
-      .setAttribute("value", L10N.getFormatStr("networkMenu.total", blocked));
+      .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", blocked));
 
     $("#timings-summary-dns .requests-menu-timings-box")
       .setAttribute("width", dns * scale);
     $("#timings-summary-dns .requests-menu-timings-total")
-      .setAttribute("value", L10N.getFormatStr("networkMenu.total", dns));
+      .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", dns));
 
     $("#timings-summary-connect .requests-menu-timings-box")
       .setAttribute("width", connect * scale);
     $("#timings-summary-connect .requests-menu-timings-total")
-      .setAttribute("value", L10N.getFormatStr("networkMenu.total", connect));
+      .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", connect));
 
     $("#timings-summary-send .requests-menu-timings-box")
       .setAttribute("width", send * scale);
     $("#timings-summary-send .requests-menu-timings-total")
-      .setAttribute("value", L10N.getFormatStr("networkMenu.total", send));
+      .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", send));
 
     $("#timings-summary-wait .requests-menu-timings-box")
       .setAttribute("width", wait * scale);
     $("#timings-summary-wait .requests-menu-timings-total")
-      .setAttribute("value", L10N.getFormatStr("networkMenu.total", wait));
+      .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", wait));
 
     $("#timings-summary-receive .requests-menu-timings-box")
       .setAttribute("width", receive * scale);
     $("#timings-summary-receive .requests-menu-timings-total")
-      .setAttribute("value", L10N.getFormatStr("networkMenu.total", receive));
+      .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", receive));
 
     $("#timings-summary-dns .requests-menu-timings-box")
       .style.transform = "translateX(" + (scale * blocked) + "px)";
     $("#timings-summary-connect .requests-menu-timings-box")
       .style.transform = "translateX(" + (scale * (blocked + dns)) + "px)";
     $("#timings-summary-send .requests-menu-timings-box")
       .style.transform = "translateX(" + (scale * (blocked + dns + connect)) + "px)";
     $("#timings-summary-wait .requests-menu-timings-box")
--- a/browser/devtools/netmonitor/test/browser_net_content-type.js
+++ b/browser/devtools/netmonitor/test/browser_net_content-type.js
@@ -4,62 +4,62 @@
 /**
  * Tests if different response content types are handled correctly.
  */
 
 function test() {
   initNetMonitor(CONTENT_TYPE_URL).then(([aTab, aDebuggee, aMonitor]) => {
     info("Starting test... ");
 
-    let { document, SourceEditor, NetMonitorView } = aMonitor.panelWin;
+    let { document, L10N, SourceEditor, NetMonitorView } = aMonitor.panelWin;
     let { RequestsMenu } = NetMonitorView;
 
     RequestsMenu.lazyUpdate = false;
 
     waitForNetworkEvents(aMonitor, 6).then(() => {
       verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
         "GET", CONTENT_TYPE_SJS + "?fmt=xml", {
           status: 200,
           type: "xml",
-          size: "0.04kb",
+          size: L10N.getFormatStr("networkMenu.sizeKB", 0.04),
           time: true
         });
       verifyRequestItemTarget(RequestsMenu.getItemAtIndex(1),
         "GET", CONTENT_TYPE_SJS + "?fmt=css", {
           status: 200,
           type: "css",
-          size: "0.03kb",
+          size: L10N.getFormatStr("networkMenu.sizeKB", 0.03),
           time: true
         });
       verifyRequestItemTarget(RequestsMenu.getItemAtIndex(2),
         "GET", CONTENT_TYPE_SJS + "?fmt=js", {
           status: 200,
           type: "js",
-          size: "0.03kb",
+          size: L10N.getFormatStr("networkMenu.sizeKB", 0.03),
           time: true
         });
       verifyRequestItemTarget(RequestsMenu.getItemAtIndex(3),
         "GET", CONTENT_TYPE_SJS + "?fmt=json", {
           status: 200,
           type: "json",
-          size: "0.03kb",
+          size: L10N.getFormatStr("networkMenu.sizeKB", 0.03),
           time: true
         });
       verifyRequestItemTarget(RequestsMenu.getItemAtIndex(4),
         "GET", CONTENT_TYPE_SJS + "?fmt=bogus", {
           status: 404,
           type: "html",
-          size: "0.02kb",
+          size: L10N.getFormatStr("networkMenu.sizeKB", 0.02),
           time: true
         });
       verifyRequestItemTarget(RequestsMenu.getItemAtIndex(5),
         "GET", TEST_IMAGE, {
           status: 200,
           type: "png",
-          size: "0.76kb",
+          size: L10N.getFormatStr("networkMenu.sizeKB", 0.76),
           time: true
         });
 
       EventUtils.sendMouseEvent({ type: "mousedown" },
         document.getElementById("details-pane-toggle"));
       EventUtils.sendMouseEvent({ type: "mousedown" },
         document.querySelectorAll("#details-pane tab")[3]);
 
--- a/browser/devtools/netmonitor/test/browser_net_post-data.js
+++ b/browser/devtools/netmonitor/test/browser_net_post-data.js
@@ -15,24 +15,24 @@ function test() {
     RequestsMenu.lazyUpdate = false;
     NetworkDetails._params.lazyEmpty = false;
 
     waitForNetworkEvents(aMonitor, 0, 2).then(() => {
       verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
         "POST", SIMPLE_SJS + "?foo=bar&baz=42&type=urlencoded", {
           status: 200,
           type: "plain",
-          size: "0.01kb",
+          size: L10N.getFormatStr("networkMenu.sizeKB", 0.01),
           time: true
         });
       verifyRequestItemTarget(RequestsMenu.getItemAtIndex(1),
         "POST", SIMPLE_SJS + "?foo=bar&baz=42&type=multipart", {
           status: 200,
           type: "plain",
-          size: "0.01kb",
+          size: L10N.getFormatStr("networkMenu.sizeKB", 0.01),
           time: true
         });
 
       EventUtils.sendMouseEvent({ type: "mousedown" },
         document.getElementById("details-pane-toggle"));
       EventUtils.sendMouseEvent({ type: "mousedown" },
         document.querySelectorAll("#details-pane tab")[2]);
 
--- a/browser/devtools/netmonitor/test/browser_net_prefs-and-l10n.js
+++ b/browser/devtools/netmonitor/test/browser_net_prefs-and-l10n.js
@@ -22,18 +22,18 @@ function test() {
 
       let bundleName = "chrome://browser/locale/devtools/netmonitor.properties";
       let stringBundle = Services.strings.createBundle(bundleName);
 
       is(L10N.getStr("netmonitor.label"),
         stringBundle.GetStringFromName("netmonitor.label"),
         "The getStr() method didn't return the expected string.");
 
-      is(L10N.getFormatStr("networkMenu.total", "foo"),
-        stringBundle.formatStringFromName("networkMenu.total", ["foo"], 1),
+      is(L10N.getFormatStr("networkMenu.totalMS", "foo"),
+        stringBundle.formatStringFromName("networkMenu.totalMS", ["foo"], 1),
         "The getFormatStr() method didn't return the expected string.");
     }
 
     function testPrefs() {
       let { Prefs } = aMonitor.panelWin;
 
       is(Prefs.root, "devtools.netmonitor",
         "The preferences object should have a correct root path.");
--- a/browser/devtools/netmonitor/test/browser_net_simple-request-data.js
+++ b/browser/devtools/netmonitor/test/browser_net_simple-request-data.js
@@ -4,17 +4,17 @@
 /**
  * Tests if requests render correct information in the menu UI.
  */
 
 function test() {
   initNetMonitor(SIMPLE_SJS).then(([aTab, aDebuggee, aMonitor]) => {
     info("Starting test... ");
 
-    let { NetMonitorView } = aMonitor.panelWin;
+    let { L10N, NetMonitorView } = aMonitor.panelWin;
     let { RequestsMenu } = NetMonitorView;
 
     RequestsMenu.lazyUpdate = false;
 
     waitForNetworkEvents(aMonitor, 1)
       .then(() => teardown(aMonitor))
       .then(finish);
 
@@ -157,17 +157,17 @@ function test() {
 
       is(requestItem.attachment.contentSize, "12",
         "The contentSize attachment has an incorrect value.");
       is(requestItem.attachment.mimeType, "text/plain; charset=utf-8",
         "The mimeType attachment has an incorrect value.");
 
       verifyRequestItemTarget(requestItem, "GET", SIMPLE_SJS, {
         type: "plain",
-        size: "0.01kb"
+        size: L10N.getFormatStr("networkMenu.sizeKB", 0.01),
       });
     });
 
     aMonitor.panelWin.once("NetMonitor:NetworkEventUpdated:ResponseContent", () => {
       let requestItem = RequestsMenu.getItemAtIndex(0);
 
       ok(requestItem.attachment.responseContent,
         "There should be a responseContent attachment available.");
@@ -175,17 +175,17 @@ function test() {
         "The responseContent attachment has an incorrect |content.mimeType| property.");
       is(requestItem.attachment.responseContent.content.text, "Hello world!",
         "The responseContent attachment has an incorrect |content.text| property.");
       is(requestItem.attachment.responseContent.content.size, 12,
         "The responseContent attachment has an incorrect |content.size| property.");
 
       verifyRequestItemTarget(requestItem, "GET", SIMPLE_SJS, {
         type: "plain",
-        size: "0.01kb"
+        size: L10N.getFormatStr("networkMenu.sizeKB", 0.01),
       });
     });
 
     aMonitor.panelWin.once("NetMonitor:NetworkEventUpdating:EventTimings", () => {
       let requestItem = RequestsMenu.getItemAtIndex(0);
 
       is(typeof requestItem.attachment.totalTime, "number",
         "The attached totalTime is incorrect.");
--- a/browser/devtools/netmonitor/test/browser_net_simple-request-details.js
+++ b/browser/devtools/netmonitor/test/browser_net_simple-request-details.js
@@ -66,17 +66,17 @@ function test() {
       is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
         "The empty notice should not be displayed in this tabpanel.");
 
       let responseScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
       let requestScope = tabpanel.querySelectorAll(".variables-view-scope")[1];
 
       is(responseScope.querySelector(".name").getAttribute("value"),
         L10N.getStr("responseHeaders") + " (" +
-        L10N.getFormatStr("networkMenu.size", "0.169") + ")",
+        L10N.getFormatStr("networkMenu.sizeKB", "0.169") + ")",
         "The response headers scope doesn't have the correct title.");
 
       ok(requestScope.querySelector(".name").getAttribute("value").contains(
         L10N.getStr("requestHeaders") + " (0."),
         // Can't test for full request headers title because the size may
         // vary across platforms ("User-Agent" header differs).
         "The request headers scope doesn't have the correct title.");
 
@@ -191,35 +191,35 @@ function test() {
 
       let tab = document.querySelectorAll("#details-pane tab")[4];
       let tabpanel = document.querySelectorAll("#details-pane tabpanel")[4];
 
       is(tab.getAttribute("selected"), "true",
         "The timings tab in the network details pane should be selected.");
 
       ok(tabpanel.querySelector("#timings-summary-blocked .requests-menu-timings-total")
-        .getAttribute("value").match(/[0-9]+ms$/),
+        .getAttribute("value").match(/[0-9]+/),
         "The blocked timing info does not appear to be correct.");
 
       ok(tabpanel.querySelector("#timings-summary-dns .requests-menu-timings-total")
-        .getAttribute("value").match(/[0-9]+ms$/),
+        .getAttribute("value").match(/[0-9]+/),
         "The dns timing info does not appear to be correct.");
 
       ok(tabpanel.querySelector("#timings-summary-connect .requests-menu-timings-total")
-        .getAttribute("value").match(/[0-9]+ms$/),
+        .getAttribute("value").match(/[0-9]+/),
         "The connect timing info does not appear to be correct.");
 
       ok(tabpanel.querySelector("#timings-summary-send .requests-menu-timings-total")
-        .getAttribute("value").match(/[0-9]+ms$/),
+        .getAttribute("value").match(/[0-9]+/),
         "The send timing info does not appear to be correct.");
 
       ok(tabpanel.querySelector("#timings-summary-wait .requests-menu-timings-total")
-        .getAttribute("value").match(/[0-9]+ms$/),
+        .getAttribute("value").match(/[0-9]+/),
         "The wait timing info does not appear to be correct.");
 
       ok(tabpanel.querySelector("#timings-summary-receive .requests-menu-timings-total")
-        .getAttribute("value").match(/[0-9]+ms$/),
+        .getAttribute("value").match(/[0-9]+/),
         "The receive timing info does not appear to be correct.");
     }
 
     aDebuggee.location.reload();
   });
 }
--- a/browser/devtools/netmonitor/test/browser_net_status-codes.js
+++ b/browser/devtools/netmonitor/test/browser_net_status-codes.js
@@ -17,45 +17,45 @@ function test() {
 
     waitForNetworkEvents(aMonitor, 5).then(() => {
       let requestItems = [];
 
       verifyRequestItemTarget(requestItems[0] = RequestsMenu.getItemAtIndex(0),
         "GET", STATUS_CODES_SJS + "?sts=100", {
           status: 101,
           type: "plain",
-          size: "0.00kb",
+          size: L10N.getFormatStr("networkMenu.sizeKB", "0.00"),
           time: true
         });
       verifyRequestItemTarget(requestItems[1] = RequestsMenu.getItemAtIndex(1),
         "GET", STATUS_CODES_SJS + "?sts=200", {
           status: 202,
           type: "plain",
-          size: "0.02kb",
+          size: L10N.getFormatStr("networkMenu.sizeKB", 0.02),
           time: true
         });
       verifyRequestItemTarget(requestItems[2] = RequestsMenu.getItemAtIndex(2),
         "GET", STATUS_CODES_SJS + "?sts=300", {
           status: 303,
           type: "plain",
-          size: "0.00kb",
+          size: L10N.getFormatStr("networkMenu.sizeKB", "0.00"),
           time: true
         });
       verifyRequestItemTarget(requestItems[3] = RequestsMenu.getItemAtIndex(3),
         "GET", STATUS_CODES_SJS + "?sts=400", {
           status: 404,
           type: "plain",
-          size: "0.02kb",
+          size: L10N.getFormatStr("networkMenu.sizeKB", 0.02),
           time: true
         });
       verifyRequestItemTarget(requestItems[4] = RequestsMenu.getItemAtIndex(4),
         "GET", STATUS_CODES_SJS + "?sts=500", {
           status: 501,
           type: "plain",
-          size: "0.02kb",
+          size: L10N.getFormatStr("networkMenu.sizeKB", 0.02),
           time: true
         });
 
       // Test summaries...
       EventUtils.sendMouseEvent({ type: "mousedown" },
         document.querySelectorAll("#details-pane tab")[0]);
 
       EventUtils.sendMouseEvent({ type: "mousedown" }, requestItems[0].target);
--- a/browser/devtools/shared/widgets/VariablesView.jsm
+++ b/browser/devtools/shared/widgets/VariablesView.jsm
@@ -913,16 +913,28 @@ VariablesView.prototype = {
   _boxObject: null,
   _searchboxNode: null,
   _searchboxContainer: null,
   _searchboxPlaceholder: "",
   _emptyTextNode: null,
   _emptyTextValue: ""
 };
 
+VariablesView.NON_SORTABLE_CLASSES = [
+  "Array",
+  "Int8Array",
+  "Uint8Array",
+  "Int16Array",
+  "Uint16Array",
+  "Int32Array",
+  "Uint32Array",
+  "Float32Array",
+  "Float64Array"
+];
+
 /**
  * Generates the string evaluated when performing simple value changes.
  *
  * @param Variable | Property aItem
  *        The current variable or property.
  * @param string aCurrentString
  *        The trimmed user inputted string.
  * @return string
@@ -978,17 +990,18 @@ VariablesView.getterOrSetterEvalMacro = 
     case "undefined":
       let mirrorType = type == "get" ? "set" : "get";
       let mirrorLookup = type == "get" ? "__lookupSetter__" : "__lookupGetter__";
 
       // If the parent object will end up without any getter or setter,
       // morph it into a plain value.
       if ((type == "set" && propertyObject.getter.type == "undefined") ||
           (type == "get" && propertyObject.setter.type == "undefined")) {
-        return VariablesView.overrideValueEvalMacro(propertyObject, "undefined");
+        // Make sure the right getter/setter to value override macro is applied to the target object.
+        return propertyObject.evaluationMacro(propertyObject, "undefined");
       }
 
       // Construct and return the getter/setter removal evaluation string.
       // e.g: Object.defineProperty(foo, "bar", {
       //   get: foo.__lookupGetter__("bar"),
       //   set: undefined,
       //   enumerable: true,
       //   configurable: true
@@ -1036,17 +1049,20 @@ VariablesView.getterOrSetterEvalMacro = 
 /**
  * Function invoked when a getter or setter is deleted.
  *
  * @param Property aItem
  *        The current getter or setter property.
  */
 VariablesView.getterOrSetterDeleteCallback = function(aItem) {
   aItem._disable();
-  aItem.ownerView.eval(VariablesView.getterOrSetterEvalMacro(aItem, ""));
+
+  // Make sure the right getter/setter to value override macro is applied to the target object.
+  aItem.ownerView.eval(aItem.evaluationMacro(aItem, ""));
+
   return true; // Don't hide the element.
 };
 
 /**
  * A Scope is an object holding Variable instances.
  * Iterable via "for (let [name, variable] in instance) { }".
  *
  * @param VariablesView aView
@@ -1412,16 +1428,23 @@ Scope.prototype = {
       if (!item._isExpanded) {
         return false;
       }
     }
     return true;
   },
 
   /**
+   * Focus this scope.
+   */
+  focus: function S_focus() {
+    this._variablesView._focusItem(this);
+  },
+
+  /**
    * Adds an event listener for a certain event on this scope's title.
    * @param string aName
    * @param function aCallback
    * @param boolean aCapture
    */
   addEventListener: function S_addEventListener(aName, aCallback, aCapture) {
     this._title.addEventListener(aName, aCallback, aCapture);
   },
@@ -1444,16 +1467,28 @@ Scope.prototype = {
 
   /**
    * Gets the name associated with this item.
    * @return string
    */
   get name() this._nameString,
 
   /**
+   * Gets the displayed value for this item.
+   * @return string
+   */
+  get displayValue() this._valueString,
+
+  /**
+   * Gets the class names used for the displayed value.
+   * @return string
+   */
+  get displayValueClassName() this._valueClassName,
+
+  /**
    * Gets the element associated with this item.
    * @return nsIDOMNode
    */
   get target() this._target,
 
   /**
    * Initializes this scope's id, view and binds event listeners.
    *
@@ -1520,17 +1555,17 @@ Scope.prototype = {
   /**
    * The click listener for this scope's title.
    */
   _onClick: function S__onClick(e) {
     if (e.target == this._inputNode) {
       return;
     }
     this.toggle();
-    this._variablesView._focusItem(this);
+    this.focus();
   },
 
   /**
    * Lazily appends a node to this scope's enumerable or non-enumerable
    * container. Once a certain number of nodes have been batched, they
    * will be appended.
    *
    * @param boolean aImmediateFlag
@@ -1902,17 +1937,17 @@ ViewHelpers.create({ constructor: Variab
    *        it will be inferred from the value.
    *        e.g. - { someProp0: { value: 42 },
    *                 someProp1: { value: true },
    *                 someProp2: { value: "nasu" },
    *                 someProp3: { value: { type: "undefined" } },
    *                 someProp4: { value: { type: "null" } },
    *                 someProp5: { value: { type: "object", class: "Object" } },
    *                 someProp6: { get: { type: "object", class: "Function" },
-   *                              set: { type: "undefined" } }
+   *                              set: { type: "undefined" } } }
    * @param object aOptions [optional]
    *        Additional options for adding the properties. Supported options:
    *        - sorted: true to sort all the properties before adding them
    *        - callback: function invoked after each property is added
    */
   addProperties: function V_addProperties(aProperties, aOptions = {}) {
     let propertyNames = Object.keys(aProperties);
 
@@ -2185,16 +2220,17 @@ ViewHelpers.create({ constructor: Variab
       if (this.ownerView.eval) {
         this.delete = VariablesView.getterOrSetterDeleteCallback;
         this.evaluationMacro = VariablesView.overrideValueEvalMacro;
       }
       // Deleting getters and setters individually is not allowed if no
       // evaluation method is provided.
       else {
         this.delete = null;
+        this.evaluationMacro = null;
       }
 
       let getter = this.addProperty("get", { value: descriptor.get });
       let setter = this.addProperty("set", { value: descriptor.set });
       getter.evaluationMacro = VariablesView.getterOrSetterEvalMacro;
       setter.evaluationMacro = VariablesView.getterOrSetterEvalMacro;
 
       getter.hideArrow();
@@ -2523,40 +2559,40 @@ ViewHelpers.create({ constructor: Variab
    */
   _onNameInputKeyPress: function V__onNameInputKeyPress(e) {
     e.stopPropagation();
 
     switch(e.keyCode) {
       case e.DOM_VK_RETURN:
       case e.DOM_VK_ENTER:
         this._saveNameInput(e);
-        this._variablesView._focusItem(this);
+        this.focus();
         return;
       case e.DOM_VK_ESCAPE:
         this._deactivateNameInput(e);
-        this._variablesView._focusItem(this);
+        this.focus();
         return;
     }
   },
 
   /**
    * The key press listener for this variable's editable value textbox.
    */
   _onValueInputKeyPress: function V__onValueInputKeyPress(e) {
     e.stopPropagation();
 
     switch(e.keyCode) {
       case e.DOM_VK_RETURN:
       case e.DOM_VK_ENTER:
         this._saveValueInput(e);
-        this._variablesView._focusItem(this);
+        this.focus();
         return;
       case e.DOM_VK_ESCAPE:
         this._deactivateValueInput(e);
-        this._variablesView._focusItem(this);
+        this.focus();
         return;
     }
   },
 
   /**
    * The click listener for the edit button.
    */
   _onEdit: function V__onEdit(e) {
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/widgets/VariablesView.xul
@@ -0,0 +1,16 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+<?xml-stylesheet href="chrome://global/skin/global.css"?>
+<?xml-stylesheet href="chrome://browser/content/devtools/widgets.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/devtools/common.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/devtools/widgets.css" type="text/css"?>
+<!DOCTYPE window [
+  <!ENTITY % viewDTD SYSTEM "chrome://browser/locale/devtools/VariablesView.dtd">
+  %viewDTD;
+]>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        title="&PropertiesViewWindowTitle;">
+  <vbox id="variables" flex="1"/>
+</window>
--- a/browser/devtools/styleinspector/test/browser_ruleview_focus.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_focus.js
@@ -14,19 +14,19 @@ function openRuleView()
   var target = TargetFactory.forTab(gBrowser.selectedTab);
   gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
     inspector = toolbox.getCurrentPanel();
     inspector.sidebar.select("ruleview");
 
     // Highlight a node.
     let node = content.document.getElementsByTagName("h1")[0];
     inspector.selection.once("new-node", testFocus);
-    executeSoon(function() {
-      inspector.selection.setNode(doc.body);
-    });
+
+    inspector.sidebar.once("ruleview-ready",
+                           () => inspector.selection.setNode(doc.body));
   });
 }
 
 function testFocus()
 {
   let win = inspector.sidebar.getWindowForTab("ruleview");
   let brace = win.document.querySelectorAll(".ruleview-ruleclose")[0];
 
--- a/browser/devtools/webconsole/HUDService.jsm
+++ b/browser/devtools/webconsole/HUDService.jsm
@@ -5,38 +5,51 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
-const CONSOLEAPI_CLASS_ID = "{b49c18f8-3379-4fc0-8c90-d7772c1a9ff3}";
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
     "resource:///modules/devtools/gDevTools.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "TargetFactory",
     "resource:///modules/devtools/Target.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
     "resource://gre/modules/Services.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
+  "resource://gre/modules/devtools/dbg-server.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "DebuggerClient",
+  "resource://gre/modules/devtools/dbg-client.jsm");
+
 XPCOMUtils.defineLazyModuleGetter(this, "WebConsoleUtils",
     "resource://gre/modules/devtools/WebConsoleUtils.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "webConsoleDefinition",
+    "resource:///modules/devtools/ToolDefinitions.jsm");
+
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
     "resource://gre/modules/commonjs/sdk/core/promise.js");
 
+XPCOMUtils.defineLazyModuleGetter(this, "ViewHelpers",
+    "resource:///modules/devtools/ViewHelpers.jsm");
+
 const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
 let l10n = new WebConsoleUtils.l10n(STRINGS_URI);
 
+const BROWSER_CONSOLE_WINDOW_FEATURES = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
+
 this.EXPORTED_SYMBOLS = ["HUDService"];
 
 ///////////////////////////////////////////////////////////////////////////
 //// The HUD service
 
 function HUD_SERVICE()
 {
   this.hudReferences = {};
@@ -70,24 +83,49 @@ HUD_SERVICE.prototype =
 
   /**
    * Open a Web Console for the given target.
    *
    * @see devtools/framework/Target.jsm for details about targets.
    *
    * @param object aTarget
    *        The target that the web console will connect to.
-   * @param nsIDOMElement aIframe
-   *        The iframe element into which to place the web console.
+   * @param nsIDOMWindow aIframeWindow
+   *        The window where the web console UI is already loaded.
+   * @param nsIDOMWindow aChromeWindow
+   *        The window of the web console owner.
    * @return object
    *         A Promise object for the opening of the new WebConsole instance.
    */
-  openWebConsole: function HS_openWebConsole(aTarget, aIframe)
+  openWebConsole:
+  function HS_openWebConsole(aTarget, aIframeWindow, aChromeWindow)
   {
-    let hud = new WebConsole(aTarget, aIframe);
+    let hud = new WebConsole(aTarget, aIframeWindow, aChromeWindow);
+    this.hudReferences[hud.hudId] = hud;
+    return hud.init();
+  },
+
+  /**
+   * Open a Browser Console for the given target.
+   *
+   * @see devtools/framework/Target.jsm for details about targets.
+   *
+   * @param object aTarget
+   *        The target that the browser console will connect to.
+   * @param nsIDOMWindow aIframeWindow
+   *        The window where the browser console UI is already loaded.
+   * @param nsIDOMWindow aChromeWindow
+   *        The window of the browser console owner.
+   * @return object
+   *         A Promise object for the opening of the new BrowserConsole instance.
+   */
+  openBrowserConsole:
+  function HS_openBrowserConsole(aTarget, aIframeWindow, aChromeWindow)
+  {
+    let hud = new BrowserConsole(aTarget, aIframeWindow, aChromeWindow);
     this.hudReferences[hud.hudId] = hud;
     return hud.init();
   },
 
   /**
    * Returns the HeadsUpDisplay object associated to a content window.
    *
    * @param nsIDOMWindow aContentWindow
@@ -139,46 +177,50 @@ HUD_SERVICE.prototype =
 };
 
 
 /**
  * A WebConsole instance is an interactive console initialized *per target*
  * that displays console log data as well as provides an interactive terminal to
  * manipulate the target's document content.
  *
- * This object only wraps the iframe that holds the Web Console UI.
+ * This object only wraps the iframe that holds the Web Console UI. This is
+ * meant to be an integration point between the Firefox UI and the Web Console
+ * UI and features.
  *
  * @constructor
  * @param object aTarget
  *        The target that the web console will connect to.
- * @param nsIDOMElement aIframe
- *        iframe into which we should create the WebConsole UI.
+ * @param nsIDOMWindow aIframeWindow
+ *        The window where the web console UI is already loaded.
+ * @param nsIDOMWindow aChromeWindow
+ *        The window of the web console owner.
  */
-function WebConsole(aTarget, aIframe)
+function WebConsole(aTarget, aIframeWindow, aChromeWindow)
 {
-  this.iframe = aIframe;
-  this.iframe.className = "web-console-frame";
-  this.chromeDocument = this.iframe.ownerDocument;
-  this.chromeWindow = this.chromeDocument.defaultView;
+  this.iframeWindow = aIframeWindow;
+  this.chromeWindow = aChromeWindow;
   this.hudId = "hud_" + Date.now();
   this.target = aTarget;
 
   this.browserWindow = this.chromeWindow.top;
+
   let element = this.browserWindow.document.documentElement;
   if (element.getAttribute("windowtype") != "navigator:browser") {
     this.browserWindow = HUDService.currentContext();
   }
 }
 
 WebConsole.prototype = {
+  iframeWindow: null,
   chromeWindow: null,
-  chromeDocument: null,
+  browserWindow: null,
   hudId: null,
   target: null,
-  iframe: null,
+  _browserConsole: false,
   _destroyer: null,
 
   /**
    * Getter for HUDService.lastFinishedRequestCallback.
    *
    * @see HUDService.lastFinishedRequestCallback
    * @type function
    */
@@ -207,47 +249,18 @@ WebConsole.prototype = {
   /**
    * Initialize the Web Console instance.
    *
    * @return object
    *         A Promise for the initialization.
    */
   init: function WC_init()
   {
-    let deferred = Promise.defer();
-
-    let onIframeLoad = function() {
-      this.iframe.removeEventListener("load", onIframeLoad, true);
-      initUI();
-    }.bind(this);
-
-    let initUI = function() {
-      this.iframeWindow = this.iframe.contentWindow.wrappedJSObject;
-      this.ui = new this.iframeWindow.WebConsoleFrame(this);
-      this.ui.init().then(onSuccess, onFailure);
-    }.bind(this);
-
-    let onSuccess = function() {
-      deferred.resolve(this);
-    }.bind(this);
-
-    let onFailure = function(aReason) {
-      deferred.reject(aReason);
-    };
-
-    let win, doc;
-    if ((win = this.iframe.contentWindow) &&
-        (doc = win.document) &&
-        doc.readyState == "complete") {
-      initUI();
-    }
-    else {
-      this.iframe.addEventListener("load", onIframeLoad, true);
-    }
-    return deferred.promise;
+    this.ui = new this.iframeWindow.WebConsoleFrame(this);
+    return this.ui.init().then(() => this);
   },
 
   /**
    * Retrieve the Web Console panel title.
    *
    * @return string
    *         The Web Console panel title.
    */
@@ -355,16 +368,20 @@ WebConsole.prototype = {
    */
   viewSourceInDebugger:
   function WC_viewSourceInDebugger(aSourceURL, aSourceLine)
   {
     let self = this;
     let panelWin = null;
     let debuggerWasOpen = true;
     let toolbox = gDevTools.getToolbox(this.target);
+    if (!toolbox) {
+      self.viewSource(aSourceURL, aSourceLine);
+      return;
+    }
 
     if (!toolbox.getPanel("jsdebugger")) {
       debuggerWasOpen = false;
       let toolboxWin = toolbox.doc.defaultView;
       toolboxWin.addEventListener("Debugger:AfterSourcesAdded",
                                   function afterSourcesAdded() {
         toolboxWin.removeEventListener("Debugger:AfterSourcesAdded",
                                        afterSourcesAdded);
@@ -400,16 +417,50 @@ WebConsole.prototype = {
         return;
       }
       panelWin.removeEventListener("Debugger:SourceShown", onSource, false);
       panelWin.DebuggerView.editor.setCaretPosition(aSourceLine - 1);
     }
   },
 
   /**
+   * Retrieve information about the JavaScript debugger's stackframes list. This
+   * is used to allow the Web Console to evaluate code in the selected
+   * stackframe.
+   *
+   * @return object|null
+   *         An object which holds:
+   *         - frames: the active ThreadClient.cachedFrames array.
+   *         - selected: depth/index of the selected stackframe in the debugger
+   *         UI.
+   *         If the debugger is not open or if it's not paused, then |null| is
+   *         returned.
+   */
+  getDebuggerFrames: function WC_getDebuggerFrames()
+  {
+    let toolbox = gDevTools.getToolbox(this.target);
+    if (!toolbox) {
+      return null;
+    }
+    let panel = toolbox.getPanel("jsdebugger");
+    if (!panel) {
+      return null;
+    }
+    let framesController = panel.panelWin.gStackFrames;
+    let thread = framesController.activeThread;
+    if (thread && thread.paused) {
+      return {
+        frames: thread.cachedFrames,
+        selected: framesController.currentFrame,
+      };
+    }
+    return null;
+  },
+
+  /**
    * Destroy the object. Call this method to avoid memory leaks when the Web
    * Console is closed.
    *
    * @return object
    *         A Promise object that is resolved once the Web Console is closed.
    */
   destroy: function WC_destroy()
   {
@@ -447,21 +498,112 @@ WebConsole.prototype = {
     else {
       onDestroy();
     }
 
     return this._destroyer.promise;
   },
 };
 
+
+/**
+ * A BrowserConsole instance is an interactive console initialized *per target*
+ * that displays console log data as well as provides an interactive terminal to
+ * manipulate the target's document content.
+ *
+ * This object only wraps the iframe that holds the Browser Console UI. This is
+ * meant to be an integration point between the Firefox UI and the Browser Console
+ * UI and features.
+ *
+ * @constructor
+ * @param object aTarget
+ *        The target that the browser console will connect to.
+ * @param nsIDOMWindow aIframeWindow
+ *        The window where the browser console UI is already loaded.
+ * @param nsIDOMWindow aChromeWindow
+ *        The window of the browser console owner.
+ */
+function BrowserConsole()
+{
+  WebConsole.apply(this, arguments);
+}
+
+ViewHelpers.create({ constructor: BrowserConsole, proto: WebConsole.prototype },
+{
+  _browserConsole: true,
+  _bc_init: null,
+  _bc_destroyer: null,
+
+  $init: WebConsole.prototype.init,
+
+  /**
+   * Initialize the Browser Console instance.
+   *
+   * @return object
+   *         A Promise for the initialization.
+   */
+  init: function BC_init()
+  {
+    if (this._bc_init) {
+      return this._bc_init;
+    }
+
+    let window = this.iframeWindow;
+    let onClose = () => {
+      window.removeEventListener("unload", onClose);
+      this.destroy();
+    };
+    window.addEventListener("unload", onClose);
+
+    this._bc_init = this.$init().then((aReason) => {
+      let title = this.ui.rootElement.getAttribute("browserConsoleTitle");
+      this.ui.rootElement.setAttribute("title", title);
+      return aReason;
+    });
+
+    return this._bc_init;
+  },
+
+  $destroy: WebConsole.prototype.destroy,
+
+  /**
+   * Destroy the object.
+   *
+   * @return object
+   *         A Promise object that is resolved once the Browser Console is closed.
+   */
+  destroy: function BC_destroy()
+  {
+    if (this._bc_destroyer) {
+      return this._bc_destroyer.promise;
+    }
+
+    this._bc_destroyer = Promise.defer();
+
+    let chromeWindow = this.chromeWindow;
+    this.$destroy().then(() =>
+      this.target.client.close(() => {
+        HeadsUpDisplayUICommands._browserConsoleID = null;
+        chromeWindow.close();
+        this._bc_destroyer.resolve(null);
+      }));
+
+    return this._bc_destroyer.promise;
+  },
+});
+
+
 //////////////////////////////////////////////////////////////////////////
 // HeadsUpDisplayUICommands
 //////////////////////////////////////////////////////////////////////////
 
 var HeadsUpDisplayUICommands = {
+  _browserConsoleID: null,
+  _browserConsoleDefer: null,
+
   /**
    * Toggle the Web Console for the current tab.
    *
    * @return object
    *         A Promise for either the opening of the toolbox that holds the Web
    *         Console, or a Promise for the closing of the toolbox.
    */
   toggleHUD: function UIC_toggleHUD()
@@ -488,11 +630,88 @@ var HeadsUpDisplayUICommands = {
     if (!tab || !TargetFactory.isKnownTab(tab)) {
       return null;
     }
     let target = TargetFactory.forTab(tab);
     let toolbox = gDevTools.getToolbox(target);
     let panel = toolbox ? toolbox.getPanel("webconsole") : null;
     return panel ? panel.hud : null;
   },
+
+  /**
+   * Toggle the Browser Console.
+   */
+  toggleBrowserConsole: function UIC_toggleBrowserConsole()
+  {
+    if (this._browserConsoleID) {
+      let hud = HUDService.getHudReferenceById(this._browserConsoleID);
+      return hud.destroy();
+    }
+
+    if (this._browserConsoleDefer) {
+      return this._browserConsoleDefer.promise;
+    }
+
+    this._browserConsoleDefer = Promise.defer();
+
+    function connect()
+    {
+      let deferred = Promise.defer();
+
+      if (!DebuggerServer.initialized) {
+        DebuggerServer.init();
+        DebuggerServer.addBrowserActors();
+      }
+
+      let client = new DebuggerClient(DebuggerServer.connectPipe());
+      client.connect(() =>
+        client.listTabs((aResponse) =>
+          deferred.resolve({ form: aResponse, client: client })
+        ));
+
+      return deferred.promise;
+    }
+
+    let target;
+    function getTarget(aConnection)
+    {
+      let options = {
+        form: aConnection.form,
+        client: aConnection.client,
+        chrome: true,
+      };
+
+      return TargetFactory.forRemoteTab(options);
+    }
+
+    function openWindow(aTarget)
+    {
+      target = aTarget;
+
+      let deferred = Promise.defer();
+
+      let win = Services.ww.openWindow(null, webConsoleDefinition.url, "_blank",
+                                       BROWSER_CONSOLE_WINDOW_FEATURES, null);
+      win.addEventListener("load", function onLoad() {
+        win.removeEventListener("load", onLoad);
+        deferred.resolve(win);
+      });
+
+      return deferred.promise;
+    }
+
+    connect().then(getTarget).then(openWindow).then((aWindow) =>
+      HUDService.openBrowserConsole(target, aWindow, aWindow)
+        .then((aBrowserConsole) => {
+          this._browserConsoleID = aBrowserConsole.hudId;
+          this._browserConsoleDefer.resolve(aBrowserConsole);
+          this._browserConsoleDefer = null;
+        }));
+
+    return this._browserConsoleDefer.promise;
+  },
+
+  get browserConsole() {
+    return HUDService.getHudReferenceById(this._browserConsoleID);
+  },
 };
 
 const HUDService = new HUD_SERVICE();
--- a/browser/devtools/webconsole/WebConsolePanel.jsm
+++ b/browser/devtools/webconsole/WebConsolePanel.jsm
@@ -36,36 +36,62 @@ WebConsolePanel.prototype = {
    *
    * @return object
    *         A Promise that is resolved when the Web Console completes opening.
    */
   open: function WCP_open()
   {
     let parentDoc = this._toolbox.doc;
     let iframe = parentDoc.getElementById("toolbox-panel-iframe-webconsole");
-    let promise;
+    iframe.className = "web-console-frame";
+
+    // Make sure the iframe content window is ready.
+    let deferredIframe = Promise.defer();
+    let win, doc;
+    if ((win = iframe.contentWindow) &&
+        (doc = win.document) &&
+        doc.readyState == "complete") {
+      deferredIframe.resolve(null);
+    }
+    else {
+      iframe.addEventListener("load", function onIframeLoad() {
+        iframe.removeEventListener("load", onIframeLoad, true);
+        deferredIframe.resolve(null);
+      }, true);
+    }
 
     // Local debugging needs to make the target remote.
+    let promiseTarget;
     if (!this.target.isRemote) {
-      promise = this.target.makeRemote();
-    } else {
-      promise = Promise.resolve(this.target);
+      promiseTarget = this.target.makeRemote();
+    }
+    else {
+      promiseTarget = Promise.resolve(this.target);
     }
 
-    return promise
-      .then(function(aTarget) {
+    // 1. Wait for the iframe to load.
+    // 2. Wait for the remote target.
+    // 3. Open the Web Console.
+    return deferredIframe.promise
+      .then(() => promiseTarget)
+      .then((aTarget) => {
         this._frameWindow._remoteTarget = aTarget;
-        return HUDService.openWebConsole(this.target, iframe);
-      }.bind(this))
-      .then(function onSuccess(aWebConsole) {
+
+        let webConsoleUIWindow = iframe.contentWindow.wrappedJSObject;
+        let chromeWindow = iframe.ownerDocument.defaultView;
+
+        return HUDService.openWebConsole(this.target, webConsoleUIWindow,
+                                         chromeWindow);
+      })
+      .then((aWebConsole) => {
         this.hud = aWebConsole;
         this._isReady = true;
         this.emit("ready");
         return this;
-      }.bind(this), function onError(aReason) {
+      }, (aReason) => {
         let msg = "WebConsolePanel open failed. " +
                   aReason.error + ": " + aReason.message;
         dump(msg + "\n");
         Cu.reportError(msg);
       });
   },
 
   get target() this._toolbox.target,
@@ -75,15 +101,13 @@ WebConsolePanel.prototype = {
 
   destroy: function WCP_destroy()
   {
     if (this._destroyer) {
       return this._destroyer;
     }
 
     this._destroyer = this.hud.destroy();
-    this._destroyer.then(function() {
-      this.emit("destroyed");
-    }.bind(this));
+    this._destroyer.then(() => this.emit("destroyed"));
 
     return this._destroyer;
   },
 };
--- a/browser/devtools/webconsole/test/Makefile.in
+++ b/browser/devtools/webconsole/test/Makefile.in
@@ -37,17 +37,16 @@ MOCHITEST_BROWSER_FILES = \
 	browser_webconsole_copying_multiple_messages_inserts_newlines_in_between.js \
 	browser_webconsole_bug_586388_select_all.js  \
 	browser_webconsole_bug_588967_input_expansion.js \
 	browser_webconsole_log_node_classes.js \
 	browser_webconsole_network_panel.js \
 	browser_webconsole_jsterm.js \
 	browser_webconsole_null_and_undefined_output.js \
 	browser_webconsole_output_order.js \
-	browser_webconsole_property_panel.js \
 	browser_webconsole_property_provider.js \
 	browser_webconsole_bug_587617_output_copy.js \
 	browser_webconsole_bug_585237_line_limit.js \
 	browser_webconsole_bug_582201_duplicate_errors.js \
 	browser_webconsole_bug_580454_timestamp_l10n.js \
 	browser_webconsole_netlogging.js \
 	browser_webconsole_bug_583816_No_input_and_Tab_key_pressed.js \
 	browser_webconsole_bug_594477_clickable_output.js \
@@ -111,20 +110,22 @@ MOCHITEST_BROWSER_FILES = \
 	browser_result_format_as_string.js \
 	browser_webconsole_bug_737873_mixedcontent.js \
 	browser_output_breaks_after_console_dir_uninspectable.js \
 	browser_console_log_inspectable_object.js \
 	browser_bug_638949_copy_link_location.js \
 	browser_output_longstring_expand.js \
 	browser_netpanel_longstring_expand.js \
 	browser_repeated_messages_accuracy.js \
+	browser_webconsole_bug_821877_csp_errors.js \
+	browser_eval_in_debugger_stackframe.js \
+	browser_console_variables_view.js \
+	browser_console_variables_view_while_debugging.js \
+	browser_console.js \
 	head.js \
-	browser_webconsole_bug_821877_csp_errors.js \
-	test-bug-821877-csperrors.html \
-	test-bug-821877-csperrors.html^headers^ \
 	$(NULL)
 
 ifeq ($(OS_ARCH), Darwin)
 MOCHITEST_BROWSER_FILES += \
         browser_webconsole_bug_804845_ctrl_key_nav.js \
         $(NULL)
 endif
 
@@ -212,11 +213,14 @@ MOCHITEST_BROWSER_FILES += \
 	test_bug_770099_bad_policy_uri.html \
 	test_bug_770099_bad_policy_uri.html^headers^ \
 	test-result-format-as-string.html \
 	test-bug-737873-mixedcontent.html \
 	test-repeated-messages.html \
 	test-bug-766001-console-log.js \
 	test-bug-766001-js-console-links.html \
 	test-bug-766001-js-errors.js \
+	test-bug-821877-csperrors.html \
+	test-bug-821877-csperrors.html^headers^ \
+	test-eval-in-stackframe.html \
 	$(NULL)
 
 include $(topsrcdir)/config/rules.mk
--- a/browser/devtools/webconsole/test/browser_bug664688_sandbox_update_after_navigation.js
+++ b/browser/devtools/webconsole/test/browser_bug664688_sandbox_update_after_navigation.js
@@ -47,17 +47,17 @@ function test()
             "jsterm input is also displayed");
 
       is(hud.outputNode.textContent.indexOf("Permission denied"), -1,
          "no permission denied errors");
 
       gBrowser.selectedBrowser.addEventListener("load", onPageLoad2, true);
       content.location = TEST_URI2;
     },
-    failureFn: finishTest,
+    failureFn: finishTestWithError,
   };
 
   function onPageLoad2() {
     gBrowser.selectedBrowser.removeEventListener("load", onPageLoad2, true);
 
     hud.jsterm.clearOutput();
     hud.jsterm.execute("window.location.href");
 
@@ -77,33 +77,33 @@ function test()
       isnot(node.textContent.indexOf("window.location.href"), -1,
             "jsterm input is also displayed");
       is(hud.outputNode.textContent.indexOf("Permission denied"), -1,
          "no permission denied errors");
 
       gBrowser.goBack();
       waitForSuccess(waitForBack);
     },
-    failureFn: finishTest,
+    failureFn: finishTestWithError,
   };
 
   let waitForBack = {
     name: "go back",
     validatorFn: function()
     {
       return content.location.href == TEST_URI1;
     },
     successFn: function()
     {
       hud.jsterm.clearOutput();
       hud.jsterm.execute("window.location.href");
 
       waitForSuccess(waitForLocation3);
     },
-    failureFn: finishTest,
+    failureFn: finishTestWithError,
   };
 
   let waitForLocation3 = {
     name: "window.location.href result is displayed after goBack()",
     validatorFn: function()
     {
       let node = hud.outputNode.getElementsByClassName("webconsole-msg-output")[0];
       return node && node.textContent.indexOf(TEST_URI1) > -1;
@@ -113,11 +113,17 @@ function test()
       let node = hud.outputNode.getElementsByClassName("webconsole-msg-input")[0];
       isnot(node.textContent.indexOf("window.location.href"), -1,
             "jsterm input is also displayed");
       is(hud.outputNode.textContent.indexOf("Permission denied"), -1,
          "no permission denied errors");
 
       executeSoon(finishTest);
     },
-    failureFn: finishTest,
+    failureFn: finishTestWithError,
   };
+
+  function finishTestWithError()
+  {
+    info("output content: " + hud.outputNode.textContent);
+    finishTest();
+  }
 }
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console.js
@@ -0,0 +1,93 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test the basic features of the Browser Console, bug 587757.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-eval-in-stackframe.html";
+
+function test()
+{
+  HUDConsoleUI.toggleBrowserConsole().then(consoleOpened);
+}
+
+function consoleOpened(hud)
+{
+  hud.jsterm.clearOutput(true);
+
+  expectUncaughtException();
+  executeSoon(() => {
+    foobarExceptionBug587757();
+  });
+
+  // Add a message from a chrome window.
+  hud.iframeWindow.console.log("bug587757a");
+
+  // Add a message from a content window.
+  content.console.log("bug587757b");
+
+  // Test eval.
+  hud.jsterm.execute("document.location.href");
+
+  // Check for network requests.
+  let xhr = new XMLHttpRequest();
+  xhr.onload = () => info("xhr loaded, status is: " + xhr.status);
+  xhr.open("get", TEST_URI, true);
+  xhr.send();
+
+  let chromeConsole = -1;
+  let contentConsole = -1;
+  let execValue = -1;
+  let exception = -1;
+  let xhrRequest = false;
+
+  let output = hud.outputNode;
+  function performChecks()
+  {
+    let text = output.textContent;
+    chromeConsole = text.indexOf("bug587757a");
+    contentConsole = text.indexOf("bug587757b");
+    execValue = text.indexOf("browser.xul");
+    exception = text.indexOf("foobarExceptionBug587757");
+
+    xhrRequest = false;
+    let urls = output.querySelectorAll(".webconsole-msg-url");
+    for (let url of urls) {
+      if (url.value.indexOf(TEST_URI) > -1) {
+        xhrRequest = true;
+        break;
+      }
+    }
+  }
+
+  function showResults()
+  {
+    isnot(chromeConsole, -1, "chrome window console.log() is displayed");
+    isnot(contentConsole, -1, "content window console.log() is displayed");
+    isnot(execValue, -1, "jsterm eval result is displayed");
+    isnot(exception, -1, "exception is displayed");
+    ok(xhrRequest, "xhr request is displayed");
+  }
+
+  waitForSuccess({
+    name: "messages displayed",
+    validatorFn: () => {
+      performChecks();
+      return chromeConsole > -1 &&
+             contentConsole > -1 &&
+             execValue > -1 &&
+             exception > -1 &&
+             xhrRequest;
+    },
+    successFn: () => {
+      showResults();
+      executeSoon(finishTest);
+    },
+    failureFn: () => {
+      showResults();
+      info("output: " + output.textContent);
+      executeSoon(finishTest);
+    },
+  });
+}
--- a/browser/devtools/webconsole/test/browser_console_log_inspectable_object.js
+++ b/browser/devtools/webconsole/test/browser_console_log_inspectable_object.js
@@ -31,27 +31,28 @@ function performTest(hud)
     },
     successFn: function()
     {
       isnot(hud.outputNode.textContent.indexOf("myObj = {"), -1,
             "myObj = ... is shown");
 
       let clickable = hud.outputNode.querySelector(".hud-clickable");
       ok(clickable, "the console.log() object .hud-clickable was found");
-      isnot(clickable.textContent.indexOf("omgBug676722"), -1,
+      isnot(clickable.textContent.indexOf("Object"), -1,
             "clickable node content is correct");
 
-      document.addEventListener("popupshown", function _onPopupShown(aEvent) {
-        document.removeEventListener("popupshown", _onPopupShown);
+      hud.jsterm.once("variablesview-fetched",
+        (aEvent, aVar) => {
+          ok(aVar, "object inspector opened on click");
 
-        isnot(aEvent.target.label.indexOf("omgBug676722"), -1,
-           "object inspector opened on click");
-
-        executeSoon(finishTest);
-      });
+          findVariableViewProperties(aVar, [{
+            name: "abba",
+            value: "omgBug676722",
+          }], { webconsole: hud }).then(finishTest);
+        });
 
       executeSoon(function() {
         EventUtils.synthesizeMouse(clickable, 2, 2, {}, hud.iframeWindow);
       });
     },
     failureFn: finishTest,
   });
 }
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_variables_view.js
@@ -0,0 +1,178 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test that makes sure web console eval happens in the user-selected stackframe
+// from the js debugger.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-eval-in-stackframe.html";
+
+let gWebConsole, gJSTerm, gVariablesView;
+
+function test()
+{
+  addTab(TEST_URI);
+  browser.addEventListener("load", function onLoad() {
+    browser.removeEventListener("load", onLoad, true);
+    openConsole(null, consoleOpened);
+  }, true);
+}
+
+function consoleOpened(hud)
+{
+  gWebConsole = hud;
+  gJSTerm = hud.jsterm;
+  gJSTerm.execute("fooObj", onExecuteFooObj);
+}
+
+function onExecuteFooObj()
+{
+  let msg = gWebConsole.outputNode.querySelector(".webconsole-msg-output");
+  ok(msg, "output message found");
+  isnot(msg.textContent.indexOf("[object Object]"), -1, "message text check");
+
+  gJSTerm.once("variablesview-fetched", onFooObjFetch);
+
+  executeSoon(() =>
+    EventUtils.synthesizeMouse(msg, 2, 2, {}, gWebConsole.iframeWindow)
+  );
+}
+
+function onFooObjFetch(aEvent, aVar)
+{
+  gVariablesView = aVar._variablesView;
+  ok(gVariablesView, "variables view object");
+
+  findVariableViewProperties(aVar, [
+    { name: "testProp", value: "testValue" },
+  ], { webconsole: gWebConsole }).then(onTestPropFound);
+}
+
+function onTestPropFound(aResults)
+{
+  let prop = aResults[0].matchedProp;
+  ok(prop, "matched the |testProp| property in the variables view");
+
+  is(content.wrappedJSObject.fooObj.testProp, aResults[0].value,
+     "|fooObj.testProp| value is correct");
+
+  // Check that property value updates work and that jsterm functions can be
+  // used.
+  updateVariablesViewProperty({
+    property: prop,
+    field: "value",
+    string: "document.title + window.location + $('p')",
+    webconsole: gWebConsole,
+    callback: onFooObjFetchAfterUpdate,
+  });
+}
+
+function onFooObjFetchAfterUpdate(aEvent, aVar)
+{
+  info("onFooObjFetchAfterUpdate");
+  let para = content.document.querySelector("p");
+  let expectedValue = content.document.title + content.location + para;
+
+  findVariableViewProperties(aVar, [
+    { name: "testProp", value: expectedValue },
+  ], { webconsole: gWebConsole }).then(onUpdatedTestPropFound);
+}
+
+function onUpdatedTestPropFound(aResults)
+{
+  let prop = aResults[0].matchedProp;
+  ok(prop, "matched the updated |testProp| property value");
+
+  is(content.wrappedJSObject.fooObj.testProp, aResults[0].value,
+     "|fooObj.testProp| value has been updated");
+
+  // Check that property name updates work.
+  updateVariablesViewProperty({
+    property: prop,
+    field: "name",
+    string: "testUpdatedProp",
+    webconsole: gWebConsole,
+    callback: onFooObjFetchAfterPropRename,
+  });
+}
+
+function onFooObjFetchAfterPropRename(aEvent, aVar)
+{
+  info("onFooObjFetchAfterPropRename");
+
+  let para = content.document.querySelector("p");
+  let expectedValue = content.document.title + content.location + para;
+
+  // Check that the new value is in the variables view.
+  findVariableViewProperties(aVar, [
+    { name: "testUpdatedProp", value: expectedValue },
+  ], { webconsole: gWebConsole }).then(onRenamedTestPropFound);
+}
+
+function onRenamedTestPropFound(aResults)
+{
+  let prop = aResults[0].matchedProp;
+  ok(prop, "matched the renamed |testProp| property");
+
+  ok(!content.wrappedJSObject.fooObj.testProp,
+     "|fooObj.testProp| has been deleted");
+  is(content.wrappedJSObject.fooObj.testUpdatedProp, aResults[0].value,
+     "|fooObj.testUpdatedProp| is correct");
+
+  // Check that property value updates that cause exceptions are reported in
+  // the web console output.
+  updateVariablesViewProperty({
+    property: prop,
+    field: "value",
+    string: "foobarzFailure()",
+    webconsole: gWebConsole,
+    callback: onPropUpdateError,
+  });
+}
+
+function onPropUpdateError(aEvent, aVar)
+{
+  info("onPropUpdateError");
+
+  let para = content.document.querySelector("p");
+  let expectedValue = content.document.title + content.location + para;
+
+  // Make sure the property did not change.
+  findVariableViewProperties(aVar, [
+    { name: "testUpdatedProp", value: expectedValue },
+  ], { webconsole: gWebConsole }).then(onRenamedTestPropFoundAgain);
+}
+
+function onRenamedTestPropFoundAgain(aResults)
+{
+  let prop = aResults[0].matchedProp;
+  ok(prop, "matched the renamed |testProp| property again");
+
+  let outputNode = gWebConsole.outputNode;
+
+  waitForSuccess({
+    name: "exception in property update reported in the web console output",
+    validatorFn: () => outputNode.textContent.indexOf("foobarzFailure") != -1,
+    successFn: testPropDelete.bind(null, prop),
+    failureFn: testPropDelete.bind(null, prop),
+  });
+}
+
+function testPropDelete(aProp)
+{
+  gVariablesView.window.focus();
+  aProp.focus();
+
+  executeSoon(() => {
+    EventUtils.synthesizeKey("VK_DELETE", {}, gVariablesView.window);
+    gWebConsole = gJSTerm = gVariablesView = null;
+  });
+
+  waitForSuccess({
+    name: "property deleted",
+    validatorFn: () => !("testUpdatedProp" in content.wrappedJSObject.fooObj),
+    successFn: finishTest,
+    failureFn: finishTest,
+  });
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_variables_view_while_debugging.js
@@ -0,0 +1,132 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test that makes sure web console eval happens in the user-selected stackframe
+// from the js debugger, when changing the value of a property in the variables
+// view.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-eval-in-stackframe.html";
+
+let gWebConsole, gJSTerm, gDebuggerWin, gThread, gDebuggerController,
+    gStackframes, gVariablesView;
+
+function test()
+{
+  addTab(TEST_URI);
+  browser.addEventListener("load", function onLoad() {
+    browser.removeEventListener("load", onLoad, true);
+    openConsole(null, consoleOpened);
+  }, true);
+}
+
+function consoleOpened(hud)
+{
+  gWebConsole = hud;
+  gJSTerm = hud.jsterm;
+
+  executeSoon(() => {
+    info("openDebugger");
+    openDebugger().then(debuggerOpened);
+  });
+}
+
+function debuggerOpened(aResult)
+{
+  gDebuggerWin = aResult.panelWin;
+  gDebuggerController = gDebuggerWin.DebuggerController;
+  gThread = gDebuggerController.activeThread;
+  gStackframes = gDebuggerController.StackFrames;
+
+  executeSoon(() => {
+    gThread.addOneTimeListener("framesadded", onFramesAdded);
+
+    info("firstCall()");
+    content.wrappedJSObject.firstCall();
+  });
+}
+
+function onFramesAdded()
+{
+  info("onFramesAdded");
+
+  executeSoon(() =>
+    openConsole(null, () =>
+      gJSTerm.execute("fooObj", onExecuteFooObj)
+    )
+  );
+}
+
+
+function onExecuteFooObj()
+{
+  let msg = gWebConsole.outputNode.querySelector(".webconsole-msg-output");
+  ok(msg, "output message found");
+  isnot(msg.textContent.indexOf("[object Object]"), -1, "message text check");
+
+  gJSTerm.once("variablesview-fetched", onFooObjFetch);
+
+  executeSoon(() => EventUtils.synthesizeMouse(msg, 2, 2, {},
+                                               gWebConsole.iframeWindow));
+}
+
+function onFooObjFetch(aEvent, aVar)
+{
+  gVariablesView = aVar._variablesView;
+  ok(gVariablesView, "variables view object");
+
+  findVariableViewProperties(aVar, [
+    { name: "testProp2", value: "testValue2" },
+    { name: "testProp", value: "testValue", dontMatch: true },
+  ], { webconsole: gWebConsole }).then(onTestPropFound);
+}
+
+function onTestPropFound(aResults)
+{
+  let prop = aResults[0].matchedProp;
+  ok(prop, "matched the |testProp2| property in the variables view");
+
+  // Check that property value updates work and that jsterm functions can be
+  // used.
+  updateVariablesViewProperty({
+    property: prop,
+    field: "value",
+    string: "document.title + foo2 + $('p')",
+    webconsole: gWebConsole,
+    callback: onFooObjFetchAfterUpdate,
+  });
+}
+
+function onFooObjFetchAfterUpdate(aEvent, aVar)
+{
+  info("onFooObjFetchAfterUpdate");
+  let para = content.document.querySelector("p");
+  let expectedValue = content.document.title + "foo2SecondCall" + para;
+
+  findVariableViewProperties(aVar, [
+    { name: "testProp2", value: expectedValue },
+  ], { webconsole: gWebConsole }).then(onUpdatedTestPropFound);
+}
+
+function onUpdatedTestPropFound(aResults)
+{
+  let prop = aResults[0].matchedProp;
+  ok(prop, "matched the updated |testProp2| property value");
+
+  // Check that testProp2 was updated.
+  executeSoon(() => gJSTerm.execute("fooObj.testProp2", onExecuteFooObjTestProp2));
+}
+
+function onExecuteFooObjTestProp2()
+{
+  let para = content.document.querySelector("p");
+  let expected = content.document.title + "foo2SecondCall" + para;
+
+  isnot(gWebConsole.outputNode.textContent.indexOf(expected), -1,
+        "fooObj.testProp2 is correct");
+
+  gWebConsole = gJSTerm = gDebuggerWin = gThread = gDebuggerController =
+    gStackframes = gVariablesView = null;
+  executeSoon(finishTest);
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_eval_in_debugger_stackframe.js
@@ -0,0 +1,150 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test that makes sure web console eval happens in the user-selected stackframe
+// from the js debugger.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-eval-in-stackframe.html";
+
+let gWebConsole, gJSTerm, gDebuggerWin, gThread, gDebuggerController, gStackframes;
+
+function test()
+{
+  addTab(TEST_URI);
+  browser.addEventListener("load", function onLoad() {
+    browser.removeEventListener("load", onLoad, true);
+    openConsole(null, consoleOpened);
+  }, true);
+}
+
+function consoleOpened(hud)
+{
+  gWebConsole = hud;
+  gJSTerm = hud.jsterm;
+  gJSTerm.execute("foo", onExecuteFoo);
+}
+
+function onExecuteFoo()
+{
+  isnot(gWebConsole.outputNode.textContent.indexOf("globalFooBug783499"), -1,
+        "|foo| value is correct");
+
+  gJSTerm.clearOutput();
+
+  // Test for Bug 690529 - Web Console and Scratchpad should evaluate
+  // expressions in the scope of the content window, not in a sandbox.
+  executeSoon(() => gJSTerm.execute("foo2 = 'newFoo'; window.foo2", onNewFoo2));
+}
+
+function onNewFoo2()
+{
+  is(gWebConsole.outputNode.textContent.indexOf("undefined"), -1,
+     "|undefined| is not displayed after adding |foo2|");
+
+  let msg = gWebConsole.outputNode.querySelector(".webconsole-msg-output");
+  ok(msg, "output result found");
+
+  isnot(msg.textContent.indexOf("newFoo"), -1,
+        "'newFoo' is displayed after adding |foo2|");
+
+  gJSTerm.clearOutput();
+
+  info("openDebugger");
+  executeSoon(() => openDebugger().then(debuggerOpened));
+}
+
+function debuggerOpened(aResult)
+{
+  gDebuggerWin = aResult.panelWin;
+  gDebuggerController = gDebuggerWin.DebuggerController;
+  gThread = gDebuggerController.activeThread;
+  gStackframes = gDebuggerController.StackFrames;
+
+  info("openConsole");
+  executeSoon(() =>
+    openConsole(null, () =>
+      gJSTerm.execute("foo + foo2", onExecuteFooAndFoo2)
+    )
+  );
+}
+
+function onExecuteFooAndFoo2()
+{
+  let expected = "globalFooBug783499newFoo";
+  isnot(gWebConsole.outputNode.textContent.indexOf(expected), -1,
+        "|foo + foo2| is displayed after starting the debugger");
+
+  executeSoon(() => {
+    gJSTerm.clearOutput();
+
+    info("openDebugger");
+    openDebugger().then(() => {
+      gThread.addOneTimeListener("framesadded", onFramesAdded);
+
+      info("firstCall()");
+      content.wrappedJSObject.firstCall();
+    });
+  });
+}
+
+function onFramesAdded()
+{
+  info("onFramesAdded, openConsole() now");
+  executeSoon(() =>
+    openConsole(null, () =>
+      gJSTerm.execute("foo + foo2", onExecuteFooAndFoo2InSecondCall)
+    )
+  );
+}
+
+function onExecuteFooAndFoo2InSecondCall()
+{
+  let expected = "globalFooBug783499foo2SecondCall";
+  isnot(gWebConsole.outputNode.textContent.indexOf(expected), -1,
+        "|foo + foo2| from |secondCall()|");
+
+  executeSoon(() => {
+    gJSTerm.clearOutput();
+
+    info("openDebugger and selectFrame(1)");
+
+    openDebugger().then(() => {
+      gStackframes.selectFrame(1);
+
+      info("openConsole");
+      executeSoon(() =>
+        openConsole(null, () =>
+          gJSTerm.execute("foo + foo2 + foo3", onExecuteFoo23InFirstCall)
+        )
+      );
+    });
+  });
+}
+
+function onExecuteFoo23InFirstCall()
+{
+  let expected = "fooFirstCallnewFoofoo3FirstCall";
+  isnot(gWebConsole.outputNode.textContent.indexOf(expected), -1,
+        "|foo + foo2 + foo3| from |firstCall()|");
+
+  executeSoon(() =>
+    gJSTerm.execute("foo = 'abba'; foo3 = 'bug783499'; foo + foo3",
+                    onExecuteFooAndFoo3ChangesInFirstCall));
+}
+
+function onExecuteFooAndFoo3ChangesInFirstCall()
+{
+  let expected = "abbabug783499";
+  isnot(gWebConsole.outputNode.textContent.indexOf(expected), -1,
+        "|foo + foo3| updated in |firstCall()|");
+
+  is(content.wrappedJSObject.foo, "globalFooBug783499", "|foo| in content window");
+  is(content.wrappedJSObject.foo2, "newFoo", "|foo2| in content window");
+  ok(!content.wrappedJSObject.foo3, "|foo3| was not added to the content window");
+
+  gWebConsole = gJSTerm = gDebuggerWin = gThread = gDebuggerController =
+    gStackframes = null;
+  executeSoon(finishTest);
+}
--- a/browser/devtools/webconsole/test/browser_output_breaks_after_console_dir_uninspectable.js
+++ b/browser/devtools/webconsole/test/browser_output_breaks_after_console_dir_uninspectable.js
@@ -16,23 +16,24 @@ function test()
     openConsole(null, performTest);
   }, true);
 }
 
 function performTest(hud)
 {
   hud.jsterm.clearOutput(true);
 
-  content.console.log("fooBug773466a");
-  content.console.dir(function funBug773466(){});
+  hud.jsterm.execute("console.log('fooBug773466a')");
+  hud.jsterm.execute("myObj = Object.create(null)");
+  hud.jsterm.execute("console.dir(myObj)");
   waitForSuccess({
     name: "eval results are shown",
     validatorFn: function()
     {
-      return hud.outputNode.textContent.indexOf("funBug773466") > -1;
+      return hud.outputNode.querySelector(".webconsole-msg-inspector");
     },
     successFn: function()
     {
       isnot(hud.outputNode.textContent.indexOf("fooBug773466a"), -1,
             "fooBug773466a shows");
       ok(hud.outputNode.querySelector(".webconsole-msg-inspector"),
          "the console.dir() tree shows");
 
--- a/browser/devtools/webconsole/test/browser_result_format_as_string.js
+++ b/browser/devtools/webconsole/test/browser_result_format_as_string.js
@@ -32,18 +32,18 @@ function performTest(hud)
     successFn: function()
     {
       is(hud.outputNode.textContent.indexOf("bug772506_content"), -1,
             "no content element found");
       ok(!hud.outputNode.querySelector("div"), "no div element found");
 
       let msg = hud.outputNode.querySelector(".webconsole-msg-output");
       ok(msg, "eval output node found");
-      isnot(msg.textContent.indexOf("HTMLDivElement"), -1,
-            "HTMLDivElement string found");
+      is(msg.textContent.indexOf("HTMLDivElement"), -1,
+         "HTMLDivElement string not displayed");
       EventUtils.synthesizeMouseAtCenter(msg, {type: "mousemove"});
       ok(!gBrowser._bug772506, "no content variable");
 
       finishTest();
     },
     failureFn: finishTest,
   });
 }
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_594477_clickable_output.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_594477_clickable_output.js
@@ -44,16 +44,18 @@ function tabLoad2(aEvent) {
     },
     failureFn: finishTest,
   });
 }
 
 function networkPanelShown(aEvent) {
   document.removeEventListener(aEvent.type, networkPanelShown, false);
 
+  info("networkPanelShown");
+
   document.addEventListener("popupshown", networkPanelShowFailure, false);
 
   // The network panel should not open for the second time.
   EventUtils.sendMouseEvent({type: "mousedown"}, outputItem);
   EventUtils.sendMouseEvent({type: "click"}, outputItem);
 
   executeSoon(function() {
     aEvent.target.addEventListener("popuphidden", networkPanelHidden, false);
@@ -65,16 +67,18 @@ function networkPanelShowFailure(aEvent)
   document.removeEventListener(aEvent.type, networkPanelShowFailure, false);
 
   ok(false, "the network panel should not show");
 }
 
 function networkPanelHidden(aEvent) {
   this.removeEventListener(aEvent.type, networkPanelHidden, false);
 
+  info("networkPanelHidden");
+
   // The network panel should not show because this is a mouse event that starts
   // in a position and ends in another.
   EventUtils.sendMouseEvent({type: "mousedown", clientX: 3, clientY: 4},
     outputItem);
   EventUtils.sendMouseEvent({type: "click", clientX: 5, clientY: 6},
     outputItem);
 
   // The network panel should not show because this is a middle-click.
@@ -89,83 +93,35 @@ function networkPanelHidden(aEvent) {
   EventUtils.sendMouseEvent({type: "click", button: 2},
     outputItem);
 
   executeSoon(function() {
     document.removeEventListener("popupshown", networkPanelShowFailure, false);
 
     // Done with the network output. Now test the jsterm output and the property
     // panel.
-    HUD.jsterm.setInputValue("document");
-    HUD.jsterm.execute();
+    HUD.jsterm.execute("document", () => {
+      info("jsterm execute 'document' callback");
 
-    waitForSuccess({
-      name: "jsterm output message",
-      validatorFn: function()
-      {
-        return outputNode.querySelector(".webconsole-msg-output .hud-clickable");
-      },
-      successFn: function()
-      {
-        document.addEventListener("popupshown", propertyPanelShown, false);
+      HUD.jsterm.once("variablesview-open", onVariablesViewOpen);
+      let outputItem = outputNode
+                       .querySelector(".webconsole-msg-output .hud-clickable");
+      ok(outputItem, "jsterm output message found");
 
-        // Send the mousedown and click events such that the property panel opens.
-        EventUtils.sendMouseEvent({type: "mousedown"}, outputItem);
-        EventUtils.sendMouseEvent({type: "click"}, outputItem);
-      },
-      failureFn: finishTest,
+      // Send the mousedown and click events such that the property panel opens.
+      EventUtils.sendMouseEvent({type: "mousedown"}, outputItem);
+      EventUtils.sendMouseEvent({type: "click"}, outputItem);
     });
   });
 }
 
-function propertyPanelShown(aEvent) {
-  document.removeEventListener(aEvent.type, propertyPanelShown, false);
-
-  document.addEventListener("popupshown", propertyPanelShowFailure, false);
-
-  // The property panel should not open for the second time.
-  EventUtils.sendMouseEvent({type: "mousedown"}, outputItem);
-  EventUtils.sendMouseEvent({type: "click"}, outputItem);
+function onVariablesViewOpen() {
+  info("onVariablesViewOpen");
 
   executeSoon(function() {
-    aEvent.target.addEventListener("popuphidden", propertyPanelHidden, false);
-    aEvent.target.hidePopup();
-  });
-}
-
-function propertyPanelShowFailure(aEvent) {
-  document.removeEventListener(aEvent.type, propertyPanelShowFailure, false);
-
-  ok(false, "the property panel should not show");
-}
-
-function propertyPanelHidden(aEvent) {
-  this.removeEventListener(aEvent.type, propertyPanelHidden, false);
-
-  // The property panel should not show because this is a mouse event that
-  // starts in a position and ends in another.
-  EventUtils.sendMouseEvent({type: "mousedown", clientX: 3, clientY: 4},
-    outputItem);
-  EventUtils.sendMouseEvent({type: "click", clientX: 5, clientY: 6},
-    outputItem);
-
-  // The property panel should not show because this is a middle-click.
-  EventUtils.sendMouseEvent({type: "mousedown", button: 1},
-    outputItem);
-  EventUtils.sendMouseEvent({type: "click", button: 1},
-    outputItem);
-
-  // The property panel should not show because this is a right-click.
-  EventUtils.sendMouseEvent({type: "mousedown", button: 2},
-    outputItem);
-  EventUtils.sendMouseEvent({type: "click", button: 2},
-    outputItem);
-
-  executeSoon(function() {
-    document.removeEventListener("popupshown", propertyPanelShowFailure, false);
     HUD = outputItem = null;
     executeSoon(finishTest);
   });
 }
 
 function test() {
   addTab(TEST_URI);
   browser.addEventListener("load", function onLoad() {
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_595350_multiple_windows_and_tabs.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_595350_multiple_windows_and_tabs.js
@@ -14,16 +14,18 @@
 
 const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 595350";
 
 let win1 = window, win2;
 let openTabs = [];
 let loadedTabCount = 0;
 
 function test() {
+  requestLongerTimeout(2);
+
   // Add two tabs in the main window.
   addTabs(win1);
 
   // Open a new window.
   win2 = OpenBrowserWindow();
   win2.addEventListener("load", onWindowLoad, true);
 }
 
@@ -59,17 +61,17 @@ function openConsoles() {
       ok(hud, "HUD is open for tab " + index);
       let window = hud.target.tab.linkedBrowser.contentWindow;
       window.console.log("message for tab " + index);
       consolesOpen++;
     }.bind(null, i));
   }
 
   waitForSuccess({
-    timeout: 10000,
+    timeout: 15000,
     name: "4 web consoles opened",
     validatorFn: function()
     {
       return consolesOpen == 4;
     },
     successFn: closeConsoles,
     failureFn: closeConsoles,
   });
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_598357_jsterm_output.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_598357_jsterm_output.js
@@ -19,21 +19,21 @@ let tempScope = {};
 Cu.import("resource://gre/modules/devtools/dbg-server.jsm", tempScope);
 
 let longString = (new Array(tempScope.DebuggerServer.LONG_STRING_LENGTH + 4)).join("a");
 let initialString = longString.substring(0,
   tempScope.DebuggerServer.LONG_STRING_INITIAL_LENGTH);
 
 let inputValues = [
   // [showsPropertyPanel?, input value, expected output format,
-  //    print() output, console output, optional console API test]
+  //    print() output, console API output, optional console API test]
 
   // 0
   [false, "'hello \\nfrom \\rthe \\\"string world!'",
-    '"hello \\nfrom \\rthe \\"string world!"',
+    '"hello \nfrom \rthe "string world!"',
     "hello \nfrom \rthe \"string world!"],
 
   // 1
   [false, "'\xFA\u1E47\u0129\xE7\xF6d\xEA \u021B\u0115\u0219\u0165'",
     "\"\xFA\u1E47\u0129\xE7\xF6d\xEA \u021B\u0115\u0219\u0165\"",
     "\xFA\u1E47\u0129\xE7\xF6d\xEA \u021B\u0115\u0219\u0165"],
 
   // 2
@@ -47,58 +47,52 @@ let inputValues = [
 
   // 5
   [false, "42", "42"],
 
   // 6
   [false, "'42'", '"42"', "42"],
 
   // 7
-  [false, "/foobar/", "/foobar/"],
+  [true, "/foobar/", "[object RegExp]", '"/foobar/"', "[object RegExp]"],
 
   // 8
   [false, "null", "null"],
 
   // 9
   [false, "undefined", "undefined"],
 
   // 10
   [false, "true", "true"],
 
   // 11
-  [false, "document.getElementById", "function getElementById() {\n    [native code]\n}",
+  [true, "document.getElementById", "[object Function]",
     "function getElementById() {\n    [native code]\n}",
-    "function getElementById() {\n    [native code]\n}",
-    "document.wrappedJSObject.getElementById"],
+    "[object Function]"],
 
   // 12
-  [false, "(function() { return 42; })", "function () { return 42; }",
-    "function () { return 42; }",
-    "(function () { return 42; })"],
+  [true, "(function() { return 42; })", "[object Function]",
+    "function () { return 42; }", "[object Function]"],
 
   // 13
-  [false, "new Date(" + dateNow + ")", (new Date(dateNow)).toString()],
+  [true, "new Date(" + dateNow + ")", "[object Date]", (new Date(dateNow)).toString(), "[object Date]"],
 
   // 14
-  [true, "document.body", "[object HTMLBodyElement", "[object HTMLBodyElement",
-    "[object HTMLBodyElement",
-    "document.wrappedJSObject.body"],
+  [true, "document.body", "[object HTMLBodyElement]"],
 
   // 15
-  [true, "window.location", TEST_URI],
+  [true, "window.location", "[object Location]", TEST_URI, "[object Location]"],
 
   // 16
-  [true, "[1,2,3,'a','b','c','4','5']", '[1, 2, 3, "a", "b", "c", "4", "5"]',
+  [true, "[1,2,3,'a','b','c','4','5']", '[object Array]',
     '1,2,3,a,b,c,4,5',
-    '[1, 2, 3, "a", "b", "c", "4", "5"]'],
+    "[object Array]"],
 
   // 17
-  [true, "({a:'b', c:'d', e:1, f:'2'})", '({a:"b", c:"d", e:1, f:"2"})',
-    "[object Object",
-    '({a:"b", c:"d", e:1, f:"2"})'],
+  [true, "({a:'b', c:'d', e:1, f:'2'})", "[object Object]"],
 
   // 18
   [false, "'" + longString + "'",
     '"' + initialString + "\"[\u2026]", initialString],
 ];
 
 longString = null;
 initialString = null;
@@ -149,20 +143,17 @@ function testGen() {
     inputValues[cpos][4] : printOutput;
 
   let consoleTest = inputValues[cpos][5] || inputValue;
 
   HUD.jsterm.clearOutput();
 
   // Test the console.log() output.
 
-  // Ugly but it does the job.
-  with (content) {
-    eval("content.console.log(" + consoleTest + ")");
-  }
+  HUD.jsterm.execute("console.log(" + consoleTest + ")");
 
   waitForSuccess({
     name: "console.log message for test #" + cpos,
     validatorFn: function()
     {
       return HUD.outputNode.querySelector(".hud-log");
     },
     successFn: subtestNext,
@@ -228,38 +219,38 @@ function testGen() {
     "jsterm output is correct for inputValues[" + cpos + "]");
 
   let messageBody = outputItem.querySelector(".webconsole-msg-body");
   ok(messageBody, "we have the message body for inputValues[" + cpos + "]");
 
   // Test click on output.
   let eventHandlerID = eventHandlers.length + 1;
 
-  let propertyPanelShown = function(aEvent) {
-    let label = aEvent.target.getAttribute("label");
-    if (!label || label.indexOf(inputValue) == -1) {
+  let propertyPanelShown = function(aEvent, aView, aOptions) {
+    if (aOptions.label.indexOf(expectedOutput) == -1) {
       return;
     }
 
-    document.removeEventListener(aEvent.type, propertyPanelShown, false);
+    HUD.jsterm.off("variablesview-open", propertyPanelShown);
+
     eventHandlers[eventHandlerID] = null;
 
     ok(showsPropertyPanel,
       "the property panel shown for inputValues[" + cpos + "]");
 
-    aEvent.target.hidePopup();
+    HUD.jsterm._splitter.state = "collapsed";
 
     popupShown[cpos] = true;
 
     if (showsPropertyPanel) {
-      subtestNext();
+      executeSoon(subtestNext);
     }
   };
 
-  document.addEventListener("popupshown", propertyPanelShown, false);
+  HUD.jsterm.on("variablesview-open", propertyPanelShown);
 
   eventHandlers.push(propertyPanelShown);
 
   // Send the mousedown, mouseup and click events to check if the property
   // panel opens.
   EventUtils.sendMouseEvent({ type: "mousedown" }, messageBody, window);
   EventUtils.sendMouseEvent({ type: "click" }, messageBody, window);
 
@@ -276,27 +267,28 @@ function testEnd() {
   if (testEnded) {
     return;
   }
 
   testEnded = true;
 
   for (let i = 0; i < eventHandlers.length; i++) {
     if (eventHandlers[i]) {
-      document.removeEventListener("popupshown", eventHandlers[i], false);
+      HUD.jsterm.off("variablesview-open", eventHandlers[i]);
     }
   }
 
   for (let i = 0; i < inputValues.length; i++) {
     if (inputValues[i][0] && !popupShown[i]) {
       ok(false, "the property panel failed to show for inputValues[" + i + "]");
     }
   }
 
   HUD = inputValues = testDriver = null;
   executeSoon(finishTest);
 }
 
 function test() {
+  requestLongerTimeout(2);
   addTab(TEST_URI);
   browser.addEventListener("load", tabLoad, true);
 }
 
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_600183_charset.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_600183_charset.js
@@ -8,31 +8,34 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-600183-charset.html";
 
 function performTest(lastFinishedRequest, aConsole)
 {
   ok(lastFinishedRequest, "charset test page was loaded and logged");
+  HUDService.lastFinishedRequestCallback = null;
 
-  aConsole.webConsoleClient.getResponseContent(lastFinishedRequest.actor,
-    function (aResponse) {
-      ok(!aResponse.contentDiscarded, "response body was not discarded");
-
-      let body = aResponse.content.text;
-      ok(body, "we have the response body");
+  executeSoon(() => {
+    aConsole.webConsoleClient.getResponseContent(lastFinishedRequest.actor,
+      (aResponse) => {
+        ok(!aResponse.contentDiscarded, "response body was not discarded");
 
-      let chars = "\u7684\u95ee\u5019!"; // 的问候!
-      isnot(body.indexOf("<p>" + chars + "</p>"), -1,
-        "found the chinese simplified string");
-      executeSoon(finishTest);
-    });
+        let body = aResponse.content.text;
+        ok(body, "we have the response body");
 
-  HUDService.lastFinishedRequestCallback = null;
+        let chars = "\u7684\u95ee\u5019!"; // 的问候!
+        isnot(body.indexOf("<p>" + chars + "</p>"), -1,
+          "found the chinese simplified string");
+
+        HUDService.lastFinishedRequestCallback = null;
+        executeSoon(finishTest);
+      });
+  });
 }
 
 function test()
 {
   addTab("data:text/html;charset=utf-8,Web Console - bug 600183 test");
 
   browser.addEventListener("load", function onLoad() {
     browser.removeEventListener("load", onLoad, true);
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_601177_log_levels.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_601177_log_levels.js
@@ -30,17 +30,17 @@ function performTest()
             "found exception");
 
   findEntry(HUD, "hud-jswarn", "undefinedPropertyBug601177",
             "found strict warning");
 
   findEntry(HUD, "hud-jswarn", "foobarBug601177strictError",
             "found strict error");
 
-  finishTest();
+  executeSoon(finishTest);
 }
 
 function findEntry(aHUD, aClass, aString, aMessage)
 {
   return testLogEntry(aHUD.outputNode, aString, aMessage, false, false,
                       aClass);
 }
 
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_602572_log_bodies_checkbox.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_602572_log_bodies_checkbox.js
@@ -15,28 +15,26 @@ function test()
   // open tab 1
   addTab("data:text/html;charset=utf-8,Web Console test for bug 602572: log bodies checkbox. tab 1");
   tabs.push(tab);
 
   browser.addEventListener("load", function onLoad1(aEvent) {
     browser.removeEventListener(aEvent.type, onLoad1, true);
 
     openConsole(null, function(aHud) {
-      info("iframe1 height " + aHud.iframe.clientHeight);
       info("iframe1 root height " + aHud.ui.rootElement.clientHeight);
 
       // open tab 2
       addTab("data:text/html;charset=utf-8,Web Console test for bug 602572: log bodies checkbox. tab 2");
       tabs.push(tab);
 
       browser.addEventListener("load", function onLoad2(aEvent) {
         browser.removeEventListener(aEvent.type, onLoad2, true);
 
         openConsole(null, function(aHud) {
-          info("iframe2 height " + aHud.iframe.clientHeight);
           info("iframe2 root height " + aHud.ui.rootElement.clientHeight);
           waitForFocus(startTest, aHud.iframeWindow);
         });
       }, true);
     });
   }, true);
 }
 
@@ -110,17 +108,16 @@ function onpopupshown2b(aEvent)
     // Switch to tab 1 and open the Web Console context menu from there.
     gBrowser.selectedTab = tabs[runCount*2];
     waitForFocus(function() {
       // Find the relevant elements in the Web Console of tab 1.
       let win1 = tabs[runCount*2].linkedBrowser.contentWindow;
       let hudId1 = HUDService.getHudIdByWindow(win1);
       huds[0] = HUDService.hudReferences[hudId1];
 
-      info("iframe1 height " + huds[0].iframe.clientHeight);
       info("iframe1 root height " + huds[0].ui.rootElement.clientHeight);
 
       menuitems[0] = huds[0].ui.rootElement.querySelector("#saveBodies");
       menupopups[0] = huds[0].ui.rootElement.querySelector("menupopup");
 
       menupopups[0].addEventListener("popupshown", onpopupshown1, false);
       menupopups[0].openPopup();
     }, tabs[runCount*2].linkedBrowser.contentWindow);
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_611795.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_611795.js
@@ -54,24 +54,28 @@ function testConsoleLogRepeats()
   let outputNode = HUD.outputNode;
 
   jsterm.clearOutput();
 
   jsterm.setInputValue("for (let i = 0; i < 10; ++i) console.log('this is a line of reasonably long text that I will use to verify that the repeated text node is of an appropriate size.');");
   jsterm.execute();
 
   waitForSuccess({
+    timeout: 10000,
     name: "10 repeated console.log messages",
     validatorFn: function()
     {
       let node = outputNode.querySelector(".webconsole-msg-console");
       return node && node.childNodes[3].firstChild.getAttribute("value") == 10;
     },
     successFn: finishTest,
-    failureFn: finishTest,
+    failureFn: function() {
+      info("output content: " + outputNode.textContent);
+      finishTest();
+    },
   });
 }
 
 /**
  * Unit test for bug 611795:
  * Repeated CSS messages get collapsed into one.
  */
 function test()
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_618311_close_panels.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_618311_close_panels.js
@@ -25,86 +25,66 @@ function test() {
     });
   }, true);
 }
 
 function performTest() {
   let hudId = HUDService.getHudIdByWindow(content);
   let HUD = HUDService.hudReferences[hudId];
 
-  HUD.jsterm.execute("document");
-
   let networkMessage = HUD.outputNode.querySelector(".webconsole-msg-network");
   ok(networkMessage, "found network message");
 
   let networkLink = networkMessage.querySelector(".webconsole-msg-link");
   ok(networkLink, "found network message link");
 
   let popupset = document.getElementById("mainPopupSet");
   ok(popupset, "found #mainPopupSet");
 
   let popupsShown = 0;
   let hiddenPopups = 0;
 
   let onpopupshown = function() {
+    document.removeEventListener("popupshown", onpopupshown, false);
     popupsShown++;
-    if (popupsShown == 2) {
-      document.removeEventListener("popupshown", onpopupshown, false);
 
-      executeSoon(function() {
-        let popups = popupset.querySelectorAll("panel[hudId=" + hudId + "]");
-        is(popups.length, 2, "found two popups");
+    executeSoon(function() {
+      let popups = popupset.querySelectorAll("panel[hudId=" + hudId + "]");
+      is(popups.length, 1, "found one popup");
 
-        document.addEventListener("popuphidden", onpopuphidden, false);
+      document.addEventListener("popuphidden", onpopuphidden, false);
 
-        registerCleanupFunction(function() {
-          is(hiddenPopups, 2, "correct number of popups hidden");
-          if (hiddenPopups != 2) {
-            document.removeEventListener("popuphidden", onpopuphidden, false);
-          }
-        });
+      registerCleanupFunction(function() {
+        is(hiddenPopups, 1, "correct number of popups hidden");
+        if (hiddenPopups != 1) {
+          document.removeEventListener("popuphidden", onpopuphidden, false);
+        }
+      });
 
-        executeSoon(closeConsole);
-      });
-    }
+      executeSoon(closeConsole);
+    });
   };
 
   let onpopuphidden = function() {
+    document.removeEventListener("popuphidden", onpopuphidden, false);
     hiddenPopups++;
-    if (hiddenPopups == 2) {
-      document.removeEventListener("popuphidden", onpopuphidden, false);
 
-      executeSoon(function() {
-        let popups = popupset.querySelectorAll("panel[hudId=" + hudId + "]");
-        is(popups.length, 0, "no popups found");
+    executeSoon(function() {
+      let popups = popupset.querySelectorAll("panel[hudId=" + hudId + "]");
+      is(popups.length, 0, "no popups found");
 
-        executeSoon(finishTest);
-      });
-    }
+      executeSoon(finishTest);
+    });
   };
 
   document.addEventListener("popupshown", onpopupshown, false);
 
   registerCleanupFunction(function() {
-    is(popupsShown, 2, "correct number of popups shown");
-    if (popupsShown != 2) {
+    is(popupsShown, 1, "correct number of popups shown");
+    if (popupsShown != 1) {
       document.removeEventListener("popupshown", onpopupshown, false);
     }
   });
 
-  waitForSuccess({
-    name: "jsterm output message",
-    validatorFn: function()
-    {
-      return HUD.outputNode.querySelector(".webconsole-msg-output");
-    },
-    successFn: function()
-    {
-      let jstermMessage = HUD.outputNode.querySelector(".webconsole-msg-output");
-      EventUtils.sendMouseEvent({ type: "mousedown" }, jstermMessage, HUD.iframeWindow);
-      EventUtils.sendMouseEvent({ type: "click" }, jstermMessage, HUD.iframeWindow);
-      EventUtils.sendMouseEvent({ type: "mousedown" }, networkLink, HUD.iframeWindow);
-      EventUtils.sendMouseEvent({ type: "mouseup" }, networkLink, HUD.iframeWindow);
-      EventUtils.sendMouseEvent({ type: "click" }, networkLink, HUD.iframeWindow);
-    },
-    failureFn: finishTest,
-  });
+  EventUtils.sendMouseEvent({ type: "mousedown" }, networkLink, HUD.iframeWindow);
+  EventUtils.sendMouseEvent({ type: "mouseup" }, networkLink, HUD.iframeWindow);
+  EventUtils.sendMouseEvent({ type: "click" }, networkLink, HUD.iframeWindow);
 }
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_632275_getters_document_width.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_632275_getters_document_width.js
@@ -1,59 +1,44 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-632275-getters.html";
 
+let getterValue = null;
+
 function test() {
   addTab(TEST_URI);
   browser.addEventListener("load", function onLoad() {
     browser.removeEventListener("load", onLoad, true);
     openConsole(null, consoleOpened);
   }, true);
 }
 
-function consoleOpened(HUD) {
-  let jsterm = HUD.jsterm;
+function consoleOpened(hud) {
+  let doc = content.wrappedJSObject.document;
+  getterValue = doc.foobar._val;
+  hud.jsterm.execute("console.dir(document)");
 
+  let onOpen = onViewOpened.bind(null, hud);
+  hud.jsterm.once("variablesview-fetched", onOpen);
+}
+
+function onViewOpened(hud, event, view)
+{
   let doc = content.wrappedJSObject.document;
 
-  let panel = jsterm.openPropertyPanel({ data: { object: doc }});
-
-  let view = panel.treeView;
-  let find = function(regex) {
-    for (let i = 0; i < view.rowCount; i++) {
-      if (regex.test(view.getCellText(i))) {
-        return true;
-      }
-    }
-    return false;
-  };
-
-  ok(!find(/^(width|height):/), "no document.width/height");
-
-  panel.destroy();
-
-  let getterValue = doc.foobar._val;
+  findVariableViewProperties(view, [
+    { name: /^(width|height)$/, dontMatch: 1 },
+    { name: "foobar._val", value: getterValue },
+    { name: "foobar.val", isGetter: true },
+  ], { webconsole: hud }).then(function() {
+    is(doc.foobar._val, getterValue, "getter did not execute");
+    is(doc.foobar.val, getterValue+1, "getter executed");
+    is(doc.foobar._val, getterValue+1, "getter executed (recheck)");
 
-  panel = jsterm.openPropertyPanel({ data: { object: doc.foobar }});
-  view = panel.treeView;
-
-  is(getterValue, doc.foobar._val, "getter did not execute");
-  is(getterValue+1, doc.foobar.val, "getter executed");
-  is(getterValue+1, doc.foobar._val, "getter executed (recheck)");
-
-  ok(find(/^val: Getter$/),
-     "getter is properly displayed");
-
-  ok(find(new RegExp("^_val: " + getterValue + "$")),
-     "getter _val is properly displayed");
-
-  panel.destroy();
-
-  executeSoon(function() {
-    let textContent = HUD.outputNode.textContent;
+    let textContent = hud.outputNode.textContent;
     is(textContent.indexOf("document.body.client"), -1,
        "no document.width/height warning displayed");
 
     finishTest();
   });
 }
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_632347_iterators_generators.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_632347_iterators_generators.js
@@ -21,104 +21,78 @@ function consoleOpened(HUD) {
   tmp = null;
 
   let jsterm = HUD.jsterm;
   let win = content.wrappedJSObject;
 
   // Make sure autocomplete does not walk through iterators and generators.
   let result = win.gen1.next();
   let completion = JSPropertyProvider(win, "gen1.");
-  is(completion, null, "no matchees for gen1");
+  is(completion, null, "no matches for gen1");
   ok(!WCU.isObjectInspectable(win.gen1),
      "gen1 is not inspectable");
 
   is(result+1, win.gen1.next(), "gen1.next() did not execute");
 
   result = win.gen2.next();
 
   completion = JSPropertyProvider(win, "gen2.");
-  is(completion, null, "no matchees for gen2");
+  is(completion, null, "no matches for gen2");
   ok(!WCU.isObjectInspectable(win.gen2),
      "gen2 is not inspectable");
 
   is((result/2+1)*2, win.gen2.next(),
      "gen2.next() did not execute");
 
   result = win.iter1.next();
   is(result[0], "foo", "iter1.next() [0] is correct");
   is(result[1], "bar", "iter1.next() [1] is correct");
 
   completion = JSPropertyProvider(win, "iter1.");
-  is(completion, null, "no matchees for iter1");
+  is(completion, null, "no matches for iter1");
   ok(!WCU.isObjectInspectable(win.iter1),
      "iter1 is not inspectable");
 
   result = win.iter1.next();
   is(result[0], "baz", "iter1.next() [0] is correct");
   is(result[1], "baaz", "iter1.next() [1] is correct");
 
   completion = JSPropertyProvider(content, "iter2.");
-  is(completion, null, "no matchees for iter2");
+  is(completion, null, "no matches for iter2");
   ok(!WCU.isObjectInspectable(win.iter2),
      "iter2 is not inspectable");
 
   completion = JSPropertyProvider(win, "window.");
   ok(completion, "matches available for window");
   ok(completion.matches.length, "matches available for window (length)");
   ok(WCU.isObjectInspectable(win),
      "window is inspectable");
 
   jsterm.clearOutput();
 
-  jsterm.setInputValue("window");
-  jsterm.execute();
+  jsterm.execute("window");
 
   waitForSuccess({
     name: "jsterm window object output",
     validatorFn: function()
     {
       return HUD.outputNode.querySelector(".webconsole-msg-output");
     },
     successFn: function()
     {
-      document.addEventListener("popupshown", function onShown(aEvent) {
-        document.removeEventListener("popupshown", onShown, false);
-        executeSoon(testPropertyPanel.bind(null, aEvent.target));
-      }, false);
-
+      jsterm.once("variablesview-fetched", testVariablesView.bind(null, HUD));
       let node = HUD.outputNode.querySelector(".webconsole-msg-output");
       EventUtils.synthesizeMouse(node, 2, 2, {}, HUD.iframeWindow);
     },
     failureFn: finishTest,
   });
 }
 
-function testPropertyPanel(aPanel) {
-  let tree = aPanel.querySelector("tree");
-  let view = tree.view;
-  let col = tree.columns[0];
-  ok(view.rowCount, "Property Panel rowCount");
-
-  let find = function(display, children) {
-    for (let i = 0; i < view.rowCount; i++) {
-      if (view.isContainer(i) == children &&
-          view.getCellText(i, col) == display) {
-        return true;
-      }
-    }
-
-    return false;
-  };
-
-  ok(find("gen1: Generator", false),
-     "gen1 is correctly displayed in the Property Panel");
-
-  ok(find("gen2: Generator", false),
-     "gen2 is correctly displayed in the Property Panel");
-
-  ok(find("iter1: Iterator", false),
-     "iter1 is correctly displayed in the Property Panel");
-
-  ok(find("iter2: Object", false),
-     "iter2 is correctly displayed in the Property Panel");
-
-  executeSoon(finishTest);
+function testVariablesView(aWebconsole, aEvent, aView) {
+  findVariableViewProperties(aView, [
+    { name: "gen1", isGenerator: true },
+    { name: "gen2", isGenerator: true },
+    { name: "iter1", isIterator: true },
+    { name: "iter2", isIterator: true },
+  ], { webconsole: aWebconsole }).then(function() {
+    executeSoon(finishTest);
+  });
 }
--- 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
@@ -9,17 +9,17 @@
 function test() {
   addTab("data:text/html;charset=utf-8,Web Console autocompletion bug in document.body");
   browser.addEventListener("load", function onLoad() {
     browser.removeEventListener("load", onLoad, true);
     openConsole(null, consoleOpened);
   }, true);
 }
 
-var gHUD;
+let gHUD;
 
 function consoleOpened(aHud) {
   gHUD = aHud;
   let jsterm = gHUD.jsterm;
   let popup = jsterm.autocompletePopup;
   let completeNode = jsterm.completeNode;
 
   let tmp = {};
@@ -76,62 +76,33 @@ function autocompletePopupHidden()
     failureFn: finishTest,
   });
 }
 
 function testPropertyPanel()
 {
   let jsterm = gHUD.jsterm;
   jsterm.clearOutput();
-  jsterm.setInputValue("document");
-  jsterm.execute();
+  jsterm.execute("document");
 
   waitForSuccess({
     name: "jsterm document object output",
     validatorFn: function()
     {
       return gHUD.outputNode.querySelector(".webconsole-msg-output");
     },
     successFn: function()
     {
-      document.addEventListener("popupshown", function onShown(aEvent) {
-        document.removeEventListener("popupshown", onShown, false);
-        executeSoon(propertyPanelShown.bind(null, aEvent.target));
-      }, false);
-
+      jsterm.once("variablesview-fetched", onVariablesViewReady);
       let node = gHUD.outputNode.querySelector(".webconsole-msg-output");
       EventUtils.synthesizeMouse(node, 2, 2, {}, gHUD.iframeWindow);
     },
     failureFn: finishTest,
   });
 }
 
-function propertyPanelShown(aPanel)
+function onVariablesViewReady(aEvent, aView)
 {
-  let tree = aPanel.querySelector("tree");
-  let view = tree.view;
-  let col = tree.columns[0];
-  ok(view.rowCount, "Property Panel rowCount");
-
-  let foundBody = false;
-  let propPanelProps = [];
-  for (let idx = 0; idx < view.rowCount; ++idx) {
-    let text = view.getCellText(idx, col);
-    if (text == "body: HTMLBodyElement" || text == "body: Object")
-      foundBody = true;
-    propPanelProps.push(text.split(":")[0]);
-  }
-
-  // NB: We pull the properties off the prototype, rather than off object itself,
-  // so that expandos like |constructor|, which the propPanel can't see, are not
-  // included.
-  for (let prop in Object.getPrototypeOf(content.document).wrappedObject) {
-    if (prop == "inputEncoding") {
-      continue;
-    }
-    ok(propPanelProps.indexOf(prop) != -1, "Property |" + prop + "| should be reflected in propertyPanel");
-  }
-
-  ok(foundBody, "found document.body");
-
-  executeSoon(finishTest);
+  findVariableViewProperties(aView, [
+    { name: "__proto__.body", value: "[object HTMLBodyElement]" },
+  ], { webconsole: gHUD }).then(finishTest);
 }
 
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_659907_console_dir.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_659907_console_dir.js
@@ -11,46 +11,19 @@ function test() {
          "object with a dir method");
   browser.addEventListener("load", function onLoad(aEvent) {
     browser.removeEventListener(aEvent.type, onLoad, true);
     openConsole(null, consoleOpened);
   }, true);
 }
 
 function consoleOpened(hud) {
-  outputNode = hud.outputNode;
-  content.console.dir(content.document);
-  waitForSuccess({
-    name: "console.dir displayed",
-    validatorFn: function()
-    {
-      return outputNode.textContent.indexOf("[object HTMLDocument") > -1;
-    },
-    successFn: testConsoleDir.bind(null, outputNode),
-    failureFn: finishTest,
-  });
+  hud.jsterm.execute("console.dir(document)");
+  hud.jsterm.once("variablesview-fetched", testConsoleDir.bind(null, hud));
 }
 
-function testConsoleDir(outputNode) {
-  let msg = outputNode.querySelectorAll(".webconsole-msg-inspector");
-  is(msg.length, 1, "one message node displayed");
-  let view = msg[0].propertyTreeView;
-  let foundQSA = false;
-  let foundLocation = false;
-  let foundWrite = false;
-  for (let i = 0; i < view.rowCount; i++) {
-    let text = view.getCellText(i);
-    if (text == "querySelectorAll: function querySelectorAll()") {
-      foundQSA = true;
-    }
-    else if (text  == "location: Location") {
-      foundLocation = true;
-    }
-    else if (text  == "write: function write()") {
-      foundWrite = true;
-    }
-  }
-  ok(foundQSA, "found document.querySelectorAll");
-  ok(foundLocation, "found document.location");
-  ok(foundWrite, "found document.write");
-  msg = view = outputNode = null;
-  executeSoon(finishTest);
+function testConsoleDir(hud, ev, view) {
+  findVariableViewProperties(view, [
+    { name: "__proto__.querySelectorAll", value: "[object Function]" },
+    { name: "location", value: "[object Location]" },
+    { name: "__proto__.write", value: "[object Function]" },
+  ], { webconsole: hud }).then(finishTest);
 }
--- a/browser/devtools/webconsole/test/browser_webconsole_chrome.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_chrome.js
@@ -11,17 +11,17 @@ function test() {
     browser.removeEventListener("load", onLoad, true);
     openConsole(null, testChrome);
   }, true);
 }
 
 function testChrome(hud) {
   ok(hud, "we have a console");
 
-  ok(hud.iframe, "we have the console iframe");
+  ok(hud.iframeWindow, "we have the console UI window");
 
   let jsterm = hud.jsterm;
   ok(jsterm, "we have a jsterm");
 
   let input = jsterm.inputNode;
   ok(hud.outputNode, "we have an output node");
 
   // Test typing 'docu'.
--- a/browser/devtools/webconsole/test/browser_webconsole_console_logging_api.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_console_logging_api.js
@@ -123,17 +123,17 @@ function testConsoleLoggingAPI(aMethod) 
   // test for multiple arguments.
   console[aMethod]("foo", "bar");
 
   waitForSuccess({
     name: "show both console arguments for " + aMethod,
     validatorFn: function()
     {
       let node = outputNode.querySelector(".hud-msg-node");
-      return node && /foo bar/.test(node.textContent);
+      return node && /"foo" "bar"/.test(node.textContent);
     },
     successFn: nextTest,
     failureFn: nextTest,
   });
 
   yield;
   testDriver.next();
   yield;
--- a/browser/devtools/webconsole/test/browser_webconsole_execution_scope.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_execution_scope.js
@@ -14,30 +14,30 @@ function test() {
     openConsole(null, testExecutionScope);
   }, true);
 }
 
 function testExecutionScope(hud) {
   let jsterm = hud.jsterm;
 
   jsterm.clearOutput();
-  jsterm.execute("window.location;");
+  jsterm.execute("window.location.href;");
 
   waitForSuccess({
     name: "jsterm execution output (two nodes)",
     validatorFn: function()
     {
       return jsterm.outputNode.querySelectorAll(".hud-msg-node").length == 2;
     },
     successFn: function()
     {
       let nodes = jsterm.outputNode.querySelectorAll(".hud-msg-node");
 
-      is(/window.location;/.test(nodes[0].textContent), true,
-        "'window.location;' written to output");
+      is(/window.location.href;/.test(nodes[0].textContent), true,
+        "'window.location.href;' written to output");
 
       isnot(nodes[1].textContent.indexOf(TEST_URI), -1,
         "command was executed in the window scope");
 
       executeSoon(finishTest);
     },
     failureFn: finishTest,
   });
--- a/browser/devtools/webconsole/test/browser_webconsole_jsterm.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_jsterm.js
@@ -130,17 +130,17 @@ function testJSTerm(hud)
       nextTest();
     },
     failureFn: nextTest,
   });
   yield;
 
   jsterm.clearOutput();
   jsterm.execute("pprint({b:2, a:1})");
-  checkResult("a: 1\n  b: 2", "pprint()", 1);
+  checkResult('"  b: 2\n  a: 1"', "pprint()", 1);
   yield;
 
   // check instanceof correctness, bug 599940
   jsterm.clearOutput();
   jsterm.execute("[] instanceof Array");
   checkResult("true", "[] instanceof Array == true", 1);
   yield;
 
@@ -166,17 +166,17 @@ function testJSTerm(hud)
   jsterm.clearOutput();
   jsterm.execute("keys(window)");
   checkResult(null, "keys(window)", 1);
   yield;
 
   // bug 614561
   jsterm.clearOutput();
   jsterm.execute("pprint('hi')");
-  checkResult('0: "h"\n  1: "i"', "pprint('hi')", 1);
+  checkResult('"  0: "h"\n  1: "i""', "pprint('hi')", 1);
   yield;
 
   // check that pprint(function) shows function source, bug 618344
   jsterm.clearOutput();
   jsterm.execute("pprint(print)");
   checkResult(function(nodes) {
     return nodes[0].textContent.indexOf("aOwner.helperResult") > -1;
   }, "pprint(function) shows source", 1);
--- a/browser/devtools/webconsole/test/browser_webconsole_output_order.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_output_order.js
@@ -29,16 +29,16 @@ function testOutputOrder(hud) {
     {
       return outputNode.querySelectorAll(".hud-msg-node").length == 3;
     },
     successFn: function()
     {
       let nodes = outputNode.querySelectorAll(".hud-msg-node");
       let executedStringFirst =
         /console\.log\('foo', 'bar'\);/.test(nodes[0].textContent);
-      let outputSecond = /foo bar/.test(nodes[2].textContent);
+      let outputSecond = /"foo" "bar"/.test(nodes[2].textContent);
       ok(executedStringFirst && outputSecond, "executed string comes first");
 
       finishTest();
     },
     failureFn: finishTest,
   });
 }
deleted file mode 100644
--- a/browser/devtools/webconsole/test/browser_webconsole_property_panel.js
+++ /dev/null
@@ -1,68 +0,0 @@
-/* vim:set ts=2 sw=2 sts=2 et: */
-/* 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/. */
-
-// Tests the functionality of the "property panel", which allows JavaScript
-// objects and DOM nodes to be inspected.
-
-const TEST_URI = "data:text/html;charset=utf8,<p>property panel test";
-
-function test() {
-  addTab(TEST_URI);
-  browser.addEventListener("load", function onLoad() {
-    browser.removeEventListener("load", onLoad, true);
-    openConsole(null, testPropertyPanel);
-  }, true);
-}
-
-function testPropertyPanel(hud) {
-  let jsterm = hud.jsterm;
-
-  let propPanel = jsterm.openPropertyPanel({
-    data: {
-      object: [
-        1,
-        /abc/,
-        null,
-        undefined,
-        function test() {},
-        {}
-      ]
-    }
-  });
-  is (propPanel.treeView.rowCount, 6, "six elements shown in propertyPanel");
-  propPanel.destroy();
-
-  propPanel = jsterm.openPropertyPanel({
-    data: {
-      object: {
-        "0.02": 0,
-        "0.01": 1,
-        "02":   2,
-        "1":    3,
-        "11":   4,
-        "1.2":  5,
-        "1.1":  6,
-        "foo":  7,
-        "bar":  8
-      }
-    }
-  });
-  is (propPanel.treeView.rowCount, 9, "nine elements shown in propertyPanel");
-
-  let view = propPanel.treeView;
-  is (view.getCellText(0), "0.01: 1", "1. element is okay");
-  is (view.getCellText(1), "0.02: 0", "2. element is okay");
-  is (view.getCellText(2), "1: 3",    "3. element is okay");
-  is (view.getCellText(3), "1.1: 6",  "4. element is okay");
-  is (view.getCellText(4), "1.2: 5",  "5. element is okay");
-  is (view.getCellText(5), "02: 2",   "6. element is okay");
-  is (view.getCellText(6), "11: 4",   "7. element is okay");
-  is (view.getCellText(7), "bar: 8",  "8. element is okay");
-  is (view.getCellText(8), "foo: 7",  "9. element is okay");
-  propPanel.destroy();
-
-  executeSoon(finishTest);
-}
-
--- a/browser/devtools/webconsole/test/head.js
+++ b/browser/devtools/webconsole/test/head.js
@@ -9,16 +9,17 @@ let HUDService = tempScope.HUDService;
 Cu.import("resource://gre/modules/devtools/WebConsoleUtils.jsm", tempScope);
 let WebConsoleUtils = tempScope.WebConsoleUtils;
 Cu.import("resource:///modules/devtools/gDevTools.jsm", tempScope);
 let gDevTools = tempScope.gDevTools;
 Cu.import("resource:///modules/devtools/Target.jsm", tempScope);
 let TargetFactory = tempScope.TargetFactory;
 Components.utils.import("resource://gre/modules/devtools/Console.jsm", tempScope);
 let console = tempScope.console;
+let Promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {}).Promise;
 
 const WEBCONSOLE_STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
 let WCU_l10n = new WebConsoleUtils.l10n(WEBCONSOLE_STRINGS_URI);
 
 function log(aMsg)
 {
   dump("*** WebConsoleTest: " + aMsg + "\n");
 }
@@ -138,17 +139,19 @@ function findLogEntry(aString)
  * @param function [aCallback]
  *        Optional function to invoke after the Web Console completes
  *        initialization (web-console-created).
  */
 function openConsole(aTab, aCallback = function() { })
 {
   let target = TargetFactory.forTab(aTab || tab);
   gDevTools.showToolbox(target, "webconsole").then(function(toolbox) {
-    aCallback(toolbox.getCurrentPanel().hud);
+    let hud = toolbox.getCurrentPanel().hud;
+    hud.jsterm._lazyVariablesView = false;
+    aCallback(hud);
   });
 }
 
 /**
  * Close the Web Console for the given tab.
  *
  * @param nsIDOMElement [aTab]
  *        Optional tab element for which you want close the Web Console. The
@@ -160,19 +163,17 @@ function openConsole(aTab, aCallback = f
 function closeConsole(aTab, aCallback = function() { })
 {
   let target = TargetFactory.forTab(aTab || tab);
   let toolbox = gDevTools.getToolbox(target);
   if (toolbox) {
     let panel = toolbox.getPanel("webconsole");
     if (panel) {
       let hudId = panel.hud.hudId;
-      toolbox.destroy().then(function() {
-        executeSoon(aCallback.bind(null, hudId));
-      }).then(null, console.error);
+      toolbox.destroy().then(aCallback.bind(null, hudId)).then(null, console.debug);
     }
     else {
       toolbox.destroy().then(aCallback.bind(null));
     }
   }
   else {
     aCallback();
   }
@@ -228,32 +229,41 @@ function waitForOpenContextMenu(aContext
   EventUtils.synthesizeMouse(targetElement, 2, 2,
                              eventDetails, targetElement.ownerDocument.defaultView);
 }
 
 function finishTest()
 {
   browser = hudId = hud = filterBox = outputNode = cs = null;
 
+  if (HUDConsoleUI.browserConsole) {
+    HUDConsoleUI.toggleBrowserConsole().then(finishTest);
+    return;
+  }
+
   let hud = HUDService.getHudByWindow(content);
   if (!hud) {
     finish();
     return;
   }
   if (hud.jsterm) {
     hud.jsterm.clearOutput(true);
   }
 
   closeConsole(hud.target.tab, finish);
 
   hud = null;
 }
 
 function tearDown()
 {
+  if (HUDConsoleUI.browserConsole) {
+    HUDConsoleUI.toggleBrowserConsole();
+  }
+
   let target = TargetFactory.forTab(gBrowser.selectedTab);
   gDevTools.closeToolbox(target);
   while (gBrowser.tabs.length > 1) {
     gBrowser.removeCurrentTab();
   }
   WCU_l10n = tab = browser = hudId = hud = filterBox = outputNode = cs = null;
 }
 
@@ -310,8 +320,450 @@ function waitForSuccess(aOptions)
 
 function openInspector(aCallback, aTab = gBrowser.selectedTab)
 {
   let target = TargetFactory.forTab(aTab);
   gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
     aCallback(toolbox.getCurrentPanel());
   });
 }
+
+/**
+ * Find variables or properties in a VariablesView instance.
+ *
+ * @param object aView
+ *        The VariablesView instance.
+ * @param array aRules
+ *        The array of rules you want to match. Each rule is an object with:
+ *        - name (string|regexp): property name to match.
+ *        - value (string|regexp): property value to match.
+ *        - isIterator (boolean): check if the property is an iterator.
+ *        - isGetter (boolean): check if the property is a getter.
+ *        - isGenerator (boolean): check if the property is a generator.
+ *        - dontMatch (boolean): make sure the rule doesn't match any property.
+ * @param object aOptions
+ *        Options for matching:
+ *        - webconsole: the WebConsole instance we work with.
+ * @return object
+ *         A Promise object that is resolved when all the rules complete
+ *         matching. The resolved callback is given an array of all the rules
+ *         you wanted to check. Each rule has a new property: |matchedProp|
+ *         which holds a reference to the Property object instance from the
+ *         VariablesView. If the rule did not match, then |matchedProp| is
+ *         undefined.
+ */
+function findVariableViewProperties(aView, aRules, aOptions)
+{
+  // Initialize the search.
+  function init()
+  {
+    // Separate out the rules that require expanding properties throughout the
+    // view.
+    let expandRules = [];
+    let rules = aRules.filter((aRule) => {
+      if (typeof aRule.name == "string" && aRule.name.indexOf(".") > -1) {
+        expandRules.push(aRule);
+        return false;
+      }
+      return true;
+    });
+
+    // Search through the view those rules that do not require any properties to
+    // be expanded. Build the array of matchers, outstanding promises to be
+    // resolved.
+    let outstanding = [];
+    finder(rules, aView, outstanding);
+
+    // Process the rules that need to expand properties.
+    let lastStep = processExpandRules.bind(null, expandRules);
+
+    // Return the results - a Promise resolved to hold the updated aRules array.
+    let returnResults = onAllRulesMatched.bind(null, aRules);
+
+    return Promise.all(outstanding).then(lastStep).then(returnResults);
+  }
+
+  function onMatch(aProp, aRule, aMatched)
+  {
+    if (aMatched && !aRule.matchedProp) {
+      aRule.matchedProp = aProp;
+    }
+  }
+
+  function finder(aRules, aVar, aPromises)
+  {
+    for (let [id, prop] in aVar) {
+      for (let rule of aRules) {
+        let matcher = matchVariablesViewProperty(prop, rule, aOptions);
+        aPromises.push(matcher.then(onMatch.bind(null, prop, rule)));
+      }
+    }
+  }
+
+  function processExpandRules(aRules)
+  {
+    let rule = aRules.shift();
+    if (!rule) {
+      return Promise.resolve(null);
+    }
+
+    let deferred = Promise.defer();
+    let expandOptions = {
+      rootVariable: aView,
+      expandTo: rule.name,
+      webconsole: aOptions.webconsole,
+    };
+
+    variablesViewExpandTo(expandOptions).then(function onSuccess(aProp) {
+      let name = rule.name;
+      let lastName = name.split(".").pop();
+      rule.name = lastName;
+
+      let matched = matchVariablesViewProperty(aProp, rule, aOptions);
+      return matched.then(onMatch.bind(null, aProp, rule)).then(function() {
+        rule.name = name;
+      });
+    }, function onFailure() {
+      return Promise.resolve(null);
+    }).then(processExpandRules.bind(null, aRules)).then(function() {
+      deferred.resolve(null);
+    });
+
+    return deferred.promise;
+  }
+
+  function onAllRulesMatched(aRules)
+  {
+    for (let rule of aRules) {
+      let matched = rule.matchedProp;
+      if (matched && !rule.dontMatch) {
+        ok(true, "rule " + rule.name + " matched for property " + matched.name);
+      }
+      else if (matched && rule.dontMatch) {
+        ok(false, "rule " + rule.name + " should not match property " +
+           matched.name);
+      }
+      else {
+        ok(rule.dontMatch, "rule " + rule.name + " did not match any property");
+      }
+    }
+    return aRules;
+  }
+
+  return init();
+}
+
+/**
+ * Check if a given Property object from the variables view matches the given
+ * rule.
+ *
+ * @param object aProp
+ *        The variable's view Property instance.
+ * @param object aRule
+ *        Rules for matching the property. See findVariableViewProperties() for
+ *        details.
+ * @param object aOptions
+ *        Options for matching. See findVariableViewProperties().
+ * @return object
+ *         A Promise that is resolved when all the checks complete. Resolution
+ *         result is a boolean that tells your promise callback the match
+ *         result: true or false.
+ */
+function matchVariablesViewProperty(aProp, aRule, aOptions)
+{
+  function resolve(aResult) {
+    return Promise.resolve(aResult);
+  }
+
+  if (aRule.name) {
+    let match = aRule.name instanceof RegExp ?
+                aRule.name.test(aProp.name) :
+                aProp.name == aRule.name;
+    if (!match) {
+      return resolve(false);
+    }
+  }
+
+  if (aRule.value) {
+    let displayValue = aProp.displayValue;
+    if (aProp.displayValueClassName == "token-string") {
+      displayValue = displayValue.substring(1, displayValue.length - 1);
+    }
+
+    let match = aRule.value instanceof RegExp ?
+                aRule.value.test(displayValue) :
+                displayValue == aRule.value;
+    if (!match) {
+      info("rule " + aRule.name + " did not match value, expected '" +
+           aRule.value + "', found '" + displayValue  + "'");
+      return resolve(false);
+    }
+  }
+
+  if ("isGetter" in aRule) {
+    let isGetter = !!(aProp.getter && aProp.get("get"));
+    if (aRule.isGetter != isGetter) {
+      info("rule " + aRule.name + " getter test failed");
+      return resolve(false);
+    }
+  }
+
+  if ("isGenerator" in aRule) {
+    let isGenerator = aProp.displayValue == "[object Generator]";
+    if (aRule.isGenerator != isGenerator) {
+      info("rule " + aRule.name + " generator test failed");
+      return resolve(false);
+    }
+  }
+
+  let outstanding = [];
+
+  if ("isIterator" in aRule) {
+    let isIterator = isVariableViewPropertyIterator(aProp, aOptions.webconsole);
+    outstanding.push(isIterator.then((aResult) => {
+      if (aResult != aRule.isIterator) {
+        info("rule " + aRule.name + " iterator test failed");
+      }
+      return aResult == aRule.isIterator;
+    }));
+  }
+
+  outstanding.push(Promise.resolve(true));
+
+  return Promise.all(outstanding).then(function _onMatchDone(aResults) {
+    let ruleMatched = aResults.indexOf(false) == -1;
+    return resolve(ruleMatched);
+  });
+}
+
+/**
+ * Check if the given variables view property is an iterator.
+ *
+ * @param object aProp
+ *        The Property instance you want to check.
+ * @param object aWebConsole
+ *        The WebConsole instance to work with.
+ * @return object
+ *         A Promise that is resolved when the check completes. The resolved
+ *         callback is given a boolean: true if the property is an iterator, or
+ *         false otherwise.
+ */
+function isVariableViewPropertyIterator(aProp, aWebConsole)
+{
+  if (aProp.displayValue == "[object Iterator]") {
+    return Promise.resolve(true);
+  }
+
+  let deferred = Promise.defer();
+
+  variablesViewExpandTo({
+    rootVariable: aProp,
+    expandTo: "__proto__.__iterator__",
+    webconsole: aWebConsole,
+  }).then(function onSuccess(aProp) {
+    deferred.resolve(true);
+  }, function onFailure() {
+    deferred.resolve(false);
+  });
+
+  return deferred.promise;
+}
+
+
+/**
+ * Recursively expand the variables view up to a given property.
+ *
+ * @param aOptions
+ *        Options for view expansion:
+ *        - rootVariable: start from the given scope/variable/property.
+ *        - expandTo: string made up of property names you want to expand.
+ *        For example: "body.firstChild.nextSibling" given |rootVariable:
+ *        document|.
+ *        - webconsole: a WebConsole instance. If this is not provided all
+ *        property expand() calls will be considered sync. Things may fail!
+ * @return object
+ *         A Promise that is resolved only when the last property in |expandTo|
+ *         is found, and rejected otherwise. Resolution reason is always the
+ *         last property - |nextSibling| in the example above. Rejection is
+ *         always the last property that was found.
+ */
+function variablesViewExpandTo(aOptions)
+{
+  let root = aOptions.rootVariable;
+  let expandTo = aOptions.expandTo.split(".");
+  let jsterm = (aOptions.webconsole || {}).jsterm;
+  let lastDeferred = Promise.defer();
+
+  function fetch(aProp)
+  {
+    if (!aProp.onexpand) {
+      ok(false, "property " + aProp.name + " cannot be expanded: !onexpand");
+      return Promise.reject(aProp);
+    }
+
+    let deferred = Promise.defer();
+
+    if (aProp._fetched || !jsterm) {
+      executeSoon(function() {
+        deferred.resolve(aProp);
+      });
+    }
+    else {
+      jsterm.once("variablesview-fetched", function _onFetchProp() {
+        executeSoon(() => deferred.resolve(aProp));
+      });
+    }
+
+    aProp.expand();
+
+    return deferred.promise;
+  }
+
+  function getNext(aProp)
+  {
+    let name = expandTo.shift();
+    let newProp = aProp.get(name);
+
+    if (expandTo.length > 0) {
+      ok(newProp, "found property " + name);
+      if (newProp) {
+        fetch(newProp).then(getNext, fetchError);
+      }
+      else {
+        lastDeferred.reject(aProp);
+      }
+    }
+    else {
+      if (newProp) {
+        lastDeferred.resolve(newProp);
+      }
+      else {
+        lastDeferred.reject(aProp);
+      }
+    }
+  }
+
+  function fetchError(aProp)
+  {
+    lastDeferred.reject(aProp);
+  }
+
+  if (!root._fetched) {
+    fetch(root).then(getNext, fetchError);
+  }
+  else {
+    getNext(root);
+  }
+
+  return lastDeferred.promise;
+}
+
+
+/**
+ * Update the content of a property in the variables view.
+ *
+ * @param object aOptions
+ *        Options for the property update:
+ *        - property: the property you want to change.
+ *        - field: string that tells what you want to change:
+ *          - use "name" to change the property name,
+ *          - or "value" to change the property value.
+ *        - string: the new string to write into the field.
+ *        - webconsole: reference to the Web Console instance we work with.
+ *        - callback: function to invoke after the property is updated.
+ */
+function updateVariablesViewProperty(aOptions)
+{
+  let view = aOptions.property._variablesView;
+  view.window.focus();
+  aOptions.property.focus();
+
+  switch (aOptions.field) {
+    case "name":
+      EventUtils.synthesizeKey("VK_ENTER", { shiftKey: true }, view.window);
+      break;
+    case "value":
+      EventUtils.synthesizeKey("VK_ENTER", {}, view.window);
+      break;
+    default:
+      throw new Error("options.field is incorrect");
+      return;
+  }
+
+  executeSoon(() => {
+    EventUtils.synthesizeKey("A", { accelKey: true }, view.window);
+
+    for (let c of aOptions.string) {
+      EventUtils.synthesizeKey(c, {}, gVariablesView.window);
+    }
+
+    if (aOptions.webconsole) {
+      aOptions.webconsole.jsterm.once("variablesview-fetched", aOptions.callback);
+    }
+
+    EventUtils.synthesizeKey("VK_ENTER", {}, view.window);
+
+    if (!aOptions.webconsole) {
+      executeSoon(aOptions.callback);
+    }
+  });
+}
+
+/**
+ * Open the JavaScript debugger.
+ *
+ * @param object aOptions
+ *        Options for opening the debugger:
+ *        - tab: the tab you want to open the debugger for.
+ * @return object
+ *         A Promise that is resolved once the debugger opens, or rejected if
+ *         the open fails. The resolution callback is given one argument, an
+ *         object that holds the following properties:
+ *         - target: the Target object for the Tab.
+ *         - toolbox: the Toolbox instance.
+ *         - panel: the jsdebugger panel instance.
+ *         - panelWin: the window object of the panel iframe.
+ */
+function openDebugger(aOptions = {})
+{
+  if (!aOptions.tab) {
+    aOptions.tab = gBrowser.selectedTab;
+  }
+
+  let deferred = Promise.defer();
+
+  let target = TargetFactory.forTab(aOptions.tab);
+  let toolbox = gDevTools.getToolbox(target);
+  let dbgPanelAlreadyOpen = toolbox.getPanel("jsdebugger");
+
+  gDevTools.showToolbox(target, "jsdebugger").then(function onSuccess(aToolbox) {
+    let panel = aToolbox.getCurrentPanel();
+    let panelWin = panel.panelWin;
+
+    panel._view.Variables.lazyEmpty = false;
+    panel._view.Variables.lazyAppend = false;
+
+    let resolveObject = {
+      target: target,
+      toolbox: aToolbox,
+      panel: panel,
+      panelWin: panelWin,
+    };
+
+    if (dbgPanelAlreadyOpen) {
+      deferred.resolve(resolveObject);
+    }
+    else {
+      panelWin.addEventListener("Debugger:AfterSourcesAdded",
+        function onAfterSourcesAdded() {
+          panelWin.removeEventListener("Debugger:AfterSourcesAdded",
+                                       onAfterSourcesAdded);
+          deferred.resolve(resolveObject);
+        });
+    }
+  }, function onFailure(aReason) {
+    console.debug("failed to open the toolbox for 'jsdebugger'", aReason);
+    deferred.reject(aReason);
+  });
+
+  return deferred.promise;
+}
+
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-eval-in-stackframe.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html dir="ltr" lang="en">
+  <head>
+    <meta charset="utf8">
+    <!--
+    - Any copyright is dedicated to the Public Domain.
+    - http://creativecommons.org/publicdomain/zero/1.0/
+    -->
+    <title>Test for bug 783499 - use the debugger API in the web console</title>
+    <script>
+      var foo = "globalFooBug783499";
+      var fooObj = {
+        testProp: "testValue",
+      };
+
+      function firstCall()
+      {
+        var foo = "fooFirstCall";
+        var foo3 = "foo3FirstCall";
+        secondCall();
+      }
+
+      function secondCall()
+      {
+        var foo2 = "foo2SecondCall";
+        var fooObj = {
+          testProp2: "testValue2",
+        };
+        var fooObj2 = {
+          testProp22: "testValue22",
+        };
+        debugger;
+      }
+    </script>
+  </head>
+  <body>
+    <p>Hello world!</p>
+  </body>
+</html>
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -14,45 +14,57 @@ Cu.import("resource://gre/modules/XPCOMU
 
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
                                    "@mozilla.org/widget/clipboardhelper;1",
                                    "nsIClipboardHelper");
 
-XPCOMUtils.defineLazyModuleGetter(this, "PropertyPanel",
-                                  "resource:///modules/PropertyPanel.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "PropertyTreeView",
-                                  "resource:///modules/PropertyPanel.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "GripClient",
+                                  "resource://gre/modules/devtools/dbg-client.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "NetworkPanel",
                                   "resource:///modules/NetworkPanel.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AutocompletePopup",
                                   "resource:///modules/devtools/AutocompletePopup.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "WebConsoleUtils",
                                   "resource://gre/modules/devtools/WebConsoleUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
                                   "resource://gre/modules/commonjs/sdk/core/promise.js");
 
+XPCOMUtils.defineLazyModuleGetter(this, "VariablesView",
+                                  "resource:///modules/devtools/VariablesView.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "ToolSidebar",
+                                  "resource:///modules/devtools/Sidebar.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
+                                  "resource:///modules/devtools/EventEmitter.jsm");
+
 const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
 let l10n = new WebConsoleUtils.l10n(STRINGS_URI);
 
 
 // The XUL namespace.
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 const MIXED_CONTENT_LEARN_MORE = "https://developer.mozilla.org/en/Security/MixedContent";
 
 const HELP_URL = "https://developer.mozilla.org/docs/Tools/Web_Console/Helpers";
 
+const VARIABLES_VIEW_URL = "chrome://browser/content/devtools/widgets/VariablesView.xul";
+
+const CONSOLE_DIR_VIEW_HEIGHT = 0.6;
+
+const IGNORED_SOURCE_URLS = ["debugger eval code", "self-hosted"];
+
 // The amount of time in milliseconds that must pass between messages to
 // trigger the display of a new group.
 const NEW_GROUP_DELAY = 5000;
 
 // The amount of time in milliseconds that we wait before performing a live
 // search.
 const SEARCH_DELAY = 200;
 
@@ -209,16 +221,23 @@ WebConsoleFrame.prototype = {
 
   /**
    * Getter for the xul:popupset that holds any popups we open.
    * @type nsIDOMElement
    */
   get popupset() this.owner.mainPopupSet,
 
   /**
+   * Holds the initialization Promise object.
+   * @private
+   * @type object
+   */
+  _initDefer: null,
+
+  /**
    * Holds the network requests currently displayed by the Web Console. Each key
    * represents the connection ID and the value is network request information.
    * @private
    * @type object
    */
   _networkRequests: null,
 
   /**
@@ -360,40 +379,37 @@ WebConsoleFrame.prototype = {
    *
    * @private
    * @return object
    *         A Promise object that is resolved/reject based on the connection
    *         result.
    */
   _initConnection: function WCF__initConnection()
   {
-    let deferred = Promise.defer();
-
+    if (this._initDefer) {
+      return this._initDefer.promise;
+    }
+
+    this._initDefer = Promise.defer();
     this.proxy = new WebConsoleConnectionProxy(this, this.owner.target);
 
-    let onSuccess = function() {
+    this.proxy.connect().then(() => { // on success
       this.saveRequestAndResponseBodies = this._saveRequestAndResponseBodies;
-      deferred.resolve(this);
-    }.bind(this);
-
-    let onFailure = function(aReason) {
+      this._initDefer.resolve(this);
+    }, (aReason) => { // on failure
       let node = this.createMessageNode(CATEGORY_JS, SEVERITY_ERROR,
                                         aReason.error + ": " + aReason.message);
       this.outputMessage(CATEGORY_JS, node);
-      deferred.reject(aReason);
-    }.bind(this);
-
-    let sendNotification = function() {
+      this._initDefer.reject(aReason);
+    }).then(() => {
       let id = WebConsoleUtils.supportsString(this.hudId);
       Services.obs.notifyObservers(id, "web-console-created", null);
-    }.bind(this);
-
-    this.proxy.connect().then(onSuccess, onFailure).then(sendNotification);
-
-    return deferred.promise;
+    });
+
+    return this._initDefer.promise;
   },
 
   /**
    * Find the Web Console UI elements and setup event listeners as needed.
    * @private
    */
   _initUI: function WCF__initUI()
   {
@@ -949,55 +965,42 @@ WebConsoleFrame.prototype = {
   logConsoleAPIMessage: function WCF_logConsoleAPIMessage(aMessage)
   {
     let body = null;
     let clipboardText = null;
     let sourceURL = aMessage.filename;
     let sourceLine = aMessage.lineNumber;
     let level = aMessage.level;
     let args = aMessage.arguments;
-    let objectActors = [];
+    let objectActors = new Set();
 
     // Gather the actor IDs.
-    args.forEach(function(aValue) {
-      if (aValue && typeof aValue == "object" && aValue.actor) {
-        objectActors.push(aValue.actor);
-        let displayStringIsLong = typeof aValue.displayString == "object" &&
-                                  aValue.displayString.type == "longString";
-        if (displayStringIsLong) {
-          objectActors.push(aValue.displayString.actor);
-        }
+    args.forEach((aValue) => {
+      if (WebConsoleUtils.isActorGrip(aValue)) {
+        objectActors.add(aValue.actor);
       }
-    }, this);
+    });
 
     switch (level) {
       case "log":
       case "info":
       case "warn":
       case "error":
       case "debug":
-      case "dir":
-      case "groupEnd": {
+      case "dir": {
         body = { arguments: args };
         let clipboardArray = [];
-        args.forEach(function(aValue) {
-          clipboardArray.push(WebConsoleUtils.objectActorGripToString(aValue));
-          if (aValue && typeof aValue == "object" && aValue.actor) {
-            let displayStringIsLong = typeof aValue.displayString == "object" &&
-                                      aValue.displayString.type == "longString";
-            if (aValue.type == "longString" || displayStringIsLong) {
-              clipboardArray.push(l10n.getStr("longStringEllipsis"));
-            }
+        args.forEach((aValue) => {
+          clipboardArray.push(VariablesView.getString(aValue));
+          if (aValue && typeof aValue == "object" &&
+              aValue.type == "longString") {
+            clipboardArray.push(l10n.getStr("longStringEllipsis"));
           }
-        }, this);
+        });
         clipboardText = clipboardArray.join(" ");
-
-        if (level == "dir") {
-          body.objectProperties = aMessage.objectProperties;
-        }
         break;
       }
 
       case "trace": {
         let filename = WebConsoleUtils.abbreviateSourceURL(aMessage.filename);
         let functionName = aMessage.functionName ||
                            l10n.getStr("stacktrace.anonymousFunction");
 
@@ -1017,16 +1020,22 @@ WebConsoleFrame.prototype = {
       }
 
       case "group":
       case "groupCollapsed":
         clipboardText = body = aMessage.groupName;
         this.groupDepth++;
         break;
 
+      case "groupEnd":
+        if (this.groupDepth > 0) {
+          this.groupDepth--;
+        }
+        break;
+
       case "time": {
         let timer = aMessage.timer;
         if (!timer) {
           return;
         }
         if (timer.error) {
           Cu.reportError(l10n.getStr(timer.error));
           return;
@@ -1055,62 +1064,41 @@ WebConsoleFrame.prototype = {
     // we ignore their arguments.
     switch (level) {
       case "group":
       case "groupCollapsed":
       case "groupEnd":
       case "trace":
       case "time":
       case "timeEnd":
-        objectActors.forEach(this._releaseObject, this);
-        objectActors = [];
+        for (let actor of objectActors) {
+          this._releaseObject(actor);
+        }
+        objectActors.clear();
     }
 
     if (level == "groupEnd") {
-      if (this.groupDepth > 0) {
-        this.groupDepth--;
-      }
       return; // no need to continue
     }
 
     let node = this.createMessageNode(CATEGORY_WEBDEV, LEVELS[level], body,
                                       sourceURL, sourceLine, clipboardText,
                                       level, aMessage.timeStamp);
 
-    if (objectActors.length) {
+    if (objectActors.size > 0) {
       node._objectActors = objectActors;
     }
 
-    // Make the node bring up the property panel, to allow the user to inspect
+    // Make the node bring up the variables view, to allow the user to inspect
     // the stack trace.
     if (level == "trace") {
       node._stacktrace = aMessage.stacktrace;
 
-      this.makeOutputMessageLink(node, function _traceNodeClickCallback() {
-        if (node._panelOpen) {
-          return;
-        }
-
-        let options = {
-          anchor: node,
-          data: { object: node._stacktrace },
-        };
-
-        let propPanel = this.jsterm.openPropertyPanel(options);
-        propPanel.panel.setAttribute("hudId", this.hudId);
-      }.bind(this));
-    }
-
-    if (level == "dir") {
-      // Initialize the inspector message node, by setting the PropertyTreeView
-      // object on the tree view. This has to be done *after* the node is
-      // shown, because the tree binding must be attached first.
-      node._onOutput = function _onMessageOutput() {
-        node.querySelector("tree").view = node.propertyTreeView;
-      };
+      this.makeOutputMessageLink(node, () =>
+        this.jsterm.openVariablesView({ rawObject: node._stacktrace }));
     }
 
     return node;
   },
 
   /**
    * Handle ConsoleAPICall objects received from the server. This method outputs
    * the window.console API call.
@@ -1123,59 +1111,29 @@ WebConsoleFrame.prototype = {
     this.outputMessage(CATEGORY_WEBDEV, this.logConsoleAPIMessage, [aMessage]);
   },
 
   /**
    * The click event handler for objects shown inline coming from the
    * window.console API.
    *
    * @private
-   * @param nsIDOMNode aMessage
-   *        The message element this handler corresponds to.
    * @param nsIDOMNode aAnchor
    *        The object inspector anchor element. This is the clickable element
    *        in the console.log message we display.
    * @param object aObjectActor
    *        The object actor grip.
    */
-  _consoleLogClick:
-  function WCF__consoleLogClick(aMessage, aAnchor, aObjectActor)
+  _consoleLogClick: function WCF__consoleLogClick(aAnchor, aObjectActor)
   {
-    if (aAnchor._panelOpen) {
-      return;
-    }
-
     let options = {
-      title: aAnchor.textContent,
-      anchor: aAnchor,
-
-      // Data to inspect.
-      data: {
-        objectPropertiesProvider: this.objectPropertiesProvider.bind(this),
-        releaseObject: this._releaseObject.bind(this),
-      },
+      label: aAnchor.textContent,
+      objectActor: aObjectActor,
     };
-
-    let propPanel;
-    let onPopupHide = function _onPopupHide() {
-      propPanel.panel.removeEventListener("popuphiding", onPopupHide, false);
-
-      if (!aMessage.parentNode && aMessage._objectActors) {
-        aMessage._objectActors.forEach(this._releaseObject, this);
-        aMessage._objectActors = null;
-      }
-    }.bind(this);
-
-    this.objectPropertiesProvider(aObjectActor.actor,
-      function _onObjectProperties(aProperties) {
-        options.data.objectProperties = aProperties;
-        propPanel = this.jsterm.openPropertyPanel(options);
-        propPanel.panel.setAttribute("hudId", this.hudId);
-        propPanel.panel.addEventListener("popuphiding", onPopupHide, false);
-      }.bind(this));
+    this.jsterm.openVariablesView(options);
   },
 
   /**
    * Reports an error in the page source, either JavaScript or CSS.
    *
    * @param nsIScriptError aScriptError
    *        The error message to report.
    * @return nsIDOMElement|undefined
@@ -1903,19 +1861,21 @@ WebConsoleFrame.prototype = {
    *
    * @private
    * @param array aItem
    *        The item you want to remove from the output queue.
    */
   _pruneItemFromQueue: function WCF__pruneItemFromQueue(aItem)
   {
     let [category, methodOrNode, args] = aItem;
-    if (typeof methodOrNode != "function" &&
-        methodOrNode._objectActors && !methodOrNode._panelOpen) {
-      methodOrNode._objectActors.forEach(this._releaseObject, this);
+    if (typeof methodOrNode != "function" && methodOrNode._objectActors) {
+      for (let actor of methodOrNode._objectActors) {
+        this._releaseObject(actor);
+      }
+      methodOrNode._objectActors.clear();
     }
 
     if (category == CATEGORY_NETWORK) {
       let connectionId = null;
       if (methodOrNode == this.logNetEvent) {
         connectionId = args[0];
       }
       else if (typeof methodOrNode != "function") {
@@ -1923,40 +1883,21 @@ WebConsoleFrame.prototype = {
       }
       if (connectionId && connectionId in this._networkRequests) {
         delete this._networkRequests[connectionId];
         this._releaseObject(connectionId);
       }
     }
     else if (category == CATEGORY_WEBDEV &&
              methodOrNode == this.logConsoleAPIMessage) {
-      let level = args[0].level;
-      let releaseObject = function _releaseObject(aValue) {
-        if (aValue && typeof aValue == "object" && aValue.actor) {
+      args[0].arguments.forEach((aValue) => {
+        if (WebConsoleUtils.isActorGrip(aValue)) {
           this._releaseObject(aValue.actor);
         }
-      }.bind(this);
-      switch (level) {
-        case "log":
-        case "info":
-        case "warn":
-        case "error":
-        case "debug":
-        case "dir":
-        case "groupEnd": {
-          args[0].arguments.forEach(releaseObject);
-          if (level == "dir") {
-            args[0].objectProperties.forEach(function(aObject) {
-              ["value", "get", "set"].forEach(function(aProp) {
-                releaseObject(aObject[aProp]);
-              });
-            });
-          }
-        }
-      }
+      });
     }
   },
 
   /**
    * Ensures that the number of message nodes of type aCategory don't exceed that
    * category's line limit by removing old messages as needed.
    *
    * @param integer aCategory
@@ -1974,62 +1915,51 @@ WebConsoleFrame.prototype = {
     let n = Math.max(0, messageNodes.length - logLimit);
     let toRemove = Array.prototype.slice.call(messageNodes, 0, n);
     toRemove.forEach(this.removeOutputMessage, this);
 
     return n;
   },
 
   /**
-   * Destroy the property inspector message node. This performs the necessary
-   * cleanup for the tree widget and removes it from the DOM.
-   *
-   * @param nsIDOMNode aMessageNode
-   *        The message node that contains the property inspector from a
-   *        console.dir call.
-   */
-  pruneConsoleDirNode: function WCF_pruneConsoleDirNode(aMessageNode)
-  {
-    if (aMessageNode.parentNode) {
-      aMessageNode.parentNode.removeChild(aMessageNode);
-    }
-
-    let tree = aMessageNode.querySelector("tree");
-    tree.parentNode.removeChild(tree);
-    aMessageNode.propertyTreeView.data = null;
-    aMessageNode.propertyTreeView = null;
-    tree.view = null;
-  },
-
-  /**
    * Remove a given message from the output.
    *
    * @param nsIDOMNode aNode
    *        The message node you want to remove.
    */
   removeOutputMessage: function WCF_removeOutputMessage(aNode)
   {
-    if (aNode._objectActors && !aNode._panelOpen) {
-      aNode._objectActors.forEach(this._releaseObject, this);
+    if (aNode._objectActors) {
+      for (let actor of aNode._objectActors) {
+        this._releaseObject(actor);
+      }
+      aNode._objectActors.clear();
     }
 
     if (aNode.classList.contains("webconsole-msg-cssparser")) {
       let repeatNode = aNode.getElementsByClassName("webconsole-msg-repeat")[0];
       if (repeatNode && repeatNode._uid) {
         delete this._cssNodes[repeatNode._uid];
       }
     }
     else if (aNode._connectionId &&
              aNode.classList.contains("webconsole-msg-network")) {
       delete this._networkRequests[aNode._connectionId];
       this._releaseObject(aNode._connectionId);
     }
     else if (aNode.classList.contains("webconsole-msg-inspector")) {
-      this.pruneConsoleDirNode(aNode);
-      return;
+      let view = aNode._variablesView;
+      let actors = view ?
+                   this.jsterm._objectActorsInVariablesViews.get(view) :
+                   new Set();
+      for (let actor of actors) {
+        this._releaseObject(actor);
+      }
+      actors.clear();
+      aNode._variablesView = null;
     }
 
     if (aNode.parentNode) {
       aNode.parentNode.removeChild(aNode);
     }
   },
 
   /**
@@ -2110,35 +2040,34 @@ WebConsoleFrame.prototype = {
     spacer.flex = 1;
     iconContainer.appendChild(spacer);
 
     // Create the message body, which contains the actual text of the message.
     let bodyNode = this.document.createElementNS(XUL_NS, "description");
     bodyNode.flex = 1;
     bodyNode.classList.add("webconsole-msg-body");
 
-    // Store the body text, since it is needed later for the property tree
-    // case.
+    // Store the body text, since it is needed later for the variables view.
     let body = aBody;
     // If a string was supplied for the body, turn it into a DOM node and an
     // associated clipboard string now.
     aClipboardText = aClipboardText ||
                      (aBody + (aSourceURL ? " @ " + aSourceURL : "") +
                               (aSourceLine ? ":" + aSourceLine : ""));
 
     // Create the containing node and append all its elements to it.
     let node = this.document.createElementNS(XUL_NS, "richlistitem");
 
     if (aBody instanceof Ci.nsIDOMNode) {
       bodyNode.appendChild(aBody);
     }
     else {
       let str = undefined;
       if (aLevel == "dir") {
-        str = WebConsoleUtils.objectActorGripToString(aBody.arguments[0]);
+        str = VariablesView.getString(aBody.arguments[0]);
       }
       else if (["log", "info", "warn", "error", "debug"].indexOf(aLevel) > -1 &&
                typeof aBody == "object") {
         this._makeConsoleLogMessageBody(node, bodyNode, aBody);
       }
       else {
         str = aBody;
       }
@@ -2163,64 +2092,50 @@ WebConsoleFrame.prototype = {
     timestampNode.classList.add("webconsole-timestamp");
     let timestamp = aTimeStamp || Date.now();
     let timestampString = l10n.timestampString(timestamp);
     timestampNode.setAttribute("value", timestampString);
 
     // Create the source location (e.g. www.example.com:6) that sits on the
     // right side of the message, if applicable.
     let locationNode;
-    if (aSourceURL) {
+    if (aSourceURL && IGNORED_SOURCE_URLS.indexOf(aSourceURL) == -1) {
       locationNode = this.createLocationNode(aSourceURL, aSourceLine);
     }
 
     node.clipboardText = aClipboardText;
     node.classList.add("hud-msg-node");
 
     node.timestamp = timestamp;
     this.setMessageType(node, aCategory, aSeverity);
 
     node.appendChild(timestampNode);
     node.appendChild(iconContainer);
-    // Display the object tree after the message node.
+
+    // Display the variables view after the message node.
     if (aLevel == "dir") {
-      // Make the body container, which is a vertical box, for grouping the text
-      // and tree widgets.
+      let viewContainer = this.document.createElement("hbox");
+      viewContainer.flex = 1;
+      viewContainer.height = this.outputNode.clientHeight *
+                             CONSOLE_DIR_VIEW_HEIGHT;
+
+      let options = {
+        objectActor: body.arguments[0],
+        targetElement: viewContainer,
+        hideFilterInput: true,
+      };
+      this.jsterm.openVariablesView(options)
+        .then((aView) => node._variablesView = aView);
+
       let bodyContainer = this.document.createElement("vbox");
       bodyContainer.flex = 1;
       bodyContainer.appendChild(bodyNode);
-      // Create the tree.
-      let tree = this.document.createElement("tree");
-      tree.setAttribute("hidecolumnpicker", "true");
-      tree.flex = 1;
-
-      let treecols = this.document.createElement("treecols");
-      let treecol = this.document.createElement("treecol");
-      treecol.setAttribute("primary", "true");
-      treecol.setAttribute("hideheader", "true");
-      treecol.setAttribute("ignoreincolumnpicker", "true");
-      treecol.flex = 1;
-      treecols.appendChild(treecol);
-      tree.appendChild(treecols);
-
-      tree.appendChild(this.document.createElement("treechildren"));
-
-      bodyContainer.appendChild(tree);
+      bodyContainer.appendChild(viewContainer);
       node.appendChild(bodyContainer);
       node.classList.add("webconsole-msg-inspector");
-      // Create the treeView object.
-      let treeView = node.propertyTreeView = new PropertyTreeView();
-
-      treeView.data = {
-        objectPropertiesProvider: this.objectPropertiesProvider.bind(this),
-        releaseObject: this._releaseObject.bind(this),
-        objectProperties: body.objectProperties,
-      };
-
-      tree.setAttribute("rows", treeView.rowCount);
     }
     else {
       node.appendChild(bodyNode);
     }
     node.appendChild(repeatContainer);
     if (locationNode) {
       node.appendChild(locationNode);
     }
@@ -2258,53 +2173,46 @@ WebConsoleFrame.prototype = {
       configurable: false
     });
 
     aBody.arguments.forEach(function(aItem) {
       if (aContainer.firstChild) {
         aContainer.appendChild(this.document.createTextNode(" "));
       }
 
-      let text = WebConsoleUtils.objectActorGripToString(aItem);
-
-      if (aItem && typeof aItem != "object" || !aItem.inspectable) {
+      let text = VariablesView.getString(aItem);
+      let inspectable = !VariablesView.isPrimitive({ value: aItem });
+
+      if (aItem && typeof aItem != "object" || !inspectable) {
         aContainer.appendChild(this.document.createTextNode(text));
 
-        let longString = null;
         if (aItem.type == "longString") {
-          longString = aItem;
-        }
-        else if (!aItem.inspectable &&
-                 typeof aItem.displayString == "object" &&
-                 aItem.displayString.type == "longString") {
-          longString = aItem.displayString;
-        }
-
-        if (longString) {
           let ellipsis = this.document.createElement("description");
           ellipsis.classList.add("hud-clickable");
           ellipsis.classList.add("longStringEllipsis");
           ellipsis.textContent = l10n.getStr("longStringEllipsis");
 
+          let formatter = function(s) '"' + s + '"';
+
           this._addMessageLinkCallback(ellipsis,
-            this._longStringClick.bind(this, aMessage, longString, null));
+            this._longStringClick.bind(this, aMessage, aItem, formatter));
 
           aContainer.appendChild(ellipsis);
         }
         return;
       }
 
       // For inspectable objects.
       let elem = this.document.createElement("description");
       elem.classList.add("hud-clickable");
       elem.setAttribute("aria-haspopup", "true");
       elem.appendChild(this.document.createTextNode(text));
 
       this._addMessageLinkCallback(elem,
-        this._consoleLogClick.bind(this, aMessage, elem, aItem));
+        this._consoleLogClick.bind(this, elem, aItem));
 
       aContainer.appendChild(elem);
     }, this);
   },
 
   /**
    * Click event handler for the ellipsis shown immediately after a long string.
    * This method retrieves the full string and updates the console output to
@@ -2677,28 +2585,76 @@ function JSTerm(aWebConsoleFrame)
 {
   this.hud = aWebConsoleFrame;
   this.hudId = this.hud.hudId;
 
   this.lastCompletion = { value: null };
   this.history = [];
   this.historyIndex = 0;
   this.historyPlaceHolder = 0;  // this.history.length;
+  this._objectActorsInVariablesViews = new Map();
+
   this._keyPress = this.keyPress.bind(this);
   this._inputEventHandler = this.inputEventHandler.bind(this);
+  this._fetchVarProperties = this._fetchVarProperties.bind(this);
+  this._fetchVarLongString = this._fetchVarLongString.bind(this);
+
+  EventEmitter.decorate(this);
 }
 
 JSTerm.prototype = {
+  SELECTED_FRAME: -1,
+
   /**
    * Stores the data for the last completion.
    * @type object
    */
   lastCompletion: null,
 
   /**
+   * The Web Console sidebar.
+   * @see this._createSidebar()
+   * @see Sidebar.jsm
+   */
+  sidebar: null,
+
+  /**
+   * The Web Console splitter between output and the sidebar.
+   * @private
+   * @type nsIDOMElement
+   */
+  _splitter: null,
+
+  /**
+   * The Variables View instance shown in the sidebar.
+   * @private
+   * @type object
+   */
+  _variablesView: null,
+
+  /**
+   * Tells if you want the variables view UI updates to be lazy or not. Tests
+   * disable lazy updates.
+   *
+   * @private
+   * @type boolean
+   */
+  _lazyVariablesView: true,
+
+  /**
+   * Holds a map between VariablesView instances and sets of ObjectActor IDs
+   * that have been retrieved from the server. This allows us to release the
+   * objects when needed.
+   *
+   * @private
+   * @type Map
+   */
+  _objectActorsInVariablesViews: null,
+
+  /**
    * Last input value.
    * @type string
    */
   lastInputValue: "",
 
   /**
    * History of code that was executed.
    * @type array
@@ -2725,17 +2681,17 @@ JSTerm.prototype = {
   COMPLETE_BACKWARD: 1,
   COMPLETE_HINT_ONLY: 2,
 
   /**
    * Initialize the JSTerminal UI.
    */
   init: function JST_init()
   {
-    let chromeDocument = this.hud.owner.chromeDocument;
+    let chromeDocument = this.hud.owner.chromeWindow.document;
     let autocompleteOptions = {
       onSelect: this.onAutocompleteSelect.bind(this),
       onClick: this.acceptProposedCompletion.bind(this),
       panelId: "webConsole_autocompletePopup",
       listBoxId: "webConsole_autocompletePopupListBox",
       position: "before_start",
       theme: "light",
       direction: "ltr",
@@ -2746,16 +2702,18 @@ JSTerm.prototype = {
 
     let doc = this.hud.document;
     this.completeNode = doc.querySelector(".jsterm-complete-node");
     this.inputNode = doc.querySelector(".jsterm-input-node");
     this.inputNode.addEventListener("keypress", this._keyPress, false);
     this.inputNode.addEventListener("input", this._inputEventHandler, false);
     this.inputNode.addEventListener("keyup", this._inputEventHandler, false);
 
+    this._splitter = doc.querySelector(".devtools-side-splitter");
+
     this.lastInputValue && this.setInputValue(this.lastInputValue);
   },
 
   /**
    * The JavaScript evaluation response handler.
    *
    * @private
    * @param nsIDOMElement [aAfterNode]
@@ -2768,33 +2726,47 @@ JSTerm.prototype = {
    *        The message received from the server.
    */
   _executeResultCallback:
   function JST__executeResultCallback(aAfterNode, aCallback, aResponse)
   {
     if (!this.hud) {
       return;
     }
-
-    let errorMessage = aResponse.errorMessage;
+    if (aResponse.error) {
+      Cu.reportError("Evaluation error " + aResponse.error + ": " +
+                     aResponse.message);
+      return;
+    }
+    let errorMessage = aResponse.exceptionMessage;
     let result = aResponse.result;
-    let inspectable = result && typeof result == "object" && result.inspectable;
+    let inspectable = false;
+    if (result && !VariablesView.isPrimitive({ value: result })) {
+      inspectable = true;
+    }
     let helperResult = aResponse.helperResult;
     let helperHasRawOutput = !!(helperResult || {}).rawOutput;
-    let resultString =
-      WebConsoleUtils.objectActorGripToString(result,
-                                              !helperHasRawOutput);
+    let resultString = VariablesView.getString(result);
 
     if (helperResult && helperResult.type) {
       switch (helperResult.type) {
         case "clearOutput":
           this.clearOutput();
           break;
         case "inspectObject":
-          this.handleInspectObject(helperResult.input, helperResult.object);
+          if (aAfterNode) {
+            if (!aAfterNode._objectActors) {
+              aAfterNode._objectActors = new Set();
+            }
+            aAfterNode._objectActors.add(helperResult.object.actor);
+          }
+          this.openVariablesView({
+            label: VariablesView.getString(helperResult.object),
+            objectActor: helperResult.object,
+          });
           break;
         case "error":
           try {
             errorMessage = l10n.getStr(helperResult.message);
           }
           catch (ex) {
             errorMessage = helperResult.message;
           }
@@ -2833,48 +2805,40 @@ JSTerm.prototype = {
                                 this._evalOutputClick.bind(this, aResponse),
                                 aAfterNode, aResponse.timestamp);
     }
     else {
       node = this.writeOutput(resultString, CATEGORY_OUTPUT, SEVERITY_LOG,
                               aAfterNode, aResponse.timestamp);
     }
 
-    if (result && typeof result == "object" && result.actor) {
-      node._objectActors = [result.actor];
-      if (typeof result.displayString == "object" &&
-          result.displayString.type == "longString") {
-        node._objectActors.push(result.displayString.actor);
-      }
-
-      // Add an ellipsis to expand the short string if the object is not
-      // inspectable.
-      let longString = null;
-      let formatter = null;
+    node._objectActors = new Set();
+
+    let error = aResponse.exception;
+    if (WebConsoleUtils.isActorGrip(error)) {
+      node._objectActors.add(error.actor);
+    }
+
+    if (WebConsoleUtils.isActorGrip(result)) {
+      node._objectActors.add(result.actor);
+
       if (result.type == "longString") {
-        longString = result;
-        if (!helperHasRawOutput) {
-          formatter = WebConsoleUtils.formatResultString.bind(WebConsoleUtils);
-        }
-      }
-      else if (!inspectable && !errorMessage &&
-               typeof result.displayString == "object" &&
-               result.displayString.type == "longString") {
-        longString = result.displayString;
-      }
-
-      if (longString) {
+        // Add an ellipsis to expand the short string if the object is not
+        // inspectable.
+
         let body = node.querySelector(".webconsole-msg-body");
         let ellipsis = this.hud.document.createElement("description");
         ellipsis.classList.add("hud-clickable");
         ellipsis.classList.add("longStringEllipsis");
         ellipsis.textContent = l10n.getStr("longStringEllipsis");
 
-        this.hud._addMessageLinkCallback(ellipsis,
-          this.hud._longStringClick.bind(this.hud, node, longString, formatter));
+        let formatter = function(s) '"' + s + '"';
+        let onclick = this.hud._longStringClick.bind(this.hud, node, result,
+                                                    formatter);
+        this.hud._addMessageLinkCallback(ellipsis, onclick);
 
         body.appendChild(ellipsis);
 
         node.clipboardText += " " + ellipsis.textContent;
       }
     }
   },
 
@@ -2895,81 +2859,627 @@ JSTerm.prototype = {
       this.writeOutput(l10n.getStr("executeEmptyInput"), CATEGORY_OUTPUT,
                        SEVERITY_LOG);
       return;
     }
 
     let node = this.writeOutput(aExecuteString, CATEGORY_INPUT, SEVERITY_LOG);
     let onResult = this._executeResultCallback.bind(this, node, aCallback);
 
-    this.webConsoleClient.evaluateJS(aExecuteString, onResult);
+    let options = { frame: this.SELECTED_FRAME };
+    this.requestEvaluation(aExecuteString, options).then(onResult, onResult);
 
     this.history.push(aExecuteString);
     this.historyIndex++;
     this.historyPlaceHolder = this.history.length;
     this.setInputValue("");
     this.clearCompletion();
   },
 
   /**
-   * Opens a new property panel that allows the inspection of the given object.
-   * The object information can be retrieved both async and sync, depending on
-   * the given options.
+   * Request a JavaScript string evaluation from the server.
+   *
+   * @param string aString
+   *        String to execute.
+   * @param object [aOptions]
+   *        Options for evaluation:
+   *        - bindObjectActor: tells the ObjectActor ID for which you want to do
+   *        the evaluation. The Debugger.Object of the OA will be bound to
+   *        |_self| during evaluation, such that it's usable in the string you
+   *        execute.
+   *        - frame: tells the stackframe depth to evaluate the string in. If
+   *        the jsdebugger is paused, you can pick the stackframe to be used for
+   *        evaluation. Use |this.SELECTED_FRAME| to always pick the
+   *        user-selected stackframe.
+   *        If you do not provide a |frame| the string will be evaluated in the
+   *        global content window.
+   * @return object
+   *         A Promise object that is resolved when the server response is
+   *         received.
+   */
+  requestEvaluation: function JST_requestEvaluation(aString, aOptions = {})
+  {
+    let deferred = Promise.defer();
+
+    function onResult(aResponse) {
+      if (!aResponse.error) {
+        deferred.resolve(aResponse);
+      }
+      else {
+        deferred.reject(aResponse);
+      }
+    }
+
+    let frameActor = null;
+    if ("frame" in aOptions) {
+      frameActor = this.getFrameActor(aOptions.frame);
+    }
+
+    let evalOptions = {
+      bindObjectActor: aOptions.bindObjectActor,
+      frameActor: frameActor,
+    };
+
+    this.webConsoleClient.evaluateJS(aString, onResult, evalOptions);
+    return deferred.promise;
+  },
+
+  /**
+   * Retrieve the FrameActor ID given a frame depth.
+   *
+   * @param number aFrame
+   *        Frame depth.
+   * @return string|null
+   *         The FrameActor ID for the given frame depth.
+   */
+  getFrameActor: function JST_getFrameActor(aFrame)
+  {
+    let state = this.hud.owner.getDebuggerFrames();
+    if (!state) {
+      return null;
+    }
+
+    let grip;
+    if (aFrame == this.SELECTED_FRAME) {
+      grip = state.frames[state.selected];
+    }
+    else {
+      grip = state.frames[aFrame];
+    }
+
+    return grip ? grip.actor : null;
+  },
+
+  /**
+   * Opens a new variables view that allows the inspection of the given object.
    *
    * @param object aOptions
-   *        Property panel options:
-   *        - title:
-   *        Panel title.
-   *        - anchor:
-   *        The DOM element you want the panel to be anchored to.
-   *        - updateButtonCallback:
-   *        An optional function you want invoked when the user clicks the
-   *        Update button. If this function is not provided the Update button is
-   *        not shown.
-   *        - data:
-   *        An object that represents the object you want to inspect. Please see
-   *        the PropertyPanel documentation - this object is passed to the
-   *        PropertyPanel constructor
+   *        Options for the variables view:
+   *        - objectActor: grip of the ObjectActor you want to show in the
+   *        variables view.
+   *        - rawObject: the raw object you want to show in the variables view.
+   *        - label: label to display in the variables view for inspected
+   *        object.
+   *        - hideFilterInput: optional boolean, |true| if you want to hide the
+   *        variables view filter input.
+   *        - targetElement: optional nsIDOMElement to append the variables view
+   *        to. An iframe element is used as a container for the view. If this
+   *        option is not used, then the variables view opens in the sidebar.
+   * @return object
+   *         A Promise object that is resolved when the variables view has
+   *         opened. The new variables view instance is given to the callbacks.
+   */
+  openVariablesView: function JST_openVariablesView(aOptions)
+  {
+    let onContainerReady = (aWindow) => {
+      let container = aWindow.document.querySelector("#variables");
+      let view = this._variablesView;
+      if (!view || aOptions.targetElement) {
+        let viewOptions = {
+          container: container,
+          hideFilterInput: aOptions.hideFilterInput,
+        };
+        view = this._createVariablesView(viewOptions);
+        if (!aOptions.targetElement) {
+          this._variablesView = view;
+        }
+      }
+      aOptions.view = view;
+      this._updateVariablesView(aOptions);
+      this.emit("variablesview-open", view, aOptions);
+      return view;
+    };
+
+    let promise;
+    if (aOptions.targetElement) {
+      let deferred = Promise.defer();
+      promise = deferred.promise;
+      let document = aOptions.targetElement.ownerDocument;
+      let iframe = document.createElement("iframe");
+
+      iframe.addEventListener("load", function onIframeLoad(aEvent) {
+        iframe.removeEventListener("load", onIframeLoad, true);
+        deferred.resolve(iframe.contentWindow);
+      }, true);
+
+      iframe.flex = 1;
+      iframe.setAttribute("src", VARIABLES_VIEW_URL);
+      aOptions.targetElement.appendChild(iframe);
+    }
+    else {
+      this._createSidebar();
+      promise = this._addVariablesViewSidebarTab();
+    }
+
+    return promise.then(onContainerReady);
+  },
+
+  /**
+   * Create the Web Console sidebar.
+   *
+   * @see Sidebar.jsm
+   * @private
+   */
+  _createSidebar: function JST__createSidebar()
+  {
+    if (!this.sidebar) {
+      let tabbox = this.hud.document.querySelector("#webconsole-sidebar");
+      this.sidebar = new ToolSidebar(tabbox, this);
+    }
+    this.sidebar.show();
+    this._splitter.setAttribute("state", "open");
+  },
+
+  /**
+   * Add the variables view tab to the sidebar.
+   *
+   * @private
+   * @return object
+   *         A Promise object for the adding of the new tab.
+   */
+  _addVariablesViewSidebarTab: function JST__addVariablesViewSidebarTab()
+  {
+    let deferred = Promise.defer();
+
+    let onTabReady = () => {
+      let window = this.sidebar.getWindowForTab("variablesview");
+      deferred.resolve(window);
+    };
+
+    let tab = this.sidebar.getTab("variablesview");
+    if (tab) {
+      if (this.sidebar.getCurrentTabID() == "variablesview") {
+        onTabReady();
+      }
+      else {
+        this.sidebar.once("variablesview-selected", onTabReady);
+        this.sidebar.select("variablesview");
+      }
+    }
+    else {
+      this.sidebar.once("variablesview-ready", onTabReady);
+      this.sidebar.addTab("variablesview", VARIABLES_VIEW_URL, true);
+    }
+
+    return deferred.promise;
+  },
+
+  /**
+   * Create a variables view instance.
+   *
+   * @private
+   * @param object aOptions
+   *        Options for the new Variables View instance:
+   *        - container: the DOM element where the variables view is inserted.
+   *        - hideFilterInput: boolean, if true the variables filter input is
+   *        hidden.
    * @return object
-   *         The new instance of PropertyPanel.
+   *         The new Variables View instance.
+   */
+  _createVariablesView: function JST__createVariablesView(aOptions)
+  {
+    let view = new VariablesView(aOptions.container);
+    view.searchPlaceholder = l10n.getStr("propertiesFilterPlaceholder");
+    view.emptyText = l10n.getStr("emptyPropertiesList");
+    view.searchEnabled = !aOptions.hideFilterInput;
+    view.lazyEmpty = this._lazyVariablesView;
+    view.lazyAppend = this._lazyVariablesView;
+    this._objectActorsInVariablesViews.set(view, new Set());
+    return view;
+  },
+
+  /**
+   * Update the variables view.
+   *
+   * @private
+   * @param object aOptions
+   *        Options for updating the variables view:
+   *        - view: the view you want to update.
+   *        - objectActor: the grip of the new ObjectActor you want to show in
+   *        the view.
+   *        - rawObject: the new raw object you want to show.
+   *        - label: the new label for the inspected object.
    */
-  openPropertyPanel: function JST_openPropertyPanel(aOptions)
+  _updateVariablesView: function JST__updateVariablesView(aOptions)
+  {
+    let view = aOptions.view;
+    view.createHierarchy();
+    view.empty();
+
+    let actors = this._objectActorsInVariablesViews.get(view);
+    for (let actor of actors) {
+      // We need to avoid pruning the object inspection starting point.
+      // That one is pruned when the console message is removed.
+      if (view._consoleLastObjectActor != actor) {
+        this.hud._releaseObject(actor);
+      }
+    }
+
+    actors.clear();
+
+    if (aOptions.objectActor) {
+      // Make sure eval works in the correct context.
+      view.eval = this._variablesViewEvaluate.bind(this, aOptions);
+      view.switch = this._variablesViewSwitch.bind(this, aOptions);
+      view.delete = this._variablesViewDelete.bind(this, aOptions);
+    }
+    else {
+      view.eval = null;
+      view.switch = null;
+      view.delete = null;
+    }
+
+    let scope = view.addScope(aOptions.label);
+    scope.expanded = true;
+    scope.locked = true;
+
+    let container = scope.addVar();
+    container.evaluationMacro = this._variablesViewSimpleValueEvalMacro;
+
+    if (aOptions.objectActor) {
+      this._fetchVarProperties(container, aOptions.objectActor);
+      view._consoleLastObjectActor = aOptions.objectActor.actor;
+    }
+    else if (aOptions.rawObject) {
+      container.populate(aOptions.rawObject);
+      view.commitHierarchy();
+      view._consoleLastObjectActor = null;
+    }
+    else {
+      throw new Error("Variables View cannot open without giving it an object " +
+                      "display.");
+    }
+
+    this.emit("variablesview-updated", view, aOptions);
+  },
+
+  /**
+   * The evaluation function used by the variables view when editing a property
+   * value.
+   *
+   * @private
+   * @param object aOptions
+   *        The options used for |this._updateVariablesView()|.
+   * @param string aString
+   *        The string that the variables view wants to evaluate.
+   */
+  _variablesViewEvaluate: function JST__variablesViewEvaluate(aOptions, aString)
+  {
+    let updater = this._updateVariablesView.bind(this, aOptions);
+    let onEval = this._silentEvalCallback.bind(this, updater);
+
+    let evalOptions = {
+      frame: this.SELECTED_FRAME,
+      bindObjectActor: aOptions.objectActor.actor,
+    };
+
+    this.requestEvaluation(aString, evalOptions).then(onEval, onEval);
+  },
+
+  /**
+   * Generates the string evaluated when performing simple value changes in the
+   * variables view.
+   *
+   * @private
+   * @param Variable | Property aItem
+   *        The current variable or property.
+   * @param string aCurrentString
+   *        The trimmed user inputted string.
+   * @return string
+   *         The string to be evaluated.
+   */
+  _variablesViewSimpleValueEvalMacro:
+  function JST__variablesViewSimpleValueEvalMacro(aItem, aCurrentString)
+  {
+    return "_self" + aItem.symbolicName + "=" + aCurrentString;
+  },
+
+
+  /**
+   * Generates the string evaluated when overriding getters and setters with
+   * plain values in the variables view.
+   *
+   * @private
+   * @param Property aItem
+   *        The current getter or setter property.
+   * @param string aCurrentString
+   *        The trimmed user inputted string.
+   * @return string
+   *         The string to be evaluated.
+   */
+  _variablesViewOverrideValueEvalMacro:
+  function JST__variablesViewOverrideValueEvalMacro(aItem, aCurrentString)
   {
-    // The property panel has one button:
-    //    `Update`: reexecutes the string executed on the command line. The
-    //    result will be inspected by this panel.
-    let buttons = [];
-
-    if (aOptions.updateButtonCallback) {
-      buttons.push({
-        label: l10n.getStr("update.button"),
-        accesskey: l10n.getStr("update.accesskey"),
-        oncommand: aOptions.updateButtonCallback,
-      });
+    let parent = aItem.ownerView;
+    let symbolicName = parent.symbolicName;
+    if (symbolicName.indexOf("_self") != 0) {
+      parent._symbolicName = "_self" + symbolicName;
+    }
+
+    let result = VariablesView.overrideValueEvalMacro.apply(this, arguments);
+
+    parent._symbolicName = symbolicName;
+
+    return result;
+  },
+
+  /**
+   * Generates the string evaluated when performing getters and setters changes
+   * in the variables view.
+   *
+   * @private
+   * @param Property aItem
+   *        The current getter or setter property.
+   * @param string aCurrentString
+   *        The trimmed user inputted string.
+   * @return string
+   *         The string to be evaluated.
+   */
+  _variablesViewGetterOrSetterEvalMacro:
+  function JST__variablesViewGetterOrSetterEvalMacro(aItem, aCurrentString)
+  {
+    let propertyObject = aItem.ownerView;
+    let parentObject = propertyObject.ownerView;
+    let parent = parentObject.symbolicName;
+    parentObject._symbolicName = "_self" + parent;
+
+    let result = VariablesView.getterOrSetterEvalMacro.apply(this, arguments);
+
+    parentObject._symbolicName = parent;
+
+    return result;
+  },
+
+  /**
+   * The property deletion function used by the variables view when a property
+   * is deleted.
+   *
+   * @private
+   * @param object aOptions
+   *        The options used for |this._updateVariablesView()|.
+   * @param object aVar
+   *        The Variable object instance for the deleted property.
+   */
+  _variablesViewDelete: function JST__variablesViewDelete(aOptions, aVar)
+  {
+    let onEval = this._silentEvalCallback.bind(this, null);
+
+    let evalOptions = {
+      frame: this.SELECTED_FRAME,
+      bindObjectActor: aOptions.objectActor.actor,
+    };
+
+    this.requestEvaluation("delete _self" + aVar.symbolicName, evalOptions)
+        .then(onEval, onEval);
+  },
+
+  /**
+   * The property rename function used by the variables view when a property
+   * is renamed.
+   *
+   * @private
+   * @param object aOptions
+   *        The options used for |this._updateVariablesView()|.
+   * @param object aVar
+   *        The Variable object instance for the renamed property.
+   * @param string aNewName
+   *        The new name for the property.
+   */
+  _variablesViewSwitch:
+  function JST__variablesViewSwitch(aOptions, aVar, aNewName)
+  {
+    let updater = this._updateVariablesView.bind(this, aOptions);
+    let onEval = this._silentEvalCallback.bind(this, updater);
+
+    let evalOptions = {
+      frame: this.SELECTED_FRAME,
+      bindObjectActor: aOptions.objectActor.actor,
+    };
+
+    let newSymbolicName = aVar.ownerView.symbolicName + '["' + aNewName + '"]';
+    if (newSymbolicName == aVar.symbolicName) {
+      return;
+    }
+
+    let code = "_self" + newSymbolicName + " = _self" + aVar.symbolicName + ";" +
+               "delete _self" + aVar.symbolicName;
+
+    this.requestEvaluation(code, evalOptions).then(onEval, onEval);
+  },
+
+  /**
+   * A noop callback for JavaScript evaluation. This method releases any
+   * result ObjectActors that come from the server for evaluation requests. This
+   * is used for editing, renaming and deleting properties in the variables
+   * view.
+   *
+   * Exceptions are displayed in the output.
+   *
+   * @private
+   * @param function aCallback
+   *        Function to invoke once the response is received.
+   * @param object aResponse
+   *        The response packet received from the server.
+   */
+  _silentEvalCallback: function JST__silentEvalCallback(aCallback, aResponse)
+  {
+    if (aResponse.error) {
+      Cu.reportError("Web Console evaluation failed. " + aResponse.error + ":" +
+                     aResponse.message);
+
+      aCallback && aCallback(aResponse);
+      return;
+    }
+
+    let exception = aResponse.exception;
+    if (exception) {
+      let node = this.writeOutput(aResponse.exceptionMessage,
+                                  CATEGORY_OUTPUT, SEVERITY_ERROR,
+                                  null, aResponse.timestamp);
+      node._objectActors = new Set();
+      if (WebConsoleUtils.isActorGrip(exception)) {
+        node._objectActors.add(exception.actor);
+      }
     }
 
-    let parent = this.hud.popupset;
-    let title = aOptions.title ?
-                l10n.getFormatStr("jsPropertyInspectTitle", [aOptions.title]) :
-                l10n.getStr("jsPropertyTitle");
-
-    let propPanel = new PropertyPanel(parent, title, aOptions.data, buttons);
-
-    propPanel.panel.openPopup(aOptions.anchor, "after_pointer", 0, 0, false, false);
-    propPanel.panel.sizeTo(350, 450);
-
-    if (aOptions.anchor) {
-      propPanel.panel.addEventListener("popuphiding", function onPopupHide() {
-        propPanel.panel.removeEventListener("popuphiding", onPopupHide, false);
-        aOptions.anchor._panelOpen = false;
-      }, false);
-      aOptions.anchor._panelOpen = true;
+    let helper = aResponse.helperResult || { type: null };
+    let helperGrip = null;
+    if (helper.type == "inspectObject") {
+      helperGrip = helper.object;
+    }
+
+    let grips = [aResponse.result, helperGrip];
+    for (let grip of grips) {
+      if (WebConsoleUtils.isActorGrip(grip)) {
+        this.hud._releaseObject(grip.actor);
+      }
+    }
+
+    aCallback && aCallback(aResponse);
+  },
+
+  /**
+   * Adds properties to a variable in the view. Triggered when a variable is
+   * expanded. It does not expand the variable.
+   *
+   * @param object aVar
+   *        The VariablseView Variable instance where the properties get added.
+   * @param object [aGrip]
+   *        Optional, the object actor grip of the variable. If the grip is not
+   *        provided, then the aVar.value is used as the object actor grip.
+   */
+  _fetchVarProperties: function JST__fetchVarProperties(aVar, aGrip)
+  {
+    // Retrieve the properties only once.
+    if (aVar._fetched) {
+      return;
+    }
+    aVar._fetched = true;
+
+    let grip = aGrip || aVar.value;
+    if (!grip) {
+      throw new Error("No object actor grip was given for the variable.");
+    }
+
+    let view = aVar._variablesView;
+    let actors = this._objectActorsInVariablesViews.get(view);
+
+    function addActorForDescriptor(aGrip) {
+      if (WebConsoleUtils.isActorGrip(aGrip)) {
+        actors.add(aGrip.actor);
+      }
     }
 
-    return propPanel;
+    let onNewProperty = (aProperty) => {
+      if (aProperty.getter || aProperty.setter) {
+        aProperty.evaluationMacro = this._variablesViewOverrideValueEvalMacro;
+        let getter = aProperty.get("get");
+        let setter = aProperty.get("set");
+        if (getter) {
+          getter.evaluationMacro = this._variablesViewGetterOrSetterEvalMacro;
+        }
+        if (setter) {
+          setter.evaluationMacro = this._variablesViewGetterOrSetterEvalMacro;
+        }
+      }
+      else {
+        aProperty.evaluationMacro = this._variablesViewSimpleValueEvalMacro;
+      }
+
+      let grips = [aProperty.value, aProperty.gettter, aProperty.settter];
+      grips.forEach(addActorForDescriptor);
+
+      let inspectable = !VariablesView.isPrimitive({ value: aProperty.value });
+      let longString = WebConsoleUtils.isActorGrip(aProperty.value) &&
+                       aProperty.value.type == "longString";
+      if (inspectable) {
+        aProperty.onexpand = this._fetchVarProperties;
+      }
+      else if (longString) {
+        aProperty.onexpand = this._fetchVarLongString;
+        aProperty.showArrow();
+      }
+    };
+
+    let client = new GripClient(this.hud.proxy.client, grip);
+    client.getPrototypeAndProperties((aResponse) => {
+      let { ownProperties, prototype } = aResponse;
+      let sortable = VariablesView.NON_SORTABLE_CLASSES.indexOf(grip.class) == -1;
+
+      // Add all the variable properties.
+      if (ownProperties) {
+        aVar.addProperties(ownProperties, {
+          sorted: sortable,
+          callback: onNewProperty,
+        });
+      }
+
+      // Add the variable's __proto__.
+      if (prototype && prototype.type != "null") {
+        let proto = aVar.addProperty("__proto__", { value: prototype });
+        onNewProperty(proto);
+      }
+
+      aVar._retrieved = true;
+      view.commitHierarchy();
+      this.emit("variablesview-fetched", aVar);
+    });
+  },
+
+  /**
+   * Fetch the full string for a given variable that displays a long string.
+   *
+   * @param object aVar
+   *        The VariablesView Variable instance where the properties get added.
+   */
+  _fetchVarLongString: function JST__fetchVarLongString(aVar)
+  {
+    if (aVar._fetched) {
+      return;
+    }
+    aVar._fetched = true;
+
+    let grip = aVar.value;
+    if (!grip) {
+      throw new Error("No long string actor grip was given for the variable.");
+    }
+
+    let client = this.webConsoleClient.longString(grip);
+    client.substring(grip.initial.length, grip.length, (aResponse) => {
+      if (aResponse.error) {
+        Cu.reportError("JST__fetchVarLongString substring failure: " +
+                       aResponse.error + ": " + aResponse.message);
+        return;
+      }
+
+      aVar.onexpand = null;
+      aVar.setGrip(grip.initial + aResponse.substring);
+      aVar.hideArrow();
+      aVar._retrieved = true;
+    });
   },
 
   /**
    * Writes a JS object to the JSTerm outputNode.
    *
    * @param string aOutputMessage
    *        The message to display.
    * @param function [aCallback]
@@ -3593,178 +4103,56 @@ JSTerm.prototype = {
   updateCompleteNode: function JSTF_updateCompleteNode(aSuffix)
   {
     // completion prefix = input, with non-control chars replaced by spaces
     let prefix = aSuffix ? this.inputNode.value.replace(/[\S]/g, " ") : "";
     this.completeNode.value = prefix + aSuffix;
   },
 
   /**
-   * The JSTerm InspectObject remote message handler. This allows the remote
-   * process to open the Property Panel for a given object.
-   *
-   * @param object aRequest
-   *        The request message from the content process. This message includes
-   *        the user input string that was evaluated to inspect an object and
-   *        the result object which is to be inspected.
-   */
-  handleInspectObject: function JST_handleInspectObject(aInput, aActor)
-  {
-    let options = {
-      title: aInput,
-
-      data: {
-        objectPropertiesProvider: this.hud.objectPropertiesProvider.bind(this.hud),
-        releaseObject: this.hud._releaseObject.bind(this.hud),
-      },
-    };
-
-    let propPanel;
-
-    let onPopupHide = function JST__onPopupHide() {
-      propPanel.panel.removeEventListener("popuphiding", onPopupHide, false);
-      this.hud._releaseObject(aActor.actor);
-    }.bind(this);
-
-    this.hud.objectPropertiesProvider(aActor.actor,
-      function _onObjectProperties(aProperties) {
-        options.data.objectProperties = aProperties;
-        propPanel = this.openPropertyPanel(options);
-        propPanel.panel.setAttribute("hudId", this.hudId);
-        propPanel.panel.addEventListener("popuphiding", onPopupHide, false);
-      }.bind(this));
-  },
-
-  /**
    * The click event handler for evaluation results in the output.
    *
    * @private
    * @param object aResponse
    *        The JavaScript evaluation response received from the server.
-   * @param nsIDOMNode aLink
-   *        The message node for which we are handling events.
    */
-  _evalOutputClick: function JST__evalOutputClick(aResponse, aLinkNode)
+  _evalOutputClick: function JST__evalOutputClick(aResponse)
   {
-    if (aLinkNode._panelOpen) {
-      return;
-    }
-
-    let options = {
-      title: aResponse.input,
-      anchor: aLinkNode,
-
-      // Data to inspect.
-      data: {
-        objectPropertiesProvider: this.hud.objectPropertiesProvider.bind(this.hud),
-        releaseObject: this.hud._releaseObject.bind(this.hud),
-      },
-    };
-
-    let propPanel;
-
-    options.updateButtonCallback = function JST__evalUpdateButton() {
-      let onResult =
-        this._evalOutputUpdatePanelCallback.bind(this, options, propPanel,
-                                                 aResponse);
-      this.webConsoleClient.evaluateJS(aResponse.input, onResult);
-    }.bind(this);
-
-    let onPopupHide = function JST__evalInspectPopupHide() {
-      propPanel.panel.removeEventListener("popuphiding", onPopupHide, false);
-
-      if (!aLinkNode.parentNode && aLinkNode._objectActors) {
-        aLinkNode._objectActors.forEach(this.hud._releaseObject, this.hud);
-        aLinkNode._objectActors = null;
-      }
-    }.bind(this);
-
-    this.hud.objectPropertiesProvider(aResponse.result.actor,
-      function _onObjectProperties(aProperties) {
-        options.data.objectProperties = aProperties;
-        propPanel = this.openPropertyPanel(options);
-        propPanel.panel.setAttribute("hudId", this.hudId);
-        propPanel.panel.addEventListener("popuphiding", onPopupHide, false);
-      }.bind(this));
-  },
-
-  /**
-   * The callback used for updating the Property Panel when the user clicks the
-   * Update button.
-   *
-   * @private
-   * @param object aOptions
-   *        The options object used for opening the initial Property Panel.
-   * @param object aPropPanel
-   *        The Property Panel instance.
-   * @param object aOldResponse
-   *        The previous JSTerm:EvalResult message received from the content
-   *        process.
-   * @param object aNewResponse
-   *        The new JSTerm:EvalResult message received after the user clicked
-   *        the Update button.
-   */
-  _evalOutputUpdatePanelCallback:
-  function JST__updatePanelCallback(aOptions, aPropPanel, aOldResponse,
-                                    aNewResponse)
-  {
-    if (aNewResponse.errorMessage) {
-      this.writeOutput(aNewResponse.errorMessage, CATEGORY_OUTPUT,
-                       SEVERITY_ERROR);
-      return;
-    }
-
-    let result = aNewResponse.result;
-    let inspectable = result && typeof result == "object" && result.inspectable;
-    let newActor = result && typeof result == "object" ? result.actor : null;
-
-    let anchor = aOptions.anchor;
-    if (anchor && newActor) {
-      if (!anchor._objectActors) {
-        anchor._objectActors = [];
-      }
-      if (anchor._objectActors.indexOf(newActor) == -1) {
-        anchor._objectActors.push(newActor);
-      }
-    }
-
-    if (!inspectable) {
-      this.writeOutput(l10n.getStr("JSTerm.updateNotInspectable"), CATEGORY_OUTPUT, SEVERITY_ERROR);
-      return;
-    }
-
-    // Update the old response object such that when the panel is reopen, the
-    // user sees the new response.
-    aOldResponse.result = aNewResponse.result;
-    aOldResponse.error = aNewResponse.error;
-    aOldResponse.errorMessage = aNewResponse.errorMessage;
-    aOldResponse.timestamp = aNewResponse.timestamp;
-
-    this.hud.objectPropertiesProvider(newActor,
-      function _onObjectProperties(aProperties) {
-        aOptions.data.objectProperties = aProperties;
-        // TODO: This updates the value of the tree.
-        // However, the states of open nodes is not saved.
-        // See bug 586246.
-        aPropPanel.treeView.data = aOptions.data;
-      }.bind(this));
+    this.openVariablesView({
+      label: VariablesView.getString(aResponse.result),
+      objectActor: aResponse.result,
+    });
   },
 
   /**
    * Destroy the JSTerm object. Call this method to avoid memory leaks.
    */
   destroy: function JST_destroy()
   {
+    if (this._variablesView) {
+      let actors = this._objectActorsInVariablesViews.get(this._variablesView);
+      for (let actor of actors) {
+        this.hud._releaseObject(actor);
+      }
+      actors.clear();
+      this._variablesView = null;
+    }
+
+    if (this.sidebar) {
+      this.sidebar.destroy();
+      this.sidebar = null;
+    }
+
     this.clearCompletion();
     this.clearOutput();
 
     this.autocompletePopup.destroy();
     this.autocompletePopup = null;
 
-    let popup = this.hud.owner.chromeDocument
+    let popup = this.hud.owner.chromeWindow.document
                 .getElementById("webConsole_autocompletePopup");
     if (popup) {
       popup.parentNode.removeChild(popup);
     }
 
     this.inputNode.removeEventListener("keypress", this._keyPress, false);
     this.inputNode.removeEventListener("input", this._inputEventHandler, false);
     this.inputNode.removeEventListener("keyup", this._inputEventHandler, false);
@@ -3932,16 +4320,18 @@ CommandController.prototype = {
         let selectedItem = this.owner.outputNode.selectedItem;
         return selectedItem && selectedItem.url;
       }
       case "cmd_fontSizeEnlarge":
       case "cmd_fontSizeReduce":
       case "cmd_fontSizeReset":
       case "cmd_selectAll":
         return true;
+      case "cmd_close":
+        return this.owner.owner._browserConsole;
     }
   },
 
   doCommand: function CommandController_doCommand(aCommand)
   {
     switch (aCommand) {
       case "cmd_copy":
         this.copy();
@@ -3959,16 +4349,19 @@ CommandController.prototype = {
         this.owner.changeFontSize("+");
         break;
       case "cmd_fontSizeReduce":
         this.owner.changeFontSize("-");
         break;
       case "cmd_fontSizeReset":
         this.owner.changeFontSize("");
         break;
+      case "cmd_close":
+        this.owner.window.close();
+        break;
     }
   }
 };
 
 ///////////////////////////////////////////////////////////////////////////////
 // Web Console connection proxy
 ///////////////////////////////////////////////////////////////////////////////
 
--- a/browser/devtools/webconsole/webconsole.xul
+++ b/browser/devtools/webconsole/webconsole.xul
@@ -8,18 +8,23 @@
 ]>
 <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/devtools/common.css"
                  type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/devtools/webconsole.css"
                  type="text/css"?>
 <?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        id="devtools-webconsole"
+        macanimationtype="document"
+        fullscreenbutton="true"
         title="&window.title;"
+        browserConsoleTitle="&browserConsole.title;"
         windowtype="devtools:webconsole"
+        width="900" height="350"
         persist="screenX screenY width height sizemode">
   <script type="text/javascript" src="chrome://global/content/globalOverlay.js"/>
   <script type="text/javascript" src="webconsole.js"/>
 
   <commandset id="editMenuCommands"/>
 
   <commandset id="consoleCommands"
               commandupdater="true"
@@ -27,25 +32,27 @@
               oncommandupdate="goUpdateConsoleCommands();">
     <command id="consoleCmd_openURL"
              oncommand="goDoCommand('consoleCmd_openURL');"/>
     <command id="consoleCmd_copyURL"
              oncommand="goDoCommand('consoleCmd_copyURL');"/>
     <command id="cmd_fullZoomEnlarge" oncommand="goDoCommand('cmd_fontSizeEnlarge');"/>
     <command id="cmd_fullZoomReduce" oncommand="goDoCommand('cmd_fontSizeReduce');"/>
     <command id="cmd_fullZoomReset" oncommand="goDoCommand('cmd_fontSizeReset');"/>
+    <command id="cmd_close" oncommand="goDoCommand('cmd_close');"/>
   </commandset>
-  <keyset id="fontSizeChangeSet">
+  <keyset id="consoleKeys">
     <key id="key_fullZoomReduce"  key="&fullZoomReduceCmd.commandkey;" command="cmd_fullZoomReduce"  modifiers="accel"/>
     <key key="&fullZoomReduceCmd.commandkey2;"  command="cmd_fullZoomReduce" modifiers="accel"/>
     <key id="key_fullZoomEnlarge" key="&fullZoomEnlargeCmd.commandkey;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
     <key key="&fullZoomEnlargeCmd.commandkey2;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
     <key key="&fullZoomEnlargeCmd.commandkey3;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
     <key id="key_fullZoomReset" key="&fullZoomResetCmd.commandkey;" command="cmd_fullZoomReset" modifiers="accel"/>
     <key key="&fullZoomResetCmd.commandkey2;" command="cmd_fullZoomReset" modifiers="accel"/>
+    <key key="&closeCmd.key;" command="cmd_close" modifiers="accel"/>
   </keyset>
   <keyset id="editMenuKeys"/>
 
   <popupset id="mainPopupSet">
     <menupopup id="output-contextmenu"
                onpopupshowing="ConsoleContextMenu.build(event);">
       <menuitem id="saveBodiesContextMenu" type="checkbox" label="&saveBodies.label;"
                 accesskey="&saveBodies.accesskey;"/>
@@ -55,17 +62,17 @@
       <menuitem id="menu_copyURL" label="&copyURLCmd.label;"
                 accesskey="&copyURLCmd.accesskey;" command="consoleCmd_copyURL"
                 selection="network" selectionType="single"/>
       <menuitem id="menu_copy"/>
       <menuitem id="menu_selectAll"/>
     </menupopup>
   </popupset>
 
-  <vbox class="hud-outer-wrapper" flex="1">
+  <hbox class="hud-outer-wrapper" flex="1">
     <vbox class="hud-console-wrapper" flex="1">
       <toolbar class="hud-console-filter-toolbar devtools-toolbar" mode="full">
         <toolbarbutton label="&btnPageNet.label;" type="menu-button"
                        category="net" class="devtools-toolbarbutton webconsole-filter-button"
                        tooltiptext="&btnPageNet.tooltip;">
           <menupopup>
             <menuitem label="&btnConsoleErrors;" type="checkbox" autocheck="false"
                       prefKey="network"/>
@@ -109,25 +116,34 @@
             <menuitem label="&btnConsoleLog;" type="checkbox" autocheck="false"
                       prefKey="log"/>
           </menupopup>
         </toolbarbutton>
 
         <spacer flex="1"/>
 
         <textbox class="compact hud-filter-box devtools-searchinput" type="search"
-                 placeholder="&filterBox.placeholder;"/>
+                 placeholder="&filterOutput.placeholder;"/>
         <toolbarbutton class="webconsole-clear-console-button devtools-toolbarbutton"
                        label="&btnClear.label;" tooltiptext="&btnClear.tooltip;"/>
       </toolbar>
+
       <richlistbox class="hud-output-node" orient="vertical" flex="1"
                    seltype="multiple" context="output-contextmenu"
                    style="direction:ltr;"/>
+
       <hbox class="jsterm-input-container" style="direction:ltr">
         <stack class="jsterm-stack-node" flex="1">
           <textbox class="jsterm-complete-node" multiline="true" rows="1"
                    tabindex="-1"/>
           <textbox class="jsterm-input-node" multiline="true" rows="1"/>
         </stack>
       </hbox>
     </vbox>
-  </vbox>
+
+    <splitter class="devtools-side-splitter" collapse="after" state="collapsed" />
+
+    <tabbox id="webconsole-sidebar" class="devtools-sidebar-tabs" hidden="true" width="300">
+      <tabs/>
+      <tabpanels flex="1"/>
+    </tabbox>
+  </hbox>
 </window>
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -202,16 +202,18 @@ These should match what Safari and other
 <!ENTITY devtoolsConnect.accesskey    "e">
 
 <!ENTITY errorConsoleCmd.label        "Error Console">
 <!ENTITY errorConsoleCmd.accesskey    "C">
 <!ENTITY errorConsoleCmd.commandkey   "j">
 
 <!ENTITY remoteWebConsoleCmd.label    "Remote Web Console">
 
+<!ENTITY browserConsoleCmd.label      "Browser Console">
+
 <!ENTITY inspectContextMenu.label     "Inspect Element">
 <!ENTITY inspectContextMenu.accesskey "Q">
 
 <!ENTITY responsiveDesignTool.label   "Responsive Design View">
 <!ENTITY responsiveDesignTool.accesskey "R">
 <!ENTITY responsiveDesignTool.commandkey "M">
 
 <!-- LOCALIZATION NOTE (scratchpad.label): This menu item label appears
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/devtools/VariablesView.dtd
@@ -0,0 +1,12 @@
+<!-- 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/. -->
+
+<!-- LOCALIZATION NOTE : FILE The correct localization of this file might be to
+  - keep it in English, or another language commonly spoken among web developers.
+  - You want to make that choice consistent across the developer tools.
+  - A good criteria is the language in which you'd find the best
+  - documentation on web development on the web. -->
+
+<!ENTITY PropertiesViewWindowTitle "Properties">
+
--- a/browser/locales/en-US/chrome/browser/devtools/debugger.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/debugger.properties
@@ -5,25 +5,16 @@
 # LOCALIZATION NOTE These strings are used inside the Debugger
 # which is available from the Web Developer sub-menu -> 'Debugger'.
 # The correct localization of this file might be to keep it in
 # English, or another language commonly spoken among web developers.
 # You want to make that choice consistent across the developer tools.
 # A good criteria is the language in which you'd find the best
 # documentation on web development on the web.
 
-# LOCALIZATION NOTE (confirmTabSwitch): The messages displayed for all the
-# title and buttons on the notification shown when a user attempts to open a
-# debugger in a new tab while a different tab is already being debugged.
-confirmTabSwitch.message=Debugger is already open in another tab. Continuing will close the other instance.
-confirmTabSwitch.buttonSwitch=Switch to debugged tab
-confirmTabSwitch.buttonSwitch.accessKey=S
-confirmTabSwitch.buttonOpen=Open anyway
-confirmTabSwitch.buttonOpen.accessKey=O
-
 # LOCALIZATION NOTE (open.commandkey): The key used to open the debugger in
 # combination to e.g. ctrl + shift
 open.commandkey=S
 
 # LOCALIZATION NOTE (debuggerMenu.accesskey): The access key used to open the
 # debugger.
 debuggerMenu.accesskey=D
 
@@ -210,8 +201,15 @@ variablesSeparatorLabel=:
 # LOCALIZATION NOTE (watchExpressionsSeparatorLabel): The text that is displayed
 # in the watch expressions list as a separator between the code and evaluation.
 watchExpressionsSeparatorLabel=\ →
 
 # LOCALIZATION NOTE (functionSearchSeparatorLabel): The text that is displayed
 # in the functions search panel as a separator between function's inferred name
 # and its real name (if available).
 functionSearchSeparatorLabel=←
+
+# LOCALIZATION NOTE (resumptionOrderPanelTitle): This is the text that appears
+# as a description in the notification panel popup, when multiple debuggers are
+# open in separate tabs and the user tries to resume them in the wrong order.
+# The substitution parameter is the URL of the last paused window that must be
+# resumed first.
+resumptionOrderPanelTitle=There are one or more paused debuggers. Please resume the most-recently paused debugger first at: %S
--- a/browser/locales/en-US/chrome/browser/devtools/netmonitor.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/netmonitor.properties
@@ -85,14 +85,14 @@ requestCookies=Request cookies
 # in the network details params tab identifying the response cookies.
 responseCookies=Response cookies
 
 # LOCALIZATION NOTE (jsonFilterText): This is the text displayed
 # in the response tab of the network details pane for the JSON filtering input.
 jsonFilterText=Filter properties
 
 # LOCALIZATION NOTE (networkMenu.size): This is the label displayed
-# in the network menu specifying the size of a request (in kb).
-networkMenu.size=%Skb
+# in the network menu specifying the size of a request (in kilobytes).
+networkMenu.sizeKB=%S KB
 
 # LOCALIZATION NOTE (networkMenu.total): This is the label displayed
-# in the network menu specifying the time for a request to finish (in ms).
-networkMenu.total=→ %Sms
+# in the network menu specifying the time for a request to finish (in milliseconds).
+networkMenu.totalMS=→ %S ms
--- a/browser/locales/en-US/chrome/browser/devtools/webConsole.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/webConsole.dtd
@@ -4,16 +4,17 @@
 
 <!-- LOCALIZATION NOTE : FILE The correct localization of this file might be to
   - keep it in English, or another language commonly spoken among web developers.
   - You want to make that choice consistent across the developer tools.
   - A good criteria is the language in which you'd find the best
   - documentation on web development on the web. -->
 
 <!ENTITY window.title "Web Console">
+<!ENTITY browserConsole.title "Browser Console">
 
 <!ENTITY networkPanel.requestURLColon             "Request URL:">
 <!ENTITY networkPanel.requestMethodColon          "Request Method:">
 <!ENTITY networkPanel.statusCodeColon             "Status Code:">
 
 <!ENTITY networkPanel.requestHeaders              "Request Headers">
 <!ENTITY networkPanel.requestCookie               "Sent Cookie">
 <!ENTITY networkPanel.requestBody                 "Request Body">
@@ -55,24 +56,26 @@
   -  console.error(). -->
 <!ENTITY btnPageLogging.label   "Logging">
 <!ENTITY btnPageLogging.tooltip "Log messages sent to the window.console object">
 <!ENTITY btnConsoleErrors       "Errors">
 <!ENTITY btnConsoleInfo         "Info">
 <!ENTITY btnConsoleWarnings     "Warnings">
 <!ENTITY btnConsoleLog          "Log">
 
-<!ENTITY filterBox.placeholder "Filter">
+<!ENTITY filterOutput.placeholder "Filter output">
 <!ENTITY btnClear.label        "Clear">
 <!ENTITY btnClear.tooltip      "Clear the Web Console output">
 
 <!ENTITY fullZoomEnlargeCmd.commandkey  "+">
 <!ENTITY fullZoomEnlargeCmd.commandkey2 "="> <!-- + is above this key on many keyboards -->
 <!ENTITY fullZoomEnlargeCmd.commandkey3 "">
 
 <!ENTITY fullZoomReduceCmd.commandkey   "-">
 <!ENTITY fullZoomReduceCmd.commandkey2  "">
 
 <!ENTITY fullZoomResetCmd.commandkey    "0">
 <!ENTITY fullZoomResetCmd.commandkey2   "">
 
 <!ENTITY copyURLCmd.label     "Copy Link Location">
 <!ENTITY copyURLCmd.accesskey "a">
+
+<!ENTITY closeCmd.key  "W">
--- a/browser/locales/en-US/chrome/browser/devtools/webconsole.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/webconsole.properties
@@ -192,8 +192,17 @@ NetworkPanel.fetchRemainingResponseConte
 # LOCALIZATION NOTE (NetworkPanel.fetchRemainingRequestContentLink): This is
 # displayed in the network panel when the request body is only partially
 # available.
 NetworkPanel.fetchRemainingRequestContentLink=Fetch the request body (%1$S bytes)
 
 # LOCALIZATION NOTE (connectionTimeout): Message displayed when the Remote Web
 # Console fails to connect to the server due to a timeout.
 connectionTimeout=Connection timeout. Check the Error Console on both ends for potential error messages. Reopen the Web Console to try again.
+
+# LOCALIZATION NOTE (propertiesFilterPlaceholder): This is the text that
+# appears in the filter text box for the properties view container.
+propertiesFilterPlaceholder=Filter properties
+
+# LOCALIZATION NOTE (emptyPropertiesList): The text that is displayed in the
+# properties pane when there are no properties to display.
+emptyPropertiesList=No properties to display
+
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -33,16 +33,17 @@
     locale/browser/devtools/tilt.properties           (%chrome/browser/devtools/tilt.properties)
     locale/browser/devtools/scratchpad.properties     (%chrome/browser/devtools/scratchpad.properties)
     locale/browser/devtools/scratchpad.dtd            (%chrome/browser/devtools/scratchpad.dtd)
     locale/browser/devtools/styleeditor.properties    (%chrome/browser/devtools/styleeditor.properties)
     locale/browser/devtools/styleeditor.dtd           (%chrome/browser/devtools/styleeditor.dtd)
     locale/browser/devtools/styleinspector.properties (%chrome/browser/devtools/styleinspector.properties)
     locale/browser/devtools/styleinspector.dtd        (%chrome/browser/devtools/styleinspector.dtd)
     locale/browser/devtools/webConsole.dtd            (%chrome/browser/devtools/webConsole.dtd)
+    locale/browser/devtools/VariablesView.dtd         (%chrome/browser/devtools/VariablesView.dtd)
     locale/browser/devtools/sourceeditor.properties   (%chrome/browser/devtools/sourceeditor.properties)
     locale/browser/devtools/sourceeditor.dtd          (%chrome/browser/devtools/sourceeditor.dtd)
     locale/browser/devtools/profiler.dtd              (%chrome/browser/devtools/profiler.dtd)
     locale/browser/devtools/profiler.properties       (%chrome/browser/devtools/profiler.properties)
     locale/browser/devtools/layoutview.dtd            (%chrome/browser/devtools/layoutview.dtd)
     locale/browser/devtools/responsiveUI.properties   (%chrome/browser/devtools/responsiveUI.properties)
     locale/browser/devtools/toolbox.dtd            (%chrome/browser/devtools/toolbox.dtd)
     locale/browser/devtools/toolbox.properties     (%chrome/browser/devtools/toolbox.properties)
--- a/browser/themes/linux/devtools/debugger.css
+++ b/browser/themes/linux/devtools/debugger.css
@@ -244,16 +244,24 @@
 
 .dbg-results-line-contents-string[match=true][focused] {
   transition-duration: 0.1s;
   transform: scale(1.75, 1.75);
 }
 
 /* Toolbar Controls */
 
+#resumption-panel-desc {
+  width: 200px;
+}
+
+#resumption-order-panel {
+  -moz-margin-start: -8px;
+}
+
 #resume {
   list-style-image: url("chrome://browser/skin/devtools/debugger-play.png");
   -moz-image-region: rect(0px,16px,16px,0px);
 }
 
 #resume[checked] {
   -moz-image-region: rect(0px,32px,16px,16px);
 }
--- a/browser/themes/linux/devtools/netmonitor.css
+++ b/browser/themes/linux/devtools/netmonitor.css
@@ -130,20 +130,20 @@
       transparent 75%);
 }
 
 .requests-menu-timings {
   transform-origin: left center;
 }
 
 .requests-menu-timings-total {
-  -moz-padding-start: 4px;
+  -moz-padding-start: 8px;
   font-size: 85%;
   font-weight: 600;
-  transform-origin: -4px center; /* negative cap size */
+  transform-origin: left center;
 }
 
 .requests-menu-timings-cap {
   width: 4px;
   height: 10px;
   border: 1px solid #fff;
 }
 
--- a/browser/themes/linux/devtools/webconsole.css
+++ b/browser/themes/linux/devtools/webconsole.css
@@ -234,8 +234,23 @@
 :-moz-any(.jsterm-input-node,
           .jsterm-complete-node) > .textbox-input-box > .textbox-textarea {
   overflow-x: hidden;
 }
 
 .jsterm-complete-node > .textbox-input-box > .textbox-textarea {
   color: GrayText;
 }
+
+.webconsole-msg-inspector iframe {
+  height: 7em;
+  margin-bottom: 15px;
+}
+
+.devtools-side-splitter {
+  background: #666;
+  width: 2px;
+}
+
+#webconsole-sidebar > tabs {
+  height: 0;
+  overflow: hidden;
+}
--- a/browser/themes/osx/devtools/debugger.css
+++ b/browser/themes/osx/devtools/debugger.css
@@ -246,16 +246,24 @@
 
 .dbg-results-line-contents-string[match=true][focused] {
   transition-duration: 0.1s;
   transform: scale(1.75, 1.75);
 }
 
 /* Toolbar Controls */
 
+#resumption-panel-desc {
+  width: 200px;
+}
+
+#resumption-order-panel {
+  -moz-margin-start: -8px;
+}
+
 #resume {
   list-style-image: url("chrome://browser/skin/devtools/debugger-play.png");
   -moz-image-region: rect(0px,16px,16px,0px);
 }
 
 #resume[checked] {
   -moz-image-region: rect(0px,32px,16px,16px);
 }
--- a/browser/themes/osx/devtools/netmonitor.css
+++ b/browser/themes/osx/devtools/netmonitor.css
@@ -130,20 +130,20 @@
       transparent 75%);
 }
 
 .requests-menu-timings {
   transform-origin: left center;
 }
 
 .requests-menu-timings-total {
-  -moz-padding-start: 4px;
+  -moz-padding-start: 8px;
   font-size: 85%;
   font-weight: 600;
-  transform-origin: -4px center; /* negative cap size */
+  transform-origin: left center;
 }
 
 .requests-menu-timings-cap {
   width: 4px;
   height: 10px;
   border: 1px solid #fff;
 }
 
--- a/browser/themes/osx/devtools/webconsole.css
+++ b/browser/themes/osx/devtools/webconsole.css
@@ -238,8 +238,23 @@
 :-moz-any(.jsterm-input-node,
           .jsterm-complete-node) > .textbox-input-box > .textbox-textarea {
   overflow-x: hidden;
 }
 
 .jsterm-complete-node > .textbox-input-box > .textbox-textarea {
   color: GrayText;
 }
+
+.webconsole-msg-inspector iframe {
+  height: 7em;
+  margin-bottom: 15px;
+}
+
+.devtools-side-splitter {
+  background: #666;
+  width: 2px;
+}
+
+#webconsole-sidebar > tabs {
+  height: 0;
+  overflow: hidden;
+}
--- a/browser/themes/windows/devtools/debugger.css
+++ b/browser/themes/windows/devtools/debugger.css
@@ -244,16 +244,24 @@
 
 .dbg-results-line-contents-string[match=true][focused] {
   transition-duration: 0.1s;
   transform: scale(1.75, 1.75);
 }
 
 /* Toolbar Controls */
 
+#resumption-panel-desc {
+  width: 200px;
+}
+
+#resumption-order-panel {
+  -moz-margin-start: -8px;
+}
+
 #resume {
   list-style-image: url("chrome://browser/skin/devtools/debugger-play.png");
   -moz-image-region: rect(0px,16px,16px,0px);
 }
 
 #resume[checked] {
   -moz-image-region: rect(0px,32px,16px,16px);
 }
--- a/browser/themes/windows/devtools/netmonitor.css
+++ b/browser/themes/windows/devtools/netmonitor.css
@@ -130,20 +130,20 @@
       transparent 75%);
 }
 
 .requests-menu-timings {
   transform-origin: left center;
 }
 
 .requests-menu-timings-total {
-  -moz-padding-start: 4px;
+  -moz-padding-start: 8px;
   font-size: 85%;
   font-weight: 600;
-  transform-origin: -4px center; /* negative cap size */
+  transform-origin: left center;
 }
 
 .requests-menu-timings-cap {
   width: 4px;
   height: 10px;
   border: 1px solid #fff;
 }
 
--- a/browser/themes/windows/devtools/webconsole.css
+++ b/browser/themes/windows/devtools/webconsole.css
@@ -242,8 +242,23 @@
 /*
  * This hardcoded width likely due to a toolkit Windows specific bug.
  * See http://hg.mozilla.org/mozilla-central/annotate/f38d6df93cad/toolkit/themes/winstripe/global/textbox-aero.css#l7
  */
 
 .hud-filter-box {
   width: 200px;
 }
+
+.webconsole-msg-inspector iframe {
+  height: 7em;
+  margin-bottom: 15px;
+}
+
+.devtools-side-splitter {
+  background: #666;
+  width: 2px;
+}
+
+#webconsole-sidebar > tabs {
+  height: 0;
+  overflow: hidden;
+}
--- a/dom/tests/browser/browser_ConsoleAPITests.js
+++ b/dom/tests/browser/browser_ConsoleAPITests.js
@@ -321,17 +321,18 @@ function testConsoleTimeEnd(aMessageObje
   ok(aMessageObject.arguments, "we have arguments");
 
   is(aMessageObject.filename, gArgs[0].filename, "filename matches");
   is(aMessageObject.lineNumber, gArgs[0].lineNumber, "lineNumber matches");
   is(aMessageObject.functionName, gArgs[0].functionName, "functionName matches");
   is(aMessageObject.arguments.length, gArgs[0].arguments.length, "arguments.length matches");
   is(aMessageObject.timer.name, gArgs[0].timer.name, "timer name matches");
   is(typeof aMessageObject.timer.duration, "number", "timer duration is a number");
-  ok(aMessageObject.timer.duration > 0, "timer duration is positive");
+  info("timer duration: " + aMessageObject.timer.duration);
+  ok(aMessageObject.timer.duration >= 0, "timer duration is positive");
 
   gArgs[0].arguments.forEach(function (a, i) {
     is(aMessageObject.arguments[i], a, "correct arg " + i);
   });
 
   startEmptyTimerTest();
 }
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Object-unsafeDereference-01.js
@@ -0,0 +1,10 @@
+// Debugger.Object.prototype.unsafeDereference returns the referent directly.
+
+var g = newGlobal();
+var dbg = new Debugger();
+var gw = dbg.addDebuggee(g);
+
+assertEq(gw.getOwnPropertyDescriptor('Math').value.unsafeDereference(), g.Math);
+
+g.eval('var obj = {}');
+assertEq(gw.getOwnPropertyDescriptor('obj').value.unsafeDereference(), g.obj);
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -4774,16 +4774,24 @@ DebuggerObject_unwrap(JSContext *cx, uns
     }
 
     args.rval().setObject(*unwrapped);
     if (!dbg->wrapDebuggeeValue(cx, args.rval()))
         return false;
     return true;
 }
 
+static JSBool
+DebuggerObject_unsafeDereference(JSContext *cx, unsigned argc, Value *vp)
+{
+    THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, "unsafeDereference", args, referent);
+    args.rval().setObject(*referent);
+    return cx->compartment->wrap(cx, args.rval());
+}
+
 static JSPropertySpec DebuggerObject_properties[] = {
     JS_PSG("proto", DebuggerObject_getProto, 0),
     JS_PSG("class", DebuggerObject_getClass, 0),
     JS_PSG("callable", DebuggerObject_getCallable, 0),
     JS_PSG("name", DebuggerObject_getName, 0),
     JS_PSG("displayName", DebuggerObject_getDisplayName, 0),
     JS_PSG("parameterNames", DebuggerObject_getParameterNames, 0),
     JS_PSG("script", DebuggerObject_getScript, 0),
@@ -4805,16 +4813,17 @@ static JSFunctionSpec DebuggerObject_met
     JS_FN("isFrozen", DebuggerObject_isFrozen, 0, 0),
     JS_FN("isExtensible", DebuggerObject_isExtensible, 0, 0),
     JS_FN("apply", DebuggerObject_apply, 0, 0),
     JS_FN("call", DebuggerObject_call, 0, 0),
     JS_FN("makeDebuggeeValue", DebuggerObject_makeDebuggeeValue, 1, 0),
     JS_FN("evalInGlobal", DebuggerObject_evalInGlobal, 1, 0),
     JS_FN("evalInGlobalWithBindings", DebuggerObject_evalInGlobalWithBindings, 2, 0),
     JS_FN("unwrap", DebuggerObject_unwrap, 0, 0),
+    JS_FN("unsafeDereference", DebuggerObject_unsafeDereference, 0, 0),
     JS_FS_END
 };
 
 
 /*** Debugger.Environment ************************************************************************/
 
 static void
 DebuggerEnv_trace(JSTracer *trc, RawObject obj)
--- a/toolkit/devtools/debugger/dbg-client.jsm
+++ b/toolkit/devtools/debugger/dbg-client.jsm
@@ -8,17 +8,18 @@
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 this.EXPORTED_SYMBOLS = ["DebuggerTransport",
                          "DebuggerClient",
                          "debuggerSocketConnect",
-                         "LongStringClient"];
+                         "LongStringClient",
+                         "GripClient"];
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
 const { defer, resolve, reject } = Promise;
 
 XPCOMUtils.defineLazyServiceGetter(this, "socketTransportService",
@@ -168,17 +169,16 @@ const ThreadStateTypes = {
 /**
  * Set of protocol messages that are sent by the server without a prior request
  * by the client.
  */
 const UnsolicitedNotifications = {
   "consoleAPICall": "consoleAPICall",
   "eventNotification": "eventNotification",
   "fileActivity": "fileActivity",
-  "locationChange": "locationChange",
   "networkEvent": "networkEvent",
   "networkEventUpdate": "networkEventUpdate",
   "newGlobal": "newGlobal",
   "newScript": "newScript",
   "newSource": "newSource",
   "tabDetached": "tabDetached",
   "tabNavigated": "tabNavigated",
   "pageError": "pageError",
@@ -509,17 +509,17 @@ DebuggerClient.prototype = {
           this.notify(aPacket.type, aPacket);
         }
 
         if (onResponse) {
           onResponse(aPacket);
         }
       } catch(ex) {
         dumpn("Error handling response: " + ex + " - stack:\n" + ex.stack);
-        Cu.reportError(ex.message + "\n" + ex.stack);
+        Cu.reportError(ex + "\n" + ex.stack);
       }
 
       this._sendRequests();
     }.bind(this));
   },
 
   /**
    * Called by DebuggerTransport when the underlying stream is closed.
--- a/toolkit/devtools/debugger/nsIJSInspector.idl
+++ b/toolkit/devtools/debugger/nsIJSInspector.idl
@@ -3,32 +3,39 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 /**
  * Utilities for integrating the JSInspector object into an XPCOM
  * application.
  */
-[scriptable, uuid(dbf84113-506a-4fd3-9183-a0348c6fa9cc)]
+[scriptable, uuid(6758d0d7-e96a-4c5c-bca8-3bcbe5a15943)]
 interface nsIJSInspector : nsISupports
 {
   /**
    * Process the thread's event queue until exit.
    *
+   * @param requestor A token the requestor passes to identify the pause.
+   *
    * @return depth Returns the number of times the event loop
    *         has been nested using this API.
    */
-  unsigned long enterNestedEventLoop();
+  unsigned long enterNestedEventLoop(in jsval requestor);
 
   /**
    * Exits the current nested event loop.
    *
    * @return depth The number of nested event loops left after
    *         exiting the event loop.
    *
    * @throws NS_ERROR_FAILURE if there are no nested event loops
    *         running.
    */
   unsigned long exitNestedEventLoop();
 
   readonly attribute unsigned long eventLoopNestLevel;
+
+  /**
+   * The token provided by the actor that last requested a nested event loop.
+   */
+  readonly attribute jsval lastNestRequestor;
 };
--- a/toolkit/devtools/debugger/nsJSInspector.cpp
+++ b/toolkit/devtools/debugger/nsJSInspector.cpp
@@ -8,46 +8,53 @@
 #include "nsIJSContextStack.h"
 #include "nsThreadUtils.h"
 #include "jsapi.h"
 #include "jsfriendapi.h"
 #include "jsdbgapi.h"
 #include "mozilla/ModuleUtils.h"
 #include "nsServiceManagerUtils.h"
 #include "nsMemory.h"
+#include "nsArray.h"
+#include "nsTArray.h"
 
 #define JSINSPECTOR_CONTRACTID \
   "@mozilla.org/jsinspector;1"
 
 #define JSINSPECTOR_CID \
 { 0xec5aa99c, 0x7abb, 0x4142, { 0xac, 0x5f, 0xaa, 0xb2, 0x41, 0x9e, 0x38, 0xe2 } }
 
 namespace mozilla {
 namespace jsinspector {
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsJSInspector)
 
 NS_IMPL_ISUPPORTS1(nsJSInspector, nsIJSInspector)
 
-nsJSInspector::nsJSInspector() : mNestedLoopLevel(0)
+nsJSInspector::nsJSInspector() : mNestedLoopLevel(0), mRequestors(1), mLastRequestor(JSVAL_NULL)
 {
+  nsTArray<JS::Value> mRequestors;
 }
 
 nsJSInspector::~nsJSInspector()
 {
+  mRequestors.Clear();
 }
 
 NS_IMETHODIMP
-nsJSInspector::EnterNestedEventLoop(uint32_t *out)
+nsJSInspector::EnterNestedEventLoop(const JS::Value& requestor, uint32_t *out)
 {
   nsresult rv;
   nsCOMPtr<nsIJSContextStack> stack =
     do_GetService("@mozilla.org/js/xpc/ContextStack;1", &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  mLastRequestor = requestor;
+  mRequestors.AppendElement(requestor);
+
   uint32_t nestLevel = ++mNestedLoopLevel;
   if (NS_SUCCEEDED(stack->Push(nullptr))) {
     while (NS_SUCCEEDED(rv) && mNestedLoopLevel >= nestLevel) {
       if (!NS_ProcessNextEvent())
         rv = NS_ERROR_UNEXPECTED;
     }
 
     JSContext *cx;
@@ -55,44 +62,56 @@ nsJSInspector::EnterNestedEventLoop(uint
     NS_ASSERTION(cx == nullptr, "JSContextStack mismatch");
   } else {
     rv = NS_ERROR_FAILURE;
   }
 
   NS_ASSERTION(mNestedLoopLevel <= nestLevel,
                "nested event didn't unwind properly");
 
-  if (mNestedLoopLevel == nestLevel)
-    --mNestedLoopLevel;
+  if (mNestedLoopLevel == nestLevel) {
+    mLastRequestor = mRequestors.ElementAt(--mNestedLoopLevel);
+  }
 
   *out = mNestedLoopLevel;
   return rv;
 }
 
 NS_IMETHODIMP
 nsJSInspector::ExitNestedEventLoop(uint32_t *out)
 {
   if (mNestedLoopLevel > 0) {
-    --mNestedLoopLevel;
+    mRequestors.RemoveElementAt(--mNestedLoopLevel);
+    if (mNestedLoopLevel > 0)
+      mLastRequestor = mRequestors.ElementAt(mNestedLoopLevel - 1);
+    else
+      mLastRequestor = JSVAL_NULL;
   } else {
     return NS_ERROR_FAILURE;
   }
 
   *out = mNestedLoopLevel;
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsJSInspector::GetEventLoopNestLevel(uint32_t *out)
 {
   *out = mNestedLoopLevel;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsJSInspector::GetLastNestRequestor(JS::Value *out)
+{
+  *out = mLastRequestor;
+  return NS_OK;
+}
+
 }
 }
 
 NS_DEFINE_NAMED_CID(JSINSPECTOR_CID);
 
 static const mozilla::Module::CIDEntry kJSInspectorCIDs[] = {
   { &kJSINSPECTOR_CID, false, NULL, mozilla::jsinspector::nsJSInspectorConstructor },
   { NULL }
--- a/toolkit/devtools/debugger/nsJSInspector.h
+++ b/toolkit/devtools/debugger/nsJSInspector.h
@@ -3,30 +3,34 @@
  * 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/. */
 
 #ifndef COMPONENTS_JSINSPECTOR_H
 #define COMPONENTS_JSINSPECTOR_H
 
 #include "nsIJSInspector.h"
 #include "mozilla/Attributes.h"
+#include "nsTArray.h"
+#include "js/Value.h"
 
 namespace mozilla {
 namespace jsinspector {
 
 class nsJSInspector MOZ_FINAL : public nsIJSInspector
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIJSINSPECTOR
 
   nsJSInspector();
 
 private:
   ~nsJSInspector();
 
   uint32_t mNestedLoopLevel;
+  nsTArray<JS::Value> mRequestors;
+  JS::Value mLastRequestor;
 };
 
 }
 }
 
 #endif
--- a/toolkit/devtools/debugger/server/dbg-script-actors.js
+++ b/toolkit/devtools/debugger/server/dbg-script-actors.js
@@ -150,24 +150,24 @@ ThreadActor.prototype = {
      */
     onNewGlobal: function TA_onNewGlobal(aGlobal) {
       // Content debugging only cares about new globals in the contant window,
       // like iframe children.
       if (aGlobal.hostAnnotations &&
           aGlobal.hostAnnotations.type == "document" &&
           aGlobal.hostAnnotations.element === this.global) {
         this.addDebuggee(aGlobal);
+        // Notify the client.
+        this.conn.send({
+          from: this.actorID,
+          type: "newGlobal",
+          // TODO: after bug 801084 lands see if we need to JSONify this.
+          hostAnnotations: aGlobal.hostAnnotations
+        });
       }
-      // Notify the client.
-      this.conn.send({
-        from: this.actorID,
-        type: "newGlobal",
-        // TODO: after bug 801084 lands see if we need to JSONify this.
-        hostAnnotations: aGlobal.hostAnnotations
-      });
     }
   },
 
   disconnect: function TA_disconnect() {
     if (this._state == "paused") {
       this.onResume();
     }
 
@@ -263,16 +263,28 @@ ThreadActor.prototype = {
       return undefined;
     }
   },
 
   /**
    * Handle a protocol request to resume execution of the debuggee.
    */
   onResume: function TA_onResume(aRequest) {
+    // In case of multiple nested event loops (due to multiple debuggers open in
+    // different tabs or multiple debugger clients connected to the same tab)
+    // only allow resumption in a LIFO order.
+    if (DebuggerServer.xpcInspector.eventLoopNestLevel > 1) {
+      let lastNestRequestor = DebuggerServer.xpcInspector.lastNestRequestor;
+      if (lastNestRequestor.connection != this.conn) {
+        return { error: "wrongOrder",
+                 message: "trying to resume in the wrong order.",
+                 lastPausedUrl: lastNestRequestor.url };
+      }
+    }
+
     if (aRequest && aRequest.forceCompletion) {
       // TODO: remove this when Debugger.Frame.prototype.pop is implemented in
       // bug 736733.
       if (typeof this.frame.pop != "function") {
         return { error: "notImplemented",
                  message: "forced completion is not yet implemented." };
       }
 
@@ -723,17 +735,20 @@ ThreadActor.prototype = {
     return packet;
   },
 
   _nest: function TA_nest() {
     if (this._hooks.preNest) {
       var nestData = this._hooks.preNest();
     }
 
-    DebuggerServer.xpcInspector.enterNestedEventLoop();
+    let requestor = Object.create(null);
+    requestor.url = this._hooks.url;
+    requestor.connection = this.conn;
+    DebuggerServer.xpcInspector.enterNestedEventLoop(requestor);
 
     dbg_assert(this.state === "running");
 
     if (this._hooks.postNest) {
       this._hooks.postNest(nestData)
     }
 
     // "continue" resumption value.
@@ -895,17 +910,17 @@ ThreadActor.prototype = {
     }
 
     if (aPool.objectActors.has(aValue)) {
       return aPool.objectActors.get(aValue).grip();
     } else if (this.threadLifetimePool.objectActors.has(aValue)) {
       return this.threadLifetimePool.objectActors.get(aValue).grip();
     }
 
-    let actor = new ObjectActor(aValue, this);
+    let actor = new PauseScopedObjectActor(aValue, this);
     aPool.addActor(actor);
     aPool.objectActors.set(aValue, actor);
     return actor.grip();
   },
 
   /**
    * Create a grip for the given debuggee object with a pause lifetime.
    *
@@ -1510,20 +1525,17 @@ SourceActor.prototype.requestTypes = {
  *        The parent thread actor for this object.
  */
 function ObjectActor(aObj, aThreadActor)
 {
   this.obj = aObj;
   this.threadActor = aThreadActor;
 }
 
-ObjectActor.prototype = Object.create(PauseScopedActor.prototype);
-
-update(ObjectActor.prototype, {
-  constructor: ObjectActor,
+ObjectActor.prototype = {
   actorPrefix: "obj",
 
   /**
    * Returns a grip for this actor for returning in a protocol message.
    */
   grip: function OA_grip() {
     let g = { "type": "object",
               "class": this.obj.class,
@@ -1547,47 +1559,47 @@ update(ObjectActor.prototype, {
 
     return g;
   },
 
   /**
    * Releases this actor from the pool.
    */
   release: function OA_release() {
-    this.registeredPool.objectActors.delete(this.obj);
+    if (this.registeredPool.objectActors) {
+      this.registeredPool.objectActors.delete(this.obj);
+    }
     this.registeredPool.removeActor(this);
     this.disconnect();
   },
 
   disconnect: function OA_disconnect() {
     this.threadActor._removeFromProtoChain(this.obj);
   },
 
   /**
    * Handle a protocol request to provide the names of the properties defined on
    * the object and not its prototype.
    *
    * @param aRequest object
    *        The protocol request object.
    */
-  onOwnPropertyNames:
-  PauseScopedActor.withPaused(function OA_onOwnPropertyNames(aRequest) {
+  onOwnPropertyNames: function OA_onOwnPropertyNames(aRequest) {
     return { from: this.actorID,
              ownPropertyNames: this.obj.getOwnPropertyNames() };
-  }),
+  },
 
   /**
    * Handle a protocol request to provide the prototype and own properties of
    * the object.
    *
    * @param aRequest object
    *        The protocol request object.
    */
-  onPrototypeAndProperties:
-  PauseScopedActor.withPaused(function OA_onPrototypeAndProperties(aRequest) {
+  onPrototypeAndProperties: function OA_onPrototypeAndProperties(aRequest) {
     if (this.obj.proto) {
       // Store the object and its prototype to the prototype chain cache, so that
       // we can evaluate native getter methods for WebIDL attributes that are
       // meant to be called on the instace and not on the prototype.
       //
       // TODO: after bug 801084, we could restrict the cache to objects where
       // this.obj.hostAnnotations.isWebIDLObject == true
       let chain = this.threadActor._findProtoChain(this.obj);
@@ -1603,45 +1615,45 @@ update(ObjectActor.prototype, {
 
     let ownProperties = {};
     for (let name of this.obj.getOwnPropertyNames()) {
       ownProperties[name] = this._propertyDescriptor(name);
     }
     return { from: this.actorID,
              prototype: this.threadActor.createValueGrip(this.obj.proto),
              ownProperties: ownProperties };
-  }),
+  },
 
   /**
    * Handle a protocol request to provide the prototype of the object.
    *
    * @param aRequest object
    *        The protocol request object.
    */
-  onPrototype: PauseScopedActor.withPaused(function OA_onPrototype(aRequest) {
+  onPrototype: function OA_onPrototype(aRequest) {
     return { from: this.actorID,
              prototype: this.threadActor.createValueGrip(this.obj.proto) };
-  }),
+  },
 
   /**
    * Handle a protocol request to provide the property descriptor of the
    * object's specified property.
    *
    * @param aRequest object
    *        The protocol request object.
    */
-  onProperty: PauseScopedActor.withPaused(function OA_onProperty(aRequest) {
+  onProperty: function OA_onProperty(aRequest) {
     if (!aRequest.name) {
       return { error: "missingParameter",
                message: "no property name was specified" };
     }
 
     return { from: this.actorID,
              descriptor: this._propertyDescriptor(aRequest.name) };
-  }),
+  },
 
   /**
    * A helper method that creates a property descriptor for the provided object,
    * properly formatted for sending in a protocol response.
    *
    * @param string aName
    *        The property that the descriptor is generated for.
    */
@@ -1719,26 +1731,94 @@ update(ObjectActor.prototype, {
   },
 
   /**
    * Handle a protocol request to provide the source code of a function.
    *
    * @param aRequest object
    *        The protocol request object.
    */
-  onDecompile: PauseScopedActor.withPaused(function OA_onDecompile(aRequest) {
+  onDecompile: function OA_onDecompile(aRequest) {
     if (this.obj.class !== "Function") {
       return { error: "objectNotFunction",
                message: "decompile request is only valid for object grips " +
                         "with a 'Function' class." };
     }
 
     return { from: this.actorID,
              decompiledCode: this.obj.decompile(!!aRequest.pretty) };
-  }),
+  },
+
+  /**
+   * Handle a protocol request to provide the parameters of a function.
+   *
+   * @param aRequest object
+   *        The protocol request object.
+   */
+  onParameterNames: function OA_onParameterNames(aRequest) {
+    if (this.obj.class !== "Function") {
+      return { error: "objectNotFunction",
+               message: "'parameterNames' request is only valid for object " +
+                        "grips with a 'Function' class." };
+    }
+
+    return { parameterNames: this.obj.parameterNames };
+  },
+
+  /**
+   * Handle a protocol request to release a thread-lifetime grip.
+   *
+   * @param aRequest object
+   *        The protocol request object.
+   */
+  onRelease: function OA_onRelease(aRequest) {
+    this.release();
+    return {};
+  },
+};
+
+ObjectActor.prototype.requestTypes = {
+  "parameterNames": ObjectActor.prototype.onParameterNames,
+  "prototypeAndProperties": ObjectActor.prototype.onPrototypeAndProperties,
+  "prototype": ObjectActor.prototype.onPrototype,
+  "property": ObjectActor.prototype.onProperty,
+  "ownPropertyNames": ObjectActor.prototype.onOwnPropertyNames,
+  "decompile": ObjectActor.prototype.onDecompile,
+  "release": ObjectActor.prototype.onRelease,
+};
+
+
+/**
+ * Creates a pause-scoped  actor for the specified object.
+ * @see ObjectActor
+ */
+function PauseScopedObjectActor()
+{
+  ObjectActor.apply(this, arguments);
+}
+
+PauseScopedObjectActor.prototype = Object.create(PauseScopedActor.prototype);
+
+update(PauseScopedObjectActor.prototype, ObjectActor.prototype);
+
+update(PauseScopedObjectActor.prototype, {
+  constructor: PauseScopedObjectActor,
+
+  onOwnPropertyNames:
+    PauseScopedActor.withPaused(ObjectActor.prototype.onOwnPropertyNames),
+
+  onPrototypeAndProperties:
+    PauseScopedActor.withPaused(ObjectActor.prototype.onPrototypeAndProperties),
+
+  onPrototype: PauseScopedActor.withPaused(ObjectActor.prototype.onPrototype),
+  onProperty: PauseScopedActor.withPaused(ObjectActor.prototype.onProperty),
+  onDecompile: PauseScopedActor.withPaused(ObjectActor.prototype.onDecompile),
+
+  onParameterNames:
+    PauseScopedActor.withPaused(ObjectActor.prototype.onParameterNames),
 
   /**
    * Handle a protocol request to provide the lexical scope of a function.
    *
    * @param aRequest object
    *        The protocol request object.
    */
   onScope: PauseScopedActor.withPaused(function OA_onScope(aRequest) {
@@ -1754,32 +1834,16 @@ update(ObjectActor.prototype, {
       return { error: "notDebuggee",
                message: "cannot access the environment of this function." };
     }
 
     return { from: this.actorID, scope: envActor.form() };
   }),
 
   /**
-   * Handle a protocol request to provide the parameters of a function.
-   *
-   * @param aRequest object
-   *        The protocol request object.
-   */
-  onParameterNames: PauseScopedActor.withPaused(function OA_onParameterNames(aRequest) {
-    if (this.obj.class !== "Function") {
-      return { error: "objectNotFunction",
-               message: "'parameterNames' request is only valid for object " +
-                        "grips with a 'Function' class." };
-    }
-
-    return { parameterNames: this.obj.parameterNames };
-  }),
-
-  /**
    * Handle a protocol request to promote a pause-lifetime grip to a
    * thread-lifetime grip.
    *
    * @param aRequest object
    *        The protocol request object.
    */
   onThreadGrip: PauseScopedActor.withPaused(function OA_onThreadGrip(aRequest) {
     this.threadActor.threadObjectGrip(this);
@@ -1798,27 +1862,20 @@ update(ObjectActor.prototype, {
                message: "Only thread-lifetime actors can be released." };
     }
 
     this.release();
     return {};
   }),
 });
 
-ObjectActor.prototype.requestTypes = {
-  "parameterNames": ObjectActor.prototype.onParameterNames,
-  "prototypeAndProperties": ObjectActor.prototype.onPrototypeAndProperties,
-  "prototype": ObjectActor.prototype.onPrototype,
-  "property": ObjectActor.prototype.onProperty,
-  "ownPropertyNames": ObjectActor.prototype.onOwnPropertyNames,
-  "scope": ObjectActor.prototype.onScope,
-  "decompile": ObjectActor.prototype.onDecompile,
-  "threadGrip": ObjectActor.prototype.onThreadGrip,
-  "release": ObjectActor.prototype.onRelease,
-};
+update(PauseScopedObjectActor.prototype.requestTypes, {
+  "scope": PauseScopedObjectActor.prototype.onScope,
+  "threadGrip": PauseScopedObjectActor.prototype.onThreadGrip,
+});
 
 
 /**
  * Creates an actor for the specied "very long" string. "Very long" is specified
  * at the server's discretion.
  *
  * @param aString String
  *        The string.
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/debugger/tests/mochitest/Makefile.in
@@ -0,0 +1,19 @@
+#
+# 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/.
+
+DEPTH		= @DEPTH@
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+relativesrcdir	= @relativesrcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MOCHITEST_CHROME_FILES	= \
+	test_unsafeDereference.html \
+	nonchrome_unsafeDereference.html \
+	$(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/debugger/tests/mochitest/moz.build
@@ -0,0 +1,5 @@
+# vim: set filetype=python:
+# 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/.
+
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/debugger/tests/mochitest/nonchrome_unsafeDereference.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<html>
+<script>
+var xhr = new XMLHttpRequest;
+xhr.timeout = 1742;
+xhr.expando = 'Expando!';
+</script>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/debugger/tests/mochitest/test_unsafeDereference.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=837723
+
+When we use Debugger.Object.prototype.unsafeDereference to get a non-D.O
+reference to a content object in chrome, that reference should be via an
+xray wrapper.
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Mozilla Bug 837723</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+
+Components.utils.import("resource://gre/modules/jsdebugger.jsm");
+addDebuggerToGlobal(this);
+
+window.onload = function () {
+  SimpleTest.waitForExplicitFinish();
+
+  var iframe = document.createElement("iframe");
+  iframe.src = "http://mochi.test:8888/chrome/toolkit/devtools/debugger/tests/mochitest/nonchrome_unsafeDereference.html";
+
+  iframe.onload = function () {
+    var dbg = new Debugger;
+    var contentDO = dbg.addDebuggee(iframe.contentWindow);
+    var xhrDesc = contentDO.getOwnPropertyDescriptor('xhr');
+
+    isnot(xhrDesc, undefined, "xhr should be visible as property of content global");
+    isnot(xhrDesc.value, undefined, "xhr should have a value");
+
+    var xhr = xhrDesc.value.unsafeDereference();
+
+    is(typeof xhr, "object", "we should be able to deference xhr's value's D.O");
+    is(xhr.timeout, 1742, "chrome should see the xhr's 'timeout' property");
+    is(xhr.expando, undefined, "chrome should not see the xhr's 'expando' property");
+
+    SimpleTest.finish();
+  }
+
+  document.body.appendChild(iframe);
+}
+
+</script>
+</pre>
+</body>
+</html>
--- a/toolkit/devtools/debugger/tests/moz.build
+++ b/toolkit/devtools/debugger/tests/moz.build
@@ -1,8 +1,9 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
+DIRS += ['mochitest']
+
 MODULE = 'test_debugger'
-
--- a/toolkit/devtools/debugger/tests/unit/test_nsjsinspector.js
+++ b/toolkit/devtools/debugger/tests/unit/test_nsjsinspector.js
@@ -1,22 +1,59 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+// Test the basic functionality of the nsIJSInspector component.
+var gCount = 0;
+const MAX = 10;
+var inspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector);
+var tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
+
+// Emulate 10 simultaneously-debugged windows from 3 separate client connections.
+var requestor = (count) => ({
+  url:"http://foo/bar/" + count,
+  connection: "conn" + (count % 3)
+});
+
 function run_test()
 {
   test_nesting();
 }
 
 function test_nesting()
 {
-  let inspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector);
   do_check_eq(inspector.eventLoopNestLevel, 0);
 
-  let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
-  tm.currentThread.dispatch({ run: function() {
-    do_check_eq(inspector.eventLoopNestLevel, 1);
-    do_check_eq(inspector.exitNestedEventLoop(), 0);
-  }}, 0);
+  tm.currentThread.dispatch({ run: enterEventLoop}, 0);
+
+  do_check_eq(inspector.enterNestedEventLoop(requestor(gCount)), 0);
+  do_check_eq(inspector.eventLoopNestLevel, 0);
+  do_check_eq(inspector.lastNestRequestor, null);
+}
+
+function enterEventLoop() {
+  if (gCount++ < MAX) {
+    tm.currentThread.dispatch({ run: enterEventLoop}, 0);
+
+    let r = Object.create(requestor(gCount));
 
-  do_check_eq(inspector.enterNestedEventLoop(), 0);
-  do_check_eq(inspector.eventLoopNestLevel, 0);
-}
\ No newline at end of file
+    do_check_eq(inspector.eventLoopNestLevel, gCount);
+    do_check_eq(inspector.lastNestRequestor.url, requestor(gCount - 1).url);
+    do_check_eq(inspector.lastNestRequestor.connection, requestor(gCount - 1).connection);
+    do_check_eq(inspector.enterNestedEventLoop(requestor(gCount)), gCount);
+  } else {
+    do_check_eq(gCount, MAX + 1);
+    tm.currentThread.dispatch({ run: exitEventLoop}, 0);
+  }
+}
+
+function exitEventLoop() {
+  if (inspector.lastNestRequestor != null) {
+    do_check_eq(inspector.lastNestRequestor.url, requestor(gCount - 1).url);
+    do_check_eq(inspector.lastNestRequestor.connection, requestor(gCount - 1).connection);
+    if (gCount-- > 1) {
+      tm.currentThread.dispatch({ run: exitEventLoop}, 0);
+    }
+
+    do_check_eq(inspector.exitNestedEventLoop(), gCount);
+    do_check_eq(inspector.eventLoopNestLevel, gCount);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/debugger/tests/unit/test_unsafeDereference.js
@@ -0,0 +1,134 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+
+// Test Debugger.Object.prototype.unsafeDereference in the presence of
+// interesting cross-compartment wrappers.
+//
+// This is not really a debugger server test; it's more of a Debugger test.
+// But we need xpcshell and Components.utils.Sandbox to get
+// cross-compartment wrappers with interesting properties, and this is the
+// xpcshell test directory most closely related to the JS Debugger API.
+
+Components.utils.import("resource://gre/modules/jsdebugger.jsm");
+addDebuggerToGlobal(this);
+
+// Add a method to Debugger.Object for fetching value properties
+// conveniently.
+Debugger.Object.prototype.getProperty = function (aName) {
+  let desc = this.getOwnPropertyDescriptor(aName);
+  if (!desc)
+    return undefined;
+  if (!desc.value) {
+    throw Error("Debugger.Object.prototype.getProperty: " +
+                "not a value property: " + aName);
+  }
+  return desc.value;
+};
+
+function run_test() {
+  // Create a low-privilege sandbox, and a chrome-privilege sandbox.
+  let contentBox = Components.utils.Sandbox('http://www.example.com');
+  let chromeBox = Components.utils.Sandbox(this);
+
+  // Create an objects in this compartment, and one in each sandbox. We'll
+  // refer to the objects as "mainObj", "contentObj", and "chromeObj", in
+  // variable and property names.
+  var mainObj = { name: "mainObj" };
+  Components.utils.evalInSandbox('var contentObj = { name: "contentObj" };',
+                                 contentBox);
+  Components.utils.evalInSandbox('var chromeObj = { name: "chromeObj" };',
+                                 chromeBox);
+
+  // Give each global a pointer to all the other globals' objects.
+  contentBox.mainObj = chromeBox.mainObj = mainObj;
+  var contentObj = chromeBox.contentObj = contentBox.contentObj;
+  var chromeObj  = contentBox.chromeObj = chromeBox.chromeObj;
+
+  // First, a whole bunch of basic sanity checks, to ensure that JavaScript
+  // evaluated in various scopes really does see the world the way this
+  // test expects it to.
+
+  // The objects appear as global variables in the sandbox, and as
+  // the sandbox object's properties in chrome.
+  do_check_true(Components.utils.evalInSandbox('mainObj', contentBox)
+                === contentBox.mainObj);
+  do_check_true(Components.utils.evalInSandbox('contentObj', contentBox)
+                === contentBox.contentObj);
+  do_check_true(Components.utils.evalInSandbox('chromeObj', contentBox)
+                === contentBox.chromeObj);
+  do_check_true(Components.utils.evalInSandbox('mainObj', chromeBox)
+                === chromeBox.mainObj);
+  do_check_true(Components.utils.evalInSandbox('contentObj', chromeBox)
+                === chromeBox.contentObj);
+  do_check_true(Components.utils.evalInSandbox('chromeObj', chromeBox)
+                === chromeBox.chromeObj);
+
+  // We (the main global) can see properties of all objects in all globals.
+  do_check_true(contentBox.mainObj.name === "mainObj");
+  do_check_true(contentBox.contentObj.name === "contentObj");
+  do_check_true(contentBox.chromeObj.name === "chromeObj");
+
+  // chromeBox can see properties of all objects in all globals.
+  do_check_eq(Components.utils.evalInSandbox('mainObj.name', chromeBox),
+              'mainObj');
+  do_check_eq(Components.utils.evalInSandbox('contentObj.name', chromeBox),
+              'contentObj');
+  do_check_eq(Components.utils.evalInSandbox('chromeObj.name', chromeBox),
+              'chromeObj');
+
+  // contentBox can see properties of the content object, but not of either
+  // chrome object, because by default, content -> chrome wrappers hide all
+  // object properties.
+  do_check_eq(Components.utils.evalInSandbox('mainObj.name', contentBox),
+              undefined);
+  do_check_eq(Components.utils.evalInSandbox('contentObj.name', contentBox),
+              'contentObj');
+  do_check_eq(Components.utils.evalInSandbox('chromeObj.name', contentBox),
+              undefined);
+
+  // When viewing an object in compartment A from the vantage point of
+  // compartment B, Debugger should give the same results as debuggee code
+  // would.
+
+  // Create a debugger, debugging our two sandboxes.
+  let dbg = new Debugger;
+
+  // Create Debugger.Object instances referring to the two sandboxes, as
+  // seen from their own compartments.
+  let contentBoxDO = dbg.addDebuggee(contentBox);
+  let chromeBoxDO = dbg.addDebuggee(chromeBox);
+
+  // Use Debugger to view the objects from contentBox. We should get the
+  // same D.O instance from both getProperty and makeDebuggeeValue, and the
+  // same property visibility we checked for above.
+  let mainFromContentDO = contentBoxDO.getProperty('mainObj');
+  do_check_eq(mainFromContentDO, contentBoxDO.makeDebuggeeValue(mainObj));
+  do_check_eq(mainFromContentDO.getProperty('name'), undefined);
+  do_check_eq(mainFromContentDO.unsafeDereference(), mainObj);
+
+  let contentFromContentDO = contentBoxDO.getProperty('contentObj');
+  do_check_eq(contentFromContentDO, contentBoxDO.makeDebuggeeValue(contentObj));
+  do_check_eq(contentFromContentDO.getProperty('name'), 'contentObj');
+  do_check_eq(contentFromContentDO.unsafeDereference(), contentObj);
+
+  let chromeFromContentDO = contentBoxDO.getProperty('chromeObj');
+  do_check_eq(chromeFromContentDO, contentBoxDO.makeDebuggeeValue(chromeObj));
+  do_check_eq(chromeFromContentDO.getProperty('name'), undefined);
+  do_check_eq(chromeFromContentDO.unsafeDereference(), chromeObj);
+
+  // Similarly, viewing from chromeBox.
+  let mainFromChromeDO = chromeBoxDO.getProperty('mainObj');
+  do_check_eq(mainFromChromeDO, chromeBoxDO.makeDebuggeeValue(mainObj));
+  do_check_eq(mainFromChromeDO.getProperty('name'), 'mainObj');
+  do_check_eq(mainFromChromeDO.unsafeDereference(), mainObj);
+
+  let contentFromChromeDO = chromeBoxDO.getProperty('contentObj');
+  do_check_eq(contentFromChromeDO, chromeBoxDO.makeDebuggeeValue(contentObj));
+  do_check_eq(contentFromChromeDO.getProperty('name'), 'contentObj');
+  do_check_eq(contentFromChromeDO.unsafeDereference(), contentObj);
+
+  let chromeFromChromeDO = chromeBoxDO.getProperty('chromeObj');
+  do_check_eq(chromeFromChromeDO, chromeBoxDO.makeDebuggeeValue(chromeObj));
+  do_check_eq(chromeFromChromeDO.getProperty('name'), 'chromeObj');
+  do_check_eq(chromeFromChromeDO.unsafeDereference(), chromeObj);
+}
--- a/toolkit/devtools/debugger/tests/unit/xpcshell.ini
+++ b/toolkit/devtools/debugger/tests/unit/xpcshell.ini
@@ -104,8 +104,9 @@ reason = bug 820380
 [test_longstringgrips-02.js]
 [test_source-01.js]
 skip-if = toolkit == "gonk"
 reason = bug 820380
 [test_breakpointstore.js]
 [test_profiler_actor.js]
 skip-if = toolkit == "gonk"
 reason = bug 820380
+[test_unsafeDereference.js]
--- a/toolkit/devtools/webconsole/WebConsoleClient.jsm
+++ b/toolkit/devtools/webconsole/WebConsoleClient.jsm
@@ -76,23 +76,45 @@ WebConsoleClient.prototype = {
 
   /**
    * Evaluate a JavaScript expression.
    *
    * @param string aString
    *        The code you want to evaluate.
    * @param function aOnResponse
    *        The function invoked when the response is received.
+   * @param object [aOptions={}]
+   *        Options for evaluation:
+   *
+   *        - bindObjectActor: an ObjectActor ID. The OA holds a reference to
+   *        a Debugger.Object that wraps a content object. This option allows
+   *        you to bind |_self| to the D.O of the given OA, during string
+   *        evaluation.
+   *
+   *        See: Debugger.Object.evalInGlobalWithBindings() for information
+   *        about bindings.
+   *
+   *        Use case: the variable view needs to update objects and it does so
+   *        by knowing the ObjectActor it inspects and binding |_self| to the
+   *        D.O of the OA. As such, variable view sends strings like these for
+   *        eval:
+   *          _self["prop"] = value;
+   *
+   *        - frameActor: a FrameActor ID. The FA holds a reference to
+   *        a Debugger.Frame. This option allows you to evaluate the string in
+   *        the frame of the given FA.
    */
-  evaluateJS: function WCC_evaluateJS(aString, aOnResponse)
+  evaluateJS: function WCC_evaluateJS(aString, aOnResponse, aOptions = {})
   {
     let packet = {
       to: this._actor,
       type: "evaluateJS",
       text: aString,
+      bindObjectActor: aOptions.bindObjectActor,
+      frameActor: aOptions.frameActor,
     };
     this._client.request(packet, aOnResponse);
   },
 
   /**
    * Autocomplete a JavaScript expression.
    *
    * @param string aString
--- a/toolkit/devtools/webconsole/WebConsoleUtils.jsm
+++ b/toolkit/devtools/webconsole/WebConsoleUtils.jsm
@@ -23,22 +23,27 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 
 XPCOMUtils.defineLazyModuleGetter(this, "NetworkHelper",
                                   "resource://gre/modules/devtools/NetworkHelper.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gActivityDistributor",
                                    "@mozilla.org/network/http-activity-distributor;1",
                                    "nsIHttpActivityDistributor");
 
+// TODO: Bug 842672 - toolkit/ imports modules from browser/.
+// Note that these are only used in JSTermHelpers, see $0 and pprint().
 XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
                                   "resource:///modules/devtools/gDevTools.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "TargetFactory",
                                   "resource:///modules/devtools/Target.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "VariablesView",
+                                  "resource:///modules/devtools/VariablesView.jsm");
+
 this.EXPORTED_SYMBOLS = ["WebConsoleUtils", "JSPropertyProvider", "JSTermHelpers",
                          "PageErrorListener", "ConsoleAPIListener",
                          "NetworkResponseListener", "NetworkMonitor",
                          "ConsoleProgressListener"];
 
 // Match the function name from the result of toString() or toSource().
 //
 // Examples:
@@ -781,17 +786,17 @@ this.WebConsoleUtils = {
    * Get the object class name. For example, the |window| object has the Window
    * class name (based on [object Window]).
    *
    * @param object aObject
    *        The object you want to get the class name for.
    * @return string
    *         The object class name.
    */
-  getObjectClassName: function WCF_getObjectClassName(aObject)
+  getObjectClassName: function WCU_getObjectClassName(aObject)
   {
     if (aObject === null) {
       return "null";
     }
     if (aObject === undefined) {
       return "undefined";
     }
 
@@ -854,16 +859,29 @@ this.WebConsoleUtils = {
 
     if (val.displayString && typeof val.displayString == "object" &&
         val.displayString.type == "longString") {
       return val.displayString.initial;
     }
 
     return val.displayString || val.type;
   },
+
+  /**
+   * Check if the given value is a grip with an actor.
+   *
+   * @param mixed aGrip
+   *        Value you want to check if it is a grip with an actor.
+   * @return boolean
+   *         True if the given value is a grip with an actor.
+   */
+  isActorGrip: function WCU_isActorGrip(aGrip)
+  {
+    return aGrip && typeof(aGrip) == "object" && aGrip.actor;
+  },
 };
 
 //////////////////////////////////////////////////////////////////////////
 // Localization
 //////////////////////////////////////////////////////////////////////////
 
 WebConsoleUtils.l10n = function WCU_l10n(aBundleURI)
 {
@@ -1537,60 +1555,53 @@ this.JSTermHelpers = function JSTermHelp
    * @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.
    * @return array of nsIDOMNode
    */
   aOwner.sandbox.$x = function JSTH_$x(aXPath, aContext)
   {
-    let nodes = [];
+    let nodes = new aOwner.window.wrappedJSObject.Array();
     let doc = aOwner.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) {
-      aOwner.window.console.error(ex.message);
+    let results = doc.evaluate(aXPath, aContext, null,
+                               Ci.nsIDOMXPathResult.ANY_TYPE, null);
+    let node;
+    while (node = results.iterateNext()) {
+      nodes.push(node);
     }
 
     return nodes;
   };
 
   /**
    * Returns the currently selected object in the highlighter.
    *
    * TODO: this implementation crosses the client/server 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.
    * See bug 787975.
    *
    * @return nsIDOMElement|null
    *         The DOM element currently selected in the highlighter.
    */
-  Object.defineProperty(aOwner.sandbox, "$0", {
+   Object.defineProperty(aOwner.sandbox, "$0", {
     get: function() {
-      try {
-        let window = aOwner.chromeWindow();
-        let target = TargetFactory.forTab(window.gBrowser.selectedTab);
-        let toolbox = gDevTools.getToolbox(target);
+      let window = aOwner.chromeWindow();
+      if (!window) {
+        return null;
+      }
+      let target = TargetFactory.forTab(window.gBrowser.selectedTab);
+      let toolbox = gDevTools.getToolbox(target);
+      let panel = toolbox ? toolbox.getPanel("inspector") : null;
+      let node = panel ? panel.selection.node : null;
 
-        return toolbox == null ?
-            undefined :
-            toolbox.getPanel("inspector").selection.node;
-      }
-      catch (ex) {
-        aOwner.window.console.error(ex.message);
-      }
+      return node ? aOwner.makeDebuggeeValue(node) : null;
     },
     enumerable: true,
     configurable: false
   });
 
   /**
    * Clears the output of the JSTerm.
    */
@@ -1605,38 +1616,33 @@ this.JSTermHelpers = function JSTermHelp
    * Returns the result of Object.keys(aObject).
    *
    * @param object aObject
    *        Object to return the property names from.
    * @return array of strings
    */
   aOwner.sandbox.keys = function JSTH_keys(aObject)
   {
-    return Object.keys(WebConsoleUtils.unwrap(aObject));
+    return aOwner.window.wrappedJSObject.Object.keys(WebConsoleUtils.unwrap(aObject));
   };
 
   /**
    * Returns the values of all properties on aObject.
    *
    * @param object aObject
    *        Object to display the values from.
    * @return array of string
    */
   aOwner.sandbox.values = function JSTH_values(aObject)
   {
-    let arrValues = [];
+    let arrValues = new aOwner.window.wrappedJSObject.Array();
     let obj = WebConsoleUtils.unwrap(aObject);
 
-    try {
-      for (let prop in obj) {
-        arrValues.push(obj[prop]);
-      }
-    }
-    catch (ex) {
-      aOwner.window.console.error(ex.message);
+    for (let prop in obj) {
+      arrValues.push(obj[prop]);
     }
 
     return arrValues;
   };
 
   /**
    * Opens a help window in MDN.
    */
@@ -1648,25 +1654,22 @@ this.JSTermHelpers = function JSTermHelp
   /**
    * Inspects the passed aObject. This is done by opening the PropertyPanel.
    *
    * @param object aObject
    *        Object to inspect.
    */
   aOwner.sandbox.inspect = function JSTH_inspect(aObject)
   {
-    let obj = WebConsoleUtils.unwrap(aObject);
-    if (!WebConsoleUtils.isObjectInspectable(obj)) {
-      return aObject;
-    }
-
+    let dbgObj = aOwner.makeDebuggeeValue(aObject);
+    let grip = aOwner.createValueGrip(dbgObj);
     aOwner.helperResult = {
       type: "inspectObject",
       input: aOwner.evalInput,
-      object: aOwner.createValueGrip(obj),
+      object: grip,
     };
   };
 
   /**
    * Prints aObject to the output.
    *
    * @param object aObject
    *        Object to print to the output.
@@ -1685,23 +1688,34 @@ this.JSTermHelpers = function JSTermHelp
 
     aOwner.helperResult = { rawOutput: true };
 
     if (typeof aObject == "function") {
       return aObject + "\n";
     }
 
     let output = [];
-    let getObjectGrip = WebConsoleUtils.getObjectGrip.bind(WebConsoleUtils);
+
     let obj = WebConsoleUtils.unwrap(aObject);
-    let props = WebConsoleUtils.inspectObject(obj, getObjectGrip);
-    props.forEach(function(aProp) {
-      output.push(aProp.name + ": " +
-                  WebConsoleUtils.getPropertyPanelValue(aProp));
-    });
+    for (let name in obj) {
+      let desc = WebConsoleUtils.getPropertyDescriptor(obj, name) || {};
+      if (desc.get || desc.set) {
+        // TODO: Bug 842672 - toolkit/ imports modules from browser/.
+        let getGrip = VariablesView.getGrip(desc.get);
+        let setGrip = VariablesView.getGrip(desc.set);
+        let getString = VariablesView.getString(getGrip);
+        let setString = VariablesView.getString(setGrip);
+        output.push(name + ":", "  get: " + getString, "  set: " + setString);
+      }
+      else {
+        let valueGrip = VariablesView.getGrip(obj[name]);
+        let valueString = VariablesView.getString(valueGrip);
+        output.push(name + ": " + valueString);
+      }
+    }
 
     return "  " + output.join("\n  ");
   };
 
   /**
    * Print a string to the output, as-is.
    *
    * @param string aString
--- a/toolkit/devtools/webconsole/dbg-webconsole-actors.js
+++ b/toolkit/devtools/webconsole/dbg-webconsole-actors.js
@@ -66,21 +66,33 @@ function WebConsoleActor(aConnection, aP
     this._window = Services.wm.getMostRecentWindow("navigator:browser");
     this._isGlobalActor = true;
   }
 
   this._actorPool = new ActorPool(this.conn);
   this.conn.addActorPool(this._actorPool);
 
   this._prefs = {};
+
+  this.dbg = new Debugger();
+  this._createGlobal();
+
+  this._protoChains = new Map();
 }
 
 WebConsoleActor.prototype =
 {
   /**
+   * Debugger instance.
+   *
+   * @see jsdebugger.jsm
+   */
+  dbg: null,
+
+  /**
    * Tells if this Web Console actor is a global actor or not.
    * @private
    * @type boolean
    */
   _isGlobalActor: false,
 
   /**
    * Actor pool for all of the actors we send to the client.
@@ -93,28 +105,49 @@ WebConsoleActor.prototype =
   /**
    * Web Console-related preferences.
    * @private
    * @type object
    */
   _prefs: null,
 
   /**
-   * Tells the current inner window associated to the sandbox. When the page
-   * is navigated, we recreate the sandbox.
+   * Tells the current inner window of the window of |this._dbgWindow|. When the
+   * page is navigated, we recreate the debugger object.
+   * @private
+   * @type object
+   */
+  _globalWindowId: 0,
+
+  /**
+   * The Debugger.Object that wraps the content window.
    * @private
    * @type object
    */
-  _sandboxWindowId: 0,
+  _dbgWindow: null,
 
   /**
-   * The JavaScript Sandbox where code is evaluated.
+   * Object that holds the API we give to the JSTermHelpers constructor. This is
+   * where the JSTerm helper functions are added.
+   *
+   * @see this._getJSTermHelpers()
+   * @private
    * @type object
    */
-  sandbox: null,
+  _jstermHelpers: null,
+
+  /**
+   * A cache of prototype chains for objects that have received a
+   * prototypeAndProperties request.
+   *
+   * @private
+   * @type Map
+   * @see dbg-script-actors.js, ThreadActor._protoChains
+   */
+  _protoChains: null,
 
   /**
    * The debugger server connection instance.
    * @type object
    */
   conn: null,
 
   /**
@@ -157,16 +190,21 @@ WebConsoleActor.prototype =
 
   grip: function WCA_grip()
   {
     return { actor: this.actorID };
   },
 
   hasNativeConsoleAPI: BrowserTabActor.prototype.hasNativeConsoleAPI,
 
+  _createValueGrip: ThreadActor.prototype.createValueGrip,
+  _stringIsLong: ThreadActor.prototype._stringIsLong,
+  _findProtoChain: ThreadActor.prototype._findProtoChain,
+  _removeFromProtoChain: ThreadActor.prototype._removeFromProtoChain,
+
   /**
    * Destroy the current WebConsoleActor instance.
    */
   disconnect: function WCA_disconnect()
   {
     if (this.pageErrorListener) {
       this.pageErrorListener.destroy();
       this.pageErrorListener = null;
@@ -180,74 +218,80 @@ WebConsoleActor.prototype =
       this.networkMonitor = null;
     }
     if (this.consoleProgressListener) {
       this.consoleProgressListener.destroy();
       this.consoleProgressListener = null;
     }
     this.conn.removeActorPool(this._actorPool);
     this._actorPool = null;
-    this.sandbox = null;
-    this._sandboxWindowId = 0;
+    this._protoChains.clear();
+    this.dbg.enabled = false;
+    this.dbg = null;
+    this._dbgWindow = null;
+    this._globalWindowId = 0;
     this.conn = this._window = null;
   },
 
   /**
-   * Create a grip for the given value. If the value is an object,
-   * a WebConsoleObjectActor will be created.
+   * Create a grip for the given value.
    *
    * @param mixed aValue
    * @return object
    */
   createValueGrip: function WCA_createValueGrip(aValue)
   {
-    return WebConsoleUtils.createValueGrip(aValue,
-                                           this.createObjectActor.bind(this));
+    return this._createValueGrip(aValue, this._actorPool);
+  },
+
+  /**
+   * Make a debuggee value for the given value.
+   *
+   * @param mixed aValue
+   *        The value you want to get a debuggee value for.
+   * @return object
+   *         Debuggee value for |aValue|.
+   */
+  makeDebuggeeValue: function WCA_makeDebuggeeValue(aValue)
+  {
+    return this._dbgWindow.makeDebuggeeValue(aValue);
   },
 
   /**
    * Create a grip for the given object.
    *
    * @param object aObject
    *        The object you want.
+   * @param object aPool
+   *        An ActorPool where the new actor instance is added.
    * @param object
    *        The object grip.
    */
-  createObjectActor: function WCA_createObjectActor(aObject)
+  objectGrip: function WCA_objectGrip(aObject, aPool)
   {
-    if (typeof aObject == "string") {
-      return this.createStringGrip(aObject);
-    }
-
-    // We need to unwrap the object, otherwise we cannot access the properties
-    // and methods added by the content scripts.
-    let obj = WebConsoleUtils.unwrap(aObject);
-    let actor = new WebConsoleObjectActor(obj, this);
-    this._actorPool.addActor(actor);
+    let actor = new ObjectActor(aObject, this);
+    aPool.addActor(actor);
     return actor.grip();
   },
 
   /**
-   * Create a grip for the given string. If the given string is a long string,
-   * then a LongStringActor grip will be used.
+   * Create a grip for the given string.
    *
    * @param string aString
    *        The string you want to create the grip for.
-   * @return string|object
-   *         The same string, as is, or a LongStringActor object that wraps the
-   *         given string.
+   * @param object aPool
+   *        An ActorPool where the new actor instance is added.
+   * @return object
+   *         A LongStringActor object that wraps the given string.
    */
-  createStringGrip: function WCA_createStringGrip(aString)
+  longStringGrip: function WCA_longStringGrip(aString, aPool)
   {
-    if (aString.length >= DebuggerServer.LONG_STRING_LENGTH) {
-      let actor = new LongStringActor(aString, this);
-      this._actorPool.addActor(actor);
-      return actor.grip();
-    }
-    return aString;
+    let actor = new LongStringActor(aString, this);
+    aPool.addActor(actor);
+    return actor.grip();
   },
 
   /**
    * Get an object actor by its ID.
    *
    * @param string aActorID
    * @return object
    */
@@ -447,54 +491,68 @@ WebConsoleActor.prototype =
    * @param object aRequest
    *        The JSON request object received from the Web Console client.
    * @return object
    *         The evaluation response packet.
    */
   onEvaluateJS: function WCA_onEvaluateJS(aRequest)
   {
     let input = aRequest.text;
-    let result, error = null;
-    let timestamp;
+    let timestamp = Date.now();
+
+    let evalOptions = {
+      bindObjectActor: aRequest.bindObjectActor,
+      frameActor: aRequest.frameActor,
+    };
+    let evalInfo = this.evalWithDebugger(input, evalOptions);
+    let evalResult = evalInfo.result;
+    let helperResult = this._jstermHelpers.helperResult;
+    delete this._jstermHelpers.helperResult;
 
-    this.helperResult = null;
-    this.evalInput = input;
-    try {
-      timestamp = Date.now();
-      result = this.evalInSandbox(input);
+    let result, error, errorMessage;
+    if (evalResult) {
+      if ("return" in evalResult) {
+        result = evalResult.return;
+      }
+      else if ("yield" in evalResult) {
+        result = evalResult.yield;
+      }
+      else if ("throw" in evalResult) {
+        error = evalResult.throw;
+        let errorToString = evalInfo.window
+                            .evalInGlobalWithBindings("ex + ''", {ex: error});
+        if (errorToString && typeof errorToString.return == "string") {
+          errorMessage = errorToString.return;
+        }
+      }
     }
-    catch (ex) {
-      error = ex;
-    }
-
-    let helperResult = this.helperResult;
-    delete this.helperResult;
-    delete this.evalInput;
 
     return {
       from: this.actorID,
       input: input,
       result: this.createValueGrip(result),
       timestamp: timestamp,
-      error: error,
-      errorMessage: error ? String(error) : null,
+      exception: error ? this.createValueGrip(error) : null,
+      exceptionMessage: errorMessage,
       helperResult: helperResult,
     };
   },
 
   /**
    * The Autocomplete request handler.
    *
    * @param object aRequest
    *        The request message - what input to autocomplete.
    * @return object
    *         The response message - matched properties.
    */
   onAutocomplete: function WCA_onAutocomplete(aRequest)
   {
+    // TODO: Bug 842682 - use the debugger API for autocomplete in the Web
+    // Console, and provide suggestions from the selected debugger stack frame.
     let result = JSPropertyProvider(this.window, aRequest.text) || {};
     return {
       from: this.actorID,
       matches: result.matches || [],
       matchProp: result.matchProp,
     };
   },
 
@@ -524,78 +582,211 @@ WebConsoleActor.prototype =
     return { updated: Object.keys(aRequest.preferences) };
   },
 
   //////////////////
   // End of request handlers.
   //////////////////
 
   /**
-   * Create the JavaScript sandbox where user input is evaluated.
+   * Create the Debugger.Object for the current window.
    * @private
    */
-  _createSandbox: function WCA__createSandbox()
+  _createGlobal: function WCA__createGlobal()
   {
-    this._sandboxWindowId = WebConsoleUtils.getInnerWindowId(this.window);
-    this.sandbox = new Cu.Sandbox(this.window, {
-      sandboxPrototype: this.window,
-      wantXrays: false,
-    });
+    let windowId = WebConsoleUtils.getInnerWindowId(this.window);
+    if (this._globalWindowId == windowId) {
+      return;
+    }
+
+    this._globalWindowId = windowId;
+
+    this._dbgWindow = this.dbg.addDebuggee(this.window);
+    this.dbg.removeDebuggee(this.window);
+
+    // Update the JSTerm helpers.
+    this._jstermHelpers = this._getJSTermHelpers(this._dbgWindow);
+  },
 
-    this.sandbox.console = this.window.console;
+  /**
+   * Create an object with the API we expose to the JSTermHelpers constructor.
+   * This object inherits properties and methods from the Web Console actor.
+   *
+   * @private
+   * @param object aDebuggerObject
+   *        A Debugger.Object that wraps a content global. This is used for the
+   *        JSTerm helpers.
+   * @return object
+   */
+  _getJSTermHelpers: function WCA__getJSTermHelpers(aDebuggerObject)
+  {
+    let helpers = Object.create(this);
+    helpers.sandbox = Object.create(null);
+    helpers._dbgWindow = aDebuggerObject;
+    JSTermHelpers(helpers);
 
-    JSTermHelpers(this);
+    // Make sure the helpers can be used during eval.
+    for (let name in helpers.sandbox) {
+      let desc = Object.getOwnPropertyDescriptor(helpers.sandbox, name);
+      if (desc.get || desc.set) {
+        continue;
+      }
+      helpers.sandbox[name] = helpers.makeDebuggeeValue(desc.value);
+    }
+    return helpers;
   },
 
   /**
-   * Evaluates a string in the sandbox.
+   * Evaluates a string using the debugger API.
+   *
+   * To allow the variables view to update properties from the web console we
+   * provide the "bindObjectActor" mechanism: the Web Console tells the
+   * ObjectActor ID for which it desires to evaluate an expression. The
+   * Debugger.Object pointed at by the actor ID is bound such that it is
+   * available during expression evaluation (evalInGlobalWithBindings()).
+   *
+   * Example:
+   *   _self['foobar'] = 'test'
+   * where |_self| refers to the desired object.
+   *
+   * The |frameActor| property allows the Web Console client to provide the
+   * frame actor ID, such that the expression can be evaluated in the
+   * user-selected stack frame.
+   *
+   * For the above to work we need the debugger and the web console to share
+   * a connection, otherwise the Web Console actor will not find the frame
+   * actor.
+   *
+   * The Debugger.Frame comes from the jsdebugger's Debugger instance, which
+   * is different from the Web Console's Debugger instance. This means that
+   * for evaluation to work, we need to create a new instance for  the jsterm
+   * helpers - they need to be Debugger.Objects coming from the jsdebugger's
+   * Debugger instance.
    *
    * @param string aString
-   *        String to evaluate in the sandbox.
-   * @return mixed
-   *         The result of the evaluation.
+   *        String to evaluate.
+   * @param object [aOptions]
+   *        Options for evaluation:
+   *        - bindObjectActor: the ObjectActor ID to use for evaluation.
+   *          |evalWithBindings()| will be called with one additional binding:
+   *          |_self| which will point to the Debugger.Object of the given
+   *          ObjectActor.
+   *        - frameActor: the FrameActor ID to use for evaluation. The given
+   *        debugger frame is used for evaluation, instead of the global window.
+   * @return object
+   *         An object that holds the following properties:
+   *         - dbg: the debugger where the string was evaluated.
+   *         - frame: (optional) the frame where the string was evaluated.
+   *         - window: the Debugger.Object for the global where the string was
+   *         evaluated.
+   *         - result: the result of the evaluation.
    */
-  evalInSandbox: function WCA_evalInSandbox(aString)
+  evalWithDebugger: function WCA_evalWithDebugger(aString, aOptions = {})
   {
-    // If the user changed to a different location, we need to update the
-    // sandbox.
-    if (this._sandboxWindowId !== WebConsoleUtils.getInnerWindowId(this.window)) {
-      this._createSandbox();
-    }
+    this._createGlobal();
 
-    // The help function needs to be easy to guess, so we make the () optional
+    // 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;
+    let bindSelf = null;
+
+    if (aOptions.bindObjectActor) {
+      let objActor = this.getActorByID(aOptions.bindObjectActor);
+      if (objActor) {
+        bindSelf = objActor.obj;
+      }
+    }
 
-    // We prefer to execute the page-provided implementations for the $() and
-    // $$() functions.
-    if (typeof window.$ == "function") {
-      $ = this.sandbox.$;
-      delete this.sandbox.$;
+    let helpers = this._jstermHelpers;
+    let found$ = false, found$$ = false;
+    let frame = null, frameActor = null;
+    if (aOptions.frameActor) {
+      frameActor = this.conn.getActor(aOptions.frameActor);
+      if (frameActor) {
+        frame = frameActor.frame;
+      }
+      else {
+        Cu.reportError("Web Console Actor: the frame actor was not found: " +
+                       aOptions.frameActor);
+      }
     }
-    if (typeof window.$$ == "function") {
-      $$ = this.sandbox.$$;
-      delete this.sandbox.$$;
+
+    let dbg = this.dbg;
+    let dbgWindow = this._dbgWindow;
+
+    if (frame) {
+      // Avoid having bindings from a different Debugger. The Debugger.Frame
+      // comes from the jsdebugger's Debugger instance.
+      dbg = frameActor.threadActor.dbg;
+      dbgWindow = dbg.addDebuggee(this.window);
+      helpers = this._getJSTermHelpers(dbgWindow);
+
+      let env = frame.environment;
+      if (env) {
+        found$ = !!env.find("$");
+        found$$ = !!env.find("$$");
+      }
+    }
+    else {
+      found$ = !!this._dbgWindow.getOwnPropertyDescriptor("$");
+      found$$ = !!this._dbgWindow.getOwnPropertyDescriptor("$$");
     }
 
-    let result = Cu.evalInSandbox(aString, this.sandbox, "1.8",
-                                  "Web Console", 1);
+    let bindings = helpers.sandbox;
+    if (bindSelf) {
+      let jsObj = bindSelf.unsafeDereference();
+      bindings._self = helpers.makeDebuggeeValue(jsObj);
+    }
+
+    let $ = null, $$ = null;
+    if (found$) {
+      $ = bindings.$;
+      delete bindings.$;
+    }
+    if (found$$) {
+      $$ = bindings.$$;
+      delete bindings.$$;
+    }
+
+    helpers.helperResult = null;
+    helpers.evalInput = aString;
+
+    let result;
+    if (frame) {
+      result = frame.evalWithBindings(aString, bindings);
+    }
+    else {
+      result = this._dbgWindow.evalInGlobalWithBindings(aString, bindings);
+    }
+
+    delete helpers.evalInput;
+    if (helpers != this._jstermHelpers) {
+      this._jstermHelpers.helperResult = helpers.helperResult;
+      delete helpers.helperResult;
+    }
 
     if ($) {
-      this.sandbox.$ = $;
+      bindings.$ = $;
     }
     if ($$) {
-      this.sandbox.$$ = $$;
+      bindings.$$ = $$;
+    }
+
+    if (bindings._self) {
+      delete bindings._self;
     }
 
-    return result;
+    return {
+      result: result,
+      dbg: dbg,
+      frame: frame,
+      window: dbgWindow,
+    };
   },
 
   //////////////////
   // Event handlers for various listeners.
   //////////////////
 
   /**
    * Handler for page errors received from the PageErrorListener. This method
@@ -721,130 +912,58 @@ WebConsoleActor.prototype =
   prepareConsoleMessageForRemote:
   function WCA_prepareConsoleMessageForRemote(aMessage)
   {
     let result = WebConsoleUtils.cloneObject(aMessage);
     delete result.wrappedJSObject;
 
     result.arguments = Array.map(aMessage.arguments || [],
       function(aObj) {
-        return this.createValueGrip(aObj);
+        let dbgObj = this.makeDebuggeeValue(aObj);
+        return this.createValueGrip(dbgObj);
       }, this);
 
-    if (result.level == "dir") {
-      result.objectProperties = [];
-      let first = result.arguments[0];
-      if (typeof first == "object" && first && first.inspectable) {
-        let actor = this.getActorByID(first.actor);
-        result.objectProperties = actor.onInspectProperties().properties;
-      }
-    }
-
     return result;
   },
 
   /**
    * Find the XUL window that owns the content window.
    *
    * @return Window
    *         The XUL window that owns the content window.
    */
   chromeWindow: function WCA_chromeWindow()
   {
-    return this.window.QueryInterface(Ci.nsIInterfaceRequestor)
-           .getInterface(Ci.nsIWebNavigation).QueryInterface(Ci.nsIDocShell)
-           .chromeEventHandler.ownerDocument.defaultView;
+    let window = null;
+    try {
+      window = this.window.QueryInterface(Ci.nsIInterfaceRequestor)
+             .getInterface(Ci.nsIWebNavigation).QueryInterface(Ci.nsIDocShell)
+             .chromeEventHandler.ownerDocument.defaultView;
+    }
+    catch (ex) {
+      // The above can fail because chromeEventHandler is not available for all
+      // kinds of |this.window|.
+    }
+
+    return window;
   },
 };
 
 WebConsoleActor.prototype.requestTypes =
 {
   startListeners: WebConsoleActor.prototype.onStartListeners,
   stopListeners: WebConsoleActor.prototype.onStopListeners,
   getCachedMessages: WebConsoleActor.prototype.onGetCachedMessages,
   evaluateJS: WebConsoleActor.prototype.onEvaluateJS,
   autocomplete: WebConsoleActor.prototype.onAutocomplete,
   clearMessagesCache: WebConsoleActor.prototype.onClearMessagesCache,
   setPreferences: WebConsoleActor.prototype.onSetPreferences,
 };
 
 /**
- * Creates an actor for the specified object.
- *
- * @constructor
- * @param object aObj
- *        The object you want.
- * @param object aWebConsoleActor
- *        The parent WebConsoleActor instance for this object.
- */
-function WebConsoleObjectActor(aObj, aWebConsoleActor)
-{
-  this.obj = aObj;
-  this.parent = aWebConsoleActor;
-}
-
-WebConsoleObjectActor.prototype =
-{
-  actorPrefix: "consoleObj",
-
-  /**
-   * Returns a grip for this actor for returning in a protocol message.
-   */
-  grip: function WCOA_grip()
-  {
-    let grip = WebConsoleUtils.getObjectGrip(this.obj);
-    grip.actor = this.actorID;
-    grip.displayString = this.parent.createStringGrip(grip.displayString);
-    return grip;
-  },
-
-  /**
-   * Releases this actor from the pool.
-   */
-  release: function WCOA_release()
-  {
-    this.parent.releaseActor(this);
-    this.parent = this.obj = null;
-  },
-
-  /**
-   * Handle a protocol request to inspect the properties of the object.
-   *
-   * @return object
-   *         Message to send to the client. This holds the 'properties' property
-   *         - an array with a descriptor for each property in the object.
-   */
-  onInspectProperties: function WCOA_onInspectProperties()
-  {
-    let createObjectActor = this.parent.createObjectActor.bind(this.parent);
-    let props = WebConsoleUtils.inspectObject(this.obj, createObjectActor);
-    return {
-      from: this.actorID,
-      properties: props,
-    };
-  },
-
-  /**
-   * Handle a protocol request to release a grip.
-   */
-  onRelease: function WCOA_onRelease()
-  {
-    this.release();
-    return {};
-  },
-};
-
-WebConsoleObjectActor.prototype.requestTypes =
-{
-  "inspectProperties": WebConsoleObjectActor.prototype.onInspectProperties,
-  "release": WebConsoleObjectActor.prototype.onRelease,
-};
-
-
-/**
  * Creates an actor for a network event.
  *
  * @constructor
  * @param object aNetworkEvent
  *        The network event you want to use the actor for.
  * @param object aWebConsoleActor
  *        The parent WebConsoleActor instance for this object.
  */
@@ -1078,17 +1197,17 @@ NetworkEventActor.prototype =
    * Add network request POST data.
    *
    * @param object aPostData
    *        The request POST data.
    */
   addRequestPostData: function NEA_addRequestPostData(aPostData)
   {
     this._request.postData = aPostData;
-    aPostData.text = this.parent.createStringGrip(aPostData.text);
+    aPostData.text = this._createStringGrip(aPostData.text);
     if (typeof aPostData.text == "object") {
       this._longStringActors.add(aPostData.text);
     }
 
     let packet = {
       from: this.actorID,
       type: "networkEventUpdate",
       updateType: "requestPostData",
@@ -1173,17 +1292,17 @@ NetworkEventActor.prototype =
    *        The response content.
    * @param boolean aDiscardedResponseBody
    *        Tells if the response content was recorded or not.
    */
   addResponseContent:
   function NEA_addResponseContent(aContent, aDiscardedResponseBody)
   {
     this._response.content = aContent;
-    aContent.text = this.parent.createStringGrip(aContent.text);
+    aContent.text = this._createStringGrip(aContent.text);
     if (typeof aContent.text == "object") {
       this._longStringActors.add(aContent.text);
     }
 
     let packet = {
       from: this.actorID,
       type: "networkEventUpdate",
       updateType: "responseContent",
@@ -1223,22 +1342,40 @@ NetworkEventActor.prototype =
    * LongStringActor for the header values, when needed.
    *
    * @private
    * @param array aHeaders
    */
   _prepareHeaders: function NEA__prepareHeaders(aHeaders)
   {
     for (let header of aHeaders) {
-      header.value = this.parent.createStringGrip(header.value);
+      header.value = this._createStringGrip(header.value);
       if (typeof header.value == "object") {
         this._longStringActors.add(header.value);
       }
     }
   },
+
+  /**
+   * Create a long string grip if needed for the given string.
+   *
+   * @private
+   * @param string aString
+   *        The string you want to create a long string grip for.
+   * @return string|object
+   *         A string is returned if |aString| is not a long string.
+   *         A LongStringActor grip is returned if |aString| is a long string.
+   */
+  _createStringGrip: function NEA__createStringGrip(aString)
+  {
+    if (this.parent._stringIsLong(aString)) {
+      return this.parent.longStringGrip(aString, this.parent._actorPool);
+    }
+    return aString;
+  },
 };
 
 NetworkEventActor.prototype.requestTypes =
 {
   "release": NetworkEventActor.prototype.onRelease,
   "getRequestHeaders": NetworkEventActor.prototype.onGetRequestHeaders,
   "getRequestCookies": NetworkEventActor.prototype.onGetRequestCookies,
   "getRequestPostData": NetworkEventActor.prototype.onGetRequestPostData,
--- a/toolkit/devtools/webconsole/test/test_bug819670_getter_throws.html
+++ b/toolkit/devtools/webconsole/test/test_bug819670_getter_throws.html
@@ -29,43 +29,43 @@ function onAttach(aState, aResponse)
 function onEvaluate(aState, aResponse)
 {
   checkObject(aResponse, {
     from: aState.actor,
     input: "document.__proto__",
     result: {
       type: "object",
       actor: /[a-z]/,
-      inspectable: true,
     },
   });
 
-  ok(!aResponse.error, "no js error");
+  ok(!aResponse.exception, "no eval exception");
   ok(!aResponse.helperResult, "no helper result");
 
   onInspect = onInspect.bind(null, aState);
-  aState.client.inspectObjectProperties(aResponse.result.actor, onInspect);
+  let client = new GripClient(aState.dbgClient, aResponse.result);
+  client.getPrototypeAndProperties(onInspect);
 }
 
 function onInspect(aState, aResponse)
 {
   ok(!aResponse.error, "no response error");
 
-  let expectedProps = [
-    { name: "ATTRIBUTE_NODE", value: 2 },
-    { name: "CDATA_SECTION_NODE", value: 4 },
-    { name: "COMMENT_NODE", value: 8 },
-    { name: "DOCUMENT_FRAGMENT_NODE", value: 11 },
-  ];
+  let expectedProps = {
+    "addBroadcastListenerFor": { value: { type: "object" } },
+    "commandDispatcher": { get: { type: "object" } },
+    "getBoxObjectFor": { value: { type: "object" } },
+    "getElementsByAttribute": { value: { type: "object" } },
+  };
 
-  let props = aResponse.properties;
+  let props = aResponse.ownProperties;
   ok(props, "response properties available");
 
   if (props) {
-    ok(props.length > expectedProps.length,
+    ok(Object.keys(props).length > Object.keys(expectedProps).length,
        "number of enumerable properties");
     checkObject(props, expectedProps);
   }
 
   closeDebugger(aState, function() {
     SimpleTest.finish();
   });
 }
--- a/toolkit/devtools/webconsole/test/test_consoleapi.html
+++ b/toolkit/devtools/webconsole/test/test_consoleapi.html
@@ -15,23 +15,23 @@
 SimpleTest.waitForExplicitFinish();
 
 let expectedConsoleCalls = [];
 
 function doConsoleCalls(aState)
 {
   let longString = (new Array(DebuggerServer.LONG_STRING_LENGTH + 2)).join("a");
 
-  console.log("foobarBaz-log", undefined);
-  console.info("foobarBaz-info", null);
-  console.warn("foobarBaz-warn", document.body);
-  console.debug(null);
-  console.trace();
-  console.dir(document, window);
-  console.log("foo", longString);
+  top.console.log("foobarBaz-log", undefined);
+  top.console.info("foobarBaz-info", null);
+  top.console.warn("foobarBaz-warn", top.document.documentElement);
+  top.console.debug(null);
+  top.console.trace();
+  top.console.dir(top.document, top.location);
+  top.console.log("foo", longString);
 
   expectedConsoleCalls = [
     {
       level: "log",
       filename: /test_consoleapi/,
       functionName: "doConsoleCalls",
       timeStamp: /^\d+$/,
       arguments: ["foobarBaz-log", { type: "undefined" }],
@@ -77,34 +77,24 @@ function doConsoleCalls(aState)
       level: "dir",
       filename: /test_consoleapi/,
       functionName: "doConsoleCalls",
       timeStamp: /^\d+$/,
       arguments: [
         {
           type: "object",
           actor: /[a-z]/,
-          className: "HTMLDocument",
+          class: "XULDocument",
         },
         {
           type: "object",
           actor: /[a-z]/,
-          className: "Window",
+          class: "Location",
         }
       ],
-      objectProperties: [
-        {
-          name: "ATTRIBUTE_NODE",
-          value: 2,
-        },
-        {
-          name: "CDATA_SECTION_NODE",
-          value: 4,
-        }, // ...
-      ],
     },
     {
       level: "log",
       filename: /test_consoleapi/,
       functionName: "doConsoleCalls",
       timeStamp: /^\d+$/,
       arguments: [
         "foo",
@@ -119,17 +109,17 @@ function doConsoleCalls(aState)
     },
   ];
 }
 
 function startTest()
 {
   removeEventListener("load", startTest);
 
-  attachConsole(["ConsoleAPI"], onAttach);
+  attachConsole(["ConsoleAPI"], onAttach, true);
 }
 
 function onAttach(aState, aResponse)
 {
   onConsoleAPICall = onConsoleAPICall.bind(null, aState);
   aState.dbgClient.addListener("consoleAPICall", onConsoleAPICall);
   doConsoleCalls(aState.actor);
 }
--- a/toolkit/devtools/webconsole/test/test_jsterm.html
+++ b/toolkit/devtools/webconsole/test/test_jsterm.html
@@ -86,41 +86,41 @@ function doSimpleEval()
 function onSimpleEval(aResponse)
 {
   checkObject(aResponse, {
     from: gState.actor,
     input: "2+2",
     result: 4,
   });
 
-  ok(!aResponse.error, "no js error");
+  ok(!aResponse.exception, "no eval exception");
   ok(!aResponse.helperResult, "no helper result");
 
   nextTest();
 }
 
 function doWindowEval()
 {
-  info("test eval 'window'");
-  gState.client.evaluateJS("window", onWindowEval);
+  info("test eval 'document'");
+  gState.client.evaluateJS("document", onWindowEval);
 }
 
 function onWindowEval(aResponse)
 {
   checkObject(aResponse, {
     from: gState.actor,
-    input: "window",
+    input: "document",
     result: {
       type: "object",
-      className: "Window",
+      class: "XULDocument",
       actor: /[a-z]/,
     },
   });
 
-  ok(!aResponse.error, "no js error");
+  ok(!aResponse.exception, "no eval exception");
   ok(!aResponse.helperResult, "no helper result");
 
   nextTest();
 }
 
 function doEvalWithException()
 {
   info("test eval with exception");
@@ -130,20 +130,20 @@ function doEvalWithException()
 function onEvalWithException(aResponse)
 {
   checkObject(aResponse, {
     from: gState.actor,
     input: "window.doTheImpossible()",
     result: {
       type: "undefined",
     },
-    errorMessage: /doTheImpossible/,
+    exceptionMessage: /doTheImpossible/,
   });
 
-  ok(aResponse.error, "js error object");
+  ok(aResponse.exception, "js eval exception");
   ok(!aResponse.helperResult, "no helper result");
 
   nextTest();
 }
 
 function doEvalWithHelper()
 {
   info("test eval with helper");
@@ -156,17 +156,17 @@ function onEvalWithHelper(aResponse)
     from: gState.actor,
     input: "clear()",
     result: {
       type: "undefined",
     },
     helperResult: { type: "clearOutput" },
   });
 
-  ok(!aResponse.error, "no js error");
+  ok(!aResponse.exception, "no eval exception");
 
   nextTest();
 }
 
 function doEvalString()
 {
   gState.client.evaluateJS("window.foobarObject.strfoo", onEvalString);
 }
--- a/toolkit/devtools/webconsole/test/test_object_actor.html
+++ b/toolkit/devtools/webconsole/test/test_object_actor.html
@@ -25,174 +25,148 @@ function startTest()
 
 function onAttach(aState, aResponse)
 {
   onConsoleCall = onConsoleCall.bind(null, aState);
   aState.dbgClient.addListener("consoleAPICall", onConsoleCall);
 
   let longString = (new Array(DebuggerServer.LONG_STRING_LENGTH + 3)).join("\u0629");
 
-  window.foobarObject = Object.create(null);
+  // Here we put the objects in the correct window, to avoid having them all
+  // wrapped by proxies for cross-compartment access.
+
+  let foobarObject = top.Object.create(null);
   foobarObject.tamarbuta = longString;
   foobarObject.foo = 1;
   foobarObject.foobar = "hello";
-  foobarObject.foobaz = document;
   foobarObject.omg = null;
   foobarObject.testfoo = false;
-  foobarObject.notInspectable = {};
-  foobarObject.omgfn = function _omgfn() {
-    return "myResult";
-  };
-  foobarObject.abArray = ["a", "b"];
+  foobarObject.notInspectable = top.Object.create(null);
+  foobarObject.omgfn = new top.Function("return 'myResult'");
+  foobarObject.abArray = new top.Array("a", "b");
+  foobarObject.foobaz = top.document;
 
-  Object.defineProperty(foobarObject, "getterAndSetter", {
+  top.Object.defineProperty(foobarObject, "getterAndSetter", {
     enumerable: true,
-    get: function fooGet() { return "foo"; },
-    set: function fooSet() { 1+2 },
+    get: new top.Function("return 'foo';"),
+    set: new top.Function("1+2"),
   });
 
-  foobarObject.longStringObj = {
-    toSource: function() longString,
-    toString: function() longString,
-    boom: "explode",
-  };
+  foobarObject.longStringObj = top.Object.create(null);
+  foobarObject.longStringObj.toSource = new top.Function("'" + longString + "'");
+  foobarObject.longStringObj.toString = new top.Function("'" + longString + "'");
+  foobarObject.longStringObj.boom = "explode";
 
-  console.log("hello", foobarObject);
+  top.wrappedJSObject.foobarObject = foobarObject;
+  top.console.log("hello", top.wrappedJSObject.foobarObject);
 
-  expectedProps = [
-    {
-      name: "abArray",
+  expectedProps = {
+    "abArray": {
       value: {
         type: "object",
-        className: "Array",
+        class: "Array",
         actor: /[a-z]/,
-        inspectable: true,
       },
     },
-    {
-      name: "foo",
+    "foo": {
       configurable: true,
       enumerable: true,
       writable: true,
       value: 1,
     },
-    {
-      name: "foobar",
+    "foobar": {
       value: "hello",
     },
-    {
-      name: "foobaz",
+    "foobaz": {
       value: {
         type: "object",
-        className: "HTMLDocument",
-        displayString: /\[object HTMLDocument/,
-        inspectable: true,
+        class: "XULDocument",
+        actor: /[a-z]/,
+      },
+    },
+    "getterAndSetter": {
+      get: {
+        type: "object",
+        class: "Function",
+        actor: /[a-z]/,
+      },
+      set: {
+        type: "object",
+        class: "Function",
         actor: /[a-z]/,
       },
     },
-    {
-      name: "getterAndSetter",
-      get: {
-        type: "function",
-        className: "Function",
-        displayString: /function fooGet/,
+    "longStringObj": {
+      value: {
+        type: "object",
+        class: "Object",
         actor: /[a-z]/,
-        inspectable: false,
-      },
-      set: {
-        type: "function",
-        className: "Function",
-        displayString: /function fooSet/,
-        actor: /[a-z]/,
-        inspectable: false,
       },
     },
-    {
-      name: "longStringObj",
+    "notInspectable": {
       value: {
         type: "object",
-        className: "Object",
+        class: "Object",
         actor: /[a-z]/,
-        inspectable: true,
-        displayString: {
-          type: "longString",
-          initial: longString.substring(0,
-            DebuggerServer.LONG_STRING_INITIAL_LENGTH),
-          length: longString.length,
-        },
       },
     },
-    {
-      name: "notInspectable",
+    "omg": {
+      value: { type: "null" },
+    },
+    "omgfn": {
       value: {
         type: "object",
-        className: "Object",
+        class: "Function",
         actor: /[a-z]/,
-        inspectable: false,
       },
     },
-    {
-      name: "omg",
-      value: { type: "null" },
-    },
-    {
-      name: "omgfn",
-      value: {
-        type: "function",
-        className: "Function",
-        displayString: /function _omgfn/,
-        actor: /[a-z]/,
-        inspectable: false,
-      },
-    },
-    {
-      name: "tamarbuta",
+    "tamarbuta": {
       value: {
         type: "longString",
         initial: longString.substring(0,
           DebuggerServer.LONG_STRING_INITIAL_LENGTH),
         length: longString.length,
       },
     },
-    {
-      name: "testfoo",
+    "testfoo": {
       value: false,
     },
-  ];
+  };
 }
 
 function onConsoleCall(aState, aType, aPacket)
 {
   is(aPacket.from, aState.actor, "console API call actor");
 
   info("checking the console API call packet");
 
   checkConsoleAPICall(aPacket.message, {
     level: "log",
     filename: /test_object_actor/,
     functionName: "onAttach",
     arguments: ["hello", {
       type: "object",
       actor: /[a-z]/,
-      inspectable: true,
     }],
   });
 
   aState.dbgClient.removeListener("consoleAPICall", onConsoleCall);
 
   info("inspecting object properties");
   let args = aPacket.message.arguments;
   onProperties = onProperties.bind(null, aState);
-  aState.client.inspectObjectProperties(args[1].actor, onProperties);
+
+  let client = new GripClient(aState.dbgClient, args[1]);
+  client.getPrototypeAndProperties(onProperties);
 }
 
 function onProperties(aState, aResponse)
 {
-  let props = aResponse.properties;
-  is(props.length, expectedProps.length,
+  let props = aResponse.ownProperties;
+  is(Object.keys(props).length, Object.keys(expectedProps).length,
      "number of enumerable properties");
   checkObject(props, expectedProps);
 
   expectedProps = [];
 
   closeDebugger(aState, function() {
     SimpleTest.finish();
   });
--- a/xpfe/components/autocomplete/src/Makefile.in
+++ b/xpfe/components/autocomplete/src/Makefile.in
@@ -7,16 +7,15 @@ DEPTH		= @DEPTH@
 topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MODULE_NAME	= xpAutoComplete
 LIBRARY_NAME	= xpautocomplete
-SHORT_LIBNAME	= xpautoc
 EXPORT_LIBRARY	= 1
 LIBXUL_LIBRARY  = 1
 IS_COMPONENT	= 1
 
 CPPSRCS		= nsAutoComplete.cpp
 
 include $(topsrcdir)/config/rules.mk