Backed out changeset 549973e335e4 for xpcshell test failures.
authorDave Camp <dcamp@mozilla.com>
Thu, 11 Apr 2013 15:24:55 -0700
changeset 128311 394cd944912b7b976c372cf2e7c2d5b20041c7db
parent 128310 549973e335e4ac447858b474d1bb5597d2a6d0b7
child 128312 643194ceabe44f115216587c32f2ca762a40217c
push id1503
push userdcamp@campd.org
push dateThu, 11 Apr 2013 22:25:11 +0000
treeherderfx-team@394cd944912b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone23.0a1
backs out549973e335e4ac447858b474d1bb5597d2a6d0b7
Backed out changeset 549973e335e4 for xpcshell test failures.
browser/base/content/nsContextMenu.js
browser/devtools/Makefile.in
browser/devtools/commandline/BuiltinCommands.jsm
browser/devtools/commandline/test/helpers.js
browser/devtools/debugger/DebuggerPanel.jsm
browser/devtools/debugger/test/head.js
browser/devtools/debugger/test/helpers.js
browser/devtools/fontinspector/test/browser_fontinspector.js
browser/devtools/framework/Makefile.in
browser/devtools/framework/Sidebar.jsm
browser/devtools/framework/Target.jsm
browser/devtools/framework/ToolDefinitions.jsm
browser/devtools/framework/Toolbox.jsm
browser/devtools/framework/ToolboxHosts.jsm
browser/devtools/framework/gDevTools.jsm
browser/devtools/framework/sidebar.js
browser/devtools/framework/target.js
browser/devtools/framework/test/browser_devtools_api.js
browser/devtools/framework/test/browser_new_activation_workflow.js
browser/devtools/framework/test/browser_target_events.js
browser/devtools/framework/test/browser_toolbox_dynamic_registration.js
browser/devtools/framework/test/browser_toolbox_hosts.js
browser/devtools/framework/test/browser_toolbox_ready.js
browser/devtools/framework/test/browser_toolbox_sidebar.js
browser/devtools/framework/test/browser_toolbox_window_shortcuts.js
browser/devtools/framework/test/browser_toolbox_window_title_changes.js
browser/devtools/framework/test/head.js
browser/devtools/framework/toolbox-hosts.js
browser/devtools/framework/toolbox.js
browser/devtools/inspector/Breadcrumbs.jsm
browser/devtools/inspector/CmdInspect.jsm
browser/devtools/inspector/Highlighter.jsm
browser/devtools/inspector/InspectorPanel.jsm
browser/devtools/inspector/Makefile.in
browser/devtools/inspector/Selection.jsm
browser/devtools/inspector/SelectorSearch.jsm
browser/devtools/inspector/breadcrumbs.js
browser/devtools/inspector/highlighter.js
browser/devtools/inspector/inspector-panel.js
browser/devtools/inspector/selection.js
browser/devtools/inspector/selector-search.js
browser/devtools/inspector/test/browser_inspector_bug_840156_destroy_after_navigation.js
browser/devtools/inspector/test/browser_inspector_initialization.js
browser/devtools/inspector/test/browser_inspector_pseudoclass_lock.js
browser/devtools/inspector/test/browser_inspector_sidebarstate.js
browser/devtools/inspector/test/head.js
browser/devtools/inspector/test/helpers.js
browser/devtools/layoutview/test/browser_layoutview.js
browser/devtools/layoutview/view.js
browser/devtools/main.js
browser/devtools/markupview/Makefile.in
browser/devtools/markupview/MarkupView.jsm
browser/devtools/markupview/markup-view.js
browser/devtools/markupview/test/browser_inspector_markup_edit.js
browser/devtools/markupview/test/head.js
browser/devtools/netmonitor/NetMonitorPanel.jsm
browser/devtools/netmonitor/netmonitor-controller.js
browser/devtools/netmonitor/test/head.js
browser/devtools/profiler/ProfilerPanel.jsm
browser/devtools/profiler/test/head.js
browser/devtools/responsivedesign/responsivedesign.jsm
browser/devtools/responsivedesign/test/head.js
browser/devtools/responsivedesign/test/helpers.js
browser/devtools/scratchpad/scratchpad.js
browser/devtools/shared/DeveloperToolbar.jsm
browser/devtools/shared/EventEmitter.jsm
browser/devtools/shared/InplaceEditor.jsm
browser/devtools/shared/Makefile.in
browser/devtools/shared/Undo.jsm
browser/devtools/shared/event-emitter.js
browser/devtools/shared/inplace-editor.js
browser/devtools/shared/test/browser_eventemitter_basic.js
browser/devtools/shared/test/head.js
browser/devtools/shared/undo.js
browser/devtools/styleeditor/StyleEditorPanel.jsm
browser/devtools/styleeditor/test/browser_styleeditor_bug_826982_location_changed.js
browser/devtools/styleeditor/test/browser_styleeditor_reopen.js
browser/devtools/styleeditor/test/head.js
browser/devtools/styleeditor/test/helpers.js
browser/devtools/styleinspector/CssHtmlTree.jsm
browser/devtools/styleinspector/CssLogic.jsm
browser/devtools/styleinspector/CssRuleView.jsm
browser/devtools/styleinspector/Makefile.in
browser/devtools/styleinspector/StyleInspector.jsm
browser/devtools/styleinspector/computed-view.js
browser/devtools/styleinspector/computedview.xhtml
browser/devtools/styleinspector/css-logic.js
browser/devtools/styleinspector/cssruleview.xhtml
browser/devtools/styleinspector/rule-view.js
browser/devtools/styleinspector/style-inspector.js
browser/devtools/styleinspector/test/browser_bug683672.js
browser/devtools/styleinspector/test/browser_bug705707_is_content_stylesheet.js
browser/devtools/styleinspector/test/browser_bug722196_rule_view_media_queries.js
browser/devtools/styleinspector/test/browser_bug722691_rule_view_increment.js
browser/devtools/styleinspector/test/browser_bug_592743_specificity.js
browser/devtools/styleinspector/test/browser_csslogic_inherited.js
browser/devtools/styleinspector/test/browser_ruleview_editor.js
browser/devtools/styleinspector/test/browser_ruleview_editor_changedvalues.js
browser/devtools/styleinspector/test/browser_ruleview_inherit.js
browser/devtools/styleinspector/test/browser_ruleview_manipulation.js
browser/devtools/styleinspector/test/browser_ruleview_override.js
browser/devtools/styleinspector/test/browser_ruleview_ui.js
browser/devtools/styleinspector/test/browser_ruleview_update.js
browser/devtools/styleinspector/test/head.js
browser/devtools/tilt/CmdTilt.jsm
browser/devtools/tilt/Makefile.in
browser/devtools/tilt/Tilt.jsm
browser/devtools/tilt/TiltGL.jsm
browser/devtools/tilt/TiltMath.jsm
browser/devtools/tilt/TiltUtils.jsm
browser/devtools/tilt/TiltVisualizer.jsm
browser/devtools/tilt/TiltVisualizerStyle.jsm
browser/devtools/tilt/test/head.js
browser/devtools/tilt/tilt-gl.js
browser/devtools/tilt/tilt-math.js
browser/devtools/tilt/tilt-utils.js
browser/devtools/tilt/tilt-visualizer-style.js
browser/devtools/tilt/tilt-visualizer.js
browser/devtools/tilt/tilt.js
browser/devtools/webconsole/HUDService.jsm
browser/devtools/webconsole/Makefile.in
browser/devtools/webconsole/WebConsolePanel.jsm
browser/devtools/webconsole/test/head.js
browser/devtools/webconsole/webconsole.js
browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
toolkit/components/url-classifier/tests/unit/test_prefixset.js
toolkit/devtools/webconsole/WebConsoleUtils.jsm
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -432,19 +432,20 @@ nsContextMenu.prototype = {
 
   initClickToPlayItems: function() {
     this.showItem("context-ctp-play", this.onCTPPlugin);
     this.showItem("context-ctp-hide", this.onCTPPlugin);
     this.showItem("context-sep-ctp", this.onCTPPlugin);
   },
 
   inspectNode: function CM_inspectNode() {
-    let {devtools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
     let gBrowser = this.browser.ownerDocument.defaultView.gBrowser;
-    let tt = devtools.TargetFactory.forTab(gBrowser.selectedTab);
+    let imported = {};
+    Cu.import("resource:///modules/devtools/Target.jsm", imported);
+    let tt = imported.TargetFactory.forTab(gBrowser.selectedTab);
     return gDevTools.showToolbox(tt, "inspector").then(function(toolbox) {
       let inspector = toolbox.getCurrentPanel();
       inspector.selection.setNode(this.target, "browser-context-menu");
     }.bind(this));
   },
 
   // Set various context menu attributes based on the state of the world.
   setTarget: function (aNode, aRangeParent, aRangeOffset) {
deleted file mode 100644
--- a/browser/devtools/Makefile.in
+++ /dev/null
@@ -1,15 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-DEPTH     = @DEPTH@
-topsrcdir = @top_srcdir@
-srcdir    = @srcdir@
-VPATH     = @srcdir@
-
-include $(topsrcdir)/config/config.mk
-
-include $(topsrcdir)/config/rules.mk
-
-libs::
-	$(NSINSTALL) $(srcdir)/main.js $(FINAL_TARGET)/modules/devtools
--- a/browser/devtools/commandline/BuiltinCommands.jsm
+++ b/browser/devtools/commandline/BuiltinCommands.jsm
@@ -9,23 +9,23 @@ const BRAND_SHORT_NAME = Cc["@mozilla.or
                            .createBundle("chrome://branding/locale/brand.properties")
                            .GetStringFromName("brandShortName");
 
 this.EXPORTED_SYMBOLS = [ "CmdAddonFlags", "CmdCommands" ];
 
 Cu.import("resource:///modules/devtools/gcli.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/osfile.jsm");
-Cu.import("resource:///modules/devtools/shared/event-emitter.js");
+Cu.import("resource://gre/modules/osfile.jsm")
+Cu.import("resource:///modules/devtools/EventEmitter.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
                                   "resource:///modules/devtools/gDevTools.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "devtools",
-                                  "resource:///modules/devtools/gDevTools.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TargetFactory",
+                                  "resource:///modules/devtools/Target.jsm");
 
 /* CmdAddon ---------------------------------------------------------------- */
 
 (function(module) {
   XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                     "resource://gre/modules/AddonManager.jsm");
 
   // We need to use an object in which to store any flags because a primitive
@@ -320,16 +320,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
     Components.utils.import("resource://gre/modules/jsdebugger.jsm", JsDebugger);
 
     let global = Components.utils.getGlobalForObject({});
     JsDebugger.addDebuggerToGlobal(global);
 
     return global.Debugger;
   });
 
+  XPCOMUtils.defineLazyModuleGetter(this, "TargetFactory",
+                                    "resource:///modules/devtools/Target.jsm");
+
   let debuggers = [];
 
   /**
   * 'calllog' command
   */
   gcli.addCommand({
     name: "calllog",
     description: gcli.lookup("calllogDesc")
@@ -349,17 +352,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
       dbg.onEnterFrame = function(frame) {
         // BUG 773652 -  Make the output from the GCLI calllog command nicer
         contentWindow.console.log("Method call: " + this.callDescription(frame));
       }.bind(this);
 
       debuggers.push(dbg);
 
       let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
-      let target = devtools.TargetFactory.forTab(gBrowser.selectedTab);
+      let target = TargetFactory.forTab(gBrowser.selectedTab);
       gDevTools.showToolbox(target, "webconsole");
 
       return gcli.lookup("calllogStartReply");
     },
 
     callDescription: function(frame) {
       let name = "<anonymous>";
       if (frame.callee.name) {
@@ -501,17 +504,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 
       dbg.onEnterFrame = function(frame) {
         // BUG 773652 -  Make the output from the GCLI calllog command nicer
         contentWindow.console.log(gcli.lookup("callLogChromeMethodCall") +
                                   ": " + this.callDescription(frame));
       }.bind(this);
 
       let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
-      let target = devtools.TargetFactory.forTab(gBrowser.selectedTab);
+      let target = TargetFactory.forTab(gBrowser.selectedTab);
       gDevTools.showToolbox(target, "webconsole");
 
       return gcli.lookup("calllogChromeStartReply");
     },
 
     valueToString: function(value) {
       if (typeof value !== "object" || value === null)
         return uneval(value);
@@ -747,30 +750,30 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   /**
   * 'console close' command
   */
   gcli.addCommand({
     name: "console close",
     description: gcli.lookup("consolecloseDesc"),
     exec: function Command_consoleClose(args, context) {
       let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
-      let target = devtools.TargetFactory.forTab(gBrowser.selectedTab);
+      let target = TargetFactory.forTab(gBrowser.selectedTab);
       return gDevTools.closeToolbox(target);
     }
   });
 
   /**
   * 'console open' command
   */
   gcli.addCommand({
     name: "console open",
     description: gcli.lookup("consoleopenDesc"),
     exec: function Command_consoleOpen(args, context) {
       let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
-      let target = devtools.TargetFactory.forTab(gBrowser.selectedTab);
+      let target = TargetFactory.forTab(gBrowser.selectedTab);
       return gDevTools.showToolbox(target, "webconsole");
     }
   });
 }(this));
 
 /* CmdCookie --------------------------------------------------------------- */
 
 (function(module) {
@@ -1511,85 +1514,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   * @return string
   *         The equivalent of |aString| but safe to use in a regex.
   */
   function escapeRegex(aString) {
     return aString.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
   }
 }(this));
 
-/* CmdTools -------------------------------------------------------------- */
-
-(function(module) {
-  gcli.addCommand({
-    name: "tools",
-    description: gcli.lookup("toolsDesc"),
-    manual: gcli.lookup("toolsManual"),
-    get hidden() gcli.hiddenByChromePref(),
-  });
-
-  gcli.addCommand({
-    name: "tools srcdir",
-    description: gcli.lookup("toolsSrcdirDesc"),
-    manual: gcli.lookup("toolsSrcdirManual"),
-    get hidden() gcli.hiddenByChromePref(),
-    params: [
-      {
-        name: "srcdir",
-        type: "string",
-        description: gcli.lookup("toolsSrcdirDir")
-      }
-    ],
-    returnType: "string",
-    exec: function(args, context) {
-      let promise = context.createPromise();
-      let existsPromise = OS.File.exists(args.srcdir + "/CLOBBER");
-      existsPromise.then(function(exists) {
-        if (exists) {
-          var str = Cc["@mozilla.org/supports-string;1"]
-            .createInstance(Ci.nsISupportsString);
-          str.data = args.srcdir;
-          Services.prefs.setComplexValue("devtools.loader.srcdir",
-              Components.interfaces.nsISupportsString, str);
-          devtools.reload();
-          promise.resolve(gcli.lookupFormat("toolsSrcdirReloaded", [args.srcdir]));
-          return;
-        }
-        promise.reject(gcli.lookupFormat("toolsSrcdirNotFound", [args.srcdir]));
-      });
-      return promise;
-    }
-  });
-
-  gcli.addCommand({
-    name: "tools builtin",
-    description: gcli.lookup("toolsBuiltinDesc"),
-    manual: gcli.lookup("toolsBuiltinManual"),
-    get hidden() gcli.hiddenByChromePref(),
-    returnType: "string",
-    exec: function(args, context) {
-      Services.prefs.clearUserPref("devtools.loader.srcdir");
-      devtools.reload();
-      return gcli.lookup("toolsBuiltinReloaded");
-    }
-  });
-
-  gcli.addCommand({
-    name: "tools reload",
-    description: gcli.lookup("toolsReloadDesc"),
-    get hidden() gcli.hiddenByChromePref() || !Services.prefs.prefHasUserValue("devtools.loader.srcdir"),
-
-    returnType: "string",
-    exec: function(args, context) {
-      devtools.reload();
-      return gcli.lookup("toolsReloaded");
-    }
-  });
-}(this));
-
 /* CmdRestart -------------------------------------------------------------- */
 
 (function(module) {
   /**
   * Restart command
   *
   * @param boolean nocache
   *        Disables loading content from cache upon restart.
@@ -1974,13 +1908,13 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   let eventEmitter = new EventEmitter();
   function onPaintFlashingChanged(context) {
     var gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
     var tab = gBrowser.selectedTab;
     eventEmitter.emit("changed", tab);
     function fireChange() {
       eventEmitter.emit("changed", tab);
     }
-    var target = devtools.TargetFactory.forTab(tab);
+    var target = TargetFactory.forTab(tab);
     target.off("navigate", fireChange);
     target.once("navigate", fireChange);
   }
 }(this));
--- a/browser/devtools/commandline/test/helpers.js
+++ b/browser/devtools/commandline/test/helpers.js
@@ -19,18 +19,17 @@
 
 this.EXPORTED_SYMBOLS = [ 'helpers' ];
 var helpers = {};
 this.helpers = helpers;
 let require = (Cu.import("resource://gre/modules/devtools/Require.jsm", {})).require;
 Components.utils.import("resource:///modules/devtools/gcli.jsm", {});
 
 let console = (Cu.import("resource://gre/modules/devtools/Console.jsm", {})).console;
-let devtools = (Cu.import("resource:///modules/devtools/gDevTools.jsm", {})).devtools;
-let TargetFactory = devtools.TargetFactory;
+let TargetFactory = (Cu.import("resource:///modules/devtools/Target.jsm", {})).TargetFactory;
 
 let Promise = (Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {})).Promise;
 let assert = { ok: ok, is: is, log: info };
 
 var util = require('util/util');
 
 var converters = require('gcli/converters');
 
--- a/browser/devtools/debugger/DebuggerPanel.jsm
+++ b/browser/devtools/debugger/DebuggerPanel.jsm
@@ -5,17 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 this.EXPORTED_SYMBOLS = ["DebuggerPanel"];
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource:///modules/devtools/shared/event-emitter.js");
+Cu.import("resource:///modules/devtools/EventEmitter.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
   "resource://gre/modules/commonjs/sdk/core/promise.js");
 
 XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
   "resource://gre/modules/devtools/dbg-server.jsm");
 
 function DebuggerPanel(iframeWindow, toolbox) {
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -7,24 +7,24 @@ const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 let tempScope = {};
 Cu.import("resource://gre/modules/Services.jsm", tempScope);
 Cu.import("resource://gre/modules/devtools/dbg-server.jsm", tempScope);
 Cu.import("resource://gre/modules/devtools/dbg-client.jsm", tempScope);
 Cu.import("resource:///modules/source-editor.jsm", tempScope);
 Cu.import("resource:///modules/devtools/gDevTools.jsm", tempScope);
+Cu.import("resource:///modules/devtools/Target.jsm", tempScope);
 let Services = tempScope.Services;
 let SourceEditor = tempScope.SourceEditor;
 let DebuggerServer = tempScope.DebuggerServer;
 let DebuggerTransport = tempScope.DebuggerTransport;
 let DebuggerClient = tempScope.DebuggerClient;
 let gDevTools = tempScope.gDevTools;
-let devtools = tempScope.devtools;
-let TargetFactory = devtools.TargetFactory;
+let TargetFactory = tempScope.TargetFactory;
 
 // Import the GCLI test helper
 let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
 Services.scriptloader.loadSubScript(testDir + "/helpers.js", this);
 
 const EXAMPLE_URL = "http://example.com/browser/browser/devtools/debugger/test/";
 const TAB1_URL = EXAMPLE_URL + "browser_dbg_tab1.html";
 const TAB2_URL = EXAMPLE_URL + "browser_dbg_tab2.html";
--- a/browser/devtools/debugger/test/helpers.js
+++ b/browser/devtools/debugger/test/helpers.js
@@ -19,18 +19,17 @@
 
 this.EXPORTED_SYMBOLS = [ 'helpers' ];
 var helpers = {};
 this.helpers = helpers;
 let require = (Cu.import("resource://gre/modules/devtools/Require.jsm", {})).require;
 Components.utils.import("resource:///modules/devtools/gcli.jsm", {});
 
 let console = (Cu.import("resource://gre/modules/devtools/Console.jsm", {})).console;
-let devtools = (Cu.import("resource:///modules/devtools/gDevTools.jsm", {})).devtools;
-let TargetFactory = devtools.TargetFactory;
+let TargetFactory = (Cu.import("resource:///modules/devtools/Target.jsm", {})).TargetFactory;
 
 let Promise = (Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {})).Promise;
 let assert = { ok: ok, is: is, log: info };
 
 var util = require('util/util');
 
 var converters = require('gcli/converters');
 
--- a/browser/devtools/fontinspector/test/browser_fontinspector.js
+++ b/browser/devtools/fontinspector/test/browser_fontinspector.js
@@ -1,14 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 let tempScope = {};
-let {devtools, gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
-let TargetFactory = devtools.TargetFactory;
+Cu.import("resource:///modules/devtools/Target.jsm", tempScope);
+let TargetFactory = tempScope.TargetFactory;
 
 let DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
 
 function test() {
   waitForExplicitFinish();
 
   let doc;
   let node;
--- a/browser/devtools/framework/Makefile.in
+++ b/browser/devtools/framework/Makefile.in
@@ -8,9 +8,8 @@ srcdir		= @srcdir@
 VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 include $(topsrcdir)/config/rules.mk
 
 libs::
 	$(NSINSTALL) $(srcdir)/*.jsm $(FINAL_TARGET)/modules/devtools
-	$(NSINSTALL) $(srcdir)/*.js $(FINAL_TARGET)/modules/devtools/framework
new file mode 100644
--- /dev/null
+++ b/browser/devtools/framework/Sidebar.jsm
@@ -0,0 +1,213 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+this.EXPORTED_SYMBOLS = ["ToolSidebar"];
+
+Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
+Cu.import("resource:///modules/devtools/EventEmitter.jsm");
+
+const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+/**
+ * ToolSidebar provides methods to register tabs in the sidebar.
+ * It's assumed that the sidebar contains a xul:tabbox.
+ *
+ * @param {Node} tabbox
+ *  <tabbox> node;
+ * @param {ToolPanel} panel
+ *  Related ToolPanel instance;
+ * @param {Boolean} showTabstripe
+ *  Show the tabs.
+ */
+this.ToolSidebar = function ToolSidebar(tabbox, panel, showTabstripe=true)
+{
+  EventEmitter.decorate(this);
+
+  this._tabbox = tabbox;
+  this._panelDoc = this._tabbox.ownerDocument;
+  this._toolPanel = panel;
+
+  this._tabbox.tabpanels.addEventListener("select", this, true);
+
+  this._tabs = new Map();
+
+  if (!showTabstripe) {
+    this._tabbox.setAttribute("hidetabs", "true");
+  }
+}
+
+ToolSidebar.prototype = {
+  /**
+   * Register a tab. A tab is a document.
+   * The document must have a title, which will be used as the name of the tab.
+   *
+   * @param {string} tab uniq id
+   * @param {string} url
+   */
+  addTab: function ToolSidebar_addTab(id, url, selected=false) {
+    let iframe = this._panelDoc.createElementNS(XULNS, "iframe");
+    iframe.className = "iframe-" + id;
+    iframe.setAttribute("flex", "1");
+    iframe.setAttribute("src", url);
+    iframe.tooltip = "aHTMLTooltip";
+
+    let tab = this._tabbox.tabs.appendItem();
+    tab.setAttribute("label", ""); // Avoid showing "undefined" while the tab is loading
+
+    let onIFrameLoaded = function() {
+      tab.setAttribute("label", iframe.contentDocument.title);
+      iframe.removeEventListener("load", onIFrameLoaded, true);
+      if ("setPanel" in iframe.contentWindow) {
+        iframe.contentWindow.setPanel(this._toolPanel, iframe);
+      }
+      this.emit(id + "-ready");
+    }.bind(this);
+
+    iframe.addEventListener("load", onIFrameLoaded, true);
+
+    let tabpanel = this._panelDoc.createElementNS(XULNS, "tabpanel");
+    tabpanel.setAttribute("id", "sidebar-panel-" + id);
+    tabpanel.appendChild(iframe);
+    this._tabbox.tabpanels.appendChild(tabpanel);
+
+    this._tooltip = this._panelDoc.createElementNS(XULNS, "tooltip");
+    this._tooltip.id = "aHTMLTooltip";
+    tabpanel.appendChild(this._tooltip);
+    this._tooltip.page = true;
+
+    tab.linkedPanel = "sidebar-panel-" + id;
+
+    // We store the index of this tab.
+    this._tabs.set(id, tab);
+
+    if (selected) {
+      // For some reason I don't understand, if we call this.select in this
+      // event loop (after inserting the tab), the tab will never get the
+      // the "selected" attribute set to true.
+      this._panelDoc.defaultView.setTimeout(function() {
+        this.select(id);
+      }.bind(this), 10);
+    }
+
+    this.emit("new-tab-registered", id);
+  },
+
+  /**
+   * Select a specific tab.
+   */
+  select: function ToolSidebar_select(id) {
+    let tab = this._tabs.get(id);
+    if (tab) {
+      this._tabbox.selectedTab = tab;
+    }
+  },
+
+  /**
+   * Return the id of the selected tab.
+   */
+  getCurrentTabID: function ToolSidebar_getCurrentTabID() {
+    let currentID = null;
+    for (let [id, tab] of this._tabs) {
+      if (this._tabbox.tabs.selectedItem == tab) {
+        currentID = id;
+        break;
+      }
+    }
+    return currentID;
+  },
+
+  /**
+   * Returns the requested tab based on the id.
+   *
+   * @param String id
+   *        unique id of the requested tab.
+   */
+  getTab: function ToolSidebar_getTab(id) {
+    return this._tabbox.tabpanels.querySelector("#sidebar-panel-" + id);
+  },
+
+  /**
+   * Event handler.
+   */
+  handleEvent: function ToolSidebar_eventHandler(event) {
+    if (event.type == "select") {
+      let previousTool = this._currentTool;
+      this._currentTool = this.getCurrentTabID();
+      if (previousTool) {
+        this.emit(previousTool + "-unselected");
+      }
+
+      this.emit(this._currentTool + "-selected");
+      this.emit("select", this._currentTool);
+    }
+  },
+
+  /**
+   * Toggle sidebar's visibility state.
+   */
+  toggle: function ToolSidebar_toggle() {
+    if (this._tabbox.hasAttribute("hidden")) {
+      this.show();
+    } else {
+      this.hide();
+    }
+  },
+
+  /**
+   * Show the sidebar.
+   */
+  show: function ToolSidebar_show() {
+    this._tabbox.removeAttribute("hidden");
+  },
+
+  /**
+   * Show the sidebar.
+   */
+  hide: function ToolSidebar_hide() {
+    this._tabbox.setAttribute("hidden", "true");
+  },
+
+  /**
+   * Return the window containing the tab content.
+   */
+  getWindowForTab: function ToolSidebar_getWindowForTab(id) {
+    if (!this._tabs.has(id)) {
+      return null;
+    }
+
+    let panel = this._panelDoc.getElementById(this._tabs.get(id).linkedPanel);
+    return panel.firstChild.contentWindow;
+  },
+
+  /**
+   * Clean-up.
+   */
+  destroy: function ToolSidebar_destroy() {
+    if (this._destroyed) {
+      return Promise.resolve(null);
+    }
+    this._destroyed = true;
+
+    this._tabbox.tabpanels.removeEventListener("select", this, true);
+
+    while (this._tabbox.tabpanels.hasChildNodes()) {
+      this._tabbox.tabpanels.removeChild(this._tabbox.tabpanels.firstChild);
+    }
+
+    while (this._tabbox.tabs.hasChildNodes()) {
+      this._tabbox.tabs.removeChild(this._tabbox.tabs.firstChild);
+    }
+
+    this._tabs = null;
+    this._tabbox = null;
+    this._panelDoc = null;
+    this._toolPanel = null;
+
+    return Promise.resolve(null);
+  },
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/framework/Target.jsm
@@ -0,0 +1,587 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [ "TargetFactory" ];
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
+Cu.import("resource:///modules/devtools/EventEmitter.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
+  "resource://gre/modules/devtools/dbg-server.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "DebuggerClient",
+  "resource://gre/modules/devtools/dbg-client.jsm");
+
+const targets = new WeakMap();
+const promiseTargets = new WeakMap();
+
+/**
+ * Functions for creating Targets
+ */
+this.TargetFactory = {
+  /**
+   * Construct a Target
+   * @param {XULTab} tab
+   *        The tab to use in creating a new target.
+   *
+   * @return A target object
+   */
+  forTab: function TF_forTab(tab) {
+    let target = targets.get(tab);
+    if (target == null) {
+      target = new TabTarget(tab);
+      targets.set(tab, target);
+    }
+    return target;
+  },
+
+  /**
+   * Return a promise of a Target for a remote tab.
+   * @param {Object} options
+   *        The options object has the following properties:
+   *        {
+   *          form: the remote protocol form of a tab,
+   *          client: a DebuggerClient instance,
+   *          chrome: true if the remote target is the whole process
+   *        }
+   *
+   * @return A promise of a target object
+   */
+  forRemoteTab: function TF_forRemoteTab(options) {
+    let promise = promiseTargets.get(options);
+    if (promise == null) {
+      let target = new TabTarget(options);
+      promise = target.makeRemote().then(() => target);
+      promiseTargets.set(options, promise);
+    }
+    return promise;
+  },
+
+  /**
+   * Creating a target for a tab that is being closed is a problem because it
+   * allows a leak as a result of coming after the close event which normally
+   * clears things up. This function allows us to ask if there is a known
+   * target for a tab without creating a target
+   * @return true/false
+   */
+  isKnownTab: function TF_isKnownTab(tab) {
+    return targets.has(tab);
+  },
+
+  /**
+   * Construct a Target
+   * @param {nsIDOMWindow} window
+   *        The chromeWindow to use in creating a new target
+   * @return A target object
+   */
+  forWindow: function TF_forWindow(window) {
+    let target = targets.get(window);
+    if (target == null) {
+      target = new WindowTarget(window);
+      targets.set(window, target);
+    }
+    return target;
+  },
+
+  /**
+   * Get all of the targets known to the local browser instance
+   * @return An array of target objects
+   */
+  allTargets: function TF_allTargets() {
+    let windows = [];
+    let wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
+                       .getService(Components.interfaces.nsIWindowMediator);
+    let en = wm.getXULWindowEnumerator(null);
+    while (en.hasMoreElements()) {
+      windows.push(en.getNext());
+    }
+
+    return windows.map(function(window) {
+      return TargetFactory.forWindow(window);
+    });
+  },
+};
+
+/**
+ * The 'version' property allows the developer tools equivalent of browser
+ * detection. Browser detection is evil, however while we don't know what we
+ * will need to detect in the future, it is an easy way to postpone work.
+ * We should be looking to use 'supports()' in place of version where
+ * possible.
+ */
+function getVersion() {
+  // FIXME: return something better
+  return 20;
+}
+
+/**
+ * A better way to support feature detection, but we're not yet at a place
+ * where we have the features well enough defined for this to make lots of
+ * sense.
+ */
+function supports(feature) {
+  // FIXME: return something better
+  return false;
+};
+
+/**
+ * A Target represents something that we can debug. Targets are generally
+ * read-only. Any changes that you wish to make to a target should be done via
+ * a Tool that attaches to the target. i.e. a Target is just a pointer saying
+ * "the thing to debug is over there".
+ *
+ * Providing a generalized abstraction of a web-page or web-browser (available
+ * either locally or remotely) is beyond the scope of this class (and maybe
+ * also beyond the scope of this universe) However Target does attempt to
+ * abstract some common events and read-only properties common to many Tools.
+ *
+ * Supported read-only properties:
+ * - name, isRemote, url
+ *
+ * Target extends EventEmitter and provides support for the following events:
+ * - close: The target window has been closed. All tools attached to this
+ *     target should close. This event is not currently cancelable.
+ * - navigate: The target window has navigated to a different URL
+ *
+ * Optional events:
+ * - will-navigate: The target window will navigate to a different URL
+ * - hidden: The target is not visible anymore (for TargetTab, another tab is selected)
+ * - visible: The target is visible (for TargetTab, tab is selected)
+ *
+ * Target also supports 2 functions to help allow 2 different versions of
+ * Firefox debug each other. The 'version' property is the equivalent of
+ * browser detection - simple and easy to implement but gets fragile when things
+ * are not quite what they seem. The 'supports' property is the equivalent of
+ * feature detection - harder to setup, but more robust long-term.
+ *
+ * Comparing Targets: 2 instances of a Target object can point at the same
+ * thing, so t1 !== t2 and t1 != t2 even when they represent the same object.
+ * To compare to targets use 't1.equals(t2)'.
+ */
+function Target() {
+  throw new Error("Use TargetFactory.newXXX or Target.getXXX to create a Target in place of 'new Target()'");
+}
+
+Object.defineProperty(Target.prototype, "version", {
+  get: getVersion,
+  enumerable: true
+});
+
+
+/**
+ * A TabTarget represents a page living in a browser tab. Generally these will
+ * be web pages served over http(s), but they don't have to be.
+ */
+function TabTarget(tab) {
+  EventEmitter.decorate(this);
+  this.destroy = this.destroy.bind(this);
+  this._handleThreadState = this._handleThreadState.bind(this);
+  this.on("thread-resumed", this._handleThreadState);
+  this.on("thread-paused", this._handleThreadState);
+  // Only real tabs need initialization here. Placeholder objects for remote
+  // targets will be initialized after a makeRemote method call.
+  if (tab && !["client", "form", "chrome"].every(tab.hasOwnProperty, tab)) {
+    this._tab = tab;
+    this._setupListeners();
+  } else {
+    this._form = tab.form;
+    this._client = tab.client;
+    this._chrome = tab.chrome;
+  }
+}
+
+TabTarget.prototype = {
+  _webProgressListener: null,
+
+  supports: supports,
+  get version() { return getVersion(); },
+
+  get tab() {
+    return this._tab;
+  },
+
+  get form() {
+    return this._form;
+  },
+
+  get client() {
+    return this._client;
+  },
+
+  get chrome() {
+    return this._chrome;
+  },
+
+  get window() {
+    // Be extra careful here, since this may be called by HS_getHudByWindow
+    // during shutdown.
+    if (this._tab && this._tab.linkedBrowser) {
+      return this._tab.linkedBrowser.contentWindow;
+    }
+  },
+
+  get name() {
+    return this._tab ? this._tab.linkedBrowser.contentDocument.title :
+                       this._form.title;
+  },
+
+  get url() {
+    return this._tab ? this._tab.linkedBrowser.contentDocument.location.href :
+                       this._form.url;
+  },
+
+  get isRemote() {
+    return !this.isLocalTab;
+  },
+
+  get isLocalTab() {
+    return !!this._tab;
+  },
+
+  get isThreadPaused() {
+    return !!this._isThreadPaused;
+  },
+
+  /**
+   * Adds remote protocol capabilities to the target, so that it can be used
+   * for tools that support the Remote Debugging Protocol even for local
+   * connections.
+   */
+  makeRemote: function TabTarget_makeRemote() {
+    if (this._remote) {
+      return this._remote.promise;
+    }
+
+    this._remote = Promise.defer();
+
+    if (this.isLocalTab) {
+      // Since a remote protocol connection will be made, let's start the
+      // DebuggerServer here, once and for all tools.
+      if (!DebuggerServer.initialized) {
+        DebuggerServer.init();
+        DebuggerServer.addBrowserActors();
+      }
+
+      this._client = new DebuggerClient(DebuggerServer.connectPipe());
+      // A local TabTarget will never perform chrome debugging.
+      this._chrome = false;
+    }
+
+    this._setupRemoteListeners();
+
+    if (this.isRemote) {
+      // In the remote debugging case, the protocol connection will have been
+      // already initialized in the connection screen code.
+      this._remote.resolve(null);
+    } else {
+      this._client.connect((aType, aTraits) => {
+        this._client.listTabs(aResponse => {
+          this._form = aResponse.tabs[aResponse.selected];
+
+          this._client.attachTab(this._form.actor, (aResponse, aTabClient) => {
+            if (!aTabClient) {
+              this._remote.reject("Unable to attach to the tab");
+              return;
+            }
+            this.threadActor = aResponse.threadActor;
+            this._remote.resolve(null);
+          });
+        });
+      });
+    }
+
+    return this._remote.promise;
+  },
+
+  /**
+   * Listen to the different events.
+   */
+  _setupListeners: function TabTarget__setupListeners() {
+    this._webProgressListener = new TabWebProgressListener(this);
+    this.tab.linkedBrowser.addProgressListener(this._webProgressListener);
+    this.tab.addEventListener("TabClose", this);
+    this.tab.parentNode.addEventListener("TabSelect", this);
+    this.tab.ownerDocument.defaultView.addEventListener("unload", this);
+  },
+
+  /**
+   * Setup listeners for remote debugging, updating existing ones as necessary.
+   */
+  _setupRemoteListeners: function TabTarget__setupRemoteListeners() {
+    this.client.addListener("tabDetached", this.destroy);
+
+    this._onTabNavigated = function onRemoteTabNavigated(aType, aPacket) {
+      let event = Object.create(null);
+      event.url = aPacket.url;
+      event.title = aPacket.title;
+      // Send any stored event payload (DOMWindow or nsIRequest) for backwards
+      // compatibility with non-remotable tools.
+      event._navPayload = this._navPayload;
+      if (aPacket.state == "start") {
+        this.emit("will-navigate", event);
+      } else {
+        this.emit("navigate", event);
+      }
+      this._navPayload = null;
+    }.bind(this);
+    this.client.addListener("tabNavigated", this._onTabNavigated);
+  },
+
+  /**
+   * Handle tabs events.
+   */
+  handleEvent: function (event) {
+    switch (event.type) {
+      case "TabClose":
+      case "unload":
+        this.destroy();
+        break;
+      case "TabSelect":
+        if (this.tab.selected) {
+          this.emit("visible", event);
+        } else {
+          this.emit("hidden", event);
+        }
+        break;
+    }
+  },
+
+  /**
+   * Handle script status.
+   */
+  _handleThreadState: function(event) {
+    switch (event) {
+      case "thread-resumed":
+        this._isThreadPaused = false;
+        break;
+      case "thread-paused":
+        this._isThreadPaused = true;
+        break;
+    }
+  },
+
+  /**
+   * Target is not alive anymore.
+   */
+  destroy: function() {
+    // If several things call destroy then we give them all the same
+    // destruction promise so we're sure to destroy only once
+    if (this._destroyer) {
+      return this._destroyer.promise;
+    }
+
+    this._destroyer = Promise.defer();
+
+    // Before taking any action, notify listeners that destruction is imminent.
+    this.emit("close");
+
+    // First of all, do cleanup tasks that pertain to both remoted and
+    // non-remoted targets.
+    this.off("thread-resumed", this._handleThreadState);
+    this.off("thread-paused", this._handleThreadState);
+
+    if (this._tab) {
+      if (this._webProgressListener) {
+        this._webProgressListener.destroy();
+      }
+
+      this._tab.ownerDocument.defaultView.removeEventListener("unload", this);
+      this._tab.removeEventListener("TabClose", this);
+      this._tab.parentNode.removeEventListener("TabSelect", this);
+    }
+
+    // If this target was not remoted, the promise will be resolved before the
+    // function returns.
+    if (this._tab && !this._client) {
+      targets.delete(this._tab);
+      this._tab = null;
+      this._client = null;
+      this._form = null;
+      this._remote = null;
+
+      this._destroyer.resolve(null);
+    } else if (this._client) {
+      // If, on the other hand, this target was remoted, the promise will be
+      // resolved after the remote connection is closed.
+      this.client.removeListener("tabNavigated", this._onTabNavigated);
+      this.client.removeListener("tabDetached", this.destroy);
+
+      this._client.close(function onClosed() {
+        if (this._tab) {
+          targets.delete(this._tab);
+        } else {
+          promiseTargets.delete(this._form);
+        }
+        this._client = null;
+        this._tab = null;
+        this._form = null;
+        this._remote = null;
+
+        this._destroyer.resolve(null);
+      }.bind(this));
+    }
+
+    return this._destroyer.promise;
+  },
+
+  toString: function() {
+    return 'TabTarget:' + (this._tab ? this._tab : (this._form && this._form.actor));
+  },
+};
+
+
+/**
+ * WebProgressListener for TabTarget.
+ *
+ * @param object aTarget
+ *        The TabTarget instance to work with.
+ */
+function TabWebProgressListener(aTarget) {
+  this.target = aTarget;
+}
+
+TabWebProgressListener.prototype = {
+  target: null,
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference]),
+
+  onStateChange: function TWPL_onStateChange(progress, request, flag, status) {
+    let isStart = flag & Ci.nsIWebProgressListener.STATE_START;
+    let isDocument = flag & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT;
+    let isNetwork = flag & Ci.nsIWebProgressListener.STATE_IS_NETWORK;
+    let isRequest = flag & Ci.nsIWebProgressListener.STATE_IS_REQUEST;
+
+    // Skip non-interesting states.
+    if (!isStart || !isDocument || !isRequest || !isNetwork) {
+      return;
+    }
+
+    // emit event if the top frame is navigating
+    if (this.target && this.target.window == progress.DOMWindow) {
+      // Emit the event if the target is not remoted or store the payload for
+      // later emission otherwise.
+      if (this.target._client) {
+        this.target._navPayload = request;
+      } else {
+        this.target.emit("will-navigate", request);
+      }
+    }
+  },
+
+  onProgressChange: function() {},
+  onSecurityChange: function() {},
+  onStatusChange: function() {},
+
+  onLocationChange: function TWPL_onLocationChange(webProgress, request, URI, flags) {
+    if (this.target &&
+        !(flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) {
+      let window = webProgress.DOMWindow;
+      // Emit the event if the target is not remoted or store the payload for
+      // later emission otherwise.
+      if (this.target._client) {
+        this.target._navPayload = window;
+      } else {
+        this.target.emit("navigate", window);
+      }
+    }
+  },
+
+  /**
+   * Destroy the progress listener instance.
+   */
+  destroy: function TWPL_destroy() {
+    if (this.target.tab) {
+      this.target.tab.linkedBrowser.removeProgressListener(this);
+    }
+    this.target._webProgressListener = null;
+    this.target = null;
+  }
+};
+
+
+/**
+ * A WindowTarget represents a page living in a xul window or panel. Generally
+ * these will have a chrome: URL
+ */
+function WindowTarget(window) {
+  EventEmitter.decorate(this);
+  this._window = window;
+  this._setupListeners();
+}
+
+WindowTarget.prototype = {
+  supports: supports,
+  get version() { return getVersion(); },
+
+  get window() {
+    return this._window;
+  },
+
+  get name() {
+    return this._window.document.title;
+  },
+
+  get url() {
+    return this._window.document.location.href;
+  },
+
+  get isRemote() {
+    return false;
+  },
+
+  get isLocalTab() {
+    return false;
+  },
+
+  get isThreadPaused() {
+    return !!this._isThreadPaused;
+  },
+
+  /**
+   * Listen to the different events.
+   */
+  _setupListeners: function() {
+    this._handleThreadState = this._handleThreadState.bind(this);
+    this.on("thread-paused", this._handleThreadState);
+    this.on("thread-resumed", this._handleThreadState);
+  },
+
+  _handleThreadState: function(event) {
+    switch (event) {
+      case "thread-resumed":
+        this._isThreadPaused = false;
+        break;
+      case "thread-paused":
+        this._isThreadPaused = true;
+        break;
+    }
+  },
+
+  /**
+   * Target is not alive anymore.
+   */
+  destroy: function() {
+    if (!this._destroyed) {
+      this._destroyed = true;
+
+      this.off("thread-paused", this._handleThreadState);
+      this.off("thread-resumed", this._handleThreadState);
+      this.emit("close");
+
+      targets.delete(this._window);
+      this._window = null;
+    }
+
+    return Promise.resolve(null);
+  },
+
+  toString: function() {
+    return 'WindowTarget:' + this.window;
+  },
+};
new file mode 100644
--- /dev/null
+++ b/browser/devtools/framework/ToolDefinitions.jsm
@@ -0,0 +1,228 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+                          "defaultTools",
+                          "webConsoleDefinition",
+                          "debuggerDefinition",
+                          "inspectorDefinition",
+                          "styleEditorDefinition",
+                          "netMonitorDefinition"
+                        ];
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+const inspectorProps = "chrome://browser/locale/devtools/inspector.properties";
+const debuggerProps = "chrome://browser/locale/devtools/debugger.properties";
+const styleEditorProps = "chrome://browser/locale/devtools/styleeditor.properties";
+const webConsoleProps = "chrome://browser/locale/devtools/webconsole.properties";
+const profilerProps = "chrome://browser/locale/devtools/profiler.properties";
+const netMonitorProps = "chrome://browser/locale/devtools/netmonitor.properties";
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource:///modules/devtools/EventEmitter.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "osString",
+  function() Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS);
+
+// Panels
+XPCOMUtils.defineLazyModuleGetter(this, "WebConsolePanel",
+  "resource:///modules/WebConsolePanel.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "DebuggerPanel",
+  "resource:///modules/devtools/DebuggerPanel.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "StyleEditorPanel",
+  "resource:///modules/devtools/StyleEditorPanel.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "InspectorPanel",
+  "resource:///modules/devtools/InspectorPanel.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "ProfilerPanel",
+  "resource:///modules/devtools/ProfilerPanel.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "NetMonitorPanel",
+  "resource:///modules/devtools/NetMonitorPanel.jsm");
+
+// Strings
+XPCOMUtils.defineLazyGetter(this, "webConsoleStrings",
+  function() Services.strings.createBundle(webConsoleProps));
+
+XPCOMUtils.defineLazyGetter(this, "debuggerStrings",
+  function() Services.strings.createBundle(debuggerProps));
+
+XPCOMUtils.defineLazyGetter(this, "styleEditorStrings",
+  function() Services.strings.createBundle(styleEditorProps));
+
+XPCOMUtils.defineLazyGetter(this, "inspectorStrings",
+  function() Services.strings.createBundle(inspectorProps));
+
+XPCOMUtils.defineLazyGetter(this, "profilerStrings",
+  function() Services.strings.createBundle(profilerProps));
+
+XPCOMUtils.defineLazyGetter(this, "netMonitorStrings",
+  function() Services.strings.createBundle(netMonitorProps));
+
+// Definitions
+let webConsoleDefinition = {
+  id: "webconsole",
+  key: l10n("cmd.commandkey", webConsoleStrings),
+  accesskey: l10n("webConsoleCmd.accesskey", webConsoleStrings),
+  modifiers: Services.appinfo.OS == "Darwin" ? "accel,alt" : "accel,shift",
+  ordinal: 0,
+  icon: "chrome://browser/skin/devtools/tool-webconsole.png",
+  url: "chrome://browser/content/devtools/webconsole.xul",
+  label: l10n("ToolboxWebconsole.label", webConsoleStrings),
+  tooltip: l10n("ToolboxWebconsole.tooltip", webConsoleStrings),
+
+  isTargetSupported: function(target) {
+    return true;
+  },
+  build: function(iframeWindow, toolbox) {
+    let panel = new WebConsolePanel(iframeWindow, toolbox);
+    return panel.open();
+  }
+};
+
+let debuggerDefinition = {
+  id: "jsdebugger",
+  key: l10n("open.commandkey", debuggerStrings),
+  accesskey: l10n("debuggerMenu.accesskey", debuggerStrings),
+  modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift",
+  ordinal: 2,
+  killswitch: "devtools.debugger.enabled",
+  icon: "chrome://browser/skin/devtools/tool-debugger.png",
+  url: "chrome://browser/content/debugger.xul",
+  label: l10n("ToolboxDebugger.label", debuggerStrings),
+  tooltip: l10n("ToolboxDebugger.tooltip", debuggerStrings),
+
+  isTargetSupported: function(target) {
+    return true;
+  },
+
+  build: function(iframeWindow, toolbox) {
+    let panel = new DebuggerPanel(iframeWindow, toolbox);
+    return panel.open();
+  }
+};
+
+let inspectorDefinition = {
+  id: "inspector",
+  accesskey: l10n("inspector.accesskey", inspectorStrings),
+  key: l10n("inspector.commandkey", inspectorStrings),
+  ordinal: 1,
+  modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift",
+  icon: "chrome://browser/skin/devtools/tool-inspector.png",
+  url: "chrome://browser/content/devtools/inspector/inspector.xul",
+  label: l10n("inspector.label", inspectorStrings),
+  tooltip: l10n("inspector.tooltip", inspectorStrings),
+
+  isTargetSupported: function(target) {
+    return !target.isRemote;
+  },
+
+  build: function(iframeWindow, toolbox) {
+    let panel = new InspectorPanel(iframeWindow, toolbox);
+    return panel.open();
+  }
+};
+
+let styleEditorDefinition = {
+  id: "styleeditor",
+  key: l10n("open.commandkey", styleEditorStrings),
+  ordinal: 3,
+  accesskey: l10n("open.accesskey", styleEditorStrings),
+  modifiers: "shift",
+  icon: "chrome://browser/skin/devtools/tool-styleeditor.png",
+  url: "chrome://browser/content/styleeditor.xul",
+  label: l10n("ToolboxStyleEditor.label", styleEditorStrings),
+  tooltip: l10n("ToolboxStyleEditor.tooltip", styleEditorStrings),
+
+  isTargetSupported: function(target) {
+    return !target.isRemote;
+  },
+
+  build: function(iframeWindow, toolbox) {
+    let panel = new StyleEditorPanel(iframeWindow, toolbox);
+    return panel.open();
+  }
+};
+
+let profilerDefinition = {
+  id: "jsprofiler",
+  accesskey: l10n("profiler.accesskey", profilerStrings),
+  key: l10n("profiler2.commandkey", profilerStrings),
+  ordinal: 4,
+  modifiers: "shift",
+  killswitch: "devtools.profiler.enabled",
+  icon: "chrome://browser/skin/devtools/tool-profiler.png",
+  url: "chrome://browser/content/profiler.xul",
+  label: l10n("profiler.label", profilerStrings),
+  tooltip: l10n("profiler.tooltip", profilerStrings),
+
+  isTargetSupported: function (target) {
+    return true;
+  },
+
+  build: function (frame, target) {
+    let panel = new ProfilerPanel(frame, target);
+    return panel.open();
+  }
+};
+
+let netMonitorDefinition = {
+  id: "netmonitor",
+  accesskey: l10n("netmonitor.accesskey", netMonitorStrings),
+  key: l10n("netmonitor.commandkey", netMonitorStrings),
+  ordinal: 5,
+  modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift",
+  killswitch: "devtools.netmonitor.enabled",
+  icon: "chrome://browser/skin/devtools/tool-profiler.png",
+  url: "chrome://browser/content/devtools/netmonitor.xul",
+  label: l10n("netmonitor.label", netMonitorStrings),
+  tooltip: l10n("netmonitor.tooltip", netMonitorStrings),
+
+  isTargetSupported: function(target) {
+    return true;
+  },
+
+  build: function(iframeWindow, toolbox) {
+    let panel = new NetMonitorPanel(iframeWindow, toolbox);
+    return panel.open();
+  }
+};
+
+this.defaultTools = [
+  styleEditorDefinition,
+  webConsoleDefinition,
+  debuggerDefinition,
+  inspectorDefinition,
+  netMonitorDefinition
+];
+
+if (Services.prefs.getBoolPref("devtools.profiler.enabled")) {
+  defaultTools.push(profilerDefinition);
+}
+
+/**
+ * Lookup l10n string from a string bundle.
+ *
+ * @param {string} name
+ *        The key to lookup.
+ * @param {StringBundle} bundle
+ *        The key to lookup.
+ * @returns A localized version of the given key.
+ */
+function l10n(name, bundle)
+{
+  try {
+    return bundle.GetStringFromName(name);
+  } catch (ex) {
+    Services.console.logStringMessage("Error reading '" + name + "'");
+    throw new Error("l10n error with " + name);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/framework/Toolbox.jsm
@@ -0,0 +1,742 @@
+/* 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 { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
+Cu.import("resource:///modules/devtools/EventEmitter.jsm");
+Cu.import("resource:///modules/devtools/gDevTools.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Hosts",
+                                  "resource:///modules/devtools/ToolboxHosts.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "CommandUtils",
+                                  "resource:///modules/devtools/DeveloperToolbar.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "toolboxStrings", function() {
+  let bundle = Services.strings.createBundle("chrome://browser/locale/devtools/toolbox.properties");
+  let l10n = function(aName, ...aArgs) {
+    try {
+      if (aArgs.length == 0) {
+        return bundle.GetStringFromName(aName);
+      } else {
+        return bundle.formatStringFromName(aName, aArgs, aArgs.length);
+      }
+    } catch (ex) {
+      Services.console.logStringMessage("Error reading '" + aName + "'");
+    }
+  };
+  return l10n;
+});
+
+XPCOMUtils.defineLazyGetter(this, "Requisition", function() {
+  Cu.import("resource://gre/modules/devtools/Require.jsm");
+  Cu.import("resource:///modules/devtools/gcli.jsm");
+
+  return require('gcli/cli').Requisition;
+});
+
+this.EXPORTED_SYMBOLS = [ "Toolbox" ];
+
+// This isn't the best place for this, but I don't know what is right now
+
+/**
+ * Implementation of 'promised', while we wait for bug 790195 to be fixed.
+ * @see Consuming promises in https://addons.mozilla.org/en-US/developers/docs/sdk/latest/packages/api-utils/promise.html
+ * @see https://bugzilla.mozilla.org/show_bug.cgi?id=790195
+ * @see https://github.com/mozilla/addon-sdk/blob/master/packages/api-utils/lib/promise.js#L179
+ */
+Promise.promised = (function() {
+  // Note: Define shortcuts and utility functions here in order to avoid
+  // slower property accesses and unnecessary closure creations on each
+  // call of this popular function.
+
+  var call = Function.call;
+  var concat = Array.prototype.concat;
+
+  // Utility function that does following:
+  // execute([ f, self, args...]) => f.apply(self, args)
+  function execute(args) { return call.apply(call, args); }
+
+  // Utility function that takes promise of `a` array and maybe promise `b`
+  // as arguments and returns promise for `a.concat(b)`.
+  function promisedConcat(promises, unknown) {
+    return promises.then(function(values) {
+      return Promise.resolve(unknown).then(function(value) {
+        return values.concat([ value ]);
+      });
+    });
+  }
+
+  return function promised(f, prototype) {
+    /**
+    Returns a wrapped `f`, which when called returns a promise that resolves to
+    `f(...)` passing all the given arguments to it, which by the way may be
+    promises. Optionally second `prototype` argument may be provided to be used
+    a prototype for a returned promise.
+
+    ## Example
+
+    var promise = promised(Array)(1, promise(2), promise(3))
+    promise.then(console.log) // => [ 1, 2, 3 ]
+    **/
+
+    return function promised() {
+      // create array of [ f, this, args... ]
+      return concat.apply([ f, this ], arguments).
+          // reduce it via `promisedConcat` to get promised array of fulfillments
+          reduce(promisedConcat, Promise.resolve([], prototype)).
+          // finally map that to promise of `f.apply(this, args...)`
+          then(execute);
+    };
+  };
+})();
+
+/**
+ * Convert an array of promises to a single promise, which is resolved (with an
+ * array containing resolved values) only when all the component promises are
+ * resolved.
+ */
+Promise.all = Promise.promised(Array);
+
+
+
+
+/**
+ * A "Toolbox" is the component that holds all the tools for one specific
+ * target. Visually, it's a document that includes the tools tabs and all
+ * the iframes where the tool panels will be living in.
+ *
+ * @param {object} target
+ *        The object the toolbox is debugging.
+ * @param {string} selectedTool
+ *        Tool to select initially
+ * @param {Toolbox.HostType} hostType
+ *        Type of host that will host the toolbox (e.g. sidebar, window)
+ */
+this.Toolbox = function Toolbox(target, selectedTool, hostType) {
+  this._target = target;
+  this._toolPanels = new Map();
+
+  this._toolRegistered = this._toolRegistered.bind(this);
+  this._toolUnregistered = this._toolUnregistered.bind(this);
+  this.destroy = this.destroy.bind(this);
+
+  this._target.on("close", this.destroy);
+
+  if (!hostType) {
+    hostType = Services.prefs.getCharPref(this._prefs.LAST_HOST);
+  }
+  if (!selectedTool) {
+    selectedTool = Services.prefs.getCharPref(this._prefs.LAST_TOOL);
+  }
+  let definitions = gDevTools.getToolDefinitionMap();
+  if (!definitions.get(selectedTool)) {
+    selectedTool = "webconsole";
+  }
+  this._defaultToolId = selectedTool;
+
+  this._host = this._createHost(hostType);
+
+  EventEmitter.decorate(this);
+
+  this._refreshHostTitle = this._refreshHostTitle.bind(this);
+  this._target.on("navigate", this._refreshHostTitle);
+  this.on("host-changed", this._refreshHostTitle);
+  this.on("select", this._refreshHostTitle);
+
+  gDevTools.on("tool-registered", this._toolRegistered);
+  gDevTools.on("tool-unregistered", this._toolUnregistered);
+}
+
+/**
+ * The toolbox can be 'hosted' either embedded in a browser window
+ * or in a separate window.
+ */
+Toolbox.HostType = {
+  BOTTOM: "bottom",
+  SIDE: "side",
+  WINDOW: "window"
+}
+
+Toolbox.prototype = {
+  _URL: "chrome://browser/content/devtools/framework/toolbox.xul",
+
+  _prefs: {
+    LAST_HOST: "devtools.toolbox.host",
+    LAST_TOOL: "devtools.toolbox.selectedTool",
+    SIDE_ENABLED: "devtools.toolbox.sideEnabled"
+  },
+
+  HostType: Toolbox.HostType,
+
+  /**
+   * Returns a *copy* of the _toolPanels collection.
+   *
+   * @return {Map} panels
+   *         All the running panels in the toolbox
+   */
+  getToolPanels: function TB_getToolPanels() {
+    let panels = new Map();
+
+    for (let [key, value] of this._toolPanels) {
+      panels.set(key, value);
+    }
+    return panels;
+  },
+
+  /**
+   * Access the panel for a given tool
+   */
+  getPanel: function TBOX_getPanel(id) {
+    return this.getToolPanels().get(id);
+  },
+
+  /**
+   * This is a shortcut for getPanel(currentToolId) because it is much more
+   * likely that we're going to want to get the panel that we've just made
+   * visible
+   */
+  getCurrentPanel: function TBOX_getCurrentPanel() {
+    return this.getToolPanels().get(this.currentToolId);
+  },
+
+  /**
+   * Get/alter the target of a Toolbox so we're debugging something different.
+   * See Target.jsm for more details.
+   * TODO: Do we allow |toolbox.target = null;| ?
+   */
+  get target() {
+    return this._target;
+  },
+
+  /**
+   * Get/alter the host of a Toolbox, i.e. is it in browser or in a separate
+   * tab. See HostType for more details.
+   */
+  get hostType() {
+    return this._host.type;
+  },
+
+  /**
+   * Get/alter the currently displayed tool.
+   */
+  get currentToolId() {
+    return this._currentToolId;
+  },
+
+  set currentToolId(value) {
+    this._currentToolId = value;
+  },
+
+  /**
+   * Get the iframe containing the toolbox UI.
+   */
+  get frame() {
+    return this._host.frame;
+  },
+
+  /**
+   * Shortcut to the document containing the toolbox UI
+   */
+  get doc() {
+    return this.frame.contentDocument;
+  },
+
+  /**
+   * Open the toolbox
+   */
+  open: function TBOX_open() {
+    let deferred = Promise.defer();
+
+    this._host.create().then(function(iframe) {
+      let domReady = function() {
+        iframe.removeEventListener("DOMContentLoaded", domReady, true);
+
+        this.isReady = true;
+
+        let closeButton = this.doc.getElementById("toolbox-close");
+        closeButton.addEventListener("command", this.destroy, true);
+
+        this._buildDockButtons();
+        this._buildTabs();
+        this._buildButtons();
+        this._addKeysToWindow();
+
+        this.selectTool(this._defaultToolId).then(function(panel) {
+          this.emit("ready");
+          deferred.resolve();
+        }.bind(this));
+      }.bind(this);
+
+      iframe.addEventListener("DOMContentLoaded", domReady, true);
+      iframe.setAttribute("src", this._URL);
+    }.bind(this));
+
+    return deferred.promise;
+  },
+
+  /**
+   * Adds the keys and commands to the Toolbox Window in window mode.
+   */
+  _addKeysToWindow: function TBOX__addKeysToWindow() {
+    if (this.hostType != Toolbox.HostType.WINDOW) {
+      return;
+    }
+    let doc = this.doc.defaultView.parent.document;
+    for (let [id, toolDefinition] of gDevTools._tools) {
+      if (toolDefinition.key) {
+        // Prevent multiple entries for the same tool.
+        if (doc.getElementById("key_" + id)) {
+          continue;
+        }
+        let key = doc.createElement("key");
+        key.id = "key_" + id;
+
+        if (toolDefinition.key.startsWith("VK_")) {
+          key.setAttribute("keycode", toolDefinition.key);
+        } else {
+          key.setAttribute("key", toolDefinition.key);
+        }
+
+        key.setAttribute("modifiers", toolDefinition.modifiers);
+        key.setAttribute("oncommand", "void(0);"); // needed. See bug 371900
+        key.addEventListener("command", function(toolId) {
+          this.selectTool(toolId);
+        }.bind(this, id), true);
+        doc.getElementById("toolbox-keyset").appendChild(key);
+      }
+    }
+  },
+
+  /**
+   * Build the buttons for changing hosts. Called every time
+   * the host changes.
+   */
+  _buildDockButtons: function TBOX_createDockButtons() {
+    let dockBox = this.doc.getElementById("toolbox-dock-buttons");
+
+    while (dockBox.firstChild) {
+      dockBox.removeChild(dockBox.firstChild);
+    }
+
+    if (!this._target.isLocalTab) {
+      return;
+    }
+
+    let closeButton = this.doc.getElementById("toolbox-close");
+    if (this.hostType === this.HostType.WINDOW) {
+      closeButton.setAttribute("hidden", "true");
+    } else {
+      closeButton.removeAttribute("hidden");
+    }
+
+    let sideEnabled = Services.prefs.getBoolPref(this._prefs.SIDE_ENABLED);
+
+    for each (let position in this.HostType) {
+      if (position == this.hostType ||
+         (!sideEnabled && position == this.HostType.SIDE)) {
+        continue;
+      }
+
+      let button = this.doc.createElement("toolbarbutton");
+      button.id = "toolbox-dock-" + position;
+      button.className = "toolbox-dock-button";
+      button.setAttribute("tooltiptext", toolboxStrings("toolboxDockButtons." +
+                                                        position + ".tooltip"));
+      button.addEventListener("command", function(position) {
+        this.switchHost(position);
+      }.bind(this, position));
+
+      dockBox.appendChild(button);
+    }
+  },
+
+  /**
+   * Add tabs to the toolbox UI for registered tools
+   */
+  _buildTabs: function TBOX_buildTabs() {
+    for (let definition of gDevTools.getToolDefinitionArray()) {
+      this._buildTabForTool(definition);
+    }
+  },
+
+  /**
+   * Add buttons to the UI as specified in the devtools.window.toolbarSpec pref
+   */
+  _buildButtons: function TBOX_buildButtons() {
+    if (!this.target.isLocalTab) {
+      return;
+    }
+
+    let toolbarSpec = CommandUtils.getCommandbarSpec("devtools.toolbox.toolbarSpec");
+    let environment = { chromeDocument: this.target.tab.ownerDocument };
+    let requisition = new Requisition(environment);
+
+    let buttons = CommandUtils.createButtons(toolbarSpec, this._target, this.doc, requisition);
+
+    let container = this.doc.getElementById("toolbox-buttons");
+    buttons.forEach(function(button) {
+      container.appendChild(button);
+    }.bind(this));
+  },
+
+  /**
+   * Build a tab for one tool definition and add to the toolbox
+   *
+   * @param {string} toolDefinition
+   *        Tool definition of the tool to build a tab for.
+   */
+  _buildTabForTool: function TBOX_buildTabForTool(toolDefinition) {
+    if (!toolDefinition.isTargetSupported(this._target)) {
+      return;
+    }
+
+    let tabs = this.doc.getElementById("toolbox-tabs");
+    let deck = this.doc.getElementById("toolbox-deck");
+
+    let id = toolDefinition.id;
+
+    let radio = this.doc.createElement("radio");
+    radio.className = "toolbox-tab devtools-tab";
+    radio.id = "toolbox-tab-" + id;
+    radio.setAttribute("flex", "1");
+    radio.setAttribute("toolid", id);
+    radio.setAttribute("tooltiptext", toolDefinition.tooltip);
+
+    radio.addEventListener("command", function(id) {
+      this.selectTool(id);
+    }.bind(this, id));
+
+    if (toolDefinition.icon) {
+      let image = this.doc.createElement("image");
+      image.setAttribute("src", toolDefinition.icon);
+      radio.appendChild(image);
+    }
+
+    let label = this.doc.createElement("label");
+    label.setAttribute("value", toolDefinition.label)
+    label.setAttribute("crop", "end");
+    label.setAttribute("flex", "1");
+
+    let vbox = this.doc.createElement("vbox");
+    vbox.className = "toolbox-panel";
+    vbox.id = "toolbox-panel-" + id;
+
+    radio.appendChild(label);
+    tabs.appendChild(radio);
+    deck.appendChild(vbox);
+
+    this._addKeysToWindow();
+  },
+
+  /**
+   * Switch to the tool with the given id
+   *
+   * @param {string} id
+   *        The id of the tool to switch to
+   */
+  selectTool: function TBOX_selectTool(id) {
+    let deferred = Promise.defer();
+
+    let selected = this.doc.querySelector(".devtools-tab[selected]");
+    if (selected) {
+      selected.removeAttribute("selected");
+    }
+    let tab = this.doc.getElementById("toolbox-tab-" + id);
+    tab.setAttribute("selected", "true");
+
+    if (this._currentToolId == id) {
+      // Return the existing panel in order to have a consistent return value.
+      return Promise.resolve(this._toolPanels.get(id));
+    }
+
+    if (!this.isReady) {
+      throw new Error("Can't select tool, wait for toolbox 'ready' event");
+    }
+    let tab = this.doc.getElementById("toolbox-tab-" + id);
+
+    if (!tab) {
+      throw new Error("No tool found");
+    }
+
+    let tabstrip = this.doc.getElementById("toolbox-tabs");
+
+    // select the right tab
+    let index = -1;
+    let tabs = tabstrip.childNodes;
+    for (let i = 0; i < tabs.length; i++) {
+      if (tabs[i] === tab) {
+        index = i;
+        break;
+      }
+    }
+    tabstrip.selectedIndex = index;
+
+    // and select the right iframe
+    let deck = this.doc.getElementById("toolbox-deck");
+    deck.selectedIndex = index;
+
+    let definition = gDevTools.getToolDefinitionMap().get(id);
+
+    this._currentToolId = id;
+
+    let iframe = this.doc.getElementById("toolbox-panel-iframe-" + id);
+    if (!iframe) {
+      iframe = this.doc.createElement("iframe");
+      iframe.className = "toolbox-panel-iframe";
+      iframe.id = "toolbox-panel-iframe-" + id;
+      iframe.setAttribute("flex", 1);
+      iframe.setAttribute("forceOwnRefreshDriver", "");
+      iframe.tooltip = "aHTMLTooltip";
+
+      let vbox = this.doc.getElementById("toolbox-panel-" + id);
+      vbox.appendChild(iframe);
+
+      let boundLoad = function() {
+        iframe.removeEventListener("DOMContentLoaded", boundLoad, true);
+
+        let built = definition.build(iframe.contentWindow, this);
+        Promise.resolve(built).then(function(panel) {
+          this._toolPanels.set(id, panel);
+
+          this.emit(id + "-ready", panel);
+          this.emit("select", id);
+          this.emit(id + "-selected", panel);
+          gDevTools.emit(id + "-ready", this, panel);
+
+          deferred.resolve(panel);
+        }.bind(this));
+      }.bind(this);
+
+      iframe.addEventListener("DOMContentLoaded", boundLoad, true);
+      iframe.setAttribute("src", definition.url);
+    } else {
+      let panel = this._toolPanels.get(id);
+      // only emit 'select' event if the iframe has been loaded
+      if (panel) {
+        this.emit("select", id);
+        this.emit(id + "-selected", panel);
+        deferred.resolve(panel);
+      }
+    }
+
+    Services.prefs.setCharPref(this._prefs.LAST_TOOL, id);
+
+    return deferred.promise;
+  },
+
+  /**
+   * Raise the toolbox host.
+   */
+  raise: function TBOX_raise() {
+    this._host.raise();
+  },
+
+  /**
+   * Refresh the host's title.
+   */
+  _refreshHostTitle: function TBOX_refreshHostTitle() {
+    let toolName;
+    let toolId = this.currentToolId;
+    if (toolId) {
+      let toolDef = gDevTools.getToolDefinitionMap().get(toolId);
+      toolName = toolDef.label;
+    } else {
+      // no tool is selected
+      toolName = toolboxStrings("toolbox.defaultTitle");
+    }
+    let title = toolboxStrings("toolbox.titleTemplate",
+                               toolName, this.target.url);
+    this._host.setTitle(title);
+  },
+
+  /**
+   * Create a host object based on the given host type.
+   *
+   * Warning: some hosts require that the toolbox target provides a reference to
+   * the attached tab. Not all Targets have a tab property - make sure you correctly
+   * mix and match hosts and targets.
+   *
+   * @param {string} hostType
+   *        The host type of the new host object
+   *
+   * @return {Host} host
+   *        The created host object
+   */
+  _createHost: function TBOX_createHost(hostType) {
+    if (!Hosts[hostType]) {
+      throw new Error('Unknown hostType: '+ hostType);
+    }
+    let newHost = new Hosts[hostType](this.target.tab);
+
+    // clean up the toolbox if its window is closed
+    newHost.on("window-closed", this.destroy);
+
+    return newHost;
+  },
+
+  /**
+   * Switch to a new host for the toolbox UI. E.g.
+   * bottom, sidebar, separate window.
+   *
+   * @param {string} hostType
+   *        The host type of the new host object
+   */
+  switchHost: function TBOX_switchHost(hostType) {
+    if (hostType == this._host.type) {
+      return;
+    }
+
+    if (!this._target.isLocalTab) {
+      return;
+    }
+
+    let newHost = this._createHost(hostType);
+    return newHost.create().then(function(iframe) {
+      // change toolbox document's parent to the new host
+      iframe.QueryInterface(Ci.nsIFrameLoaderOwner);
+      iframe.swapFrameLoaders(this.frame);
+
+      this._host.off("window-closed", this.destroy);
+      this._host.destroy();
+
+      this._host = newHost;
+
+      Services.prefs.setCharPref(this._prefs.LAST_HOST, this._host.type);
+
+      this._buildDockButtons();
+      this._addKeysToWindow();
+
+      this.emit("host-changed");
+    }.bind(this));
+  },
+
+  /**
+   * Handler for the tool-registered event.
+   * @param  {string} event
+   *         Name of the event ("tool-registered")
+   * @param  {string} toolId
+   *         Id of the tool that was registered
+   */
+  _toolRegistered: function TBOX_toolRegistered(event, toolId) {
+    let defs = gDevTools.getToolDefinitionMap();
+    let tool = defs.get(toolId);
+
+    this._buildTabForTool(tool);
+  },
+
+  /**
+   * Handler for the tool-unregistered event.
+   * @param  {string} event
+   *         Name of the event ("tool-unregistered")
+   * @param  {string} toolId
+   *         Id of the tool that was unregistered
+   */
+  _toolUnregistered: function TBOX_toolUnregistered(event, toolId) {
+    let radio = this.doc.getElementById("toolbox-tab-" + toolId);
+    let panel = this.doc.getElementById("toolbox-panel-" + toolId);
+
+    if (radio) {
+      if (this._currentToolId == toolId) {
+        let nextToolName = null;
+        if (radio.nextSibling) {
+          nextToolName = radio.nextSibling.getAttribute("toolid");
+        }
+        if (radio.previousSibling) {
+          nextToolName = radio.previousSibling.getAttribute("toolid");
+        }
+        if (nextToolName) {
+          this.selectTool(nextToolName);
+        }
+      }
+      radio.parentNode.removeChild(radio);
+    }
+
+    if (panel) {
+      panel.parentNode.removeChild(panel);
+    }
+
+    if (this.hostType == Toolbox.HostType.WINDOW) {
+      let doc = this.doc.defaultView.parent.document;
+      let key = doc.getElementById("key_" + id);
+      if (key) {
+        key.parentNode.removeChild(key);
+      }
+    }
+
+    if (this._toolPanels.has(toolId)) {
+      let instance = this._toolPanels.get(toolId);
+      instance.destroy();
+      this._toolPanels.delete(toolId);
+    }
+  },
+
+
+  /**
+   * Get the toolbox's notification box
+   *
+   * @return The notification box element.
+   */
+  getNotificationBox: function TBOX_getNotificationBox() {
+    return this.doc.getElementById("toolbox-notificationbox");
+  },
+
+  /**
+   * Remove all UI elements, detach from target and clear up
+   */
+  destroy: function TBOX_destroy() {
+    // If several things call destroy then we give them all the same
+    // destruction promise so we're sure to destroy only once
+    if (this._destroyer) {
+      return this._destroyer;
+    }
+    // Assign the "_destroyer" property before calling the other
+    // destroyer methods to guarantee that the Toolbox's destroy
+    // method is only executed once.
+    let deferred = Promise.defer();
+    this._destroyer = deferred.promise;
+
+    this._target.off("navigate", this._refreshHostTitle);
+    this.off("select", this._refreshHostTitle);
+    this.off("host-changed", this._refreshHostTitle);
+
+    gDevTools.off("tool-registered", this._toolRegistered);
+    gDevTools.off("tool-unregistered", this._toolUnregistered);
+
+    let outstanding = [];
+
+    for (let [id, panel] of this._toolPanels) {
+      outstanding.push(panel.destroy());
+    }
+
+    let container = this.doc.getElementById("toolbox-buttons");
+    while(container.firstChild) {
+      container.removeChild(container.firstChild);
+    }
+
+    outstanding.push(this._host.destroy());
+
+    // Targets need to be notified that the toolbox is being torn down, so that
+    // remote protocol connections can be gracefully terminated.
+    if (this._target) {
+      this._target.off("close", this.destroy);
+      outstanding.push(this._target.destroy());
+    }
+    this._target = null;
+
+    Promise.all(outstanding).then(function() {
+      this.emit("destroyed");
+      // Free _host after the call to destroyed in order to let a chance
+      // to destroyed listeners to still query toolbox attributes
+      this._host = null;
+      deferred.resolve();
+    }.bind(this));
+
+    return this._destroyer;
+  }
+};
new file mode 100644
--- /dev/null
+++ b/browser/devtools/framework/ToolboxHosts.jsm
@@ -0,0 +1,283 @@
+/* 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 Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
+Cu.import("resource:///modules/devtools/EventEmitter.jsm");
+
+this.EXPORTED_SYMBOLS = [ "Hosts" ];
+
+/**
+ * A toolbox host represents an object that contains a toolbox (e.g. the
+ * sidebar or a separate window). Any host object should implement the
+ * following functions:
+ *
+ * create() - create the UI and emit a 'ready' event when the UI is ready to use
+ * destroy() - destroy the host's UI
+ */
+
+this.Hosts = {
+  "bottom": BottomHost,
+  "side": SidebarHost,
+  "window": WindowHost
+}
+
+/**
+ * Host object for the dock on the bottom of the browser
+ */
+function BottomHost(hostTab) {
+  this.hostTab = hostTab;
+
+  EventEmitter.decorate(this);
+}
+
+BottomHost.prototype = {
+  type: "bottom",
+
+  heightPref: "devtools.toolbox.footer.height",
+
+  /**
+   * Create a box at the bottom of the host tab.
+   */
+  create: function BH_create() {
+    let deferred = Promise.defer();
+
+    let gBrowser = this.hostTab.ownerDocument.defaultView.gBrowser;
+    let ownerDocument = gBrowser.ownerDocument;
+
+    this._splitter = ownerDocument.createElement("splitter");
+    this._splitter.setAttribute("class", "devtools-horizontal-splitter");
+
+    this.frame = ownerDocument.createElement("iframe");
+    this.frame.className = "devtools-toolbox-bottom-iframe";
+    this.frame.height = Services.prefs.getIntPref(this.heightPref);
+
+    this._nbox = gBrowser.getNotificationBox(this.hostTab.linkedBrowser);
+    this._nbox.appendChild(this._splitter);
+    this._nbox.appendChild(this.frame);
+
+    let frameLoad = function() {
+      this.frame.removeEventListener("DOMContentLoaded", frameLoad, true);
+      this.emit("ready", this.frame);
+
+      deferred.resolve(this.frame);
+    }.bind(this);
+
+    this.frame.tooltip = "aHTMLTooltip";
+    this.frame.addEventListener("DOMContentLoaded", frameLoad, true);
+
+    // we have to load something so we can switch documents if we have to
+    this.frame.setAttribute("src", "about:blank");
+
+    focusTab(this.hostTab);
+
+    return deferred.promise;
+  },
+
+  /**
+   * Raise the host.
+   */
+  raise: function BH_raise() {
+    focusTab(this.hostTab);
+  },
+
+  /**
+   * Set the toolbox title.
+   */
+  setTitle: function BH_setTitle(title) {
+    // Nothing to do for this host type.
+  },
+
+  /**
+   * Destroy the bottom dock.
+   */
+  destroy: function BH_destroy() {
+    if (!this._destroyed) {
+      this._destroyed = true;
+
+      Services.prefs.setIntPref(this.heightPref, this.frame.height);
+      this._nbox.removeChild(this._splitter);
+      this._nbox.removeChild(this.frame);
+    }
+
+    return Promise.resolve(null);
+  }
+}
+
+
+/**
+ * Host object for the in-browser sidebar
+ */
+function SidebarHost(hostTab) {
+  this.hostTab = hostTab;
+
+  EventEmitter.decorate(this);
+}
+
+SidebarHost.prototype = {
+  type: "side",
+
+  widthPref: "devtools.toolbox.sidebar.width",
+
+  /**
+   * Create a box in the sidebar of the host tab.
+   */
+  create: function SH_create() {
+    let deferred = Promise.defer();
+
+    let gBrowser = this.hostTab.ownerDocument.defaultView.gBrowser;
+    let ownerDocument = gBrowser.ownerDocument;
+
+    this._splitter = ownerDocument.createElement("splitter");
+    this._splitter.setAttribute("class", "devtools-side-splitter");
+
+    this.frame = ownerDocument.createElement("iframe");
+    this.frame.className = "devtools-toolbox-side-iframe";
+    this.frame.width = Services.prefs.getIntPref(this.widthPref);
+
+    this._sidebar = gBrowser.getSidebarContainer(this.hostTab.linkedBrowser);
+    this._sidebar.appendChild(this._splitter);
+    this._sidebar.appendChild(this.frame);
+
+    let frameLoad = function() {
+      this.frame.removeEventListener("DOMContentLoaded", frameLoad, true);
+      this.emit("ready", this.frame);
+
+      deferred.resolve(this.frame);
+    }.bind(this);
+
+    this.frame.addEventListener("DOMContentLoaded", frameLoad, true);
+    this.frame.tooltip = "aHTMLTooltip";
+    this.frame.setAttribute("src", "about:blank");
+
+    focusTab(this.hostTab);
+
+    return deferred.promise;
+  },
+
+  /**
+   * Raise the host.
+   */
+  raise: function SH_raise() {
+    focusTab(this.hostTab);
+  },
+
+  /**
+   * Set the toolbox title.
+   */
+  setTitle: function SH_setTitle(title) {
+    // Nothing to do for this host type.
+  },
+
+  /**
+   * Destroy the sidebar.
+   */
+  destroy: function SH_destroy() {
+    if (!this._destroyed) {
+      this._destroyed = true;
+
+      Services.prefs.setIntPref(this.widthPref, this.frame.width);
+      this._sidebar.removeChild(this._splitter);
+      this._sidebar.removeChild(this.frame);
+    }
+
+    return Promise.resolve(null);
+  }
+}
+
+/**
+ * Host object for the toolbox in a separate window
+ */
+function WindowHost() {
+  this._boundUnload = this._boundUnload.bind(this);
+
+  EventEmitter.decorate(this);
+}
+
+WindowHost.prototype = {
+  type: "window",
+
+  WINDOW_URL: "chrome://browser/content/devtools/framework/toolbox-window.xul",
+
+  /**
+   * Create a new xul window to contain the toolbox.
+   */
+  create: function WH_create() {
+    let deferred = Promise.defer();
+
+    let flags = "chrome,centerscreen,resizable,dialog=no";
+    let win = Services.ww.openWindow(null, this.WINDOW_URL, "_blank",
+                                     flags, null);
+
+    let frameLoad = function(event) {
+      win.removeEventListener("load", frameLoad, true);
+      this.frame = win.document.getElementById("toolbox-iframe");
+      this.emit("ready", this.frame);
+
+      deferred.resolve(this.frame);
+    }.bind(this);
+
+    win.addEventListener("load", frameLoad, true);
+    win.addEventListener("unload", this._boundUnload);
+
+    win.focus();
+
+    this._window = win;
+
+    return deferred.promise;
+  },
+
+  /**
+   * Catch the user closing the window.
+   */
+  _boundUnload: function(event) {
+    if (event.target.location != this.WINDOW_URL) {
+      return;
+    }
+    this._window.removeEventListener("unload", this._boundUnload);
+
+    this.emit("window-closed");
+  },
+
+  /**
+   * Raise the host.
+   */
+  raise: function RH_raise() {
+    this._window.focus();
+  },
+
+  /**
+   * Set the toolbox title.
+   */
+  setTitle: function WH_setTitle(title) {
+    this._window.document.title = title;
+  },
+
+  /**
+   * Destroy the window.
+   */
+  destroy: function WH_destroy() {
+    if (!this._destroyed) {
+      this._destroyed = true;
+
+      this._window.removeEventListener("unload", this._boundUnload);
+      this._window.close();
+    }
+
+    return Promise.resolve(null);
+  }
+}
+
+/**
+ *  Switch to the given tab in a browser and focus the browser window
+ */
+function focusTab(tab) {
+  let browserWindow = tab.ownerDocument.defaultView;
+  browserWindow.focus();
+  browserWindow.gBrowser.selectedTab = tab;
+}
--- a/browser/devtools/framework/gDevTools.jsm
+++ b/browser/devtools/framework/gDevTools.jsm
@@ -1,200 +1,27 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-this.EXPORTED_SYMBOLS = [ "gDevTools", "DevTools", "gDevToolsBrowser", "devtools" ];
+this.EXPORTED_SYMBOLS = [ "gDevTools", "DevTools", "gDevToolsBrowser" ];
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource:///modules/devtools/shared/event-emitter.js");
-Cu.import("resource://gre/modules/FileUtils.jsm");
-Cu.import("resource://gre/modules/NetUtil.jsm");
 Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
-
-XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
-
-let loader = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {}).Loader;
-
-// Used when the tools should be loaded from the Firefox package itself (the default)
-
-var BuiltinProvider = {
-  load: function(done) {
-    this.loader = new loader.Loader({
-      paths: {
-        "": "resource://gre/modules/commonjs/",
-        "main" : "resource:///modules/devtools/main",
-        "devtools": "resource:///modules/devtools",
-        "devtools/toolkit": "resource://gre/modules/devtools"
-      },
-      globals: {},
-    });
-    this.main = loader.main(this.loader, "main");
-
-    return Promise.resolve(undefined);
-  },
-
-  unload: function(reason) {
-    loader.unload(this.loader, reason);
-    delete this.loader;
-  },
-};
-
-var SrcdirProvider = {
-  load: function(done) {
-    let srcdir = Services.prefs.getComplexValue("devtools.loader.srcdir",
-                                                Ci.nsISupportsString);
-    srcdir = OS.Path.normalize(srcdir.data.trim());
-    let devtoolsDir = OS.Path.join(srcdir, "browser/devtools");
-    let toolkitDir = OS.Path.join(srcdir, "toolkit/devtools");
-
-    this.loader = new loader.Loader({
-      paths: {
-        "": "resource://gre/modules/commonjs/",
-        "devtools/toolkit": "file://" + toolkitDir,
-        "devtools": "file://" + devtoolsDir,
-        "main": "file://" + devtoolsDir + "/main.js"
-      },
-      globals: {}
-    });
-
-    this.main = loader.main(this.loader, "main");
-
-    return this._writeManifest(devtoolsDir).then((data) => {
-      this._writeManifest(toolkitDir);
-    }).then(null, Cu.reportError);
-  },
-
-  unload: function(reason) {
-    loader.unload(this.loader, reason);
-    delete this.loader;
-  },
-
-  _readFile: function(filename) {
-    let deferred = Promise.defer();
-    let file = new FileUtils.File(filename);
-    NetUtil.asyncFetch(file, (inputStream, status) => {
-      if (!Components.isSuccessCode(status)) {
-        deferred.reject(new Error("Couldn't load manifest: " + filename + "\n"));
-        return;
-      }
-      var data = NetUtil.readInputStreamToString(inputStream, inputStream.available());
-      deferred.resolve(data);
-    });
-    return deferred.promise;
-  },
-
-  _writeFile: function(filename, data) {
-    let deferred = Promise.defer();
-    let file = new FileUtils.File(filename);
-
-    var ostream = FileUtils.openSafeFileOutputStream(file)
-
-    var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
-                    createInstance(Ci.nsIScriptableUnicodeConverter);
-    converter.charset = "UTF-8";
-    var istream = converter.convertToInputStream(data);
-    NetUtil.asyncCopy(istream, ostream, (status) => {
-      if (!Components.isSuccessCode(status)) {
-        deferred.reject(new Error("Couldn't write manifest: " + filename + "\n"));
-        return;
-      }
-
-      deferred.resolve(null);
-    });
-    return deferred.promise;
-  },
-
-  _writeManifest: function(dir) {
-    return this._readFile(dir + "/jar.mn").then((data) => {
-      // The file data is contained within inputStream.
-      // You can read it into a string with
-      let entries = [];
-      let lines = data.split(/\n/);
-      let preprocessed = /^\s*\*/;
-      let contentEntry = new RegExp("^\\s+content/(\\w+)/(\\S+)\\s+\\((\\S+)\\)");
-      for (let line of lines) {
-        if (preprocessed.test(line)) {
-          dump("Unable to override preprocessed file: " + line + "\n");
-          continue;
-        }
-        let match = contentEntry.exec(line);
-        if (match) {
-          let entry = "override chrome://" + match[1] + "/content/" + match[2] + "\tfile://" + dir + "/" + match[3];
-          entries.push(entry);
-        }
-      }
-      return this._writeFile(dir + "/chrome.manifest", entries.join("\n"));
-    }).then(() => {
-      Components.manager.addBootstrappedManifestLocation(new FileUtils.File(dir));
-    });
-  }
-};
-
-this.devtools = {
-  _provider: null,
-
-  get main() this._provider.main,
-
-  // This is a gross gross hack.  In one place (computed-view.js) we use
-  // Iterator, but the addon-sdk loader takes Iterator off the global.
-  // Give computed-view.js a way to get back to the Iterator until we have
-  // a chance to fix that crap.
-  _Iterator: Iterator,
-
-  setProvider: function(provider) {
-    if (provider === this._provider) {
-      return;
-    }
-
-    if (this._provider) {
-      delete this.require;
-      this._provider.unload("newprovider");
-      gDevTools._teardown();
-    }
-    this._provider = provider;
-    this._provider.load();
-    this.require = loader.Require(this._provider.loader, { id: "devtools" })
-
-    let exports = this._provider.main;
-    // Let clients find exports on this object.
-    for (let key of Object.getOwnPropertyNames(exports)) {
-      this[key] = exports[key];
-    }
-  },
-
-  /**
-   * Choose a default tools provider based on the preferences.
-   */
-  _chooseProvider: function() {
-    if (Services.prefs.prefHasUserValue("devtools.loader.srcdir")) {
-      this.setProvider(SrcdirProvider);
-    } else {
-      this.setProvider(BuiltinProvider);
-    }
-  },
-
-  /**
-   * Reload the current provider.
-   */
-  reload: function() {
-    var events = devtools.require("sdk/system/events");
-    events.emit("startupcache-invalidate", {});
-
-    this._provider.unload("reload");
-    delete this._provider;
-    gDevTools._teardown();
-    this._chooseProvider();
-  },
-};
+Cu.import("resource:///modules/devtools/EventEmitter.jsm");
+Cu.import("resource:///modules/devtools/ToolDefinitions.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Toolbox",
+  "resource:///modules/devtools/Toolbox.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TargetFactory",
+  "resource:///modules/devtools/Target.jsm");
 
 const FORBIDDEN_IDS = new Set(["toolbox", ""]);
 
 /**
  * DevTools is a class that represents a set of developer tools, it holds a
  * set of tools and keeps track of open toolboxes in the browser.
  */
 this.DevTools = function DevTools() {
@@ -202,16 +29,21 @@ this.DevTools = function DevTools() {
   this._toolboxes = new Map(); // Map<target, toolbox>
 
   // destroy() is an observer's handler so we need to preserve context.
   this.destroy = this.destroy.bind(this);
 
   EventEmitter.decorate(this);
 
   Services.obs.addObserver(this.destroy, "quit-application", false);
+
+  // Register the set of default tools
+  for (let definition of defaultTools) {
+    this.registerTool(definition);
+  }
 }
 
 DevTools.prototype = {
   /**
    * Register a new developer tool.
    *
    * A definition is a light object that holds different information about a
    * developer tool. This object is not supposed to have any operational code.
@@ -352,17 +184,17 @@ DevTools.prototype = {
 
       return promise.then(function() {
         toolbox.raise();
         return toolbox;
       });
     }
     else {
       // No toolbox for target, create one
-      toolbox = new devtools.Toolbox(target, toolId, hostType);
+      toolbox = new Toolbox(target, toolId, hostType);
 
       this._toolboxes.set(target, toolbox);
 
       toolbox.once("destroyed", function() {
         this._toolboxes.delete(target);
         this.emit("toolbox-destroyed", target);
       }.bind(this));
 
@@ -406,25 +238,16 @@ DevTools.prototype = {
     let toolbox = this._toolboxes.get(target);
     if (toolbox == null) {
       return;
     }
     return toolbox.destroy();
   },
 
   /**
-   * Called to tear down a tools provider.
-   */
-  _teardown: function DT_teardown() {
-    for (let [target, toolbox] of this._toolboxes) {
-      toolbox.destroy();
-    }
-  },
-
-  /**
    * All browser windows have been closed, tidy up remaining objects.
    */
   destroy: function() {
     Services.obs.removeObserver(this.destroy, "quit-application");
 
     for (let [key, tool] of this._tools) {
       this.unregisterTool(key, true);
     }
@@ -456,50 +279,42 @@ let gDevToolsBrowser = {
   _trackedBrowserWindows: new Set(),
 
   /**
    * This function is for the benefit of Tools:DevToolbox in
    * browser/base/content/browser-sets.inc and should not be used outside
    * of there
    */
   toggleToolboxCommand: function(gBrowser) {
-    let target = devtools.TargetFactory.forTab(gBrowser.selectedTab);
+    let target = TargetFactory.forTab(gBrowser.selectedTab);
     let toolbox = gDevTools.getToolbox(target);
 
     toolbox ? toolbox.destroy() : gDevTools.showToolbox(target);
   },
 
-  toggleBrowserToolboxCommand: function(gBrowser) {
-    let target = devtools.TargetFactory.forWindow(gBrowser.ownerDocument.defaultView);
-    let toolbox = gDevTools.getToolbox(target);
-
-    toolbox ? toolbox.destroy()
-     : gDevTools.showToolbox(target, "inspector", Toolbox.HostType.WINDOW);
-  },
-
   /**
    * This function is for the benefit of Tools:{toolId} commands,
    * triggered from the WebDeveloper menu and keyboard shortcuts.
    *
    * selectToolCommand's behavior:
    * - if the toolbox is closed,
    *   we open the toolbox and select the tool
    * - if the toolbox is open, and the targetted tool is not selected,
    *   we select it
    * - if the toolbox is open, and the targetted tool is selected,
    *   and the host is NOT a window, we close the toolbox
    * - if the toolbox is open, and the targetted tool is selected,
    *   and the host is a window, we raise the toolbox window
    */
   selectToolCommand: function(gBrowser, toolId) {
-    let target = devtools.TargetFactory.forTab(gBrowser.selectedTab);
+    let target = TargetFactory.forTab(gBrowser.selectedTab);
     let toolbox = gDevTools.getToolbox(target);
 
     if (toolbox && toolbox.currentToolId == toolId) {
-      if (toolbox.hostType == devtools.Toolbox.HostType.WINDOW) {
+      if (toolbox.hostType == Toolbox.HostType.WINDOW) {
         toolbox.raise();
       } else {
         toolbox.destroy();
       }
     } else {
       gDevTools.showToolbox(target, toolId);
     }
   },
@@ -712,18 +527,18 @@ let gDevToolsBrowser = {
   /**
    * Update the "Toggle Tools" checkbox in the developer tools menu. This is
    * called when a toolbox is created or destroyed.
    */
   _updateMenuCheckbox: function DT_updateMenuCheckbox() {
     for (let win of gDevToolsBrowser._trackedBrowserWindows) {
 
       let hasToolbox = false;
-      if (devtools.TargetFactory.isKnownTab(win.gBrowser.selectedTab)) {
-        let target = devtools.TargetFactory.forTab(win.gBrowser.selectedTab);
+      if (TargetFactory.isKnownTab(win.gBrowser.selectedTab)) {
+        let target = TargetFactory.forTab(win.gBrowser.selectedTab);
         if (gDevTools._toolboxes.has(target)) {
           hasToolbox = true;
         }
       }
 
       let broadcaster = win.document.getElementById("devtoolsMenuBroadcaster_DevToolbox");
       if (hasToolbox) {
         broadcaster.setAttribute("checked", "true");
@@ -819,12 +634,8 @@ gDevTools.on("tool-registered", function
 gDevTools.on("tool-unregistered", function(ev, toolId) {
   gDevToolsBrowser._removeToolFromWindows(toolId);
 });
 
 gDevTools.on("toolbox-ready", gDevToolsBrowser._updateMenuCheckbox);
 gDevTools.on("toolbox-destroyed", gDevToolsBrowser._updateMenuCheckbox);
 
 Services.obs.addObserver(gDevToolsBrowser.destroy, "quit-application", false);
-
-// Now, load the tools.
-devtools._chooseProvider();
-
deleted file mode 100644
--- a/browser/devtools/framework/sidebar.js
+++ /dev/null
@@ -1,211 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; 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/. */
-
-var Promise = require("sdk/core/promise");
-var EventEmitter = require("devtools/shared/event-emitter");
-
-const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-
-/**
- * ToolSidebar provides methods to register tabs in the sidebar.
- * It's assumed that the sidebar contains a xul:tabbox.
- *
- * @param {Node} tabbox
- *  <tabbox> node;
- * @param {ToolPanel} panel
- *  Related ToolPanel instance;
- * @param {Boolean} showTabstripe
- *  Show the tabs.
- */
-function ToolSidebar(tabbox, panel, showTabstripe=true)
-{
-  EventEmitter.decorate(this);
-
-  this._tabbox = tabbox;
-  this._panelDoc = this._tabbox.ownerDocument;
-  this._toolPanel = panel;
-
-  this._tabbox.tabpanels.addEventListener("select", this, true);
-
-  this._tabs = new Map();
-
-  if (!showTabstripe) {
-    this._tabbox.setAttribute("hidetabs", "true");
-  }
-}
-
-exports.ToolSidebar = ToolSidebar;
-
-ToolSidebar.prototype = {
-  /**
-   * Register a tab. A tab is a document.
-   * The document must have a title, which will be used as the name of the tab.
-   *
-   * @param {string} tab uniq id
-   * @param {string} url
-   */
-  addTab: function ToolSidebar_addTab(id, url, selected=false) {
-    let iframe = this._panelDoc.createElementNS(XULNS, "iframe");
-    iframe.className = "iframe-" + id;
-    iframe.setAttribute("flex", "1");
-    iframe.setAttribute("src", url);
-    iframe.tooltip = "aHTMLTooltip";
-
-    let tab = this._tabbox.tabs.appendItem();
-    tab.setAttribute("label", ""); // Avoid showing "undefined" while the tab is loading
-
-    let onIFrameLoaded = function() {
-      tab.setAttribute("label", iframe.contentDocument.title);
-      iframe.removeEventListener("load", onIFrameLoaded, true);
-      if ("setPanel" in iframe.contentWindow) {
-        iframe.contentWindow.setPanel(this._toolPanel, iframe);
-      }
-      this.emit(id + "-ready");
-    }.bind(this);
-
-    iframe.addEventListener("load", onIFrameLoaded, true);
-
-    let tabpanel = this._panelDoc.createElementNS(XULNS, "tabpanel");
-    tabpanel.setAttribute("id", "sidebar-panel-" + id);
-    tabpanel.appendChild(iframe);
-    this._tabbox.tabpanels.appendChild(tabpanel);
-
-    this._tooltip = this._panelDoc.createElementNS(XULNS, "tooltip");
-    this._tooltip.id = "aHTMLTooltip";
-    tabpanel.appendChild(this._tooltip);
-    this._tooltip.page = true;
-
-    tab.linkedPanel = "sidebar-panel-" + id;
-
-    // We store the index of this tab.
-    this._tabs.set(id, tab);
-
-    if (selected) {
-      // For some reason I don't understand, if we call this.select in this
-      // event loop (after inserting the tab), the tab will never get the
-      // the "selected" attribute set to true.
-      this._panelDoc.defaultView.setTimeout(function() {
-        this.select(id);
-      }.bind(this), 10);
-    }
-
-    this.emit("new-tab-registered", id);
-  },
-
-  /**
-   * Select a specific tab.
-   */
-  select: function ToolSidebar_select(id) {
-    let tab = this._tabs.get(id);
-    if (tab) {
-      this._tabbox.selectedTab = tab;
-    }
-  },
-
-  /**
-   * Return the id of the selected tab.
-   */
-  getCurrentTabID: function ToolSidebar_getCurrentTabID() {
-    let currentID = null;
-    for (let [id, tab] of this._tabs) {
-      if (this._tabbox.tabs.selectedItem == tab) {
-        currentID = id;
-        break;
-      }
-    }
-    return currentID;
-  },
-
-  /**
-   * Returns the requested tab based on the id.
-   *
-   * @param String id
-   *        unique id of the requested tab.
-   */
-  getTab: function ToolSidebar_getTab(id) {
-    return this._tabbox.tabpanels.querySelector("#sidebar-panel-" + id);
-  },
-
-  /**
-   * Event handler.
-   */
-  handleEvent: function ToolSidebar_eventHandler(event) {
-    if (event.type == "select") {
-      let previousTool = this._currentTool;
-      this._currentTool = this.getCurrentTabID();
-      if (previousTool) {
-        this.emit(previousTool + "-unselected");
-      }
-
-      this.emit(this._currentTool + "-selected");
-      this.emit("select", this._currentTool);
-    }
-  },
-
-  /**
-   * Toggle sidebar's visibility state.
-   */
-  toggle: function ToolSidebar_toggle() {
-    if (this._tabbox.hasAttribute("hidden")) {
-      this.show();
-    } else {
-      this.hide();
-    }
-  },
-
-  /**
-   * Show the sidebar.
-   */
-  show: function ToolSidebar_show() {
-    this._tabbox.removeAttribute("hidden");
-  },
-
-  /**
-   * Show the sidebar.
-   */
-  hide: function ToolSidebar_hide() {
-    this._tabbox.setAttribute("hidden", "true");
-  },
-
-  /**
-   * Return the window containing the tab content.
-   */
-  getWindowForTab: function ToolSidebar_getWindowForTab(id) {
-    if (!this._tabs.has(id)) {
-      return null;
-    }
-
-    let panel = this._panelDoc.getElementById(this._tabs.get(id).linkedPanel);
-    return panel.firstChild.contentWindow;
-  },
-
-  /**
-   * Clean-up.
-   */
-  destroy: function ToolSidebar_destroy() {
-    if (this._destroyed) {
-      return Promise.resolve(null);
-    }
-    this._destroyed = true;
-
-    this._tabbox.tabpanels.removeEventListener("select", this, true);
-
-    while (this._tabbox.tabpanels.hasChildNodes()) {
-      this._tabbox.tabpanels.removeChild(this._tabbox.tabpanels.firstChild);
-    }
-
-    while (this._tabbox.tabs.hasChildNodes()) {
-      this._tabbox.tabs.removeChild(this._tabbox.tabs.firstChild);
-    }
-
-    this._tabs = null;
-    this._tabbox = null;
-    this._panelDoc = null;
-    this._toolPanel = null;
-
-    return Promise.resolve(null);
-  },
-}
deleted file mode 100644
--- a/browser/devtools/framework/target.js
+++ /dev/null
@@ -1,584 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const {Cc, Ci, Cu} = require("chrome");
-
-var Promise = require("sdk/core/promise");
-var EventEmitter = require("devtools/shared/event-emitter");
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
-  "resource://gre/modules/devtools/dbg-server.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "DebuggerClient",
-  "resource://gre/modules/devtools/dbg-client.jsm");
-
-const targets = new WeakMap();
-const promiseTargets = new WeakMap();
-
-/**
- * Functions for creating Targets
- */
-exports.TargetFactory = {
-  /**
-   * Construct a Target
-   * @param {XULTab} tab
-   *        The tab to use in creating a new target.
-   *
-   * @return A target object
-   */
-  forTab: function TF_forTab(tab) {
-    let target = targets.get(tab);
-    if (target == null) {
-      target = new TabTarget(tab);
-      targets.set(tab, target);
-    }
-    return target;
-  },
-
-  /**
-   * Return a promise of a Target for a remote tab.
-   * @param {Object} options
-   *        The options object has the following properties:
-   *        {
-   *          form: the remote protocol form of a tab,
-   *          client: a DebuggerClient instance,
-   *          chrome: true if the remote target is the whole process
-   *        }
-   *
-   * @return A promise of a target object
-   */
-  forRemoteTab: function TF_forRemoteTab(options) {
-    let promise = promiseTargets.get(options);
-    if (promise == null) {
-      let target = new TabTarget(options);
-      promise = target.makeRemote().then(() => target);
-      promiseTargets.set(options, promise);
-    }
-    return promise;
-  },
-
-  /**
-   * Creating a target for a tab that is being closed is a problem because it
-   * allows a leak as a result of coming after the close event which normally
-   * clears things up. This function allows us to ask if there is a known
-   * target for a tab without creating a target
-   * @return true/false
-   */
-  isKnownTab: function TF_isKnownTab(tab) {
-    return targets.has(tab);
-  },
-
-  /**
-   * Construct a Target
-   * @param {nsIDOMWindow} window
-   *        The chromeWindow to use in creating a new target
-   * @return A target object
-   */
-  forWindow: function TF_forWindow(window) {
-    let target = targets.get(window);
-    if (target == null) {
-      target = new WindowTarget(window);
-      targets.set(window, target);
-    }
-    return target;
-  },
-
-  /**
-   * Get all of the targets known to the local browser instance
-   * @return An array of target objects
-   */
-  allTargets: function TF_allTargets() {
-    let windows = [];
-    let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
-                       .getService(Ci.nsIWindowMediator);
-    let en = wm.getXULWindowEnumerator(null);
-    while (en.hasMoreElements()) {
-      windows.push(en.getNext());
-    }
-
-    return windows.map(function(window) {
-      return TargetFactory.forWindow(window);
-    });
-  },
-};
-
-/**
- * The 'version' property allows the developer tools equivalent of browser
- * detection. Browser detection is evil, however while we don't know what we
- * will need to detect in the future, it is an easy way to postpone work.
- * We should be looking to use 'supports()' in place of version where
- * possible.
- */
-function getVersion() {
-  // FIXME: return something better
-  return 20;
-}
-
-/**
- * A better way to support feature detection, but we're not yet at a place
- * where we have the features well enough defined for this to make lots of
- * sense.
- */
-function supports(feature) {
-  // FIXME: return something better
-  return false;
-};
-
-/**
- * A Target represents something that we can debug. Targets are generally
- * read-only. Any changes that you wish to make to a target should be done via
- * a Tool that attaches to the target. i.e. a Target is just a pointer saying
- * "the thing to debug is over there".
- *
- * Providing a generalized abstraction of a web-page or web-browser (available
- * either locally or remotely) is beyond the scope of this class (and maybe
- * also beyond the scope of this universe) However Target does attempt to
- * abstract some common events and read-only properties common to many Tools.
- *
- * Supported read-only properties:
- * - name, isRemote, url
- *
- * Target extends EventEmitter and provides support for the following events:
- * - close: The target window has been closed. All tools attached to this
- *     target should close. This event is not currently cancelable.
- * - navigate: The target window has navigated to a different URL
- *
- * Optional events:
- * - will-navigate: The target window will navigate to a different URL
- * - hidden: The target is not visible anymore (for TargetTab, another tab is selected)
- * - visible: The target is visible (for TargetTab, tab is selected)
- *
- * Target also supports 2 functions to help allow 2 different versions of
- * Firefox debug each other. The 'version' property is the equivalent of
- * browser detection - simple and easy to implement but gets fragile when things
- * are not quite what they seem. The 'supports' property is the equivalent of
- * feature detection - harder to setup, but more robust long-term.
- *
- * Comparing Targets: 2 instances of a Target object can point at the same
- * thing, so t1 !== t2 and t1 != t2 even when they represent the same object.
- * To compare to targets use 't1.equals(t2)'.
- */
-function Target() {
-  throw new Error("Use TargetFactory.newXXX or Target.getXXX to create a Target in place of 'new Target()'");
-}
-
-Object.defineProperty(Target.prototype, "version", {
-  get: getVersion,
-  enumerable: true
-});
-
-
-/**
- * A TabTarget represents a page living in a browser tab. Generally these will
- * be web pages served over http(s), but they don't have to be.
- */
-function TabTarget(tab) {
-  EventEmitter.decorate(this);
-  this.destroy = this.destroy.bind(this);
-  this._handleThreadState = this._handleThreadState.bind(this);
-  this.on("thread-resumed", this._handleThreadState);
-  this.on("thread-paused", this._handleThreadState);
-  // Only real tabs need initialization here. Placeholder objects for remote
-  // targets will be initialized after a makeRemote method call.
-  if (tab && !["client", "form", "chrome"].every(tab.hasOwnProperty, tab)) {
-    this._tab = tab;
-    this._setupListeners();
-  } else {
-    this._form = tab.form;
-    this._client = tab.client;
-    this._chrome = tab.chrome;
-  }
-}
-
-TabTarget.prototype = {
-  _webProgressListener: null,
-
-  supports: supports,
-  get version() { return getVersion(); },
-
-  get tab() {
-    return this._tab;
-  },
-
-  get form() {
-    return this._form;
-  },
-
-  get client() {
-    return this._client;
-  },
-
-  get chrome() {
-    return this._chrome;
-  },
-
-  get window() {
-    // Be extra careful here, since this may be called by HS_getHudByWindow
-    // during shutdown.
-    if (this._tab && this._tab.linkedBrowser) {
-      return this._tab.linkedBrowser.contentWindow;
-    }
-  },
-
-  get name() {
-    return this._tab ? this._tab.linkedBrowser.contentDocument.title :
-                       this._form.title;
-  },
-
-  get url() {
-    return this._tab ? this._tab.linkedBrowser.contentDocument.location.href :
-                       this._form.url;
-  },
-
-  get isRemote() {
-    return !this.isLocalTab;
-  },
-
-  get isLocalTab() {
-    return !!this._tab;
-  },
-
-  get isThreadPaused() {
-    return !!this._isThreadPaused;
-  },
-
-  /**
-   * Adds remote protocol capabilities to the target, so that it can be used
-   * for tools that support the Remote Debugging Protocol even for local
-   * connections.
-   */
-  makeRemote: function TabTarget_makeRemote() {
-    if (this._remote) {
-      return this._remote.promise;
-    }
-
-    this._remote = Promise.defer();
-
-    if (this.isLocalTab) {
-      // Since a remote protocol connection will be made, let's start the
-      // DebuggerServer here, once and for all tools.
-      if (!DebuggerServer.initialized) {
-        DebuggerServer.init();
-        DebuggerServer.addBrowserActors();
-      }
-
-      this._client = new DebuggerClient(DebuggerServer.connectPipe());
-      // A local TabTarget will never perform chrome debugging.
-      this._chrome = false;
-    }
-
-    this._setupRemoteListeners();
-
-    if (this.isRemote) {
-      // In the remote debugging case, the protocol connection will have been
-      // already initialized in the connection screen code.
-      this._remote.resolve(null);
-    } else {
-      this._client.connect((aType, aTraits) => {
-        this._client.listTabs(aResponse => {
-          this._form = aResponse.tabs[aResponse.selected];
-
-          this._client.attachTab(this._form.actor, (aResponse, aTabClient) => {
-            if (!aTabClient) {
-              this._remote.reject("Unable to attach to the tab");
-              return;
-            }
-            this.threadActor = aResponse.threadActor;
-            this._remote.resolve(null);
-          });
-        });
-      });
-    }
-
-    return this._remote.promise;
-  },
-
-  /**
-   * Listen to the different events.
-   */
-  _setupListeners: function TabTarget__setupListeners() {
-    this._webProgressListener = new TabWebProgressListener(this);
-    this.tab.linkedBrowser.addProgressListener(this._webProgressListener);
-    this.tab.addEventListener("TabClose", this);
-    this.tab.parentNode.addEventListener("TabSelect", this);
-    this.tab.ownerDocument.defaultView.addEventListener("unload", this);
-  },
-
-  /**
-   * Setup listeners for remote debugging, updating existing ones as necessary.
-   */
-  _setupRemoteListeners: function TabTarget__setupRemoteListeners() {
-    this.client.addListener("tabDetached", this.destroy);
-
-    this._onTabNavigated = function onRemoteTabNavigated(aType, aPacket) {
-      let event = Object.create(null);
-      event.url = aPacket.url;
-      event.title = aPacket.title;
-      // Send any stored event payload (DOMWindow or nsIRequest) for backwards
-      // compatibility with non-remotable tools.
-      event._navPayload = this._navPayload;
-      if (aPacket.state == "start") {
-        this.emit("will-navigate", event);
-      } else {
-        this.emit("navigate", event);
-      }
-      this._navPayload = null;
-    }.bind(this);
-    this.client.addListener("tabNavigated", this._onTabNavigated);
-  },
-
-  /**
-   * Handle tabs events.
-   */
-  handleEvent: function (event) {
-    switch (event.type) {
-      case "TabClose":
-      case "unload":
-        this.destroy();
-        break;
-      case "TabSelect":
-        if (this.tab.selected) {
-          this.emit("visible", event);
-        } else {
-          this.emit("hidden", event);
-        }
-        break;
-    }
-  },
-
-  /**
-   * Handle script status.
-   */
-  _handleThreadState: function(event) {
-    switch (event) {
-      case "thread-resumed":
-        this._isThreadPaused = false;
-        break;
-      case "thread-paused":
-        this._isThreadPaused = true;
-        break;
-    }
-  },
-
-  /**
-   * Target is not alive anymore.
-   */
-  destroy: function() {
-    // If several things call destroy then we give them all the same
-    // destruction promise so we're sure to destroy only once
-    if (this._destroyer) {
-      return this._destroyer.promise;
-    }
-
-    this._destroyer = Promise.defer();
-
-    // Before taking any action, notify listeners that destruction is imminent.
-    this.emit("close");
-
-    // First of all, do cleanup tasks that pertain to both remoted and
-    // non-remoted targets.
-    this.off("thread-resumed", this._handleThreadState);
-    this.off("thread-paused", this._handleThreadState);
-
-    if (this._tab) {
-      if (this._webProgressListener) {
-        this._webProgressListener.destroy();
-      }
-
-      this._tab.ownerDocument.defaultView.removeEventListener("unload", this);
-      this._tab.removeEventListener("TabClose", this);
-      this._tab.parentNode.removeEventListener("TabSelect", this);
-    }
-
-    // If this target was not remoted, the promise will be resolved before the
-    // function returns.
-    if (this._tab && !this._client) {
-      targets.delete(this._tab);
-      this._tab = null;
-      this._client = null;
-      this._form = null;
-      this._remote = null;
-
-      this._destroyer.resolve(null);
-    } else if (this._client) {
-      // If, on the other hand, this target was remoted, the promise will be
-      // resolved after the remote connection is closed.
-      this.client.removeListener("tabNavigated", this._onTabNavigated);
-      this.client.removeListener("tabDetached", this.destroy);
-
-      this._client.close(function onClosed() {
-        if (this._tab) {
-          targets.delete(this._tab);
-        } else {
-          promiseTargets.delete(this._form);
-        }
-        this._client = null;
-        this._tab = null;
-        this._form = null;
-        this._remote = null;
-
-        this._destroyer.resolve(null);
-      }.bind(this));
-    }
-
-    return this._destroyer.promise;
-  },
-
-  toString: function() {
-    return 'TabTarget:' + (this._tab ? this._tab : (this._form && this._form.actor));
-  },
-};
-
-
-/**
- * WebProgressListener for TabTarget.
- *
- * @param object aTarget
- *        The TabTarget instance to work with.
- */
-function TabWebProgressListener(aTarget) {
-  this.target = aTarget;
-}
-
-TabWebProgressListener.prototype = {
-  target: null,
-
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference]),
-
-  onStateChange: function TWPL_onStateChange(progress, request, flag, status) {
-    let isStart = flag & Ci.nsIWebProgressListener.STATE_START;
-    let isDocument = flag & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT;
-    let isNetwork = flag & Ci.nsIWebProgressListener.STATE_IS_NETWORK;
-    let isRequest = flag & Ci.nsIWebProgressListener.STATE_IS_REQUEST;
-
-    // Skip non-interesting states.
-    if (!isStart || !isDocument || !isRequest || !isNetwork) {
-      return;
-    }
-
-    // emit event if the top frame is navigating
-    if (this.target && this.target.window == progress.DOMWindow) {
-      // Emit the event if the target is not remoted or store the payload for
-      // later emission otherwise.
-      if (this.target._client) {
-        this.target._navPayload = request;
-      } else {
-        this.target.emit("will-navigate", request);
-      }
-    }
-  },
-
-  onProgressChange: function() {},
-  onSecurityChange: function() {},
-  onStatusChange: function() {},
-
-  onLocationChange: function TWPL_onLocationChange(webProgress, request, URI, flags) {
-    if (this.target &&
-        !(flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) {
-      let window = webProgress.DOMWindow;
-      // Emit the event if the target is not remoted or store the payload for
-      // later emission otherwise.
-      if (this.target._client) {
-        this.target._navPayload = window;
-      } else {
-        this.target.emit("navigate", window);
-      }
-    }
-  },
-
-  /**
-   * Destroy the progress listener instance.
-   */
-  destroy: function TWPL_destroy() {
-    if (this.target.tab) {
-      this.target.tab.linkedBrowser.removeProgressListener(this);
-    }
-    this.target._webProgressListener = null;
-    this.target = null;
-  }
-};
-
-
-/**
- * A WindowTarget represents a page living in a xul window or panel. Generally
- * these will have a chrome: URL
- */
-function WindowTarget(window) {
-  EventEmitter.decorate(this);
-  this._window = window;
-  this._setupListeners();
-}
-
-WindowTarget.prototype = {
-  supports: supports,
-  get version() { return getVersion(); },
-
-  get window() {
-    return this._window;
-  },
-
-  get name() {
-    return this._window.document.title;
-  },
-
-  get url() {
-    return this._window.document.location.href;
-  },
-
-  get isRemote() {
-    return false;
-  },
-
-  get isLocalTab() {
-    return false;
-  },
-
-  get isThreadPaused() {
-    return !!this._isThreadPaused;
-  },
-
-  /**
-   * Listen to the different events.
-   */
-  _setupListeners: function() {
-    this._handleThreadState = this._handleThreadState.bind(this);
-    this.on("thread-paused", this._handleThreadState);
-    this.on("thread-resumed", this._handleThreadState);
-  },
-
-  _handleThreadState: function(event) {
-    switch (event) {
-      case "thread-resumed":
-        this._isThreadPaused = false;
-        break;
-      case "thread-paused":
-        this._isThreadPaused = true;
-        break;
-    }
-  },
-
-  /**
-   * Target is not alive anymore.
-   */
-  destroy: function() {
-    if (!this._destroyed) {
-      this._destroyed = true;
-
-      this.off("thread-paused", this._handleThreadState);
-      this.off("thread-resumed", this._handleThreadState);
-      this.emit("close");
-
-      targets.delete(this._window);
-      this._window = null;
-    }
-
-    return Promise.resolve(null);
-  },
-
-  toString: function() {
-    return 'WindowTarget:' + this.window;
-  },
-};
--- a/browser/devtools/framework/test/browser_devtools_api.js
+++ b/browser/devtools/framework/test/browser_devtools_api.js
@@ -2,18 +2,20 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests devtools API
 
 const Cu = Components.utils;
 const toolId = "test-tool";
 
 let tempScope = {};
-Cu.import("resource:///modules/devtools/shared/event-emitter.js", tempScope);
+Cu.import("resource:///modules/devtools/EventEmitter.jsm", tempScope);
 let EventEmitter = tempScope.EventEmitter;
+Cu.import("resource:///modules/devtools/Target.jsm", tempScope);
+let TargetFactory = tempScope.TargetFactory;
 
 function test() {
   addTab("about:blank", function(aBrowser, aTab) {
     runTests(aTab);
   });
 }
 
 function runTests(aTab) {
--- a/browser/devtools/framework/test/browser_new_activation_workflow.js
+++ b/browser/devtools/framework/test/browser_new_activation_workflow.js
@@ -3,16 +3,18 @@
 
 // Tests devtools API
 
 const Cu = Components.utils;
 
 let toolbox, target;
 
 let tempScope = {};
+Cu.import("resource:///modules/devtools/Target.jsm", tempScope);
+let TargetFactory = tempScope.TargetFactory;
 
 function test() {
   addTab("about:blank", function(aBrowser, aTab) {
     target = TargetFactory.forTab(gBrowser.selectedTab);
     loadWebConsole(aTab).then(function() {
       console.log('loaded');
     }, console.error);
   });
--- a/browser/devtools/framework/test/browser_target_events.js
+++ b/browser/devtools/framework/test/browser_target_events.js
@@ -1,12 +1,16 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+var tempScope = {};
+Cu.import("resource:///modules/devtools/Target.jsm", tempScope);
+var TargetFactory = tempScope.TargetFactory;
+
 var target;
 
 function test()
 {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", onLoad, true);
--- a/browser/devtools/framework/test/browser_toolbox_dynamic_registration.js
+++ b/browser/devtools/framework/test/browser_toolbox_dynamic_registration.js
@@ -1,14 +1,18 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 let toolbox;
 
+let temp = {};
+Cu.import("resource:///modules/devtools/Target.jsm", temp);
+let TargetFactory = temp.TargetFactory;
+
 function test()
 {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
   let target = TargetFactory.forTab(gBrowser.selectedTab);
 
   gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) {
--- a/browser/devtools/framework/test/browser_toolbox_hosts.js
+++ b/browser/devtools/framework/test/browser_toolbox_hosts.js
@@ -1,17 +1,21 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 let temp = {}
 Cu.import("resource:///modules/devtools/gDevTools.jsm", temp);
 let DevTools = temp.DevTools;
 
-let Toolbox = devtools.Toolbox;
+Cu.import("resource:///modules/devtools/Toolbox.jsm", temp);
+let Toolbox = temp.Toolbox;
+
+Cu.import("resource:///modules/devtools/Target.jsm", temp);
+let TargetFactory = temp.TargetFactory;
 
 let toolbox, target;
 
 function test()
 {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
--- a/browser/devtools/framework/test/browser_toolbox_ready.js
+++ b/browser/devtools/framework/test/browser_toolbox_ready.js
@@ -1,12 +1,16 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+let tempScope = {};
+Cu.import("resource:///modules/devtools/Target.jsm", tempScope);
+let TargetFactory = tempScope.TargetFactory;
+
 function test()
 {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
   let target = TargetFactory.forTab(gBrowser.selectedTab);
 
   gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) {
--- a/browser/devtools/framework/test/browser_toolbox_sidebar.js
+++ b/browser/devtools/framework/test/browser_toolbox_sidebar.js
@@ -1,14 +1,18 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test() {
   const Cu = Components.utils;
-  let {ToolSidebar} = devtools.require("devtools/framework/sidebar");
+  let tempScope = {};
+  Cu.import("resource:///modules/devtools/gDevTools.jsm", tempScope);
+  Cu.import("resource:///modules/devtools/Target.jsm", tempScope);
+  Cu.import("resource:///modules/devtools/Sidebar.jsm", tempScope);
+  let {TargetFactory: TargetFactory, gDevTools: gDevTools, ToolSidebar: ToolSidebar} = tempScope;
 
   const toolURL = "data:text/xml;charset=utf8,<?xml version='1.0'?>" +
                   "<?xml-stylesheet href='chrome://browser/skin/devtools/common.css' type='text/css'?>" +
                   "<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'>" +
                   "<hbox flex='1'><description flex='1'>foo</description><splitter class='devtools-side-splitter'/>" +
                   "<tabbox flex='1' id='sidebar' class='devtools-sidebar-tabs'><tabs/><tabpanels flex='1'/></tabbox>" +
                   "</hbox>" +
                   "</window>";
--- a/browser/devtools/framework/test/browser_toolbox_window_shortcuts.js
+++ b/browser/devtools/framework/test/browser_toolbox_window_shortcuts.js
@@ -1,12 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-let Toolbox = devtools.Toolbox;
+let temp = {};
+Cu.import("resource:///modules/devtools/Toolbox.jsm", temp);
+let Toolbox = temp.Toolbox;
+temp = null;
 
 let toolbox, toolIDs, idIndex;
 
 function test() {
   waitForExplicitFinish();
 
   if (window.navigator.userAgent.indexOf("Mac OS X 10.8") != -1 ||
       window.navigator.userAgent.indexOf("Windows NT 5.1") != -1) {
--- a/browser/devtools/framework/test/browser_toolbox_window_title_changes.js
+++ b/browser/devtools/framework/test/browser_toolbox_window_title_changes.js
@@ -1,13 +1,21 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-let Toolbox = devtools.Toolbox;
 let temp = {};
+Cu.import("resource:///modules/devtools/Toolbox.jsm", temp);
+let Toolbox = temp.Toolbox;
+temp = {};
+Cu.import("resource:///modules/devtools/Target.jsm", temp);
+let TargetFactory = temp.TargetFactory;
+temp = {};
+Cu.import("resource:///modules/devtools/gDevTools.jsm", temp);
+let gDevTools = temp.gDevTools;
+temp = {};
 Cu.import("resource://gre/modules/Services.jsm", temp);
 let Services = temp.Services;
 temp = null;
 
 function test() {
   waitForExplicitFinish();
 
   const URL_1 = "data:text/plain;charset=UTF-8,abcde";
--- a/browser/devtools/framework/test/head.js
+++ b/browser/devtools/framework/test/head.js
@@ -1,23 +1,20 @@
 /* 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/. */
 
-let TargetFactory = gDevTools.TargetFactory;
-
 let tempScope = {};
+Components.utils.import("resource:///modules/devtools/Target.jsm", tempScope);
+let TargetFactory = tempScope.TargetFactory;
 Components.utils.import("resource://gre/modules/devtools/Console.jsm", tempScope);
 let console = tempScope.console;
 Components.utils.import("resource://gre/modules/commonjs/sdk/core/promise.js", tempScope);
 let Promise = tempScope.Promise;
 
-let {devtools} = Components.utils.import("resource:///modules/devtools/gDevTools.jsm", {});
-let TargetFactory = devtools.TargetFactory;
-
 /**
  * Open a new tab at a URL and call a callback on load
  */
 function addTab(aURL, aCallback)
 {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
deleted file mode 100644
--- a/browser/devtools/framework/toolbox-hosts.js
+++ /dev/null
@@ -1,282 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const {Cu} = require("chrome");
-
-let Promise = require("sdk/core/promise");
-let EventEmitter = require("devtools/shared/event-emitter");
-
-Cu.import("resource://gre/modules/Services.jsm");
-
-/**
- * A toolbox host represents an object that contains a toolbox (e.g. the
- * sidebar or a separate window). Any host object should implement the
- * following functions:
- *
- * create() - create the UI and emit a 'ready' event when the UI is ready to use
- * destroy() - destroy the host's UI
- */
-
-exports.Hosts = {
-  "bottom": BottomHost,
-  "side": SidebarHost,
-  "window": WindowHost
-}
-
-/**
- * Host object for the dock on the bottom of the browser
- */
-function BottomHost(hostTab) {
-  this.hostTab = hostTab;
-
-  EventEmitter.decorate(this);
-}
-
-BottomHost.prototype = {
-  type: "bottom",
-
-  heightPref: "devtools.toolbox.footer.height",
-
-  /**
-   * Create a box at the bottom of the host tab.
-   */
-  create: function BH_create() {
-    let deferred = Promise.defer();
-
-    let gBrowser = this.hostTab.ownerDocument.defaultView.gBrowser;
-    let ownerDocument = gBrowser.ownerDocument;
-
-    this._splitter = ownerDocument.createElement("splitter");
-    this._splitter.setAttribute("class", "devtools-horizontal-splitter");
-
-    this.frame = ownerDocument.createElement("iframe");
-    this.frame.className = "devtools-toolbox-bottom-iframe";
-    this.frame.height = Services.prefs.getIntPref(this.heightPref);
-
-    this._nbox = gBrowser.getNotificationBox(this.hostTab.linkedBrowser);
-    this._nbox.appendChild(this._splitter);
-    this._nbox.appendChild(this.frame);
-
-    let frameLoad = function() {
-      this.frame.removeEventListener("DOMContentLoaded", frameLoad, true);
-      this.emit("ready", this.frame);
-
-      deferred.resolve(this.frame);
-    }.bind(this);
-
-    this.frame.tooltip = "aHTMLTooltip";
-    this.frame.addEventListener("DOMContentLoaded", frameLoad, true);
-
-    // we have to load something so we can switch documents if we have to
-    this.frame.setAttribute("src", "about:blank");
-
-    focusTab(this.hostTab);
-
-    return deferred.promise;
-  },
-
-  /**
-   * Raise the host.
-   */
-  raise: function BH_raise() {
-    focusTab(this.hostTab);
-  },
-
-  /**
-   * Set the toolbox title.
-   */
-  setTitle: function BH_setTitle(title) {
-    // Nothing to do for this host type.
-  },
-
-  /**
-   * Destroy the bottom dock.
-   */
-  destroy: function BH_destroy() {
-    if (!this._destroyed) {
-      this._destroyed = true;
-
-      Services.prefs.setIntPref(this.heightPref, this.frame.height);
-      this._nbox.removeChild(this._splitter);
-      this._nbox.removeChild(this.frame);
-    }
-
-    return Promise.resolve(null);
-  }
-}
-
-
-/**
- * Host object for the in-browser sidebar
- */
-function SidebarHost(hostTab) {
-  this.hostTab = hostTab;
-
-  EventEmitter.decorate(this);
-}
-
-SidebarHost.prototype = {
-  type: "side",
-
-  widthPref: "devtools.toolbox.sidebar.width",
-
-  /**
-   * Create a box in the sidebar of the host tab.
-   */
-  create: function SH_create() {
-    let deferred = Promise.defer();
-
-    let gBrowser = this.hostTab.ownerDocument.defaultView.gBrowser;
-    let ownerDocument = gBrowser.ownerDocument;
-
-    this._splitter = ownerDocument.createElement("splitter");
-    this._splitter.setAttribute("class", "devtools-side-splitter");
-
-    this.frame = ownerDocument.createElement("iframe");
-    this.frame.className = "devtools-toolbox-side-iframe";
-    this.frame.width = Services.prefs.getIntPref(this.widthPref);
-
-    this._sidebar = gBrowser.getSidebarContainer(this.hostTab.linkedBrowser);
-    this._sidebar.appendChild(this._splitter);
-    this._sidebar.appendChild(this.frame);
-
-    let frameLoad = function() {
-      this.frame.removeEventListener("DOMContentLoaded", frameLoad, true);
-      this.emit("ready", this.frame);
-
-      deferred.resolve(this.frame);
-    }.bind(this);
-
-    this.frame.addEventListener("DOMContentLoaded", frameLoad, true);
-    this.frame.tooltip = "aHTMLTooltip";
-    this.frame.setAttribute("src", "about:blank");
-
-    focusTab(this.hostTab);
-
-    return deferred.promise;
-  },
-
-  /**
-   * Raise the host.
-   */
-  raise: function SH_raise() {
-    focusTab(this.hostTab);
-  },
-
-  /**
-   * Set the toolbox title.
-   */
-  setTitle: function SH_setTitle(title) {
-    // Nothing to do for this host type.
-  },
-
-  /**
-   * Destroy the sidebar.
-   */
-  destroy: function SH_destroy() {
-    if (!this._destroyed) {
-      this._destroyed = true;
-
-      Services.prefs.setIntPref(this.widthPref, this.frame.width);
-      this._sidebar.removeChild(this._splitter);
-      this._sidebar.removeChild(this.frame);
-    }
-
-    return Promise.resolve(null);
-  }
-}
-
-/**
- * Host object for the toolbox in a separate window
- */
-function WindowHost() {
-  this._boundUnload = this._boundUnload.bind(this);
-
-  EventEmitter.decorate(this);
-}
-
-WindowHost.prototype = {
-  type: "window",
-
-  WINDOW_URL: "chrome://browser/content/devtools/framework/toolbox-window.xul",
-
-  /**
-   * Create a new xul window to contain the toolbox.
-   */
-  create: function WH_create() {
-    let deferred = Promise.defer();
-
-    let flags = "chrome,centerscreen,resizable,dialog=no";
-    let win = Services.ww.openWindow(null, this.WINDOW_URL, "_blank",
-                                     flags, null);
-
-    let frameLoad = function(event) {
-      win.removeEventListener("load", frameLoad, true);
-      this.frame = win.document.getElementById("toolbox-iframe");
-      this.emit("ready", this.frame);
-
-      deferred.resolve(this.frame);
-    }.bind(this);
-
-    win.addEventListener("load", frameLoad, true);
-    win.addEventListener("unload", this._boundUnload);
-
-    win.focus();
-
-    this._window = win;
-
-    return deferred.promise;
-  },
-
-  /**
-   * Catch the user closing the window.
-   */
-  _boundUnload: function(event) {
-    if (event.target.location != this.WINDOW_URL) {
-      return;
-    }
-    this._window.removeEventListener("unload", this._boundUnload);
-
-    this.emit("window-closed");
-  },
-
-  /**
-   * Raise the host.
-   */
-  raise: function RH_raise() {
-    this._window.focus();
-  },
-
-  /**
-   * Set the toolbox title.
-   */
-  setTitle: function WH_setTitle(title) {
-    this._window.document.title = title;
-  },
-
-  /**
-   * Destroy the window.
-   */
-  destroy: function WH_destroy() {
-    if (!this._destroyed) {
-      this._destroyed = true;
-
-      this._window.removeEventListener("unload", this._boundUnload);
-      this._window.close();
-    }
-
-    return Promise.resolve(null);
-  }
-}
-
-/**
- *  Switch to the given tab in a browser and focus the browser window
- */
-function focusTab(tab) {
-  let browserWindow = tab.ownerDocument.defaultView;
-  browserWindow.focus();
-  browserWindow.gBrowser.selectedTab = tab;
-}
deleted file mode 100644
--- a/browser/devtools/framework/toolbox.js
+++ /dev/null
@@ -1,680 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const {Cc, Ci, Cu} = require("chrome");
-
-let Promise = require("sdk/core/promise");
-let EventEmitter = require("devtools/shared/event-emitter");
-
-Cu.import('resource://gre/modules/XPCOMUtils.jsm');
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource:///modules/devtools/gDevTools.jsm");
-
-loader.lazyGetter(this, "Hosts", () => require("devtools/framework/toolbox-hosts").Hosts);
-
-XPCOMUtils.defineLazyModuleGetter(this, "CommandUtils",
-                                  "resource:///modules/devtools/DeveloperToolbar.jsm");
-
-XPCOMUtils.defineLazyGetter(this, "toolboxStrings", function() {
-  let bundle = Services.strings.createBundle("chrome://browser/locale/devtools/toolbox.properties");
-  let l10n = function(aName, ...aArgs) {
-    try {
-      if (aArgs.length == 0) {
-        return bundle.GetStringFromName(aName);
-      } else {
-        return bundle.formatStringFromName(aName, aArgs, aArgs.length);
-      }
-    } catch (ex) {
-      Services.console.logStringMessage("Error reading '" + aName + "'");
-    }
-  };
-  return l10n;
-});
-
-XPCOMUtils.defineLazyGetter(this, "Requisition", function() {
-  let scope = {};
-  Cu.import("resource://gre/modules/devtools/Require.jsm", scope);
-  Cu.import("resource:///modules/devtools/gcli.jsm", scope);
-
-  let req = scope.require;
-  return req('gcli/cli').Requisition;
-});
-
-/**
- * A "Toolbox" is the component that holds all the tools for one specific
- * target. Visually, it's a document that includes the tools tabs and all
- * the iframes where the tool panels will be living in.
- *
- * @param {object} target
- *        The object the toolbox is debugging.
- * @param {string} selectedTool
- *        Tool to select initially
- * @param {Toolbox.HostType} hostType
- *        Type of host that will host the toolbox (e.g. sidebar, window)
- */
-function Toolbox(target, selectedTool, hostType) {
-  this._target = target;
-  this._toolPanels = new Map();
-
-  this._toolRegistered = this._toolRegistered.bind(this);
-  this._toolUnregistered = this._toolUnregistered.bind(this);
-  this.destroy = this.destroy.bind(this);
-
-  this._target.on("close", this.destroy);
-
-  if (!hostType) {
-    hostType = Services.prefs.getCharPref(this._prefs.LAST_HOST);
-  }
-  if (!selectedTool) {
-    selectedTool = Services.prefs.getCharPref(this._prefs.LAST_TOOL);
-  }
-  let definitions = gDevTools.getToolDefinitionMap();
-  if (!definitions.get(selectedTool)) {
-    selectedTool = "webconsole";
-  }
-  this._defaultToolId = selectedTool;
-
-  this._host = this._createHost(hostType);
-
-  EventEmitter.decorate(this);
-
-  this._refreshHostTitle = this._refreshHostTitle.bind(this);
-  this._target.on("navigate", this._refreshHostTitle);
-  this.on("host-changed", this._refreshHostTitle);
-  this.on("select", this._refreshHostTitle);
-
-  gDevTools.on("tool-registered", this._toolRegistered);
-  gDevTools.on("tool-unregistered", this._toolUnregistered);
-}
-exports.Toolbox = Toolbox;
-
-/**
- * The toolbox can be 'hosted' either embedded in a browser window
- * or in a separate window.
- */
-Toolbox.HostType = {
-  BOTTOM: "bottom",
-  SIDE: "side",
-  WINDOW: "window"
-}
-
-Toolbox.prototype = {
-  _URL: "chrome://browser/content/devtools/framework/toolbox.xul",
-
-  _prefs: {
-    LAST_HOST: "devtools.toolbox.host",
-    LAST_TOOL: "devtools.toolbox.selectedTool",
-    SIDE_ENABLED: "devtools.toolbox.sideEnabled"
-  },
-
-  HostType: Toolbox.HostType,
-
-  /**
-   * Returns a *copy* of the _toolPanels collection.
-   *
-   * @return {Map} panels
-   *         All the running panels in the toolbox
-   */
-  getToolPanels: function TB_getToolPanels() {
-    let panels = new Map();
-
-    for (let [key, value] of this._toolPanels) {
-      panels.set(key, value);
-    }
-    return panels;
-  },
-
-  /**
-   * Access the panel for a given tool
-   */
-  getPanel: function TBOX_getPanel(id) {
-    return this.getToolPanels().get(id);
-  },
-
-  /**
-   * This is a shortcut for getPanel(currentToolId) because it is much more
-   * likely that we're going to want to get the panel that we've just made
-   * visible
-   */
-  getCurrentPanel: function TBOX_getCurrentPanel() {
-    return this.getToolPanels().get(this.currentToolId);
-  },
-
-  /**
-   * Get/alter the target of a Toolbox so we're debugging something different.
-   * See Target.jsm for more details.
-   * TODO: Do we allow |toolbox.target = null;| ?
-   */
-  get target() {
-    return this._target;
-  },
-
-  /**
-   * Get/alter the host of a Toolbox, i.e. is it in browser or in a separate
-   * tab. See HostType for more details.
-   */
-  get hostType() {
-    return this._host.type;
-  },
-
-  /**
-   * Get/alter the currently displayed tool.
-   */
-  get currentToolId() {
-    return this._currentToolId;
-  },
-
-  set currentToolId(value) {
-    this._currentToolId = value;
-  },
-
-  /**
-   * Get the iframe containing the toolbox UI.
-   */
-  get frame() {
-    return this._host.frame;
-  },
-
-  /**
-   * Shortcut to the document containing the toolbox UI
-   */
-  get doc() {
-    return this.frame.contentDocument;
-  },
-
-  /**
-   * Open the toolbox
-   */
-  open: function TBOX_open() {
-    let deferred = Promise.defer();
-
-    this._host.create().then(function(iframe) {
-      let domReady = function() {
-        iframe.removeEventListener("DOMContentLoaded", domReady, true);
-
-        this.isReady = true;
-
-        let closeButton = this.doc.getElementById("toolbox-close");
-        closeButton.addEventListener("command", this.destroy, true);
-
-        this._buildDockButtons();
-        this._buildTabs();
-        this._buildButtons();
-        this._addKeysToWindow();
-
-        this.selectTool(this._defaultToolId).then(function(panel) {
-          this.emit("ready");
-          deferred.resolve();
-        }.bind(this));
-      }.bind(this);
-
-      iframe.addEventListener("DOMContentLoaded", domReady, true);
-      iframe.setAttribute("src", this._URL);
-    }.bind(this));
-
-    return deferred.promise;
-  },
-
-  /**
-   * Adds the keys and commands to the Toolbox Window in window mode.
-   */
-  _addKeysToWindow: function TBOX__addKeysToWindow() {
-    if (this.hostType != Toolbox.HostType.WINDOW) {
-      return;
-    }
-    let doc = this.doc.defaultView.parent.document;
-    for (let [id, toolDefinition] of gDevTools._tools) {
-      if (toolDefinition.key) {
-        // Prevent multiple entries for the same tool.
-        if (doc.getElementById("key_" + id)) {
-          continue;
-        }
-        let key = doc.createElement("key");
-        key.id = "key_" + id;
-
-        if (toolDefinition.key.startsWith("VK_")) {
-          key.setAttribute("keycode", toolDefinition.key);
-        } else {
-          key.setAttribute("key", toolDefinition.key);
-        }
-
-        key.setAttribute("modifiers", toolDefinition.modifiers);
-        key.setAttribute("oncommand", "void(0);"); // needed. See bug 371900
-        key.addEventListener("command", function(toolId) {
-          this.selectTool(toolId);
-        }.bind(this, id), true);
-        doc.getElementById("toolbox-keyset").appendChild(key);
-      }
-    }
-  },
-
-  /**
-   * Build the buttons for changing hosts. Called every time
-   * the host changes.
-   */
-  _buildDockButtons: function TBOX_createDockButtons() {
-    let dockBox = this.doc.getElementById("toolbox-dock-buttons");
-
-    while (dockBox.firstChild) {
-      dockBox.removeChild(dockBox.firstChild);
-    }
-
-    if (!this._target.isLocalTab) {
-      return;
-    }
-
-    let closeButton = this.doc.getElementById("toolbox-close");
-    if (this.hostType === this.HostType.WINDOW) {
-      closeButton.setAttribute("hidden", "true");
-    } else {
-      closeButton.removeAttribute("hidden");
-    }
-
-    let sideEnabled = Services.prefs.getBoolPref(this._prefs.SIDE_ENABLED);
-
-    for each (let position in this.HostType) {
-      if (position == this.hostType ||
-         (!sideEnabled && position == this.HostType.SIDE)) {
-        continue;
-      }
-
-      let button = this.doc.createElement("toolbarbutton");
-      button.id = "toolbox-dock-" + position;
-      button.className = "toolbox-dock-button";
-      button.setAttribute("tooltiptext", toolboxStrings("toolboxDockButtons." +
-                                                        position + ".tooltip"));
-      button.addEventListener("command", function(position) {
-        this.switchHost(position);
-      }.bind(this, position));
-
-      dockBox.appendChild(button);
-    }
-  },
-
-  /**
-   * Add tabs to the toolbox UI for registered tools
-   */
-  _buildTabs: function TBOX_buildTabs() {
-    for (let definition of gDevTools.getToolDefinitionArray()) {
-      this._buildTabForTool(definition);
-    }
-  },
-
-  /**
-   * Add buttons to the UI as specified in the devtools.window.toolbarSpec pref
-   */
-  _buildButtons: function TBOX_buildButtons() {
-    if (!this.target.isLocalTab) {
-      return;
-    }
-
-    let toolbarSpec = CommandUtils.getCommandbarSpec("devtools.toolbox.toolbarSpec");
-    let environment = { chromeDocument: this.target.tab.ownerDocument };
-    let requisition = new Requisition(environment);
-
-    let buttons = CommandUtils.createButtons(toolbarSpec, this._target, this.doc, requisition);
-
-    let container = this.doc.getElementById("toolbox-buttons");
-    buttons.forEach(function(button) {
-      container.appendChild(button);
-    }.bind(this));
-  },
-
-  /**
-   * Build a tab for one tool definition and add to the toolbox
-   *
-   * @param {string} toolDefinition
-   *        Tool definition of the tool to build a tab for.
-   */
-  _buildTabForTool: function TBOX_buildTabForTool(toolDefinition) {
-    if (!toolDefinition.isTargetSupported(this._target)) {
-      return;
-    }
-
-    let tabs = this.doc.getElementById("toolbox-tabs");
-    let deck = this.doc.getElementById("toolbox-deck");
-
-    let id = toolDefinition.id;
-
-    let radio = this.doc.createElement("radio");
-    radio.className = "toolbox-tab devtools-tab";
-    radio.id = "toolbox-tab-" + id;
-    radio.setAttribute("flex", "1");
-    radio.setAttribute("toolid", id);
-    radio.setAttribute("tooltiptext", toolDefinition.tooltip);
-
-    radio.addEventListener("command", function(id) {
-      this.selectTool(id);
-    }.bind(this, id));
-
-    if (toolDefinition.icon) {
-      let image = this.doc.createElement("image");
-      image.setAttribute("src", toolDefinition.icon);
-      radio.appendChild(image);
-    }
-
-    let label = this.doc.createElement("label");
-    label.setAttribute("value", toolDefinition.label)
-    label.setAttribute("crop", "end");
-    label.setAttribute("flex", "1");
-
-    let vbox = this.doc.createElement("vbox");
-    vbox.className = "toolbox-panel";
-    vbox.id = "toolbox-panel-" + id;
-
-    radio.appendChild(label);
-    tabs.appendChild(radio);
-    deck.appendChild(vbox);
-
-    this._addKeysToWindow();
-  },
-
-  /**
-   * Switch to the tool with the given id
-   *
-   * @param {string} id
-   *        The id of the tool to switch to
-   */
-  selectTool: function TBOX_selectTool(id) {
-    let deferred = Promise.defer();
-
-    let selected = this.doc.querySelector(".devtools-tab[selected]");
-    if (selected) {
-      selected.removeAttribute("selected");
-    }
-    let tab = this.doc.getElementById("toolbox-tab-" + id);
-    tab.setAttribute("selected", "true");
-
-    if (this._currentToolId == id) {
-      // Return the existing panel in order to have a consistent return value.
-      return Promise.resolve(this._toolPanels.get(id));
-    }
-
-    if (!this.isReady) {
-      throw new Error("Can't select tool, wait for toolbox 'ready' event");
-    }
-    let tab = this.doc.getElementById("toolbox-tab-" + id);
-
-    if (!tab) {
-      throw new Error("No tool found");
-    }
-
-    let tabstrip = this.doc.getElementById("toolbox-tabs");
-
-    // select the right tab
-    let index = -1;
-    let tabs = tabstrip.childNodes;
-    for (let i = 0; i < tabs.length; i++) {
-      if (tabs[i] === tab) {
-        index = i;
-        break;
-      }
-    }
-    tabstrip.selectedIndex = index;
-
-    // and select the right iframe
-    let deck = this.doc.getElementById("toolbox-deck");
-    deck.selectedIndex = index;
-
-    let definition = gDevTools.getToolDefinitionMap().get(id);
-
-    this._currentToolId = id;
-
-    let iframe = this.doc.getElementById("toolbox-panel-iframe-" + id);
-    if (!iframe) {
-      iframe = this.doc.createElement("iframe");
-      iframe.className = "toolbox-panel-iframe";
-      iframe.id = "toolbox-panel-iframe-" + id;
-      iframe.setAttribute("flex", 1);
-      iframe.setAttribute("forceOwnRefreshDriver", "");
-      iframe.tooltip = "aHTMLTooltip";
-
-      let vbox = this.doc.getElementById("toolbox-panel-" + id);
-      vbox.appendChild(iframe);
-
-      let boundLoad = function() {
-        iframe.removeEventListener("DOMContentLoaded", boundLoad, true);
-
-        let built = definition.build(iframe.contentWindow, this);
-        Promise.resolve(built).then(function(panel) {
-          this._toolPanels.set(id, panel);
-
-          this.emit(id + "-ready", panel);
-          this.emit("select", id);
-          this.emit(id + "-selected", panel);
-          gDevTools.emit(id + "-ready", this, panel);
-
-          deferred.resolve(panel);
-        }.bind(this));
-      }.bind(this);
-
-      iframe.addEventListener("DOMContentLoaded", boundLoad, true);
-      iframe.setAttribute("src", definition.url);
-    } else {
-      let panel = this._toolPanels.get(id);
-      // only emit 'select' event if the iframe has been loaded
-      if (panel) {
-        this.emit("select", id);
-        this.emit(id + "-selected", panel);
-        deferred.resolve(panel);
-      }
-    }
-
-    Services.prefs.setCharPref(this._prefs.LAST_TOOL, id);
-
-    return deferred.promise;
-  },
-
-  /**
-   * Raise the toolbox host.
-   */
-  raise: function TBOX_raise() {
-    this._host.raise();
-  },
-
-  /**
-   * Refresh the host's title.
-   */
-  _refreshHostTitle: function TBOX_refreshHostTitle() {
-    let toolName;
-    let toolId = this.currentToolId;
-    if (toolId) {
-      let toolDef = gDevTools.getToolDefinitionMap().get(toolId);
-      toolName = toolDef.label;
-    } else {
-      // no tool is selected
-      toolName = toolboxStrings("toolbox.defaultTitle");
-    }
-    let title = toolboxStrings("toolbox.titleTemplate",
-                               toolName, this.target.url);
-    this._host.setTitle(title);
-  },
-
-  /**
-   * Create a host object based on the given host type.
-   *
-   * Warning: some hosts require that the toolbox target provides a reference to
-   * the attached tab. Not all Targets have a tab property - make sure you correctly
-   * mix and match hosts and targets.
-   *
-   * @param {string} hostType
-   *        The host type of the new host object
-   *
-   * @return {Host} host
-   *        The created host object
-   */
-  _createHost: function TBOX_createHost(hostType) {
-    if (!Hosts[hostType]) {
-      throw new Error('Unknown hostType: '+ hostType);
-    }
-    let newHost = new Hosts[hostType](this.target.tab);
-
-    // clean up the toolbox if its window is closed
-    newHost.on("window-closed", this.destroy);
-
-    return newHost;
-  },
-
-  /**
-   * Switch to a new host for the toolbox UI. E.g.
-   * bottom, sidebar, separate window.
-   *
-   * @param {string} hostType
-   *        The host type of the new host object
-   */
-  switchHost: function TBOX_switchHost(hostType) {
-    if (hostType == this._host.type) {
-      return;
-    }
-
-    if (!this._target.isLocalTab) {
-      return;
-    }
-
-    let newHost = this._createHost(hostType);
-    return newHost.create().then(function(iframe) {
-      // change toolbox document's parent to the new host
-      iframe.QueryInterface(Ci.nsIFrameLoaderOwner);
-      iframe.swapFrameLoaders(this.frame);
-
-      this._host.off("window-closed", this.destroy);
-      this._host.destroy();
-
-      this._host = newHost;
-
-      Services.prefs.setCharPref(this._prefs.LAST_HOST, this._host.type);
-
-      this._buildDockButtons();
-      this._addKeysToWindow();
-
-      this.emit("host-changed");
-    }.bind(this));
-  },
-
-  /**
-   * Handler for the tool-registered event.
-   * @param  {string} event
-   *         Name of the event ("tool-registered")
-   * @param  {string} toolId
-   *         Id of the tool that was registered
-   */
-  _toolRegistered: function TBOX_toolRegistered(event, toolId) {
-    let defs = gDevTools.getToolDefinitionMap();
-    let tool = defs.get(toolId);
-
-    this._buildTabForTool(tool);
-  },
-
-  /**
-   * Handler for the tool-unregistered event.
-   * @param  {string} event
-   *         Name of the event ("tool-unregistered")
-   * @param  {string} toolId
-   *         Id of the tool that was unregistered
-   */
-  _toolUnregistered: function TBOX_toolUnregistered(event, toolId) {
-    let radio = this.doc.getElementById("toolbox-tab-" + toolId);
-    let panel = this.doc.getElementById("toolbox-panel-" + toolId);
-
-    if (radio) {
-      if (this._currentToolId == toolId) {
-        let nextToolName = null;
-        if (radio.nextSibling) {
-          nextToolName = radio.nextSibling.getAttribute("toolid");
-        }
-        if (radio.previousSibling) {
-          nextToolName = radio.previousSibling.getAttribute("toolid");
-        }
-        if (nextToolName) {
-          this.selectTool(nextToolName);
-        }
-      }
-      radio.parentNode.removeChild(radio);
-    }
-
-    if (panel) {
-      panel.parentNode.removeChild(panel);
-    }
-
-    if (this.hostType == Toolbox.HostType.WINDOW) {
-      let doc = this.doc.defaultView.parent.document;
-      let key = doc.getElementById("key_" + id);
-      if (key) {
-        key.parentNode.removeChild(key);
-      }
-    }
-
-    if (this._toolPanels.has(toolId)) {
-      let instance = this._toolPanels.get(toolId);
-      instance.destroy();
-      this._toolPanels.delete(toolId);
-    }
-  },
-
-
-  /**
-   * Get the toolbox's notification box
-   *
-   * @return The notification box element.
-   */
-  getNotificationBox: function TBOX_getNotificationBox() {
-    return this.doc.getElementById("toolbox-notificationbox");
-  },
-
-  /**
-   * Remove all UI elements, detach from target and clear up
-   */
-  destroy: function TBOX_destroy() {
-    // If several things call destroy then we give them all the same
-    // destruction promise so we're sure to destroy only once
-    if (this._destroyer) {
-      return this._destroyer;
-    }
-    // Assign the "_destroyer" property before calling the other
-    // destroyer methods to guarantee that the Toolbox's destroy
-    // method is only executed once.
-    let deferred = Promise.defer();
-    this._destroyer = deferred.promise;
-
-    this._target.off("navigate", this._refreshHostTitle);
-    this.off("select", this._refreshHostTitle);
-    this.off("host-changed", this._refreshHostTitle);
-
-    gDevTools.off("tool-registered", this._toolRegistered);
-    gDevTools.off("tool-unregistered", this._toolUnregistered);
-
-    let outstanding = [];
-
-    for (let [id, panel] of this._toolPanels) {
-      outstanding.push(panel.destroy());
-    }
-
-    let container = this.doc.getElementById("toolbox-buttons");
-    while(container.firstChild) {
-      container.removeChild(container.firstChild);
-    }
-
-    outstanding.push(this._host.destroy());
-
-    // Targets need to be notified that the toolbox is being torn down, so that
-    // remote protocol connections can be gracefully terminated.
-    if (this._target) {
-      this._target.off("close", this.destroy);
-      outstanding.push(this._target.destroy());
-    }
-    this._target = null;
-
-    Promise.all(outstanding).then(function() {
-      this.emit("destroyed");
-      // Free _host after the call to destroyed in order to let a chance
-      // to destroyed listeners to still query toolbox attributes
-      this._host = null;
-      deferred.resolve();
-    }.bind(this));
-
-    return this._destroyer;
-  }
-};
new file mode 100644
--- /dev/null
+++ b/browser/devtools/inspector/Breadcrumbs.jsm
@@ -0,0 +1,599 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+const Cc = Components.classes;
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+
+const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
+const ENSURE_SELECTION_VISIBLE_DELAY = 50; // ms
+
+this.EXPORTED_SYMBOLS = ["HTMLBreadcrumbs"];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource:///modules/devtools/DOMHelpers.jsm");
+Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
+
+const LOW_PRIORITY_ELEMENTS = {
+  "HEAD": true,
+  "BASE": true,
+  "BASEFONT": true,
+  "ISINDEX": true,
+  "LINK": true,
+  "META": true,
+  "SCRIPT": true,
+  "STYLE": true,
+  "TITLE": true,
+};
+
+///////////////////////////////////////////////////////////////////////////
+//// HTML Breadcrumbs
+
+/**
+ * Display the ancestors of the current node and its children.
+ * Only one "branch" of children are displayed (only one line).
+ *
+ * FIXME: Bug 822388 - Use the BreadcrumbsWidget in the Inspector.
+ *
+ * Mechanism:
+ * . If no nodes displayed yet:
+ *    then display the ancestor of the selected node and the selected node;
+ *   else select the node;
+ * . If the selected node is the last node displayed, append its first (if any).
+ */
+this.HTMLBreadcrumbs = function HTMLBreadcrumbs(aInspector)
+{
+  this.inspector = aInspector;
+  this.selection = this.inspector.selection;
+  this.chromeWin = this.inspector.panelWin;
+  this.chromeDoc = this.inspector.panelDoc;
+  this.DOMHelpers = new DOMHelpers(this.chromeWin);
+  this._init();
+}
+
+HTMLBreadcrumbs.prototype = {
+  _init: function BC__init()
+  {
+    this.container = this.chromeDoc.getElementById("inspector-breadcrumbs");
+    this.container.addEventListener("mousedown", this, true);
+    this.container.addEventListener("keypress", this, true);
+
+    // We will save a list of already displayed nodes in this array.
+    this.nodeHierarchy = [];
+
+    // Last selected node in nodeHierarchy.
+    this.currentIndex = -1;
+
+    // By default, hide the arrows. We let the <scrollbox> show them
+    // in case of overflow.
+    this.container.removeAttribute("overflows");
+    this.container._scrollButtonUp.collapsed = true;
+    this.container._scrollButtonDown.collapsed = true;
+
+    this.onscrollboxreflow = function() {
+      if (this.container._scrollButtonDown.collapsed)
+        this.container.removeAttribute("overflows");
+      else
+        this.container.setAttribute("overflows", true);
+    }.bind(this);
+
+    this.container.addEventListener("underflow", this.onscrollboxreflow, false);
+    this.container.addEventListener("overflow", this.onscrollboxreflow, false);
+
+    this.update = this.update.bind(this);
+    this.updateSelectors = this.updateSelectors.bind(this);
+    this.selection.on("new-node", this.update);
+    this.selection.on("pseudoclass", this.updateSelectors);
+    this.selection.on("attribute-changed", this.updateSelectors);
+    this.update();
+  },
+
+  /**
+   * Build a string that represents the node: tagName#id.class1.class2.
+   *
+   * @param aNode The node to pretty-print
+   * @returns a string
+   */
+  prettyPrintNodeAsText: function BC_prettyPrintNodeText(aNode)
+  {
+    let text = aNode.tagName.toLowerCase();
+    if (aNode.id) {
+      text += "#" + aNode.id;
+    }
+    for (let i = 0; i < aNode.classList.length; i++) {
+      text += "." + aNode.classList[i];
+    }
+    for (let i = 0; i < PSEUDO_CLASSES.length; i++) {
+      let pseudo = PSEUDO_CLASSES[i];
+      if (DOMUtils.hasPseudoClassLock(aNode, pseudo)) {
+        text += pseudo;
+      }
+    }
+
+    return text;
+  },
+
+
+  /**
+   * Build <label>s that represent the node:
+   *   <label class="breadcrumbs-widget-item-tag">tagName</label>
+   *   <label class="breadcrumbs-widget-item-id">#id</label>
+   *   <label class="breadcrumbs-widget-item-classes">.class1.class2</label>
+   *
+   * @param aNode The node to pretty-print
+   * @returns a document fragment.
+   */
+  prettyPrintNodeAsXUL: function BC_prettyPrintNodeXUL(aNode)
+  {
+    let fragment = this.chromeDoc.createDocumentFragment();
+
+    let tagLabel = this.chromeDoc.createElement("label");
+    tagLabel.className = "breadcrumbs-widget-item-tag plain";
+
+    let idLabel = this.chromeDoc.createElement("label");
+    idLabel.className = "breadcrumbs-widget-item-id plain";
+
+    let classesLabel = this.chromeDoc.createElement("label");
+    classesLabel.className = "breadcrumbs-widget-item-classes plain";
+
+    let pseudosLabel = this.chromeDoc.createElement("label");
+    pseudosLabel.className = "breadcrumbs-widget-item-pseudo-classes plain";
+
+    tagLabel.textContent = aNode.tagName.toLowerCase();
+    idLabel.textContent = aNode.id ? ("#" + aNode.id) : "";
+
+    let classesText = "";
+    for (let i = 0; i < aNode.classList.length; i++) {
+      classesText += "." + aNode.classList[i];
+    }
+    classesLabel.textContent = classesText;
+
+    let pseudos = PSEUDO_CLASSES.filter(function(pseudo) {
+      return DOMUtils.hasPseudoClassLock(aNode, pseudo);
+    }, this);
+    pseudosLabel.textContent = pseudos.join("");
+
+    fragment.appendChild(tagLabel);
+    fragment.appendChild(idLabel);
+    fragment.appendChild(classesLabel);
+    fragment.appendChild(pseudosLabel);
+
+    return fragment;
+  },
+
+  /**
+   * Open the sibling menu.
+   *
+   * @param aButton the button representing the node.
+   * @param aNode the node we want the siblings from.
+   */
+  openSiblingMenu: function BC_openSiblingMenu(aButton, aNode)
+  {
+    // We make sure that the targeted node is selected
+    // because we want to use the nodemenu that only works
+    // for inspector.selection
+    this.selection.setNode(aNode, "breadcrumbs");
+
+    let title = this.chromeDoc.createElement("menuitem");
+    title.setAttribute("label", this.inspector.strings.GetStringFromName("breadcrumbs.siblings"));
+    title.setAttribute("disabled", "true");
+
+    let separator = this.chromeDoc.createElement("menuseparator");
+
+    let items = [title, separator];
+
+    let nodes = aNode.parentNode.childNodes;
+    for (let i = 0; i < nodes.length; i++) {
+      if (nodes[i].nodeType == aNode.ELEMENT_NODE) {
+        let item = this.chromeDoc.createElement("menuitem");
+        if (nodes[i] === aNode) {
+          item.setAttribute("disabled", "true");
+          item.setAttribute("checked", "true");
+        }
+
+        item.setAttribute("type", "radio");
+        item.setAttribute("label", this.prettyPrintNodeAsText(nodes[i]));
+
+        let selection = this.selection;
+        item.onmouseup = (function(aNode) {
+          return function() {
+            selection.setNode(aNode, "breadcrumbs");
+          }
+        })(nodes[i]);
+
+        items.push(item);
+      }
+    }
+    this.inspector.showNodeMenu(aButton, "before_start", items);
+  },
+
+  /**
+   * Generic event handler.
+   *
+   * @param nsIDOMEvent event
+   *        The DOM event object.
+   */
+  handleEvent: function BC_handleEvent(event)
+  {
+    if (event.type == "mousedown" && event.button == 0) {
+      // on Click and Hold, open the Siblings menu
+
+      let timer;
+      let container = this.container;
+
+      function openMenu(event) {
+        cancelHold();
+        let target = event.originalTarget;
+        if (target.tagName == "button") {
+          target.onBreadcrumbsHold();
+        }
+      }
+
+      function handleClick(event) {
+        cancelHold();
+        let target = event.originalTarget;
+        if (target.tagName == "button") {
+          target.onBreadcrumbsClick();
+        }
+      }
+
+      let window = this.chromeWin;
+      function cancelHold(event) {
+        window.clearTimeout(timer);
+        container.removeEventListener("mouseout", cancelHold, false);
+        container.removeEventListener("mouseup", handleClick, false);
+      }
+
+      container.addEventListener("mouseout", cancelHold, false);
+      container.addEventListener("mouseup", handleClick, false);
+      timer = window.setTimeout(openMenu, 500, event);
+    }
+
+    if (event.type == "keypress" && this.selection.isElementNode()) {
+      let node = null;
+      switch (event.keyCode) {
+        case this.chromeWin.KeyEvent.DOM_VK_LEFT:
+          if (this.currentIndex != 0) {
+            node = this.nodeHierarchy[this.currentIndex - 1].node;
+          }
+          break;
+        case this.chromeWin.KeyEvent.DOM_VK_RIGHT:
+          if (this.currentIndex < this.nodeHierarchy.length - 1) {
+            node = this.nodeHierarchy[this.currentIndex + 1].node;
+          }
+          break;
+        case this.chromeWin.KeyEvent.DOM_VK_UP:
+          node = this.selection.node.previousSibling;
+          while (node && (node.nodeType != node.ELEMENT_NODE)) {
+            node = node.previousSibling;
+          }
+          break;
+        case this.chromeWin.KeyEvent.DOM_VK_DOWN:
+          node = this.selection.node.nextSibling;
+          while (node && (node.nodeType != node.ELEMENT_NODE)) {
+            node = node.nextSibling;
+          }
+          break;
+      }
+      if (node) {
+        this.selection.setNode(node, "breadcrumbs");
+      }
+      event.preventDefault();
+      event.stopPropagation();
+    }
+  },
+
+  /**
+   * Remove nodes and delete properties.
+   */
+  destroy: function BC_destroy()
+  {
+    this.nodeHierarchy.forEach(function(crumb) {
+      if (LayoutHelpers.isNodeConnected(crumb.node)) {
+        DOMUtils.clearPseudoClassLocks(crumb.node);
+      }
+    });
+
+    this.selection.off("new-node", this.update);
+    this.selection.off("pseudoclass", this.updateSelectors);
+    this.selection.off("attribute-changed", this.updateSelectors);
+
+    this.container.removeEventListener("underflow", this.onscrollboxreflow, false);
+    this.container.removeEventListener("overflow", this.onscrollboxreflow, false);
+    this.onscrollboxreflow = null;
+
+    this.empty();
+    this.container.removeEventListener("mousedown", this, true);
+    this.container.removeEventListener("keypress", this, true);
+    this.container = null;
+    this.nodeHierarchy = null;
+  },
+
+  /**
+   * Empty the breadcrumbs container.
+   */
+  empty: function BC_empty()
+  {
+    while (this.container.hasChildNodes()) {
+      this.container.removeChild(this.container.firstChild);
+    }
+  },
+
+  /**
+   * Re-init the cache and remove all the buttons.
+   */
+  invalidateHierarchy: function BC_invalidateHierarchy()
+  {
+    this.inspector.hideNodeMenu();
+    this.nodeHierarchy = [];
+    this.empty();
+  },
+
+  /**
+   * Set which button represent the selected node.
+   *
+   * @param aIdx Index of the displayed-button to select
+   */
+  setCursor: function BC_setCursor(aIdx)
+  {
+    // Unselect the previously selected button
+    if (this.currentIndex > -1 && this.currentIndex < this.nodeHierarchy.length) {
+      this.nodeHierarchy[this.currentIndex].button.removeAttribute("checked");
+    }
+    if (aIdx > -1) {
+      this.nodeHierarchy[aIdx].button.setAttribute("checked", "true");
+      if (this.hadFocus)
+        this.nodeHierarchy[aIdx].button.focus();
+    }
+    this.currentIndex = aIdx;
+  },
+
+  /**
+   * Get the index of the node in the cache.
+   *
+   * @param aNode
+   * @returns integer the index, -1 if not found
+   */
+  indexOf: function BC_indexOf(aNode)
+  {
+    let i = this.nodeHierarchy.length - 1;
+    for (let i = this.nodeHierarchy.length - 1; i >= 0; i--) {
+      if (this.nodeHierarchy[i].node === aNode) {
+        return i;
+      }
+    }
+    return -1;
+  },
+
+  /**
+   * Remove all the buttons and their references in the cache
+   * after a given index.
+   *
+   * @param aIdx
+   */
+  cutAfter: function BC_cutAfter(aIdx)
+  {
+    while (this.nodeHierarchy.length > (aIdx + 1)) {
+      let toRemove = this.nodeHierarchy.pop();
+      this.container.removeChild(toRemove.button);
+    }
+  },
+
+  /**
+   * Build a button representing the node.
+   *
+   * @param aNode The node from the page.
+   * @returns aNode The <button>.
+   */
+  buildButton: function BC_buildButton(aNode)
+  {
+    let button = this.chromeDoc.createElement("button");
+    button.appendChild(this.prettyPrintNodeAsXUL(aNode));
+    button.className = "breadcrumbs-widget-item";
+
+    button.setAttribute("tooltiptext", this.prettyPrintNodeAsText(aNode));
+
+    button.onkeypress = function onBreadcrumbsKeypress(e) {
+      if (e.charCode == Ci.nsIDOMKeyEvent.DOM_VK_SPACE ||
+          e.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_RETURN)
+        button.click();
+    }
+
+    button.onBreadcrumbsClick = function onBreadcrumbsClick() {
+      this.selection.setNode(aNode, "breadcrumbs");
+    }.bind(this);
+
+    button.onclick = (function _onBreadcrumbsRightClick(event) {
+      button.focus();
+      if (event.button == 2) {
+        this.openSiblingMenu(button, aNode);
+      }
+    }).bind(this);
+
+    button.onBreadcrumbsHold = (function _onBreadcrumbsHold() {
+      this.openSiblingMenu(button, aNode);
+    }).bind(this);
+    return button;
+  },
+
+  /**
+   * Connecting the end of the breadcrumbs to a node.
+   *
+   * @param aNode The node to reach.
+   */
+  expand: function BC_expand(aNode)
+  {
+      let fragment = this.chromeDoc.createDocumentFragment();
+      let toAppend = aNode;
+      let lastButtonInserted = null;
+      let originalLength = this.nodeHierarchy.length;
+      let stopNode = null;
+      if (originalLength > 0) {
+        stopNode = this.nodeHierarchy[originalLength - 1].node;
+      }
+      while (toAppend && toAppend.tagName && toAppend != stopNode) {
+        let button = this.buildButton(toAppend);
+        fragment.insertBefore(button, lastButtonInserted);
+        lastButtonInserted = button;
+        this.nodeHierarchy.splice(originalLength, 0, {node: toAppend, button: button});
+        toAppend = this.DOMHelpers.getParentObject(toAppend);
+      }
+      this.container.appendChild(fragment, this.container.firstChild);
+  },
+
+  /**
+   * Get a child of a node that can be displayed in the breadcrumbs
+   * and that is probably visible. See LOW_PRIORITY_ELEMENTS.
+   *
+   * @param aNode The parent node.
+   * @returns nsIDOMNode|null
+   */
+  getInterestingFirstNode: function BC_getInterestingFirstNode(aNode)
+  {
+    let nextChild = this.DOMHelpers.getChildObject(aNode, 0);
+    let fallback = null;
+
+    while (nextChild) {
+      if (nextChild.nodeType == aNode.ELEMENT_NODE) {
+        if (!(nextChild.tagName in LOW_PRIORITY_ELEMENTS)) {
+          return nextChild;
+        }
+        if (!fallback) {
+          fallback = nextChild;
+        }
+      }
+      nextChild = this.DOMHelpers.getNextSibling(nextChild);
+    }
+    return fallback;
+  },
+
+
+  /**
+   * Find the "youngest" ancestor of a node which is already in the breadcrumbs.
+   *
+   * @param aNode
+   * @returns Index of the ancestor in the cache
+   */
+  getCommonAncestor: function BC_getCommonAncestor(aNode)
+  {
+    let node = aNode;
+    while (node) {
+      let idx = this.indexOf(node);
+      if (idx > -1) {
+        return idx;
+      } else {
+        node = this.DOMHelpers.getParentObject(node);
+      }
+    }
+    return -1;
+  },
+
+  /**
+   * Make sure that the latest node in the breadcrumbs is not the selected node
+   * if the selected node still has children.
+   */
+  ensureFirstChild: function BC_ensureFirstChild()
+  {
+    // If the last displayed node is the selected node
+    if (this.currentIndex == this.nodeHierarchy.length - 1) {
+      let node = this.nodeHierarchy[this.currentIndex].node;
+      let child = this.getInterestingFirstNode(node);
+      // If the node has a child
+      if (child) {
+        // Show this child
+        this.expand(child);
+      }
+    }
+  },
+
+  /**
+   * Ensure the selected node is visible.
+   */
+  scroll: function BC_scroll()
+  {
+    // FIXME bug 684352: make sure its immediate neighbors are visible too.
+
+    let scrollbox = this.container;
+    let element = this.nodeHierarchy[this.currentIndex].button;
+
+    // Repeated calls to ensureElementIsVisible would interfere with each other
+    // and may sometimes result in incorrect scroll positions.
+    this.chromeWin.clearTimeout(this._ensureVisibleTimeout);
+    this._ensureVisibleTimeout = this.chromeWin.setTimeout(function() {
+      scrollbox.ensureElementIsVisible(element);
+    }, ENSURE_SELECTION_VISIBLE_DELAY);
+  },
+
+  updateSelectors: function BC_updateSelectors()
+  {
+    for (let i = this.nodeHierarchy.length - 1; i >= 0; i--) {
+      let crumb = this.nodeHierarchy[i];
+      let button = crumb.button;
+
+      while(button.hasChildNodes()) {
+        button.removeChild(button.firstChild);
+      }
+      button.appendChild(this.prettyPrintNodeAsXUL(crumb.node));
+      button.setAttribute("tooltiptext", this.prettyPrintNodeAsText(crumb.node));
+    }
+  },
+
+  /**
+   * Update the breadcrumbs display when a new node is selected.
+   */
+  update: function BC_update()
+  {
+    this.inspector.hideNodeMenu();
+
+    let cmdDispatcher = this.chromeDoc.commandDispatcher;
+    this.hadFocus = (cmdDispatcher.focusedElement &&
+                     cmdDispatcher.focusedElement.parentNode == this.container);
+
+    if (!this.selection.isConnected()) {
+      this.cutAfter(-1); // remove all the crumbs
+      return;
+    }
+
+    if (!this.selection.isElementNode()) {
+      this.setCursor(-1); // no selection
+      return;
+    }
+
+    let idx = this.indexOf(this.selection.node);
+
+    // Is the node already displayed in the breadcrumbs?
+    if (idx > -1) {
+      // Yes. We select it.
+      this.setCursor(idx);
+    } else {
+      // No. Is the breadcrumbs display empty?
+      if (this.nodeHierarchy.length > 0) {
+        // No. We drop all the element that are not direct ancestors
+        // of the selection
+        let parent = this.DOMHelpers.getParentObject(this.selection.node);
+        let idx = this.getCommonAncestor(parent);
+        this.cutAfter(idx);
+      }
+      // we append the missing button between the end of the breadcrumbs display
+      // and the current node.
+      this.expand(this.selection.node);
+
+      // we select the current node button
+      idx = this.indexOf(this.selection.node);
+      this.setCursor(idx);
+    }
+    // Add the first child of the very last node of the breadcrumbs if possible.
+    this.ensureFirstChild();
+    this.updateSelectors();
+
+    // Make sure the selected node and its neighbours are visible.
+    this.scroll();
+  },
+}
+
+XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () {
+  return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
+});
--- a/browser/devtools/inspector/CmdInspect.jsm
+++ b/browser/devtools/inspector/CmdInspect.jsm
@@ -5,18 +5,18 @@
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 this.EXPORTED_SYMBOLS = [ ];
 
 Cu.import("resource:///modules/devtools/gcli.jsm");
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 
 XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
                                   "resource:///modules/devtools/gDevTools.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "devtools",
-                                  "resource:///modules/devtools/gDevTools.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TargetFactory",
+                                  "resource:///modules/devtools/Target.jsm");
 
 /**
  * 'inspect' command
  */
 gcli.addCommand({
   name: "inspect",
   description: gcli.lookup("inspectDesc"),
   manual: gcli.lookup("inspectManual"),
@@ -25,15 +25,15 @@ gcli.addCommand({
       name: "selector",
       type: "node",
       description: gcli.lookup("inspectNodeDesc"),
       manual: gcli.lookup("inspectNodeManual")
     }
   ],
   exec: function Command_inspect(args, context) {
     let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
-    let target = devtools.TargetFactory.forTab(gBrowser.selectedTab);
+    let target = TargetFactory.forTab(gBrowser.selectedTab);
 
     return gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
       toolbox.getCurrentPanel().selection.setNode(args.selector, "gcli");
     }.bind(this));
   }
 });
new file mode 100644
--- /dev/null
+++ b/browser/devtools/inspector/Highlighter.jsm
@@ -0,0 +1,799 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource:///modules/devtools/EventEmitter.jsm");
+
+this.EXPORTED_SYMBOLS = ["Highlighter"];
+
+const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
+  // add ":visited" and ":link" after bug 713106 is fixed
+
+/**
+ * A highlighter mechanism.
+ *
+ * The highlighter is built dynamically into the browser element.
+ * The caller is in charge of destroying the highlighter (ie, the highlighter
+ * won't be destroyed if a new tab is selected for example).
+ *
+ * API:
+ *
+ *   // Constructor and destructor.
+ *   Highlighter(aTab, aInspector)
+ *   void destroy();
+ *
+ *   // Show and hide the highlighter
+ *   void show();
+ *   void hide();
+ *   boolean isHidden();
+ *
+ *   // Redraw the highlighter if the visible portion of the node has changed.
+ *   void invalidateSize(aScroll);
+ *
+ * Events:
+ *
+ *   "closed" - Highlighter is closing
+ *   "highlighting" - Highlighter is highlighting
+ *   "locked" - The selected node has been locked
+ *   "unlocked" - The selected ndoe has been unlocked
+ *
+ * Structure:
+ *  <stack class="highlighter-container">
+ *    <box class="highlighter-outline-container">
+ *      <box class="highlighter-outline" locked="true/false"/>
+ *    </box>
+ *    <box class="highlighter-controls">
+ *      <box class="highlighter-nodeinfobar-container" position="top/bottom" locked="true/false">
+ *        <box class="highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-top"/>
+ *        <hbox class="highlighter-nodeinfobar">
+ *          <toolbarbutton class="highlighter-nodeinfobar-inspectbutton highlighter-nodeinfobar-button"/>
+ *          <hbox class="highlighter-nodeinfobar-text">tagname#id.class1.class2</hbox>
+ *          <toolbarbutton class="highlighter-nodeinfobar-menu highlighter-nodeinfobar-button">…</toolbarbutton>
+ *        </hbox>
+ *        <box class="highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-bottom"/>
+ *      </box>
+ *    </box>
+ *  </stack>
+ *
+ */
+
+
+/**
+ * Constructor.
+ *
+ * @param aTarget The inspection target.
+ * @param aInspector Inspector panel.
+ * @param aToolbox The toolbox holding the inspector.
+ */
+this.Highlighter = function Highlighter(aTarget, aInspector, aToolbox)
+{
+  this.target = aTarget;
+  this.tab = aTarget.tab;
+  this.toolbox = aToolbox;
+  this.browser = this.tab.linkedBrowser;
+  this.chromeDoc = this.tab.ownerDocument;
+  this.chromeWin = this.chromeDoc.defaultView;
+  this.inspector = aInspector
+
+  EventEmitter.decorate(this);
+
+  this._init();
+}
+
+Highlighter.prototype = {
+  get selection() {
+    return this.inspector.selection;
+  },
+
+  _init: function Highlighter__init()
+  {
+    this.unlockAndFocus = this.unlockAndFocus.bind(this);
+    this.updateInfobar = this.updateInfobar.bind(this);
+    this.highlight = this.highlight.bind(this);
+
+    let stack = this.browser.parentNode;
+    this.win = this.browser.contentWindow;
+    this._highlighting = false;
+
+    this.highlighterContainer = this.chromeDoc.createElement("stack");
+    this.highlighterContainer.className = "highlighter-container";
+
+    this.outline = this.chromeDoc.createElement("box");
+    this.outline.className = "highlighter-outline";
+
+    let outlineContainer = this.chromeDoc.createElement("box");
+    outlineContainer.appendChild(this.outline);
+    outlineContainer.className = "highlighter-outline-container";
+
+    // The controlsBox will host the different interactive
+    // elements of the highlighter (buttons, toolbars, ...).
+    let controlsBox = this.chromeDoc.createElement("box");
+    controlsBox.className = "highlighter-controls";
+    this.highlighterContainer.appendChild(outlineContainer);
+    this.highlighterContainer.appendChild(controlsBox);
+
+    // Insert the highlighter right after the browser
+    stack.insertBefore(this.highlighterContainer, stack.childNodes[1]);
+
+    this.buildInfobar(controlsBox);
+
+    this.transitionDisabler = null;
+    this.pageEventsMuter = null;
+
+    this.unlockAndFocus();
+
+    this.selection.on("new-node", this.highlight);
+    this.selection.on("new-node", this.updateInfobar);
+    this.selection.on("pseudoclass", this.updateInfobar);
+    this.selection.on("attribute-changed", this.updateInfobar);
+
+    this.onToolSelected = function(event, id) {
+      if (id != "inspector") {
+        this.chromeWin.clearTimeout(this.pageEventsMuter);
+        this.detachMouseListeners();
+        this.disabled = true;
+        this.hide();
+      } else {
+        if (!this.locked) {
+          this.attachMouseListeners();
+        }
+        this.disabled = false;
+        this.show();
+      }
+    }.bind(this);
+    this.toolbox.on("select", this.onToolSelected);
+
+    this.hidden = true;
+    this.highlight();
+  },
+
+  /**
+   * Destroy the nodes. Remove listeners.
+   */
+  destroy: function Highlighter_destroy()
+  {
+    this.inspectButton.removeEventListener("command", this.unlockAndFocus);
+    this.inspectButton = null;
+
+    this.toolbox.off("select", this.onToolSelected);
+    this.toolbox = null;
+
+    this.selection.off("new-node", this.highlight);
+    this.selection.off("new-node", this.updateInfobar);
+    this.selection.off("pseudoclass", this.updateInfobar);
+    this.selection.off("attribute-changed", this.updateInfobar);
+
+    this.detachMouseListeners();
+    this.detachPageListeners();
+
+    this.chromeWin.clearTimeout(this.transitionDisabler);
+    this.chromeWin.clearTimeout(this.pageEventsMuter);
+    this.boundCloseEventHandler = null;
+    this._contentRect = null;
+    this._highlightRect = null;
+    this._highlighting = false;
+    this.outline = null;
+    this.nodeInfo = null;
+    this.highlighterContainer.parentNode.removeChild(this.highlighterContainer);
+    this.highlighterContainer = null;
+    this.win = null
+    this.browser = null;
+    this.chromeDoc = null;
+    this.chromeWin = null;
+    this.tabbrowser = null;
+
+    this.emit("closed");
+  },
+
+  /**
+   * Show the outline, and select a node.
+   */
+  highlight: function Highlighter_highlight()
+  {
+    if (this.selection.reason != "highlighter") {
+      this.lock();
+    }
+
+    let canHighlightNode = this.selection.isNode() &&
+                          this.selection.isConnected() &&
+                          this.selection.isElementNode();
+
+    if (canHighlightNode) {
+      if (this.selection.reason != "navigateaway") {
+        this.disabled = false;
+      }
+      this.show();
+      this.updateInfobar();
+      this.invalidateSize();
+      if (!this._highlighting &&
+          this.selection.reason != "highlighter") {
+        LayoutHelpers.scrollIntoViewIfNeeded(this.selection.node);
+      }
+    } else {
+      this.disabled = true;
+      this.hide();
+    }
+  },
+
+  /**
+   * Update the highlighter size and position.
+   */
+  invalidateSize: function Highlighter_invalidateSize()
+  {
+    let canHiglightNode = this.selection.isNode() &&
+                          this.selection.isConnected() &&
+                          this.selection.isElementNode();
+
+    if (!canHiglightNode)
+      return;
+
+    let clientRect = this.selection.node.getBoundingClientRect();
+    let rect = LayoutHelpers.getDirtyRect(this.selection.node);
+    this.highlightRectangle(rect);
+
+    this.moveInfobar();
+
+    if (this._highlighting) {
+      this.showOutline();
+      this.emit("highlighting");
+    }
+  },
+
+  /**
+   * Show the highlighter if it has been hidden.
+   */
+  show: function() {
+    if (!this.hidden || this.disabled) return;
+    this.showOutline();
+    this.showInfobar();
+    this.computeZoomFactor();
+    this.attachPageListeners();
+    this.invalidateSize();
+    this.hidden = false;
+  },
+
+  /**
+   * Hide the highlighter, the outline and the infobar.
+   */
+  hide: function() {
+    if (this.hidden) return;
+    this.hideOutline();
+    this.hideInfobar();
+    this.detachPageListeners();
+    this.hidden = true;
+  },
+
+  /**
+   * Is the highlighter visible?
+   *
+   * @return boolean
+   */
+  isHidden: function() {
+    return this.hidden;
+  },
+
+  /**
+   * Lock a node. Stops the inspection.
+   */
+  lock: function() {
+    if (this.locked === true) return;
+    this.outline.setAttribute("locked", "true");
+    this.nodeInfo.container.setAttribute("locked", "true");
+    this.detachMouseListeners();
+    this.locked = true;
+    this.emit("locked");
+  },
+
+  /**
+   * Start inspecting.
+   * Unlock the current node (if any), and select any node being hovered.
+   */
+  unlock: function() {
+    if (this.locked === false) return;
+    this.outline.removeAttribute("locked");
+    this.nodeInfo.container.removeAttribute("locked");
+    this.attachMouseListeners();
+    this.locked = false;
+    if (this.selection.isElementNode() &&
+        this.selection.isConnected()) {
+      this.showOutline();
+    }
+    this.emit("unlocked");
+  },
+
+  /**
+   * Focus the browser before unlocking.
+   */
+  unlockAndFocus: function Highlighter_unlockAndFocus() {
+    if (this.locked === false) return;
+    this.chromeWin.focus();
+    this.unlock();
+  },
+
+  /**
+   * Hide the infobar
+   */
+   hideInfobar: function Highlighter_hideInfobar() {
+     this.nodeInfo.container.setAttribute("force-transitions", "true");
+     this.nodeInfo.container.setAttribute("hidden", "true");
+   },
+
+  /**
+   * Show the infobar
+   */
+   showInfobar: function Highlighter_showInfobar() {
+     this.nodeInfo.container.removeAttribute("hidden");
+     this.moveInfobar();
+     this.nodeInfo.container.removeAttribute("force-transitions");
+   },
+
+  /**
+   * Hide the outline
+   */
+   hideOutline: function Highlighter_hideOutline() {
+     this.outline.setAttribute("hidden", "true");
+   },
+
+  /**
+   * Show the outline
+   */
+   showOutline: function Highlighter_showOutline() {
+     if (this._highlighting)
+       this.outline.removeAttribute("hidden");
+   },
+
+  /**
+   * Build the node Infobar.
+   *
+   * <box class="highlighter-nodeinfobar-container">
+   *   <box class="Highlighter-nodeinfobar-arrow-top"/>
+   *   <hbox class="highlighter-nodeinfobar">
+   *     <toolbarbutton class="highlighter-nodeinfobar-button highlighter-nodeinfobar-inspectbutton"/>
+   *     <hbox class="highlighter-nodeinfobar-text">
+   *       <xhtml:span class="highlighter-nodeinfobar-tagname"/>
+   *       <xhtml:span class="highlighter-nodeinfobar-id"/>
+   *       <xhtml:span class="highlighter-nodeinfobar-classes"/>
+   *       <xhtml:span class="highlighter-nodeinfobar-pseudo-classes"/>
+   *     </hbox>
+   *     <toolbarbutton class="highlighter-nodeinfobar-button highlighter-nodeinfobar-menu"/>
+   *   </hbox>
+   *   <box class="Highlighter-nodeinfobar-arrow-bottom"/>
+   * </box>
+   *
+   * @param nsIDOMElement aParent
+   *        The container of the infobar.
+   */
+  buildInfobar: function Highlighter_buildInfobar(aParent)
+  {
+    let container = this.chromeDoc.createElement("box");
+    container.className = "highlighter-nodeinfobar-container";
+    container.setAttribute("position", "top");
+    container.setAttribute("disabled", "true");
+
+    let nodeInfobar = this.chromeDoc.createElement("hbox");
+    nodeInfobar.className = "highlighter-nodeinfobar";
+
+    let arrowBoxTop = this.chromeDoc.createElement("box");
+    arrowBoxTop.className = "highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-top";
+
+    let arrowBoxBottom = this.chromeDoc.createElement("box");
+    arrowBoxBottom.className = "highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-bottom";
+
+    let tagNameLabel = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span");
+    tagNameLabel.className = "highlighter-nodeinfobar-tagname";
+
+    let idLabel = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span");
+    idLabel.className = "highlighter-nodeinfobar-id";
+
+    let classesBox = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span");
+    classesBox.className = "highlighter-nodeinfobar-classes";
+
+    let pseudoClassesBox = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span");
+    pseudoClassesBox.className = "highlighter-nodeinfobar-pseudo-classes";
+
+    // Add some content to force a better boundingClientRect down below.
+    pseudoClassesBox.textContent = "&nbsp;";
+
+    // Create buttons
+
+    this.inspectButton = this.chromeDoc.createElement("toolbarbutton");
+    this.inspectButton.className = "highlighter-nodeinfobar-button highlighter-nodeinfobar-inspectbutton"
+    let toolbarInspectButton = this.inspector.panelDoc.getElementById("inspector-inspect-toolbutton");
+    this.inspectButton.setAttribute("tooltiptext", toolbarInspectButton.getAttribute("tooltiptext"));
+    this.inspectButton.addEventListener("command", this.unlockAndFocus);
+
+    let nodemenu = this.chromeDoc.createElement("toolbarbutton");
+    nodemenu.setAttribute("type", "menu");
+    nodemenu.className = "highlighter-nodeinfobar-button highlighter-nodeinfobar-menu"
+    nodemenu.setAttribute("tooltiptext",
+                          this.strings.GetStringFromName("nodeMenu.tooltiptext"));
+
+    nodemenu.onclick = function() {
+      this.inspector.showNodeMenu(nodemenu, "after_start");
+    }.bind(this);
+
+    // <hbox class="highlighter-nodeinfobar-text"/>
+    let texthbox = this.chromeDoc.createElement("hbox");
+    texthbox.className = "highlighter-nodeinfobar-text";
+    texthbox.setAttribute("align", "center");
+    texthbox.setAttribute("flex", "1");
+
+    texthbox.addEventListener("mousedown", function(aEvent) {
+      // On click, show the node:
+      if (this.selection.isElementNode()) {
+        LayoutHelpers.scrollIntoViewIfNeeded(this.selection.node);
+      }
+    }.bind(this), true);
+
+    texthbox.appendChild(tagNameLabel);
+    texthbox.appendChild(idLabel);
+    texthbox.appendChild(classesBox);
+    texthbox.appendChild(pseudoClassesBox);
+
+    nodeInfobar.appendChild(this.inspectButton);
+    nodeInfobar.appendChild(texthbox);
+    nodeInfobar.appendChild(nodemenu);
+
+    container.appendChild(arrowBoxTop);
+    container.appendChild(nodeInfobar);
+    container.appendChild(arrowBoxBottom);
+
+    aParent.appendChild(container);
+
+    let barHeight = container.getBoundingClientRect().height;
+
+    this.nodeInfo = {
+      tagNameLabel: tagNameLabel,
+      idLabel: idLabel,
+      classesBox: classesBox,
+      pseudoClassesBox: pseudoClassesBox,
+      container: container,
+      barHeight: barHeight,
+    };
+  },
+
+  /**
+   * Highlight a rectangular region.
+   *
+   * @param object aRect
+   *        The rectangle region to highlight.
+   * @returns boolean
+   *          True if the rectangle was highlighted, false otherwise.
+   */
+  highlightRectangle: function Highlighter_highlightRectangle(aRect)
+  {
+    if (!aRect) {
+      this.unhighlight();
+      return;
+    }
+
+    let oldRect = this._contentRect;
+
+    if (oldRect && aRect.top == oldRect.top && aRect.left == oldRect.left &&
+        aRect.width == oldRect.width && aRect.height == oldRect.height) {
+      return; // same rectangle
+    }
+
+    let aRectScaled = LayoutHelpers.getZoomedRect(this.win, aRect);
+
+    if (aRectScaled.left >= 0 && aRectScaled.top >= 0 &&
+        aRectScaled.width > 0 && aRectScaled.height > 0) {
+
+      this.showOutline();
+
+      // The bottom div and the right div are flexibles (flex=1).
+      // We don't need to resize them.
+      let top = "top:" + aRectScaled.top + "px;";
+      let left = "left:" + aRectScaled.left + "px;";
+      let width = "width:" + aRectScaled.width + "px;";
+      let height = "height:" + aRectScaled.height + "px;";
+      this.outline.setAttribute("style", top + left + width + height);
+
+      this._highlighting = true;
+    } else {
+      this.unhighlight();
+    }
+
+    this._contentRect = aRect; // save orig (non-scaled) rect
+    this._highlightRect = aRectScaled; // and save the scaled rect.
+
+    return;
+  },
+
+  /**
+   * Clear the highlighter surface.
+   */
+  unhighlight: function Highlighter_unhighlight()
+  {
+    this._highlighting = false;
+    this.hideOutline();
+  },
+
+  /**
+   * Update node information (tagName#id.class)
+   */
+  updateInfobar: function Highlighter_updateInfobar()
+  {
+    if (!this.selection.isElementNode()) {
+      this.nodeInfo.tagNameLabel.textContent = "";
+      this.nodeInfo.idLabel.textContent = "";
+      this.nodeInfo.classesBox.textContent = "";
+      this.nodeInfo.pseudoClassesBox.textContent = "";
+      return;
+    }
+
+    let node = this.selection.node;
+
+    // Tag name
+    this.nodeInfo.tagNameLabel.textContent = node.tagName;
+
+    // ID
+    this.nodeInfo.idLabel.textContent = node.id ? "#" + node.id : "";
+
+    // Classes
+    let classes = this.nodeInfo.classesBox;
+
+    classes.textContent = node.classList.length ?
+                            "." + Array.join(node.classList, ".") : "";
+
+    // Pseudo-classes
+    let pseudos = PSEUDO_CLASSES.filter(function(pseudo) {
+      return DOMUtils.hasPseudoClassLock(node, pseudo);
+    }, this);
+
+    let pseudoBox = this.nodeInfo.pseudoClassesBox;
+    pseudoBox.textContent = pseudos.join("");
+  },
+
+  /**
+   * Move the Infobar to the right place in the highlighter.
+   */
+  moveInfobar: function Highlighter_moveInfobar()
+  {
+    if (this._highlightRect) {
+      let winHeight = this.win.innerHeight * this.zoom;
+      let winWidth = this.win.innerWidth * this.zoom;
+
+      let rect = {top: this._highlightRect.top,
+                  left: this._highlightRect.left,
+                  width: this._highlightRect.width,
+                  height: this._highlightRect.height};
+
+      rect.top = Math.max(rect.top, 0);
+      rect.left = Math.max(rect.left, 0);
+      rect.width = Math.max(rect.width, 0);
+      rect.height = Math.max(rect.height, 0);
+
+      rect.top = Math.min(rect.top, winHeight);
+      rect.left = Math.min(rect.left, winWidth);
+
+      this.nodeInfo.container.removeAttribute("disabled");
+      // Can the bar be above the node?
+      if (rect.top < this.nodeInfo.barHeight) {
+        // No. Can we move the toolbar under the node?
+        if (rect.top + rect.height +
+            this.nodeInfo.barHeight > winHeight) {
+          // No. Let's move it inside.
+          this.nodeInfo.container.style.top = rect.top + "px";
+          this.nodeInfo.container.setAttribute("position", "overlap");
+        } else {
+          // Yes. Let's move it under the node.
+          this.nodeInfo.container.style.top = rect.top + rect.height + "px";
+          this.nodeInfo.container.setAttribute("position", "bottom");
+        }
+      } else {
+        // Yes. Let's move it on top of the node.
+        this.nodeInfo.container.style.top =
+          rect.top - this.nodeInfo.barHeight + "px";
+        this.nodeInfo.container.setAttribute("position", "top");
+      }
+
+      let barWidth = this.nodeInfo.container.getBoundingClientRect().width;
+      let left = rect.left + rect.width / 2 - barWidth / 2;
+
+      // Make sure the whole infobar is visible
+      if (left < 0) {
+        left = 0;
+        this.nodeInfo.container.setAttribute("hide-arrow", "true");
+      } else {
+        if (left + barWidth > winWidth) {
+          left = winWidth - barWidth;
+          this.nodeInfo.container.setAttribute("hide-arrow", "true");
+        } else {
+          this.nodeInfo.container.removeAttribute("hide-arrow");
+        }
+      }
+      this.nodeInfo.container.style.left = left + "px";
+    } else {
+      this.nodeInfo.container.style.left = "0";
+      this.nodeInfo.container.style.top = "0";
+      this.nodeInfo.container.setAttribute("position", "top");
+      this.nodeInfo.container.setAttribute("hide-arrow", "true");
+    }
+  },
+
+  /**
+   * Store page zoom factor.
+   */
+  computeZoomFactor: function Highlighter_computeZoomFactor() {
+    this.zoom =
+      this.win.QueryInterface(Ci.nsIInterfaceRequestor)
+      .getInterface(Ci.nsIDOMWindowUtils)
+      .fullZoom;
+  },
+
+  /////////////////////////////////////////////////////////////////////////
+  //// Event Handling
+
+  attachMouseListeners: function Highlighter_attachMouseListeners()
+  {
+    this.browser.addEventListener("mousemove", this, true);
+    this.browser.addEventListener("click", this, true);
+    this.browser.addEventListener("dblclick", this, true);
+    this.browser.addEventListener("mousedown", this, true);
+    this.browser.addEventListener("mouseup", this, true);
+  },
+
+  detachMouseListeners: function Highlighter_detachMouseListeners()
+  {
+    this.browser.removeEventListener("mousemove", this, true);
+    this.browser.removeEventListener("click", this, true);
+    this.browser.removeEventListener("dblclick", this, true);
+    this.browser.removeEventListener("mousedown", this, true);
+    this.browser.removeEventListener("mouseup", this, true);
+  },
+
+  attachPageListeners: function Highlighter_attachPageListeners()
+  {
+    this.browser.addEventListener("resize", this, true);
+    this.browser.addEventListener("scroll", this, true);
+    this.browser.addEventListener("MozAfterPaint", this, true);
+  },
+
+  detachPageListeners: function Highlighter_detachPageListeners()
+  {
+    this.browser.removeEventListener("resize", this, true);
+    this.browser.removeEventListener("scroll", this, true);
+    this.browser.removeEventListener("MozAfterPaint", this, true);
+  },
+
+  /**
+   * Generic event handler.
+   *
+   * @param nsIDOMEvent aEvent
+   *        The DOM event object.
+   */
+  handleEvent: function Highlighter_handleEvent(aEvent)
+  {
+    switch (aEvent.type) {
+      case "click":
+        this.handleClick(aEvent);
+        break;
+      case "mousemove":
+        this.brieflyIgnorePageEvents();
+        this.handleMouseMove(aEvent);
+        break;
+      case "resize":
+        this.computeZoomFactor();
+        break;
+      case "MozAfterPaint":
+      case "scroll":
+        this.brieflyDisableTransitions();
+        this.invalidateSize();
+        break;
+      case "dblclick":
+      case "mousedown":
+      case "mouseup":
+        aEvent.stopPropagation();
+        aEvent.preventDefault();
+        break;
+    }
+  },
+
+  /**
+   * Disable the CSS transitions for a short time to avoid laggy animations
+   * during scrolling or resizing.
+   */
+  brieflyDisableTransitions: function Highlighter_brieflyDisableTransitions()
+  {
+    if (this.transitionDisabler) {
+      this.chromeWin.clearTimeout(this.transitionDisabler);
+    } else {
+      this.outline.setAttribute("disable-transitions", "true");
+      this.nodeInfo.container.setAttribute("disable-transitions", "true");
+    }
+    this.transitionDisabler =
+      this.chromeWin.setTimeout(function() {
+        this.outline.removeAttribute("disable-transitions");
+        this.nodeInfo.container.removeAttribute("disable-transitions");
+        this.transitionDisabler = null;
+      }.bind(this), 500);
+  },
+
+  /**
+   * Don't listen to page events while inspecting with the mouse.
+   */
+  brieflyIgnorePageEvents: function Highlighter_brieflyIgnorePageEvents()
+  {
+    // The goal is to keep smooth animations while inspecting.
+    // CSS Transitions might be interrupted because of a MozAfterPaint
+    // event that would triger an invalidateSize() call.
+    // So we don't listen to events that would trigger an invalidateSize()
+    // call.
+    //
+    // Side effect, zoom levels are not updated during this short period.
+    // It's very unlikely this would happen, but just in case, we call
+    // computeZoomFactor() when reattaching the events.
+    if (this.pageEventsMuter) {
+      this.chromeWin.clearTimeout(this.pageEventsMuter);
+    } else {
+      this.detachPageListeners();
+    }
+    this.pageEventsMuter =
+      this.chromeWin.setTimeout(function() {
+        this.attachPageListeners();
+        // Just in case the zoom level changed while ignoring the paint events
+        this.computeZoomFactor();
+        this.pageEventsMuter = null;
+      }.bind(this), 500);
+  },
+
+  /**
+   * Handle clicks.
+   *
+   * @param nsIDOMEvent aEvent
+   *        The DOM event.
+   */
+  handleClick: function Highlighter_handleClick(aEvent)
+  {
+    // Stop inspection when the user clicks on a node.
+    if (aEvent.button == 0) {
+      this.lock();
+      let node = this.selection.node;
+      this.selection.setNode(node, "highlighter-lock");
+      aEvent.preventDefault();
+      aEvent.stopPropagation();
+    }
+  },
+
+  /**
+   * Handle mousemoves in panel.
+   *
+   * @param nsiDOMEvent aEvent
+   *        The MouseEvent triggering the method.
+   */
+  handleMouseMove: function Highlighter_handleMouseMove(aEvent)
+  {
+    let doc = aEvent.target.ownerDocument;
+
+    // This should never happen, but just in case, we don't let the
+    // highlighter highlight browser nodes.
+    if (doc && doc != this.chromeDoc) {
+      let element = LayoutHelpers.getElementFromPoint(aEvent.target.ownerDocument,
+        aEvent.clientX, aEvent.clientY);
+      if (element && element != this.selection.node) {
+        this.selection.setNode(element, "highlighter");
+      }
+    }
+  },
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () {
+  return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils)
+});
+
+XPCOMUtils.defineLazyGetter(Highlighter.prototype, "strings", function () {
+    return Services.strings.createBundle(
+            "chrome://browser/locale/devtools/inspector.properties");
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/inspector/InspectorPanel.jsm
@@ -0,0 +1,640 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+this.EXPORTED_SYMBOLS = ["InspectorPanel"];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
+Cu.import("resource:///modules/devtools/EventEmitter.jsm");
+Cu.import("resource:///modules/devtools/CssLogic.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "MarkupView",
+  "resource:///modules/devtools/MarkupView.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Selection",
+  "resource:///modules/devtools/Selection.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "HTMLBreadcrumbs",
+  "resource:///modules/devtools/Breadcrumbs.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Highlighter",
+  "resource:///modules/devtools/Highlighter.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ToolSidebar",
+  "resource:///modules/devtools/Sidebar.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SelectorSearch",
+  "resource:///modules/devtools/SelectorSearch.jsm");
+
+const LAYOUT_CHANGE_TIMER = 250;
+
+/**
+ * Represents an open instance of the Inspector for a tab.
+ * The inspector controls the highlighter, the breadcrumbs,
+ * the markup view, and the sidebar (computed view, rule view
+ * and layout view).
+ */
+this.InspectorPanel = function InspectorPanel(iframeWindow, toolbox) {
+  this._toolbox = toolbox;
+  this._target = toolbox._target;
+  this.panelDoc = iframeWindow.document;
+  this.panelWin = iframeWindow;
+  this.panelWin.inspector = this;
+
+  EventEmitter.decorate(this);
+}
+
+InspectorPanel.prototype = {
+  /**
+   * open is effectively an asynchronous constructor
+   */
+  open: function InspectorPanel_open() {
+    let deferred = Promise.defer();
+
+    this.onNavigatedAway = this.onNavigatedAway.bind(this);
+    this.target.on("navigate", this.onNavigatedAway);
+
+    this.nodemenu = this.panelDoc.getElementById("inspector-node-popup");
+    this.lastNodemenuItem = this.nodemenu.lastChild;
+    this._setupNodeMenu = this._setupNodeMenu.bind(this);
+    this._resetNodeMenu = this._resetNodeMenu.bind(this);
+    this.nodemenu.addEventListener("popupshowing", this._setupNodeMenu, true);
+    this.nodemenu.addEventListener("popuphiding", this._resetNodeMenu, true);
+
+    // Create an empty selection
+    this._selection = new Selection();
+    this.onNewSelection = this.onNewSelection.bind(this);
+    this.selection.on("new-node", this.onNewSelection);
+    this.onBeforeNewSelection = this.onBeforeNewSelection.bind(this);
+    this.selection.on("before-new-node", this.onBeforeNewSelection);
+    this.onDetached = this.onDetached.bind(this);
+    this.selection.on("detached", this.onDetached);
+
+    this.breadcrumbs = new HTMLBreadcrumbs(this);
+
+    if (this.target.isLocalTab) {
+      this.browser = this.target.tab.linkedBrowser;
+      this.scheduleLayoutChange = this.scheduleLayoutChange.bind(this);
+      this.browser.addEventListener("resize", this.scheduleLayoutChange, true);
+
+      this.highlighter = new Highlighter(this.target, this, this._toolbox);
+      let button = this.panelDoc.getElementById("inspector-inspect-toolbutton");
+      button.hidden = false;
+      this.onLockStateChanged = function() {
+        if (this.highlighter.locked) {
+          button.removeAttribute("checked");
+          this._toolbox.raise();
+        } else {
+          button.setAttribute("checked", "true");
+        }
+      }.bind(this);
+      this.highlighter.on("locked", this.onLockStateChanged);
+      this.highlighter.on("unlocked", this.onLockStateChanged);
+
+      // Show a warning when the debugger is paused.
+      // We show the warning only when the inspector
+      // is selected.
+      this.updateDebuggerPausedWarning = function() {
+        let notificationBox = this._toolbox.getNotificationBox();
+        let notification = notificationBox.getNotificationWithValue("inspector-script-paused");
+        if (!notification && this._toolbox.currentToolId == "inspector" &&
+            this.target.isThreadPaused) {
+          let message = this.strings.GetStringFromName("debuggerPausedWarning.message");
+          notificationBox.appendNotification(message,
+            "inspector-script-paused", "", notificationBox.PRIORITY_WARNING_HIGH);
+        }
+
+        if (notification && this._toolbox.currentToolId != "inspector") {
+          notificationBox.removeNotification(notification);
+        }
+
+        if (notification && !this.target.isThreadPaused) {
+          notificationBox.removeNotification(notification);
+        }
+
+      }.bind(this);
+      this.target.on("thread-paused", this.updateDebuggerPausedWarning);
+      this.target.on("thread-resumed", this.updateDebuggerPausedWarning);
+      this._toolbox.on("select", this.updateDebuggerPausedWarning);
+      this.updateDebuggerPausedWarning();
+    }
+
+    this._initMarkup();
+    this.isReady = false;
+
+    this.once("markuploaded", function() {
+      this.isReady = true;
+
+      // All the components are initialized. Let's select a node.
+      if (this.target.isLocalTab) {
+        let root = this.browser.contentDocument.documentElement;
+        this._selection.setNode(root);
+      } else if (this.target.window) {
+        let root = this.target.window.document.documentElement;
+        this._selection.setNode(root);
+      }
+
+      if (this.highlighter) {
+        this.highlighter.unlock();
+      }
+
+      this.emit("ready");
+      deferred.resolve(this);
+    }.bind(this));
+
+    this.setupSearchBox();
+    this.setupSidebar();
+
+    return deferred.promise;
+  },
+
+  /**
+   * Selection object (read only)
+   */
+  get selection() {
+    return this._selection;
+  },
+
+  /**
+   * Target getter.
+   */
+  get target() {
+    return this._target;
+  },
+
+  /**
+   * Target setter.
+   */
+  set target(value) {
+    this._target = value;
+  },
+
+  /**
+   * Expose gViewSourceUtils so that other tools can make use of them.
+   */
+  get viewSourceUtils() {
+    return this.panelWin.gViewSourceUtils;
+  },
+
+  /**
+   * Indicate that a tool has modified the state of the page.  Used to
+   * decide whether to show the "are you sure you want to navigate"
+   * notification.
+   */
+  markDirty: function InspectorPanel_markDirty() {
+    this.isDirty = true;
+  },
+
+  /**
+   * Hooks the searchbar to show result and auto completion suggestions.
+   */
+  setupSearchBox: function InspectorPanel_setupSearchBox() {
+    // Initiate the selectors search object.
+    let setNodeFunction = function(node) {
+      this.selection.setNode(node, "selectorsearch");
+    }.bind(this);
+    if (this.searchSuggestions) {
+      this.searchSuggestions.destroy();
+      this.searchSuggestions = null;
+    }
+    this.searchBox = this.panelDoc.getElementById("inspector-searchbox");
+    this.searchSuggestions = new SelectorSearch(this.browser.contentDocument,
+                                                this.searchBox,
+                                                setNodeFunction);
+  },
+
+  /**
+   * Build the sidebar.
+   */
+  setupSidebar: function InspectorPanel_setupSidebar() {
+    let tabbox = this.panelDoc.querySelector("#inspector-sidebar");
+    this.sidebar = new ToolSidebar(tabbox, this);
+
+    let defaultTab = Services.prefs.getCharPref("devtools.inspector.activeSidebar");
+
+    this._setDefaultSidebar = function(event, toolId) {
+      Services.prefs.setCharPref("devtools.inspector.activeSidebar", toolId);
+    }.bind(this);
+
+    this.sidebar.on("select", this._setDefaultSidebar);
+    this.toggleHighlighter = this.toggleHighlighter.bind(this);
+
+    this.sidebar.addTab("ruleview",
+                        "chrome://browser/content/devtools/cssruleview.xhtml",
+                        "ruleview" == defaultTab);
+
+    this.sidebar.addTab("computedview",
+                        "chrome://browser/content/devtools/computedview.xhtml",
+                        "computedview" == defaultTab);
+
+    if (Services.prefs.getBoolPref("devtools.fontinspector.enabled")) {
+      this.sidebar.addTab("fontinspector",
+                          "chrome://browser/content/devtools/fontinspector/font-inspector.xhtml",
+                          "fontinspector" == defaultTab);
+    }
+
+    this.sidebar.addTab("layoutview",
+                        "chrome://browser/content/devtools/layoutview/view.xhtml",
+                        "layoutview" == defaultTab);
+
+    let ruleViewTab = this.sidebar.getTab("ruleview");
+    ruleViewTab.addEventListener("mouseover", this.toggleHighlighter, false);
+    ruleViewTab.addEventListener("mouseout", this.toggleHighlighter, false);
+
+    this.sidebar.show();
+  },
+
+  /**
+   * Reset the inspector on navigate away.
+   */
+  onNavigatedAway: function InspectorPanel_onNavigatedAway(event, payload) {
+    let newWindow = payload._navPayload || payload;
+    this.selection.setNode(null);
+    this._destroyMarkup();
+    this.isDirty = false;
+    let self = this;
+
+    function onDOMReady() {
+      newWindow.removeEventListener("DOMContentLoaded", onDOMReady, true);
+
+      if (self._destroyed) {
+        return;
+      }
+
+      if (!self.selection.node) {
+        self.selection.setNode(newWindow.document.documentElement, "navigateaway");
+      }
+      self._initMarkup();
+      self.setupSearchBox();
+    }
+
+    if (newWindow.document.readyState == "loading") {
+      newWindow.addEventListener("DOMContentLoaded", onDOMReady, true);
+    } else {
+      onDOMReady();
+    }
+  },
+
+  /**
+   * When a new node is selected.
+   */
+  onNewSelection: function InspectorPanel_onNewSelection() {
+    this.cancelLayoutChange();
+  },
+
+  /**
+   * When a new node is selected, before the selection has changed.
+   */
+  onBeforeNewSelection: function InspectorPanel_onBeforeNewSelection(event,
+                                                                     node) {
+    if (this.breadcrumbs.indexOf(node) == -1) {
+      // only clear locks if we'd have to update breadcrumbs
+      this.clearPseudoClasses();
+    }
+  },
+
+  /**
+   * When a node is deleted, select its parent node.
+   */
+  onDetached: function InspectorPanel_onDetached(event, parentNode) {
+    this.cancelLayoutChange();
+    this.breadcrumbs.cutAfter(this.breadcrumbs.indexOf(parentNode));
+    this.selection.setNode(parentNode, "detached");
+  },
+
+  /**
+   * Destroy the inspector.
+   */
+  destroy: function InspectorPanel__destroy() {
+    if (this._destroyed) {
+      return Promise.resolve(null);
+    }
+    this._destroyed = true;
+
+    this.cancelLayoutChange();
+
+    if (this.browser) {
+      this.browser.removeEventListener("resize", this.scheduleLayoutChange, true);
+      this.browser = null;
+    }
+
+    this.target.off("navigate", this.onNavigatedAway);
+
+    if (this.highlighter) {
+      this.highlighter.off("locked", this.onLockStateChanged);
+      this.highlighter.off("unlocked", this.onLockStateChanged);
+      this.highlighter.destroy();
+    }
+
+    this.target.off("thread-paused", this.updateDebuggerPausedWarning);
+    this.target.off("thread-resumed", this.updateDebuggerPausedWarning);
+    this._toolbox.off("select", this.updateDebuggerPausedWarning);
+
+    this._toolbox = null;
+
+    this.sidebar.off("select", this._setDefaultSidebar);
+    this.sidebar.destroy();
+    this.sidebar = null;
+
+    this.nodemenu.removeEventListener("popupshowing", this._setupNodeMenu, true);
+    this.nodemenu.removeEventListener("popuphiding", this._resetNodeMenu, true);
+    this.breadcrumbs.destroy();
+    this.searchSuggestions.destroy();
+    this.selection.off("new-node", this.onNewSelection);
+    this.selection.off("before-new-node", this.onBeforeNewSelection);
+    this.selection.off("detached", this.onDetached);
+    this._destroyMarkup();
+    this._selection.destroy();
+    this._selection = null;
+    this.panelWin.inspector = null;
+    this.target = null;
+    this.panelDoc = null;
+    this.panelWin = null;
+    this.breadcrumbs = null;
+    this.searchSuggestions = null;
+    this.lastNodemenuItem = null;
+    this.nodemenu = null;
+    this.highlighter = null;
+
+    return Promise.resolve(null);
+  },
+
+  /**
+   * Show the node menu.
+   */
+  showNodeMenu: function InspectorPanel_showNodeMenu(aButton, aPosition, aExtraItems) {
+    if (aExtraItems) {
+      for (let item of aExtraItems) {
+        this.nodemenu.appendChild(item);
+      }
+    }
+    this.nodemenu.openPopup(aButton, aPosition, 0, 0, true, false);
+  },
+
+  hideNodeMenu: function InspectorPanel_hideNodeMenu() {
+    this.nodemenu.hidePopup();
+  },
+
+  /**
+   * Disable the delete item if needed. Update the pseudo classes.
+   */
+  _setupNodeMenu: function InspectorPanel_setupNodeMenu() {
+    // Set the pseudo classes
+    for (let name of ["hover", "active", "focus"]) {
+      let menu = this.panelDoc.getElementById("node-menu-pseudo-" + name);
+
+      if (this.selection.isElementNode()) {
+        let checked = DOMUtils.hasPseudoClassLock(this.selection.node, ":" + name);
+        menu.setAttribute("checked", checked);
+        menu.removeAttribute("disabled");
+      } else {
+        menu.setAttribute("disabled", "true");
+      }
+    }
+
+    // Disable delete item if needed
+    let deleteNode = this.panelDoc.getElementById("node-menu-delete");
+    if (this.selection.isRoot() || this.selection.isDocumentTypeNode()) {
+      deleteNode.setAttribute("disabled", "true");
+    } else {
+      deleteNode.removeAttribute("disabled");
+    }
+
+    // Disable / enable "Copy Unique Selector", "Copy inner HTML" &
+    // "Copy outer HTML" as appropriate
+    let unique = this.panelDoc.getElementById("node-menu-copyuniqueselector");
+    let copyInnerHTML = this.panelDoc.getElementById("node-menu-copyinner");
+    let copyOuterHTML = this.panelDoc.getElementById("node-menu-copyouter");
+    if (this.selection.isElementNode()) {
+      unique.removeAttribute("disabled");
+      copyInnerHTML.removeAttribute("disabled");
+      copyOuterHTML.removeAttribute("disabled");
+    } else {
+      unique.setAttribute("disabled", "true");
+      copyInnerHTML.setAttribute("disabled", "true");
+      copyOuterHTML.setAttribute("disabled", "true");
+    }
+  },
+
+  _resetNodeMenu: function InspectorPanel_resetNodeMenu() {
+    // Remove any extra items
+    while (this.lastNodemenuItem.nextSibling) {
+      let toDelete = this.lastNodemenuItem.nextSibling;
+      toDelete.parentNode.removeChild(toDelete);
+    }
+  },
+
+  _initMarkup: function InspectorPanel_initMarkup() {
+    let doc = this.panelDoc;
+
+    this._markupBox = doc.getElementById("markup-box");
+
+    // create tool iframe
+    this._markupFrame = doc.createElement("iframe");
+    this._markupFrame.setAttribute("flex", "1");
+    this._markupFrame.setAttribute("tooltip", "aHTMLTooltip");
+    this._markupFrame.setAttribute("context", "inspector-node-popup");
+
+    // This is needed to enable tooltips inside the iframe document.
+    this._boundMarkupFrameLoad = function InspectorPanel_initMarkupPanel_onload() {
+      this._markupFrame.contentWindow.focus();
+      this._onMarkupFrameLoad();
+    }.bind(this);
+    this._markupFrame.addEventListener("load", this._boundMarkupFrameLoad, true);
+
+    this._markupBox.setAttribute("hidden", true);
+    this._markupBox.appendChild(this._markupFrame);
+    this._markupFrame.setAttribute("src", "chrome://browser/content/devtools/markup-view.xhtml");
+  },
+
+  _onMarkupFrameLoad: function InspectorPanel__onMarkupFrameLoad() {
+    this._markupFrame.removeEventListener("load", this._boundMarkupFrameLoad, true);
+    delete this._boundMarkupFrameLoad;
+
+    this._markupBox.removeAttribute("hidden");
+
+    let controllerWindow = this._toolbox.doc.defaultView;
+    this.markup = new MarkupView(this, this._markupFrame, controllerWindow);
+
+    this.emit("markuploaded");
+  },
+
+  _destroyMarkup: function InspectorPanel__destroyMarkup() {
+    if (this._boundMarkupFrameLoad) {
+      this._markupFrame.removeEventListener("load", this._boundMarkupFrameLoad, true);
+      delete this._boundMarkupFrameLoad;
+    }
+
+    if (this.markup) {
+      this.markup.destroy();
+      delete this.markup;
+    }
+
+    if (this._markupFrame) {
+      this._markupFrame.parentNode.removeChild(this._markupFrame);
+      delete this._markupFrame;
+    }
+  },
+
+  /**
+   * Toggle a pseudo class.
+   */
+  togglePseudoClass: function InspectorPanel_togglePseudoClass(aPseudo) {
+    if (this.selection.isElementNode()) {
+      if (DOMUtils.hasPseudoClassLock(this.selection.node, aPseudo)) {
+        this.breadcrumbs.nodeHierarchy.forEach(function(crumb) {
+          DOMUtils.removePseudoClassLock(crumb.node, aPseudo);
+        });
+      } else {
+        let hierarchical = aPseudo == ":hover" || aPseudo == ":active";
+        let node = this.selection.node;
+        do {
+          DOMUtils.addPseudoClassLock(node, aPseudo);
+          node = node.parentNode;
+        } while (hierarchical && node.parentNode)
+      }
+    }
+    this.selection.emit("pseudoclass");
+    this.breadcrumbs.scroll();
+  },
+
+  /**
+   * Clear any pseudo-class locks applied to the current hierarchy.
+   */
+  clearPseudoClasses: function InspectorPanel_clearPseudoClasses() {
+    this.breadcrumbs.nodeHierarchy.forEach(function(crumb) {
+      try {
+        DOMUtils.clearPseudoClassLocks(crumb.node);
+      } catch(e) {
+       // Ignore dead nodes after navigation.
+      }
+    });
+  },
+
+  /**
+   * Toggle the highlighter when ruleview is hovered.
+   */
+  toggleHighlighter: function InspectorPanel_toggleHighlighter(event)
+  {
+    if (event.type == "mouseover") {
+      this.highlighter.hide();
+    }
+    else if (event.type == "mouseout") {
+      this.highlighter.show();
+    }
+  },
+
+  /**
+   * Copy the innerHTML of the selected Node to the clipboard.
+   */
+  copyInnerHTML: function InspectorPanel_copyInnerHTML()
+  {
+    if (!this.selection.isNode()) {
+      return;
+    }
+    let toCopy = this.selection.node.innerHTML;
+    if (toCopy) {
+      clipboardHelper.copyString(toCopy);
+    }
+  },
+
+  /**
+   * Copy the outerHTML of the selected Node to the clipboard.
+   */
+  copyOuterHTML: function InspectorPanel_copyOuterHTML()
+  {
+    if (!this.selection.isNode()) {
+      return;
+    }
+    let toCopy = this.selection.node.outerHTML;
+    if (toCopy) {
+      clipboardHelper.copyString(toCopy);
+    }
+  },
+
+  /**
+   * Copy a unique selector of the selected Node to the clipboard.
+   */
+  copyUniqueSelector: function InspectorPanel_copyUniqueSelector()
+  {
+    if (!this.selection.isNode()) {
+      return;
+    }
+
+    let toCopy = CssLogic.findCssSelector(this.selection.node);
+    if (toCopy) {
+      clipboardHelper.copyString(toCopy);
+    }
+  },
+
+  /**
+   * Delete the selected node.
+   */
+  deleteNode: function IUI_deleteNode() {
+    if (!this.selection.isNode() ||
+         this.selection.isRoot()) {
+      return;
+    }
+
+    let toDelete = this.selection.node;
+
+    let parent = this.selection.node.parentNode;
+
+    // If the markup panel is active, use the markup panel to delete
+    // the node, making this an undoable action.
+    if (this.markup) {
+      this.markup.deleteNode(toDelete);
+    } else {
+      // remove the node from content
+      parent.removeChild(toDelete);
+    }
+  },
+
+  /**
+   * Schedule a low-priority change event for things like paint
+   * and resize.
+   */
+  scheduleLayoutChange: function Inspector_scheduleLayoutChange()
+  {
+    if (this._timer) {
+      return null;
+    }
+    this._timer = this.panelWin.setTimeout(function() {
+      this.emit("layout-change");
+      this._timer = null;
+    }.bind(this), LAYOUT_CHANGE_TIMER);
+  },
+
+  /**
+   * Cancel a pending low-priority change event if any is
+   * scheduled.
+   */
+  cancelLayoutChange: function Inspector_cancelLayoutChange()
+  {
+    if (this._timer) {
+      this.panelWin.clearTimeout(this._timer);
+      delete this._timer;
+    }
+  },
+
+}
+
+/////////////////////////////////////////////////////////////////////////
+//// Initializers
+
+XPCOMUtils.defineLazyGetter(InspectorPanel.prototype, "strings",
+  function () {
+    return Services.strings.createBundle(
+            "chrome://browser/locale/devtools/inspector.properties");
+  });
+
+XPCOMUtils.defineLazyGetter(this, "clipboardHelper", function() {
+  return Cc["@mozilla.org/widget/clipboardhelper;1"].
+    getService(Ci.nsIClipboardHelper);
+});
+
+
+XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () {
+  return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
+});
--- a/browser/devtools/inspector/Makefile.in
+++ b/browser/devtools/inspector/Makefile.in
@@ -8,9 +8,8 @@ srcdir		= @srcdir@
 VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 include $(topsrcdir)/config/rules.mk
 
 libs::
 	$(NSINSTALL) $(srcdir)/*.jsm $(FINAL_TARGET)/modules/devtools/
-	$(NSINSTALL) $(srcdir)/*.js $(FINAL_TARGET)/modules/devtools/inspector
new file mode 100644
--- /dev/null
+++ b/browser/devtools/inspector/Selection.jsm
@@ -0,0 +1,235 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+const Cu = Components.utils;
+Cu.import("resource:///modules/devtools/EventEmitter.jsm");
+
+this.EXPORTED_SYMBOLS = ["Selection"];
+
+/**
+ * API
+ *
+ *   new Selection(node=null, track={attributes,detached});
+ *   destroy()
+ *   node (readonly)
+ *   setNode(node, origin="unknown")
+ *
+ * Helpers:
+ *
+ *   window
+ *   document
+ *   isRoot()
+ *   isNode()
+ *   isHTMLNode()
+ *
+ * Check the nature of the node:
+ *
+ *   isElementNode()
+ *   isAttributeNode()
+ *   isTextNode()
+ *   isCDATANode()
+ *   isEntityRefNode()
+ *   isEntityNode()
+ *   isProcessingInstructionNode()
+ *   isCommentNode()
+ *   isDocumentNode()
+ *   isDocumentTypeNode()
+ *   isDocumentFragmentNode()
+ *   isNotationNode()
+ *
+ * Events:
+ *   "new-node" when the inner node changed
+ *   "before-new-node" when the inner node is set to change
+ *   "attribute-changed" when an attribute is changed (only if tracked)
+ *   "detached" when the node (or one of its parents) is removed from the document (only if tracked)
+ *   "reparented" when the node (or one of its parents) is moved under a different node (only if tracked)
+ */
+
+/**
+ * A Selection object. Hold a reference to a node.
+ * Includes some helpers, fire some helpful events.
+ *
+ * @param node Inner node.
+ *    Can be null. Can be (un)set in the future via the "node" property;
+ * @param trackAttribute Tell if events should be fired when the attributes of
+ *    the ndoe change.
+ *
+ */
+this.Selection = function Selection(node=null, track={attributes:true,detached:true}) {
+  EventEmitter.decorate(this);
+  this._onMutations = this._onMutations.bind(this);
+  this.track = track;
+  this.setNode(node);
+}
+
+Selection.prototype = {
+  _node: null,
+
+  _onMutations: function(mutations) {
+    let attributeChange = false;
+    let detached = false;
+    let parentNode = null;
+    for (let m of mutations) {
+      if (!attributeChange && m.type == "attributes") {
+        attributeChange = true;
+      }
+      if (m.type == "childList") {
+        if (!detached && !this.isConnected()) {
+          parentNode = m.target;
+          detached = true;
+        }
+      }
+    }
+
+    if (attributeChange)
+      this.emit("attribute-changed");
+    if (detached)
+      this.emit("detached", parentNode);
+  },
+
+  _attachEvents: function SN__attachEvents() {
+    if (!this.window || !this.isNode() || !this.track) {
+      return;
+    }
+
+    if (this.track.attributes) {
+      this._nodeObserver = new this.window.MutationObserver(this._onMutations);
+      this._nodeObserver.observe(this.node, {attributes: true});
+    }
+
+    if (this.track.detached) {
+      this._docObserver = new this.window.MutationObserver(this._onMutations);
+      this._docObserver.observe(this.document.documentElement, {childList: true, subtree: true});
+    }
+  },
+
+  _detachEvents: function SN__detachEvents() {
+    // `disconnect` fail if node's document has
+    // been deleted.
+    try {
+      if (this._nodeObserver)
+        this._nodeObserver.disconnect();
+    } catch(e) {}
+    try {
+      if (this._docObserver)
+        this._docObserver.disconnect();
+    } catch(e) {}
+  },
+
+  destroy: function SN_destroy() {
+    this._detachEvents();
+    this.setNode(null);
+  },
+
+  setNode: function SN_setNode(value, reason="unknown") {
+    this.reason = reason;
+    if (value !== this._node) {
+      this.emit("before-new-node", value, reason);
+      let previousNode = this._node;
+      this._detachEvents();
+      this._node = value;
+      this._attachEvents();
+      this.emit("new-node", previousNode, this.reason);
+    }
+  },
+
+  get node() {
+    return this._node;
+  },
+
+  get window() {
+    if (this.isNode()) {
+      return this.node.ownerDocument.defaultView;
+    }
+    return null;
+  },
+
+  get document() {
+    if (this.isNode()) {
+      return this.node.ownerDocument;
+    }
+    return null;
+  },
+
+  isRoot: function SN_isRootNode() {
+    return this.isNode() &&
+           this.isConnected() &&
+           this.node.ownerDocument.documentElement === this.node;
+  },
+
+  isNode: function SN_isNode() {
+    return (this.node &&
+            !Components.utils.isDeadWrapper(this.node) &&
+            this.node.ownerDocument &&
+            this.node.ownerDocument.defaultView &&
+            this.node instanceof this.node.ownerDocument.defaultView.Node);
+  },
+
+  isConnected: function SN_isConnected() {
+    try {
+      let doc = this.document;
+      return doc && doc.defaultView && doc.documentElement.contains(this.node);
+    } catch (e) {
+      // "can't access dead object" error
+      return false;
+    }
+  },
+
+  isHTMLNode: function SN_isHTMLNode() {
+    let xhtml_ns = "http://www.w3.org/1999/xhtml";
+    return this.isNode() && this.node.namespaceURI == xhtml_ns;
+  },
+
+  // Node type
+
+  isElementNode: function SN_isElementNode() {
+    return this.isNode() && this.node.nodeType == this.window.Node.ELEMENT_NODE;
+  },
+
+  isAttributeNode: function SN_isAttributeNode() {
+    return this.isNode() && this.node.nodeType == this.window.Node.ATTRIBUTE_NODE;
+  },
+
+  isTextNode: function SN_isTextNode() {
+    return this.isNode() && this.node.nodeType == this.window.Node.TEXT_NODE;
+  },
+
+  isCDATANode: function SN_isCDATANode() {
+    return this.isNode() && this.node.nodeType == this.window.Node.CDATA_SECTION_NODE;
+  },
+
+  isEntityRefNode: function SN_isEntityRefNode() {
+    return this.isNode() && this.node.nodeType == this.window.Node.ENTITY_REFERENCE_NODE;
+  },
+
+  isEntityNode: function SN_isEntityNode() {
+    return this.isNode() && this.node.nodeType == this.window.Node.ENTITY_NODE;
+  },
+
+  isProcessingInstructionNode: function SN_isProcessingInstructionNode() {
+    return this.isNode() && this.node.nodeType == this.window.Node.PROCESSING_INSTRUCTION_NODE;
+  },
+
+  isCommentNode: function SN_isCommentNode() {
+    return this.isNode() && this.node.nodeType == this.window.Node.PROCESSING_INSTRUCTION_NODE;
+  },
+
+  isDocumentNode: function SN_isDocumentNode() {
+    return this.isNode() && this.node.nodeType == this.window.Node.DOCUMENT_NODE;
+  },
+
+  isDocumentTypeNode: function SN_isDocumentTypeNode() {
+    return this.isNode() && this.node.nodeType ==this.window. Node.DOCUMENT_TYPE_NODE;
+  },
+
+  isDocumentFragmentNode: function SN_isDocumentFragmentNode() {
+    return this.isNode() && this.node.nodeType == this.window.Node.DOCUMENT_FRAGMENT_NODE;
+  },
+
+  isNotationNode: function SN_isNotationNode() {
+    return this.isNode() && this.node.nodeType == this.window.Node.NOTATION_NODE;
+  },
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/inspector/SelectorSearch.jsm
@@ -0,0 +1,549 @@
+/* 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 Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "AutocompletePopup",
+                                  "resource:///modules/devtools/AutocompletePopup.jsm");
+this.EXPORTED_SYMBOLS = ["SelectorSearch"];
+
+// Maximum number of selector suggestions shown in the panel.
+const MAX_SUGGESTIONS = 15;
+
+/**
+ * Converts any input box on a page to a CSS selector search and suggestion box.
+ *
+ * @constructor
+ * @param nsIDOMDocument aContentDocument
+ *        The content document which inspector is attached to.
+ * @param nsiInputElement aInputNode
+ *        The input element to which the panel will be attached and from where
+ *        search input will be taken.
+ * @param Function aCallback
+ *        The method to callback when a search is available.
+ *        This method is called with the matched node as the first argument.
+ */
+this.SelectorSearch = function(aContentDocument, aInputNode, aCallback) {
+  this.doc = aContentDocument;
+  this.callback = aCallback;
+  this.searchBox = aInputNode;
+  this.panelDoc = this.searchBox.ownerDocument;
+
+  // initialize variables.
+  this._lastSearched = null;
+  this._lastValidSearch = "";
+  this._lastToLastValidSearch = null;
+  this._searchResults = null;
+  this._searchSuggestions = {};
+  this._searchIndex = 0;
+
+  // bind!
+  this._showPopup = this._showPopup.bind(this);
+  this._onHTMLSearch = this._onHTMLSearch.bind(this);
+  this._onSearchKeypress = this._onSearchKeypress.bind(this);
+  this._onListBoxKeypress = this._onListBoxKeypress.bind(this);
+
+  // Options for the AutocompletePopup.
+  let options = {
+    panelId: "inspector-searchbox-panel",
+    listBoxId: "searchbox-panel-listbox",
+    fixedWidth: true,
+    autoSelect: true,
+    position: "before_start",
+    direction: "ltr",
+    onClick: this._onListBoxKeypress,
+    onKeypress: this._onListBoxKeypress,
+  };
+  this.searchPopup = new AutocompletePopup(this.panelDoc, options);
+
+  // event listeners.
+  this.searchBox.addEventListener("command", this._onHTMLSearch, true);
+  this.searchBox.addEventListener("keypress", this._onSearchKeypress, true);
+}
+
+this.SelectorSearch.prototype = {
+
+  // The possible states of the query.
+  States: {
+    CLASS: "class",
+    ID: "id",
+    TAG: "tag",
+  },
+
+  // The current state of the query.
+  _state: null,
+
+  // The query corresponding to last state computation.
+  _lastStateCheckAt: null,
+
+  /**
+   * Computes the state of the query. State refers to whether the query
+   * currently requires a class suggestion, or a tag, or an Id suggestion.
+   * This getter will effectively compute the state by traversing the query
+   * character by character each time the query changes.
+   *
+   * @example
+   *        '#f' requires an Id suggestion, so the state is States.ID
+   *        'div > .foo' requires class suggestion, so state is States.CLASS
+   */
+  get state() {
+    if (!this.searchBox || !this.searchBox.value) {
+      return null;
+    }
+
+    let query = this.searchBox.value;
+    if (this._lastStateCheckAt == query) {
+      // If query is the same, return early.
+      return this._state;
+    }
+    this._lastStateCheckAt = query;
+
+    this._state = null;
+    let subQuery = "";
+    // Now we iterate over the query and decide the state character by character.
+    // The logic here is that while iterating, the state can go from one to
+    // another with some restrictions. Like, if the state is Class, then it can
+    // never go to Tag state without a space or '>' character; Or like, a Class
+    // state with only '.' cannot go to an Id state without any [a-zA-Z] after
+    // the '.' which means that '.#' is a selector matching a class name '#'.
+    // Similarily for '#.' which means a selctor matching an id '.'.
+    for (let i = 1; i <= query.length; i++) {
+      // Calculate the state.
+      subQuery = query.slice(0, i);
+      let [secondLastChar, lastChar] = subQuery.slice(-2);
+      switch (this._state) {
+        case null:
+          // This will happen only in the first iteration of the for loop.
+          lastChar = secondLastChar;
+        case this.States.TAG:
+          this._state = lastChar == "."
+            ? this.States.CLASS
+            : lastChar == "#"
+              ? this.States.ID
+              : this.States.TAG;
+          break;
+
+        case this.States.CLASS:
+          if (subQuery.match(/[\.]+[^\.]*$/)[0].length > 2) {
+            // Checks whether the subQuery has atleast one [a-zA-Z] after the '.'.
+            this._state = (lastChar == " " || lastChar == ">")
+            ? this.States.TAG
+            : lastChar == "#"
+              ? this.States.ID
+              : this.States.CLASS;
+          }
+          break;
+
+        case this.States.ID:
+          if (subQuery.match(/[#]+[^#]*$/)[0].length > 2) {
+            // Checks whether the subQuery has atleast one [a-zA-Z] after the '#'.
+            this._state = (lastChar == " " || lastChar == ">")
+            ? this.States.TAG
+            : lastChar == "."
+              ? this.States.CLASS
+              : this.States.ID;
+          }
+          break;
+      }
+    }
+    return this._state;
+  },
+
+  /**
+   * Removes event listeners and cleans up references.
+   */
+  destroy: function SelectorSearch_destroy() {
+    // event listeners.
+    this.searchBox.removeEventListener("command", this._onHTMLSearch, true);
+    this.searchBox.removeEventListener("keypress", this._onSearchKeypress, true);
+    this.searchPopup.destroy();
+    this.searchPopup = null;
+    this.searchBox = null;
+    this.doc = null;
+    this.panelDoc = null;
+    this._searchResults = null;
+    this._searchSuggestions = null;
+    this.callback = null;
+  },
+
+  /**
+   * The command callback for the input box. This function is automatically
+   * invoked as the user is typing if the input box type is search.
+   */
+  _onHTMLSearch: function SelectorSearch__onHTMLSearch() {
+    let query = this.searchBox.value;
+    if (query == this._lastSearched) {
+      return;
+    }
+    this._lastSearched = query;
+    this._searchIndex = 0;
+
+    if (query.length == 0) {
+      this._lastValidSearch = "";
+      this.searchBox.removeAttribute("filled");
+      this.searchBox.classList.remove("devtools-no-search-result");
+      if (this.searchPopup.isOpen) {
+        this.searchPopup.hidePopup();
+      }
+      return;
+    }
+
+    this.searchBox.setAttribute("filled", true);
+    try {
+      this._searchResults = this.doc.querySelectorAll(query);
+    }
+    catch (ex) {
+      this._searchResults = [];
+    }
+    if (this._searchResults.length > 0) {
+      this._lastValidSearch = query;
+      // Even though the selector matched atleast one node, there is still
+      // possibility of suggestions.
+      if (query.match(/[\s>+]$/)) {
+        // If the query has a space or '>' at the end, create a selector to match
+        // the children of the selector inside the search box by adding a '*'.
+        this._lastValidSearch += "*";
+      }
+      else if (query.match(/[\s>+][\.#a-zA-Z][\.#>\s+]*$/)) {
+        // If the query is a partial descendant selector which does not matches
+        // any node, remove the last incomplete part and add a '*' to match
+        // everything. For ex, convert 'foo > b' to 'foo > *' .
+        let lastPart = query.match(/[\s>+][\.#a-zA-Z][^>\s+]*$/)[0];
+        this._lastValidSearch = query.slice(0, -1 * lastPart.length + 1) + "*";
+      }
+
+      if (!query.slice(-1).match(/[\.#\s>+]/)) {
+        // Hide the popup if we have some matching nodes and the query is not
+        // ending with [.# >] which means that the selector is not at the
+        // beginning of a new class, tag or id.
+        if (this.searchPopup.isOpen) {
+          this.searchPopup.hidePopup();
+        }
+      }
+      else {
+        this.showSuggestions();
+      }
+      this.searchBox.classList.remove("devtools-no-search-result");
+      this.callback(this._searchResults[0]);
+    }
+    else {
+      if (query.match(/[\s>+]$/)) {
+        this._lastValidSearch = query + "*";
+      }
+      else if (query.match(/[\s>+][\.#a-zA-Z][\.#>\s+]*$/)) {
+        let lastPart = query.match(/[\s+>][\.#a-zA-Z][^>\s+]*$/)[0];
+        this._lastValidSearch = query.slice(0, -1 * lastPart.length + 1) + "*";
+      }
+      this.searchBox.classList.add("devtools-no-search-result");
+      this.showSuggestions();
+    }
+  },
+
+  /**
+   * Handles keypresses inside the input box.
+   */
+  _onSearchKeypress: function SelectorSearch__onSearchKeypress(aEvent) {
+    let query = this.searchBox.value;
+    switch(aEvent.keyCode) {
+      case aEvent.DOM_VK_ENTER:
+      case aEvent.DOM_VK_RETURN:
+        if (query == this._lastSearched) {
+          this._searchIndex = (this._searchIndex + 1) % this._searchResults.length;
+        }
+        else {
+          this._onHTMLSearch();
+          return;
+        }
+        break;
+
+      case aEvent.DOM_VK_UP:
+        if (this.searchPopup.isOpen && this.searchPopup.itemCount > 0) {
+          this.searchPopup.focus();
+          if (this.searchPopup.selectedIndex == this.searchPopup.itemCount - 1) {
+            this.searchPopup.selectedIndex =
+              Math.max(0, this.searchPopup.itemCount - 2);
+          }
+          else {
+            this.searchPopup.selectedIndex = this.searchPopup.itemCount - 1;
+          }
+          this.searchBox.value = this.searchPopup.selectedItem.label;
+        }
+        else if (--this._searchIndex < 0) {
+          this._searchIndex = this._searchResults.length - 1;
+        }
+        break;
+
+      case aEvent.DOM_VK_DOWN:
+        if (this.searchPopup.isOpen && this.searchPopup.itemCount > 0) {
+          this.searchPopup.focus();
+          this.searchPopup.selectedIndex = 0;
+          this.searchBox.value = this.searchPopup.selectedItem.label;
+        }
+        this._searchIndex = (this._searchIndex + 1) % this._searchResults.length;
+        break;
+
+      case aEvent.DOM_VK_TAB:
+        if (this.searchPopup.isOpen &&
+            this.searchPopup.getItemAtIndex(this.searchPopup.itemCount - 1)
+                .preLabel == query) {
+          this.searchPopup.selectedIndex = this.searchPopup.itemCount - 1;
+          this.searchBox.value = this.searchPopup.selectedItem.label;
+          this._onHTMLSearch();
+        }
+        break;
+
+      case aEvent.DOM_VK_BACK_SPACE:
+      case aEvent.DOM_VK_DELETE:
+        // need to throw away the lastValidSearch.
+        this._lastToLastValidSearch = null;
+        // This gets the most complete selector from the query. For ex.
+        // '.foo.ba' returns '.foo' , '#foo > .bar.baz' returns '#foo > .bar'
+        // '.foo +bar' returns '.foo +' and likewise.
+        this._lastValidSearch = (query.match(/(.*)[\.#][^\.# ]{0,}$/) ||
+                                 query.match(/(.*[\s>+])[a-zA-Z][^\.# ]{0,}$/) ||
+                                 ["",""])[1];
+        return;
+
+      default:
+        return;
+    }
+
+    aEvent.preventDefault();
+    aEvent.stopPropagation();
+    if (this._searchResults.length > 0) {
+      this.callback(this._searchResults[this._searchIndex]);
+    }
+  },
+
+  /**
+   * Handles keypress and mouse click on the suggestions richlistbox.
+   */
+  _onListBoxKeypress: function SelectorSearch__onListBoxKeypress(aEvent) {
+    switch(aEvent.keyCode || aEvent.button) {
+      case aEvent.DOM_VK_ENTER:
+      case aEvent.DOM_VK_RETURN:
+      case aEvent.DOM_VK_TAB:
+      case 0: // left mouse button
+        aEvent.stopPropagation();
+        aEvent.preventDefault();
+        this.searchBox.value = this.searchPopup.selectedItem.label;
+        this.searchBox.focus();
+        this._onHTMLSearch();
+        break;
+
+      case aEvent.DOM_VK_UP:
+        if (this.searchPopup.selectedIndex == 0) {
+          this.searchPopup.selectedIndex = -1;
+          aEvent.stopPropagation();
+          aEvent.preventDefault();
+          this.searchBox.focus();
+        }
+        else {
+          let index = this.searchPopup.selectedIndex;
+          this.searchBox.value = this.searchPopup.getItemAtIndex(index - 1).label;
+        }
+        break;
+
+      case aEvent.DOM_VK_DOWN:
+        if (this.searchPopup.selectedIndex == this.searchPopup.itemCount - 1) {
+          this.searchPopup.selectedIndex = -1;
+          aEvent.stopPropagation();
+          aEvent.preventDefault();
+          this.searchBox.focus();
+        }
+        else {
+          let index = this.searchPopup.selectedIndex;
+          this.searchBox.value = this.searchPopup.getItemAtIndex(index + 1).label;
+        }
+        break;
+
+      case aEvent.DOM_VK_BACK_SPACE:
+        aEvent.stopPropagation();
+        aEvent.preventDefault();
+        this.searchBox.focus();
+        if (this.searchBox.selectionStart > 0) {
+          this.searchBox.value =
+            this.searchBox.value.substring(0, this.searchBox.selectionStart - 1);
+        }
+        this._lastToLastValidSearch = null;
+        let query = this.searchBox.value;
+        this._lastValidSearch = (query.match(/(.*)[\.#][^\.# ]{0,}$/) ||
+                                 query.match(/(.*[\s>+])[a-zA-Z][^\.# ]{0,}$/) ||
+                                 ["",""])[1];
+        this._onHTMLSearch();
+        break;
+    }
+  },
+
+  
+  /**
+   * Populates the suggestions list and show the suggestion popup.
+   */
+  _showPopup: function SelectorSearch__showPopup(aList, aFirstPart) {
+    // Sort alphabetically in increaseing order.
+    aList = aList.sort();
+    // Sort based on count= in decreasing order.
+    aList = aList.sort(function([a1,a2], [b1,b2]) {
+      return a2 < b2;
+    });
+
+    let total = 0;
+    let query = this.searchBox.value;
+    let toLowerCase = false;
+    let items = [];
+    // In case of tagNames, change the case to small.
+    if (query.match(/.*[\.#][^\.#]{0,}$/) == null) {
+      toLowerCase = true;
+    }
+    for (let [value, count] of aList) {
+      // for cases like 'div ' or 'div >' or 'div+'
+      if (query.match(/[\s>+]$/)) {
+        value = query + value;
+      }
+      // for cases like 'div #a' or 'div .a' or 'div > d' and likewise
+      else if (query.match(/[\s>+][\.#a-zA-Z][^\s>+\.#]*$/)) {
+        let lastPart = query.match(/[\s>+][\.#a-zA-Z][^>\s+\.#]*$/)[0];
+        value = query.slice(0, -1 * lastPart.length + 1) + value;
+      }
+      // for cases like 'div.class' or '#foo.bar' and likewise
+      else if (query.match(/[a-zA-Z][#\.][^#\.\s+>]*$/)) {
+        let lastPart = query.match(/[a-zA-Z][#\.][^#\.\s>+]*$/)[0];
+        value = query.slice(0, -1 * lastPart.length + 1) + value;
+      }
+      let item = {
+        preLabel: query,
+        label: value,
+        count: count
+      };
+      if (toLowerCase) {
+        item.label = value.toLowerCase();
+      }
+      items.unshift(item);
+      if (++total > MAX_SUGGESTIONS - 1) {
+        break;
+      }
+    }
+    if (total > 0) {
+      this.searchPopup.setItems(items);
+      this.searchPopup.openPopup(this.searchBox);
+    }
+    else {
+      this.searchPopup.hidePopup();
+    }
+  },
+
+  /**
+   * Suggests classes,ids and tags based on the user input as user types in the
+   * searchbox.
+   */
+  showSuggestions: function SelectorSearch_showSuggestions() {
+    let query = this.searchBox.value;
+    if (this._lastValidSearch != "" &&
+        this._lastToLastValidSearch != this._lastValidSearch) {
+      this._searchSuggestions = {
+        ids: new Map(),
+        classes: new Map(),
+        tags: new Map(),
+      };
+
+      let nodes = [];
+      try {
+        nodes = this.doc.querySelectorAll(this._lastValidSearch);
+      } catch (ex) {}
+      for (let node of nodes) {
+        this._searchSuggestions.ids.set(node.id, 1);
+        this._searchSuggestions.tags
+            .set(node.tagName,
+                 (this._searchSuggestions.tags.get(node.tagName) || 0) + 1);
+        for (let className of node.classList) {
+          this._searchSuggestions.classes
+            .set(className,
+                 (this._searchSuggestions.classes.get(className) || 0) + 1);
+        }
+      }
+      this._lastToLastValidSearch = this._lastValidSearch;
+    }
+    else if (this._lastToLastValidSearch != this._lastValidSearch) {
+      this._searchSuggestions = {
+        ids: new Map(),
+        classes: new Map(),
+        tags: new Map(),
+      };
+
+      if (query.length == 0) {
+        return;
+      }
+
+      let nodes = null;
+      if (this.state == this.States.CLASS) {
+        nodes = this.doc.querySelectorAll("[class]");
+        for (let node of nodes) {
+          for (let className of node.classList) {
+            this._searchSuggestions.classes
+              .set(className,
+                   (this._searchSuggestions.classes.get(className) || 0) + 1);
+          }
+        }
+      }
+      else if (this.state == this.States.ID) {
+        nodes = this.doc.querySelectorAll("[id]");
+        for (let node of nodes) {
+          this._searchSuggestions.ids.set(node.id, 1);
+        }
+      }
+      else if (this.state == this.States.TAG) {
+        nodes = this.doc.getElementsByTagName("*");
+        for (let node of nodes) {
+          this._searchSuggestions.tags
+              .set(node.tagName,
+                   (this._searchSuggestions.tags.get(node.tagName) || 0) + 1);
+        }
+      }
+      else {
+        return;
+      }
+      this._lastToLastValidSearch = this._lastValidSearch;
+    }
+
+    // Filter the suggestions based on search box value.
+    let result = [];
+    let firstPart = "";
+    if (this.state == this.States.TAG) {
+      // gets the tag that is being completed. For ex. 'div.foo > s' returns 's',
+      // 'di' returns 'di' and likewise.
+      firstPart = (query.match(/[\s>+]?([a-zA-Z]*)$/) || ["",query])[1];
+      for (let [tag, count] of this._searchSuggestions.tags) {
+        if (tag.toLowerCase().startsWith(firstPart.toLowerCase())) {
+          result.push([tag, count]);
+        }
+      }
+    }
+    else if (this.state == this.States.CLASS) {
+      // gets the class that is being completed. For ex. '.foo.b' returns 'b'
+      firstPart = query.match(/\.([^\.]*)$/)[1];
+      for (let [className, count] of this._searchSuggestions.classes) {
+        if (className.startsWith(firstPart)) {
+          result.push(["." + className, count]);
+        }
+      }
+      firstPart = "." + firstPart;
+    }
+    else if (this.state == this.States.ID) {
+      // gets the id that is being completed. For ex. '.foo#b' returns 'b'
+      firstPart = query.match(/#([^#]*)$/)[1];
+      for (let [id, count] of this._searchSuggestions.ids) {
+        if (id.startsWith(firstPart)) {
+          result.push(["#" + id, 1]);
+        }
+      }
+      firstPart = "#" + firstPart;
+    }
+
+    this._showPopup(result, firstPart);
+  },
+};
deleted file mode 100644
--- a/browser/devtools/inspector/breadcrumbs.js
+++ /dev/null
@@ -1,597 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; 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/. */
-
-const {Cc, Cu, Ci} = require("chrome");
-
-const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
-const ENSURE_SELECTION_VISIBLE_DELAY = 50; // ms
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource:///modules/devtools/DOMHelpers.jsm");
-Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
-
-const LOW_PRIORITY_ELEMENTS = {
-  "HEAD": true,
-  "BASE": true,
-  "BASEFONT": true,
-  "ISINDEX": true,
-  "LINK": true,
-  "META": true,
-  "SCRIPT": true,
-  "STYLE": true,
-  "TITLE": true,
-};
-
-///////////////////////////////////////////////////////////////////////////
-//// HTML Breadcrumbs
-
-/**
- * Display the ancestors of the current node and its children.
- * Only one "branch" of children are displayed (only one line).
- *
- * FIXME: Bug 822388 - Use the BreadcrumbsWidget in the Inspector.
- *
- * Mechanism:
- * . If no nodes displayed yet:
- *    then display the ancestor of the selected node and the selected node;
- *   else select the node;
- * . If the selected node is the last node displayed, append its first (if any).
- */
-function HTMLBreadcrumbs(aInspector)
-{
-  this.inspector = aInspector;
-  this.selection = this.inspector.selection;
-  this.chromeWin = this.inspector.panelWin;
-  this.chromeDoc = this.inspector.panelDoc;
-  this.DOMHelpers = new DOMHelpers(this.chromeWin);
-  this._init();
-}
-
-exports.HTMLBreadcrumbs = HTMLBreadcrumbs;
-
-HTMLBreadcrumbs.prototype = {
-  _init: function BC__init()
-  {
-    this.container = this.chromeDoc.getElementById("inspector-breadcrumbs");
-    this.container.addEventListener("mousedown", this, true);
-    this.container.addEventListener("keypress", this, true);
-
-    // We will save a list of already displayed nodes in this array.
-    this.nodeHierarchy = [];
-
-    // Last selected node in nodeHierarchy.
-    this.currentIndex = -1;
-
-    // By default, hide the arrows. We let the <scrollbox> show them
-    // in case of overflow.
-    this.container.removeAttribute("overflows");
-    this.container._scrollButtonUp.collapsed = true;
-    this.container._scrollButtonDown.collapsed = true;
-
-    this.onscrollboxreflow = function() {
-      if (this.container._scrollButtonDown.collapsed)
-        this.container.removeAttribute("overflows");
-      else
-        this.container.setAttribute("overflows", true);
-    }.bind(this);
-
-    this.container.addEventListener("underflow", this.onscrollboxreflow, false);
-    this.container.addEventListener("overflow", this.onscrollboxreflow, false);
-
-    this.update = this.update.bind(this);
-    this.updateSelectors = this.updateSelectors.bind(this);
-    this.selection.on("new-node", this.update);
-    this.selection.on("pseudoclass", this.updateSelectors);
-    this.selection.on("attribute-changed", this.updateSelectors);
-    this.update();
-  },
-
-  /**
-   * Build a string that represents the node: tagName#id.class1.class2.
-   *
-   * @param aNode The node to pretty-print
-   * @returns a string
-   */
-  prettyPrintNodeAsText: function BC_prettyPrintNodeText(aNode)
-  {
-    let text = aNode.tagName.toLowerCase();
-    if (aNode.id) {
-      text += "#" + aNode.id;
-    }
-    for (let i = 0; i < aNode.classList.length; i++) {
-      text += "." + aNode.classList[i];
-    }
-    for (let i = 0; i < PSEUDO_CLASSES.length; i++) {
-      let pseudo = PSEUDO_CLASSES[i];
-      if (DOMUtils.hasPseudoClassLock(aNode, pseudo)) {
-        text += pseudo;
-      }
-    }
-
-    return text;
-  },
-
-
-  /**
-   * Build <label>s that represent the node:
-   *   <label class="breadcrumbs-widget-item-tag">tagName</label>
-   *   <label class="breadcrumbs-widget-item-id">#id</label>
-   *   <label class="breadcrumbs-widget-item-classes">.class1.class2</label>
-   *
-   * @param aNode The node to pretty-print
-   * @returns a document fragment.
-   */
-  prettyPrintNodeAsXUL: function BC_prettyPrintNodeXUL(aNode)
-  {
-    let fragment = this.chromeDoc.createDocumentFragment();
-
-    let tagLabel = this.chromeDoc.createElement("label");
-    tagLabel.className = "breadcrumbs-widget-item-tag plain";
-
-    let idLabel = this.chromeDoc.createElement("label");
-    idLabel.className = "breadcrumbs-widget-item-id plain";
-
-    let classesLabel = this.chromeDoc.createElement("label");
-    classesLabel.className = "breadcrumbs-widget-item-classes plain";
-
-    let pseudosLabel = this.chromeDoc.createElement("label");
-    pseudosLabel.className = "breadcrumbs-widget-item-pseudo-classes plain";
-
-    tagLabel.textContent = aNode.tagName.toLowerCase();
-    idLabel.textContent = aNode.id ? ("#" + aNode.id) : "";
-
-    let classesText = "";
-    for (let i = 0; i < aNode.classList.length; i++) {
-      classesText += "." + aNode.classList[i];
-    }
-    classesLabel.textContent = classesText;
-
-    let pseudos = PSEUDO_CLASSES.filter(function(pseudo) {
-      return DOMUtils.hasPseudoClassLock(aNode, pseudo);
-    }, this);
-    pseudosLabel.textContent = pseudos.join("");
-
-    fragment.appendChild(tagLabel);
-    fragment.appendChild(idLabel);
-    fragment.appendChild(classesLabel);
-    fragment.appendChild(pseudosLabel);
-
-    return fragment;
-  },
-
-  /**
-   * Open the sibling menu.
-   *
-   * @param aButton the button representing the node.
-   * @param aNode the node we want the siblings from.
-   */
-  openSiblingMenu: function BC_openSiblingMenu(aButton, aNode)
-  {
-    // We make sure that the targeted node is selected
-    // because we want to use the nodemenu that only works
-    // for inspector.selection
-    this.selection.setNode(aNode, "breadcrumbs");
-
-    let title = this.chromeDoc.createElement("menuitem");
-    title.setAttribute("label", this.inspector.strings.GetStringFromName("breadcrumbs.siblings"));
-    title.setAttribute("disabled", "true");
-
-    let separator = this.chromeDoc.createElement("menuseparator");
-
-    let items = [title, separator];
-
-    let nodes = aNode.parentNode.childNodes;
-    for (let i = 0; i < nodes.length; i++) {
-      if (nodes[i].nodeType == aNode.ELEMENT_NODE) {
-        let item = this.chromeDoc.createElement("menuitem");
-        if (nodes[i] === aNode) {
-          item.setAttribute("disabled", "true");
-          item.setAttribute("checked", "true");
-        }
-
-        item.setAttribute("type", "radio");
-        item.setAttribute("label", this.prettyPrintNodeAsText(nodes[i]));
-
-        let selection = this.selection;
-        item.onmouseup = (function(aNode) {
-          return function() {
-            selection.setNode(aNode, "breadcrumbs");
-          }
-        })(nodes[i]);
-
-        items.push(item);
-      }
-    }
-    this.inspector.showNodeMenu(aButton, "before_start", items);
-  },
-
-  /**
-   * Generic event handler.
-   *
-   * @param nsIDOMEvent event
-   *        The DOM event object.
-   */
-  handleEvent: function BC_handleEvent(event)
-  {
-    if (event.type == "mousedown" && event.button == 0) {
-      // on Click and Hold, open the Siblings menu
-
-      let timer;
-      let container = this.container;
-
-      function openMenu(event) {
-        cancelHold();
-        let target = event.originalTarget;
-        if (target.tagName == "button") {
-          target.onBreadcrumbsHold();
-        }
-      }
-
-      function handleClick(event) {
-        cancelHold();
-        let target = event.originalTarget;
-        if (target.tagName == "button") {
-          target.onBreadcrumbsClick();
-        }
-      }
-
-      let window = this.chromeWin;
-      function cancelHold(event) {
-        window.clearTimeout(timer);
-        container.removeEventListener("mouseout", cancelHold, false);
-        container.removeEventListener("mouseup", handleClick, false);
-      }
-
-      container.addEventListener("mouseout", cancelHold, false);
-      container.addEventListener("mouseup", handleClick, false);
-      timer = window.setTimeout(openMenu, 500, event);
-    }
-
-    if (event.type == "keypress" && this.selection.isElementNode()) {
-      let node = null;
-      switch (event.keyCode) {
-        case this.chromeWin.KeyEvent.DOM_VK_LEFT:
-          if (this.currentIndex != 0) {
-            node = this.nodeHierarchy[this.currentIndex - 1].node;
-          }
-          break;
-        case this.chromeWin.KeyEvent.DOM_VK_RIGHT:
-          if (this.currentIndex < this.nodeHierarchy.length - 1) {
-            node = this.nodeHierarchy[this.currentIndex + 1].node;
-          }
-          break;
-        case this.chromeWin.KeyEvent.DOM_VK_UP:
-          node = this.selection.node.previousSibling;
-          while (node && (node.nodeType != node.ELEMENT_NODE)) {
-            node = node.previousSibling;
-          }
-          break;
-        case this.chromeWin.KeyEvent.DOM_VK_DOWN:
-          node = this.selection.node.nextSibling;
-          while (node && (node.nodeType != node.ELEMENT_NODE)) {
-            node = node.nextSibling;
-          }
-          break;
-      }
-      if (node) {
-        this.selection.setNode(node, "breadcrumbs");
-      }
-      event.preventDefault();
-      event.stopPropagation();
-    }
-  },
-
-  /**
-   * Remove nodes and delete properties.
-   */
-  destroy: function BC_destroy()
-  {
-    this.nodeHierarchy.forEach(function(crumb) {
-      if (LayoutHelpers.isNodeConnected(crumb.node)) {
-        DOMUtils.clearPseudoClassLocks(crumb.node);
-      }
-    });
-
-    this.selection.off("new-node", this.update);
-    this.selection.off("pseudoclass", this.updateSelectors);
-    this.selection.off("attribute-changed", this.updateSelectors);
-
-    this.container.removeEventListener("underflow", this.onscrollboxreflow, false);
-    this.container.removeEventListener("overflow", this.onscrollboxreflow, false);
-    this.onscrollboxreflow = null;
-
-    this.empty();
-    this.container.removeEventListener("mousedown", this, true);
-    this.container.removeEventListener("keypress", this, true);
-    this.container = null;
-    this.nodeHierarchy = null;
-  },
-
-  /**
-   * Empty the breadcrumbs container.
-   */
-  empty: function BC_empty()
-  {
-    while (this.container.hasChildNodes()) {
-      this.container.removeChild(this.container.firstChild);
-    }
-  },
-
-  /**
-   * Re-init the cache and remove all the buttons.
-   */
-  invalidateHierarchy: function BC_invalidateHierarchy()
-  {
-    this.inspector.hideNodeMenu();
-    this.nodeHierarchy = [];
-    this.empty();
-  },
-
-  /**
-   * Set which button represent the selected node.
-   *
-   * @param aIdx Index of the displayed-button to select
-   */
-  setCursor: function BC_setCursor(aIdx)
-  {
-    // Unselect the previously selected button
-    if (this.currentIndex > -1 && this.currentIndex < this.nodeHierarchy.length) {
-      this.nodeHierarchy[this.currentIndex].button.removeAttribute("checked");
-    }
-    if (aIdx > -1) {
-      this.nodeHierarchy[aIdx].button.setAttribute("checked", "true");
-      if (this.hadFocus)
-        this.nodeHierarchy[aIdx].button.focus();
-    }
-    this.currentIndex = aIdx;
-  },
-
-  /**
-   * Get the index of the node in the cache.
-   *
-   * @param aNode
-   * @returns integer the index, -1 if not found
-   */
-  indexOf: function BC_indexOf(aNode)
-  {
-    let i = this.nodeHierarchy.length - 1;
-    for (let i = this.nodeHierarchy.length - 1; i >= 0; i--) {
-      if (this.nodeHierarchy[i].node === aNode) {
-        return i;
-      }
-    }
-    return -1;
-  },
-
-  /**
-   * Remove all the buttons and their references in the cache
-   * after a given index.
-   *
-   * @param aIdx
-   */
-  cutAfter: function BC_cutAfter(aIdx)
-  {
-    while (this.nodeHierarchy.length > (aIdx + 1)) {
-      let toRemove = this.nodeHierarchy.pop();
-      this.container.removeChild(toRemove.button);
-    }
-  },
-
-  /**
-   * Build a button representing the node.
-   *
-   * @param aNode The node from the page.
-   * @returns aNode The <button>.
-   */
-  buildButton: function BC_buildButton(aNode)
-  {
-    let button = this.chromeDoc.createElement("button");
-    button.appendChild(this.prettyPrintNodeAsXUL(aNode));
-    button.className = "breadcrumbs-widget-item";
-
-    button.setAttribute("tooltiptext", this.prettyPrintNodeAsText(aNode));
-
-    button.onkeypress = function onBreadcrumbsKeypress(e) {
-      if (e.charCode == Ci.nsIDOMKeyEvent.DOM_VK_SPACE ||
-          e.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_RETURN)
-        button.click();
-    }
-
-    button.onBreadcrumbsClick = function onBreadcrumbsClick() {
-      this.selection.setNode(aNode, "breadcrumbs");
-    }.bind(this);
-
-    button.onclick = (function _onBreadcrumbsRightClick(event) {
-      button.focus();
-      if (event.button == 2) {
-        this.openSiblingMenu(button, aNode);
-      }
-    }).bind(this);
-
-    button.onBreadcrumbsHold = (function _onBreadcrumbsHold() {
-      this.openSiblingMenu(button, aNode);
-    }).bind(this);
-    return button;
-  },
-
-  /**
-   * Connecting the end of the breadcrumbs to a node.
-   *
-   * @param aNode The node to reach.
-   */
-  expand: function BC_expand(aNode)
-  {
-      let fragment = this.chromeDoc.createDocumentFragment();
-      let toAppend = aNode;
-      let lastButtonInserted = null;
-      let originalLength = this.nodeHierarchy.length;
-      let stopNode = null;
-      if (originalLength > 0) {
-        stopNode = this.nodeHierarchy[originalLength - 1].node;
-      }
-      while (toAppend && toAppend.tagName && toAppend != stopNode) {
-        let button = this.buildButton(toAppend);
-        fragment.insertBefore(button, lastButtonInserted);
-        lastButtonInserted = button;
-        this.nodeHierarchy.splice(originalLength, 0, {node: toAppend, button: button});
-        toAppend = this.DOMHelpers.getParentObject(toAppend);
-      }
-      this.container.appendChild(fragment, this.container.firstChild);
-  },
-
-  /**
-   * Get a child of a node that can be displayed in the breadcrumbs
-   * and that is probably visible. See LOW_PRIORITY_ELEMENTS.
-   *
-   * @param aNode The parent node.
-   * @returns nsIDOMNode|null
-   */
-  getInterestingFirstNode: function BC_getInterestingFirstNode(aNode)
-  {
-    let nextChild = this.DOMHelpers.getChildObject(aNode, 0);
-    let fallback = null;
-
-    while (nextChild) {
-      if (nextChild.nodeType == aNode.ELEMENT_NODE) {
-        if (!(nextChild.tagName in LOW_PRIORITY_ELEMENTS)) {
-          return nextChild;
-        }
-        if (!fallback) {
-          fallback = nextChild;
-        }
-      }
-      nextChild = this.DOMHelpers.getNextSibling(nextChild);
-    }
-    return fallback;
-  },
-
-
-  /**
-   * Find the "youngest" ancestor of a node which is already in the breadcrumbs.
-   *
-   * @param aNode
-   * @returns Index of the ancestor in the cache
-   */
-  getCommonAncestor: function BC_getCommonAncestor(aNode)
-  {
-    let node = aNode;
-    while (node) {
-      let idx = this.indexOf(node);
-      if (idx > -1) {
-        return idx;
-      } else {
-        node = this.DOMHelpers.getParentObject(node);
-      }
-    }
-    return -1;
-  },
-
-  /**
-   * Make sure that the latest node in the breadcrumbs is not the selected node
-   * if the selected node still has children.
-   */
-  ensureFirstChild: function BC_ensureFirstChild()
-  {
-    // If the last displayed node is the selected node
-    if (this.currentIndex == this.nodeHierarchy.length - 1) {
-      let node = this.nodeHierarchy[this.currentIndex].node;
-      let child = this.getInterestingFirstNode(node);
-      // If the node has a child
-      if (child) {
-        // Show this child
-        this.expand(child);
-      }
-    }
-  },
-
-  /**
-   * Ensure the selected node is visible.
-   */
-  scroll: function BC_scroll()
-  {
-    // FIXME bug 684352: make sure its immediate neighbors are visible too.
-
-    let scrollbox = this.container;
-    let element = this.nodeHierarchy[this.currentIndex].button;
-
-    // Repeated calls to ensureElementIsVisible would interfere with each other
-    // and may sometimes result in incorrect scroll positions.
-    this.chromeWin.clearTimeout(this._ensureVisibleTimeout);
-    this._ensureVisibleTimeout = this.chromeWin.setTimeout(function() {
-      scrollbox.ensureElementIsVisible(element);
-    }, ENSURE_SELECTION_VISIBLE_DELAY);
-  },
-
-  updateSelectors: function BC_updateSelectors()
-  {
-    for (let i = this.nodeHierarchy.length - 1; i >= 0; i--) {
-      let crumb = this.nodeHierarchy[i];
-      let button = crumb.button;
-
-      while(button.hasChildNodes()) {
-        button.removeChild(button.firstChild);
-      }
-      button.appendChild(this.prettyPrintNodeAsXUL(crumb.node));
-      button.setAttribute("tooltiptext", this.prettyPrintNodeAsText(crumb.node));
-    }
-  },
-
-  /**
-   * Update the breadcrumbs display when a new node is selected.
-   */
-  update: function BC_update()
-  {
-    this.inspector.hideNodeMenu();
-
-    let cmdDispatcher = this.chromeDoc.commandDispatcher;
-    this.hadFocus = (cmdDispatcher.focusedElement &&
-                     cmdDispatcher.focusedElement.parentNode == this.container);
-
-    if (!this.selection.isConnected()) {
-      this.cutAfter(-1); // remove all the crumbs
-      return;
-    }
-
-    if (!this.selection.isElementNode()) {
-      this.setCursor(-1); // no selection
-      return;
-    }
-
-    let idx = this.indexOf(this.selection.node);
-
-    // Is the node already displayed in the breadcrumbs?
-    if (idx > -1) {
-      // Yes. We select it.
-      this.setCursor(idx);
-    } else {
-      // No. Is the breadcrumbs display empty?
-      if (this.nodeHierarchy.length > 0) {
-        // No. We drop all the element that are not direct ancestors
-        // of the selection
-        let parent = this.DOMHelpers.getParentObject(this.selection.node);
-        let idx = this.getCommonAncestor(parent);
-        this.cutAfter(idx);
-      }
-      // we append the missing button between the end of the breadcrumbs display
-      // and the current node.
-      this.expand(this.selection.node);
-
-      // we select the current node button
-      idx = this.indexOf(this.selection.node);
-      this.setCursor(idx);
-    }
-    // Add the first child of the very last node of the breadcrumbs if possible.
-    this.ensureFirstChild();
-    this.updateSelectors();
-
-    // Make sure the selected node and its neighbours are visible.
-    this.scroll();
-  },
-}
-
-XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () {
-  return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
-});
deleted file mode 100644
--- a/browser/devtools/inspector/highlighter.js
+++ /dev/null
@@ -1,798 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; 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/. */
-
-const {Cu, Cc, Ci} = require("chrome");
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-let EventEmitter = require("devtools/shared/event-emitter");
-
-const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
-  // add ":visited" and ":link" after bug 713106 is fixed
-
-/**
- * A highlighter mechanism.
- *
- * The highlighter is built dynamically into the browser element.
- * The caller is in charge of destroying the highlighter (ie, the highlighter
- * won't be destroyed if a new tab is selected for example).
- *
- * API:
- *
- *   // Constructor and destructor.
- *   Highlighter(aTab, aInspector)
- *   void destroy();
- *
- *   // Show and hide the highlighter
- *   void show();
- *   void hide();
- *   boolean isHidden();
- *
- *   // Redraw the highlighter if the visible portion of the node has changed.
- *   void invalidateSize(aScroll);
- *
- * Events:
- *
- *   "closed" - Highlighter is closing
- *   "highlighting" - Highlighter is highlighting
- *   "locked" - The selected node has been locked
- *   "unlocked" - The selected ndoe has been unlocked
- *
- * Structure:
- *  <stack class="highlighter-container">
- *    <box class="highlighter-outline-container">
- *      <box class="highlighter-outline" locked="true/false"/>
- *    </box>
- *    <box class="highlighter-controls">
- *      <box class="highlighter-nodeinfobar-container" position="top/bottom" locked="true/false">
- *        <box class="highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-top"/>
- *        <hbox class="highlighter-nodeinfobar">
- *          <toolbarbutton class="highlighter-nodeinfobar-inspectbutton highlighter-nodeinfobar-button"/>
- *          <hbox class="highlighter-nodeinfobar-text">tagname#id.class1.class2</hbox>
- *          <toolbarbutton class="highlighter-nodeinfobar-menu highlighter-nodeinfobar-button">…</toolbarbutton>
- *        </hbox>
- *        <box class="highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-bottom"/>
- *      </box>
- *    </box>
- *  </stack>
- *
- */
-
-
-/**
- * Constructor.
- *
- * @param aTarget The inspection target.
- * @param aInspector Inspector panel.
- * @param aToolbox The toolbox holding the inspector.
- */
-function Highlighter(aTarget, aInspector, aToolbox)
-{
-  this.target = aTarget;
-  this.tab = aTarget.tab;
-  this.toolbox = aToolbox;
-  this.browser = this.tab.linkedBrowser;
-  this.chromeDoc = this.tab.ownerDocument;
-  this.chromeWin = this.chromeDoc.defaultView;
-  this.inspector = aInspector
-
-  EventEmitter.decorate(this);
-
-  this._init();
-}
-
-exports.Highlighter = Highlighter;
-
-Highlighter.prototype = {
-  get selection() {
-    return this.inspector.selection;
-  },
-
-  _init: function Highlighter__init()
-  {
-    this.unlockAndFocus = this.unlockAndFocus.bind(this);
-    this.updateInfobar = this.updateInfobar.bind(this);
-    this.highlight = this.highlight.bind(this);
-
-    let stack = this.browser.parentNode;
-    this.win = this.browser.contentWindow;
-    this._highlighting = false;
-
-    this.highlighterContainer = this.chromeDoc.createElement("stack");
-    this.highlighterContainer.className = "highlighter-container";
-
-    this.outline = this.chromeDoc.createElement("box");
-    this.outline.className = "highlighter-outline";
-
-    let outlineContainer = this.chromeDoc.createElement("box");
-    outlineContainer.appendChild(this.outline);
-    outlineContainer.className = "highlighter-outline-container";
-
-    // The controlsBox will host the different interactive
-    // elements of the highlighter (buttons, toolbars, ...).
-    let controlsBox = this.chromeDoc.createElement("box");
-    controlsBox.className = "highlighter-controls";
-    this.highlighterContainer.appendChild(outlineContainer);
-    this.highlighterContainer.appendChild(controlsBox);
-
-    // Insert the highlighter right after the browser
-    stack.insertBefore(this.highlighterContainer, stack.childNodes[1]);
-
-    this.buildInfobar(controlsBox);
-
-    this.transitionDisabler = null;
-    this.pageEventsMuter = null;
-
-    this.unlockAndFocus();
-
-    this.selection.on("new-node", this.highlight);
-    this.selection.on("new-node", this.updateInfobar);
-    this.selection.on("pseudoclass", this.updateInfobar);
-    this.selection.on("attribute-changed", this.updateInfobar);
-
-    this.onToolSelected = function(event, id) {
-      if (id != "inspector") {
-        this.chromeWin.clearTimeout(this.pageEventsMuter);
-        this.detachMouseListeners();
-        this.disabled = true;
-        this.hide();
-      } else {
-        if (!this.locked) {
-          this.attachMouseListeners();
-        }
-        this.disabled = false;
-        this.show();
-      }
-    }.bind(this);
-    this.toolbox.on("select", this.onToolSelected);
-
-    this.hidden = true;
-    this.highlight();
-  },
-
-  /**
-   * Destroy the nodes. Remove listeners.
-   */
-  destroy: function Highlighter_destroy()
-  {
-    this.inspectButton.removeEventListener("command", this.unlockAndFocus);
-    this.inspectButton = null;
-
-    this.toolbox.off("select", this.onToolSelected);
-    this.toolbox = null;
-
-    this.selection.off("new-node", this.highlight);
-    this.selection.off("new-node", this.updateInfobar);
-    this.selection.off("pseudoclass", this.updateInfobar);
-    this.selection.off("attribute-changed", this.updateInfobar);
-
-    this.detachMouseListeners();
-    this.detachPageListeners();
-
-    this.chromeWin.clearTimeout(this.transitionDisabler);
-    this.chromeWin.clearTimeout(this.pageEventsMuter);
-    this.boundCloseEventHandler = null;
-    this._contentRect = null;
-    this._highlightRect = null;
-    this._highlighting = false;
-    this.outline = null;
-    this.nodeInfo = null;
-    this.highlighterContainer.parentNode.removeChild(this.highlighterContainer);
-    this.highlighterContainer = null;
-    this.win = null
-    this.browser = null;
-    this.chromeDoc = null;
-    this.chromeWin = null;
-    this.tabbrowser = null;
-
-    this.emit("closed");
-  },
-
-  /**
-   * Show the outline, and select a node.
-   */
-  highlight: function Highlighter_highlight()
-  {
-    if (this.selection.reason != "highlighter") {
-      this.lock();
-    }
-
-    let canHighlightNode = this.selection.isNode() &&
-                          this.selection.isConnected() &&
-                          this.selection.isElementNode();
-
-    if (canHighlightNode) {
-      if (this.selection.reason != "navigateaway") {
-        this.disabled = false;
-      }
-      this.show();
-      this.updateInfobar();
-      this.invalidateSize();
-      if (!this._highlighting &&
-          this.selection.reason != "highlighter") {
-        LayoutHelpers.scrollIntoViewIfNeeded(this.selection.node);
-      }
-    } else {
-      this.disabled = true;
-      this.hide();
-    }
-  },
-
-  /**
-   * Update the highlighter size and position.
-   */
-  invalidateSize: function Highlighter_invalidateSize()
-  {
-    let canHiglightNode = this.selection.isNode() &&
-                          this.selection.isConnected() &&
-                          this.selection.isElementNode();
-
-    if (!canHiglightNode)
-      return;
-
-    let clientRect = this.selection.node.getBoundingClientRect();
-    let rect = LayoutHelpers.getDirtyRect(this.selection.node);
-    this.highlightRectangle(rect);
-
-    this.moveInfobar();
-
-    if (this._highlighting) {
-      this.showOutline();
-      this.emit("highlighting");
-    }
-  },
-
-  /**
-   * Show the highlighter if it has been hidden.
-   */
-  show: function() {
-    if (!this.hidden || this.disabled) return;
-    this.showOutline();
-    this.showInfobar();
-    this.computeZoomFactor();
-    this.attachPageListeners();
-    this.invalidateSize();
-    this.hidden = false;
-  },
-
-  /**
-   * Hide the highlighter, the outline and the infobar.
-   */
-  hide: function() {
-    if (this.hidden) return;
-    this.hideOutline();
-    this.hideInfobar();
-    this.detachPageListeners();
-    this.hidden = true;
-  },
-
-  /**
-   * Is the highlighter visible?
-   *
-   * @return boolean
-   */
-  isHidden: function() {
-    return this.hidden;
-  },
-
-  /**
-   * Lock a node. Stops the inspection.
-   */
-  lock: function() {
-    if (this.locked === true) return;
-    this.outline.setAttribute("locked", "true");
-    this.nodeInfo.container.setAttribute("locked", "true");
-    this.detachMouseListeners();
-    this.locked = true;
-    this.emit("locked");
-  },
-
-  /**
-   * Start inspecting.
-   * Unlock the current node (if any), and select any node being hovered.
-   */
-  unlock: function() {
-    if (this.locked === false) return;
-    this.outline.removeAttribute("locked");
-    this.nodeInfo.container.removeAttribute("locked");
-    this.attachMouseListeners();
-    this.locked = false;
-    if (this.selection.isElementNode() &&
-        this.selection.isConnected()) {
-      this.showOutline();
-    }
-    this.emit("unlocked");
-  },
-
-  /**
-   * Focus the browser before unlocking.
-   */
-  unlockAndFocus: function Highlighter_unlockAndFocus() {
-    if (this.locked === false) return;
-    this.chromeWin.focus();
-    this.unlock();
-  },
-
-  /**
-   * Hide the infobar
-   */
-   hideInfobar: function Highlighter_hideInfobar() {
-     this.nodeInfo.container.setAttribute("force-transitions", "true");
-     this.nodeInfo.container.setAttribute("hidden", "true");
-   },
-
-  /**
-   * Show the infobar
-   */
-   showInfobar: function Highlighter_showInfobar() {
-     this.nodeInfo.container.removeAttribute("hidden");
-     this.moveInfobar();
-     this.nodeInfo.container.removeAttribute("force-transitions");
-   },
-
-  /**
-   * Hide the outline
-   */
-   hideOutline: function Highlighter_hideOutline() {
-     this.outline.setAttribute("hidden", "true");
-   },
-
-  /**
-   * Show the outline
-   */
-   showOutline: function Highlighter_showOutline() {
-     if (this._highlighting)
-       this.outline.removeAttribute("hidden");
-   },
-
-  /**
-   * Build the node Infobar.
-   *
-   * <box class="highlighter-nodeinfobar-container">
-   *   <box class="Highlighter-nodeinfobar-arrow-top"/>
-   *   <hbox class="highlighter-nodeinfobar">
-   *     <toolbarbutton class="highlighter-nodeinfobar-button highlighter-nodeinfobar-inspectbutton"/>
-   *     <hbox class="highlighter-nodeinfobar-text">
-   *       <xhtml:span class="highlighter-nodeinfobar-tagname"/>
-   *       <xhtml:span class="highlighter-nodeinfobar-id"/>
-   *       <xhtml:span class="highlighter-nodeinfobar-classes"/>
-   *       <xhtml:span class="highlighter-nodeinfobar-pseudo-classes"/>
-   *     </hbox>
-   *     <toolbarbutton class="highlighter-nodeinfobar-button highlighter-nodeinfobar-menu"/>
-   *   </hbox>
-   *   <box class="Highlighter-nodeinfobar-arrow-bottom"/>
-   * </box>
-   *
-   * @param nsIDOMElement aParent
-   *        The container of the infobar.
-   */
-  buildInfobar: function Highlighter_buildInfobar(aParent)
-  {
-    let container = this.chromeDoc.createElement("box");
-    container.className = "highlighter-nodeinfobar-container";
-    container.setAttribute("position", "top");
-    container.setAttribute("disabled", "true");
-
-    let nodeInfobar = this.chromeDoc.createElement("hbox");
-    nodeInfobar.className = "highlighter-nodeinfobar";
-
-    let arrowBoxTop = this.chromeDoc.createElement("box");
-    arrowBoxTop.className = "highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-top";
-
-    let arrowBoxBottom = this.chromeDoc.createElement("box");
-    arrowBoxBottom.className = "highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-bottom";
-
-    let tagNameLabel = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span");
-    tagNameLabel.className = "highlighter-nodeinfobar-tagname";
-
-    let idLabel = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span");
-    idLabel.className = "highlighter-nodeinfobar-id";
-
-    let classesBox = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span");
-    classesBox.className = "highlighter-nodeinfobar-classes";
-
-    let pseudoClassesBox = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span");
-    pseudoClassesBox.className = "highlighter-nodeinfobar-pseudo-classes";
-
-    // Add some content to force a better boundingClientRect down below.
-    pseudoClassesBox.textContent = "&nbsp;";
-
-    // Create buttons
-
-    this.inspectButton = this.chromeDoc.createElement("toolbarbutton");
-    this.inspectButton.className = "highlighter-nodeinfobar-button highlighter-nodeinfobar-inspectbutton"
-    let toolbarInspectButton = this.inspector.panelDoc.getElementById("inspector-inspect-toolbutton");
-    this.inspectButton.setAttribute("tooltiptext", toolbarInspectButton.getAttribute("tooltiptext"));
-    this.inspectButton.addEventListener("command", this.unlockAndFocus);
-
-    let nodemenu = this.chromeDoc.createElement("toolbarbutton");
-    nodemenu.setAttribute("type", "menu");
-    nodemenu.className = "highlighter-nodeinfobar-button highlighter-nodeinfobar-menu"
-    nodemenu.setAttribute("tooltiptext",
-                          this.strings.GetStringFromName("nodeMenu.tooltiptext"));
-
-    nodemenu.onclick = function() {
-      this.inspector.showNodeMenu(nodemenu, "after_start");
-    }.bind(this);
-
-    // <hbox class="highlighter-nodeinfobar-text"/>
-    let texthbox = this.chromeDoc.createElement("hbox");
-    texthbox.className = "highlighter-nodeinfobar-text";
-    texthbox.setAttribute("align", "center");
-    texthbox.setAttribute("flex", "1");
-
-    texthbox.addEventListener("mousedown", function(aEvent) {
-      // On click, show the node:
-      if (this.selection.isElementNode()) {
-        LayoutHelpers.scrollIntoViewIfNeeded(this.selection.node);
-      }
-    }.bind(this), true);
-
-    texthbox.appendChild(tagNameLabel);
-    texthbox.appendChild(idLabel);
-    texthbox.appendChild(classesBox);
-    texthbox.appendChild(pseudoClassesBox);
-
-    nodeInfobar.appendChild(this.inspectButton);
-    nodeInfobar.appendChild(texthbox);
-    nodeInfobar.appendChild(nodemenu);
-
-    container.appendChild(arrowBoxTop);
-    container.appendChild(nodeInfobar);
-    container.appendChild(arrowBoxBottom);
-
-    aParent.appendChild(container);
-
-    let barHeight = container.getBoundingClientRect().height;
-
-    this.nodeInfo = {
-      tagNameLabel: tagNameLabel,
-      idLabel: idLabel,
-      classesBox: classesBox,
-      pseudoClassesBox: pseudoClassesBox,
-      container: container,
-      barHeight: barHeight,
-    };
-  },
-
-  /**
-   * Highlight a rectangular region.
-   *
-   * @param object aRect
-   *        The rectangle region to highlight.
-   * @returns boolean
-   *          True if the rectangle was highlighted, false otherwise.
-   */
-  highlightRectangle: function Highlighter_highlightRectangle(aRect)
-  {
-    if (!aRect) {
-      this.unhighlight();
-      return;
-    }
-
-    let oldRect = this._contentRect;
-
-    if (oldRect && aRect.top == oldRect.top && aRect.left == oldRect.left &&
-        aRect.width == oldRect.width && aRect.height == oldRect.height) {
-      return; // same rectangle
-    }
-
-    let aRectScaled = LayoutHelpers.getZoomedRect(this.win, aRect);
-
-    if (aRectScaled.left >= 0 && aRectScaled.top >= 0 &&
-        aRectScaled.width > 0 && aRectScaled.height > 0) {
-
-      this.showOutline();
-
-      // The bottom div and the right div are flexibles (flex=1).
-      // We don't need to resize them.
-      let top = "top:" + aRectScaled.top + "px;";
-      let left = "left:" + aRectScaled.left + "px;";
-      let width = "width:" + aRectScaled.width + "px;";
-      let height = "height:" + aRectScaled.height + "px;";
-      this.outline.setAttribute("style", top + left + width + height);
-
-      this._highlighting = true;
-    } else {
-      this.unhighlight();
-    }
-
-    this._contentRect = aRect; // save orig (non-scaled) rect
-    this._highlightRect = aRectScaled; // and save the scaled rect.
-
-    return;
-  },
-
-  /**
-   * Clear the highlighter surface.
-   */
-  unhighlight: function Highlighter_unhighlight()
-  {
-    this._highlighting = false;
-    this.hideOutline();
-  },
-
-  /**
-   * Update node information (tagName#id.class)
-   */
-  updateInfobar: function Highlighter_updateInfobar()
-  {
-    if (!this.selection.isElementNode()) {
-      this.nodeInfo.tagNameLabel.textContent = "";
-      this.nodeInfo.idLabel.textContent = "";
-      this.nodeInfo.classesBox.textContent = "";
-      this.nodeInfo.pseudoClassesBox.textContent = "";
-      return;
-    }
-
-    let node = this.selection.node;
-
-    // Tag name
-    this.nodeInfo.tagNameLabel.textContent = node.tagName;
-
-    // ID
-    this.nodeInfo.idLabel.textContent = node.id ? "#" + node.id : "";
-
-    // Classes
-    let classes = this.nodeInfo.classesBox;
-
-    classes.textContent = node.classList.length ?
-                            "." + Array.join(node.classList, ".") : "";
-
-    // Pseudo-classes
-    let pseudos = PSEUDO_CLASSES.filter(function(pseudo) {
-      return DOMUtils.hasPseudoClassLock(node, pseudo);
-    }, this);
-
-    let pseudoBox = this.nodeInfo.pseudoClassesBox;
-    pseudoBox.textContent = pseudos.join("");
-  },
-
-  /**
-   * Move the Infobar to the right place in the highlighter.
-   */
-  moveInfobar: function Highlighter_moveInfobar()
-  {
-    if (this._highlightRect) {
-      let winHeight = this.win.innerHeight * this.zoom;
-      let winWidth = this.win.innerWidth * this.zoom;
-
-      let rect = {top: this._highlightRect.top,
-                  left: this._highlightRect.left,
-                  width: this._highlightRect.width,
-                  height: this._highlightRect.height};
-
-      rect.top = Math.max(rect.top, 0);
-      rect.left = Math.max(rect.left, 0);
-      rect.width = Math.max(rect.width, 0);
-      rect.height = Math.max(rect.height, 0);
-
-      rect.top = Math.min(rect.top, winHeight);
-      rect.left = Math.min(rect.left, winWidth);
-
-      this.nodeInfo.container.removeAttribute("disabled");
-      // Can the bar be above the node?
-      if (rect.top < this.nodeInfo.barHeight) {
-        // No. Can we move the toolbar under the node?
-        if (rect.top + rect.height +
-            this.nodeInfo.barHeight > winHeight) {
-          // No. Let's move it inside.
-          this.nodeInfo.container.style.top = rect.top + "px";
-          this.nodeInfo.container.setAttribute("position", "overlap");
-        } else {
-          // Yes. Let's move it under the node.
-          this.nodeInfo.container.style.top = rect.top + rect.height + "px";
-          this.nodeInfo.container.setAttribute("position", "bottom");
-        }
-      } else {
-        // Yes. Let's move it on top of the node.
-        this.nodeInfo.container.style.top =
-          rect.top - this.nodeInfo.barHeight + "px";
-        this.nodeInfo.container.setAttribute("position", "top");
-      }
-
-      let barWidth = this.nodeInfo.container.getBoundingClientRect().width;
-      let left = rect.left + rect.width / 2 - barWidth / 2;
-
-      // Make sure the whole infobar is visible
-      if (left < 0) {
-        left = 0;
-        this.nodeInfo.container.setAttribute("hide-arrow", "true");
-      } else {
-        if (left + barWidth > winWidth) {
-          left = winWidth - barWidth;
-          this.nodeInfo.container.setAttribute("hide-arrow", "true");
-        } else {
-          this.nodeInfo.container.removeAttribute("hide-arrow");
-        }
-      }
-      this.nodeInfo.container.style.left = left + "px";
-    } else {
-      this.nodeInfo.container.style.left = "0";
-      this.nodeInfo.container.style.top = "0";
-      this.nodeInfo.container.setAttribute("position", "top");
-      this.nodeInfo.container.setAttribute("hide-arrow", "true");
-    }
-  },
-
-  /**
-   * Store page zoom factor.
-   */
-  computeZoomFactor: function Highlighter_computeZoomFactor() {
-    this.zoom =
-      this.win.QueryInterface(Ci.nsIInterfaceRequestor)
-      .getInterface(Ci.nsIDOMWindowUtils)
-      .fullZoom;
-  },
-
-  /////////////////////////////////////////////////////////////////////////
-  //// Event Handling
-
-  attachMouseListeners: function Highlighter_attachMouseListeners()
-  {
-    this.browser.addEventListener("mousemove", this, true);
-    this.browser.addEventListener("click", this, true);
-    this.browser.addEventListener("dblclick", this, true);
-    this.browser.addEventListener("mousedown", this, true);
-    this.browser.addEventListener("mouseup", this, true);
-  },
-
-  detachMouseListeners: function Highlighter_detachMouseListeners()
-  {
-    this.browser.removeEventListener("mousemove", this, true);
-    this.browser.removeEventListener("click", this, true);
-    this.browser.removeEventListener("dblclick", this, true);
-    this.browser.removeEventListener("mousedown", this, true);
-    this.browser.removeEventListener("mouseup", this, true);
-  },
-
-  attachPageListeners: function Highlighter_attachPageListeners()
-  {
-    this.browser.addEventListener("resize", this, true);
-    this.browser.addEventListener("scroll", this, true);
-    this.browser.addEventListener("MozAfterPaint", this, true);
-  },
-
-  detachPageListeners: function Highlighter_detachPageListeners()
-  {
-    this.browser.removeEventListener("resize", this, true);
-    this.browser.removeEventListener("scroll", this, true);
-    this.browser.removeEventListener("MozAfterPaint", this, true);
-  },
-
-  /**
-   * Generic event handler.
-   *
-   * @param nsIDOMEvent aEvent
-   *        The DOM event object.
-   */
-  handleEvent: function Highlighter_handleEvent(aEvent)
-  {
-    switch (aEvent.type) {
-      case "click":
-        this.handleClick(aEvent);
-        break;
-      case "mousemove":
-        this.brieflyIgnorePageEvents();
-        this.handleMouseMove(aEvent);
-        break;
-      case "resize":
-        this.computeZoomFactor();
-        break;
-      case "MozAfterPaint":
-      case "scroll":
-        this.brieflyDisableTransitions();
-        this.invalidateSize();
-        break;
-      case "dblclick":
-      case "mousedown":
-      case "mouseup":
-        aEvent.stopPropagation();
-        aEvent.preventDefault();
-        break;
-    }
-  },
-
-  /**
-   * Disable the CSS transitions for a short time to avoid laggy animations
-   * during scrolling or resizing.
-   */
-  brieflyDisableTransitions: function Highlighter_brieflyDisableTransitions()
-  {
-    if (this.transitionDisabler) {
-      this.chromeWin.clearTimeout(this.transitionDisabler);
-    } else {
-      this.outline.setAttribute("disable-transitions", "true");
-      this.nodeInfo.container.setAttribute("disable-transitions", "true");
-    }
-    this.transitionDisabler =
-      this.chromeWin.setTimeout(function() {
-        this.outline.removeAttribute("disable-transitions");
-        this.nodeInfo.container.removeAttribute("disable-transitions");
-        this.transitionDisabler = null;
-      }.bind(this), 500);
-  },
-
-  /**
-   * Don't listen to page events while inspecting with the mouse.
-   */
-  brieflyIgnorePageEvents: function Highlighter_brieflyIgnorePageEvents()