Backed out changeset 9117c7ad29d5 for MaxHeap regression.
authorDave Camp <dcamp@mozilla.com>
Sun, 28 Apr 2013 14:42:23 -0700
changeset 130176 fe0a70afa6cbc059e47cc6b917bfc72565965a28
parent 130175 9117c7ad29d5721103deb401eda5a30c1dd40f69
child 130177 aeb0f8faf02b159c8261814bf55dc483ceb183bd
push id24603
push userryanvm@gmail.com
push dateMon, 29 Apr 2013 15:53:34 +0000
treeherdermozilla-central@a885c91e069a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone23.0a1
backs out9117c7ad29d5721103deb401eda5a30c1dd40f69
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out changeset 9117c7ad29d5 for MaxHeap regression.
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/connect/connect.js
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_options.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/moz.build
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/test/unit/test_undoStack.js
browser/devtools/shared/undo.js
browser/devtools/styleeditor/StyleEditorDebuggee.jsm
browser/devtools/styleeditor/StyleEditorPanel.jsm
browser/devtools/styleeditor/StyleEditorUI.jsm
browser/devtools/styleeditor/StyleSheetEditor.jsm
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/browser_tilt_picking_inspector.js
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/debugger/dbg-transport.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
@@ -400,16 +400,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")
@@ -429,17 +432,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) {
@@ -581,17 +584,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);
@@ -827,30 +830,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) {
@@ -1591,85 +1594,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.
@@ -2054,13 +1988,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,598 @@
+/* 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();
+
+    let attachTab = () => {
+      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);
+      });
+    };
+
+    if (this.isLocalTab) {
+      this._client.connect((aType, aTraits) => {
+        this._client.listTabs(aResponse => {
+          this._form = aResponse.tabs[aResponse.selected];
+          attachTab();
+        });
+      });
+    } else if (!this.chrome) {
+      // In the remote debugging case, the protocol connection will have been
+      // already initialized in the connection screen code.
+      attachTab();
+    } else {
+      // Remote chrome debugging doesn't need anything at this point.
+      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;
+      event.nativeConsoleAPI = aPacket.nativeConsoleAPI;
+      // Send any stored event payload (DOMWindow or nsIRequest) for backwards
+      // compatibility with non-remotable tools.
+      if (aPacket.state == "start") {
+        event._navPayload = this._navRequest;
+        this.emit("will-navigate", event);
+        this._navRequest = null;
+      } else {
+        event._navPayload = this._navWindow;
+        this.emit("navigate", event);
+        this._navWindow = 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._navRequest = 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._navWindow = 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._navRequest = null;
+    this.target._navWindow = 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,225 @@
+/* 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 true;
+  },
+
+  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,
+  profilerDefinition,
+  netMonitorDefinition
+];
+
+/**
+ * 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,784 @@
+/* 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 != "options") {
+    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._buildOptions();
+        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;
+  },
+
+  _buildOptions: function TBOX__buildOptions() {
+    this.optionsButton = this.doc.getElementById("toolbox-tab-options");
+    this.optionsButton.addEventListener("command", function() {
+      this.selectTool("options");
+    }.bind(this), false);
+
+    let iframe = this.doc.getElementById("toolbox-panel-iframe-options");
+    this._toolPanels.set("options", iframe);
+
+    let key = this.doc.getElementById("toolbox-options-key");
+    key.addEventListener("command", function(toolId) {
+      this.selectTool(toolId);
+    }.bind(this, "options"), true);
+  },
+
+  /**
+   * 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.getToolDefinitionMap()) {
+      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.selectedItem = tab;
+
+    // and select the right iframe
+    let deck = this.doc.getElementById("toolbox-deck");
+    // offset by 1 due to options panel
+    if (id == "options") {
+      deck.selectedIndex = 0;
+      this.optionsButton.setAttribute("checked", true);
+    }
+    else {
+      deck.selectedIndex = index != -1 ? index + 1: -1;
+      this.optionsButton.removeAttribute("checked");
+    }
+
+    let definition = gDevTools.getToolDefinitionMap().get(id);
+
+    this._currentToolId = id;
+
+    let resolveSelected = panel => {
+      this.emit("select", id);
+      this.emit(id + "-selected", panel);
+      deferred.resolve(panel);
+    };
+
+    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);
+          gDevTools.emit(id + "-ready", this, panel);
+
+          resolveSelected(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 && (!panel.contentDocument ||
+                    panel.contentDocument.readyState == "complete")) {
+        resolveSelected(panel);
+      }
+      else if (panel) {
+        let boundLoad = function() {
+          panel.removeEventListener("DOMContentLoaded", boundLoad, true);
+          resolveSelected(panel);
+        };
+        panel.addEventListener("DOMContentLoaded", boundLoad, true);
+      }
+    }
+
+    if (id != "options") {
+      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;
+    let toolDef = gDevTools.getToolDefinitionMap().get(toolId);
+    if (toolDef) {
+      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|object} toolId
+   *         Definition or id of the tool that was unregistered. Passing the
+   *         tool id should be avoided as it is a temporary measure.
+   */
+  _toolUnregistered: function TBOX_toolUnregistered(event, toolId) {
+    if (typeof toolId != "string") {
+      toolId = toolId.id;
+    }
+
+    if (this._toolPanels.has(toolId)) {
+      let instance = this._toolPanels.get(toolId);
+      instance.destroy();
+      this._toolPanels.delete(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);
+      }
+    }
+  },
+
+
+  /**
+   * 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 = [];
+
+    this._toolPanels.delete("options");
+    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/connect/connect.js
+++ b/browser/devtools/framework/connect/connect.js
@@ -4,20 +4,20 @@
  * 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:///modules/devtools/Target.jsm");
 Cu.import("resource:///modules/devtools/Toolbox.jsm");
+Cu.import("resource:///modules/devtools/gDevTools.jsm");
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
-let {devtools, gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
 
 let gClient;
 let gConnectionTimeout;
 
 XPCOMUtils.defineLazyGetter(window, 'l10n', function () {
   return Services.strings.createBundle('chrome://browser/locale/devtools/connection-screen.properties');
 });
 
@@ -164,13 +164,13 @@ function handleConnectionTimeout() {
  * Opens the toolbox.
  */
 function openToolbox(form, chrome=false) {
   let options = {
     form: form,
     client: gClient,
     chrome: chrome
   };
-  devtools.TargetFactory.forRemoteTab(options).then((target) => {
-    gDevTools.showToolbox(target, "webconsole", devtools.Toolbox.HostType.WINDOW);
+  TargetFactory.forRemoteTab(options).then((target) => {
+    gDevTools.showToolbox(target, "webconsole", Toolbox.HostType.WINDOW);
     window.close();
   });
 }
--- 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");2
-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.
-    Object.getOwnPropertyNames(exports).forEach(key => {
-      XPCOMUtils.defineLazyGetter(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.
@@ -270,23 +102,23 @@ DevTools.prototype = {
     this._tools.delete(toolId);
 
     if (!isQuitApplication) {
       this.emit("tool-unregistered", tool);
     }
   },
 
   getDefaultTools: function DT_getDefaultTools() {
-    return devtools.defaultTools;
+    return defaultTools;
   },
 
   getAdditionalTools: function DT_getAdditionalTools() {
     let tools = [];
     for (let [key, value] of this._tools) {
-      if (devtools.defaultTools.indexOf(value) == -1) {
+      if (defaultTools.indexOf(value) == -1) {
         tools.push(value);
       }
     }
     return tools;
   },
 
   /**
    * Allow ToolBoxes to get at the list of tools that they should populate
@@ -379,17 +211,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));
 
@@ -433,25 +265,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);
     }
@@ -483,50 +306,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);
     }
   },
@@ -743,18 +558,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");
@@ -862,11 +677,8 @@ gDevTools.on("tool-unregistered", functi
   }
   gDevToolsBrowser._removeToolFromWindows(toolId, killswitch);
 });
 
 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,595 +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();
-
-    let attachTab = () => {
-      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);
-      });
-    };
-
-    if (this.isLocalTab) {
-      this._client.connect((aType, aTraits) => {
-        this._client.listTabs(aResponse => {
-          this._form = aResponse.tabs[aResponse.selected];
-          attachTab();
-        });
-      });
-    } else if (!this.chrome) {
-      // In the remote debugging case, the protocol connection will have been
-      // already initialized in the connection screen code.
-      attachTab();
-    } else {
-      // Remote chrome debugging doesn't need anything at this point.
-      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;
-      event.nativeConsoleAPI = aPacket.nativeConsoleAPI;
-      // Send any stored event payload (DOMWindow or nsIRequest) for backwards
-      // compatibility with non-remotable tools.
-      if (aPacket.state == "start") {
-        event._navPayload = this._navRequest;
-        this.emit("will-navigate", event);
-        this._navRequest = null;
-      } else {
-        event._navPayload = this._navWindow;
-        this.emit("navigate", event);
-        this._navWindow = 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._navRequest = 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._navWindow = 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._navRequest = null;
-    this.target._navWindow = 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_options.js
+++ b/browser/devtools/framework/test/browser_toolbox_options.js
@@ -1,11 +1,14 @@
 /* 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;
 let doc = null, toolbox = null, panelWin = null, index = 0, prefValues = [], prefNodes = [];
 
 function test() {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
   let target = TargetFactory.forTab(gBrowser.selectedTab);
 
--- 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,722 +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 != "options") {
-    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._buildOptions();
-        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;
-  },
-
-  _buildOptions: function TBOX__buildOptions() {
-    this.optionsButton = this.doc.getElementById("toolbox-tab-options");
-    this.optionsButton.addEventListener("command", function() {
-      this.selectTool("options");
-    }.bind(this), false);
-
-    let iframe = this.doc.getElementById("toolbox-panel-iframe-options");
-    this._toolPanels.set("options", iframe);
-
-    let key = this.doc.getElementById("toolbox-options-key");
-    key.addEventListener("command", function(toolId) {
-      this.selectTool(toolId);
-    }.bind(this, "options"), true);
-  },
-
-  /**
-   * 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.getToolDefinitionMap()) {
-      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.selectedItem = tab;
-
-    // and select the right iframe
-    let deck = this.doc.getElementById("toolbox-deck");
-    // offset by 1 due to options panel
-    if (id == "options") {
-      deck.selectedIndex = 0;
-      this.optionsButton.setAttribute("checked", true);
-    }
-    else {
-      deck.selectedIndex = index != -1 ? index + 1: -1;
-      this.optionsButton.removeAttribute("checked");
-    }
-
-    let definition = gDevTools.getToolDefinitionMap().get(id);
-
-    this._currentToolId = id;
-
-    let resolveSelected = panel => {
-      this.emit("select", id);
-      this.emit(id + "-selected", panel);
-      deferred.resolve(panel);
-    };
-
-    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);
-          gDevTools.emit(id + "-ready", this, panel);
-
-          resolveSelected(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 && (!panel.contentDocument ||
-                    panel.contentDocument.readyState == "complete")) {
-        resolveSelected(panel);
-      }
-      else if (panel) {
-        let boundLoad = function() {
-          panel.removeEventListener("DOMContentLoaded", boundLoad, true);
-          resolveSelected(panel);
-        };
-        panel.addEventListener("DOMContentLoaded", boundLoad, true);
-      }
-    }
-
-    if (id != "options") {
-      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;
-    let toolDef = gDevTools.getToolDefinitionMap().get(toolId);
-    if (toolDef) {
-      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|object} toolId
-   *         Definition or id of the tool that was unregistered. Passing the
-   *         tool id should be avoided as it is a temporary measure.
-   */
-  _toolUnregistered: function TBOX_toolUnregistered(event, toolId) {
-    if (typeof toolId != "string") {
-      toolId = toolId.id;
-    }
-
-    if (this._toolPanels.has(toolId)) {
-      let instance = this._toolPanels.get(toolId);
-      instance.destroy();
-      this._toolPanels.delete(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);
-      }
-    }
-  },
-
-
-  /**
-   * 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 = [];
-
-    this._toolPanels.delete("options");
-    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();