merge m-c to fx-team
authorTim Taubert <ttaubert@mozilla.com>
Sat, 22 Jun 2013 06:10:39 +0200
changeset 147516 cea75ce9a55902e805cb5c8bf7bd46f9870504a3
parent 147488 def4320f90aacb4128d5460b05d05451cc6697a1 (current diff)
parent 147515 deb4d87d16846983c04f917a46a7b9bc507d5b02 (diff)
child 147517 2cfff9240e9a4e3f4aec865df1d641e2c679e8b9
child 147652 c8bec69452510bdf8a342966b649570818b2a5c0
push id2697
push userbbajaj@mozilla.com
push dateMon, 05 Aug 2013 18:49:53 +0000
treeherdermozilla-beta@dfec938c7b63 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone24.0a1
first release with
nightly linux32
cea75ce9a559 / 24.0a1 / 20130622031042 / files
nightly linux64
cea75ce9a559 / 24.0a1 / 20130622031042 / files
nightly mac
cea75ce9a559 / 24.0a1 / 20130622031042 / files
nightly win32
cea75ce9a559 / 24.0a1 / 20130622031042 / files
nightly win64
cea75ce9a559 / 24.0a1 / 20130622031042 / 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 fx-team
browser/devtools/debugger/DebuggerUI.jsm
browser/devtools/debugger/test/browser_dbg_createRemote.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1068,29 +1068,23 @@ pref("devtools.layoutview.open", false);
 pref("devtools.responsiveUI.enabled", true);
 
 // Enable the Debugger
 pref("devtools.debugger.enabled", true);
 pref("devtools.debugger.chrome-enabled", true);
 pref("devtools.debugger.chrome-debugging-host", "localhost");
 pref("devtools.debugger.chrome-debugging-port", 6080);
 pref("devtools.debugger.remote-host", "localhost");
-pref("devtools.debugger.remote-autoconnect", false);
-pref("devtools.debugger.remote-connection-retries", 3);
 pref("devtools.debugger.remote-timeout", 20000);
+pref("devtools.debugger.pause-on-exceptions", false);
 pref("devtools.debugger.source-maps-enabled", true);
 
 // The default Debugger UI settings
-pref("devtools.debugger.ui.win-x", 0);
-pref("devtools.debugger.ui.win-y", 0);
-pref("devtools.debugger.ui.win-width", 900);
-pref("devtools.debugger.ui.win-height", 400);
 pref("devtools.debugger.ui.panes-sources-width", 200);
 pref("devtools.debugger.ui.panes-instruments-width", 300);
-pref("devtools.debugger.ui.pause-on-exceptions", false);
 pref("devtools.debugger.ui.panes-visible-on-startup", false);
 pref("devtools.debugger.ui.variables-sorting-enabled", true);
 pref("devtools.debugger.ui.variables-only-enum-visible", false);
 pref("devtools.debugger.ui.variables-searchbox-visible", false);
 
 // Enable the Profiler
 pref("devtools.profiler.enabled", true);
 
@@ -1188,17 +1182,17 @@ pref("browser.menu.showCharacterEncoding
 // Allow using tab-modal prompts when possible.
 pref("prompts.tab_modal.enabled", true);
 // Whether the Panorama should animate going in/out of tabs
 pref("browser.panorama.animate_zoom", true);
 
 // Defines the url to be used for new tabs.
 pref("browser.newtab.url", "about:newtab");
 // Activates preloading of the new tab url.
-pref("browser.newtab.preload", false);
+pref("browser.newtab.preload", true);
 
 // Toggles the content of 'about:newtab'. Shows the grid when enabled.
 pref("browser.newtabpage.enabled", true);
 
 // number of rows of newtab grid
 pref("browser.newtabpage.rows", 3);
 
 // number of columns of newtab grid
--- a/browser/base/content/browser-doctype.inc
+++ b/browser/base/content/browser-doctype.inc
@@ -14,12 +14,10 @@
 <!ENTITY % placesDTD SYSTEM "chrome://browser/locale/places/places.dtd">
 %placesDTD;
 #ifdef MOZ_SAFE_BROWSING
 <!ENTITY % safebrowsingDTD SYSTEM "chrome://browser/locale/safebrowsing/phishing-afterload-warning-message.dtd">
 %safebrowsingDTD;
 #endif
 <!ENTITY % aboutHomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd">
 %aboutHomeDTD;
-<!ENTITY % debuggerDTD SYSTEM "chrome://browser/locale/devtools/debugger.dtd">
-%debuggerDTD;
 ]>
 
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -90,17 +90,17 @@
     <command id="Browser:OpenLocation" oncommand="openLocation();"/>
     <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:ChromeDebugger" oncommand="BrowserDebuggerProcess.init();" disabled="true" hidden="true"/>
     <command id="Tools:BrowserConsole" oncommand="HUDConsoleUI.toggleBrowserConsole();"/>
     <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);"/>
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -110,20 +110,20 @@ XPCOMUtils.defineLazyGetter(this, "Popup
 });
 
 XPCOMUtils.defineLazyGetter(this, "DeveloperToolbar", function() {
   let tmp = {};
   Cu.import("resource:///modules/devtools/DeveloperToolbar.jsm", tmp);
   return new tmp.DeveloperToolbar(window, document.getElementById("developer-toolbar"));
 });
 
-XPCOMUtils.defineLazyGetter(this, "DebuggerUI", function() {
+XPCOMUtils.defineLazyGetter(this, "BrowserDebuggerProcess", function() {
   let tmp = {};
-  Cu.import("resource:///modules/devtools/DebuggerUI.jsm", tmp);
-  return new tmp.DebuggerUI(window);
+  Cu.import("resource:///modules/devtools/DebuggerProcess.jsm", tmp);
+  return tmp.BrowserDebuggerProcess;
 });
 
 XPCOMUtils.defineLazyModuleGetter(this, "Social",
   "resource:///modules/Social.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs",
   "resource://gre/modules/PageThumbs.jsm");
 
--- a/browser/base/content/test/newtab/browser_newtab_perwindow_private_browsing.js
+++ b/browser/base/content/test/newtab/browser_newtab_perwindow_private_browsing.js
@@ -40,19 +40,29 @@ function runTests() {
 
 var windowsToClose = [];
 function testOnWindow(options) {
   var win = OpenBrowserWindow(options);
   win.addEventListener("load", function onLoad() {
     win.removeEventListener("load", onLoad, false);
     windowsToClose.push(win);
     gWindow = win;
-    executeSoon(TestRunner.next);
+    whenDelayedStartupFinished(win, TestRunner.next);
   }, false);
 }
 
+function whenDelayedStartupFinished(win, callback) {
+  const topic = "browser-delayed-startup-finished";
+  Services.obs.addObserver(function onStartup(subject) {
+    if (win == subject) {
+      Services.obs.removeObserver(onStartup, topic);
+      executeSoon(callback);
+    }
+  }, topic, false);
+}
+
 registerCleanupFunction(function () {
   gWindow = window;
   windowsToClose.forEach(function(win) {
     win.close();
   });
 });
 
--- a/browser/devtools/commandline/test/browser_cmd_appcache_invalid.js
+++ b/browser/devtools/commandline/test/browser_cmd_appcache_invalid.js
@@ -33,17 +33,17 @@ function test() {
             output: [
               /Manifest has a character encoding of ISO-8859-1\. Manifests must have the utf-8 character encoding\./,
               /The first line of the manifest must be "CACHE MANIFEST" at line 1\./,
               /"CACHE MANIFEST" is only valid on the first line but was found at line 3\./,
               /images\/sound-icon\.png points to a resource that is not available at line 9\./,
               /images\/background\.png points to a resource that is not available at line 10\./,
               /NETWORK section line 13 \(\/checking\.cgi\) prevents caching of line 13 \(\/checking\.cgi\) in the NETWORK section\./,
               /\/checking\.cgi points to a resource that is not available at line 13\./,
-              /Asterisk \(\*\) incorrectly used in the NETWORK section at line 14\. If a line in the NETWORK section contains only a single asterisk character, then any URI not listed in the manifest will be treated as if the URI was listed in the NETWORK section\. Otherwice such URIs will be treated as unavailable\. Other uses of the \* character are prohibited/,
+              /Asterisk \(\*\) incorrectly used in the NETWORK section at line 14\. If a line in the NETWORK section contains only a single asterisk character, then any URI not listed in the manifest will be treated as if the URI was listed in the NETWORK section\. Otherwise such URIs will be treated as unavailable\. Other uses of the \* character are prohibited/,
               /\.\.\/rel\.html points to a resource that is not available at line 17\./,
               /\.\.\/\.\.\/rel\.html points to a resource that is not available at line 18\./,
               /\.\.\/\.\.\/\.\.\/rel\.html points to a resource that is not available at line 19\./,
               /\.\.\/\.\.\/\.\.\/\.\.\/rel\.html points to a resource that is not available at line 20\./,
               /\.\.\/\.\.\/\.\.\/\.\.\/\.\.\/rel\.html points to a resource that is not available at line 21\./,
               /\/\.\.\/ is not a valid URI prefix at line 22\./,
               /\/test\.css points to a resource that is not available at line 23\./,
               /\/test\.js points to a resource that is not available at line 24\./,
@@ -59,17 +59,17 @@ function test() {
               /\/settings\/home points to a resource that is not available at line 37\./,
               /\/settings\/app\.js points to a resource that is not available at line 38\./,
               /The file http:\/\/sub1\.test1\.example\.com\/browser\/browser\/devtools\/commandline\/test\/browser_cmd_appcache_invalid_page3\.html was modified after http:\/\/sub1\.test1\.example\.com\/browser\/browser\/devtools\/commandline\/test\/browser_cmd_appcache_invalid_appcache\.appcache\. Unless the text in the manifest file is changed the cached version will be used instead at line 39\./,
               /browser_cmd_appcache_invalid_page3\.html has cache-control set to no-store\. This will prevent the application cache from storing the file at line 39\./,
               /http:\/\/example\.com\/logo\.png points to a resource that is not available at line 40\./,
               /http:\/\/example\.com\/check\.png points to a resource that is not available at line 41\./,
               /Spaces in URIs need to be replaced with % at line 42\./,
               /http:\/\/example\.com\/cr oss\.png points to a resource that is not available at line 42\./,
-              /Asterisk \(\*\) incorrectly used in the CACHE section at line 43\. If a line in the NETWORK section contains only a single asterisk character, then any URI not listed in the manifest will be treated as if the URI was listed in the NETWORK section\. Otherwice such URIs will be treated as unavailable\. Other uses of the \* character are prohibited/,
+              /Asterisk \(\*\) incorrectly used in the CACHE section at line 43\. If a line in the NETWORK section contains only a single asterisk character, then any URI not listed in the manifest will be treated as if the URI was listed in the NETWORK section\. Otherwise such URIs will be treated as unavailable\. Other uses of the \* character are prohibited/,
               /The SETTINGS section may only contain a single value, "prefer-online" or "fast" at line 47\./,
               /FALLBACK section line 50 \(\/section1\/ \/offline1\.html\) prevents caching of line 30 \(\/section1\/blockedbyfallback\.html\) in the CACHE section\./,
               /\/offline1\.html points to a resource that is not available at line 50\./,
               /FALLBACK section line 51 \(\/section2\/ offline2\.html\) prevents caching of line 32 \(\/section2\/blockedbyfallback\.html\) in the CACHE section\./,
               /offline2\.html points to a resource that is not available at line 51\./,
               /Only two URIs separated by spaces are allowed in the FALLBACK section at line 52\./,
               /Asterisk \(\*\) incorrectly used in the FALLBACK section at line 53\. URIs in the FALLBACK section simply need to match a prefix of the request URI\./,
               /offline3\.html points to a resource that is not available at line 53\./,
--- a/browser/devtools/debugger/CmdDebugger.jsm
+++ b/browser/devtools/debugger/CmdDebugger.jsm
@@ -29,28 +29,28 @@ XPCOMUtils.defineLazyModuleGetter(this, 
  *          the label of a breakpoint could change
  * - url: The URL of the source file
  * - lineNumber: The line number of the breakpoint in the source file
  * - lineText: The text of the line at the breakpoint
  * - truncatedLineText: lineText truncated to MAX_LINE_TEXT_LENGTH
  */
 function getAllBreakpoints(dbg) {
   let breakpoints = [];
-  let SourceUtils = dbg.panelWin.SourceUtils;
+  let sources = dbg.panelWin.DebuggerView.Sources;
+  let { trimUrlLength: tr } = dbg.panelWin.SourceUtils;
 
-  for (let source in dbg.panelWin.DebuggerView.Sources) {
+  for (let source in sources) {
     for (let { attachment: breakpoint } in source) {
       breakpoints.push({
         id: source.value + ":" + breakpoint.lineNumber,
         label: source.label + ":" + breakpoint.lineNumber,
         url: source.value,
         lineNumber: breakpoint.lineNumber,
         lineText: breakpoint.lineText,
-        truncatedLineText: SourceUtils.trimUrlLength(breakpoint.lineText,
-                                                  MAX_LINE_TEXT_LENGTH, "end")
+        truncatedLineText: tr(breakpoint.lineText, MAX_LINE_TEXT_LENGTH, "end")
       });
     }
   }
 
   return breakpoints;
 }
 
 /**
@@ -194,21 +194,19 @@ gcli.addCommand({
   description: gcli.lookup("breakdelDesc"),
   params: [
     {
       name: "breakpoint",
       type: {
         name: "selection",
         lookup: function(context) {
           let dbg = getPanel(context, "jsdebugger");
-
           if (dbg == null) {
             return [];
           }
-
           return getAllBreakpoints(dbg).map(breakpoint => {
             return {
               name: breakpoint.label,
               value: breakpoint,
               description: breakpoint.truncatedLineText
             };
           });
         }
@@ -218,18 +216,18 @@ gcli.addCommand({
   ],
   returnType: "string",
   exec: function(args, context) {
     let dbg = getPanel(context, "jsdebugger");
     if (!dbg) {
       return gcli.lookup("debuggerStopped");
     }
 
-    let breakpoint = dbg.getBreakpoint(args.breakpoint.url,
-                                       args.breakpoint.lineNumber);
+    let breakpoint = dbg.getBreakpoint(
+      args.breakpoint.url, args.breakpoint.lineNumber);
 
     if (breakpoint == null) {
       return gcli.lookup("breakNotFound");
     }
 
     let deferred = context.defer();
     try {
       dbg.removeBreakpoint(breakpoint, function() {
--- a/browser/devtools/debugger/DebuggerPanel.jsm
+++ b/browser/devtools/debugger/DebuggerPanel.jsm
@@ -10,19 +10,16 @@ const { classes: Cc, interfaces: Ci, uti
 this.EXPORTED_SYMBOLS = ["DebuggerPanel"];
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource:///modules/devtools/shared/event-emitter.js");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
   "resource://gre/modules/commonjs/sdk/core/promise.js");
 
-XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
-  "resource://gre/modules/devtools/dbg-server.jsm");
-
 this.DebuggerPanel = function DebuggerPanel(iframeWindow, toolbox) {
   this.panelWin = iframeWindow;
   this._toolbox = toolbox;
 
   this._view = this.panelWin.DebuggerView;
   this._controller = this.panelWin.DebuggerController;
   this._controller._target = this.target;
   this._bkp = this._controller.Breakpoints;
rename from browser/devtools/debugger/DebuggerUI.jsm
rename to browser/devtools/debugger/DebuggerProcess.jsm
--- a/browser/devtools/debugger/DebuggerUI.jsm
+++ b/browser/devtools/debugger/DebuggerProcess.jsm
@@ -1,416 +1,56 @@
 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 const DBG_XUL = "chrome://browser/content/devtools/debugger.xul";
-const DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties";
 const CHROME_DEBUGGER_PROFILE_NAME = "-chrome-debugger";
 
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
+Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
 
-var require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
+let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 let Telemetry = require("devtools/shared/telemetry");
 
-XPCOMUtils.defineLazyModuleGetter(this,
-  "DebuggerServer", "resource://gre/modules/devtools/dbg-server.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this,
-  "Services", "resource://gre/modules/Services.jsm");
-
-this.EXPORTED_SYMBOLS = ["DebuggerUI"];
-
-/**
- * Provides a simple mechanism of managing debugger instances.
- *
- * @param nsIDOMWindow aWindow
- *        The chrome window for which the DebuggerUI instance is created.
- */
-this.DebuggerUI = function DebuggerUI(aWindow) {
-  this.chromeWindow = aWindow;
-  this.listenToTabs();
-};
-
-DebuggerUI.prototype = {
-  /**
-   * Update the status of tool's menuitems and buttons when
-   * the user switches tabs.
-   */
-  listenToTabs: function() {
-    let win = this.chromeWindow;
-    let tabs = win.gBrowser.tabContainer;
-
-    let bound_refreshCommand = this.refreshCommand.bind(this);
-    tabs.addEventListener("TabSelect", bound_refreshCommand, true);
-
-    win.addEventListener("unload", function onClose(aEvent) {
-      win.removeEventListener("unload", onClose, false);
-      tabs.removeEventListener("TabSelect", bound_refreshCommand, true);
-    }, false);
-  },
-
-  /**
-   * Called by the DebuggerPane to update the Debugger toggle switches with the
-   * debugger state.
-   */
-  refreshCommand: function() {
-    let scriptDebugger = this.getDebugger();
-    let command = this.chromeWindow.document.getElementById("Tools:Debugger");
-    let selectedTab = this.chromeWindow.gBrowser.selectedTab;
-
-    if (scriptDebugger && scriptDebugger.ownerTab === selectedTab) {
-      command.setAttribute("checked", "true");
-    } else {
-      command.setAttribute("checked", "false");
-    }
-  },
-
-  /**
-   * Starts a debugger for the current tab, or stops it if already started.
-   *
-   * @return DebuggerPane | null
-   *         The script debugger instance if it's started, null if stopped.
-   */
-  toggleDebugger: function() {
-    let scriptDebugger = this.findDebugger();
-    let selectedTab = this.chromeWindow.gBrowser.selectedTab;
-
-    if (scriptDebugger) {
-      scriptDebugger.close();
-      return null;
-    }
-    return new DebuggerPane(this, selectedTab);
-  },
-
-  /**
-   * Starts a remote debugger in a new window, or stops it if already started.
-   *
-   * @return RemoteDebuggerWindow | null
-   *         The remote debugger instance if it's started, null if stopped.
-   */
-  toggleRemoteDebugger: function() {
-    let remoteDebugger = this.getRemoteDebugger();
-
-    if (remoteDebugger) {
-      remoteDebugger.close();
-      return null;
-    }
-    return new RemoteDebuggerWindow(this);
-  },
-
-  /**
-   * Starts a chrome debugger in a new process, or stops it if already started.
-   *
-   * @return ChromeDebuggerProcess | null
-   *         The chrome debugger instance if it's started, null if stopped.
-   */
-  toggleChromeDebugger: function(aOnClose, aOnRun) {
-    let chromeDebugger = this.getChromeDebugger();
-
-    if (chromeDebugger) {
-      chromeDebugger.close();
-      return null;
-    }
-    return new ChromeDebuggerProcess(this, aOnClose, aOnRun);
-  },
-
-  /**
-   * Gets the current script debugger from any open window.
-   *
-   * @return DebuggerPane | null
-   *         The script debugger instance if it exists, null otherwise.
-   */
-  findDebugger: function() {
-    let enumerator = Services.wm.getEnumerator("navigator:browser");
-    while (enumerator.hasMoreElements()) {
-      let chromeWindow = enumerator.getNext().QueryInterface(Ci.nsIDOMWindow);
-      let scriptDebugger = chromeWindow.DebuggerUI.getDebugger();
-      if (scriptDebugger) {
-        return scriptDebugger;
-      }
-    }
-    return null;
-  },
-
-  /**
-   * Get the current script debugger.
-   *
-   * @return DebuggerPane | null
-   *         The script debugger instance if it exists, null otherwise.
-   */
-  getDebugger: function() {
-    return '_scriptDebugger' in this ? this._scriptDebugger : null;
-  },
-
-  /**
-   * Get the remote debugger for the current chrome window.
-   *
-   * @return RemoteDebuggerWindow | null
-   *         The remote debugger instance if it exists, null otherwise.
-   */
-  getRemoteDebugger: function() {
-    return '_remoteDebugger' in this ? this._remoteDebugger : null;
-  },
-
-  /**
-   * Get the chrome debugger for the current firefox instance.
-   *
-   * @return ChromeDebuggerProcess | null
-   *         The chrome debugger instance if it exists, null otherwise.
-   */
-  getChromeDebugger: function() {
-    return '_chromeDebugger' in this ? this._chromeDebugger : null;
-  }
-};
+this.EXPORTED_SYMBOLS = ["BrowserDebuggerProcess"];
 
 /**
- * Creates a pane that will host the debugger.
+ * Constructor for creating a process that will hold a chrome debugger.
  *
- * @param DebuggerUI aDebuggerUI
- *        The parent instance creating the new debugger.
- * @param XULElement aTab
- *        The tab in which to create the debugger.
+ * @param function aOnClose [optional]
+ *        A function called when the process stops running.
+ * @param function aOnRun [optional]
+ *        A function called when the process starts running.
  */
-this.DebuggerPane = function DebuggerPane(aDebuggerUI, aTab) {
-  this.globalUI = aDebuggerUI;
-  this._win = aDebuggerUI.chromeWindow;
-  this._tab = aTab;
-
-  this.close = this.close.bind(this);
-  this._initServer();
-  this._create();
-}
-
-DebuggerPane.prototype = {
-  /**
-   * Initializes the debugger server.
-   */
-  _initServer: function() {
-    if (!DebuggerServer.initialized) {
-      DebuggerServer.init();
-      DebuggerServer.addBrowserActors();
-    }
-  },
-
-  /**
-   * Creates and initializes the widgets containing the debugger UI.
-   */
-  _create: function() {
-    this.globalUI._scriptDebugger = this;
-
-    let gBrowser = this._win.gBrowser;
-    let ownerDocument = gBrowser.parentNode.ownerDocument;
-
-    this._splitter = ownerDocument.createElement("splitter");
-    this._splitter.setAttribute("class", "devtools-horizontal-splitter");
-
-    this._frame = ownerDocument.createElement("iframe");
-
-    this._nbox = gBrowser.getNotificationBox(this._tab.linkedBrowser);
-    this._nbox.appendChild(this._splitter);
-    this._nbox.appendChild(this._frame);
-
-    let self = this;
-
-    this._frame.addEventListener("Debugger:Loaded", function dbgLoaded() {
-      self._frame.removeEventListener("Debugger:Loaded", dbgLoaded, true);
-      self._frame.addEventListener("Debugger:Unloaded", self.close, true);
-
-      // Bind shortcuts for accessing the breakpoint methods in the debugger.
-      let bkp = self.contentWindow.DebuggerController.Breakpoints;
-      self.addBreakpoint = bkp.addBreakpoint;
-      self.removeBreakpoint = bkp.removeBreakpoint;
-      self.getBreakpoint = bkp.getBreakpoint;
-      self.breakpoints = bkp.store;
-    }, true);
-
-    this._frame.setAttribute("src", DBG_XUL);
-    this.globalUI.refreshCommand();
-  },
-
-  /**
-   * Closes the debugger, removing child nodes and event listeners.
-   *
-   * @param function aCloseCallback
-   *        Clients can pass a close callback to be notified when
-   *        the panel successfully closes.
-   */
-  close: function(aCloseCallback) {
-    if (!this.globalUI) {
-      return;
-    }
-    delete this.globalUI._scriptDebugger;
-
-    // This method is also used as an event handler, so only
-    // use aCloseCallback if it's a function.
-    if (typeof aCloseCallback == "function") {
-      let frame = this._frame;
-      frame.addEventListener("unload", function onUnload() {
-        frame.removeEventListener("unload", onUnload, true);
-        aCloseCallback();
-      }, true)
-    }
-
-    this._frame.removeEventListener("Debugger:Unloaded", this.close, true);
-    this._nbox.removeChild(this._splitter);
-    this._nbox.removeChild(this._frame);
-
-    this._splitter = null;
-    this._frame = null;
-    this._nbox = null;
-    this._win = null;
-    this._tab = null;
-
-    // Remove shortcuts for accessing the breakpoint methods in the debugger.
-    delete this.addBreakpoint;
-    delete this.removeBreakpoint;
-    delete this.getBreakpoint;
-    delete this.breakpoints;
-
-    this.globalUI.refreshCommand();
-    this.globalUI = null;
-  },
-
-  /**
-   * Gets the chrome window owning this debugger instance.
-   * @return XULWindow
-   */
-  get ownerWindow() {
-    return this._win;
-  },
-
-  /**
-   * Gets the tab owning this debugger instance.
-   * @return XULElement
-   */
-  get ownerTab() {
-    return this._tab;
-  },
-
-  /**
-   * Gets the debugger content window.
-   * @return nsIDOMWindow
-   */
-  get contentWindow() {
-    return this._frame ? this._frame.contentWindow : null;
-  }
-};
-
-/**
- * Creates a window that will host a remote debugger.
- *
- * @param DebuggerUI aDebuggerUI
- *        The parent instance creating the new debugger.
- */
-this.RemoteDebuggerWindow = function RemoteDebuggerWindow(aDebuggerUI) {
-  this.globalUI = aDebuggerUI;
-  this._win = aDebuggerUI.chromeWindow;
-
-  this.close = this.close.bind(this);
-  this._create();
-}
-
-RemoteDebuggerWindow.prototype = {
-  /**
-   * Creates and initializes the widgets containing the remote debugger UI.
-   */
-  _create: function() {
-    this.globalUI._remoteDebugger = this;
-
-    this._dbgwin = this.globalUI.chromeWindow.open(DBG_XUL,
-      L10N.getStr("remoteDebuggerWindowTitle"), "chrome,dependent,resizable");
-
-    let self = this;
-
-    this._dbgwin.addEventListener("Debugger:Loaded", function dbgLoaded() {
-      self._dbgwin.removeEventListener("Debugger:Loaded", dbgLoaded, true);
-      self._dbgwin.addEventListener("Debugger:Unloaded", self.close, true);
-
-      // Bind shortcuts for accessing the breakpoint methods in the debugger.
-      let bkp = self.contentWindow.DebuggerController.Breakpoints;
-      self.addBreakpoint = bkp.addBreakpoint;
-      self.removeBreakpoint = bkp.removeBreakpoint;
-      self.getBreakpoint = bkp.getBreakpoint;
-      self.breakpoints = bkp.store;
-    }, true);
-
-    this._dbgwin._remoteFlag = true;
-  },
-
-  /**
-   * Closes the remote debugger, along with the parent window if necessary.
-   */
-  close: function() {
-    if (!this.globalUI) {
-      return;
-    }
-    delete this.globalUI._remoteDebugger;
-
-    this._dbgwin.removeEventListener("Debugger:Unloaded", this.close, true);
-    this._dbgwin.close();
-    this._dbgwin = null;
-    this._win = null;
-
-    // Remove shortcuts for accessing the breakpoint methods in the debugger.
-    delete this.addBreakpoint;
-    delete this.removeBreakpoint;
-    delete this.getBreakpoint;
-    delete this.breakpoints;
-
-    this.globalUI = null;
-  },
-
-  /**
-   * Gets the chrome window owning this debugger instance.
-   * @return XULWindow
-   */
-  get ownerWindow() {
-    return this._win;
-  },
-
-  /**
-   * Gets the remote debugger content window.
-   * @return nsIDOMWindow.
-   */
-  get contentWindow() {
-    return this._dbgwin;
-  }
-};
-
-/**
- * Creates a process that will hold a chrome debugger.
- *
- * @param DebuggerUI aDebuggerUI
- *        The parent instance creating the new debugger.
- * @param function aOnClose
- *        Optional, a function called when the process exits.
- * @param function aOnRun
- *        Optional, a function called when the process starts running.
- */
-this.ChromeDebuggerProcess = function ChromeDebuggerProcess(aDebuggerUI, aOnClose, aOnRun) {
-  this.globalUI = aDebuggerUI;
-  this._win = aDebuggerUI.chromeWindow;
+this.BrowserDebuggerProcess = function BrowserDebuggerProcess(aOnClose, aOnRun) {
   this._closeCallback = aOnClose;
   this._runCallback = aOnRun;
   this._telemetry = new Telemetry();
 
   this._initServer();
   this._initProfile();
   this._create();
 }
 
-ChromeDebuggerProcess.prototype = {
+/**
+ * Initializes and starts a chrome debugger process.
+ * @return object
+ */
+BrowserDebuggerProcess.init = function(aOnClose, aOnRun) {
+  return new BrowserDebuggerProcess(aOnClose, aOnRun);
+};
+
+BrowserDebuggerProcess.prototype = {
   /**
    * Initializes the debugger server.
    */
   _initServer: function() {
     if (!DebuggerServer.initialized) {
       DebuggerServer.init();
       DebuggerServer.addBrowserActors();
     }
@@ -426,17 +66,19 @@ ChromeDebuggerProcess.prototype = {
 
     let profileName;
     try {
       // Attempt to get the required chrome debugging profile name string.
       profileName = profileService.selectedProfile.name + CHROME_DEBUGGER_PROFILE_NAME;
     } catch (e) {
       // Requested profile string could not be retrieved.
       profileName = CHROME_DEBUGGER_PROFILE_NAME;
-      Cu.reportError(e);
+      let msg = "Querying the current profile failed. " + e.name + ": " + e.message;
+      dumpn(msg);
+      Cu.reportError(msg);
     }
 
     let profileObject;
     try {
       // Attempt to get the required chrome debugging profile toolkit object.
       profileObject = profileService.getProfileByName(profileName);
 
       // The profile exists but the corresponding folder may have been deleted.
@@ -448,112 +90,70 @@ ChromeDebuggerProcess.prototype = {
           this._dbgProfile = profileObject;
           return;
         }
       }
       // Requested profile was found but the folder was deleted. Cleanup needed.
       profileObject.remove(true);
     } catch (e) {
       // Requested profile object was not found.
-      Cu.reportError(e);
+      let msg = "Creating a profile failed. " + e.name + ": " + e.message;
+      dumpn(msg);
+      Cu.reportError(msg);
     }
 
     // Create a new chrome debugging profile.
     this._dbgProfile = profileService.createProfile(null, null, profileName);
     profileService.flush();
   },
 
   /**
    * Creates and initializes the profile & process for the remote debugger.
    */
   _create: function() {
-    this.globalUI._chromeDebugger = this;
-
-    let file = Services.dirsvc.get("XREExeF", Ci.nsIFile);
-
-    dumpn("Initializing chrome debugging process");
-    let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
-    process.init(file);
+    dumpn("Initializing chrome debugging process.");
+    let process = this._dbgProcess = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
+    process.init(Services.dirsvc.get("XREExeF", Ci.nsIFile));
 
-    let args = [
-      "-no-remote", "-P", this._dbgProfile.name,
-      "-chrome", DBG_XUL];
-
-    dumpn("Running chrome debugging process");
-    process.runwAsync(args, args.length, { observe: this.close.bind(this) });
-    this._dbgProcess = process;
+    dumpn("Running chrome debugging process.");
+    let args = ["-no-remote", "-foreground", "-P", this._dbgProfile.name, "-chrome", DBG_XUL];
+    process.runwAsync(args, args.length, { observe: () => this.close() });
 
     this._telemetry.toolOpened("jsbrowserdebugger");
 
+    dumpn("Chrome debugger is now running...");
     if (typeof this._runCallback == "function") {
       this._runCallback.call({}, this);
     }
   },
 
   /**
    * Closes the remote debugger, removing the profile and killing the process.
    */
   close: function() {
-    dumpn("Closing chrome debugging process");
-
-    this._telemetry.toolClosed("jsbrowserdebugger");
-
-    if (!this.globalUI) {
-      dumpn("globalUI is missing");
-      return;
-    }
-    delete this.globalUI._chromeDebugger;
-
     if (this._dbgProcess.isRunning) {
       dumpn("Killing chrome debugging process...");
       this._dbgProcess.kill();
     }
-    dumpn("...done.");
+
+    this._telemetry.toolClosed("jsbrowserdebugger");
+
+    dumpn("Chrome debugger is now closed...");
     if (typeof this._closeCallback == "function") {
       this._closeCallback.call({}, this);
     }
-
-    this._dbgProcess = null;
-    this._dbgProfile = null;
-    this._win = null;
-
-    this.globalUI = null;
   }
 };
 
 /**
- * Localization convenience methods.
- */
-let L10N = {
-  /**
-   * L10N shortcut function.
-   *
-   * @param string aName
-   * @return string
-   */
-  getStr: function(aName) {
-    return this.stringBundle.GetStringFromName(aName);
-  }
-};
-
-XPCOMUtils.defineLazyGetter(L10N, "stringBundle", function() {
-  return Services.strings.createBundle(DBG_STRINGS_URI);
-});
-
-/**
  * Shortcuts for accessing various debugger preferences.
  */
-let Prefs = {};
-
-/**
- * Gets the preferred default remote browser debugging port.
- * @return number
- */
-XPCOMUtils.defineLazyGetter(Prefs, "chromeDebuggingPort", function() {
-  return Services.prefs.getIntPref("devtools.debugger.chrome-debugging-port");
+let Prefs = new ViewHelpers.Prefs("devtools.debugger", {
+  chromeDebuggingHost: ["Char", "chrome-debugging-host"],
+  chromeDebuggingPort: ["Int", "chrome-debugging-port"]
 });
 
 /**
  * Helper method for debugging.
  * @param string
  */
 function dumpn(str) {
   if (wantLogging) {
--- a/browser/devtools/debugger/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -11,17 +11,16 @@ const DBG_STRINGS_URI = "chrome://browse
 const NEW_SOURCE_IGNORED_URLS = ["debugger eval code", "self-hosted", "XStringBundle"];
 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
 
 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");
 Cu.import("resource:///modules/devtools/BreadcrumbsWidget.jsm");
 Cu.import("resource:///modules/devtools/SideMenuWidget.jsm");
 Cu.import("resource:///modules/devtools/VariablesView.jsm");
 Cu.import("resource:///modules/devtools/VariablesViewController.jsm");
@@ -62,41 +61,28 @@ let DebuggerController = {
    * @return object
    *         A promise that is resolved when the debugger finishes startup.
    */
   startupDebugger: function() {
     if (this._isInitialized) {
       return this._startup.promise;
     }
     this._isInitialized = true;
-    window.removeEventListener("DOMContentLoaded", this.startupDebugger, true);
+
+    // Chrome debugging lives in a different process and needs to handle
+    // debugger startup by itself.
+    if (window._isChromeDebugger) {
+      window.removeEventListener("DOMContentLoaded", this.startupDebugger, true);
+    }
 
     let deferred = this._startup = Promise.defer();
 
     DebuggerView.initialize(() => {
       DebuggerView._isInitialized = true;
 
-      VariablesViewController.attach(DebuggerView.Variables, {
-        getGripClient: aObject => {
-          return this.activeThread.pauseGrip(aObject);
-        }
-      });
-
-      // Relay events from the VariablesView.
-      DebuggerView.Variables.on("fetched", (aEvent, aType) => {
-        switch (aType) {
-          case "variables":
-            window.dispatchEvent(document, "Debugger:FetchedVariables");
-            break;
-          case "properties":
-            window.dispatchEvent(document, "Debugger:FetchedProperties");
-            break;
-        }
-      });
-
       // Chrome debugging needs to initiate the connection by itself.
       if (window._isChromeDebugger) {
         this.connect().then(deferred.resolve);
       } else {
         deferred.resolve();
       }
     });
 
@@ -110,22 +96,28 @@ let DebuggerController = {
    *         A promise that is resolved when the debugger finishes shutdown.
    */
   shutdownDebugger: function() {
     if (this._isDestroyed) {
       return this._shutdown.promise;
     }
     this._isDestroyed = true;
     this._startup = null;
-    window.removeEventListener("unload", this.shutdownDebugger, true);
+
+    // Chrome debugging lives in a different process and needs to handle
+    // debugger shutdown by itself.
+    if (window._isChromeDebugger) {
+      window.removeEventListener("unload", this.shutdownDebugger, true);
+    }
 
     let deferred = this._shutdown = Promise.defer();
 
     DebuggerView.destroy(() => {
       DebuggerView._isDestroyed = true;
+
       this.SourceScripts.disconnect();
       this.StackFrames.disconnect();
       this.ThreadState.disconnect();
 
       this.disconnect();
       deferred.resolve();
 
       // Chrome debugging needs to close its parent process on shutdown.
@@ -163,19 +155,19 @@ let DebuggerController = {
         this._startChromeDebugging(client, form.chromeDebugger, deferred.resolve);
       } else {
         this._startDebuggingTab(client, threadActor, deferred.resolve);
       }
 
       return deferred.promise;
     }
 
-    // Chrome debugging needs to make the connection to the debuggee.
-    let transport = debuggerSocketConnect(Prefs.chromeDebuggingHost,
-                                          Prefs.chromeDebuggingPort);
+    // Chrome debugging needs to make its own connection to the debuggee.
+    let transport = debuggerSocketConnect(
+      Prefs.chromeDebuggingHost, Prefs.chromeDebuggingPort);
 
     let client = new DebuggerClient(transport);
     client.addListener("tabNavigated", this._onTabNavigated);
     client.addListener("tabDetached", this._onTabDetached);
 
     client.connect((aType, aTraits) => {
       client.listTabs((aResponse) => {
         this._startChromeDebugging(client, aResponse.chromeDebugger, deferred.resolve);
@@ -190,17 +182,18 @@ let DebuggerController = {
    */
   disconnect: function() {
     // Return early if the client didn't even have a chance to instantiate.
     if (!this.client) {
       return;
     }
 
     // When debugging local or a remote instance, the connection is closed by
-    // the RemoteTarget.
+    // the RemoteTarget. Chrome debugging needs to specifically close its own
+    // connection to the debuggee.
     if (window._isChromeDebugger) {
       this.client.removeListener("tabNavigated", this._onTabNavigated);
       this.client.removeListener("tabDetached", this._onTabDetached);
       this.client.close();
     }
 
     this._connection = null;
     this.client = null;
@@ -234,16 +227,25 @@ let DebuggerController = {
   /**
    * Called when the debugged tab is closed.
    */
   _onTabDetached: function() {
     this.shutdownDebugger();
   },
 
   /**
+   * Warn if resuming execution produced a wrongOrder error.
+   */
+  _ensureResumptionOrder: function(aResponse) {
+    if (aResponse.error == "wrongOrder") {
+      DebuggerView.Toolbar.showResumeWarning(aResponse.lastPausedUrl);
+    }
+  },
+
+  /**
    * Sets up a debugging session.
    *
    * @param DebuggerClient aClient
    *        The debugger client.
    * @param string aThreadActor
    *        The remote protocol grip of the tab.
    * @param function aCallback
    *        A function to invoke once the client attached to the active thread.
@@ -269,25 +271,16 @@ let DebuggerController = {
 
       if (aCallback) {
         aCallback();
       }
     }, { useSourceMaps: Prefs.sourceMapsEnabled });
   },
 
   /**
-   * Warn if resuming execution produced a wrongOrder error.
-   */
-  _ensureResumptionOrder: function(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.
@@ -325,20 +318,19 @@ let DebuggerController = {
     this.client.reconfigureThread(aUseSourceMaps, (aResponse) => {
       if (aResponse.error) {
         let msg = "Couldn't reconfigure thread: " + aResponse.message;
         Cu.reportError(msg);
         dumpn(msg);
         return;
       }
 
-      // Update the source list widget.
-      DebuggerView.Sources.empty();
-      SourceUtils.clearCache();
+      DebuggerView._handleTabNavigation();
       this.SourceScripts._handleTabNavigation();
+
       // Update the stack frame list.
       this.activeThread._clearFrames();
       this.activeThread.fillFrames(CALL_STACK_PAGE_SIZE);
     });
   },
 
   /**
    * Attempts to quit the current process if allowed.
@@ -411,23 +403,22 @@ ThreadState.prototype = {
   },
 
   /**
    * Update the UI after a thread state change.
    */
   _update: function(aEvent) {
     DebuggerView.Toolbar.toggleResumeButtonState(this.activeThread.state);
 
-    if (DebuggerController._target && (aEvent == "paused" || aEvent == "resumed")) {
-      DebuggerController._target.emit("thread-" + aEvent);
+    if (gTarget && (aEvent == "paused" || aEvent == "resumed")) {
+      gTarget.emit("thread-" + aEvent);
     }
   }
 };
 
-
 /**
  * Keeps the stack frame list up-to-date, using the thread client's
  * stack frame cache.
  */
 function StackFrames() {
   this._onPaused = this._onPaused.bind(this);
   this._onResumed = this._onResumed.bind(this);
   this._onFrames = this._onFrames.bind(this);
@@ -539,16 +530,17 @@ StackFrames.prototype = {
     // Ignore useless notifications.
     if (!this.activeThread.cachedFrames.length) {
       return;
     }
 
     // Conditional breakpoints are { breakpoint, expression } tuples. The
     // boolean evaluation of the expression decides if the active thread
     // automatically resumes execution or not.
+    // TODO: handle all of this server-side: Bug 812172.
     if (this.currentBreakpointLocation) {
       let { url, line } = this.currentBreakpointLocation;
       let breakpointClient = DebuggerController.Breakpoints.getBreakpoint(url, line);
       if (breakpointClient) {
         // Make sure a breakpoint actually exists at the specified url and line.
         let conditionalExpression = breakpointClient.conditionalExpression;
         if (conditionalExpression) {
           // Evaluating the current breakpoint's conditional expression will
@@ -569,49 +561,46 @@ StackFrames.prototype = {
         this.activeThread.resume(DebuggerController._ensureResumptionOrder);
         return;
       }
     }
 
 
     // Watch expressions are evaluated in the context of the topmost frame,
     // and the results are displayed in the variables view.
+    // TODO: handle all of this server-side: Bug 832470, comment 14.
     if (this.currentWatchExpressions) {
       // Evaluation causes the stack frames to be cleared and active thread to
-      // pause, sending a 'clientEvaluated' packed and adding the frames again.
+      // pause, sending a 'clientEvaluated' packet and adding the frames again.
       this.evaluate(this.currentWatchExpressions, 0);
       this._isWatchExpressionsEvaluation = true;
       return;
     }
     // Got our evaluation of the current watch expressions.
     if (this._isWatchExpressionsEvaluation) {
       this._isWatchExpressionsEvaluation = false;
       // If an error was thrown during the evaluation of the watch expressions,
-      // then at least one expression evaluation could not be performed.
+      // then at least one expression evaluation could not be performed. So
+      // remove the most recent watch expression and try again.
       if (this.currentEvaluation.throw) {
-        DebuggerView.WatchExpressions.removeExpressionAt(0);
+        DebuggerView.WatchExpressions.removeAt(0);
         DebuggerController.StackFrames.syncWatchExpressions();
         return;
       }
-      // If the watch expressions were evaluated successfully, attach
-      // the results to the topmost frame.
-      let topmostFrame = this.activeThread.cachedFrames[0];
-      topmostFrame.watchExpressionsEvaluation = this.currentEvaluation.return;
     }
 
 
     // Make sure the debugger view panes are visible.
     DebuggerView.showInstrumentsPane();
 
     // Make sure all the previous stackframes are removed before re-adding them.
     DebuggerView.StackFrames.empty();
 
     for (let frame of this.activeThread.cachedFrames) {
-      let depth = frame.depth;
-      let { url, line } = frame.where;
+      let { depth, where: { url, line } } = frame;
       let frameLocation = NetworkHelper.convertToUnicode(unescape(url));
       let frameTitle = StackFrameUtils.getFrameTitle(frame);
 
       DebuggerView.StackFrames.addFrame(frameTitle, frameLocation, line, depth);
     }
     if (this.currentFrame == null) {
       DebuggerView.StackFrames.selectedDepth = 0;
     }
@@ -655,24 +644,24 @@ StackFrames.prototype = {
   /**
    * Marks the stack frame at the specified depth as selected and updates the
    * properties view with the stack frame's data.
    *
    * @param number aDepth
    *        The depth of the frame in the stack.
    */
   selectFrame: function(aDepth) {
+    // Make sure the frame at the specified depth exists first.
     let frame = this.activeThread.cachedFrames[this.currentFrame = aDepth];
     if (!frame) {
       return;
     }
-    let { environment, watchExpressionsEvaluation } = frame;
-    let { url, line } = frame.where;
 
     // Check if the frame does not represent the evaluation of debuggee code.
+    let { environment, where: { url, line } } = frame;
     if (!environment) {
       return;
     }
 
     // Move the editor's caret to the proper url and line.
     DebuggerView.updateEditor(url, line);
     // Highlight the breakpoint at the specified url and line if it exists.
     DebuggerView.Sources.highlightBreakpoint(url, line);
@@ -681,96 +670,83 @@ StackFrames.prototype = {
     // Start recording any added variables or properties in any scope.
     DebuggerView.Variables.createHierarchy();
     // Clear existing scopes and create each one dynamically.
     DebuggerView.Variables.empty();
 
 
     // If watch expressions evaluation results are available, create a scope
     // to contain all the values.
-    if (this.syncedWatchExpressions && watchExpressionsEvaluation) {
+    if (this.syncedWatchExpressions && aDepth == 0) {
       let label = L10N.getStr("watchExpressionsScopeLabel");
       let scope = DebuggerView.Variables.addScope(label);
 
       // Customize the scope for holding watch expressions evaluations.
       scope.descriptorTooltip = false;
       scope.contextMenuId = "debuggerWatchExpressionsContextMenu";
       scope.separatorStr = L10N.getStr("watchExpressionsSeparatorLabel");
       scope.switch = DebuggerView.WatchExpressions.switchExpression;
       scope.delete = DebuggerView.WatchExpressions.deleteExpression;
 
-      // The evaluation hasn't thrown, so display the returned results and
-      // always expand the watch expressions scope by default.
-      this._fetchWatchExpressions(scope, watchExpressionsEvaluation);
+      // The evaluation hasn't thrown, so fetch and add the returned results.
+      this._fetchWatchExpressions(scope, this.currentEvaluation.return);
+
+      // The watch expressions scope is always automatically expanded.
       scope.expand();
     }
 
     do {
-      // Create a scope to contain all the inspected variables.
+      // Create a scope to contain all the inspected variables in the
+      // current environment.
       let label = StackFrameUtils.getScopeLabel(environment);
       let scope = DebuggerView.Variables.addScope(label);
       let innermost = environment == frame.environment;
 
       // Handle special additions to the innermost scope.
       if (innermost) {
         this._insertScopeFrameReferences(scope, frame);
       }
 
+      // Handle the expansion of the scope, lazily populating it with the
+      // variables in the current environment.
       DebuggerView.Variables.controller.addExpander(scope, environment);
 
       // The innermost scope is always automatically expanded, because it
       // contains the variables in the current stack frame which are likely to
       // be inspected.
       if (innermost || this.autoScopeExpand) {
         scope.expand();
       }
     } while ((environment = environment.parent));
 
     // Signal that variables have been fetched.
     window.dispatchEvent(document, "Debugger:FetchedVariables");
     DebuggerView.Variables.commitHierarchy();
   },
 
   /**
-   * Adds the watch expressions evaluation results to a scope in the view.
-   *
-   * @param Scope aScope
-   *        The scope where the watch expressions will be placed into.
-   * @param object aExp
-   *        The grip of the evaluation results.
+   * Loads more stack frames from the debugger server cache.
    */
-  _fetchWatchExpressions: function(aScope, aExp) {
-    // Fetch the expressions only once.
-    if (aScope._fetched) {
-      return;
-    }
-    aScope._fetched = true;
+  addMoreFrames: function() {
+    this.activeThread.fillFrames(
+      this.activeThread.cachedFrames.length + CALL_STACK_PAGE_SIZE);
+  },
 
-    // Add nodes for every watch expression in scope.
-    this.activeThread.pauseGrip(aExp).getPrototypeAndProperties((aResponse) => {
-      let ownProperties = aResponse.ownProperties;
-      let totalExpressions = DebuggerView.WatchExpressions.itemCount;
-
-      for (let i = 0; i < totalExpressions; i++) {
-        let name = DebuggerView.WatchExpressions.getExpression(i);
-        let expVal = ownProperties[i].value;
-        let expRef = aScope.addItem(name, ownProperties[i]);
-        DebuggerView.Variables.controller.addExpander(expRef, expVal);
-
-        // Revert some of the custom watch expressions scope presentation flags.
-        expRef.switch = null;
-        expRef.delete = null;
-        expRef.descriptorTooltip = true;
-        expRef.separatorStr = L10N.getStr("variablesSeparatorLabel");
-      }
-
-      // Signal that watch expressions have been fetched.
-      window.dispatchEvent(document, "Debugger:FetchedWatchExpressions");
-      DebuggerView.Variables.commitHierarchy();
-    });
+  /**
+   * Evaluate an expression in the context of the selected frame. This is used
+   * for modifying the value of variables or properties in scope.
+   *
+   * @param string aExpression
+   *        The expression to evaluate.
+   * @param number aFrame [optional]
+   *        The frame depth used for evaluation.
+   */
+  evaluate: function(aExpression, aFrame = this.currentFrame || 0) {
+    let frame = this.activeThread.cachedFrames[aFrame];
+    this.activeThread.eval(frame.actor, aExpression);
   },
 
   /**
    * Add nodes for special frame references in the innermost scope.
    *
    * @param Scope aScope
    *        The scope where the references will be placed into.
    * @param object aFrame
@@ -790,28 +766,61 @@ StackFrames.prototype = {
     // Add "this".
     if (aFrame.this) {
       let thisRef = aScope.addItem("this", { value: aFrame.this });
       DebuggerView.Variables.controller.addExpander(thisRef, aFrame.this);
     }
   },
 
   /**
-   * Loads more stack frames from the debugger server cache.
+   * Adds the watch expressions evaluation results to a scope in the view.
+   *
+   * @param Scope aScope
+   *        The scope where the watch expressions will be placed into.
+   * @param object aExp
+   *        The grip of the evaluation results.
    */
-  addMoreFrames: function() {
-    this.activeThread.fillFrames(
-      this.activeThread.cachedFrames.length + CALL_STACK_PAGE_SIZE);
+  _fetchWatchExpressions: function(aScope, aExp) {
+    // Fetch the expressions only once.
+    if (aScope._fetched) {
+      return;
+    }
+    aScope._fetched = true;
+
+    // Add nodes for every watch expression in scope.
+    this.activeThread.pauseGrip(aExp).getPrototypeAndProperties((aResponse) => {
+      let ownProperties = aResponse.ownProperties;
+      let totalExpressions = DebuggerView.WatchExpressions.itemCount;
+
+      for (let i = 0; i < totalExpressions; i++) {
+        let name = DebuggerView.WatchExpressions.getString(i);
+        let expVal = ownProperties[i].value;
+        let expRef = aScope.addItem(name, ownProperties[i]);
+        DebuggerView.Variables.controller.addExpander(expRef, expVal);
+
+        // Revert some of the custom watch expressions scope presentation flags,
+        // so that they don't propagate to child items.
+        expRef.switch = null;
+        expRef.delete = null;
+        expRef.descriptorTooltip = true;
+        expRef.separatorStr = L10N.getStr("variablesSeparatorLabel");
+      }
+
+      // Signal that watch expressions have been fetched.
+      window.dispatchEvent(document, "Debugger:FetchedWatchExpressions");
+      DebuggerView.Variables.commitHierarchy();
+    });
   },
 
   /**
    * Updates a list of watch expressions to evaluate on each pause.
+   * TODO: handle all of this server-side: Bug 832470, comment 14.
    */
   syncWatchExpressions: function() {
-    let list = DebuggerView.WatchExpressions.getExpressions();
+    let list = DebuggerView.WatchExpressions.getAllStrings();
 
     // Sanity check all watch expressions before syncing them. To avoid
     // having the whole watch expressions array throw because of a single
     // faulty expression, simply convert it to a string describing the error.
     // There's no other information necessary to be offered in such cases.
     let sanitizedExpressions = list.map(function(str) {
       // Reflect.parse throws when it encounters a syntax error.
       try {
@@ -829,52 +838,38 @@ StackFrames.prototype = {
             sanitizedExpressions.map(function(str)
               "eval(\"" +
                 "try {" +
                   // Make sure all quotes are escaped in the expression's syntax,
                   // and add a newline after the statement to avoid comments
                   // breaking the code integrity inside the eval block.
                   str.replace(/"/g, "\\$&") + "\" + " + "'\\n'" + " + \"" +
                 "} catch (e) {" +
-                  "e.name + ': ' + e.message;" + // FIXME: bug 812765, 812764
+                  "e.name + ': ' + e.message;" + // TODO: Bug 812765, 812764.
                 "}" +
               "\")"
             ).join(",") +
           "]";
     } else {
       this.syncedWatchExpressions =
         this.currentWatchExpressions = null;
     }
     this.currentFrame = null;
     this._onFrames();
-  },
-
-  /**
-   * Evaluate an expression in the context of the selected frame. This is used
-   * for modifying the value of variables or properties in scope.
-   *
-   * @param string aExpression
-   *        The expression to evaluate.
-   * @param number aFrame [optional]
-   *        The frame depth used for evaluation.
-   */
-  evaluate: function(aExpression, aFrame = this.currentFrame || 0) {
-    let frame = this.activeThread.cachedFrames[aFrame];
-    this.activeThread.eval(frame.actor, aExpression);
   }
 };
 
 /**
  * Keeps the source script list up-to-date, using the thread client's
  * source script cache.
  */
 function SourceScripts() {
   this._cache = new Map(); // Can't use a WeakMap because keys are strings.
+  this._onNewGlobal = this._onNewGlobal.bind(this);
   this._onNewSource = this._onNewSource.bind(this);
-  this._onNewGlobal = this._onNewGlobal.bind(this);
   this._onSourcesAdded = this._onSourcesAdded.bind(this);
   this._onFetch = this._onFetch.bind(this);
   this._onTimeout = this._onTimeout.bind(this);
   this._onFinished = this._onFinished.bind(this);
 }
 
 SourceScripts.prototype = {
   get activeThread() DebuggerController.activeThread,
@@ -967,17 +962,17 @@ SourceScripts.prototype = {
     window.dispatchEvent(document, "Debugger:AfterNewSource");
   },
 
   /**
    * Callback for the debugger's active thread getSources() method.
    */
   _onSourcesAdded: function(aResponse) {
     if (aResponse.error) {
-      Cu.reportError(new Error("Error getting sources: " + aResponse.message));
+      Cu.reportError("Error getting sources: " + aResponse.message);
       return;
     }
 
     // Add all the sources in the debugger view sources container.
     for (let source of aResponse.sources) {
       // Ignore bogus scripts, e.g. generated from 'clientEvaluate' packets.
       if (NEW_SOURCE_IGNORED_URLS.indexOf(source.url) != -1) {
         continue;
@@ -1280,33 +1275,35 @@ Breakpoints.prototype = {
     let line = aEditorBreakpoint.line + 1;
 
     this.removeBreakpoint(this.getBreakpoint(url, line));
   },
 
   /**
    * Update the breakpoints in the editor view. This function takes the list of
    * breakpoints in the debugger and adds them back into the editor view.
-   * This is invoked when the selected script is changed.
+   * This is invoked when the selected script is changed, or when new sources
+   * are received via the _onNewSource and _onSourcesAdded event listeners.
    */
   updateEditorBreakpoints: function() {
     for each (let breakpointClient in this.store) {
       if (DebuggerView.Sources.selectedValue == breakpointClient.location.url) {
         this._showBreakpoint(breakpointClient, {
           noPaneUpdate: true,
           noPaneHighlight: true
         });
       }
     }
   },
 
   /**
    * Update the breakpoints in the pane view. This function takes the list of
    * breakpoints in the debugger and adds them back into the breakpoints pane.
-   * This is invoked when scripts are added.
+   * This is invoked when new sources are received via the _onNewSource and
+   * _onSourcesAdded event listeners.
    */
   updatePaneBreakpoints: function() {
     for each (let breakpointClient in this.store) {
       if (DebuggerView.Sources.containsValue(breakpointClient.location.url)) {
         this._showBreakpoint(breakpointClient, {
           noEditorUpdate: true,
           noPaneHighlight: true
         });
@@ -1331,16 +1328,21 @@ Breakpoints.prototype = {
    *        An object containing some of the following boolean properties:
    *          - conditionalExpression: tells this breakpoint's conditional expression
    *          - openPopup: tells if the expression popup should be shown
    *          - noEditorUpdate: tells if you want to skip editor updates
    *          - noPaneUpdate: tells if you want to skip breakpoint pane updates
    *          - noPaneHighlight: tells if you don't want to highlight the breakpoint
    */
   addBreakpoint: function(aLocation, aCallback, aFlags = {}) {
+    // Make sure a proper location is available.
+    if (!aLocation) {
+      aCallback && aCallback(null, new Error("Invalid breakpoint location."));
+      return;
+    }
     let breakpointClient = this.getBreakpoint(aLocation.url, aLocation.line);
 
     // If the breakpoint was already added, callback immediately.
     if (breakpointClient) {
       aCallback && aCallback(breakpointClient);
       return;
     }
 
@@ -1370,19 +1372,20 @@ Breakpoints.prototype = {
       }
 
       // Remember the breakpoint client in the store.
       this.store[aBreakpointClient.actor] = aBreakpointClient;
 
       // Attach any specified conditional expression to the breakpoint client.
       aBreakpointClient.conditionalExpression = aFlags.conditionalExpression;
 
-      // Preserve information about the breakpoint's line text, to display it in
-      // the sources pane without requiring fetching the source.
-      aBreakpointClient.lineText = DebuggerView.getEditorLine(line - 1).trim();
+      // Preserve information about the breakpoint's line text, to display it
+      // in the sources pane without requiring fetching the source (for example,
+      // after the target navigated).
+      aBreakpointClient.lineText = DebuggerView.getEditorLineText(line - 1).trim();
 
       // Show the breakpoint in the editor and breakpoints pane.
       this._showBreakpoint(aBreakpointClient, aFlags);
 
       // We're done here.
       aCallback && aCallback(aBreakpointClient, aResponse.error);
     });
   },
@@ -1395,17 +1398,22 @@ Breakpoints.prototype = {
    * @param function aCallback [optional]
    *        Optional function to invoke once the breakpoint is removed. The
    *        callback is invoked with one argument
    *          - aBreakpointClient: the breakpoint location (url and line)
    * @param object aFlags [optional]
    *        @see DebuggerController.Breakpoints.addBreakpoint
    */
   removeBreakpoint: function(aBreakpointClient, aCallback, aFlags = {}) {
-    let breakpointActor = (aBreakpointClient || {}).actor;
+    // Make sure a proper breakpoint client is available.
+    if (!aBreakpointClient) {
+      aCallback && aCallback(null, new Error("Invalid breakpoint client."));
+      return;
+    }
+    let breakpointActor = aBreakpointClient.actor;
 
     // If the breakpoint was already removed, callback immediately.
     if (!this.store[breakpointActor]) {
       aCallback && aCallback(aBreakpointClient.location);
       return;
     }
 
     aBreakpointClient.remove(() => {
@@ -1449,16 +1457,19 @@ Breakpoints.prototype = {
         actor: aBreakpointClient.actor,
         openPopupFlag: aFlags.openPopup
       });
     }
     // Highlight the breakpoint in the pane if required.
     if (!aFlags.noPaneHighlight) {
       DebuggerView.Sources.highlightBreakpoint(url, line, aFlags);
     }
+
+    // Notify that we've shown a breakpoint.
+    window.dispatchEvent(document, "Debugger:BreakpointShown", aBreakpointClient);
   },
 
   /**
    * Update the editor and breakpoints pane to hide a specified breakpoint.
    *
    * @param object aBreakpointClient
    *        The BreakpointActor client object to hide.
    * @param object aFlags [optional]
@@ -1475,27 +1486,30 @@ Breakpoints.prototype = {
         this.editor.removeBreakpoint(line - 1);
         this._skipEditorBreakpointCallbacks = false;
       }
     }
     // Update the breakpoints pane if required.
     if (!aFlags.noPaneUpdate) {
       DebuggerView.Sources.removeBreakpoint(url, line);
     }
+
+    // Notify that we've hidden a breakpoint.
+    window.dispatchEvent(document, "Debugger:BreakpointHidden", aBreakpointClient);
   },
 
   /**
-   * Get the breakpoint object at the given location.
+   * Get the BreakpointActor client object at the given location.
    *
    * @param string aUrl
    *        The URL of where the breakpoint is.
    * @param number aLine
    *        The line number where the breakpoint is.
    * @return object
-   *         The BreakpointActor object.
+   *         The BreakpointActor client object.
    */
   getBreakpoint: function(aUrl, aLine) {
     for each (let breakpointClient in this.store) {
       if (breakpointClient.location.url == aUrl &&
           breakpointClient.location.line == aLine) {
         return breakpointClient;
       }
     }
@@ -1509,78 +1523,58 @@ Breakpoints.prototype = {
 let L10N = new ViewHelpers.L10N(DBG_STRINGS_URI);
 
 /**
  * Shortcuts for accessing various debugger preferences.
  */
 let Prefs = new ViewHelpers.Prefs("devtools.debugger", {
   chromeDebuggingHost: ["Char", "chrome-debugging-host"],
   chromeDebuggingPort: ["Int", "chrome-debugging-port"],
-  windowX: ["Int", "ui.win-x"],
-  windowY: ["Int", "ui.win-y"],
-  windowWidth: ["Int", "ui.win-width"],
-  windowHeight: ["Int", "ui.win-height"],
   sourcesWidth: ["Int", "ui.panes-sources-width"],
   instrumentsWidth: ["Int", "ui.panes-instruments-width"],
-  pauseOnExceptions: ["Bool", "ui.pause-on-exceptions"],
   panesVisibleOnStartup: ["Bool", "ui.panes-visible-on-startup"],
   variablesSortingEnabled: ["Bool", "ui.variables-sorting-enabled"],
   variablesOnlyEnumVisible: ["Bool", "ui.variables-only-enum-visible"],
   variablesSearchboxVisible: ["Bool", "ui.variables-searchbox-visible"],
-  sourceMapsEnabled: ["Bool", "source-maps-enabled"],
-  remoteHost: ["Char", "remote-host"],
-  remotePort: ["Int", "remote-port"],
-  remoteAutoConnect: ["Bool", "remote-autoconnect"],
-  remoteConnectionRetries: ["Int", "remote-connection-retries"],
-  remoteTimeout: ["Int", "remote-timeout"]
-});
-
-/**
- * Returns true if this is a remote debugger instance.
- * @return boolean
- */
-XPCOMUtils.defineLazyGetter(window, "_isRemoteDebugger", function() {
-  // We're inside a single top level XUL window, not an iframe container.
-  return !(window.frameElement instanceof XULElement) &&
-         !!window._remoteFlag;
+  pauseOnExceptions: ["Bool", "pause-on-exceptions"],
+  sourceMapsEnabled: ["Bool", "source-maps-enabled"]
 });
 
 /**
  * Returns true if this is a chrome debugger instance.
  * @return boolean
  */
 XPCOMUtils.defineLazyGetter(window, "_isChromeDebugger", function() {
-  // We're inside a single top level XUL window, but not a remote debugger.
-  return !(window.frameElement instanceof XULElement) &&
-         !window._remoteFlag;
+  // We're inside a single top level XUL window in a different process.
+  return !(window.frameElement instanceof XULElement);
 });
 
 /**
  * Preliminary setup for the DebuggerController object.
  */
 DebuggerController.initialize();
 DebuggerController.Parser = new Parser();
 DebuggerController.ThreadState = new ThreadState();
 DebuggerController.StackFrames = new StackFrames();
 DebuggerController.SourceScripts = new SourceScripts();
 DebuggerController.Breakpoints = new Breakpoints();
 
 /**
  * Export some properties to the global scope for easier access.
  */
 Object.defineProperties(window, {
-  "create": {
-    get: function() ViewHelpers.create,
-  },
   "dispatchEvent": {
     get: function() ViewHelpers.dispatchEvent,
   },
   "editor": {
     get: function() DebuggerView.editor
   },
+  "gTarget": {
+    get: function() DebuggerController._target
+  },
   "gClient": {
     get: function() DebuggerController.client
   },
   "gThreadClient": {
     get: function() DebuggerController.activeThread
   },
   "gThreadState": {
     get: function() DebuggerController.ThreadState
--- a/browser/devtools/debugger/debugger-panes.js
+++ b/browser/devtools/debugger/debugger-panes.js
@@ -6,54 +6,53 @@
 "use strict";
 
 /**
  * Functions handling the sources UI.
  */
 function SourcesView() {
   dumpn("SourcesView was instantiated");
 
-  this._breakpointsCache = new Map(); // Can't use a WeakMap because keys are strings.
-  this._onBreakpointRemoved = this._onBreakpointRemoved.bind(this);
   this._onEditorLoad = this._onEditorLoad.bind(this);
   this._onEditorUnload = this._onEditorUnload.bind(this);
   this._onEditorSelection = this._onEditorSelection.bind(this);
   this._onEditorContextMenu = this._onEditorContextMenu.bind(this);
   this._onSourceSelect = this._onSourceSelect.bind(this);
   this._onSourceClick = this._onSourceClick.bind(this);
+  this._onBreakpointRemoved = this._onBreakpointRemoved.bind(this);
   this._onBreakpointClick = this._onBreakpointClick.bind(this);
   this._onBreakpointCheckboxClick = this._onBreakpointCheckboxClick.bind(this);
   this._onConditionalPopupShowing = this._onConditionalPopupShowing.bind(this);
   this._onConditionalPopupShown = this._onConditionalPopupShown.bind(this);
   this._onConditionalPopupHiding = this._onConditionalPopupHiding.bind(this);
   this._onConditionalTextboxInput = this._onConditionalTextboxInput.bind(this);
   this._onConditionalTextboxKeyPress = this._onConditionalTextboxKeyPress.bind(this);
 }
 
-create({ constructor: SourcesView, proto: MenuContainer.prototype }, {
+SourcesView.prototype = Heritage.extend(WidgetMethods, {
   /**
    * Initialization function, called when the debugger is started.
    */
   initialize: function() {
     dumpn("Initializing the SourcesView");
 
-    this.node = new SideMenuWidget(document.getElementById("sources"));
+    this.widget = new SideMenuWidget(document.getElementById("sources"));
     this.emptyText = L10N.getStr("noSourcesText");
     this.unavailableText = L10N.getStr("noMatchingSourcesText");
 
     this._commandset = document.getElementById("debuggerCommands");
     this._popupset = document.getElementById("debuggerPopupset");
     this._cmPopup = document.getElementById("sourceEditorContextMenu");
     this._cbPanel = document.getElementById("conditional-breakpoint-panel");
     this._cbTextbox = document.getElementById("conditional-breakpoint-panel-textbox");
 
     window.addEventListener("Debugger:EditorLoaded", this._onEditorLoad, false);
     window.addEventListener("Debugger:EditorUnloaded", this._onEditorUnload, false);
-    this.node.addEventListener("select", this._onSourceSelect, false);
-    this.node.addEventListener("click", this._onSourceClick, false);
+    this.widget.addEventListener("select", this._onSourceSelect, false);
+    this.widget.addEventListener("click", this._onSourceClick, false);
     this._cbPanel.addEventListener("popupshowing", this._onConditionalPopupShowing, false);
     this._cbPanel.addEventListener("popupshown", this._onConditionalPopupShown, false);
     this._cbPanel.addEventListener("popuphiding", this._onConditionalPopupHiding, false);
     this._cbTextbox.addEventListener("input", this._onConditionalTextboxInput, false);
     this._cbTextbox.addEventListener("keypress", this._onConditionalTextboxKeyPress, false);
 
     this.autoFocusOnSelection = false;
 
@@ -64,18 +63,18 @@ create({ constructor: SourcesView, proto
   /**
    * Destruction function, called when the debugger is closed.
    */
   destroy: function() {
     dumpn("Destroying the SourcesView");
 
     window.removeEventListener("Debugger:EditorLoaded", this._onEditorLoad, false);
     window.removeEventListener("Debugger:EditorUnloaded", this._onEditorUnload, false);
-    this.node.removeEventListener("select", this._onSourceSelect, false);
-    this.node.removeEventListener("click", this._onSourceClick, false);
+    this.widget.removeEventListener("select", this._onSourceSelect, false);
+    this.widget.removeEventListener("click", this._onSourceClick, false);
     this._cbPanel.removeEventListener("popupshowing", this._onConditionalPopupShowing, false);
     this._cbPanel.removeEventListener("popupshowing", this._onConditionalPopupShown, false);
     this._cbPanel.removeEventListener("popuphiding", this._onConditionalPopupHiding, false);
     this._cbTextbox.removeEventListener("input", this._onConditionalTextboxInput, false);
     this._cbTextbox.removeEventListener("keypress", this._onConditionalTextboxKeyPress, false);
   },
 
   /**
@@ -102,17 +101,17 @@ create({ constructor: SourcesView, proto
    *        - forced: force the source to be immediately added
    */
   addSource: function(aSource, aOptions = {}) {
     let url = aSource.url;
     let label = SourceUtils.getSourceLabel(url.split(" -> ").pop());
     let group = SourceUtils.getSourceGroup(url.split(" -> ").pop());
 
     // Append a source item to this container.
-    let sourceItem = this.push([label, url, group], {
+    this.push([label, url, group], {
       staged: aOptions.staged, /* stage the item to be appended later? */
       attachment: {
         source: aSource
       }
     });
   },
 
   /**
@@ -145,30 +144,28 @@ create({ constructor: SourcesView, proto
     let sourceItem = this.getItemByValue(url);
 
     // Create the element node and menu popup for the breakpoint item.
     let breakpointView = this._createBreakpointView.call(this, aOptions);
     let contextMenu = this._createContextMenu.call(this, aOptions);
 
     // Append a breakpoint child item to the corresponding source item.
     let breakpointItem = sourceItem.append(breakpointView.container, {
-      attachment: Object.create(aOptions, {
-        view: { value: breakpointView },
-        popup: { value: contextMenu }
+      attachment: Heritage.extend(aOptions, {
+        view: breakpointView,
+        popup: contextMenu
       }),
       attributes: [
         ["contextmenu", contextMenu.menupopupId]
       ],
       // Make sure that when the breakpoint item is removed, the corresponding
       // menupopup and commandset are also destroyed.
       finalize: this._onBreakpointRemoved
     });
 
-    this._breakpointsCache.set(this._getBreakpointKey(url, line), breakpointItem);
-
     // If this is a conditional breakpoint, display a panel to input the
     // corresponding conditional expression.
     if (aOptions.openPopupFlag) {
       this.highlightBreakpoint(url, line, { openPopup: true });
     }
   },
 
   /**
@@ -200,22 +197,23 @@ create({ constructor: SourcesView, proto
 
   /**
    * Returns the breakpoint at the specified source location and line number.
    *
    * @param string aSourceLocation
    *        The breakpoint source location.
    * @param number aLineNumber
    *        The breakpoint line number.
-   * @return MenuItem
+   * @return object
    *         The corresponding breakpoint item if found, null otherwise.
    */
   getBreakpoint: function(aSourceLocation, aLineNumber) {
-    let breakpointKey = this._getBreakpointKey(aSourceLocation, aLineNumber);
-    return this._breakpointsCache.get(breakpointKey);
+    return this.getItemForPredicate((aItem) =>
+      aItem.attachment.sourceLocation == aSourceLocation &&
+      aItem.attachment.lineNumber == aLineNumber);
   },
 
   /**
    * Enables a breakpoint.
    *
    * @param string aSourceLocation
    *        The breakpoint source location.
    * @param number aLineNumber
@@ -235,29 +233,32 @@ create({ constructor: SourcesView, proto
     if (!breakpointItem) {
       return false;
     }
 
     // Set a new id to the corresponding breakpoint element if required.
     if (aOptions.id) {
       breakpointItem.attachment.view.container.id = "breakpoint-" + aOptions.id;
     }
-
     // Update the checkbox state if necessary.
     if (!aOptions.silent) {
       breakpointItem.attachment.view.checkbox.setAttribute("checked", "true");
     }
 
     let { sourceLocation: url, lineNumber: line } = breakpointItem.attachment;
     let breakpointLocation = { url: url, line: line };
-    DebuggerController.Breakpoints.addBreakpoint(breakpointLocation, aOptions.callback, {
-      noPaneUpdate: true,
-      noPaneHighlight: true,
-      conditionalExpression: breakpointItem.attachment.conditionalExpression
-    });
+
+    // Only create a new breakpoint if it doesn't exist yet.
+    if (!DebuggerController.Breakpoints.getBreakpoint(url, line)) {
+      DebuggerController.Breakpoints.addBreakpoint(breakpointLocation, aOptions.callback, {
+        noPaneUpdate: true,
+        noPaneHighlight: true,
+        conditionalExpression: breakpointItem.attachment.conditionalExpression
+      });
+    }
 
     // Breakpoint is now enabled.
     breakpointItem.attachment.disabled = false;
     return true;
   },
 
   /**
    * Disables a breakpoint.
@@ -283,22 +284,26 @@ create({ constructor: SourcesView, proto
 
     // Update the checkbox state if necessary.
     if (!aOptions.silent) {
       breakpointItem.attachment.view.checkbox.removeAttribute("checked");
     }
 
     let { sourceLocation: url, lineNumber: line } = breakpointItem.attachment;
     let breakpointClient = DebuggerController.Breakpoints.getBreakpoint(url, line);
-    DebuggerController.Breakpoints.removeBreakpoint(breakpointClient, aOptions.callback, {
-      noPaneUpdate: true
-    });
 
-    // Remember the conditional expression for when the breakpoint is enabled.
-    breakpointItem.attachment.conditionalExpression = breakpointClient.conditionalExpression;
+    // Only remove the breakpoint if it exists.
+    if (breakpointClient) {
+      DebuggerController.Breakpoints.removeBreakpoint(breakpointClient, aOptions.callback, {
+        noPaneUpdate: true
+      });
+      // Remember the current conditional expression, to be reapplied when the
+      // breakpoint is re-enabled via enableBreakpoint().
+      breakpointItem.attachment.conditionalExpression = breakpointClient.conditionalExpression;
+    }
 
     // Breakpoint is now disabled.
     breakpointItem.attachment.disabled = true;
     return true;
   },
 
   /**
    * Highlights a breakpoint in this sources container.
@@ -342,74 +347,74 @@ create({ constructor: SourcesView, proto
     this._unselectBreakpoint();
     this._hideConditionalPopup();
   },
 
   /**
    * Gets the currently selected breakpoint item.
    * @return object
    */
-  get selectedBreakpoint() this._selectedBreakpoint,
+  get selectedBreakpointItem() this._selectedBreakpoint,
 
   /**
    * Gets the currently selected breakpoint client.
    * @return object
    */
-  get selectedClient() {
+  get selectedBreakpointClient() {
     let breakpointItem = this._selectedBreakpoint;
     if (breakpointItem) {
       let { sourceLocation: url, lineNumber: line } = breakpointItem.attachment;
       return DebuggerController.Breakpoints.getBreakpoint(url, line);
     }
     return null;
   },
 
   /**
    * Marks a breakpoint as selected in this sources container.
    *
-   * @param MenuItem aItem
+   * @param object aItem
    *        The breakpoint item to select.
    */
   _selectBreakpoint: function(aItem) {
     if (this._selectedBreakpoint == aItem) {
       return;
     }
     this._unselectBreakpoint();
     this._selectedBreakpoint = aItem;
-    this._selectedBreakpoint.markSelected();
+    this._selectedBreakpoint.target.classList.add("selected");
 
     // Ensure the currently selected breakpoint is visible.
-    this.node.ensureElementIsVisible(aItem.target);
+    this.widget.ensureElementIsVisible(aItem.target);
   },
 
   /**
    * Marks the current breakpoint as unselected in this sources container.
    */
   _unselectBreakpoint: function() {
     if (this._selectedBreakpoint) {
-      this._selectedBreakpoint.markDeselected();
+      this._selectedBreakpoint.target.classList.remove("selected");
       this._selectedBreakpoint = null;
     }
   },
 
   /**
    * Opens a conditional breakpoint's expression input popup.
    */
   _openConditionalPopup: function() {
-    let selectedBreakpoint = this.selectedBreakpoint;
-    let selectedClient = this.selectedClient;
+    let selectedBreakpointItem = this.selectedBreakpointItem;
+    let selectedBreakpointClient = this.selectedBreakpointClient;
 
-    if (selectedClient.conditionalExpression === undefined) {
-      this._cbTextbox.value = selectedClient.conditionalExpression = "";
+    if (selectedBreakpointClient.conditionalExpression === undefined) {
+      this._cbTextbox.value = selectedBreakpointClient.conditionalExpression = "";
     } else {
-      this._cbTextbox.value = selectedClient.conditionalExpression;
+      this._cbTextbox.value = selectedBreakpointClient.conditionalExpression;
     }
 
     this._cbPanel.hidden = false;
-    this._cbPanel.openPopup(this.selectedBreakpoint.attachment.view.lineNumber,
+    this._cbPanel.openPopup(selectedBreakpointItem.attachment.view.lineNumber,
       BREAKPOINT_CONDITIONAL_POPUP_POSITION,
       BREAKPOINT_CONDITIONAL_POPUP_OFFSET_X,
       BREAKPOINT_CONDITIONAL_POPUP_OFFSET_Y);
   },
 
   /**
    * Hides a conditional breakpoint's expression input popup.
    */
@@ -525,69 +530,53 @@ create({ constructor: SourcesView, proto
       let menuitem = document.createElement("menuitem");
       let command = document.createElement("command");
 
       let prefix = "bp-cMenu-"; // "breakpoints context menu"
       let commandId = prefix + aName + "-" + aOptions.actor + "-command";
       let menuitemId = prefix + aName + "-" + aOptions.actor + "-menuitem";
 
       let label = L10N.getStr("breakpointMenuItem." + aName);
-      let func = this["_on" + aName.charAt(0).toUpperCase() + aName.slice(1)];
+      let func = "_on" + aName.charAt(0).toUpperCase() + aName.slice(1);
 
       command.id = commandId;
       command.setAttribute("label", label);
-      command.addEventListener("command", func.bind(this, aOptions), false);
+      command.addEventListener("command", () => this[func](aOptions.actor), false);
 
       menuitem.id = menuitemId;
       menuitem.setAttribute("command", commandId);
-      menuitem.setAttribute("command", commandId);
-      menuitem.setAttribute("hidden", aHiddenFlag);
+      aHiddenFlag && menuitem.setAttribute("hidden", "true");
 
       commandset.appendChild(command);
       menupopup.appendChild(menuitem);
     }
 
     /**
      * Creates a simple menu separator element and appends it to the current
      * menupopup hierarchy.
      */
     function createMenuSeparator() {
       let menuseparator = document.createElement("menuseparator");
       menupopup.appendChild(menuseparator);
     }
   },
 
   /**
-   * Destroys the context menu for a breakpoint.
-   *
-   * @param object aContextMenu
-   *        An object containing the breakpoint commandset and menu popup ids.
-   */
-  _destroyContextMenu: function(aContextMenu) {
-    dumpn("Destroying context menu: " +
-      aContextMenu.commandsetId + " & " + aContextMenu.menupopupId);
-
-    let commandset = document.getElementById(aContextMenu.commandsetId);
-    let menupopup = document.getElementById(aContextMenu.menupopupId);
-    commandset.parentNode.removeChild(commandset);
-    menupopup.parentNode.removeChild(menupopup);
-  },
-
-  /**
    * Function called each time a breakpoint item is removed.
    *
-   * @param MenuItem aItem
-   *        The corresponding menu item.
+   * @param object aItem
+   *        The corresponding item.
    */
   _onBreakpointRemoved: function(aItem) {
     dumpn("Finalizing breakpoint item: " + aItem);
 
-    let { sourceLocation: url, lineNumber: line, popup } = aItem.attachment;
-    this._destroyContextMenu(popup);
-    this._breakpointsCache.delete(this._getBreakpointKey(url, line));
+    // Destroy the context menu for the breakpoint.
+    let contextMenu = aItem.attachment.popup;
+    document.getElementById(contextMenu.commandsetId).remove();
+    document.getElementById(contextMenu.menupopupId).remove();
   },
 
   /**
    * The load listener for the source editor.
    */
   _onEditorLoad: function({ detail: editor }) {
     editor.addEventListener("Selection", this._onEditorSelection, false);
     editor.addEventListener("ContextMenu", this._onEditorContextMenu, false);
@@ -625,22 +614,23 @@ create({ constructor: SourcesView, proto
     let offset = DebuggerView.editor.getOffsetAtLocation(x, y);
     let line = DebuggerView.editor.getLineAtOffset(offset);
     this._editorContextMenuLineNumber = line;
   },
 
   /**
    * The select listener for the sources container.
    */
-  _onSourceSelect: function() {
-    if (!this.refresh()) {
+  _onSourceSelect: function({ detail: sourceItem }) {
+    if (!sourceItem) {
       return;
     }
+    // The container is not empty and an actual item was selected.
+    let selectedSource = sourceItem.attachment.source;
 
-    let selectedSource = this.selectedItem.attachment.source;
     if (DebuggerView.editorSource != selectedSource) {
       DebuggerView.editorSource = selectedSource;
     }
   },
 
   /**
    * The click listener for the sources container.
    */
@@ -651,16 +641,17 @@ create({ constructor: SourcesView, proto
 
   /**
    * The click listener for a breakpoint container.
    */
   _onBreakpointClick: function(e) {
     let sourceItem = this.getItemForElement(e.target);
     let breakpointItem = this.getItemForElement.call(sourceItem, e.target);
     let { sourceLocation: url, lineNumber: line } = breakpointItem.attachment;
+
     let breakpointClient = DebuggerController.Breakpoints.getBreakpoint(url, line);
     let conditionalExpression = (breakpointClient || {}).conditionalExpression;
 
     this.highlightBreakpoint(url, line, {
       updateEditor: true,
       openPopup: conditionalExpression !== undefined && e.button == 0
     });
   },
@@ -672,17 +663,17 @@ create({ constructor: SourcesView, proto
     let sourceItem = this.getItemForElement(e.target);
     let breakpointItem = this.getItemForElement.call(sourceItem, e.target);
     let { sourceLocation: url, lineNumber: line, disabled } = breakpointItem.attachment;
 
     this[disabled ? "enableBreakpoint" : "disableBreakpoint"](url, line, {
       silent: true
     });
 
-    // Don't update the editor location (propagate into DVS__onBreakpointClick).
+    // Don't update the editor location (avoid propagating into _onBreakpointClick).
     e.preventDefault();
     e.stopPropagation();
   },
 
   /**
    * The popup showing listener for the breakpoints conditional expression panel.
    */
   _onConditionalPopupShowing: function() {
@@ -703,17 +694,17 @@ create({ constructor: SourcesView, proto
   _onConditionalPopupHiding: function() {
     this._conditionalPopupVisible = false;
   },
 
   /**
    * The input listener for the breakpoints conditional expression textbox.
    */
   _onConditionalTextboxInput: function() {
-    this.selectedClient.conditionalExpression = this._cbTextbox.value;
+    this.selectedBreakpointClient.conditionalExpression = this._cbTextbox.value;
   },
 
   /**
    * The keypress listener for the breakpoints conditional expression textbox.
    */
   _onConditionalTextboxKeyPress: function(e) {
     if (e.keyCode == e.DOM_VK_RETURN || e.keyCode == e.DOM_VK_ENTER) {
       this._hideConditionalPopup();
@@ -774,167 +765,243 @@ create({ constructor: SourcesView, proto
       DebuggerController.Breakpoints.addBreakpoint({ url: url, line: line }, null, {
         conditionalExpression: "",
         openPopup: true
       });
     }
   },
 
   /**
-   * Listener handling the "setConditional" menuitem command.
+   * Function invoked on the "setConditional" menuitem command.
    *
-   * @param object aDetails
-   *        The breakpoint details (sourceLocation, lineNumber etc.).
+   * @param string aId
+   *        The original breakpoint client actor. If a breakpoint was disabled
+   *        and then re-enabled, then this will not correspond to the entry in
+   *        the controller's breakpoints store.
+   * @param function aCallback [optional]
+   *        A function to invoke once this operation finishes.
    */
-  _onSetConditional: function(aDetails) {
-    let { sourceLocation: url, lineNumber: line, actor } = aDetails;
-    let breakpointItem = this.getBreakpoint(url, line);
+  _onSetConditional: function(aId, aCallback = () => {}) {
+    let targetBreakpoint = this.getItemForPredicate(aItem => aItem.attachment.actor == aId);
+    let { sourceLocation: url, lineNumber: line } = targetBreakpoint.attachment;
+
+    // Highlight the breakpoint and show a conditional expression popup.
     this.highlightBreakpoint(url, line, { openPopup: true });
+
+    // Breakpoint is now highlighted.
+    aCallback();
   },
 
   /**
-   * Listener handling the "enableSelf" menuitem command.
+   * Function invoked on the "enableSelf" menuitem command.
    *
-   * @param object aDetails
-   *        The breakpoint details (sourceLocation, lineNumber etc.).
+   * @param string aId
+   *        The original breakpoint client actor. If a breakpoint was disabled
+   *        and then re-enabled, then this will not correspond to the entry in
+   *        the controller's breakpoints store.
+   * @param function aCallback [optional]
+   *        A function to invoke once this operation finishes.
    */
-  _onEnableSelf: function(aDetails) {
-    let { sourceLocation: url, lineNumber: line, actor } = aDetails;
+  _onEnableSelf: function(aId, aCallback = () => {}) {
+    let targetBreakpoint = this.getItemForPredicate(aItem => aItem.attachment.actor == aId);
+    let { sourceLocation: url, lineNumber: line, actor } = targetBreakpoint.attachment;
 
+    // Enable the breakpoint, in this container and the controller store.
     if (this.enableBreakpoint(url, line)) {
       let prefix = "bp-cMenu-"; // "breakpoints context menu"
       let enableSelfId = prefix + "enableSelf-" + actor + "-menuitem";
       let disableSelfId = prefix + "disableSelf-" + actor + "-menuitem";
       document.getElementById(enableSelfId).setAttribute("hidden", "true");
       document.getElementById(disableSelfId).removeAttribute("hidden");
+
+      // Breakpoint is now enabled.
+      // Breakpoints can only be set while the debuggee is paused, so if the
+      // active thread wasn't paused, wait for a resume before continuing.
+      if (gThreadClient.state != "paused") {
+        gThreadClient.addOneTimeListener("resumed", aCallback);
+      } else {
+        aCallback();
+      }
     }
   },
 
   /**
-   * Listener handling the "disableSelf" menuitem command.
+   * Function invoked on the "disableSelf" menuitem command.
    *
-   * @param object aDetails
-   *        The breakpoint details (sourceLocation, lineNumber etc.).
+   * @param string aId
+   *        The original breakpoint client actor. If a breakpoint was disabled
+   *        and then re-enabled, then this will not correspond to the entry in
+   *        the controller's breakpoints store.
+   * @param function aCallback [optional]
+   *        A function to invoke once this operation finishes.
    */
-  _onDisableSelf: function(aDetails) {
-    let { sourceLocation: url, lineNumber: line, actor } = aDetails;
+  _onDisableSelf: function(aId, aCallback = () => {}) {
+    let targetBreakpoint = this.getItemForPredicate(aItem => aItem.attachment.actor == aId);
+    let { sourceLocation: url, lineNumber: line, actor } = targetBreakpoint.attachment;
 
+    // Disable the breakpoint, in this container and the controller store.
     if (this.disableBreakpoint(url, line)) {
       let prefix = "bp-cMenu-"; // "breakpoints context menu"
       let enableSelfId = prefix + "enableSelf-" + actor + "-menuitem";
       let disableSelfId = prefix + "disableSelf-" + actor + "-menuitem";
       document.getElementById(enableSelfId).removeAttribute("hidden");
       document.getElementById(disableSelfId).setAttribute("hidden", "true");
-    }
-  },
 
-  /**
-   * Listener handling the "deleteSelf" menuitem command.
-   *
-   * @param object aDetails
-   *        The breakpoint details (sourceLocation, lineNumber etc.).
-   */
-  _onDeleteSelf: function(aDetails) {
-    let { sourceLocation: url, lineNumber: line } = aDetails;
-    let breakpointClient = DebuggerController.Breakpoints.getBreakpoint(url, line);
-
-    this.removeBreakpoint(url, line);
-    DebuggerController.Breakpoints.removeBreakpoint(breakpointClient);
-  },
-
-  /**
-   * Listener handling the "enableOthers" menuitem command.
-   *
-   * @param object aDetails
-   *        The breakpoint details (sourceLocation, lineNumber etc.).
-   */
-  _onEnableOthers: function(aDetails) {
-    for (let [, item] of this._breakpointsCache) {
-      if (item.attachment.actor != aDetails.actor) {
-        this._onEnableSelf(item.attachment);
-      }
-    }
-  },
-
-  /**
-   * Listener handling the "disableOthers" menuitem command.
-   *
-   * @param object aDetails
-   *        The breakpoint details (sourceLocation, lineNumber etc.).
-   */
-  _onDisableOthers: function(aDetails) {
-    for (let [, item] of this._breakpointsCache) {
-      if (item.attachment.actor != aDetails.actor) {
-        this._onDisableSelf(item.attachment);
-      }
+      // Breakpoint is now disabled.
+      aCallback();
     }
   },
 
   /**
-   * Listener handling the "deleteOthers" menuitem command.
+   * Function invoked on the "deleteSelf" menuitem command.
    *
-   * @param object aDetails
-   *        The breakpoint details (sourceLocation, lineNumber etc.).
+   * @param string aId
+   *        The original breakpoint client actor. If a breakpoint was disabled
+   *        and then re-enabled, then this will not correspond to the entry in
+   *        the controller's breakpoints store.
+   * @param function aCallback [optional]
+   *        A function to invoke once this operation finishes.
    */
-  _onDeleteOthers: function(aDetails) {
-    for (let [, item] of this._breakpointsCache) {
-      if (item.attachment.actor != aDetails.actor) {
-        this._onDeleteSelf(item.attachment);
-      }
-    }
+  _onDeleteSelf: function(aId, aCallback = () => {}) {
+    let targetBreakpoint = this.getItemForPredicate(aItem => aItem.attachment.actor == aId);
+    let { sourceLocation: url, lineNumber: line } = targetBreakpoint.attachment;
+
+    // Remove the breakpoint, from this container and the controller store.
+    this.removeBreakpoint(url, line);
+    gBreakpoints.removeBreakpoint(gBreakpoints.getBreakpoint(url, line), aCallback);
   },
 
   /**
-   * Listener handling the "enableAll" menuitem command.
+   * Function invoked on the "enableOthers" menuitem command.
    *
-   * @param object aDetails
-   *        The breakpoint details (sourceLocation, lineNumber etc.).
+   * @param string aId
+   *        The original breakpoint client actor. If a breakpoint was disabled
+   *        and then re-enabled, then this will not correspond to the entry in
+   *        the controller's breakpoints store.
+   * @param function aCallback [optional]
+   *        A function to invoke once this operation finishes.
    */
-  _onEnableAll: function(aDetails) {
-    this._onEnableOthers(aDetails);
-    this._onEnableSelf(aDetails);
+  _onEnableOthers: function(aId, aCallback = () => {}) {
+    let targetBreakpoint = this.getItemForPredicate(aItem => aItem.attachment.actor == aId);
+
+    // Find a disabled breakpoint and re-enable it. Do this recursively until
+    // all required breakpoints are enabled, because each operation is async.
+    for (let source in this) {
+      for (let otherBreakpoint in source) {
+        if (otherBreakpoint != targetBreakpoint &&
+            otherBreakpoint.attachment.disabled) {
+          this._onEnableSelf(otherBreakpoint.attachment.actor, () =>
+            this._onEnableOthers(aId, aCallback));
+          return;
+        }
+      }
+    }
+    // All required breakpoints are now enabled.
+    aCallback();
   },
 
   /**
-   * Listener handling the "disableAll" menuitem command.
+   * Function invoked on the "disableOthers" menuitem command.
    *
-   * @param object aDetails
-   *        The breakpoint details (sourceLocation, lineNumber etc.).
+   * @param string aId
+   *        The original breakpoint client actor. If a breakpoint was disabled
+   *        and then re-enabled, then this will not correspond to the entry in
+   *        the controller's breakpoints store.
+   * @param function aCallback [optional]
+   *        A function to invoke once this operation finishes.
    */
-  _onDisableAll: function(aDetails) {
-    this._onDisableOthers(aDetails);
-    this._onDisableSelf(aDetails);
+  _onDisableOthers: function(aId, aCallback = () => {}) {
+    let targetBreakpoint = this.getItemForPredicate(aItem => aItem.attachment.actor == aId);
+
+    // Find an enabled breakpoint and disable it. Do this recursively until
+    // all required breakpoints are disabled, because each operation is async.
+    for (let source in this) {
+      for (let otherBreakpoint in source) {
+        if (otherBreakpoint != targetBreakpoint &&
+           !otherBreakpoint.attachment.disabled) {
+          this._onDisableSelf(otherBreakpoint.attachment.actor, () =>
+            this._onDisableOthers(aId, aCallback));
+          return;
+        }
+      }
+    }
+    // All required breakpoints are now disabled.
+    aCallback();
   },
 
   /**
-   * Listener handling the "deleteAll" menuitem command.
+   * Function invoked on the "deleteOthers" menuitem command.
    *
-   * @param object aDetails
-   *        The breakpoint details (sourceLocation, lineNumber etc.).
+   * @param string aId
+   *        The original breakpoint client actor. If a breakpoint was disabled
+   *        and then re-enabled, then this will not correspond to the entry in
+   *        the controller's breakpoints store.
+   * @param function aCallback [optional]
+   *        A function to invoke once this operation finishes.
    */
-  _onDeleteAll: function(aDetails) {
-    this._onDeleteOthers(aDetails);
-    this._onDeleteSelf(aDetails);
+  _onDeleteOthers: function(aId, aCallback = () => {}) {
+    let targetBreakpoint = this.getItemForPredicate(aItem => aItem.attachment.actor == aId);
+
+    // Find a breakpoint and delete it. Do this recursively until all required
+    // breakpoints are deleted, because each operation is async.
+    for (let source in this) {
+      for (let otherBreakpoint in source) {
+        if (otherBreakpoint != targetBreakpoint) {
+          this._onDeleteSelf(otherBreakpoint.attachment.actor, () =>
+            this._onDeleteOthers(aId, aCallback));
+          return;
+        }
+      }
+    }
+    // All required breakpoints are now deleted.
+    aCallback();
   },
 
   /**
-   * Gets an identifier for a breakpoint's details in the current cache.
+   * Function invoked on the "enableAll" menuitem command.
    *
-   * @param string aSourceLocation
-   *        The breakpoint source location.
-   * @param number aLineNumber
-   *        The breakpoint line number.
-   * @return string
-   *         The breakpoint identifier.
+   * @param string aId
+   *        The original breakpoint client actor. If a breakpoint was disabled
+   *        and then re-enabled, then this will not correspond to the entry in
+   *        the controller's breakpoints store.
+   * @param function aCallback [optional]
+   *        A function to invoke once this operation finishes.
    */
-  _getBreakpointKey: function(aSourceLocation, aLineNumber) {
-    return [aSourceLocation, aLineNumber].join();
+  _onEnableAll: function(aId) {
+    this._onEnableOthers(aId, () => this._onEnableSelf(aId));
   },
 
-  _breakpointsCache: null,
+  /**
+   * Function invoked on the "disableAll" menuitem command.
+   *
+   * @param string aId
+   *        The original breakpoint client actor. If a breakpoint was disabled
+   *        and then re-enabled, then this will not correspond to the entry in
+   *        the controller's breakpoints store.
+   * @param function aCallback [optional]
+   *        A function to invoke once this operation finishes.
+   */
+  _onDisableAll: function(aId) {
+    this._onDisableOthers(aId, () => this._onDisableSelf(aId));
+  },
+
+  /**
+   * Function invoked on the "deleteAll" menuitem command.
+   *
+   * @param string aId
+   *        The original breakpoint client actor. If a breakpoint was disabled
+   *        and then re-enabled, then this will not correspond to the entry in
+   *        the controller's breakpoints store.
+   * @param function aCallback [optional]
+   *        A function to invoke once this operation finishes.
+   */
+  _onDeleteAll: function(aId) {
+    this._onDeleteOthers(aId, () => this._onDeleteSelf(aId));
+  },
+
   _commandset: null,
   _popupset: null,
   _cmPopup: null,
   _cbPanel: null,
   _cbTextbox: null,
   _selectedBreakpoint: null,
   _editorContextMenuLineNumber: -1,
   _conditionalPopupVisible: false
@@ -1170,49 +1237,46 @@ let SourceUtils = {
 };
 
 /**
  * Functions handling the watch expressions UI.
  */
 function WatchExpressionsView() {
   dumpn("WatchExpressionsView was instantiated");
 
-  this._cache = []; // Array instead of a map because indices are important.
   this.switchExpression = this.switchExpression.bind(this);
   this.deleteExpression = this.deleteExpression.bind(this);
   this._createItemView = this._createItemView.bind(this);
   this._onClick = this._onClick.bind(this);
   this._onClose = this._onClose.bind(this);
   this._onBlur = this._onBlur.bind(this);
   this._onKeyPress = this._onKeyPress.bind(this);
 }
 
-create({ constructor: WatchExpressionsView, proto: MenuContainer.prototype }, {
+WatchExpressionsView.prototype = Heritage.extend(WidgetMethods, {
   /**
    * Initialization function, called when the debugger is started.
    */
   initialize: function() {
     dumpn("Initializing the WatchExpressionsView");
 
-    this.node = new ListWidget(document.getElementById("expressions"));
-    this._variables = document.getElementById("variables");
-
-    this.node.permaText = L10N.getStr("addWatchExpressionText");
-    this.node.itemFactory = this._createItemView;
-    this.node.setAttribute("context", "debuggerWatchExpressionsContextMenu");
-    this.node.addEventListener("click", this._onClick, false);
+    this.widget = new ListWidget(document.getElementById("expressions"));
+    this.widget.permaText = L10N.getStr("addWatchExpressionText");
+    this.widget.itemFactory = this._createItemView;
+    this.widget.setAttribute("context", "debuggerWatchExpressionsContextMenu");
+    this.widget.addEventListener("click", this._onClick, false);
   },
 
   /**
    * Destruction function, called when the debugger is closed.
    */
   destroy: function() {
     dumpn("Destroying the WatchExpressionsView");
 
-    this.node.removeEventListener("click", this._onClick, false);
+    this.widget.removeEventListener("click", this._onClick, false);
   },
 
   /**
    * Adds a watch expression in this container.
    *
    * @param string aExpression [optional]
    *        An optional initial watch expression text.
    */
@@ -1221,56 +1285,42 @@ create({ constructor: WatchExpressionsVi
     DebuggerView.showInstrumentsPane();
 
     // Append a watch expression item to this container.
     let expressionItem = this.push([, aExpression], {
       index: 0, /* specifies on which position should the item be appended */
       relaxed: true, /* this container should allow dupes & degenerates */
       attachment: {
         initialExpression: aExpression,
-        currentExpression: "",
-        id: this._generateId()
+        currentExpression: ""
       }
     });
 
     // Automatically focus the new watch expression input.
     expressionItem.attachment.inputNode.select();
     expressionItem.attachment.inputNode.focus();
-    this._variables.scrollTop = 0;
-
-    this._cache.splice(0, 0, expressionItem);
-  },
-
-  /**
-   * Removes the watch expression with the specified index from this container.
-   *
-   * @param number aIndex
-   *        The index used to identify the watch expression.
-   */
-  removeExpressionAt: function(aIndex) {
-    this.remove(this._cache[aIndex]);
-    this._cache.splice(aIndex, 1);
+    DebuggerView.Variables.parentNode.scrollTop = 0;
   },
 
   /**
    * Changes the watch expression corresponding to the specified variable item.
    * This function is called whenever a watch expression's code is edited in
    * the variables view container.
    *
    * @param Variable aVar
    *        The variable representing the watch expression evaluation.
    * @param string aExpression
    *        The new watch expression text.
    */
   switchExpression: function(aVar, aExpression) {
     let expressionItem =
-      [i for (i of this._cache) if (i.attachment.currentExpression == aVar.name)][0];
+      [i for (i in this) if (i.attachment.currentExpression == aVar.name)][0];
 
     // Remove the watch expression if it's going to be empty or a duplicate.
-    if (!aExpression || this.getExpressions().indexOf(aExpression) != -1) {
+    if (!aExpression || this.getAllStrings().indexOf(aExpression) != -1) {
       this.deleteExpression(aVar);
       return;
     }
 
     // Save the watch expression code string.
     expressionItem.attachment.currentExpression = aExpression;
     expressionItem.attachment.inputNode.value = aExpression;
 
@@ -1283,45 +1333,45 @@ create({ constructor: WatchExpressionsVi
    * This function is called whenever a watch expression's value is edited in
    * the variables view container.
    *
    * @param Variable aVar
    *        The variable representing the watch expression evaluation.
    */
   deleteExpression: function(aVar) {
     let expressionItem =
-      [i for (i of this._cache) if (i.attachment.currentExpression == aVar.name)][0];
+      [i for (i in this) if (i.attachment.currentExpression == aVar.name)][0];
 
-    // Remove the watch expression at its respective index.
-    this.removeExpressionAt(this._cache.indexOf(expressionItem));
+    // Remove the watch expression.
+    this.remove(expressionItem);
 
     // Synchronize with the controller's watch expressions store.
     DebuggerController.StackFrames.syncWatchExpressions();
   },
 
   /**
    * Gets the watch expression code string for an item in this container.
    *
    * @param number aIndex
    *        The index used to identify the watch expression.
    * @return string
    *         The watch expression code string.
    */
-  getExpression: function(aIndex) {
-    return this._cache[aIndex].attachment.currentExpression;
+  getString: function(aIndex) {
+    return this.getItemAtIndex(aIndex).attachment.currentExpression;
   },
 
   /**
    * Gets the watch expressions code strings for all items in this container.
    *
    * @return array
    *         The watch expressions code strings.
    */
-  getExpressions: function() {
-    return [item.attachment.currentExpression for (item of this._cache)];
+  getAllStrings: function() {
+    return this.orderedItems.map((e) => e.attachment.currentExpression);
   },
 
   /**
    * Customization function for creating an item's UI.
    *
    * @param nsIDOMNode aElementNode
    *        The element associated with the displayed item.
    * @param any aAttachment
@@ -1338,45 +1388,42 @@ create({ constructor: WatchExpressionsVi
 
     let closeNode = document.createElement("toolbarbutton");
     closeNode.className = "plain variables-view-delete";
 
     closeNode.addEventListener("click", this._onClose, false);
     inputNode.addEventListener("blur", this._onBlur, false);
     inputNode.addEventListener("keypress", this._onKeyPress, false);
 
-    aElementNode.id = "expression-" + aAttachment.id;
     aElementNode.className = "dbg-expression title";
-
     aElementNode.appendChild(arrowNode);
     aElementNode.appendChild(inputNode);
     aElementNode.appendChild(closeNode);
 
     aAttachment.arrowNode = arrowNode;
     aAttachment.inputNode = inputNode;
     aAttachment.closeNode = closeNode;
   },
 
   /**
    * Called when the add watch expression key sequence was pressed.
    */
   _onCmdAddExpression: function(aText) {
     // Only add a new expression if there's no pending input.
-    if (this.getExpressions().indexOf("") == -1) {
+    if (this.getAllStrings().indexOf("") == -1) {
       this.addExpression(aText || DebuggerView.editor.getSelectedText());
     }
   },
 
   /**
    * Called when the remove all watch expressions key sequence was pressed.
    */
   _onCmdRemoveAllExpressions: function() {
     // Empty the view of all the watch expressions and clear the cache.
     this.empty();
-    this._cache.length = 0;
 
     // Synchronize with the controller's watch expressions store.
     DebuggerController.StackFrames.syncWatchExpressions();
   },
 
   /**
    * The click listener for this container.
    */
@@ -1391,18 +1438,18 @@ create({ constructor: WatchExpressionsVi
       this.addExpression();
     }
   },
 
   /**
    * The click listener for a watch expression's close button.
    */
   _onClose: function(e) {
-    let expressionItem = this.getItemForElement(e.target);
-    this.removeExpressionAt(this._cache.indexOf(expressionItem));
+    // Remove the watch expression.
+    this.remove(this.getItemForElement(e.target));
 
     // Synchronize with the controller's watch expressions store.
     DebuggerController.StackFrames.syncWatchExpressions();
 
     // Prevent clicking the expression element itself.
     e.preventDefault();
     e.stopPropagation();
   },
@@ -1412,21 +1459,21 @@ create({ constructor: WatchExpressionsVi
    */
   _onBlur: function({ target: textbox }) {
     let expressionItem = this.getItemForElement(textbox);
     let oldExpression = expressionItem.attachment.currentExpression;
     let newExpression = textbox.value.trim();
 
     // Remove the watch expression if it's empty.
     if (!newExpression) {
-      this.removeExpressionAt(this._cache.indexOf(expressionItem));
+      this.remove(expressionItem);
     }
     // Remove the watch expression if it's a duplicate.
-    else if (!oldExpression && this.getExpressions().indexOf(newExpression) != -1) {
-      this.removeExpressionAt(this._cache.indexOf(expressionItem));
+    else if (!oldExpression && this.getAllStrings().indexOf(newExpression) != -1) {
+      this.remove(expressionItem);
     }
     // Expression is eligible.
     else {
       expressionItem.attachment.currentExpression = newExpression;
     }
 
     // Synchronize with the controller's watch expressions store.
     DebuggerController.StackFrames.syncWatchExpressions();
@@ -1438,31 +1485,17 @@ create({ constructor: WatchExpressionsVi
   _onKeyPress: function(e) {
     switch(e.keyCode) {
       case e.DOM_VK_RETURN:
       case e.DOM_VK_ENTER:
       case e.DOM_VK_ESCAPE:
         DebuggerView.editor.focus();
         return;
     }
-  },
-
-  /**
-   * Gets an identifier for a new watch expression item in the current cache.
-   * @return string
-   */
-  _generateId: (function() {
-    let count = 0;
-    return function() {
-      return (++count) + "";
-    };
-  })(),
-
-  _variables: null,
-  _cache: null
+  }
 });
 
 /**
  * Functions handling the global search UI.
  */
 function GlobalSearchView() {
   dumpn("GlobalSearchView was instantiated");
 
@@ -1470,86 +1503,88 @@ function GlobalSearchView() {
   this._performGlobalSearch = this._performGlobalSearch.bind(this);
   this._createItemView = this._createItemView.bind(this);
   this._onScroll = this._onScroll.bind(this);
   this._onHeaderClick = this._onHeaderClick.bind(this);
   this._onLineClick = this._onLineClick.bind(this);
   this._onMatchClick = this._onMatchClick.bind(this);
 }
 
-create({ constructor: GlobalSearchView, proto: MenuContainer.prototype }, {
+GlobalSearchView.prototype = Heritage.extend(WidgetMethods, {
   /**
    * Initialization function, called when the debugger is started.
    */
   initialize: function() {
     dumpn("Initializing the GlobalSearchView");
 
-    this.node = new ListWidget(document.getElementById("globalsearch"));
+    this.widget = new ListWidget(document.getElementById("globalsearch"));
     this._splitter = document.querySelector("#globalsearch + .devtools-horizontal-splitter");
 
-    this.node.emptyText = L10N.getStr("noMatchingStringsText");
-    this.node.itemFactory = this._createItemView;
-    this.node.addEventListener("scroll", this._onScroll, false);
+    this.widget.emptyText = L10N.getStr("noMatchingStringsText");
+    this.widget.itemFactory = this._createItemView;
+    this.widget.addEventListener("scroll", this._onScroll, false);
   },
 
   /**
    * Destruction function, called when the debugger is closed.
    */
   destroy: function() {
     dumpn("Destroying the GlobalSearchView");
 
-    this.node.removeEventListener("scroll", this._onScroll, false);
+    this.widget.removeEventListener("scroll", this._onScroll, false);
   },
 
   /**
    * Gets the visibility state of the global search container.
    * @return boolean
    */
   get hidden()
-    this.node.getAttribute("hidden") == "true" ||
+    this.widget.getAttribute("hidden") == "true" ||
     this._splitter.getAttribute("hidden") == "true",
 
   /**
    * Sets the results container hidden or visible. It's hidden by default.
    * @param boolean aFlag
    */
   set hidden(aFlag) {
-    this.node.setAttribute("hidden", aFlag);
+    this.widget.setAttribute("hidden", aFlag);
     this._splitter.setAttribute("hidden", aFlag);
   },
 
   /**
    * Hides and removes all items from this search container.
    */
   clearView: function() {
     this.hidden = true;
     this.empty();
     window.dispatchEvent(document, "Debugger:GlobalSearch:ViewCleared");
   },
 
   /**
-   * Focuses the next found match in the source editor.
+   * Selects the next found item in this container.
+   * Does not change the currently focused node.
    */
-  focusNextMatch: function() {
+  selectNext: function() {
     let totalLineResults = LineResults.size();
     if (!totalLineResults) {
       return;
     }
     if (++this._currentlyFocusedMatch >= totalLineResults) {
       this._currentlyFocusedMatch = 0;
     }
     this._onMatchClick({
       target: LineResults.getElementAtIndex(this._currentlyFocusedMatch)
     });
   },
 
   /**
-   * Focuses the previously found match in the source editor.
+   * Selects the previously found item in this container.
+   * Does not change the currently focused node.
    */
-  focusPrevMatch: function() {
+  selectPrev: function() {
     let totalLineResults = LineResults.size();
     if (!totalLineResults) {
       return;
     }
     if (--this._currentlyFocusedMatch < 0) {
       this._currentlyFocusedMatch = totalLineResults - 1;
     }
     this._onMatchClick({
@@ -1816,31 +1851,33 @@ create({ constructor: GlobalSearchView, 
    */
   _expandResultsIfNeeded: function(aTarget) {
     let sourceResultsItem = SourceResults.getItemForElement(aTarget);
     if (sourceResultsItem.instance.toggled ||
         sourceResultsItem.instance.expanded) {
       return;
     }
     let { top, height } = aTarget.getBoundingClientRect();
-    let { clientHeight } = this.node._parent;
+    let { clientHeight } = this.widget._parent;
 
     if (top - height <= clientHeight || this._forceExpandResults) {
       sourceResultsItem.instance.expand();
     }
   },
 
   /**
    * Scrolls a match into view if not already visible.
    *
    * @param nsIDOMNode aMatch
    *        The match to scroll into view.
    */
   _scrollMatchIntoViewIfNeeded: function(aMatch) {
-    let boxObject = this.node._parent.boxObject.QueryInterface(Ci.nsIScrollBoxObject);
+    // TODO: Accessing private widget properties. Figure out what's the best
+    // way to expose such things. Bug 876271.
+    let boxObject = this.widget._parent.boxObject.QueryInterface(Ci.nsIScrollBoxObject);
     boxObject.ensureElementIsVisible(aMatch);
   },
 
   /**
    * Starts a bounce animation for a match.
    *
    * @param nsIDOMNode aMatch
    *        The match to start a bounce animation for.
@@ -2202,17 +2239,17 @@ LineResults.prototype.__iterator__ = fun
  *
  * @param nsIDOMNode aElement
  *        The element used to identify the item.
  * @return object
  *         The matched item, or null if nothing is found.
  */
 SourceResults.getItemForElement =
 LineResults.getItemForElement = function(aElement) {
-  return MenuContainer.prototype.getItemForElement.call(this, aElement);
+  return WidgetMethods.getItemForElement.call(this, aElement);
 };
 
 /**
  * Gets the element associated with a particular item at a specified index.
  *
  * @param number aIndex
  *        The index used to identify the item.
  * @return nsIDOMNode
--- a/browser/devtools/debugger/debugger-toolbar.js
+++ b/browser/devtools/debugger/debugger-toolbar.js
@@ -53,17 +53,17 @@ ToolbarView.prototype = {
     this._stepOverButton.addEventListener("mousedown", this._onStepOverPressed, false);
     this._stepInButton.addEventListener("mousedown", this._onStepInPressed, false);
     this._stepOutButton.addEventListener("mousedown", this._onStepOutPressed, false);
 
     this._stepOverButton.setAttribute("tooltiptext", this._stepOverTooltip);
     this._stepInButton.setAttribute("tooltiptext", this._stepInTooltip);
     this._stepOutButton.setAttribute("tooltiptext", this._stepOutTooltip);
 
-    // TODO: bug 806775
+    // TODO: bug 806775 - group scripts by globals using hostAnnotations.
     // this.toggleChromeGlobalsContainer(window._isChromeDebugger);
   },
 
   /**
    * Destruction function, called when the debugger is closed.
    */
   destroy: function() {
     dumpn("Destroying the ToolbarView");
@@ -71,16 +71,31 @@ ToolbarView.prototype = {
     this._instrumentsPaneToggleButton.removeEventListener("mousedown", this._onTogglePanesPressed, false);
     this._resumeButton.removeEventListener("mousedown", this._onResumePressed, false);
     this._stepOverButton.removeEventListener("mousedown", this._onStepOverPressed, false);
     this._stepInButton.removeEventListener("mousedown", this._onStepInPressed, false);
     this._stepOutButton.removeEventListener("mousedown", this._onStepOutPressed, false);
   },
 
   /**
+   * 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(aPausedUrl) {
+    let label = L10N.getFormatStr("resumptionOrderPanelTitle", aPausedUrl);
+    let descriptionNode = document.getElementById("resumption-panel-desc");
+    descriptionNode.setAttribute("value", label);
+
+    this._resumeOrderPanel.openPopup(this._resumeButton);
+  },
+
+  /**
    * Sets the resume button state based on the debugger active thread.
    *
    * @param string aState
    *        Either "paused" or "attached".
    */
   toggleResumeButtonState: function(aState) {
     // If we're paused, check and show a resume label on the button.
     if (aState == "paused") {
@@ -90,29 +105,16 @@ 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(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(aVisibleFlag) {
     this._chromeGlobals.setAttribute("hidden", !aVisibleFlag);
   },
@@ -243,50 +245,57 @@ OptionsView.prototype = {
   _onPopupHidden: function() {
     window.dispatchEvent(document, "Debugger:OptionsPopupHidden");
   },
 
   /**
    * Listener handling the 'pause on exceptions' menuitem command.
    */
   _togglePauseOnExceptions: function() {
-    DebuggerController.activeThread.pauseOnExceptions(Prefs.pauseOnExceptions =
-      this._pauseOnExceptionsItem.getAttribute("checked") == "true");
+    let pref = Prefs.pauseOnExceptions =
+      this._pauseOnExceptionsItem.getAttribute("checked") == "true";
+
+    DebuggerController.activeThread.pauseOnExceptions(pref);
   },
 
   /**
    * Listener handling the 'show panes on startup' menuitem command.
    */
   _toggleShowPanesOnStartup: function() {
     Prefs.panesVisibleOnStartup =
       this._showPanesOnStartupItem.getAttribute("checked") == "true";
   },
 
   /**
    * Listener handling the 'show non-enumerables' menuitem command.
    */
   _toggleShowVariablesOnlyEnum: function() {
-    DebuggerView.Variables.onlyEnumVisible = Prefs.variablesOnlyEnumVisible =
+    let pref = Prefs.variablesOnlyEnumVisible =
       this._showVariablesOnlyEnumItem.getAttribute("checked") == "true";
+
+    DebuggerView.Variables.onlyEnumVisible = pref;
   },
 
   /**
    * Listener handling the 'show variables searchbox' menuitem command.
    */
   _toggleShowVariablesFilterBox: function() {
-    DebuggerView.Variables.searchEnabled = Prefs.variablesSearchboxVisible =
+    let pref = Prefs.variablesSearchboxVisible =
       this._showVariablesFilterBoxItem.getAttribute("checked") == "true";
+
+    DebuggerView.Variables.searchEnabled = pref;
   },
 
   /**
    * Listener handling the 'show original source' menuitem command.
    */
   _toggleShowOriginalSource: function() {
     function reconfigure() {
       window.removeEventListener("Debugger:OptionsPopupHidden", reconfigure, false);
+
       // The popup panel needs more time to hide after triggering onpopuphidden.
       window.setTimeout(function() {
         DebuggerController.reconfigureThread(pref);
       }, POPUP_HIDDEN_DELAY);
     }
 
     let pref = Prefs.sourceMapsEnabled =
       this._showOriginalSourceItem.getAttribute("checked") == "true";
@@ -294,65 +303,62 @@ OptionsView.prototype = {
     // Don't block the UI while reconfiguring the server.
     window.addEventListener("Debugger:OptionsPopupHidden", reconfigure, false);
   },
 
   _button: null,
   _pauseOnExceptionsItem: null,
   _showPanesOnStartupItem: null,
   _showVariablesOnlyEnumItem: null,
-  _showOriginalSourceItem: null,
-  _showVariablesFilterBoxItem: null
+  _showVariablesFilterBoxItem: null,
+  _showOriginalSourceItem: null
 };
 
 /**
  * Functions handling the chrome globals UI.
  */
 function ChromeGlobalsView() {
   dumpn("ChromeGlobalsView was instantiated");
 
   this._onSelect = this._onSelect.bind(this);
   this._onClick = this._onClick.bind(this);
 }
 
-create({ constructor: ChromeGlobalsView, proto: MenuContainer.prototype }, {
+ChromeGlobalsView.prototype = Heritage.extend(WidgetMethods, {
   /**
    * Initialization function, called when the debugger is started.
    */
   initialize: function() {
     dumpn("Initializing the ChromeGlobalsView");
 
-    this.node = document.getElementById("chrome-globals");
+    this.widget = document.getElementById("chrome-globals");
     this.emptyText = L10N.getStr("noGlobalsText");
     this.unavailableText = L10N.getStr("noMatchingGlobalsText");
 
-    this.node.addEventListener("select", this._onSelect, false);
-    this.node.addEventListener("click", this._onClick, false);
+    this.widget.addEventListener("select", this._onSelect, false);
+    this.widget.addEventListener("click", this._onClick, false);
 
     // Show an empty label by default.
     this.empty();
   },
 
   /**
    * Destruction function, called when the debugger is closed.
    */
   destroy: function() {
     dumpn("Destroying the ChromeGlobalsView");
 
-    this.node.removeEventListener("select", this._onSelect, false);
-    this.node.removeEventListener("click", this._onClick, false);
+    this.widget.removeEventListener("select", this._onSelect, false);
+    this.widget.removeEventListener("click", this._onClick, false);
   },
 
   /**
    * The select listener for the chrome globals container.
    */
   _onSelect: function() {
-    if (!this.refresh()) {
-      return;
-    }
     // TODO: bug 806775, do something useful for chrome debugging.
   },
 
   /**
    * The click listener for the chrome globals container.
    */
   _onClick: function() {
     // Use this container as a filtering target.
@@ -361,55 +367,54 @@ create({ constructor: ChromeGlobalsView,
 });
 
 /**
  * Functions handling the stackframes UI.
  */
 function StackFramesView() {
   dumpn("StackFramesView was instantiated");
 
-  this._framesCache = new Map(); // Can't use a WeakMap because keys are numbers.
   this._onStackframeRemoved = this._onStackframeRemoved.bind(this);
   this._onSelect = this._onSelect.bind(this);
   this._onScroll = this._onScroll.bind(this);
   this._afterScroll = this._afterScroll.bind(this);
 }
 
-create({ constructor: StackFramesView, proto: MenuContainer.prototype }, {
+StackFramesView.prototype = Heritage.extend(WidgetMethods, {
   /**
    * Initialization function, called when the debugger is started.
    */
   initialize: function() {
     dumpn("Initializing the StackFramesView");
 
     let commandset = this._commandset = document.createElement("commandset");
     let menupopup = this._menupopup = document.createElement("menupopup");
     commandset.id = "stackframesCommandset";
     menupopup.id = "stackframesMenupopup";
 
     document.getElementById("debuggerPopupset").appendChild(menupopup);
     document.getElementById("debuggerCommands").appendChild(commandset);
 
-    this.node = new BreadcrumbsWidget(document.getElementById("stackframes"));
-    this.node.addEventListener("select", this._onSelect, false);
-    this.node.addEventListener("scroll", this._onScroll, true);
+    this.widget = new BreadcrumbsWidget(document.getElementById("stackframes"));
+    this.widget.addEventListener("select", this._onSelect, false);
+    this.widget.addEventListener("scroll", this._onScroll, true);
     window.addEventListener("resize", this._onScroll, true);
 
     this.autoFocusOnFirstItem = false;
     this.autoFocusOnSelection = false;
   },
 
   /**
    * Destruction function, called when the debugger is closed.
    */
   destroy: function() {
     dumpn("Destroying the StackFramesView");
 
-    this.node.removeEventListener("select", this._onSelect, false);
-    this.node.removeEventListener("scroll", this._onScroll, true);
+    this.widget.removeEventListener("select", this._onSelect, false);
+    this.widget.removeEventListener("scroll", this._onScroll, true);
     window.removeEventListener("resize", this._onScroll, true);
   },
 
   /**
    * Adds a frame in this stackframes container.
    *
    * @param string aFrameTitle
    *        The frame title to be displayed in the list.
@@ -421,40 +426,38 @@ create({ constructor: StackFramesView, p
    *        The frame depth specified by the debugger.
    */
   addFrame: function(aFrameTitle, aSourceLocation, aLineNumber, aDepth) {
     // Create the element node and menu entry for the stack frame item.
     let frameView = this._createFrameView.apply(this, arguments);
     let menuEntry = this._createMenuEntry.apply(this, arguments);
 
     // Append a stack frame item to this container.
-    let stackframeItem = this.push(frameView, {
+    this.push([frameView], {
       index: 0, /* specifies on which position should the item be appended */
       attachment: {
         popup: menuEntry,
         depth: aDepth
       },
       attributes: [
         ["contextmenu", "stackframesMenupopup"],
         ["tooltiptext", aSourceLocation]
       ],
       // Make sure that when the stack frame item is removed, the corresponding
       // menuitem and command are also destroyed.
       finalize: this._onStackframeRemoved
     });
-
-    this._framesCache.set(aDepth, stackframeItem);
   },
 
   /**
    * Selects the frame at the specified depth in this container.
    * @param number aDepth
    */
   set selectedDepth(aDepth) {
-    this.selectedItem = this._framesCache.get(aDepth);
+    this.selectedItem = (aItem) => aItem.attachment.depth == aDepth;
   },
 
   /**
    * Specifies if the active thread has more frames that need to be loaded.
    */
   dirty: false,
 
   /**
@@ -549,54 +552,41 @@ create({ constructor: StackFramesView, p
 
     return {
       command: command,
       menuitem: menuitem
     };
   },
 
   /**
-   * Destroys a context menu item for a stack frame.
-   *
-   * @param object aMenuEntry
-   *        An object containing the stack frame command and menu item.
-   */
-  _destroyMenuEntry: function(aMenuEntry) {
-    dumpn("Destroying context menu: " +
-      aMenuEntry.command.id + " & " + aMenuEntry.menuitem.id);
-
-    let command = aMenuEntry.command;
-    let menuitem = aMenuEntry.menuitem;
-    command.parentNode.removeChild(command);
-    menuitem.parentNode.removeChild(menuitem);
-  },
-
-  /**
    * Function called each time a stack frame item is removed.
    *
-   * @param MenuItem aItem
-   *        The corresponding menu item.
+   * @param object aItem
+   *        The corresponding item.
    */
   _onStackframeRemoved: function(aItem) {
     dumpn("Finalizing stackframe item: " + aItem);
 
-    let { popup, depth } = aItem.attachment;
-    this._destroyMenuEntry(popup);
-    this._framesCache.delete(depth);
+    // Destroy the context menu item for the stack frame.
+    let contextItem = aItem.attachment.popup;
+    contextItem.command.remove();
+    contextItem.menuitem.remove();
   },
 
   /**
    * The select listener for the stackframes container.
    */
   _onSelect: function(e) {
     let stackframeItem = this.selectedItem;
     if (stackframeItem) {
       // The container is not empty and an actual item was selected.
       gStackFrames.selectFrame(stackframeItem.attachment.depth);
 
+      // Update the context menu to show the currently selected stackframe item
+      // as a checked entry.
       for (let otherItem in this) {
         if (otherItem != stackframeItem) {
           otherItem.attachment.popup.menuitem.removeAttribute("checked");
         } else {
           otherItem.attachment.popup.menuitem.setAttribute("checked", "");
         }
       }
     }
@@ -613,32 +603,33 @@ create({ constructor: StackFramesView, p
     window.clearTimeout(this._scrollTimeout);
     this._scrollTimeout = window.setTimeout(this._afterScroll, STACK_FRAMES_SCROLL_DELAY);
   },
 
   /**
    * Requests the addition of more frames from the controller.
    */
   _afterScroll: function() {
-    let list = this.node._list;
+    // TODO: Accessing private widget properties. Figure out what's the best
+    // way to expose such things. Bug 876271.
+    let list = this.widget._list;
     let scrollPosition = list.scrollPosition;
     let scrollWidth = list.scrollWidth;
 
     // If the stackframes container scrolled almost to the end, with only
     // 1/10 of a breadcrumb remaining, load more content.
     if (scrollPosition - scrollWidth / 10 < 1) {
       list.ensureElementIsVisible(this.getItemAtIndex(CALL_STACK_PAGE_SIZE - 1).target);
       this.dirty = false;
 
       // Loads more stack frames from the debugger server cache.
       DebuggerController.StackFrames.addMoreFrames();
     }
   },
 
-  _framesCache: null,
   _commandset: null,
   _menupopup: null,
   _scrollTimeout: null
 });
 
 /**
  * Utility functions for handling stackframes.
  */
@@ -752,17 +743,17 @@ FilterView.prototype = {
       L10N.getFormatStr("searchPanelFunction", this._filteredFunctionsKey));
     this._tokenOperatorLabel.setAttribute("value",
       L10N.getFormatStr("searchPanelToken", this._tokenSearchKey));
     this._lineOperatorLabel.setAttribute("value",
       L10N.getFormatStr("searchPanelLine", this._lineSearchKey));
     this._variableOperatorLabel.setAttribute("value",
       L10N.getFormatStr("searchPanelVariable", this._variableSearchKey));
 
-    // TODO: bug 806775
+    // TODO: bug 806775 - group scripts by globals using hostAnnotations.
     // if (window._isChromeDebugger) {
     //   this.target = DebuggerView.ChromeGlobals;
     // } else {
     this.target = DebuggerView.Sources;
     // }
   },
 
   /**
@@ -816,17 +807,19 @@ FilterView.prototype = {
     let variableFlagIndex = rawValue.indexOf(SEARCH_VARIABLE_FLAG);
     let lineFlagIndex = rawValue.lastIndexOf(SEARCH_LINE_FLAG);
     let tokenFlagIndex = rawValue.lastIndexOf(SEARCH_TOKEN_FLAG);
 
     // This is not a global, function or variable search, allow file/line flags.
     if (globalFlagIndex != 0 && functionFlagIndex != 0 && variableFlagIndex != 0) {
       let fileEnd = lineFlagIndex != -1
         ? lineFlagIndex
-        : tokenFlagIndex != -1 ? tokenFlagIndex : rawLength;
+        : tokenFlagIndex != -1
+          ? tokenFlagIndex
+          : rawLength;
 
       let lineEnd = tokenFlagIndex != -1
         ? tokenFlagIndex
         : rawLength;
 
       operator = "";
       file = rawValue.slice(0, fileEnd);
       line = ~~(rawValue.slice(fileEnd + 1, lineEnd)) || 0;
@@ -941,20 +934,20 @@ FilterView.prototype = {
       if (!found) {
         view.setUnavailable();
       }
     }
     // Synchronize with the view's filtered sources container.
     DebuggerView.FilteredSources.syncFileSearch();
 
     // Hide all the groups with no visible children.
-    view.node.hideEmptyGroups();
+    view.widget.hideEmptyGroups();
 
     // Ensure the currently selected item is visible.
-    view.node.ensureSelectionIsVisible({ withGroup: true });
+    view.widget.ensureSelectionIsVisible({ withGroup: true });
 
     // Remember the previously searched file to avoid redundant filtering.
     this._prevSearchedFile = aFile;
   },
 
   /**
    * Performs a line search if necessary.
    * (Jump to lines in the currently visible source).
@@ -1109,39 +1102,39 @@ FilterView.prototype = {
 
     // Select the next or previous file search entry.
     if (isFileSearch) {
       if (isReturnKey) {
         DebuggerView.FilteredSources.clearView();
         DebuggerView.editor.focus();
         this.clearSearch();
       } else {
-        DebuggerView.FilteredSources[["focusNext", "focusPrev"][action]]();
+        DebuggerView.FilteredSources[["selectNext", "selectPrev"][action]]();
       }
       this._prevSearchedFile = file;
       return;
     }
 
     // Perform a global search based on the specified operator.
     if (isGlobal) {
       if (isReturnKey && (isDifferentToken || DebuggerView.GlobalSearch.hidden)) {
         DebuggerView.GlobalSearch.performSearch(token);
       } else {
-        DebuggerView.GlobalSearch[["focusNextMatch", "focusPrevMatch"][action]]();
+        DebuggerView.GlobalSearch[["selectNext", "selectPrev"][action]]();
       }
       this._prevSearchedToken = token;
       return;
     }
 
     // Perform a function search based on the specified operator.
     if (isFunction) {
       if (isReturnKey && (isDifferentToken || DebuggerView.FilteredFunctions.hidden)) {
         DebuggerView.FilteredFunctions.performSearch(token);
       } else if (!isReturnKey) {
-        DebuggerView.FilteredFunctions[["focusNext", "focusPrev"][action]]();
+        DebuggerView.FilteredFunctions[["selectNext", "selectPrev"][action]]();
       } else {
         DebuggerView.FilteredFunctions.clearView();
         DebuggerView.editor.focus();
         this.clearSearch();
       }
       this._prevSearchedToken = token;
       return;
     }
@@ -1280,38 +1273,41 @@ FilterView.prototype = {
   _prevSearchedToken: ""
 };
 
 /**
  * Functions handling the filtered sources UI.
  */
 function FilteredSourcesView() {
   dumpn("FilteredSourcesView was instantiated");
-  ResultsPanelContainer.call(this);
 
-  this.onClick = this.onClick.bind(this);
-  this.onSelect = this.onSelect.bind(this);
+  this._onClick = this._onClick.bind(this);
+  this._onSelect = this._onSelect.bind(this);
 }
 
-create({ constructor: FilteredSourcesView, proto: ResultsPanelContainer.prototype }, {
+FilteredSourcesView.prototype = Heritage.extend(ResultsPanelContainer.prototype, {
   /**
    * Initialization function, called when the debugger is started.
    */
   initialize: function() {
     dumpn("Initializing the FilteredSourcesView");
 
     this.anchor = document.getElementById("searchbox");
+    this.widget.addEventListener("select", this._onSelect, false);
+    this.widget.addEventListener("click", this._onClick, false);
   },
 
   /**
    * Destruction function, called when the debugger is closed.
    */
   destroy: function() {
     dumpn("Destroying the FilteredSourcesView");
 
+    this.widget.removeEventListener("select", this._onSelect, false);
+    this.widget.removeEventListener("click", this._onClick, false);
     this.anchor = null;
   },
 
   /**
    * Updates the list of sources displayed in this container.
    */
   syncFileSearch: function() {
     this.empty();
@@ -1323,89 +1319,91 @@ create({ constructor: FilteredSourcesVie
       this.hidden = true;
       return;
     }
 
     // Get the currently visible items in the sources container.
     let visibleItems = DebuggerView.Sources.visibleItems;
     let displayedItems = visibleItems.slice(0, RESULTS_PANEL_MAX_RESULTS);
 
+    // Append a location item item to this container.
     for (let item of displayedItems) {
-      // Append a location item item to this container.
       let trimmedLabel = SourceUtils.trimUrlLength(item.label);
       let trimmedValue = SourceUtils.trimUrlLength(item.value, 0, "start");
       let locationItem = this.push([trimmedLabel, trimmedValue], {
         relaxed: true, /* this container should allow dupes & degenerates */
         attachment: {
           fullLabel: item.label,
           fullValue: item.value
         }
       });
     }
 
     // Select the first entry in this container.
-    this.select(0);
+    this.selectedIndex = 0;
 
     // Only display the results panel if there's at least one entry available.
     this.hidden = this.itemCount == 0;
   },
 
   /**
    * The click listener for this container.
    */
-  onClick: function(e) {
+  _onClick: function(e) {
     let locationItem = this.getItemForElement(e.target);
     if (locationItem) {
-      this.select(locationItem);
+      this.selectedItem = locationItem;
       DebuggerView.Filtering.clearSearch();
     }
   },
 
   /**
    * The select listener for this container.
    *
-   * @param MenuItem aItem
+   * @param object aItem
    *        The item associated with the element to select.
    */
-  onSelect: function(e) {
-    let locationItem = this.getItemForElement(e.target);
+  _onSelect: function({ detail: locationItem }) {
     if (locationItem) {
       DebuggerView.updateEditor(locationItem.attachment.fullValue, 0);
     }
   }
 });
 
 /**
  * Functions handling the function search UI.
  */
 function FilteredFunctionsView() {
   dumpn("FilteredFunctionsView was instantiated");
-  ResultsPanelContainer.call(this);
 
   this._performFunctionSearch = this._performFunctionSearch.bind(this);
-  this.onClick = this.onClick.bind(this);
-  this.onSelect = this.onSelect.bind(this);
+  this._onClick = this._onClick.bind(this);
+  this._onSelect = this._onSelect.bind(this);
 }
 
-create({ constructor: FilteredFunctionsView, proto: ResultsPanelContainer.prototype }, {
+FilteredFunctionsView.prototype = Heritage.extend(ResultsPanelContainer.prototype, {
   /**
    * Initialization function, called when the debugger is started.
    */
   initialize: function() {
     dumpn("Initializing the FilteredFunctionsView");
 
     this.anchor = document.getElementById("searchbox");
+    this.widget.addEventListener("select", this._onSelect, false);
+    this.widget.addEventListener("click", this._onClick, false);
   },
 
   /**
    * Destruction function, called when the debugger is closed.
    */
   destroy: function() {
     dumpn("Destroying the FilteredFunctionsView");
 
+    this.widget.removeEventListener("select", this._onSelect, false);
+    this.widget.removeEventListener("click", this._onClick, false);
     this.anchor = null;
   },
 
   /**
    * Allows searches to be scheduled and delayed to avoid redundant calls.
    */
   delayedSearch: true,
 
@@ -1557,36 +1555,37 @@ create({ constructor: FilteredFunctionsV
       let functionItem = this.push([trimmedLabel, trimmedValue, description], {
         index: -1, /* specifies on which position should the item be appended */
         relaxed: true, /* this container should allow dupes & degenerates */
         attachment: item
       });
     }
 
     // Select the first entry in this container.
-    this.select(0);
+    this.selectedIndex = 0;
+
+    // Only display the results panel if there's at least one entry available.
     this.hidden = this.itemCount == 0;
   },
 
   /**
    * The click listener for this container.
    */
-  onClick: function(e) {
+  _onClick: function(e) {
     let functionItem = this.getItemForElement(e.target);
     if (functionItem) {
-      this.select(functionItem);
+      this.selectedItem = functionItem;
       DebuggerView.Filtering.clearSearch();
     }
   },
 
   /**
    * The select listener for this container.
    */
-  onSelect: function(e) {
-    let functionItem = this.getItemForElement(e.target);
+  _onSelect: function({ detail: functionItem }) {
     if (functionItem) {
       let sourceUrl = functionItem.attachment.sourceUrl;
       let scriptOffset = functionItem.attachment.scriptOffset;
       let actualLocation = functionItem.attachment.actualLocation;
 
       DebuggerView.updateEditor(sourceUrl, actualLocation.start.line, {
         charOffset: scriptOffset,
         columnOffset: actualLocation.start.column,
--- a/browser/devtools/debugger/debugger-view.js
+++ b/browser/devtools/debugger/debugger-view.js
@@ -36,38 +36,30 @@ let DebuggerView = {
    * Initializes the debugger view.
    *
    * @param function aCallback
    *        Called after the view finishes initializing.
    */
   initialize: function(aCallback) {
     dumpn("Initializing the DebuggerView");
 
-    this._initializeWindow();
     this._initializePanes();
 
     this.Toolbar.initialize();
     this.Options.initialize();
     this.Filtering.initialize();
     this.FilteredSources.initialize();
     this.FilteredFunctions.initialize();
     this.ChromeGlobals.initialize();
     this.StackFrames.initialize();
     this.Sources.initialize();
     this.WatchExpressions.initialize();
     this.GlobalSearch.initialize();
 
-    this.Variables = new VariablesView(document.getElementById("variables"));
-    this.Variables.searchPlaceholder = L10N.getStr("emptyVariablesFilterText");
-    this.Variables.emptyText = L10N.getStr("emptyVariablesText");
-    this.Variables.onlyEnumVisible = Prefs.variablesOnlyEnumVisible;
-    this.Variables.searchEnabled = Prefs.variablesSearchboxVisible;
-    this.Variables.eval = DebuggerController.StackFrames.evaluate;
-    this.Variables.lazyEmpty = true;
-
+    this._initializeVariablesView();
     this._initializeEditor(aCallback);
   },
 
   /**
    * Destroys the debugger view.
    *
    * @param function aCallback
    *        Called after the view finishes destroying.
@@ -81,58 +73,23 @@ let DebuggerView = {
     this.FilteredSources.destroy();
     this.FilteredFunctions.destroy();
     this.ChromeGlobals.destroy();
     this.StackFrames.destroy();
     this.Sources.destroy();
     this.WatchExpressions.destroy();
     this.GlobalSearch.destroy();
 
-    this._destroyWindow();
     this._destroyPanes();
     this._destroyEditor();
+
     aCallback();
   },
 
   /**
-   * Initializes the UI for the window.
-   */
-  _initializeWindow: function() {
-    dumpn("Initializing the DebuggerView window");
-
-    let isRemote = window._isRemoteDebugger;
-    let isChrome = window._isChromeDebugger;
-
-    if (isRemote || isChrome) {
-      window.moveTo(Prefs.windowX, Prefs.windowY);
-      window.resizeTo(Prefs.windowWidth, Prefs.windowHeight);
-
-      if (isRemote) {
-        document.title = L10N.getStr("remoteDebuggerWindowTitle");
-      } else {
-        document.title = L10N.getStr("chromeDebuggerWindowTitle");
-      }
-    }
-  },
-
-  /**
-   * Destroys the UI for the window.
-   */
-  _destroyWindow: function() {
-    dumpn("Destroying the DebuggerView window");
-
-    if (window._isRemoteDebugger || window._isChromeDebugger) {
-      Prefs.windowX = window.screenX;
-      Prefs.windowY = window.screenY;
-      Prefs.windowWidth = window.outerWidth;
-      Prefs.windowHeight = window.outerHeight;
-    }
-  },
-
-  /**
    * Initializes the UI for all the displayed panes.
    */
   _initializePanes: function() {
     dumpn("Initializing the DebuggerView panes");
 
     this._sourcesPane = document.getElementById("sources-pane");
     this._instrumentsPane = document.getElementById("instruments-pane");
     this._instrumentsPaneToggleButton = document.getElementById("instruments-pane-toggle");
@@ -155,16 +112,47 @@ let DebuggerView = {
     Prefs.instrumentsWidth = this._instrumentsPane.getAttribute("width");
 
     this._sourcesPane = null;
     this._instrumentsPane = null;
     this._instrumentsPaneToggleButton = null;
   },
 
   /**
+   * Initializes the VariablesView instance and attaches a controller.
+   */
+  _initializeVariablesView: function() {
+    this.Variables = new VariablesView(document.getElementById("variables"), {
+      searchPlaceholder: L10N.getStr("emptyVariablesFilterText"),
+      emptyText: L10N.getStr("emptyVariablesText"),
+      onlyEnumVisible: Prefs.variablesOnlyEnumVisible,
+      searchEnabled: Prefs.variablesSearchboxVisible,
+      eval: DebuggerController.StackFrames.evaluate,
+      lazyEmpty: true
+    });
+
+    // Attach a controller that handles interfacing with the debugger protocol.
+    VariablesViewController.attach(this.Variables, {
+      getGripClient: aObject => gThreadClient.pauseGrip(aObject)
+    });
+
+    // Relay events from the VariablesView.
+    this.Variables.on("fetched", (aEvent, aType) => {
+      switch (aType) {
+        case "variables":
+          window.dispatchEvent(document, "Debugger:FetchedVariables");
+          break;
+        case "properties":
+          window.dispatchEvent(document, "Debugger:FetchedProperties");
+          break;
+      }
+    });
+  },
+
+  /**
    * Initializes the SourceEditor instance.
    *
    * @param function aCallback
    *        Called after the editor finishes initializing.
    */
   _initializeEditor: function(aCallback) {
     dumpn("Initializing the DebuggerView editor");
 
@@ -387,30 +375,30 @@ let DebuggerView = {
    * Gets the text in the source editor's specified line.
    *
    * @param number aLine [optional]
    *        The line to get the text from.
    *        If unspecified, it defaults to the current caret position line.
    * @return string
    *         The specified line's text.
    */
-  getEditorLine: function(aLine) {
+  getEditorLineText: function(aLine) {
     let line = aLine || this.editor.getCaretPosition().line;
     let start = this.editor.getLineStart(line);
     let end = this.editor.getLineEnd(line);
     return this.editor.getText(start, end);
   },
 
   /**
    * Gets the text in the source editor's selection bounds.
    *
    * @return string
    *         The selected text.
    */
-  getEditorSelection: function() {
+  getEditorSelectionText: function() {
     let selection = this.editor.getSelection();
     return this.editor.getText(selection.start, selection.end);
   },
 
   /**
    * Gets the visibility state of the instruments pane.
    * @return boolean
    */
@@ -478,61 +466,56 @@ let DebuggerView = {
       this._editorSource = null;
     }
   },
 
   Toolbar: null,
   Options: null,
   Filtering: null,
   FilteredSources: null,
+  FilteredFunctions: null,
+  GlobalSearch: null,
   ChromeGlobals: null,
   StackFrames: null,
   Sources: null,
+  Variables: null,
   WatchExpressions: null,
-  GlobalSearch: null,
-  Variables: null,
   _editor: null,
   _editorSource: null,
   _loadingText: "",
   _sourcesPane: null,
   _instrumentsPane: null,
   _instrumentsPaneToggleButton: null,
   _collapsePaneString: "",
   _expandPaneString: "",
   _isInitialized: false,
   _isDestroyed: false
 };
 
 /**
- * A stacked list of items, compatible with MenuContainer instances, used for
+ * A stacked list of items, compatible with WidgetMethods instances, used for
  * displaying views like the watch expressions, filtering or search results etc.
  *
- * You should never need to access these methods directly, use the wrapper
- * MenuContainer instances.
+ * You should never need to access these methods directly, use the wrapped
+ * WidgetMethods instead.
  *
- * Custom methods introduced by this view, not necessary for a MenuContainer:
- *   - set emptyText(aValue:string)
- *   - set permaText(aValue:string)
- *   - set itemType(aType:string)
- *   - set itemFactory(aCallback:function)
- *
- * @param nsIDOMNode aAssociatedNode
- *        The element associated with the displayed container.
+ * @param nsIDOMNode aNode
+ *        The element associated with the widget.
  */
-function ListWidget(aAssociatedNode) {
-  this._parent = aAssociatedNode;
+function ListWidget(aNode) {
+  this._parent = aNode;
 
   // Create an internal list container.
   this._list = document.createElement("vbox");
   this._parent.appendChild(this._list);
 
   // Delegate some of the associated node's methods to satisfy the interface
-  // required by MenuContainer instances.
-  ViewHelpers.delegateWidgetAttributeMethods(this, aAssociatedNode);
-  ViewHelpers.delegateWidgetEventMethods(this, aAssociatedNode);
+  // required by WidgetMethods instances.
+  ViewHelpers.delegateWidgetAttributeMethods(this, aNode);
+  ViewHelpers.delegateWidgetEventMethods(this, aNode);
 }
 
 ListWidget.prototype = {
   /**
    * Overrides an item's element type (e.g. "vbox" or "hbox") in this container.
    * @param string aType
    */
   itemType: "hbox",
@@ -723,26 +706,22 @@ ListWidget.prototype = {
   _permaTextNode: null,
   _permaTextValue: "",
   _emptyTextNode: null,
   _emptyTextValue: ""
 };
 
 /**
  * A custom items container, used for displaying views like the
- * FilteredSources, FilteredFunctions etc., inheriting the generic MenuContainer.
+ * FilteredSources, FilteredFunctions etc., inheriting the generic WidgetMethods.
  */
 function ResultsPanelContainer() {
-  this._createItemView = this._createItemView.bind(this);
 }
 
-create({ constructor: ResultsPanelContainer, proto: MenuContainer.prototype }, {
-  onClick: null,
-  onSelect: null,
-
+ResultsPanelContainer.prototype = Heritage.extend(WidgetMethods, {
   /**
    * Sets the anchor node for this container panel.
    * @param nsIDOMNode aNode
    */
   set anchor(aNode) {
     this._anchor = aNode;
 
     // If the anchor node is not null, create a panel to attach to the anchor
@@ -750,71 +729,45 @@ create({ constructor: ResultsPanelContai
     if (aNode) {
       if (!this._panel) {
         this._panel = document.createElement("panel");
         this._panel.className = "results-panel";
         this._panel.setAttribute("level", "top");
         this._panel.setAttribute("noautofocus", "true");
         document.documentElement.appendChild(this._panel);
       }
-      if (!this.node) {
-        this.node = new ListWidget(this._panel);
-        this.node.itemType = "vbox";
-        this.node.itemFactory = this._createItemView;
-        this.node.addEventListener("click", this.onClick, false);
+      if (!this.widget) {
+        this.widget = new ListWidget(this._panel);
+        this.widget.itemType = "vbox";
+        this.widget.itemFactory = this._createItemView;
       }
     }
     // Cleanup the anchor and remove the previously created panel.
     else {
-      if (this._panel) {
-        document.documentElement.removeChild(this._panel);
-        this._panel = null;
-      }
-      if (this.node) {
-        this.node.removeEventListener("click", this.onClick, false);
-        this.node = null;
-      }
+      this._panel.remove();
+      this._panel = null;
+      this.widget = null;
     }
   },
 
   /**
    * Gets the anchor node for this container panel.
    * @return nsIDOMNode
    */
   get anchor() this._anchor,
 
   /**
-   * Sets the default top, left and position params when opening the panel.
-   * @param object aOptions
-   */
-  set options(aOptions) {
-    this._top = aOptions.top;
-    this._left = aOptions.left;
-    this._position = aOptions.position;
-  },
-
-  /**
-   * Gets the default params for when opening the panel.
-   * @return object
-   */
-  get options() ({
-    top: this._top,
-    left: this._left,
-    position: this._position
-  }),
-
-  /**
    * Sets the container panel hidden or visible. It's hidden by default.
    * @param boolean aFlag
    */
   set hidden(aFlag) {
     if (aFlag) {
       this._panel.hidePopup();
     } else {
-      this._panel.openPopup(this._anchor, this._position, this._left, this._top);
+      this._panel.openPopup(this._anchor, this.position, this.left, this.top);
       this.anchor.focus();
     }
   },
 
   /**
    * Gets this container's visibility state.
    * @return boolean
    */
@@ -823,62 +776,40 @@ create({ constructor: ResultsPanelContai
     this._panel.state == "hiding",
 
   /**
    * Removes all items from this container and hides it.
    */
   clearView: function() {
     this.hidden = true;
     this.empty();
-    window.dispatchEvent(document, "Debugger:ResultsPanelContainer:ViewCleared");
   },
 
   /**
-   * Focuses the next found item in this container.
+   * Selects the next found item in this container.
+   * Does not change the currently focused node.
    */
-  focusNext: function() {
+  selectNext: function() {
     let nextIndex = this.selectedIndex + 1;
     if (nextIndex >= this.itemCount) {
       nextIndex = 0;
     }
-    this.select(this.getItemAtIndex(nextIndex));
+    this.selectedItem = this.getItemAtIndex(nextIndex);
   },
 
   /**
-   * Focuses the previously found item in this container.
+   * Selects the previously found item in this container.
+   * Does not change the currently focused node.
    */
-  focusPrev: function() {
+  selectPrev: function() {
     let prevIndex = this.selectedIndex - 1;
     if (prevIndex < 0) {
       prevIndex = this.itemCount - 1;
     }
-    this.select(this.getItemAtIndex(prevIndex));
-  },
-
-  /**
-   * Updates the selected item in this container.
-   *
-   * @param MenuItem | number aItem
-   *        The item associated with the element to select.
-   */
-  select: function(aItem) {
-    if (typeof aItem == "number") {
-      this.select(this.getItemAtIndex(aItem));
-      return;
-    }
-
-    // Update the currently selected item in this container using the
-    // selectedItem setter in the MenuContainer prototype chain.
-    this.selectedItem = aItem;
-
-    // Invoke the attached selection callback if available in any
-    // inheriting prototype.
-    if (this.onSelect) {
-      this.onSelect({ target: aItem.target });
-    }
+    this.selectedItem = this.getItemAtIndex(prevIndex);
   },
 
   /**
    * Customization function for creating an item's UI.
    *
    * @param nsIDOMNode aElementNode
    *        The element associated with the displayed item.
    * @param any aAttachment
@@ -887,16 +818,17 @@ create({ constructor: ResultsPanelContai
    *        The item's label.
    * @param string aValue
    *        The item's value.
    * @param string aDescription
    *        An optional description of the item.
    */
   _createItemView: function(aElementNode, aAttachment, aLabel, aValue, aDescription) {
     let labelsGroup = document.createElement("hbox");
+
     if (aDescription) {
       let preLabelNode = document.createElement("label");
       preLabelNode.className = "plain results-panel-item-pre";
       preLabelNode.setAttribute("value", aDescription);
       labelsGroup.appendChild(preLabelNode);
     }
     if (aLabel) {
       let labelNode = document.createElement("label");
@@ -911,54 +843,12 @@ create({ constructor: ResultsPanelContai
 
     aElementNode.className = "light results-panel-item";
     aElementNode.appendChild(labelsGroup);
     aElementNode.appendChild(valueNode);
   },
 
   _anchor: null,
   _panel: null,
-  _position: RESULTS_PANEL_POPUP_POSITION,
-  _left: 0,
-  _top: 0
+  position: RESULTS_PANEL_POPUP_POSITION,
+  left: 0,
+  top: 0
 });
-
-/**
- * A simple way of displaying a "Connect to..." prompt.
- */
-function RemoteDebuggerPrompt() {
-  this.remote = {};
-}
-
-RemoteDebuggerPrompt.prototype = {
-  /**
-   * Shows the prompt and waits for a remote host and port to connect to.
-   *
-   * @param boolean aIsReconnectingFlag
-   *        True to show the reconnect message instead of the connect request.
-   */
-  show: function(aIsReconnectingFlag) {
-    let check = { value: Prefs.remoteAutoConnect };
-    let input = { value: Prefs.remoteHost + ":" + Prefs.remotePort };
-    let parts;
-
-    while (true) {
-      let result = Services.prompt.prompt(null,
-        L10N.getStr("remoteDebuggerPromptTitle"),
-        L10N.getStr(aIsReconnectingFlag
-          ? "remoteDebuggerReconnectMessage"
-          : "remoteDebuggerPromptMessage"), input,
-        L10N.getStr("remoteDebuggerPromptCheck"), check);
-
-      if (!result) {
-        return false;
-      }
-      if ((parts = input.value.split(":")).length == 2) {
-        let [host, port] = parts;
-
-        if (host.length && port.length) {
-          this.remote = { host: host, port: port, auto: check.value };
-          return true;
-        }
-      }
-    }
-  }
-};
--- a/browser/devtools/debugger/debugger.css
+++ b/browser/devtools/debugger/debugger.css
@@ -2,18 +2,17 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* Sources search view */
 
 #globalsearch {
-  overflow-x: hidden;
-  overflow-y: auto;
+  overflow: auto;
 }
 
 /* Watch expressions view */
 
 #expressions {
   overflow-x: hidden;
   overflow-y: auto;
 }
--- a/browser/devtools/debugger/debugger.xul
+++ b/browser/devtools/debugger/debugger.xul
@@ -10,17 +10,23 @@
 <?xml-stylesheet href="chrome://browser/skin/devtools/debugger.css" type="text/css"?>
 <!DOCTYPE window [
   <!ENTITY % debuggerDTD SYSTEM "chrome://browser/locale/devtools/debugger.dtd">
   %debuggerDTD;
 ]>
 <?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
 <?xul-overlay href="chrome://browser/content/devtools/source-editor-overlay.xul"?>
 
-<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        macanimationtype="document"
+        fullscreenbutton="true"
+        screenX="4" screenY="4"
+        width="960" height="480"
+        persist="screenX screenY width height sizemode">
+
   <script type="text/javascript" src="chrome://global/content/globalOverlay.js"/>
   <script type="text/javascript" src="debugger-controller.js"/>
   <script type="text/javascript" src="debugger-view.js"/>
   <script type="text/javascript" src="debugger-toolbar.js"/>
   <script type="text/javascript" src="debugger-panes.js"/>
 
   <commandset id="editMenuCommands"/>
   <commandset id="sourceEditorCommands"/>
--- a/browser/devtools/debugger/test/Makefile.in
+++ b/browser/devtools/debugger/test/Makefile.in
@@ -10,17 +10,16 @@ relativesrcdir  = @relativesrcdir@
 
 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 \
-	$(browser_dbg_createRemote.js disabled for intermittent failures, bug 753225) \
 	browser_dbg_debuggerstatement.js \
 	browser_dbg_listtabs-01.js \
 	browser_dbg_listtabs-02.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 \
@@ -81,16 +80,18 @@ MOCHITEST_BROWSER_TESTS = \
 	browser_dbg_function-search.js \
 	browser_dbg_pause-resume.js \
 	browser_dbg_pause-warning.js \
 	browser_dbg_update-editor-mode.js \
 	browser_dbg_select-line.js \
 	browser_dbg_breakpoint-new-script.js \
 	browser_dbg_bug723069_editor-breakpoints.js \
 	browser_dbg_bug723071_editor-breakpoints-pane.js \
+	browser_dbg_bug723071_editor-breakpoints-highlight.js \
+	browser_dbg_bug723071_editor-breakpoints-contextmenu.js \
 	browser_dbg_bug740825_conditional-breakpoints-01.js \
 	browser_dbg_bug740825_conditional-breakpoints-02.js \
 	browser_dbg_bug727429_watch-expressions-01.js \
 	browser_dbg_bug727429_watch-expressions-02.js \
 	browser_dbg_bug731394_editor-contextmenu.js \
 	browser_dbg_bug737803_editor_actual_location.js \
 	browser_dbg_bug786070_hide_nonenums.js \
 	browser_dbg_displayName.js \
--- a/browser/devtools/debugger/test/browser_dbg_breakpoint-new-script.js
+++ b/browser/devtools/debugger/test/browser_dbg_breakpoint-new-script.js
@@ -24,17 +24,17 @@ function test()
   });
 }
 
 function testAddBreakpoint()
 {
   gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
     Services.tm.currentThread.dispatch({ run: function() {
 
-      var frames = gDebugger.DebuggerView.StackFrames._container._list;
+      var frames = gDebugger.DebuggerView.StackFrames.widget._list;
 
       is(gDebugger.DebuggerController.activeThread.state, "paused",
          "The debugger statement was reached.");
 
       is(frames.querySelectorAll(".dbg-stackframe").length, 1,
          "Should have one frame.");
 
       gPane.addBreakpoint({ url: TAB_URL, line: 9 }, function (aResponse, bpClient) {
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_bug723071_editor-breakpoints-contextmenu.js
@@ -0,0 +1,463 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test if the context menu associated with each breakpoint does what it should.
+ */
+
+const TAB_URL = EXAMPLE_URL + "browser_dbg_script-switching.html";
+
+let gPane = null;
+let gTab = null;
+let gDebuggee = null;
+let gDebugger = null;
+let gEditor = null;
+let gSources = null;
+
+function test()
+{
+  debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
+    gTab = aTab;
+    gDebuggee = aDebuggee;
+    gPane = aPane;
+    gDebugger = gPane.panelWin;
+    gEditor = gDebugger.DebuggerView.editor;
+    gSources = gDebugger.DebuggerView.Sources;
+    gSources.preferredSource = EXAMPLE_URL + "test-script-switching-02.js";
+
+    gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
+      let url = aEvent.detail.url;
+      if (url.indexOf("-02") != -1) {
+        gDebugger.removeEventListener(aEvent.type, _onEvent);
+        performTestWhileNotPaused();
+      }
+    });
+  });
+
+  function addBreakpoints(callback) {
+    gPane.addBreakpoint({url: gSources.values[0], line: 5}, function(cl, err) {
+      gPane.addBreakpoint({url: gSources.values[1], line: 6}, function(cl, err) {
+        gPane.addBreakpoint({url: gSources.values[1], line: 7}, function(cl, err) {
+          gPane.addBreakpoint({url: gSources.values[1], line: 8}, function(cl, err) {
+            gPane.addBreakpoint({url: gSources.values[1], line: 9}, function(cl, err) {
+              callback();
+            });
+          });
+        });
+      });
+    });
+  }
+
+  function performTestWhileNotPaused()
+  {
+    info("Performing test while not paused...");
+
+    addBreakpoints(function() {
+      initialChecks();
+
+      checkBreakpointToggleSelf(0, function() {
+        checkBreakpointToggleOthers(0, function() {
+          checkBreakpointToggleSelf(1, function() {
+            checkBreakpointToggleOthers(1, function() {
+              checkBreakpointToggleSelf(2, function() {
+                checkBreakpointToggleOthers(2, function() {
+                  checkBreakpointToggleSelf(3, function() {
+                    checkBreakpointToggleOthers(3, function() {
+                      checkBreakpointToggleSelf(4, function() {
+                        checkBreakpointToggleOthers(4, function() {
+                          testDeleteAll(function() {
+                            performTestWhilePaused();
+                          });
+                        });
+                      });
+                    });
+                  });
+                });
+              });
+            });
+          });
+        });
+      });
+    });
+  }
+
+  function performTestWhilePaused()
+  {
+    info("Performing test while paused...");
+
+    addBreakpoints(function() {
+      initialChecks();
+
+      pauseAndCheck(function() {
+        checkBreakpointToggleSelf(0, function() {
+          checkBreakpointToggleOthers(0, function() {
+            checkBreakpointToggleSelf(1, function() {
+              checkBreakpointToggleOthers(1, function() {
+                checkBreakpointToggleSelf(2, function() {
+                  checkBreakpointToggleOthers(2, function() {
+                    checkBreakpointToggleSelf(3, function() {
+                      checkBreakpointToggleOthers(3, function() {
+                        checkBreakpointToggleSelf(4, function() {
+                          checkBreakpointToggleOthers(4, function() {
+                            testDeleteAll(function() {
+                              closeDebuggerAndFinish();
+                            });
+                          }, true);
+                        });
+                      }, true);
+                    });
+                  }, true);
+                });
+              }, true);
+            });
+          }, true);
+        });
+      });
+    });
+  }
+
+  function pauseAndCheck(callback) {
+    gDebugger.gThreadClient.addOneTimeListener("resumed", function() {
+      pauseAndCallback(function() {
+        is(gSources.selectedLabel, "test-script-switching-01.js",
+          "The currently selected source is incorrect (1).");
+        is(gSources.selectedIndex, 1,
+          "The currently selected source is incorrect (2).");
+
+        waitForCaretPos(4, function() {
+          ok(true, "The editor location is correct after pausing.");
+          callback();
+        });
+      });
+    });
+  }
+
+  function pauseAndCallback(callback) {
+    let scriptShown = false;
+    let framesadded = false;
+    let testContinued = false;
+
+    gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
+      let url = aEvent.detail.url;
+      if (url.indexOf("-01") != -1) {
+        gDebugger.removeEventListener(aEvent.type, _onEvent);
+        scriptShown = true;
+        executeSoon(continueTest);
+      }
+    });
+
+    gDebugger.gThreadClient.addOneTimeListener("framesadded", function() {
+      framesadded = true;
+      executeSoon(continueTest);
+    });
+
+    function continueTest() {
+      if (scriptShown && framesadded && !testContinued) {
+        testContinued = true;
+        callback();
+      }
+    }
+
+    EventUtils.sendMouseEvent({ type: "click" },
+      gDebuggee.document.querySelector("button"),
+      gDebuggee.window);
+  }
+
+  function initialChecks() {
+    for (let source in gSources) {
+      for (let breakpoint in source) {
+        let { sourceLocation: url, lineNumber: line, actor } = breakpoint.attachment;
+
+        ok(gPane.getBreakpoint(url, line),
+          "All breakpoint items should have corresponding clients (1).");
+        ok(breakpoint.attachment.actor,
+          "All breakpoint items should have corresponding clients (2).");
+        is(!!breakpoint.attachment.disabled, false,
+          "All breakpoints should initially be enabled.");
+
+        let prefix = "bp-cMenu-"; // "breakpoints context menu"
+        let enableSelfId = prefix + "enableSelf-" + actor + "-menuitem";
+        let disableSelfId = prefix + "disableSelf-" + actor + "-menuitem";
+
+        is(gDebugger.document.getElementById(enableSelfId).getAttribute("hidden"), "true",
+          "The 'Enable breakpoint' context menu item should initially be hidden'.");
+        ok(!gDebugger.document.getElementById(disableSelfId).hasAttribute("hidden"),
+          "The 'Disable breakpoint' context menu item should initially not be hidden'.");
+        is(breakpoint.attachment.view.checkbox.getAttribute("checked"), "true",
+          "All breakpoints should initially have a checked checkbox.");
+      }
+    }
+  }
+
+  function checkBreakpointToggleSelf(index, callback) {
+    EventUtils.sendMouseEvent({ type: "click" },
+      gDebugger.document.querySelectorAll(".dbg-breakpoint")[index],
+      gDebugger);
+
+    let selectedBreakpoint = gSources.selectedBreakpointItem;
+    let { sourceLocation: url, lineNumber: line, actor } = selectedBreakpoint.attachment;
+
+    ok(gPane.getBreakpoint(url, line),
+      "There should be a breakpoint client available (1).");
+    ok(gSources.selectedBreakpointClient,
+      "There should be a breakpoint client available (2).");
+    is(!!selectedBreakpoint.attachment.disabled, false,
+      "The breakpoint should not be disabled yet.");
+
+    let prefix = "bp-cMenu-"; // "breakpoints context menu"
+    let enableSelfId = prefix + "enableSelf-" + actor + "-menuitem";
+    let disableSelfId = prefix + "disableSelf-" + actor + "-menuitem";
+
+    is(gDebugger.document.getElementById(enableSelfId).getAttribute("hidden"), "true",
+      "The 'Enable breakpoint' context menu item should be hidden'.");
+    ok(!gDebugger.document.getElementById(disableSelfId).hasAttribute("hidden"),
+      "The 'Disable breakpoint' context menu item should not be hidden'.");
+
+    waitForCaretPos(selectedBreakpoint.attachment.lineNumber - 1, function() {
+      ok(true, "The editor location is correct (" + index + ").");
+
+      gDebugger.addEventListener("Debugger:BreakpointHidden", function _onEvent(aEvent) {
+        gDebugger.removeEventListener(aEvent.type, _onEvent);
+
+        ok(!gPane.getBreakpoint(url, line),
+          "There should be no breakpoint client available (2).");
+        ok(!gSources.selectedBreakpointClient,
+          "There should be no breakpoint client available (3).");
+
+        gDebugger.addEventListener("Debugger:BreakpointShown", function _onEvent(aEvent) {
+          gDebugger.removeEventListener(aEvent.type, _onEvent);
+
+          ok(gPane.getBreakpoint(url, line),
+            "There should be a breakpoint client available (4).");
+          ok(gSources.selectedBreakpointClient,
+            "There should be a breakpoint client available (5).");
+
+          callback();
+        });
+
+        // Test re-disabling this breakpoint.
+        executeSoon(function() {
+          gSources._onEnableSelf(selectedBreakpoint.attachment.actor);
+          is(selectedBreakpoint.attachment.disabled, false,
+            "The current breakpoint should now be enabled.")
+
+          is(gDebugger.document.getElementById(enableSelfId).getAttribute("hidden"), "true",
+            "The 'Enable breakpoint' context menu item should be hidden'.");
+          ok(!gDebugger.document.getElementById(disableSelfId).hasAttribute("hidden"),
+            "The 'Disable breakpoint' context menu item should not be hidden'.");
+          ok(selectedBreakpoint.attachment.view.checkbox.hasAttribute("checked"),
+            "The breakpoint should now be checked.");
+        });
+      });
+
+      // Test disabling this breakpoint.
+      executeSoon(function() {
+        gSources._onDisableSelf(selectedBreakpoint.attachment.actor);
+        is(selectedBreakpoint.attachment.disabled, true,
+          "The current breakpoint should now be disabled.")
+
+        ok(!gDebugger.document.getElementById(enableSelfId).hasAttribute("hidden"),
+          "The 'Enable breakpoint' context menu item should not be hidden'.");
+        is(gDebugger.document.getElementById(disableSelfId).getAttribute("hidden"), "true",
+          "The 'Disable breakpoint' context menu item should be hidden'.");
+        ok(!selectedBreakpoint.attachment.view.checkbox.hasAttribute("checked"),
+          "The breakpoint should now be unchecked.");
+      });
+    });
+  }
+
+  function checkBreakpointToggleOthers(index, callback, whilePaused) {
+    let count = 4
+    gDebugger.addEventListener("Debugger:BreakpointHidden", function _onEvent(aEvent) {
+      info(count + " breakpoints remain to be hidden...");
+      if (!(--count)) {
+        gDebugger.removeEventListener(aEvent.type, _onEvent);
+        ok(true, "All breakpoints except one were hidden.");
+
+        let selectedBreakpoint = gSources.selectedBreakpointItem;
+        let { sourceLocation: url, lineNumber: line, actor } = selectedBreakpoint.attachment;
+
+        ok(gPane.getBreakpoint(url, line),
+          "There should be a breakpoint client available (6).");
+        ok(gSources.selectedBreakpointClient,
+          "There should be a breakpoint client available (7).");
+        is(!!selectedBreakpoint.attachment.disabled, false,
+          "The targetted breakpoint should not have been disabled.");
+
+        for (let source in gSources) {
+          for (let otherBreakpoint in source) {
+            if (otherBreakpoint != selectedBreakpoint) {
+              ok(!gPane.getBreakpoint(
+                otherBreakpoint.attachment.sourceLocation,
+                otherBreakpoint.attachment.lineNumber),
+                "There should be no breakpoint client for a disabled breakpoint (8).");
+              is(otherBreakpoint.attachment.disabled, true,
+                "Non-targetted breakpoints should have been disabled (9).");
+            }
+          }
+        }
+
+        count = 4;
+        gDebugger.addEventListener("Debugger:BreakpointShown", function _onEvent(aEvent) {
+          info(count + " breakpoints remain to be reshown...");
+          if (!(--count)) {
+            gDebugger.removeEventListener(aEvent.type, _onEvent);
+            ok(true, "All breakpoints are now reshown.");
+
+            for (let source in gSources) {
+              for (let someBreakpoint in source) {
+                ok(gPane.getBreakpoint(
+                  someBreakpoint.attachment.sourceLocation,
+                  someBreakpoint.attachment.lineNumber),
+                  "There should be a breakpoint client for all enabled breakpoints (10).");
+                is(someBreakpoint.attachment.disabled, false,
+                  "All breakpoints should now have been enabled (11).");
+              }
+            }
+
+            count = 5;
+            gDebugger.addEventListener("Debugger:BreakpointHidden", function _onEvent(aEvent) {
+              info(count + " breakpoints remain to be rehidden...");
+              if (!(--count)) {
+                gDebugger.removeEventListener(aEvent.type, _onEvent);
+                ok(true, "All breakpoints are now rehidden.");
+
+                for (let source in gSources) {
+                  for (let someBreakpoint in source) {
+                    ok(!gPane.getBreakpoint(
+                      someBreakpoint.attachment.sourceLocation,
+                      someBreakpoint.attachment.lineNumber),
+                      "There should be no breakpoint client for a disabled breakpoint (12).");
+                    is(someBreakpoint.attachment.disabled, true,
+                      "All breakpoints should now have been disabled (13).");
+                  }
+                }
+
+                count = 5;
+                gDebugger.addEventListener("Debugger:BreakpointShown", function _onEvent(aEvent) {
+                  info(count + " breakpoints remain to be reshown...");
+                  if (!(--count)) {
+                    gDebugger.removeEventListener(aEvent.type, _onEvent);
+                    ok(true, "All breakpoints are now rehidden.");
+
+                    for (let source in gSources) {
+                      for (let someBreakpoint in source) {
+                        ok(gPane.getBreakpoint(
+                          someBreakpoint.attachment.sourceLocation,
+                          someBreakpoint.attachment.lineNumber),
+                          "There should be a breakpoint client for all enabled breakpoints (14).");
+                        is(someBreakpoint.attachment.disabled, false,
+                          "All breakpoints should now have been enabled (15).");
+                      }
+                    }
+
+                    // Done.
+                    if (!whilePaused) {
+                      gDebugger.gThreadClient.addOneTimeListener("resumed", callback);
+                    } else {
+                      callback();
+                    }
+                  }
+                });
+
+                // Test re-enabling all breakpoints.
+                enableAll();
+              }
+            });
+
+            // Test disabling all breakpoints.
+            if (!whilePaused) {
+              gDebugger.gThreadClient.addOneTimeListener("resumed", disableAll);
+            } else {
+              disableAll();
+            }
+          }
+        });
+
+        // Test re-enabling other breakpoints.
+        enableOthers();
+      }
+    });
+
+    // Test disabling other breakpoints.
+    if (!whilePaused) {
+      gDebugger.gThreadClient.addOneTimeListener("resumed", disableOthers);
+    } else {
+      disableOthers();
+    }
+  }
+
+  function testDeleteAll(callback) {
+    let count = 5
+    gDebugger.addEventListener("Debugger:BreakpointHidden", function _onEvent(aEvent) {
+      info(count + " breakpoints remain to be hidden...");
+      if (!(--count)) {
+        gDebugger.removeEventListener(aEvent.type, _onEvent);
+        ok(true, "All breakpoints were hidden.");
+
+        ok(!gSources.selectedBreakpointItem,
+          "There should be no breakpoint item available (16).");
+        ok(!gSources.selectedBreakpointClient,
+          "There should be no breakpoint client available (17).");
+
+        for (let source in gSources) {
+          for (let otherBreakpoint in source) {
+            ok(false, "It's a trap!");
+          }
+        }
+
+        // Done.
+        callback();
+      }
+    });
+
+    // Test deleting all breakpoints.
+    deleteAll();
+  }
+
+  function disableOthers() {
+    gSources._onDisableOthers(gSources.selectedBreakpointItem.attachment.actor);
+  }
+  function enableOthers() {
+    gSources._onEnableOthers(gSources.selectedBreakpointItem.attachment.actor);
+  }
+  function disableAll() {
+    gSources._onDisableAll(gSources.selectedBreakpointItem.attachment.actor);
+  }
+  function enableAll() {
+    gSources._onEnableAll(gSources.selectedBreakpointItem.attachment.actor);
+  }
+  function deleteAll() {
+    gSources._onDeleteAll(gSources.selectedBreakpointItem.attachment.actor);
+  }
+
+  function waitForCaretPos(number, callback)
+  {
+    // Poll every few milliseconds until the source editor line is active.
+    let count = 0;
+    let intervalID = window.setInterval(function() {
+      info("count: " + count + " ");
+      if (++count > 50) {
+        ok(false, "Timed out while polling for the line.");
+        window.clearInterval(intervalID);
+        return closeDebuggerAndFinish();
+      }
+      if (gEditor.getCaretPosition().line != number) {
+        return;
+      }
+      // We got the source editor at the expected line, it's safe to callback.
+      window.clearInterval(intervalID);
+      callback();
+    }, 100);
+  }
+
+  registerCleanupFunction(function() {
+    removeTab(gTab);
+    gPane = null;
+    gTab = null;
+    gDebuggee = null;
+    gDebugger = null;
+    gEditor = null;
+    gSources = null;
+  });
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_bug723071_editor-breakpoints-highlight.js
@@ -0,0 +1,221 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test if breakpoints are highlighted when they should.
+ */
+
+const TAB_URL = EXAMPLE_URL + "browser_dbg_script-switching.html";
+
+let gPane = null;
+let gTab = null;
+let gDebuggee = null;
+let gDebugger = null;
+let gEditor = null;
+let gSources = null;
+
+function test()
+{
+  debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
+    gTab = aTab;
+    gDebuggee = aDebuggee;
+    gPane = aPane;
+    gDebugger = gPane.panelWin;
+    gEditor = gDebugger.DebuggerView.editor;
+    gSources = gDebugger.DebuggerView.Sources;
+    gSources.preferredSource = EXAMPLE_URL + "test-script-switching-02.js";
+
+    gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
+      let url = aEvent.detail.url;
+      if (url.indexOf("-02") != -1) {
+        gDebugger.removeEventListener(aEvent.type, _onEvent);
+        performTest();
+      }
+    });
+  });
+
+  function performTest()
+  {
+    initialChecks();
+    gPane.addBreakpoint({url: gSources.values[0], line: 5}, function(cl, err) {
+      initialChecks();
+      gPane.addBreakpoint({url: gSources.values[1], line: 6}, function(cl, err) {
+        initialChecks();
+        gPane.addBreakpoint({url: gSources.values[1], line: 7}, function(cl, err) {
+          initialChecks();
+          gPane.addBreakpoint({url: gSources.values[1], line: 8}, function(cl, err) {
+            initialChecks();
+            gPane.addBreakpoint({url: gSources.values[1], line: 9}, function(cl, err) {
+              initialChecks();
+              testHighlight1();
+            });
+          });
+        });
+      });
+    });
+  }
+
+  function initialChecks() {
+    is(gSources.selectedValue, gSources.values[1],
+      "The currently selected source is incorrect (0).");
+    is(gEditor.getCaretPosition().line, 0,
+      "The editor caret line was incorrect (0).");
+    is(gEditor.getCaretPosition().col, 0,
+      "The editor caret column was incorrect (0).");
+  }
+
+  function testHighlight1() {
+    gSources.highlightBreakpoint(gSources.values[0], 5);
+    checkHighlight(gSources.values[0], 5);
+
+    is(gSources.selectedValue, gSources.values[1],
+      "The currently selected source is incorrect (1).");
+
+    is(gEditor.getCaretPosition().line, 0,
+      "The editor caret line was incorrect (1).");
+    is(gEditor.getCaretPosition().col, 0,
+      "The editor caret column was incorrect (1).");
+
+    EventUtils.sendMouseEvent({ type: "click" },
+      gDebugger.document.querySelectorAll(".dbg-breakpoint")[0],
+      gDebugger);
+
+    waitForCaretPos(4, function() {
+      ok(true, "The editor location is correct (1).");
+      testHighlight2();
+    });
+  }
+
+  function testHighlight2() {
+    gSources.highlightBreakpoint(gSources.values[1], 6);
+    checkHighlight(gSources.values[1], 6);
+
+    is(gSources.selectedValue, gSources.values[0],
+      "The currently selected source is incorrect (2).");
+
+    is(gEditor.getCaretPosition().line, 4,
+      "The editor caret line was incorrect (2).");
+    is(gEditor.getCaretPosition().col, 0,
+      "The editor caret column was incorrect (2).");
+
+    EventUtils.sendMouseEvent({ type: "click" },
+      gDebugger.document.querySelectorAll(".dbg-breakpoint")[1],
+      gDebugger);
+
+    waitForCaretPos(5, function() {
+      ok(true, "The editor location is correct (2).");
+      testHighlight3();
+    });
+  }
+
+  function testHighlight3() {
+    gSources.highlightBreakpoint(gSources.values[1], 7);
+    checkHighlight(gSources.values[1], 7);
+
+    is(gSources.selectedValue, gSources.values[1],
+      "The currently selected source is incorrect (3).");
+
+    is(gEditor.getCaretPosition().line, 5,
+      "The editor caret line was incorrect (3).");
+    is(gEditor.getCaretPosition().col, 0,
+      "The editor caret column was incorrect (3).");
+
+    EventUtils.sendMouseEvent({ type: "click" },
+      gDebugger.document.querySelectorAll(".dbg-breakpoint")[2],
+      gDebugger);
+
+    waitForCaretPos(6, function() {
+      ok(true, "The editor location is correct (3).");
+      testHighlight4();
+    });
+  }
+
+  function testHighlight4() {
+    gSources.highlightBreakpoint(gSources.values[1], 8);
+    checkHighlight(gSources.values[1], 8);
+
+    is(gSources.selectedValue, gSources.values[1],
+      "The currently selected source is incorrect (4).");
+
+    is(gEditor.getCaretPosition().line, 6,
+      "The editor caret line was incorrect (4).");
+    is(gEditor.getCaretPosition().col, 0,
+      "The editor caret column was incorrect (4).");
+
+    EventUtils.sendMouseEvent({ type: "click" },
+      gDebugger.document.querySelectorAll(".dbg-breakpoint")[3],
+      gDebugger);
+
+    waitForCaretPos(7, function() {
+      ok(true, "The editor location is correct (4).");
+      testHighlight5();
+    });
+  }
+
+  function testHighlight5() {
+    gSources.highlightBreakpoint(gSources.values[1], 9);
+    checkHighlight(gSources.values[1], 9);
+
+    is(gSources.selectedValue, gSources.values[1],
+      "The currently selected source is incorrect (5).");
+
+    is(gEditor.getCaretPosition().line, 7,
+      "The editor caret line was incorrect (5).");
+    is(gEditor.getCaretPosition().col, 0,
+      "The editor caret column was incorrect (5).");
+
+    EventUtils.sendMouseEvent({ type: "click" },
+      gDebugger.document.querySelectorAll(".dbg-breakpoint")[4],
+      gDebugger);
+
+    waitForCaretPos(8, function() {
+      ok(true, "The editor location is correct (5).");
+      closeDebuggerAndFinish();
+    });
+  }
+
+  function checkHighlight(aUrl, aLine) {
+    is(gSources.selectedBreakpointItem, gSources.getBreakpoint(aUrl, aLine),
+      "The currently selected breakpoint item is incorrect.");
+    is(gSources.selectedBreakpointClient, gPane.getBreakpoint(aUrl, aLine),
+      "The currently selected breakpoint client is incorrect.");
+
+    is(gSources.selectedBreakpointItem.attachment.sourceLocation, aUrl,
+      "The selected breakpoint item's source location attachment is incorrect.");
+    is(gSources.selectedBreakpointItem.attachment.lineNumber, aLine,
+      "The selected breakpoint item's source line number is incorrect.");
+
+    ok(gSources.selectedBreakpointItem.target.classList.contains("selected"),
+      "The selected breakpoint item's target should have a selected class.");
+  }
+
+  function waitForCaretPos(number, callback)
+  {
+    // Poll every few milliseconds until the source editor line is active.
+    let count = 0;
+    let intervalID = window.setInterval(function() {
+      info("count: " + count + " ");
+      if (++count > 50) {
+        ok(false, "Timed out while polling for the line.");
+        window.clearInterval(intervalID);
+        return closeDebuggerAndFinish();
+      }
+      if (gEditor.getCaretPosition().line != number) {
+        return;
+      }
+      // We got the source editor at the expected line, it's safe to callback.
+      window.clearInterval(intervalID);
+      callback();
+    }, 100);
+  }
+
+  registerCleanupFunction(function() {
+    removeTab(gTab);
+    gPane = null;
+    gTab = null;
+    gDebuggee = null;
+    gDebugger = null;
+    gEditor = null;
+    gSources = null;
+  });
+}
--- a/browser/devtools/debugger/test/browser_dbg_bug723071_editor-breakpoints-pane.js
+++ b/browser/devtools/debugger/test/browser_dbg_bug723071_editor-breakpoints-pane.js
@@ -80,18 +80,18 @@ function test()
 
     isnot(gSources.selectedValue, gSources.values[0],
       "The correct script is selected");
 
     is(Object.keys(gBreakpoints).length, 0, "no breakpoints");
     ok(!gPane.getBreakpoint("chocolate", 3), "getBreakpoint('chocolate', 3) returns falsey");
     is(gEditor.getBreakpoints().length, 0, "no breakpoints in the editor");
 
-    gBreakpointsParent = gSources._container._parent;
-    gBreakpointsList = gSources._container._list;
+    gBreakpointsParent = gSources.widget._parent;
+    gBreakpointsList = gSources.widget._list;
 
     is(gBreakpointsParent.childNodes.length, 1, // one sources list
       "Found junk in the breakpoints container.");
     is(gBreakpointsList.childNodes.length, 1, // one sources group
       "Found junk in the breakpoints container.");
     is(gBreakpointsList.querySelectorAll(".dbg-breakpoint").length, 0,
       "No breakpoints should be visible at this point.");
 
@@ -265,17 +265,17 @@ function test()
       let line = bkp.getElementsByClassName("dbg-breakpoint-line")[0];
       let text = bkp.getElementsByClassName("dbg-breakpoint-text")[0];
       let check = bkp.querySelector("checkbox");
 
       is(bkp.id, id,
         "Breakpoint element " + id + " found successfully.");
       is(line.getAttribute("value"), this.line,
         "The expected information wasn't found in the breakpoint element.");
-      is(text.getAttribute("value"), gDebugger.DebuggerView.getEditorLine(this.line - 1).trim(),
+      is(text.getAttribute("value"), gDebugger.DebuggerView.getEditorLineText(this.line - 1).trim(),
         "The expected line text wasn't found in the breakpoint element.");
       is(check.getAttribute("checked"), "true",
         "The breakpoint enable checkbox is checked as expected.");
     }
   }
 
   function finalCheck() {
     is(Object.keys(gBreakpoints).length, 0, "no breakpoint in the debugger");
--- a/browser/devtools/debugger/test/browser_dbg_bug727429_watch-expressions-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_bug727429_watch-expressions-01.js
@@ -23,223 +23,215 @@ function test()
     gWatch = gDebugger.DebuggerView.WatchExpressions;
 
     gDebugger.DebuggerView.toggleInstrumentsPane({ visible: true, animated: false });
     performTest();
   });
 
   function performTest()
   {
-    is(gWatch.getExpressions().length, 0,
+    is(gWatch.getAllStrings().length, 0,
       "There should initially be no watch expressions");
 
     addAndCheckExpressions(1, 0, "a");
     addAndCheckExpressions(2, 0, "b");
     addAndCheckExpressions(3, 0, "c");
 
     removeAndCheckExpression(2, 1, "a");
     removeAndCheckExpression(1, 0, "a");
 
 
     addAndCheckExpressions(2, 0, "", true);
     gDebugger.editor.focus();
-    is(gWatch.getExpressions().length, 1,
+    is(gWatch.getAllStrings().length, 1,
       "Empty watch expressions are automatically removed");
 
+
     addAndCheckExpressions(2, 0, "a", true);
     gDebugger.editor.focus();
-    is(gWatch.getExpressions().length, 1,
+    is(gWatch.getAllStrings().length, 1,
       "Duplicate watch expressions are automatically removed");
 
     addAndCheckExpressions(2, 0, "a\t", true);
     addAndCheckExpressions(2, 0, "a\r", true);
     addAndCheckExpressions(2, 0, "a\n", true);
     gDebugger.editor.focus();
-    is(gWatch.getExpressions().length, 1,
+    is(gWatch.getAllStrings().length, 1,
       "Duplicate watch expressions are automatically removed");
 
     addAndCheckExpressions(2, 0, "\ta", true);
     addAndCheckExpressions(2, 0, "\ra", true);
     addAndCheckExpressions(2, 0, "\na", true);
     gDebugger.editor.focus();
-    is(gWatch.getExpressions().length, 1,
+    is(gWatch.getAllStrings().length, 1,
       "Duplicate watch expressions are automatically removed");
 
 
     addAndCheckCustomExpression(2, 0, "bazΩΩka");
     addAndCheckCustomExpression(3, 0, "bambøøcha");
 
 
     EventUtils.sendMouseEvent({ type: "click" },
       gWatch.getItemAtIndex(0).attachment.closeNode,
       gDebugger);
 
-    is(gWatch.getExpressions().length, 2,
+    is(gWatch.getAllStrings().length, 2,
       "Watch expressions are removed when the close button is pressed");
-    is(gWatch.getExpressions()[0], "bazΩΩka",
+    is(gWatch.getAllStrings()[0], "bazΩΩka",
       "The expression at index " + 0 + " should be correct (1)");
-    is(gWatch.getExpressions()[1], "a",
+    is(gWatch.getAllStrings()[1], "a",
       "The expression at index " + 1 + " should be correct (2)");
 
 
     EventUtils.sendMouseEvent({ type: "click" },
       gWatch.getItemAtIndex(0).attachment.closeNode,
       gDebugger);
 
-    is(gWatch.getExpressions().length, 1,
+    is(gWatch.getAllStrings().length, 1,
       "Watch expressions are removed when the close button is pressed");
-    is(gWatch.getExpressions()[0], "a",
+    is(gWatch.getAllStrings()[0], "a",
       "The expression at index " + 0 + " should be correct (3)");
 
 
     EventUtils.sendMouseEvent({ type: "click" },
       gWatch.getItemAtIndex(0).attachment.closeNode,
       gDebugger);
 
-    is(gWatch.getExpressions().length, 0,
+    is(gWatch.getAllStrings().length, 0,
       "Watch expressions are removed when the close button is pressed");
 
 
     EventUtils.sendMouseEvent({ type: "click" },
-      gWatch._container._parent,
+      gWatch.widget._parent,
       gDebugger);
 
-    is(gWatch.getExpressions().length, 1,
+    is(gWatch.getAllStrings().length, 1,
       "Watch expressions are added when the view container is pressed");
 
 
     closeDebuggerAndFinish();
   }
 
   function addAndCheckCustomExpression(total, index, string, noBlur) {
     addAndCheckExpressions(total, index, "", true);
 
     for (let i = 0; i < string.length; i++) {
       EventUtils.sendChar(string[i], gDebugger);
     }
 
     gDebugger.editor.focus();
 
-    let id = gWatch.getItemAtIndex(index).attachment.id;
-    let element = gDebugger.document.getElementById("expression-" + id);
+    let element = gWatch.getItemAtIndex(index).target;
 
     is(gWatch.getItemAtIndex(index).attachment.initialExpression, "",
       "The initial expression at index " + index + " should be correct (1)");
     is(gWatch.getItemForElement(element).attachment.initialExpression, "",
       "The initial expression at index " + index + " should be correct (2)");
 
     is(gWatch.getItemAtIndex(index).attachment.currentExpression, string,
       "The expression at index " + index + " should be correct (1)");
     is(gWatch.getItemForElement(element).attachment.currentExpression, string,
       "The expression at index " + index + " should be correct (2)");
 
-    is(gWatch.getExpression(index), string,
+    is(gWatch.getString(index), string,
       "The expression at index " + index + " should be correct (3)");
-    is(gWatch.getExpressions()[index], string,
+    is(gWatch.getAllStrings()[index], string,
       "The expression at index " + index + " should be correct (4)");
   }
 
   function addAndCheckExpressions(total, index, string, noBlur) {
     gWatch.addExpression(string);
 
-    is(gWatch.getExpressions().length, total,
+    is(gWatch.getAllStrings().length, total,
       "There should be " + total + " watch expressions available (1)");
     is(gWatch.itemCount, total,
       "There should be " + total + " watch expressions available (2)");
 
     ok(gWatch.getItemAtIndex(index),
       "The expression at index " + index + " should be available");
-    ok(gWatch.getItemAtIndex(index).attachment.id,
-      "The expression at index " + index + " should have an id");
     is(gWatch.getItemAtIndex(index).attachment.initialExpression, string,
       "The expression at index " + index + " should have an initial expression");
 
-    let id = gWatch.getItemAtIndex(index).attachment.id;
-    let element = gDebugger.document.getElementById("expression-" + id);
+    let element = gWatch.getItemAtIndex(index).target;
 
     ok(element,
-      "Three should be a new expression item in the view");
+      "There should be a new expression item in the view");
     ok(gWatch.getItemForElement(element),
       "The watch expression item should be accessible");
     is(gWatch.getItemForElement(element), gWatch.getItemAtIndex(index),
       "The correct watch expression item was accessed");
 
-    ok(gWatch.getItemAtIndex(index) instanceof gDebugger.MenuItem,
+    ok(gWatch.widget.getItemAtIndex(index) instanceof XULElement,
       "The correct watch expression element was accessed (1)");
-    ok(gWatch._container.getItemAtIndex(index) instanceof XULElement,
+    is(element, gWatch.widget.getItemAtIndex(index),
       "The correct watch expression element was accessed (2)");
-    is(element, gWatch._container.getItemAtIndex(index),
-      "The correct watch expression element was accessed (3)");
 
     is(gWatch.getItemForElement(element).attachment.arrowNode.hidden, false,
       "The arrow node should be visible");
     is(gWatch.getItemForElement(element).attachment.closeNode.hidden, false,
       "The close button should be visible");
     is(gWatch.getItemForElement(element).attachment.inputNode.getAttribute("focused"), "true",
       "The textbox input should be focused");
 
-    is(gWatch._variables.scrollTop, 0,
+    is(gDebugger.DebuggerView.Variables.parentNode.scrollTop, 0,
       "The variables view should be scrolled to top");
 
-    is(gWatch._cache[0], gWatch.getItemAtIndex(index),
+    is(gWatch.orderedItems[0], gWatch.getItemAtIndex(index),
       "The correct watch expression was added to the cache (1)");
-    is(gWatch._cache[0], gWatch.getItemForElement(element),
+    is(gWatch.orderedItems[0], gWatch.getItemForElement(element),
       "The correct watch expression was added to the cache (2)");
 
     if (!noBlur) {
       gDebugger.editor.focus();
 
       is(gWatch.getItemAtIndex(index).attachment.initialExpression, string,
         "The initial expression at index " + index + " should be correct (1)");
       is(gWatch.getItemForElement(element).attachment.initialExpression, string,
         "The initial expression at index " + index + " should be correct (2)");
 
       is(gWatch.getItemAtIndex(index).attachment.currentExpression, string,
         "The expression at index " + index + " should be correct (1)");
       is(gWatch.getItemForElement(element).attachment.currentExpression, string,
         "The expression at index " + index + " should be correct (2)");
 
-      is(gWatch.getExpression(index), string,
+      is(gWatch.getString(index), string,
         "The expression at index " + index + " should be correct (3)");
-      is(gWatch.getExpressions()[index], string,
+      is(gWatch.getAllStrings()[index], string,
         "The expression at index " + index + " should be correct (4)");
     }
   }
 
   function removeAndCheckExpression(total, index, string) {
-    gWatch.removeExpressionAt(index);
+    gWatch.removeAt(index);
 
-    is(gWatch.getExpressions().length, total,
+    is(gWatch.getAllStrings().length, total,
       "There should be " + total + " watch expressions available (1)");
     is(gWatch.itemCount, total,
       "There should be " + total + " watch expressions available (2)");
 
     ok(gWatch.getItemAtIndex(index),
       "The expression at index " + index + " should still be available");
-    ok(gWatch.getItemAtIndex(index).attachment.id,
-      "The expression at index " + index + " should still have an id");
     is(gWatch.getItemAtIndex(index).attachment.initialExpression, string,
       "The expression at index " + index + " should still have an initial expression");
 
-    let id = gWatch.getItemAtIndex(index).attachment.id;
-    let element = gDebugger.document.getElementById("expression-" + id);
+    let element = gWatch.getItemAtIndex(index).target;
 
     is(gWatch.getItemAtIndex(index).attachment.initialExpression, string,
       "The initial expression at index " + index + " should be correct (1)");
     is(gWatch.getItemForElement(element).attachment.initialExpression, string,
       "The initial expression at index " + index + " should be correct (2)");
 
     is(gWatch.getItemAtIndex(index).attachment.currentExpression, string,
       "The expression at index " + index + " should be correct (1)");
     is(gWatch.getItemForElement(element).attachment.currentExpression, string,
       "The expression at index " + index + " should be correct (2)");
 
-    is(gWatch.getExpression(index), string,
+    is(gWatch.getString(index), string,
       "The expression at index " + index + " should be correct (3)");
-    is(gWatch.getExpressions()[index], string,
+    is(gWatch.getAllStrings()[index], string,
       "The expression at index " + index + " should be correct (4)");
   }
 
   registerCleanupFunction(function() {
     removeTab(gTab);
     gPane = null;
     gTab = null;
     gDebuggee = null;
--- a/browser/devtools/debugger/test/browser_dbg_bug727429_watch-expressions-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_bug727429_watch-expressions-02.js
@@ -57,19 +57,19 @@ function test()
     gWatch.addExpression("4.2.toExponential(-4.2)");
     gWatch.addExpression("throw new Error(\"bazinga\")");
     gWatch.addExpression("({ get error() { throw new Error(\"bazinga\") } }).error");
     gWatch.addExpression("throw { get name() { throw \"bazinga\" } }");
   }
 
   function performTest()
   {
-    is(gWatch._container._parent.querySelectorAll(".dbg-expression[hidden=true]").length, 0,
+    is(gWatch.widget._parent.querySelectorAll(".dbg-expression[hidden=true]").length, 0,
       "There should be 0 hidden nodes in the watch expressions container");
-    is(gWatch._container._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 27,
+    is(gWatch.widget._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 27,
       "There should be 27 visible nodes in the watch expressions container");
 
     test1(function() {
       test2(function() {
         test3(function() {
           test4(function() {
             test5(function() {
               test6(function() {
@@ -85,139 +85,139 @@ function test()
           });
         });
       });
     });
   }
 
   function finishTest()
   {
-    is(gWatch._container._parent.querySelectorAll(".dbg-expression[hidden=true]").length, 0,
+    is(gWatch.widget._parent.querySelectorAll(".dbg-expression[hidden=true]").length, 0,
       "There should be 0 hidden nodes in the watch expressions container");
-    is(gWatch._container._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 27,
+    is(gWatch.widget._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 27,
       "There should be 27 visible nodes in the watch expressions container");
 
     closeDebuggerAndFinish();
   }
 
   function test1(callback) {
     waitForWatchExpressions(function() {
       info("Performing test1");
       checkWatchExpressions("ReferenceError: a is not defined",
                             { type: "object", class: "Object" },
                             { type: "object", class: "String" },
-                            undefined,
+                            { type: "undefined" },
                             26);
       callback();
     });
     executeSoon(function() {
       gDebuggee.test(); // ermahgerd!!
     });
   }
 
   function test2(callback) {
     waitForWatchExpressions(function() {
       info("Performing test2");
-      checkWatchExpressions(undefined,
+      checkWatchExpressions({ type: "undefined" },
                             { type: "object", class: "Window" },
-                            undefined,
+                            { type: "undefined" },
                             "sensational",
                             26);
       callback();
     });
     EventUtils.sendMouseEvent({ type: "mousedown" },
       gDebugger.document.getElementById("resume"),
       gDebugger);
   }
 
   function test3(callback) {
     waitForWatchExpressions(function() {
       info("Performing test3");
       checkWatchExpressions({ type: "object", class: "Object" },
                             { type: "object", class: "Window" },
-                            undefined,
+                            { type: "undefined" },
                             "sensational",
                             26);
       callback();
     });
     EventUtils.sendMouseEvent({ type: "mousedown" },
       gDebugger.document.getElementById("resume"),
       gDebugger);
   }
 
   function test4(callback) {
     waitForWatchExpressions(function() {
       info("Performing test4");
       checkWatchExpressions(5,
                             { type: "object", class: "Window" },
-                            undefined,
+                            { type: "undefined" },
                             "sensational",
                             27);
       callback();
     });
     executeSoon(function() {
       gWatch.addExpression("a = 5");
       EventUtils.sendKey("RETURN", gDebugger);
     });
   }
 
   function test5(callback) {
     waitForWatchExpressions(function() {
       info("Performing test5");
       checkWatchExpressions(5,
                             { type: "object", class: "Window" },
-                            undefined,
+                            { type: "undefined" },
                             "sensational",
                             27);
       callback();
     });
     executeSoon(function() {
       gWatch.addExpression("encodeURI(\"\\\")");
       EventUtils.sendKey("RETURN", gDebugger);
     });
   }
 
   function test6(callback) {
     waitForWatchExpressions(function() {
       info("Performing test6");
       checkWatchExpressions(5,
                             { type: "object", class: "Window" },
-                            undefined,
+                            { type: "undefined" },
                             "sensational",
                             27);
       callback();
     })
     executeSoon(function() {
       gWatch.addExpression("decodeURI(\"\\\")");
       EventUtils.sendKey("RETURN", gDebugger);
     });
   }
 
   function test7(callback) {
     waitForWatchExpressions(function() {
       info("Performing test7");
       checkWatchExpressions(5,
                             { type: "object", class: "Window" },
-                            undefined,
+                            { type: "undefined" },
                             "sensational",
                             27);
       callback();
     });
     executeSoon(function() {
       gWatch.addExpression("?");
       EventUtils.sendKey("RETURN", gDebugger);
     });
   }
 
   function test8(callback) {
     waitForWatchExpressions(function() {
       info("Performing test8");
       checkWatchExpressions(5,
                             { type: "object", class: "Window" },
-                            undefined,
+                            { type: "undefined" },
                             "sensational",
                             27);
       callback();
     });
     executeSoon(function() {
       gWatch.addExpression("a");
       EventUtils.sendKey("RETURN", gDebugger);
     });
@@ -248,19 +248,19 @@ function test()
   }
 
   function checkWatchExpressions(expected_a,
                                  expected_this,
                                  expected_prop,
                                  expected_arguments,
                                  total)
   {
-    is(gWatch._container._parent.querySelectorAll(".dbg-expression[hidden=true]").length, total,
+    is(gWatch.widget._parent.querySelectorAll(".dbg-expression[hidden=true]").length, total,
       "There should be " + total + " hidden nodes in the watch expressions container");
-    is(gWatch._container._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
+    is(gWatch.widget._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
       "There should be 0 visible nodes in the watch expressions container");
 
     let label = gDebugger.L10N.getStr("watchExpressionsScopeLabel");
     let scope = gVars._currHierarchy.get(label);
 
     ok(scope, "There should be a wach expressions scope in the variables view");
     is(scope._store.size, total, "There should be " + total + " evaluations availalble");
 
@@ -350,23 +350,34 @@ function test()
     is(w9.value.type, "object", "The ninth value type is correct");
     is(w9.value.class, "Array", "The ninth value class is correct");
     is(w10.value.type, "object", "The tenth value type is correct");
     is(w10.value.class, "Array", "The tenth value class is correct");
     is(w11.value, "4", "The eleventh value is correct");
     is(w12.value.type, "object", "The eleventh value type is correct");
     is(w12.value.class, "Array", "The twelfth value class is correct");
     is(w13.value, false, "The 13th value is correct");
-    is(w14.value, expected_arguments, "The 14th value is correct");
+
+    if (typeof expected_arguments == "object") {
+      is(w14.value.type, expected_arguments.type, "The 14th value type is correct");
+      is(w14.value.class, expected_arguments.class, "The 14th value class is correct");
+    } else {
+      is(w14.value, expected_arguments, "The 14th value is correct");
+    }
 
     is(w15.value, "SyntaxError: unterminated string literal", "The 15th value is correct");
     is(w16.value, "SyntaxError: unterminated string literal", "The 16th value is correct");
     is(w17.value, "URIError: malformed URI sequence", "The 17th value is correct");
-    is(w18.value, undefined, "The 18th value is correct");
-    is(w19.value, undefined, "The 19th value is correct");
+
+    is(w18.value.type, "undefined", "The 18th value type is correct");
+    is(w18.value.class, undefined, "The 18th value class is correct");
+
+    is(w19.value.type, "undefined", "The 19th value type is correct");
+    is(w19.value.class, undefined, "The 19th value class is correct");
+
     is(w20.value, "SyntaxError: syntax error", "The 20th value is correct");
     is(w21.value, "SyntaxError: syntax error", "The 21th value is correct");
     is(w22.value, "TypeError: (intermediate value).foo is not a function", "The 22th value is correct");
     is(w23.value, "RangeError: invalid array length", "The 23th value is correct");
     is(w24.value, "RangeError: precision -4 out of range", "The 24st value is correct");
     is(w25.value, "Error: bazinga", "The 25nd value is correct");
     is(w26.value, "Error: bazinga", "The 26rd value is correct");
   }
--- a/browser/devtools/debugger/test/browser_dbg_bug740825_conditional-breakpoints-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_bug740825_conditional-breakpoints-01.js
@@ -132,39 +132,39 @@ function test()
 
   function test10(callback)
   {
     gDebugger.addEventListener("Debugger:AfterFramesCleared", function listener() {
       gDebugger.removeEventListener("Debugger:AfterFramesCleared", listener, true);
 
       isnot(gSources.selectedItem, null,
         "There should be a selected script in the scripts pane.")
-      is(gSources.selectedBreakpoint, null,
+      is(gSources.selectedBreakpointItem, null,
         "There should be no selected breakpoint in the scripts pane.")
-      is(gSources.selectedClient, null,
+      is(gSources.selectedBreakpointClient, null,
         "There should be no selected client in the scripts pane.");
       is(gSources._conditionalPopupVisible, false,
         "The breakpoint conditional expression popup should not be shown.");
 
-      is(gDebugger.DebuggerView.StackFrames._container._list.querySelectorAll(".dbg-stackframe").length, 0,
+      is(gDebugger.DebuggerView.StackFrames.widget._list.querySelectorAll(".dbg-stackframe").length, 0,
         "There should be no visible stackframes.");
-      is(gDebugger.DebuggerView.Sources._container._list.querySelectorAll(".dbg-breakpoint").length, 13,
+      is(gDebugger.DebuggerView.Sources.widget._list.querySelectorAll(".dbg-breakpoint").length, 13,
         "There should be thirteen visible breakpoints.");
 
       testReload();
     }, true);
 
     gDebugger.DebuggerController.activeThread.resume();
   }
 
   function resumeAndTestBreakpoint(url, line, callback)
   {
     resume(line, function() {
       waitForCaretPos(line - 1, function() {
-        testBreakpoint(gSources.selectedBreakpoint, gSources.selectedClient, url, line, true);
+        testBreakpoint(gSources.selectedBreakpointItem, gSources.selectedBreakpointClient, url, line, true);
         callback();
       });
     });
   }
 
   function testBreakpoint(aBreakpointItem, aBreakpointClient, url, line, editor)
   {
     is(aBreakpointItem.attachment.sourceLocation, gSources.selectedValue,
@@ -276,19 +276,19 @@ function test()
         testBreakpoint.apply(this, _get(gSources.selectedValue, 18));
         testBreakpoint.apply(this, _get(gSources.selectedValue, 19));
         testBreakpoint.apply(this, _get(gSources.selectedValue, 21));
         testBreakpoint.apply(this, _get(gSources.selectedValue, 22));
         testBreakpoint.apply(this, _get(gSources.selectedValue, 23));
 
       isnot(gSources.selectedItem, null,
         "There should be a selected script in the scripts pane.")
-      is(gSources.selectedBreakpoint, null,
+      is(gSources.selectedBreakpointItem, null,
         "There should be no selected breakpoint in the scripts pane.")
-      is(gSources.selectedClient, null,
+      is(gSources.selectedBreakpointClient, null,
         "There should be no selected client in the scripts pane.");
       is(gSources._conditionalPopupVisible, false,
         "The breakpoint conditional expression popup should not be shown.");
 
         closeDebuggerAndFinish();
       });
     });
 
@@ -325,18 +325,18 @@ function test()
     let count = 0;
     let intervalID = window.setInterval(function() {
       info("count: " + count + " ");
       if (++count > 50) {
         ok(false, "Timed out while polling for the breakpoint.");
         window.clearInterval(intervalID);
         return closeDebuggerAndFinish();
       }
-      if ((gSources.selectedClient !== expected) &&
-          (gSources.selectedClient || bogusClient).location.line !== expected) {
+      if ((gSources.selectedBreakpointClient !== expected) &&
+          (gSources.selectedBreakpointClient || bogusClient).location.line !== expected) {
         return;
       }
       // We arrived at the expected line, it's safe to callback.
       window.clearInterval(intervalID);
       callback();
     }, 100);
   }
 
@@ -346,17 +346,17 @@ function test()
     let count = 0;
     let intervalID = window.setInterval(function() {
       info("count: " + count + " ");
       if (++count > 50) {
         ok(false, "Timed out while polling for the breakpoints.");
         window.clearInterval(intervalID);
         return closeDebuggerAndFinish();
       }
-      if (gSources._container._list.querySelectorAll(".dbg-breakpoint").length != total) {
+      if (gSources.widget._list.querySelectorAll(".dbg-breakpoint").length != total) {
         return;
       }
       // We got all the breakpoints, it's safe to callback.
       window.clearInterval(intervalID);
       callback();
     }, 100);
   }
 
--- a/browser/devtools/debugger/test/browser_dbg_bug740825_conditional-breakpoints-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_bug740825_conditional-breakpoints-02.js
@@ -87,89 +87,89 @@ function test()
 
   function addBreakpoint1()
   {
     gPane.addBreakpoint({ url: gSources.selectedValue, line: 12 });
 
     waitForBreakpoint(12, function() {
       waitForCaretPos(10, function() {
         waitForPopup(false, function() {
-          testBreakpoint(gSources.selectedBreakpoint,
-                         gSources.selectedClient,
+          testBreakpoint(gSources.selectedBreakpointItem,
+                         gSources.selectedBreakpointClient,
                          gSources.selectedValue, 12, false, false, false);
 
           executeSoon(addBreakpoint2);
         });
       });
     });
   }
 
   function addBreakpoint2()
   {
     gSources._editorContextMenuLineNumber = 12;
     gSources._onCmdAddBreakpoint();
 
     waitForBreakpoint(13, function() {
       waitForCaretPos(12, function() {
         waitForPopup(false, function() {
-          testBreakpoint(gSources.selectedBreakpoint,
-                         gSources.selectedClient,
+          testBreakpoint(gSources.selectedBreakpointItem,
+                         gSources.selectedBreakpointClient,
                          gSources.selectedValue, 13, false, false, true);
 
           executeSoon(modBreakpoint2);
         });
       });
     });
   }
 
   function modBreakpoint2()
   {
     gSources._editorContextMenuLineNumber = 12;
     gSources._onCmdAddConditionalBreakpoint();
 
     waitForBreakpoint(13, function() {
       waitForCaretPos(12, function() {
         waitForPopup(true, function() {
-          testBreakpoint(gSources.selectedBreakpoint,
-                         gSources.selectedClient,
+          testBreakpoint(gSources.selectedBreakpointItem,
+                         gSources.selectedBreakpointClient,
                          gSources.selectedValue, 13, true, true, true);
 
           executeSoon(addBreakpoint3);
         });
       });
     });
   }
 
   function addBreakpoint3()
   {
     gSources._editorContextMenuLineNumber = 13;
     gSources._onCmdAddConditionalBreakpoint();
 
     waitForBreakpoint(14, function() {
       waitForCaretPos(13, function() {
         waitForPopup(true, function() {
-          testBreakpoint(gSources.selectedBreakpoint,
-                         gSources.selectedClient,
+          testBreakpoint(gSources.selectedBreakpointItem,
+                         gSources.selectedBreakpointClient,
                          gSources.selectedValue, 14, true, true, true);
 
           executeSoon(modBreakpoint3);
         });
       });
     });
   }
 
   function modBreakpoint3()
   {
     write("bamboocha");
     EventUtils.sendKey("RETURN", gDebugger);
 
     waitForBreakpoint(14, function() {
       waitForCaretPos(13, function() {
         waitForPopup(false, function() {
-          is(gSources.selectedClient.conditionalExpression, "bamboocha",
+          is(gSources.selectedBreakpointClient.conditionalExpression, "bamboocha",
             "The bamboocha expression wasn't fonud on the conditional breakpoint");
 
           executeSoon(setContextMenu);
         });
       });
     });
   }
 
@@ -205,35 +205,35 @@ function test()
   function addBreakpoint4()
   {
     gEditor.setCaretPosition(14);
     gSources._onCmdAddBreakpoint();
 
     waitForBreakpoint(15, function() {
       waitForCaretPos(14, function() {
         waitForPopup(false, function() {
-          testBreakpoint(gSources.selectedBreakpoint,
-                         gSources.selectedClient,
+          testBreakpoint(gSources.selectedBreakpointItem,
+                         gSources.selectedBreakpointClient,
                          gSources.selectedValue, 15, false, false, true);
 
           executeSoon(delBreakpoint4);
         });
       });
     });
   }
 
   function delBreakpoint4()
   {
     gEditor.setCaretPosition(14);
     gSources._onCmdAddBreakpoint();
 
     waitForBreakpoint(null, function() {
       waitForCaretPos(14, function() {
         waitForPopup(false, function() {
-          is(gSources.selectedBreakpoint, null,
+          is(gSources.selectedBreakpointItem, null,
             "There should be no selected breakpoint in the breakpoints pane.")
           is(gSources._conditionalPopupVisible, false,
             "The breakpoint conditional expression popup should not be shown.");
 
           executeSoon(moveHighlight1);
         });
       });
     });
@@ -241,93 +241,93 @@ function test()
 
   function moveHighlight1()
   {
     gEditor.setCaretPosition(13);
 
     waitForBreakpoint(14, function() {
       waitForCaretPos(13, function() {
         waitForPopup(false, function() {
-          testBreakpoint(gSources.selectedBreakpoint,
-                         gSources.selectedClient,
+          testBreakpoint(gSources.selectedBreakpointItem,
+                         gSources.selectedBreakpointClient,
                          gSources.selectedValue, 14, false, true, true);
 
           executeSoon(testHighlights1);
         });
       });
     });
   }
 
   function testHighlights1()
   {
-    isnot(gSources.selectedBreakpoint, null,
+    isnot(gSources.selectedBreakpointItem, null,
       "There should be a selected breakpoint in the breakpoints pane.");
-    is(gSources.selectedBreakpoint.attachment.sourceLocation, gSources.selectedValue,
+    is(gSources.selectedBreakpointItem.attachment.sourceLocation, gSources.selectedValue,
       "The selected breakpoint should have the correct location.");
-    is(gSources.selectedBreakpoint.attachment.lineNumber, 14,
+    is(gSources.selectedBreakpointItem.attachment.lineNumber, 14,
       "The selected breakpoint should have the correct line number.");
     is(gSources._conditionalPopupVisible, false,
       "The breakpoint conditional expression popup should not be shown.");
     is(gEditor.getCaretPosition().line, 13,
       "The source editor caret position should be at line 13");
     is(gEditor.getCaretPosition().col, 0,
       "The source editor caret position should be at column 0");
 
     gEditor.setCaretPosition(12);
 
     waitForCaretPos(12, function() {
       waitForPopup(false, function() {
-        isnot(gSources.selectedBreakpoint, null,
+        isnot(gSources.selectedBreakpointItem, null,
           "There should be a selected breakpoint in the breakpoints pane.");
-        is(gSources.selectedBreakpoint.attachment.sourceLocation, gSources.selectedValue,
+        is(gSources.selectedBreakpointItem.attachment.sourceLocation, gSources.selectedValue,
           "The selected breakpoint should have the correct location.");
-        is(gSources.selectedBreakpoint.attachment.lineNumber, 13,
+        is(gSources.selectedBreakpointItem.attachment.lineNumber, 13,
           "The selected breakpoint should have the correct line number.");
         is(gSources._conditionalPopupVisible, false,
           "The breakpoint conditional expression popup should not be shown.");
         is(gEditor.getCaretPosition().line, 12,
           "The source editor caret position should be at line 12");
         is(gEditor.getCaretPosition().col, 0,
           "The source editor caret position should be at column 0");
 
         gEditor.setCaretPosition(11);
 
         waitForCaretPos(11, function() {
           waitForPopup(false, function() {
-            isnot(gSources.selectedBreakpoint, null,
+            isnot(gSources.selectedBreakpointItem, null,
               "There should be a selected breakpoint in the breakpoints pane.");
-            is(gSources.selectedBreakpoint.attachment.sourceLocation, gSources.selectedValue,
+            is(gSources.selectedBreakpointItem.attachment.sourceLocation, gSources.selectedValue,
               "The selected breakpoint should have the correct location.");
-            is(gSources.selectedBreakpoint.attachment.lineNumber, 12,
+            is(gSources.selectedBreakpointItem.attachment.lineNumber, 12,
               "The selected breakpoint should have the correct line number.");
             is(gSources._conditionalPopupVisible, false,
               "The breakpoint conditional expression popup should not be shown.");
             is(gEditor.getCaretPosition().line, 11,
               "The source editor caret position should be at line 11");
             is(gEditor.getCaretPosition().col, 0,
               "The source editor caret position should be at column 0");
 
             gEditor.setCaretPosition(10);
 
             waitForCaretPos(10, function() {
               waitForPopup(false, function() {
-                is(gSources.selectedBreakpoint, null,
+                is(gSources.selectedBreakpointItem, null,
                   "There should not be a selected breakpoint in the breakpoints pane.");
                 is(gSources._conditionalPopupVisible, false,
                   "The breakpoint conditional expression popup should not be shown.");
                 is(gEditor.getCaretPosition().line, 10,
                   "The source editor caret position should be at line 10");
                 is(gEditor.getCaretPosition().col, 0,
                   "The source editor caret position should be at column 0");
 
                 gEditor.setCaretPosition(14);
 
                 waitForCaretPos(14, function() {
                   waitForPopup(false, function() {
-                    is(gSources.selectedBreakpoint, null,
+                    is(gSources.selectedBreakpointItem, null,
                       "There should not be a selected breakpoint in the breakpoints pane.");
                     is(gSources._conditionalPopupVisible, false,
                       "The breakpoint conditional expression popup should not be shown.");
                     is(gEditor.getCaretPosition().line, 14,
                       "The source editor caret position should be at line 14");
                     is(gEditor.getCaretPosition().col, 0,
                       "The source editor caret position should be at column 0");
 
@@ -340,64 +340,64 @@ function test()
         });
       });
     });
   }
 
   function testHighlights2()
   {
     EventUtils.sendMouseEvent({ type: "click" },
-      gSources._container._list.querySelectorAll(".dbg-breakpoint")[2],
+      gSources.widget._list.querySelectorAll(".dbg-breakpoint")[2],
       gDebugger);
 
     waitForCaretPos(13, function() {
       waitForPopup(true, function() {
-        isnot(gSources.selectedBreakpoint, null,
+        isnot(gSources.selectedBreakpointItem, null,
           "There should be a selected breakpoint in the breakpoints pane.");
-        is(gSources.selectedBreakpoint.attachment.sourceLocation, gSources.selectedValue,
+        is(gSources.selectedBreakpointItem.attachment.sourceLocation, gSources.selectedValue,
           "The selected breakpoint should have the correct location.");
-        is(gSources.selectedBreakpoint.attachment.lineNumber, 14,
+        is(gSources.selectedBreakpointItem.attachment.lineNumber, 14,
           "The selected breakpoint should have the correct line number.");
         is(gSources._conditionalPopupVisible, true,
           "The breakpoint conditional expression popup should be shown.");
         is(gEditor.getCaretPosition().line, 13,
           "The source editor caret position should be at line 13");
         is(gEditor.getCaretPosition().col, 0,
           "The source editor caret position should be at column 0");
 
         EventUtils.sendMouseEvent({ type: "click" },
-          gSources._container._list.querySelectorAll(".dbg-breakpoint")[1],
+          gSources.widget._list.querySelectorAll(".dbg-breakpoint")[1],
           gDebugger);
 
         waitForCaretPos(12, function() {
           waitForPopup(true, function() {
-            isnot(gSources.selectedBreakpoint, null,
+            isnot(gSources.selectedBreakpointItem, null,
               "There should be a selected breakpoint in the breakpoints pane.");
-            is(gSources.selectedBreakpoint.attachment.sourceLocation, gSources.selectedValue,
+            is(gSources.selectedBreakpointItem.attachment.sourceLocation, gSources.selectedValue,
               "The selected breakpoint should have the correct location.");
-            is(gSources.selectedBreakpoint.attachment.lineNumber, 13,
+            is(gSources.selectedBreakpointItem.attachment.lineNumber, 13,
               "The selected breakpoint should have the correct line number.");
             is(gSources._conditionalPopupVisible, true,
               "The breakpoint conditional expression popup should be shown.");
             is(gEditor.getCaretPosition().line, 12,
               "The source editor caret position should be at line 12");
             is(gEditor.getCaretPosition().col, 0,
               "The source editor caret position should be at column 0");
 
             EventUtils.sendMouseEvent({ type: "click" },
-              gSources._container._list.querySelectorAll(".dbg-breakpoint")[0],
+              gSources.widget._list.querySelectorAll(".dbg-breakpoint")[0],
               gDebugger);
 
             waitForCaretPos(11, function() {
               waitForPopup(false, function() {
-                isnot(gSources.selectedBreakpoint, null,
+                isnot(gSources.selectedBreakpointItem, null,
                   "There should be a selected breakpoint in the breakpoints pane.");
-                is(gSources.selectedBreakpoint.attachment.sourceLocation, gSources.selectedValue,
+                is(gSources.selectedBreakpointItem.attachment.sourceLocation, gSources.selectedValue,
                   "The selected breakpoint should have the correct location.");
-                is(gSources.selectedBreakpoint.attachment.lineNumber, 12,
+                is(gSources.selectedBreakpointItem.attachment.lineNumber, 12,
                   "The selected breakpoint should have the correct line number.");
                 is(gSources._conditionalPopupVisible, false,
                   "The breakpoint conditional expression popup should be shown.");
                 is(gEditor.getCaretPosition().line, 11,
                   "The source editor caret position should be at line 11");
                 is(gEditor.getCaretPosition().col, 0,
                   "The source editor caret position should be at column 0");
 
@@ -412,34 +412,34 @@ function test()
 
   function delBreakpoint2()
   {
     gSources._editorContextMenuLineNumber = 12;
     gSources._onCmdAddBreakpoint();
 
     waitForBreakpoint(null, function() {
       waitForPopup(false, function() {
-        is(gSources.selectedBreakpoint, null,
+        is(gSources.selectedBreakpointItem, null,
           "There should be no selected breakpoint in the breakpoints pane.")
         is(gSources._conditionalPopupVisible, false,
           "The breakpoint conditional expression popup should not be shown.");
 
         executeSoon(delBreakpoint3);
       });
     });
   }
 
   function delBreakpoint3()
   {
     gSources._editorContextMenuLineNumber = 13;
     gSources._onCmdAddBreakpoint();
 
     waitForBreakpoint(null, function() {
       waitForPopup(false, function() {
-        is(gSources.selectedBreakpoint, null,
+        is(gSources.selectedBreakpointItem, null,
           "There should be no selected breakpoint in the breakpoints pane.")
         is(gSources._conditionalPopupVisible, false,
           "The breakpoint conditional expression popup should not be shown.");
 
         executeSoon(testBreakpoints);
       });
     });
   }
@@ -497,18 +497,18 @@ function test()
     let count = 0;
     let intervalID = window.setInterval(function() {
       info("count: " + count + " ");
       if (++count > 50) {
         ok(false, "Timed out while polling for the breakpoint.");
         window.clearInterval(intervalID);
         return closeDebuggerAndFinish();
       }
-      if ((gSources.selectedClient !== expected) &&
-          (gSources.selectedClient || bogusClient).location.line !== expected) {
+      if ((gSources.selectedBreakpointClient !== expected) &&
+          (gSources.selectedBreakpointClient || bogusClient).location.line !== expected) {
         return;
       }
       // We arrived at the expected line, it's safe to callback.
       window.clearInterval(intervalID);
       callback();
     }, 100);
   }
 
--- a/browser/devtools/debugger/test/browser_dbg_createChrome.js
+++ b/browser/devtools/debugger/test/browser_dbg_createChrome.js
@@ -59,18 +59,17 @@ function testSimpleCall() {
 
     ok(profile,
       "The remote debugger profile wasn't *actually* created properly!");
     is(profile.localDir.path, gProcess._dbgProfile.localDir.path,
       "The remote debugger profile doesn't have the correct localDir!");
     is(profile.rootDir.path, gProcess._dbgProfile.rootDir.path,
       "The remote debugger profile doesn't have the correct rootDir!");
 
-    let chromeDebug = DebuggerUI.toggleChromeDebugger();
-    info("toggleChromeDebugger() returned " + chromeDebug);
+    gProcess.close();
   }}, 0);
 }
 
 function aOnClosing() {
   ok(!gProcess._dbgProcess.isRunning,
     "The remote debugger process isn't closed as it should be!");
   is(gProcess._dbgProcess.exitValue, (Services.appinfo.OS == "WINNT" ? 0 : 256),
     "The remote debugger process didn't die cleanly.");
deleted file mode 100644
--- a/browser/devtools/debugger/test/browser_dbg_createRemote.js
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
-// Tests that a remote debugger can be created in a new window.
-
-var gWindow = null;
-var gTab = null;
-var gRemoteHost = null;
-var gRemotePort = null;
-var gRemoteTimeout = null;
-var gAutoConnect = null;
-
-const TEST_URL = EXAMPLE_URL + "browser_dbg_iframes.html";
-
-function test() {
-  debug_remote(TEST_URL, function(aTab, aDebuggee, aWindow) {
-    gTab = aTab;
-    gWindow = aWindow;
-    let gDebugger = gWindow.panelWin;
-
-    info("Current remote window x: " +
-      Services.prefs.getIntPref("devtools.debugger.ui.win-x"));
-    info("Current remote window y: " +
-      Services.prefs.getIntPref("devtools.debugger.ui.win-y"));
-    info("Current remote window width: " +
-      Services.prefs.getIntPref("devtools.debugger.ui.win-width"));
-    info("Current remote window height: " +
-      Services.prefs.getIntPref("devtools.debugger.ui.win-height"));
-
-    is(gDebugger.Prefs.windowX,
-      Services.prefs.getIntPref("devtools.debugger.ui.win-x"),
-      "Current window x pref corresponds to the debugger pref.");
-
-    is(gDebugger.Prefs.windowY,
-      Services.prefs.getIntPref("devtools.debugger.ui.win-y"),
-      "Current window y pref corresponds to the debugger pref.");
-
-    is(gDebugger.Prefs.windowWidth,
-      Services.prefs.getIntPref("devtools.debugger.ui.win-width"),
-      "Current window width pref corresponds to the debugger pref.");
-
-    is(gDebugger.Prefs.windowHeight,
-      Services.prefs.getIntPref("devtools.debugger.ui.win-height"),
-      "Current window height pref corresponds to the debugger pref.");
-
-
-    info("Current remote host: " +
-      Services.prefs.getCharPref("devtools.debugger.remote-host"));
-    info("Current remote port: " +
-      Services.prefs.getIntPref("devtools.debugger.remote-port"));
-    info("Current remote retries: " +
-      Services.prefs.getIntPref("devtools.debugger.remote-connection-retries"));
-    info("Current remote timeout: " +
-      Services.prefs.getIntPref("devtools.debugger.remote-timeout"));
-    info("Current autoconnect flag: " +
-      Services.prefs.getBoolPref("devtools.debugger.remote-autoconnect"));
-
-    is(gDebugger.Prefs.remoteHost,
-      Services.prefs.getCharPref("devtools.debugger.remote-host"),
-      "Current remote host corresponds to the debugger pref.");
-
-    is(gDebugger.Prefs.remotePort,
-      Services.prefs.getIntPref("devtools.debugger.remote-port"),
-      "Current remote port corresponds to the debugger pref.");
-
-    is(gDebugger.Prefs.remoteConnectionRetries,
-      Services.prefs.getIntPref("devtools.debugger.remote-connection-retries"),
-      "Current remote retries corresponds to the debugger pref.");
-
-    is(gDebugger.Prefs.remoteTimeout,
-      Services.prefs.getIntPref("devtools.debugger.remote-timeout"),
-      "Current remote timeout corresponds to the debugger pref.");
-
-    is(gDebugger.Prefs.remoteAutoConnect,
-      Services.prefs.getBoolPref("devtools.debugger.remote-autoconnect"),
-      "Current autoconnect flag corresponds to the debugger pref.");
-
-
-    is(gDebugger.document.getElementById("close").getAttribute("hidden"), "true",
-      "The close button should be hidden in a remote debugger.");
-
-    is(gDebugger.DebuggerController.activeThread.paused, false,
-      "Should be running after debug_remote.");
-
-    gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
-      Services.tm.currentThread.dispatch({ run: function() {
-
-        let frames = gDebugger.DebuggerView.StackFrames._container._list;
-        let childNodes = frames.childNodes;
-
-        is(gDebugger.DebuggerController.activeThread.paused, true,
-          "Should be paused after an interrupt request.");
-
-        is(frames.querySelectorAll(".dbg-stackframe").length, 1,
-          "Should have one frame in the stack.");
-
-        gDebugger.DebuggerController.activeThread.addOneTimeListener("resumed", function() {
-          Services.tm.currentThread.dispatch({ run: function() {
-            closeDebuggerAndFinish(true);
-          }}, 0);
-        });
-
-        EventUtils.sendMouseEvent({ type: "mousedown" },
-          gDebugger.document.getElementById("resume"),
-          gDebugger);
-      }}, 0);
-    });
-
-    let iframe = gTab.linkedBrowser.contentWindow.wrappedJSObject.frames[0];
-    is(iframe.document.title, "Browser Debugger Test Tab", "Found the iframe");
-
-    function handler() {
-      if (iframe.document.readyState != "complete") {
-        return;
-      }
-      iframe.window.removeEventListener("load", handler, false);
-      executeSoon(iframe.runDebuggerStatement);
-    };
-    iframe.window.addEventListener("load", handler, false);
-    handler();
-  },
-  function beforeTabAdded() {
-    if (!DebuggerServer.initialized) {
-      DebuggerServer.init(function() { return true; });
-      DebuggerServer.addBrowserActors();
-    }
-    DebuggerServer.closeListener();
-
-    gRemoteHost = Services.prefs.getCharPref("devtools.debugger.remote-host");
-    gRemotePort = Services.prefs.getIntPref("devtools.debugger.remote-port");
-    gRemoteTimeout = Services.prefs.getIntPref("devtools.debugger.remote-timeout");
-    gAutoConnect = Services.prefs.getBoolPref("devtools.debugger.remote-autoconnect");
-
-    // Open the listener at some point in the future to test automatic reconnect.
-    openListener(gRemoteHost, gRemotePort + 1, gRemoteTimeout / 10);
-  });
-}
-
-let attempts = 0;
-
-function openListener(host, port, timeout) {
-  Services.prefs.setCharPref("devtools.debugger.remote-host", host);
-  Services.prefs.setIntPref("devtools.debugger.remote-port", port);
-  Services.prefs.setIntPref("devtools.debugger.remote-timeout", timeout);
-  Services.prefs.setBoolPref("devtools.debugger.remote-autoconnect", true);
-
-  info("Attempting to open a new listener on port " + port);
-  try {
-    info("Closing listener...");
-    DebuggerServer.closeListener();
-    info("Opening listener...");
-    DebuggerServer.openListener(port);
-  } catch (e) {
-    info(e);
-    info("Exception caught when opening listener on port " + port);
-    info("Retrying with port " + (++port));
-
-    if (++attempts < 100) {
-      DebuggerServer.closeListener();
-      openListener(port);
-    } else {
-      ok(false, "Timed out while opening a listener.");
-    }
-  }
-}
-
-registerCleanupFunction(function() {
-  Services.prefs.setCharPref("devtools.debugger.remote-host", gRemoteHost);
-  Services.prefs.setIntPref("devtools.debugger.remote-port", gRemotePort);
-  Services.prefs.setIntPref("devtools.debugger.remote-timeout", gRemoteTimeout);
-  Services.prefs.setBoolPref("devtools.debugger.remote-autoconnect", gAutoConnect);
-  removeTab(gTab);
-  gWindow = null;
-  gTab = null;
-  gRemoteHost = null;
-  gRemotePort = null;
-  gRemoteTimeout = null;
-  gAutoConnect = null;
-});
--- a/browser/devtools/debugger/test/browser_dbg_displayName.js
+++ b/browser/devtools/debugger/test/browser_dbg_displayName.js
@@ -23,17 +23,17 @@ function test() {
     testAnonCall();
   });
 }
 
 function testAnonCall() {
   gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
     Services.tm.currentThread.dispatch({ run: function() {
 
-      let frames = gDebugger.DebuggerView.StackFrames._container._list;
+      let frames = gDebugger.DebuggerView.StackFrames.widget._list;
 
       is(gDebugger.DebuggerController.activeThread.state, "paused",
         "Should only be getting stack frames while paused.");
 
       is(frames.querySelectorAll(".dbg-stackframe").length, 3,
         "Should have three frames.");
 
       is(frames.querySelector("#stackframe-0 .dbg-stackframe-title").getAttribute("value"),
@@ -45,17 +45,17 @@ function testAnonCall() {
 
   gDebuggee.evalCall();
 }
 
 function testInferredName() {
   gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
     Services.tm.currentThread.dispatch({ run: function() {
 
-      let frames = gDebugger.DebuggerView.StackFrames._container._list;
+      let frames = gDebugger.DebuggerView.StackFrames.widget._list;
 
       is(gDebugger.DebuggerController.activeThread.state, "paused",
         "Should only be getting stack frames while paused.");
 
       is(frames.querySelectorAll(".dbg-stackframe").length, 3,
         "Should have three frames.");
 
       is(frames.querySelector("#stackframe-0 .dbg-stackframe-title").getAttribute("value"),
--- a/browser/devtools/debugger/test/browser_dbg_iframes.js
+++ b/browser/devtools/debugger/test/browser_dbg_iframes.js
@@ -18,17 +18,17 @@ function test() {
     gDebugger = gPane.panelWin;
 
     is(gDebugger.DebuggerController.activeThread.paused, false,
       "Should be running after debug_tab_pane.");
 
     gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
       Services.tm.currentThread.dispatch({ run: function() {
 
-        let frames = gDebugger.DebuggerView.StackFrames._container._list;
+        let frames = gDebugger.DebuggerView.StackFrames.widget._list;
         let childNodes = frames.childNodes;
 
         is(gDebugger.DebuggerController.activeThread.paused, true,
           "Should be paused after an interrupt request.");
 
         is(frames.querySelectorAll(".dbg-stackframe").length, 1,
           "Should have one frame in the stack.");
 
--- a/browser/devtools/debugger/test/browser_dbg_location-changes-blank.js
+++ b/browser/devtools/debugger/test/browser_dbg_location-changes-blank.js
@@ -43,17 +43,17 @@ function test()
   {
     if (scriptShown && framesAdded) {
       Services.tm.currentThread.dispatch({ run: testSimpleCall }, 0);
     }
   }
 }
 
 function testSimpleCall() {
-  var frames = gDebugger.DebuggerView.StackFrames._container._list,
+  var frames = gDebugger.DebuggerView.StackFrames.widget._list,
       childNodes = frames.childNodes;
 
   is(gDebugger.DebuggerController.activeThread.state, "paused",
     "Should only be getting stack frames while paused.");
 
   is(frames.querySelectorAll(".dbg-stackframe").length, 1,
     "Should have only one frame.");
 
@@ -80,17 +80,17 @@ function testLocationChange()
       gDebugger.addEventListener("Debugger:AfterSourcesAdded", function _onEvent(aEvent) {
         gDebugger.removeEventListener(aEvent.type, _onEvent);
 
         is(gDebugger.DebuggerView.Sources.selectedValue, "",
           "There should be no selected script.");
         is(gDebugger.editor.getText().length, 0,
           "The source editor not have any text displayed.");
 
-        let menulist = gDebugger.DebuggerView.Sources._container;
+        let menulist = gDebugger.DebuggerView.Sources.widget;
         let noScripts = gDebugger.L10N.getStr("noSourcesText");
         is(menulist.getAttribute("label"), noScripts,
           "The menulist should display a notice that there are no scripts availalble.");
         is(menulist.getAttribute("tooltiptext"), "",
           "The menulist shouldn't have any tooltip text attributed when there are no scripts available.");
 
         closeDebuggerAndFinish();
       });
--- a/browser/devtools/debugger/test/browser_dbg_location-changes-bp.js
+++ b/browser/devtools/debugger/test/browser_dbg_location-changes-bp.js
@@ -30,17 +30,17 @@ function test()
 }
 
 function testAddBreakpoint()
 {
   let controller = gDebugger.DebuggerController;
   controller.activeThread.addOneTimeListener("framesadded", function() {
     Services.tm.currentThread.dispatch({ run: function() {
 
-      var frames = gDebugger.DebuggerView.StackFrames._container._list;
+      var frames = gDebugger.DebuggerView.StackFrames.widget._list;
 
       is(controller.activeThread.state, "paused",
          "The debugger statement was reached.");
 
       is(frames.querySelectorAll(".dbg-stackframe").length, 1,
          "Should have one frame.");
 
       gPane.addBreakpoint({ url: SCRIPT_URL, line: 5 }, testResume);
--- a/browser/devtools/debugger/test/browser_dbg_location-changes-new.js
+++ b/browser/devtools/debugger/test/browser_dbg_location-changes-new.js
@@ -43,17 +43,17 @@ function test()
   {
     if (scriptShown && framesAdded) {
       Services.tm.currentThread.dispatch({ run: testSimpleCall }, 0);
     }
   }
 }
 
 function testSimpleCall() {
-  var frames = gDebugger.DebuggerView.StackFrames._container._list,
+  var frames = gDebugger.DebuggerView.StackFrames.widget._list,
       childNodes = frames.childNodes;
 
   is(gDebugger.DebuggerController.activeThread.state, "paused",
     "Should only be getting stack frames while paused.");
 
   is(frames.querySelectorAll(".dbg-stackframe").length, 1,
     "Should have only one frame.");
 
@@ -80,17 +80,17 @@ function testLocationChange()
       gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
         gDebugger.removeEventListener(aEvent.type, _onEvent);
 
         isnot(gDebugger.DebuggerView.Sources.selectedValue, null,
           "There should be a selected script.");
         isnot(gDebugger.editor.getText().length, 0,
           "The source editor should have some text displayed.");
 
-        let menulist = gDebugger.DebuggerView.Sources._container;
+        let menulist = gDebugger.DebuggerView.Sources.widget;
         let noScripts = gDebugger.L10N.getStr("noSourcesText");
         isnot(menulist.getAttribute("label"), noScripts,
           "The menulist should not display a notice that there are no scripts availalble.");
         isnot(menulist.getAttribute("tooltiptext"), "",
           "The menulist should have a tooltip text attributed.");
 
         closeDebuggerAndFinish();
       });
--- a/browser/devtools/debugger/test/browser_dbg_location-changes.js
+++ b/browser/devtools/debugger/test/browser_dbg_location-changes.js
@@ -22,17 +22,17 @@ function test()
     testSimpleCall();
   });
 }
 
 function testSimpleCall() {
   gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
     Services.tm.currentThread.dispatch({
       run: function() {
-        var frames = gDebugger.DebuggerView.StackFrames._container._list,
+        var frames = gDebugger.DebuggerView.StackFrames.widget._list,
             childNodes = frames.childNodes;
 
         is(gDebugger.DebuggerController.activeThread.state, "paused",
           "Should only be getting stack frames while paused.");
 
         is(frames.querySelectorAll(".dbg-stackframe").length, 1,
           "Should have only one frame.");
 
--- a/browser/devtools/debugger/test/browser_dbg_pause-exceptions.js
+++ b/browser/devtools/debugger/test/browser_dbg_pause-exceptions.js
@@ -13,19 +13,19 @@ var gTab = null;
 var gDebugger = null;
 var gPrevPref = null;
 
 requestLongerTimeout(2);
 
 function test()
 {
   gPrevPref = Services.prefs.getBoolPref(
-    "devtools.debugger.ui.pause-on-exceptions");
+    "devtools.debugger.pause-on-exceptions");
   Services.prefs.setBoolPref(
-    "devtools.debugger.ui.pause-on-exceptions", true);
+    "devtools.debugger.pause-on-exceptions", true);
 
   debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
     gTab = aTab;
     gPane = aPane;
     gDebugger = gPane.panelWin;
 
     gDebugger.DebuggerController.StackFrames.autoScopeExpand = true;
     gDebugger.DebuggerView.Variables.nonEnumVisible = false;
@@ -66,17 +66,17 @@ function testWithFrame()
           if (++count < 2) {
             is(count, 1, "B. First Debugger:FetchedVariables event received.");
             return;
           }
           is(count, 2, "B. Second Debugger:FetchedVariables event received.");
           gDebugger.removeEventListener("Debugger:FetchedVariables", testB, false);
           Services.tm.currentThread.dispatch({ run: function() {
 
-            var frames = gDebugger.DebuggerView.StackFrames._container._list,
+            var frames = gDebugger.DebuggerView.StackFrames.widget._list,
                 scopes = gDebugger.DebuggerView.Variables._list,
                 innerScope = scopes.firstChild,
                 innerNodes = innerScope.querySelector(".variables-view-element-details").childNodes;
 
             is(gDebugger.DebuggerController.activeThread.state, "paused",
               "Should only be getting stack frames while paused.");
 
             is(frames.querySelectorAll(".dbg-stackframe").length, 1,
--- a/browser/devtools/debugger/test/browser_dbg_pause-resume.js
+++ b/browser/devtools/debugger/test/browser_dbg_pause-resume.js
@@ -32,17 +32,17 @@ function testPause() {
   is(button.getAttribute("tooltiptext"),
      gL10N.getFormatStr("pauseButtonTooltip",
       gLH.prettyKey(gDebugger.document.getElementById("resumeKey"))),
     "Button tooltip should be pause when running.");
 
   gDebugger.DebuggerController.activeThread.addOneTimeListener("paused", function() {
     Services.tm.currentThread.dispatch({ run: function() {
 
-      let frames = gDebugger.DebuggerView.StackFrames._container._list;
+      let frames = gDebugger.DebuggerView.StackFrames.widget._list;
       let childNodes = frames.childNodes;
 
       is(gDebugger.DebuggerController.activeThread.paused, true,
         "Should be paused after an interrupt request.");
 
       is(button.getAttribute("tooltiptext"),
          gL10N.getFormatStr("resumeButtonTooltip",
           gLH.prettyKey(gDebugger.document.getElementById("resumeKey"))),
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-01.js
@@ -31,17 +31,17 @@ function testSimpleCall() {
 }
 
 function testlabelshortening() {
   gDebugger.DebuggerController.activeThread.resume(function() {
     let sv = gDebugger.SourceUtils;
     let vs = gDebugger.DebuggerView.Sources;
     let ss = gDebugger.DebuggerController.SourceScripts;
     vs.empty();
-    vs._container.removeEventListener("select", vs._onScriptsChange, false);
+    vs.widget.removeEventListener("select", vs._onScriptsChange, false);
 
     is(sv.trimUrlQuery("a/b/c.d?test=1&random=4#reference"), "a/b/c.d",
       "Trimming the url query isn't done properly.");
 
     let ellipsis = Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString);
     let nanana = new Array(20).join(NaN);
 
     let superLargeLabel = new Array(100).join("Beer can in Jamaican sounds like Bacon!");
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-06.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-06.js
@@ -24,116 +24,167 @@ function testSimpleCall() {
     Services.tm.currentThread.dispatch({ run: function() {
 
       let globalScope = gDebugger.DebuggerView.Variables.addScope("Test-Global");
       let localScope = gDebugger.DebuggerView.Variables.addScope("Test-Local");
 
       let windowVar = globalScope.addItem("window");
       let documentVar = globalScope.addItem("document");
       let localVar0 = localScope.addItem("localVariable");
+
       let localVar1 = localScope.addItem("localVar1");
       let localVar2 = localScope.addItem("localVar2");
       let localVar3 = localScope.addItem("localVar3");
       let localVar4 = localScope.addItem("localVar4");
       let localVar5 = localScope.addItem("localVar5");
 
       localVar0.setGrip(42);
       localVar1.setGrip(true);
       localVar2.setGrip("nasu");
 
       localVar3.setGrip({ "type": "undefined" });
       localVar4.setGrip({ "type": "null" });
       localVar5.setGrip({ "type": "object", "class": "Object" });
 
-      localVar5.addItems({ "someProp0": { "value": 42, "enumerable": true },
-                           "someProp1": { "value": true , "enumerable": true},
-                           "someProp2": { "value": "nasu", "enumerable": true},
-                           "someProp3": { "value": { "type": "undefined" }, "enumerable": true},
-                           "someProp4": { "value": { "type": "null" }, "enumerable": true },
-                           "someProp5": {
-                             "value": { "type": "object", "class": "Object" },
-                             "enumerable": true
-                           }
-                         });
+      localVar5.addItems({
+        "someProp0": { "value": 42, "enumerable": true },
+        "someProp1": { "value": true , "enumerable": true},
+        "someProp2": { "value": "nasu", "enumerable": true},
+        "someProp3": { "value": { "type": "undefined" }, "enumerable": true},
+        "someProp4": { "value": { "type": "null" }, "enumerable": true },
+        "someProp5": {
+          "value": { "type": "object", "class": "Object" },
+          "enumerable": true
+        },
+        "someUndefined": {
+          "value": { "type": "undefined" },
+          "enumerable": true
+        },
+        "someAccessor": {
+          "get": { "type": "object", "class": "Function" },
+          "set": { "type": "undefined" },
+          "enumerable": true
+        }
+      });
 
-      localVar5.get("someProp5").addItems({ "someProp0": { "value": 42, "enumerable": true },
-                                            "someProp1": { "value": true, "enumerable": true },
-                                            "someProp2": { "value": "nasu", "enumerable": true },
-                                            "someProp3": { "value": { "type": "undefined" }, "enumerable": true },
-                                            "someProp4": { "value": { "type": "null" }, "enumerable": true },
-                                            "someAccessor": { "get": { "type": "object", "class": "Function" },
-                                                              "set": { "type": "undefined" }, "enumerable": true }
-                                          });
+      localVar5.get("someProp5").addItems({
+        "someProp0": { "value": 42, "enumerable": true },
+        "someProp1": { "value": true, "enumerable": true },
+        "someProp2": { "value": "nasu", "enumerable": true },
+        "someProp3": { "value": { "type": "undefined" }, "enumerable": true },
+        "someProp4": { "value": { "type": "null" }, "enumerable": true },
+        "someProp5": {
+          "value": { "type": "object", "class": "Object" },
+          "enumerable": true
+        },
+        "someUndefined": {
+          "value": { "type": "undefined" },
+          "enumerable": true
+        },
+        "someAccessor": {
+          "get": { "type": "object", "class": "Function" },
+          "set": { "type": "undefined" },
+          "enumerable": true
+        }
+      });
 
       windowVar.setGrip({ "type": "object", "class": "Window" });
-      windowVar.addItems({ "helloWorld": { "value": "hello world" } });
+      windowVar.addItems({
+        "helloWorld": { "value": "hello world" }
+      });
 
       documentVar.setGrip({ "type": "object", "class": "HTMLDocument" });
-      documentVar.addItems({ "onload": { "value": { "type": "null" } },
-                             "onunload": { "value": { "type": "null" } },
-                             "onfocus": { "value": { "type": "null" } },
-                             "onblur": { "value": { "type": "null" } },
-                             "onclick": { "value": { "type": "null" } },
-                             "onkeypress": { "value": { "type": "null" } } });
+      documentVar.addItems({
+        "onload": { "value": { "type": "null" } },
+        "onunload": { "value": { "type": "null" } },
+        "onfocus": { "value": { "type": "null" } },
+        "onblur": { "value": { "type": "null" } },
+        "onclick": { "value": { "type": "null" } },
+        "onkeypress": { "value": { "type": "null" } }
+      });
 
 
       ok(windowVar, "The windowVar hasn't been created correctly.");
       ok(documentVar, "The documentVar hasn't been created correctly.");
       ok(localVar0, "The localVar0 hasn't been created correctly.");
       ok(localVar1, "The localVar1 hasn't been created correctly.");
       ok(localVar2, "The localVar2 hasn't been created correctly.");
       ok(localVar3, "The localVar3 hasn't been created correctly.");
       ok(localVar4, "The localVar4 hasn't been created correctly.");
       ok(localVar5, "The localVar5 hasn't been created correctly.");
 
-
       for each (let elt in globalScope.target.querySelector(".nonenum").childNodes) {
         info("globalScope :: " + { id: elt.id, className: elt.className }.toSource());
       }
       is(globalScope.target.querySelector(".nonenum").childNodes.length, 2,
         "The globalScope doesn't contain all the created variable elements.");
 
       for each (let elt in localScope.target.querySelector(".nonenum").childNodes) {
         info("localScope :: " + { id: elt.id, className: elt.className }.toSource());
       }
       is(localScope.target.querySelector(".nonenum").childNodes.length, 6,
         "The localScope doesn't contain all the created variable elements.");
 
-
-      is(localVar5.target.querySelector(".variables-view-element-details").childNodes.length, 6,
+      is(localVar5.target.querySelector(".variables-view-element-details").childNodes.length, 8,
         "The localVar5 doesn't contain all the created properties.");
-
-      is(localVar5.get("someProp5").target.querySelector(".variables-view-element-details").childNodes.length, 6,
+      is(localVar5.get("someProp5").target.querySelector(".variables-view-element-details").childNodes.length, 8,
         "The localVar5.someProp5 doesn't contain all the created properties.");
 
-
       is(windowVar.target.querySelector(".value").getAttribute("value"), "[object Window]",
         "The grip information for the windowVar wasn't set correctly.");
-
       is(documentVar.target.querySelector(".value").getAttribute("value"), "[object HTMLDocument]",
         "The grip information for the documentVar wasn't set correctly.");
 
       is(localVar0.target.querySelector(".value").getAttribute("value"), "42",
         "The grip information for the localVar0 wasn't set correctly.");
-
       is(localVar1.target.querySelector(".value").getAttribute("value"), "true",
         "The grip information for the localVar1 wasn't set correctly.");
-
       is(localVar2.target.querySelector(".value").getAttribute("value"), "\"nasu\"",
         "The grip information for the localVar2 wasn't set correctly.");
-
       is(localVar3.target.querySelector(".value").getAttribute("value"), "undefined",
         "The grip information for the localVar3 wasn't set correctly.");
-
       is(localVar4.target.querySelector(".value").getAttribute("value"), "null",
         "The grip information for the localVar4 wasn't set correctly.");
-
       is(localVar5.target.querySelector(".value").getAttribute("value"), "[object Object]",
         "The grip information for the localVar5 wasn't set correctly.");
 
+      is(localVar5.get("someProp0").target.querySelector(".value").getAttribute("value"), "42",
+        "The grip information for the localVar0 wasn't set correctly.");
+      is(localVar5.get("someProp1").target.querySelector(".value").getAttribute("value"), "true",
+        "The grip information for the localVar1 wasn't set correctly.");
+      is(localVar5.get("someProp2").target.querySelector(".value").getAttribute("value"), "\"nasu\"",
+        "The grip information for the localVar2 wasn't set correctly.");
+      is(localVar5.get("someProp3").target.querySelector(".value").getAttribute("value"), "undefined",
+        "The grip information for the localVar3 wasn't set correctly.");
+      is(localVar5.get("someProp4").target.querySelector(".value").getAttribute("value"), "null",
+        "The grip information for the localVar4 wasn't set correctly.");
+      is(localVar5.get("someProp5").target.querySelector(".value").getAttribute("value"), "[object Object]",
+        "The grip information for the localVar5 wasn't set correctly.");
+      is(localVar5.get("someUndefined").target.querySelector(".value").getAttribute("value"), "undefined",
+        "The grip information for the someUndefined wasn't set correctly.");
+      is(localVar5.get("someAccessor").target.querySelector(".value").getAttribute("value"), "",
+        "The grip information for the someAccessor wasn't set correctly.");
+
+      is(localVar5.get("someProp5").get("someProp0").target.querySelector(".value").getAttribute("value"), "42",
+        "The grip information for the sub-localVar0 wasn't set correctly.");
+      is(localVar5.get("someProp5").get("someProp1").target.querySelector(".value").getAttribute("value"), "true",
+        "The grip information for the sub-localVar1 wasn't set correctly.");
+      is(localVar5.get("someProp5").get("someProp2").target.querySelector(".value").getAttribute("value"), "\"nasu\"",
+        "The grip information for the sub-localVar2 wasn't set correctly.");
+      is(localVar5.get("someProp5").get("someProp3").target.querySelector(".value").getAttribute("value"), "undefined",
+        "The grip information for the sub-localVar3 wasn't set correctly.");
+      is(localVar5.get("someProp5").get("someProp4").target.querySelector(".value").getAttribute("value"), "null",
+        "The grip information for the sub-localVar4 wasn't set correctly.");
+      is(localVar5.get("someProp5").get("someProp5").target.querySelector(".value").getAttribute("value"), "[object Object]",
+        "The grip information for the sub-localVar5 wasn't set correctly.");
+      is(localVar5.get("someProp5").get("someUndefined").target.querySelector(".value").getAttribute("value"), "undefined",
+        "The grip information for the sub-someUndefined wasn't set correctly.");
+      is(localVar5.get("someProp5").get("someAccessor").target.querySelector(".value").getAttribute("value"), "",
+        "The grip information for the sub-someAccessor wasn't set correctly.");
+
       gDebugger.DebuggerController.activeThread.resume(function() {
         closeDebuggerAndFinish();
       });
     }}, 0);
   });
 
   gDebuggee.simpleCall();
 }
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-07.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-07.js
@@ -24,17 +24,17 @@ function test()
 }
 
 function testFrameParameters()
 {
   gDebugger.addEventListener("Debugger:FetchedVariables", function test() {
     gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
     Services.tm.currentThread.dispatch({ run: function() {
 
-      var frames = gDebugger.DebuggerView.StackFrames._container._list,
+      var frames = gDebugger.DebuggerView.StackFrames.widget._list,
           localScope = gDebugger.DebuggerView.Variables._list.querySelector(".variables-view-scope"),
           localNodes = localScope.querySelector(".variables-view-element-details").childNodes;
 
       is(gDebugger.DebuggerController.activeThread.state, "paused",
         "Should only be getting stack frames while paused.");
 
       is(frames.querySelectorAll(".dbg-stackframe").length, 3,
         "Should have three frames.");
@@ -83,17 +83,17 @@ function testFrameParameters()
     content.document.querySelector("button"),
     content.window);
 }
 
 function resumeAndFinish() {
   gDebugger.addEventListener("Debugger:AfterFramesCleared", function listener() {
     gDebugger.removeEventListener("Debugger:AfterFramesCleared", listener, true);
 
-    var frames = gDebugger.DebuggerView.StackFrames._container._list;
+    var frames = gDebugger.DebuggerView.StackFrames.widget._list;
     is(frames.querySelectorAll(".dbg-stackframe").length, 0,
       "Should have no frames.");
 
     closeDebuggerAndFinish();
   }, true);
 
   gDebugger.DebuggerController.activeThread.resume();
 }
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-08.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-08.js
@@ -24,17 +24,17 @@ function test()
 }
 
 function testFrameParameters()
 {
   gDebugger.addEventListener("Debugger:FetchedVariables", function test() {
     gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
     Services.tm.currentThread.dispatch({ run: function() {
 
-      var frames = gDebugger.DebuggerView.StackFrames._container._list,
+      var frames = gDebugger.DebuggerView.StackFrames.widget._list,
           localScope = gDebugger.DebuggerView.Variables._list.querySelectorAll(".variables-view-scope")[0],
           localNodes = localScope.querySelector(".variables-view-element-details").childNodes,
           localNonEnums = localScope.querySelector(".nonenum").childNodes;
 
       is(gDebugger.DebuggerController.activeThread.state, "paused",
         "Should only be getting stack frames while paused.");
 
       is(frames.querySelectorAll(".dbg-stackframe").length, 3,
@@ -221,17 +221,17 @@ function testFrameParameters()
     content.document.querySelector("button"),
     content.window);
 }
 
 function resumeAndFinish() {
   gDebugger.addEventListener("Debugger:AfterFramesCleared", function listener() {
     gDebugger.removeEventListener("Debugger:AfterFramesCleared", listener, true);
 
-    var frames = gDebugger.DebuggerView.StackFrames._container._list;
+    var frames = gDebugger.DebuggerView.StackFrames.widget._list;
     is(frames.querySelectorAll(".dbg-stackframe").length, 0,
       "Should have no frames.");
 
     closeDebuggerAndFinish();
   }, true);
 
   gDebugger.DebuggerController.activeThread.resume();
 }
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-09.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-09.js
@@ -35,17 +35,17 @@ function testFrameParameters()
     // scope and the regular one.
     if (++count < 2) {
       info("Number of received Debugger:FetchedVariables events: " + count);
       return;
     }
     gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
     Services.tm.currentThread.dispatch({ run: function() {
 
-      var frames = gDebugger.DebuggerView.StackFrames._container._list,
+      var frames = gDebugger.DebuggerView.StackFrames.widget._list,
           globalScope = gDebugger.DebuggerView.Variables._list.querySelectorAll(".variables-view-scope")[2],
           globalNodes = globalScope.querySelector(".variables-view-element-details").childNodes;
 
       is(gDebugger.DebuggerController.activeThread.state, "paused",
         "Should only be getting stack frames while paused.");
 
       is(frames.querySelectorAll(".dbg-stackframe").length, 3,
         "Should have three frames.");
@@ -80,17 +80,17 @@ function testFrameParameters()
     content.document.querySelector("button"),
     content.window);
 }
 
 function resumeAndFinish() {
   gDebugger.addEventListener("Debugger:AfterFramesCleared", function listener() {
     gDebugger.removeEventListener("Debugger:AfterFramesCleared", listener, true);
     Services.tm.currentThread.dispatch({ run: function() {
-      var frames = gDebugger.DebuggerView.StackFrames._container._list;
+      var frames = gDebugger.DebuggerView.StackFrames.widget._list;
 
       is(frames.querySelectorAll(".dbg-stackframe").length, 0,
         "Should have no frames.");
 
       closeDebuggerAndFinish();
     }}, 0);
   }, true);
 
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-10.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-10.js
@@ -35,17 +35,17 @@ function testWithFrame()
     // scope, two from the |with| scopes and the regular one.
     if (++count < 4) {
       info("Number of received Debugger:FetchedVariables events: " + count);
       return;
     }
     gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
     Services.tm.currentThread.dispatch({ run: function() {
 
-      var frames = gDebugger.DebuggerView.StackFrames._container._list,
+      var frames = gDebugger.DebuggerView.StackFrames.widget._list,
           scopes = gDebugger.DebuggerView.Variables._list,
           innerScope = scopes.querySelectorAll(".variables-view-scope")[0],
           globalScope = scopes.querySelectorAll(".variables-view-scope")[4],
           innerNodes = innerScope.querySelector(".variables-view-element-details").childNodes,
           globalNodes = globalScope.querySelector(".variables-view-element-details").childNodes;
 
       is(gDebugger.DebuggerController.activeThread.state, "paused",
         "Should only be getting stack frames while paused.");
@@ -85,17 +85,17 @@ function testWithFrame()
     content.document.querySelector("button"),
     content.window);
 }
 
 function resumeAndFinish() {
   gDebugger.addEventListener("Debugger:AfterFramesCleared", function listener() {
     gDebugger.removeEventListener("Debugger:AfterFramesCleared", listener, true);
     Services.tm.currentThread.dispatch({ run: function() {
-      var frames = gDebugger.DebuggerView.StackFrames._container._list;
+      var frames = gDebugger.DebuggerView.StackFrames.widget._list;
 
       is(frames.querySelectorAll(".dbg-stackframe").length, 0,
         "Should have no frames.");
 
       closeDebuggerAndFinish();
     }}, 0);
   }, true);
 
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-11.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-11.js
@@ -216,17 +216,17 @@ function testFrameParameters()
     content.document.querySelector("button"),
     content.window);
 }
 
 function resumeAndFinish() {
   gDebugger.addEventListener("Debugger:AfterFramesCleared", function listener() {
     gDebugger.removeEventListener("Debugger:AfterFramesCleared", listener, true);
     Services.tm.currentThread.dispatch({ run: function() {
-      let frames = gDebugger.DebuggerView.StackFrames._container._list;
+      let frames = gDebugger.DebuggerView.StackFrames.widget._list;
 
       is(frames.querySelectorAll(".dbg-stackframe").length, 0,
         "Should have no frames.");
 
       closeDebuggerAndFinish();
     }}, 0);
   }, true);
 
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-data-getset-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-data-getset-01.js
@@ -243,36 +243,45 @@ function testEdit(what, string, callback
         let w1 = scope.get(Object.keys(expected)[0]);
         let w2 = scope.get(Object.keys(expected)[1]);
         let w3 = scope.get(Object.keys(expected)[2]);
 
         if (w1) {
           if (isNaN(expected[w1.name]) && typeof expected[w1.name] == "number") {
             ok(isNaN(w1.value),
               "The first expression value is correct after the edit (NaN).");
+          } else if (expected[w1.name] === undefined) {
+            is(w1.value.type, "undefined",
+              "The first expression value is correct after the edit (undefined).");
           } else {
             is(w1.value, expected[w1.name],
               "The first expression value is correct after the edit.");
           }
           info(w1.value + " is equal to " + expected[w1.name]);
         }
         if (w2) {
           if (isNaN(expected[w2.name]) && typeof expected[w2.name] == "number") {
             ok(isNaN(w2.value),
               "The second expression value is correct after the edit (NaN).");
+          } else if (expected[w2.name] === undefined) {
+            is(w2.value.type, "undefined",
+              "The second expression value is correct after the edit (undefined).");
           } else {
             is(w2.value, expected[w2.name],
               "The second expression value is correct after the edit.");
           }
           info(w2.value + " is equal to " + expected[w2.name]);
         }
         if (w3) {
           if (isNaN(expected[w3.name]) && typeof expected[w3.name] == "number") {
             ok(isNaN(w3.value),
               "The third expression value is correct after the edit (NaN).");
+          } else if (expected[w3.name] === undefined) {
+            is(w3.value.type, "undefined",
+              "The third expression value is correct after the edit (undefined).");
           } else {
             is(w3.value, expected[w3.name],
               "The third expression value is correct after the edit.");
           }
           info(w3.value + " is equal to " + expected[w3.name]);
         }
 
         callback();
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-edit-watch.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-edit-watch.js
@@ -52,19 +52,19 @@ function testFrameEval() {
       is(varT.querySelector(".name").getAttribute("value"), "t",
         "Should have the right name for 't'.");
 
       is(aArg.querySelector(".value").getAttribute("value"), "undefined",
         "Should have the right initial value for 'aArg'.");
       is(varT.querySelector(".value").getAttribute("value"), "\"Browser Debugger Watch Expressions Test\"",
         "Should have the right initial value for 't'.");
 
-      is(gWatch._container._parent.querySelectorAll(".dbg-expression[hidden=true]").length, 5,
+      is(gWatch.widget._parent.querySelectorAll(".dbg-expression[hidden=true]").length, 5,
         "There should be 5 hidden nodes in the watch expressions container");
-      is(gWatch._container._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
+      is(gWatch.widget._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
         "There should be 0 visible nodes in the watch expressions container");
 
       let label = gDebugger.L10N.getStr("watchExpressionsScopeLabel");
       let scope = gVars._currHierarchy.get(label);
 
       ok(scope, "There should be a wach expressions scope in the variables view");
       is(scope._store.size, 5, "There should be 5 evaluations availalble");
 
@@ -91,17 +91,19 @@ function testFrameEval() {
         "Should have the right value type for 'ermahgerd'.");
 
       is(scope.get("aArg")._isContentVisible, true,
         "Should have the right visibility state for 'aArg'.");
       is(scope.get("aArg").target.querySelectorAll(".variables-view-delete").length, 1,
         "Should have the one close button visible for 'aArg'.");
       is(scope.get("aArg").name, "aArg",
         "Should have the right name for 'aArg'.");
-      is(scope.get("aArg").value, undefined,
+      is(scope.get("aArg").value.type, "undefined",
+        "Should have the right value for 'aArg'.");
+      is(scope.get("aArg").value.class, undefined,
         "Should have the right value for 'aArg'.");
 
       is(scope.get("document.title")._isContentVisible, true,
         "Should have the right visibility state for 'document.title'.");
       is(scope.get("document.title").target.querySelectorAll(".variables-view-delete").length, 1,
         "Should have the one close button visible for 'document.title'.");
       is(scope.get("document.title").name, "document.title",
         "Should have the right name for 'document.title'.");
@@ -325,169 +327,169 @@ function testExprDeletion(aVar, aTest, a
   }
 
   EventUtils.sendMouseEvent({ type: "click" },
     aVar.querySelector(".variables-view-delete"),
     gDebugger);
 }
 
 function test1(scope) {
-  is(gWatch._container._parent.querySelectorAll(".dbg-expression[hidden=true]").length, 5,
+  is(gWatch.widget._parent.querySelectorAll(".dbg-expression[hidden=true]").length, 5,
     "There should be 5 hidden nodes in the watch expressions container");
-  is(gWatch._container._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
+  is(gWatch.widget._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
     "There should be 0 visible nodes in the watch expressions container");
 
   ok(scope, "There should be a wach expressions scope in the variables view");
   is(scope._store.size, 5, "There should be 5 evaluations availalble");
 
-  is(gWatch._cache[0].attachment.inputNode.value, "document.title = 43",
+  is(gWatch.getItemAtIndex(0).attachment.inputNode.value, "document.title = 43",
     "The first textbox input value is not the correct one");
-  is(gWatch._cache[0].attachment.currentExpression, "document.title = 43",
+  is(gWatch.getItemAtIndex(0).attachment.currentExpression, "document.title = 43",
     "The first textbox input value is not the correct one");
-  is(gWatch._cache[1].attachment.inputNode.value, "document.title",
+  is(gWatch.getItemAtIndex(1).attachment.inputNode.value, "document.title",
     "The second textbox input value is not the correct one");
-  is(gWatch._cache[1].attachment.currentExpression, "document.title",
+  is(gWatch.getItemAtIndex(1).attachment.currentExpression, "document.title",
     "The second textbox input value is not the correct one");
-  is(gWatch._cache[2].attachment.inputNode.value, "aArg",
+  is(gWatch.getItemAtIndex(2).attachment.inputNode.value, "aArg",
     "The third textbox input value is not the correct one");
-  is(gWatch._cache[2].attachment.currentExpression, "aArg",
+  is(gWatch.getItemAtIndex(2).attachment.currentExpression, "aArg",
     "The third textbox input value is not the correct one");
-  is(gWatch._cache[3].attachment.inputNode.value, "ermahgerd",
+  is(gWatch.getItemAtIndex(3).attachment.inputNode.value, "ermahgerd",
     "The fourth textbox input value is not the correct one");
-  is(gWatch._cache[3].attachment.currentExpression, "ermahgerd",
+  is(gWatch.getItemAtIndex(3).attachment.currentExpression, "ermahgerd",
     "The fourth textbox input value is not the correct one");
-  is(gWatch._cache[4].attachment.inputNode.value, "this",
+  is(gWatch.getItemAtIndex(4).attachment.inputNode.value, "this",
     "The fifth textbox input value is not the correct one");
-  is(gWatch._cache[4].attachment.currentExpression, "this",
+  is(gWatch.getItemAtIndex(4).attachment.currentExpression, "this",
     "The fifth textbox input value is not the correct one");
 }
 
 function test2(scope) {
-  is(gWatch._container._parent.querySelectorAll(".dbg-expression[hidden=true]").length, 5,
+  is(gWatch.widget._parent.querySelectorAll(".dbg-expression[hidden=true]").length, 5,
     "There should be 5 hidden nodes in the watch expressions container");
-  is(gWatch._container._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
+  is(gWatch.widget._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
     "There should be 0 visible nodes in the watch expressions container");
 
   ok(scope, "There should be a wach expressions scope in the variables view");
   is(scope._store.size, 5, "There should be 5 evaluations availalble");
 
-  is(gWatch._cache[0].attachment.inputNode.value, "document.title = 43",
+  is(gWatch.getItemAtIndex(0).attachment.inputNode.value, "document.title = 43",
     "The first textbox input value is not the correct one");
-  is(gWatch._cache[0].attachment.currentExpression, "document.title = 43",
+  is(gWatch.getItemAtIndex(0).attachment.currentExpression, "document.title = 43",
     "The first textbox input value is not the correct one");
-  is(gWatch._cache[1].attachment.inputNode.value, "document.title",
+  is(gWatch.getItemAtIndex(1).attachment.inputNode.value, "document.title",
     "The second textbox input value is not the correct one");
-  is(gWatch._cache[1].attachment.currentExpression, "document.title",
+  is(gWatch.getItemAtIndex(1).attachment.currentExpression, "document.title",
     "The second textbox input value is not the correct one");
-  is(gWatch._cache[2].attachment.inputNode.value, "aArg = 44",
+  is(gWatch.getItemAtIndex(2).attachment.inputNode.value, "aArg = 44",
     "The third textbox input value is not the correct one");
-  is(gWatch._cache[2].attachment.currentExpression, "aArg = 44",
+  is(gWatch.getItemAtIndex(2).attachment.currentExpression, "aArg = 44",
     "The third textbox input value is not the correct one");
-  is(gWatch._cache[3].attachment.inputNode.value, "ermahgerd",
+  is(gWatch.getItemAtIndex(3).attachment.inputNode.value, "ermahgerd",
     "The fourth textbox input value is not the correct one");
-  is(gWatch._cache[3].attachment.currentExpression, "ermahgerd",
+  is(gWatch.getItemAtIndex(3).attachment.currentExpression, "ermahgerd",
     "The fourth textbox input value is not the correct one");
-  is(gWatch._cache[4].attachment.inputNode.value, "this",
+  is(gWatch.getItemAtIndex(4).attachment.inputNode.value, "this",
     "The fifth textbox input value is not the correct one");
-  is(gWatch._cache[4].attachment.currentExpression, "this",
+  is(gWatch.getItemAtIndex(4).attachment.currentExpression, "this",
     "The fifth textbox input value is not the correct one");
 }
 
 function test3(scope) {
-  is(gWatch._container._parent.querySelectorAll(".dbg-expression[hidden=true]").length, 4,
+  is(gWatch.widget._parent.querySelectorAll(".dbg-expression[hidden=true]").length, 4,
     "There should be 4 hidden nodes in the watch expressions container");
-  is(gWatch._container._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
+  is(gWatch.widget._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
     "There should be 0 visible nodes in the watch expressions container");
 
   ok(scope, "There should be a wach expressions scope in the variables view");
   is(scope._store.size, 4, "There should be 4 evaluations availalble");
 
-  is(gWatch._cache[0].attachment.inputNode.value, "document.title = 43",
+  is(gWatch.getItemAtIndex(0).attachment.inputNode.value, "document.title = 43",
     "The first textbox input value is not the correct one");
-  is(gWatch._cache[0].attachment.currentExpression, "document.title = 43",
+  is(gWatch.getItemAtIndex(0).attachment.currentExpression, "document.title = 43",
     "The first textbox input value is not the correct one");
-  is(gWatch._cache[1].attachment.inputNode.value, "document.title",
+  is(gWatch.getItemAtIndex(1).attachment.inputNode.value, "document.title",
     "The second textbox input value is not the correct one");
-  is(gWatch._cache[1].attachment.currentExpression, "document.title",
+  is(gWatch.getItemAtIndex(1).attachment.currentExpression, "document.title",
     "The second textbox input value is not the correct one");
-  is(gWatch._cache[2].attachment.inputNode.value, "ermahgerd",
+  is(gWatch.getItemAtIndex(2).attachment.inputNode.value, "ermahgerd",
     "The third textbox input value is not the correct one");
-  is(gWatch._cache[2].attachment.currentExpression, "ermahgerd",
+  is(gWatch.getItemAtIndex(2).attachment.currentExpression, "ermahgerd",
     "The third textbox input value is not the correct one");
-  is(gWatch._cache[3].attachment.inputNode.value, "this",
+  is(gWatch.getItemAtIndex(3).attachment.inputNode.value, "this",
     "The fourth textbox input value is not the correct one");
-  is(gWatch._cache[3].attachment.currentExpression, "this",
+  is(gWatch.getItemAtIndex(3).attachment.currentExpression, "this",
     "The fourth textbox input value is not the correct one");
 }
 
 function test4(scope) {
-  is(gWatch._container._parent.querySelectorAll(".dbg-expression[hidden=true]").length, 3,
+  is(gWatch.widget._parent.querySelectorAll(".dbg-expression[hidden=true]").length, 3,
     "There should be 3 hidden nodes in the watch expressions container");
-  is(gWatch._container._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
+  is(gWatch.widget._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
     "There should be 0 visible nodes in the watch expressions container");
 
   ok(scope, "There should be a wach expressions scope in the variables view");
   is(scope._store.size, 3, "There should be 3 evaluations availalble");
 
-  is(gWatch._cache[0].attachment.inputNode.value, "document.title",
+  is(gWatch.getItemAtIndex(0).attachment.inputNode.value, "document.title",
     "The first textbox input value is not the correct one");
-  is(gWatch._cache[0].attachment.currentExpression, "document.title",
+  is(gWatch.getItemAtIndex(0).attachment.currentExpression, "document.title",
     "The first textbox input value is not the correct one");
-  is(gWatch._cache[1].attachment.inputNode.value, "ermahgerd",
+  is(gWatch.getItemAtIndex(1).attachment.inputNode.value, "ermahgerd",
     "The second textbox input value is not the correct one");
-  is(gWatch._cache[1].attachment.currentExpression, "ermahgerd",
+  is(gWatch.getItemAtIndex(1).attachment.currentExpression, "ermahgerd",
     "The second textbox input value is not the correct one");
-  is(gWatch._cache[2].attachment.inputNode.value, "this",
+  is(gWatch.getItemAtIndex(2).attachment.inputNode.value, "this",
     "The third textbox input value is not the correct one");
-  is(gWatch._cache[2].attachment.currentExpression, "this",
+  is(gWatch.getItemAtIndex(2).attachment.currentExpression, "this",
     "The third textbox input value is not the correct one");
 }
 
 function test5(scope) {
-  is(gWatch._container._parent.querySelectorAll(".dbg-expression[hidden=true]").length, 2,
+  is(gWatch.widget._parent.querySelectorAll(".dbg-expression[hidden=true]").length, 2,
     "There should be 2 hidden nodes in the watch expressions container");
-  is(gWatch._container._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
+  is(gWatch.widget._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
     "There should be 0 visible nodes in the watch expressions container");
 
   ok(scope, "There should be a wach expressions scope in the variables view");
   is(scope._store.size, 2, "There should be 2 evaluations availalble");
 
-  is(gWatch._cache[0].attachment.inputNode.value, "ermahgerd",
+  is(gWatch.getItemAtIndex(0).attachment.inputNode.value, "ermahgerd",
     "The second textbox input value is not the correct one");
-  is(gWatch._cache[0].attachment.currentExpression, "ermahgerd",
+  is(gWatch.getItemAtIndex(0).attachment.currentExpression, "ermahgerd",
     "The second textbox input value is not the correct one");
-  is(gWatch._cache[1].attachment.inputNode.value, "this",
+  is(gWatch.getItemAtIndex(1).attachment.inputNode.value, "this",
     "The third textbox input value is not the correct one");
-  is(gWatch._cache[1].attachment.currentExpression, "this",
+  is(gWatch.getItemAtIndex(1).attachment.currentExpression, "this",
     "The third textbox input value is not the correct one");
 }
 
 function test6(scope) {
-  is(gWatch._container._parent.querySelectorAll(".dbg-expression[hidden=true]").length, 1,
+  is(gWatch.widget._parent.querySelectorAll(".dbg-expression[hidden=true]").length, 1,
     "There should be 1 hidden nodes in the watch expressions container");
-  is(gWatch._container._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
+  is(gWatch.widget._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
     "There should be 0 visible nodes in the watch expressions container");
 
   ok(scope, "There should be a wach expressions scope in the variables view");
   is(scope._store.size, 1, "There should be 1 evaluation availalble");
 
-  is(gWatch._cache[0].attachment.inputNode.value, "ermahgerd",
+  is(gWatch.getItemAtIndex(0).attachment.inputNode.value, "ermahgerd",
     "The third textbox input value is not the correct one");
-  is(gWatch._cache[0].attachment.currentExpression, "ermahgerd",
+  is(gWatch.getItemAtIndex(0).attachment.currentExpression, "ermahgerd",
     "The third textbox input value is not the correct one");
 }
 
 function test7(scope) {
-  is(gWatch._container._parent.querySelectorAll(".dbg-expression[hidden=true]").length, 0,
+  is(gWatch.widget._parent.querySelectorAll(".dbg-expression[hidden=true]").length, 0,
     "There should be 0 hidden nodes in the watch expressions container");
-  is(gWatch._container._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
+  is(gWatch.widget._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
     "There should be 0 visible nodes in the watch expressions container");
 
   is(scope, undefined, "There should be no watch expressions scope available.");
-  is(gWatch._cache.length, 0, "The watch expressions cache should be empty.");
+  is(gWatch.itemCount, 0, "The watch expressions container should be empty.");
 }
 
 function addWatchExpression(string) {
   gWatch.addExpression(string);
   gDebugger.editor.focus();
 }
 
 function addCmdWatchExpression(string) {
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-filter-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-filter-01.js
@@ -310,17 +310,17 @@ function prepareVariables(aCallback)
     // scope, two from the |with| scopes and the regular one.
     if (++count < 4) {
       info("Number of received Debugger:FetchedVariables events: " + count);
       return;
     }
     gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
     Services.tm.currentThread.dispatch({ run: function() {
 
-      var frames = gDebugger.DebuggerView.StackFrames._container._list,
+      var frames = gDebugger.DebuggerView.StackFrames.widget._list,
           scopes = gDebugger.DebuggerView.Variables._list,
           innerScope = scopes.querySelectorAll(".variables-view-scope")[0],
           mathScope = scopes.querySelectorAll(".variables-view-scope")[1],
           testScope = scopes.querySelectorAll(".variables-view-scope")[2],
           loadScope = scopes.querySelectorAll(".variables-view-scope")[3],
           globalScope = scopes.querySelectorAll(".variables-view-scope")[4];
 
       let innerScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-filter-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-filter-02.js
@@ -246,17 +246,17 @@ function prepareVariables(aCallback)
     // scope, two from the |with| scopes and the regular one.
     if (++count < 4) {
       info("Number of received Debugger:FetchedVariables events: " + count);
       return;
     }
     gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
     Services.tm.currentThread.dispatch({ run: function() {
 
-      var frames = gDebugger.DebuggerView.StackFrames._container._list,
+      var frames = gDebugger.DebuggerView.StackFrames.widget._list,
           scopes = gDebugger.DebuggerView.Variables._list,
           innerScope = scopes.querySelectorAll(".variables-view-scope")[0],
           mathScope = scopes.querySelectorAll(".variables-view-scope")[1],
           testScope = scopes.querySelectorAll(".variables-view-scope")[2],
           loadScope = scopes.querySelectorAll(".variables-view-scope")[3],
           globalScope = scopes.querySelectorAll(".variables-view-scope")[4];
 
       let innerScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-filter-05.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-filter-05.js
@@ -210,17 +210,17 @@ function prepareVariables(aCallback)
     // scope, two from the |with| scopes and the regular one.
     if (++count < 4) {
       info("Number of received Debugger:FetchedVariables events: " + count);
       return;
     }
     gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
     Services.tm.currentThread.dispatch({ run: function() {
 
-      var frames = gDebugger.DebuggerView.StackFrames._container._list,
+      var frames = gDebugger.DebuggerView.StackFrames.widget._list,
           scopes = gDebugger.DebuggerView.Variables._list,
           innerScope = scopes.querySelectorAll(".variables-view-scope")[0],
           mathScope = scopes.querySelectorAll(".variables-view-scope")[1],
           testScope = scopes.querySelectorAll(".variables-view-scope")[2],
           loadScope = scopes.querySelectorAll(".variables-view-scope")[3],
           globalScope = scopes.querySelectorAll(".variables-view-scope")[4];
 
       let innerScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-filter-06.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-filter-06.js
@@ -180,17 +180,17 @@ function prepareVariables(aCallback)
     // scope and the regular one.
     if (++count < 2) {
       info("Number of received Debugger:FetchedVariables events: " + count);
       return;
     }
     gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
     Services.tm.currentThread.dispatch({ run: function() {
 
-      var frames = gDebugger.DebuggerView.StackFrames._container._list,
+      var frames = gDebugger.DebuggerView.StackFrames.widget._list,
           scopes = gDebugger.DebuggerView.Variables._list,
           innerScope = scopes.querySelectorAll(".variables-view-scope")[0],
           mathScope = scopes.querySelectorAll(".variables-view-scope")[1],
           testScope = scopes.querySelectorAll(".variables-view-scope")[2],
           loadScope = scopes.querySelectorAll(".variables-view-scope")[3],
           globalScope = scopes.querySelectorAll(".variables-view-scope")[4];
 
       let innerScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-filter-07.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-filter-07.js
@@ -180,17 +180,17 @@ function prepareVariables(aCallback)
     // scope and the regular one.
     if (++count < 2) {
       info("Number of received Debugger:FetchedVariables events: " + count);
       return;
     }
     gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
     Services.tm.currentThread.dispatch({ run: function() {
 
-      var frames = gDebugger.DebuggerView.StackFrames._container._list,
+      var frames = gDebugger.DebuggerView.StackFrames.widget._list,
           scopes = gDebugger.DebuggerView.Variables._list,
           innerScope = scopes.querySelectorAll(".variables-view-scope")[0],
           mathScope = scopes.querySelectorAll(".variables-view-scope")[1],
           testScope = scopes.querySelectorAll(".variables-view-scope")[2],
           loadScope = scopes.querySelectorAll(".variables-view-scope")[3],
           globalScope = scopes.querySelectorAll(".variables-view-scope")[4];
 
       let innerScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-filter-08.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-filter-08.js
@@ -250,17 +250,17 @@ function prepareVariables(aCallback)
     // scope and the regular one.
     if (++count < 2) {
       info("Number of received Debugger:FetchedVariables events: " + count);
       return;
     }
     gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
     Services.tm.currentThread.dispatch({ run: function() {
 
-      var frames = gDebugger.DebuggerView.StackFrames._container._list,
+      var frames = gDebugger.DebuggerView.StackFrames.widget._list,
           scopes = gDebugger.DebuggerView.Variables._list,
           innerScope = scopes.querySelectorAll(".variables-view-scope")[0],
           mathScope = scopes.querySelectorAll(".variables-view-scope")[1],
           testScope = scopes.querySelectorAll(".variables-view-scope")[2],
           loadScope = scopes.querySelectorAll(".variables-view-scope")[3],
           globalScope = scopes.querySelectorAll(".variables-view-scope")[4];
 
       let innerScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-reexpand.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-reexpand.js
@@ -55,17 +55,17 @@ function testVariablesExpand()
     // scope, two from the |with| scopes and the regular one.
     if (++count < 4) {
       info("Number of received Debugger:FetchedVariables events: " + count);
       return;
     }
     gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
     Services.tm.currentThread.dispatch({ run: function() {
 
-      var frames = gDebugger.DebuggerView.StackFrames._container._list,
+      var frames = gDebugger.DebuggerView.StackFrames.widget._list,
           scopes = gDebugger.DebuggerView.Variables._list,
           innerScope = scopes.querySelectorAll(".variables-view-scope")[0],
           mathScope = scopes.querySelectorAll(".variables-view-scope")[1],
           testScope = scopes.querySelectorAll(".variables-view-scope")[2],
           loadScope = scopes.querySelectorAll(".variables-view-scope")[3],
           globalScope = scopes.querySelectorAll(".variables-view-scope")[4];
 
       let innerScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
--- a/browser/devtools/debugger/test/browser_dbg_script-switching.html
+++ b/browser/devtools/debugger/test/browser_dbg_script-switching.html
@@ -4,10 +4,11 @@
     <meta charset='utf-8'/>
     <title>Browser Debugger Script Switching Test</title>
     <!-- Any copyright is dedicated to the Public Domain.
          http://creativecommons.org/publicdomain/zero/1.0/ -->
     <script type="text/javascript" src="test-script-switching-01.js"></script>
     <script type="text/javascript" src="test-script-switching-02.js"></script>
   </head>
   <body>
+    <button onclick="firstCall()">Click me!</button>
   </body>
 </html>
--- a/browser/devtools/debugger/test/browser_dbg_scripts-searching-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_scripts-searching-01.js
@@ -112,120 +112,120 @@ function testScriptSearching() {
       "The editor didn't jump to the correct token. (5.1)");
 
 
     token = "debugger;";
     write(":bogus#" + token);
     ok(gEditor.getCaretPosition().line == 8 &&
        gEditor.getCaretPosition().col == 2 + token.length,
       "The editor didn't jump to the correct token. (6)");
-    isnot(gSources._container.getAttribute("label"), noMatchingSources,
+    isnot(gSources.widget.getAttribute("label"), noMatchingSources,
       "The scripts container should not display a notice that matches are found.");
 
     write(":13#" + token);
     ok(gEditor.getCaretPosition().line == 8 &&
        gEditor.getCaretPosition().col == 2 + token.length,
       "The editor didn't jump to the correct token. (7)");
-    isnot(gSources._container.getAttribute("label"), noMatchingSources,
+    isnot(gSources.widget.getAttribute("label"), noMatchingSources,
       "The scripts container should not display a notice that matches are found.");
 
     write(":#" + token);
     ok(gEditor.getCaretPosition().line == 8 &&
        gEditor.getCaretPosition().col == 2 + token.length,
       "The editor didn't jump to the correct token. (8)");
-    isnot(gSources._container.getAttribute("label"), noMatchingSources,
+    isnot(gSources.widget.getAttribute("label"), noMatchingSources,
       "The scripts container should not display a notice that matches are found.");
 
     write("::#" + token);
     ok(gEditor.getCaretPosition().line == 8 &&
        gEditor.getCaretPosition().col == 2 + token.length,
       "The editor didn't jump to the correct token. (9)");
-    isnot(gSources._container.getAttribute("label"), noMatchingSources,
+    isnot(gSources.widget.getAttribute("label"), noMatchingSources,
       "The scripts container should not display a notice that matches are found.");
 
     write(":::#" + token);
     ok(gEditor.getCaretPosition().line == 8 &&
        gEditor.getCaretPosition().col == 2 + token.length,
       "The editor didn't jump to the correct token. (10)");
-    isnot(gSources._container.getAttribute("label"), noMatchingSources,
+    isnot(gSources.widget.getAttribute("label"), noMatchingSources,
       "The scripts container should not display a notice that matches are found.");
 
 
     write("#" + token + ":bogus");
     ok(gEditor.getCaretPosition().line == 8 &&
        gEditor.getCaretPosition().col == 2 + token.length,
       "The editor didn't jump to the correct token. (6)");
-    isnot(gSources._container.getAttribute("label"), noMatchingSources,
+    isnot(gSources.widget.getAttribute("label"), noMatchingSources,
       "The scripts container should not display a notice that matches are found.");
 
     write("#" + token + ":13");
     ok(gEditor.getCaretPosition().line == 8 &&
        gEditor.getCaretPosition().col == 2 + token.length,
       "The editor didn't jump to the correct token. (7)");
-    isnot(gSources._container.getAttribute("label"), noMatchingSources,
+    isnot(gSources.widget.getAttribute("label"), noMatchingSources,
       "The scripts container should not display a notice that matches are found.");
 
     write("#" + token + ":");
     ok(gEditor.getCaretPosition().line == 8 &&
        gEditor.getCaretPosition().col == 2 + token.length,
       "The editor didn't jump to the correct token. (8)");
-    isnot(gSources._container.getAttribute("label"), noMatchingSources,
+    isnot(gSources.widget.getAttribute("label"), noMatchingSources,
       "The scripts container should not display a notice that matches are found.");
 
     write("#" + token + "::");
     ok(gEditor.getCaretPosition().line == 8 &&
        gEditor.getCaretPosition().col == 2 + token.length,
       "The editor didn't jump to the correct token. (9)");
-    isnot(gSources._container.getAttribute("label"), noMatchingSources,
+    isnot(gSources.widget.getAttribute("label"), noMatchingSources,
       "The scripts container should not display a notice that matches are found.");
 
     write("#" + token + ":::");
     ok(gEditor.getCaretPosition().line == 8 &&
        gEditor.getCaretPosition().col == 2 + token.length,
       "The editor didn't jump to the correct token. (10)");
-    isnot(gSources._container.getAttribute("label"), noMatchingSources,
+    isnot(gSources.widget.getAttribute("label"), noMatchingSources,
       "The scripts container should not display a notice that matches are found.");
 
 
     write(":i am not a number");
     ok(gEditor.getCaretPosition().line == 8 &&
        gEditor.getCaretPosition().col == 2 + token.length,
       "The editor didn't remain at the correct token. (11)");
-    isnot(gSources._container.getAttribute("label"), noMatchingSources,
+    isnot(gSources.widget.getAttribute("label"), noMatchingSources,
       "The scripts container should not display a notice that matches are found.");
 
     write("#__i do not exist__");
     ok(gEditor.getCaretPosition().line == 8 &&
        gEditor.getCaretPosition().col == 2 + token.length,
       "The editor didn't remain at the correct token. (12)");
-    isnot(gSources._container.getAttribute("label"), noMatchingSources,
+    isnot(gSources.widget.getAttribute("label"), noMatchingSources,
       "The scripts container should not display a notice that matches are found.");
 
 
     token = "debugger";
     write("#" + token);
     ok(gEditor.getCaretPosition().line == 2 &&
        gEditor.getCaretPosition().col == 44 + token.length,
       "The editor didn't jump to the correct token. (12.1)");
-    isnot(gSources._container.getAttribute("label"), noMatchingSources,
+    isnot(gSources.widget.getAttribute("label"), noMatchingSources,
       "The scripts container should not display a notice that matches are found.");
 
     clear();
     EventUtils.sendKey("RETURN", gDebugger);
     ok(gEditor.getCaretPosition().line == 2 &&
        gEditor.getCaretPosition().col == 44 + token.length,
       "The editor shouldn't jump to another token. (12.2)");
-    isnot(gSources._container.getAttribute("label"), noMatchingSources,
+    isnot(gSources.widget.getAttribute("label"), noMatchingSources,
       "The scripts container should not display a notice that matches are found.");
 
     EventUtils.sendKey("ENTER", gDebugger);
     ok(gEditor.getCaretPosition().line == 2 &&
        gEditor.getCaretPosition().col == 44 + token.length,
       "The editor shouldn't jump to another token. (12.3)");
-    isnot(gSources._container.getAttribute("label"), noMatchingSources,
+    isnot(gSources.widget.getAttribute("label"), noMatchingSources,
       "The scripts container should not display a notice that matches are found.");
 
 
     write(":1:2:3:a:b:c:::12");
     ok(gEditor.getCaretPosition().line == 11 &&
        gEditor.getCaretPosition().col == 0,
       "The editor didn't jump to the correct line. (13)");
 
@@ -261,17 +261,17 @@ function testScriptSearching() {
 
 
     clear();
     ok(gEditor.getCaretPosition().line == 19 &&
        gEditor.getCaretPosition().col == 4 + token.length,
       "The editor didn't remain at the correct token. (19)");
     is(gSources.visibleItems.length, 1,
       "Not all the scripts are shown after the search. (20)");
-    isnot(gSources._container.getAttribute("label"), noMatchingSources,
+    isnot(gSources.widget.getAttribute("label"), noMatchingSources,
       "The scripts container should not display a notice that matches are found.");
 
     closeDebuggerAndFinish();
   }}, 0);
 }
 
 function clear() {
   gSearchBox.focus();
--- a/browser/devtools/debugger/test/browser_dbg_scripts-searching-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_scripts-searching-02.js
@@ -190,29 +190,29 @@ function fourthSearch(i, string, token) 
     is(gSources.selectedIndex, 1,
       "The scripts container should have retained its selected index after the searchbox was emptied.");
 
     write("BOGUS");
     ok(gEditor.getCaretPosition().line == 5 &&
        gEditor.getCaretPosition().col == 8 + token.length + i,
       "The editor didn't remain at the correct token. (6)");
 
-    is(gSources._container.getAttribute("label"), noMatchingSources,
+    is(gSources.widget.getAttribute("label"), noMatchingSources,
       "The scripts container should display a notice that no scripts match the searched token.");
     is(gSources.visibleItems.length, 0,
       "No scripts should be displayed in the scripts container after a bogus search.");
     is(gSources.selectedIndex, 1,
       "The scripts container should retain its selected index after a bogus search.");
 
     clear();
     ok(gEditor.getCaretPosition().line == 5 &&
        gEditor.getCaretPosition().col == 8 + token.length + i,
       "The editor didn't remain at the correct token. (7)");
 
-    isnot(gSources._container.getAttribute("label"), noMatchingSources,
+    isnot(gSources.widget.getAttribute("label"), noMatchingSources,
       "The scripts container should not display a notice after the searchbox was emptied.");
     is(gSources.visibleItems.length, 2,
       "Not all the scripts are shown after the searchbox was emptied.");
     is(gSources.selectedIndex, 1,
       "The scripts container should have retained its selected index after the searchbox was emptied of a bogus search.");
 
     noMatchingSourcesSingleCharCheck(token, i);
   });
@@ -221,17 +221,17 @@ function fourthSearch(i, string, token) 
 function noMatchingSourcesSingleCharCheck(token, i) {
   let noMatchingSources = gDebugger.L10N.getStr("noMatchingSourcesText");
 
   write("x");
   ok(gEditor.getCaretPosition().line == 5 &&
      gEditor.getCaretPosition().col == 8 + token.length + i,
     "The editor didn't remain at the correct token. (8)");
 
-  is(gSources._container.getAttribute("label"), noMatchingSources,
+  is(gSources.widget.getAttribute("label"), noMatchingSources,
     "The scripts container should display a notice after no matches are found.");
   is(gSources.visibleItems.length, 0,
     "No scripts should be shown after no matches are found.");
   is(gSources.selectedIndex, 1,
     "The scripts container should have retained its selected index after no matches are found.");
 
   closeDebuggerAndFinish();
 }
--- a/browser/devtools/debugger/test/browser_dbg_scripts-searching-03.js
+++ b/browser/devtools/debugger/test/browser_dbg_scripts-searching-03.js
@@ -62,19 +62,19 @@ function testScriptSearching() {
     gSearchView = gDebugger.DebuggerView.GlobalSearch;
     gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
 
     firstSearch();
   });
 }
 
 function firstSearch() {
-  is(gSearchView._container._list.childNodes.length, 0,
+  is(gSearchView.widget._list.childNodes.length, 0,
     "The global search pane shouldn't have any child nodes yet.");
-  is(gSearchView._container._parent.hidden, true,
+  is(gSearchView.widget._parent.hidden, true,
     "The global search pane shouldn't be visible yet.");
   is(gSearchView._splitter.hidden, true,
     "The global search pane splitter shouldn't be visible yet.");
 
   gDebugger.addEventListener("Debugger:GlobalSearch:MatchFound", function _onEvent(aEvent) {
     gDebugger.removeEventListener(aEvent.type, _onEvent);
     info("Current script url:\n" + gSources.selectedValue + "\n");
     info("Debugger editor text:\n" + gEditor.getText() + "\n");
@@ -178,19 +178,19 @@ function firstSearch() {
     }
   });
   executeSoon(function() {
     write("!de");
   });
 }
 
 function secondSearch() {
-  isnot(gSearchView._container._list.childNodes.length, 0,
+  isnot(gSearchView.widget._list.childNodes.length, 0,
     "The global search pane should have some child nodes from the previous search.");
-  is(gSearchView._container._parent.hidden, false,
+  is(gSearchView.widget._parent.hidden, false,
     "The global search pane should be visible from the previous search.");
   is(gSearchView._splitter.hidden, false,
     "The global search pane splitter should be visible from the previous search.");
 
   gDebugger.addEventListener("Debugger:GlobalSearch:MatchFound", function _onEvent(aEvent) {
     gDebugger.removeEventListener(aEvent.type, _onEvent);
     info("Current script url:\n" + gSources.selectedValue + "\n");
     info("Debugger editor text:\n" + gEditor.getText() + "\n");
@@ -284,19 +284,19 @@ function secondSearch() {
     backspace(2);
     append("ED");
   });
 }
 
 function testClearView() {
   gSearchView.clearView();
 
-  is(gSearchView._container._list.childNodes.length, 0,
+  is(gSearchView.widget._list.childNodes.length, 0,
     "The global search pane shouldn't have any child nodes after clearView().");
-  is(gSearchView._container._parent.hidden, true,
+  is(gSearchView.widget._parent.hidden, true,
     "The global search pane shouldn't be visible after clearView().");
   is(gSearchView._splitter.hidden, true,
     "The global search pane splitter shouldn't be visible after clearView().");
 
   closeDebuggerAndFinish();
 }
 
 function clear() {
--- a/browser/devtools/debugger/test/browser_dbg_scripts-searching-04.js
+++ b/browser/devtools/debugger/test/browser_dbg_scripts-searching-04.js
@@ -62,19 +62,19 @@ function testScriptSearching() {
     gSearchView = gDebugger.DebuggerView.GlobalSearch;
     gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
 
     doSearch();
   });
 }
 
 function doSearch() {
-  is(gSearchView._container._list.childNodes.length, 0,
+  is(gSearchView.widget._list.childNodes.length, 0,
     "The global search pane shouldn't have any child nodes yet.");
-  is(gSearchView._container._parent.hidden, true,
+  is(gSearchView.widget._parent.hidden, true,
     "The global search pane shouldn't be visible yet.");
   is(gSearchView._splitter.hidden, true,
     "The global search pane splitter shouldn't be visible yet.");
 
   gDebugger.addEventListener("Debugger:GlobalSearch:MatchFound", function _onEvent(aEvent) {
     gDebugger.removeEventListener(aEvent.type, _onEvent);
     info("Current script url:\n" + gSources.selectedValue + "\n");
     info("Debugger editor text:\n" + gEditor.getText() + "\n");
@@ -84,19 +84,19 @@ function doSearch() {
       executeSoon(function() {
         info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
         ok(gEditor.getCaretPosition().line == 5 &&
            gEditor.getCaretPosition().col == 0,
           "The editor shouldn't have jumped to a matching line yet.");
         is(gSources.visibleItems.length, 2,
           "Not all the scripts are shown after the global search.");
 
-        isnot(gSearchView._container._list.childNodes.length, 0,
+        isnot(gSearchView.widget._list.childNodes.length, 0,
           "The global search pane should be visible now.");
-        isnot(gSearchView._container._parent.hidden, true,
+        isnot(gSearchView.widget._parent.hidden, true,
           "The global search pane should be visible now.");
         isnot(gSearchView._splitter.hidden, true,
           "The global search pane splitter should be visible now.");
 
         doFirstJump();
       });
     } else {
       ok(false, "The current script shouldn't have changed after a global search.");
@@ -226,19 +226,19 @@ function testSearchTokenEmpty() {
       executeSoon(function() {
         info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
         ok(gEditor.getCaretPosition().line == 5 &&
            gEditor.getCaretPosition().col == 6,
           "The editor didn't remain at the correct line. (5)");
         is(gSources.visibleItems.length, 2,
           "Not all the correct scripts are shown after the search. (5)");
 
-        is(gSearchView._container._list.childNodes.length, 0,
+        is(gSearchView.widget._list.childNodes.length, 0,
           "The global search pane shouldn't have any child nodes after clear().");
-        is(gSearchView._container._parent.hidden, true,
+        is(gSearchView.widget._parent.hidden, true,
           "The global search pane shouldn't be visible after clear().");
         is(gSearchView._splitter.hidden, true,
           "The global search pane splitter shouldn't be visible after clear().");
 
         closeDebuggerAndFinish();
       });
     } else {
       ok(false, "How did you get here? Go away!");
--- a/browser/devtools/debugger/test/browser_dbg_scripts-searching-05.js
+++ b/browser/devtools/debugger/test/browser_dbg_scripts-searching-05.js
@@ -62,19 +62,19 @@ function testScriptSearching() {
     gSearchView = gDebugger.DebuggerView.GlobalSearch;
     gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
 
     doSearch();
   });
 }
 
 function doSearch() {
-  is(gSearchView._container._list.childNodes.length, 0,
+  is(gSearchView.widget._list.childNodes.length, 0,
     "The global search pane shouldn't have any child nodes yet.");
-  is(gSearchView._container._parent.hidden, true,
+  is(gSearchView.widget._parent.hidden, true,
     "The global search pane shouldn't be visible yet.");
   is(gSearchView._splitter.hidden, true,
     "The global search pane splitter shouldn't be visible yet.");
 
   gDebugger.addEventListener("Debugger:GlobalSearch:MatchFound", function _onEvent(aEvent) {
     gDebugger.removeEventListener(aEvent.type, _onEvent);
     info("Current script url:\n" + gSources.selectedValue + "\n");
     info("Debugger editor text:\n" + gEditor.getText() + "\n");
@@ -84,19 +84,19 @@ function doSearch() {
       executeSoon(function() {
         info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
         ok(gEditor.getCaretPosition().line == 5 &&
            gEditor.getCaretPosition().col == 0,
           "The editor shouldn't have jumped to a matching line yet.");
         is(gSources.visibleItems.length, 2,
           "Not all the scripts are shown after the global search.");
 
-        isnot(gSearchView._container._list.childNodes.length, 0,
+        isnot(gSearchView.widget._list.childNodes.length, 0,
           "The global search pane should be visible now.");
-        isnot(gSearchView._container._parent.hidden, true,
+        isnot(gSearchView.widget._parent.hidden, true,
           "The global search pane should be visible now.");
         isnot(gSearchView._splitter.hidden, true,
           "The global search pane splitter should be visible now.");
 
         testLocationChange();
       });
     } else {
       ok(false, "The current script shouldn't have changed after a global search.");
@@ -109,19 +109,19 @@ function doSearch() {
 
 function testLocationChange()
 {
   gDebugger.DebuggerController._target.once("navigate", function onTabNavigated(aEvent, aPacket) {
     ok(true, "tabNavigated event was fired after location change.");
     info("Still attached to the tab.");
 
     executeSoon(function() {
-      is(gSearchView._container._list.childNodes.length, 0,
+      is(gSearchView.widget._list.childNodes.length, 0,
         "The global search pane shouldn't have any child nodes after a page navigation.");
-      is(gSearchView._container._parent.hidden, true,
+      is(gSearchView.widget._parent.hidden, true,
         "The global search pane shouldn't be visible after a page navigation.");
       is(gSearchView._splitter.hidden, true,
         "The global search pane splitter shouldn't be visible after a page navigation.");
 
       is(gDebugger.DebuggerController.SourceScripts.getCache().length, 0,
         "The scripts sources cache for global searching should be cleared after a page navigation.")
 
       closeDebuggerAndFinish();
--- a/browser/devtools/debugger/test/browser_dbg_scripts-searching-08.js
+++ b/browser/devtools/debugger/test/browser_dbg_scripts-searching-08.js
@@ -60,17 +60,17 @@ function testScriptSearching() {
   gSources = gDebugger.DebuggerView.Sources;
   gSearchView = gDebugger.DebuggerView.GlobalSearch;
   gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
 
   doSearch();
 }
 
 function doSearch() {
-  is(gSearchView._container._parent.hidden, true,
+  is(gSearchView.widget._parent.hidden, true,
     "The global search pane shouldn't be visible yet.");
 
   gDebugger.addEventListener("Debugger:GlobalSearch:MatchFound", function _onEvent(aEvent) {
     gDebugger.removeEventListener(aEvent.type, _onEvent);
     info("Current script url:\n" + gSources.selectedValue + "\n");
     info("Debugger editor text:\n" + gEditor.getText() + "\n");
 
     let url = gSources.selectedValue;
@@ -84,17 +84,17 @@ function doSearch() {
   });
   executeSoon(function() {
     write("!a");
   });
 }
 
 function testFocusLost()
 {
-  is(gSearchView._container._parent.hidden, false,
+  is(gSearchView.widget._parent.hidden, false,
     "The global search pane should be visible after a search.");
 
   gDebugger.addEventListener("Debugger:GlobalSearch:ViewCleared", function _onEvent(aEvent) {
     gDebugger.removeEventListener(aEvent.type, _onEvent);
     info("Current script url:\n" + gSources.selectedValue + "\n");
     info("Debugger editor text:\n" + gEditor.getText() + "\n");
 
     let url = gSources.selectedValue;
@@ -107,17 +107,17 @@ function testFocusLost()
     }
   });
   executeSoon(function() {
     gDebugger.DebuggerView.editor.focus();
   });
 }
 
 function reshowSearch() {
-  is(gSearchView._container._parent.hidden, true,
+  is(gSearchView.widget._parent.hidden, true,
     "The global search pane shouldn't be visible after the search was stopped.");
 
   gDebugger.addEventListener("Debugger:GlobalSearch:MatchFound", function _onEvent(aEvent) {
     gDebugger.removeEventListener(aEvent.type, _onEvent);
     info("Current script url:\n" + gSources.selectedValue + "\n");
     info("Debugger editor text:\n" + gEditor.getText() + "\n");
 
     let url = gSources.selectedValue;
@@ -131,17 +131,17 @@ function reshowSearch() {
   });
   executeSoon(function() {
     sendEnter();
   });
 }
 
 function testEscape()
 {
-  is(gSearchView._container._parent.hidden, false,
+  is(gSearchView.widget._parent.hidden, false,
     "The global search pane should be visible after a re-search.");
 
   gDebugger.addEventListener("Debugger:GlobalSearch:ViewCleared", function _onEvent(aEvent) {
     gDebugger.removeEventListener(aEvent.type, _onEvent);
     info("Current script url:\n" + gSources.selectedValue + "\n");
     info("Debugger editor text:\n" + gEditor.getText() + "\n");
 
     let url = gSources.selectedValue;
@@ -155,17 +155,17 @@ function testEscape()
   });
   executeSoon(function() {
     sendEscape();
   });
 }
 
 function finalCheck()
 {
-  is(gSearchView._container._parent.hidden, true,
+  is(gSearchView.widget._parent.hidden, true,
     "The global search pane shouldn't be visible after the search was escaped.");
 
   closeDebuggerAndFinish();
 }
 
 function clear() {
   gSearchBox.focus();
   gSearchBox.value = "";
--- a/browser/devtools/debugger/test/browser_dbg_scripts-searching-files_ui.js
+++ b/browser/devtools/debugger/test/browser_dbg_scripts-searching-files_ui.js
@@ -479,22 +479,22 @@ function clickAndSwitch() {
         sourceshown = true;
         reopen();
       });
     } else {
       ok(false, "How did you get here?");
     }
   });
 
-  ok(gFilteredSources._container._parent.querySelectorAll(".results-panel-item")[0]
+  ok(gFilteredSources.widget._parent.querySelectorAll(".results-panel-item")[0]
      .classList.contains("results-panel-item"),
      "The first visible item target isn't the correct one.");
 
   EventUtils.sendMouseEvent({ type: "click" },
-    gFilteredSources._container._parent.querySelectorAll(".results-panel-item")[0],
+    gFilteredSources.widget._parent.querySelectorAll(".results-panel-item")[0],
     gDebugger);
 }
 
 function clickAndSwitchAgain() {
   let sourceshown = false;
   let popuphidden = false;
   let popupshown = false;
   let reopened = false;
@@ -566,22 +566,22 @@ function clickAndSwitchAgain() {
         sourceshown = true;
         reopen();
       });
     } else {
       ok(false, "How did you get here?");
     }
   });
 
-  ok(gFilteredSources._container._parent.querySelectorAll(".results-panel-item")[2]
+  ok(gFilteredSources.widget._parent.querySelectorAll(".results-panel-item")[2]
      .classList.contains("results-panel-item"),
      "The first visible item target isn't the correct one.");
 
   EventUtils.sendMouseEvent({ type: "click" },
-    gFilteredSources._container._parent.querySelectorAll(".results-panel-item")[2],
+    gFilteredSources.widget._parent.querySelectorAll(".results-panel-item")[2],
     gDebugger);
 }
 
 function switchFocusWithEscape() {
   gDebugger.addEventListener("popuphidden", function _onEvent(aEvent) {
     gDebugger.removeEventListener(aEvent.type, _onEvent);
 
     info("Current script url:\n" + gSources.selectedValue + "\n");
--- a/browser/devtools/debugger/test/browser_dbg_scripts-sorting.js
+++ b/browser/devtools/debugger/test/browser_dbg_scripts-sorting.js
@@ -41,17 +41,17 @@ function resumeAndFinish() {
     });
   });
 }
 
 function addScriptAndCheckOrder(method, callback) {
   let sv = gDebugger.SourceUtils;
   let vs = gDebugger.DebuggerView.Sources;
   vs.empty();
-  vs._container.removeEventListener("select", vs._onScriptsChange, false);
+  vs.widget.removeEventListener("select", vs._onScriptsChange, false);
 
   let urls = [
     { href: "ici://some.address.com/random/", leaf: "subrandom/" },
     { href: "ni://another.address.org/random/subrandom/", leaf: "page.html" },
     { href: "san://interesting.address.gro/random/", leaf: "script.js" },
     { href: "si://interesting.address.moc/random/", leaf: "script.js" },
     { href: "si://interesting.address.moc/random/", leaf: "x/script.js" },
     { href: "si://interesting.address.moc/random/", leaf: "x/y/script.js?a=1" },
--- a/browser/devtools/debugger/test/browser_dbg_select-line.js
+++ b/browser/devtools/debugger/test/browser_dbg_select-line.js
@@ -92,17 +92,17 @@ function testSelectLine() {
         // getCaretPosition is 0-based.
         is(gDebugger.editor.getCaretPosition().line, 4,
            "The correct line is selected.");
 
         closeDebuggerAndFinish();
       });
     });
 
-    let frames = gDebugger.DebuggerView.StackFrames._container._list;
+    let frames = gDebugger.DebuggerView.StackFrames.widget._list;
     let childNodes = frames.childNodes;
 
     is(frames.querySelectorAll(".dbg-stackframe").length, 4,
       "Should have two frames.");
 
     is(childNodes.length, frames.querySelectorAll(".dbg-stackframe").length,
       "All children should be frames.");
 
--- a/browser/devtools/debugger/test/browser_dbg_source_maps-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_source_maps-02.js
@@ -98,17 +98,17 @@ function testSetBreakpoint() {
 function testHitBreakpoint() {
   let { activeThread } = gDebugger.DebuggerController;
   activeThread.resume(function (aResponse) {
     ok(!aResponse.error, "Shouldn't get an error resuming");
     is(aResponse.type, "resumed", "Type should be 'resumed'");
 
     activeThread.addOneTimeListener("framesadded", function (aEvent, aPacket) {
       // Make sure that we have JavaScript stack frames.
-      let frames = gDebugger.DebuggerView.StackFrames._container._list;
+      let frames = gDebugger.DebuggerView.StackFrames.widget._list;
       let childNodes = frames.childNodes;
 
       is(frames.querySelectorAll(".dbg-stackframe").length, 1,
         "Correct number of frames.");
       ok(frames.querySelector("#stackframe-0 .dbg-stackframe-details")
          .getAttribute("value").search(/js/),
          "First frame should be a JS frame.");
 
@@ -137,17 +137,17 @@ function testToggleOnPause() {
     ok(aEvent.detail.url.indexOf(".js") == -1,
        "The debugger should not show the generated js script file.");
     ok(gDebugger.editor.getText().search(/isnt/) != -1,
        "The debugger's editor should not have the coffee script source displayed.");
     ok(gDebugger.editor.getText().search(/function/) == -1,
        "The debugger's editor should have the JS source displayed.");
 
     // Make sure that we have coffee script stack frames.
-    let frames = gDebugger.DebuggerView.StackFrames._container._list;
+    let frames = gDebugger.DebuggerView.StackFrames.widget._list;
     let childNodes = frames.childNodes;
 
     is(frames.querySelectorAll(".dbg-stackframe").length, 1,
       "Correct number of frames.");
     ok(frames.querySelector("#stackframe-0 .dbg-stackframe-details")
        .getAttribute("value").search(/coffee/),
        "First frame should be a coffee script frame.");
 
--- a/browser/devtools/debugger/test/browser_dbg_stack-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_stack-01.js
@@ -19,17 +19,17 @@ function test() {
     testSimpleCall();
   });
 }
 
 function testSimpleCall() {
   gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
     Services.tm.currentThread.dispatch({ run: function() {
 
-      let frames = gDebugger.DebuggerView.StackFrames._container._list;
+      let frames = gDebugger.DebuggerView.StackFrames.widget._list;
       let childNodes = frames.childNodes;
 
       is(gDebugger.DebuggerController.activeThread.state, "paused",
         "Should only be getting stack frames while paused.");
 
       is(frames.querySelectorAll(".dbg-stackframe").length, 1,
         "Should have only one frame.");
 
--- a/browser/devtools/debugger/test/browser_dbg_stack-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_stack-02.js
@@ -19,17 +19,17 @@ function test() {
     testEvalCall();
   });
 }
 
 function testEvalCall() {
   gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
     Services.tm.currentThread.dispatch({ run: function() {
 
-      let frames = gDebugger.DebuggerView.StackFrames._container._list;
+      let frames = gDebugger.DebuggerView.StackFrames.widget._list;
       let childNodes = frames.childNodes;
 
       is(gDebugger.DebuggerController.activeThread.state, "paused",
         "Should only be getting stack frames while paused.");
 
       is(frames.querySelectorAll(".dbg-stackframe").length, 2,
         "Should have two frames.");
 
--- a/browser/devtools/debugger/test/browser_dbg_stack-03.js
+++ b/browser/devtools/debugger/test/browser_dbg_stack-03.js
@@ -21,17 +21,17 @@ function test() {
 }
 
 function testRecurse() {
   gDebuggee.gRecurseLimit = (gDebugger.gCallStackPageSize * 2) + 1;
 
   gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
     Services.tm.currentThread.dispatch({ run: function() {
 
-      let frames = gDebugger.DebuggerView.StackFrames._container._list;
+      let frames = gDebugger.DebuggerView.StackFrames.widget._list;
       let pageSize = gDebugger.gCallStackPageSize;
       let recurseLimit = gDebuggee.gRecurseLimit;
       let childNodes = frames.childNodes;
 
       is(frames.querySelectorAll(".dbg-stackframe").length, pageSize,
         "Should have the max limit of frames.");
 
       is(childNodes.length, frames.querySelectorAll(".dbg-stackframe").length,
--- a/browser/devtools/debugger/test/browser_dbg_stack-04.js
+++ b/browser/devtools/debugger/test/browser_dbg_stack-04.js
@@ -19,17 +19,17 @@ function test() {
     testEvalCallResume();
   });
 }
 
 function testEvalCallResume() {
   gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
     Services.tm.currentThread.dispatch({ run: function() {
 
-      let frames = gDebugger.DebuggerView.StackFrames._container._list;
+      let frames = gDebugger.DebuggerView.StackFrames.widget._list;
       let childNodes = frames.childNodes;
 
       is(gDebugger.DebuggerController.activeThread.state, "paused",
         "Should only be getting stack frames while paused.");
 
       is(frames.querySelectorAll(".dbg-stackframe").length, 2,
         "Should have two frames.");
 
--- a/browser/devtools/debugger/test/browser_dbg_stack-05.js
+++ b/browser/devtools/debugger/test/browser_dbg_stack-05.js
@@ -46,17 +46,17 @@ function test() {
     if (scriptShown && framesAdded) {
       Services.tm.currentThread.dispatch({ run: testRecurse }, 0);
     }
   }
 }
 
 function testRecurse()
 {
-  let frames = gDebugger.DebuggerView.StackFrames._container._list;
+  let frames = gDebugger.DebuggerView.StackFrames.widget._list;
   let childNodes = frames.childNodes;
 
   is(frames.querySelectorAll(".dbg-stackframe").length, 4,
     "Correct number of frames.");
 
   is(childNodes.length, frames.querySelectorAll(".dbg-stackframe").length,
     "All children should be frames.");
 
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -187,44 +187,21 @@ function debug_tab_pane(aURL, aOnDebuggi
         dbg._view.Variables.lazyAppend = false;
         dbg._view.Variables.lazyExpand = false;
         aOnDebugging(tab, debuggee, dbg);
       });
     });
   });
 }
 
-function debug_remote(aURL, aOnDebugging, aBeforeTabAdded) {
-  // Make any necessary preparations (start the debugger server etc.)
-  if (aBeforeTabAdded) {
-    aBeforeTabAdded();
-  }
-
-  let tab = addTab(aURL, function() {
-    let debuggee = tab.linkedBrowser.contentWindow.wrappedJSObject;
-
-    info("Opening Remote Debugger");
-    let win = DebuggerUI.toggleRemoteDebugger();
-
-    // Wait for the initial resume...
-    win.panelWin.gClient.addOneTimeListener("resumed", function() {
-      info("Remote Debugger has started");
-      win._dbgwin.DebuggerView.Variables.lazyEmpty = false;
-      win._dbgwin.DebuggerView.Variables.lazyAppend = false;
-      win._dbgwin.DebuggerView.Variables.lazyExpand = false;
-      aOnDebugging(tab, debuggee, win);
-    });
-  });
-}
-
 function debug_chrome(aURL, aOnClosing, aOnDebugging) {
   let tab = addTab(aURL, function() {
     let debuggee = tab.linkedBrowser.contentWindow.wrappedJSObject;
 
     info("Opening Browser Debugger");
-    let win = DebuggerUI.toggleChromeDebugger(aOnClosing, function(process) {
+    let win = BrowserDebuggerProcess.init(aOnClosing, function(process) {
 
       // The remote debugging process has started...
       info("Browser Debugger has started");
       aOnDebugging(tab, debuggee, process);
     });
   });
 }
--- a/browser/devtools/main.js
+++ b/browser/devtools/main.js
@@ -102,17 +102,17 @@ Tools.inspector = {
   build: function(iframeWindow, toolbox) {
     let panel = new InspectorPanel(iframeWindow, toolbox);
     return panel.open();
   }
 };
 
 Tools.jsdebugger = {
   id: "jsdebugger",
-  key: l10n("open.commandkey", debuggerStrings),
+  key: l10n("debuggerMenu.commandkey", debuggerStrings),
   accesskey: l10n("debuggerMenu.accesskey", debuggerStrings),
   modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift",
   ordinal: 3,
   visibilityswitch: "devtools.debugger.enabled",
   icon: "chrome://browser/skin/devtools/tool-debugger.png",
   url: "chrome://browser/content/devtools/debugger.xul",
   label: l10n("ToolboxDebugger.label", debuggerStrings),
   tooltip: l10n("ToolboxDebugger.tooltip", debuggerStrings),
--- a/browser/devtools/netmonitor/netmonitor-controller.js
+++ b/browser/devtools/netmonitor/netmonitor-controller.js
@@ -548,19 +548,16 @@ EventEmitter.decorate(this);
  */
 NetMonitorController.TargetEventsHandler = new TargetEventsHandler();
 NetMonitorController.NetworkEventsHandler = new NetworkEventsHandler();
 
 /**
  * Export some properties to the global scope for easier access.
  */
 Object.defineProperties(window, {
-  "create": {
-    get: function() ViewHelpers.create
-  },
   "gNetwork": {
     get: function() NetMonitorController.NetworkEventsHandler
   }
 });
 
 /**
  * Helper method for debugging.
  * @param string
--- a/browser/devtools/netmonitor/netmonitor-view.js
+++ b/browser/devtools/netmonitor/netmonitor-view.js
@@ -42,23 +42,23 @@ const CONTENT_MIME_TYPE_MAPPINGS = {
   "/css": SourceEditor.MODES.CSS
 };
 const DEFAULT_EDITOR_CONFIG = {
   mode: SourceEditor.MODES.TEXT,
   readOnly: true,
   showLineNumbers: true
 };
 const GENERIC_VARIABLES_VIEW_SETTINGS = {
-  lazyEmpty: false,
+  lazyEmpty: true,
   lazyEmptyDelay: 10, // ms
   searchEnabled: true,
-  descriptorTooltip: false,
   editableValueTooltip: "",
   editableNameTooltip: "",
   preventDisableOnChage: true,
+  preventDescriptorModifiers: true,
   eval: () => {},
   switch: () => {}
 };
 
 /**
  * Object defining the network monitor view components.
  */
 let NetMonitorView = {
@@ -255,50 +255,48 @@ ToolbarView.prototype = {
 /**
  * Functions handling the requests menu (containing details about each request,
  * like status, method, file, domain, as well as a waterfall representing
  * timing imformation).
  */
 function RequestsMenuView() {
   dumpn("RequestsMenuView was instantiated");
 
-  this._cache = new Map(); // Can't use a WeakMap because keys are strings.
   this._flushRequests = this._flushRequests.bind(this);
-  this._onRequestItemRemoved = this._onRequestItemRemoved.bind(this);
   this._onSelect = this._onSelect.bind(this);
   this._onResize = this._onResize.bind(this);
   this._byFile = this._byFile.bind(this);
   this._byDomain = this._byDomain.bind(this);
   this._byType = this._byType.bind(this);
 }
 
-create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
+RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
   /**
    * Initialization function, called when the network monitor is started.
    */
   initialize: function() {
     dumpn("Initializing the RequestsMenuView");
 
-    this.node = new SideMenuWidget($("#requests-menu-contents"), false);
+    this.widget = new SideMenuWidget($("#requests-menu-contents"), false);
     this._summary = $("#request-menu-network-summary");
 
-    this.node.maintainSelectionVisible = false;
-    this.node.autoscrollWithAppendedItems = true;
+    this.widget.maintainSelectionVisible = false;
+    this.widget.autoscrollWithAppendedItems = true;
 
-    this.node.addEventListener("select", this._onSelect, false);
+    this.widget.addEventListener("select", this._onSelect, false);
     window.addEventListener("resize", this._onResize, false);
   },
 
   /**
    * Destruction function, called when the network monitor is closed.
    */
   destroy: function() {
     dumpn("Destroying the SourcesView");
 
-    this.node.removeEventListener("select", this._onSelect, false);
+    this.widget.removeEventListener("select", this._onSelect, false);
     window.removeEventListener("resize", this._onResize, false);
   },
 
   /**
    * Resets this container (removes all the networking information).
    */
   reset: function() {
     this.empty();
@@ -333,60 +331,57 @@ create({ constructor: RequestsMenuView, 
     // Create the element node for the network request item.
     let menuView = this._createMenuView(aMethod, aUrl);
 
     // Remember the first and last event boundaries.
     this._registerFirstRequestStart(unixTime);
     this._registerLastRequestEnd(unixTime);
 
     // Append a network request item to this container.
-    let requestItem = this.push(menuView, {
+    let requestItem = this.push([menuView, aId], {
       attachment: {
-        id: aId,
         startedDeltaMillis: unixTime - this._firstRequestStartedMillis,
         startedMillis: unixTime,
         method: aMethod,
         url: aUrl,
         isXHR: aIsXHR
-      },
-      finalize: this._onRequestItemRemoved
+      }
     });
 
     $("#details-pane-toggle").disabled = false;
     $("#requests-menu-empty-notice").hidden = true;
 
     this.refreshSummary();
-    this._cache.set(aId, requestItem);
+    this.refreshZebra();
   },
 
   /**
    * Filters all network requests in this container by a specified type.
    *
    * @param string aType
-   *        Either null, "html", "css", "js", "xhr", "fonts", "images", "media"
+   *        Either "all", "html", "css", "js", "xhr", "fonts", "images", "media"
    *        or "flash".
    */
-  filterOn: function(aType) {
+  filterOn: function(aType = "all") {
     let target = $("#requests-menu-filter-" + aType + "-button");
     let buttons = document.querySelectorAll(".requests-menu-footer-button");
 
     for (let button of buttons) {
       if (button != target) {
         button.removeAttribute("checked");
       } else {
-        button.setAttribute("checked", "");
+        button.setAttribute("checked", "true");
       }
     }
 
-    // Filter on nothing.
-    if (!target) {
-      this.filterContents(() => true);
-    }
     // Filter on whatever was requested.
-    else switch (aType) {
+    switch (aType) {
+      case "all":
+        this.filterContents(() => true);
+        break;
       case "html":
         this.filterContents(this._onHtml);
         break;
       case "css":
         this.filterContents(this._onCss);
         break;
       case "js":
         this.filterContents(this._onJs);
@@ -404,23 +399,25 @@ create({ constructor: RequestsMenuView, 
         this.filterContents(this._onMedia);
         break;
       case "flash":
         this.filterContents(this._onFlash);
         break;
     }
 
     this.refreshSummary();
+    this.refreshZebra();
   },
 
   /**
    * Sorts all network requests in this container by a specified detail.
    *
    * @param string aType
-   *        Either "status", "method", "file", "domain", "type", "size" or "waterfall".
+   *        Either "status", "method", "file", "domain", "type", "size" or
+   *        "waterfall".
    */
   sortBy: function(aType = "waterfall") {
     let target = $("#requests-menu-" + aType + "-button");
     let headers = document.querySelectorAll(".requests-menu-header-button");
 
     for (let header of headers) {
       if (header != target) {
         header.removeAttribute("sorted");
@@ -486,25 +483,28 @@ create({ constructor: RequestsMenuView, 
       case "waterfall":
         if (direction == "ascending") {
           this.sortContents(this._byTiming);
         } else {
           this.sortContents((a, b) => !this._byTiming(a, b));
         }
         break;
     }
+
+    this.refreshSummary();
+    this.refreshZebra();
   },
 
   /**
    * Predicates used when filtering items.
    *
-   * @param MenuItem aItem
-   *        The filtered menu item.
+   * @param object aItem
+   *        The filtered item.
    * @return boolean
-   *         True if the menu item should be visible, false otherwise.
+   *         True if the item should be visible, false otherwise.
    */
   _onHtml: function({ attachment: { mimeType } })
     mimeType && mimeType.contains("/html"),
 
   _onCss: function({ attachment: { mimeType } })
     mimeType && mimeType.contains("/css"),
 
   _onJs: function({ attachment: { mimeType } })
@@ -539,20 +539,20 @@ create({ constructor: RequestsMenuView, 
       mimeType.contains("/x-flv") ||
       mimeType.contains("/x-shockwave-flash"))) ||
     url.contains(".swf") ||
     url.contains(".flv"),
 
   /**
    * Predicates used when sorting items.
    *
-   * @param MenuItem aFirst
-   *        The first menu item used in the comparison.
-   * @param MenuItem aSecond
-   *        The second menu item used in the comparison.
+   * @param object aFirst
+   *        The first item used in the comparison.
+   * @param object aSecond
+   *        The second item used in the comparison.
    * @return number
    *         -1 to sort aFirst to a lower index than aSecond
    *          0 to leave aFirst and aSecond unchanged with respect to each other
    *          1 to sort aSecond to a lower index than aFirst
    */
   _byTiming: function({ attachment: first }, { attachment: second })
     first.startedMillis > second.startedMillis,
 
@@ -599,16 +599,36 @@ create({ constructor: RequestsMenuView, 
     this._summary.setAttribute("value", str
       .replace("#1", visibleRequestsCount)
       .replace("#2", L10N.numberWithDecimals((totalBytes || 0) / 1024, 2))
       .replace("#3", L10N.numberWithDecimals((totalMillis || 0) / 1000, 2))
     );
   },
 
   /**
+   * Adds odd/even attributes to all the visible items in this container.
+   */
+  refreshZebra: function() {
+    let visibleItems = this.orderedVisibleItems;
+
+    for (let i = 0, len = visibleItems.length; i < len; i++) {
+      let requestItem = visibleItems[i];
+      let requestTarget = requestItem.target;
+
+      if (i % 2 == 0) {
+        requestTarget.setAttribute("even", "");
+        requestTarget.removeAttribute("odd");
+      } else {
+        requestTarget.setAttribute("odd", "");
+        requestTarget.removeAttribute("even");
+      }
+    }
+  },
+
+  /**
    * Schedules adding additional information to a network request.
    *
    * @param string aId
    *        An identifier coming from the network monitor controller.
    * @param object aData
    *        An object containing several { key: value } tuples of network info.
    *        Supported keys are "httpVersion", "status", "statusText" etc.
    */
@@ -629,17 +649,17 @@ create({ constructor: RequestsMenuView, 
 
   /**
    * Starts adding all queued additional information about network requests.
    */
   _flushRequests: function() {
     // For each queued additional information packet, get the corresponding
     // request item in the view and update it based on the specified data.
     for (let [id, data] of this._updateQueue) {
-      let requestItem = this._cache.get(id);
+      let requestItem = this.getItemByValue(id);
       if (!requestItem) {
         // Packet corresponds to a dead request item, target navigated.
         continue;
       }
 
       // Each information packet may contain several { key: value } tuples of
       // network info, so update the view based on each one.
       for (let key in data) {
@@ -702,32 +722,33 @@ create({ constructor: RequestsMenuView, 
             requestItem.attachment.eventTimings = value;
             this._createWaterfallView(requestItem, value.timings);
             break;
         }
       }
       // This update may have additional information about a request which
       // isn't shown yet in the network details pane.
       let selectedItem = this.selectedItem;
-      if (selectedItem && selectedItem.attachment.id == id) {
+      if (selectedItem && selectedItem.value == id) {
         NetMonitorView.NetworkDetails.populate(selectedItem.attachment);
       }
     }
 
     // We're done flushing all the requests, clear the update queue.
     this._updateQueue = [];
 
     // Make sure all the requests are sorted and filtered.
     // Freshly added requests may not yet contain all the information required
     // for sorting and filtering predicates, so this is done each time the
     // network requests table is flushed (don't worry, events are drained first
     // so this doesn't happen once per network event update).
     this.sortContents();
     this.filterContents();
     this.refreshSummary();
+    this.refreshZebra();
   },
 
   /**
    * Customization function for creating an item's UI.
    *
    * @param string aMethod
    *        Specifies the request method (e.g. "GET", "POST", etc.)
    * @param string aUrl
@@ -763,17 +784,17 @@ create({ constructor: RequestsMenuView, 
     }
 
     return fragment;
   },
 
   /**
    * Updates the information displayed in a network request item view.
    *
-   * @param MenuItem aItem
+   * @param object aItem
    *        The network request item in this container.
    * @param string aKey
    *        The type of information that is to be updated.
    * @param any aValue
    *        The new value to be shown.
    */
   _updateMenuView: function(aItem, aKey, aValue) {
     switch (aKey) {
@@ -812,17 +833,17 @@ create({ constructor: RequestsMenuView, 
         break;
       }
     }
   },
 
   /**
    * Creates a waterfall representing timing information in a network request item view.
    *
-   * @param MenuItem aItem
+   * @param object aItem
    *        The network request item in this container.
    * @param object aTimings
    *        An object containing timing information.
    */
   _createWaterfallView: function(aItem, aTimings) {
     let { target, attachment } = aItem;
     let sections = ["dns", "connect", "send", "wait", "receive"];
     // Skipping "blocked" because it doesn't work yet.
@@ -887,17 +908,17 @@ create({ constructor: RequestsMenuView, 
 
     // Redraw and set the canvas background for each waterfall view.
     this._showWaterfallDivisionLabels(scale);
     this._drawWaterfallBackground(scale);
     this._flushWaterfallBackgrounds();
 
     // Apply CSS transforms to each waterfall in this container totalTime
     // accurately translate and resize as needed.
-    for (let [, { target, attachment }] of this._cache) {
+    for (let { target, attachment } in this) {
       let timingsNode = $(".requests-menu-timings", target);
       let startCapNode = $(".requests-menu-timings-cap.start", target);
       let endCapNode = $(".requests-menu-timings-cap.end", target);
       let totalNode = $(".requests-menu-timings-total", target);
       let direction = window.isRTL ? -1 : 1;
 
       // Render the timing information at a specific horizontal translation
       // based on the delta to the first monitored event network.
@@ -1023,17 +1044,17 @@ create({ constructor: RequestsMenuView, 
     ctx.putImageData(imageData, 0, 0);
     this._cachedWaterfallBackground = "url(" + canvas.toDataURL() + ")";
   },
 
   /**
    * Reapplies the current waterfall background on all request items.
    */
   _flushWaterfallBackgrounds: function() {
-    for (let [, { target }] of this._cache) {
+    for (let { target } in this) {
       let waterfallNode = $(".requests-menu-waterfall", target);
       waterfallNode.style.backgroundImage = this._cachedWaterfallBackground;
     }
   },
 
   /**
    * Hides the overflowing columns in the requests table.
    */
@@ -1059,27 +1080,16 @@ create({ constructor: RequestsMenuView, 
       let bounds = $(id).getBoundingClientRect();
       if (bounds.right > availableWidth - REQUESTS_HEADERS_SAFE_BOUNDS) {
         table.setAttribute(attribute, "");
       }
     });
   },
 
   /**
-   * Function called each time a network request item is removed.
-   *
-   * @param MenuItem aItem
-   *        The corresponding menu item.
-   */
-  _onRequestItemRemoved: function(aItem) {
-    dumpn("Finalizing network request item: " + aItem);
-    this._cache.delete(aItem.attachment.id);
-  },
-
-  /**
    * The selection listener for this container.
    */
   _onSelect: function({ detail: item }) {
     if (item) {
       NetMonitorView.NetworkDetails.populate(item.attachment);
       NetMonitorView.NetworkDetails.toggle(true);
     } else {
       NetMonitorView.NetworkDetails.toggle(false);
@@ -1168,32 +1178,32 @@ create({ constructor: RequestsMenuView, 
     return aItemsArray.reduce((prev, curr) => prev + curr.attachment.contentSize || 0, 0);
   },
 
   /**
    * Gets the oldest (first performed) request in a set. Returns null for an
    * empty set.
    *
    * @param array aItemsArray
-   * @return MenuItem
+   * @return object
    */
   _getOldestRequest: function(aItemsArray) {
     if (!aItemsArray.length) {
       return null;
     }
     return aItemsArray.reduce((prev, curr) =>
       prev.attachment.startedMillis < curr.attachment.startedMillis ? prev : curr);
   },
 
   /**
    * Gets the newest (latest performed) request in a set. Returns null for an
    * empty set.
    *
    * @param array aItemsArray
-   * @return MenuItem
+   * @return object
    */
   _getNewestRequest: function(aItemsArray) {
     if (!aItemsArray.length) {
       return null;
     }
     return aItemsArray.reduce((prev, curr) =>
       prev.attachment.startedMillis > curr.attachment.startedMillis ? prev : curr);
   },
@@ -1212,17 +1222,16 @@ create({ constructor: RequestsMenuView, 
         this._cachedWaterfallWidth = containerBounds.width - waterfallBounds.left;
       } else {
         this._cachedWaterfallWidth = waterfallBounds.right;
       }
     }
     return this._cachedWaterfallWidth;
   },
 
-  _cache: null,
   _summary: null,
   _canvas: null,
   _ctx: null,
   _cachedWaterfallWidth: 0,
   _cachedWaterfallBackground: "",
   _firstRequestStartedMillis: -1,
   _lastRequestEndedMillis: -1,
   _updateQueue: [],
@@ -1241,47 +1250,47 @@ function NetworkDetailsView() {
 
 NetworkDetailsView.prototype = {
   /**
    * Initialization function, called when the network monitor is started.
    */
   initialize: function() {
     dumpn("Initializing the RequestsMenuView");
 
-    this.node = $("#details-pane");
+    this.widget = $("#details-pane");
 
     this._headers = new VariablesView($("#all-headers"),
-      Object.create(GENERIC_VARIABLES_VIEW_SETTINGS, {
-        emptyText: { value: L10N.getStr("headersEmptyText"), enumerable: true },
-        searchPlaceholder: { value: L10N.getStr("headersFilterText"), enumerable: true }
+      Heritage.extend(GENERIC_VARIABLES_VIEW_SETTINGS, {
+        emptyText: L10N.getStr("headersEmptyText"),
+        searchPlaceholder: L10N.getStr("headersFilterText")
       }));
     this._cookies = new VariablesView($("#all-cookies"),
-      Object.create(GENERIC_VARIABLES_VIEW_SETTINGS, {
-        emptyText: { value: L10N.getStr("cookiesEmptyText"), enumerable: true },
-        searchPlaceholder: { value: L10N.getStr("cookiesFilterText"), enumerable: true }
+      Heritage.extend(GENERIC_VARIABLES_VIEW_SETTINGS, {
+        emptyText: L10N.getStr("cookiesEmptyText"),
+        searchPlaceholder: L10N.getStr("cookiesFilterText")
       }));
     this._params = new VariablesView($("#request-params"),
-      Object.create(GENERIC_VARIABLES_VIEW_SETTINGS, {
-        emptyText: { value: L10N.getStr("paramsEmptyText"), enumerable: true },
-        searchPlaceholder: { value: L10N.getStr("paramsFilterText"), enumerable: true }
+      Heritage.extend(GENERIC_VARIABLES_VIEW_SETTINGS, {
+        emptyText: L10N.getStr("paramsEmptyText"),
+        searchPlaceholder: L10N.getStr("paramsFilterText")
       }));
     this._json = new VariablesView($("#response-content-json"),
-      Object.create(GENERIC_VARIABLES_VIEW_SETTINGS, {
-        searchPlaceholder: { value: L10N.getStr("jsonFilterText"), enumerable: true }
+      Heritage.extend(GENERIC_VARIABLES_VIEW_SETTINGS, {
+        searchPlaceholder: L10N.getStr("jsonFilterText")
       }));
 
     this._paramsQueryString = L10N.getStr("paramsQueryString");
     this._paramsFormData = L10N.getStr("paramsFormData");
     this._paramsPostPayload = L10N.getStr("paramsPostPayload");
     this._requestHeaders = L10N.getStr("requestHeaders");
     this._responseHeaders = L10N.getStr("responseHeaders");
     this._requestCookies = L10N.getStr("requestCookies");
     this._responseCookies = L10N.getStr("responseCookies");
 
-    $("tabpanels", this.node).addEventListener("select", this._onTabSelect);
+    $("tabpanels", this.widget).addEventListener("select", this._onTabSelect);
   },
 
   /**
    * Destruction function, called when the network monitor is closed.
    */
   destroy: function() {
     dumpn("Destroying the SourcesView");
   },
@@ -1329,17 +1338,17 @@ NetworkDetailsView.prototype = {
     this._onTabSelect();
   },
 
   /**
    * Listener handling the tab selection event.
    */
   _onTabSelect: function() {
     let { src, populated } = this._dataSrc || {};
-    let tab = this.node.selectedIndex;
+    let tab = this.widget.selectedIndex;
 
     // Make sure the data source is valid and don't populate the same tab twice.
     if (!src || populated[tab]) {
       return;
     }
 
     switch (tab) {
       case 0: // "Headers"
@@ -1441,17 +1450,17 @@ NetworkDetailsView.prototype = {
   _addHeaders: function(aName, aResponse) {
     let kb = aResponse.headersSize / 1024;
     let size = L10N.numberWithDecimals(kb, HEADERS_SIZE_DECIMALS);
     let text = L10N.getFormatStr("networkMenu.sizeKB", size);
     let headersScope = this._headers.addScope(aName + " (" + text + ")");
     headersScope.expanded = true;
 
     for (let header of aResponse.headers) {
-      let headerVar = headersScope.addItem(header.name, { null: true }, true);
+      let headerVar = headersScope.addItem(header.name, {}, true);
       gNetwork.getString(header.value).then((aString) => headerVar.setGrip(aString));
     }
   },
 
   /**
    * Sets the network request cookies shown in this view.
    *
    * @param object aResponse
@@ -1484,17 +1493,17 @@ NetworkDetailsView.prototype = {
    * @param object aResponse
    *        The message received from the server.
    */
   _addCookies: function(aName, aResponse) {
     let cookiesScope = this._cookies.addScope(aName);
     cookiesScope.expanded = true;
 
     for (let cookie of aResponse.cookies) {
-      let cookieVar = cookiesScope.addItem(cookie.name, { null: true }, true);
+      let cookieVar = cookiesScope.addItem(cookie.name, {}, true);
       gNetwork.getString(cookie.value).then((aString) => cookieVar.setGrip(aString));
 
       // By default the cookie name and value are shown. If this is the only
       // information available, then nothing else is to be displayed.
       let cookieProps = Object.keys(cookie);
       if (cookieProps.length == 2) {
         continue;
       }
@@ -1586,17 +1595,17 @@ NetworkDetailsView.prototype = {
         name: NetworkHelper.convertToUnicode(unescape(param[0])),
         value: NetworkHelper.convertToUnicode(unescape(param[1]))
       });
 
     let paramsScope = this._params.addScope(aName);
     paramsScope.expanded = true;
 
     for (let param of paramsArray) {
-      let headerVar = paramsScope.addItem(param.name, { null: true }, true);
+      let headerVar = paramsScope.addItem(param.name, {}, true);
       headerVar.setGrip(param.value);
     }
   },
 
   /**
    * Sets the network response body shown in this view.
    *
    * @param string aUrl
--- a/browser/devtools/netmonitor/netmonitor.xul
+++ b/browser/devtools/netmonitor/netmonitor.xul
@@ -97,17 +97,17 @@
         </hbox>
         <toolbarbutton id="details-pane-toggle"
                        class="devtools-toolbarbutton"
                        tooltiptext="&netmonitorUI.panesButton.tooltip;"
                        disabled="true"
                        tabindex="0"/>
       </toolbar>
       <label id="requests-menu-empty-notice"
-             value="&netmonitorUI.emptyNotice;"/>
+             value="&netmonitorUI.emptyNotice2;"/>
       <vbox id="requests-menu-contents" flex="1">
         <hbox id="requests-menu-item-template" hidden="true">
           <hbox class="requests-menu-subitem requests-menu-status-and-method"
                 align="center">
             <box class="requests-menu-status"/>
             <label class="plain requests-menu-method"
                    crop="end"
                    flex="1"/>
@@ -133,17 +133,18 @@
         </hbox>
       </vbox>
       <hbox id="requests-menu-footer">
         <spacer id="requests-menu-spacer-start"
                 class="requests-menu-footer-spacer"
                 flex="100"/>
         <button id="requests-menu-filter-all-button"
                 class="requests-menu-footer-button"
-                onclick="NetMonitorView.RequestsMenu.filterOn(null)"
+                checked="true"
+                onclick="NetMonitorView.RequestsMenu.filterOn('all')"
                 label="&netmonitorUI.footer.filterAll;">
         </button>
         <button id="requests-menu-filter-html-button"
                 class="requests-menu-footer-button"
                 onclick="NetMonitorView.RequestsMenu.filterOn('html')"
                 label="&netmonitorUI.footer.filterHTML;">
         </button>
         <button id="requests-menu-filter-css-button"
--- a/browser/devtools/netmonitor/test/browser_net_filter-01.js
+++ b/browser/devtools/netmonitor/test/browser_net_filter-01.js
@@ -19,17 +19,17 @@ function test() {
 
       isnot(RequestsMenu.selectedItem, null,
         "There should be a selected item in the requests menu.");
       is(RequestsMenu.selectedIndex, 0,
         "The first item should be selected in the requests menu.");
       is(NetMonitorView.detailsPaneHidden, false,
         "The details pane should not be hidden after toggle button was pressed.");
 
-      testButtons();
+      testButtons("all");
       testContents([1, 1, 1, 1, 1, 1, 1, 1])
         .then(() => {
           EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
           testButtons("html");
           return testContents([1, 0, 0, 0, 0, 0, 0, 0]);
         })
         .then(() => {
           EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-css-button"));
@@ -63,17 +63,17 @@ function test() {
         })
         .then(() => {
           EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-flash-button"));
           testButtons("flash");
           return testContents([0, 0, 0, 0, 0, 0, 0, 1]);
         })
         .then(() => {
           EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
-          testButtons();
+          testButtons("all");
           return testContents([1, 1, 1, 1, 1, 1, 1, 1]);
         })
         .then(() => {
           return teardown(aMonitor);
         })
         .then(finish);
     });
 
--- a/browser/devtools/netmonitor/test/browser_net_filter-02.js
+++ b/browser/devtools/netmonitor/test/browser_net_filter-02.js
@@ -19,17 +19,17 @@ function test() {
 
       isnot(RequestsMenu.selectedItem, null,
         "There should be a selected item in the requests menu.");
       is(RequestsMenu.selectedIndex, 0,
         "The first item should be selected in the requests menu.");
       is(NetMonitorView.detailsPaneHidden, false,
         "The details pane should not be hidden after toggle button was pressed.");
 
-      testButtons();
+      testButtons("all");
       testContents([1, 1, 1, 1, 1, 1, 1, 1])
         .then(() => {
           info("Testing html filtering.");
           EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
           testButtons("html");
           return testContents([1, 0, 0, 0, 0, 0, 0, 0]);
         })
         .then(() => {
--- a/browser/devtools/netmonitor/test/browser_net_filter-03.js
+++ b/browser/devtools/netmonitor/test/browser_net_filter-03.js
@@ -20,22 +20,22 @@ function test() {
 
       isnot(RequestsMenu.selectedItem, null,
         "There should be a selected item in the requests menu.");
       is(RequestsMenu.selectedIndex, 0,
         "The first item should be selected in the requests menu.");
       is(NetMonitorView.detailsPaneHidden, false,
         "The details pane should not be hidden after toggle button was pressed.");
 
-      testButtons();
+      testButtons("all");
       testContents([0, 1, 2, 3, 4, 5, 6], 7, 0)
         .then(() => {
           info("Sorting by size, ascending.");
           EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-size-button"));
-          testButtons();
+          testButtons("all");
           return testContents([6, 4, 5, 0, 1, 2, 3], 7, 6);
         })
         .then(() => {
           info("Testing html filtering.");
           EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
           testButtons("html");
           return testContents([6, 4, 5, 0, 1, 2, 3], 1, 6);
         })
--- a/browser/devtools/netmonitor/test/browser_net_simple-request-data.js
+++ b/browser/devtools/netmonitor/test/browser_net_simple-request-data.js
@@ -24,20 +24,20 @@ function test() {
       is(RequestsMenu.itemCount, 1,
         "The requests menu should not be empty after the first request.");
       is(NetMonitorView.detailsPaneHidden, true,
         "The details pane should still be hidden after the first request.");
 
       let requestItem = RequestsMenu.getItemAtIndex(0);
       let target = requestItem.target;
 
-      is(typeof requestItem.attachment.id, "string",
-        "The attached id is incorrect.");
-      isnot(requestItem.attachment.id, "",
-        "The attached id should not be empty.");
+      is(typeof requestItem.value, "string",
+        "The attached request id is incorrect.");
+      isnot(requestItem.value, "",
+        "The attached request id should not be empty.");
 
       is(typeof requestItem.attachment.startedDeltaMillis, "number",
         "The attached startedDeltaMillis is incorrect.");
       is(requestItem.attachment.startedDeltaMillis, 0,
         "The attached startedDeltaMillis should be zero.");
 
       is(typeof requestItem.attachment.startedMillis, "number",
         "The attached startedMillis is incorrect.");
--- a/browser/devtools/netmonitor/test/browser_net_sort-01.js
+++ b/browser/devtools/netmonitor/test/browser_net_sort-01.js
@@ -14,136 +14,161 @@ function test() {
 
     RequestsMenu.lazyUpdate = false;
 
     waitForNetworkEvents(aMonitor, 5).then(() => {
       testContents([0, 1, 2, 3, 4])
         .then(() => {
           info("Testing swap(0, 0)");
           RequestsMenu.swapItemsAtIndices(0, 0);
+          RequestsMenu.refreshZebra();
           return testContents([0, 1, 2, 3, 4]);
         })
         .then(() => {
           info("Testing swap(0, 1)");
           RequestsMenu.swapItemsAtIndices(0, 1);
+          RequestsMenu.refreshZebra();
           return testContents([1, 0, 2, 3, 4]);
         })
         .then(() => {
           info("Testing swap(0, 2)");
           RequestsMenu.swapItemsAtIndices(0, 2);
+          RequestsMenu.refreshZebra();
           return testContents([1, 2, 0, 3, 4]);
         })
         .then(() => {
           info("Testing swap(0, 3)");
           RequestsMenu.swapItemsAtIndices(0, 3);
+          RequestsMenu.refreshZebra();
           return testContents([1, 2, 3, 0, 4]);
         })
         .then(() => {
           info("Testing swap(0, 4)");
           RequestsMenu.swapItemsAtIndices(0, 4);
+          RequestsMenu.refreshZebra();
           return testContents([1, 2, 3, 4, 0]);
         })
         .then(() => {
           info("Testing swap(1, 0)");
           RequestsMenu.swapItemsAtIndices(1, 0);
+          RequestsMenu.refreshZebra();
           return testContents([0, 2, 3, 4, 1]);
         })
         .then(() => {
           info("Testing swap(1, 1)");
           RequestsMenu.swapItemsAtIndices(1, 1);
+          RequestsMenu.refreshZebra();
           return testContents([0, 2, 3, 4, 1]);
         })
         .then(() => {
           info("Testing swap(1, 2)");
           RequestsMenu.swapItemsAtIndices(1, 2);
+          RequestsMenu.refreshZebra();
           return testContents([0, 1, 3, 4, 2]);
         })
         .then(() => {
           info("Testing swap(1, 3)");
           RequestsMenu.swapItemsAtIndices(1, 3);
+          RequestsMenu.refreshZebra();
           return testContents([0, 3, 1, 4, 2]);
         })
         .then(() => {
           info("Testing swap(1, 4)");
           RequestsMenu.swapItemsAtIndices(1, 4);
+          RequestsMenu.refreshZebra();
           return testContents([0, 3, 4, 1, 2]);
         })
         .then(() => {
           info("Testing swap(2, 0)");
           RequestsMenu.swapItemsAtIndices(2, 0);
+          RequestsMenu.refreshZebra();
           return testContents([2, 3, 4, 1, 0]);
         })
         .then(() => {
           info("Testing swap(2, 1)");
           RequestsMenu.swapItemsAtIndices(2, 1);
+          RequestsMenu.refreshZebra();
           return testContents([1, 3, 4, 2, 0]);
         })
         .then(() => {
           info("Testing swap(2, 2)");
           RequestsMenu.swapItemsAtIndices(2, 2);
+          RequestsMenu.refreshZebra();
           return testContents([1, 3, 4, 2, 0]);
         })
         .then(() => {
           info("Testing swap(2, 3)");
           RequestsMenu.swapItemsAtIndices(2, 3);
+          RequestsMenu.refreshZebra();
           return testContents([1, 2, 4, 3, 0]);
         })
         .then(() => {
           info("Testing swap(2, 4)");
           RequestsMenu.swapItemsAtIndices(2, 4);
+          RequestsMenu.refreshZebra();
           return testContents([1, 4, 2, 3, 0]);
         })
         .then(() => {
           info("Testing swap(3, 0)");
           RequestsMenu.swapItemsAtIndices(3, 0);
+          RequestsMenu.refreshZebra();
           return testContents([1, 4, 2, 0, 3]);
         })
         .then(() => {
           info("Testing swap(3, 1)");
           RequestsMenu.swapItemsAtIndices(3, 1);
+          RequestsMenu.refreshZebra();
           return testContents([3, 4, 2, 0, 1]);
         })
         .then(() => {
           info("Testing swap(3, 2)");
           RequestsMenu.swapItemsAtIndices(3, 2);
+          RequestsMenu.refreshZebra();
           return testContents([2, 4, 3, 0, 1]);
         })
         .then(() => {
           info("Testing swap(3, 3)");
           RequestsMenu.swapItemsAtIndices(3, 3);
+          RequestsMenu.refreshZebra();
           return testContents([2, 4, 3, 0, 1]);
         })
         .then(() => {
           info("Testing swap(3, 4)");
           RequestsMenu.swapItemsAtIndices(3, 4);
+          RequestsMenu.refreshZebra();
           return testContents([2, 3, 4, 0, 1]);
         })
         .then(() => {
           info("Testing swap(4, 0)");
           RequestsMenu.swapItemsAtIndices(4, 0);
+          RequestsMenu.refreshZebra();
           return testContents([2, 3, 0, 4, 1]);
         })
         .then(() => {
           info("Testing swap(4, 1)");
           RequestsMenu.swapItemsAtIndices(4, 1);
+          RequestsMenu.refreshZebra();
           return testContents([2, 3, 0, 1, 4]);
         })
         .then(() => {
           info("Testing swap(4, 2)");
           RequestsMenu.swapItemsAtIndices(4, 2);
+          RequestsMenu.refreshZebra();
           return testContents([4, 3, 0, 1, 2]);
         })
         .then(() => {
           info("Testing swap(4, 3)");
           RequestsMenu.swapItemsAtIndices(4, 3);
+          RequestsMenu.refreshZebra();
           return testContents([3, 4, 0, 1, 2]);
         })
         .then(() => {
           info("Testing swap(4, 4)");
           RequestsMenu.swapItemsAtIndices(4, 4);
+          RequestsMenu.refreshZebra();
           return testContents([3, 4, 0, 1, 2]);
         })
         .then(() => {
           info("Clearing sort.");
           RequestsMenu.sortBy();
           return testContents([0, 1, 2, 3, 4]);
         })
         .then(() => {
--- a/browser/devtools/netmonitor/test/head.js
+++ b/browser/devtools/netmonitor/test/head.js
@@ -186,16 +186,23 @@ function waitForNetworkEvents(aMonitor, 
 
   return deferred.promise;
 }
 
 function verifyRequestItemTarget(aRequestItem, aMethod, aUrl, aData = {}) {
   info("> Verifying: " + aMethod + " " + aUrl + " " + aData.toSource());
   info("> Request: " + aRequestItem.attachment.toSource());
 
+  let requestsMenu = aRequestItem.ownerView;
+  let widgetIndex = requestsMenu.indexOfItem(aRequestItem);
+  let visibleIndex = requestsMenu.orderedVisibleItems.indexOf(aRequestItem);
+
+  info("Widget index of item: " + widgetIndex);
+  info("Visible index of item: " + visibleIndex);
+
   let { fuzzyUrl, status, statusText, type, fullMimeType, size, time } = aData;
   let { attachment, target } = aRequestItem
 
   let uri = Services.io.newURI(aUrl, null, null).QueryInterface(Ci.nsIURL);
   let name = uri.fileName || "/";
   let query = uri.query;
   let hostPort = uri.hostPort;
 
@@ -254,9 +261,23 @@ function verifyRequestItemTarget(aReques
   if (time !== undefined) {
     let value = target.querySelector(".requests-menu-timings-total").getAttribute("value");
     let tooltip = target.querySelector(".requests-menu-timings-total").getAttribute("tooltiptext");
     info("Displayed time: " + value);
     info("Tooltip time: " + tooltip);
     ok(~~(value.match(/[0-9]+/)) >= 0, "The displayed time is incorrect.");
     ok(~~(tooltip.match(/[0-9]+/)) >= 0, "The tooltip time is incorrect.");
   }
+
+  if (visibleIndex != -1) {
+    if (visibleIndex % 2 == 0) {
+      ok(aRequestItem.target.hasAttribute("even"),
+        "Unexpected 'even' attribute for " + aRequestItem.value);
+      ok(!aRequestItem.target.hasAttribute("odd"),
+        "Unexpected 'odd' attribute for " + aRequestItem.value);
+    } else {
+      ok(!aRequestItem.target.hasAttribute("even"),
+        "Unexpected 'even' attribute for " + aRequestItem.value);
+      ok(aRequestItem.target.hasAttribute("odd"),
+        "Unexpected 'odd' attribute for " + aRequestItem.value);
+    }
+  }
 }
--- a/browser/devtools/profiler/ProfilerPanel.jsm
+++ b/browser/devtools/profiler/ProfilerPanel.jsm
@@ -265,22 +265,22 @@ ProfileUI.prototype = {
     this.uid = null;
     this.iframe = null;
     this.messages = null;
   }
 };
 
 function SidebarView(el) {
   EventEmitter.decorate(this);
-  this.node = new SideMenuWidget(el);
+  this.widget = new SideMenuWidget(el);
 }
 
-ViewHelpers.create({ constructor: SidebarView, proto: MenuContainer.prototype }, {
+SidebarView.prototype = Heritage.extend(WidgetMethods, {
   getItemByProfile: function (profile) {
-    return this.orderedItems.filter((item) => item.attachment.uid === profile.uid)[0];
+    return this.getItemForPredicate(item => item.attachment.uid === profile.uid);
   },
 
   setProfileState: function (profile, state) {
     let item = this.getItemByProfile(profile);
     let label = item.target.querySelector(".profiler-sidebar-item > span");
 
     switch (state) {
       case PROFILE_IDLE:
@@ -397,17 +397,17 @@ ProfilerPanel.prototype = {
     let promise = !target.isRemote ? target.makeRemote() : Promise.resolve(target);
 
     return promise
       .then((target) => {
         let deferred = Promise.defer();
 
         this.controller = new ProfilerController(this.target);
         this.sidebar = new SidebarView(this.document.querySelector("#profiles-list"));
-        this.sidebar.node.addEventListener("select", (ev) => {
+        this.sidebar.widget.addEventListener("select", (ev) => {
           if (!ev.detail)
             return;
 
           let profile = this.profiles.get(ev.detail.attachment.uid);
           this.activeProfile = profile;
 
           if (profile.isReady) {
             profile.flushMessages();
@@ -484,17 +484,17 @@ ProfilerPanel.prototype = {
     box.id = "profile-" + uid;
     let h3 = this.document.createElement("h3");
     h3.textContent = name;
     let span = this.document.createElement("span");
     span.textContent = L10N.getStr("profiler.stateIdle");
     box.appendChild(h3);
     box.appendChild(span);
 
-    this.sidebar.push(box, { attachment: { uid: uid, name: name, state: PROFILE_IDLE } });
+    this.sidebar.push([box], { attachment: { uid: uid, name: name, state: PROFILE_IDLE } });
 
     let profile = new ProfileUI(uid, name, this);
     this.profiles.set(uid, profile);
 
     this.emit("profileCreated", uid);
     return profile;
   },
 
--- a/browser/devtools/scratchpad/scratchpad.js
+++ b/browser/devtools/scratchpad/scratchpad.js
@@ -268,17 +268,17 @@ var Scratchpad = {
 
 
   /**
    * Sidebar that contains the VariablesView for object inspection.
    */
   get sidebar()
   {
     if (!this._sidebar) {
-      this._sidebar = new ScratchpadSidebar();
+      this._sidebar = new ScratchpadSidebar(this);
     }
     return this._sidebar;
   },
 
   /**
    * Get the Cu.Sandbox object for the active tab content window object. Note
    * that the returned object is cached for later reuse. The cached object is
    * kept only for the current location in the current tab of the current
@@ -470,17 +470,17 @@ var Scratchpad = {
         this.writeAsComment(aResult);
         resolve();
       }
       else {
         this.deselect();
         this.sidebar.open(aString, aResult).then(resolve, reject);
       }
     }, reject);
-    
+
     return deferred.promise;
   },
 
   /**
    * Reload the current page and execute the entire editor content when
    * the page finishes loading. Note that this operation should be available
    * only in the content context.
    *
@@ -877,17 +877,17 @@ var Scratchpad = {
                               GetStringFromName("clearRecentMenuItems.label"));
       clearItems.setAttribute("command", "sp-cmd-clearRecentFiles");
       recentFilesPopup.appendChild(clearItems);
     }
   },
 
   /**
    * Clear a range of files from the list.
-   * 
+   *
    * @param integer aIndex
    *        Index of file in menu to remove.
    * @param integer aLength
    *        Number of files from the index 'aIndex' to remove.
    */
   clearFiles: function SP_clearFile(aIndex, aLength)
   {
     let filePaths = this.getRecentFiles();
@@ -1476,36 +1476,31 @@ var Scratchpad = {
   },
 };
 
 
 /**
  * Encapsulates management of the sidebar containing the VariablesView for
  * object inspection.
  */
-function ScratchpadSidebar()
+function ScratchpadSidebar(aScratchpad)
 {
   let ToolSidebar = devtools.require("devtools/framework/sidebar").ToolSidebar;
   let tabbox = document.querySelector("#scratchpad-sidebar");
   this._sidebar = new ToolSidebar(tabbox, this);
-  this._splitter = document.querySelector(".devtools-side-splitter");
+  this._scratchpad = aScratchpad;
 }
 
 ScratchpadSidebar.prototype = {
   /*
    * The ToolSidebar for this sidebar.
    */
   _sidebar: null,
 
   /*
-   * The splitter element between the sidebar and the editor.
-   */
-  _splitter: null,
-
-  /*
    * The VariablesView for this sidebar.
    */
   variablesView: null,
 
   /*
    * Whether the sidebar is currently shown.
    */
   visible: false,
@@ -1526,17 +1521,21 @@ ScratchpadSidebar.prototype = {
     this.show();
 
     let deferred = Promise.defer();
 
     let onTabReady = () => {
       if (!this.variablesView) {
         let window = this._sidebar.getWindowForTab("variablesview");
         let container = window.document.querySelector("#variables");
-        this.variablesView = new VariablesView(container);
+        this.variablesView = new VariablesView(container, {
+          searchEnabled: true,
+          searchPlaceholder: this._scratchpad.strings
+                             .GetStringFromName("propertiesFilterPlaceholder")
+        });
       }
       this._update(aObject).then(() => deferred.resolve());
     };
 
     if (this._sidebar.getCurrentTabID() == "variablesview") {
       onTabReady();
     }
     else {
@@ -1550,29 +1549,27 @@ ScratchpadSidebar.prototype = {
   /**
    * Show the sidebar.
    */
   show: function SS_show()
   {
     if (!this.visible) {
       this.visible = true;
       this._sidebar.show();
-      this._splitter.setAttribute("state", "open");
     }
   },
 
   /**
    * Hide the sidebar.
    */
   hide: function SS_hide()
   {
     if (this.visible) {
       this.visible = false;
       this._sidebar.hide();
-      this._splitter.setAttribute("state", "collapsed");
     }
   },
 
   /**
    * Update the object currently inspected by the sidebar.
    *
    * @param object aObject
    *        The object to inspect in the sidebar.
--- a/browser/devtools/scratchpad/scratchpad.xul
+++ b/browser/devtools/scratchpad/scratchpad.xul
@@ -283,19 +283,19 @@
               accesskey="&resetContext2.accesskey;"
               command="sp-cmd-resetContext"/>
   </menupopup>
 </popupset>
 
 <notificationbox id="scratchpad-notificationbox" flex="1">
   <hbox flex="1">
     <vbox id="scratchpad-editor" flex="1"/>
-    <splitter class="devtools-side-splitter"
-              collapse="after"
-              state="collapsed"/>
-    <tabbox id="scratchpad-sidebar" class="devtools-sidebar-tabs" width="300">
+    <splitter class="devtools-side-splitter"/>
+    <tabbox id="scratchpad-sidebar" class="devtools-sidebar-tabs"
+                                    width="300"
+                                    hidden="true">
       <tabs/>
       <tabpanels flex="1"/>
     </tabbox>
   </hbox>
 </notificationbox>
 
 </window>
--- a/browser/devtools/scratchpad/test/browser_scratchpad_inspect.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_inspect.js
@@ -36,11 +36,20 @@ function runTests()
             break outer;
           }
         }
       }
     }
 
     ok(found, "found the property");
 
-    finish();
+    let tabbox = sidebar._sidebar._tabbox;
+    is(tabbox.width, 300, "Scratchpad sidebar width is correct");
+    ok(!tabbox.hasAttribute("hidden"), "Scratchpad sidebar visible");
+    sidebar.hide();
+    ok(tabbox.hasAttribute("hidden"), "Scratchpad sidebar hidden");
+    sp.inspect().then(function() {
+      is(tabbox.width, 300, "Scratchpad sidebar width is still correct");
+      ok(!tabbox.hasAttribute("hidden"), "Scratchpad sidebar visible again");
+      finish();
+    });
   });
-}
\ No newline at end of file
+}
--- a/browser/devtools/shared/widgets/BreadcrumbsWidget.jsm
+++ b/browser/devtools/shared/widgets/BreadcrumbsWidget.jsm
@@ -12,30 +12,17 @@ const ENSURE_SELECTION_VISIBLE_DELAY = 5
 
 Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
 Cu.import("resource:///modules/devtools/shared/event-emitter.js");
 
 this.EXPORTED_SYMBOLS = ["BreadcrumbsWidget"];
 
 /**
  * A breadcrumb-like list of items.
- *
- * You can use this widget alone, but it works great with a MenuContainer!
- * In that case, you should never need to access the methods in the
- * BreadcrumbsWidget directly, use the wrapper MenuContainer instance instead.
- *
- * @see ViewHelpers.jsm
- *
- * function MyView() {
- *   this.node = new BreadcrumbsWidget(document.querySelector(".my-node"));
- * }
- * ViewHelpers.create({ constructor: MyView, proto: MenuContainer.prototype }, {
- *   myMethod: function() {},
- *   ...
- * });
+ * This widget should be used in tandem with the WidgetMethods in ViewHelpers.jsm
  *
  * @param nsIDOMNode aNode
  *        The element associated with the widget.
  */
 this.BreadcrumbsWidget = function BreadcrumbsWidget(aNode) {
   this.document = aNode.ownerDocument;
   this.window = this.document.defaultView;
   this._parent = aNode;
--- a/browser/devtools/shared/widgets/SideMenuWidget.jsm
+++ b/browser/devtools/shared/widgets/SideMenuWidget.jsm
@@ -16,30 +16,17 @@ Cu.import("resource:///modules/devtools/
 
 XPCOMUtils.defineLazyModuleGetter(this, "NetworkHelper",
   "resource://gre/modules/devtools/NetworkHelper.jsm");
 
 this.EXPORTED_SYMBOLS = ["SideMenuWidget"];
 
 /**
  * A simple side menu, with the ability of grouping menu items.
- *
- * You can use this widget alone, but it works great with a MenuContainer!
- * In that case, you should never need to access the methods in the
- * SideMenuWidget directly, use the wrapper MenuContainer instance instead.
- *
- * @see ViewHelpers.jsm
- *
- * function MyView() {
- *   this.node = new SideMenuWidget(document.querySelector(".my-node"));
- * }
- * ViewHelpers.create({ constructor: MyView, proto: MenuContainer.prototype }, {
- *   myMethod: function() {},
- *   ...
- * });
+ * This widget should be used in tandem with the WidgetMethods in ViewHelpers.jsm
  *
  * @param nsIDOMNode aNode
  *        The element associated with the widget.
  * @param boolean aShowArrows
  *        Specifies if items in this container should display horizontal arrows.
  */
 this.SideMenuWidget = function SideMenuWidget(aNode, aShowArrows = true) {
   this.document = aNode.ownerDocument;
--- a/browser/devtools/shared/widgets/VariablesView.jsm
+++ b/browser/devtools/shared/widgets/VariablesView.jsm
@@ -48,17 +48,17 @@ const STR = Services.strings.createBundl
  *
  * @param nsIDOMNode aParentNode
  *        The parent node to hold this view.
  * @param object aFlags [optional]
  *        An object contaning initialization options for this view.
  *        e.g. { lazyEmpty: true, searchEnabled: true ... }
  */
 this.VariablesView = function VariablesView(aParentNode, aFlags = {}) {
-  this._store = [];
+  this._store = []; // Can't use a Map because Scope names needn't be unique.
   this._itemsByElement = new WeakMap();
   this._prevHierarchy = new Map();
   this._currHierarchy = new Map();
 
   this._parent = aParentNode;
   this._parent.classList.add("variables-view-container");
   this._appendEmptyNotice();
 
@@ -79,23 +79,23 @@ this.VariablesView = function VariablesV
 
   EventEmitter.decorate(this);
 };
 
 VariablesView.prototype = {
   /**
    * Helper setter for populating this container with a raw object.
    *
-   * @param object aData
+   * @param object aObject
    *        The raw object to display. You can only provide this object
    *        if you want the variables view to work in sync mode.
    */
   set rawObject(aObject) {
     this.empty();
-    this.addScope().addItem().populate(aObject);
+    this.addScope().addItem().populate(aObject, { sorted: true });
   },
 
   /**
    * Adds a scope to contain any inspected variables.
    *
    * @param string aName
    *        The scope's name (e.g. "Local", "Global" etc.).
    * @return Scope
@@ -241,16 +241,26 @@ VariablesView.prototype = {
 
   /**
    * Specifies if after an eval or switch operation, the variable or property
    * which has been edited should be disabled.
    */
   preventDisableOnChage: false,
 
   /**
+   * Specifies if, whenever a variable or property descriptor is available,
+   * configurable, enumerable, writable, frozen, sealed and extensible
+   * attributes should not affect presentation.
+   *
+   * This flag is applied recursively onto each scope in this view and
+   * affects only the child nodes when they're created.
+   */
+  preventDescriptorModifiers: false,
+
+  /**
    * The tooltip text shown on a variable or property's value if an |eval|
    * function is provided, in order to change the variable or property's value.
    *
    * This flag is applied recursively onto each scope in this view and
    * affects only the child nodes when they're created.
    */
   editableValueTooltip: STR.GetStringFromName("variablesEditableValueTooltip"),
 
@@ -278,25 +288,16 @@ VariablesView.prototype = {
    * |delete| function is provided, in order to delete the variable or property.
    *
    * This flag is applied recursively onto each scope in this view and
    * affects only the child nodes when they're created.
    */
   deleteButtonTooltip: STR.GetStringFromName("variablesCloseButtonTooltip"),
 
   /**
-   * Specifies if the configurable, enumerable or writable tooltip should be
-   * shown whenever a variable or property descriptor is available.
-   *
-   * This flag is applied recursively onto each scope in this view and
-   * affects only the child nodes when they're created.
-   */
-  descriptorTooltip: true,
-
-  /**
    * Specifies the context menu attribute set on variables and properties.
    *
    * This flag is applied recursively onto each scope in this view and
    * affects only the child nodes when they're created.
    */
   contextMenuId: "",
 
   /**
@@ -1081,34 +1082,38 @@ VariablesView.getterOrSetterDeleteCallba
  */
 function Scope(aView, aName, aFlags = {}) {
   this.ownerView = aView;
 
   this._onClick = this._onClick.bind(this);
   this._openEnum = this._openEnum.bind(this);
   this._openNonEnum = this._openNonEnum.bind(this);
   this._batchAppend = this._batchAppend.bind(this);
-  this._batchItems = [];
 
   // Inherit properties and flags from the parent view. You can override
   // each of these directly onto any scope, variable or property instance.
   this.eval = aView.eval;
   this.switch = aView.switch;
   this.delete = aView.delete;
   this.editableValueTooltip = aView.editableValueTooltip;
   this.editableNameTooltip = aView.editableNameTooltip;
   this.editButtonTooltip = aView.editButtonTooltip;
   this.deleteButtonTooltip = aView.deleteButtonTooltip;
-  this.descriptorTooltip = aView.descriptorTooltip;
+  this.preventDescriptorModifiers = aView.preventDescriptorModifiers;
   this.contextMenuId = aView.contextMenuId;
   this.separatorStr = aView.separatorStr;
 
-  this._store = new Map();
-  this._enumItems = [];
-  this._nonEnumItems = [];
+  // Creating maps and arrays thousands of times for variables or properties
+  // with a large number of children fills up a lot of memory. Make sure
+  // these are instantiated only if needed.
+  XPCOMUtils.defineLazyGetter(this, "_store", () => new Map());
+  XPCOMUtils.defineLazyGetter(this, "_enumItems", () => []);
+  XPCOMUtils.defineLazyGetter(this, "_nonEnumItems", () => []);
+  XPCOMUtils.defineLazyGetter(this, "_batchItems", () => []);
+
   this._init(aName.trim(), aFlags);
 }
 
 Scope.prototype = {
   /**
    * Whether this Scope should be prefetched when it is remoted.
    */
   shouldPrefetch: true,
@@ -1986,17 +1991,17 @@ Scope.prototype = {
   ownerView: null,
   eval: null,
   switch: null,
   delete: null,
   editableValueTooltip: "",
   editableNameTooltip: "",
   editButtonTooltip: "",
   deleteButtonTooltip: "",
-  descriptorTooltip: true,
+  preventDescriptorModifiers: false,
   contextMenuId: "",
   separatorStr: "",
 
   _store: null,
   _enumItems: null,
   _nonEnumItems: null,
   _fetched: false,
   _retrieved: false,
@@ -2029,34 +2034,34 @@ Scope.prototype = {
  * @param Scope aScope
  *        The scope to contain this variable.
  * @param string aName
  *        The variable's name.
  * @param object aDescriptor
  *        The variable's descriptor.
  */
 function Variable(aScope, aName, aDescriptor) {
-  this._displayTooltip = this._displayTooltip.bind(this);
+  this._setTooltips = this._setTooltips.bind(this);
   this._activateNameInput = this._activateNameInput.bind(this);
   this._activateValueInput = this._activateValueInput.bind(this);
 
   // Treat safe getter descriptors as descriptors with a value.
   if ("getterValue" in aDescriptor) {
     aDescriptor.value = aDescriptor.getterValue;
     delete aDescriptor.get;
     delete aDescriptor.set;
   }
 
   Scope.call(this, aScope, aName, this._initialDescriptor = aDescriptor);
   this.setGrip(aDescriptor.value);
   this._symbolicName = aName;
   this._absoluteName = aScope.name + "[\"" + aName + "\"]";
 }
 
-ViewHelpers.create({ constructor: Variable, proto: Scope.prototype }, {
+Variable.prototype = Heritage.extend(Scope.prototype, {
   /**
    * Whether this Scope should be prefetched when it is remoted.
    */
   get shouldPrefetch(){
     return this.name == "window" || this.name == "this";
   },
 
   /**
@@ -2260,17 +2265,17 @@ ViewHelpers.create({ constructor: Variab
   _init: function(aName, aDescriptor) {
     this._idString = generateId(this._nameString = aName);
     this._displayScope(aName, "variables-view-variable variable-or-property");
 
     // Don't allow displaying variable information there's no name available.
     if (this._nameString) {
       this._displayVariable();
       this._customizeVariable();
-      this._prepareTooltip();
+      this._prepareTooltips();
       this._setAttributes();
       this._addEventListeners();
     }
 
     this._onInit(this.ownerView._store.size < LAZY_APPEND_BATCH);
   },
 
   /**
@@ -2348,129 +2353,174 @@ ViewHelpers.create({ constructor: Variab
       this.expand();
     }
   },
 
   /**
    * Adds specific nodes for this variable based on custom flags.
    */
   _customizeVariable: function() {
-    if (this.ownerView.eval) {
+    let ownerView = this.ownerView;
+    let descriptor = this._initialDescriptor;
+
+    if (ownerView.eval) {
       if (!this._isUndefined && (this.getter || this.setter)) {
         let editNode = this._editNode = this.document.createElement("toolbarbutton");
         editNode.className = "plain variables-view-edit";
         editNode.addEventListener("mousedown", this._onEdit.bind(this), false);
         this._title.appendChild(editNode);
       }
     }
-    if (this.ownerView.delete) {
-      if (!this._isUndefined || !(this.ownerView.getter && this.ownerView.setter)) {
+    if (ownerView.delete) {
+      if (!this._isUndefined || !(ownerView.getter && ownerView.setter)) {
         let deleteNode = this._deleteNode = this.document.createElement("toolbarbutton");
         deleteNode.className = "plain variables-view-delete";
         deleteNode.addEventListener("click", this._onDelete.bind(this), false);
         this._title.appendChild(deleteNode);
       }
     }
-    if (this.ownerView.contextMenuId) {
-      this._title.setAttribute("context", this.ownerView.contextMenuId);
+    if (ownerView.contextMenuId) {
+      this._title.setAttribute("context", ownerView.contextMenuId);
+    }
+
+    if (ownerView.preventDescriptorModifiers) {
+      return;
+    }
+
+    if (!descriptor.writable && !ownerView.getter && !ownerView.setter) {
+      let nonWritableIcon = this.document.createElement("hbox");
+      nonWritableIcon.className = "variable-or-property-non-writable-icon";
+      this._title.appendChild(nonWritableIcon);
+    }
+    if (descriptor.value && typeof descriptor.value == "object") {
+      if (descriptor.value.frozen) {
+        let frozenLabel = this.document.createElement("label");
+        frozenLabel.className = "plain variable-or-property-frozen-label";
+        frozenLabel.setAttribute("value", "F");
+        this._title.appendChild(frozenLabel);
+      }
+      if (descriptor.value.sealed) {
+        let sealedLabel = this.document.createElement("label");
+        sealedLabel.className = "plain variable-or-property-sealed-label";
+        sealedLabel.setAttribute("value", "S");
+        this._title.appendChild(sealedLabel);
+      }
+      if (!descriptor.value.extensible) {
+        let nonExtensibleLabel = this.document.createElement("label");
+        nonExtensibleLabel.className = "plain variable-or-property-non-extensible-label";
+        nonExtensibleLabel.setAttribute("value", "N");
+        this._title.appendChild(nonExtensibleLabel);
+      }
     }
   },
 
   /**
-   * Prepares a tooltip for this variable.
+   * Prepares all tooltips for this variable.
    */
-  _prepareTooltip: function() {
-    this._target.addEventListener("mouseover", this._displayTooltip, false);
+  _prepareTooltips: function() {
+    this._target.addEventListener("mouseover", this._setTooltips, false);
   },
 
   /**
-   * Creates a tooltip for this variable.
+   * Sets all tooltips for this variable.
    */
-  _displayTooltip: function() {
-    this._target.removeEventListener("mouseover", this._displayTooltip, false);
-
-    if (this.ownerView.descriptorTooltip) {
-      let document = this.document;
-
-      let tooltip = document.createElement("tooltip");
-      tooltip.id = "tooltip-" + this._idString;
-      tooltip.setAttribute("orient", "horizontal");
-
-      let labels = ["configurable", "enumerable", "writable", "native-getter",
-                    "frozen", "sealed", "non-extensible"];
-      for (let label of labels) {
-        let labelElement = document.createElement("label");
-        labelElement.setAttribute("value", label);
-        tooltip.appendChild(labelElement);
-      }
-
-      this._target.appendChild(tooltip);
-      this._target.setAttribute("tooltip", tooltip.id);
+  _setTooltips: function() {
+    this._target.removeEventListener("mouseover", this._setTooltips, false);
+
+    let ownerView = this.ownerView;
+    if (ownerView.preventDescriptorModifiers) {
+      return;
     }
-    if (this.ownerView.eval && !this._isUndefined && (this.getter || this.setter)) {
-      this._editNode.setAttribute("tooltiptext", this.ownerView.editButtonTooltip);
-    }
-    if (this.ownerView.eval) {
-      this._valueLabel.setAttribute("tooltiptext", this.ownerView.editableValueTooltip);
+
+    let tooltip = this.document.createElement("tooltip");
+    tooltip.id = "tooltip-" + this._idString;
+    tooltip.setAttribute("orient", "horizontal");
+
+    let labels = [
+      "configurable", "enumerable", "writable",
+      "frozen", "sealed", "extensible", "WebIDL"];
+
+    for (let label of labels) {
+      let labelElement = this.document.createElement("label");
+      labelElement.setAttribute("value", label);
+      tooltip.appendChild(labelElement);
     }
-    if (this.ownerView.switch) {
-      this._name.setAttribute("tooltiptext", this.ownerView.editableNameTooltip);
+
+    this._target.appendChild(tooltip);
+    this._target.setAttribute("tooltip", tooltip.id);
+
+    if (this._editNode && ownerView.eval) {
+      this._editNode.setAttribute("tooltiptext", ownerView.editButtonTooltip);
     }
-    if (this.ownerView.delete) {
-      this._deleteNode.setAttribute("tooltiptext", this.ownerView.deleteButtonTooltip);
+    if (this._valueLabel && ownerView.eval) {
+      this._valueLabel.setAttribute("tooltiptext", ownerView.editableValueTooltip);
+    }
+    if (this._name && ownerView.switch) {
+      this._name.setAttribute("tooltiptext", ownerView.editableNameTooltip);
+    }
+    if (this._deleteNode && ownerView.delete) {
+      this._deleteNode.setAttribute("tooltiptext", ownerView.deleteButtonTooltip);
     }
   },
 
   /**
    * Sets a variable's configurable, enumerable and writable attributes,
    * and specifies if it's a 'this', '<exception>' or '__proto__' reference.
    */
   _setAttributes: function() {
+    let ownerView = this.ownerView;
+    if (ownerView.preventDescriptorModifiers) {
+      return;
+    }
+
     let descriptor = this._initialDescriptor;
+    let target = this._target;
     let name = this._nameString;
 
-    if (this.ownerView.eval) {
-      this._target.setAttribute("editable", "");
+    if (ownerView.eval) {
+      target.setAttribute("editable", "");
     }
-    if (!descriptor.null) {
-      if (!descriptor.configurable) {
-        this._target.setAttribute("non-configurable", "");
-      }
-      if (!descriptor.enumerable) {
-        this._target.setAttribute("non-enumerable", "");
+
+    if (!descriptor.configurable) {
+      target.setAttribute("non-configurable", "");
+    }
+    if (!descriptor.enumerable) {
+      target.setAttribute("non-enumerable", "");
+    }
+    if (!descriptor.writable && !ownerView.getter && !ownerView.setter) {
+      target.setAttribute("non-writable", "");
+    }
+
+    if (descriptor.value && typeof descriptor.value == "object") {
+      if (descriptor.value.frozen) {
+        target.setAttribute("frozen", "");
       }
-      if (!descriptor.writable && !this.ownerView.getter && !this.ownerView.setter) {
-        this._target.setAttribute("non-writable", "");
+      if (descriptor.value.sealed) {
+        target.setAttribute("sealed", "");
       }
-      if (descriptor.value && typeof descriptor.value == "object") {
-        if (descriptor.value.frozen) {
-          this._target.setAttribute("frozen", "");
-        }
-        if (descriptor.value.sealed) {
-          this._target.setAttribute("sealed", "");
-        }
-        if (!descriptor.value.extensible) {
-          this._target.setAttribute("non-extensible", "");
-        }
+      if (!descriptor.value.extensible) {
+        target.setAttribute("non-extensible", "");
       }
     }
+
     if (descriptor && "getterValue" in descriptor) {
-      this._target.setAttribute("safe-getter", "");
+      target.setAttribute("safe-getter", "");
     }
     if (name == "this") {
-      this._target.setAttribute("self", "");
+      target.setAttribute("self", "");
     }
+
     else if (name == "<exception>") {
-      this._target.setAttribute("exception", "");
+      target.setAttribute("exception", "");
     }
     else if (name == "<return>") {
-      this._target.setAttribute("return", "");
+      target.setAttribute("return", "");
     }
     else if (name == "__proto__") {
-      this._target.setAttribute("proto", "");
+      target.setAttribute("proto", "");
     }
   },
 
   /**
    * Adds the necessary event listeners for this variable.
    */
   _addEventListeners: function() {
     this._name.addEventListener("dblclick", this._activateNameInput, false);
@@ -2771,34 +2821,34 @@ ViewHelpers.create({ constructor: Variab
  *        The property's descriptor.
  */
 function Property(aVar, aName, aDescriptor) {
   Variable.call(this, aVar, aName, aDescriptor);
   this._symbolicName = aVar._symbolicName + "[\"" + aName + "\"]";
   this._absoluteName = aVar._absoluteName + "[\"" + aName + "\"]";
 }
 
-ViewHelpers.create({ constructor: Property, proto: Variable.prototype }, {
+Property.prototype = Heritage.extend(Variable.prototype, {
   /**
    * Initializes this property's id, view and binds event listeners.
    *
    * @param string aName
    *        The property's name.
    * @param object aDescriptor
    *        The property's descriptor.
    */
   _init: function(aName, aDescriptor) {
     this._idString = generateId(this._nameString = aName);
     this._displayScope(aName, "variables-view-property variable-or-property");
 
     // Don't allow displaying property information there's no name available.
     if (this._nameString) {
       this._displayVariable();
       this._customizeVariable();
-      this._prepareTooltip();
+      this._prepareTooltips();
       this._setAttributes();
       this._addEventListeners();
     }
 
     this._onInit(this.ownerView._store.size < LAZY_APPEND_BATCH);
   },
 
   /**
@@ -2932,21 +2982,21 @@ VariablesView.isPrimitive = function(aDe
   let setter = aDescriptor.set;
   if (getter || setter) {
     return false;
   }
 
   // As described in the remote debugger protocol, the value grip
   // must be contained in a 'value' property.
   let grip = aDescriptor.value;
-  if (!grip || typeof grip != "object") {
+  if (typeof grip != "object") {
     return true;
   }
 
-  // For convenience, undefined, null and long strings are considered primitives.
+  // For convenience, undefined, null and long strings are considered types.
   let type = grip.type;
   if (type == "undefined" || type == "null" || type == "longString") {
     return true;
   }
 
   return false;
 };
 
@@ -2963,19 +3013,18 @@ VariablesView.isUndefined = function(aDe
   let setter = aDescriptor.set;
   if (typeof getter == "object" && getter.type == "undefined" &&
       typeof setter == "object" && setter.type == "undefined") {
     return true;
   }
 
   // As described in the remote debugger protocol, the value grip
   // must be contained in a 'value' property.
-  // For convenience, undefined is considered a type.
   let grip = aDescriptor.value;
-  if (grip && grip.type == "undefined") {
+  if (typeof grip == "object" && grip.type == "undefined") {
     return true;
   }
 
   return false;
 };
 
 /**
  * Returns true if the descriptor represents a falsy value.
--- a/browser/devtools/shared/widgets/ViewHelpers.jsm
+++ b/browser/devtools/shared/widgets/ViewHelpers.jsm
@@ -10,44 +10,46 @@ const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 const PANE_APPEARANCE_DELAY = 50;
 const PAGE_SIZE_ITEM_COUNT_RATIO = 5;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
-this.EXPORTED_SYMBOLS = ["ViewHelpers", "MenuItem", "MenuContainer"];
+this.EXPORTED_SYMBOLS = ["Heritage", "ViewHelpers", "WidgetMethods"];
+
+/**
+ * Inheritance helpers from the addon SDK's core/heritage.
+ * Remove these when all devtools are loadered.
+ */
+this.Heritage = {
+  /**
+   * @see extend in sdk/core/heritage.
+   */
+  extend: function(aPrototype, aProperties = {}) {
+    return Object.create(aPrototype, this.getOwnPropertyDescriptors(aProperties));
+  },
+
+  /**
+   * @see getOwnPropertyDescriptors in sdk/core/heritage.
+   */
+  getOwnPropertyDescriptors: function(aObject) {
+    return Object.getOwnPropertyNames(aObject).reduce((aDescriptor, aName) => {
+      aDescriptor[aName] = Object.getOwnPropertyDescriptor(aObject, aName);
+      return aDescriptor;
+    }, {});
+  }
+};
 
 /**
  * Helpers for creating and messaging between UI components.
  */
 this.ViewHelpers = {
   /**
-   * Sugar for prototypal inheritance using Object.create.
-   * Creates a new object with the specified prototype object and properties.
-   *
-   * @param object aObject
-   *        An object containing the following properties:
-   *          - constructor: the function to configure the prototype for
-   *          - proto: the prototype to extend
-   * @param object aProperties
-   *        The properties extending the prototype.
-   */
-  create: function({ constructor, proto }, aProperties = {}) {
-    let descriptors = {
-      constructor: { value: constructor }
-    };
-    for (let name in aProperties) {
-      descriptors[name] = Object.getOwnPropertyDescriptor(aProperties, name);
-    }
-    constructor.prototype = Object.create(proto, descriptors);
-  },
-
-  /**
    * Convenience method, dispatching a custom event.
    *
    * @param nsIDOMNode aTarget
    *        A custom target element to dispatch the event from.
    * @param string aType
    *        The name of the event.
    * @param any aDetail
    *        The data passed when initializing the event.
@@ -64,32 +66,30 @@ this.ViewHelpers = {
 
     let event = document.createEvent("CustomEvent");
     event.initCustomEvent(aType, true, true, aDetail);
     return dispatcher.dispatchEvent(event);
   },
 
   /**
    * Helper delegating some of the DOM attribute methods of a node to a widget.
-   * @see MenuContainer constructor
    *
    * @param object aWidget
    *        The widget to assign the methods to.
    * @param nsIDOMNode aNode
    *        A node to delegate the methods to.
    */
   delegateWidgetAttributeMethods: function(aWidget, aNode) {
     aWidget.getAttribute = aNode.getAttribute.bind(aNode);
     aWidget.setAttribute = aNode.setAttribute.bind(aNode);
     aWidget.removeAttribute = aNode.removeAttribute.bind(aNode);
   },
 
   /**
    * Helper delegating some of the DOM event methods of a node to a widget.
-   * @see MenuContainer constructor
    *
    * @param object aWidget
    *        The widget to assign the methods to.
    * @param nsIDOMNode aNode
    *        A node to delegate the methods to.
    */
   delegateWidgetEventMethods: function(aWidget, aNode) {
     aWidget.addEventListener = aNode.addEventListener.bind(aNode);
@@ -103,16 +103,28 @@ this.ViewHelpers = {
    * @return boolean
    *         True if it looks, walks and quacks like an event emitter.
    */
   isEventEmitter: function(aObject) {
     return aObject && aObject.on && aObject.off && aObject.once && aObject.emit;
   },
 
   /**
+   * Checks if the specified object is an instance of a DOM node.
+   *
+   * @return boolean
+   *         True if it's a node, false otherwise.
+   */
+  isNode: function(aObject) {
+    return aObject instanceof Ci.nsIDOMNode ||
+           aObject instanceof Ci.nsIDOMElement ||
+           aObject instanceof Ci.nsIDOMDocumentFragment;
+  },
+
+  /**
    * Prevents event propagation when navigation keys are pressed.
    *
    * @param Event e
    *        The event to be prevented.
    */
   preventScrolling: function(e) {
     switch (e.keyCode) {
       case e.DOM_VK_UP:
@@ -341,152 +353,117 @@ ViewHelpers.Prefs.prototype = {
     Object.defineProperty(this, aAccessorName, {
       get: () => this._get(aType, [this.root, aPrefName].join(".")),
       set: (aValue) => this._set(aType, [this.root, aPrefName].join("."), aValue)
     });
   }
 };
 
 /**
- * A generic MenuItem is used to describe elements present in a MenuContainer.
+ * A generic Item is used to describe children present in a Widget.
  * The label, value and description properties are necessarily strings.
  * Iterable via "for (let childItem in parentItem) { }".
  *
+ * @param object aOwnerView
+ *        The owner view creating this item.
  * @param any aAttachment
  *        Some attached primitive/object.
  * @param nsIDOMNode | nsIDOMDocumentFragment | array aContents [optional]
  *        A prebuilt node, or an array containing the following properties:
- *        - aLabel: the label displayed in the container
+ *        - aLabel: the label displayed in the widget
  *        - aValue: the actual internal value of the item
  *        - aDescription: an optional description of the item
  */
-this.MenuItem = function MenuItem(aAttachment, aContents = []) {
+function Item(aOwnerView, aAttachment, aContents = []) {
+  this.ownerView = aOwnerView;
   this.attachment = aAttachment;
 
-  // Allow the insertion of prebuilt nodes.
-  if (aContents instanceof Ci.nsIDOMNode ||
-      aContents instanceof Ci.nsIDOMDocumentFragment) {
-    this._prebuiltTarget = aContents;
+  let [aLabel, aValue, aDescription] = aContents;
+  this._label = aLabel + "";
+  this._value = aValue + "";
+  this._description = (aDescription || "") + "";
+
+  // Allow the insertion of prebuilt nodes, otherwise delegate the item view
+  // creation to a widget.
+  if (ViewHelpers.isNode(aLabel)) {
+    this._prebuiltTarget = aLabel;
   }
-  // Delegate the item view creation to a container widget.
-  else {
-    let [aLabel, aValue, aDescription] = aContents;
-    this._label = aLabel + "";
-    this._value = aValue + "";
-    this._description = (aDescription || "") + "";
-  }
+
+  XPCOMUtils.defineLazyGetter(this, "_itemsByElement", () => new Map());
 };
 
-MenuItem.prototype = {
+Item.prototype = {
   get label() this._label,
   get value() this._value,
   get description() this._description,
   get target() this._target,
 
   /**
-   * Immediately appends a child item to this menu item.
+   * Immediately appends a child item to this item.
    *
    * @param nsIDOMNode aElement
    *        An nsIDOMNode representing the child element to append.
    * @param object aOptions [optional]
    *        Additional options or flags supported by this operation:
    *          - attachment: some attached primitive/object for the item
    *          - attributes: a batch of attributes set to the displayed element
-   *          - finalize: function invoked when the child node is removed
-   * @return MenuItem
+   *          - finalize: function invoked when the child item is removed
+   * @return Item
    *         The item associated with the displayed element.
    */
   append: function(aElement, aOptions = {}) {
-    let item = new MenuItem(aOptions.attachment);
+    let item = new Item(this, aOptions.attachment);
 
-    // Handle any additional options before appending the child node.
+    // Entangle the item with the newly inserted child node.
+    this._entangleItem(item, this._target.appendChild(aElement));
+
+    // Handle any additional options after entangling the item.
     if (aOptions.attributes) {
-      this.setAttributes(aOptions.attributes);
+      aOptions.attributes.forEach(e => item._target.setAttribute(e[0], e[1]));
     }
     if (aOptions.finalize) {
       item.finalize = aOptions.finalize;
     }
 
-    // Entangle the item with the newly inserted child node.
-    this._entangleItem(item, this._target.appendChild(aElement));
-
     // Return the item associated with the displayed element.
     return item;
   },
 
   /**
-   * Immediately removes the specified child item from this menu item.
+   * Immediately removes the specified child item from this item.
    *
-   * @param MenuItem aItem
+   * @param Item aItem
    *        The item associated with the element to remove.
    */
   remove: function(aItem) {
     if (!aItem) {
       return;
     }
     this._target.removeChild(aItem._target);
     this._untangleItem(aItem);
   },
 
   /**
-   * Visually marks this menu item as selected.