Bug 707302 - Optimize and refactor the debugger frontend to use constructors instead of attached functions to DOM nodes, r=rcampbell
authorVictor Porof <vporof@mozilla.com>
Fri, 26 Oct 2012 20:10:17 +0300
changeset 111638 3380c067c0c6d7cd886fb4087c992db0a9559958
parent 111637 3e9ca49bd35f7e1101a4ee2f9ce04d98ff341a86
child 111639 926a3d0784bc426bd5ad08b40b092e2fb52eb76f
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
reviewersrcampbell
bugs707302
milestone19.0a1
Bug 707302 - Optimize and refactor the debugger frontend to use constructors instead of attached functions to DOM nodes, r=rcampbell
browser/devtools/commandline/CmdBreak.jsm
browser/devtools/commandline/test/browser_dbg_cmd.js
browser/devtools/commandline/test/browser_dbg_cmd_break.js
browser/devtools/debugger/DebuggerUI.jsm
browser/devtools/debugger/VariablesView.jsm
browser/devtools/debugger/debugger-controller.js
browser/devtools/debugger/debugger-panes.js
browser/devtools/debugger/debugger-toolbar.js
browser/devtools/debugger/debugger-view.js
browser/devtools/debugger/debugger.css
browser/devtools/debugger/debugger.xul
browser/devtools/debugger/test/browser_dbg_bfcache.js
browser/devtools/debugger/test/browser_dbg_breakpoint-new-script.js
browser/devtools/debugger/test/browser_dbg_bug723069_editor-breakpoints.js
browser/devtools/debugger/test/browser_dbg_bug723071_editor-breakpoints-pane.js
browser/devtools/debugger/test/browser_dbg_bug731394_editor-contextmenu.js
browser/devtools/debugger/test/browser_dbg_bug737803_editor_actual_location.js
browser/devtools/debugger/test/browser_dbg_bug786070_hide_nonenums.js
browser/devtools/debugger/test/browser_dbg_createRemote.js
browser/devtools/debugger/test/browser_dbg_displayName.js
browser/devtools/debugger/test/browser_dbg_iframes.js
browser/devtools/debugger/test/browser_dbg_leaktest.js
browser/devtools/debugger/test/browser_dbg_location-changes-blank.js
browser/devtools/debugger/test/browser_dbg_location-changes-new.js
browser/devtools/debugger/test/browser_dbg_location-changes.js
browser/devtools/debugger/test/browser_dbg_multiple-windows.js
browser/devtools/debugger/test/browser_dbg_pane-collapse.js
browser/devtools/debugger/test/browser_dbg_panesize.js
browser/devtools/debugger/test/browser_dbg_pause-exceptions.js
browser/devtools/debugger/test/browser_dbg_pause-resume.js
browser/devtools/debugger/test/browser_dbg_propertyview-01.js
browser/devtools/debugger/test/browser_dbg_propertyview-02.js
browser/devtools/debugger/test/browser_dbg_propertyview-03.js
browser/devtools/debugger/test/browser_dbg_propertyview-04.js
browser/devtools/debugger/test/browser_dbg_propertyview-05.js
browser/devtools/debugger/test/browser_dbg_propertyview-06.js
browser/devtools/debugger/test/browser_dbg_propertyview-07.js
browser/devtools/debugger/test/browser_dbg_propertyview-08.js
browser/devtools/debugger/test/browser_dbg_propertyview-09.js
browser/devtools/debugger/test/browser_dbg_propertyview-10.js
browser/devtools/debugger/test/browser_dbg_propertyview-edit.js
browser/devtools/debugger/test/browser_dbg_reload-same-script.js
browser/devtools/debugger/test/browser_dbg_script-switching.js
browser/devtools/debugger/test/browser_dbg_scripts-searching-01.js
browser/devtools/debugger/test/browser_dbg_scripts-searching-02.js
browser/devtools/debugger/test/browser_dbg_scripts-searching-03.js
browser/devtools/debugger/test/browser_dbg_scripts-searching-04.js
browser/devtools/debugger/test/browser_dbg_scripts-searching-05.js
browser/devtools/debugger/test/browser_dbg_scripts-searching-06.js
browser/devtools/debugger/test/browser_dbg_scripts-searching-07.js
browser/devtools/debugger/test/browser_dbg_scripts-searching-08.js
browser/devtools/debugger/test/browser_dbg_scripts-searching-popup.js
browser/devtools/debugger/test/browser_dbg_scripts-sorting.js
browser/devtools/debugger/test/browser_dbg_select-line.js
browser/devtools/debugger/test/browser_dbg_stack-01.js
browser/devtools/debugger/test/browser_dbg_stack-02.js
browser/devtools/debugger/test/browser_dbg_stack-03.js
browser/devtools/debugger/test/browser_dbg_stack-04.js
browser/devtools/debugger/test/browser_dbg_stack-05.js
browser/devtools/debugger/test/browser_dbg_update-editor-mode.js
browser/devtools/debugger/test/head.js
browser/devtools/jar.mn
browser/locales/en-US/chrome/browser/devtools/debugger.properties
browser/themes/gnomestripe/devtools/debugger.css
browser/themes/pinstripe/devtools/debugger.css
browser/themes/winstripe/devtools/debugger.css
toolkit/devtools/debugger/dbg-client.jsm
--- a/browser/devtools/commandline/CmdBreak.jsm
+++ b/browser/devtools/commandline/CmdBreak.jsm
@@ -75,19 +75,19 @@ gcli.addCommand({
       name: "file",
       type: {
         name: "selection",
         data: function() {
           let win = HUDService.currentContext();
           let dbg = win.DebuggerUI.getDebugger();
           let files = [];
           if (dbg) {
-            let scriptsView = dbg.contentWindow.DebuggerView.Scripts;
-            for each (let script in scriptsView.scriptLocations) {
-              files.push(script);
+            let sourcesView = dbg.contentWindow.DebuggerView.Sources;
+            for (let item in sourcesView) {
+              files.push(item.value);
             }
           }
           return files;
         }
       },
       description: gcli.lookup("breakaddlineFileDesc")
     },
     {
--- a/browser/devtools/commandline/test/browser_dbg_cmd.js
+++ b/browser/devtools/commandline/test/browser_dbg_cmd.js
@@ -12,18 +12,18 @@ function testDbgCmd() {
     typed: "dbg open",
     blankOutput: true
   });
 
   let pane = DebuggerUI.findDebugger();
   ok(pane, "Debugger was opened.");
   let frame = pane._frame;
 
-  frame.addEventListener("Debugger:Connecting", function dbgConnected(aEvent) {
-    frame.removeEventListener("Debugger:Connecting", dbgConnected, true);
+  frame.addEventListener("Debugger:Connected", function dbgConnected(aEvent) {
+    frame.removeEventListener("Debugger:Connected", dbgConnected, true);
 
     // Wait for the initial resume...
     aEvent.target.ownerDocument.defaultView.gClient
         .addOneTimeListener("resumed", function() {
 
       info("Starting tests.");
 
       let contentDoc = content.window.document;
--- a/browser/devtools/commandline/test/browser_dbg_cmd_break.js
+++ b/browser/devtools/commandline/test/browser_dbg_cmd_break.js
@@ -33,17 +33,17 @@ function testBreakCommands() {
     hints:                ' <file> <line>',
     markup: 'VVVVVVVVVVVVVV',
     status: 'ERROR'
   });
 
   let pane = DebuggerUI.toggleDebugger();
 
   var dbgConnected = DeveloperToolbarTest.checkCalled(function() {
-    pane._frame.removeEventListener("Debugger:Connecting", dbgConnected, true);
+    pane._frame.removeEventListener("Debugger:Connected", dbgConnected, true);
 
     // Wait for the initial resume.
     let client = pane.contentWindow.gClient;
 
     var resumed = DeveloperToolbarTest.checkCalled(function() {
 
       var framesAdded = DeveloperToolbarTest.checkCalled(function() {
         helpers.setInput('break add line ' + TEST_URI + ' ' + content.wrappedJSObject.line0);
@@ -111,10 +111,10 @@ function testBreakCommands() {
 
       // Trigger newScript notifications using eval.
       content.wrappedJSObject.firstCall();
     });
 
     client.addOneTimeListener("resumed", resumed);
   });
 
-  pane._frame.addEventListener("Debugger:Connecting", dbgConnected, true);
+  pane._frame.addEventListener("Debugger:Connected", dbgConnected, true);
 }
--- a/browser/devtools/debugger/DebuggerUI.jsm
+++ b/browser/devtools/debugger/DebuggerUI.jsm
@@ -9,39 +9,44 @@ const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 const DBG_XUL = "chrome://browser/content/debugger.xul";
 const DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties";
 const CHROME_DEBUGGER_PROFILE_NAME = "_chrome-debugger-profile";
 const TAB_SWITCH_NOTIFICATION = "debugger-tab-switch";
 
-Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this,
+  "DebuggerServer", "resource://gre/modules/devtools/dbg-server.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this,
+  "Services", "resource:///modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this,
+  "FileUtils", "resource:///modules/FileUtils.jsm");
+
 let EXPORTED_SYMBOLS = ["DebuggerUI"];
 
 /**
  * Provides a simple mechanism of managing debugger instances.
  *
  * @param nsIDOMWindow aWindow
  *        The chrome window for which the DebuggerUI instance is created.
  */
 function DebuggerUI(aWindow) {
   this.chromeWindow = aWindow;
   this.listenToTabs();
 }
 
 DebuggerUI.prototype = {
-
   /**
    * Update the status of tool's menuitems and buttons when
-   * the user switch tabs.
+   * the user switches tabs.
    */
   listenToTabs: function DUI_listenToTabs() {
     let win = this.chromeWindow;
     let tabs = win.gBrowser.tabContainer;
 
     let bound_refreshCommand = this.refreshCommand.bind(this);
     tabs.addEventListener("TabSelect", bound_refreshCommand, true);
 
@@ -64,17 +69,19 @@ DebuggerUI.prototype = {
       command.setAttribute("checked", "true");
     } else {
       command.setAttribute("checked", "false");
     }
   },
 
   /**
    * Starts a debugger for the current tab, or stops it if already started.
-   * @return DebuggerPane if the debugger is started, null if it's stopped.
+   *
+   * @return DebuggerPane | null
+   *         The script debugger instance if it's started, null if stopped.
    */
   toggleDebugger: function DUI_toggleDebugger() {
     let scriptDebugger = this.findDebugger();
     let selectedTab = this.chromeWindow.gBrowser.selectedTab;
 
     if (scriptDebugger) {
       if (scriptDebugger.ownerTab !== selectedTab) {
         this.showTabSwitchNotification();
@@ -83,44 +90,48 @@ DebuggerUI.prototype = {
       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 if the debugger is started, null if stopped.
+   *
+   * @return RemoteDebuggerWindow | null
+   *         The remote debugger instance if it's started, null if stopped.
    */
   toggleRemoteDebugger: function DUI_toggleRemoteDebugger() {
     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 if the debugger is started, null if stopped.
+   *
+   * @return ChromeDebuggerProcess | null
+   *         The chrome debugger instance if it's started, null if stopped.
    */
   toggleChromeDebugger: function DUI_toggleChromeDebugger(aOnClose, aOnRun) {
     let chromeDebugger = this.getChromeDebugger();
 
     if (chromeDebugger) {
       chromeDebugger.close();
       return null;
     }
     return new ChromeDebuggerProcess(this, aOnClose, aOnRun);
   },
 
   /**
-   * Gets the script debugger in any open window.
+   * Gets the current script debugger from any open window.
    *
    * @return DebuggerPane | null
    *         The script debugger instance if it exists, null otherwise.
    */
   findDebugger: function DUI_findDebugger() {
     let enumerator = Services.wm.getEnumerator("navigator:browser");
     while (enumerator.hasMoreElements()) {
       let chromeWindow = enumerator.getNext().QueryInterface(Ci.nsIDOMWindow);
@@ -161,63 +172,61 @@ DebuggerUI.prototype = {
   getChromeDebugger: function DUI_getChromeDebugger() {
     return '_chromeDebugger' in this ? this._chromeDebugger : null;
   },
 
   /**
    * Get the preferences associated with the debugger frontend.
    * @return object
    */
-  get preferences() {
-    return DebuggerPreferences;
-  },
+  get preferences() Prefs,
 
   /**
    * Currently, there can only be one debugger per tab.
    * Show an asynchronous notification which asks the user to switch the
    * script debugger to the current tab if it's already open in another one.
    */
-  showTabSwitchNotification: function DUI_showTabSwitchNotification()
-  {
+  showTabSwitchNotification: function DUI_showTabSwitchNotification() {
     let gBrowser = this.chromeWindow.gBrowser;
     let selectedBrowser = gBrowser.selectedBrowser;
 
     let nbox = gBrowser.getNotificationBox(selectedBrowser);
     let notification = nbox.getNotificationWithValue(TAB_SWITCH_NOTIFICATION);
     if (notification) {
       nbox.removeNotification(notification);
       return;
     }
+    let self = this;
 
     let buttons = [{
       id: "debugger.confirmTabSwitch.buttonSwitch",
       label: L10N.getStr("confirmTabSwitch.buttonSwitch"),
       accessKey: L10N.getStr("confirmTabSwitch.buttonSwitch.accessKey"),
       callback: function DUI_notificationButtonSwitch() {
-        let scriptDebugger = this.findDebugger();
+        let scriptDebugger = self.findDebugger();
         let targetWindow = scriptDebugger.globalUI.chromeWindow;
         targetWindow.gBrowser.selectedTab = scriptDebugger.ownerTab;
         targetWindow.focus();
-      }.bind(this)
+      }
     }, {
       id: "debugger.confirmTabSwitch.buttonOpen",
       label: L10N.getStr("confirmTabSwitch.buttonOpen"),
       accessKey: L10N.getStr("confirmTabSwitch.buttonOpen.accessKey"),
       callback: function DUI_notificationButtonOpen() {
-        let scriptDebugger = this.findDebugger();
+        let scriptDebugger = self.findDebugger();
         let targetWindow = scriptDebugger.globalUI.chromeWindow;
         scriptDebugger.close();
-        let self = this;
-        targetWindow.addEventListener("Debugger:Shutdown", function toggle() {
-          targetWindow.removeEventListener("Debugger:Shutdown", toggle, false);
+
+        targetWindow.addEventListener("Debugger:Shutdown", function onShutdown() {
+          targetWindow.removeEventListener("Debugger:Shutdown", onShutdown, false);
           Services.tm.currentThread.dispatch({ run: function() {
             self.toggleDebugger();
           }}, 0);
         }, false);
-      }.bind(this)
+      }
     }];
 
     let message = L10N.getStr("confirmTabSwitch.message");
     let imageURL = "chrome://browser/skin/Info.png";
 
     notification = nbox.appendNotification(
       message, TAB_SWITCH_NOTIFICATION,
       imageURL, nbox.PRIORITY_WARNING_HIGH, buttons, null);
@@ -236,22 +245,22 @@ DebuggerUI.prototype = {
  * @param XULElement aTab
  *        The tab in which to create the debugger.
  */
 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 DP__initServer() {
     if (!DebuggerServer.initialized) {
       // Always allow connections from nsIPipe transports.
       DebuggerServer.init(function() true);
       DebuggerServer.addBrowserActors();
@@ -266,35 +275,34 @@ DebuggerPane.prototype = {
 
     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._frame.height = DebuggerPreferences.height;
+    this._frame.height = Prefs.height;
 
     this._nbox = gBrowser.getNotificationBox(this._tab.linkedBrowser);
     this._nbox.appendChild(this._splitter);
     this._nbox.appendChild(this._frame);
 
-    this.close = this.close.bind(this);
     let self = this;
 
     this._frame.addEventListener("Debugger:Loaded", function dbgLoaded() {
       self._frame.removeEventListener("Debugger:Loaded", dbgLoaded, true);
-      self._frame.addEventListener("Debugger:Close", self.close, true);
-      self._frame.addEventListener("unload", self.close, 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.
@@ -303,151 +311,154 @@ DebuggerPane.prototype = {
    *        Clients can pass a close callback to be notified when
    *        the panel successfully closes.
    */
   close: function DP_close(aCloseCallback) {
     if (!this.globalUI) {
       return;
     }
     delete this.globalUI._scriptDebugger;
-    this._win = null;
-    this._tab = null;
-
-    DebuggerPreferences.height = this._frame.height;
-    this._frame.removeEventListener("Debugger:Close", this.close, true);
-    this._frame.removeEventListener("unload", this.close, true);
 
     // This method is also used as an event handler, so only
     // use aCloseCallback if it's a function.
-    if (typeof(aCloseCallback) == "function") {
+    if (typeof aCloseCallback == "function") {
       let frame = this._frame;
       frame.addEventListener("unload", function onUnload() {
         frame.removeEventListener("unload", onUnload, true);
         aCloseCallback();
       }, true)
     }
 
+    Prefs.height = this._frame.height;
+    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 if a debugger window exists, null otherwise
+   * @return nsIDOMWindow
    */
   get contentWindow() {
     return this._frame ? this._frame.contentWindow : null;
-  },
-
-  /**
-   * Shortcut for accessing the list of breakpoints in the debugger.
-   * @return object if a debugger window exists, null otherwise
-   */
-  get breakpoints() {
-    let contentWindow = this.contentWindow;
-    if (contentWindow) {
-      return contentWindow.DebuggerController.Breakpoints.store;
-    }
-    return null;
   }
 };
 
 /**
  * Creates a window that will host a remote debugger.
  *
  * @param DebuggerUI aDebuggerUI
  *        The parent instance creating the new debugger.
  */
 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 DP__create() {
     this.globalUI._remoteDebugger = this;
 
     this._dbgwin = this.globalUI.chromeWindow.open(DBG_XUL,
       L10N.getStr("remoteDebuggerWindowTitle"),
-      "width=" + DebuggerPreferences.remoteWinWidth + "," +
-      "height=" + DebuggerPreferences.remoteWinHeight + "," +
+      "width=" + Prefs.remoteWinWidth + "," +
+      "height=" + Prefs.remoteWinHeight + "," +
       "chrome,dependent,resizable,centerscreen");
 
-    this._dbgwin._remoteFlag = true;
-
-    this.close = this.close.bind(this);
     let self = this;
 
     this._dbgwin.addEventListener("Debugger:Loaded", function dbgLoaded() {
       self._dbgwin.removeEventListener("Debugger:Loaded", dbgLoaded, true);
-      self._dbgwin.addEventListener("Debugger:Close", self.close, true);
-      self._dbgwin.addEventListener("unload", self.close, 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 DP_close() {
     if (!this.globalUI) {
       return;
     }
     delete this.globalUI._remoteDebugger;
-    this.globalUI = null;
-    this._win = null;
 
     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 if a debugger window exists, null otherwise.
+   * @return nsIDOMWindow.
    */
   get contentWindow() {
     return this._dbgwin;
-  },
-
-  /**
-   * Shortcut for accessing the list of breakpoints in the remote debugger.
-   * @return object if a debugger window exists, null otherwise.
-   */
-  get breakpoints() {
-    let contentWindow = this.contentWindow;
-    if (contentWindow) {
-      return contentWindow.DebuggerController.Breakpoints.store;
-    }
-    return null;
   }
 };
 
 /**
  * Creates a process that will hold a chrome debugger.
  *
  * @param DebuggerUI aDebuggerUI
  *        The parent instance creating the new debugger.
@@ -463,26 +474,25 @@ function ChromeDebuggerProcess(aDebugger
   this._runCallback = aOnRun;
 
   this._initServer();
   this._initProfile();
   this._create();
 }
 
 ChromeDebuggerProcess.prototype = {
-
   /**
    * Initializes the debugger server.
    */
   _initServer: function RDP__initServer() {
     if (!DebuggerServer.initialized) {
       DebuggerServer.init();
       DebuggerServer.addBrowserActors();
     }
-    DebuggerServer.openListener(DebuggerPreferences.remotePort);
+    DebuggerServer.openListener(Prefs.remotePort);
   },
 
   /**
    * Initializes a profile for the remote debugger process.
    */
   _initProfile: function RDP__initProfile() {
     let profileService = Cc["@mozilla.org/toolkit/profile-service;1"]
       .createInstance(Ci.nsIToolkitProfileService);
@@ -510,78 +520,77 @@ ChromeDebuggerProcess.prototype = {
                                       : "firefox-bin"]);
 
     let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
     process.init(file);
 
     let args = [
       "-no-remote", "-P", this._dbgProfile.name,
       "-chrome", DBG_XUL,
-      "-width", DebuggerPreferences.remoteWinWidth,
-      "-height", DebuggerPreferences.remoteWinHeight];
+      "-width", Prefs.remoteWinWidth,
+      "-height", Prefs.remoteWinHeight];
 
     process.runwAsync(args, args.length, { observe: this.close.bind(this) });
     this._dbgProcess = process;
 
-    if (typeof this._runCallback === "function") {
+    if (typeof this._runCallback == "function") {
       this._runCallback.call({}, this);
     }
   },
 
   /**
    * Closes the remote debugger, removing the profile and killing the process.
    */
   close: function RDP_close() {
     if (!this.globalUI) {
       return;
     }
     delete this.globalUI._chromeDebugger;
-    this.globalUI = null;
-    this._win = null;
 
     if (this._dbgProcess.isRunning) {
       this._dbgProcess.kill();
     }
     if (this._dbgProfile) {
       this._dbgProfile.remove(false);
     }
-    if (typeof this._closeCallback === "function") {
+    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 L10N_getStr(aName) {
     return this.stringBundle.GetStringFromName(aName);
   }
 };
 
 XPCOMUtils.defineLazyGetter(L10N, "stringBundle", function() {
   return Services.strings.createBundle(DBG_STRINGS_URI);
 });
 
 /**
- * Various debugger preferences.
+ * Shortcuts for accessing various debugger preferences.
  */
-let DebuggerPreferences = {
-
+let Prefs = {
   /**
    * Gets the preferred height of the debugger pane.
    * @return number
    */
   get height() {
     if (this._height === undefined) {
       this._height = Services.prefs.getIntPref("devtools.debugger.ui.height");
     }
@@ -597,35 +606,35 @@ let DebuggerPreferences = {
     this._height = value;
   }
 };
 
 /**
  * Gets the preferred width of the remote debugger window.
  * @return number
  */
-XPCOMUtils.defineLazyGetter(DebuggerPreferences, "remoteWinWidth", function() {
+XPCOMUtils.defineLazyGetter(Prefs, "remoteWinWidth", function() {
   return Services.prefs.getIntPref("devtools.debugger.ui.remote-win.width");
 });
 
 /**
  * Gets the preferred height of the remote debugger window.
  * @return number
  */
-XPCOMUtils.defineLazyGetter(DebuggerPreferences, "remoteWinHeight", function() {
+XPCOMUtils.defineLazyGetter(Prefs, "remoteWinHeight", function() {
   return Services.prefs.getIntPref("devtools.debugger.ui.remote-win.height");
 });
 
 /**
  * Gets the preferred default remote debugging host.
  * @return string
  */
-XPCOMUtils.defineLazyGetter(DebuggerPreferences, "remoteHost", function() {
+XPCOMUtils.defineLazyGetter(Prefs, "remoteHost", function() {
   return Services.prefs.getCharPref("devtools.debugger.remote-host");
 });
 
 /**
  * Gets the preferred default remote debugging port.
  * @return number
  */
-XPCOMUtils.defineLazyGetter(DebuggerPreferences, "remotePort", function() {
+XPCOMUtils.defineLazyGetter(Prefs, "remotePort", function() {
   return Services.prefs.getIntPref("devtools.debugger.remote-port");
 });
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/VariablesView.jsm
@@ -0,0 +1,1131 @@
+/* -*- 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";
+
+Components.utils.import('resource://gre/modules/Services.jsm');
+
+let EXPORTED_SYMBOLS = ["VariablesView", "create"];
+
+/**
+ * A tree view for inspecting scopes, objects and properties.
+ * Iterable via "for (let [id, scope] in instance) { }".
+ * Requires the devtools common.css and debugger.css skin stylesheets.
+ *
+ * To allow replacing variable or property values in this view, provide an
+ * "eval" function property.
+ *
+ * @param nsIDOMNode aParentNode
+ *        The parent node to hold this view.
+ */
+function VariablesView(aParentNode) {
+  this._store = new Map();
+  this._prevHierarchy = new Map();
+  this._currHierarchy = new Map();
+  this._parent = aParentNode;
+  this._appendEmptyNotice();
+
+  // Create an internal list container.
+  this._list = this.document.createElement("vbox");
+  this._parent.appendChild(this._list);
+}
+
+VariablesView.prototype = {
+  /**
+   * Adds a scope to contain any inspected variables.
+   *
+   * @param string aName
+   *        The scope's name (e.g. "Local", "Global" etc.).
+   * @return Scope
+   *         The newly created Scope instance.
+   */
+  addScope: function VV_addScope(aName) {
+    this._removeEmptyNotice();
+
+    let scope = new Scope(this, aName);
+    this._store.set(scope.id, scope);
+    return scope;
+  },
+
+  /**
+   * Removes all items from this container.
+   */
+  empty: function VV_empty() {
+    let list = this._list;
+    let firstChild;
+
+    while (firstChild = list.firstChild) {
+      list.removeChild(firstChild);
+    }
+
+    this._store = new Map();
+    this._appendEmptyNotice();
+  },
+
+  /**
+   * Specifies if enumerable properties and variables should be displayed.
+   * @param boolean aFlag
+   */
+  set enumVisible(aFlag) {
+    this._enumVisible = aFlag;
+
+    for (let [_, scope] in this) {
+      scope._nonEnumVisible = aFlag;
+    }
+  },
+
+  /**
+   * Specifies if non-enumerable properties and variables should be displayed.
+   * @param boolean aFlag
+   */
+  set nonEnumVisible(aFlag) {
+    this._nonEnumVisible = aFlag;
+
+    for (let [_, scope] in this) {
+      scope._nonEnumVisible = aFlag;
+    }
+  },
+
+  /**
+   * Sets the text displayed in this container when there are no available items.
+   * @param string aValue
+   */
+  set emptyText(aValue) {
+    if (this._emptyTextNode) {
+      this._emptyTextNode.setAttribute("value", aValue);
+    }
+    this._emptyTextValue = aValue;
+  },
+
+  /**
+   * Creates and appends a label signaling that this container is empty.
+   */
+  _appendEmptyNotice: function VV__appendEmptyNotice() {
+    if (this._emptyTextNode) {
+      return;
+    }
+
+    let label = this.document.createElement("label");
+    label.className = "empty list-item";
+    label.setAttribute("value", this._emptyTextValue);
+
+    this._parent.appendChild(label);
+    this._emptyTextNode = label;
+  },
+
+  /**
+   * Removes the label signaling that this container is empty.
+   */
+  _removeEmptyNotice: function VV__removeEmptyNotice() {
+    if (!this._emptyTextNode) {
+      return;
+    }
+
+    this._parent.removeChild(this._emptyTextNode);
+    this._emptyTextNode = null;
+  },
+
+  /**
+   * Gets the parent node holding this view.
+   * @return nsIDOMNode
+   */
+  get parentNode() this._parent,
+
+  /**
+   * Gets the owner document holding this view.
+   * @return nsIHTMLDocument
+   */
+  get document() this._parent.ownerDocument,
+
+  /**
+   * Gets the default window holding this view.
+   * @return nsIDOMWindow
+   */
+  get window() this.document.defaultView,
+
+  eval: null,
+  _store: null,
+  _prevHierarchy: null,
+  _currHierarchy: null,
+  _enumVisible: true,
+  _nonEnumVisible: true,
+  _list: null,
+  _parent: null,
+  _emptyTextNode: null,
+  _emptyTextValue: ""
+};
+
+/**
+ * A Scope is an object holding Variable instances.
+ * Iterable via "for (let [name, variable] in instance) { }".
+ *
+ * @param VariablesView aView
+ *        The view to contain this scope.
+ * @param string aName
+ *        The scope's name.
+ * @param object aFlags [optional]
+ *        Additional options or flags for this scope.
+ */
+function Scope(aView, aName, aFlags = {}) {
+  this.show = this.show.bind(this);
+  this.hide = this.hide.bind(this);
+  this.expand = this.expand.bind(this);
+  this.collapse = this.collapse.bind(this);
+  this.toggle = this.toggle.bind(this);
+
+  this.ownerView = aView;
+  this.eval = aView.eval;
+
+  this._store = new Map();
+  this._init(aName.trim(), aFlags);
+}
+
+Scope.prototype = {
+  /**
+   * Adds a variable to contain any inspected properties.
+   *
+   * @param string aName
+   *        The variable's name.
+   * @param object aDescriptor
+   *        Specifies the value and/or type & class of the variable,
+   *        or 'get' & 'set' accessor properties. If the type is implicit,
+   *        it will be inferred from the value.
+   *        e.g. - { value: 42 }
+   *             - { value: true }
+   *             - { value: "nasu" }
+   *             - { value: { type: "undefined" } }
+   *             - { value: { type: "null" } }
+   *             - { value: { type: "object", class: "Object" } }
+   *             - { get: { type: "object", class: "Function" },
+   *                 set: { type: "undefined" } }
+   * @return Variable
+   *         The newly created Variable instance, null if it already exists.
+   */
+  addVar: function S_addVar(aName, aDescriptor = {}) {
+    if (this._store.has(aName)) {
+      return null;
+    }
+
+    let variable = new Variable(this, aName, aDescriptor);
+    this._store.set(aName, variable);
+    this._variablesView._currHierarchy.set(variable._absoluteName, variable);
+    return variable;
+  },
+
+  /**
+   * Gets the variable in this container having the specified name.
+   *
+   * @return Variable
+   *         The matched variable, or null if nothing is found.
+   */
+  get: function S_get(aName) {
+    return this._store.get(aName);
+  },
+
+  /**
+   * Shows the scope.
+   */
+  show: function S_show() {
+    this._target.hidden = false;
+    this._isShown = true;
+
+    if (this.onshow) {
+      this.onshow(this);
+    }
+  },
+
+  /**
+   * Hides the scope.
+   */
+  hide: function S_hide() {
+    this._target.hidden = true;
+    this._isShown = false;
+
+    if (this.onhide) {
+      this.onhide(this);
+    }
+  },
+
+  /**
+   * Expands the scope, showing all the added details.
+   *
+   * @param boolean aSkipAnimationFlag
+   *        Pass true to not show an opening animation.
+   */
+  expand: function S_expand(aSkipAnimationFlag) {
+    if (this._locked) {
+      return;
+    }
+    if (this._variablesView._enumVisible) {
+      this._arrow.setAttribute("open", "");
+      this._enum.setAttribute("open", "");
+    }
+    if (this._variablesView._nonEnumVisible) {
+      this._nonenum.setAttribute("open", "");
+    }
+    if (!aSkipAnimationFlag) {
+      this._enum.setAttribute("animated", "");
+      this._nonenum.setAttribute("animated", "");
+    }
+    this._isExpanded = true;
+
+    if (this.onexpand) {
+      this.onexpand(this);
+    }
+  },
+
+  /**
+   * Collapses the scope, hiding all the added details.
+   */
+  collapse: function S_collapse() {
+    if (this._locked) {
+      return;
+    }
+    this._arrow.removeAttribute("open");
+    this._enum.removeAttribute("open");
+    this._nonenum.removeAttribute("open");
+    this._enum.removeAttribute("animated");
+    this._nonenum.removeAttribute("animated");
+    this._isExpanded = false;
+
+    if (this.oncollapse) {
+      this.oncollapse(this);
+    }
+  },
+
+  /**
+   * Toggles between the scope's collapsed and expanded state.
+   */
+  toggle: function S_toggle() {
+    this.expanded ^= 1;
+
+    if (this.ontoggle) {
+      this.ontoggle(this);
+    }
+  },
+
+  /**
+   * Shows the scope's expand/collapse arrow.
+   */
+  showArrow: function S_showArrow() {
+    this._arrow.removeAttribute("invisible");
+    this._isArrowVisible = true;
+  },
+
+  /**
+   * Hides the scope's expand/collapse arrow.
+   */
+  hideArrow: function S_hideArrow() {
+    this._arrow.setAttribute("invisible", "");
+    this._isArrowVisible = false;
+  },
+
+  /**
+   * Gets the visibility state.
+   * @return boolean
+   */
+  get visible() this._isShown,
+
+  /**
+   * Gets the expanded state.
+   * @return boolean
+   */
+  get expanded() this._isExpanded,
+
+  /**
+   * Gets the twisty visibility state.
+   * @return boolean
+   */
+  get twisty() this._isArrowVisible,
+
+  /**
+   * Sets the visibility state.
+   * @param boolean aFlag
+   */
+  set visible(aFlag) aFlag ? this.show() : this.hide(),
+
+  /**
+   * Sets the expanded state.
+   * @param boolean aFlag
+   */
+  set expanded(aFlag) aFlag ? this.expand() : this.collapse(),
+
+  /**
+   * Sets the twisty visibility state.
+   * @param boolean aFlag
+   */
+  set twisty(aFlag) aFlag ? this.showArrow() : this.hideArrow(),
+
+  /**
+   * Gets the id associated with this item.
+   * @return string
+   */
+  get id() this._idString,
+
+  /**
+   * Gets the name associated with this item.
+   * @return string
+   */
+  get name() this._nameString,
+
+  /**
+   * Gets the element associated with this item.
+   * @return nsIDOMNode
+   */
+  get target() this._target,
+
+  /**
+   * Initializes this scope's id, view and binds event listeners.
+   *
+   * @param string aName
+   *        The scope's name.
+   * @param object aFlags [optional]
+   *        Additional options or flags for this scope.
+   * @param string aClassName [optional]
+   *        A custom class name for this scope.
+   */
+  _init: function S__init(aName, aFlags = {}, aClassName = "scope") {
+    this._idString = generateId(this._nameString = aName);
+    this._createScope(aName, aClassName);
+    this._addEventListeners();
+    this.parentNode.appendChild(this._target);
+  },
+
+  /**
+   * Creates the necessary nodes for this scope.
+   *
+   * @param string aName
+   *        The scope's name.
+   * @param string aClassName
+   *        A custom class name for this scope.
+   */
+  _createScope: function S__createScope(aName, aClassName) {
+    let document = this.document;
+
+    let element = this._target = document.createElement("vbox");
+    element.id = this._id;
+    element.className = aClassName;
+
+    let arrow = this._arrow = document.createElement("hbox");
+    arrow.className = "arrow";
+
+    let name = this._name = document.createElement("label");
+    name.className = "name plain";
+    name.setAttribute("value", aName);
+
+    let title = this._title = document.createElement("hbox");
+    title.className = "title" + (aClassName == "scope" ? " devtools-toolbar" : "");
+    title.setAttribute("align", "center");
+
+    let enumerable = this._enum = document.createElement("vbox");
+    let nonenum = this._nonenum = document.createElement("vbox");
+    enumerable.className = "details";
+    nonenum.className = "details nonenum";
+
+    title.appendChild(arrow);
+    title.appendChild(name);
+
+    element.appendChild(title);
+    element.appendChild(enumerable);
+    element.appendChild(nonenum);
+  },
+
+  /**
+   * Adds the necessary event listeners for this scope.
+   */
+  _addEventListeners: function S__addEventListeners() {
+    this._title.addEventListener("mousedown", this.toggle, false);
+  },
+
+  /**
+   * Specifies if enumerable properties and variables should be displayed.
+   * @param boolean aFlag
+   */
+  set _enumVisible(aFlag) {
+    for (let [_, variable] in this) {
+      variable._enumVisible = aFlag;
+
+      if (!this.expanded) {
+        continue;
+      }
+      if (aFlag) {
+        this._enum.setAttribute("open", "");
+      } else {
+        this._enum.removeAttribute("open");
+      }
+    }
+  },
+
+  /**
+   * Specifies if non-enumerable properties and variables should be displayed.
+   * @param boolean aFlag
+   */
+  set _nonEnumVisible(aFlag) {
+    for (let [_, variable] in this) {
+      variable._nonEnumVisible = aFlag;
+
+      if (!this.expanded) {
+        continue;
+      }
+      if (aFlag) {
+        this._nonenum.setAttribute("open", "");
+      } else {
+        this._nonenum.removeAttribute("open");
+      }
+    }
+  },
+
+  /**
+   * Gets top level variables view instance.
+   * @return VariablesView
+   */
+  get _variablesView() {
+    let parentView = this.ownerView;
+    let topView;
+
+    while (topView = parentView.ownerView) {
+      parentView = topView;
+    }
+    return parentView;
+  },
+
+  /**
+   * Gets the parent node holding this scope.
+   * @return nsIDOMNode
+   */
+  get parentNode() this.ownerView._list,
+
+  /**
+   * Gets the owner document holding this scope.
+   * @return nsIHTMLDocument
+   */
+  get document() this.ownerView.document,
+
+  /**
+   * Gets the default window holding this scope.
+   * @return nsIDOMWindow
+   */
+  get window() this.ownerView.window,
+
+  ownerView: null,
+  eval: null,
+  _committed: false,
+  _locked: false,
+  _isShown: true,
+  _isExpanded: false,
+  _isArrowVisible: true,
+  _store: null,
+  _idString: null,
+  _nameString: null,
+  _target: null,
+  _arrow: null,
+  _name: null,
+  _title: null,
+  _enum: null,
+  _nonenum: null
+};
+
+/**
+ * A Variable is a Scope holding Property instances.
+ * Iterable via "for (let [name, property] in instance) { }".
+ *
+ * @param Scope aScope
+ *        The scope to contain this varialbe.
+ * @param string aName
+ *        The variable's name.
+ * @param object aDescriptor
+ *        The variable's descriptor.
+ */
+function Variable(aScope, aName, aDescriptor) {
+  this._activateInput = this._activateInput.bind(this);
+  this._deactivateInput = this._deactivateInput.bind(this);
+  this._saveInput = this._saveInput.bind(this);
+  this._onInputKeyPress = this._onInputKeyPress.bind(this);
+
+  Scope.call(this, aScope, aName, aDescriptor);
+  this._setGrip(aDescriptor.value);
+  this._symbolicName = aName;
+  this._absoluteName = aScope.name + "." + aName;
+  this._initialDescriptor = aDescriptor;
+}
+
+create({ constructor: Variable, proto: Scope.prototype }, {
+  /**
+   * Adds a property for this variable.
+   *
+   * @param string aName
+   *        The property's name.
+   * @param object aDescriptor
+   *        Specifies the value and/or type & class of the property,
+   *        or 'get' & 'set' accessor properties. If the type is implicit,
+   *        it will be inferred from the value.
+   *        e.g. - { value: 42 }
+   *             - { value: true }
+   *             - { value: "nasu" }
+   *             - { value: { type: "undefined" } }
+   *             - { value: { type: "null" } }
+   *             - { value: { type: "object", class: "Object" } }
+   *             - { get: { type: "object", class: "Function" },
+   *                 set: { type: "undefined" } }
+   * @return Property
+   *         The newly created Property instance, null if it already exists.
+   */
+  addProperty: function V_addProperty(aName, aDescriptor = {}) {
+    if (this._store.has(aName)) {
+      return null;
+    }
+
+    let property = new Property(this, aName, aDescriptor);
+    this._store.set(aName, property);
+    this._variablesView._currHierarchy.set(property._absoluteName, property);
+    return property;
+  },
+
+  /**
+   * Adds properties for this variable.
+   *
+   * @param object aProperties
+   *        An object containing some { name: descriptor } data properties,
+   *        specifying the value and/or type & class of the variable,
+   *        or 'get' & 'set' accessor properties. If the type is implicit,
+   *        it will be inferred from the value.
+   *        e.g. - { someProp0: { value: 42 },
+   *                 someProp1: { value: true },
+   *                 someProp2: { value: "nasu" },
+   *                 someProp3: { value: { type: "undefined" } },
+   *                 someProp4: { value: { type: "null" } },
+   *                 someProp5: { valu§e: { type: "object", class: "Object" } },
+   *                 someProp6: { get: { type: "object", class: "Function" },
+   *                              set: { type: "undefined" } }
+   */
+  addProperties: function V_addProperties(aProperties) {
+    // Sort all of the properties before adding them.
+    let sortedPropertyNames = Object.keys(aProperties).sort();
+
+    for (let name of sortedPropertyNames) {
+      this.addProperty(name, aProperties[name]);
+    }
+  },
+
+  /**
+   * Sets the specific grip for this variable.
+   * The grip should contain the value or the type & class, as defined in the
+   * remote debugger protocol. For convenience, undefined and null are
+   * both considered types.
+   *
+   * @param any aGrip
+   *        Specifies the value and/or type & class of the variable.
+   *        e.g. - 42
+   *             - true
+   *             - "nasu"
+   *             - { type: "undefined" }
+   *             - { type: "null" }
+   *             - { type: "object", class: "Object" }
+   */
+  _setGrip: function V__setGrip(aGrip) {
+    if (aGrip === undefined) {
+      aGrip = { type: "undefined" };
+    }
+    if (aGrip === null) {
+      aGrip = { type: "null" };
+    }
+    this._applyGrip(aGrip);
+  },
+
+  /**
+   * Applies the necessary text content and class name to a value node based
+   * on a grip.
+   *
+   * @param any aGrip
+   *        @see Variable._setGrip
+   */
+  _applyGrip: function V__applyGrip(aGrip) {
+    let prevGrip = this._valueGrip;
+    if (prevGrip) {
+      this._valueLabel.classList.remove(VariablesView.getClass(prevGrip));
+    }
+    this._valueGrip = aGrip;
+    this._valueString = VariablesView.getString(aGrip);
+    this._valueClassName = VariablesView.getClass(aGrip);
+
+    this._valueLabel.classList.add(this._valueClassName);
+    this._valueLabel.setAttribute("value", this._valueString);
+  },
+
+  /**
+   * Initializes this variable's id, view and binds event listeners.
+   *
+   * @param string aName
+   *        The variable's name.
+   * @param object aDescriptor
+   *        The variable's descriptor.
+   */
+  _init: function V__init(aName, aDescriptor) {
+    this._idString = generateId(this._nameString = aName);
+    this._createScope(aName, "variable");
+    this._displayVariable(aDescriptor);
+    this._displayTooltip();
+    this._setAttributes(aName, aDescriptor);
+    this._addEventListeners();
+
+    if (aDescriptor.enumerable || aName == "this" || aName == "<exception>") {
+      this.ownerView._enum.appendChild(this._target);
+    } else {
+      this.ownerView._nonenum.appendChild(this._target);
+    }
+  },
+
+  /**
+   * Creates the necessary nodes for this variable.
+   *
+   * @param object aDescriptor
+   *        The property's descriptor.
+   */
+  _displayVariable: function V__displayVariable(aDescriptor) {
+    let document = this.document;
+
+    let separatorLabel = this._separatorLabel = document.createElement("label");
+    separatorLabel.className = "plain";
+    separatorLabel.setAttribute("value", ":");
+
+    let valueLabel = this._valueLabel = document.createElement("label");
+    valueLabel.className = "value plain";
+
+    this._title.appendChild(separatorLabel);
+    this._title.appendChild(valueLabel);
+
+    if (VariablesView.isPrimitive(aDescriptor)) {
+      this.hideArrow();
+    }
+    if (aDescriptor.get || aDescriptor.set) {
+      this.addProperty("get ", { value: aDescriptor.get });
+      this.addProperty("set ", { value: aDescriptor.set });
+      this.expand();
+      separatorLabel.hidden = true;
+      valueLabel.hidden = true;
+    }
+  },
+
+  /**
+   * Creates a tooltip for this variable.
+   */
+  _displayTooltip: function V__displayTooltip() {
+    let document = this.document;
+
+    let tooltip = document.createElement("tooltip");
+    tooltip.id = "tooltip-" + this.id;
+
+    let configurableLabel = document.createElement("label");
+    configurableLabel.setAttribute("value", "configurable");
+
+    let enumerableLabel = document.createElement("label");
+    enumerableLabel.setAttribute("value", "enumerable");
+
+    let writableLabel = document.createElement("label");
+    writableLabel.setAttribute("value", "writable");
+
+    tooltip.setAttribute("orient", "horizontal")
+    tooltip.appendChild(configurableLabel);
+    tooltip.appendChild(enumerableLabel);
+    tooltip.appendChild(writableLabel);
+
+    this._target.appendChild(tooltip);
+    this._target.setAttribute("tooltip", tooltip.id);
+  },
+
+  /**
+   * Sets a variable's configurable, enumerable and writable attributes,
+   * and specifies if it's a 'this', '<exception>' or '__proto__' reference.
+   *
+   * @param object aName
+   *        The varialbe name.
+   * @param object aDescriptor
+   *        The variable's descriptor.
+   */
+  _setAttributes: function V__setAttributes(aName, aDescriptor) {
+    if (aDescriptor) {
+      if (!aDescriptor.configurable) {
+        this._target.setAttribute("non-configurable", "");
+      }
+      if (!aDescriptor.enumerable) {
+        this._target.setAttribute("non-enumerable", "");
+      }
+      if (!aDescriptor.writable) {
+        this._target.setAttribute("non-writable", "");
+      }
+    }
+    if (aName == "this") {
+      this._target.setAttribute("self", "");
+    }
+    if (aName == "<exception>") {
+      this._target.setAttribute("exception", "");
+    }
+    if (aName == "__proto__") {
+      this._target.setAttribute("proto", "");
+    }
+  },
+
+  /**
+   * Adds the necessary event listeners for this variable.
+   */
+  _addEventListeners: function V__addEventListeners() {
+    this._arrow.addEventListener("mousedown", this.toggle, false);
+    this._name.addEventListener("mousedown", this.toggle, false);
+    this._valueLabel.addEventListener("click", this._activateInput, false);
+  },
+
+  /**
+   * Makes this variable's value editable.
+   */
+  _activateInput: function V__activateInput(e) {
+    if (!this.eval) {
+      return;
+    }
+
+    let title = this._title;
+    let valueLabel = this._valueLabel;
+    let initialString = this._valueLabel.getAttribute("value");
+
+    // Create a texbox input element which will be shown in the current
+    // element's value location.
+    let input = this.document.createElement("textbox");
+    input.setAttribute("value", initialString);
+    input.className = "element-input";
+    input.width = valueLabel.clientWidth + 1;
+
+    title.removeChild(valueLabel);
+    title.appendChild(input);
+    input.select();
+
+    // When the value is a string (displayed as "value"), then we probably want
+    // to change it to another string in the textbox, so to avoid typing the ""
+    // again, tackle with the selection bounds just a bit.
+    if (valueLabel.getAttribute("value").match(/^"[^"]*"$/)) {
+      input.selectionEnd--;
+      input.selectionStart++;
+    }
+
+    input.addEventListener("keypress", this._onInputKeyPress, false);
+    input.addEventListener("blur", this._deactivateInput, false);
+
+    this._prevExpandable = this.twisty;
+    this._prevExpanded = this.expanded;
+    this.collapse();
+    this.hideArrow();
+    this._locked = true;
+  },
+
+  /**
+   * Deactivates this variable's editable mode.
+   */
+  _deactivateInput: function V__deactivateInput(e) {
+    let input = e.target;
+    let title = this._title;
+    let valueLabel = this._valueLabel;
+
+    title.removeChild(input);
+    title.appendChild(valueLabel);
+
+    input.removeEventListener("keypress", this._onInputKeyPress, false);
+    input.removeEventListener("blur", this._deactivateInput, false);
+
+    this._locked = false;
+    this.twisty = this._prevExpandable;
+    this.expanded = this._prevExpanded;
+  },
+
+  /**
+   * Deactivates this variable's editable mode and evaluates a new value.
+   */
+  _saveInput: function V__saveInput(e) {
+    let input = e.target;
+    let valueLabel = this._valueLabel;
+    let initialString = this._valueLabel.getAttribute("value");
+    let currentString = input.value;
+
+    this._deactivateInput(e);
+
+    if (initialString != currentString) {
+      this._separatorLabel.hidden = true;
+      this._valueLabel.hidden = true;
+      this.collapse();
+      this.eval("(" + this._symbolicName + "=" + currentString + ")");
+    }
+  },
+
+  /**
+   * The key press listener for this variable's editable mode textbox.
+   */
+  _onInputKeyPress: function V__onInputKeyPress(e) {
+    switch(e.keyCode) {
+      case e.DOM_VK_RETURN:
+      case e.DOM_VK_ENTER:
+        this._saveInput(e);
+        return;
+      case e.DOM_VK_ESCAPE:
+        this._deactivateInput(e);
+        return;
+    }
+  },
+
+  _symbolicName: "",
+  _absoluteName: "",
+  _initialDescriptor: null,
+  _separatorLabel: null,
+  _valueLabel: null,
+  _tooltip: null,
+  _valueGrip: null,
+  _valueString: "",
+  _valueClassName: "",
+  _prevExpandable: false,
+  _prevExpanded: false
+});
+
+/**
+ * A Property is a Variable holding additional child Property instances.
+ * Iterable via "for (let [name, property] in instance) { }".
+ *
+ * @param Variable aVar
+ *        The variable to contain this property.
+ * @param string aName
+ *        The property's name.
+ * @param object aDescriptor
+ *        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;
+  this._initialDescriptor = aDescriptor;
+}
+
+create({ constructor: Property, proto: 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 P__init(aName, aDescriptor) {
+    this._idString = generateId(this._nameString = aName);
+    this._createScope(aName, "property");
+    this._displayVariable(aDescriptor);
+    this._displayTooltip();
+    this._setAttributes(aName, aDescriptor);
+    this._addEventListeners();
+
+    if (aDescriptor.enumerable) {
+      this.ownerView._enum.appendChild(this._target);
+    } else {
+      this.ownerView._nonenum.appendChild(this._target);
+    }
+  }
+});
+
+/**
+ * A generator-iterator over the VariablesView, Scopes, Variables and Properties.
+ */
+VariablesView.prototype.__iterator__ =
+Scope.prototype.__iterator__ =
+Variable.prototype.__iterator__ =
+Property.prototype.__iterator__ = function VV_iterator() {
+  for (let item of this._store) {
+    yield item;
+  }
+};
+
+/**
+ * Start recording a hierarchy of any added scopes, variables or properties.
+ * @see VariablesView.commitHierarchy
+ */
+VariablesView.prototype.createHierarchy = function VV_createHierarchy() {
+  this._prevHierarchy = this._currHierarchy;
+  this._currHierarchy = new Map();
+};
+
+/**
+ * Briefly flash the variables that changed between the previous and current
+ * scope/variable/property hierarchies.
+ */
+VariablesView.prototype.commitHierarchy = function VV_commitHierarchy() {
+  let prevHierarchy = this._prevHierarchy;
+  let currHierarchy = this._currHierarchy;
+
+  for (let [absoluteName, currVariable] of currHierarchy) {
+    // Ignore variables which were already commmitted.
+    if (currVariable._committed) {
+      continue;
+    }
+
+    // Try to get the previous instance of the inspected variable to
+    // determine the difference in state.
+    let prevVariable = prevHierarchy.get(absoluteName);
+    let changed = false;
+
+    // If the inspected variable existed in a previous hierarchy, check if
+    // the displayed value (a representation of the grip) has changed.
+    if (prevVariable) {
+      let prevString = prevVariable._valueString;
+      let currString = currVariable._valueString;
+      changed = prevString != currString;
+    }
+
+    // Make sure this variable is not handled in ulteror commits for the
+    // same hierarchy.
+    currVariable._committed = true;
+
+    // This variable was either not changed or removed, no need to continue.
+    if (!changed) {
+      continue;
+    }
+
+    // Apply an attribute determining the flash type and duration.
+    // Dispatch this action after all the nodes have been drawn, so that
+    // the transition efects can take place.
+    Services.tm.currentThread.dispatch({ run: function(aTarget) {
+      aTarget.setAttribute("changed", "");
+
+      aTarget.addEventListener("transitionend", function onEvent() {
+        aTarget.removeEventListener("transitionend", onEvent, false);
+        aTarget.removeAttribute("changed");
+      }, false);
+    }.bind(this, currVariable.target)}, 0);
+  }
+};
+
+/**
+ * Returns true if the descriptor represents an undefined, null or
+ * primitive value.
+ *
+ * @param object aDescriptor
+ *        The variable's descriptor.
+ */
+VariablesView.isPrimitive = function VV_isPrimitive(aDescriptor) {
+  if (!aDescriptor || typeof aDescriptor != "object") {
+    return true;
+  }
+
+  // For accessor property descriptors, the getter and setter need to be
+  // contained in 'get' and 'set' properties.
+  let getter = aDescriptor.get;
+  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") {
+    return true;
+  }
+
+  // For convenience, undefined and null are both considered types.
+  let type = grip.type;
+  if (["undefined", "null"].indexOf(type + "") != -1) {
+    return true;
+  }
+
+  return false;
+};
+
+/**
+ * Returns a custom formatted property string for a grip.
+ *
+ * @param any aGrip
+ *        @see Variable._setGrip
+ * @param boolean aConciseFlag
+ *        Return a concisely formatted property string.
+ * @return string
+ *         The formatted property string.
+ */
+VariablesView.getString = function VV_getString(aGrip, aConciseFlag) {
+  if (aGrip && typeof aGrip == "object") {
+    switch (aGrip.type) {
+      case "undefined":
+        return "undefined";
+      case "null":
+        return "null";
+      default:
+        if (!aConciseFlag) {
+          return "[" + aGrip.type + " " + aGrip.class + "]";
+        } else {
+          return aGrip.class;
+        }
+    }
+  } else {
+    switch (typeof aGrip) {
+      case "string":
+        return "\"" + aGrip + "\"";
+      case "boolean":
+        return aGrip ? "true" : "false";
+    }
+  }
+  return aGrip + "";
+};
+
+/**
+ * Returns a custom class style for a grip.
+ *
+ * @param any aGrip
+ *        @see Variable._setGrip
+ * @return string
+ *         The custom class style.
+ */
+VariablesView.getClass = function VV_getClass(aGrip) {
+  if (aGrip && typeof aGrip == "object") {
+    switch (aGrip.type) {
+      case "undefined":
+        return "token-undefined";
+      case "null":
+        return "token-null";
+    }
+  } else {
+    switch (typeof aGrip) {
+      case "string":
+        return "token-string";
+      case "boolean":
+        return "token-boolean";
+      case "number":
+        return "token-number";
+    }
+  }
+  return "token-other";
+};
+
+/**
+ * A monotonically-increasing counter, that guarantees the uniqueness of scope,
+ * variables and properties ids.
+ *
+ * @param string aName
+ *        An optional string to prefix the id with.
+ * @return number
+ *         A unique id.
+ */
+let generateId = (function() {
+  let count = 0;
+  return function(aName = "") {
+    return aName.toLowerCase().trim().replace(/\s+/g, "-") + (++count);
+  }
+})();
+
+/**
+ * Sugar for prototypal inheritance using Object.create.
+ * Creates a new object with the specified prototype object and properties.
+ *
+ * @param object target
+ * @param object properties
+ */
+function create({ constructor, proto }, properties = {}) {
+  let propertiesObject = {
+    constructor: { value: constructor }
+  };
+  for (let name in properties) {
+    propertiesObject[name] = Object.getOwnPropertyDescriptor(properties, name);
+  }
+  constructor.prototype = Object.create(proto, propertiesObject);
+}
--- a/browser/devtools/debugger/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -4,214 +4,201 @@
  * 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 NEW_SCRIPT_DISPLAY_DELAY = 100; // ms
-const FRAME_STEP_CACHE_DURATION = 100; // ms
 const DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties";
-const SCRIPTS_URL_MAX_LENGTH = 64; // chars
-const SYNTAX_HIGHLIGHT_MAX_FILE_SIZE = 1048576; // 1 MB in bytes
+const NEW_SCRIPT_DISPLAY_DELAY = 200; // ms
+const FRAME_STEP_CLEAR_DELAY = 100; // ms
+const CALL_STACK_PAGE_SIZE = 25; // frames
 
-Cu.import("resource:///modules/source-editor.jsm");
+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/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/NetUtil.jsm");
-Cu.import('resource://gre/modules/Services.jsm');
+Cu.import("resource:///modules/source-editor.jsm");
 Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
+Cu.import("resource:///modules/devtools/VariablesView.jsm");
 
 /**
- * Controls the debugger view by handling the source scripts, the current
- * thread state and thread stack frame cache.
+ * Object defining the debugger controller components.
  */
 let DebuggerController = {
-
   /**
-   * Makes a few preliminary changes and bindings to the controller.
+   * Initializes the debugger controller.
    */
-  init: function() {
+  initialize: function DC_initialize() {
+    dumpn("Initializing the DebuggerController");
     this._startupDebugger = this._startupDebugger.bind(this);
     this._shutdownDebugger = this._shutdownDebugger.bind(this);
     this._onTabNavigated = this._onTabNavigated.bind(this);
     this._onTabDetached = this._onTabDetached.bind(this);
 
-    window.addEventListener("DOMContentLoaded", this._startupDebugger, true);
+    window.addEventListener("load", this._startupDebugger, true);
     window.addEventListener("unload", this._shutdownDebugger, true);
   },
 
   /**
-   * Initializes the debugger view and connects a debugger client to the server.
+   * Initializes the view and connects a debugger client to the server.
    */
   _startupDebugger: function DC__startupDebugger() {
     if (this._isInitialized) {
       return;
     }
     this._isInitialized = true;
-    window.removeEventListener("DOMContentLoaded", this._startupDebugger, true);
+    window.removeEventListener("load", this._startupDebugger, true);
 
-    DebuggerView.cacheView();
-    DebuggerView.initializeKeys();
-    DebuggerView.initializePanes();
-    DebuggerView.initializeEditor(function() {
-      DebuggerView.GlobalSearch.initialize();
-      DebuggerView.Scripts.initialize();
-      DebuggerView.StackFrames.initialize();
-      DebuggerView.Breakpoints.initialize();
-      DebuggerView.Properties.initialize();
-      DebuggerView.toggleCloseButton(!this._isRemoteDebugger &&
-                                     !this._isChromeDebugger);
-
-      this.dispatchEvent("Debugger:Loaded");
+    DebuggerView.initialize(function() {
+      window.dispatchEvent("Debugger:Loaded");
       this._connect();
     }.bind(this));
   },
 
   /**
-   * Destroys the debugger view, disconnects the debugger client and cleans up
-   * any active listeners.
+   * Destroys the view and disconnects the debugger client from the server.
    */
   _shutdownDebugger: function DC__shutdownDebugger() {
-    if (this._isDestroyed) {
+    if (this._isDestroyed || !DebuggerView._isInitialized) {
       return;
     }
     this._isDestroyed = true;
     window.removeEventListener("unload", this._shutdownDebugger, true);
 
-    DebuggerView.GlobalSearch.destroy();
-    DebuggerView.Scripts.destroy();
-    DebuggerView.StackFrames.destroy();
-    DebuggerView.Breakpoints.destroy();
-    DebuggerView.Properties.destroy();
-    DebuggerView.destroyPanes();
-    DebuggerView.destroyEditor();
+    DebuggerView.destroy(function() {
+      this.SourceScripts.disconnect();
+      this.StackFrames.disconnect();
+      this.ThreadState.disconnect();
 
-    DebuggerController.SourceScripts.disconnect();
-    DebuggerController.StackFrames.disconnect();
-    DebuggerController.ThreadState.disconnect();
-
-    this.dispatchEvent("Debugger:Unloaded");
-    this._disconnect();
-    this._isChromeDebugger && this._quitApp();
+      this._disconnect();
+      window.dispatchEvent("Debugger:Unloaded");
+      window._isChromeDebugger && this._quitApp();
+    }.bind(this));
   },
 
   /**
    * Prepares the hostname and port number for a remote debugger connection
    * and handles connection retries and timeouts.
    *
-   * @return boolean true if connection should proceed normally
+   * @return boolean
+   *         True if connection should proceed normally, false otherwise.
    */
   _prepareConnection: function DC__prepareConnection() {
     // If we exceeded the total number of connection retries, bail.
     if (this._remoteConnectionTry === Prefs.remoteConnectionRetries) {
       Services.prompt.alert(null,
         L10N.getStr("remoteDebuggerPromptTitle"),
         L10N.getStr("remoteDebuggerConnectionFailedMessage"));
-      this.dispatchEvent("Debugger:Close");
+
+      // If the connection was not established before a certain number of
+      // retries, close the remote debugger.
+      this._shutdownDebugger();
       return false;
     }
 
     // TODO: This is ugly, need to rethink the design for the UI in #751677.
     if (!Prefs.remoteAutoConnect) {
       let prompt = new RemoteDebuggerPrompt();
       let result = prompt.show(!!this._remoteConnectionTimeout);
+
       // If the connection was not established before the user canceled the
       // prompt, close the remote debugger.
-      if (!result && !DebuggerController.activeThread) {
-        this.dispatchEvent("Debugger:Close");
+      if (!result) {
+        this._shutdownDebugger();
         return false;
       }
+
       Prefs.remoteHost = prompt.remote.host;
       Prefs.remotePort = prompt.remote.port;
+      Prefs.remoteAutoConnect = prompt.remote.auto;
     }
 
     // If this debugger is connecting remotely to a server, we need to check
     // after a while if the connection actually succeeded.
     this._remoteConnectionTry = ++this._remoteConnectionTry || 1;
     this._remoteConnectionTimeout = window.setTimeout(function() {
       // If we couldn't connect to any server yet, try again...
-      if (!DebuggerController.activeThread) {
-        DebuggerController._onRemoteConnectionTimeout();
-        DebuggerController._connect();
+      if (!this.activeThread) {
+        this._onRemoteConnectionTimeout();
+        this._connect();
       }
-    }, Prefs.remoteTimeout);
+    }.bind(this), Prefs.remoteTimeout);
 
+    // Proceed with the connection normally.
     return true;
   },
 
   /**
    * Called when a remote connection timeout occurs.
    */
   _onRemoteConnectionTimeout: function DC__onRemoteConnectionTimeout() {
     Cu.reportError("Couldn't connect to " +
       Prefs.remoteHost + ":" + Prefs.remotePort);
   },
 
   /**
    * Initializes a debugger client and connects it to the debugger server,
    * wiring event handlers as necessary.
    */
   _connect: function DC__connect() {
-    if (this._isRemoteDebugger) {
-      if (!this._prepareConnection()) {
-        return;
-      }
+    if (window._isRemoteDebugger && !this._prepareConnection()) {
+      return;
     }
-
-    let transport = (this._isChromeDebugger || this._isRemoteDebugger)
+    let transport = (window._isChromeDebugger || window._isRemoteDebugger)
       ? debuggerSocketConnect(Prefs.remoteHost, Prefs.remotePort)
       : DebuggerServer.connectPipe();
 
     let client = this.client = new DebuggerClient(transport);
-
     client.addListener("tabNavigated", this._onTabNavigated);
     client.addListener("tabDetached", this._onTabDetached);
 
     client.connect(function(aType, aTraits) {
       client.listTabs(function(aResponse) {
         let tab = aResponse.tabs[aResponse.selected];
         this._startDebuggingTab(client, tab);
-        this.dispatchEvent("Debugger:Connecting");
+        window.dispatchEvent("Debugger:Connected");
       }.bind(this));
     }.bind(this));
   },
 
   /**
-   * Closes the debugger client and removes event handlers as necessary.
+   * Disconnects the debugger client and removes event handlers as necessary.
    */
   _disconnect: function DC__disconnect() {
+    // Return early if the client didn't even have a chance to instantiate.
+    if (!this.client) {
+      return;
+    }
     this.client.removeListener("tabNavigated", this._onTabNavigated);
     this.client.removeListener("tabDetached", this._onTabDetached);
     this.client.close();
 
     this.client = null;
     this.tabClient = null;
     this.activeThread = null;
   },
 
   /**
-   * This function is called on each location change in this tab.
+   * Called for each location change in the debugged tab.
    */
-  _onTabNavigated: function DC__onTabNavigated(aNotification, aPacket) {
-    DebuggerController.ThreadState._handleTabNavigation(function() {
-      DebuggerController.StackFrames._handleTabNavigation(function() {
-        DebuggerController.SourceScripts._handleTabNavigation();
-      });
-    });
+  _onTabNavigated: function DC__onTabNavigated() {
+    DebuggerView._handleTabNavigation();
+    this.ThreadState._handleTabNavigation();
+    this.StackFrames._handleTabNavigation();
+    this.SourceScripts._handleTabNavigation();
   },
 
   /**
-   * Stops debugging the current tab.
+   * Called when the debugged tab is closed.
    */
   _onTabDetached: function DC__onTabDetached() {
-    this.dispatchEvent("Debugger:Close");
+    this._shutdownDebugger();
   },
 
   /**
    * Sets up a debugging session.
    *
    * @param DebuggerClient aClient
    *        The debugger client.
    * @param object aTabGrip
@@ -233,23 +220,20 @@ let DebuggerController = {
 
       aClient.attachThread(aResponse.threadActor, function(aResponse, aThreadClient) {
         if (!aThreadClient) {
           Cu.reportError("Couldn't attach to thread: " + aResponse.error);
           return;
         }
         this.activeThread = aThreadClient;
 
-        DebuggerController.ThreadState.connect(function() {
-          DebuggerController.StackFrames.connect(function() {
-            DebuggerController.SourceScripts.connect(function() {
-              aThreadClient.resume();
-            });
-          });
-        });
+        this.ThreadState.connect();
+        this.StackFrames.connect();
+        this.SourceScripts.connect();
+        aThreadClient.resume();
 
       }.bind(this));
     }.bind(this));
   },
 
   /**
    * Attempts to quit the current process if allowed.
    */
@@ -258,1451 +242,933 @@ let DebuggerController = {
       .createInstance(Ci.nsISupportsPRBool);
 
     Services.obs.notifyObservers(canceled, "quit-application-requested", null);
 
     // Somebody canceled our quit request.
     if (canceled.data) {
       return;
     }
-
     Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit);
   },
 
   /**
    * Convenience method, dispatching a custom event.
    *
    * @param string aType
    *        The name of the event.
-   * @param string aDetail
+   * @param any aDetail
    *        The data passed when initializing the event.
    */
   dispatchEvent: function DC_dispatchEvent(aType, aDetail) {
     let evt = document.createEvent("CustomEvent");
     evt.initCustomEvent(aType, true, false, aDetail);
     document.documentElement.dispatchEvent(evt);
   }
 };
 
 /**
- * Returns true if this is a remote debugger instance.
- * @return boolean
- */
-XPCOMUtils.defineLazyGetter(DebuggerController, "_isRemoteDebugger", function() {
-  // We're inside a single top level XUL window, not an iframe container.
-  return !(window.frameElement instanceof XULElement) &&
-         !!window._remoteFlag;
-});
-
-/**
- * Returns true if this is a chrome debugger instance.
- * @return boolean
- */
-XPCOMUtils.defineLazyGetter(DebuggerController, "_isChromeDebugger", function() {
-  // We're inside a single top level XUL window, but not a remote debugger.
-  return !(window.frameElement instanceof XULElement) &&
-         !window._remoteFlag;
-});
-
-/**
  * ThreadState keeps the UI up to date with the state of the
  * thread (paused/attached/etc.).
  */
 function ThreadState() {
   this._update = this._update.bind(this);
 }
 
 ThreadState.prototype = {
-
-  /**
-   * Gets the current thread the client has connected to.
-   */
-  get activeThread() {
-    return DebuggerController.activeThread;
-  },
+  get activeThread() DebuggerController.activeThread,
 
   /**
    * Connect to the current thread client.
-   *
-   * @param function aCallback
-   *        The next function in the initialization sequence.
    */
-  connect: function TS_connect(aCallback) {
+  connect: function TS_connect() {
+    dumpn("ThreadState is connecting...");
     this.activeThread.addListener("paused", this._update);
     this.activeThread.addListener("resumed", this._update);
     this.activeThread.addListener("detached", this._update);
-
     this._handleTabNavigation();
-
-    aCallback && aCallback();
   },
 
   /**
    * Disconnect from the client.
    */
   disconnect: function TS_disconnect() {
     if (!this.activeThread) {
       return;
     }
+    dumpn("ThreadState is disconnecting...");
     this.activeThread.removeListener("paused", this._update);
     this.activeThread.removeListener("resumed", this._update);
     this.activeThread.removeListener("detached", this._update);
   },
 
   /**
    * Handles any initialization on a tab navigation event issued by the client.
    */
-  _handleTabNavigation: function TS__handleTabNavigation(aCallback) {
-    DebuggerView.StackFrames.updateState(this.activeThread.state);
-
-    aCallback && aCallback();
+  _handleTabNavigation: function TS__handleTabNavigation() {
+    if (!this.activeThread) {
+      return;
+    }
+    dumpn("Handling tab navigation in the ThreadState");
+    this._update(this.activeThread.state);
   },
 
   /**
    * Update the UI after a thread state change.
    */
   _update: function TS__update(aEvent) {
-    DebuggerView.StackFrames.updateState(this.activeThread.state);
+    DebuggerView.Toolbar.toggleResumeButtonState(this.activeThread.state);
   }
 };
 
 /**
- * Keeps the stack frame list up-to-date, using the thread client's stack frame
- * cache.
+ * 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._onResume = this._onResume.bind(this);
+  this._onResumed = this._onResumed.bind(this);
   this._onFrames = this._onFrames.bind(this);
   this._onFramesCleared = this._onFramesCleared.bind(this);
   this._afterFramesCleared = this._afterFramesCleared.bind(this);
+  this.evaluate = this.evaluate.bind(this);
 }
 
 StackFrames.prototype = {
-
-  /**
-   * The maximum number of frames allowed to be loaded at a time.
-   */
-  pageSize: 25,
-
-  /**
-   * The currently selected frame depth.
-   */
-  selectedFrame: null,
-
-  /**
-   * A flag that defines whether the debuggee will pause whenever an exception
-   * is thrown.
-   */
-  pauseOnExceptions: false,
+  get activeThread() DebuggerController.activeThread,
+  currentFrame: null,
+  currentException: null,
 
   /**
-   * Gets the current thread the client has connected to.
+   * Connect to the current thread client.
    */
-  get activeThread() {
-    return DebuggerController.activeThread;
-  },
-
-  /**
-   * Watch the given thread client.
-   *
-   * @param function aCallback
-   *        The next function in the initialization sequence.
-   */
-  connect: function SF_connect(aCallback) {
-    window.addEventListener("Debugger:FetchedVariables", this._onFetchedVars, false);
+  connect: function SF_connect() {
+    dumpn("StackFrames is connecting...");
     this.activeThread.addListener("paused", this._onPaused);
-    this.activeThread.addListener("resumed", this._onResume);
+    this.activeThread.addListener("resumed", this._onResumed);
     this.activeThread.addListener("framesadded", this._onFrames);
     this.activeThread.addListener("framescleared", this._onFramesCleared);
-
     this._handleTabNavigation();
-    this.updatePauseOnExceptions(this.pauseOnExceptions);
-
-    aCallback && aCallback();
   },
 
   /**
    * Disconnect from the client.
    */
   disconnect: function SF_disconnect() {
     if (!this.activeThread) {
       return;
     }
-    window.removeEventListener("Debugger:FetchedVariables", this._onFetchedVars, false);
+    dumpn("StackFrames is disconnecting...");
     this.activeThread.removeListener("paused", this._onPaused);
-    this.activeThread.removeListener("resumed", this._onResume);
+    this.activeThread.removeListener("resumed", this._onResumed);
     this.activeThread.removeListener("framesadded", this._onFrames);
     this.activeThread.removeListener("framescleared", this._onFramesCleared);
   },
 
   /**
    * Handles any initialization on a tab navigation event issued by the client.
    */
-  _handleTabNavigation: function SF__handleTabNavigation(aCallback) {
+  _handleTabNavigation: function SF__handleTabNavigation() {
+    dumpn("Handling tab navigation in the StackFrames");
     // Nothing to do here yet.
-
-    aCallback && aCallback();
   },
 
   /**
    * Handler for the thread client's paused notification.
    *
    * @param string aEvent
    *        The name of the notification ("paused" in this case).
    * @param object aPacket
    *        The response packet.
    */
   _onPaused: function SF__onPaused(aEvent, aPacket) {
     // In case the pause was caused by an exception, store the exception value.
     if (aPacket.why.type == "exception") {
-      this.exception = aPacket.why.exception;
+      this.currentException = aPacket.why.exception;
     }
 
-    this.activeThread.fillFrames(this.pageSize);
+    this.activeThread.fillFrames(CALL_STACK_PAGE_SIZE);
     DebuggerView.editor.focus();
   },
 
   /**
    * Handler for the thread client's resumed notification.
    */
-  _onResume: function SF__onResume() {
+  _onResumed: function SF__onResumed() {
     DebuggerView.editor.setDebugLocation(-1);
   },
 
   /**
    * Handler for the thread client's framesadded notification.
    */
   _onFrames: function SF__onFrames() {
+    // Ignore useless notifications.
     if (!this.activeThread.cachedFrames.length) {
-      DebuggerView.StackFrames.emptyText();
-      DebuggerView.Properties.emptyText();
       return;
     }
     DebuggerView.StackFrames.empty();
-    DebuggerView.Properties.empty();
+    DebuggerView.Variables.empty();
 
-    for each (let frame in this.activeThread.cachedFrames) {
+    for (let frame of this.activeThread.cachedFrames) {
       this._addFrame(frame);
     }
-    if (!this.selectedFrame) {
+    if (!this.currentFrame) {
       this.selectFrame(0);
     }
     if (this.activeThread.moreFrames) {
       DebuggerView.StackFrames.dirty = true;
     }
   },
 
   /**
    * Handler for the thread client's framescleared notification.
    */
   _onFramesCleared: function SF__onFramesCleared() {
-    this.selectedFrame = null;
-    this.exception = null;
+    this.currentFrame = null;
+    this.currentException = null;
     // After each frame step (in, over, out), framescleared is fired, which
     // forces the UI to be emptied and rebuilt on framesadded. Most of the times
     // this is not necessary, and will result in a brief redraw flicker.
     // To avoid it, invalidate the UI only after a short time if necessary.
-    window.setTimeout(this._afterFramesCleared, FRAME_STEP_CACHE_DURATION);
+    window.setTimeout(this._afterFramesCleared, FRAME_STEP_CLEAR_DELAY);
   },
 
   /**
    * Called soon after the thread client's framescleared notification.
    */
   _afterFramesCleared: function SF__afterFramesCleared() {
-    if (!this.activeThread.cachedFrames.length) {
-      DebuggerView.StackFrames.emptyText();
-      DebuggerView.Properties.emptyText();
-      DebuggerController.dispatchEvent("Debugger:AfterFramesCleared");
-    }
-  },
-
-  /**
-   * Update the source editor's current debug location based on the selected
-   * frame and script.
-   */
-  updateEditorLocation: function SF_updateEditorLocation() {
-    let frame = this.activeThread.cachedFrames[this.selectedFrame];
-    if (!frame) {
+    // Ignore useless notifications.
+    if (this.activeThread.cachedFrames.length) {
       return;
     }
-
-    let url = frame.where.url;
-    let line = frame.where.line;
-    let editor = DebuggerView.editor;
-
-    this.updateEditorToLocation(url, line, true);
+    DebuggerView.StackFrames.empty();
+    DebuggerView.Variables.empty();
+    DebuggerView.Breakpoints.unhighlightBreakpoint();
+    window.dispatchEvent("Debugger:AfterFramesCleared");
   },
 
   /**
-   * Update the source editor's current caret and debug location based on
-   * a specified url and line.
-   *
-   * @param string aUrl
-   *        The target source url.
-   * @param number aLine
-   *        The target line number in the source.
-   * @param boolean aNoSwitch
-   *        Pass true to not switch to the script if not currently selected.
-   * @param boolean aNoCaretFlag
-   *        Pass true to not set the caret location at the specified line.
-   * @param boolean aNoDebugFlag
-   *        Pass true to not set the debug location at the specified line.
-   */
-  updateEditorToLocation:
-  function SF_updateEditorToLocation(aUrl, aLine, aNoSwitch, aNoCaretFlag, aNoDebugFlag) {
-    let editor = DebuggerView.editor;
-
-    function set() {
-      if (!aNoCaretFlag) {
-        editor.setCaretPosition(aLine - 1);
-      }
-      if (!aNoDebugFlag) {
-        editor.setDebugLocation(aLine - 1);
-      }
-    }
-
-    // Move the editor's caret to the proper url and line.
-    if (DebuggerView.Scripts.isSelected(aUrl)) {
-      return set();
-    }
-    if (!aNoSwitch && DebuggerView.Scripts.contains(aUrl)) {
-      DebuggerView.Scripts.selectScript(aUrl);
-      return set();
-    }
-    editor.setCaretPosition(-1);
-    editor.setDebugLocation(-1);
-  },
-
-  /**
-   * Inform the debugger client whether the debuggee should be paused whenever
-   * an exception is thrown.
-   *
-   * @param boolean aFlag
-   *        The new value of the flag: true for pausing, false otherwise.
-   */
-  updatePauseOnExceptions: function SF_updatePauseOnExceptions(aFlag) {
-    this.pauseOnExceptions = aFlag;
-    this.activeThread.pauseOnExceptions(this.pauseOnExceptions);
-  },
-
-  /**
-   * Marks the stack frame in the specified depth as selected and updates the
+   * 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 SF_selectFrame(aDepth) {
-    // Deselect any previously highlighted frame.
-    if (this.selectedFrame !== null) {
-      DebuggerView.StackFrames.unhighlightFrame(this.selectedFrame);
+    let frame = this.activeThread.cachedFrames[this.currentFrame = aDepth];
+    if (!frame) {
+      return;
     }
+    let env = frame.environment;
+    let { url, line } = frame.where;
 
-    // Highlight the current frame.
-    this.selectedFrame = aDepth;
-    DebuggerView.StackFrames.highlightFrame(this.selectedFrame);
-
-    let frame = this.activeThread.cachedFrames[aDepth];
-    if (!frame) {
+    // Check if the frame does not represent the evaluation of debuggee code.
+    if (!env) {
       return;
     }
 
-    let url = frame.where.url;
-    let line = frame.where.line;
-
-    // Move the editor's caret to the proper line.
-    this.updateEditorToLocation(url, line);
+    // Move the editor's caret to the proper url and line.
+    DebuggerView.updateEditor(url, line);
+    // Highlight the stack frame at the specified depth.
+    DebuggerView.StackFrames.highlightFrame(aDepth);
+    // Highlight the breakpoint at the specified url and line if it exists.
+    DebuggerView.Breakpoints.highlightBreakpoint(url, line);
+    // Start recording any added variables or properties in any scope.
+    DebuggerView.Variables.createHierarchy();
+    // Clear existing scopes and create each one dynamically.
+    DebuggerView.Variables.empty();
 
-    // Start recording any added variables or properties in any scope.
-    DebuggerView.Properties.createHierarchyStore();
-
-    // Clear existing scopes and create each one dynamically.
-    DebuggerView.Properties.empty();
+    let self = this;
+    let name = "";
 
-    if (frame.environment) {
-      let env = frame.environment;
-      do {
-        // Construct the scope name.
-        let name = env.type.charAt(0).toUpperCase() + env.type.slice(1);
-        // Call the outermost scope Global.
-        if (!env.parent) {
-          name = L10N.getStr("globalScopeLabel");
-        }
-        let label = L10N.getFormatStr("scopeLabel", [name]);
-        switch (env.type) {
-          case "with":
-          case "object":
-            label += " [" + env.object.class + "]";
-            break;
-          case "function":
-            if (env.functionName) {
-              label += " [" + env.functionName + "]";
-            }
-            break;
-          default:
-            break;
-        }
+    do {
+      // Name the outermost scope Global.
+      if (!env.parent) {
+        name = L10N.getStr("globalScopeLabel");
+      }
+      // Otherwise construct the scope name.
+      else {
+        name = env.type.charAt(0).toUpperCase() + env.type.slice(1);
+      }
 
-        let scope = DebuggerView.Properties.addScope(label);
+      let label = L10N.getFormatStr("scopeLabel", [name]);
+      switch (env.type) {
+        case "with":
+        case "object":
+          label += " [" + env.object.class + "]";
+          break;
+        case "function":
+          label += " [" + env.functionName + "]";
+          break;
+      }
+
+      // Create a scope to contain all the inspected variables.
+      let scope = DebuggerView.Variables.addScope(label);
 
-        // Special additions to the innermost scope.
-        if (env == frame.environment) {
-          // Add any thrown exception.
-          if (aDepth == 0 && this.exception) {
-            let excVar = scope.addVar("<exception>");
-            if (typeof this.exception == "object") {
-              excVar.setGrip({
-                type: this.exception.type,
-                class: this.exception.class
-              });
-              this._addExpander(excVar, this.exception);
-            } else {
-              excVar.setGrip(this.exception);
-            }
-          }
+      // Special additions to the innermost scope.
+      if (env == frame.environment) {
+        // Add any thrown exception.
+        if (aDepth == 0 && this.currentException) {
+          let excVar = scope.addVar("<exception>", { value: this.currentException });
+          this._addExpander(excVar, this.currentException);
+        }
+        // Add "this".
+        if (frame.this) {
+          let thisVar = scope.addVar("this", { value: frame.this });
+          this._addExpander(thisVar, frame.this);
+        }
+        // Expand the innermost scope by default.
+        scope.expand(true);
+      }
 
-          // Add "this".
-          if (frame.this) {
-            let thisVar = scope.addVar("this");
-            thisVar.setGrip({
-              type: frame.this.type,
-              class: frame.this.class
-            });
-            this._addExpander(thisVar, frame.this);
-          }
-
-          // Expand the innermost scope by default.
-          scope.expand(true);
-          scope.addToHierarchy();
-        }
+      switch (env.type) {
+        case "with":
+        case "object":
+          // Add nodes for all variables in the environment object scope.
+          this.activeThread.pauseGrip(env.object).getPrototypeAndProperties(function(aResponse) {
+            self._addScopeVariables(aResponse.ownProperties, scope);
 
-        switch (env.type) {
-          case "with":
-          case "object":
-            let objClient = this.activeThread.pauseGrip(env.object);
-            objClient.getPrototypeAndProperties(function SF_getProps(aResponse) {
-              this._addScopeVariables(aResponse.ownProperties, scope);
-              // Signal that variables have been fetched.
-              DebuggerController.dispatchEvent("Debugger:FetchedVariables");
-            }.bind(this));
-            break;
-          case "block":
-          case "function":
-            // Add nodes for every argument.
-            let variables = env.bindings.arguments;
-            for each (let variable in variables) {
-              let name = Object.getOwnPropertyNames(variable)[0];
-              let paramVar = scope.addVar(name, variable[name]);
-              let paramVal = variable[name].value;
-              paramVar.setGrip(paramVal);
-              this._addExpander(paramVar, paramVal);
-            }
-            // Add nodes for every other variable in scope.
-            this._addScopeVariables(env.bindings.variables, scope);
-            break;
-          default:
-            Cu.reportError("Unknown Debugger.Environment type: " + env.type);
-            break;
-        }
-      } while (env = env.parent);
-    }
+            // Signal that variables have been fetched.
+            window.dispatchEvent("Debugger:FetchedVariables");
+            DebuggerView.Variables.commitHierarchy();
+          });
+          break;
+        case "block":
+        case "function":
+          // Add nodes for every argument.
+          for (let variable of env.bindings.arguments) {
+            let name = Object.getOwnPropertyNames(variable)[0];
+            let paramVar = scope.addVar(name, variable[name]);
+            let paramVal = variable[name].value;
+            this._addExpander(paramVar, paramVal);
+          }
+          // Add nodes for every other variable in scope.
+          this._addScopeVariables(env.bindings.variables, scope);
+          break;
+        default:
+          Cu.reportError("Unknown Debugger.Environment type: " + env.type);
+          break;
+      }
+    } while (env = env.parent);
 
     // Signal that variables have been fetched.
-    DebuggerController.dispatchEvent("Debugger:FetchedVariables");
-  },
-
-  /**
-   * Called afters variables have been fetched after a frame was selected.
-   */
-  _onFetchedVars: function SF__onFetchedVars() {
-    DebuggerView.Properties.commitHierarchy();
+    window.dispatchEvent("Debugger:FetchedVariables");
+    DebuggerView.Variables.commitHierarchy();
   },
 
   /**
    * Add nodes for every variable in scope.
    *
    * @param object aVariables
    *        The map of names to variables, as specified in the Remote
    *        Debugging Protocol.
-   * @param object aScope
+   * @param Scope aScope
    *        The scope where the nodes will be placed into.
    */
   _addScopeVariables: function SF_addScopeVariables(aVariables, aScope) {
-    // Sort all of the variables before adding them, for better UX.
-    let variables = {};
-    for each (let prop in Object.keys(aVariables).sort()) {
-      variables[prop] = aVariables[prop];
+    if (!aVariables) {
+      return;
     }
+    // Sort all of the variables before adding them.
+    let sortedVariableNames = Object.keys(aVariables).sort();
 
     // Add the sorted variables to the specified scope.
-    for (let variable in variables) {
-      let paramVar = aScope.addVar(variable, variables[variable]);
-      let paramVal = variables[variable].value;
-      paramVar.setGrip(paramVal);
+    for (let name of sortedVariableNames) {
+      let paramVar = aScope.addVar(name, aVariables[name]);
+      let paramVal = aVariables[name].value;
       this._addExpander(paramVar, paramVal);
     }
   },
 
   /**
-   * Adds an 'onexpand' callback for a variable, lazily handling the addition of
-   * new properties.
+   * Adds an 'onexpand' callback for a variable, lazily handling
+   * the addition of new properties.
+   *
+   * @param Variable aVar
+   *        The variable where the properties will be placed into.
+   * @param any aGrip
+   *        The grip of the variable.
    */
-  _addExpander: function SF__addExpander(aVar, aObject) {
-    // No need for expansion for null and undefined values.
-    if (!aVar || !aObject || typeof aObject !== "object" ||
-        aObject.type !== "object") {
+  _addExpander: function SF__addExpander(aVar, aGrip) {
+    // No need for expansion for primitive values.
+    if (VariablesView.isPrimitive({ value: aGrip })) {
       return;
     }
-
-    // Force the twisty to show up.
-    aVar.forceShowArrow();
-    aVar.onexpand = this._addVarProperties.bind(this, aVar, aObject);
+    aVar.onexpand = this._addVarProperties.bind(this, aVar, aGrip);
   },
 
   /**
    * Adds properties to a variable in the view. Triggered when a variable is
    * expanded.
+   *
+   * @param Variable aVar
+   *        The variable where the properties will be placed into.
+   * @param any aGrip
+   *        The grip of the variable.
    */
-  _addVarProperties: function SF__addVarProperties(aVar, aObject) {
+  _addVarProperties: function SF__addVarProperties(aVar, aGrip) {
     // Retrieve the properties only once.
     if (aVar.fetched) {
       return;
     }
 
-    let objClient = this.activeThread.pauseGrip(aObject);
-    objClient.getPrototypeAndProperties(function SF_onProtoAndProps(aResponse) {
-      // Sort all of the properties before adding them, for better UX.
-      let properties = {};
-      for each (let prop in Object.keys(aResponse.ownProperties).sort()) {
-        properties[prop] = aResponse.ownProperties[prop];
-      }
-      aVar.addProperties(properties);
+    this.activeThread.pauseGrip(aGrip).getPrototypeAndProperties(function(aResponse) {
+      let { ownProperties, prototype } = aResponse;
 
-      // Expansion handlers must be set after the properties are added.
-      for (let prop in aResponse.ownProperties) {
-        this._addExpander(aVar[prop], aResponse.ownProperties[prop].value);
+      // Add all the variable properties.
+      if (ownProperties) {
+        aVar.addProperties(ownProperties);
+        // Expansion handlers must be set after the properties are added.
+        for (let name in ownProperties) {
+          this._addExpander(aVar.get(name), ownProperties[name].value);
+        }
       }
 
-      // Add __proto__.
-      if (aResponse.prototype.type !== "null") {
-        let properties = { "__proto__ ": { value: aResponse.prototype } };
-        aVar.addProperties(properties);
+      // Add the variable's __proto__.
+      if (prototype.type != "null") {
+        aVar.addProperties({ "__proto__ ": { value: prototype } });
+        // Expansion handlers must be set after the properties are added.
+        this._addExpander(aVar.get("__proto__ "), prototype);
+      }
 
-        // Expansion handlers must be set after the properties are added.
-        this._addExpander(aVar["__proto__ "], aResponse.prototype);
-      }
       aVar.fetched = true;
     }.bind(this));
   },
 
   /**
    * Adds the specified stack frame to the list.
    *
-   * @param Debugger.Frame aFrame
+   * @param object aFrame
    *        The new frame to add.
    */
   _addFrame: function SF__addFrame(aFrame) {
     let depth = aFrame.depth;
-    let label = DebuggerController.SourceScripts.getScriptLabel(aFrame.where.url);
-
-    let startText = this._getFrameTitle(aFrame);
-    let endText = label + ":" + aFrame.where.line;
+    let { url, line } = aFrame.where;
 
-    let frame = DebuggerView.StackFrames.addFrame(depth, startText, endText);
-    if (frame) {
-      frame.debuggerFrame = aFrame;
-    }
+    let startText = StackFrameUtils.getFrameTitle(aFrame);
+    let endText = SourceUtils.getSourceLabel(url) + ":" + line;
+
+    DebuggerView.StackFrames.addFrame(startText, endText, depth, {
+      attachment: aFrame
+    });
   },
 
   /**
    * Loads more stack frames from the debugger server cache.
    */
   addMoreFrames: function SF_addMoreFrames() {
     this.activeThread.fillFrames(
-      this.activeThread.cachedFrames.length + this.pageSize);
-  },
-
-  /**
-   * Create a textual representation for the stack frame specified, for
-   * displaying in the stack frame list.
-   *
-   * @param Debugger.Frame aFrame
-   *        The stack frame to label.
-   */
-  _getFrameTitle: function SF__getFrameTitle(aFrame) {
-    if (aFrame.type == "call") {
-      return aFrame["calleeName"] ? aFrame["calleeName"] : "(anonymous)";
-    }
-    return "(" + aFrame.type + ")";
+      this.activeThread.cachedFrames.length + CALL_STACK_PAGE_SIZE);
   },
 
   /**
    * Evaluate an expression in the context of the selected frame. This is used
-   * for modifying the value of variables in scope.
+   * for modifying the value of variables or properties in scope.
    *
    * @param string aExpression
    *        The expression to evaluate.
    */
   evaluate: function SF_evaluate(aExpression) {
-    let frame = this.activeThread.cachedFrames[this.selectedFrame];
+    let frame = this.activeThread.cachedFrames[this.currentFrame];
     this.activeThread.eval(frame.actor, aExpression);
   }
 };
 
 /**
  * Keeps the source script list up-to-date, using the thread client's
  * source script cache.
+ *
+ * FIXME: Currently, "sources" are actually "scripts", this should change in
+ * Bug 795368 - Add "sources" and "newSource" packets to the RDP, and use them
+ * instead of "scripts" and "newScript".
  */
 function SourceScripts() {
   this._onNewScript = this._onNewScript.bind(this);
   this._onScriptsAdded = this._onScriptsAdded.bind(this);
-  this._onShowScript = this._onShowScript.bind(this);
-  this._onLoadSource = this._onLoadSource.bind(this);
-  this._onLoadSourceFinished = this._onLoadSourceFinished.bind(this);
 }
 
 SourceScripts.prototype = {
-
-  /**
-   * A cache containing simplified labels from script urls.
-   */
-  _labelsCache: {},
-
-  /**
-   * Gets the current thread the client has connected to.
-   */
-  get activeThread() {
-    return DebuggerController.activeThread;
-  },
+  get activeThread() DebuggerController.activeThread,
+  get debuggerClient() DebuggerController.client,
 
   /**
-   * Gets the current debugger client.
+   * Connect to the current thread client.
    */
-  get debuggerClient() {
-    return DebuggerController.client;
-  },
-
-  /**
-   * Watch the given thread client.
-   *
-   * @param function aCallback
-   *        The next function in the initialization sequence.
-   */
-  connect: function SS_connect(aCallback) {
-    window.addEventListener("Debugger:LoadSource", this._onLoadSource, false);
+  connect: function SS_connect() {
     this.debuggerClient.addListener("newScript", this._onNewScript);
-
     this._handleTabNavigation();
-
-    aCallback && aCallback();
   },
 
   /**
    * Disconnect from the client.
    */
   disconnect: function SS_disconnect() {
     if (!this.activeThread) {
       return;
     }
-    window.removeEventListener("Debugger:LoadSource", this._onLoadSource, false);
     this.debuggerClient.removeListener("newScript", this._onNewScript);
   },
 
   /**
    * Handles any initialization on a tab navigation event issued by the client.
    */
-  _handleTabNavigation: function SS__handleTabNavigation(aCallback) {
-    this._clearLabelsCache();
-    this._onScriptsCleared();
+  _handleTabNavigation: function SS__handleTabNavigation() {
+    if (!this.activeThread) {
+      return;
+    }
+    dumpn("Handling tab navigation in the SourceScripts");
 
-    // Retrieve the list of scripts known to the server from before the client
-    // was ready to handle new script notifications.
+    // Retrieve the list of script sources known to the server from before
+    // the client was ready to handle "newScript" notifications.
     this.activeThread.getScripts(this._onScriptsAdded);
-
-    aCallback && aCallback();
   },
 
   /**
    * Handler for the debugger client's unsolicited newScript notification.
    */
   _onNewScript: function SS__onNewScript(aNotification, aPacket) {
     // Ignore scripts generated from 'clientEvaluate' packets.
     if (aPacket.url == "debugger eval code") {
       return;
     }
 
-    this._addScript({
+    // Add the source in the debugger view sources container.
+    this._addSource({
       url: aPacket.url,
       startLine: aPacket.startLine,
       source: aPacket.source
-    }, true);
-
-    let preferredScriptUrl = DebuggerView.Scripts.preferredScriptUrl;
+    }, {
+      forced: true
+    });
 
-    // Select this script if it's the preferred one.
-    if (aPacket.url === DebuggerView.Scripts.preferredScriptUrl) {
-      DebuggerView.Scripts.selectScript(aPacket.url);
+    let container = DebuggerView.Sources;
+    let preferredValue = container.preferredValue;
+
+    // Select this source if it's the preferred one.
+    if (aPacket.url == preferredValue) {
+      container.selectedValue = preferredValue;
     }
     // ..or the first entry if there's none selected yet after a while
     else {
       window.setTimeout(function() {
-        // If after a certain delay the preferred script still wasn't received,
+        // If after a certain delay the preferred source still wasn't received,
         // just give up on waiting and display the first entry.
-        if (!DebuggerView.Scripts.selected) {
-          DebuggerView.Scripts.selectIndex(0);
-          // Selecting a script would make it "preferred", which is a lie here,
-          // because we're only displaying a script to make sure there's always
-          // something available in the SourceEditor and the scripts menulist.
-          // Hence the need revert back to the initial preferred script, just
-          // in case it will be available soon.
-          DebuggerView.Scripts.preferredScriptUrl = preferredScriptUrl;
+        if (!container.selectedValue) {
+          container.selectedIndex = 0;
         }
       }, NEW_SCRIPT_DISPLAY_DELAY);
     }
 
-    // If there are any stored breakpoints for this script, display them again,
-    // both in the editor and the pane.
-    for each (let breakpoint in DebuggerController.Breakpoints.store) {
-      if (breakpoint.location.url == aPacket.url) {
-        DebuggerController.Breakpoints.displayBreakpoint(breakpoint);
-      }
-    }
-
-    DebuggerController.dispatchEvent("Debugger:AfterNewScript");
-  },
-
-  /**
-   * Callback for the getScripts() method.
-   */
-  _onScriptsAdded: function SS__onScriptsAdded(aResponse) {
-    for each (let script in aResponse.scripts) {
-      this._addScript(script, false);
-    }
-    DebuggerView.Scripts.commitScripts();
+    // If there are any stored breakpoints for this source, display them again,
+    // both in the editor and the breakpoints pane.
+    DebuggerController.Breakpoints.updateEditorBreakpoints();
     DebuggerController.Breakpoints.updatePaneBreakpoints();
 
-    let preferredScriptUrl = DebuggerView.Scripts.preferredScriptUrl;
-
-    // Select the preferred script if it exists and was part of the response.
-    if (preferredScriptUrl && DebuggerView.Scripts.contains(preferredScriptUrl)) {
-      DebuggerView.Scripts.selectScript(preferredScriptUrl);
-    }
-    // ..or the first entry if there's not one selected yet.
-    else if (!DebuggerView.Scripts.selected) {
-      DebuggerView.Scripts.selectIndex(0);
-    }
-
-    DebuggerController.dispatchEvent("Debugger:AfterScriptsAdded");
-  },
-
-  /**
-   * Called during navigation to clear the currently-loaded scripts.
-   */
-  _onScriptsCleared: function SS__onScriptsCleared() {
-    DebuggerView.GlobalSearch.hideAndEmpty();
-    DebuggerView.GlobalSearch.clearCache();
-    DebuggerView.Scripts.clearSearch();
-    DebuggerView.Scripts.empty();
-    DebuggerView.Breakpoints.emptyText();
-    DebuggerView.editor.setText("");
-  },
-
-  /**
-   * Sets the proper editor mode (JS or HTML) according to the specified
-   * content type, or by determining the type from the URL.
-   *
-   * @param string aUrl
-   *        The script URL.
-   * @param string aContentType [optional]
-   *        The script content type.
-   */
-  _setEditorMode: function SS__setEditorMode(aUrl, aContentType) {
-    if (aContentType) {
-      if (/javascript/.test(aContentType)) {
-        DebuggerView.editor.setMode(SourceEditor.MODES.JAVASCRIPT);
-      } else {
-        DebuggerView.editor.setMode(SourceEditor.MODES.HTML);
-      }
-      return;
-    }
-
-    // Use JS mode for files with .js and .jsm extensions.
-    if (/\.jsm?$/.test(this.trimUrlQuery(aUrl))) {
-      DebuggerView.editor.setMode(SourceEditor.MODES.JAVASCRIPT);
-    } else {
-      DebuggerView.editor.setMode(SourceEditor.MODES.HTML);
-    }
-  },
-
-  /**
-   * Trims the query part or reference identifier of a url string, if necessary.
-   *
-   * @param string aUrl
-   *        The script url.
-   * @return string
-   *         The url with the trimmed query.
-   */
-  trimUrlQuery: function SS_trimUrlQuery(aUrl) {
-    let length = aUrl.length;
-    let q1 = aUrl.indexOf('?');
-    let q2 = aUrl.indexOf('&');
-    let q3 = aUrl.indexOf('#');
-    let q = Math.min(q1 !== -1 ? q1 : length,
-                     q2 !== -1 ? q2 : length,
-                     q3 !== -1 ? q3 : length);
-
-    return aUrl.slice(0, q);
-  },
-
-  /**
-   * Trims the url by shortening it if it exceeds a certain length, adding an
-   * ellipsis at the end.
-   *
-   * @param string aUrl
-   *        The script URL.
-   * @param number aMaxLength [optional]
-   *        The max string length.
-   * @return string
-   *         The shortened url.
-   */
-  trimUrlLength: function SS_trimUrlLength(aUrl, aMaxLength = SCRIPTS_URL_MAX_LENGTH) {
-    if (aUrl.length > aMaxLength) {
-      let ellipsis = Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString);
-      return aUrl.substring(0, aMaxLength) + ellipsis.data;
-    }
-    return aUrl;
+    // Signal that a new script has been added.
+    window.dispatchEvent("Debugger:AfterNewScript");
   },
 
   /**
-   * Trims as much as possible from a url, while keeping the result unique
-   * in the Debugger View scripts container.
-   *
-   * @param string | nsIURL aUrl
-   *        The script URL.
-   * @param string aLabel [optional]
-   *        The resulting label at each step.
-   * @param number aSeq [optional]
-   *        The current iteration step.
-   * @return string
-   *         The resulting label at the final step.
+   * Callback for the debugger's active thread getScripts() method.
    */
-  _trimUrl: function SS__trimUrl(aUrl, aLabel, aSeq) {
-    if (!(aUrl instanceof Ci.nsIURL)) {
-      try {
-        // Use an nsIURL to parse all the url path parts.
-        aUrl = Services.io.newURI(aUrl, null, null).QueryInterface(Ci.nsIURL);
-      } catch (e) {
-        // This doesn't look like a url, or nsIURL can't handle it.
-        return aUrl;
-      }
-    }
-    if (!aSeq) {
-      let name = aUrl.fileName;
-      if (name) {
-        // This is a regular file url, get only the file name (contains the
-        // base name and extension if available).
-
-        // If this url contains an invalid query, unfortunately nsIURL thinks
-        // it's part of the file extension. It must be removed.
-        aLabel = aUrl.fileName.replace(/\&.*/, "");
-      } else {
-        // This is not a file url, hence there is no base name, nor extension.
-        // Proceed using other available information.
-        aLabel = "";
-      }
-      aSeq = 1;
+  _onScriptsAdded: function SS__onScriptsAdded(aResponse) {
+    // Add all the sources in the debugger view sources container.
+    for (let script of aResponse.scripts) {
+      this._addSource(script);
     }
 
-    // If we have a label and it doesn't start with a query...
-    if (aLabel && aLabel.indexOf("?") !== 0) {
+    let container = DebuggerView.Sources;
+    let preferredValue = container.preferredValue;
+
+    // Flushes all the prepared sources into the sources container.
+    container.commit();
 
-      if (DebuggerView.Scripts.containsIgnoringQuery(aUrl.spec)) {
-        // A page may contain multiple requests to the same url but with different
-        // queries. It would be redundant to show each one.
-        return aLabel;
-      }
-      if (!DebuggerView.Scripts.containsLabel(aLabel)) {
-        // We found the shortest unique label for the url.
-        return aLabel;
-      }
+    // Select the preferred source if it exists and was part of the response.
+    if (container.containsValue(preferredValue)) {
+      container.selectedValue = preferredValue;
+    }
+    // ..or the first entry if there's no one selected yet.
+    else if (!container.selectedValue) {
+      container.selectedIndex = 0;
     }
 
-    // Append the url query.
-    if (aSeq === 1) {
-      let query = aUrl.query;
-      if (query) {
-        return this._trimUrl(aUrl, aLabel + "?" + query, aSeq + 1);
-      }
-      aSeq++;
-    }
-    // Append the url reference.
-    if (aSeq === 2) {
-      let ref = aUrl.ref;
-      if (ref) {
-        return this._trimUrl(aUrl, aLabel + "#" + aUrl.ref, aSeq + 1);
-      }
-      aSeq++;
-    }
-    // Prepend the url directory.
-    if (aSeq === 3) {
-      let dir = aUrl.directory;
-      if (dir) {
-        return this._trimUrl(aUrl, dir.replace(/^\//, "") + aLabel, aSeq + 1);
-      }
-      aSeq++;
-    }
-    // Prepend the hostname and port number.
-    if (aSeq === 4) {
-      let host = aUrl.hostPort;
-      if (host) {
-        return this._trimUrl(aUrl, host + "/" + aLabel, aSeq + 1);
-      }
-      aSeq++;
-    }
-    // Use the whole url spec but ignoring the reference.
-    if (aSeq === 5) {
-      return this._trimUrl(aUrl, aUrl.specIgnoringRef, aSeq + 1);
-    }
-    // Give up.
-    return aUrl.spec;
+    // If there are any stored breakpoints for the sources, display them again,
+    // both in the editor and the breakpoints pane.
+    DebuggerController.Breakpoints.updateEditorBreakpoints();
+    DebuggerController.Breakpoints.updatePaneBreakpoints();
+
+    // Signal that scripts have been added.
+    window.dispatchEvent("Debugger:AfterScriptsAdded");
   },
 
   /**
-   * Gets a unique, simplified label from a script url.
-   *
-   * @param string aUrl
-   *        The script url.
-   * @param string aHref
-   *        The content location href to be used. If unspecified, it will
-   *        default to the script url prepath.
-   * @return string
-   *         The simplified label.
-   */
-  getScriptLabel: function SS_getScriptLabel(aUrl, aHref) {
-    if (!this._labelsCache[aUrl]) {
-      this._labelsCache[aUrl] = this.trimUrlLength(this._trimUrl(aUrl));
-    }
-    return this._labelsCache[aUrl];
-  },
-
-  /**
-   * Clears the labels cache, populated by SS_getScriptLabel.
-   * This should be done every time the content location changes.
-   */
-  _clearLabelsCache: function SS__clearLabelsCache() {
-    this._labelsCache = {};
-  },
-
-  /**
-   * Add the specified script to the list.
+   * Add the specified source to the debugger view sources list.
    *
    * @param object aScript
-   *        The script object coming from the active thread.
-   * @param boolean aForceFlag
-   *        True to force the script to be immediately added.
-   */
-  _addScript: function SS__addScript(aScript, aForceFlag) {
-    DebuggerView.Scripts.addScript(
-      this.getScriptLabel(aScript.url), aScript, aForceFlag);
-  },
-
-  /**
-   * Load the editor with the script text if available, otherwise fire an event
-   * to load and display the script text.
-   *
-   * @param object aScript
-   *        The script object coming from the active thread.
+   *        The source object coming from the active thread.
    * @param object aOptions [optional]
-   *        Additional options for showing the script. Supported options:
-   *        - targetLine: place the editor at the given line number.
+   *        Additional options for adding the source. Supported options:
+   *        - forced: force the source to be immediately added
    */
-  showScript: function SS_showScript(aScript, aOptions = {}) {
-    if (aScript.loaded) {
-      // Scripts may take a longer time to load than expected, therefore the
-      // required one may change at any time after a previous request was made.
-      if (aScript.url === DebuggerView.Scripts.selected) {
-        this._onShowScript(aScript, aOptions);
-      }
-      return;
-    }
+  _addSource: function SS__addSource(aSource, aOptions = {}) {
+    let url = aSource.url;
+    let label = SourceUtils.getSourceLabel(url);
 
-    let editor = DebuggerView.editor;
-    editor.setMode(SourceEditor.MODES.TEXT);
-    editor.setText(L10N.getStr("loadingText"));
-    editor.resetUndo();
-
-    // Notify that we need to load a script file.
-    DebuggerController.dispatchEvent("Debugger:LoadSource", {
-      script: aScript,
-      options: aOptions
+    DebuggerView.Sources.push(label, url, {
+      forced: aOptions.forced,
+      attachment: aSource
     });
   },
 
   /**
-   * Display the script source once it loads.
+   * Gets a specified source's text.
    *
-   * @private
-   * @param object aScript
-   *        The script object coming from the active thread.
-   * @param object aOptions [optional]
-   *        Additional options for showing the script. Supported options:
-   *        - targetLine: place the editor at the given line number.
-   */
-  _onShowScript: function SS__onShowScript(aScript, aOptions = {}) {
-    if (aScript.text.length < SYNTAX_HIGHLIGHT_MAX_FILE_SIZE) {
-      this._setEditorMode(aScript.url, aScript.contentType);
-    }
-
-    let editor = DebuggerView.editor;
-    editor.setText(aScript.text);
-    editor.resetUndo();
-
-    DebuggerController.Breakpoints.updateEditorBreakpoints();
-    DebuggerController.StackFrames.updateEditorLocation();
-
-    // Handle any additional options for showing the script.
-    if (aOptions.targetLine) {
-      editor.setCaretPosition(aOptions.targetLine - 1);
-    }
-
-    // Notify that we shown script file.
-    DebuggerController.dispatchEvent("Debugger:ScriptShown", {
-      url: aScript.url
-    });
-  },
-
-  /**
-   * Handles notifications to load a source script.
+   * @param object aSource
+   *        The source object coming from the active thread.
+   * @param function aCallback
+   *        Function called after the source text has been loaded.
    */
-  _onLoadSource: function SS__onLoadSource(aEvent) {
-    let script = aEvent.detail.script;
-    let options = aEvent.detail.options;
-
-    let sourceClient = this.activeThread.source(script.source);
-    sourceClient.source(function (aResponse) {
-      if (aResponse.error) {
-        return this._logError(script.url, -1);
-      }
-
-      this._onLoadSourceFinished(script.url,
-                                 aResponse.source,
-                                 options);
-    }.bind(this));
-  },
-
-  /**
-   * Called when a script's source has been loaded.
-   *
-   * @private
-   * @param string aScriptUrl
-   *        The URL of the source script.
-   * @param string aSourceText
-   *        The text of the source script.
-   * @param object aOptions [optional]
-   *        Additional options for showing the script. Supported options:
-   *        - targetLine: place the editor at the given line number.
-   */
-  _onLoadSourceFinished:
-  function SS__onLoadSourceFinished(aScriptUrl, aSourceText, aOptions) {
-    let element = DebuggerView.Scripts.getScriptByLocation(aScriptUrl);
-
-    // Tab navigated before we got a chance to finish loading and displaying
-    // the source. The outcome is that the expected url is not present anymore
-    // in the scripts container, hence the original script object coming from
-    // the active thread no longer exists. There's really nothing that needs
-    // to be done in this case, nor something that can be currently avoided.
-    if (!element) {
+  getText: function SS_getText(aSource, aCallback) {
+    // If already loaded, return the source text immediately.
+    if (aSource.loaded) {
+      aCallback(aSource.url, aSource.text);
       return;
     }
 
-    let script = element.getUserData("sourceScript");
-
-    script.loaded = true;
-    script.text = aSourceText;
-    element.setUserData("sourceScript", script, null);
-
-    if (aOptions.silent) {
-      aOptions.callback && aOptions.callback(aScriptUrl, aSourceText);
-      return;
-    }
-
-    this.showScript(script, aOptions);
-  },
-
-  /**
-   * Gets the text in a 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 text
-   */
-  getLineText: function SS_getLineText(aLine) {
-    let editor = DebuggerView.editor;
-    let line = aLine || editor.getCaretPosition().line;
-    let start = editor.getLineStart(line);
-    let end = editor.getLineEnd(line);
-    return editor.getText(start, end);
-  },
-
-  /**
-   * Log an error message in the error console when a script fails to load.
-   *
-   * @param string aUrl
-   *        The URL of the source script.
-   * @param string aStatus
-   *        The failure status code.
-   */
-  _logError: function SS__logError(aUrl, aStatus) {
-    Cu.reportError(L10N.getFormatStr("loadingError", [aUrl, aStatus]));
-  },
+    // Get the source text from the active thread.
+    this.activeThread.source(aSource.source).source(function(aResponse) {
+      if (aResponse.error) {
+        Cu.reportError("Error loading " + aUrl);
+        return;
+      }
+      aCallback(aSource.url, aResponse.source);
+    });
+  }
 };
 
 /**
  * Handles all the breakpoints in the current debugger.
  */
 function Breakpoints() {
   this._onEditorBreakpointChange = this._onEditorBreakpointChange.bind(this);
   this._onEditorBreakpointAdd = this._onEditorBreakpointAdd.bind(this);
   this._onEditorBreakpointRemove = this._onEditorBreakpointRemove.bind(this);
   this.addBreakpoint = this.addBreakpoint.bind(this);
   this.removeBreakpoint = this.removeBreakpoint.bind(this);
   this.getBreakpoint = this.getBreakpoint.bind(this);
 }
 
 Breakpoints.prototype = {
+  get activeThread() DebuggerController.ThreadState.activeThread,
+  get editor() DebuggerView.editor,
+
+  /**
+   * The list of breakpoints in the debugger as tracked by the current
+   * debugger instance. This is an object where the values are BreakpointActor
+   * objects received from the client, while the keys are actor names, for
+   * example "conn0.breakpoint3".
+   */
+  store: {},
 
   /**
    * Skip editor breakpoint change events.
    *
    * This property tells the source editor event handler to skip handling of
    * the BREAKPOINT_CHANGE events. This is used when the debugger adds/removes
    * breakpoints from the editor. Typically, the BREAKPOINT_CHANGE event handler
    * adds/removes events from the debugger, but when breakpoints are added from
    * the public debugger API, we need to do things in reverse.
    *
    * This implementation relies on the fact that the source editor fires the
    * BREAKPOINT_CHANGE events synchronously.
-   *
-   * @private
-   * @type boolean
    */
-  _skipEditorBreakpointChange: false,
+  _skipEditorBreakpointCallbacks: false,
 
   /**
-   * The list of breakpoints in the debugger as tracked by the current
-   * debugger instance. This is an object where the values are BreakpointActor
-   * objects received from the client, while the keys are actor names, for
-   * example "conn0.breakpoint3".
-   *
-   * @type object
-   */
-  store: {},
-
-  /**
-   * Gets the current thread the client has connected to.
-   */
-  get activeThread() {
-    return DebuggerController.ThreadState.activeThread;
-  },
-
-  /**
-   * Gets the source editor in the debugger view.
-   */
-  get editor() {
-    return DebuggerView.editor;
-  },
-
-  /**
-   * Sets up the source editor breakpoint handlers.
+   * Adds the source editor breakpoint handlers.
    */
   initialize: function BP_initialize() {
     this.editor.addEventListener(
       SourceEditor.EVENTS.BREAKPOINT_CHANGE, this._onEditorBreakpointChange);
   },
 
   /**
-   * Removes all currently added breakpoints.
+   * Removes the source editor breakpoint handlers & all the added breakpoints.
    */
   destroy: function BP_destroy() {
-    for each (let breakpoint in this.store) {
-      this.removeBreakpoint(breakpoint);
+    this.editor.removeEventListener(
+      SourceEditor.EVENTS.BREAKPOINT_CHANGE, this._onEditorBreakpointChange);
+
+    for each (let breakpointClient in this.store) {
+      this.removeBreakpoint(breakpointClient);
     }
   },
 
   /**
    * Event handler for breakpoint changes that happen in the editor. This
-   * function syncs the breakpoint changes in the editor to those in the
-   * debugger.
+   * function syncs the breakpoints in the editor to those in the debugger.
    *
-   * @private
    * @param object aEvent
    *        The SourceEditor.EVENTS.BREAKPOINT_CHANGE event object.
    */
   _onEditorBreakpointChange: function BP__onEditorBreakpointChange(aEvent) {
-    if (this._skipEditorBreakpointChange) {
+    if (this._skipEditorBreakpointCallbacks) {
       return;
     }
-
+    this._skipEditorBreakpointCallbacks = true;
     aEvent.added.forEach(this._onEditorBreakpointAdd, this);
     aEvent.removed.forEach(this._onEditorBreakpointRemove, this);
+    this._skipEditorBreakpointCallbacks = false;
   },
 
   /**
    * Event handler for new breakpoints that come from the editor.
    *
-   * @private
-   * @param object aBreakpoint
+   * @param object aEditorBreakpoint
    *        The breakpoint object coming from the editor.
    */
-  _onEditorBreakpointAdd: function BP__onEditorBreakpointAdd(aBreakpoint) {
-    let url = DebuggerView.Scripts.selected;
-    if (!url) {
-      return;
-    }
-
-    let line = aBreakpoint.line + 1;
+  _onEditorBreakpointAdd: function BP__onEditorBreakpointAdd(aEditorBreakpoint) {
+    let url = DebuggerView.Sources.selectedValue;
+    let line = aEditorBreakpoint.line + 1;
 
-    this.addBreakpoint({ url: url, line: line }, function (aBp) {
-      if (aBp.requestedLocation) {
-        this.editor.removeBreakpoint(aBp.requestedLocation.line - 1);
-
-        let breakpoints = this.getBreakpoints(url, aBp.location.line);
-        if (breakpoints.length > 1) {
-          this.removeBreakpoint(breakpoints[0], null, true, true);
-        } else {
-          this.updateEditorBreakpoints();
-        }
+    this.addBreakpoint({ url: url, line: line }, function(aBreakpointClient) {
+      // If the breakpoint client has an "actualLocation" attached, then
+      // the original requested placement for the breakpoint wasn't accepted.
+      // In this case, we need to update the editor with the new location.
+      if (aBreakpointClient.actualLocation) {
+        this.editor.removeBreakpoint(line - 1);
+        this.editor.addBreakpoint(aBreakpointClient.actualLocation.line - 1);
       }
-    }.bind(this), true);
+    }.bind(this));
   },
 
   /**
    * Event handler for breakpoints that are removed from the editor.
    *
-   * @private
-   * @param object aBreakpoint
+   * @param object aEditorBreakpoint
    *        The breakpoint object that was removed from the editor.
    */
-  _onEditorBreakpointRemove: function BP__onEditorBreakpointRemove(aBreakpoint) {
-    let url = DebuggerView.Scripts.selected;
-    if (!url) {
-      return;
-    }
+  _onEditorBreakpointRemove: function BP__onEditorBreakpointRemove(aEditorBreakpoint) {
+    let url = DebuggerView.Sources.selectedValue;
+    let line = aEditorBreakpoint.line + 1;
 
-    let line = aBreakpoint.line + 1;
-
-    let breakpoint = this.getBreakpoint(url, line);
-    if (breakpoint) {
-      this.removeBreakpoint(breakpoint, null, true);
-    }
+    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.
+   * breakpoints in the debugger and adds them back into the editor view.
+   * This is invoked when the selected script is changed.
    */
   updateEditorBreakpoints: function BP_updateEditorBreakpoints() {
-    let url = DebuggerView.Scripts.selected;
-    if (!url) {
-      return;
-    }
-
-    this._skipEditorBreakpointChange = true;
-    for each (let breakpoint in this.store) {
-      if (breakpoint.location.url == url) {
-        this.editor.addBreakpoint(breakpoint.location.line - 1);
+    for each (let breakpointClient in this.store) {
+      if (DebuggerView.Sources.selectedValue == breakpointClient.location.url) {
+        this._showBreakpoint(breakpointClient, { noPaneUpdate: true });
       }
     }
-    this._skipEditorBreakpointChange = false;
   },
 
   /**
-   * Update the breakpoints in the pane view. This function is invoked when the
-   * scripts are added (typically after a page navigation).
+   * 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.
    */
   updatePaneBreakpoints: function BP_updatePaneBreakpoints() {
-    let url = DebuggerView.Scripts.selected;
-    if (!url) {
-      return;
-    }
-
-    this._skipEditorBreakpointChange = true;
-    for each (let breakpoint in this.store) {
-      if (DebuggerView.Scripts.contains(breakpoint.location.url)) {
-        this.displayBreakpoint(breakpoint, true);
+    for each (let breakpointClient in this.store) {
+      if (DebuggerView.Sources.containsValue(breakpointClient.location.url)) {
+        this._showBreakpoint(breakpointClient, { noEditorUpdate: true });
       }
     }
-    this._skipEditorBreakpointChange = false;
   },
 
   /**
    * Add a breakpoint.
    *
    * @param object aLocation
    *        The location where you want the breakpoint. This object must have
    *        two properties:
-   *          - url - the URL of the script.
+   *          - url - the url of the source.
    *          - line - the line number (starting from 1).
-   * @param function [aCallback]
+   * @param function aCallback [optional]
    *        Optional function to invoke once the breakpoint is added. The
    *        callback is invoked with two arguments:
-   *          - aBreakpointClient - the BreakpointActor client object, if the
-   *          breakpoint has been added successfully.
-   *          - aResponseError - if there was any error.
-   * @param boolean [aNoEditorUpdate=false]
-   *        Tells if you want to skip editor updates. Typically the editor is
-   *        updated to visually indicate that a breakpoint has been added.
-   * @param boolean [aNoPaneUpdate=false]
-   *        Tells if you want to skip any breakpoint pane updates.
+   *          - aBreakpointClient: the BreakpointActor client object
+   *          - aResponseError: if there was any error
+   * @param object aFlags [optional]
+   *        An object containing some of the following boolean properties:
+   *          - noEditorUpdate: tells if you want to skip editor updates
+   *          - noPaneUpdate: tells if you want to skip breakpoint pane updates
    */
   addBreakpoint:
-  function BP_addBreakpoint(aLocation, aCallback, aNoEditorUpdate, aNoPaneUpdate) {
-    let breakpoint = this.getBreakpoint(aLocation.url, aLocation.line);
-    if (breakpoint) {
-      aCallback && aCallback(breakpoint);
+  function BP_addBreakpoint(aLocation, aCallback, aFlags = {}) {
+    let breakpointClient = this.getBreakpoint(aLocation.url, aLocation.line);
+
+    // If the breakpoint was already added, callback immediately.
+    if (breakpointClient) {
+      aCallback && aCallback(breakpointClient);
       return;
     }
 
-    this.activeThread.setBreakpoint(aLocation, function(aResponse, aBpClient) {
-      let loc = aResponse.actualLocation;
+    this.activeThread.setBreakpoint(aLocation, function(aResponse, aBreakpointClient) {
+      let { url, line } = aResponse.actualLocation || aLocation;
 
-      if (loc) {
-        aBpClient.requestedLocation = {
-          line: aBpClient.location.line,
-          url: aBpClient.location.url
-        };
-
-        aBpClient.location.line = loc.line;
-        aBpClient.location.url = loc.url;
+      // Prevent this new breakpoint from being repositioned on top of an
+      // already existing one.
+      if (this.getBreakpoint(url, line)) {
+        this._hideBreakpoint(aBreakpointClient);
+        aBreakpointClient.remove();
+        return;
       }
 
-      this.store[aBpClient.actor] = aBpClient;
-      this.displayBreakpoint(aBpClient, aNoEditorUpdate, aNoPaneUpdate);
-      aCallback && aCallback(aBpClient, aResponse.error);
-    }.bind(this));
-  },
+      // If the breakpoint response has an "actualLocation" attached, then
+      // the original requested placement for the breakpoint wasn't accepted.
+      if (aResponse.actualLocation) {
+        // Store the originally requested location in case it's ever needed.
+        aBreakpointClient.requestedLocation = {
+          url: aBreakpointClient.location.url,
+          line: aBreakpointClient.location.line
+        };
+        // Store the response actual location to be used.
+        aBreakpointClient.actualLocation = aResponse.actualLocation;
+        // Update the breakpoint client with the actual location.
+        aBreakpointClient.location.url = aResponse.actualLocation.url;
+        aBreakpointClient.location.line = aResponse.actualLocation.line;
+      }
 
-  /**
-   * Update the editor to display the specified breakpoint in the gutter.
-   *
-   * @param object aBreakpoint
-   *        The breakpoint you want to display.
-   * @param boolean [aNoEditorUpdate=false]
-   *        Tells if you want to skip editor updates. Typically the editor is
-   *        updated to visually indicate that a breakpoint has been added.
-   * @param boolean [aNoPaneUpdate=false]
-   *        Tells if you want to skip any breakpoint pane updates.
-   */
-  displayBreakpoint:
-  function BP_displayBreakpoint(aBreakpoint, aNoEditorUpdate, aNoPaneUpdate) {
-    if (!aNoEditorUpdate) {
-      let url = DebuggerView.Scripts.selected;
-      if (url == aBreakpoint.location.url) {
-        this._skipEditorBreakpointChange = true;
-        this.editor.addBreakpoint(aBreakpoint.location.line - 1);
-        this._skipEditorBreakpointChange = false;
-      }
-    }
-    if (!aNoPaneUpdate) {
-      let { url: url, line: line } = aBreakpoint.location;
+      // Remember the breakpoint client in the store.
+      this.store[aBreakpointClient.actor] = aBreakpointClient;
 
-      if (!aBreakpoint.lineText || !aBreakpoint.lineInfo) {
-        let scripts = DebuggerController.SourceScripts;
-        aBreakpoint.lineText = scripts.getLineText(line - 1);
-        aBreakpoint.lineInfo = scripts.getScriptLabel(url) + ":" + line;
-      }
-      DebuggerView.Breakpoints.addBreakpoint(
-        aBreakpoint.actor,
-        aBreakpoint.lineInfo,
-        aBreakpoint.lineText, url, line);
-    }
+      // Preserve some information about the breakpoint's source url and line
+      // to display in the breakpoints pane.
+      aBreakpointClient.lineText = DebuggerView.getEditorLine(line - 1);
+      aBreakpointClient.lineInfo = SourceUtils.getSourceLabel(url) + ":" + line;
+
+      // Show the breakpoint in the editor and breakpoints pane.
+      this._showBreakpoint(aBreakpointClient, aFlags);
+
+      // We're done here.
+      aCallback && aCallback(aBreakpointClient, aResponse.error);
+    }.bind(this));
   },
 
   /**
    * Remove a breakpoint.
    *
-   * @param object aBreakpoint
-   *        The breakpoint you want to remove.
-   * @param function [aCallback]
+   * @param object aBreakpointClient
+   *        The BreakpointActor client object to remove.
+   * @param function aCallback [optional]
    *        Optional function to invoke once the breakpoint is removed. The
-   *        callback is invoked with one argument: the breakpoint location
-   *        object which holds the url and line properties.
-   * @param boolean [aNoEditorUpdate=false]
-   *        Tells if you want to skip editor updates. Typically the editor is
-   *        updated to visually indicate that a breakpoint has been removed.
-   * @param boolean [aNoPaneUpdate=false]
-   *        Tells if you want to skip any breakpoint pane updates.
+   *        callback is invoked with one argument
+   *          - aBreakpointClient: the breakpoint location (url and line)
+   * @param object aFlags [optional]
+   *        An object containing some of the following boolean properties:
+   *          - noEditorUpdate: tells if you want to skip editor updates
+   *          - noPaneUpdate: tells if you want to skip breakpoint pane updates
    */
   removeBreakpoint:
-  function BP_removeBreakpoint(aBreakpoint, aCallback, aNoEditorUpdate, aNoPaneUpdate) {
-    if (!(aBreakpoint.actor in this.store)) {
-      aCallback && aCallback(aBreakpoint.location);
+  function BP_removeBreakpoint(aBreakpointClient, aCallback, aFlags = {}) {
+    let breakpointActor = (aBreakpointClient || {}).actor;
+
+    // If the breakpoint was already removed, callback immediately.
+    if (!this.store[breakpointActor]) {
+      aCallback && aCallback(aBreakpointClient.location);
       return;
     }
 
-    aBreakpoint.remove(function() {
-      delete this.store[aBreakpoint.actor];
+    aBreakpointClient.remove(function() {
+      // Delete the breakpoint client from the store.
+      delete this.store[breakpointActor];
+
+      // Hide the breakpoint from the editor and breakpoints pane.
+      this._hideBreakpoint(aBreakpointClient, aFlags);
+
+      // We're done here.
+      aCallback && aCallback(aBreakpointClient.location);
+    }.bind(this));
+  },
+
+  /**
+   * Update the editor and breakpoints pane to show a specified breakpoint.
+   *
+   * @param object aBreakpointClient
+   *        The BreakpointActor client object to show.
+   * @param object aFlags [optional]
+   *        An object containing some of the following boolean properties:
+   *          - noEditorUpdate: tells if you want to skip editor updates
+   *          - noPaneUpdate: tells if you want to skip breakpoint pane updates
+   */
+  _showBreakpoint: function BP__showBreakpoint(aBreakpointClient, aFlags = {}) {
+    let currentSourceUrl = DebuggerView.Sources.selectedValue;
+    let { url, line } = aBreakpointClient.location;
 
-      if (!aNoEditorUpdate) {
-        let url = DebuggerView.Scripts.selected;
-        if (url == aBreakpoint.location.url) {
-          this._skipEditorBreakpointChange = true;
-          this.editor.removeBreakpoint(aBreakpoint.location.line - 1);
-          this._skipEditorBreakpointChange = false;
-        }
+    if (!aFlags.noEditorUpdate) {
+      if (url == currentSourceUrl) {
+        this._skipEditorBreakpointCallbacks = true;
+        this.editor.addBreakpoint(line - 1);
+        this._skipEditorBreakpointCallbacks = false;
       }
-      if (!aNoPaneUpdate) {
-        DebuggerView.Breakpoints.removeBreakpoint(aBreakpoint.actor);
+    }
+    if (!aFlags.noPaneUpdate) {
+      let { lineText, lineInfo } = aBreakpointClient;
+      let actor = aBreakpointClient.actor;
+      DebuggerView.Breakpoints.addBreakpoint(lineInfo, lineText, url, line, actor);
+    }
+  },
+
+  /**
+   * Update the editor and breakpoints pane to hide a specified breakpoint.
+   *
+   * @param object aBreakpointClient
+   *        The BreakpointActor client object to hide.
+   * @param object aFlags [optional]
+   *        An object containing some of the following boolean properties:
+   *          - noEditorUpdate: tells if you want to skip editor updates
+   *          - noPaneUpdate: tells if you want to skip breakpoint pane updates
+   */
+  _hideBreakpoint: function BP__hideBreakpoint(aBreakpointClient, aFlags = {}) {
+    let currentSourceUrl = DebuggerView.Sources.selectedValue;
+    let { url, line } = aBreakpointClient.location;
+
+    if (!aFlags.noEditorUpdate) {
+      if (url == currentSourceUrl) {
+        this._skipEditorBreakpointCallbacks = true;
+        this.editor.removeBreakpoint(line - 1);
+        this._skipEditorBreakpointCallbacks = false;
       }
-
-      aCallback && aCallback(aBreakpoint.location);
-    }.bind(this));
+    }
+    if (!aFlags.noPaneUpdate) {
+      DebuggerView.Breakpoints.removeBreakpoint(url, line);
+    }
   },
 
   /**
    * Get the breakpoint 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.
    */
   getBreakpoint: function BP_getBreakpoint(aUrl, aLine) {
-    for each (let breakpoint in this.store) {
-      if (breakpoint.location.url == aUrl && breakpoint.location.line == aLine) {
-        return breakpoint;
+    for each (let breakpointClient in this.store) {
+      if (breakpointClient.location.url == aUrl &&
+          breakpointClient.location.line == aLine) {
+        return breakpointClient;
       }
     }
     return null;
-  },
-
-  getBreakpoints: function BP_getBreakpoints(aUrl, aLine) {
-    let breakpoints = [];
-
-    for each (let breakpoint in this.store) {
-      if (breakpoint.location.url == aUrl && breakpoint.location.line == aLine) {
-        breakpoints.push(breakpoint);
-      }
-    }
-
-    return breakpoints;
   }
 };
 
 /**
  * Localization convenience methods.
  */
 let L10N = {
-
   /**
    * L10N shortcut function.
    *
    * @param string aName
    * @return string
    */
   getStr: function L10N_getStr(aName) {
     return this.stringBundle.GetStringFromName(aName);
@@ -1719,32 +1185,35 @@ let L10N = {
     return this.stringBundle.formatStringFromName(aName, aArray, aArray.length);
   }
 };
 
 XPCOMUtils.defineLazyGetter(L10N, "stringBundle", function() {
   return Services.strings.createBundle(DBG_STRINGS_URI);
 });
 
+XPCOMUtils.defineLazyGetter(L10N, "ellipsis", function() {
+  return Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString).data;
+});
+
 const STACKFRAMES_WIDTH = "devtools.debugger.ui.stackframes-width";
 const STACKFRAMES_VISIBLE = "devtools.debugger.ui.stackframes-pane-visible";
 const VARIABLES_WIDTH = "devtools.debugger.ui.variables-width";
 const VARIABLES_PANE_VISIBLE = "devtools.debugger.ui.variables-pane-visible";
+const NON_ENUM_VISIBLE = "devtools.debugger.ui.non-enum-visible";
 const REMOTE_AUTO_CONNECT = "devtools.debugger.remote-autoconnect";
 const REMOTE_HOST = "devtools.debugger.remote-host";
 const REMOTE_PORT = "devtools.debugger.remote-port";
 const REMOTE_CONNECTION_RETRIES = "devtools.debugger.remote-connection-retries";
 const REMOTE_TIMEOUT = "devtools.debugger.remote-timeout";
-const NON_ENUM_VISIBLE = "devtools.debugger.ui.non-enum-visible";
 
 /**
  * Shortcuts for accessing various debugger preferences.
  */
 let Prefs = {
-
   /**
    * Gets the preferred stackframes pane width.
    * @return number
    */
   get stackframesWidth() {
     if (this._stackframesWidth === undefined) {
       this._stackframesWidth = Services.prefs.getIntPref(STACKFRAMES_WIDTH);
     }
@@ -1893,30 +1362,83 @@ XPCOMUtils.defineLazyGetter(Prefs, "remo
  * Gets the remote debugging connection timeout (in milliseconds).
  * @return number
  */
 XPCOMUtils.defineLazyGetter(Prefs, "remoteTimeout", function() {
   return Services.prefs.getIntPref(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;
+});
+
+/**
+ * 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;
+});
+
+/**
  * Preliminary setup for the DebuggerController object.
  */
-DebuggerController.init();
+DebuggerController.initialize();
 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 in tests.
+ * Export some properties to the global scope for easier access.
  */
-Object.defineProperty(window, "gClient", {
-  get: function() { return DebuggerController.client; }
+Object.defineProperties(window, {
+  "gClient": {
+    get: function() DebuggerController.client
+  },
+  "gTabClient": {
+    get: function() DebuggerController.tabClient
+  },
+  "gThreadClient": {
+    get: function() DebuggerController.activeThread
+  },
+  "gThreadState": {
+    get: function() DebuggerController.ThreadState
+  },
+  "gStackFrames": {
+    get: function() DebuggerController.StackFrames
+  },
+  "gSourceScripts": {
+    get: function() DebuggerController.SourceScripts
+  },
+  "gBreakpoints": {
+    get: function() DebuggerController.Breakpoints
+  },
+  "gCallStackPageSize": {
+    get: function() CALL_STACK_PAGE_SIZE,
+  },
+  "dispatchEvent": {
+    get: function() DebuggerController.dispatchEvent,
+  },
+  "editor": {
+    get: function() DebuggerView.editor
+  }
 });
 
-Object.defineProperty(window, "gTabClient", {
-  get: function() { return DebuggerController.tabClient; }
-});
+/**
+ * Helper method for debugging.
+ * @param string
+ */
+function dumpn(str) {
+  if (wantLogging) {
+    dump("DBG-FRONTEND: " + str + "\n");
+  }
+}
 
-Object.defineProperty(window, "gThreadClient", {
-  get: function() { return DebuggerController.activeThread; }
-});
+let wantLogging = Services.prefs.getBoolPref("devtools.debugger.log");
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/debugger-panes.js
@@ -0,0 +1,1532 @@
+/* -*- 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";
+
+/**
+ * Functions handling the stackframes UI.
+ */
+function StackFramesView() {
+  dumpn("StackFramesView was instantiated");
+  MenuContainer.call(this);
+  this._onClick = this._onClick.bind(this);
+  this._onScroll = this._onScroll.bind(this);
+}
+
+create({ constructor: StackFramesView, proto: MenuContainer.prototype }, {
+  /**
+   * Initialization function, called when the debugger is started.
+   */
+  initialize: function DVSF_initialize() {
+    dumpn("Initializing the StackFramesView");
+    this._container = new StackList(document.getElementById("stackframes"));
+    this._container.emptyText = L10N.getStr("emptyStackText");
+
+    this._container.addEventListener("click", this._onClick, false);
+    this._container.addEventListener("scroll", this._onScroll, true);
+    window.addEventListener("resize", this._onScroll, true);
+
+    this._cache = new Map();
+  },
+
+  /**
+   * Destruction function, called when the debugger is closed.
+   */
+  destroy: function DVSF_destroy() {
+    dumpn("Destroying the StackFramesView");
+    this._container.removeEventListener("click", this._onClick, true);
+    this._container.removeEventListener("scroll", this._onScroll, true);
+    window.removeEventListener("resize", this._onScroll, true);
+  },
+
+  /**
+   * Adds a frame in this stackframes container.
+   *
+   * @param string aFrameName
+   *        Name to be displayed in the list.
+   * @param string aFrameDetails
+   *        Details to be displayed in the list.
+   * @param number aDepth
+   *        The frame depth specified by the debugger.
+   * @param object aOptions [optional]
+   *        Additional options or flags supported by this operation:
+   *          - attachment: any kind of primitive/object to attach
+   */
+  addFrame:
+  function DVSF_addFrame(aFrameName, aFrameDetails, aDepth, aOptions = {}) {
+    // Stackframes are UI elements which benefit from visible panes.
+    DebuggerView.showPanesIfPreffered();
+
+    // Append a stackframe item to this container.
+    let stackframeItem = this.push(aFrameName, aFrameDetails, {
+      forced: true,
+      unsorted: true,
+      relaxed: true,
+      attachment: aOptions.attachment
+    });
+
+    // Check if stackframe was already appended.
+    if (!stackframeItem) {
+      return;
+    }
+
+    let element = stackframeItem.target;
+    element.id = "stackframe-" + aDepth;
+    element.className = "dbg-stackframe list-item";
+    element.labelNode.className = "dbg-stackframe-name plain";
+    element.valueNode.className = "dbg-stackframe-details plain";
+
+    this._cache.set(aDepth, stackframeItem);
+  },
+
+  /**
+   * Highlights a frame in this stackframes container.
+   *
+   * @param number aDepth
+   *        The frame depth specified by the debugger controller.
+   */
+  highlightFrame: function DVSF_highlightFrame(aDepth) {
+    this._container.selectedItem = this._cache.get(aDepth).target;
+  },
+
+  /**
+   * Specifies if the active thread has more frames that need to be loaded.
+   */
+  dirty: false,
+
+  /**
+   * The click listener for the stackframes container.
+   */
+  _onClick: function DVSF__onClick(e) {
+    let item = this.getItemForElement(e.target);
+    DebuggerController.StackFrames.selectFrame(item.attachment.depth);
+  },
+
+  /**
+   * The scroll listener for the stackframes container.
+   */
+  _onScroll: function DVSF__onScroll() {
+    // Update the stackframes container only if we have to.
+    if (this.dirty) {
+      let list = this._container._list;
+
+      // If the stackframes container was scrolled past 95% of the height,
+      // load more content.
+      if (list.scrollTop >= (list.scrollHeight - list.clientHeight) * 0.95) {
+        DebuggerController.StackFrames.addMoreFrames();
+        this.dirty = false;
+      }
+    }
+  },
+
+  _cache: null
+});
+
+/**
+ * Utility functions for handling stackframes.
+ */
+let StackFrameUtils = {
+  /**
+   * Create a textual representation for the specified stack frame
+   * to display in the stack frame container.
+   *
+   * @param object aFrame
+   *        The stack frame to label.
+   */
+  getFrameTitle: function SFU_getFrameTitle(aFrame) {
+    if (aFrame.type == "call") {
+      return aFrame.calleeName || "(anonymous)";
+    }
+    return "(" + aFrame.type + ")";
+  }
+};
+
+/**
+ * Functions handling the breakpoints UI.
+ */
+function BreakpointsView() {
+  dumpn("BreakpointsView was instantiated");
+  MenuContainer.call(this);
+  this._createItemView = this._createItemView.bind(this);
+  this._onBreakpointRemoved = this._onBreakpointRemoved.bind(this);
+  this._onClick = this._onClick.bind(this);
+  this._onCheckboxClick = this._onCheckboxClick.bind(this);
+}
+
+create({ constructor: BreakpointsView, proto: MenuContainer.prototype }, {
+  /**
+   * Initialization function, called when the debugger is started.
+   */
+  initialize: function DVB_initialize() {
+    dumpn("Initializing the BreakpointsView");
+    this._container = new StackList(document.getElementById("breakpoints"));
+    this._popupset = document.getElementById("debugger-popups");
+
+    this._container.emptyText = L10N.getStr("emptyBreakpointsText");
+    this._container.itemFactory = this._createItemView;
+    this._container.uniquenessQualifier = 2;
+    this._container.addEventListener("click", this._onClick, false);
+
+    this._cache = new Map();
+  },
+
+  /**
+   * Destruction function, called when the debugger is closed.
+   */
+  destroy: function DVB_destroy() {
+    dumpn("Destroying the BreakpointsView");
+    this._container.removeEventListener("click", this._onClick, false);
+  },
+
+  /**
+   * Adds a breakpoint in this breakpoints container.
+   *
+   * @param string aLineInfo
+   *        Line information (parent source etc.) to be displayed in the list.
+   * @param string aLineText
+   *        Line text to be displayed in the list.
+   * @param string aSourceLocation
+   *        The breakpoint source location specified by the debugger controller.
+   * @param number aLineNumber
+   *        The breakpoint line number specified by the debugger controller.
+   * @parm string aId
+   *       A breakpoint identifier specified by the debugger controller.
+   */
+  addBreakpoint:
+  function DVB_addBreakpoint(aLineInfo, aLineText, aSourceLocation, aLineNumber, aId) {
+    // Append a breakpoint item to this container.
+    let breakpointItem = this.push(aLineInfo.trim(), aLineText.trim(), {
+      forced: true,
+      attachment: {
+        enabled: true,
+        sourceLocation: aSourceLocation,
+        lineNumber: aLineNumber
+      }
+    });
+
+    // Check if breakpoint was already appended.
+    if (!breakpointItem) {
+      this.enableBreakpoint(aSourceLocation, aLineNumber, { id: aId });
+      return;
+    }
+
+    let element = breakpointItem.target;
+    element.id = "breakpoint-" + aId;
+    element.className = "dbg-breakpoint list-item";
+    element.infoNode.className = "dbg-breakpoint-info plain";
+    element.textNode.className = "dbg-breakpoint-text plain";
+    element.setAttribute("contextmenu", this._createContextMenu(element));
+
+    breakpointItem.finalize = this._onBreakpointRemoved;
+    this._cache.set(this._key(aSourceLocation, aLineNumber), breakpointItem);
+  },
+
+  /**
+   * Removes a breakpoint from this breakpoints container.
+   *
+   * @param string aSourceLocation
+   *        The breakpoint source location.
+   * @param number aLineNumber
+   *        The breakpoint line number.
+   */
+  removeBreakpoint: function DVB_removeBreakpoint(aSourceLocation, aLineNumber) {
+    let breakpointItem = this.getBreakpoint(aSourceLocation, aLineNumber);
+    if (breakpointItem) {
+      this.remove(breakpointItem);
+      this._cache.delete(this._key(aSourceLocation, aLineNumber));
+    }
+  },
+
+  /**
+   * Enables a breakpoint.
+   *
+   * @param string aSourceLocation
+   *        The breakpoint source location.
+   * @param number aLineNumber
+   *        The breakpoint line number.
+   * @param object aOptions [optional]
+   *        Additional options or flags supported by this operation:
+   *          - silent: pass true to not update the checkbox checked state;
+   *                    this is usually necessary when the checked state will
+   *                    be updated automatically (e.g: on a checkbox click).
+   *          - callback: function to invoke once the breakpoint is disabled
+   *          - id: a new id to be applied to the corresponding element node
+   * @return boolean
+   *         True if breakpoint existed and was enabled, false otherwise.
+   */
+  enableBreakpoint:
+  function DVB_enableBreakpoint(aSourceLocation, aLineNumber, aOptions = {}) {
+    let breakpointItem = this.getBreakpoint(aSourceLocation, aLineNumber);
+    if (breakpointItem) {
+      // Set a new id to the corresponding breakpoint element if required.
+      if (aOptions.id) {
+        breakpointItem.target.id = "breakpoint-" + aOptions.id;
+      }
+      // Update the checkbox state if necessary.
+      if (!aOptions.silent) {
+        breakpointItem.target.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
+      });
+
+      // Breakpoint is now enabled.
+      breakpointItem.attachment.enabled = true;
+      return true;
+    }
+    return false;
+  },
+
+  /**
+   * Disables a breakpoint.
+   *
+   * @param string aSourceLocation
+   *        The breakpoint source location.
+   * @param number aLineNumber
+   *        The breakpoint line number.
+   * @param object aOptions [optional]
+   *        Additional options or flags supported by this operation:
+   *          - silent: pass true to not update the checkbox checked state;
+   *                    this is usually necessary when the checked state will
+   *                    be updated automatically (e.g: on a checkbox click).
+   *          - callback: function to invoke once the breakpoint is disabled
+   * @return boolean
+   *         True if breakpoint existed and was disabled, false otherwise.
+   */
+  disableBreakpoint:
+  function DVB_disableBreakpoint(aSourceLocation, aLineNumber, aOptions = {}) {
+    let breakpointItem = this.getBreakpoint(aSourceLocation, aLineNumber);
+    if (breakpointItem) {
+      // Update the checkbox state if necessary.
+      if (!aOptions.silent) {
+        breakpointItem.target.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
+      });
+
+      // Breakpoint is now disabled.
+      breakpointItem.attachment.enabled = false;
+      return true;
+    }
+    return false;
+  },
+
+  /**
+   * Highlights a breakpoint in this breakpoints container.
+   *
+   * @param string aSourceLocation
+   *        The breakpoint source location.
+   * @param number aLineNumber
+   *        The breakpoint line number.
+   */
+  highlightBreakpoint: function DVB_highlightBreakpoint(aSourceLocation, aLineNumber) {
+    let breakpointItem = this.getBreakpoint(aSourceLocation, aLineNumber);
+    if (breakpointItem) {
+      this._container.selectedItem = breakpointItem.target;
+    } else {
+      this._container.selectedIndex = -1;
+    }
+  },
+
+  /**
+   * Unhighlights the current breakpoint in this breakpoints container.
+   */
+  unhighlightBreakpoint: function DVB_highlightBreakpoint() {
+    this.highlightBreakpoint(null);
+  },
+
+  /**
+   * Checks whether a breakpoint with the specified source location and
+   * line number exists in this container, and returns the corresponding item
+   * if true, null otherwise.
+   *
+   * @param string aSourceLocation
+   *        The breakpoint source location.
+   * @param number aLineNumber
+   *        The breakpoint line number.
+   * @return object
+   *         The corresponding item.
+   */
+  getBreakpoint: function DVB_getBreakpoint(aSourceLocation, aLineNumber) {
+    return this._cache.get(this._key(aSourceLocation, aLineNumber));
+  },
+
+  /**
+   * Customization function for creating an item's UI.
+   *
+   * @param nsIDOMNode aElementNode
+   *        The element associated with the displayed item.
+   * @param string aInfo
+   *        The breakpoint's line info.
+   * @param string aText
+   *        The breakpoint's line text.
+   * @param any aAttachment [optional]
+   *        Some attached primitive/object.
+   */
+  _createItemView: function DVB__createItemView(aElementNode, aInfo, aText, aAttachment) {
+    let checkbox = document.createElement("checkbox");
+    checkbox.setAttribute("checked", "true");
+    checkbox.addEventListener("click", this._onCheckboxClick, false);
+
+    let lineInfo = document.createElement("label");
+    lineInfo.setAttribute("value", aInfo);
+    lineInfo.setAttribute("crop", "end");
+
+    let lineText = document.createElement("label");
+    lineText.setAttribute("value", aText);
+    lineText.setAttribute("crop", "end");
+    lineText.setAttribute("tooltiptext",
+      aText.substr(0, BREAKPOINT_LINE_TOOLTIP_MAX_LENGTH));
+
+    let state = document.createElement("vbox");
+    state.className = "state";
+    state.appendChild(checkbox);
+
+    let content = document.createElement("vbox");
+    content.className = "content";
+    content.setAttribute("flex", "1");
+    content.appendChild(lineInfo);
+    content.appendChild(lineText);
+
+    aElementNode.appendChild(state);
+    aElementNode.appendChild(content);
+
+    aElementNode.checkbox = checkbox;
+    aElementNode.infoNode = lineInfo;
+    aElementNode.textNode = lineText;
+  },
+
+  /**
+   * Creates a context menu for a breakpoint element.
+   *
+   * @param nsIDOMNode aElementNode
+   *        The element associated with the displayed breakpoint item.
+   * @return string
+   *         The newly created menupopup id.
+   */
+  _createContextMenu: function DVB__createContextMenu(aElementNode) {
+    let breakpointId = aElementNode.id;
+    let commandsetId = "breakpointCommandset-" + breakpointId;
+    let menupopupId = "breakpointMenupopup-" + breakpointId;
+
+    let commandset = document.createElement("commandset");
+    let menupopup = document.createElement("menupopup");
+    commandset.setAttribute("id", commandsetId);
+    menupopup.setAttribute("id", menupopupId);
+
+    createMenuItem.call(this, "deleteAll");
+    createMenuSeparator();
+    createMenuItem.call(this, "enableAll");
+    createMenuItem.call(this, "disableAll");
+    createMenuSeparator();
+    createMenuItem.call(this, "enableOthers");
+    createMenuItem.call(this, "disableOthers");
+    createMenuItem.call(this, "deleteOthers");
+    createMenuSeparator();
+    createMenuItem.call(this, "enableSelf", true);
+    createMenuItem.call(this, "disableSelf");
+    createMenuItem.call(this, "deleteSelf");
+
+    this._popupset.appendChild(menupopup);
+    document.documentElement.appendChild(commandset);
+
+    aElementNode.commandset = commandset;
+    aElementNode.menupopup = menupopup;
+    return menupopupId;
+
+    /**
+     * Creates a menu item specified by a name with the appropriate attributes
+     * (label and handler).
+     *
+     * @param string aName
+     *        A global identifier for the menu item.
+     * @param boolean aHiddenFlag
+     *        True if this menuitem should be hidden.
+     */
+    function createMenuItem(aName, aHiddenFlag) {
+      let menuitem = document.createElement("menuitem");
+      let command = document.createElement("command");
+
+      let prefix = "bp-cMenu-";
+      let commandId = prefix + aName + "-" + breakpointId + "-command";
+      let menuitemId = prefix + aName + "-" + breakpointId + "-menuitem";
+
+      let label = L10N.getStr("breakpointMenuItem." + aName);
+      let func = this["_on" + aName.charAt(0).toUpperCase() + aName.slice(1)];
+
+      command.setAttribute("id", commandId);
+      command.setAttribute("label", label);
+      command.addEventListener("command", func.bind(this, aElementNode), false);
+
+      menuitem.setAttribute("id", menuitemId);
+      menuitem.setAttribute("command", commandId);
+      menuitem.setAttribute("hidden", aHiddenFlag);
+
+      commandset.appendChild(command);
+      menupopup.appendChild(menuitem);
+      aElementNode[aName] = { menuitem: menuitem, command: command };
+    }
+
+    /**
+     * 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 a context menu for a breakpoint.
+   *
+   * @param nsIDOMNode aElementNode
+   *        The element associated with the displayed breakpoint item.
+   */
+  _destroyContextMenu: function DVB__destroyContextMenu(aElementNode) {
+    let commandset = aElementNode.commandset;
+    let menupopup = aElementNode.menupopup;
+
+    commandset.parentNode.removeChild(commandset);
+    menupopup.parentNode.removeChild(menupopup);
+  },
+
+  /**
+   * Function called each time a breakpoint item is removed.
+   */
+  _onBreakpointRemoved: function DVB__onBreakpointRemoved(aItem) {
+    this._destroyContextMenu(aItem.target);
+  },
+
+  /**
+   * The click listener for the breakpoints container.
+   */
+  _onClick: function DVB__onClick(e) {
+    let breakpointItem = this.getItemForElement(e.target);
+    let { sourceLocation: url, lineNumber: line } = breakpointItem.attachment;
+
+    DebuggerView.updateEditor(url, line, { noDebug: true });
+    this.highlightBreakpoint(url, line);
+  },
+
+  /**
+   * The click listener for a breakpoint checkbox.
+   */
+  _onCheckboxClick: function DVB__onCheckboxClick(e) {
+    // Don't update the editor location.
+    e.preventDefault();
+    e.stopPropagation();
+
+    let breakpointItem = this.getItemForElement(e.target);
+    let { sourceLocation: url, lineNumber: line, enabled } = breakpointItem.attachment;
+
+    this[enabled
+      ? "disableBreakpoint"
+      : "enableBreakpoint"](url, line, { silent: true });
+  },
+
+  /**
+   * Listener handling the "enableSelf" menuitem command.
+   *
+   * @param object aTarget
+   *        The corresponding breakpoint element node.
+   */
+  _onEnableSelf: function DVB__onEnableSelf(aTarget) {
+    if (!aTarget) {
+      return;
+    }
+    let breakpointItem = this.getItemForElement(aTarget);
+    let { sourceLocation: url, lineNumber: line } = breakpointItem.attachment;
+
+    if (this.enableBreakpoint(url, line)) {
+      aTarget.enableSelf.menuitem.setAttribute("hidden", "true");
+      aTarget.disableSelf.menuitem.removeAttribute("hidden");
+    }
+  },
+
+  /**
+   * Listener handling the "disableSelf" menuitem command.
+   *
+   * @param object aTarget
+   *        The corresponding breakpoint element node.
+   */
+  _onDisableSelf: function DVB__onDisableSelf(aTarget) {
+    if (!aTarget) {
+      return;
+    }
+    let breakpointItem = this.getItemForElement(aTarget);
+    let { sourceLocation: url, lineNumber: line } = breakpointItem.attachment;
+
+    if (this.disableBreakpoint(url, line)) {
+      aTarget.enableSelf.menuitem.removeAttribute("hidden");
+      aTarget.disableSelf.menuitem.setAttribute("hidden", "true");
+    }
+  },
+
+  /**
+   * Listener handling the "deleteSelf" menuitem command.
+   *
+   * @param object aTarget
+   *        The corresponding breakpoint element node.
+   */
+  _onDeleteSelf: function DVB__onDeleteSelf(aTarget) {
+    if (!aTarget) {
+      return;
+    }
+    let breakpointItem = this.getItemForElement(aTarget);
+    let { sourceLocation: url, lineNumber: line } = breakpointItem.attachment;
+    let breakpointClient = DebuggerController.Breakpoints.getBreakpoint(url, line);
+
+    this.removeBreakpoint(url, line);
+    DebuggerController.Breakpoints.removeBreakpoint(breakpointClient);
+  },
+
+  /**
+   * Listener handling the "enableOthers" menuitem command.
+   *
+   * @param object aTarget
+   *        The corresponding breakpoint element node.
+   */
+  _onEnableOthers: function DVB__onEnableOthers(aTarget) {
+    for (let item in this) {
+      if (item.target != aTarget) {
+        this._onEnableSelf(item.target);
+      }
+    }
+  },
+
+  /**
+   * Listener handling the "disableOthers" menuitem command.
+   *
+   * @param object aTarget
+   *        The corresponding breakpoint element node.
+   */
+  _onDisableOthers: function DVB__onDisableOthers(aTarget) {
+    for (let item in this) {
+      if (item.target != aTarget) {
+        this._onDisableSelf(item.target);
+      }
+    }
+  },
+
+  /**
+   * Listener handling the "deleteOthers" menuitem command.
+   *
+   * @param object aTarget
+   *        The corresponding breakpoint element node.
+   */
+  _onDeleteOthers: function DVB__onDeleteOthers(aTarget) {
+    for (let item in this) {
+      if (item.target != aTarget) {
+        this._onDeleteSelf(item.target);
+      }
+    }
+  },
+
+  /**
+   * Listener handling the "enableAll" menuitem command.
+   *
+   * @param object aTarget
+   *        The corresponding breakpoint element node.
+   */
+  _onEnableAll: function DVB__onEnableAll(aTarget) {
+    this._onEnableOthers(aTarget);
+    this._onEnableSelf(aTarget);
+  },
+
+  /**
+   * Listener handling the "disableAll" menuitem command.
+   *
+   * @param object aTarget
+   *        The corresponding breakpoint element node.
+   */
+  _onDisableAll: function DVB__onDisableAll(aTarget) {
+    this._onDisableOthers(aTarget);
+    this._onDisableSelf(aTarget);
+  },
+
+  /**
+   * Listener handling the "deleteAll" menuitem command.
+   *
+   * @param object aTarget
+   *        The corresponding breakpoint element node.
+   */
+  _onDeleteAll: function DVB__onDeleteAll(aTarget) {
+    this._onDeleteOthers(aTarget);
+    this._onDeleteSelf(aTarget);
+  },
+
+  /**
+   * Gets an identifier for a breakpoint item for the current cache.
+   */
+  _key: function DVB__key(aSourceLocation, aLineNumber) {
+    return aSourceLocation + aLineNumber;
+  },
+
+  _popupset: null,
+  _cache: null
+});
+
+/**
+ * Functions handling the global search UI.
+ */
+function GlobalSearchView() {
+  dumpn("GlobalSearchView was instantiated");
+  MenuContainer.call(this);
+  this._startSearch = this._startSearch.bind(this);
+  this._onFetchSourceFinished = this._onFetchSourceFinished.bind(this);
+  this._onFetchSourcesFinished = this._onFetchSourcesFinished.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 }, {
+  /**
+   * Initialization function, called when the debugger is started.
+   */
+  initialize: function DVGS_initialize() {
+    dumpn("Initializing the GlobalSearchView");
+    this._container = new StackList(document.getElementById("globalsearch"));
+    this._splitter = document.getElementById("globalsearch-splitter");
+
+    this._container.emptyText = L10N.getStr("noMatchingStringsText");
+    this._container.itemFactory = this._createItemView;
+    this._container.addEventListener("scroll", this._onScroll, false);
+
+    this._cache = new Map();
+  },
+
+  /**
+   * Destruction function, called when the debugger is closed.
+   */
+  destroy: function DVGS_destroy() {
+    dumpn("Destroying the GlobalSearchView");
+    this._container.removeEventListener("scroll", this._onScroll, false);
+  },
+
+  /**
+   * Gets the visibility state of the global search container.
+   * @return boolean
+   */
+  get hidden() this._container.getAttribute("hidden") == "true",
+
+  /**
+   * Sets the results container hidden or visible. It's hidden by default.
+   * @param boolean aFlag
+   */
+  set hidden(aFlag) {
+    this._container.setAttribute("hidden", aFlag);
+    this._splitter.setAttribute("hidden", aFlag);
+  },
+
+  /**
+   * Removes all items from this container and hides it.
+   */
+  clearView: function DVGS_clearView() {
+    this.hidden = true;
+    this.empty();
+    window.dispatchEvent("Debugger:GlobalSearch:ViewCleared");
+  },
+
+  /**
+   * Clears all the fetched sources from cache.
+   */
+  clearCache: function DVGS_clearCache() {
+    this._cache = new Map();
+    window.dispatchEvent("Debugger:GlobalSearch:CacheCleared");
+  },
+
+  /**
+   * Focuses the next found match in the source editor.
+   */
+  focusNextMatch: function DVGS_focusNextMatch() {
+    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.
+   */
+  focusPrevMatch: function DVGS_focusPrevMatch() {
+    let totalLineResults = LineResults.size();
+    if (!totalLineResults) {
+      return;
+    }
+    if (--this._currentlyFocusedMatch < 0) {
+      this._currentlyFocusedMatch = totalLineResults - 1;
+    }
+    this._onMatchClick({
+      target: LineResults.getElementAtIndex(this._currentlyFocusedMatch)
+    });
+  },
+
+  /**
+   * Schedules searching for a string in all of the sources.
+   */
+  scheduleSearch: function DVGS_scheduleSearch() {
+    window.clearTimeout(this._searchTimeout);
+    this._searchTimeout = window.setTimeout(this._startSearch, GLOBAL_SEARCH_ACTION_DELAY);
+  },
+
+  /**
+   * Starts searching for a string in all of the sources.
+   */
+  _startSearch: function DVGS__startSearch() {
+    let locations = DebuggerView.Sources.values;
+    this._sourcesCount = locations.length;
+
+    this._fetchSources(
+      this._onFetchSourceFinished,
+      this._onFetchSourcesFinished, locations);
+  },
+
+  /**
+   * Starts fetching all the sources, silently.
+   *
+   * @param function aFetchCallback
+   *        Called after each source is fetched.
+   * @param function aFetchedCallback
+   *        Called if all the sources were already fetched.
+   * @param array aLocations
+   *        The locations for the sources to fetch.
+   */
+  _fetchSources: function DVGS__fetchSources(aFetchCallback, aFetchedCallback, aLocations) {
+    // If all the sources were already fetched, then don't do anything.
+    if (this._cache.size() == aLocations.length) {
+      aFetchedCallback();
+      return;
+    }
+
+    // Fetch each new source.
+    for (let location of aLocations) {
+      if (this._cache.has(location)) {
+        continue;
+      }
+      let sourceItem = DebuggerView.Sources.getItemByValue(location);
+      DebuggerController.SourceScripts.getText(sourceItem.attachment, aFetchCallback);
+    }
+  },
+
+  /**
+   * Called when a source has been fetched.
+   *
+   * @param string aLocation
+   *        The location of the source.
+   * @param string aContents
+   *        The text contents of the source.
+   */
+  _onFetchSourceFinished: function DVGS__onFetchSourceFinished(aLocation, aContents) {
+    // Remember the source in a cache so we don't have to fetch it again.
+    this._cache.set(aLocation, aContents);
+
+    // Check if all sources were fetched and stored in the cache.
+    if (this._cache.size() == this._sourcesCount) {
+      this._onFetchSourcesFinished();
+    }
+  },
+
+  /**
+   * Called when all the sources have been fetched.
+   */
+  _onFetchSourcesFinished: function DVGS__onFetchSourcesFinished() {
+    // All sources are fetched and stored in the cache, we can start searching.
+    this._performGlobalSearch();
+  },
+
+  /**
+   * Finds string matches in all the  sources stored in the cache, and groups
+   * them by location and line number.
+   */
+  _performGlobalSearch: function DVGS__performGlobalSearch() {
+    // Get the currently searched token from the filtering input.
+    let token = DebuggerView.Filtering.searchedToken;
+
+    // Make sure we're actually searching for something.
+    if (!token) {
+      this.clearView();
+      window.dispatchEvent("Debugger:GlobalSearch:TokenEmpty");
+      return;
+    }
+
+    // Search is not case sensitive, prepare the actual searched token.
+    let lowerCaseToken = token.toLowerCase();
+    let tokenLength = token.length;
+
+    // Prepare the results map, containing search details for each line.
+    let globalResults = new GlobalResults();
+
+    for (let [location, contents] of this._cache) {
+      // Verify that the search token is found anywhere in the source.
+      if (!contents.toLowerCase().contains(lowerCaseToken)) {
+        continue;
+      }
+      let lines = contents.split("\n");
+      let sourceResults = new SourceResults();
+
+      for (let i = 0, len = lines.length; i < len; i++) {
+        let line = lines[i];
+        let lowerCaseLine = line.toLowerCase();
+
+        // Search is not case sensitive, and is tied to each line in the source.
+        if (!lowerCaseLine.contains(lowerCaseToken)) {
+          continue;
+        }
+
+        let lineNumber = i;
+        let lineResults = new LineResults();
+
+        lowerCaseLine.split(lowerCaseToken).reduce(function(prev, curr, index, {length}) {
+          let prevLength = prev.length;
+          let currLength = curr.length;
+          let unmatched = line.substr(prevLength, currLength);
+          lineResults.add(unmatched);
+
+          if (index != length - 1) {
+            let matched = line.substr(prevLength + currLength, tokenLength);
+            let range = {
+              start: prevLength + currLength,
+              length: matched.length
+            };
+            lineResults.add(matched, range, true);
+            sourceResults.matchCount++;
+          }
+          return prev + token + curr;
+        }, "");
+
+        if (sourceResults.matchCount) {
+          sourceResults.add(lineNumber, lineResults);
+        }
+      }
+      if (sourceResults.matchCount) {
+        globalResults.add(location, sourceResults);
+      }
+    }
+
+    // Empty this container to rebuild the search results.
+    this.empty();
+
+    // Signal if there are any matches, and the rebuild the results.
+    if (globalResults.itemCount) {
+      this.hidden = false;
+      this._currentlyFocusedMatch = -1;
+      this._createGlobalResultsUI(globalResults);
+      window.dispatchEvent("Debugger:GlobalSearch:MatchFound");
+    } else {
+      window.dispatchEvent("Debugger:GlobalSearch:MatchNotFound");
+    }
+  },
+
+  /**
+   * Creates global search results entries and adds them to this container.
+   *
+   * @param GlobalResults aGlobalResults
+   *        An object containing all source results, grouped by source location.
+   */
+  _createGlobalResultsUI: function DVGS__createGlobalResultsUI(aGlobalResults) {
+    let i = 0;
+
+    for (let [location, sourceResults] in aGlobalResults) {
+      if (i++ == 0) {
+        this._createSourceResultsUI(location, sourceResults, true);
+      } else {
+        // Dispatch subsequent document manipulation operations, to avoid
+        // blocking the main thread when a large number of search results
+        // is found, thus giving the impression of faster searching.
+        Services.tm.currentThread.dispatch({ run:
+          this._createSourceResultsUI.bind(this, location, sourceResults) }, 0);
+      }
+    }
+  },
+
+  /**
+   * Creates source search results entries and adds them to this container.
+   *
+   * @param string aLocation
+   *        The location of the source.
+   * @param SourceResults aSourceResults
+   *        An object containing all the matched lines for a specific source.
+   * @param boolean aExpandFlag
+   *        True to expand the source results.
+   */
+  _createSourceResultsUI:
+  function DVGS__createSourceResultsUI(aLocation, aSourceResults, aExpandFlag) {
+    // Append a source results item to this container.
+    let sourceResultsItem = this.push(aLocation, aSourceResults.matchCount, {
+      forced: true,
+      unsorted: true,
+      relaxed: true,
+      attachment: {
+        sourceResults: aSourceResults,
+        expandFlag: aExpandFlag
+      }
+    });
+  },
+
+  /**
+   * Customization function for creating an item's UI.
+   *
+   * @param nsIDOMNode aElementNode
+   *        The element associated with the displayed item.
+   * @param string aLocation
+   *        The source result's location.
+   * @param string aMatchCount
+   *        The source result's match count.
+   * @param any aAttachment [optional]
+   *        Some attached primitive/object.
+   */
+  _createItemView:
+  function DVGS__createItemView(aElementNode, aLocation, aMatchCount, aAttachment) {
+    let { sourceResults, expandFlag } = aAttachment;
+
+    sourceResults.createView(aElementNode, aLocation, aMatchCount, expandFlag, {
+      onHeaderClick: this._onHeaderClick,
+      onLineClick: this._onLineClick,
+      onMatchClick: this._onMatchClick
+    });
+  },
+
+  /**
+   * The click listener for a results header.
+   */
+  _onHeaderClick: function DVGS__onHeaderClick(e) {
+    let sourceResultsItem = SourceResults.getItemForElement(e.target);
+    sourceResultsItem.instance.toggle(e);
+  },
+
+  /**
+   * The click listener for a results line.
+   */
+  _onLineClick: function DVGLS__onLineClick(e) {
+    let lineResultsItem = LineResults.getItemForElement(e.target);
+    this._onMatchClick({ target: lineResultsItem.firstMatch });
+  },
+
+  /**
+   * The click listener for a result match.
+   */
+  _onMatchClick: function DVGLS__onMatchClick(e) {
+    if (e instanceof Event) {
+      e.preventDefault();
+      e.stopPropagation();
+    }
+    let target = e.target;
+    let sourceResultsItem = SourceResults.getItemForElement(target);
+    let lineResultsItem = LineResults.getItemForElement(target);
+
+    sourceResultsItem.instance.expand(true);
+    this._currentlyFocusedMatch = LineResults.indexOfElement(target);
+    this._scrollMatchIntoViewIfNeeded(target);
+    this._bounceMatch(target);
+
+    let location = sourceResultsItem.location;
+    let lineNumber = lineResultsItem.lineNumber;
+    DebuggerView.updateEditor(location, lineNumber + 1, { noDebug: true });
+
+    let editor = DebuggerView.editor;
+    let offset = editor.getCaretOffset();
+    let { start, length } = lineResultsItem.lineData.range;
+    editor.setSelection(offset + start, offset + start + length);
+  },
+
+  /**
+   * The scroll listener for the global search container.
+   */
+  _onScroll: function DVGS__onScroll(e) {
+    for (let item in this) {
+      this._expandResultsIfNeeded(item.target);
+    }
+  },
+
+  /**
+   * Expands the source results it they are currently visible.
+   *
+   * @param nsIDOMNode aTarget
+   *        The element associated with the displayed item.
+   */
+  _expandResultsIfNeeded: function DVGS__expandResultsIfNeeded(aTarget) {
+    let sourceResultsItem = SourceResults.getItemForElement(aTarget);
+    if (sourceResultsItem.instance.toggled ||
+        sourceResultsItem.instance.expanded) {
+      return;
+    }
+    let { top, height } = aTarget.getBoundingClientRect();
+    let { clientHeight } = this._container._parent;
+
+    if (top - height <= clientHeight || this._forceExpandResults) {
+      sourceResultsItem.instance.expand(true);
+    }
+  },
+
+  /**
+   * Scrolls a match into view.
+   *
+   * @param nsIDOMNode aMatch
+   *        The match to scroll into view.
+   */
+  _scrollMatchIntoViewIfNeeded:  function DVGS__scrollMatchIntoViewIfNeeded(aMatch) {
+    let { top, height } = aMatch.getBoundingClientRect();
+    let { clientHeight } = this._container._parent;
+
+    let style = window.getComputedStyle(aMatch);
+    let topBorderSize = window.parseInt(style.getPropertyValue("border-top-width"));
+    let bottomBorderSize = window.parseInt(style.getPropertyValue("border-bottom-width"));
+
+    let marginY = top - (height + topBorderSize + bottomBorderSize) * 2;
+    if (marginY <= 0) {
+      this._container._parent.scrollTop += marginY;
+    }
+    if (marginY + height > clientHeight) {
+      this._container._parent.scrollTop += height - (clientHeight - marginY);
+    }
+  },
+
+  /**
+   * Starts a bounce animation for a match.
+   *
+   * @param nsIDOMNode aMatch
+   *        The match to start a bounce animation for.
+   */
+  _bounceMatch: function DVGS__bounceMatch(aMatch) {
+    Services.tm.currentThread.dispatch({ run: function() {
+      aMatch.setAttribute("focused", "");
+
+      aMatch.addEventListener("transitionend", function onEvent() {
+        aMatch.removeEventListener("transitionend", onEvent);
+        aMatch.removeAttribute("focused");
+      });
+    }}, 0);
+  },
+
+  _splitter: null,
+  _currentlyFocusedMatch: -1,
+  _forceExpandResults: false,
+  _searchTimeout: null,
+  _sourcesCount: -1,
+  _cache: null
+});
+
+/**
+ * An object containing all source results, grouped by source location.
+ * Iterable via "for (let [location, sourceResults] in globalResults) { }".
+ */
+function GlobalResults() {
+  this._store = new Map();
+  SourceResults._itemsByElement = new Map();
+  LineResults._itemsByElement = new Map();
+}
+
+GlobalResults.prototype = {
+  /**
+   * Adds source results to this store.
+   *
+   * @param string aLocation
+   *        The location of the source.
+   * @param SourceResults aSourceResults
+   *        An object containing all the matched lines for a specific source.
+   */
+  add: function GR_add(aLocation, aSourceResults) {
+    this._store.set(aLocation, aSourceResults);
+  },
+
+  /**
+   * Gets the number of source results in this store.
+   */
+  get itemCount() this._store.size(),
+
+  _store: null
+};
+
+/**
+ * An object containing all the matched lines for a specific source.
+ * Iterable via "for (let [lineNumber, lineResults] in sourceResults) { }".
+ */
+function SourceResults() {
+  this._store = new Map();
+  this.matchCount = 0;
+}
+
+SourceResults.prototype = {
+  /**
+   * Adds line results to this store.
+   *
+   * @param number aLineNumber
+   *        The line location in the source.
+   * @param LineResults aLineResults
+   *        An object containing all the matches for a specific line.
+   */
+  add: function SR_add(aLineNumber, aLineResults) {
+    this._store.set(aLineNumber, aLineResults);
+  },
+
+  /**
+   * The number of matches in this store. One line may have multiple matches.
+   */
+  matchCount: -1,
+
+  /**
+   * Expands the element, showing all the added details.
+   *
+   * @param boolean aSkipAnimationFlag
+   *        Pass true to not show an opening animation.
+   */
+  expand: function SR_expand(aSkipAnimationFlag) {
+    this._target.resultsContainer.setAttribute("open", "");
+    this._target.arrow.setAttribute("open", "");
+
+    if (!aSkipAnimationFlag) {
+      this._target.resultsContainer.setAttribute("animated", "");
+    }
+  },
+
+  /**
+   * Collapses the element, hiding all the added details.
+   */
+  collapse: function SR_collapse() {
+    this._target.resultsContainer.removeAttribute("animated");
+    this._target.resultsContainer.removeAttribute("open");
+    this._target.arrow.removeAttribute("open");
+  },
+
+  /**
+   * Toggles between the element collapse/expand state.
+   */
+  toggle: function SR_toggle(e) {
+    if (e instanceof Event) {
+      this._toggled = true;
+    }
+    this.expanded ^= 1;
+  },
+
+  /**
+   * Gets this element's expanded state.
+   * @return boolean
+   */
+  get expanded() this._target.resultsContainer.hasAttribute("open"),
+
+  /**
+   * Sets this element's expanded state.
+   * @param boolean aFlag
+   */
+  set expanded(aFlag) this[aFlag ? "expand" : "collapse"](),
+
+  /**
+   * Returns true if this element was toggled via user interaction.
+   */
+  get toggled() this._toggled,
+
+  /**
+   * Gets the element associated with this item.
+   * @return nsIDOMNode
+   */
+  get target() this._target,
+
+  /**
+   * Customization function for creating this item's UI.
+   *
+   * @param nsIDOMNode aElementNode
+   *        The element associated with the displayed item.
+   * @param string aLocation
+   *        The source result's location.
+   * @param string aMatchCount
+   *        The source result's match count.
+   * @param boolean aExpandFlag
+   *        True to expand the source results.
+   * @param object aCallbacks
+   *        An object containing all the necessary callback functions:
+   *          - onHeaderClick
+   *          - onMatchClick
+   */
+  createView:
+  function SR_createView(aElementNode, aLocation, aMatchCount, aExpandFlag, aCallbacks) {
+    this._target = aElementNode;
+
+    let arrow = document.createElement("box");
+    arrow.className = "arrow";
+
+    let locationNode = document.createElement("label");
+    locationNode.className = "plain location";
+    locationNode.setAttribute("value", SourceUtils.trimUrlLength(aLocation));
+
+    let matchCountNode = document.createElement("label");
+    matchCountNode.className = "plain match-count";
+    matchCountNode.setAttribute("value", "(" + aMatchCount + ")");
+
+    let resultsHeader = document.createElement("hbox");
+    resultsHeader.className = "dbg-results-header";
+    resultsHeader.setAttribute("align", "center")
+    resultsHeader.appendChild(arrow);
+    resultsHeader.appendChild(locationNode);
+    resultsHeader.appendChild(matchCountNode);
+    resultsHeader.addEventListener("click", aCallbacks.onHeaderClick, false);
+
+    let resultsContainer = document.createElement("vbox");
+    resultsContainer.className = "dbg-results-container";
+
+    for (let [lineNumber, lineResults] of this._store) {
+      lineResults.createView(resultsContainer, lineNumber, aCallbacks)
+    }
+
+    aElementNode.arrow = arrow;
+    aElementNode.resultsHeader = resultsHeader;
+    aElementNode.resultsContainer = resultsContainer;
+
+    if (aExpandFlag) {
+      this.expand(true);
+    }
+
+    let resultsBox = document.createElement("vbox");
+    resultsBox.setAttribute("flex", "1");
+    resultsBox.appendChild(resultsHeader);
+    resultsBox.appendChild(resultsContainer);
+
+    aElementNode.id = "source-results-" + aLocation;
+    aElementNode.className = "dbg-source-results";
+    aElementNode.appendChild(resultsBox);
+
+    SourceResults._itemsByElement.set(aElementNode, {
+      location: aLocation,
+      matchCount: aMatchCount,
+      autoExpand: aExpandFlag,
+      instance: this
+    });
+  },
+
+  _store: null,
+  _target: null,
+  _toggled: false
+};
+
+/**
+ * An object containing all the matches for a specific line.
+ * Iterable via "for (let chunk in lineResults) { }".
+ */
+function LineResults() {
+  this._store = [];
+}
+
+LineResults.prototype = {
+  /**
+   * Adds string details to this store.
+   *
+   * @param string aString
+   *        The text contents chunk in the line.
+   * @param object aRange
+   *        An object containing the { start, length } of the chunk.
+   * @param boolean aMatchFlag
+   *        True if the chunk is a matched string, false if just text content.
+   */
+  add: function LC_add(aString, aRange, aMatchFlag) {
+    this._store.push({
+      string: aString,
+      range: aRange,
+      match: !!aMatchFlag
+    });
+  },
+
+  /**
+   * Gets the element associated with this item.
+   * @return nsIDOMNode
+   */
+  get target() this._target,
+
+  /**
+   * Customization function for creating this item's UI.
+   *
+   * @param nsIDOMNode aContainer
+   *        The element associated with the displayed item.
+   * @param number aLineNumber
+   *        The line location in the source.
+   * @param object aCallbacks
+   *        An object containing all the necessary callback functions:
+   *          - onMatchClick
+   *          - onLineClick
+   */
+  createView: function LR_createView(aContainer, aLineNumber, aCallbacks) {
+    this._target = aContainer;
+
+    let lineNumberNode = document.createElement("label");
+    let lineContentsNode = document.createElement("hbox");
+    let lineString = "";
+    let lineLength = 0;
+    let firstMatch = null;
+
+    lineNumberNode.className = "plain line-number";
+    lineNumberNode.setAttribute("value", aLineNumber + 1);
+    lineContentsNode.className = "line-contents";
+    lineContentsNode.setAttribute("flex", "1");
+
+    for (let chunk of this._store) {
+      let { string, range, match } = chunk;
+      lineString = string.substr(0, GLOBAL_SEARCH_LINE_MAX_LENGTH - lineLength);
+      lineLength += string.length;
+
+      let label = document.createElement("label");
+      label.className = "plain string";
+      label.setAttribute("value", lineString);
+      label.setAttribute("match", match);
+      lineContentsNode.appendChild(label);
+
+      if (match) {
+        this._entangleMatch(aLineNumber, label, chunk);
+        label.addEventListener("click", aCallbacks.onMatchClick, false);
+        firstMatch = firstMatch || label;
+      }
+      if (lineLength >= GLOBAL_SEARCH_LINE_MAX_LENGTH) {
+        lineContentsNode.appendChild(this._ellipsis.cloneNode());
+        break;
+      }
+    }
+
+    this._entangleLine(lineContentsNode, firstMatch);
+    lineContentsNode.addEventListener("click", aCallbacks.onLineClick, false);
+
+    let searchResult = document.createElement("hbox");
+    searchResult.className = "dbg-search-result";
+    searchResult.appendChild(lineNumberNode);
+    searchResult.appendChild(lineContentsNode);
+    aContainer.appendChild(searchResult);
+  },
+
+  /**
+   * Handles a match while creating the view.
+   * @param number aLineNumber
+   * @param nsIDOMNode aNode
+   * @param object aMatchChunk
+   */
+  _entangleMatch: function LR__entangleMatch(aLineNumber, aNode, aMatchChunk) {
+    LineResults._itemsByElement.set(aNode, {
+      lineNumber: aLineNumber,
+      lineData: aMatchChunk
+    });
+  },
+
+  /**
+   * Handles a line while creating the view.
+   * @param nsIDOMNode aNode
+   * @param nsIDOMNode aFirstMatch
+   */
+  _entangleLine: function LR__entangleLine(aNode, aFirstMatch) {
+    LineResults._itemsByElement.set(aNode, {
+      firstMatch: aFirstMatch,
+      nonenumerable: true
+    });
+  },
+
+  /**
+   * An nsIDOMNode label with an ellipsis value.
+   */
+  _ellipsis: (function() {
+    let label = document.createElement("label");
+    label.className = "plain string";
+    label.setAttribute("value", L10N.ellipsis);
+    return label;
+  })(),
+
+  _store: null,
+  _target: null
+};
+
+/**
+ * A generator-iterator over the global, source or line results.
+ */
+GlobalResults.prototype.__iterator__ =
+SourceResults.prototype.__iterator__ =
+LineResults.prototype.__iterator__ = function DVGS_iterator() {
+  for (let item of this._store) {
+    yield item;
+  }
+};
+
+/**
+ * Gets the item associated with the specified element.
+ *
+ * @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 DVGS_getItemForElement(aElement) {
+  return MenuContainer.prototype.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
+ *         The matched element, or null if nothing is found.
+ */
+SourceResults.getElementAtIndex =
+LineResults.getElementAtIndex = function DVGS_getElementAtIndex(aIndex) {
+  for (let [element, item] of this._itemsByElement) {
+    if (!item.nonenumerable && !aIndex--) {
+      return element;
+    }
+  }
+};
+
+/**
+ * Gets the index of an item associated with the specified element.
+ *
+ * @return number
+ *         The index of the matched item, or -1 if nothing is found.
+ */
+SourceResults.indexOfElement =
+LineResults.indexOfElement = function DVGS_indexOFElement(aElement) {
+  let count = 0;
+  for (let [element, item] of this._itemsByElement) {
+    if (element == aElement) {
+      return count;
+    }
+    if (!item.nonenumerable) {
+      count++;
+    }
+  }
+  return -1;
+};
+
+/**
+ * Gets the number of cached items associated with a specified element.
+ *
+ * @return number
+ *         The number of key/value pairs in the corresponding map.
+ */
+SourceResults.size =
+LineResults.size = function DVGS_size() {
+  let count = 0;
+  for (let [_, item] of this._itemsByElement) {
+    if (!item.nonenumerable) {
+      count++;
+    }
+  }
+  return count;
+};
+
+/**
+ * Preliminary setup for the DebuggerView object.
+ */
+DebuggerView.StackFrames = new StackFramesView();
+DebuggerView.Breakpoints = new BreakpointsView();
+DebuggerView.GlobalSearch = new GlobalSearchView();
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/debugger-toolbar.js
@@ -0,0 +1,904 @@
+/* -*- 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";
+
+/**
+ * Functions handling the toolbar view: close button, expand/collapse button,
+ * pause/resume and stepping buttons etc.
+ */
+function ToolbarView() {
+  dumpn("ToolbarView was instantiated");
+  this._onCloseClick = this._onCloseClick.bind(this);
+  this._onTogglePanesPressed = this._onTogglePanesPressed.bind(this);
+  this._onResumePressed = this._onResumePressed.bind(this);
+  this._onStepOverPressed = this._onStepOverPressed.bind(this);
+  this._onStepInPressed = this._onStepInPressed.bind(this);
+  this._onStepOutPressed = this._onStepOutPressed.bind(this);
+}
+
+ToolbarView.prototype = {
+  /**
+   * Initialization function, called when the debugger is started.
+   */
+  initialize: function DVT_initialize() {
+    dumpn("Initializing the ToolbarView");
+    this._closeButton = document.getElementById("close");
+    this._togglePanesButton = document.getElementById("toggle-panes");
+    this._resumeButton = document.getElementById("resume");
+    this._stepOverButton = document.getElementById("step-over");
+    this._stepInButton = document.getElementById("step-in");
+    this._stepOutButton = document.getElementById("step-out");
+    this._chromeGlobals = document.getElementById("chrome-globals");
+    this._scripts = document.getElementById("sources");
+
+    let resumeKey = LayoutHelpers.prettyKey(document.getElementById("resumeKey"));
+    let stepOverKey = LayoutHelpers.prettyKey(document.getElementById("stepOverKey"));
+    let stepInKey = LayoutHelpers.prettyKey(document.getElementById("stepInKey"));
+    let stepOutKey = LayoutHelpers.prettyKey(document.getElementById("stepOutKey"));
+    this._resumeTooltip = L10N.getFormatStr("resumeButtonTooltip", [resumeKey]);
+    this._pauseTooltip = L10N.getFormatStr("pauseButtonTooltip", [resumeKey]);
+    this._stepOverTooltip = L10N.getFormatStr("stepOverTooltip", [stepOverKey]);
+    this._stepInTooltip = L10N.getFormatStr("stepInTooltip", [stepInKey]);
+    this._stepOutTooltip = L10N.getFormatStr("stepOutTooltip", [stepOutKey]);
+
+    this._closeButton.addEventListener("click", this._onCloseClick, false);
+    this._togglePanesButton.addEventListener("mousedown", this._onTogglePanesPressed, false);
+    this._resumeButton.addEventListener("mousedown", this._onResumePressed, false);
+    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);
+
+    this.toggleCloseButton(!window._isRemoteDebugger && !window._isChromeDebugger);
+    this.toggleChromeGlobalsContainer(window._isChromeDebugger);
+  },
+
+  /**
+   * Destruction function, called when the debugger is closed.
+   */
+  destroy: function DVT_destroy() {
+    dumpn("Destroying the ToolbarView");
+    this._closeButton.removeEventListener("click", this._onCloseClick, false);
+    this._togglePanesButton.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);
+  },
+
+  /**
+   * Sets the close button hidden or visible. It's hidden by default.
+   *
+   * @param boolean aVisibleFlag
+   *        Specifies the intended visibility.
+   */
+  toggleCloseButton: function DVT_toggleCloseButton(aVisibleFlag) {
+    this._closeButton.setAttribute("hidden", !aVisibleFlag);
+  },
+
+  /**
+   * Sets the resume button state based on the debugger active thread.
+   *
+   * @param string aState
+   *        Either "paused" or "attached".
+   */
+  toggleResumeButtonState: function DVT_toggleResumeButtonState(aState) {
+    // If we're paused, check and show a resume label on the button.
+    if (aState == "paused") {
+      this._resumeButton.setAttribute("checked", "true");
+      this._resumeButton.setAttribute("tooltiptext", this._resumeTooltip);
+    }
+    // If we're attached, do the opposite.
+    else if (aState == "attached") {
+      this._resumeButton.removeAttribute("checked");
+      this._resumeButton.setAttribute("tooltiptext", this._pauseTooltip);
+    }
+  },
+
+  /**
+   * Sets the chrome globals container hidden or visible. It's hidden by default.
+   *
+   * @param boolean aVisibleFlag
+   *        Specifies the intended visibility.
+   */
+  toggleChromeGlobalsContainer: function DVT_toggleChromeGlobalsContainer(aVisibleFlag) {
+    this._chromeGlobals.setAttribute("hidden", !aVisibleFlag);
+  },
+
+  /**
+   * Sets the sources container hidden or visible. It's visible by default.
+   *
+   * @param boolean aVisibleFlag
+   *        Specifies the intended visibility.
+   */
+  toggleSourcesContainer: function DVT_toggleSourcesContainer(aVisibleFlag) {
+    this._sources.setAttribute("hidden", !aVisibleFlag);
+  },
+
+  /**
+   * Listener handling the close button click event.
+   */
+  _onCloseClick: function DVT__onCloseClick() {
+    DebuggerController._shutdownDebugger();
+  },
+
+  /**
+   * Listener handling the toggle button click event.
+   */
+  _onTogglePanesPressed: function DVT__onTogglePanesPressed() {
+    DebuggerView.togglePanes({
+      visible: DebuggerView.panesHidden,
+      animated: true,
+      silent: false
+    });
+  },
+
+  /**
+   * Listener handling the pause/resume button click event.
+   */
+  _onResumePressed: function DVT__onResumePressed() {
+    if (DebuggerController.activeThread.paused) {
+      DebuggerController.activeThread.resume();
+    } else {
+      DebuggerController.activeThread.interrupt();
+    }
+  },
+
+  /**
+   * Listener handling the step over button click event.
+   */
+  _onStepOverPressed: function DVT__onStepOverPressed() {
+    if (DebuggerController.activeThread.paused) {
+      DebuggerController.activeThread.stepOver();
+    }
+  },
+
+  /**
+   * Listener handling the step in button click event.
+   */
+  _onStepInPressed: function DVT__onStepInPressed() {
+    if (DebuggerController.activeThread.paused) {
+      DebuggerController.activeThread.stepIn();
+    }
+  },
+
+  /**
+   * Listener handling the step out button click event.
+   */
+  _onStepOutPressed: function DVT__onStepOutPressed() {
+    if (DebuggerController.activeThread.paused) {
+      DebuggerController.activeThread.stepOut();
+    }
+  },
+
+  _closeButton: null,
+  _togglePanesButton: null,
+  _resumeButton: null,
+  _stepOverButton: null,
+  _stepInButton: null,
+  _stepOutButton: null,
+  _chromeGlobals: null,
+  _sources: null,
+  _resumeTooltip: "",
+  _pauseTooltip: "",
+  _stepOverTooltip: "",
+  _stepInTooltip: "",
+  _stepOutTooltip: ""
+};
+
+/**
+ * Functions handling the options UI.
+ */
+function OptionsView() {
+  dumpn("OptionsView was instantiated");
+  this._onPoeClick = this._onPoeClick.bind(this);
+  this._onShowNonenumClick = this._onShowNonenumClick.bind(this);
+}
+
+OptionsView.prototype = {
+  /**
+   * Initialization function, called when the debugger is started.
+   */
+  initialize: function DVO_initialize() {
+    dumpn("Initializing the OptionsView");
+    this._poeCheckbox = document.getElementById("pause-on-exceptions");
+    this._showNonenumCheckbox = document.getElementById("show-nonenum");
+
+    this._poeCheckbox.addEventListener("click", this._onPoeClick, false);
+    this._showNonenumCheckbox.addEventListener("click", this._onShowNonenumClick, false);
+
+    this._poeCheckbox.checked = false; // Never pause on exceptions by default.
+    this._showNonenumCheckbox.checked = Prefs.nonEnumVisible;
+  },
+
+  /**
+   * Destruction function, called when the debugger is closed.
+   */
+  destroy: function DVO_destroy() {
+    dumpn("Destroying the OptionsView");
+    this._poeCheckbox.removeEventListener("click", this._onPoeClick, false);
+    this._showNonenumCheckbox.removeEventListener("click", this._onShowNonenumClick, false);
+  },
+
+  /**
+   * Listener handling the 'pause on exceptions' checkbox click event.
+   */
+  _onPoeClick: function DVO__onPOEClick() {
+    DebuggerController.activeThread.pauseOnExceptions(this._poeCheckbox.checked);
+  },
+
+  /**
+   * Listener handling the 'show non-enumerables' checkbox click event.
+   */
+  _onShowNonenumClick: function DVO__onShowNonenumClick() {
+    DebuggerView.Variables.nonEnumVisible =
+      Prefs.nonEnumVisible = this._showNonenumCheckbox.checked;
+  },
+
+  _poeCheckbox: null,
+  _showNonenumCheckbox: null
+};
+
+/**
+ * Functions handling the chrome globals UI.
+ */
+function ChromeGlobalsView() {
+  dumpn("ChromeGlobalsView was instantiated");
+  MenuContainer.call(this);
+  this._onSelect = this._onSelect.bind(this);
+  this._onClick = this._onClick.bind(this);
+}
+
+create({ constructor: ChromeGlobalsView, proto: MenuContainer.prototype }, {
+  /**
+   * Initialization function, called when the debugger is started.
+   */
+  initialize: function DVCG_initialize() {
+    dumpn("Initializing the ChromeGlobalsView");
+    this._container = document.getElementById("chrome-globals");
+    this._emptyLabel = L10N.getStr("noGlobalsText");
+    this._unavailableLabel = L10N.getStr("noMatchingGlobalsText");
+
+    this._container.addEventListener("select", this._onSelect, false);
+    this._container.addEventListener("click", this._onClick, false);
+
+    this.empty();
+  },
+
+  /**
+   * Destruction function, called when the debugger is closed.
+   */
+  destroy: function DVT_destroy() {
+    dumpn("Destroying the ChromeGlobalsView");
+    this._container.removeEventListener("select", this._onSelect, false);
+    this._container.removeEventListener("click", this._onClick, false);
+  },
+
+  /**
+   * The select listener for the chrome globals container.
+   */
+  _onSelect: function DVCG__onSelect() {
+    if (!this.refresh()) {
+      return;
+    }
+    // TODO: Do something useful for chrome debugging.
+  },
+
+  /**
+   * The click listener for the chrome globals container.
+   */
+  _onClick: function DVCG__onClick() {
+    DebuggerView.Filtering.target = this;
+  }
+});
+
+/**
+ * Functions handling the sources UI.
+ */
+function SourcesView() {
+  dumpn("SourcesView was instantiated");
+  MenuContainer.call(this);
+  this._onSelect = this._onSelect.bind(this);
+  this._onClick = this._onClick.bind(this);
+}
+
+create({ constructor: SourcesView, proto: MenuContainer.prototype }, {
+  /**
+   * Initialization function, called when the debugger is started.
+   */
+  initialize: function DVS_initialize() {
+    dumpn("Initializing the SourcesView");
+    this._container = document.getElementById("sources");
+    this._emptyLabel = L10N.getStr("noScriptsText");
+    this._unavailableLabel = L10N.getStr("noMatchingScriptsText");
+
+    this._container.addEventListener("select", this._onSelect, false);
+    this._container.addEventListener("click", this._onClick, false);
+
+    this.empty();
+  },
+
+  /**
+   * Destruction function, called when the debugger is closed.
+   */
+  destroy: function DVS_destroy() {
+    dumpn("Destroying the SourcesView");
+    this._container.removeEventListener("select", this._onSelect, false);
+    this._container.removeEventListener("click", this._onClick, false);
+  },
+
+  /**
+   * The select listener for the sources container.
+   */
+  _onSelect: function DVS__onSelect() {
+    if (!this.refresh()) {
+      return;
+    }
+    DebuggerView.setEditorSource(this.selectedItem.attachment);
+  },
+
+  /**
+   * The click listener for the sources container.
+   */
+  _onClick: function DVS__onClick() {
+    DebuggerView.Filtering.target = this;
+  }
+});
+
+/**
+ * Utility functions for handling sources.
+ */
+let SourceUtils = {
+  _labelsCache: new Map(),
+
+  /**
+   * Gets a unique, simplified label from a source url.
+   *
+   * @param string aUrl
+   *        The source url.
+   * @return string
+   *         The simplified label.
+   */
+  getSourceLabel: function SU_getSourceLabel(aUrl) {
+    if (!this._labelsCache.has(aUrl)) {
+      this._labelsCache.set(aUrl, this.trimUrlLength(this.trimUrl(aUrl)));
+    }
+    return this._labelsCache.get(aUrl);
+  },
+
+  /**
+   * Clears the labels cache, populated by SourceUtils.getSourceLabel.
+   * This should be done every time the content location changes.
+   */
+  clearLabelsCache: function SU_clearLabelsCache() {
+    this._labelsCache = new Map();
+  },
+
+  /**
+   * Trims the url by shortening it if it exceeds a certain length, adding an
+   * ellipsis at the end.
+   *
+   * @param string aUrl
+   *        The source url.
+   * @param number aMaxLength [optional]
+   *        The max source url length.
+   * @return string
+   *         The shortened url.
+   */
+  trimUrlLength: function SU_trimUrlLength(aUrl, aMaxLength = SOURCE_URL_MAX_LENGTH) {
+    if (aUrl.length > aMaxLength) {
+      return aUrl.substring(0, aMaxLength) + L10N.ellipsis;
+    }
+    return aUrl;
+  },
+
+  /**
+   * Trims the query part or reference identifier of a url string, if necessary.
+   *
+   * @param string aUrl
+   *        The source url.
+   * @return string
+   *         The shortened url.
+   */
+  trimUrlQuery: function SU_trimUrlQuery(aUrl) {
+    let length = aUrl.length;
+    let q1 = aUrl.indexOf('?');
+    let q2 = aUrl.indexOf('&');
+    let q3 = aUrl.indexOf('#');
+    let q = Math.min(q1 != -1 ? q1 : length,
+                     q2 != -1 ? q2 : length,
+                     q3 != -1 ? q3 : length);
+
+    return aUrl.slice(0, q);
+  },
+
+  /**
+   * Trims as much as possible from a url, while keeping the result unique
+   * in the sources container.
+   *
+   * @param string | nsIURL aUrl
+   *        The script url.
+   * @param string aLabel [optional]
+   *        The resulting label at each step.
+   * @param number aSeq [optional]
+   *        The current iteration step.
+   * @return string
+   *         The resulting label at the final step.
+   */
+  trimUrl: function SU_trimUrl(aUrl, aLabel, aSeq) {
+    if (!(aUrl instanceof Ci.nsIURL)) {
+      try {
+        // Use an nsIURL to parse all the url path parts.
+        aUrl = Services.io.newURI(aUrl, null, null).QueryInterface(Ci.nsIURL);
+      } catch (e) {
+        // This doesn't look like a url, or nsIURL can't handle it.
+        return aUrl;
+      }
+    }
+    if (!aSeq) {
+      let name = aUrl.fileName;
+      if (name) {
+        // This is a regular file url, get only the file name (contains the
+        // base name and extension if available).
+
+        // If this url contains an invalid query, unfortunately nsIURL thinks
+        // it's part of the file extension. It must be removed.
+        aLabel = aUrl.fileName.replace(/\&.*/, "");
+      } else {
+        // This is not a file url, hence there is no base name, nor extension.
+        // Proceed using other available information.
+        aLabel = "";
+      }
+      aSeq = 1;
+    }
+
+    // If we have a label and it doesn't start with a query...
+    if (aLabel && aLabel.indexOf("?") != 0) {
+      if (DebuggerView.Sources.containsTrimmedValue(aUrl.spec)) {
+        // A page may contain multiple requests to the same url but with different
+        // queries. It would be redundant to show each one.
+        return aLabel;
+      }
+      if (!DebuggerView.Sources.containsLabel(aLabel)) {
+        // We found the shortest unique label for the url.
+        return aLabel;
+      }
+    }
+
+    // Append the url query.
+    if (aSeq == 1) {
+      let query = aUrl.query;
+      if (query) {
+        return this.trimUrl(aUrl, aLabel + "?" + query, aSeq + 1);
+      }
+      aSeq++;
+    }
+    // Append the url reference.
+    if (aSeq == 2) {
+      let ref = aUrl.ref;
+      if (ref) {
+        return this.trimUrl(aUrl, aLabel + "#" + aUrl.ref, aSeq + 1);
+      }
+      aSeq++;
+    }
+    // Prepend the url directory.
+    if (aSeq == 3) {
+      let dir = aUrl.directory;
+      if (dir) {
+        return this.trimUrl(aUrl, dir.replace(/^\//, "") + aLabel, aSeq + 1);
+      }
+      aSeq++;
+    }
+    // Prepend the hostname and port number.
+    if (aSeq == 4) {
+      let host = aUrl.hostPort;
+      if (host) {
+        return this.trimUrl(aUrl, host + "/" + aLabel, aSeq + 1);
+      }
+      aSeq++;
+    }
+    // Use the whole url spec but ignoring the reference.
+    if (aSeq == 5) {
+      return this.trimUrl(aUrl, aUrl.specIgnoringRef, aSeq + 1);
+    }
+    // Give up.
+    return aUrl.spec;
+  }
+};
+
+/**
+ * Functions handling the filtering UI.
+ */
+function FilterView() {
+  dumpn("FilterView was instantiated");
+  this._onClick = this._onClick.bind(this);
+  this._onSearch = this._onSearch.bind(this);
+  this._onKeyPress = this._onKeyPress.bind(this);
+  this._onBlur = this._onBlur.bind(this);
+}
+
+FilterView.prototype = {
+  /**
+   * Initialization function, called when the debugger is started.
+   */
+  initialize: function DVF_initialize() {
+    dumpn("Initializing the FilterView");
+    this._searchbox = document.getElementById("searchbox");
+    this._searchboxPanel = document.getElementById("searchbox-panel");
+    this._globalOperatorButton = document.getElementById("global-operator-button");
+    this._globalOperatorLabel = document.getElementById("global-operator-label");
+    this._tokenOperatorButton = document.getElementById("token-operator-button");
+    this._tokenOperatorLabel = document.getElementById("token-operator-label");
+    this._lineOperatorButton = document.getElementById("line-operator-button");
+    this._lineOperatorLabel = document.getElementById("line-operator-label");
+
+    this._globalSearchKey = LayoutHelpers.prettyKey(document.getElementById("globalSearchKey"));
+    this._fileSearchKey = LayoutHelpers.prettyKey(document.getElementById("fileSearchKey"));
+    this._lineSearchKey = LayoutHelpers.prettyKey(document.getElementById("lineSearchKey"));
+    this._tokenSearchKey = LayoutHelpers.prettyKey(document.getElementById("tokenSearchKey"));
+
+    this._searchbox.addEventListener("click", this._onClick, false);
+    this._searchbox.addEventListener("select", this._onSearch, false);
+    this._searchbox.addEventListener("input", this._onSearch, false);
+    this._searchbox.addEventListener("keypress", this._onKeyPress, false);
+    this._searchbox.addEventListener("blur", this._onBlur, false);
+
+    this._globalOperatorButton.setAttribute("label", SEARCH_GLOBAL_FLAG);
+    this._tokenOperatorButton.setAttribute("label", SEARCH_TOKEN_FLAG);
+    this._lineOperatorButton.setAttribute("label", SEARCH_LINE_FLAG);
+
+    this._globalOperatorLabel.setAttribute("value",
+      L10N.getFormatStr("searchPanelGlobal", [this._globalSearchKey]));
+    this._tokenOperatorLabel.setAttribute("value",
+      L10N.getFormatStr("searchPanelToken", [this._tokenSearchKey]));
+    this._lineOperatorLabel.setAttribute("value",
+      L10N.getFormatStr("searchPanelLine", [this._lineSearchKey]));
+
+    if (window._isChromeDebugger) {
+      this.target = DebuggerView.ChromeGlobals;
+    } else {
+      this.target = DebuggerView.Sources;
+    }
+  },
+
+  /**
+   * Destruction function, called when the debugger is closed.
+   */
+  destroy: function DVF_destroy() {
+    dumpn("Destroying the FilterView");
+    this._searchbox.removeEventListener("click", this._onClick, false);
+    this._searchbox.removeEventListener("select", this._onSearch, false);
+    this._searchbox.removeEventListener("input", this._onSearch, false);
+    this._searchbox.removeEventListener("keypress", this._onKeyPress, false);
+    this._searchbox.removeEventListener("blur", this._onBlur, false);
+  },
+
+  /**
+   * Sets the target container to be currently filtered.
+   * @param object aView
+   */
+  set target(aView) {
+    var placeholder = "";
+    switch (aView) {
+      case DebuggerView.ChromeGlobals:
+        placeholder = L10N.getFormatStr("emptyChromeGlobalsFilterText", [this._fileSearchKey]);
+        break;
+      case DebuggerView.Sources:
+        placeholder = L10N.getFormatStr("emptyFilterText", [this._fileSearchKey]);
+        break;
+    }
+    this._searchbox.setAttribute("placeholder", placeholder);
+    this._target = aView;
+  },
+
+  /**
+   * Gets the entered file, line and token entered in the searchbox.
+   * @return array
+   */
+  get searchboxInfo() {
+    let file, line, token, global;
+
+    let rawValue = this._searchbox.value;
+    let rawLength = rawValue.length;
+    let globalFlagIndex = rawValue.indexOf(SEARCH_GLOBAL_FLAG);
+    let lineFlagIndex = rawValue.lastIndexOf(SEARCH_LINE_FLAG);
+    let tokenFlagIndex = rawValue.lastIndexOf(SEARCH_TOKEN_FLAG);
+
+    // This is not a global search, allow file or line flags.
+    if (globalFlagIndex != 0) {
+      let fileEnd = lineFlagIndex != -1
+        ? lineFlagIndex
+        : tokenFlagIndex != -1 ? tokenFlagIndex : rawLength;
+
+      let lineEnd = tokenFlagIndex != -1
+        ? tokenFlagIndex
+        : rawLength;
+
+      file = rawValue.slice(0, fileEnd);
+      line = ~~(rawValue.slice(fileEnd + 1, lineEnd)) || -1;
+      token = rawValue.slice(lineEnd + 1);
+      global = false;
+    }
+    // Global searches dissalow the use of file or line flags.
+    else {
+      file = "";
+      line = -1;
+      token = rawValue.slice(1);
+      global = true;
+    }
+
+    return [file, line, token, global];
+  },
+
+  /**
+   * Returns the currently searched file.
+   * @return string
+   */
+  get searchedFile() this.searchboxInfo[0],
+
+  /**
+   * Returns the currently searched line.
+   * @return number
+   */
+  get searchedLine() this.searchboxInfo[1],
+
+  /**
+   * Returns the currently searched token.
+   * @return string
+   */
+  get searchedToken() this.searchboxInfo[2],
+
+  /**
+   * Clears the text from the searchbox and resets any changed view.
+   */
+  clearSearch: function DVF_clearSearch() {
+    this._searchbox.value = "";
+    this._onSearch();
+  },
+
+  /**
+   * Performs a file search if necessary.
+   *
+   * @param string aFile
+   *        The source location to search for.
+   */
+  _performFileSearch: function DVF__performFileSearch(aFile) {
+    // Don't search for files if the input hasn't changed.
+    if (this._prevSearchedFile == aFile) {
+      return;
+    }
+
+    let view = this._target;
+
+    // If we're not searching for a file anymore, unhide all the items.
+    if (!aFile) {
+      for (let item in view) {
+        item.target.hidden = false;
+      }
+      view.refresh();
+    }
+    // If the searched file string is valid, hide non-matched items.
+    else {
+      let found = false;
+      let lowerCaseFile = aFile.toLowerCase();
+
+      for (let item in view) {
+        let element = item.target;
+        let lowerCaseLabel = item.label.toLowerCase();
+
+        // Search is not case sensitive, and is tied to the label not the value.
+        if (lowerCaseLabel.match(lowerCaseFile)) {
+          element.hidden = false;
+
+          // Automatically select the first match.
+          if (!found) {
+            found = true;
+            view.selectedItem = item;
+          }
+        }
+        // Item not matched, hide the corresponding node.
+        else {
+          element.hidden = true;
+        }
+      }
+      // If no matches were found, display the appropriate info.
+      if (!found) {
+        view.setUnavailable();
+      }
+    }
+    this._prevSearchedFile = aFile;
+  },
+
+  /**
+   * Performs a line search if necessary.
+   * (Jump to lines in the currently visible source).
+   *
+   * @param number aLine
+   *        The source line number to jump to.
+   */
+  _performLineSearch: function DVF__performLineSearch(aLine) {
+    // Don't search for lines if the input hasn't changed.
+    if (this._prevSearchedLine != aLine && aLine > -1) {
+      DebuggerView.editor.setCaretPosition(aLine - 1);
+    }
+    this._prevSearchedLine = aLine;
+  },
+
+  /**
+   * Performs a token search if necessary.
+   * (Search for tokens in the currently visible source).
+   *
+   * @param string aToken
+   *        The source token to find.
+   */
+  _performTokenSearch: function DVF__performTokenSearch(aToken) {
+    // Don't search for tokens if the input hasn't changed.
+    if (this._prevSearchedToken != aToken && aToken.length > 0) {
+      let editor = DebuggerView.editor;
+      let offset = editor.find(aToken, { ignoreCase: true });
+      if (offset > -1) {
+        editor.setSelection(offset, offset + aToken.length)
+      }
+    }
+    this._prevSearchedToken = aToken;
+  },
+
+  /**
+   * The click listener for the search container.
+   */
+  _onClick: function DVF__onClick() {
+    this._searchboxPanel.openPopup(this._searchbox);
+  },
+
+  /**
+   * The search listener for the search container.
+   */
+  _onSearch: function DVF__onScriptsSearch() {
+    this._searchboxPanel.hidePopup();
+    let [file, line, token, global] = this.searchboxInfo;
+
+    // If this is a global search, schedule it for when the user stops typing,
+    // or hide the corresponding pane otherwise.
+    if (global) {
+      DebuggerView.GlobalSearch.scheduleSearch();
+    } else {
+      DebuggerView.GlobalSearch.clearView();
+      this._performFileSearch(file);
+      this._performLineSearch(line);
+      this._performTokenSearch(token);
+    }
+  },
+
+  /**
+   * The key press listener for the search container.
+   */
+  _onKeyPress: function DVF__onScriptsKeyPress(e) {
+    let [file, line, token, global] = this.searchboxInfo;
+    let action;
+
+    switch (e.keyCode) {
+      case e.DOM_VK_DOWN:
+      case e.DOM_VK_RETURN:
+      case e.DOM_VK_ENTER:
+        action = 0;
+        break;
+      case e.DOM_VK_UP:
+        action = 1;
+        break;
+      case e.DOM_VK_ESCAPE:
+        action = 2;
+        break;
+      default:
+        action = -1;
+    }
+
+    if (action == 2) {
+      DebuggerView.editor.focus();
+      return;
+    }
+    if (action == -1 || !token) {
+      return;
+    }
+
+    e.preventDefault();
+    e.stopPropagation();
+
+    if (global) {
+      if (DebuggerView.GlobalSearch.hidden) {
+        DebuggerView.GlobalSearch.scheduleSearch();
+      } else {
+        DebuggerView.GlobalSearch[["focusNextMatch", "focusPrevMatch"][action]]();
+      }
+    } else {
+      let editor = DebuggerView.editor;
+      let offset = editor[["findNext", "findPrevious"][action]](true);
+      if (offset > -1) {
+        editor.setSelection(offset, offset + token.length)
+      }
+    }
+  },
+
+  /**
+   * The blur listener for the search container.
+   */
+  _onBlur: function DVF__onBlur() {
+    DebuggerView.GlobalSearch.clearView();
+    this._searchboxPanel.hidePopup();
+  },
+
+  /**
+   * Called when a filtering key sequence was pressed.
+   *
+   * @param string aOperator
+   *        The operator to use for filtering.
+   */
+  _doSearch: function DVF__doSearch(aOperator = "") {
+    this._searchbox.focus();
+    this._searchbox.value = aOperator;
+    DebuggerView.GlobalSearch.clearView();
+  },
+
+  /**
+   * Called when the source location filter key sequence was pressed.
+   */
+  _doFileSearch: function DVF__doFileSearch() {
+    this._doSearch();
+    this._searchboxPanel.openPopup(this._searchbox);
+  },
+
+  /**
+   * Called when the source line filter key sequence was pressed.
+   */
+  _doLineSearch: function DVF__doLineSearch() {
+    this._doSearch(SEARCH_LINE_FLAG);
+    this._searchboxPanel.hidePopup();
+  },
+
+  /**
+   * Called when the source token filter key sequence was pressed.
+   */
+  _doTokenSearch: function DVF__doTokenSearch() {
+    this._doSearch(SEARCH_TOKEN_FLAG);
+    this._searchboxPanel.hidePopup();
+  },
+
+  /**
+   * Called when the global search filter key sequence was pressed.
+   */
+  _doGlobalSearch: function DVF__doGlobalSearch() {
+    this._doSearch(SEARCH_GLOBAL_FLAG);
+    this._searchboxPanel.hidePopup();
+  },
+
+  _searchbox: null,
+  _searchboxPanel: null,
+  _globalOperatorButton: null,
+  _globalOperatorLabel: null,
+  _tokenOperatorButton: null,
+  _tokenOperatorLabel: null,
+  _lineOperatorButton: null,
+  _lineOperatorLabel: null,
+  _globalSearchKey: "",
+  _fileSearchKey: "",
+  _lineSearchKey: "",
+  _tokenSearchKey: "",
+  _target: null,
+  _prevSearchedFile: "",
+  _prevSearchedLine: -1,
+  _prevSearchedToken: ""
+};
+
+/**
+ * Preliminary setup for the DebuggerView object.
+ */
+DebuggerView.Toolbar = new ToolbarView();
+DebuggerView.Options = new OptionsView();
+DebuggerView.ChromeGlobals = new ChromeGlobalsView();
+DebuggerView.Sources = new SourcesView();
+DebuggerView.Filtering = new FilterView();
--- a/browser/devtools/debugger/debugger-view.js
+++ b/browser/devtools/debugger/debugger-view.js
@@ -1,214 +1,402 @@
 /* -*- 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 BREAKPOINT_LINE_TOOLTIP_MAX_SIZE = 1000; // chars
+const SOURCE_URL_MAX_LENGTH = 64; // chars
+const SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE = 1048576; // 1 MB in bytes
 const PANES_APPEARANCE_DELAY = 50; // ms
-const PROPERTY_VIEW_FLASH_DURATION = 400; // ms
-const GLOBAL_SEARCH_MATCH_FLASH_DURATION = 100; // ms
-const GLOBAL_SEARCH_URL_MAX_SIZE = 100; // chars
-const GLOBAL_SEARCH_LINE_MAX_SIZE = 300; // chars
+const BREAKPOINT_LINE_TOOLTIP_MAX_LENGTH = 1000; // chars
+const GLOBAL_SEARCH_LINE_MAX_LENGTH = 300; // chars
 const GLOBAL_SEARCH_ACTION_DELAY = 150; // ms
-
 const SEARCH_GLOBAL_FLAG = "!";
 const SEARCH_LINE_FLAG = ":";
 const SEARCH_TOKEN_FLAG = "#";
 
 /**
- * Object mediating visual changes and event listeners between the debugger and
- * the html view.
+ * Object defining the debugger view components.
  */
 let DebuggerView = {
-
   /**
-   * An instance of SourceEditor.
-   */
-  editor: null,
-
-  /**
-   * Caches frequently used global view elements.
+   * Initializes the debugger view.
+   *
+   * @param function aCallback
+   *        Called after the view finishes initializing.
    */
-  cacheView: function DV_cacheView() {
-    this._onTogglePanesButtonPressed = this._onTogglePanesButtonPressed.bind(this);
-
-    // Panes and view containers
-    this._togglePanesButton = document.getElementById("toggle-panes");
-    this._stackframesAndBreakpoints = document.getElementById("stackframes+breakpoints");
-    this._stackframes = document.getElementById("stackframes");
-    this._breakpoints = document.getElementById("breakpoints");
-    this._variables = document.getElementById("variables");
-    this._scripts = document.getElementById("scripts");
-    this._globalSearch = document.getElementById("globalsearch");
-    this._globalSearchSplitter = document.getElementById("globalsearch-splitter");
+  initialize: function DV_initialize(aCallback) {
+    dumpn("Initializing the DebuggerView");
+    this.Toolbar.initialize();
+    this.Options.initialize();
+    this.ChromeGlobals.initialize();
+    this.Sources.initialize();
+    this.Filtering.initialize();
+    this.StackFrames.initialize();
+    this.Breakpoints.initialize();
+    this.GlobalSearch.initialize();
 
-    // Keys
-    this._fileSearchKey = document.getElementById("fileSearchKey");
-    this._lineSearchKey = document.getElementById("lineSearchKey");
-    this._tokenSearchKey = document.getElementById("tokenSearchKey");
-    this._globalSearchKey = document.getElementById("globalSearchKey");
-    this._resumeKey = document.getElementById("resumeKey");
-    this._stepOverKey = document.getElementById("stepOverKey");
-    this._stepInKey = document.getElementById("stepInKey");
-    this._stepOutKey = document.getElementById("stepOutKey");
+    this.Variables = new VariablesView(document.getElementById("variables"));
+    this.Variables.emptyText = L10N.getStr("emptyVariablesText");
+    this.Variables.nonEnumVisible = Prefs.nonEnumVisible;
+    this.Variables.eval = DebuggerController.StackFrames.evaluate;
 
-    // Buttons, textboxes etc.
-    this._resumeButton = document.getElementById("resume");
-    this._stepOverButton = document.getElementById("step-over");
-    this._stepInButton = document.getElementById("step-in");
-    this._stepOutButton = document.getElementById("step-out");
-    this._scriptsSearchbox = document.getElementById("scripts-search");
-    this._globalOperatorLabel = document.getElementById("global-operator-label");
-    this._globalOperatorButton = document.getElementById("global-operator-button");
-    this._tokenOperatorLabel = document.getElementById("token-operator-label");
-    this._tokenOperatorButton = document.getElementById("token-operator-button");
-    this._lineOperatorLabel = document.getElementById("line-operator-label");
-    this._lineOperatorButton = document.getElementById("line-operator-button");
+    this._initializePanes();
+    this._initializeEditor(aCallback)
+    this._isInitialized = true;
   },
 
   /**
-   * Applies the correct key labels and tooltips across global view elements.
+   * Destroys the debugger view.
+   *
+   * @param function aCallback
+   *        Called after the view finishes destroying.
    */
-  initializeKeys: function DV_initializeKeys() {
-    this._resumeButton.setAttribute("tooltiptext",
-      L10N.getFormatStr("pauseButtonTooltip", [LayoutHelpers.prettyKey(this._resumeKey)]));
-    this._stepOverButton.setAttribute("tooltiptext",
-      L10N.getFormatStr("stepOverTooltip", [LayoutHelpers.prettyKey(this._stepOverKey)]));
-    this._stepInButton.setAttribute("tooltiptext",
-      L10N.getFormatStr("stepInTooltip", [LayoutHelpers.prettyKey(this._stepInKey)]));
-    this._stepOutButton.setAttribute("tooltiptext",
-      L10N.getFormatStr("stepOutTooltip", [LayoutHelpers.prettyKey(this._stepOutKey)]));
+  destroy: function DV_destroy(aCallback) {
+    dumpn("Destroying the DebuggerView");
+    this.Toolbar.destroy();
+    this.Options.destroy();
+    this.ChromeGlobals.destroy();
+    this.Sources.destroy();
+    this.Filtering.destroy();
+    this.StackFrames.destroy();
+    this.Breakpoints.destroy();
+    this.GlobalSearch.destroy();
 
-    this._scriptsSearchbox.setAttribute("placeholder",
-      L10N.getFormatStr("emptyFilterText", [LayoutHelpers.prettyKey(this._fileSearchKey)]));
-    this._globalOperatorLabel.setAttribute("value",
-      L10N.getFormatStr("searchPanelGlobal", [LayoutHelpers.prettyKey(this._globalSearchKey)]));
-    this._tokenOperatorLabel.setAttribute("value",
-      L10N.getFormatStr("searchPanelToken", [LayoutHelpers.prettyKey(this._tokenSearchKey)]));
-    this._lineOperatorLabel.setAttribute("value",
-      L10N.getFormatStr("searchPanelLine", [LayoutHelpers.prettyKey(this._lineSearchKey)]));
-
-    this._globalOperatorButton.setAttribute("label", SEARCH_GLOBAL_FLAG);
-    this._tokenOperatorButton.setAttribute("label", SEARCH_TOKEN_FLAG);
-    this._lineOperatorButton.setAttribute("label", SEARCH_LINE_FLAG);
+    this._destroyPanes();
+    this._destroyEditor();
+    aCallback();
   },
 
   /**
-   * Initializes UI properties for all the displayed panes.
+   * Initializes the UI for all the displayed panes.
    */
-  initializePanes: function DV_initializePanes() {
-    this._togglePanesButton.addEventListener("click", this._onTogglePanesButtonPressed);
+  _initializePanes: function DV__initializePanes() {
+    dumpn("Initializing the DebuggerView panes");
+
+    this._togglePanesButton = document.getElementById("toggle-panes");
+    this._stackframesAndBreakpoints = document.getElementById("stackframes+breakpoints");
+    this._variables = document.getElementById("variables");
 
     this._stackframesAndBreakpoints.setAttribute("width", Prefs.stackframesWidth);
     this._variables.setAttribute("width", Prefs.variablesWidth);
+    this.togglePanes({ visible: false, animated: false, silent: true });
+  },
 
-    this.toggleStackframesAndBreakpointsPane({ silent: true });
-    this.toggleVariablesPane({ silent: true });
+  /**
+   * Destroys the UI for all the displayed panes.
+   */
+  _destroyPanes: function DV__initializePanes() {
+    dumpn("Destroying the DebuggerView panes");
+
+    Prefs.stackframesWidth = this._stackframesAndBreakpoints.getAttribute("width");
+    Prefs.variablesWidth = this._variables.getAttribute("width");
+
+    this._togglePanesButton = null;
+    this._stackframesAndBreakpoints = null;
+    this._variables = null;
   },
 
   /**
    * Initializes the SourceEditor instance.
    *
    * @param function aCallback
    *        Called after the editor finishes initializing.
    */
-  initializeEditor: function DV_initializeEditor(aCallback) {
+  _initializeEditor: function DV__initializeEditor(aCallback) {
+    dumpn("Initializing the DebuggerView editor");
+
     let placeholder = document.getElementById("editor");
-
     let config = {
       mode: SourceEditor.MODES.JAVASCRIPT,
+      readOnly: true,
       showLineNumbers: true,
-      readOnly: true,
       showAnnotationRuler: true,
-      showOverviewRuler: true,
+      showOverviewRuler: true
     };
 
     this.editor = new SourceEditor();
     this.editor.init(placeholder, config, function() {
       this._onEditorLoad();
       aCallback();
     }.bind(this));
   },
 
   /**
-   * Removes the displayed panes and saves any necessary state.
+   * The load event handler for the source editor, also executing any necessary
+   * post-load operations.
    */
-  destroyPanes: function DV_destroyPanes() {
-    this._togglePanesButton.removeEventListener("click", this._onTogglePanesButtonPressed);
-
-    Prefs.stackframesWidth = this._stackframesAndBreakpoints.getAttribute("width");
-    Prefs.variablesWidth = this._variables.getAttribute("width");
+  _onEditorLoad: function DV__onEditorLoad() {
+    dumpn("Finished loading the DebuggerView editor");
 
-    this._breakpoints.parentNode.removeChild(this._breakpoints);
-    this._stackframes.parentNode.removeChild(this._stackframes);
-    this._stackframesAndBreakpoints.parentNode.removeChild(this._stackframesAndBreakpoints);
-    this._variables.parentNode.removeChild(this._variables);
-    this._globalSearch.parentNode.removeChild(this._globalSearch);
-
-    // Delete all the cached global view elements.
-    for (let i in this) {
-      if (!(this[i] instanceof Function)) delete this[i];
-    }
+    DebuggerController.Breakpoints.initialize();
+    this.editor.focus();
   },
 
   /**
-   * Removes the SourceEditor instance and added breakpoints.
+   * Destroys the SourceEditor instance and also executes any necessary
+   * post-unload operations.
    */
-  destroyEditor: function DV_destroyEditor() {
+  _destroyEditor: function DV__destroyEditor() {
+    dumpn("Destroying the DebuggerView editor");
+
     DebuggerController.Breakpoints.destroy();
     this.editor = null;
   },
 
   /**
-   * The load event handler for the source editor. This method does post-load
-   * editor initialization.
+   * Sets the proper editor mode (JS or HTML) according to the specified
+   * content type, or by determining the type from the url.
+   *
+   * @param string aUrl
+   *        The script url.
+   * @param string aContentType [optional]
+   *        The script content type.
+   */
+  setEditorMode: function DV_setEditorMode(aUrl, aContentType) {
+    if (!this.editor) {
+      return;
+    }
+    dumpn("Setting the DebuggerView editor mode: " + aUrl +
+          ", content type: " + aContentType);
+
+    if (aContentType) {
+      if (/javascript/.test(aContentType)) {
+        this.editor.setMode(SourceEditor.MODES.JAVASCRIPT);
+      } else {
+        this.editor.setMode(SourceEditor.MODES.HTML);
+      }
+    } else {
+      // Use JS mode for files with .js and .jsm extensions.
+      if (/\.jsm?$/.test(SourceUtils.trimUrlQuery(aUrl))) {
+        this.editor.setMode(SourceEditor.MODES.JAVASCRIPT);
+      } else {
+        this.editor.setMode(SourceEditor.MODES.HTML);
+      }
+    }
+  },
+
+  /**
+   * Load the editor with the specified source text.
+   *
+   * @param object aSource
+   *        The source object coming from the active thread.
+   * @param object aOptions [optional]
+   *        Additional options for showing the source. Supported options:
+   *        - targetLine: place the caret position at the given line number
+   *        - debugLine: place the debug location at the given line number
+   *        - callback: function called when the source is shown
    */
-  _onEditorLoad: function DV__onEditorLoad() {
-    DebuggerController.Breakpoints.initialize();
-    this.editor.focus();
+  setEditorSource: function DV_setEditorSource(aSource, aOptions = {}) {
+    if (!this.editor) {
+      return;
+    }
+    dumpn("Setting the DebuggerView editor source: " + aSource.url +
+          ", loaded: " + aSource.loaded +
+          ", options: " + aOptions.toSource());
+
+    // If the source is not loaded, display a placeholder text.
+    if (!aSource.loaded) {
+      this.editor.setMode(SourceEditor.MODES.TEXT);
+      this.editor.setText(L10N.getStr("loadingText"));
+      this.editor.resetUndo();
+
+      // Get the source text from the active thread.
+      DebuggerController.SourceScripts.getText(aSource, function(aUrl, aText) {
+        aSource.loaded = true;
+        aSource.text = aText;
+        this.setEditorSource(aSource, aOptions);
+      }.bind(this));
+    }
+    // If the source is already loaded, display it immediately.
+    else {
+      if (aSource.text.length < SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE) {
+        this.setEditorMode(aSource.url, aSource.contentType);
+      }
+      this.editor.setText(aSource.text);
+      this.editor.resetUndo();
+      this.updateEditor();
+
+      DebuggerView.Sources.selectedValue = aSource.url;
+      DebuggerController.Breakpoints.updateEditorBreakpoints();
+
+      // Handle any additional options for showing the source.
+      if (aOptions.targetLine) {
+        editor.setCaretPosition(aOptions.targetLine - 1);
+      }
+      if (aOptions.debugLine) {
+        editor.setDebugLocation(aOptions.debugLine - 1);
+      }
+      if (aOptions.callback) {
+        aOptions.callback(aSource);
+      }
+      // Notify that we've shown a source file.
+      window.dispatchEvent("Debugger:SourceShown", aSource);
+    }
   },
 
   /**
-   * Called when the panes toggle button is clicked.
+   * Update the source editor's current caret and debug location based on
+   * a requested url and line. If unspecified, they default to the location
+   * given by the currently active frame in the stack.
+   *
+   * @param string aUrl [optional]
+   *        The target source url.
+   * @param number aLine [optional]
+   *        The target line number in the source.
+   * @param object aFlags [optional]
+   *        An object containing some of the following boolean properties:
+   *          - noSwitch: don't switch to the source if not currently selected
+   *          - noCaret: don't set the caret location at the specified line
+   *          - noDebug: don't set the debug location at the specified line
    */
-  _onTogglePanesButtonPressed: function DV__onTogglePanesButtonPressed() {
-    this.toggleStackframesAndBreakpointsPane({
-      visible: !!this._togglePanesButton.getAttribute("stackframesAndBreakpointsHidden"),
-      animated: true
-    });
-    this.toggleVariablesPane({
-      visible: !!this._togglePanesButton.getAttribute("variablesHidden"),
-      animated: true
-    });
-    this._onPanesToggle();
+  updateEditor: function DV_updateEditor(aUrl, aLine, aFlags = {}) {
+    if (!this.editor) {
+      return;
+    }
+    // If the location is not specified, default to the location given by
+    // the currently active frame in the stack.
+    if (!aUrl && !aLine) {
+      let cachedFrames = DebuggerController.activeThread.cachedFrames;
+      let currentFrame = DebuggerController.StackFrames.currentFrame;
+      let frame = cachedFrames[currentFrame];
+      if (frame) {
+        let { url, line } = frame.where;
+        this.updateEditor(url, line, { noSwitch: true });
+      }
+      return;
+    }
+
+    dumpn("Updating the DebuggerView editor: " + aUrl + " @ " + aLine +
+          ", flags: " + aFlags.toSource());
+
+    // If the currently displayed source is the requested one, update.
+    if (this.Sources.selectedValue == aUrl) {
+      updateLine(aLine);
+    }
+    // If the requested source exists, display it and update.
+    else if (this.Sources.containsValue(aUrl) && !aFlags.noSwitch) {
+      this.Sources.selectedValue = aUrl;
+      updateLine(aLine);
+    }
+    // Dumb request, invalidate the caret position and debug location.
+    else {
+      updateLine(0);
+    }
+
+    // Updates the source editor's caret position and debug location.
+    // @param number a Line
+    function updateLine(aLine) {
+      if (!aFlags.noCaret) {
+        DebuggerView.editor.setCaretPosition(aLine - 1);
+      }
+      if (!aFlags.noDebug) {
+        DebuggerView.editor.setDebugLocation(aLine - 1);
+      }
+    }
   },
 
   /**
-   * Sets the close button hidden or visible. It's hidden by default.
-   * @param boolean aVisibleFlag
+   * 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.
    */
-  toggleCloseButton: function DV_toggleCloseButton(aVisibleFlag) {
-    document.getElementById("close").setAttribute("hidden", !aVisibleFlag);
+  getEditorLine: function SS_getEditorLine(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);
   },
 
   /**
-   * Sets the stackframes and breakpoints pane hidden or visible.
+   * Gets the visibility state of the panes.
+   * @return boolean
+   */
+  get panesHidden()
+    this.stackframesAndBreakpointsHidden && this.variablesHidden,
+
+  /**
+   * Gets the visibility state of the stackframes and breakpoints pane.
+   * @return boolean
+   */
+  get stackframesAndBreakpointsHidden()
+    !!this._togglePanesButton.getAttribute("stackframesAndBreakpointsHidden"),
+
+  /**
+   * Gets the visibility state of the varialbes pane.
+   * @return boolean
+   */
+  get variablesHidden()
+    !!this._togglePanesButton.getAttribute("variablesHidden"),
+
+  /**
+   * Sets all the panes hidden or visible.
    *
    * @param object aFlags [optional]
-   *        An object containing some of the following booleans:
+   *        An object containing some of the following boolean properties:
    *        - visible: true if the pane should be shown, false for hidden
    *        - animated: true to display an animation on toggle
    *        - silent: true to not update any designated prefs
    */
-  toggleStackframesAndBreakpointsPane:
-  function DV_toggleStackframesAndBreakpointsPane(aFlags = {}) {
+  togglePanes: function DV__togglePanes(aFlags = {}) {
+    this._toggleStackframesAndBreakpointsPane(aFlags);
+    this._toggleVariablesPane(aFlags);
+  },
+
+  /**
+   * Shows the stackframes, breakpoints and variable panes if currently hidden
+   * and the preferences dictate otherwise.
+   */
+  showPanesIfPreffered: function DV_showPanesIfPreffered() {
+    let self = this;
+
+    // Try to keep animations as smooth as possible, so wait a few cycles.
+    window.setTimeout(function() {
+      let target;
+
+      if (Prefs.stackframesPaneVisible && self.stackframesAndBreakpointsHidden) {
+        self._toggleStackframesAndBreakpointsPane({
+          visible: true,
+          animated: true,
+          silent: true
+        });
+        target = self._stackframesAndBreakpoints;
+      }
+      if (Prefs.variablesPaneVisible && self.variablesHidden) {
+        self._toggleVariablesPane({
+          visible: true,
+          animated: true,
+          silent: true
+        });
+        target = self._variables;
+      }
+      // Displaying the panes may have the effect of triggering scrollbars to
+      // appear in the source editor, which would render the currently
+      // highlighted line to appear behind them in some cases.
+      if (target) {
+        target.addEventListener("transitionend", function onEvent() {
+          target.removeEventListener("transitionend", onEvent, false);
+          self.updateEditor();
+        }, false);
+      }
+    }, PANES_APPEARANCE_DELAY);
+  },
+
+  /**
+   * Sets the stackframes and breakpoints (left) pane hidden or visible.
+   * @see DebuggerView.togglePanes
+   */
+  _toggleStackframesAndBreakpointsPane:
+  function DV__toggleStackframesAndBreakpointsPane(aFlags) {
     if (aFlags.animated) {
       this._stackframesAndBreakpoints.setAttribute("animated", "");
     } else {
       this._stackframesAndBreakpoints.removeAttribute("animated");
     }
     if (aFlags.visible) {
       this._stackframesAndBreakpoints.style.marginLeft = "0";
       this._togglePanesButton.removeAttribute("stackframesAndBreakpointsHidden");
@@ -220,26 +408,21 @@ let DebuggerView = {
       this._togglePanesButton.setAttribute("tooltiptext", L10N.getStr("expandPanes"));
     }
     if (!aFlags.silent) {
       Prefs.stackframesPaneVisible = !!aFlags.visible;
     }
   },
 
   /**
-   * Sets the variable spane hidden or visible.
-   *
-   * @param object aFlags [optional]
-   *        An object containing some of the following booleans:
-   *        - visible: true if the pane should be shown, false for hidden
-   *        - animated: true to display an animation on toggle
-   *        - silent: true to not update any designated prefs
+   * Sets the variables (right) pane hidden or visible.
+   * @see DebuggerView.togglePanes
    */
-  toggleVariablesPane:
-  function DV_toggleVariablesPane(aFlags = {}) {
+  _toggleVariablesPane:
+  function DV__toggleVariablesPane(aFlags) {
     if (aFlags.animated) {
       this._variables.setAttribute("animated", "");
     } else {
       this._variables.removeAttribute("animated");
     }
     if (aFlags.visible) {
       this._variables.style.marginRight = "0";
       this._togglePanesButton.removeAttribute("variablesHidden");
@@ -251,3396 +434,998 @@ let DebuggerView = {
       this._togglePanesButton.setAttribute("tooltiptext", L10N.getStr("expandPanes"));
     }
     if (!aFlags.silent) {
       Prefs.variablesPaneVisible = !!aFlags.visible;
     }
   },
 
   /**
-   * Shows the stackframes, breakpoints and variable panes if currently hidden
-   * and the preferences dictate otherwise.
+   * Handles any initialization on a tab navigation event issued by the client.
+   */
+  _handleTabNavigation: function DV__handleTabNavigation() {
+    dumpn("Handling tab navigation in the DebuggerView");
+
+    this.ChromeGlobals.empty();
+    this.Sources.empty();
+    this.Filtering.clearSearch();
+    this.GlobalSearch.clearView();
+    this.GlobalSearch.clearCache();
+    this.StackFrames.empty();
+    this.Breakpoints.empty();
+    this.Variables.empty();
+    SourceUtils.clearLabelsCache();
+
+    if (this.editor) {
+      this.editor.setText("");
+    }
+  },
+
+  Toolbar: null,
+  Options: null,
+  ChromeGlobals: null,
+  Sources: null,
+  Filtering: null,
+  StackFrames: null,
+  Breakpoints: null,
+  GlobalSearch: null,
+  Variables: null,
+  _editor: null,
+  _togglePanesButton: null,
+  _stackframesAndBreakpoints: null,
+  _variables: null,
+  _isInitialized: false,
+};
+
+/**
+ * A generic item used to describe elements present in views like the
+ * ChromeGlobals, Sources, Stackframes, Breakpoints etc.
+ *
+ * @param string aLabel
+ *        The label displayed in the container.
+ * @param string aValue
+ *        The actual internal value of the item.
+ * @param string aDescription [optional]
+ *        An optional description of the item.
+ * @param any aAttachment [optional]
+ *        Some attached primitive/object.
+ */
+function MenuItem(aLabel, aValue, aDescription, aAttachment) {
+  this._label = aLabel + "";
+  this._value = aValue + "";
+  this._description = aDescription + "";
+  this.attachment = aAttachment;
+}
+
+MenuItem.prototype = {
+  /**
+   * Gets the label set for this item.
+   * @return string
+   */
+  get label() this._label,
+
+  /**
+   * Gets the value set for this item.
+   * @return string
+   */
+  get value() this._value,
+
+  /**
+   * Gets the description set for this item.
+   * @return string
+   */
+  get description() this._description,
+
+  /**
+   * Gets the element associated with this item.
+   * @return nsIDOMNode
    */
-  showPanesIfAllowed: function DV_showPanesIfAllowed() {
-    // Try to keep animations as smooth as possible, so wait a few cycles.
-    window.setTimeout(function() {
-      let shown;
+  get target() this._target,
+
+  _label: "",
+  _value: "",
+  _description: "",
+  _target: null,
+  finalize: null,
+  attachment: null
+};
+
+/**
+ * A generic items container, used for displaying views like the
+ * ChromeGlobals, Sources, Stackframes, Breakpoints etc.
+ * Iterable via "for (let item in menuContainer) { }".
+ *
+ * Language:
+ *   - An "item" is an instance (or compatible iterface) of a MenuItem.
+ *   - An "element" or "node" is a nsIDOMNode.
+ *
+ * The container node supplied to all instances of this constructor can either
+ * be a <menulist> element, or any other object interfacing the following
+ * methods:
+ *   - function:nsIDOMNode appendItem(aLabel:string, aValue:string)
+ *   - function:nsIDOMNode insertItemAt(aIndex:number, aLabel:string, aValue:string)
+ *   - function:nsIDOMNode getItemAtIndex(aIndex:number)
+ *   - function removeChild(aChild:nsIDOMNode)
+ *   - function removeAllItems()
+ *   - get:number itemCount()
+ *   - get:number selectedIndex()
+ *   - set selectedIndex(aIndex:number)
+ *   - get:nsIDOMNode selectedItem()
+ *   - set selectedItem(aChild:nsIDOMNode)
+ *   - function getAttribute(aName:string)
+ *   - function setAttribute(aName:string, aValue:string)
+ *   - function removeAttribute(aName:string)
+ *   - function addEventListener(aName:string, aCallback:function, aBubbleFlag:boolean)
+ *   - function removeEventListener(aName:string, aCallback:function, aBubbleFlag:boolean)
+ *
+ * @param nsIDOMNode aContainerNode [optional]
+ *        The element associated with the displayed container. Although required,
+ *        derived objects may set this value later, upon debugger initialization.
+ */
+function MenuContainer(aContainerNode) {
+  this._container = aContainerNode;
+  this._stagedItems = [];
+  this._itemsByLabel = new Map();
+  this._itemsByValue = new Map();
+  this._itemsByElement = new Map();
+}
+
+MenuContainer.prototype = {
+  /**
+   * Prepares an item to be added to this container. This allows for a large
+   * number of items to be batched up before being alphabetically sorted and
+   * added in this menu.
+   *
+   * If the "forced" flag is true, the item will be immediately inserted at the
+   * correct position in this container, so that all the items remain sorted.
+   * This can (possibly) be much slower than batching up multiple items.
+   *
+   * By default, this container assumes that all the items should be displayed
+   * sorted by their label. This can be overridden with the "unsorted" flag.
+   *
+   * Furthermore, this container makes sure that all the items are unique
+   * (two items with the same label or value are not allowed) and non-degenerate
+   * (items with "undefined" or "null" labels/values). This can, as well, be
+   * overridden via the "relaxed" flag.
+   *
+   * @param string aLabel
+   *        The label displayed in the container.
+   * @param string aValue
+   *        The actual internal value of the item.
+   * @param object aOptions [optional]
+   *        Additional options or flags supported by this operation:
+   *          - forced: true to force the item to be immediately added
+   *          - unsorted: true if the items should not remain sorted
+   *          - relaxed: true if this container should allow dupes & degenerates
+   *          - description: an optional description of the item
+   *          - attachment: some attached primitive/object
+   * @return MenuItem
+   *         The item associated with the displayed element if a forced push,
+   *         undefined if the item was staged for a later commit.
+   */
+  push: function DVMC_push(aLabel, aValue, aOptions = {}) {
+    let item = new MenuItem(
+      aLabel, aValue, aOptions.description, aOptions.attachment);
+
+    // Batch the item to be added later.
+    if (!aOptions.forced) {
+      this._stagedItems.push(item);
+    }
+    // Find the target position in this container and insert the item there.
+    else if (!aOptions.unsorted) {
+      return this._insertItemAt(this._findExpectedIndex(aLabel), item, aOptions);
+    }
+    // Just append the item in this container.
+    else {
+      return this._appendItem(item, aOptions);
+    }
+  },
+
+  /**
+   * Flushes all the prepared items into this container.
+   *
+   * @param object aOptions [optional]
+   *        Additional options or flags supported by this operation:
+   *          - unsorted: true if the items should not be sorted beforehand
+   */
+  commit: function DVMC_commit(aOptions = {}) {
+    let stagedItems = this._stagedItems;
+
+    // By default, sort the items before adding them to this container.
+    if (!aOptions.unsorted) {
+      stagedItems.sort(function(a, b) a.label.toLowerCase() > b.label.toLowerCase());
+    }
+    // Append the prepared items to this container.
+    for (let item of stagedItems) {
+      this._appendItem(item, aOptions);
+    }
+    // Recreate the temporary items list for ulterior pushes.
+    this._stagedItems = [];
+  },
+
+  /**
+   * Updates this container to reflect the information provided by the
+   * currently selected item.
+   *
+   * @return boolean
+   *         True if a selected item was available, false otherwise.
+   */
+  refresh: function DVMC_refresh() {
+    let selectedValue = this.selectedValue;
+    if (!selectedValue) {
+      return false;
+    }
+
+    let entangledLabel = this.getItemByValue(selectedValue).label;
+
+    this._container.setAttribute("label", entangledLabel);
+    this._container.setAttribute("tooltiptext", selectedValue);
+    return true;
+  },
 
-      if (Prefs.stackframesPaneVisible &&
-          this._togglePanesButton.getAttribute("stackframesAndBreakpointsHidden")) {
-        this.toggleStackframesAndBreakpointsPane({
-          visible: true,
-          animated: true,
-          silent: true
-        });
-        shown = true;
+  /**
+   * Immediately removes the specified item from this container.
+   *
+   * @param MenuItem aItem
+   *        The item associated with the element to remove.
+   */
+  remove: function DVMC__remove(aItem) {
+    this._container.removeChild(aItem.target);
+    this._untangleItem(aItem);
+  },
+
+  /**
+   * Removes all items from this container.
+   */
+  empty: function DVMC_empty() {
+    this._preferredValue = this.selectedValue;
+    this._container.selectedIndex = -1;
+    this._container.setAttribute("label", this._emptyLabel);
+    this._container.removeAttribute("tooltiptext");
+    this._container.removeAllItems();
+
+    for (let [_, item] of this._itemsByElement) {
+      this._untangleItem(item);
+    }
+
+    this._itemsByLabel = new Map();
+    this._itemsByValue = new Map();
+    this._itemsByElement = new Map();
+    this._stagedItems = [];
+  },
+
+  /**
+   * Does not remove any item in this container. Instead, it overrides the
+   * current label to signal that it is unavailable and removes the tooltip.
+   */
+  setUnavailable: function DVMC_setUnavailable() {
+    this._container.setAttribute("label", this._unavailableLabel);
+    this._container.removeAttribute("tooltiptext");
+  },
+
+  /**
+   * Checks whether an item with the specified label is among the elements
+   * shown in this container.
+   *
+   * @param string aLabel
+   *        The item's label.
+   * @return boolean
+   *         True if the label is known, false otherwise.
+   */
+  containsLabel: function DVMC_containsLabel(aLabel) {
+    return this._itemsByLabel.has(aLabel) ||
+           this._stagedItems.some(function(o) o.label == aLabel);
+  },
+
+  /**
+   * Checks whether an item with the specified value is among the elements
+   * shown in this container.
+   *
+   * @param string aValue
+   *        The item's value.
+   * @return boolean
+   *         True if the value is known, false otherwise.
+   */
+  containsValue: function DVMC_containsValue(aValue) {
+    return this._itemsByValue.has(aValue) ||
+           this._stagedItems.some(function(o) o.value == aValue);
+  },
+
+  /**
+   * Checks whether an item with the specified trimmed value is among the
+   * elements shown in this container.
+   *
+   * @param string aValue
+   *        The item's value.
+   * @param function aTrim [optional]
+   *        A custom trimming function.
+   * @return boolean
+   *         True if the trimmed value is known, false otherwise.
+   */
+  containsTrimmedValue:
+  function DVMC_containsTrimmedValue(aValue,
+                                     aTrim = SourceUtils.trimUrlQuery) {
+    let trimmedValue = aTrim(aValue);
+
+    for (let [value] of this._itemsByValue) {
+      if (aTrim(value) == trimmedValue) {
+        return true;
       }
-      if (Prefs.variablesPaneVisible &&
-          this._togglePanesButton.getAttribute("variablesHidden")) {
-        this.toggleVariablesPane({
-          visible: true,
-          animated: true,
-          silent: true
-        });
-        shown = true;
+    }
+    return this._stagedItems.some(function(o) aTrim(o.value) == trimmedValue);
+  },
+
+  /**
+   * Gets the preferred selected value to be displayed in this container.
+   * @return string
+   */
+  get preferredValue() this._preferredValue,
+
+  /**
+   * Retrieves the selected element's index in this container.
+   * @return number
+   */
+  get selectedIndex() this._container.selectedIndex,
+
+  /**
+   * Retrieves the item associated with the selected element.
+   * @return MenuItem
+   */
+  get selectedItem()
+    this._container.selectedItem ?
+    this._itemsByElement.get(this._container.selectedItem) : null,
+
+  /**
+   * Retrieves the label of the selected element.
+   * @return string
+   */
+  get selectedLabel()
+    this._container.selectedItem ?
+    this._itemsByElement.get(this._container.selectedItem).label : null,
+
+  /**
+   * Retrieves the value of the selected element.
+   * @return string
+   */
+  get selectedValue()
+    this._container.selectedItem ?
+    this._itemsByElement.get(this._container.selectedItem).value : null,
+
+  /**
+   * Selects the element at the specified index in this container.
+   * @param number aIndex
+   */
+  set selectedIndex(aIndex) this._container.selectedIndex = aIndex,
+
+  /**
+   * Selects the element with the entangled item in this container.
+   * @param MenuItem aItem
+   */
+  set selectedItem(aItem) this._container.selectedItem = aItem.target,
+
+  /**
+   * Selects the element with the specified label in this container.
+   * @param string aLabel
+   */
+  set selectedLabel(aLabel) {
+    let item = this._itemsByLabel.get(aValue);
+    if (item) {
+      this._container.selectedItem = item.target;
+    }
+  },
+
+  /**
+   * Selects the element with the specified value in this container.
+   * @param string aValue
+   */
+  set selectedValue(aValue) {
+    let item = this._itemsByValue.get(aValue);
+    if (item) {
+      this._container.selectedItem = item.target;
+    }
+  },
+
+  /**
+   * Gets the item in the container having the specified label.
+   *
+   * @param string aLabel
+   *        The label used to identify the element.
+   * @return MenuItem
+   *         The matched item, or null if nothing is found.
+   */
+  getItemByLabel: function DVMC_getItemByLabel(aLabel) {
+    return this._itemsByLabel.get(aLabel);
+  },
+
+  /**
+   * Gets the item in the container having the specified value.
+   *
+   * @param string aValue
+   *        The value used to identify the element.
+   * @return MenuItem
+   *         The matched item, or null if nothing is found.
+   */
+  getItemByValue: function DVMC_getItemByValue(aValue) {
+    return this._itemsByValue.get(aValue);
+  },
+
+  /**
+   * Gets the item in the container associated with the specified element.
+   *
+   * @param nsIDOMNode aElement
+   *        The element used to identify the item.
+   * @return MenuItem
+   *         The matched item, or null if nothing is found.
+   */
+  getItemForElement:
+  function DVMC_getItemForElement(aElement) {
+    while (aElement) {
+      let item = this._itemsByElement.get(aElement);
+      if (item) {
+        return item;
       }
-      if (shown) {
-        this._onPanesToggle();
-      }
-    }.bind(this), PANES_APPEARANCE_DELAY);
+      aElement = aElement.parentNode;
+    }
+    return null;
+  },
+
+  /**
+   * Returns the list of labels in this container.
+   * @return array
+   */
+  get labels() {
+    let labels = [];
+    for (let [label] of this._itemsByLabel) {
+      labels.push(label);
+    }
+    return labels;
+  },
+
+  /**
+   * Returns the list of values in this container.
+   * @return array
+   */
+  get values() {
+    let values = [];
+    for (let [value] of this._itemsByValue) {
+      values.push(value);
+    }
+    return values;
+  },
+
+  /**
+   * Gets the total visible (non-hidden) items in this container.
+   * @return number
+   */
+  get visibleItems() {
+    let count = 0;
+    for (let [element] of this._itemsByElement) {
+      count += element.hidden ? 0 : 1;
+    }
+    return count;
   },
 
   /**
-   * Displaying the panes may have the effect of triggering scrollbars to
-   * appear in the source editor, which would render the currently highlighted
-   * line to appear behind them in some cases.
+   * Specifies the required conditions for an item to be considered unique.
+   * Possible values:
+   *   - 1: label AND value are different from all other items
+   *   - 2: label OR value are different from all other items
+   *   - 3: only label is required to be different
+   *   - 4: only value is required to be different
+   */
+  uniquenessQualifier: 1,
+
+  /**
+   * Checks if an item is unique in this container.
+   *
+   * @param MenuItem aItem
+   *        An object containing a label and a value property.
+   * @return boolean
+   *         True if the element is unique, false otherwise.
+   */
+  isUnique: function DVMC_isUnique(aItem) {
+    switch (this.uniquenessQualifier) {
+      case 1:
+        return !this._itemsByLabel.has(aItem.label) &&
+               !this._itemsByValue.has(aItem.value);
+      case 2:
+        return !this._itemsByLabel.has(aItem.label) ||
+               !this._itemsByValue.has(aItem.value);
+      case 3:
+        return !this._itemsByLabel.has(aItem.label);
+      case 4:
+        return !this._itemsByValue.has(aItem.value);
+    }
+    return false;
+  },
+
+  /**
+   * Checks if an item's label and value are eligible for this container.
+   *
+   * @param MenuItem aItem
+   *        An object containing a label and a value property.
+   * @return boolean
+   *         True if the element is eligible, false otherwise.
+   */
+  isEligible: function DVMC_isEligible(aItem) {
+    return this.isUnique(aItem) &&
+           aItem.label != "undefined" && aItem.label != "null" &&
+           aItem.value != "undefined" && aItem.value != "null";
+  },
+
+  /**
+   * Finds the expected item index in this container based on its label.
+   *
+   * @param string aLabel
+   *        The label used to identify the element.
+   * @return number
+   *         The expected item index.
+   */
+  _findExpectedIndex: function DVMC__findExpectedIndex(aLabel) {
+    let container = this._container;
+    let itemCount = container.itemCount;
+
+    for (let i = 0; i < itemCount; i++) {
+      if (this.getItemForElement(container.getItemAtIndex(i)).label > aLabel) {
+        return i;
+      }
+    }
+    return itemCount;
+  },
+
+  /**
+   * Immediately appends an item in this container.
+   *
+   * @param MenuItem aItem
+   *        An object containing a label and a value property.
+   * @param object aOptions [optional]
+   *        Additional options or flags supported by this operation:
+   *          - relaxed: true if this container should allow dupes & degenerates
+   * @return MenuItem
+   *         The item associated with the displayed element, null if rejected.
+   */
+  _appendItem:
+  function DVMC__appendItem(aItem, aOptions = {}) {
+    if (!aOptions.relaxed && !this.isEligible(aItem)) {
+      return null;
+    }
+
+    return this._entangleItem(aItem, this._container.appendItem(
+      aItem.label, aItem.value, "", aOptions.attachment));
+  },
+
+  /**
+   * Immediately inserts an item in this container at the specified index.
+   *
+   * @param number aIndex
+   *        The position in the container intended for this item.
+   * @param MenuItem aItem
+   *        An object containing a label and a value property.
+   * @param object aOptions [optional]
+   *        Additional options or flags supported by this operation:
+   *          - relaxed: true if this container should allow dupes & degenerates
+   * @return MenuItem
+   *         The item associated with the displayed element, null if rejected.
    */
-  _onPanesToggle: function DV__onPanesToggle() {
-    document.addEventListener("transitionend", function onEvent() {
-      document.removeEventListener("transitionend", onEvent);
-      DebuggerController.StackFrames.updateEditorLocation();
-    });
+  _insertItemAt:
+  function DVMC__insertItemAt(aIndex, aItem, aOptions) {
+    if (!aOptions.relaxed && !this.isEligible(aItem)) {
+      return null;
+    }
+
+    return this._entangleItem(aItem, this._container.insertItemAt(
+      aIndex, aItem.label, aItem.value, "", aOptions.attachment));
+  },
+
+  /**
+   * Entangles an item (model) with a displayed node element (view).
+   *
+   * @param MenuItem aItem
+   *        The item describing the element.
+   * @param nsIDOMNode aElement
+   *        The element displaying the item.
+   * @return MenuItem
+   *         The same item.
+   */
+  _entangleItem: function DVMC__entangleItem(aItem, aElement) {
+    this._itemsByLabel.set(aItem.label, aItem);
+    this._itemsByValue.set(aItem.value, aItem);
+    this._itemsByElement.set(aElement, aItem);
+
+    aItem._target = aElement;
+    return aItem;
+  },
+
+  /**
+   * Untangles an item (model) from a displayed node element (view).
+   *
+   * @param MenuItem aItem
+   *        The item describing the element.
+   * @return MenuItem
+   *         The same item.
+   */
+  _untangleItem: function DVMC__untangleItem(aItem) {
+    if (aItem.finalize instanceof Function) {
+      aItem.finalize(aItem);
+    }
+
+    this._itemsByLabel.delete(aItem.label);
+    this._itemsByValue.delete(aItem.value);
+    this._itemsByElement.delete(aItem.target);
+
+    aItem._target = null;
+    return aItem;
+  },
+
+  /**
+   * A generator-iterator over all the items in this container.
+   */
+  __iterator__: function DVMC_iterator() {
+    for (let [_, item] of this._itemsByElement) {
+      yield item;
+    }
+  },
+
+  _container: null,
+  _stagedItems: null,
+  _itemsByLabel: null,
+  _itemsByValue: null,
+  _itemsByElement: null,
+  _preferredValue: null,
+  _emptyLabel: "",
+  _unavailableLabel: ""
+};
+
+/**
+ * A stacked list of items, compatible with MenuContainer instances, used for
+ * displaying views like the StackFrames, Breakpoints etc.
+ *
+ * Custom methods introduced by this view, not necessary for a MenuContainer:
+ * set emptyText(aValue:string)
+ * set itemType(aType:string)
+ * set itemFactory(aCallback:function)
+ *
+ * TODO: Use this in #796135 - "Provide some obvious UI for scripts filtering".
+ *
+ * @param nsIDOMNode aAssociatedNode
+ *        The element associated with the displayed container.
+ */
+function StackList(aAssociatedNode) {
+  this._parent = aAssociatedNode;
+  this._appendEmptyNotice();
+
+  // Create an internal list container.
+  this._list = document.createElement("vbox");
+  this._parent.appendChild(this._list);
+}
+
+StackList.prototype = {
+  /**
+   * Immediately appends an item in this container.
+   *
+   * @param string aLabel
+   *        The label displayed in the container.
+   * @param string aValue
+   *        The actual internal value of the item.
+   * @param string aDescription [optional]
+   *        An optional description of the item.
+   * @param any aAttachment [optional]
+   *        Some attached primitive/object.
+   * @return nsIDOMNode
+   *         The element associated with the displayed item.
+   */
+  appendItem:
+  function DVSL_appendItem(aLabel, aValue, aDescription, aAttachment) {
+    return this.insertItemAt(
+      Number.MAX_VALUE, aLabel, aValue, aDescription, aAttachment);
   },
 
   /**
-   * The cached global view elements.
+   * Immediately inserts an item in this container at the specified index.
+   *
+   * @param number aIndex
+   *        The position in the container intended for this item.
+   * @param string aLabel
+   *        The label displayed in the container.
+   * @param string aValue
+   *        The actual internal value of the item.
+   * @param string aDescription [optional]
+   *        An optional description of the item.
+   * @param any aAttachment [optional]
+   *        Some attached primitive/object.
+   * @return nsIDOMNode
+   *         The element associated with the displayed item.
+   */
+  insertItemAt:
+  function DVSL_insertItemAt(aIndex, aLabel, aValue, aDescription, aAttachment) {
+    let list = this._list;
+    let childNodes = list.childNodes;
+
+    let element = document.createElement(this.itemType);
+    this._createItemView(element, aLabel, aValue, aAttachment);
+    this._removeEmptyNotice();
+
+    return list.insertBefore(element, childNodes[aIndex]);
+  },
+
+  /**
+   * Returns the child node in this container situated at the specified index.
+   *
+   * @param number aIndex
+   *        The position in the container intended for this item.
+   * @return nsIDOMNode
+   *         The element associated with the displayed item.
+   */
+  getItemAtIndex: function DVSL_getItemAtIndex(aIndex) {
+    return this._list.childNodes[aIndex];
+  },
+
+  /**
+   * Immediately removes the specified child node from this container.
+   *
+   * @param nsIDOMNode aChild
+   *        The element associated with the displayed item.
+   */
+  removeChild: function DVSL__removeChild(aChild) {
+    this._list.removeChild(aChild);
+
+    if (!this.itemCount) {
+      this._appendEmptyNotice();
+    }
+  },
+
+  /**
+   * Immediately removes all of the child nodes from this container.
+   */
+  removeAllItems: function DVSL_removeAllItems() {
+    let parent = this._parent;
+    let list = this._list;
+    let firstChild;
+
+    while (firstChild = list.firstChild) {
+      list.removeChild(firstChild);
+    }
+    parent.scrollTop = 0;
+    parent.scrollLeft = 0;
+
+    this._selectedItem = null;
+    this._selectedIndex = -1;
+    this._appendEmptyNotice();
+  },
+
+  /**
+   * Gets the number of child nodes present in this container.
+   * @return number
+   */
+  get itemCount() this._list.childNodes.length,
+
+  /**
+   * Gets the index of the selected child node in this container.
+   * @return number
+   */
+  get selectedIndex() this._selectedIndex,
+
+  /**
+   * Sets the index of the selected child node in this container.
+   * Only one child node may be selected at a time.
+   * @param number aIndex
+   */
+  set selectedIndex(aIndex) this.selectedItem = this._list.childNodes[aIndex],
+
+  /**
+   * Gets the currently selected child node in this container.
+   * @return nsIDOMNode
+   */
+  get selectedItem() this._selectedItem,
+
+  /**
+   * Sets the currently selected child node in this container.
+   * @param nsIDOMNode aChild
+   */
+  set selectedItem(aChild) {
+    let childNodes = this._list.childNodes;
+
+    if (!aChild) {
+      this._selectedItem = null;
+      this._selectedIndex = -1;
+    }
+    for (let node of childNodes) {
+      if (node == aChild) {
+        node.classList.add("selected");
+        this._selectedIndex = Array.indexOf(childNodes, node);
+        this._selectedItem = node;
+      } else {
+        node.classList.remove("selected");
+      }
+    }
+  },
+
+  /**
+   * Applies an attribute to this container.
+   *
+   * @param string aName
+   *        The name of the attribute to set.
+   * @return string
+   *         The attribute value.
    */
-  _togglePanesButton: null,
-  _stackframesAndBreakpoints: null,
-  _stackframes: null,
-  _breakpoints: null,
-  _variables: null,
-  _scripts: null,
-  _globalSearch: null,
-  _globalSearchSplitter: null,
-  _fileSearchKey: null,
-  _lineSearchKey: null,
-  _tokenSearchKey: null,
-  _globalSearchKey: null,
-  _resumeKey: null,
-  _stepOverKey: null,
-  _stepInKey: null,
-  _stepOutKey: null,
-  _resumeButton: null,
-  _stepOverButton: null,
-  _stepInButton: null,
-  _stepOutButton: null,
-  _scriptsSearchbox: null,
-  _globalOperatorLabel: null,
-  _globalOperatorButton: null,
-  _tokenOperatorLabel: null,
-  _tokenOperatorButton: null,
-  _lineOperatorLabel: null,
-  _lineOperatorButton: null
+  getAttribute: function DVSL_setAttribute(aName) {
+    return this._parent.getAttribute(aName);
+  },
+
+  /**
+   * Applies an attribute to this container.
+   *
+   * @param string aName
+   *        The name of the attribute to set.
+   * @param any aValue
+   *        The supplied attribute value.
+   */
+  setAttribute: function DVSL_setAttribute(aName, aValue) {
+    this._parent.setAttribute(aName, aValue);
+  },
+
+  /**
+   * Removes an attribute applied to this container.
+   *
+   * @param string aName
+   *        The name of the attribute to remove.
+   */
+  removeAttribute: function DVSL_removeAttribute(aName) {
+    this._parent.removeAttribute(aName);
+  },
+
+  /**
+   * Adds an event listener to this container.
+   *
+   * @param string aName
+   *        The name of the listener to set.
+   * @param function aCallback
+   *        The function to be called when the event is triggered.
+   * @param boolean aBubbleFlag
+   *        True if the event should bubble.
+   */
+  addEventListener:
+  function DVSL_addEventListener(aName, aCallback, aBubbleFlag) {
+    this._parent.addEventListener(aName, aCallback, aBubbleFlag);
+  },
+
+  /**
+   * Removes an event listener added to this container.
+   *
+   * @param string aName
+   *        The name of the listener to remove.
+   * @param function aCallback
+   *        The function called when the event was triggered.
+   * @param boolean aBubbleFlag
+   *        True if the event was bubbling.
+   */
+  removeEventListener:
+  function DVSL_removeEventListener(aName, aCallback, aBubbleFlag) {
+    this._parent.removeEventListener(aName, aCallback, aBubbleFlag);
+  },
+
+  /**
+   * Sets the text displayed in this container when there are no available items.
+   * @param string aValue
+   */
+  set emptyText(aValue) {
+    if (this._emptyTextNode) {
+      this._emptyTextNode.setAttribute("value", aValue);
+    }
+    this._emptyTextValue = aValue;
+  },
+
+  /**
+   * Overrides the item's element type (e.g. "vbox" or "hbox").
+   * @param string aType
+   */
+  itemType: "hbox",
+
+  /**
+   * Overrides the customization function for creating an item's UI.
+   * @param function aCallback
+   */
+  set itemFactory(aCallback) this._createItemView = aCallback,
+
+  /**
+   * Customization function for creating an item's UI for this container.
+   *
+   * @param nsIDOMNode aElementNode
+   *        The element associated with the displayed item.
+   * @param string aLabel
+   *        The item's label.
+   * @param string aValue
+   *        The item's value.
+   */
+  _createItemView: function DVSL__createItemView(aElementNode, aLabel, aValue) {
+    let labelNode = document.createElement("label");
+    let valueNode = document.createElement("label");
+    let spacer = document.createElement("spacer");
+
+    labelNode.setAttribute("value", aLabel);
+    valueNode.setAttribute("value", aValue);
+    spacer.setAttribute("flex", "1");
+
+    aElementNode.appendChild(labelNode);
+    aElementNode.appendChild(spacer);
+    aElementNode.appendChild(valueNode);
+
+    aElementNode.labelNode = labelNode;
+    aElementNode.valueNode = valueNode;
+  },
+
+  /**
+   * Creates and appends a label signaling that this container is empty.
+   */
+  _appendEmptyNotice: function DVSL__appendEmptyNotice() {
+    if (this._emptyTextNode) {
+      return;
+    }
+
+    let label = document.createElement("label");
+    label.className = "empty list-item";
+    label.setAttribute("value", this._emptyTextValue);
+
+    this._parent.appendChild(label);
+    this._emptyTextNode = label;
+  },
+
+  /**
+   * Removes the label signaling that this container is empty.
+   */
+  _removeEmptyNotice: function DVSL__removeEmptyNotice() {
+    if (!this._emptyTextNode) {
+      return;
+    }
+
+    this._parent.removeChild(this._emptyTextNode);
+    this._emptyTextNode = null;
+  },
+
+  _parent: null,
+  _list: null,
+  _selectedIndex: -1,
+  _selectedItem: null,
+  _emptyTextNode: null,
+  _emptyTextValue: ""
 };
 
 /**
  * A simple way of displaying a "Connect to..." prompt.
  */
 function RemoteDebuggerPrompt() {
-
-  /**
-   * The remote host and port the user wants to connect to.
-   */
   this.remote = {};
 }
 
 RemoteDebuggerPrompt.prototype = {
-
   /**
-   * Shows the prompt and sets the uri using the user input.
+   * Shows the prompt and waits for a remote host and port to connect to.
    *
    * @param boolean aIsReconnectingFlag
-   *                True to show the reconnect message instead.
+   *        True to show the reconnect message instead of the connect request.
    */
   show: function RDP_show(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);
 
-      Prefs.remoteAutoConnect = check.value;
-
       if (!result) {
         return false;
       }
-      if ((parts = input.value.split(":")).length === 2) {
+      if ((parts = input.value.split(":")).length == 2) {
         let [host, port] = parts;
 
         if (host.length && port.length) {
-          this.remote = { host: host, port: port };
+          this.remote = { host: host, port: port, auto: check.value };
           return true;
         }
       }
     }
   }
 };
-
-/**
- * Functions handling the global search UI.
- */
-function GlobalSearchView() {
-  this._onFetchScriptFinished = this._onFetchScriptFinished.bind(this);
-  this._onFetchScriptsFinished = this._onFetchScriptsFinished.bind(this);
-  this._onLineClick = this._onLineClick.bind(this);
-  this._onMatchClick = this._onMatchClick.bind(this);
-  this._onResultsScroll = this._onResultsScroll.bind(this);
-  this._onFocusLost = this._onFocusLost.bind(this);
-  this._startSearch = this._startSearch.bind(this);
-}
-
-GlobalSearchView.prototype = {
-
-  /**
-   * Hides or shows the search results container.
-   * @param boolean value
-   */
-  set hidden(value) {
-    this._pane.hidden = value;
-    this._splitter.hidden = value;
-  },
-
-  /**
-   * True if the search results container is hidden.
-   * @return boolean
-   */
-  get hidden() this._pane.hidden,
-
-  /**
-   * Removes all elements from the search results container, leaving it empty.
-   */
-  empty: function DVGS_empty() {
-    while (this._pane.firstChild) {
-      this._pane.removeChild(this._pane.firstChild);
-    }
-    this._pane.scrollTop = 0;
-    this._pane.scrollLeft = 0;
-    this._currentlyFocusedMatch = -1;
-  },
-
-  /**
-   * Hides and empties the search results container.
-   */
-  hideAndEmpty: function DVGS_hideAndEmpty() {
-    this.hidden = true;
-    this.empty();
-    DebuggerController.dispatchEvent("Debugger:GlobalSearch:ViewCleared");
-  },
-
-  /**
-   * Clears all the fetched scripts from the cache.
-   */
-  clearCache: function DVGS_clearCache() {
-    this._scriptSources = new Map();
-    DebuggerController.dispatchEvent("Debugger:GlobalSearch:CacheCleared");
-  },
-
-  /**
-   * Starts fetching all the script sources, silently.
-   *
-   * @param function aFetchCallback [optional]
-   *        Called after each script is fetched.
-   * @param function aFetchedCallback [optional]
-   *        Called if all the scripts were already fetched.
-   * @param array aUrls [optional]
-   *        The urls for the scripts to fetch. If undefined, it defaults to
-   *        all the currently known scripts.
-   */
-  fetchScripts:
-  function DVGS_fetchScripts(aFetchCallback = null,
-                             aFetchedCallback = null,
-                             aUrls = DebuggerView.Scripts.scriptLocations) {
-
-    // If all the scripts sources were already fetched, then don't do anything.
-    if (this._scriptSources.size() === aUrls.length) {
-      aFetchedCallback && aFetchedCallback();
-      return;
-    }
-
-    // Fetch each new script's source.
-    for (let url of aUrls) {
-      if (this._scriptSources.has(url)) {
-        continue;
-      }
-      DebuggerController.dispatchEvent("Debugger:LoadSource", {
-        script: DebuggerView.Scripts.getScriptByLocation(url).getUserData("sourceScript"),
-        options: {
-          silent: true,
-          callback: aFetchCallback
-        }
-      });
-    }
-  },
-
-  /**
-   * Schedules searching for a token in all the scripts.
-   */
-  scheduleSearch: function DVGS_scheduleSearch() {
-    window.clearTimeout(this._searchTimeout);
-    this._searchTimeout = window.setTimeout(this._startSearch, GLOBAL_SEARCH_ACTION_DELAY);
-  },
-
-  /**
-   * Starts searching for a token in all the scripts.
-   */
-  _startSearch: function DVGS__startSearch() {
-    let scriptLocations = DebuggerView.Scripts.scriptLocations;
-    this._scriptCount = scriptLocations.length;
-
-    this.fetchScripts(
-      this._onFetchScriptFinished, this._onFetchScriptsFinished, scriptLocations);
-  },
-
-  /**
-   * Called when a script's source has been fetched.
-   *
-   * @param string aScriptUrl
-   *        The URL of the source script.
-   * @param string aSourceText
-   *        The text of the source script.
-   */
-  _onFetchScriptFinished: function DVGS__onFetchScriptFinished(aScriptUrl, aSourceText) {
-    this._scriptSources.set(aScriptUrl, aSourceText);
-
-    if (this._scriptSources.size() === this._scriptCount) {
-      this._onFetchScriptsFinished();
-    }
-  },
-
-  /**
-   * Called when all the script's sources have been fetched.
-   */
-  _onFetchScriptsFinished: function DVGS__onFetchScriptsFinished() {
-    this.empty();
-
-    let token = DebuggerView.Scripts.searchToken;
-    let lowerCaseToken = token.toLowerCase();
-
-    // Make sure we're actually searching for something.
-    if (!token) {
-      DebuggerController.dispatchEvent("Debugger:GlobalSearch:TokenEmpty");
-      this.hidden = true;
-      return;
-    }
-
-    // Prepare the results map, containing search details for each script/line.
-    let globalResults = new Map();
-
-    for (let [url, text] of this._scriptSources) {
-      // Check if the search token is not found anywhere in the script source.
-      if (!text.toLowerCase().contains(lowerCaseToken)) {
-        continue;
-      }
-      let lines = text.split("\n");
-      let scriptResults = {
-        lineResults: [],
-        matchCount: 0
-      };
-
-      for (let i = 0, len = lines.length; i < len; i++) {
-        let line = lines[i];
-        let lowerCaseLine = line.toLowerCase();
-
-        // Search is not case sensitive, and is tied to each line in the source.
-        if (!lowerCaseLine.contains(lowerCaseToken)) {
-          continue;
-        }
-
-        let lineNumber = i;
-        let lineContents = [];
-
-        lowerCaseLine.split(lowerCaseToken).reduce(function(prev, curr, index, {length}) {
-          let unmatched = line.substr(prev.length, curr.length);
-          lineContents.push({ string: unmatched });
-
-          if (index !== length - 1) {
-            let matched = line.substr(prev.length + curr.length, token.length);
-            let range = {
-              start: prev.length + curr.length,
-              length: matched.length
-            };
-            lineContents.push({
-              string: matched,
-              range: range,
-              match: true
-            });
-            scriptResults.matchCount++;
-          }
-          return prev + token + curr;
-        }, "");
-
-        scriptResults.lineResults.push({
-          lineNumber: lineNumber,
-          lineContents: lineContents
-        });
-      }
-      if (scriptResults.matchCount) {
-        globalResults.set(url, scriptResults);
-      }
-    }
-
-    if (globalResults.size()) {
-      this._createGlobalResultsUI(globalResults);
-      this.hidden = false;
-      DebuggerController.dispatchEvent("Debugger:GlobalSearch:MatchFound");
-    } else {
-      this.hidden = true;
-      DebuggerController.dispatchEvent("Debugger:GlobalSearch:MatchNotFound");
-    }
-  },
-
-  /**
-   * Creates global search results elements and adds them to the results container.
-   *
-   * @param Map aGlobalResults
-   *        A map containing the search results, grouped by script url.
-   */
-  _createGlobalResultsUI:
-  function DVGS__createGlobalResultsUI(aGlobalResults) {
-    let i = 0;
-
-    for (let [scriptUrl, scriptResults] of aGlobalResults) {
-      if (i++ === 0) {
-        this._createScriptResultsUI(scriptUrl, scriptResults, true);
-      } else {
-        // Dispatch subsequent document manipulation operations, to avoid
-        // blocking the main thread when a large number of search results
-        // is found, thus giving the impression of faster searching.
-        Services.tm.currentThread.dispatch({ run:
-          this._createScriptResultsUI.bind(this, scriptUrl, scriptResults) }, 0);
-      }
-    }
-  },
-
-  /**
-   * Creates script search results elements and adds them to the results container.
-   *
-   * @param string aScriptUrl
-   *        The URL of the source script.
-   * @param array aScriptResults
-   *        An array containing the search results for a single script url.
-   * @param boolean aExpandFlag
-   *        True to expand the script results container.
-   */
-  _createScriptResultsUI:
-  function DVGS__createScriptResultsUI(aScriptUrl, aScriptResults, aExpandFlag) {
-    let { lineResults, matchCount } = aScriptResults;
-    let element;
-
-    for (let lineResult of lineResults) {
-      element = this._createLineSearchResultsUI({
-        scriptUrl: aScriptUrl,
-        matchCount: matchCount,
-        lineNumber: lineResult.lineNumber + 1,
-        lineContents: lineResult.lineContents
-      });
-    }
-    if (aExpandFlag) {
-      element.expand(true);
-    }
-  },
-
-  /**
-   * Creates per-line search results elements and adds them to the results container.
-   *
-   * @param object aLineResults
-   *        An object containing the search results for each line in a script.
-   * @return object
-   *         The newly created html node representing the added search results.
-   */
-  _createLineSearchResultsUI:
-  function DVGS__createLineSearchresultsUI(aLineResults) {
-    let scriptResultsId = "search-results-" + aLineResults.scriptUrl;
-    let scriptResults = document.getElementById(scriptResultsId);
-
-    // Create the script results container if not available yet.
-    if (!scriptResults) {
-      let trimFunc = DebuggerController.SourceScripts.trimUrlLength;
-      let urlLabel = trimFunc(aLineResults.scriptUrl, GLOBAL_SEARCH_URL_MAX_SIZE);
-
-      let resultsUrl = document.createElement("label");
-      resultsUrl.className = "plain script-url";
-      resultsUrl.setAttribute("value", urlLabel);
-
-      let resultsCount = document.createElement("label");
-      resultsCount.className = "plain match-count";
-      resultsCount.setAttribute("value", "(" + aLineResults.matchCount + ")");
-
-      let arrow = document.createElement("box");
-      arrow.className = "arrow";
-
-      let resultsHeader = document.createElement("hbox");
-      resultsHeader.className = "dbg-results-header";
-      resultsHeader.setAttribute("align", "center")
-      resultsHeader.appendChild(arrow);
-      resultsHeader.appendChild(resultsUrl);
-      resultsHeader.appendChild(resultsCount);
-
-      let resultsContainer = document.createElement("vbox");
-      resultsContainer.className = "dbg-results-container";
-
-      scriptResults = document.createElement("vbox");
-      scriptResults.id = scriptResultsId;
-      scriptResults.className = "dbg-script-results";
-      scriptResults.header = resultsHeader;
-      scriptResults.container = resultsContainer;
-      scriptResults.appendChild(resultsHeader);
-      scriptResults.appendChild(resultsContainer);
-      this._pane.appendChild(scriptResults);
-
-      /**
-       * Expands the element, showing all the added details.
-       *
-       * @param boolean aSkipAnimationFlag
-       *        Pass true to not show an opening animation.
-       * @return object
-       *         The same element.
-       */
-      scriptResults.expand = function DVGS_element_expand(aSkipAnimationFlag) {
-        resultsContainer.setAttribute("open", "");
-        arrow.setAttribute("open", "");
-
-        if (!aSkipAnimationFlag) {
-          resultsContainer.setAttribute("animated", "");
-        }
-        return scriptResults;
-      };
-
-      /**
-       * Collapses the element, hiding all the added details.
-       * @return object
-       *         The same element.
-       */
-      scriptResults.collapse = function DVGS_element_collapse() {
-        resultsContainer.removeAttribute("animated");
-        resultsContainer.removeAttribute("open");
-        arrow.removeAttribute("open");
-        return scriptResults;
-      };
-
-      /**
-       * Toggles between the element collapse/expand state.
-       * @return object
-       *         The same element.
-       */
-      scriptResults.toggle = function DVGS_element_toggle(e) {
-        if (e instanceof Event) {
-          scriptResults._userToggle = true;
-        }
-        scriptResults.expanded = !scriptResults.expanded;
-        return scriptResults;
-      };
-
-      /**
-       * Returns if the element is expanded.
-       * @return boolean
-       *         True if the element is expanded.
-       */
-      Object.defineProperty(scriptResults, "expanded", {
-        get: function DVP_element_getExpanded() {
-          return arrow.hasAttribute("open");
-        },
-        set: function DVP_element_setExpanded(value) {
-          if (value) {
-            scriptResults.expand();
-          } else {
-            scriptResults.collapse();
-          }
-        }
-      });
-
-      /**
-       * Called when a header in the search results container is clicked.
-       */
-      resultsHeader.addEventListener("click", scriptResults.toggle, false);
-    }
-
-    let lineNumber = document.createElement("label");
-    lineNumber.className = "plain line-number";
-    lineNumber.setAttribute("value", aLineResults.lineNumber);
-
-    let lineContents = document.createElement("hbox");
-    lineContents.setAttribute("flex", "1");
-    lineContents.className = "line-contents";
-    lineContents.addEventListener("click", this._onLineClick, false);
-
-    let lineContent;
-    let totalLength = 0;
-    let ellipsis = Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString);
-
-    for (lineContent of aLineResults.lineContents) {
-      let string = lineContent.string;
-      let match = lineContent.match;
-
-      string = string.substr(0, GLOBAL_SEARCH_LINE_MAX_SIZE - totalLength);
-      totalLength += string.length;
-
-      let label = document.createElement("label");
-      label.className = "plain string";
-      label.setAttribute("value", string);
-      label.setAttribute("match", match || false);
-      lineContents.appendChild(label);
-
-      if (match) {
-        label.addEventListener("click", this._onMatchClick, false);
-        label.setUserData("lineResults", aLineResults, null);
-        label.setUserData("lineContentRange", lineContent.range, null);
-        label.container = scriptResults;
-      }
-      if (totalLength >= GLOBAL_SEARCH_LINE_MAX_SIZE) {
-        label = document.createElement("label");
-        label.className = "plain string";
-        label.setAttribute("value", ellipsis.data);
-        lineContents.appendChild(label);
-        break;
-      }
-    }
-
-    let searchResult = document.createElement("hbox");
-    searchResult.className = "dbg-search-result";
-    searchResult.appendChild(lineNumber);
-    searchResult.appendChild(lineContents);
-
-    let resultsContainer = scriptResults.container;
-    resultsContainer.appendChild(searchResult);
-
-    // Return the element for later use if necessary.
-    return scriptResults;
-  },
-
-  /**
-   * Focuses the next found match in the source editor.
-   */
-  focusNextMatch: function DVGS_focusNextMatch() {
-    let matches = this._pane.querySelectorAll(".string[match=true]");
-    if (!matches.length) {
-      return;
-    }
-    if (++this._currentlyFocusedMatch >= matches.length) {
-      this._currentlyFocusedMatch = 0;
-    }
-    this._onMatchClick({ target: matches[this._currentlyFocusedMatch] });
-  },
-
-  /**
-   * Focuses the previously found match in the source editor.
-   */
-  focusPrevMatch: function DVGS_focusPrevMatch() {
-    let matches = this._pane.querySelectorAll(".string[match=true]");
-    if (!matches.length) {
-      return;
-    }
-    if (--this._currentlyFocusedMatch < 0) {
-      this._currentlyFocusedMatch = matches.length - 1;
-    }
-    this._onMatchClick({ target: matches[this._currentlyFocusedMatch] });
-  },
-
-  /**
-   * Called when a line in the search results container is clicked.
-   */
-  _onLineClick: function DVGS__onLineClick(e) {
-    let firstMatch = e.target.parentNode.querySelector(".string[match=true]");
-    this._onMatchClick({ target: firstMatch });
-  },
-
-  /**
-   * Called when a match in the search results container is clicked.
-   */
-  _onMatchClick: function DVGLS__onMatchClick(e) {
-    if (e instanceof Event) {
-      e.preventDefault();
-      e.stopPropagation();
-    }
-    let match = e.target;
-
-    match.container.expand(true);
-    this._scrollMatchIntoViewIfNeeded(match);
-    this._animateMatchBounce(match);
-
-    let results = match.getUserData("lineResults");
-    let range = match.getUserData("lineContentRange");
-
-    let stackframes = DebuggerController.StackFrames;
-    stackframes.updateEditorToLocation(results.scriptUrl, results.lineNumber, 0, 0, 1);
-
-    let editor = DebuggerView.editor;
-    let offset = editor.getCaretOffset();
-    editor.setSelection(offset + range.start, offset + range.start + range.length);
-  },
-
-  /**
-   * Listener handling the searchbox blur event.
-   */
-  _onFocusLost: function DVGS__onFocusLost(e) {
-    this.hideAndEmpty();
-  },
-
-  /**
-   * Listener handling the global search container scroll event.
-   */
-  _onResultsScroll: function DVGS__onResultsScroll(e) {
-    this._expandAllVisibleResults();
-  },
-
-  /**
-   * Expands all the script results that are currently visible.
-   */
-  _expandAllVisibleResults: function DVGS__expandAllVisibleResults() {
-    let collapsed = this._pane.querySelectorAll(".dbg-results-container:not([open])");
-
-    for (let i = 0, l = collapsed.length; i < l; i++) {
-      this._expandResultsIfNeeded(collapsed[i].parentNode);
-    }
-  },
-
-  /**
-   * Expands the script results it they are currently visible.
-   * @param nsIDOMElement aTarget
-   */
-  _expandResultsIfNeeded: function DVGS__expandResultsIfNeeded(aTarget) {
-    if (aTarget.expanded || aTarget._userToggle) {
-      return;
-    }
-    let { clientHeight } = this._pane;
-    let { top, height } = aTarget.getBoundingClientRect();
-
-    if (top - height <= clientHeight || this._forceExpandResults) {
-      aTarget.expand(true);
-    }
-  },
-
-  /**
-   * Scrolls a match into view.
-   * @param nsIDOMElement aTarget
-   */
-  _scrollMatchIntoViewIfNeeded: function DVGS__scrollMatchIntoViewIfNeeded(aTarget) {
-    let { clientHeight } = this._pane;
-    let { top, height } = aTarget.getBoundingClientRect();
-
-    let style = window.getComputedStyle(aTarget);
-    let topBorderSize = window.parseInt(style.getPropertyValue("border-top-width"));
-    let bottomBorderSize = window.parseInt(style.getPropertyValue("border-bottom-width"));
-
-    let marginY = top - (height + topBorderSize + bottomBorderSize) * 2;
-    if (marginY <= 0) {
-      this._pane.scrollTop += marginY;
-    }
-    if (marginY + height > clientHeight) {
-      this._pane.scrollTop += height - (clientHeight - marginY);
-    }
-  },
-
-  /**
-   * Starts a bounce animation for a match.
-   * @param nsIDOMElement aTarget
-   */
-  _animateMatchBounce: function DVGS__animateMatchBounce(aTarget) {
-    aTarget.setAttribute("focused", "");
-
-    window.setTimeout(function() {
-     aTarget.removeAttribute("focused");
-    }, GLOBAL_SEARCH_MATCH_FLASH_DURATION);
-  },
-
-  /**
-   * Map containing the sources for all the currently known scripts.
-   */
-  _scriptSources: new Map(),
-
-  /**
-   * The currently focused match from the search results container.
-   */
-  _currentlyFocusedMatch: -1,
-
-  /**
-   * The cached global search results container.
-   */
-  _pane: null,
-  _splitter: null,
-  _searchbox: null,
-
-  /**
-   * Initialization function, called when the debugger is initialized.
-   */
-  initialize: function DVGS_initialize() {
-    this._pane = DebuggerView._globalSearch;
-    this._splitter = DebuggerView._globalSearchSplitter;
-    this._searchbox = DebuggerView._scriptsSearchbox;
-
-    this._pane.addEventListener("scroll", this._onResultsScroll, false);
-    this._searchbox.addEventListener("blur", this._onFocusLost, false);
-  },
-
-  /**
-   * Destruction function, called when the debugger is shut down.
-   */
-  destroy: function DVS_destroy() {
-    this._pane.removeEventListener("scroll", this._onResultsScroll, false);
-    this._searchbox.removeEventListener("blur", this._onFocusLost, false);
-
-    this.hideAndEmpty();
-    this._pane = null;
-    this._splitter = null;
-    this._searchbox = null;
-    this._scriptSources = null;
-  }
-};
-
-/**
- * Functions handling the scripts UI.
- */
-function ScriptsView() {
-  this._onScriptsChange = this._onScriptsChange.bind(this);
-  this._onScriptsSearchClick = this._onScriptsSearchClick.bind(this);
-  this._onScriptsSearchBlur = this._onScriptsSearchBlur.bind(this);
-  this._onScriptsSearch = this._onScriptsSearch.bind(this);
-  this._onScriptsKeyPress = this._onScriptsKeyPress.bind(this);
-}
-
-ScriptsView.prototype = {
-
-  /**
-   * Removes all elements from the scripts container, leaving it empty.
-   */
-  empty: function DVS_empty() {
-    this._scripts.selectedIndex = -1;
-    this._scripts.setAttribute("label", L10N.getStr("noScriptsText"));
-    this._scripts.removeAttribute("tooltiptext");
-
-    while (this._scripts.firstChild) {
-      this._scripts.removeChild(this._scripts.firstChild);
-    }
-  },
-
-  /**
-   * Removes the input in the searchbox and unhides all the scripts.
-   */
-  clearSearch: function DVS_clearSearch() {
-    this._searchbox.value = "";
-    this._onScriptsSearch({});
-  },
-
-  /**
-   * Checks whether the script with the specified URL is among the scripts
-   * known to the debugger (ignoring the query & reference).
-   *
-   * @param string aUrl
-   *        The script URL.
-   * @return boolean
-   */
-  containsIgnoringQuery: function DVS_containsIgnoringQuery(aUrl) {
-    let sourceScripts = DebuggerController.SourceScripts;
-    aUrl = sourceScripts.trimUrlQuery(aUrl);
-
-    if (this._tmpScripts.some(function(element) {
-      return sourceScripts.trimUrlQuery(element.script.url) == aUrl;
-    })) {
-      return true;
-    }
-    if (this.scriptLocations.some(function(url) {
-      return sourceScripts.trimUrlQuery(url) == aUrl;
-    })) {
-      return true;
-    }
-    return false;
-  },
-
-  /**
-   * Checks whether the script with the specified URL is among the scripts
-   * known to the debugger and shown in the list.
-   *
-   * @param string aUrl
-   *        The script URL.
-   * @return boolean
-   */
-  contains: function DVS_contains(aUrl) {
-    if (this._tmpScripts.some(function(element) {
-      return element.script.url == aUrl;
-    })) {
-      return true;
-    }
-    if (this._scripts.getElementsByAttribute("value", aUrl).length > 0) {
-      return true;
-    }
-    return false;
-  },
-
-  /**
-   * Checks whether the script with the specified label is among the scripts
-   * known to the debugger and shown in the list.
-   *
-   * @param string aLabel
-   *        The script label.
-   * @return boolean
-   */
-  containsLabel: function DVS_containsLabel(aLabel) {
-    if (this._tmpScripts.some(function(element) {
-      return element.label == aLabel;
-    })) {
-      return true;
-    }
-    if (this._scripts.getElementsByAttribute("label", aLabel).length > 0) {
-      return true;
-    }
-    return false;
-  },
-
-  /**
-   * Selects the script with the specified index from the list.
-   *
-   * @param number aIndex
-   *        The script index.
-   */
-  selectIndex: function DVS_selectIndex(aIndex) {
-    this._scripts.selectedIndex = aIndex;
-  },
-
-  /**
-   * Selects the script with the specified URL from the list.
-   *
-   * @param string aUrl
-   *        The script URL.
-   */
-  selectScript: function DVS_selectScript(aUrl) {
-    for (let i = 0, l = this._scripts.itemCount; i < l; i++) {
-      if (this._scripts.getItemAtIndex(i).value == aUrl) {
-        this._scripts.selectedIndex = i;
-        return;
-      }
-    }
-  },
-
-  /**
-   * Checks whether the script with the specified URL is selected in the list.
-   *
-   * @param string aUrl
-   *        The script URL.
-   */
-  isSelected: function DVS_isSelected(aUrl) {
-    if (this._scripts.selectedItem &&
-        this._scripts.selectedItem.value == aUrl) {
-      return true;
-    }
-    return false;
-  },
-
-  /**
-   * Retrieve the URL of the selected script.
-   * @return string | null
-   */
-  get selected() {
-    return this._scripts.selectedItem ?
-           this._scripts.selectedItem.value : null;
-  },
-
-  /**
-   * Gets the most recently selected script url.
-   * @return string | null
-   */
-  get preferredScriptUrl()
-    this._preferredScriptUrl ? this._preferredScriptUrl : null,
-
-  /**
-   * Sets the most recently selected script url.
-   * @param string
-   */
-  set preferredScriptUrl(value) this._preferredScriptUrl = value,
-
-  /**
-   * Gets the script in the container having the specified label.
-   *
-   * @param string aLabel
-   *        The label used to identify the script.
-   * @return element | null
-   *         The matched element, or null if nothing is found.
-   */
-  getScriptByLabel: function DVS_getScriptByLabel(aLabel) {
-    return this._scripts.getElementsByAttribute("label", aLabel)[0];
-  },
-
-  /**
-   * Returns the list of labels in the scripts container.
-   * @return array
-   */
-  get scriptLabels() {
-    let labels = [];
-    for (let i = 0, l = this._scripts.itemCount; i < l; i++) {
-      labels.push(this._scripts.getItemAtIndex(i).label);
-    }
-    return labels;
-  },
-
-  /**
-   * Gets the script in the container having the specified label.
-   *
-   * @param string aUrl
-   *        The url used to identify the script.
-   * @return element | null
-   *         The matched element, or null if nothing is found.
-   */
-  getScriptByLocation: function DVS_getScriptByLocation(aUrl) {
-    return this._scripts.getElementsByAttribute("value", aUrl)[0];
-  },
-
-  /**
-   * Returns the list of URIs for scripts in the page.
-   * @return array
-   */
-  get scriptLocations() {
-    let locations = [];
-    for (let i = 0, l = this._scripts.itemCount; i < l; i++) {
-      locations.push(this._scripts.getItemAtIndex(i).value);
-    }
-    return locations;
-  },
-
-  /**
-   * Gets the number of visible (hidden=false) scripts in the container.
-   * @return number
-   */
-  get visibleItemsCount() {
-    let count = 0;
-    for (let i = 0, l = this._scripts.itemCount; i < l; i++) {
-      count += this._scripts.getItemAtIndex(i).hidden ? 0 : 1;
-    }
-    return count;
-  },
-
-  /**
-   * Prepares a script to be added to the scripts container. This allows
-   * for a large number of scripts to be batched up before being
-   * alphabetically sorted and added in the container.
-   * @see ScriptsView.commitScripts
-   *
-   * If aForceFlag is true, the script will be immediately inserted at the
-   * necessary position in the container so that all the scripts remain sorted.
-   * This can be much slower than batching up multiple scripts.
-   *
-   * @param string aLabel
-   *        The simplified script location to be shown.
-   * @param string aScript
-   *        The source script.
-   * @param boolean aForceFlag
-   *        True to force the script to be immediately added.
-   */
-  addScript: function DVS_addScript(aLabel, aScript, aForceFlag) {
-    // Batch the script to be added later.
-    if (!aForceFlag) {
-      this._tmpScripts.push({ label: aLabel, script: aScript });
-      return;
-    }
-
-    // Find the target position in the menulist and insert the script there.
-    for (let i = 0, l = this._scripts.itemCount; i < l; i++) {
-      if (this._scripts.getItemAtIndex(i).label > aLabel) {
-        this._createScriptElement(aLabel, aScript, i);
-        return;
-      }
-    }
-    // The script is alphabetically the last one.
-    this._createScriptElement(aLabel, aScript, -1);
-  },
-
-  /**
-   * Adds all the prepared scripts to the scripts container.
-   * If a script already exists (was previously added), nothing happens.
-   */
-  commitScripts: function DVS_commitScripts() {
-    let newScripts = this._tmpScripts;
-    this._tmpScripts = [];
-
-    if (!newScripts || !newScripts.length) {
-      return;
-    }
-    newScripts.sort(function(a, b) {
-      return a.label.toLowerCase() > b.label.toLowerCase();
-    });
-
-    for (let i = 0, l = newScripts.length; i < l; i++) {
-      let item = newScripts[i];
-      this._createScriptElement(item.label, item.script, -1);
-    }
-  },
-
-  /**
-   * Creates a custom script element and adds it to the scripts container.
-   * If the script with the specified label already exists, nothing happens.
-   *
-   * @param string aLabel
-   *        The simplified script location to be shown.
-   * @param string aScript
-   *        The source script.
-   * @param number aIndex
-   *        The index where to insert to new script in the container.
-   *        Pass -1 to append the script at the end.
-   */
-  _createScriptElement: function DVS__createScriptElement(aLabel, aScript, aIndex)
-  {
-    // Make sure we don't duplicate anything.
-    if (aLabel == "null" || this.containsLabel(aLabel) || this.contains(aScript.url)) {
-      return;
-    }
-
-    let scriptItem =
-      aIndex == -1 ? this._scripts.appendItem(aLabel, aScript.url)
-                   : this._scripts.insertItemAt(aIndex, aLabel, aScript.url);
-
-    scriptItem.setAttribute("tooltiptext", aScript.url);
-    scriptItem.setUserData("sourceScript", aScript, null);
-  },
-
-  /**
-   * Gets the entered file, line and token entered in the searchbox.
-   *
-   * @return array
-   *         A [file, line, token] array.
-   */
-  get searchboxInfo() {
-    let file, line, token, isGlobal;
-
-    let rawValue = this._searchbox.value;
-    let rawLength = rawValue.length;
-    let lineFlagIndex = rawValue.lastIndexOf(SEARCH_LINE_FLAG);
-    let tokenFlagIndex = rawValue.lastIndexOf(SEARCH_TOKEN_FLAG);
-    let globalFlagIndex = rawValue.lastIndexOf(SEARCH_GLOBAL_FLAG);
-
-    if (globalFlagIndex !== 0) {
-      let fileEnd = lineFlagIndex !== -1 ? lineFlagIndex : tokenFlagIndex !== -1 ? tokenFlagIndex : rawLength;
-      let lineEnd = tokenFlagIndex !== -1 ? tokenFlagIndex : rawLength;
-
-      file = rawValue.slice(0, fileEnd);
-      line = window.parseInt(rawValue.slice(fileEnd + 1, lineEnd)) || -1;
-      token = rawValue.slice(lineEnd + 1);
-      isGlobal = false;
-    } else {
-      file = "";
-      line = -1;
-      token = rawValue.slice(1);
-      isGlobal = true;
-    }
-
-    return [file, line, token, isGlobal];
-  },
-
-  /**
-   * Returns the current search token.
-   */
-  get searchToken() this.searchboxInfo[2],
-
-  /**
-   * The click listener for the scripts container.
-   */
-  _onScriptsChange: function DVS__onScriptsChange() {
-    let selectedItem = this._scripts.selectedItem;
-    if (!selectedItem) {
-      return;
-    }
-
-    this._preferredScript = selectedItem;
-    this._preferredScriptUrl = selectedItem.value;
-    this._scripts.setAttribute("tooltiptext", selectedItem.value);
-    DebuggerController.SourceScripts.showScript(selectedItem.getUserData("sourceScript"));
-  },
-
-  _prevSearchedFile: "",
-  _prevSearchedLine: 0,
-  _prevSearchedToken: "",
-
-  /**
-   * Performs a file search if necessary.
-   *
-   * @param string aFile
-   *        The script filename to search for.
-   */
-  _performFileSearch: function DVS__performFileSearch(aFile) {
-    let scripts = this._scripts;
-
-    // Presume we won't find anything.
-    scripts.selectedItem = this._preferredScript;
-    scripts.setAttribute("label", this._preferredScript.label);
-    scripts.setAttribute("tooltiptext", this._preferredScript.value);
-
-    // If we're not searching for a file anymore, unhide all the scripts.
-    if (!aFile && this._someScriptsHidden) {
-      this._someScriptsHidden = false;
-
-      for (let i = 0, l = scripts.itemCount; i < l; i++) {
-        scripts.getItemAtIndex(i).hidden = false;
-      }
-    } else if (this._prevSearchedFile !== aFile) {
-      let lowerCaseFile = aFile.toLowerCase();
-      let found = false;
-
-      for (let i = 0, l = scripts.itemCount; i < l; i++) {
-        let item = scripts.getItemAtIndex(i);
-        let lowerCaseLabel = item.label.toLowerCase();
-
-        // Search is not case sensitive, and is tied to the label not the url.
-        if (lowerCaseLabel.match(aFile)) {
-          item.hidden = false;
-
-          if (!found) {
-            found = true;
-            scripts.selectedItem = item;
-            scripts.setAttribute("label", item.label);
-            scripts.setAttribute("tooltiptext", item.value);
-          }
-        }
-        // Hide what doesn't match our search.
-        else {
-          item.hidden = true;
-          this._someScriptsHidden = true;
-        }
-      }
-      if (!found) {
-        scripts.setAttribute("label", L10N.getStr("noMatchingScriptsText"));
-        scripts.removeAttribute("tooltiptext");
-      }
-    }
-    this._prevSearchedFile = aFile;
-  },
-
-  /**
-   * Performs a line search if necessary.
-   *
-   * @param number aLine
-   *        The script line number to jump to.
-   */
-  _performLineSearch: function DVS__performLineSearch(aLine) {
-    // Jump to lines in the currently visible source.
-    if (this._prevSearchedLine !== aLine && aLine > -1) {
-      DebuggerView.editor.setCaretPosition(aLine - 1);
-    }
-    this._prevSearchedLine = aLine;
-  },
-
-  /**
-   * Performs a token search if necessary.
-   *
-   * @param string aToken
-   *        The script token to find.
-   */
-  _performTokenSearch: function DVS__performTokenSearch(aToken) {
-    // Search for tokens in the currently visible source.
-    if (this._prevSearchedToken !== aToken && aToken.length > 0) {
-      let editor = DebuggerView.editor;
-      let offset = editor.find(aToken, { ignoreCase: true });
-      if (offset > -1) {
-        editor.setSelection(offset, offset + aToken.length)
-      }
-    }
-    this._prevSearchedToken = aToken;
-  },
-
-  /**
-   * The focus listener for the scripts search box.
-   */
-  _onScriptsSearchClick: function DVS__onScriptsSearchClick() {
-    this._searchboxPanel.openPopup(this._searchbox);
-  },
-
-  /**
-   * The blur listener for the scripts search box.
-   */
-  _onScriptsSearchBlur: function DVS__onScriptsSearchBlur() {
-    this._searchboxPanel.hidePopup();
-  },
-
-  /**
-   * The search listener for the scripts search box.
-   */
-  _onScriptsSearch: function DVS__onScriptsSearch() {
-    // If the webpage has no scripts, searching is redundant.
-    if (!this._scripts.itemCount) {
-      return;
-    }
-    this._searchboxPanel.hidePopup();
-
-    let [file, line, token, isGlobal] = this.searchboxInfo;
-
-    // If this is a global script search, schedule a search in all the sources,
-    // or hide the pane otherwise.
-    if (isGlobal) {
-      DebuggerView.GlobalSearch.scheduleSearch();
-    } else {
-      DebuggerView.GlobalSearch.hideAndEmpty();
-      this._performFileSearch(file);
-      this._performLineSearch(line);
-      this._performTokenSearch(token);
-    }
-  },
-
-  /**
-   * The keypress listener for the scripts search box.
-   */
-  _onScriptsKeyPress: function DVS__onScriptsKeyPress(e) {
-    if (e.keyCode === e.DOM_VK_ESCAPE) {
-      DebuggerView.editor.focus();
-      return;
-    }
-    var action;
-
-    if (e.keyCode === e.DOM_VK_DOWN ||
-        e.keyCode === e.DOM_VK_RETURN ||
-        e.keyCode === e.DOM_VK_ENTER) {
-      action = 1;
-    } else if (e.keyCode === e.DOM_VK_UP) {
-      action = 2;
-    }
-
-    if (action) {
-      let [file, line, token, isGlobal] = this.searchboxInfo;
-
-      if (token.length) {
-        e.preventDefault();
-        e.stopPropagation();
-      } else {
-        return;
-      }
-      if (isGlobal) {
-        if (DebuggerView.GlobalSearch.hidden) {
-          DebuggerView.GlobalSearch.scheduleSearch();
-        } else {
-          DebuggerView.GlobalSearch[action === 1
-            ? "focusNextMatch"
-            : "focusPrevMatch"]();
-        }
-        return;
-      }
-
-      let editor = DebuggerView.editor;
-      let offset = editor[action === 1 ? "findNext" : "findPrevious"](true);
-      if (offset > -1) {
-        editor.setSelection(offset, offset + token.length)
-      }
-    }
-  },
-
-  /**
-   * Called when the scripts filter key sequence was pressed.
-   */
-  _onSearch: function DVS__onSearch(aValue = "") {
-    this._searchbox.focus();
-    this._searchbox.value = aValue;
-    DebuggerView.GlobalSearch.hideAndEmpty();
-  },
-
-  /**
-   * Called when the scripts path filter key sequence was pressed.
-   */
-  _onFileSearch: function DVS__onFileSearch() {
-    this._onSearch();
-    this._searchboxPanel.openPopup(this._searchbox);
-  },
-
-  /**
-   * Called when the scripts token filter key sequence was pressed.
-   */
-  _onLineSearch: function DVS__onLineSearch() {
-    this._onSearch(SEARCH_LINE_FLAG);
-    this._searchboxPanel.hidePopup();
-  },
-
-  /**
-   * Called when the scripts token filter key sequence was pressed.
-   */
-  _onTokenSearch: function DVS__onTokenSearch() {
-    this._onSearch(SEARCH_TOKEN_FLAG);
-    this._searchboxPanel.hidePopup();
-  },
-
-  /**
-   * Called when the scripts token filter key sequence was pressed.
-   */
-  _onGlobalSearch: function DVS__onGlobalSearch() {
-    this._onSearch(SEARCH_GLOBAL_FLAG);
-    this._searchboxPanel.hidePopup();
-  },
-
-  /**
-   * The cached scripts container and search box.
-   */
-  _scripts: null,
-  _searchbox: null,
-  _searchboxPanel: null,
-
-  /**
-   * Initialization function, called when the debugger is initialized.
-   */
-  initialize: function DVS_initialize() {
-    this._scripts = DebuggerView._scripts;
-    this._searchbox = document.getElementById("scripts-search");
-    this._searchboxPanel = document.getElementById("scripts-search-panel");
-
-    this._scripts.addEventListener("select", this._onScriptsChange, false);
-    this._searchbox.addEventListener("click", this._onScriptsSearchClick, false);
-    this._searchbox.addEventListener("blur", this._onScriptsSearchBlur, false);
-    this._searchbox.addEventListener("select", this._onScriptsSearch, false);
-    this._searchbox.addEventListener("input", this._onScriptsSearch, false);
-    this._searchbox.addEventListener("keypress", this._onScriptsKeyPress, false);
-    this.commitScripts();
-  },
-
-  /**
-   * Destruction function, called when the debugger is shut down.
-   */
-  destroy: function DVS_destroy() {
-    this._scripts.removeEventListener("select", this._onScriptsChange, false);
-    this._searchbox.removeEventListener("click", this._onScriptsSearchClick, false);
-    this._searchbox.removeEventListener("blur", this._onScriptsSearchBlur, false);
-    this._searchbox.removeEventListener("select", this._onScriptsSearch, false);
-    this._searchbox.removeEventListener("input", this._onScriptsSearch, false);
-    this._searchbox.removeEventListener("keypress", this._onScriptsKeyPress, false);
-
-    this.empty();
-    this._scripts = null;
-    this._searchbox = null;
-    this._searchboxPanel = null;
-  }
-};
-
-/**
- * Functions handling the html stackframes UI.
- */
-function StackFramesView() {
-  this._onFramesScroll = this._onFramesScroll.bind(this);
-  this._onPauseExceptionsClick = this._onPauseExceptionsClick.bind(this);
-  this._onCloseButtonClick = this._onCloseButtonClick.bind(this);
-  this._onResume = this._onResume.bind(this);
-  this._onStepOver = this._onStepOver.bind(this);
-  this._onStepIn = this._onStepIn.bind(this);
-  this._onStepOut = this._onStepOut.bind(this);
-}
-
-StackFramesView.prototype = {
-
- /**
-  * Sets the current frames state based on the debugger active thread state.
-  *
-  * @param string aState
-  *        Either "paused" or "attached".
-  */
-  updateState: function DVF_updateState(aState) {
-    let resume = DebuggerView._resumeButton;
-    let resumeKey = LayoutHelpers.prettyKey(DebuggerView._resumeKey);
-
-    // If we're paused, show a pause label and a resume label on the button.
-    if (aState == "paused") {
-      resume.setAttribute("tooltiptext", L10N.getFormatStr("resumeButtonTooltip", [resumeKey]));
-      resume.setAttribute("checked", true);
-    }
-    // If we're attached, do the opposite.
-    else if (aState == "attached") {
-      resume.setAttribute("tooltiptext", L10N.getFormatStr("pauseButtonTooltip", [resumeKey]));
-      resume.removeAttribute("checked");
-    }
-  },
-
-  /**
-   * Removes all elements from the stackframes container, leaving it empty.
-   */
-  empty: function DVF_empty() {
-    while (this._frames.firstChild) {
-      this._frames.removeChild(this._frames.firstChild);
-    }
-  },
-
-  /**
-   * Removes all elements from the stackframes container, and adds a child node
-   * with an empty text note attached.
-   */
-  emptyText: function DVF_emptyText() {
-    // Make sure the container is empty first.
-    this.empty();
-
-    let item = document.createElement("label");
-
-    // The empty node should look grayed out to avoid confusion.
-    item.className = "list-item empty";
-    item.setAttribute("value", L10N.getStr("emptyStackText"));
-
-    this._frames.appendChild(item);
-  },
-
-  /**
-   * Adds a frame to the stackframes container.
-   * If the frame already exists (was previously added), null is returned.
-   * Otherwise, the newly created element is returned.
-   *
-   * @param number aDepth
-   *        The frame depth specified by the debugger.
-   * @param string aFrameNameText
-   *        The name to be displayed in the list.
-   * @param string aFrameDetailsText
-   *        The details to be displayed in the list.
-   * @return object
-   *         The newly created html node representing the added frame.
-   */
-  addFrame: function DVF_addFrame(aDepth, aFrameNameText, aFrameDetailsText) {
-    // Make sure we don't duplicate anything.
-    if (document.getElementById("stackframe-" + aDepth)) {
-      return null;
-    }
-    // Stackframes are UI elements which benefit from visible panes.
-    DebuggerView.showPanesIfAllowed();
-
-    let frame = document.createElement("box");
-    let frameName = document.createElement("label");
-    let frameDetails = document.createElement("label");
-
-    // Create a list item to be added to the stackframes container.
-    frame.id = "stackframe-" + aDepth;
-    frame.className = "dbg-stackframe list-item";
-
-    // This list should display the name and details for the frame.
-    frameName.className = "dbg-stackframe-name plain";
-    frameDetails.className = "dbg-stackframe-details plain";
-    frameName.setAttribute("value", aFrameNameText);
-    frameDetails.setAttribute("value", aFrameDetailsText);
-
-    let spacer = document.createElement("spacer");
-    spacer.setAttribute("flex", "1");
-
-    frame.appendChild(frameName);
-    frame.appendChild(spacer);
-    frame.appendChild(frameDetails);
-
-    this._frames.appendChild(frame);
-
-    // Return the element for later use if necessary.
-    return frame;
-  },
-
-  /**
-   * Highlights a frame from the stackframe container as selected/deselected.
-   *
-   * @param number aDepth
-   *        The frame depth specified by the debugger.
-   * @param boolean aFlag
-   *        True if the frame should be deselected, false otherwise.
-   */
-  highlightFrame: function DVF_highlightFrame(aDepth, aFlag) {
-    let frame = document.getElementById("stackframe-" + aDepth);
-
-    // The list item wasn't found in the stackframe container.
-    if (!frame) {
-      return;
-    }
-
-    // Add the 'selected' css class if the frame isn't already selected.
-    if (!aFlag && !frame.classList.contains("selected")) {
-      frame.classList.add("selected");
-    }
-    // Remove the 'selected' css class if the frame is already selected.
-    else if (aFlag && frame.classList.contains("selected")) {
-      frame.classList.remove("selected");
-    }
-  },
-
-  /**
-   * Deselects a frame from the stackframe container.
-   *
-   * @param number aDepth
-   *        The frame depth specified by the debugger.
-   */
-  unhighlightFrame: function DVF_unhighlightFrame(aDepth) {
-    this.highlightFrame(aDepth, true);
-  },
-
-  /**
-   * Gets the current dirty state.
-   *
-   * @return boolean value
-   *         True if should load more frames.
-   */
-  get dirty() {
-    return this._dirty;
-  },
-
-  /**
-   * Sets if the active thread has more frames that need to be loaded.
-   *
-   * @param boolean aValue
-   *        True if should load more frames.
-   */
-  set dirty(aValue) {
-    this._dirty = aValue;
-  },
-
-  /**
-   * Listener handling the stackframes container click event.
-   */
-  _onFramesClick: function DVF__onFramesClick(aEvent) {
-    let target = aEvent.target;
-
-    while (target) {
-      if (target.debuggerFrame) {
-        DebuggerController.StackFrames.selectFrame(target.debuggerFrame.depth);
-        return;
-      }
-      target = target.parentNode;
-    }
-  },
-
-  /**
-   * Listener handling the stackframes container scroll event.
-   */
-  _onFramesScroll: function DVF__onFramesScroll(aEvent) {
-    // Update the stackframes container only if we have to.
-    if (this._dirty) {
-      let clientHeight = this._frames.clientHeight;
-      let scrollTop = this._frames.scrollTop;
-      let scrollHeight = this._frames.scrollHeight;
-
-      // If the stackframes container was scrolled past 95% of the height,
-      // load more content.
-      if (scrollTop >= (scrollHeight - clientHeight) * 0.95) {
-        this._dirty = false;
-
-        DebuggerController.StackFrames.addMoreFrames();
-      }
-    }
-  },
-
-  /**
-   * Listener handling the close button click event.
-   */
-  _onCloseButtonClick: function DVF__onCloseButtonClick() {
-    DebuggerController.dispatchEvent("Debugger:Close");
-  },
-
-  /**
-   * Listener handling the pause-on-exceptions click event.
-   */
-  _onPauseExceptionsClick: function DVF__onPauseExceptionsClick() {
-    let option = document.getElementById("pause-exceptions");
-    DebuggerController.StackFrames.updatePauseOnExceptions(option.checked);
-  },
-
-  /**