merge m-c to fx-team; a=desktop-only
authorTim Taubert <tim.taubert@gmx.de>
Fri, 20 Apr 2012 10:56:36 +0200
changeset 95291 cd8b66649278a03fc9bfff2e52422c9c7e7d8338
parent 95284 22bfdebf5cae295b3aa946406d7654c356b55801 (current diff)
parent 95290 ac3ea3b31fe01e9aa8a94d7937a3eaa158052cf7 (diff)
child 95350 17af008937e32c24833a2b1af4fa511ca45c47a1
child 95367 1eaf11b3cb5f0d3f265acad0d9b27b384701ad8e
push id886
push userlsblakk@mozilla.com
push dateMon, 04 Jun 2012 19:57:52 +0000
treeherdermozilla-beta@bbd8d5efd6d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdesktop-only
milestone14.0a1
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
merge m-c to fx-team; a=desktop-only
browser/devtools/highlighter/test/browser_inspector_duplicate_ruleview.js
browser/devtools/highlighter/test/browser_inspector_registertools.js
browser/devtools/highlighter/test/browser_inspector_store.js
browser/themes/gnomestripe/browser.css
browser/themes/pinstripe/browser.css
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -264,17 +264,17 @@
     />
     <key id="key_debugger" key="&debuggerMenu.commandkey;" command="Tools:Debugger"
 #ifdef XP_MACOSX
         modifiers="accel,alt"
 #else
         modifiers="accel,shift"
 #endif
     />
-    <key id="key_inspect" key="&inspectMenu.commandkey;" command="Tools:Inspect"
+    <key id="key_inspect" key="&inspectMenu.commandkey;" command="Inspector:Inspect"
 #ifdef XP_MACOSX
         modifiers="accel,alt"
 #else
         modifiers="accel,shift"
 #endif
     />
     <key id="key_scratchpad" keycode="&scratchpad.keycode;" modifiers="shift"
          keytext="&scratchpad.keytext;" command="Tools:Scratchpad"/>
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -1006,34 +1006,36 @@
                      oncommand="InspectorUI.closeInspectorUI(false);"
                      tooltiptext="&inspectCloseButton.tooltiptext;"/>
 #endif
       <toolbarbutton id="inspector-inspect-toolbutton"
                      class="devtools-toolbarbutton"
                      command="Inspector:Inspect"/>
       <toolbarbutton id="inspector-treepanel-toolbutton"
                      class="devtools-toolbarbutton"
-                     label="&htmlPanel.label;"
-                     accesskey="&htmlPanel.accesskey;"
-                     tooltiptext="&htmlPanel.tooltiptext;"
+                     tabindex="0"
+                     aria-label="&markupButton.arialabel;"
+                     accesskey="&markupButton.accesskey;"
                      command="Inspector:HTMLPanel"/>
       <arrowscrollbox id="inspector-breadcrumbs"
                       flex="1" orient="horizontal"
                       clicktoscroll="true"/>
       <hbox id="inspector-tools">
         <toolbarbutton id="inspector-3D-button"
                        class="devtools-toolbarbutton"
                        hidden="true"
                        label="&inspect3DViewButton.label;"
                        accesskey="&inspect3DViewButton.accesskey;"
+                       tabindex="0"
                        command="Inspector:Tilt"/>
         <toolbarbutton id="inspector-style-button"
                        class="devtools-toolbarbutton"
                        label="&inspectStyleButton.label;"
                        accesskey="&inspectStyleButton.accesskey;"
+                       tabindex="0"
                        command="Inspector:Sidebar"/>
         <!-- registered tools go here -->
       </hbox>
 #ifndef XP_MACOSX
       <toolbarbutton id="highlighter-closebutton"
                      oncommand="InspectorUI.closeInspectorUI(false);"
                      tooltiptext="&inspectCloseButton.tooltiptext;"/>
 #endif
--- a/browser/base/content/newtab/newTab.xul
+++ b/browser/base/content/newtab/newTab.xul
@@ -10,17 +10,17 @@
 
 <!DOCTYPE window [
   <!ENTITY % newTabDTD SYSTEM "chrome://browser/locale/newTab.dtd">
   %newTabDTD;
 ]>
 
 <xul:window id="newtab-window" xmlns="http://www.w3.org/1999/xhtml"
             xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-            xul:disablefastfind="true" xul:title="&newtab.pageTitle;">
+            disablefastfind="true" title="&newtab.pageTitle;">
 
   <div id="newtab-scrollbox">
 
     <div id="newtab-vertical-margin">
       <div id="newtab-margin-top"/>
 
       <div id="newtab-horizontal-margin">
         <div class="newtab-side-margin"/>
--- a/browser/devtools/highlighter/Makefile.in
+++ b/browser/devtools/highlighter/Makefile.in
@@ -40,18 +40,21 @@
 DEPTH		= ../../..
 topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 EXTRA_JS_MODULES = \
-	inspector.jsm \
 	domplate.jsm \
 	InsideOutBox.jsm \
 	TreePanel.jsm \
 	highlighter.jsm \
 	$(NULL)
 
+EXTRA_PP_JS_MODULES = \
+	inspector.jsm \
+	$(NULL)
+
 TEST_DIRS += test
 
 include $(topsrcdir)/config/rules.mk
--- a/browser/devtools/highlighter/TreePanel.jsm
+++ b/browser/devtools/highlighter/TreePanel.jsm
@@ -21,16 +21,17 @@
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Rob Campbell <rcampbell@mozilla.com> (original author)
  *   Mihai Șucan <mihai.sucan@gmail.com>
  *   Julian Viereck <jviereck@mozilla.com>
  *   Paul Rouget <paul@mozilla.com>
  *   Kyle Simpson <getify@mozilla.com>
+ *   Dave Camp <dcamp@mozilla.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -41,16 +42,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 const Cu = Components.utils;
 
 Cu.import("resource:///modules/domplate.jsm");
 Cu.import("resource:///modules/InsideOutBox.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource:///modules/inspector.jsm");
 
 var EXPORTED_SYMBOLS = ["TreePanel", "DOMHelpers"];
 
 const INSPECTOR_URI = "chrome://browser/content/inspector.html";
 
 /**
  * TreePanel
  * A container for the Inspector's HTML Tree Panel widget constructor function.
@@ -117,18 +119,20 @@ TreePanel.prototype = {
     this.ioBox.createObjectBox(this.IUI.win.document.documentElement);
     this.treeLoaded = true;
     this.treeIFrame.addEventListener("click", this.onTreeClick.bind(this), false);
     this.treeIFrame.addEventListener("dblclick", this.onTreeDblClick.bind(this), false);
     this.treeIFrame.focus();
     delete this.initializingTreePanel;
     Services.obs.notifyObservers(null,
       this.IUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, null);
-    if (this.IUI.selection)
-      this.select(this.IUI.selection, true);
+    if (this.pendingSelection) {
+      this.select(this.pendingSelection.node, this.pendingSelection.scroll);
+      delete this.pendingSelection;
+    }
   },
 
   /**
    * Open the inspector's tree panel and initialize it.
    */
   open: function TP_open()
   {
     if (this._open) {
@@ -214,16 +218,21 @@ TreePanel.prototype = {
     if (this.treePanelDiv) {
       this.treePanelDiv.ownerPanel = null;
       let parent = this.treePanelDiv.parentNode;
       parent.removeChild(this.treePanelDiv);
       delete this.treePanelDiv;
       delete this.treeBrowserDocument;
     }
 
+    if (this.ioBox) {
+      this.ioBox.destroy();
+      delete this.ioBox;
+    }
+
     this.treeLoaded = false;
   },
 
   /**
    * Is the TreePanel open?
    * @returns boolean
    */
   isOpen: function TP_isOpen()
@@ -567,18 +576,21 @@ TreePanel.prototype = {
 
   /**
    * Simple tree select method.
    * @param aNode the DOM node in the content document to select.
    * @param aScroll boolean scroll to the visible node?
    */
   select: function TP_select(aNode, aScroll)
   {
-    if (this.ioBox)
+    if (this.ioBox) {
       this.ioBox.select(aNode, true, true, aScroll);
+    } else {
+      this.pendingSelection = { node: aNode, scroll: aScroll };
+    }
   },
 
   ///////////////////////////////////////////////////////////////////////////
   //// Utility functions
 
   /**
    * Does the given object have a class attribute?
    * @param aNode
@@ -686,21 +698,16 @@ TreePanel.prototype = {
 
     if (this.treeIFrame) {
       this.treeIFrame.removeEventListener("dblclick", this.onTreeDblClick, false);
       this.treeIFrame.removeEventListener("click", this.onTreeClick, false);
       let parent = this.treeIFrame.parentNode;
       parent.removeChild(this.treeIFrame);
       delete this.treeIFrame;
     }
-
-    if (this.ioBox) {
-      this.ioBox.destroy();
-      delete this.ioBox;
-    }
   }
 };
 
 
 /**
  * DOMHelpers
  * Makes DOM traversal easier. Goes through iframes.
  *
--- a/browser/devtools/highlighter/highlighter.jsm
+++ b/browser/devtools/highlighter/highlighter.jsm
@@ -181,17 +181,16 @@ Highlighter.prototype = {
     this.hide();
   },
 
   /**
    * Destroy the nodes. Remove listeners.
    */
   destroy: function Highlighter_destroy()
   {
-    this.detachKeysListeners();
     this.detachMouseListeners();
     this.detachPageListeners();
 
     this.chromeWin.clearTimeout(this.transitionDisabler);
     this.boundCloseEventHandler = null;
     this._contentRect = null;
     this._highlightRect = null;
     this._highlighting = false;
@@ -297,30 +296,28 @@ Highlighter.prototype = {
 
   /**
    * Show the highlighter if it has been hidden.
    */
   show: function() {
     if (!this.hidden) return;
     this.veilContainer.removeAttribute("hidden");
     this.nodeInfo.container.removeAttribute("hidden");
-    this.attachKeysListeners();
     this.attachPageListeners();
     this.invalidateSize();
     this.hidden = false;
   },
 
   /**
    * Hide the highlighter, the veil and the infobar.
    */
   hide: function() {
     if (this.hidden) return;
     this.veilContainer.setAttribute("hidden", "true");
     this.nodeInfo.container.setAttribute("hidden", "true");
-    this.detachKeysListeners();
     this.detachPageListeners();
     this.hidden = true;
   },
 
   /**
    * Is the highlighter visible?
    *
    * @return boolean
@@ -806,28 +803,16 @@ Highlighter.prototype = {
 
   detachPageListeners: function Highlighter_detachPageListeners()
   {
     this.browser.removeEventListener("resize", this, true);
     this.browser.removeEventListener("scroll", this, true);
     this.browser.removeEventListener("MozAfterPaint", this, true);
   },
 
-  attachKeysListeners: function Highlighter_attachKeysListeners()
-  {
-    this.browser.addEventListener("keypress", this, true);
-    this.highlighterContainer.addEventListener("keypress", this, true);
-  },
-
-  detachKeysListeners: function Highlighter_detachKeysListeners()
-  {
-    this.browser.removeEventListener("keypress", this, true);
-    this.highlighterContainer.removeEventListener("keypress", this, true);
-  },
-
   /**
    * Generic event handler.
    *
    * @param nsIDOMEvent aEvent
    *        The DOM event object.
    */
   handleEvent: function Highlighter_handleEvent(aEvent)
   {
@@ -847,24 +832,16 @@ Highlighter.prototype = {
         this.invalidateSize();
         break;
       case "dblclick":
       case "mousedown":
       case "mouseup":
         aEvent.stopPropagation();
         aEvent.preventDefault();
         break;
-      case "keypress":
-        switch (aEvent.keyCode) {
-          case this.chromeWin.KeyEvent.DOM_VK_RETURN:
-            this.locked ? this.unlock() : this.lock();
-            aEvent.preventDefault();
-            aEvent.stopPropagation();
-            break;
-        }
     }
   },
 
   /**
    * Disable the CSS transitions for a short time to avoid laggy animations
    * during scrolling or resizing.
    */
   brieflyDisableTransitions: function Highlighter_brieflyDisableTransitions()
--- a/browser/devtools/highlighter/inspector.jsm
+++ b/browser/devtools/highlighter/inspector.jsm
@@ -24,16 +24,17 @@
  *   Rob Campbell <rcampbell@mozilla.com> (original author)
  *   Mihai Șucan <mihai.sucan@gmail.com>
  *   Julian Viereck <jviereck@mozilla.com>
  *   Paul Rouget <paul@mozilla.com>
  *   Kyle Simpson <ksimpson@mozilla.com>
  *   Johan Charlez <johan.charlez@gmail.com>
  *   Mike Ratcliffe <mratcliffe@mozilla.com>
  *   Murali S R <murali.sr92@yahoo.com>
+ *   Dave Camp <dcamp@mozilla.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -49,17 +50,16 @@ const Cu = Components.utils;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 
 var EXPORTED_SYMBOLS = ["InspectorUI"];
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource:///modules/TreePanel.jsm");
-Cu.import("resource:///modules/devtools/CssRuleView.jsm");
 Cu.import("resource:///modules/highlighter.jsm");
 Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
 
 // Inspector notifications dispatched through the nsIObserverService.
 const INSPECTOR_NOTIFICATIONS = {
   // Fires once the Inspector completes the initialization and opens up on
   // screen.
   OPENED: "inspector-opened",
@@ -71,196 +71,327 @@ const INSPECTOR_NOTIFICATIONS = {
   DESTROYED: "inspector-destroyed",
 
   // Fires when the Inspector is reopened after tab-switch.
   STATE_RESTORED: "inspector-state-restored",
 
   // Fires when the Tree Panel is opened and initialized.
   TREEPANELREADY: "inspector-treepanel-ready",
 
-  // Fires when the CSS Rule View is opened and initialized.
-  RULEVIEWREADY: "inspector-ruleview-ready",
-
   // Event notifications for the attribute-value editor
   EDITOR_OPENED: "inspector-editor-opened",
   EDITOR_CLOSED: "inspector-editor-closed",
   EDITOR_SAVED: "inspector-editor-saved",
 };
 
 const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
 
+/**
+ * Represents an open instance of the Inspector for a tab.
+ * This is the object handed out to sidebars and other API consumers.
+ *
+ * Right now it's a thin layer over InspectorUI, but we will
+ * start moving per-tab state into this object soon, eventually
+ * replacing the per-winID InspectorStore objects.
+ *
+ * The lifetime of this object is also not yet correct.  This object
+ * is currently destroyed when the inspector is torn down, either by user
+ * closing the inspector or by user switching the tab.  This should
+ * only be destroyed when user closes the inspector.
+ */
+function Inspector(aIUI)
+{
+  this._IUI = aIUI;
+  this._winID = aIUI.winID;
+  this._listeners = {};
+}
+
+Inspector.prototype = {
+  /**
+   * True if the highlighter is locked on a node.
+   */
+  get locked() {
+    return !this._IUI.inspecting;
+  },
+
+  /**
+   * The currently selected node in the highlighter.
+   */
+  get selection() {
+    return this._IUI.selection;
+  },
+
+  /**
+   * 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 Inspector_markDirty()
+  {
+    this._IUI.isDirty = true;
+  },
+
+  /**
+   * The chrome window the inspector lives in.
+   */
+  get chromeWindow() {
+    return this._IUI.chromeWin;
+  },
+
+  /**
+   * Notify the inspector that the current selection has changed.
+   *
+   * @param string aContext
+   *        An string that will be passed to the change event.  Allows
+   *        a tool to recognize when it sent a change notification itself
+   *        to avoid unnecessary refresh.
+   */
+  change: function Inspector_change(aContext)
+  {
+    this._IUI.nodeChanged(aContext);
+  },
+
+  /**
+   * Called by the InspectorUI when the inspector is being destroyed.
+   */
+  _destroy: function Inspector__destroy()
+  {
+    delete this._IUI;
+    delete this._listeners;
+  },
+
+  /// Event stuff.  Would like to refactor this eventually.
+  /// Emulates the jetpack event source, which has a nice API.
+
+  /**
+   * Connect a listener to this object.
+   *
+   * @param string aEvent
+   *        The event name to which we're connecting.
+   * @param function aListener
+   *        Called when the event is fired.
+   */
+  on: function Inspector_on(aEvent, aListener)
+  {
+    if (!(aEvent in this._listeners)) {
+      this._listeners[aEvent] = [];
+    }
+    this._listeners[aEvent].push(aListener);
+  },
+
+  /**
+   * Listen for the next time an event is fired.
+   *
+   * @param string aEvent
+   *        The event name to which we're connecting.
+   * @param function aListener
+   *        Called when the event is fired.  Will be called at most one time.
+   */
+  once: function Inspector_once(aEvent, aListener)
+  {
+    let handler = function() {
+      this.removeListener(aEvent, handler);
+      aListener();
+    }.bind(this);
+    this.on(aEvent, handler);
+  },
+
+  /**
+   * Remove a previously-registered event listener.  Works for events
+   * registered with either on or once.
+   *
+   * @param string aEvent
+   *        The event name whose listener we're disconnecting.
+   * @param function aListener
+   *        The listener to remove.
+   */
+  removeListener: function Inspector_removeListener(aEvent, aListener)
+  {
+    this._listeners[aEvent] = this._listeners[aEvent].filter(function(l) aListener != l);
+  },
+
+  /**
+   * Emit an event on the inspector.  All arguments to this method will
+   * be sent to listner functions.
+   */
+  _emit: function Inspector__emit(aEvent)
+  {
+    if (!(aEvent in this._listeners))
+      return;
+    for each (let listener in this._listeners[aEvent]) {
+      listener.apply(null, arguments);
+    }
+  }
+}
+
 ///////////////////////////////////////////////////////////////////////////
 //// InspectorUI
 
 /**
  * Main controller class for the Inspector.
  *
  * @constructor
  * @param nsIDOMWindow aWindow
  *        The chrome window for which the Inspector instance is created.
  */
 function InspectorUI(aWindow)
 {
+  // Let style inspector tools register themselves.
+  let tmp = {};
+  Cu.import("resource:///modules/devtools/StyleInspector.jsm", tmp);
+
   this.chromeWin = aWindow;
   this.chromeDoc = aWindow.document;
   this.tabbrowser = aWindow.gBrowser;
   this.tools = {};
   this.toolEvents = {};
   this.store = new InspectorStore();
   this.INSPECTOR_NOTIFICATIONS = INSPECTOR_NOTIFICATIONS;
-
-  // Set the tooltip of the inspect button.
-  let keysbundle = Services.strings.createBundle(
-    "chrome://global/locale/keys.properties");
-  let returnString = keysbundle.GetStringFromName("VK_RETURN");
-  let tooltip = this.strings.formatStringFromName("inspectButton.tooltiptext",
-    [returnString], 1);
-  let button = this.chromeDoc.getElementById("inspector-inspect-toolbutton");
-  button.setAttribute("tooltiptext", tooltip);
+  this.buildButtonsTooltip();
 }
 
 InspectorUI.prototype = {
   browser: null,
   tools: null,
   toolEvents: null,
   inspecting: false,
   ruleViewEnabled: true,
   isDirty: false,
   store: null,
 
+  _currentInspector: null,
+  _sidebar: null,
+
+  /**
+   * The Inspector object for the current tab.
+   */
+  get currentInspector() this._currentInspector,
+
+  /**
+   * The InspectorStyleSidebar for the current tab.
+   */
+  get sidebar() this._sidebar,
+
   /**
    * Toggle the inspector interface elements on or off.
    *
    * @param aEvent
    *        The event that requested the UI change. Toolbar button or menu.
    */
   toggleInspectorUI: function IUI_toggleInspectorUI(aEvent)
   {
     if (this.isInspectorOpen) {
       this.closeInspectorUI();
     } else {
       this.openInspectorUI();
     }
   },
 
   /**
-   * Show the Sidebar.
+   * Add a tooltip to the Inspect and Markup buttons.
+   * The tooltips include the related keyboard shortcut.
    */
-  showSidebar: function IUI_showSidebar()
+  buildButtonsTooltip: function IUI_buildButtonsTooltip()
   {
-    this.sidebarBox.removeAttribute("hidden");
-    this.sidebarSplitter.removeAttribute("hidden");
-    this.stylingButton.checked = true;
+    let keysbundle = Services.strings.createBundle("chrome://global-platform/locale/platformKeys.properties");
+
+    // Inspect Button - the shortcut string is built from the <key> element
 
-    // If no tool is already selected, show the last-used sidebar if available,
-    // otherwise just show the first.
+    let key = this.chromeDoc.getElementById("key_inspect");
+
+    let modifiersAttr = key.getAttribute("modifiers");
+
+    let combo = [];
 
-    if (!Array.some(this.sidebarToolbar.children,
-      function(btn) btn.hasAttribute("checked"))) {
+    if (modifiersAttr.match("accel"))
+#ifdef XP_MACOSX
+      combo.push(keysbundle.GetStringFromName("VK_META"));
+#else
+      combo.push(keysbundle.GetStringFromName("VK_CONTROL"));
+#endif
+    if (modifiersAttr.match("shift"))
+      combo.push(keysbundle.GetStringFromName("VK_SHIFT"));
+    if (modifiersAttr.match("alt"))
+      combo.push(keysbundle.GetStringFromName("VK_ALT"));
+    if (modifiersAttr.match("ctrl"))
+      combo.push(keysbundle.GetStringFromName("VK_CONTROL"));
+    if (modifiersAttr.match("meta"))
+      combo.push(keysbundle.GetStringFromName("VK_META"));
+
+    combo.push(key.getAttribute("key"));
+
+    let separator = keysbundle.GetStringFromName("MODIFIER_SEPARATOR");
 
-      let activePanel = this.sidebarTools[0];
-      let activeId = this.store.getValue(this.winID, "activeSidebar");
-      if (activeId && this.tools[activeId]) {
-        activePanel = this.tools[activeId];
-      }
-      this.activateSidebarPanel(activePanel.id);
-    }
+    let tooltip = this.strings.formatStringFromName("inspectButton.tooltiptext",
+      [combo.join(separator)], 1);
+    let button = this.chromeDoc.getElementById("inspector-inspect-toolbutton");
+    button.setAttribute("tooltiptext", tooltip);
+
+    // Markup Button - the shortcut string is built from the accesskey attribute
 
-    this.store.setValue(this.winID, "sidebarOpen", true);
-    Services.prefs.setBoolPref("devtools.inspector.sidebarOpen", true);
+    button = this.chromeDoc.getElementById("inspector-treepanel-toolbutton");
+#ifdef XP_MACOSX
+    // On Mac, no accesskey
+    tooltip = this.strings.GetStringFromName("markupButton.tooltip");
+#else
+    let altString = keysbundle.GetStringFromName("VK_ALT");
+    let accesskey = button.getAttribute("accesskey");
+    let separator = keysbundle.GetStringFromName("MODIFIER_SEPARATOR");
+    let shortcut = altString + separator + accesskey;
+    tooltip = this.strings.formatStringFromName("markupButton.tooltipWithAccesskey",
+      [shortcut], 1);
+#endif
+    button.setAttribute("tooltiptext", tooltip);
+
   },
 
   /**
-   * Tear down the sidebar.
+   * Toggle the status of the inspector, starting or stopping it. Invoked
+   * from the toolbar's Inspect button.
    */
-  _destroySidebar: function IUI_destroySidebar()
+  toggleInspection: function IUI_toggleInspection()
   {
-    this.sidebarBox.setAttribute("hidden", "true");
-    this.sidebarSplitter.setAttribute("hidden", "true");
-    this.stylingButton.checked = false;
-  },
+    if (!this.isInspectorOpen) {
+      this.openInspectorUI();
+      return;
+    }
 
-  /**
-   * Hide the sidebar.
-   */
-  hideSidebar: function IUI_hideSidebar()
-  {
-    this._destroySidebar();
-    this.store.setValue(this.winID, "sidebarOpen", false);
-    Services.prefs.setBoolPref("devtools.inspector.sidebarOpen", false);
+    if (this.inspecting) {
+      this.stopInspecting();
+    } else {
+      this.startInspecting();
+    }
   },
 
   /**
    * Show or hide the sidebar. Called from the Styling button on the
    * highlighter toolbar.
    */
   toggleSidebar: function IUI_toggleSidebar()
   {
     if (!this.isSidebarOpen) {
       this.showSidebar();
     } else {
       this.hideSidebar();
     }
   },
 
   /**
-   * Activate a sidebar panel by id.
-   */
-  activateSidebarPanel: function IUI_activateSidebarPanel(aID)
-  {
-    let buttonId = this.getToolbarButtonId(aID);
-    this.chromeDoc.getElementById(buttonId).click();
-  },
-
-  get activeSidebarPanel()
-  {
-    for each (let tool in this.sidebarTools) {
-      if (this.sidebarDeck.selectedPanel == this.getToolIframe(tool)) {
-        return tool.id;
-      }
-    }
-    return null;
-  },
-
-  /**
-   * Getter to test if the Sidebar is open or not.
-   */
-  get isSidebarOpen()
-  {
-    return this.stylingButton.checked &&
-          !this.sidebarBox.hidden &&
-          !this.sidebarSplitter.hidden;
-  },
-
-  /**
-   * Toggle the status of the inspector, starting or stopping it. Invoked
-   * from the toolbar's Inspect button.
-   */
-  toggleInspection: function IUI_toggleInspection()
-  {
-    if (this.inspecting) {
-      this.stopInspecting();
-    } else {
-      this.startInspecting();
-    }
-  },
-
-  /**
    * Toggle the TreePanel.
    */
   toggleHTMLPanel: function TP_toggle()
   {
     if (this.treePanel.isOpen()) {
       this.treePanel.close();
       Services.prefs.setBoolPref("devtools.inspector.htmlPanelOpen", false);
-      this.store.setValue(this.winID, "htmlPanelOpen", false);
+      this.currentInspector._htmlPanelOpen = false;
     } else {
       this.treePanel.open();
       Services.prefs.setBoolPref("devtools.inspector.htmlPanelOpen", true);
-      this.store.setValue(this.winID, "htmlPanelOpen", true);
+      this.currentInspector._htmlPanelOpen = true;
     }
   },
 
   /**
    * Is the inspector UI open? Simply check if the toolbar is visible or not.
    *
    * @returns boolean
    */
@@ -319,118 +450,101 @@ InspectorUI.prototype = {
     this.browser = this.tabbrowser.selectedBrowser;
     this.win = this.browser.contentWindow;
     this.winID = this.getWindowID(this.win);
     this.toolbar = this.chromeDoc.getElementById("inspector-toolbar");
     this.inspectMenuitem = this.chromeDoc.getElementById("Tools:Inspect");
     this.inspectToolbutton =
       this.chromeDoc.getElementById("inspector-inspect-toolbutton");
 
-    this.initTools();
     this.chromeWin.Tilt.setup();
 
     this.treePanel = new TreePanel(this.chromeWin, this);
-
-    if (Services.prefs.getBoolPref("devtools.ruleview.enabled") &&
-        !this.toolRegistered("ruleview")) {
-      this.registerRuleView();
-    }
-
-    if (Services.prefs.getBoolPref("devtools.styleinspector.enabled") &&
-        !this.toolRegistered("styleinspector")) {
-      this.stylePanel = new StyleInspector(this.chromeWin, this);
-    }
-
     this.toolbar.hidden = false;
     this.inspectMenuitem.setAttribute("checked", true);
 
     // initialize the HTML Breadcrumbs
     this.breadcrumbs = new HTMLBreadcrumbs(this);
 
     this.isDirty = false;
 
     this.progressListener = new InspectorProgressListener(this);
 
     this.chromeWin.addEventListener("keypress", this, false);
 
     // initialize the highlighter
     this.highlighter = new Highlighter(this.chromeWin);
 
+    this.initializeStore();
+
+    this._sidebar = new InspectorStyleSidebar({
+      document: this.chromeDoc,
+      inspector: this._currentInspector,
+    });
+
+    // Create UI for any sidebars registered with
+    // InspectorUI.registerSidebar()
+    for each (let tool in InspectorUI._registeredSidebars) {
+      this._sidebar.addTool(tool);
+    }
+
     this.setupNavigationKeys();
     this.highlighterReady();
-  },
 
-  /**
-   * Register the Rule View in the Sidebar.
-   */
-  registerRuleView: function IUI_registerRuleView()
-  {
-    let isOpen = this.isRuleViewOpen.bind(this);
+    // Focus the first focusable element in the toolbar
+    this.chromeDoc.commandDispatcher.advanceFocusIntoSubtree(this.toolbar);
 
-    this.ruleViewObject = {
-      id: "ruleview",
-      label: this.strings.GetStringFromName("ruleView.label"),
-      tooltiptext: this.strings.GetStringFromName("ruleView.tooltiptext"),
-      accesskey: this.strings.GetStringFromName("ruleView.accesskey"),
-      context: this,
-      get isOpen() isOpen(),
-      show: this.openRuleView,
-      hide: this.closeRuleView,
-      onSelect: this.selectInRuleView,
-      onChanged: this.changeInRuleView,
-      panel: null,
-      unregister: this.destroyRuleView,
-      sidebar: true,
-    };
+    // If nothing is focused in the toolbar, it means that the focus manager
+    // is limited to some specific elements and has moved the focus somewhere else.
+    // So in this case, we want to focus the content window.
+    // See: https://developer.mozilla.org/en/XUL_Tutorial/Focus_and_Selection#Platform_Specific_Behaviors
+    if (!this.toolbar.querySelector("-moz-focusring")) {
+      this.win.focus();
+    }
 
-    this.registerTool(this.ruleViewObject);
-  },
-
-  /**
-   * Register and initialize any included tools.
-   */
-  initTools: function IUI_initTools()
-  {
-    // Extras go here.
   },
 
   /**
    * Initialize the InspectorStore.
    */
   initializeStore: function IUI_initializeStore()
   {
     // First time opened, add the TabSelect listener
     if (this.store.isEmpty()) {
       this.tabbrowser.tabContainer.addEventListener("TabSelect", this, false);
     }
 
     // Has this windowID been inspected before?
     if (this.store.hasID(this.winID)) {
-      let selectedNode = this.store.getValue(this.winID, "selectedNode");
+      this._currentInspector = this.store.getInspector(this.winID);
+      let selectedNode = this.currentInspector._selectedNode;
       if (selectedNode) {
         this.inspectNode(selectedNode);
       }
-      this.isDirty = this.store.getValue(this.winID, "isDirty");
+      this.isDirty = this.currentInspector._isDirty;
     } else {
       // First time inspecting, set state to no selection + live inspection.
-      this.store.addStore(this.winID);
-      this.store.setValue(this.winID, "selectedNode", null);
-      this.store.setValue(this.winID, "inspecting", true);
-      this.store.setValue(this.winID, "isDirty", this.isDirty);
+      let inspector = new Inspector(this);
+      this.store.addInspector(this.winID, inspector);
+      inspector._selectedNode = null;
+      inspector._inspecting = true;
+      inspector._isDirty = this.isDirty;
 
-      this.store.setValue(this.winID, "htmlPanelOpen",
-        Services.prefs.getBoolPref("devtools.inspector.htmlPanelOpen"));
+      inspector._htmlPanelOpen =
+        Services.prefs.getBoolPref("devtools.inspector.htmlPanelOpen");
 
-      this.store.setValue(this.winID, "sidebarOpen",
-        Services.prefs.getBoolPref("devtools.inspector.sidebarOpen"));
+      inspector._sidebarOpen =
+        Services.prefs.getBoolPref("devtools.inspector.sidebarOpen");
 
-      this.store.setValue(this.winID, "activeSidebar",
-        Services.prefs.getCharPref("devtools.inspector.activeSidebar"));
+      inspector._activeSidebar =
+        Services.prefs.getCharPref("devtools.inspector.activeSidebar");
 
       this.win.addEventListener("pagehide", this, true);
+
+      this._currentInspector = inspector;
     }
   },
 
   /**
    * Browse nodes according to the breadcrumbs layout, only for some specific
    * elements of the UI.
    */
    setupNavigationKeys: function IUI_setupNavigationKeys()
@@ -459,22 +573,22 @@ InspectorUI.prototype = {
       this.toolbar.removeEventListener("keypress", this.onKeypress, true);
    },
 
   /**
    * Close inspector UI and associated panels. Unhighlight and stop inspecting.
    * Remove event listeners for document scrolling, resize,
    * tabContainer.TabSelect and others.
    *
-   * @param boolean aKeepStore
-   *        Tells if you want the store associated to the current tab/window to
-   *        be cleared or not. Set this to true to not clear the store, or false
-   *        otherwise.
+   * @param boolean aKeepInspector
+   *        Tells if you want the inspector associated to the current tab/window to
+   *        be cleared or not. Set this to true to save the inspector, or false
+   *        to destroy it.
    */
-  closeInspectorUI: function IUI_closeInspectorUI(aKeepStore)
+  closeInspectorUI: function IUI_closeInspectorUI(aKeepInspector)
   {
     // if currently editing an attribute value, closing the
     // highlighter/HTML panel dismisses the editor
     if (this.treePanel && this.treePanel.editingContext)
       this.treePanel.closeEditor();
 
     this.treePanel.destroy();
 
@@ -487,67 +601,70 @@ InspectorUI.prototype = {
     this.closing = true;
     this.toolbar.hidden = true;
 
     this.removeNavigationKeys();
 
     this.progressListener.destroy();
     delete this.progressListener;
 
-    if (!aKeepStore) {
-      this.store.deleteStore(this.winID);
+    if (!aKeepInspector) {
       this.win.removeEventListener("pagehide", this, true);
       this.clearPseudoClassLocks();
     } else {
-      // Update the store before closing.
+      // Update the inspector before closing.
       if (this.selection) {
-        this.store.setValue(this.winID, "selectedNode",
-          this.selection);
+        this.currentInspector._selectedNode = this.selection;
       }
-      this.store.setValue(this.winID, "inspecting", this.inspecting);
-      this.store.setValue(this.winID, "isDirty", this.isDirty);
+      this.currentInspector._inspecting = this.inspecting;
+      this.currentInspector._isDirty = this.isDirty;
     }
 
     if (this.store.isEmpty()) {
       this.tabbrowser.tabContainer.removeEventListener("TabSelect", this, false);
     }
 
     this.chromeWin.removeEventListener("keypress", this, false);
 
     this.stopInspecting();
 
-    this.toolsDo(function IUI_toolsHide(aTool) {
-      this.unregisterTool(aTool);
-    }.bind(this));
-
     // close the sidebar
-    this._destroySidebar();
+    if (this._sidebar) {
+      this._sidebar.destroy();
+      this._sidebar = null;
+    }
 
     if (this.highlighter) {
       this.highlighter.destroy();
       this.highlighter = null;
     }
 
     if (this.breadcrumbs) {
       this.breadcrumbs.destroy();
       this.breadcrumbs = null;
     }
 
+    delete this._currentInspector;
+    if (!aKeepInspector)
+      this.store.deleteInspector(this.winID);
+
     this.inspectMenuitem.setAttribute("checked", false);
     this.browser = this.win = null; // null out references to browser and window
     this.winID = null;
     this.selection = null;
     this.closing = false;
     this.isDirty = false;
 
     delete this.treePanel;
     delete this.stylePanel;
     delete this.toolbar;
+
     Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.CLOSED, null);
-    if (!aKeepStore)
+
+    if (!aKeepInspector)
       Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.DESTROYED, winId);
   },
 
   /**
    * Begin inspecting webpage, attach page event listeners, activate
    * highlighter event listeners.
    */
   startInspecting: function IUI_startInspecting()
@@ -555,18 +672,24 @@ InspectorUI.prototype = {
     // if currently editing an attribute value, starting
     // "live inspection" mode closes the editor
     if (this.treePanel && this.treePanel.editingContext)
       this.treePanel.closeEditor();
 
     this.inspectToolbutton.checked = true;
 
     this.inspecting = true;
-    this.toolsDim(true);
     this.highlighter.unlock();
+    this._notifySelected();
+    this._currentInspector._emit("unlocked");
+  },
+
+  _notifySelected: function IUI__notifySelected(aFrom)
+  {
+    this._currentInspector._emit("select", aFrom);
   },
 
   /**
    * Stop inspecting webpage, detach page listeners, disable highlighter
    * event listeners.
    * @param aPreventScroll
    *        Prevent scroll in the HTML tree?
    */
@@ -574,23 +697,25 @@ InspectorUI.prototype = {
   {
     if (!this.inspecting) {
       return;
     }
 
     this.inspectToolbutton.checked = false;
 
     this.inspecting = false;
-    this.toolsDim(false);
     if (this.highlighter.getNode()) {
       this.select(this.highlighter.getNode(), true, !aPreventScroll);
     } else {
       this.select(null, true, true);
     }
+
     this.highlighter.lock();
+    this._notifySelected();
+    this._currentInspector._emit("locked");
   },
 
   /**
    * Select an object in the inspector.
    * @param aNode
    *        node to inspect
    * @param forceUpdate
    *        force an update?
@@ -608,30 +733,30 @@ InspectorUI.prototype = {
 
     if (!aNode)
       aNode = this.defaultSelection;
 
     if (forceUpdate || aNode != this.selection) {
       if (aFrom != "breadcrumbs") {
         this.clearPseudoClassLocks();
       }
-      
+
       this.selection = aNode;
       if (!this.inspecting) {
         this.highlighter.highlight(this.selection);
       }
     }
 
     this.breadcrumbs.update();
     this.chromeWin.Tilt.update(aNode);
     this.treePanel.select(aNode, aScroll);
 
-    this.toolsSelect(aScroll);
+    this._notifySelected(aFrom);
   },
-  
+
   /**
    * Toggle the pseudo-class lock on the currently inspected element. If the
    * pseudo-class is :hover or :active, that pseudo-class will also be toggled
    * on every ancestor of the element, mirroring real :hover and :active
    * behavior.
    * 
    * @param aPseudo the pseudo-class lock to toggle, e.g. ":hover"
    */
@@ -644,17 +769,17 @@ InspectorUI.prototype = {
     } else {
       let hierarchical = aPseudo == ":hover" || aPseudo == ":active";
       let node = this.selection;
       do {
         DOMUtils.addPseudoClassLock(node, aPseudo);
         node = node.parentNode;
       } while (hierarchical && node.parentNode)
     }
-    this.nodeChanged();
+    this.nodeChanged("pseudoclass");
   },
 
   /**
    * Clear all pseudo-class locks applied to elements in the node hierarchy
    */
   clearPseudoClassLocks: function IUI_clearPseudoClassLocks()
   {
     this.breadcrumbs.nodeHierarchy.forEach(function(crumb) {
@@ -668,27 +793,24 @@ InspectorUI.prototype = {
    * @param object aUpdater
    *        The tool that triggered the update (if any), that tool's
    *        onChanged will not be called.
    */
   nodeChanged: function IUI_nodeChanged(aUpdater)
   {
     this.highlighter.invalidateSize();
     this.breadcrumbs.updateSelectors();
-    this.toolsOnChanged(aUpdater);
+    this._currentInspector._emit("change", aUpdater);
   },
 
   /////////////////////////////////////////////////////////////////////////
   //// Event Handling
 
   highlighterReady: function IUI_highlighterReady()
   {
-    // Setup the InspectorStore or restore state
-    this.initializeStore();
-
     let self = this;
 
     this.highlighter.addListener("locked", function() {
       self.stopInspecting();
     });
 
     this.highlighter.addListener("unlocked", function() {
       self.startInspecting();
@@ -697,34 +819,33 @@ InspectorUI.prototype = {
     this.highlighter.addListener("nodeselected", function() {
       self.select(self.highlighter.getNode(), false, false);
     });
 
     this.highlighter.addListener("pseudoclasstoggled", function(aPseudo) {
       self.togglePseudoClassLock(aPseudo);
     });
 
-    if (this.store.getValue(this.winID, "inspecting")) {
+    if (this.currentInspector._inspecting) {
       this.startInspecting();
       this.highlighter.unlock();
     } else {
       this.highlighter.lock();
     }
 
     Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.STATE_RESTORED, null);
 
-    this.win.focus();
     this.highlighter.highlight();
 
-    if (this.store.getValue(this.winID, "htmlPanelOpen")) {
+    if (this.currentInspector._htmlPanelOpen) {
       this.treePanel.open();
     }
 
-    if (this.store.getValue(this.winID, "sidebarOpen")) {
-      this.showSidebar();
+    if (this.currentInspector._sidebarOpen) {
+      this._sidebar.show();
     }
 
     Services.obs.notifyObservers({wrappedJSObject: this},
                                  INSPECTOR_NOTIFICATIONS.OPENED, null);
   },
 
   /**
    * Main callback handler for events.
@@ -778,17 +899,17 @@ InspectorUI.prototype = {
         if (!win || win.frameElement || win.top != win) {
           break;
         }
 
         win.removeEventListener(event.type, this, true);
 
         winID = this.getWindowID(win);
         if (winID && winID != this.winID) {
-          this.store.deleteStore(winID);
+          this.store.deleteInspector(winID);
         }
 
         if (this.store.isEmpty()) {
           this.tabbrowser.tabContainer.removeEventListener("TabSelect", this,
                                                          false);
         }
         break;
     }
@@ -884,178 +1005,16 @@ InspectorUI.prototype = {
     parent.removeChild(selection);
     this.breadcrumbs.invalidateHierarchy();
 
     // select the parent node in the highlighter, treepanel, breadcrumbs
     this.inspectNode(parent);
   },
 
   /////////////////////////////////////////////////////////////////////////
-  //// CssRuleView methods
-
-  /**
-   * Is the cssRuleView open?
-   */
-  isRuleViewOpen: function IUI_isRuleViewOpen()
-  {
-    return this.isSidebarOpen && this.ruleButton.hasAttribute("checked") &&
-      (this.sidebarDeck.selectedPanel == this.getToolIframe(this.ruleViewObject));
-  },
-
-  /**
-   * Convenience getter to retrieve the Rule Button.
-   */
-  get ruleButton()
-  {
-    return this.chromeDoc.getElementById(
-      this.getToolbarButtonId(this.ruleViewObject.id));
-  },
-
-  /**
-   * Open the CssRuleView.
-   */
-  openRuleView: function IUI_openRuleView()
-  {
-    let iframe = this.getToolIframe(this.ruleViewObject);
-    if (iframe.getAttribute("src")) {
-      // We're already loading this tool, let it finish.
-      return;
-    }
-
-    let boundLoadListener = function() {
-      iframe.removeEventListener("load", boundLoadListener, true);
-      let doc = iframe.contentDocument;
-
-      let winID = this.winID;
-      let ruleViewStore = this.store.getValue(winID, "ruleView");
-      if (!ruleViewStore) {
-        ruleViewStore = {};
-        this.store.setValue(winID, "ruleView", ruleViewStore);
-      }
-
-      this.ruleView = new CssRuleView(doc, ruleViewStore);
-
-      // Add event handlers bound to this.
-      this.boundRuleViewChanged = this.ruleViewChanged.bind(this);
-      this.ruleView.element.addEventListener("CssRuleViewChanged",
-                                             this.boundRuleViewChanged);
-      this.cssRuleViewBoundCSSLinkClicked = this.ruleViewCSSLinkClicked.bind(this);
-      this.ruleView.element.addEventListener("CssRuleViewCSSLinkClicked",
-                                             this.cssRuleViewBoundCSSLinkClicked);
-
-      doc.documentElement.appendChild(this.ruleView.element);
-      this.ruleView.highlight(this.selection);
-      Services.obs.notifyObservers(null,
-        INSPECTOR_NOTIFICATIONS.RULEVIEWREADY, null);
-    }.bind(this);
-
-    iframe.addEventListener("load", boundLoadListener, true);
-
-    iframe.setAttribute("src", "chrome://browser/content/devtools/cssruleview.xul");
-  },
-
-  /**
-   * Stub to Close the CSS Rule View. Does nothing currently because the
-   * Rule View lives in the sidebar.
-   */
-  closeRuleView: function IUI_closeRuleView()
-  {
-    // do nothing for now
-  },
-
-  /**
-   * Update the selected node in the Css Rule View.
-   * @param {nsIDOMnode} the selected node.
-   */
-  selectInRuleView: function IUI_selectInRuleView(aNode)
-  {
-    if (this.ruleView)
-      this.ruleView.highlight(aNode);
-  },
-  
-  /**
-   * Update the rules for the current node in the Css Rule View.
-   */
-  changeInRuleView: function IUI_selectInRuleView()
-  {
-    if (this.ruleView)
-      this.ruleView.nodeChanged();
-  },
-
-  ruleViewChanged: function IUI_ruleViewChanged()
-  {
-    this.isDirty = true;
-    this.nodeChanged(this.ruleViewObject);
-  },
-
-  /**
-   * When a css link is clicked this method is called in order to either:
-   *   1. Open the link in view source (for element style attributes)
-   *   2. Open the link in the style editor
-   *
-   *   Like the style editor, we only view stylesheets contained in
-   *   document.styleSheets.
-   *
-   * @param aEvent The event containing the style rule to act on
-   */
-  ruleViewCSSLinkClicked: function(aEvent)
-  {
-    if (!this.chromeWin) {
-      return;
-    }
-
-    let rule = aEvent.detail.rule;
-    let styleSheet = rule.sheet;
-    let doc = this.chromeWin.content.document;
-    let styleSheets = doc.styleSheets;
-    let contentSheet = false;
-    let line = rule.ruleLine || 0;
-
-    // Array.prototype.indexOf always returns -1 here so we loop through
-    // the styleSheets object instead.
-    for each (let sheet in styleSheets) {
-      if (sheet == styleSheet) {
-        contentSheet = true;
-        break;
-      }
-    }
-
-    if (contentSheet)  {
-      this.chromeWin.StyleEditor.openChrome(styleSheet, line);
-    } else {
-      let href = styleSheet ? styleSheet.href : "";
-      if (rule.elementStyle.element) {
-        href = rule.elementStyle.element.ownerDocument.location.href;
-      }
-      let viewSourceUtils = this.chromeWin.gViewSourceUtils;
-      viewSourceUtils.viewSource(href, null, doc, line);
-    }
-  },
-
-  /**
-   * Destroy the rule view.
-   */
-  destroyRuleView: function IUI_destroyRuleView()
-  {
-    if (this.ruleView) {
-      this.ruleView.element.removeEventListener("CssRuleViewChanged",
-                                                this.boundRuleViewChanged);
-      this.ruleView.element.removeEventListener("CssRuleViewCSSLinkClicked",
-                                                this.cssRuleViewBoundCSSLinkClicked);
-      delete this.boundRuleViewChanged;
-      delete this.cssRuleViewBoundCSSLinkClicked;
-      this.ruleView.destroy();
-      delete this.ruleView;
-    }
-
-    let iframe = this.getToolIframe(this.ruleViewObject);
-    iframe.parentNode.removeChild(iframe);
-  },
-
-  /////////////////////////////////////////////////////////////////////////
   //// Utility Methods
 
   /**
    * inspect the given node, highlighting it on the page and selecting the
    * correct row in the tree panel
    *
    * @param aNode
    *        the element in the document to inspect
@@ -1130,351 +1089,16 @@ InspectorUI.prototype = {
    * @returns String
    */
   getToolbarButtonId: function IUI_createButtonId(anId)
   {
     return "inspector-" + anId + "-toolbutton";
   },
 
   /**
-   * Save a registered tool's callback for a specified event.
-   * @param aWidget xul:widget
-   * @param aEvent a DOM event name
-   * @param aCallback Function the click event handler for the button
-   */
-  bindToolEvent: function IUI_bindToolEvent(aWidget, aEvent, aCallback)
-  {
-    this.toolEvents[aWidget.id + "_" + aEvent] = aCallback;
-    aWidget.addEventListener(aEvent, aCallback, false);
-  },
-
-  /**
-   * Register an external tool with the inspector.
-   *
-   * aRegObj = {
-   *   id: "toolname",
-   *   context: myTool,
-   *   label: "Button or tab label",
-   *   icon: "chrome://somepath.png",
-   *   tooltiptext: "Button tooltip",
-   *   accesskey: "S",
-   *   isOpen: object.property, (getter) returning true if tool is open.
-   *   onSelect: object.method,
-   *   show: object.method, called to show the tool when button is pressed.
-   *   hide: object.method, called to hide the tool when button is pressed.
-   *   dim: object.method, called to disable a tool during highlighting.
-   *   unregister: object.method, called when tool should be destroyed.
-   *   panel: myTool.panel, set if tool is in a separate panel, null otherwise.
-   *   sidebar: boolean, true if tool lives in sidebar tab.
-   * }
-   *
-   * @param aRegObj Object
-   *        The Registration Object used to register this tool described
-   *        above. The tool should cache this object for later deregistration.
-   */
-  registerTool: function IUI_registerTool(aRegObj)
-  {
-    if (this.toolRegistered(aRegObj.id)) {
-      return;
-    }
-
-    this.tools[aRegObj.id] = aRegObj;
-
-    let buttonContainer = this.chromeDoc.getElementById("inspector-tools");
-    let btn;
-
-    // if this is a sidebar tool, create the sidebar features for it and bail.
-    if (aRegObj.sidebar) {
-      this.createSidebarTool(aRegObj);
-      return;
-    }
-
-    btn = this.chromeDoc.createElement("toolbarbutton");
-    let buttonId = this.getToolbarButtonId(aRegObj.id);
-    btn.setAttribute("id", buttonId);
-    btn.setAttribute("class", "devtools-toolbarbutton");
-    btn.setAttribute("label", aRegObj.label);
-    btn.setAttribute("tooltiptext", aRegObj.tooltiptext);
-    btn.setAttribute("accesskey", aRegObj.accesskey);
-    btn.setAttribute("image", aRegObj.icon || "");
-    buttonContainer.insertBefore(btn, this.stylingButton);
-
-    this.bindToolEvent(btn, "click",
-      function IUI_toolButtonClick(aEvent) {
-        if (btn.checked) {
-          this.toolHide(aRegObj);
-        } else {
-          this.toolShow(aRegObj);
-        }
-      }.bind(this));
-
-    // if the tool has a panel, register the popuphiding event
-    if (aRegObj.panel) {
-      this.bindToolEvent(aRegObj.panel, "popuphiding",
-        function IUI_toolPanelHiding() {
-          btn.checked = false;
-        });
-    }
-  },
-
-  get sidebarBox()
-  {
-    return this.chromeDoc.getElementById("devtools-sidebar-box");
-  },
-
-  get sidebarToolbar()
-  {
-    return this.chromeDoc.getElementById("devtools-sidebar-toolbar");
-  },
-
-  get sidebarDeck()
-  {
-    return this.chromeDoc.getElementById("devtools-sidebar-deck");
-  },
-
-  get sidebarSplitter()
-  {
-    return this.chromeDoc.getElementById("devtools-side-splitter");
-  },
-
-  get stylingButton()
-  {
-    return this.chromeDoc.getElementById("inspector-style-button");
-  },
-
-  /**
-   * Creates a tab and tabpanel for our tool to reside in.
-   * @param {Object} aRegObj the Registration Object for our tool.
-   */
-  createSidebarTool: function IUI_createSidebarTab(aRegObj)
-  {
-    // toolbutton elements
-    let btn = this.chromeDoc.createElement("toolbarbutton");
-    let buttonId = this.getToolbarButtonId(aRegObj.id);
-
-    btn.id = buttonId;
-    btn.setAttribute("label", aRegObj.label);
-    btn.setAttribute("class", "devtools-toolbarbutton");
-    btn.setAttribute("tooltiptext", aRegObj.tooltiptext);
-    btn.setAttribute("accesskey", aRegObj.accesskey);
-    btn.setAttribute("image", aRegObj.icon || "");
-    btn.setAttribute("type", "radio");
-    btn.setAttribute("group", "sidebar-tools");
-    this.sidebarToolbar.appendChild(btn);
-
-    // create tool iframe
-    let iframe = this.chromeDoc.createElement("iframe");
-    iframe.id = "devtools-sidebar-iframe-" + aRegObj.id;
-    iframe.setAttribute("flex", "1");
-    iframe.setAttribute("tooltip", "aHTMLTooltip");
-    iframe.addEventListener("mousedown", iframe.focus);
-    this.sidebarDeck.appendChild(iframe);
-
-    // wire up button to show the iframe
-    this.bindToolEvent(btn, "click", function showIframe() {
-      this.toolShow(aRegObj);
-    }.bind(this));
-  },
-
-  /**
-   * Return the registered object's iframe.
-   * @param aRegObj see registerTool function.
-   * @return iframe or null
-   */
-  getToolIframe: function IUI_getToolIFrame(aRegObj)
-  {
-    return this.chromeDoc.getElementById("devtools-sidebar-iframe-" + aRegObj.id);
-  },
-
-  /**
-   * Show the specified tool.
-   * @param aTool Object (see comment for IUI_registerTool)
-   */
-  toolShow: function IUI_toolShow(aTool)
-  {
-    let btn = this.chromeDoc.getElementById(this.getToolbarButtonId(aTool.id));
-    btn.setAttribute("checked", "true");
-    if (aTool.sidebar) {
-      Services.prefs.setCharPref("devtools.inspector.activeSidebar", aTool.id);
-      this.store.setValue(this.winID, "activeSidebar", aTool.id);
-      this.sidebarDeck.selectedPanel = this.getToolIframe(aTool);
-      this.sidebarTools.forEach(function(other) {
-        if (other != aTool)
-          this.chromeDoc.getElementById(
-            this.getToolbarButtonId(other.id)).removeAttribute("checked");
-      }.bind(this));
-    }
-
-    aTool.show.call(aTool.context, this.selection);
-  },
-
-  /**
-   * Hide the specified tool.
-   * @param aTool Object (see comment for IUI_registerTool)
-   */
-  toolHide: function IUI_toolHide(aTool)
-  {
-    aTool.hide.call(aTool.context);
-
-    let btn = this.chromeDoc.getElementById(this.getToolbarButtonId(aTool.id));
-    btn.removeAttribute("checked");
-  },
-
-  /**
-   * Unregister the events associated with the registered tool's widget.
-   * @param aWidget XUL:widget (toolbarbutton|panel).
-   * @param aEvent a DOM event.
-   */
-  unbindToolEvent: function IUI_unbindToolEvent(aWidget, aEvent)
-  {
-    let toolEvent = aWidget.id + "_" + aEvent;
-    aWidget.removeEventListener(aEvent, this.toolEvents[toolEvent], false);
-    delete this.toolEvents[toolEvent]
-  },
-
-  /**
-   * Unregister the registered tool, unbinding click events for the buttons
-   * and showing and hiding events for the panel.
-   * @param aRegObj Object
-   *        The registration object used to register the tool.
-   */
-  unregisterTool: function IUI_unregisterTool(aRegObj)
-  {
-    // if this is a sidebar tool, use the sidebar unregistration method
-    if (aRegObj.sidebar) {
-      this.unregisterSidebarTool(aRegObj);
-      return;
-    }
-
-    let button = this.chromeDoc.getElementById(this.getToolbarButtonId(aRegObj.id));
-    let buttonContainer = this.chromeDoc.getElementById("inspector-tools");
-
-    // unbind click events on button
-    this.unbindToolEvent(button, "click");
-
-    // unbind panel popuphiding events if present.
-    if (aRegObj.panel)
-      this.unbindToolEvent(aRegObj.panel, "popuphiding");
-
-    // remove the button from its container
-    buttonContainer.removeChild(button);
-
-    // call unregister callback and remove from collection
-    if (aRegObj.unregister)
-      aRegObj.unregister.call(aRegObj.context);
-
-    delete this.tools[aRegObj.id];
-  },
-
-  /**
-   * Unregister the registered sidebar tool, unbinding click events for the
-   * button.
-   * @param aRegObj Object
-   *        The registration object used to register the tool.
-   */
-  unregisterSidebarTool: function IUI_unregisterSidebarTool(aRegObj)
-  {
-    // unbind tool button click event
-    let buttonId = this.getToolbarButtonId(aRegObj.id);
-    let btn = this.chromeDoc.getElementById(buttonId);
-    this.unbindToolEvent(btn, "click");
-
-    // Remove focus listener
-    let iframe = this.getToolIframe(aRegObj);
-    iframe.removeEventListener("mousedown", iframe.focus);
-
-    // remove sidebar buttons and tools
-    this.sidebarToolbar.removeChild(btn);
-
-    // call unregister callback and remove from collection, this also removes
-    // the iframe.
-    if (aRegObj.unregister)
-      aRegObj.unregister.call(aRegObj.context);
-
-    delete this.tools[aRegObj.id];
-  },
-
-  /**
-   * For each tool in the tools collection select the current node that is
-   * selected in the highlighter
-   * @param aScroll boolean
-   *        Do you want to scroll the treepanel?
-   */
-  toolsSelect: function IUI_toolsSelect(aScroll)
-  {
-    let selection = this.selection;
-    this.toolsDo(function IUI_toolsOnSelect(aTool) {
-      if (aTool.isOpen) {
-        aTool.onSelect.call(aTool.context, selection, aScroll);
-      }
-    });
-  },
-
-  /**
-   * Dim or undim each tool in the tools collection
-   * @param aState true = dim, false = undim
-   */
-  toolsDim: function IUI_toolsDim(aState)
-  {
-    this.toolsDo(function IUI_toolsDim(aTool) {
-      if ("dim" in aTool) {
-        aTool.dim.call(aTool.context, aState);
-      }
-    });
-  },
-
-  /**
-   * Notify registered tools of changes to the highlighted element.
-   *
-   * @param object aUpdater
-   *        The tool that triggered the update (if any), that tool's
-   *        onChanged will not be called.
-   */
-  toolsOnChanged: function IUI_toolsChanged(aUpdater)
-  {
-    this.toolsDo(function IUI_toolsOnChanged(aTool) {
-      if (("onChanged" in aTool) && aTool != aUpdater) {
-        aTool.onChanged.call(aTool.context);
-      }
-    });
-  },
-
-  /**
-   * Loop through all registered tools and pass each into the provided function
-   * @param aFunction The function to which each tool is to be passed
-   */
-  toolsDo: function IUI_toolsDo(aFunction)
-  {
-    for each (let tool in this.tools) {
-      aFunction(tool);
-    }
-  },
-
-  /**
-   * Convenience getter to retrieve only the sidebar tools.
-   */
-  get sidebarTools()
-  {
-    let sidebarTools = [];
-    for each (let tool in this.tools)
-      if (tool.sidebar)
-        sidebarTools.push(tool);
-    return sidebarTools;
-  },
-
-  /**
-   * Check if a tool is registered?
-   * @param aId The id of the tool to check
-   */
-  toolRegistered: function IUI_toolRegistered(aId)
-  {
-    return aId in this.tools;
-  },
-
-  /**
    * Destroy the InspectorUI instance. This is called by the InspectorUI API
    * "user", see BrowserShutdown() in browser.js.
    */
   destroy: function IUI_destroy()
   {
     if (this.isInspectorOpen) {
       this.closeInspectorUI();
     }
@@ -1504,47 +1128,59 @@ InspectorStore.prototype = {
    * otherwise.
    */
   isEmpty: function IS_isEmpty()
   {
     return this.length == 0 ? true : false;
   },
 
   /**
-   * Add a new store.
+   * Add a new inspector.
    *
    * @param string aID The Store ID you want created.
+   * @param Inspector aInspector The inspector to add.
    * @returns boolean True if the store was added successfully, or false
    * otherwise.
    */
-  addStore: function IS_addStore(aID)
+  addInspector: function IS_addInspector(aID, aInspector)
   {
     let result = false;
 
     if (!(aID in this.store)) {
-      this.store[aID] = {};
+      this.store[aID] = aInspector;
       this.length++;
       result = true;
     }
 
     return result;
   },
 
   /**
-   * Delete a store by ID.
+   * Get the inspector for a window, if any.
+   *
+   * @param string aID The Store ID you want created.
+   */
+  getInspector: function IS_getInspector(aID)
+  {
+    return this.store[aID] || null;
+  },
+
+  /**
+   * Delete an inspector by ID.
    *
    * @param string aID The store ID you want deleted.
    * @returns boolean True if the store was removed successfully, or false
    * otherwise.
    */
-  deleteStore: function IS_deleteStore(aID)
+  deleteInspector: function IS_deleteInspector(aID)
   {
     let result = false;
 
     if (aID in this.store) {
+      this.store[aID]._destroy();
       delete this.store[aID];
       this.length--;
       result = true;
     }
 
     return result;
   },
 
@@ -1553,73 +1189,16 @@ InspectorStore.prototype = {
    *
    * @param string aID The store ID you want to check.
    * @returns boolean True if the store ID is registered, or false otherwise.
    */
   hasID: function IS_hasID(aID)
   {
     return (aID in this.store);
   },
-
-  /**
-   * Retrieve a value from a store for a given key.
-   *
-   * @param string aID The store ID you want to read the value from.
-   * @param string aKey The key name of the value you want.
-   * @returns mixed the value associated to your store and key.
-   */
-  getValue: function IS_getValue(aID, aKey)
-  {
-    if (!this.hasID(aID))
-      return null;
-    if (aKey in this.store[aID])
-      return this.store[aID][aKey];
-    return null;
-  },
-
-  /**
-   * Set a value for a given key and store.
-   *
-   * @param string aID The store ID where you want to store the value into.
-   * @param string aKey The key name for which you want to save the value.
-   * @param mixed aValue The value you want stored.
-   * @returns boolean True if the value was stored successfully, or false
-   * otherwise.
-   */
-  setValue: function IS_setValue(aID, aKey, aValue)
-  {
-    let result = false;
-
-    if (aID in this.store) {
-      this.store[aID][aKey] = aValue;
-      result = true;
-    }
-
-    return result;
-  },
-
-  /**
-   * Delete a value for a given key and store.
-   *
-   * @param string aID The store ID where you want to store the value into.
-   * @param string aKey The key name for which you want to save the value.
-   * @returns boolean True if the value was removed successfully, or false
-   * otherwise.
-   */
-  deleteValue: function IS_deleteValue(aID, aKey)
-  {
-    let result = false;
-
-    if (aID in this.store && aKey in this.store[aID]) {
-      delete this.store[aID][aKey];
-      result = true;
-    }
-
-    return result;
-  }
 };
 
 /**
  * The InspectorProgressListener object is an nsIWebProgressListener which
  * handles onStateChange events for the inspected browser. If the user makes
  * changes to the web page and he tries to navigate away, he is prompted to
  * confirm page navigation, such that he's given the chance to prevent the loss
  * of edits.
@@ -1754,16 +1333,322 @@ InspectorProgressListener.prototype = {
     if (notification) {
       notificationBox.removeNotification(notification, true);
     }
 
     delete this.IUI;
   },
 };
 
+InspectorUI._registeredSidebars = [];
+
+/**
+ * Register an inspector sidebar template.
+ * Already running sidebars will not be affected, see bug 740665.
+ *
+ * @param aRegistration Object
+ * {
+ *   id: "toolname",
+ *   label: "Button or tab label",
+ *   icon: "chrome://somepath.png",
+ *   tooltiptext: "Button tooltip",
+ *   accesskey: "S",
+ *   contentURL: string URI, source of the tool's iframe content.
+ *   load: Called when the sidebar has been created and the contentURL loaded.
+ *         Passed an Inspector object and an iframe object.
+ *   destroy: Called when the sidebar is destroyed by the inspector.
+ *     Passed whatever was returned by the tool's create function.
+ * }
+ */
+InspectorUI.registerSidebar = function IUI_registerSidebar(aRegistration)
+{
+  // Only allow a given tool ID to be registered once.
+  if (InspectorUI._registeredSidebars.some(function(elt) elt.id == aRegistration.id))
+    return false;
+
+  InspectorUI._registeredSidebars.push(aRegistration);
+
+  return true;
+}
+
+/**
+ * Unregister a previously-registered inspector sidebar.
+ * Already running sidebars will not be affected, see bug 740665.
+ *
+ * @param aID string
+ */
+InspectorUI.unregisterSidebar = function IUI_unregisterSidebar(aID)
+{
+  InspectorUI._registeredSidebars = InspectorUI._registeredSidebars.filter(function(aReg) aReg.id != aID);
+}
+
+///////////////////////////////////////////////////////////////////////////
+//// Style Sidebar
+
+/**
+ * Manages the UI and loading of registered sidebar tools.
+ * @param aOptions object
+ *   Initialization information for the style sidebar, including:
+ *     document: The chrome document in which the style sidebar
+ *             should be created.
+ *     inspector: The Inspector object tied to this sidebar.
+ */
+function InspectorStyleSidebar(aOptions)
+{
+  this._tools = {};
+  this._chromeDoc = aOptions.document;
+  this._inspector = aOptions.inspector;
+}
+
+InspectorStyleSidebar.prototype = {
+
+  get visible() !this._box.hasAttribute("hidden"),
+  get activePanel() this._deck.selectedPanel._toolID,
+
+  destroy: function ISS_destroy()
+  {
+    for each (let toolID in Object.getOwnPropertyNames(this._tools)) {
+      this.removeTool(toolID);
+    }
+    delete this._tools;
+    this._teardown();
+  },
+
+  /**
+   * Called by InspectorUI to create the UI for a registered sidebar tool.
+   * Will create a toolbar button and an iframe for the tool.
+   * @param aRegObj object
+   *        See the documentation for InspectorUI.registerSidebar().
+   */
+  addTool: function ISS_addTool(aRegObj)
+  {
+    if (aRegObj.id in this._tools) {
+      return;
+    }
+
+    let btn = this._chromeDoc.createElement("toolbarbutton");
+    btn.setAttribute("label", aRegObj.label);
+    btn.setAttribute("class", "devtools-toolbarbutton");
+    btn.setAttribute("tooltiptext", aRegObj.tooltiptext);
+    btn.setAttribute("accesskey", aRegObj.accesskey);
+    btn.setAttribute("image", aRegObj.icon || "");
+    btn.setAttribute("type", "radio");
+    btn.setAttribute("group", "sidebar-tools");
+    this._toolbar.appendChild(btn);
+
+    // create tool iframe
+    let frame = this._chromeDoc.createElement("iframe");
+    frame.setAttribute("flex", "1");
+    frame._toolID = aRegObj.id;
+    this._deck.appendChild(frame);
+
+    // wire up button to show the iframe
+    let onClick = function() {
+      this.activatePanel(aRegObj.id);
+    }.bind(this);
+    btn.addEventListener("click", onClick, true);
+
+    this._tools[aRegObj.id] = {
+      id: aRegObj.id,
+      registration: aRegObj,
+      button: btn,
+      frame: frame,
+      loaded: false,
+      context: null,
+      onClick: onClick
+    };
+  },
+
+  /**
+   * Remove a tool from the sidebar.
+   *
+   * @param aID string
+   *        The string ID of the tool to remove.
+   */
+  removeTool: function ISS_removeTool(aID)
+  {
+    if (!aID in this._tools) {
+      return;
+    }
+    let tool = this._tools[aID];
+    delete this._tools[aID];
+
+    if (tool.loaded && tool.registration.destroy) {
+      tool.registration.destroy(tool.context);
+    }
+
+    if (tool.onLoad) {
+      tool.frame.removeEventListener("load", tool.onLoad, true);
+      delete tool.onLoad;
+    }
+
+    if (tool.onClick) {
+      tool.button.removeEventListener("click", tool.onClick, true);
+      delete tool.onClick;
+    }
+
+    tool.button.parentNode.removeChild(tool.button);
+    tool.frame.parentNode.removeChild(tool.frame);
+  },
+
+  /**
+   * Hide or show the sidebar.
+   */
+  toggle: function ISS_toggle()
+  {
+    if (!this.visible) {
+      this.show();
+    } else {
+      this.hide();
+    }
+  },
+
+  /**
+   * Shows the sidebar, updating the stored visibility pref.
+   */
+  show: function ISS_show()
+  {
+    this._box.removeAttribute("hidden");
+    this._splitter.removeAttribute("hidden");
+    this._toggleButton.checked = true;
+
+    this._showDefault();
+
+    this._inspector._sidebarOpen = true;
+    Services.prefs.setBoolPref("devtools.inspector.sidebarOpen", true);
+  },
+
+  /**
+   * Hides the sidebar, updating the stored visiblity pref.
+   */
+  hide: function ISS_hide()
+  {
+    this._teardown();
+    this._inspector._sidebarOpen = false;
+    Services.prefs.setBoolPref("devtools.inspector.sidebarOpen", false);
+  },
+
+  /**
+   * Hides the sidebar UI elements.
+   */
+  _teardown: function ISS__teardown()
+  {
+    this._toggleButton.checked = false;
+    this._box.setAttribute("hidden", true);
+    this._splitter.setAttribute("hidden", true);
+  },
+
+  /**
+   * Sets the current sidebar panel.
+   *
+   * @param aID string
+   *        The ID of the panel to make visible.
+   */
+  activatePanel: function ISS_activatePanel(aID) {
+    let tool = this._tools[aID];
+    Services.prefs.setCharPref("devtools.inspector.activeSidebar", aID);
+    this._inspector._activeSidebar = aID;
+    this._deck.selectedPanel = tool.frame;
+    this._showContent(tool);
+    tool.button.setAttribute("checked", "true");
+    let hasSelected = Array.forEach(this._toolbar.children, function(btn) {
+      if (btn != tool.button) {
+        btn.removeAttribute("checked");
+      }
+    });
+  },
+
+  /**
+   * Make the iframe content of a given tool visible.  If this is the first
+   * time the tool has been shown, load its iframe content and call the
+   * registration object's load method.
+   *
+   * @param aTool object
+   *        The tool object we're loading.
+   */
+  _showContent: function ISS__showContent(aTool)
+  {
+    // If the current tool is already loaded, notify that we're
+    // showing this sidebar.
+    if (aTool.loaded) {
+      this._inspector._emit("sidebaractivated", aTool.id);
+      this._inspector._emit("sidebaractivated-" + aTool.id);
+      return;
+    }
+
+    // If we're already loading, we're done.
+    if (aTool.onLoad) {
+      return;
+    }
+
+    // This will be canceled in removeTool if necessary.
+    aTool.onLoad = function(evt) {
+      if (evt.target.location != aTool.registration.contentURL) {
+        return;
+      }
+      aTool.frame.removeEventListener("load", aTool.onLoad, true);
+      delete aTool.onLoad;
+      aTool.loaded = true;
+      aTool.context = aTool.registration.load(this._inspector, aTool.frame);
+
+      this._inspector._emit("sidebaractivated", aTool.id);
+      this._inspector._emit("sidebaractivated-" + aTool.id);
+    }.bind(this);
+    aTool.frame.addEventListener("load", aTool.onLoad, true);
+    aTool.frame.setAttribute("src", aTool.registration.contentURL);
+  },
+
+  /**
+   * For testing purposes, mostly - return the tool-provided context
+   * for a given tool.  Will only work after the tool has been loaded
+   * and instantiated.
+   */
+  _toolContext: function ISS__toolContext(aID) {
+    return aID in this._tools ? this._tools[aID].context : null;
+  },
+
+  /**
+   * Also mostly for testing, return the list of tool objects stored in
+   * the sidebar.
+   */
+  _toolObjects: function ISS__toolObjects() {
+    return [this._tools[i] for each (i in Object.getOwnPropertyNames(this._tools))];
+  },
+
+  /**
+   * If no tool is already selected, show the last-used sidebar.  If there
+   * was no last-used sidebar, just show the first one.
+   */
+  _showDefault: function ISS__showDefault()
+  {
+    let hasSelected = Array.some(this._toolbar.children,
+      function(btn) btn.hasAttribute("checked"));
+
+    // Make sure the selected panel is loaded...
+    this._showContent(this._tools[this.activePanel]);
+
+    if (hasSelected) {
+      return;
+    }
+
+    let activeID = this._inspector._activeSidebar;
+    if (!activeID || !(activeID in this._tools)) {
+      activeID = Object.getOwnPropertyNames(this._tools)[0];
+    }
+    this.activatePanel(activeID);
+  },
+
+  // DOM elements
+  get _toggleButton() this._chromeDoc.getElementById("inspector-style-button"),
+  get _box() this._chromeDoc.getElementById("devtools-sidebar-box"),
+  get _splitter() this._chromeDoc.getElementById("devtools-side-splitter"),
+  get _toolbar() this._chromeDoc.getElementById("devtools-sidebar-toolbar"),
+  get _deck() this._chromeDoc.getElementById("devtools-sidebar-deck"),
+};
+
 ///////////////////////////////////////////////////////////////////////////
 //// HTML Breadcrumbs
 
 /**
  * Display the ancestors of the current node and its children.
  * Only one "branch" of children are displayed (only one line).
  *
  * Mechanism:
@@ -1796,16 +1681,32 @@ HTMLBreadcrumbs.prototype = {
 
     // Siblings menu
     this.menu = this.IUI.chromeDoc.createElement("menupopup");
     this.menu.id = "inspector-breadcrumbs-menu";
 
     let popupSet = this.IUI.chromeDoc.getElementById("mainPopupSet");
     popupSet.appendChild(this.menu);
 
+    // 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.menu.addEventListener("popuphiding", (function() {
       while (this.menu.hasChildNodes()) {
         this.menu.removeChild(this.menu.firstChild);
       }
       let button = this.container.querySelector("button[siblings-menu-open]");
       button.removeAttribute("siblings-menu-open");
     }).bind(this), false);
   },
@@ -1973,16 +1874,20 @@ HTMLBreadcrumbs.prototype = {
     }
   },
 
   /**
    * Remove nodes and delete properties.
    */
   destroy: function BC_destroy()
   {
+    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.menu.parentNode.removeChild(this.menu);
     this.container = null;
     this.nodeHierarchy = null;
   },
 
   /**
@@ -2065,16 +1970,22 @@ HTMLBreadcrumbs.prototype = {
   {
     let button = this.IUI.chromeDoc.createElement("button");
     let inspector = this.IUI;
     button.appendChild(this.prettyPrintNodeAsXUL(aNode));
     button.className = "inspector-breadcrumbs-button";
 
     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() {
       inspector.stopInspecting();
       inspector.select(aNode, true, true, "breadcrumbs");
     };
 
     button.onclick = (function _onBreadcrumbsRightClick(aEvent) {
       if (aEvent.button == 2) {
         this.openSiblingMenu(button, aNode);
@@ -2252,22 +2163,16 @@ HTMLBreadcrumbs.prototype = {
 //// Initializers
 
 XPCOMUtils.defineLazyGetter(InspectorUI.prototype, "strings",
   function () {
     return Services.strings.createBundle(
             "chrome://browser/locale/devtools/inspector.properties");
   });
 
-XPCOMUtils.defineLazyGetter(this, "StyleInspector", function () {
-  var obj = {};
-  Cu.import("resource:///modules/devtools/StyleInspector.jsm", obj);
-  return obj.StyleInspector;
-});
-
 XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () {
   return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
 });
 
 XPCOMUtils.defineLazyGetter(this, "clipboardHelper", function() {
   return Cc["@mozilla.org/widget/clipboardhelper;1"].
     getService(Ci.nsIClipboardHelper);
 });
--- a/browser/devtools/highlighter/test/Makefile.in
+++ b/browser/devtools/highlighter/test/Makefile.in
@@ -44,37 +44,34 @@ include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _BROWSER_FILES = \
 		browser_inspector_initialization.js \
 		browser_inspector_treeSelection.js \
 		browser_inspector_highlighter.js \
 		browser_inspector_iframeTest.js \
 		browser_inspector_scrolling.js \
-		browser_inspector_store.js \
 		browser_inspector_tab_switch.js \
 		browser_inspector_treePanel_output.js \
 		browser_inspector_treePanel_input.html \
 		browser_inspector_treePanel_result.html \
-		browser_inspector_registertools.js \
 		browser_inspector_bug_665880.js \
 		browser_inspector_bug_674871.js \
 		browser_inspector_editor.js \
 		browser_inspector_editor_name.js \
 		browser_inspector_bug_566084_location_changed.js \
 		browser_inspector_infobar.js \
 		browser_inspector_bug_690361.js \
 		browser_inspector_bug_672902_keyboard_shortcuts.js \
 		browser_inspector_keybindings.js \
 		browser_inspector_breadcrumbs.html \
 		browser_inspector_breadcrumbs.js \
 		browser_inspector_bug_699308_iframe_navigation.js \
 		browser_inspector_changes.js \
 		browser_inspector_ruleviewstore.js \
-		browser_inspector_duplicate_ruleview.js \
 		browser_inspector_invalidate.js \
 		browser_inspector_sidebarstate.js \
 		browser_inspector_treePanel_menu.js \
 		browser_inspector_pseudoclass_lock.js \
 		browser_inspector_pseudoClass_menu.js \
 		head.js \
 		$(NULL)
 
--- a/browser/devtools/highlighter/test/browser_inspector_bug_672902_keyboard_shortcuts.js
+++ b/browser/devtools/highlighter/test/browser_inspector_bug_672902_keyboard_shortcuts.js
@@ -99,17 +99,18 @@ function test()
   }
 
   function highlightParentNode()
   {
     InspectorUI.highlighter.removeListener("nodeselected", highlightParentNode);
     is(InspectorUI.selection, node, "selected body element");
 
     // Test that locking works.
-    EventUtils.synthesizeKey("VK_RETURN", { });
+    synthesizeKeyFromKeyTag("key_inspect");
+
     executeSoon(isTheNodeLocked);
   }
 
   function isTheNodeLocked()
   {
     ok(!InspectorUI.inspecting, "the node is locked");
     Services.obs.addObserver(finishUp,
                              InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED,
--- a/browser/devtools/highlighter/test/browser_inspector_changes.js
+++ b/browser/devtools/highlighter/test/browser_inspector_changes.js
@@ -45,17 +45,17 @@ function createDocument()
   doc.body.innerHTML = '<div id="testdiv">Test div!</div>';
   doc.title = "Inspector Change Test";
   startInspectorTests();
 }
 
 
 function getInspectorProp(aName)
 {
-  for each (let view in InspectorUI.stylePanel.cssHtmlTree.propertyViews) {
+  for each (let view in computedViewTree().propertyViews) {
     if (view.name == aName) {
       return view;
     }
   }
   return null;
 }
 
 function startInspectorTests()
@@ -74,28 +74,26 @@ function runInspectorTests()
   testDiv.style.fontSize = "10px";
 
   InspectorUI.inspectNode(testDiv);
   InspectorUI.stopInspecting();
 
   // Start up the style inspector panel...
   Services.obs.addObserver(stylePanelTests, "StyleInspector-populated", false);
 
-  executeSoon(function() {
-    InspectorUI.showSidebar();
-    document.getElementById(InspectorUI.getToolbarButtonId("styleinspector")).click();
-  });
+  InspectorUI.sidebar.show();
+  InspectorUI.sidebar.activatePanel("computedview");
 }
 
 function stylePanelTests()
 {
   Services.obs.removeObserver(stylePanelTests, "StyleInspector-populated");
 
-  ok(InspectorUI.isSidebarOpen, "Inspector Sidebar is open");
-  ok(InspectorUI.stylePanel.cssHtmlTree, "Style Panel has a cssHtmlTree");
+  ok(InspectorUI.sidebar.visible, "Inspector Sidebar is open");
+  ok(computedViewTree(), "Style Panel has a cssHtmlTree");
 
   let propView = getInspectorProp("font-size");
   is(propView.value, "10px", "Style inspector should be showing the correct font size.");
 
   Services.obs.addObserver(stylePanelAfterChange, "StyleInspector-populated", false);
 
   testDiv.style.fontSize = "15px";
   InspectorUI.nodeChanged();
@@ -109,21 +107,23 @@ function stylePanelAfterChange()
   is(propView.value, "15px", "Style inspector should be showing the new font size.");
 
   stylePanelNotActive();
 }
 
 function stylePanelNotActive()
 {
   // Tests changes made while the style panel is not active.
-  InspectorUI.ruleButton.click();
+  InspectorUI.sidebar.activatePanel("ruleview");
+
   executeSoon(function() {
+    Services.obs.addObserver(stylePanelAfterSwitch, "StyleInspector-populated", false);
     testDiv.style.fontSize = "20px";
-    Services.obs.addObserver(stylePanelAfterSwitch, "StyleInspector-populated", false);
-    document.getElementById(InspectorUI.getToolbarButtonId("styleinspector")).click();
+    InspectorUI.nodeChanged();
+    InspectorUI.sidebar.activatePanel("computedview");
   });
 }
 
 function stylePanelAfterSwitch()
 {
   Services.obs.removeObserver(stylePanelAfterSwitch, "StyleInspector-populated");
 
   let propView = getInspectorProp("font-size");
deleted file mode 100644
--- a/browser/devtools/highlighter/test/browser_inspector_duplicate_ruleview.js
+++ /dev/null
@@ -1,128 +0,0 @@
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-
-let div;
-let tab1;
-let tab2;
-let tab1window;
-
-function inspectorTabOpen1()
-{
-  ok(window.InspectorUI, "InspectorUI variable exists");
-  ok(!InspectorUI.inspecting, "Inspector is not highlighting");
-  ok(InspectorUI.store.isEmpty(), "Inspector.store is empty");
-
-  Services.obs.addObserver(inspectorUIOpen1,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
-  InspectorUI.openInspectorUI();
-}
-
-function inspectorUIOpen1()
-{
-  Services.obs.removeObserver(inspectorUIOpen1,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
-
-  // Make sure the inspector is open.
-  ok(InspectorUI.inspecting, "Inspector is highlighting");
-  ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
-  ok(!InspectorUI.isSidebarOpen, "Inspector Sidebar is not open");
-  ok(!InspectorUI.store.isEmpty(), "InspectorUI.store is not empty");
-  is(InspectorUI.store.length, 1, "Inspector.store.length = 1");
-
-  // Highlight a node.
-  div = content.document.getElementsByTagName("div")[0];
-  InspectorUI.inspectNode(div);
-  is(InspectorUI.selection, div, "selection matches the div element");
-
-  Services.obs.addObserver(inspectorRuleViewOpened,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY, false);
-
-  InspectorUI.showSidebar();
-  InspectorUI.openRuleView();
-}
-
-function inspectorRuleViewOpened() {
-  Services.obs.removeObserver(inspectorRuleViewOpened,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY);
-
-  // Open the second tab.
-  tab2 = gBrowser.addTab();
-  gBrowser.selectedTab = tab2;
-
-  gBrowser.selectedBrowser.addEventListener("load", function(evt) {
-    gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee,
-      true);
-    waitForFocus(inspectorTabOpen2, content);
-  }, true);
-
-  content.location = "data:text/html,<p>tab 2: the inspector should close now";
-}
-
-function inspectorTabOpen2()
-{
-  // Make sure the inspector is closed.
-  ok(!InspectorUI.inspecting, "Inspector is not highlighting");
-  ok(!InspectorUI.treePanel, "Inspector Tree Panel is closed");
-  ok(!InspectorUI.isSidebarOpen, "Inspector Sidebar is not open");
-  is(InspectorUI.store.length, 1, "Inspector.store.length = 1");
-
-  Services.obs.addObserver(inspectorFocusTab1,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY, false);
-  // Switch back to tab 1.
-  executeSoon(function() {
-    gBrowser.selectedTab = tab1;
-  });
-}
-
-function inspectorFocusTab1()
-{
-  Services.obs.removeObserver(inspectorFocusTab1,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY, false);
-  Services.obs.addObserver(inspectorRuleTrap,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY, false);
-
-  // Make sure the inspector is open.
-  ok(InspectorUI.inspecting, "Inspector is highlighting");
-  ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
-  is(InspectorUI.store.length, 1, "Inspector.store.length = 1");
-  is(InspectorUI.selection, div, "selection matches the div element");
-  ok(InspectorUI.isSidebarOpen, "sidebar is open");
-  ok(InspectorUI.isRuleViewOpen(), "rule view is open");
-
-  // The rule view element plus its popupSet
-  is(InspectorUI.ruleView.doc.documentElement.children.length, 2, "RuleView elements.length == 2");
-
-  requestLongerTimeout(4);
-  executeSoon(function() {
-    InspectorUI.closeInspectorUI();
-    gBrowser.removeCurrentTab(); // tab 1
-    gBrowser.removeCurrentTab(); // tab 2
-    finish();
-  });
-}
-
-function inspectorRuleTrap()
-{
-  Services.obs.removeObserver(inspectorRuleTrap,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY, false);
-  is(InspectorUI.ruleView.doc.documentElement.children.length, 1, "RuleView elements.length == 1");
-}
-
-function test()
-{
-  waitForExplicitFinish();
-
-  tab1 = gBrowser.addTab();
-  gBrowser.selectedTab = tab1;
-  gBrowser.selectedBrowser.addEventListener("load", function(evt) {
-    gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee,
-      true);
-    waitForFocus(inspectorTabOpen1, content);
-  }, true);
-
-  content.location = "data:text/html,<p>tab switching tests for inspector" +
-    "<div>tab 1</div>";
-}
-
--- a/browser/devtools/highlighter/test/browser_inspector_editor.js
+++ b/browser/devtools/highlighter/test/browser_inspector_editor.js
@@ -72,19 +72,19 @@ function doEditorTestSteps()
   is(attrValNode_class.innerHTML, "barbaz", "we have the correct `class` attribute-value node in the HTML panel");
 
   // double-click the `id` attribute-value node to open the editor
   executeSoon(function() {
     // firing 2 clicks right in a row to simulate a double-click
     EventUtils.synthesizeMouse(attrValNode_id, 2, 2, {clickCount: 2}, attrValNode_id.ownerDocument.defaultView);
   });
 
+
   yield; // End of Step 1
 
-
   // Step 2: validate editing session, enter new attribute value into editor, and save input
   ok(InspectorUI.treePanel.editingContext, "Step 2: editor session started");
   let selection = InspectorUI.selection;
 
   ok(selection, "Selection is: " + selection);
 
   let editorVisible = editor.classList.contains("editing");
   ok(editorVisible, "editor popup visible");
--- a/browser/devtools/highlighter/test/browser_inspector_initialization.js
+++ b/browser/devtools/highlighter/test/browser_inspector_initialization.js
@@ -84,47 +84,43 @@ function runInspectorTests()
 
   InspectorUI.treePanel.open();
 }
 
 function treePanelTests()
 {
   Services.obs.removeObserver(treePanelTests,
     InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY);
-  Services.obs.addObserver(stylePanelTests,
-    "StyleInspector-opened", false);
-
   ok(InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is open");
 
-  executeSoon(function() {
-    InspectorUI.showSidebar();
-    document.getElementById(InspectorUI.getToolbarButtonId("styleinspector")).click();
-  });
+  InspectorUI.sidebar.show();
+  InspectorUI.currentInspector.once("sidebaractivated-computedview",
+    stylePanelTests)
+  InspectorUI.sidebar.activatePanel("computedview");
 }
 
 function stylePanelTests()
 {
-  Services.obs.removeObserver(stylePanelTests, "StyleInspector-opened");
+  ok(InspectorUI.sidebar.visible, "Inspector Sidebar is open");
+  is(InspectorUI.sidebar.activePanel, "computedview", "Computed View is open");
+  ok(computedViewTree(), "Computed view has a cssHtmlTree");
 
-  ok(InspectorUI.isSidebarOpen, "Inspector Sidebar is open");
-  ok(InspectorUI.stylePanel.cssHtmlTree, "Style Panel has a cssHtmlTree");
-
-  InspectorUI.ruleButton.click();
+  InspectorUI.sidebar.activatePanel("ruleview");
   executeSoon(function() {
     ruleViewTests();
   });
 }
 
 function ruleViewTests()
 {
   Services.obs.addObserver(runContextMenuTest,
       InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
 
-  ok(InspectorUI.isRuleViewOpen(), "Rule View is open");
-  ok(InspectorUI.ruleView, "InspectorUI has a cssRuleView");
+  is(InspectorUI.sidebar.activePanel, "ruleview", "Rule View is open");
+  ok(ruleView(), "InspectorUI has a cssRuleView");
 
   executeSoon(function() {
     InspectorUI.closeInspectorUI();
   });
 }
 
 function runContextMenuTest()
 {
@@ -147,18 +143,16 @@ function runContextMenuTest()
 }
 
 function inspectNodesFromContextTest()
 {
   Services.obs.removeObserver(inspectNodesFromContextTest, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
   Services.obs.addObserver(openInspectorForContextTest, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
   ok(!InspectorUI.inspecting, "Inspector is not actively highlighting");
   is(InspectorUI.selection, salutation, "Inspector is highlighting salutation");
-  ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is closed");
-  ok(!InspectorUI.stylePanel.isOpen(), "Inspector Style Panel is closed");
   executeSoon(function() {
     InspectorUI.closeInspectorUI(true);
   });
 }
 
 function openInspectorForContextTest()
 {
   Services.obs.removeObserver(openInspectorForContextTest, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
@@ -202,21 +196,17 @@ function finishInspectorTests(subject, t
 {
   Services.obs.removeObserver(finishInspectorTests,
     InspectorUI.INSPECTOR_NOTIFICATIONS.DESTROYED);
 
   is(parseInt(aWinIdString), winId, "winId of destroyed Inspector matches");
   ok(!InspectorUI.highlighter, "Highlighter is gone");
   ok(!InspectorUI.treePanel, "Inspector Tree Panel is closed");
   ok(!InspectorUI.inspecting, "Inspector is not inspecting");
-  ok(!InspectorUI.isSidebarOpen, "Inspector Sidebar is closed");
-  ok(!InspectorUI.stylePanel, "Inspector Style Panel is gone");
-  ok(!InspectorUI.ruleView, "Inspector Rule View is gone");
-  is(InspectorUI.sidebarToolbar.children.length, 0, "No items in the Sidebar toolbar");
-  is(InspectorUI.sidebarDeck.children.length, 0, "No items in the Sidebar deck");
+  ok(!InspectorUI._sidebar, "Inspector Sidebar is closed");
   ok(!InspectorUI.toolbar, "toolbar is hidden");
 
   Services.obs.removeObserver(inspectNodesFromContextTestTrap, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
   gBrowser.removeCurrentTab();
   finish();
 }
 
 function test()
--- a/browser/devtools/highlighter/test/browser_inspector_keybindings.js
+++ b/browser/devtools/highlighter/test/browser_inspector_keybindings.js
@@ -35,31 +35,29 @@ function test()
       InspectorUI.highlighter.addListener("nodeselected", lockNode);
       InspectorUI.inspectNode(node);
     });
   }
 
   function lockNode()
   {
     InspectorUI.highlighter.removeListener("nodeselected", lockNode);
-    EventUtils.synthesizeKey("VK_RETURN", { });
-
+    synthesizeKeyFromKeyTag("key_inspect");
     executeSoon(isTheNodeLocked);
   }
 
   function isTheNodeLocked()
   {
     is(InspectorUI.selection, node, "selection matches node");
     ok(!InspectorUI.inspecting, "the node is locked");
     unlockNode();
   }
 
   function unlockNode() {
-    EventUtils.synthesizeKey("VK_RETURN", { });
-
+    synthesizeKeyFromKeyTag("key_inspect");
     executeSoon(isTheNodeUnlocked);
   }
 
   function isTheNodeUnlocked()
   {
     ok(InspectorUI.inspecting, "the node is unlocked");
 
     // Let's close the inspector
--- a/browser/devtools/highlighter/test/browser_inspector_pseudoclass_lock.js
+++ b/browser/devtools/highlighter/test/browser_inspector_pseudoclass_lock.js
@@ -18,64 +18,60 @@ function test()
     doc = content.document;
     waitForFocus(createDocument, content);
   }, true);
 
   content.location = "data:text/html,pseudo-class lock tests";
 }
 
 function createDocument()
-{  
+{
   div = doc.createElement("div");
   div.textContent = "test div";
 
   let head = doc.getElementsByTagName('head')[0];
   let style = doc.createElement('style');
   let rules = doc.createTextNode('div { color: red; } div:hover { color: blue; }');
 
   style.appendChild(rules);
   head.appendChild(style);
   doc.body.appendChild(div);
-  
+
   setupTests();
 }
 
 function setupTests()
 {
   Services.obs.addObserver(selectNode,
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
   InspectorUI.openInspectorUI();
 }
 
 function selectNode()
 {
   Services.obs.removeObserver(selectNode,
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
 
   executeSoon(function() {
-    InspectorUI.highlighter.addListener("nodeselected", openRuleView);
+    InspectorUI.highlighter.addListener("locked", openRuleView);
     InspectorUI.inspectNode(div);
+    InspectorUI.stopInspecting();
   });
 }
 
 function openRuleView()
 {
-  Services.obs.addObserver(performTests,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY, false);
-
-  InspectorUI.showSidebar();
-  InspectorUI.openRuleView();
+  InspectorUI.sidebar.show();
+  InspectorUI.currentInspector.once("sidebaractivated-ruleview", performTests);
+  InspectorUI.sidebar.activatePanel("ruleview");
 }
 
 function performTests()
 {
-  Services.obs.removeObserver(performTests,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY);
-
-  InspectorUI.highlighter.removeListener("nodeselected", performTests);
+  InspectorUI.highlighter.removeListener("locked", performTests);
 
   // toggle the class
   InspectorUI.highlighter.pseudoClassLockToggled(pseudo);
 
   testAdded();
 
   // toggle the lock off
   InspectorUI.highlighter.pseudoClassLockToggled(pseudo);
@@ -102,20 +98,20 @@ function testAdded()
     node = node.parentNode;
   } while (node.parentNode)
 
   // infobar selector contains pseudo-class
   let pseudoClassesBox = document.getElementById("highlighter-nodeinfobar-pseudo-classes");
   is(pseudoClassesBox.textContent, pseudo, "pseudo-class in infobar selector");
   
   // ruleview contains pseudo-class rule
-  is(InspectorUI.ruleView.element.children.length, 3,
+  is(ruleView().element.children.length, 3,
      "rule view is showing 3 rules for pseudo-class locked div");
      
-  is(InspectorUI.ruleView.element.children[1]._ruleEditor.rule.selectorText,
+  is(ruleView().element.children[1]._ruleEditor.rule.selectorText,
      "div:hover", "rule view is showing " + pseudo + " rule");
 }
 
 function testRemoved()
 {
   // lock removed from node and ancestors  
   let node = div;
   do {
@@ -127,17 +123,17 @@ function testRemoved()
 
 function testRemovedFromUI()
 {
   // infobar selector doesn't contain pseudo-class
   let pseudoClassesBox = document.getElementById("highlighter-nodeinfobar-pseudo-classes");
   is(pseudoClassesBox.textContent, "", "pseudo-class removed from infobar selector");    
 
   // ruleview no longer contains pseudo-class rule
-  is(InspectorUI.ruleView.element.children.length, 2,
+  is(ruleView().element.children.length, 2,
      "rule view is showing 2 rules after removing lock");    
 }
 
 function testInspectorClosed()
 {
   Services.obs.removeObserver(testInspectorClosed,
     InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
 
deleted file mode 100644
--- a/browser/devtools/highlighter/test/browser_inspector_registertools.js
+++ /dev/null
@@ -1,254 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Inspector Highlighter Tests.
- *
- * The Initial Developer of the Original Code is
- * The Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2011
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Michael Ratcliffe <mratcliffe@mozilla.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-let doc;
-let h1;
-let p2;
-let toolsLength = 0;
-let toolEvents = 0;
-let tool1;
-let tool2;
-let tool3;
-let initToolsMethod = InspectorUI.initTools;
-
-function createDocument()
-{
-  let div = doc.createElement("div");
-  h1 = doc.createElement("h1");
-  let p1 = doc.createElement("p");
-  p2 = doc.createElement("p");
-  let div2 = doc.createElement("div");
-  let p3 = doc.createElement("p");
-  doc.title = "Inspector Tree Selection Test";
-  h1.textContent = "Inspector Tree Selection Test";
-  p1.textContent = "This is some example text";
-  p2.textContent = "Lorem ipsum dolor sit amet, consectetur adipisicing " +
-    "elit, sed do eiusmod tempor incididunt ut labore et dolore magna " +
-    "aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco " +
-    "laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure " +
-    "dolor in reprehenderit in voluptate velit esse cillum dolore eu " +
-    "fugiat nulla pariatur. Excepteur sint occaecat cupidatat non " +
-    "proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
-  p3.textContent = "Lorem ipsum dolor sit amet, consectetur adipisicing " +
-    "elit, sed do eiusmod tempor incididunt ut labore et dolore magna " +
-    "aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco " +
-    "laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure " +
-    "dolor in reprehenderit in voluptate velit esse cillum dolore eu " +
-    "fugiat nulla pariatur. Excepteur sint occaecat cupidatat non " +
-    "proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
-  div.appendChild(h1);
-  div.appendChild(p1);
-  div.appendChild(p2);
-  div2.appendChild(p3);
-  doc.body.appendChild(div);
-  doc.body.appendChild(div2);
-  setupHighlighterTests();
-}
-
-function setupHighlighterTests()
-{
-  ok(h1, "we have the header node");
-  Services.obs.addObserver(inspectorOpen, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
-  registerTools();
-  InspectorUI.toggleInspectorUI();
-}
-
-function inspectorOpen()
-{
-  info("we received the inspector-opened notification");
-  Services.obs.removeObserver(inspectorOpen, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
-  toolsLength = InspectorUI.tools.length;
-  toolEvents = InspectorUI.toolEvents.length;
-  info("tools registered");
-  InspectorUI.highlighter.addListener("nodeselected", startToolTests);
-  InspectorUI.inspectNode(h1);
-}
-
-function startToolTests(evt)
-{
-  InspectorUI.highlighter.removeListener("nodeselected", startToolTests);
-  InspectorUI.stopInspecting();
-  info("Getting InspectorUI.tools");
-  let tools = InspectorUI.tools;
-
-  tool1 = InspectorUI.tools["tool_1"];
-  tool2 = InspectorUI.tools["tool_2"];
-  tool3 = InspectorUI.tools["tool_3"];
-
-  info("Checking panel states 1");
-  ok(!tool1.isOpen, "Panel 1 is closed");
-  ok(!tool2.isOpen, "Panel 2 is closed");
-  ok(!tool3.isOpen, "Panel 3 is closed");
-
-  info("Calling show method for all tools");
-  InspectorUI.toolShow(tool1);
-  InspectorUI.toolShow(tool2);
-  InspectorUI.toolShow(tool3);
-
-  info("Checking panel states 2");
-  ok(tool1.isOpen, "Panel 1 is open");
-  ok(tool2.isOpen, "Panel 2 is open");
-  ok(tool3.isOpen, "Panel 3 is open");
-
-  info("Calling selectNode method for all tools, should see 3 selects");
-  InspectorUI.inspectNode(p2);
-
-  info("Calling hide method for all tools");
-  InspectorUI.toolHide(tool1);
-  InspectorUI.toolHide(tool2);
-  InspectorUI.toolHide(tool3);
-  
-  info("Checking panel states 3");
-  ok(!tool1.isOpen, "Panel 1 is closed");
-  ok(!tool2.isOpen, "Panel 2 is closed");
-  ok(!tool3.isOpen, "Panel 3 is closed");
-
-  info("Showing tools 1 & 3");
-  InspectorUI.toolShow(tool1);
-  InspectorUI.toolShow(tool3);
-
-  info("Checking panel states 4");
-  ok(tool1.isOpen, "Panel 1 is open");
-  ok(!tool2.isOpen, "Panel 2 is closed");
-  ok(tool3.isOpen, "Panel 3 is open");
-
-  Services.obs.addObserver(unregisterTools, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
-  InspectorUI.closeInspectorUI(true);
-}
-
-function unregisterTools()
-{
-  Services.obs.removeObserver(unregisterTools, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
-  let tools = InspectorUI.tools;
-
-  ok(!(tool1 in tools), "Tool 1 removed");
-  ok(!(tool2 in tools), "Tool 2 removed");
-  ok(!(tool3 in tools), "Tool 3 removed");
-  is(tools.length, toolsLength, "Number of Registered Tools matches original");
-  is(InspectorUI.toolEvents.length, toolEvents, "Number of tool events matches original");
-  finishUp();
-}
-
-function finishUp() {
-  gBrowser.removeCurrentTab();
-  InspectorUI.initTools = initToolsMethod;
-  finish();
-}
-
-function test()
-{
-  waitForExplicitFinish();
-  ignoreAllUncaughtExceptions();
-  gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", function() {
-    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
-    doc = content.document;
-    waitForFocus(createDocument, content);
-  }, true);
-  
-  content.location = "data:text/html,registertool tests for inspector";
-}
-
-function registerTools()
-{
-  InspectorUI.initTools = function() {
-    info("(re)registering tools");
-    registerTool(new testTool("tool_1", "Tool 1", "Tool 1 tooltip", "I"));
-    registerTool(new testTool("tool_2", "Tool 2", "Tool 2 tooltip", "J"));
-    registerTool(new testTool("tool_3", "Tool 3", "Tool 3 tooltip", "K"));
-  }
-}
-
-function registerTool(aTool)
-{
-  InspectorUI.registerTool({
-    id: aTool.id,
-    label: aTool.label,
-    tooltiptext: aTool.tooltip,
-    accesskey: aTool.accesskey,
-    context: aTool,
-    get isOpen() aTool.isOpen(),
-    onSelect: aTool.selectNode,
-    show: aTool.show,
-    hide: aTool.hide,
-    unregister: aTool.destroy,
-  });
-}
-
-// Tool Object
-function testTool(aToolId, aLabel, aTooltip, aAccesskey)
-{
-  this.id = aToolId;
-  this.label = aLabel;
-  this.tooltip = aTooltip;
-  this.accesskey = aAccesskey;
-  this._isOpen = false;
-}
-
-testTool.prototype = {
-  isOpen: function BIR_isOpen() {
-    return this._isOpen;
-  },
-
-  selectNode: function BIR_selectNode(aNode) {
-    is(InspectorUI.selection, aNode,
-       "selectNode: currently selected node was passed: " + this.id);
-  },
-
-  show: function BIR_show(aNode) {
-    this._isOpen = true;
-    is(InspectorUI.selection, aNode,
-       "show: currently selected node was passed: " + this.id);
-  },
-
-  hide: function BIR_hide() {
-    info(this.id + " hide");
-    this._isOpen = false;
-  },
-
-  destroy: function BIR_destroy() {
-    info("tool destroyed " + this.id);
-    if (this.isOpen())
-      this.hide();
-    delete this.id;
-    delete this.label;
-    delete this.tooltip;
-    delete this.accesskey;
-  },
-};
--- a/browser/devtools/highlighter/test/browser_inspector_ruleviewstore.js
+++ b/browser/devtools/highlighter/test/browser_inspector_ruleviewstore.js
@@ -43,28 +43,17 @@
  * Tests that properties disabled in the rule view survive a tab switch.
  */
 
 let div;
 let tab1;
 
 function waitForRuleView(aCallback)
 {
-  if (InspectorUI.ruleView) {
-    aCallback();
-    return;
-  }
-
-  let ruleViewFrame = InspectorUI.getToolIframe(InspectorUI.ruleViewObject);
-  ruleViewFrame.addEventListener("load", function(evt) {
-    ruleViewFrame.removeEventListener(evt.type, arguments.callee, true);
-    executeSoon(function() {
-      aCallback();
-    });
-  }, true);
+  InspectorUI.currentInspector.once("sidebaractivated-ruleview", aCallback);
 }
 
 function inspectorTabOpen1()
 {
   Services.obs.addObserver(inspectorUIOpen1,
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
   InspectorUI.openInspectorUI();
 }
@@ -72,27 +61,27 @@ function inspectorTabOpen1()
 function inspectorUIOpen1()
 {
   Services.obs.removeObserver(inspectorUIOpen1,
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
 
   // Highlight a node.
   div = content.document.getElementsByTagName("div")[0];
   InspectorUI.inspectNode(div);
+  InspectorUI.stopInspecting();
 
   // Open the rule view sidebar.
   waitForRuleView(ruleViewOpened1);
-
-  InspectorUI.showSidebar();
-  InspectorUI.ruleButton.click();
+  InspectorUI.sidebar.show();
+  InspectorUI.sidebar.activatePanel("ruleview");
 }
 
 function ruleViewOpened1()
 {
-  let prop = InspectorUI.ruleView._elementStyle.rules[0].textProps[0];
+  let prop = ruleView()._elementStyle.rules[0].textProps[0];
   is(prop.name, "background-color", "First prop is the background color prop.");
   prop.setEnabled(false);
 
   // Open second tab and switch to it
   gBrowser.selectedTab = gBrowser.addTab();
 
   gBrowser.selectedBrowser.addEventListener("load", function(evt) {
     gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee,
@@ -119,17 +108,17 @@ function inspectorFocusTab1()
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
 
   // Now wait for the rule view to load again...
   waitForRuleView(ruleViewOpened2);
 }
 
 function ruleViewOpened2()
 {
-  let prop = InspectorUI.ruleView._elementStyle.rules[0].textProps[0];
+  let prop = ruleView()._elementStyle.rules[0].textProps[0];
   is(prop.name, "background-color", "First prop is the background color prop.");
   ok(!prop.enabled, "First prop should be disabled.");
 
   InspectorUI.closeInspectorUI();
   gBrowser.removeCurrentTab();
   finish();
 }
 
--- a/browser/devtools/highlighter/test/browser_inspector_sidebarstate.js
+++ b/browser/devtools/highlighter/test/browser_inspector_sidebarstate.js
@@ -3,32 +3,31 @@
 
 let doc;
 
 function createDocument()
 {
   doc.body.innerHTML = '<h1>Sidebar state test</h1>';
   doc.title = "Sidebar State Test";
 
+  InspectorUI.openInspectorUI();
+
   // Open the sidebar and wait for the default view (the rule view) to show.
-  Services.obs.addObserver(inspectorRuleViewOpened,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY, false);
+  InspectorUI.currentInspector.once("sidebaractivated-ruleview", inspectorRuleViewOpened);
 
-  InspectorUI.openInspectorUI();
-  InspectorUI.showSidebar();
+  InspectorUI.sidebar.show();
+  InspectorUI.sidebar.activatePanel("ruleview");
 }
 
 function inspectorRuleViewOpened()
 {
-  Services.obs.removeObserver(inspectorRuleViewOpened,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY);
-  is(InspectorUI.activeSidebarPanel, "ruleview", "Rule View is selected by default");
+  is(InspectorUI.sidebar.activePanel, "ruleview", "Rule View is selected by default");
 
   // Select the computed view and turn off the inspector.
-  InspectorUI.activateSidebarPanel("styleinspector");
+  InspectorUI.sidebar.activatePanel("computedview");
 
   Services.obs.addObserver(inspectorClosed,
     InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
   InspectorUI.closeInspectorUI();
 }
 
 function inspectorClosed()
 {
@@ -41,17 +40,17 @@ function inspectorClosed()
 
   InspectorUI.openInspectorUI();
 }
 
 function computedViewPopulated()
 {
   Services.obs.removeObserver(computedViewPopulated,
     "StyleInspector-populated");
-  is(InspectorUI.activeSidebarPanel, "styleinspector", "Computed view is selected by default.");
+  is(InspectorUI.sidebar.activePanel, "computedview", "Computed view is selected by default.");
 
   finishTest();
 }
 
 
 function finishTest()
 {
   InspectorUI.closeInspectorUI();
deleted file mode 100644
--- a/browser/devtools/highlighter/test/browser_inspector_store.js
+++ /dev/null
@@ -1,106 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Inspector Store Tests.
- *
- * The Initial Developer of the Original Code is
- * The Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2010
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Mihai Șucan <mihai.sucan@gmail.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-function test()
-{
-  let InspectorStore = InspectorUI.store;
-
-  is(InspectorStore.length, 0, "InspectorStore is empty");
-  ok(InspectorStore.isEmpty(), "InspectorStore is empty (confirmed)");
-  is(typeof InspectorStore.store, "object",
-    "InspectorStore.store is an object");
-
-  ok(InspectorStore.addStore("foo"), "addStore('foo') returns true");
-
-  is(InspectorStore.length, 1, "InspectorStore.length = 1");
-  ok(!InspectorStore.isEmpty(), "InspectorStore is not empty");
-  is(typeof InspectorStore.store.foo, "object", "store.foo is an object");
-
-  ok(InspectorStore.addStore("fooBar"), "addStore('fooBar') returns true");
-
-  is(InspectorStore.length, 2, "InspectorStore.length = 2");
-  is(typeof InspectorStore.store.fooBar, "object", "store.fooBar is an object");
-
-  ok(!InspectorStore.addStore("fooBar"), "addStore('fooBar') returns false");
-
-  ok(InspectorStore.deleteStore("fooBar"),
-    "deleteStore('fooBar') returns true");
-
-  is(InspectorStore.length, 1, "InspectorStore.length = 1");
-  ok(!InspectorStore.store.fooBar, "store.fooBar is deleted");
-
-  ok(!InspectorStore.deleteStore("fooBar"),
-    "deleteStore('fooBar') returns false");
-
-  ok(!InspectorStore.hasID("fooBar"), "hasID('fooBar') returns false");
-
-  ok(InspectorStore.hasID("foo"), "hasID('foo') returns true");
-
-  ok(InspectorStore.setValue("foo", "key1", "val1"), "setValue() returns true");
-
-  ok(!InspectorStore.setValue("fooBar", "key1", "val1"),
-    "setValue() returns false");
-
-  is(InspectorStore.getValue("foo", "key1"), "val1",
-    "getValue() returns the correct value");
-
-  is(InspectorStore.store.foo.key1, "val1", "store.foo.key1 = 'val1'");
-
-  ok(!InspectorStore.getValue("fooBar", "key1"),
-    "getValue() returns null for unknown store");
-
-  ok(!InspectorStore.getValue("fooBar", "key1"),
-    "getValue() returns null for unknown store");
-
-  ok(InspectorStore.deleteValue("foo", "key1"),
-    "deleteValue() returns true for known value");
-
-  ok(!InspectorStore.store.foo.key1, "deleteValue() removed the value.");
-
-  ok(!InspectorStore.deleteValue("fooBar", "key1"),
-    "deleteValue() returns false for unknown store.");
-
-  ok(!InspectorStore.deleteValue("foo", "key1"),
-    "deleteValue() returns false for unknown value.");
-
-  ok(InspectorStore.deleteStore("foo"), "deleteStore('foo') returns true");
-
-  ok(InspectorStore.isEmpty(), "InspectorStore is empty");
-}
-
--- a/browser/devtools/highlighter/test/browser_inspector_tab_switch.js
+++ b/browser/devtools/highlighter/test/browser_inspector_tab_switch.js
@@ -57,17 +57,17 @@ function inspectorTabOpen1()
 function inspectorUIOpen1()
 {
   Services.obs.removeObserver(inspectorUIOpen1,
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
 
   // Make sure the inspector is open.
   ok(InspectorUI.inspecting, "Inspector is highlighting");
   ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
-  ok(!InspectorUI.isSidebarOpen, "Inspector Sidebar is not open");
+  ok(!InspectorUI.sidebar.visible, "Inspector Sidebar is not open");
   ok(!InspectorUI.store.isEmpty(), "InspectorUI.store is not empty");
   is(InspectorUI.store.length, 1, "Inspector.store.length = 1");
 
   // Highlight a node.
   div = content.document.getElementsByTagName("div")[0];
   InspectorUI.inspectNode(div);
   is(InspectorUI.selection, div, "selection matches the div element");
 
@@ -84,17 +84,16 @@ function inspectorUIOpen1()
   content.location = "data:text/html,<p>tab 2: the inspector should close now";
 }
 
 function inspectorTabOpen2()
 {
   // Make sure the inspector is closed.
   ok(!InspectorUI.inspecting, "Inspector is not highlighting");
   ok(!InspectorUI.treePanel, "Inspector Tree Panel is closed");
-  ok(!InspectorUI.isSidebarOpen, "Inspector Sidebar is not open");
   is(InspectorUI.store.length, 1, "Inspector.store.length = 1");
 
   // Activate the inspector again.
   executeSoon(function() {
     Services.obs.addObserver(inspectorUIOpen2,
       InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
     clearUserPrefs();
     InspectorUI.openInspectorUI();
@@ -110,16 +109,17 @@ function inspectorUIOpen2()
   ok(InspectorUI.inspecting, "Inspector is highlighting");
   ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
   is(InspectorUI.store.length, 2, "Inspector.store.length = 2");
 
   // Disable highlighting.
   InspectorUI.toggleInspection();
   ok(!InspectorUI.inspecting, "Inspector is not highlighting");
 
+
   // Switch back to tab 1.
   executeSoon(function() {
     Services.obs.addObserver(inspectorFocusTab1,
       InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
     gBrowser.selectedTab = tab1;
   });
 }
 
@@ -145,58 +145,60 @@ function inspectorOpenTreePanelTab1()
   Services.obs.removeObserver(inspectorOpenTreePanelTab1,
     InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY);
 
   ok(InspectorUI.inspecting, "Inspector is highlighting");
   ok(InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is open");
   is(InspectorUI.store.length, 2, "Inspector.store.length = 2");
   is(InspectorUI.selection, div, "selection matches the div element");
 
-  Services.obs.addObserver(inspectorSidebarStyleView1, "StyleInspector-opened", false);
+  InspectorUI.currentInspector.once("sidebaractivated-computedview",
+    inspectorSidebarStyleView1);
 
   executeSoon(function() {
-    InspectorUI.showSidebar();
-    InspectorUI.activateSidebarPanel("styleinspector");
+    InspectorUI.sidebar.show();
+    InspectorUI.sidebar.activatePanel("computedview");
   });
 }
 
 function inspectorSidebarStyleView1()
 {
-  Services.obs.removeObserver(inspectorSidebarStyleView1, "StyleInspector-opened");
-  ok(InspectorUI.isSidebarOpen, "Inspector Sidebar is open");
-  ok(InspectorUI.stylePanel, "Inspector Has a Style Panel Instance");
-  InspectorUI.sidebarTools.forEach(function(aTool) {
-    let btn = document.getElementById(InspectorUI.getToolbarButtonId(aTool.id));
+  ok(InspectorUI.sidebar.visible, "Inspector Sidebar is open");
+  ok(computedView(), "Inspector Has a computed view Instance");
+
+  InspectorUI.sidebar._toolObjects().forEach(function (aTool) {
+    let btn = aTool.button;
     is(btn.hasAttribute("checked"),
-      (aTool == InspectorUI.stylePanel.registrationObject),
-      "Button " + btn.id + " has correct checked attribute");
+      (aTool.id == "computedview"),
+      "Button " + btn.label + " has correct checked attribute");
   });
 
   // Switch back to tab 2.
   Services.obs.addObserver(inspectorFocusTab2,
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
   gBrowser.selectedTab = tab2;
 }
 
 function inspectorFocusTab2()
 {
   Services.obs.removeObserver(inspectorFocusTab2,
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
 
   // Make sure the inspector is still open.
   ok(!InspectorUI.inspecting, "Inspector is not highlighting");
   ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
-  ok(!InspectorUI.isSidebarOpen, "Inspector Sidebar is not open");
+  ok(!InspectorUI.sidebar.visible, "Inspector Sidebar is not open");
   is(InspectorUI.store.length, 2, "Inspector.store.length is 2");
   isnot(InspectorUI.selection, div, "selection does not match the div element");
 
-  // Make sure keybindings still sork
-  EventUtils.synthesizeKey("VK_RETURN", { });
 
   executeSoon(function() {
+    // Make sure keybindings still work
+    synthesizeKeyFromKeyTag("key_inspect");
+
     ok(InspectorUI.inspecting, "Inspector is highlighting");
     InspectorUI.toggleInspection();
 
     // Switch back to tab 1.
     Services.obs.addObserver(inspectorSecondFocusTab1,
       InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
     gBrowser.selectedTab = tab1;
   });
@@ -207,23 +209,23 @@ function inspectorSecondFocusTab1()
   Services.obs.removeObserver(inspectorSecondFocusTab1,
     InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY);
 
   ok(InspectorUI.inspecting, "Inspector is highlighting");
   ok(InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is open");
   is(InspectorUI.store.length, 2, "Inspector.store.length = 2");
   is(InspectorUI.selection, div, "selection matches the div element");
 
-  ok(InspectorUI.isSidebarOpen, "Inspector Sidebar is open");
-  ok(InspectorUI.stylePanel, "Inspector Has a Style Panel Instance");
-  InspectorUI.sidebarTools.forEach(function(aTool) {
-    let btn = document.getElementById(InspectorUI.getToolbarButtonId(aTool.id));
+  ok(InspectorUI.sidebar.visible, "Inspector Sidebar is open");
+  ok(computedView(), "Inspector Has a Style Panel Instance");
+  InspectorUI.sidebar._toolObjects().forEach(function(aTool) {
+    let btn = aTool.button;
     is(btn.hasAttribute("checked"),
-      (aTool == InspectorUI.stylePanel.registrationObject),
-      "Button " + btn.id + " has correct checked attribute");
+      (aTool.id == "computedview"),
+      "Button " + btn.label + " has correct checked attribute");
   });
 
   // Switch back to tab 2.
   Services.obs.addObserver(inspectorSecondFocusTab2,
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
   gBrowser.selectedTab = tab2;
 }
 
--- a/browser/devtools/highlighter/test/head.js
+++ b/browser/devtools/highlighter/test/head.js
@@ -84,8 +84,47 @@ function getHighlitNode()
 function midPoint(aPointA, aPointB)
 {
   let pointC = { };
   pointC.x = (aPointB.x - aPointA.x) / 2 + aPointA.x;
   pointC.y = (aPointB.y - aPointA.y) / 2 + aPointA.y;
   return pointC;
 }
 
+function computedView()
+{
+  return InspectorUI.sidebar._toolContext("computedview");
+}
+
+function computedViewTree()
+{
+  return computedView().view;
+}
+
+function ruleView()
+{
+  return InspectorUI.sidebar._toolContext("ruleview").view;
+}
+function synthesizeKeyFromKeyTag(aKeyId) {
+  let key = document.getElementById(aKeyId);
+  isnot(key, null, "Successfully retrieved the <key> node");
+
+  let modifiersAttr = key.getAttribute("modifiers");
+
+  let name = null;
+
+  if (key.getAttribute("keycode"))
+    name = key.getAttribute("keycode");
+  else if (key.getAttribute("key"))
+    name = key.getAttribute("key");
+
+  isnot(name, null, "Successfully retrieved keycode/key");
+
+  let modifiers = {
+    shiftKey: modifiersAttr.match("shift"),
+    ctrlKey: modifiersAttr.match("ctrl"),
+    altKey: modifiersAttr.match("alt"),
+    metaKey: modifiersAttr.match("meta"),
+    accelKey: modifiersAttr.match("accel")
+  }
+
+  EventUtils.synthesizeKey(name, modifiers);
+}
--- a/browser/devtools/styleinspector/CssHtmlTree.jsm
+++ b/browser/devtools/styleinspector/CssHtmlTree.jsm
@@ -301,46 +301,60 @@ CssHtmlTree.prototype = {
       // Refresh source filter ... this must be done after templateRoot has been
       // processed.
       this.refreshSourceFilter();
       this.numVisibleProperties = 0;
       let fragment = this.doc.createDocumentFragment();
       this._refreshProcess = new UpdateProcess(this.win, CssHtmlTree.propertyNames, {
         onItem: function(aPropertyName) {
           // Per-item callback.
-          if (this.viewedElement != aElement || !this.styleInspector.isOpen()) {
-            return false;
-          }
           let propView = new PropertyView(this, aPropertyName);
           fragment.appendChild(propView.buildMain());
           fragment.appendChild(propView.buildSelectorContainer());
+
           if (propView.visible) {
             this.numVisibleProperties++;
           }
           propView.refreshAllSelectors();
           this.propertyViews.push(propView);
         }.bind(this),
         onDone: function() {
           // Completed callback.
           this.htmlComplete = true;
           this.propertyContainer.appendChild(fragment);
           this.noResults.hidden = this.numVisibleProperties > 0;
           this._refreshProcess = null;
-          Services.obs.notifyObservers(null, "StyleInspector-populated", null);
+
+          // If a refresh was scheduled during the building, complete it.
+          if (this._needsRefresh) {
+            delete this._needsRefresh;
+            this.refreshPanel();
+          } else {
+            Services.obs.notifyObservers(null, "StyleInspector-populated", null);
+          }
         }.bind(this)});
 
       this._refreshProcess.schedule();
     }
   },
 
   /**
    * Refresh the panel content.
    */
   refreshPanel: function CssHtmlTree_refreshPanel()
   {
+    // If we're still in the process of creating the initial layout,
+    // leave it alone.
+    if (!this.htmlComplete) {
+      if (this._refreshProcess) {
+        this._needsRefresh = true;
+      }
+      return;
+    }
+
     if (this._refreshProcess) {
       this._refreshProcess.cancel();
     }
 
     this.noResults.hidden = true;
 
     // Reset visible property count
     this.numVisibleProperties = 0;
@@ -350,17 +364,17 @@ CssHtmlTree.prototype = {
 
     let display = this.propertyContainer.style.display;
     this._refreshProcess = new UpdateProcess(this.win, this.propertyViews, {
       onItem: function(aPropView) {
         aPropView.refresh();
       }.bind(this),
       onDone: function() {
         this._refreshProcess = null;
-        this.noResults.hidden = this.numVisibleProperties > 0
+        this.noResults.hidden = this.numVisibleProperties > 0;
         Services.obs.notifyObservers(null, "StyleInspector-populated", null);
       }.bind(this)
     });
     this._refreshProcess.schedule();
   },
 
   /**
    * Called when the user enters a search term.
@@ -1198,21 +1212,16 @@ SelectorView.prototype = {
       let source = this.selectorInfo.sourceElement;
       let IUI = this.tree.styleInspector.IUI;
       if (IUI && IUI.selection == source) {
         result = "this";
       } else {
         result = CssLogic.getShortName(source);
       }
 
-      aElement.parentNode.querySelector(".rule-link > a").
-        addEventListener("click", function(aEvent) {
-          this.tree.styleInspector.selectFromPath(source);
-          aEvent.preventDefault();
-        }.bind(this), false);
       result += ".style";
     }
 
     return result;
   },
 
   maybeOpenStyleEditor: function(aEvent)
   {
--- a/browser/devtools/styleinspector/CssLogic.jsm
+++ b/browser/devtools/styleinspector/CssLogic.jsm
@@ -643,16 +643,20 @@ CssLogic.prototype = {
     let element = this.viewedElement;
     let filter = this.sourceFilter;
     let sheetIndex = 0;
 
     this._matchId++;
     this._passId++;
     this._matchedRules = [];
 
+    if (!element) {
+      return;
+    }
+
     do {
       let status = this.viewedElement === element ?
                    CssLogic.STATUS.MATCHED : CssLogic.STATUS.PARENT_MATCH;
 
       try {
         domRules = this.domUtils.getCSSStyleRules(element);
       } catch (ex) {
         Services.console.
--- a/browser/devtools/styleinspector/CssRuleView.jsm
+++ b/browser/devtools/styleinspector/CssRuleView.jsm
@@ -709,16 +709,17 @@ function CssRuleView(aDoc, aStore)
   this.element.addEventListener("mouseup",
                                 this._boundMouseUp);
   this._boundMouseMove = this._onMouseMove.bind(this);
 
   this._boundCopy = this._onCopy.bind(this);
   this.element.addEventListener("copy", this._boundCopy);
 
   this._createContextMenu();
+  this._showEmpty();
 }
 
 CssRuleView.prototype = {
   // The element that we're inspecting.
   _viewedElement: null,
 
   destroy: function CssRuleView_destroy()
   {
@@ -758,25 +759,26 @@ CssRuleView.prototype = {
   highlight: function CssRuleView_highlight(aElement)
   {
     if (this._viewedElement === aElement) {
       return;
     }
 
     this.clear();
 
+    if (this._elementStyle) {
+      delete this._elementStyle;
+    }
+
     this._viewedElement = aElement;
     if (!this._viewedElement) {
+      this._showEmpty();
       return;
     }
 
-    if (this._elementStyle) {
-      delete this._elementStyle.onChanged;
-    }
-
     this._elementStyle = new ElementStyle(aElement, this.store);
     this._elementStyle.onChanged = function() {
       this._changed();
     }.bind(this);
 
     this._createEditors();
 
     // When creating a new property, we fake the normal property
@@ -804,16 +806,31 @@ CssRuleView.prototype = {
   nodeChanged: function CssRuleView_nodeChanged()
   {
     this._clearRules();
     this._elementStyle.populate();
     this._createEditors();
   },
 
   /**
+   * Show the user that the rule view has no node selected.
+   */
+  _showEmpty: function CssRuleView_showEmpty()
+  {
+    if (this.doc.getElementById("noResults") > 0) {
+      return;
+    }
+
+    createChild(this.element, "div", {
+      id: "noResults",
+      textContent: CssLogic.l10n("rule.empty")
+    });
+  },
+
+  /**
    * Clear the rules.
    */
   _clearRules: function CssRuleView_clearRules()
   {
     while (this.element.hasChildNodes()) {
       this.element.removeChild(this.element.lastChild);
     }
   },
--- a/browser/devtools/styleinspector/StyleInspector.jsm
+++ b/browser/devtools/styleinspector/StyleInspector.jsm
@@ -18,16 +18,17 @@
  * The Initial Developer of the Original Code is
  * The Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2011
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Mike Ratcliffe <mratcliffe@mozilla.com> (Original Author)
  *   Rob Campbell <rcampbell@mozilla.com>
+ *   Dave Camp <dcamp@mozilla.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -38,333 +39,240 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 const Cu = Components.utils;
 const Ci = Components.interfaces;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource:///modules/devtools/CssRuleView.jsm");
+Cu.import("resource:///modules/inspector.jsm");
 
-var EXPORTED_SYMBOLS = ["StyleInspector"];
+// This module doesn't currently export any symbols directly, it only
+// registers inspector tools.
+var EXPORTED_SYMBOLS = [];
 
 /**
- * StyleInspector Constructor Function.
- * @param {window} aContext, the chrome window context we're calling from.
- * @param {InspectorUI} aIUI (optional) An InspectorUI instance if called from the
- *        Highlighter.
+ * Lookup l10n string from a string bundle.
+ * @param {string} aName The key to lookup.
+ * @returns A localized version of the given key.
  */
-function StyleInspector(aContext, aIUI)
+function l10n(aName)
+{
+  try {
+    return _strings.GetStringFromName(aName);
+  } catch (ex) {
+    Services.console.logStringMessage("Error reading '" + aName + "'");
+    throw new Error("l10n error with " + aName);
+  }
+}
+
+function RegisterStyleTools()
 {
-  this._init(aContext, aIUI);
+  // Register the rules view
+  if (Services.prefs.getBoolPref("devtools.ruleview.enabled")) {
+    InspectorUI.registerSidebar({
+      id: "ruleview",
+      label: l10n("ruleView.label"),
+      tooltiptext: l10n("ruleView.tooltiptext"),
+      accesskey: l10n("ruleView.accesskey"),
+      contentURL: "chrome://browser/content/devtools/cssruleview.xul",
+      load: function(aInspector, aFrame) new RuleViewTool(aInspector, aFrame),
+      destroy: function(aContext) aContext.destroy()
+    });
+  }
+
+  // Register the computed styles view
+  if (Services.prefs.getBoolPref("devtools.styleinspector.enabled")) {
+    InspectorUI.registerSidebar({
+      id: "computedview",
+      label: this.l10n("style.highlighter.button.label2"),
+      tooltiptext: this.l10n("style.highlighter.button.tooltip2"),
+      accesskey: this.l10n("style.highlighter.accesskey2"),
+      contentURL: "chrome://browser/content/devtools/csshtmltree.xul",
+      load: function(aInspector, aFrame) new ComputedViewTool(aInspector, aFrame),
+      destroy: function(aContext) aContext.destroy()
+    });
+  }
 }
 
-StyleInspector.prototype = {
+function RuleViewTool(aInspector, aFrame)
+{
+  this.inspector = aInspector;
+  this.chromeWindow = this.inspector.chromeWindow;
+  this.doc = aFrame.contentDocument;
+
+  if (!this.inspector._ruleViewStore) {
+   this.inspector._ruleViewStore = {};
+  }
+  this.view = new CssRuleView(this.doc, this.inspector._ruleViewStore);
+  this.doc.documentElement.appendChild(this.view.element);
 
-  /**
-   * Initialization method called from constructor.
-   * @param {window} aContext, the chrome window context we're calling from.
-   * @param {InspectorUI} aIUI (optional) An InspectorUI instance if called from
-   *        the Highlighter.
-   */
-  _init: function SI__init(aContext, aIUI)
-  {
-    this.window = aContext;
-    this.IUI = aIUI;
-    this.document = this.window.document;
-    this.cssLogic = new CssLogic();
-    this.panelReady = false;
-    this.iframeReady = false;
+  this._changeHandler = function() {
+    this.inspector.markDirty();
+    this.inspector.change("ruleview");
+  }.bind(this);
+
+  this.view.element.addEventListener("CssRuleViewChanged", this._changeHandler)
+
+  this._cssLinkHandler = function(aEvent) {
+    let rule = aEvent.detail.rule;
+    let styleSheet = rule.sheet;
+    let doc = this.chromeWindow.content.document;
+    let styleSheets = doc.styleSheets;
+    let contentSheet = false;
+    let line = rule.ruleLine || 0;
 
-    // Were we invoked from the Highlighter?
-    if (this.IUI) {
-      this.openDocked = true;
-      let isOpen = this.isOpen.bind(this);
+    // Array.prototype.indexOf always returns -1 here so we loop through
+    // the styleSheets object instead.
+    for each (let sheet in styleSheets) {
+      if (sheet == styleSheet) {
+        contentSheet = true;
+        break;
+      }
+    }
+
+    if (contentSheet)  {
+      this.chromeWindow.StyleEditor.openChrome(styleSheet, line);
+    } else {
+      let href = styleSheet ? styleSheet.href : "";
+      if (rule.elementStyle.element) {
+        href = rule.elementStyle.element.ownerDocument.location.href;
+      }
+      let viewSourceUtils = this.chromeWindow.gViewSourceUtils;
+      viewSourceUtils.viewSource(href, null, doc, line);
+    }
+  }.bind(this);
 
-      this.registrationObject = {
-        id: "styleinspector",
-        label: this.l10n("style.highlighter.button.label2"),
-        tooltiptext: this.l10n("style.highlighter.button.tooltip2"),
-        accesskey: this.l10n("style.highlighter.accesskey2"),
-        context: this,
-        get isOpen() isOpen(),
-        onSelect: this.selectNode,
-        onChanged: this.updateNode,
-        show: this.open,
-        hide: this.close,
-        dim: this.dimTool,
-        panel: null,
-        unregister: this.destroy,
-        sidebar: true,
-      };
+  this.view.element.addEventListener("CssRuleViewCSSLinkClicked",
+                                     this._cssLinkHandler);
+
+  this._onSelect = this.onSelect.bind(this);
+  this.inspector.on("select", this._onSelect);
+
+  this._onChange = this.onChange.bind(this);
+  this.inspector.on("change", this._onChange);
 
-      // Register the registrationObject with the Highlighter
-      this.IUI.registerTool(this.registrationObject);
-      this.createSidebarContent(true);
+  this.onSelect();
+}
+
+RuleViewTool.prototype = {
+  onSelect: function RVT_onSelect(aEvent, aFrom) {
+    let node = this.inspector.selection;
+    if (!node) {
+      this.view.highlight(null);
+      return;
+    }
+
+    if (this.inspector.locked) {
+      this.view.highlight(node);
     }
   },
 
-  /**
-   * Create the iframe in the IUI sidebar's tab panel.
-   * @param {Boolean} aPreserveOnHide Prevents destroy from being called.
-   */
-  createSidebarContent: function SI_createSidebarContent(aPreserveOnHide)
-  {
-    this.preserveOnHide = !!aPreserveOnHide;
-
-    let boundIframeOnLoad = function loadedInitializeIframe() {
-      if (this.iframe &&
-          this.iframe.getAttribute("src") ==
-          "chrome://browser/content/devtools/csshtmltree.xul") {
-        let selectedNode = this.selectedNode || null;
-        this.cssHtmlTree = new CssHtmlTree(this);
-        this.cssLogic.highlight(selectedNode);
-        this.cssHtmlTree.highlight(selectedNode);
-        this.iframe.removeEventListener("load", boundIframeOnLoad, true);
-        this.iframeReady = true;
-
-        // Now that we've loaded, select any node we were previously asked
-        // to show.
-        this.selectNode(this.selectedNode);
-
-        Services.obs.notifyObservers(null, "StyleInspector-opened", null);
-      }
-    }.bind(this);
-
-    this.iframe = this.IUI.getToolIframe(this.registrationObject);
-
-    this.iframe.addEventListener("load", boundIframeOnLoad, true);
-  },
-
-  /**
-   * Factory method to create the actual style panel
-   * @param {Boolean} aPreserveOnHide Prevents destroy from being called
-   * onpopuphide. USE WITH CAUTION: When this value is set to true then you are
-   * responsible to manually call destroy from outside the style inspector.
-   * @param {function} aCallback (optional) callback to fire when ready.
-   */
-  createPanel: function SI_createPanel(aPreserveOnHide, aCallback)
-  {
-    let popupSet = this.document.getElementById("mainPopupSet");
-    let panel = this.document.createElement("panel");
-    this.preserveOnHide = !!aPreserveOnHide;
+  onChange: function RVT_onChange(aEvent, aFrom) {
+    // We're not that good yet at refreshing, only
+    // refresh when we really need to.
+    if (aFrom != "pseudoclass") {
+      return;
+    }
 
-    panel.setAttribute("class", "styleInspector");
-    panel.setAttribute("orient", "vertical");
-    panel.setAttribute("ignorekeys", "true");
-    panel.setAttribute("noautofocus", "true");
-    panel.setAttribute("noautohide", "true");
-    panel.setAttribute("titlebar", "normal");
-    panel.setAttribute("close", "true");
-    panel.setAttribute("label", this.l10n("panelTitle"));
-    panel.setAttribute("width", 350);
-    panel.setAttribute("height", this.window.screen.height / 2);
-
-    let iframe = this.document.createElement("iframe");
-    let boundIframeOnLoad = function loadedInitializeIframe()
-    {
-      this.iframe.removeEventListener("load", boundIframeOnLoad, true);
-      this.iframeReady = true;
-      if (aCallback)
-        aCallback(this);
-    }.bind(this);
-
-    iframe.flex = 1;
-    iframe.setAttribute("tooltip", "aHTMLTooltip");
-    iframe.addEventListener("load", boundIframeOnLoad, true);
-    iframe.setAttribute("src", "chrome://browser/content/devtools/csshtmltree.xul");
-
-    panel.appendChild(iframe);
-    popupSet.appendChild(panel);
-
-    this._boundPopupShown = this.popupShown.bind(this);
-    this._boundPopupHidden = this.popupHidden.bind(this);
-    panel.addEventListener("popupshown", this._boundPopupShown, false);
-    panel.addEventListener("popuphidden", this._boundPopupHidden, false);
-
-    this.panel = panel;
-    this.iframe = iframe;
-
-    return panel;
-  },
-
-  /**
-   * Event handler for the popupshown event.
-   */
-  popupShown: function SI_popupShown()
-  {
-    this.panelReady = true;
-    if (this.iframeReady) {
-      this.cssHtmlTree = new CssHtmlTree(this);
-      let selectedNode = this.selectedNode || null;
-      this.cssLogic.highlight(selectedNode);
-      this.cssHtmlTree.highlight(selectedNode);
-      Services.obs.notifyObservers(null, "StyleInspector-opened", null);
-    }
-  },
-
-  /**
-   * Event handler for the popuphidden event.
-   * Hide the popup and conditionally destroy it
-   */
-  popupHidden: function SI_popupHidden()
-  {
-    if (this.preserveOnHide) {
-      Services.obs.notifyObservers(null, "StyleInspector-closed", null);
-    } else {
-      this.destroy();
+    if (this.inspector.locked) {
+      this.view.nodeChanged();
     }
   },
 
-  /**
-   * Check if the style inspector is open.
-   * @returns boolean
-   */
-  isOpen: function SI_isOpen()
-  {
-    return this.openDocked ? this.IUI.isSidebarOpen &&
-            (this.IUI.sidebarDeck.selectedPanel == this.iframe) :
-           this.panel && this.panel.state && this.panel.state == "open";
-  },
+  destroy: function RVT_destroy() {
+    this.inspector.removeListener("select", this._onSelect);
+    this.inspector.removeListener("change", this._onChange);
+    this.view.element.removeEventListener("CssRuleViewChanged",
+                                          this._changeHandler);
+    this.view.element.removeEventListener("CssRuleViewCSSLinkClicked",
+                                          this._cssLinkHandler);
+    this.doc.documentElement.removeChild(this.view.element);
+
+    this.view.destroy();
+
+    delete this._changeHandler;
+    delete this.view;
+    delete this.doc;
+    delete this.inspector;
+  }
+}
 
-  isLoaded: function SI_isLoaded()
-  {
-    return this.openDocked ? this.iframeReady : this.iframeReady && this.panelReady;
-  },
+function ComputedViewTool(aInspector, aFrame)
+{
+  this.inspector = aInspector;
+  this.iframe = aFrame;
+  this.window = aInspector.chromeWindow;
+  this.document = this.window.document;
+  this.cssLogic = new CssLogic();
+  this.view = new CssHtmlTree(this);
+
+  this._onSelect = this.onSelect.bind(this);
+  this.inspector.on("select", this._onSelect);
 
-  /**
-   * Select from Path (via CssHtmlTree_pathClick)
-   * @param aNode The node to inspect.
-   */
-  selectFromPath: function SI_selectFromPath(aNode)
+  this._onChange = this.onChange.bind(this);
+  this.inspector.on("change", this._onChange);
+
+  // Since refreshes of the computed view are non-destructive,
+  // refresh when the tab is changed so we can notice script-driven
+  // changes.
+  this.inspector.on("sidebaractivated", this._onChange);
+
+  this.cssLogic.highlight(null);
+  this.view.highlight(null);
+
+  this.onSelect();
+}
+
+ComputedViewTool.prototype = {
+  onSelect: function CVT_onSelect(aEvent)
   {
-    if (this.IUI && this.IUI.selection) {
-      if (aNode != this.IUI.selection) {
-        this.IUI.inspectNode(aNode);
-      }
-    } else {
-      this.selectNode(aNode);
-    }
-  },
-
-  /**
-   * Select a node to inspect in the Style Inspector panel
-   * @param aNode The node to inspect.
-   */
-  selectNode: function SI_selectNode(aNode)
-  {
-    this.selectedNode = aNode;
-    if (this.isLoaded() && !this.dimmed) {
-      this.cssLogic.highlight(aNode);
-      this.cssHtmlTree.highlight(aNode);
-    }
-  },
-
-  /**
-   * Update the display for the currently-selected node.
-   */
-  updateNode: function SI_updateNode()
-  {
-    if (this.isLoaded() && !this.dimmed) {
-      this.cssLogic.highlight(this.selectedNode);
-      this.cssHtmlTree.refreshPanel();
+    if (this.inspector.locked) {
+      this.cssLogic.highlight(this.inspector.selection);
+      this.view.highlight(this.inspector.selection);
     }
   },
 
-  /**
-   * Dim or undim a panel by setting or removing a dimmed attribute.
-   * @param aState
-   *        true = dim, false = undim
-   */
-  dimTool: function SI_dimTool(aState)
+  onChange: function CVT_change(aEvent, aFrom)
   {
-    this.dimmed = aState;
-  },
+    if (aFrom == "computedview" ||
+        this.inspector.selection != this.cssLogic.viewedElement) {
+      return;
+    }
 
-  /**
-   * Open the panel.
-   * @param {DOMNode} aSelection the (optional) DOM node to select.
-   */
-  open: function SI_open(aSelection)
-  {
-    this.selectNode(aSelection);
-    if (this.openDocked) {
-      if (!this.iframeReady) {
-        this.iframe.setAttribute("src", "chrome://browser/content/devtools/csshtmltree.xul");
-      }
-    } else {
-      this.panel.openPopup(this.window.gBrowser.selectedBrowser, "end_before", 0, 0,
-        false, false);
-    }
+    this.cssLogic.highlight(this.inspector.selection);
+    this.view.refreshPanel();
   },
 
-  /**
-   * Close the panel.
-   */
-  close: function SI_close()
-  {
-    if (this.openDocked) {
-      Services.obs.notifyObservers(null, "StyleInspector-closed", null);
-    } else {
-      this.panel.hidePopup();
-    }
-  },
-
-  /**
-   * Memoized lookup of a l10n string from a string bundle.
-   * @param {string} aName The key to lookup.
-   * @returns A localized version of the given key.
-   */
-  l10n: function SI_l10n(aName)
+  destroy: function CVT_destroy(aContext)
   {
-    try {
-      return _strings.GetStringFromName(aName);
-    } catch (ex) {
-      Services.console.logStringMessage("Error reading '" + aName + "'");
-      throw new Error("l10n error with " + aName);
-    }
-  },
-
-  /**
-   * Destroy the style panel, remove listeners etc.
-   */
-  destroy: function SI_destroy()
-  {
-    if (this.isOpen())
-      this.close();
-    if (this.cssHtmlTree)
-      this.cssHtmlTree.destroy();
-    if (this.iframe) {
-      this.iframe.parentNode.removeChild(this.iframe);
-      delete this.iframe;
-    }
+    this.inspector.removeListener("select", this._onSelect);
+    this.inspector.removeListener("change", this._onChange);
+    this.inspector.removeListener("sidebaractivated", this._onChange);
+    this.view.destroy();
+    delete this.view;
 
     delete this.cssLogic;
     delete this.cssHtmlTree;
-    if (this.panel) {
-      this.panel.removeEventListener("popupshown", this._boundPopupShown, false);
-      this.panel.removeEventListener("popuphidden", this._boundPopupHidden, false);
-      delete this._boundPopupShown;
-      delete this._boundPopupHidden;
-      this.panel.parentNode.removeChild(this.panel);
-      delete this.panel;
-    }
-    delete this.doc;
-    delete this.win;
-    delete CssHtmlTree.win;
-    Services.obs.notifyObservers(null, "StyleInspector-closed", null);
-  },
-};
+    delete this.iframe;
+    delete this.window;
+    delete this.document;
+  }
+}
 
 XPCOMUtils.defineLazyGetter(this, "_strings", function() Services.strings
   .createBundle("chrome://browser/locale/devtools/styleinspector.properties"));
 
 XPCOMUtils.defineLazyGetter(this, "CssLogic", function() {
   let tmp = {};
   Cu.import("resource:///modules/devtools/CssLogic.jsm", tmp);
   return tmp.CssLogic;
 });
 
 XPCOMUtils.defineLazyGetter(this, "CssHtmlTree", function() {
   let tmp = {};
   Cu.import("resource:///modules/devtools/CssHtmlTree.jsm", tmp);
   return tmp.CssHtmlTree;
 });
+
+RegisterStyleTools();
--- a/browser/devtools/styleinspector/test/browser_bug589375_keybindings.js
+++ b/browser/devtools/styleinspector/test/browser_bug589375_keybindings.js
@@ -9,29 +9,22 @@ let stylePanel;
 
 function createDocument()
 {
   doc.body.innerHTML = '<style type="text/css"> ' +
     '.matches {color: #F00;}</style>' +
     '<span class="matches">Some styled text</span>' +
     '</div>';
   doc.title = "Style Inspector key binding test";
-  stylePanel = new StyleInspector(window);
-  Services.obs.addObserver(runStyleInspectorTests, "StyleInspector-opened", false);
-  stylePanel.createPanel(false, function() {
-    stylePanel.open(doc.body);
-  });
+  stylePanel = new ComputedViewPanel(window);
+  stylePanel.createPanel(doc.body, runStyleInspectorTests);
 }
 
 function runStyleInspectorTests()
 {
-  Services.obs.removeObserver(runStyleInspectorTests, "StyleInspector-opened", false);
-
-  ok(stylePanel.isOpen(), "style inspector is open");
-
   Services.obs.addObserver(SI_test, "StyleInspector-populated", false);
   SI_inspectNode();
 }
 
 function SI_inspectNode()
 {
   var span = doc.querySelector(".matches");
   ok(span, "captain, we have the matches span");
@@ -61,18 +54,18 @@ function SI_test()
   matchedExpander.addEventListener("focus", function expanderFocused() {
     this.removeEventListener("focus", expanderFocused);
     info("property expander is focused");
     info("checking expand / collapse");
     testKey(iframe.contentWindow, "VK_SPACE", rulesTable);
     testKey(iframe.contentWindow, "VK_RETURN", rulesTable);
 
     checkHelpLinkKeybinding();
-    Services.obs.addObserver(finishUp, "StyleInspector-closed", false);
-    stylePanel.close();
+    stylePanel.destroy();
+    finishUp();
   });
 
   info("Adding focus event handler to search filter");
   searchbar.addEventListener("focus", function searchbarFocused() {
     this.removeEventListener("focus", searchbarFocused);
     info("search filter is focused");
     info("tabbing to property expander node");
     EventUtils.synthesizeKey("VK_TAB", {}, iframe.contentWindow);
@@ -89,16 +82,17 @@ function SI_test()
 function getFirstVisiblePropertyView()
 {
   let propView = null;
   stylePanel.cssHtmlTree.propertyViews.some(function(aPropView) {
     if (aPropView.visible) {
       propView = aPropView;
       return true;
     }
+    return false;
   });
 
   return propView;
 }
 
 function testKey(aContext, aVirtKey, aRulesTable)
 {
   info("testing " + aVirtKey + " key");
@@ -122,18 +116,16 @@ function checkHelpLinkKeybinding()
     linkClicked = true;
   };
   EventUtils.synthesizeKey("VK_F1", {}, iframe.contentWindow);
   is(linkClicked, true, "MDN link will be shown");
 }
 
 function finishUp()
 {
-  Services.obs.removeObserver(finishUp, "StyleInspector-closed", false);
-  ok(!stylePanel.isOpen(), "style inspector is closed");
   doc = stylePanel = null;
   gBrowser.removeCurrentTab();
   finish();
 }
 
 function test()
 {
   waitForExplicitFinish();
--- a/browser/devtools/styleinspector/test/browser_bug683672.js
+++ b/browser/devtools/styleinspector/test/browser_bug683672.js
@@ -21,35 +21,28 @@ function test()
   browser.addEventListener("load", tabLoaded, true);
 }
 
 function tabLoaded()
 {
   browser.removeEventListener("load", tabLoaded, true);
   doc = content.document;
   // ok(StyleInspector.isEnabled, "style inspector preference is enabled");
-  stylePanel = new StyleInspector(window);
-  Services.obs.addObserver(runTests, "StyleInspector-opened", false);
-  stylePanel.createPanel(false, function() {
-    stylePanel.open(doc.body);
-  });
+  stylePanel = new ComputedViewPanel(window);
+  stylePanel.createPanel(doc.body, runTests);
 }
 
 function runTests()
 {
-  Services.obs.removeObserver(runTests, "StyleInspector-opened", false);
-
-  ok(stylePanel.isOpen(), "style inspector is open");
-
   testMatchedSelectors();
   //testUnmatchedSelectors();
 
   info("finishing up");
-  Services.obs.addObserver(finishUp, "StyleInspector-closed", false);
-  stylePanel.close();
+  stylePanel.destroy();
+  finishUp();
 }
 
 function testMatchedSelectors()
 {
   info("checking selector counts, matched rules and titles");
   let div = content.document.getElementById("test");
   ok(div, "captain, we have the div");
 
@@ -92,14 +85,12 @@ function testUnmatchedSelectors()
       "CssLogic returns the correct number of unmatched selectors for body");
 
   is(propertyView.hasUnmatchedSelectors, true,
       "hasUnmatchedSelectors returns true");
 }
 
 function finishUp()
 {
-  Services.obs.removeObserver(finishUp, "StyleInspector-closed", false);
-  ok(!stylePanel.isOpen(), "style inspector is closed");
   doc = stylePanel = null;
   gBrowser.removeCurrentTab();
   finish();
 }
--- a/browser/devtools/styleinspector/test/browser_bug722196_property_view_media_queries.js
+++ b/browser/devtools/styleinspector/test/browser_bug722196_property_view_media_queries.js
@@ -17,29 +17,22 @@ function test()
   addTab(TEST_URI);
   browser.addEventListener("load", docLoaded, true);
 }
 
 function docLoaded()
 {
   browser.removeEventListener("load", docLoaded, true);
   doc = content.document;
-  stylePanel = new StyleInspector(window);
-  Services.obs.addObserver(checkSheets, "StyleInspector-opened", false);
-  stylePanel.createPanel(false, function() {
-    stylePanel.open(doc.body);
-  });
+  stylePanel = new ComputedViewPanel(window);
+  stylePanel.createPanel(doc.body, checkSheets);
 }
 
 function checkSheets()
 {
-  Services.obs.removeObserver(checkSheets, "StyleInspector-opened", false);
-
-  ok(stylePanel.isOpen(), "style inspector is open");
-
   var div = doc.querySelector("div");
   ok(div, "captain, we have the div");
 
   stylePanel.selectNode(div);
 
   let cssLogic = stylePanel.cssLogic;
   cssLogic.processMatchedSelectors();
 
@@ -50,19 +43,18 @@ function checkSheets()
 
   let source1 = inline + ":8";
   let source2 = inline + ":15 @media screen and (min-width: 1px)";
   is(cssLogic._matchedRules[0][0].source, source1,
     "rule.source gives correct output for rule 1");
   is(cssLogic._matchedRules[1][0].source, source2,
     "rule.source gives correct output for rule 2");
 
-  Services.obs.addObserver(finishUp, "StyleInspector-closed", false);
-  stylePanel.close();
+  stylePanel.destroy();
+  finishUp();
 }
 
 function finishUp()
 {
-  Services.obs.removeObserver(finishUp, "StyleInspector-closed", false);
   doc = null;
   gBrowser.removeCurrentTab();
   finish();
 }
--- a/browser/devtools/styleinspector/test/browser_bug_692400_element_style.js
+++ b/browser/devtools/styleinspector/test/browser_bug_692400_element_style.js
@@ -7,38 +7,37 @@
 let doc;
 let stylePanel;
 
 function createDocument()
 {
   doc.body.innerHTML = "<div style='color:blue;'></div>";
 
   doc.title = "Style Inspector Selector Text Test";
-  stylePanel = new StyleInspector(window);
+  stylePanel = new ComputedViewPanel(window);
 
+  Services.obs.addObserver(SI_checkText, "StyleInspector-populated", false);
 
-  stylePanel.createPanel(false, function() {
-    Services.obs.addObserver(SI_checkText, "StyleInspector-populated", false);
+  let span = doc.querySelector("div");
+  ok(span, "captain, we have the test div");
 
-    let span = doc.querySelector("div");
-    ok(span, "captain, we have the test div");
-    stylePanel.open(span);
-  });
+  stylePanel.createPanel(span);
 }
 
 function SI_checkText()
 {
   Services.obs.removeObserver(SI_checkText, "StyleInspector-populated", false);
 
   let propertyView = null;
   stylePanel.cssHtmlTree.propertyViews.some(function(aView) {
     if (aView.name == "color") {
       propertyView = aView;
       return true;
     }
+    return false;
   });
 
   ok(propertyView, "found PropertyView for color");
 
   is(propertyView.hasMatchedSelectors, true, "hasMatchedSelectors is true");
 
   propertyView.matchedExpanded = true;
   propertyView.refreshMatchedSelectors();
@@ -52,23 +51,22 @@ function SI_checkText()
   try {
     is(td.textContent.trim(), selector.humanReadableText(td).trim(),
       "selector text is correct");
   } catch (ex) {
     info("EXCEPTION: " + ex);
     ok(false, "getting the selector text should not raise an exception");
   }
 
-  Services.obs.addObserver(finishUp, "StyleInspector-closed", false);
-  stylePanel.close();
+  stylePanel.destroy();
+  finishUp();
 }
 
 function finishUp()
 {
-  Services.obs.removeObserver(finishUp, "StyleInspector-closed", false);
   doc = stylePanel = null;
   gBrowser.removeCurrentTab();
   finish();
 }
 
 function test()
 {
   waitForExplicitFinish();
--- a/browser/devtools/styleinspector/test/browser_computedview_734259_style_editor_link.js
+++ b/browser/devtools/styleinspector/test/browser_computedview_734259_style_editor_link.js
@@ -25,29 +25,25 @@ function createDocument()
     '<p id="closing">more text</p>\n' +
     '<p>even more text</p>' +
     '</div>';
   doc.title = "Rule view style editor link test";
 
   let span = doc.querySelector("span");
   ok(span, "captain, we have the span");
 
-  stylePanel = new StyleInspector(window);
   Services.obs.addObserver(testInlineStyle, "StyleInspector-populated", false);
-  stylePanel.createPanel(false, function() {
-    stylePanel.open(span);
-  });
+  stylePanel = new ComputedViewPanel(window);
+  stylePanel.createPanel(span);
 }
 
 function testInlineStyle()
 {
   Services.obs.removeObserver(testInlineStyle, "StyleInspector-populated", false);
 
-  ok(stylePanel.isOpen(), "style inspector is open");
-
   info("expanding property");
   expandProperty(0, function propertyExpanded() {
     Services.ww.registerNotification(function onWindow(aSubject, aTopic) {
       if (aTopic != "domwindowopened") {
         return;
       }
       info("window opened");
       win = aSubject.QueryInterface(Ci.nsIDOMWindow);
@@ -108,18 +104,18 @@ function validateStyleEditorSheet(aEdito
 {
   info("validating style editor stylesheet");
 
   let sheet = doc.styleSheets[0];
   is(aEditor.styleSheet, sheet, "loaded stylesheet matches document stylesheet");
   info("closing window");
   win.close();
 
-  Services.obs.addObserver(finishUp, "StyleInspector-closed", false);
-  stylePanel.close();
+  stylePanel.destroy();
+  finishUp();
 }
 
 function expandProperty(aIndex, aCallback)
 {
   let iframe = stylePanel.iframe;
   let contentDoc = iframe.contentDocument;
   let contentWindow = iframe.contentWindow;
   let expando = contentDoc.querySelectorAll(".expandable")[aIndex];
@@ -133,18 +129,16 @@ function getLinkByIndex(aIndex)
 {
   let contentDoc = stylePanel.iframe.contentDocument;
   let links = contentDoc.querySelectorAll(".rule-link .link");
   return links[aIndex];
 }
 
 function finishUp()
 {
-  Services.obs.removeObserver(finishUp, "StyleInspector-closed", false);
-  ok(!stylePanel.isOpen(), "style inspector is closed");
   doc = win = stylePanel = null;
   gBrowser.removeCurrentTab();
   finish();
 }
 
 function test()
 {
   waitForExplicitFinish();
--- a/browser/devtools/styleinspector/test/browser_computedview_bug_703643_context_menu_copy.js
+++ b/browser/devtools/styleinspector/test/browser_computedview_bug_703643_context_menu_copy.js
@@ -30,29 +30,25 @@ function createDocument()
     '<p id="closing">more text</p>\n' +
     '<p>even more text</p>' +
     '</div>';
   doc.title = "Computed view context menu test";
 
   let span = doc.querySelector("span");
   ok(span, "captain, we have the span");
 
-  stylePanel = new StyleInspector(window);
+  stylePanel = new ComputedViewPanel(window);
   Services.obs.addObserver(runStyleInspectorTests, "StyleInspector-populated", false);
-  stylePanel.createPanel(false, function() {
-    stylePanel.open(span);
-  });
+  stylePanel.createPanel(span);
 }
 
 function runStyleInspectorTests()
 {
   Services.obs.removeObserver(runStyleInspectorTests, "StyleInspector-populated", false);
 
-  ok(stylePanel.isOpen(), "style inspector is open");
-
   cssHtmlTree = stylePanel.cssHtmlTree;
 
   let contentDocument = stylePanel.iframe.contentDocument;
   let prop = contentDocument.querySelector(".property-view");
   ok(prop, "captain, we have the property-view node");
 
   // We need the context menu to open in the correct place in order for
   // popupNode to be propertly set.
@@ -161,24 +157,22 @@ function failedClipboard(aExpectedPatter
     "results (escaped for accurate comparison):\n");
   info("Actual: " + escape(actual));
   info("Expected: " + escape(aExpectedPattern));
   aCallback();
 }
 
 function closeStyleInspector()
 {
-  Services.obs.addObserver(finishUp, "StyleInspector-closed", false);
-  stylePanel.close();
+  stylePanel.destroy();
+  finishUp();
 }
 
 function finishUp()
 {
-  Services.obs.removeObserver(finishUp, "StyleInspector-closed", false);
-  ok(!stylePanel.isOpen(), "style inspector is closed");
   doc = stylePanel = cssHtmlTree = null;
   gBrowser.removeCurrentTab();
   finish();
 }
 
 function test()
 {
   waitForExplicitFinish();
--- a/browser/devtools/styleinspector/test/browser_ruleview_734259_style_editor_link.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_734259_style_editor_link.js
@@ -58,28 +58,24 @@ function inspectorUIOpen()
   is(InspectorUI.store.length, 1, "Inspector.store.length = 1");
 
   // Highlight a node.
   let div = content.document.getElementsByTagName("div")[0];
   InspectorUI.inspectNode(div);
   InspectorUI.stopInspecting();
   is(InspectorUI.selection, div, "selection matches the div element");
 
-  Services.obs.addObserver(testInlineStyle,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY, false);
+  InspectorUI.currentInspector.once("sidebaractivated-ruleview", testInlineStyle);
 
-  InspectorUI.showSidebar();
-  InspectorUI.openRuleView();
+  InspectorUI.sidebar.show();
+  InspectorUI.sidebar.activatePanel("ruleview");
 }
 
 function testInlineStyle()
 {
-  Services.obs.removeObserver(testInlineStyle,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY, false);
-
   executeSoon(function() {
     info("clicking an inline style");
 
     Services.ww.registerNotification(function onWindow(aSubject, aTopic) {
       if (aTopic != "domwindowopened") {
         return;
       }
 
@@ -144,26 +140,25 @@ function validateStyleEditorSheet(aEdito
   is(aEditor.styleSheet, sheet, "loaded stylesheet matches document stylesheet");
   win.close();
 
   finishup();
 }
 
 function getLinkByIndex(aIndex)
 {
-  let ruleView = document.querySelector("#devtools-sidebar-iframe-ruleview");
-  let contentDoc = ruleView.contentDocument;
+  let contentDoc = ruleView().doc;
+  contentWindow = contentDoc.defaultView;
   let links = contentDoc.querySelectorAll(".ruleview-rule-source");
-  contentWindow = ruleView.contentWindow;
   return links[aIndex];
 }
 
 function finishup()
 {
-  InspectorUI.hideSidebar();
+  InspectorUI.sidebar.hide();
   InspectorUI.closeInspectorUI();
   gBrowser.removeCurrentTab();
   doc = contentWindow = win = null;
   finish();
 }
 
 function test()
 {
--- a/browser/devtools/styleinspector/test/browser_ruleview_bug_703643_context_menu_copy.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_bug_703643_context_menu_copy.js
@@ -6,16 +6,21 @@ let doc;
 let tempScope = {};
 Cu.import("resource:///modules/devtools/CssRuleView.jsm", tempScope);
 let inplaceEditor = tempScope._getInplaceEditorForSpan;
 
 XPCOMUtils.defineLazyGetter(this, "osString", function() {
   return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
 });
 
+function ruleViewFrame()
+{
+  return InspectorUI.sidebar._tools["ruleview"].frame;
+}
+
 function createDocument()
 {
   doc.body.innerHTML = '<style type="text/css"> ' +
     'html { color: #000000; } ' +
     'span { font-variant: small-caps; color: #000000; } ' +
     '.nomatches {color: #ff0000;}</style> <div id="first" style="margin: 10em; ' +
     'font-size: 14pt; font-family: helvetica, sans-serif; color: #AAA">\n' +
     '<h1>Some header text</h1>\n' +
@@ -83,28 +88,23 @@ function inspectorUIOpen()
   is(InspectorUI.store.length, 1, "Inspector.store.length = 1");
 
   // Highlight a node.
   let div = content.document.getElementsByTagName("div")[0];
   InspectorUI.inspectNode(div);
   InspectorUI.stopInspecting();
   is(InspectorUI.selection, div, "selection matches the div element");
 
-  Services.obs.addObserver(testClip,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY, false);
-
-  InspectorUI.showSidebar();
-  InspectorUI.openRuleView();
+  InspectorUI.currentInspector.once("sidebaractivated-ruleview", testClip)
+  InspectorUI.sidebar.show();
+  InspectorUI.sidebar.activatePanel("ruleview");
 }
 
 function testClip()
 {
-  Services.obs.removeObserver(testClip,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY, false);
-
   executeSoon(function() {
     info("Checking that _onCopyRule() returns " +
          "the correct clipboard value");
     let expectedPattern = "element {[\\r\\n]+" +
       "    margin: 10em;[\\r\\n]+" +
       "    font-size: 14pt;[\\r\\n]+" +
       "    font-family: helvetica,sans-serif;[\\r\\n]+" +
       "    color: rgb\\(170, 170, 170\\);[\\r\\n]+" +
@@ -115,44 +115,42 @@ function testClip()
       },
       checkCopyRule, checkCopyRuleWithEditorSelected, function() {
         failedClipboard(expectedPattern, checkCopyRuleWithEditorSelected);
       });
   });
 }
 
 function checkCopyRule() {
-  let ruleView = document.querySelector("#devtools-sidebar-iframe-ruleview");
-  let contentDoc = ruleView.contentDocument;
+  let contentDoc = ruleViewFrame().contentDocument;
   let props = contentDoc.querySelectorAll(".ruleview-property");
 
   is(props.length, 5, "checking property length");
 
   let prop = props[2];
   let propName = prop.querySelector(".ruleview-propertyname").textContent;
   let propValue = prop.querySelector(".ruleview-propertyvalue").textContent;
 
   is(propName, "font-family", "checking property name");
   is(propValue, "helvetica,sans-serif", "checking property value");
 
   // We need the context menu to open in the correct place in order for
   // popupNode to be propertly set.
   EventUtils.synthesizeMouse(prop, 1, 1, { type: "contextmenu", button: 2 },
-    ruleView.contentWindow);
+    ruleViewFrame().contentWindow);
 
-  InspectorUI.ruleView._boundCopyRule();
+  ruleView()._boundCopyRule();
   let menu = contentDoc.querySelector("#rule-view-context-menu");
   ok(menu, "we have the context menu");
   menu.hidePopup();
 }
 
 function checkCopyRuleWithEditorSelected()
 {
-  let ruleView = document.querySelector("#devtools-sidebar-iframe-ruleview");
-  let contentDoc = ruleView.contentDocument;
+  let contentDoc = ruleViewFrame().contentDocument;
   let rows = contentDoc.querySelectorAll(".rule-view-row");
   let propNodes = contentDoc.querySelectorAll(".ruleview-property");
   let propNode = propNodes[2];
   let propNameNode = propNode.querySelector(".ruleview-propertyname");
 
   ok(propNameNode, "we have the property name node");
 
   info("Checking that _boundCopyRule()  returns the correct clipboard value");
@@ -167,121 +165,119 @@ function checkCopyRuleWithEditorSelected
   waitForEditorFocus(elementRuleEditor.element, function onNewElement(aEditor) {
     ok(aEditor, "we have the editor");
 
     waitForBlur.editor = aEditor;
 
     // We need the context menu to open in the correct place in order for
     // popupNode to be propertly set.
     EventUtils.synthesizeMouse(aEditor.input, 1, 1,
-      { type: "contextmenu", button: 2 }, ruleView.contentWindow);
+      { type: "contextmenu", button: 2 }, ruleViewFrame().contentWindow);
 
     SimpleTest.waitForClipboard(function IUI_boundCopyCheckWithSelection() {
       let menu = contentDoc.querySelector("#rule-view-context-menu");
       ok(menu, "we have the context menu");
       menu.hidePopup();
 
       return checkClipboardData(expectedPattern);
-    }, InspectorUI.ruleView._boundCopyRule, waitForBlur, function() {
+    }, ruleView()._boundCopyRule, waitForBlur, function() {
       failedClipboard(expectedPattern, checkCopyProperty);
     });
   });
-  EventUtils.synthesizeMouse(propNameNode, 1, 1, { }, ruleView.contentWindow);
+  EventUtils.synthesizeMouse(propNameNode, 1, 1, { }, ruleViewFrame().contentWindow);
 }
 
 function waitForBlur()
 {
   waitForEditorBlur(waitForBlur.editor, function() {
     waitForBlur.editor = null;
     checkCopyProperty();
   });
   waitForBlur.editor.input.blur();
 }
 
 function checkCopyProperty()
 {
-  let ruleView = document.querySelector("#devtools-sidebar-iframe-ruleview");
-  let contentDoc = ruleView.contentDocument;
+  let contentDoc = ruleViewFrame().contentDocument;
   let props = contentDoc.querySelectorAll(".ruleview-property");
   let prop = props[2];
 
   info("Checking that _onCopyDeclaration() returns " +
        "the correct clipboard value");
   let expectedPattern = "font-family: helvetica,sans-serif;";
 
   // We need the context menu to open in the correct place in order for
   // popupNode to be propertly set.
   EventUtils.synthesizeMouse(prop, 1, 1, { type: "contextmenu", button: 2 },
-    ruleView.contentWindow);
+    ruleViewFrame().contentWindow);
 
   SimpleTest.waitForClipboard(function IUI_boundCopyPropCheck() {
     return checkClipboardData(expectedPattern);
   },
-  InspectorUI.ruleView._boundCopyDeclaration,
+  ruleView()._boundCopyDeclaration,
   checkCopyPropertyName, function() {
     failedClipboard(expectedPattern, checkCopyPropertyName);
   });
 }
 
 function checkCopyPropertyName()
 {
   info("Checking that _onCopyProperty() returns " +
        "the correct clipboard value");
   let expectedPattern = "font-family";
 
   SimpleTest.waitForClipboard(function IUI_boundCopyPropNameCheck() {
     return checkClipboardData(expectedPattern);
   },
-  InspectorUI.ruleView._boundCopyProperty,
+  ruleView()._boundCopyProperty,
   checkCopyPropertyValue, function() {
     failedClipboard(expectedPattern, checkCopyPropertyValue);
   });
 }
 
 function checkCopyPropertyValue()
 {
   info("Checking that _onCopyPropertyValue() " +
        " returns the correct clipboard value");
   let expectedPattern = "helvetica,sans-serif";
 
   SimpleTest.waitForClipboard(function IUI_boundCopyPropValueCheck() {
     return checkClipboardData(expectedPattern);
   },
-  InspectorUI.ruleView._boundCopyPropertyValue,
+  ruleView()._boundCopyPropertyValue,
   checkCopySelection, function() {
     failedClipboard(expectedPattern, checkCopySelection);
   });
 }
 
 function checkCopySelection()
 {
-  let ruleView = document.querySelector("#devtools-sidebar-iframe-ruleview");
-  let contentDoc = ruleView.contentDocument;
+  let contentDoc = ruleViewFrame().contentDocument;
   let props = contentDoc.querySelectorAll(".ruleview-property");
 
   let range = document.createRange();
   range.setStart(props[0], 0);
   range.setEnd(props[4], 8);
 
-  let selection = ruleView.contentWindow.getSelection();
+  let selection = ruleViewFrame().contentWindow.getSelection();
   selection.addRange(range);
 
   info("Checking that _boundCopy()  returns the correct" +
     "clipboard value");
   let expectedPattern = "    margin: 10em;[\\r\\n]+" +
                         "    font-size: 14pt;[\\r\\n]+" +
                         "    font-family: helvetica,sans-serif;[\\r\\n]+" +
                         "    color: rgb\\(170, 170, 170\\);[\\r\\n]+" +
                         "}[\\r\\n]+" +
                         "html {[\\r\\n]+" +
                         "    color: rgb\\(0, 0, 0\\);[\\r\\n]*";
 
   SimpleTest.waitForClipboard(function IUI_boundCopyCheck() {
     return checkClipboardData(expectedPattern);
-  },InspectorUI.ruleView._boundCopy, finishup, function() {
+  },ruleView()._boundCopy, finishup, function() {
     failedClipboard(expectedPattern, finishup);
   });
 }
 
 function checkClipboardData(aExpectedPattern)
 {
   let actual = SpecialPowers.getClipboardData("text/unicode");
   let expectedRegExp = new RegExp(aExpectedPattern, "g");
@@ -307,17 +303,17 @@ function failedClipboard(aExpectedPatter
     "results (escaped for accurate comparison):\n");
   info("Actual: " + escape(actual));
   info("Expected: " + escape(aExpectedPattern));
   aCallback();
 }
 
 function finishup()
 {
-  InspectorUI.hideSidebar();
+  InspectorUI.sidebar.hide();
   InspectorUI.closeInspectorUI();
   gBrowser.removeCurrentTab();
   doc = null;
   finish();
 }
 
 function test()
 {
--- a/browser/devtools/styleinspector/test/browser_ruleview_focus.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_focus.js
@@ -8,28 +8,17 @@
 let tempScope = {};
 Cu.import("resource:///modules/devtools/CssRuleView.jsm", tempScope);
 let inplaceEditor = tempScope._getInplaceEditorForSpan;
 let doc;
 let stylePanel;
 
 function waitForRuleView(aCallback)
 {
-  if (InspectorUI.ruleView) {
-    aCallback();
-    return;
-  }
-
-  let ruleViewFrame = InspectorUI.getToolIframe(InspectorUI.ruleViewObject);
-  ruleViewFrame.addEventListener("load", function(evt) {
-    ruleViewFrame.removeEventListener(evt.type, arguments.callee, true);
-    executeSoon(function() {
-      aCallback();
-    });
-  }, true);
+  InspectorUI.currentInspector.once("sidebaractivated-ruleview", aCallback);
 }
 
 function waitForEditorFocus(aParent, aCallback)
 {
   aParent.addEventListener("focus", function onFocus(evt) {
     if (inplaceEditor(evt.target)) {
       aParent.removeEventListener("focus", onFocus, true);
       let editor = inplaceEditor(evt.target);
@@ -49,27 +38,27 @@ function openRuleView()
     // Highlight a node.
     let node = content.document.getElementsByTagName("h1")[0];
     InspectorUI.inspectNode(node);
     InspectorUI.stopInspecting();
 
     // Open the rule view sidebar.
     waitForRuleView(testFocus);
 
-    InspectorUI.showSidebar();
-    InspectorUI.ruleButton.click();
+    InspectorUI.sidebar.show();
+    InspectorUI.sidebar.activatePanel("ruleview");
 
     testFocus();
   }, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
   InspectorUI.openInspectorUI();
 }
 
 function testFocus()
 {
-  let ruleViewFrame = InspectorUI.getToolIframe(InspectorUI.ruleViewObject);
+  let ruleViewFrame = InspectorUI.sidebar._tools["ruleview"].frame;
   let brace = ruleViewFrame.contentDocument.querySelectorAll(".ruleview-ruleclose")[0];
   waitForEditorFocus(brace.parentNode, function onNewElement(aEditor) {
     aEditor.input.value = "color";
     waitForEditorFocus(brace.parentNode, function onEditingValue(aEditor) {
       // If we actually get this focus we're ok.
       ok(true, "We got focus.");
       aEditor.input.value = "green";
 
@@ -84,17 +73,17 @@ function testFocus()
     EventUtils.sendKey("return");
   });
 
   brace.focus();
 }
 
 function finishUp()
 {
-  InspectorUI.hideSidebar();
+  InspectorUI.sidebar.hide();
   InspectorUI.closeInspectorUI();
   doc = stylePanel = null;
   gBrowser.removeCurrentTab();
   finish();
 }
 
 function test()
 {
--- a/browser/devtools/styleinspector/test/browser_ruleview_ui.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_ui.js
@@ -67,17 +67,23 @@ function startTest()
                           "cssruleviewtest",
                           "width=200,height=350");
   ruleDialog.addEventListener("load", function onLoad(evt) {
     ruleDialog.removeEventListener("load", onLoad);
     let doc = ruleDialog.document;
     ruleView = new CssRuleView(doc);
     doc.documentElement.appendChild(ruleView.element);
     ruleView.element.addEventListener("CssRuleViewChanged", ruleViewChanged, false);
+    is(ruleView.element.querySelectorAll("#noResults").length, 1, "Has a no-results element.");
     ruleView.highlight(testElement);
+    is(ruleView.element.querySelectorAll("#noResults").length, 0, "After a highlight, no longer has a no-results element.");
+    ruleView.highlight(null);
+    is(ruleView.element.querySelectorAll("#noResults").length, 1, "After highlighting null, has a no-results element again.");
+    ruleView.highlight(testElement);
+
     waitForFocus(testCancelNew, ruleDialog);
   }, true);
 }
 
 function testCancelNew()
 {
   // Start at the beginning: start to add a rule to the element's style
   // declaration, but leave it empty.
--- a/browser/devtools/styleinspector/test/browser_styleinspector.js
+++ b/browser/devtools/styleinspector/test/browser_styleinspector.js
@@ -21,60 +21,51 @@ function createDocument()
     'style list-items in the box at right. If you are reading this, ' +
     'you should go do something else instead. Maybe read a book. Or better ' +
     'yet, write some test-cases for another bit of code. ' +
     '<span style="font-style: italic">Maybe more inspector test-cases!</span></p>\n' +
     '<p id="closing">end transmission</p>\n' +
     '<p>Inspect using inspectstyle(document.querySelectorAll("span")[0])</p>' +
     '</div>';
   doc.title = "Style Inspector Test";
-  stylePanel = new StyleInspector(window);
-  Services.obs.addObserver(runStyleInspectorTests, "StyleInspector-opened", false);
-  stylePanel.createPanel(false, function() {
-    stylePanel.open(doc.body);
-  });
+  stylePanel = new ComputedViewPanel(window);
+  stylePanel.createPanel(doc.body, runStyleInspectorTests);
 }
 
 function runStyleInspectorTests()
 {
-  Services.obs.removeObserver(runStyleInspectorTests, "StyleInspector-opened", false);
-
-  ok(stylePanel.isOpen(), "style inspector is open");
-
   var spans = doc.querySelectorAll("span");
   ok(spans, "captain, we have the spans");
 
   let htmlTree = stylePanel.cssHtmlTree;
 
   for (var i = 0, numSpans = spans.length; i < numSpans; i++) {
     stylePanel.selectNode(spans[i]);
 
     is(spans[i], htmlTree.viewedElement,
       "style inspector node matches the selected node");
     is(htmlTree.viewedElement, stylePanel.cssLogic.viewedElement,
        "cssLogic node matches the cssHtmlTree node");
   }
 
   SI_CheckProperty();
-  Services.obs.addObserver(finishUp, "StyleInspector-closed", false);
-  stylePanel.close();
+  stylePanel.destroy();
+  finishUp();
 }
 
 function SI_CheckProperty()
 {
   let cssLogic = stylePanel.cssLogic;
   let propertyInfo = cssLogic.getPropertyInfo("color");
   ok(propertyInfo.matchedRuleCount > 0, "color property has matching rules");
   //ok(propertyInfo.unmatchedRuleCount > 0, "color property has unmatched rules");
 }
 
 function finishUp()
 {
-  Services.obs.removeObserver(finishUp, "StyleInspector-closed", false);
-  ok(!stylePanel.isOpen(), "style inspector is closed");
   doc = stylePanel = null;
   gBrowser.removeCurrentTab();
   finish();
 }
 
 function test()
 {
   waitForExplicitFinish();
--- a/browser/devtools/styleinspector/test/browser_styleinspector_bug_672744_search_filter.js
+++ b/browser/devtools/styleinspector/test/browser_styleinspector_bug_672744_search_filter.js
@@ -10,29 +10,22 @@ let stylePanel;
 function createDocument()
 {
   doc.body.innerHTML = '<style type="text/css"> ' +
     '.matches {color: #F00;}</style>' +
     '<span id="matches" class="matches">Some styled text</span>' +
     '</div>';
   doc.title = "Style Inspector Search Filter Test";
   // ok(StyleInspector.isEnabled, "style inspector preference is enabled");
-  stylePanel = new StyleInspector(window);
-  Services.obs.addObserver(runStyleInspectorTests, "StyleInspector-opened", false);
-  stylePanel.createPanel(false, function() {
-    stylePanel.open(doc.body);
-  });
+  stylePanel = new ComputedViewPanel(window);
+  stylePanel.createPanel(doc.body, runStyleInspectorTests);
 }
 
 function runStyleInspectorTests()
 {
-  Services.obs.removeObserver(runStyleInspectorTests, "StyleInspector-opened", false);
-
-  ok(stylePanel.isOpen(), "style inspector is open");
-
   Services.obs.addObserver(SI_toggleDefaultStyles, "StyleInspector-populated", false);
   SI_inspectNode();
 }
 
 function SI_inspectNode()
 {
   var span = doc.querySelector("#matches");
   ok(span, "captain, we have the matches span");
@@ -81,24 +74,22 @@ function SI_checkFilter()
 
   info("check that the correct properties are visible");
   propertyViews.forEach(function(propView) {
     let name = propView.name;
     is(propView.visible, name.indexOf("color") > -1,
       "span " + name + " property visibility check");
   });
 
-  Services.obs.addObserver(finishUp, "StyleInspector-closed", false);
-  stylePanel.close();
+  stylePanel.destroy();
+  finishUp();
 }
 
 function finishUp()
 {
-  Services.obs.removeObserver(finishUp, "StyleInspector-closed", false);
-  ok(!stylePanel.isOpen(), "style inspector is closed");
   doc = stylePanel = null;
   gBrowser.removeCurrentTab();
   finish();
 }
 
 function test()
 {
   waitForExplicitFinish();
--- a/browser/devtools/styleinspector/test/browser_styleinspector_bug_672746_default_styles.js
+++ b/browser/devtools/styleinspector/test/browser_styleinspector_bug_672746_default_styles.js
@@ -10,29 +10,22 @@ let stylePanel;
 function createDocument()
 {
   doc.body.innerHTML = '<style type="text/css"> ' +
     '.matches {color: #F00;}</style>' +
     '<span id="matches" class="matches">Some styled text</span>' +
     '</div>';
   doc.title = "Style Inspector Default Styles Test";
   // ok(StyleInspector.isEnabled, "style inspector preference is enabled");
-  stylePanel = new StyleInspector(window);
-  Services.obs.addObserver(runStyleInspectorTests, "StyleInspector-opened", false);
-  stylePanel.createPanel(false, function() {
-    stylePanel.open(doc.body);
-  });
+  stylePanel = new ComputedViewPanel(window);
+  stylePanel.createPanel(doc.body, runStyleInspectorTests);
 }
 
 function runStyleInspectorTests()
 {
-  Services.obs.removeObserver(runStyleInspectorTests, "StyleInspector-opened", false);
-
-  ok(stylePanel.isOpen(), "style inspector is open");
-
   Services.obs.addObserver(SI_check, "StyleInspector-populated", false);
   SI_inspectNode();
 }
 
 function SI_inspectNode()
 {
   let span = doc.querySelector("#matches");
   ok(span, "captain, we have the matches span");
@@ -70,35 +63,34 @@ function SI_checkDefaultStyles()
 {
   Services.obs.removeObserver(SI_checkDefaultStyles, "StyleInspector-populated", false);
   // Check that the default styles are now applied.
   is(propertyVisible("color"), true,
       "span color property is visible");
   is(propertyVisible("background-color"), true,
       "span background-color property is visible");
 
-  Services.obs.addObserver(finishUp, "StyleInspector-closed", false);
-  stylePanel.close();
+  stylePanel.destroy();
+  finishUp();
 }
 
 function propertyVisible(aName)
 {
   info("Checking property visibility for " + aName);
   let propertyViews = stylePanel.cssHtmlTree.propertyViews;
   for each (let propView in propertyViews) {
     if (propView.name == aName) {
       return propView.visible;
     }
   }
+  return false;
 }
 
 function finishUp()
 {
-  Services.obs.removeObserver(finishUp, "StyleInspector-closed", false);
-  ok(!stylePanel.isOpen(), "style inspector is closed");
   doc = stylePanel = null;
   gBrowser.removeCurrentTab();
   finish();
 }
 
 function test()
 {
   waitForExplicitFinish();
--- a/browser/devtools/styleinspector/test/browser_styleinspector_bug_689759_no_results_placeholder.js
+++ b/browser/devtools/styleinspector/test/browser_styleinspector_bug_689759_no_results_placeholder.js
@@ -8,29 +8,22 @@ let doc;
 let stylePanel;
 
 function createDocument()
 {
   doc.body.innerHTML = '<style type="text/css"> ' +
     '.matches {color: #F00;}</style>' +
     '<span id="matches" class="matches">Some styled text</span>';
   doc.title = "Tests that the no results placeholder works properly";
-  stylePanel = new StyleInspector(window);
-  Services.obs.addObserver(runStyleInspectorTests, "StyleInspector-opened", false);
-  stylePanel.createPanel(false, function() {
-    stylePanel.open(doc.body);
-  });
+  stylePanel = new ComputedViewPanel(window);
+  stylePanel.createPanel(doc.body, runStyleInspectorTests);
 }
 
 function runStyleInspectorTests()
 {
-  Services.obs.removeObserver(runStyleInspectorTests, "StyleInspector-opened", false);
-
-  ok(stylePanel.isOpen(), "style inspector is open");
-
   Services.obs.addObserver(SI_AddFilterText, "StyleInspector-populated", false);
 
   let span = doc.querySelector("#matches");
   ok(span, "captain, we have the matches span");
 
   let htmlTree = stylePanel.cssHtmlTree;
   stylePanel.selectNode(span);
 
@@ -85,24 +78,22 @@ function SI_checkPlaceholderHidden()
 {
   Services.obs.removeObserver(SI_checkPlaceholderHidden, "StyleInspector-populated", false);
   let placeholder = stylePanel.cssHtmlTree.noResults;
   let iframe = stylePanel.iframe;
   let display = iframe.contentWindow.getComputedStyle(placeholder).display;
 
   is(display, "none", "placeholder is hidden");
 
-  Services.obs.addObserver(finishUp, "StyleInspector-closed", false);
-  stylePanel.close();
+  stylePanel.destroy();
+  finishUp();
 }
 
 function finishUp()
 {
-  Services.obs.removeObserver(finishUp, "StyleInspector-closed", false);
-  ok(!stylePanel.isOpen(), "style inspector is closed");
   doc = stylePanel = null;
   gBrowser.removeCurrentTab();
   finish();
 }
 
 function test()
 {
   waitForExplicitFinish();
--- a/browser/devtools/styleinspector/test/head.js
+++ b/browser/devtools/styleinspector/test/head.js
@@ -31,21 +31,23 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 let tempScope = {};
-Cu.import("resource:///modules/devtools/StyleInspector.jsm", tempScope);
+Cu.import("resource:///modules/devtools/CssLogic.jsm", tempScope);
+Cu.import("resource:///modules/devtools/CssHtmlTree.jsm", tempScope);
 Cu.import("resource://gre/modules/HUDService.jsm", tempScope);
-let StyleInspector = tempScope.StyleInspector;
 let HUDService = tempScope.HUDService;
 let ConsoleUtils = tempScope.ConsoleUtils;
+let CssLogic = tempScope.CssLogic;
+let CssHtmlTree = tempScope.CssHtmlTree;
 
 function log(aMsg)
 {
   dump("*** WebConsoleTest: " + aMsg + "\n");
 }
 
 function pprint(aObj)
 {
@@ -187,12 +189,155 @@ function tearDown()
     log(ex);
   }
   while (gBrowser.tabs.length > 1) {
     gBrowser.removeCurrentTab();
   }
   tab = browser = hudId = hud = filterBox = outputNode = cs = null;
 }
 
+/**
+ * Shows the computed view in its own panel.
+ */
+function ComputedViewPanel(aContext)
+{
+  this._init(aContext);
+}
+
+ComputedViewPanel.prototype = {
+  _init: function CVP_init(aContext)
+  {
+    this.window = aContext;
+    this.document = this.window.document;
+    this.cssLogic = new CssLogic();
+    this.panelReady = false;
+    this.iframeReady = false;
+  },
+
+  /**
+   * Factory method to create the actual style panel
+   * @param {function} aCallback (optional) callback to fire when ready.
+   */
+  createPanel: function SI_createPanel(aSelection, aCallback)
+  {
+    let popupSet = this.document.getElementById("mainPopupSet");
+    let panel = this.document.createElement("panel");
+
+    panel.setAttribute("class", "styleInspector");
+    panel.setAttribute("orient", "vertical");
+    panel.setAttribute("ignorekeys", "true");
+    panel.setAttribute("noautofocus", "true");
+    panel.setAttribute("noautohide", "true");
+    panel.setAttribute("titlebar", "normal");
+    panel.setAttribute("close", "true");
+    panel.setAttribute("label", "Computed View");
+    panel.setAttribute("width", 350);
+    panel.setAttribute("height", this.window.screen.height / 2);
+
+    this._openCallback = aCallback;
+    this.selectedNode = aSelection;
+
+    let iframe = this.document.createElement("iframe");
+    let boundIframeOnLoad = function loadedInitializeIframe()
+    {
+      this.iframeReady = true;
+      this.iframe.removeEventListener("load", boundIframeOnLoad, true);
+      this.panel.openPopup(this.window.gBrowser.selectedBrowser, "end_before", 0, 0, false, false);
+    }.bind(this);
+
+    iframe.flex = 1;
+    iframe.setAttribute("tooltip", "aHTMLTooltip");
+    iframe.addEventListener("load", boundIframeOnLoad, true);
+    iframe.setAttribute("src", "chrome://browser/content/devtools/csshtmltree.xul");
+
+    panel.appendChild(iframe);
+    popupSet.appendChild(panel);
+
+    this._boundPopupShown = this.popupShown.bind(this);
+    panel.addEventListener("popupshown", this._boundPopupShown, false);
+
+    this.panel = panel;
+    this.iframe = iframe;
+
+    return panel;
+  },
+
+  /**
+   * Event handler for the popupshown event.
+   */
+  popupShown: function SI_popupShown()
+  {
+    this.panelReady = true;
+    this.cssHtmlTree = new CssHtmlTree(this);
+    let selectedNode = this.selectedNode || null;
+    this.cssLogic.highlight(selectedNode);
+    this.cssHtmlTree.highlight(selectedNode);
+    if (this._openCallback) {
+      this._openCallback();
+      delete this._openCallback;
+    }
+  },
+
+  isLoaded: function SI_isLoaded()
+  {
+    return this.iframeReady && this.panelReady;
+  },
+
+  /**
+   * Select from Path (via CssHtmlTree_pathClick)
+   * @param aNode The node to inspect.
+   */
+  selectFromPath: function SI_selectFromPath(aNode)
+  {
+    this.selectNode(aNode);
+  },
+
+  /**
+   * Select a node to inspect in the Style Inspector panel
+   * @param aNode The node to inspect.
+   */
+  selectNode: function SI_selectNode(aNode)
+  {
+    this.selectedNode = aNode;
+
+    if (this.isLoaded()) {
+      this.cssLogic.highlight(aNode);
+      this.cssHtmlTree.highlight(aNode);
+    }
+  },
+
+  /**
+   * Destroy the style panel, remove listeners etc.
+   */
+  destroy: function SI_destroy()
+  {
+    this.panel.hidePopup();
+
+    if (this.cssHtmlTree) {
+      this.cssHtmlTree.destroy();
+      delete this.cssHtmlTree;
+    }
+
+    if (this.iframe) {
+      this.iframe.parentNode.removeChild(this.iframe);
+      delete this.iframe;
+    }
+
+    delete this.cssLogic;
+    this.panel.removeEventListener("popupshown", this._boundPopupShown, false);
+    delete this._boundPopupShown;
+    this.panel.parentNode.removeChild(this.panel);
+    delete this.panel;
+    delete this.doc;
+    delete this.win;
+    delete CssHtmlTree.win;
+  },
+};
+
+function ruleView()
+{
+  return InspectorUI.sidebar._toolContext("ruleview").view;
+}
+
 registerCleanupFunction(tearDown);
 
 waitForExplicitFinish();
 
--- a/browser/devtools/tilt/test/browser_tilt_04_initialization.js
+++ b/browser/devtools/tilt/test/browser_tilt_04_initialization.js
@@ -11,42 +11,39 @@ function test() {
     info("Skipping initialization test because WebGL isn't supported.");
     return;
   }
 
   waitForExplicitFinish();
 
   createTab(function() {
     let id = TiltUtils.getWindowId(gBrowser.selectedBrowser.contentWindow);
-    let initialActiveElement;
 
     is(id, Tilt.currentWindowId,
       "The unique window identifiers should match for the same window.");
 
     createTilt({
       onInspectorOpen: function() {
-        initialActiveElement = document.activeElement;
-
         is(Tilt.visualizers[id], null,
           "A instance of the visualizer shouldn't be initialized yet.");
       },
       onTiltOpen: function(instance)
       {
         is(document.activeElement, instance.presenter.canvas,
           "The visualizer canvas should be focused on initialization.");
 
         ok(Tilt.visualizers[id] instanceof TiltVisualizer,
           "A new instance of the visualizer wasn't created properly.");
         ok(Tilt.visualizers[id].isInitialized(),
           "The new instance of the visualizer wasn't initialized properly.");
       },
       onTiltClose: function()
       {
-        is(document.activeElement, initialActiveElement,
-          "The focus wasn't correctly given back to the initial element.");
+        is(document.activeElement, gBrowser.selectedBrowser,
+          "The focus wasn't correctly given back to the selectedBrowser.");
 
         is(Tilt.visualizers[id], null,
           "The current instance of the visualizer wasn't destroyed properly.");
       },
       onEnd: function()
       {
         gBrowser.removeCurrentTab();
         finish();
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -601,19 +601,16 @@ just addresses the organization to follo
 <!ENTITY syncSetup.accesskey          "Y">
 <!ENTITY syncSyncNowItem.label        "Sync Now">
 <!ENTITY syncSyncNowItem.accesskey    "S">
 <!ENTITY syncToolbarButton.label      "Sync">
 
 <!ENTITY addonBarCloseButton.tooltip  "Close Add-on Bar">
 <!ENTITY toggleAddonBarCmd.key        "/">
 
-<!-- LOCALIZATION NOTE (htmlPanel.label): This is a label for a button that
-activates the Web Developer->Inspect UI's HTML Tree Panel. -->
-<!ENTITY htmlPanel.label              "HTML">
-
-<!-- LOCALIZATION NOTE (htmlPanel.tooltiptext): The text that appears when a user
-hovers over the HTML panel's toolbar button. -->
-<!ENTITY htmlPanel.tooltiptext        "HTML panel">
-
-<!-- LOCALIZATION NOTE (htmlPanel.accesskey): The key bound to the HTML panel's
+<!-- LOCALIZATION NOTE (markupButton.arialabel): The markup button is the button
+located in front of the breadcrumbs display in the inspector toolbar. The button
+doesn't display any label, but exposes a label to screen-readers with "aria-label".
+-->
+<!ENTITY markupButton.arialabel          "Markup">
+<!-- LOCALIZATION NOTE (markupButton.accesskey): The key bound to the Markup panel's
 toolbar button -->
-<!ENTITY htmlPanel.accesskey          "H">
+<!ENTITY markupButton.accesskey          "M">
--- a/browser/locales/en-US/chrome/browser/devtools/inspector.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/inspector.properties
@@ -15,26 +15,28 @@ confirmNavigationAway.buttonLeave=Leave 
 confirmNavigationAway.buttonLeaveAccesskey=L
 confirmNavigationAway.buttonStay=Stay on Page
 confirmNavigationAway.buttonStayAccesskey=S
 
 breadcrumbs.siblings=Siblings
 # LOCALIZATION NOTE (htmlPanel): Used in the Inspector tool's openInspectorUI
 # method when registering the HTML panel.
 
-# LOCALIZATION NOTE (ruleView.*): Button label, accesskey and tooltip text
-# associated with the Highlighter's CSS Rule View in the Style Sidebar.
-ruleView.label=Rules
-ruleView.accesskey=R
-ruleView.tooltiptext=View and Edit CSS
-
 # LOCALIZATION NOTE (inspectButton.tooltiptext):
 # This button appears in the Inspector Toolbar. inspectButton is stateful,
-# if it's pressed users can select an element with the mouse. Pressing the
-# "Return" key # changes that state. %S is the keyboard shortcut (VK_RETURN in
-# chrome://global/locale/keys.properties).
+# if it's pressed users can select an element with the mouse.
+# %S is the keyboard shortcut.
 inspectButton.tooltiptext=Select element with mouse (%S)
 
+# LOCALIZATION NOTE (markupButton.*):
+# This button is the button located at the beginning of the breadcrumbs
+# in the inspector toolbar. Its tooltip is built dynamically.
+# markupButton.tooltip is used on Mac.
+# On Windows and Linux, we use markupButton.tooltipWithAccesskey, where we append
+# the keyboard shortcut.
+markupButton.tooltip=Markup Panel
+markupButton.tooltipWithAccesskey=Markup Panel (%S)
+
 
 # LOCALIZATION NOTE (nodeMenu.tooltiptext)
 # This menu appears in the Infobar (on top of the highlighted node) once
 # the node is selected.
 nodeMenu.tooltiptext=Node operations
--- a/browser/locales/en-US/chrome/browser/devtools/styleinspector.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/styleinspector.properties
@@ -116,8 +116,18 @@ rule.contextmenu.copyproperty.accesskey=
 
 # LOCALIZATION NOTE (rule.contextmenu.copypropertyvalue): The rule view's
 # context menu copy property entry allows a CSS property value to be copied.
 rule.contextmenu.copypropertyvalue=Copy property value
 
 # LOCALIZATION NOTE (rule.contextmenu.copypropertyvalue.accesskey): The rule
 # view's context menu copy property value access key.
 rule.contextmenu.copypropertyvalue.accesskey=U
+
+# LOCALIZATION NOTE (ruleView.*): Button label, accesskey and tooltip text
+# associated with the Highlighter's CSS Rule View in the Style Sidebar.
+ruleView.label=Rules
+ruleView.accesskey=R
+ruleView.tooltiptext=View and Edit CSS
+
+# LOCALIZATION NOTE (ruleView.empty): Text displayed when the highlighter is
+# first opened and there's no node selected in the rule view.
+rule.empty=No element selected.
--- a/browser/themes/gnomestripe/browser.css
+++ b/browser/themes/gnomestripe/browser.css
@@ -2162,44 +2162,86 @@ html|*#highlighter-nodeinfobar-pseudo-cl
   -moz-transition-property: background-color;
   -moz-transition-duration: 500ms;
   background-color: rgba(0,0,0,0);
 }
 
 /* Highlighter toolbar - breadcrumbs */
 
 #inspector-breadcrumbs {
-  padding: 0 3px;
+  -moz-margin-end: 3px;
   /* A fake 1px-shadow is included in the border-images of the
      inspector-breadcrumbs-buttons, to match toolbar-buttons style.
      This negative margin compensate the extra row of pixels created
      by the shadow.*/
   margin-bottom: -1px;
 }
 
 #inspector-breadcrumbs > .scrollbutton-up,
 #inspector-breadcrumbs > .scrollbutton-down {
   -moz-appearance: none;
+  border: 1px solid hsla(210,8%,5%,.45);
+  background: -moz-linear-gradient(hsla(212,7%,57%,.35), hsla(212,7%,57%,.1)) padding-box;
+  box-shadow: 0 1px 0 hsla(210,16%,76%,.15) inset, 0 0 0 1px hsla(210,16%,76%,.15) inset, 0 1px 0 hsla(210,16%,76%,.15);
+  margin: 0 0 1px;
+}
+
+#inspector-breadcrumbs > .scrollbutton-up {
+  -moz-border-start-width: 0;
+}
+
+#inspector-breadcrumbs > .scrollbutton-up:not([disabled]):active:hover,
+#inspector-breadcrumbs > .scrollbutton-down:not([disabled]):active:hover {
+  border-color: hsla(210,8%,5%,.6);
+  background: -moz-linear-gradient(hsla(220,6%,10%,.3), hsla(212,7%,57%,.15) 65%, hsla(212,7%,57%,.3));
+  box-shadow: 0 0 3px hsla(210,8%,5%,.25) inset, 0 1px 3px hsla(210,8%,5%,.25) inset, 0 1px 0 hsla(210,16%,76%,.15);
+}
+
+#inspector-breadcrumbs > .scrollbutton-up:not([disabled]):active:hover > .toolbarbutton-icon,
+#inspector-breadcrumbs > .scrollbutton-down:not([disabled]):active:hover > .toolbarbutton-icon {
+  -moz-image-region: rect(0px 14px 16px 7px);
+}
+
+#inspector-breadcrumbs > .scrollbutton-up > .toolbarbutton-icon,
+#inspector-breadcrumbs > .scrollbutton-down > .toolbarbutton-icon {
+  -moz-appearance: none;
+  list-style-image: url("chrome://browser/skin/devtools/breadcrumbs-scrollbutton.png");
+  -moz-image-region: rect(0px 7px 16px 0px);
+  margin: 0 5px;
+}
+
+#inspector-breadcrumbs > .scrollbutton-up[disabled] > .toolbarbutton-icon,
+#inspector-breadcrumbs > .scrollbutton-down[disabled] > .toolbarbutton-icon {
+  opacity: 0.5;
+}
+
+#inspector-breadcrumbs > .scrollbutton-up > .toolbarbutton-icon:-moz-locale-dir(rtl),
+#inspector-breadcrumbs > .scrollbutton-down > .toolbarbutton-icon:-moz-locale-dir(ltr) {
+  -moz-transform: scaleX(-1);
 }
 
 .inspector-breadcrumbs-button {
   -moz-appearance: none;
   background-color: transparent;
   border-style: solid;
   border-width: 1px 13px 2px 13px;
   color: hsl(210,30%,85%);
-  max-width: 85px;
+  width: 85px; /* Can't use max-width. See bug 723132 */
   /* The content of the button can be larger than the button */
   overflow: hidden;
   min-height: 25px;
 
   margin: 0 -11px 0 0;
   padding: 0 9px;
 }
 
+.inspector-breadcrumbs-button:-moz-focusring > label {
+  border-bottom: 1px dotted hsla(210,30%,85%,0.4);
+}
+
 .inspector-breadcrumbs-button[checked] > .inspector-breadcrumbs-tag {
   color: hsl(208,100%,60%);
 }
 
 .inspector-breadcrumbs-button[checked] > .inspector-breadcrumbs-id {
   color: hsl(205,100%,70%);
 }
 
@@ -2215,16 +2257,17 @@ html|*#highlighter-nodeinfobar-pseudo-cl
 .inspector-breadcrumbs-pseudo-classes {
   color: hsl(20, 100%, 85%);
 }
 
 /* Highlighter toolbar - breadcrumbs - LTR */
 
 .inspector-breadcrumbs-button:-moz-locale-dir(ltr):first-of-type {
   margin-left: 0;
+  border-left-width: 0;
 }
 
 .inspector-breadcrumbs-button {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/ltr-middle.png") 1 13 2 13 fill stretch;
 }
 
 .inspector-breadcrumbs-button[siblings-menu-open]:not([checked]),
 .inspector-breadcrumbs-button:not([checked]):hover:active {
@@ -2257,16 +2300,20 @@ html|*#highlighter-nodeinfobar-pseudo-cl
 .inspector-breadcrumbs-button:first-of-type[checked]:hover:active {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/ltr-start-selected-pressed.png") 1 13 2 13 fill stretch;
 }
 
 .inspector-breadcrumbs-button:last-of-type {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/ltr-end.png") 1 13 2 13 fill stretch;
 }
 
+#inspector-breadcrumbs[overflows] > .inspector-breadcrumbs-button:-moz-locale-dir(ltr):last-of-type {
+  border-right-width: 0;
+}
+
 .inspector-breadcrumbs-button[siblings-menu-open]:last-of-type:not([checked]),
 .inspector-breadcrumbs-button:last-of-type:not([checked]):hover:active {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/ltr-end-pressed.png") 1 13 2 13 fill stretch;
 }
 
 .inspector-breadcrumbs-button:last-of-type[checked] {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/ltr-end-selected.png") 1 13 2 13 fill stretch;
 }
@@ -2275,16 +2322,17 @@ html|*#highlighter-nodeinfobar-pseudo-cl
 .inspector-breadcrumbs-button:last-of-type[checked]:hover:active {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/ltr-end-selected-pressed.png") 1 13 2 13 fill stretch;
 }
 
 /* Highlighter toolbar - breadcrumbs - RTL */
 
 .inspector-breadcrumbs-button:-moz-locale-dir(rtl):first-of-type {
   margin-right: 0;
+  border-right-width: 0;
 }
 
 .inspector-breadcrumbs-button:-moz-locale-dir(rtl) {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/rtl-middle.png") 1 13 2 13 fill stretch;
 }
 
 .inspector-breadcrumbs-button[siblings-menu-open]:not([checked]):-moz-locale-dir(rtl),
 .inspector-breadcrumbs-button:not([checked]):hover:active:-moz-locale-dir(rtl) {
@@ -2317,32 +2365,56 @@ html|*#highlighter-nodeinfobar-pseudo-cl
 .inspector-breadcrumbs-button:first-of-type[checked]:hover:active:-moz-locale-dir(rtl) {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/rtl-start-selected-pressed.png") 1 13 2 13 fill stretch;
 }
 
 .inspector-breadcrumbs-button:last-of-type:-moz-locale-dir(rtl) {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/rtl-end.png") 1 13 2 13 fill stretch;
 }
 
+#inspector-breadcrumbs[overflows] > .inspector-breadcrumbs-button:last-of-type:-moz-locale-dir(rtl) {
+  border-left-width: 0;
+}
+
 .inspector-breadcrumbs-button[siblings-menu-open]:last-of-type:not([checked]):-moz-locale-dir(rtl),
 .inspector-breadcrumbs-button:last-of-type:not([checked]):hover:active:-moz-locale-dir(rtl) {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/rtl-end-pressed.png") 1 13 2 13 fill stretch;
 }
 
 .inspector-breadcrumbs-button:last-of-type[checked]:-moz-locale-dir(rtl) {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/rtl-end-selected.png") 1 13 2 13 fill stretch;
 }
 
 .inspector-breadcrumbs-button[siblings-menu-open]:last-of-type[checked]:-moz-locale-dir(rtl),
 .inspector-breadcrumbs-button:last-of-type[checked]:hover:active:-moz-locale-dir(rtl) {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/rtl-end-selected-pressed.png") 1 13 2 13 fill stretch;
 }
 
 /* Highlighter toolbar - HTML Tree */
 
+#inspector-treepanel-toolbutton {
+  list-style-image: url("chrome://browser/skin/devtools/treepanel-button.png");
+  -moz-margin-end: 0;
+  -moz-image-region: rect(0px 18px 16px 0px);
+}
+
+#inspector-treepanel-toolbutton[checked] {
+  -moz-image-region: rect(0px 36px 16px 18px);
+}
+
+#inspector-breadcrumbs > .scrollbutton-down:-moz-locale-dir(rtl),
+#inspector-treepanel-toolbutton:-moz-locale-dir(ltr) {
+  border-radius: 3px 0 0 3px;
+}
+
+#inspector-breadcrumbs > .scrollbutton-down:-moz-locale-dir(ltr),
+#inspector-treepanel-toolbutton:-moz-locale-dir(rtl) {
+  border-radius: 0 3px 3px 0;
+}
+
 #inspector-tree-splitter {
   -moz-appearance: none;
   border-top: 1px solid black;
   border-bottom-width: 0;
   min-height: 3px;
   height: 3px;
   margin-bottom: -3px;
   position: relative;
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..722438060a89a64789e73bfd79b7a50546c61b4b
GIT binary patch
literal 545
zc$@(t0^a?JP)<h;3K|Lk000e1NJLTq000gE000mO1^@s6CX`M(0000PbVXQnQ*UN;
zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz!%0LzRCwB)lTAwkQ51&H9VZPW#Uf3k
z0yBsTLaA0l1V$}dN2?YQ^cVUQZQIx?ceSWZP_zpAqS^$~hmE1un5m`tQ8+WsbnfVo
zO|2X_+~J&g=DqKIXNaOG(9A`e23xEyea~F4vKZvcx|tEngjHewfkydQWDQs6AE=n^
zneBoC;qPqBKhTO)m5X@au7{@GHZJv91;`}YSRJ&4P@Xo>d8HAW>TTnaC9&4Y#AUVt
zTGXK4{=o}eUNp$>Y~}c(pfcR{dVst><|*hzXaS-3Iuu2@b5wjsFC7zZ2lrg4iO2Ug
z=g9rpE#WQ;6f|j&ddvU?g}?`zm*hm)^we(oaq4jA6l-T*Ufq=#d~*g>ZlLC=l8i@u
z2oF;W<od9b+7AUPf&jFJyuK5~J4;X7NDLMiix0X`%AbhdsS&iW*zFq#$qK|2&$t~t
zV<YCt`2#w0npt?r30(|BKtk0d`Z`8sdLW`GRo`eF)eT-kKWX5u!sVl_8Br7=k^fTT
zP;=D5Sqf`$G{VB^jtbK_etmMdwc566_ym$tkWXtf!*VyPZrf_N03qo=-ztT1p(RnZ
j#u<gRS<`>Vw*UhG3c2M}ko?5#00000NkvXXu0mjfohkL*
--- a/browser/themes/gnomestripe/devtools/common.css
+++ b/browser/themes/gnomestripe/devtools/common.css
@@ -52,16 +52,21 @@
   text-shadow: 0 -1px 0 hsla(210,8%,5%,.45);
   border: 1px solid hsla(210,8%,5%,.45);
   border-radius: 3px;
   background: -moz-linear-gradient(hsla(212,7%,57%,.35), hsla(212,7%,57%,.1)) padding-box;
   box-shadow: 0 1px 0 hsla(210,16%,76%,.15) inset, 0 0 0 1px hsla(210,16%,76%,.15) inset, 0 1px 0 hsla(210,16%,76%,.15);
   margin: 0 3px;
 }
 
+.devtools-toolbarbutton:-moz-focusring {
+  outline: 1px dotted hsla(210,30%,85%,0.4);
+  outline-offset: -4px;
+}
+
 .devtools-toolbarbutton:not([label]) {
   min-width: 32px;
 }
 
 .devtools-toolbarbutton:not([checked]):hover:active {
   border-color: hsla(210,8%,5%,.6);
   background: -moz-linear-gradient(hsla(220,6%,10%,.3), hsla(212,7%,57%,.15) 65%, hsla(212,7%,57%,.3));
   box-shadow: 0 0 3px hsla(210,8%,5%,.25) inset, 0 1px 3px hsla(210,8%,5%,.25) inset, 0 1px 0 hsla(210,16%,76%,.15);
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2a8802216bf178bc05813fc592df30d91cd49415
GIT binary patch
literal 805
zc$@(x1KRwFP)<h;3K|Lk000e1NJLTq001Na000mO1^@s6e(T*M0000PbVXQnQ*UN;
zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!$4Nv%RCwCVR$FTmK@^_Z-89vNv{qwm
zODndik(x^}`U{GbLJ)lr5%fv)L45Of_%7H-U%enj2$F&~P-`(Ips`Au*oIzAYMV46
zo88&*oRE}pd&&6Lz{d=kbG|)u_M2~(a?Z&?`cX$mPdkjq03LwSBHjWN0eL`GTreKx
z95A>5$VXf7@J4<aQ^f@h;7i0O@1F^rJ|eS&^!K?xqhqlLFwOq6u=3M08XAZ@R3$3I
zV=!wbk(wVPC;SC_L7hWGLr#-fB^e@&#T<J<^!G1!$v73Q=}hX?v?(NtIowu|0=?5e
zewyBdxhVsT#he{a$feolW-)1+CXoTgV$Kc?X`Y@g+&H}T?ncM3104YOE}Aky9IG%c
z05(KTv1rUmsM1HYw&TF15T{Gy9`7O2?sgJIwP}ho(ssB&y8TPsf1xO9imIL-wNV6$
zl2S_g;*-yAgo0;XGOhpmEnT`ee(9mb$&$#!$M1EWyLS7j!{La?@R$~#na<|NG=&mu
zTSZtqIXpaUf~MAGhysBCZm5$@<gy|UO-*&ISFK2`QkB#ct!qlz<vi+d-@UIij$=z;
z>|UBGmWoNkFo+B=7IV;tniNex>IV$MLwpTQ?d3;XfCB6w1LF^KBG>F_-H+(p2QOJ&
z=qI(xQRtVtI}78ax~0>Nl`T>!E)#AoiVyU?eDUP^NF>}Y)8d&gNiBBy>KiD0o`@X+
zKN@h;xZ3NDl3s619>&;gX6pGB`uZ^*7F{f;GZYN!hRMn@1&xHm8XO%J4nbWvOz@*T
z@kMnecO^Mh8sUTIEo&lI=TSuM#9t*0SwF(qy|hy<SET(2V?{sG1U0YSx^s70@S}ZC
zDa0?sxMsh#XlAy=v#FcgezgCMW)ZWtM!8ku_JZIKbn6sM(vp`Wc2l<p($AA9cGT74
j|L|<*AGFVpzXA*Z|5Kl~6^V5Y00000NkvXXu0mjflR<IX
--- a/browser/themes/gnomestripe/jar.mn
+++ b/browser/themes/gnomestripe/jar.mn
@@ -98,16 +98,17 @@ browser.jar:
   skin/classic/browser/devtools/webconsole.png                  (devtools/webconsole.png)
   skin/classic/browser/devtools/gcli.css              (devtools/gcli.css)
   skin/classic/browser/devtools/htmlpanel.css         (devtools/htmlpanel.css)
   skin/classic/browser/devtools/orion.css             (devtools/orion.css)
   skin/classic/browser/devtools/orion-container.css   (devtools/orion-container.css)
   skin/classic/browser/devtools/orion-task.png        (devtools/orion-task.png)
   skin/classic/browser/devtools/orion-breakpoint.png  (devtools/orion-breakpoint.png)
   skin/classic/browser/devtools/orion-debug-location.png (devtools/orion-debug-location.png)
+  skin/classic/browser/devtools/breadcrumbs-scrollbutton.png                 (devtools/breadcrumbs-scrollbutton.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-end-pressed.png              (devtools/breadcrumbs/ltr-end-pressed.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-end-selected-pressed.png     (devtools/breadcrumbs/ltr-end-selected-pressed.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-end-selected.png             (devtools/breadcrumbs/ltr-end-selected.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-end.png                      (devtools/breadcrumbs/ltr-end.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-middle-pressed.png           (devtools/breadcrumbs/ltr-middle-pressed.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-middle-selected-pressed.png  (devtools/breadcrumbs/ltr-middle-selected-pressed.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-middle-selected.png          (devtools/breadcrumbs/ltr-middle-selected.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-middle.png                   (devtools/breadcrumbs/ltr-middle.png)
@@ -131,16 +132,17 @@ browser.jar:
   skin/classic/browser/devtools/styleeditor.css       (devtools/styleeditor.css)
   skin/classic/browser/devtools/debugger.css          (devtools/debugger.css)
   skin/classic/browser/devtools/magnifying-glass.png  (devtools/magnifying-glass.png)
   skin/classic/browser/devtools/itemToggle.png        (devtools/itemToggle.png)
   skin/classic/browser/devtools/itemArrow-rtl.png     (devtools/itemArrow-rtl.png)
   skin/classic/browser/devtools/itemArrow-ltr.png     (devtools/itemArrow-ltr.png)
   skin/classic/browser/devtools/inspect-button.png    (devtools/inspect-button.png)
   skin/classic/browser/devtools/dropmarker.png        (devtools/dropmarker.png)
+  skin/classic/browser/devtools/treepanel-button.png  (devtools/treepanel-button.png)
 #ifdef MOZ_SERVICES_SYNC
   skin/classic/browser/sync-16-throbber.png
   skin/classic/browser/sync-16.png
   skin/classic/browser/sync-24-throbber.png
   skin/classic/browser/sync-32.png
   skin/classic/browser/sync-bg.png
   skin/classic/browser/sync-128.png
   skin/classic/browser/sync-desktopIcon.png
--- a/browser/themes/pinstripe/browser.css
+++ b/browser/themes/pinstripe/browser.css
@@ -2907,38 +2907,85 @@ html|*#highlighter-nodeinfobar-pseudo-cl
   -moz-transition-property: background-color;
   -moz-transition-duration: 500ms;
   background-color: rgba(0,0,0,0);
 }
 
 /* Highlighter toolbar - breadcrumbs */
 
 #inspector-breadcrumbs {
-  padding: 0 6px;
+  -moz-margin-end: 3px;
   /* A fake 1px-shadow is included in the border-images of the
      inspector-breadcrumbs-buttons, to match toolbar-buttons style.
      This negative margin compensate the extra row of pixels created
      by the shadow.*/
   margin-bottom: -1px;
 }
 
+#inspector-breadcrumbs > .scrollbutton-up,
+#inspector-breadcrumbs > .scrollbutton-down {
+  -moz-appearance: none;
+  border: 1px solid hsla(210,8%,5%,.45);
+  background: -moz-linear-gradient(hsla(212,7%,57%,.35), hsla(212,7%,57%,.1)) padding-box;
+  box-shadow: 0 1px 0 hsla(210,16%,76%,.15) inset, 0 0 0 1px hsla(210,16%,76%,.15) inset, 0 1px 0 hsla(210,16%,76%,.15);
+  margin: 0 0 1px;
+}
+
+#inspector-breadcrumbs > .scrollbutton-up {
+  -moz-border-start-width: 0;
+}
+
+#inspector-breadcrumbs > .scrollbutton-up:not([disabled]):active:hover,
+#inspector-breadcrumbs > .scrollbutton-down:not([disabled]):active:hover {
+  border-color: hsla(210,8%,5%,.6);
+  background: -moz-linear-gradient(hsla(220,6%,10%,.3), hsla(212,7%,57%,.15) 65%, hsla(212,7%,57%,.3));
+  box-shadow: 0 0 3px hsla(210,8%,5%,.25) inset, 0 1px 3px hsla(210,8%,5%,.25) inset, 0 1px 0 hsla(210,16%,76%,.15);
+}
+
+#inspector-breadcrumbs > .scrollbutton-up > .toolbarbutton-icon,
+#inspector-breadcrumbs > .scrollbutton-down > .toolbarbutton-icon {
+  -moz-appearance: none;
+  list-style-image: url("chrome://browser/skin/devtools/breadcrumbs-scrollbutton.png");
+  -moz-image-region: rect(0px 7px 16px 0px);
+  margin: 0 5px;
+}
+
+#inspector-breadcrumbs > .scrollbutton-up[disabled] > .toolbarbutton-icon,
+#inspector-breadcrumbs > .scrollbutton-down[disabled] > .toolbarbutton-icon {
+  opacity: 0.5;
+}
+
+#inspector-breadcrumbs > .scrollbutton-up:not([disabled]):active:hover > .toolbarbutton-icon,
+#inspector-breadcrumbs > .scrollbutton-down:not([disabled]):active:hover > .toolbarbutton-icon {
+  -moz-image-region: rect(0px 14px 16px 7px);
+}
+
+#inspector-breadcrumbs > .scrollbutton-up > .toolbarbutton-icon:-moz-locale-dir(rtl),
+#inspector-breadcrumbs > .scrollbutton-down > .toolbarbutton-icon:-moz-locale-dir(ltr) {
+  -moz-transform: scaleX(-1);
+}
+
 .inspector-breadcrumbs-button {
   -moz-appearance: none;
   border-style: solid;
   border-width: 1px 13px 2px 13px;
   color: hsl(210,30%,85%);
-  max-width: 85px;
+  width: 85px; /* Can't use max-width. See bug 723132 */
   /* The content of the button can be larger than the button */
   overflow: hidden;
   min-height: 25px;
 
   margin: 0 -11px 0 0;
   padding: 0 9px;
 }
 
+.inspector-breadcrumbs-button:-moz-focusring > label {
+  border-bottom: 1px dotted hsla(210,30%,85%,0.4);
+}
+
 .inspector-breadcrumbs-button[checked] > .inspector-breadcrumbs-tag {
   color: hsl(208,100%,60%);
 }
 
 .inspector-breadcrumbs-button[checked] > .inspector-breadcrumbs-id {
   color: hsl(205,100%,70%);
 }
 
@@ -2953,17 +3000,17 @@ html|*#highlighter-nodeinfobar-pseudo-cl
 
 .inspector-breadcrumbs-pseudo-classes {
   color: hsl(20, 100%, 85%);
 }
 
 /* Highlighter toolbar - breadcrumbs - LTR */
 
 .inspector-breadcrumbs-button:-moz-locale-dir(ltr):first-of-type {
-  margin-left: 0;
+  border-left-width: 0;
 }
 
 .inspector-breadcrumbs-button {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/ltr-middle.png") 1 13 2 13 fill stretch;
 }
 
 .inspector-breadcrumbs-button[siblings-menu-open]:not([checked]),
 .inspector-breadcrumbs-button:not([checked]):hover:active {
@@ -2996,16 +3043,20 @@ html|*#highlighter-nodeinfobar-pseudo-cl
 .inspector-breadcrumbs-button:first-of-type[checked]:hover:active {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/ltr-start-selected-pressed.png") 1 13 2 13 fill stretch;
 }
 
 .inspector-breadcrumbs-button:last-of-type {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/ltr-end.png") 1 13 2 13 fill stretch;
 }
 
+#inspector-breadcrumbs[overflows] > .inspector-breadcrumbs-button:-moz-locale-dir(ltr):last-of-type {
+  border-right-width: 0;
+}
+
 .inspector-breadcrumbs-button[siblings-menu-open]:last-of-type:not([checked]),
 .inspector-breadcrumbs-button:last-of-type:not([checked]):hover:active {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/ltr-end-pressed.png") 1 13 2 13 fill stretch;
 }
 
 .inspector-breadcrumbs-button:last-of-type[checked] {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/ltr-end-selected.png") 1 13 2 13 fill stretch;
 }
@@ -3013,17 +3064,17 @@ html|*#highlighter-nodeinfobar-pseudo-cl
 .inspector-breadcrumbs-button[siblings-menu-open]:last-of-type[checked],
 .inspector-breadcrumbs-button:last-of-type[checked]:hover:active {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/ltr-end-selected-pressed.png") 1 13 2 13 fill stretch;
 }
 
 /* Highlighter toolbar - breadcrumbs - RTL */
 
 .inspector-breadcrumbs-button:-moz-locale-dir(rtl):first-of-type {
-  margin-right: 0;
+  border-right-width: 0;
 }
 
 .inspector-breadcrumbs-button:-moz-locale-dir(rtl) {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/rtl-middle.png") 1 13 2 13 fill stretch;
 }
 
 .inspector-breadcrumbs-button[siblings-menu-open]:not([checked]):-moz-locale-dir(rtl),
 .inspector-breadcrumbs-button:not([checked]):hover:active:-moz-locale-dir(rtl) {
@@ -3056,32 +3107,56 @@ html|*#highlighter-nodeinfobar-pseudo-cl
 .inspector-breadcrumbs-button:first-of-type[checked]:hover:active:-moz-locale-dir(rtl) {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/rtl-start-selected-pressed.png") 1 13 2 13 fill stretch;
 }
 
 .inspector-breadcrumbs-button:last-of-type:-moz-locale-dir(rtl) {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/rtl-end.png") 1 13 2 13 fill stretch;
 }
 
+#inspector-breadcrumbs[overflows] > .inspector-breadcrumbs-button:last-of-type:-moz-locale-dir(rtl) {
+  border-left-width: 0;
+}
+
 .inspector-breadcrumbs-button[siblings-menu-open]:last-of-type:not([checked]):-moz-locale-dir(rtl),
 .inspector-breadcrumbs-button:last-of-type:not([checked]):hover:active:-moz-locale-dir(rtl) {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/rtl-end-pressed.png") 1 13 2 13 fill stretch;
 }
 
 .inspector-breadcrumbs-button:last-of-type[checked]:-moz-locale-dir(rtl) {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/rtl-end-selected.png") 1 13 2 13 fill stretch;
 }
 
 .inspector-breadcrumbs-button[siblings-menu-open]:last-of-type[checked]:-moz-locale-dir(rtl),
 .inspector-breadcrumbs-button:last-of-type[checked]:hover:active:-moz-locale-dir(rtl) {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/rtl-end-selected-pressed.png") 1 13 2 13 fill stretch;
 }
 
 /* Highlighter toolbar - HTML Tree */
 
+#inspector-treepanel-toolbutton {
+  list-style-image: url("chrome://browser/skin/devtools/treepanel-button.png");
+  -moz-margin-end: 0;
+  -moz-image-region: rect(0px 18px 16px 0px);
+}
+
+#inspector-treepanel-toolbutton[checked] {
+  -moz-image-region: rect(0px 36px 16px 18px);
+}
+
+#inspector-breadcrumbs > .scrollbutton-down:-moz-locale-dir(rtl),
+#inspector-treepanel-toolbutton:-moz-locale-dir(ltr) {
+  border-radius: @toolbarbuttonCornerRadius@ 0 0 @toolbarbuttonCornerRadius@;
+}
+
+#inspector-breadcrumbs > .scrollbutton-down:-moz-locale-dir(ltr),
+#inspector-treepanel-toolbutton:-moz-locale-dir(rtl) {
+  border-radius: 0 @toolbarbuttonCornerRadius@ @toolbarbuttonCornerRadius@ 0;
+}
+
 #inspector-tree-splitter {
   -moz-appearance: none;
   border-top: 1px solid black;
   border-bottom-width: 0;
   min-height: 3px;
   height: 3px;
   margin-bottom: -3px;
   position: relative;
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..722438060a89a64789e73bfd79b7a50546c61b4b
GIT binary patch
literal 545
zc$@(t0^a?JP)<h;3K|Lk000e1NJLTq000gE000mO1^@s6CX`M(0000PbVXQnQ*UN;
zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz!%0LzRCwB)lTAwkQ51&H9VZPW#Uf3k
z0yBsTLaA0l1V$}dN2?YQ^cVUQZQIx?ceSWZP_zpAqS^$~hmE1un5m`tQ8+WsbnfVo
zO|2X_+~J&g=DqKIXNaOG(9A`e23xEyea~F4vKZvcx|tEngjHewfkydQWDQs6AE=n^
zneBoC;qPqBKhTO)m5X@au7{@GHZJv91;`}YSRJ&4P@Xo>d8HAW>TTnaC9&4Y#AUVt
zTGXK4{=o}eUNp$>Y~}c(pfcR{dVst><|*hzXaS-3Iuu2@b5wjsFC7zZ2lrg4iO2Ug
z=g9rpE#WQ;6f|j&ddvU?g}?`zm*hm)^we(oaq4jA6l-T*Ufq=#d~*g>ZlLC=l8i@u
z2oF;W<od9b+7AUPf&jFJyuK5~J4;X7NDLMiix0X`%AbhdsS&iW*zFq#$qK|2&$t~t
zV<YCt`2#w0npt?r30(|BKtk0d`Z`8sdLW`GRo`eF)eT-kKWX5u!sVl_8Br7=k^fTT
zP;=D5Sqf`$G{VB^jtbK_etmMdwc566_ym$tkWXtf!*VyPZrf_N03qo=-ztT1p(RnZ
j#u<gRS<`>Vw*UhG3c2M}ko?5#00000NkvXXu0mjfohkL*
--- a/browser/themes/pinstripe/devtools/common.css
+++ b/browser/themes/pinstripe/devtools/common.css
@@ -53,16 +53,21 @@
   color: hsl(210,30%,85%);
   text-shadow: 0 -1px 0 hsla(210,8%,5%,.45);
   border: 1px solid hsla(210,8%,5%,.45);
   border-radius: @toolbarbuttonCornerRadius@;
   background: -moz-linear-gradient(hsla(212,7%,57%,.35), hsla(212,7%,57%,.1)) padding-box;
   box-shadow: 0 1px 0 hsla(210,16%,76%,.15) inset, 0 0 0 1px hsla(210,16%,76%,.15) inset, 0 1px 0 hsla(210,16%,76%,.15);
 }
 
+.devtools-toolbarbutton:-moz-focusring {
+  outline: 1px dotted hsla(210,30%,85%,0.4);
+  outline-offset: -4px;
+}
+
 .devtools-toolbarbutton > .toolbarbutton-text {
   margin: 1px 6px;
 }
 
 .devtools-toolbarbutton:not([label]) {
   min-width: 32px;
 }
 
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2a8802216bf178bc05813fc592df30d91cd49415
GIT binary patch
literal 805
zc$@(x1KRwFP)<h;3K|Lk000e1NJLTq001Na000mO1^@s6e(T*M0000PbVXQnQ*UN;
zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!$4Nv%RCwCVR$FTmK@^_Z-89vNv{qwm
zODndik(x^}`U{GbLJ)lr5%fv)L45Of_%7H-U%enj2$F&~P-`(Ips`Au*oIzAYMV46
zo88&*oRE}pd&&6Lz{d=kbG|)u_M2~(a?Z&?`cX$mPdkjq03LwSBHjWN0eL`GTreKx
z95A>5$VXf7@J4<aQ^f@h;7i0O@1F^rJ|eS&^!K?xqhqlLFwOq6u=3M08XAZ@R3$3I
zV=!wbk(wVPC;SC_L7hWGLr#-fB^e@&#T<J<^!G1!$v73Q=}hX?v?(NtIowu|0=?5e
zewyBdxhVsT#he{a$feolW-)1+CXoTgV$Kc?X`Y@g+&H}T?ncM3104YOE}Aky9IG%c
z05(KTv1rUmsM1HYw&TF15T{Gy9`7O2?sgJIwP}ho(ssB&y8TPsf1xO9imIL-wNV6$
zl2S_g;*-yAgo0;XGOhpmEnT`ee(9mb$&$#!$M1EWyLS7j!{La?@R$~#na<|NG=&mu
zTSZtqIXpaUf~MAGhysBCZm5$@<gy|UO-*&ISFK2`QkB#ct!qlz<vi+d-@UIij$=z;
z>|UBGmWoNkFo+B=7IV;tniNex>IV$MLwpTQ?d3;XfCB6w1LF^KBG>F_-H+(p2QOJ&
z=qI(xQRtVtI}78ax~0>Nl`T>!E)#AoiVyU?eDUP^NF>}Y)8d&gNiBBy>KiD0o`@X+
zKN@h;xZ3NDl3s619>&;gX6pGB`uZ^*7F{f;GZYN!hRMn@1&xHm8XO%J4nbWvOz@*T
z@kMnecO^Mh8sUTIEo&lI=TSuM#9t*0SwF(qy|hy<SET(2V?{sG1U0YSx^s70@S}ZC
zDa0?sxMsh#XlAy=v#FcgezgCMW)ZWtM!8ku_JZIKbn6sM(vp`Wc2l<p($AA9cGT74
j|L|<*AGFVpzXA*Z|5Kl~6^V5Y00000NkvXXu0mjflR<IX
--- a/browser/themes/pinstripe/jar.mn
+++ b/browser/themes/pinstripe/jar.mn
@@ -138,16 +138,17 @@ browser.jar:
   skin/classic/browser/devtools/orion-container.css         (devtools/orion-container.css)
   skin/classic/browser/devtools/orion-task.png              (devtools/orion-task.png)
   skin/classic/browser/devtools/orion-breakpoint.png        (devtools/orion-breakpoint.png)
   skin/classic/browser/devtools/orion-debug-location.png    (devtools/orion-debug-location.png)
   skin/classic/browser/devtools/toolbarbutton-close.png     (devtools/toolbarbutton-close.png)
 * skin/classic/browser/devtools/webconsole.css                  (devtools/webconsole.css)
   skin/classic/browser/devtools/webconsole_networkpanel.css     (devtools/webconsole_networkpanel.css)
   skin/classic/browser/devtools/webconsole.png                  (devtools/webconsole.png)
+  skin/classic/browser/devtools/breadcrumbs-scrollbutton.png                 (devtools/breadcrumbs-scrollbutton.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-end-pressed.png              (devtools/breadcrumbs/ltr-end-pressed.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-end-selected-pressed.png     (devtools/breadcrumbs/ltr-end-selected-pressed.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-end-selected.png             (devtools/breadcrumbs/ltr-end-selected.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-end.png                      (devtools/breadcrumbs/ltr-end.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-middle-pressed.png           (devtools/breadcrumbs/ltr-middle-pressed.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-middle-selected-pressed.png  (devtools/breadcrumbs/ltr-middle-selected-pressed.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-middle-selected.png          (devtools/breadcrumbs/ltr-middle-selected.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-middle.png                   (devtools/breadcrumbs/ltr-middle.png)
@@ -172,16 +173,17 @@ browser.jar:
   skin/classic/browser/devtools/debugger.css                (devtools/debugger.css)
   skin/classic/browser/devtools/magnifying-glass.png        (devtools/magnifying-glass.png)
   skin/classic/browser/devtools/itemToggle.png              (devtools/itemToggle.png)
   skin/classic/browser/devtools/itemArrow-rtl.png           (devtools/itemArrow-rtl.png)
   skin/classic/browser/devtools/itemArrow-ltr.png           (devtools/itemArrow-ltr.png)
   skin/classic/browser/devtools/background-noise-toolbar.png (devtools/background-noise-toolbar.png)
   skin/classic/browser/devtools/inspect-button.png          (devtools/inspect-button.png)
   skin/classic/browser/devtools/dropmarker.png              (devtools/dropmarker.png)
+  skin/classic/browser/devtools/treepanel-button.png        (devtools/treepanel-button.png)
 #ifdef MOZ_SERVICES_SYNC
   skin/classic/browser/sync-throbber.png
   skin/classic/browser/sync-16.png
   skin/classic/browser/sync-32.png
   skin/classic/browser/sync-bg.png
   skin/classic/browser/sync-128.png
   skin/classic/browser/sync-desktopIcon.png
   skin/classic/browser/sync-mobileIcon.png
--- a/browser/themes/winstripe/browser.css
+++ b/browser/themes/winstripe/browser.css
@@ -2829,44 +2829,86 @@ html|*#highlighter-nodeinfobar-pseudo-cl
   -moz-transition-property: background-color;
   -moz-transition-duration: 500ms;
   background-color: rgba(0,0,0,0);
 }
 
 /* Highlighter toolbar - breadcrumbs */
 
 #inspector-breadcrumbs {
-  padding: 0 6px;
+  -moz-margin-end: 3px;
   /* A fake 1px-shadow is included in the border-images of the
      inspector-breadcrumbs-buttons, to match toolbar-buttons style.
      This negative margin compensate the extra row of pixels created
      by the shadow.*/
   margin: -1px 0;
 }
 
 #inspector-breadcrumbs > .scrollbutton-up,
 #inspector-breadcrumbs > .scrollbutton-down {
   -moz-appearance: none;
+  border: 1px solid hsla(210,8%,5%,.45);
+  background: -moz-linear-gradient(hsla(212,7%,57%,.35), hsla(212,7%,57%,.1)) padding-box;
+  box-shadow: 0 1px 0 hsla(210,16%,76%,.15) inset, 0 0 0 1px hsla(210,16%,76%,.15) inset, 0 1px 0 hsla(210,16%,76%,.15);
+  margin: 0 0 1px;
+}
+
+#inspector-breadcrumbs > .scrollbutton-up {
+  -moz-border-start-width: 0;
+}
+
+#inspector-breadcrumbs > .scrollbutton-up:not([disabled]):active:hover,
+#inspector-breadcrumbs > .scrollbutton-down:not([disabled]):active:hover {
+  border-color: hsla(210,8%,5%,.6);
+  background: -moz-linear-gradient(hsla(220,6%,10%,.3), hsla(212,7%,57%,.15) 65%, hsla(212,7%,57%,.3));
+  box-shadow: 0 0 3px hsla(210,8%,5%,.25) inset, 0 1px 3px hsla(210,8%,5%,.25) inset, 0 1px 0 hsla(210,16%,76%,.15);
+}
+
+#inspector-breadcrumbs > .scrollbutton-up > .toolbarbutton-icon,
+#inspector-breadcrumbs > .scrollbutton-down > .toolbarbutton-icon {
+  -moz-appearance: none;
+  list-style-image: url("chrome://browser/skin/devtools/breadcrumbs-scrollbutton.png");
+  -moz-image-region: rect(0px 7px 16px 0px);
+  margin: 0 5px;
+}
+
+#inspector-breadcrumbs > .scrollbutton-up[disabled] > .toolbarbutton-icon,
+#inspector-breadcrumbs > .scrollbutton-down[disabled] > .toolbarbutton-icon {
+  opacity: 0.5;
+}
+
+#inspector-breadcrumbs > .scrollbutton-up:not([disabled]):active:hover > .toolbarbutton-icon,
+#inspector-breadcrumbs > .scrollbutton-down:not([disabled]):active:hover > .toolbarbutton-icon {
+  -moz-image-region: rect(0px 14px 16px 7px);
+}
+
+#inspector-breadcrumbs > .scrollbutton-up > .toolbarbutton-icon:-moz-locale-dir(rtl),
+#inspector-breadcrumbs > .scrollbutton-down > .toolbarbutton-icon:-moz-locale-dir(ltr) {
+  -moz-transform: scaleX(-1);
 }
 
 .inspector-breadcrumbs-button {
   -moz-appearance: none;
   background-color: transparent;
   border-style: solid;
   border-width: 2px 13px;
   outline: none;
   color: hsl(210,30%,85%);
-  max-width: 85px;
+  width: 85px; /* Can't use max-width. See bug 723132 */
   /* The content of the button can be larger than the button */
   overflow: hidden;
   min-height: 25px;
   margin: 0 -11px 0 0;
   padding: 0 9px;
 }
 
+.inspector-breadcrumbs-button:-moz-focusring > label {
+  border-bottom: 1px dotted hsla(210,30%,85%,0.4);
+}
+
 .inspector-breadcrumbs-button[checked] > .inspector-breadcrumbs-tag {
   color: hsl(200,100%,60%);
 }
 
 .inspector-breadcrumbs-button[checked] > .inspector-breadcrumbs-id {
   color: hsl(200,100%,70%);
 }
 
@@ -2881,17 +2923,17 @@ html|*#highlighter-nodeinfobar-pseudo-cl
 
 .inspector-breadcrumbs-pseudo-classes {
   color: hsl(20, 100%, 85%);
 }
 
 /* Highlighter toolbar - breadcrumbs - LTR */
 
 .inspector-breadcrumbs-button:-moz-locale-dir(ltr):first-of-type {
-  margin-left: 0;
+  border-left-width: 0;
 }
 
 .inspector-breadcrumbs-button {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/ltr-middle.png") 2 13 2 13 fill stretch;
 }
 
 .inspector-breadcrumbs-button[siblings-menu-open]:not([checked]),
 .inspector-breadcrumbs-button:not([checked]):hover:active {
@@ -2924,16 +2966,20 @@ html|*#highlighter-nodeinfobar-pseudo-cl
 .inspector-breadcrumbs-button:first-of-type[checked]:hover:active {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/ltr-start-selected-pressed.png") 2 13 2 13 fill stretch;
 }
 
 .inspector-breadcrumbs-button:last-of-type {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/ltr-end.png") 2 13 2 13 fill stretch;
 }
 
+#inspector-breadcrumbs[overflows] > .inspector-breadcrumbs-button:-moz-locale-dir(ltr):last-of-type {
+  border-right-width: 0;
+}
+
 .inspector-breadcrumbs-button[siblings-menu-open]:last-of-type:not([checked]),
 .inspector-breadcrumbs-button:last-of-type:not([checked]):hover:active {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/ltr-end-pressed.png") 2 13 2 13 fill stretch;
 }
 
 .inspector-breadcrumbs-button:last-of-type[checked] {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/ltr-end-selected.png") 2 13 2 13 fill stretch;
 }
@@ -2941,17 +2987,17 @@ html|*#highlighter-nodeinfobar-pseudo-cl
 .inspector-breadcrumbs-button[siblings-menu-open]:last-of-type[checked],
 .inspector-breadcrumbs-button:last-of-type[checked]:hover:active {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/ltr-end-selected-pressed.png") 2 13 2 13 fill stretch;
 }
 
 /* Highlighter toolbar - breadcrumbs - RTL */
 
 .inspector-breadcrumbs-button:-moz-locale-dir(rtl):first-of-type {
-  margin-right: 0;
+  border-right-width: 0;
 }
 
 .inspector-breadcrumbs-button:-moz-locale-dir(rtl) {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/rtl-middle.png") 2 13 2 13 fill stretch;
 }
 
 .inspector-breadcrumbs-button[siblings-menu-open]:not([checked]):-moz-locale-dir(rtl),
 .inspector-breadcrumbs-button:not([checked]):hover:active:-moz-locale-dir(rtl) {
@@ -2984,32 +3030,56 @@ html|*#highlighter-nodeinfobar-pseudo-cl
 .inspector-breadcrumbs-button:first-of-type[checked]:hover:active:-moz-locale-dir(rtl) {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/rtl-start-selected-pressed.png") 2 13 2 13 fill stretch;
 }
 
 .inspector-breadcrumbs-button:last-of-type:-moz-locale-dir(rtl) {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/rtl-end.png") 2 13 2 13 fill stretch;
 }
 
+#inspector-breadcrumbs[overflows] > .inspector-breadcrumbs-button:last-of-type:-moz-locale-dir(rtl) {
+  border-left-width: 0;
+}
+
 .inspector-breadcrumbs-button[siblings-menu-open]:last-of-type:not([checked]):-moz-locale-dir(rtl),
 .inspector-breadcrumbs-button:last-of-type:not([checked]):hover:active:-moz-locale-dir(rtl) {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/rtl-end-pressed.png") 2 13 2 13 fill stretch;
 }
 
 .inspector-breadcrumbs-button:last-of-type[checked]:-moz-locale-dir(rtl) {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/rtl-end-selected.png") 2 13 2 13 fill stretch;
 }
 
 .inspector-breadcrumbs-button[siblings-menu-open]:last-of-type[checked]:-moz-locale-dir(rtl),
 .inspector-breadcrumbs-button:last-of-type[checked]:hover:active:-moz-locale-dir(rtl) {
   -moz-border-image: url("chrome://browser/skin/devtools/breadcrumbs/rtl-end-selected-pressed.png") 2 13 2 13 fill stretch;
 }
 
 /* Highlighter toolbar - HTML Tree */
 
+#inspector-treepanel-toolbutton {
+  list-style-image: url("chrome://browser/skin/devtools/treepanel-button.png");
+  -moz-margin-end: 0;
+  -moz-image-region: rect(0px 18px 16px 0px);
+}
+
+#inspector-treepanel-toolbutton[checked] {
+  -moz-image-region: rect(0px 36px 16px 18px);
+}
+
+#inspector-breadcrumbs > .scrollbutton-down:-moz-locale-dir(rtl),
+#inspector-treepanel-toolbutton:-moz-locale-dir(ltr) {
+  border-radius: 3px 0 0 3px;
+}
+
+#inspector-breadcrumbs > .scrollbutton-down:-moz-locale-dir(ltr),
+#inspector-treepanel-toolbutton:-moz-locale-dir(rtl) {
+  border-radius: 0 3px 3px 0;
+}
+
 #inspector-tree-splitter {
   -moz-appearance: none;
   border-top: 1px solid black;
   border-bottom-width: 0;
   min-height: 3px;
   height: 3px;
   margin-bottom: -3px;
   position: relative;
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..722438060a89a64789e73bfd79b7a50546c61b4b
GIT binary patch
literal 545
zc$@(t0^a?JP)<h;3K|Lk000e1NJLTq000gE000mO1^@s6CX`M(0000PbVXQnQ*UN;
zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz!%0LzRCwB)lTAwkQ51&H9VZPW#Uf3k
z0yBsTLaA0l1V$}dN2?YQ^cVUQZQIx?ceSWZP_zpAqS^$~hmE1un5m`tQ8+WsbnfVo
zO|2X_+~J&g=DqKIXNaOG(9A`e23xEyea~F4vKZvcx|tEngjHewfkydQWDQs6AE=n^
zneBoC;qPqBKhTO)m5X@au7{@GHZJv91;`}YSRJ&4P@Xo>d8HAW>TTnaC9&4Y#AUVt
zTGXK4{=o}eUNp$>Y~}c(pfcR{dVst><|*hzXaS-3Iuu2@b5wjsFC7zZ2lrg4iO2Ug
z=g9rpE#WQ;6f|j&ddvU?g}?`zm*hm)^we(oaq4jA6l-T*Ufq=#d~*g>ZlLC=l8i@u
z2oF;W<od9b+7AUPf&jFJyuK5~J4;X7NDLMiix0X`%AbhdsS&iW*zFq#$qK|2&$t~t
zV<YCt`2#w0npt?r30(|BKtk0d`Z`8sdLW`GRo`eF)eT-kKWX5u!sVl_8Br7=k^fTT
zP;=D5Sqf`$G{VB^jtbK_etmMdwc566_ym$tkWXtf!*VyPZrf_N03qo=-ztT1p(RnZ
j#u<gRS<`>Vw*UhG3c2M}ko?5#00000NkvXXu0mjfohkL*
--- a/browser/themes/winstripe/devtools/common.css
+++ b/browser/themes/winstripe/devtools/common.css
@@ -52,16 +52,21 @@
   text-shadow: 0 -1px 0 hsla(210,8%,5%,.45);
   border: 1px solid hsla(211,68%,6%,.5);
   border-radius: 3px;
   background: -moz-linear-gradient(hsla(209,13%,54%,.35), hsla(209,13%,54%,.1) 85%, hsla(209,13%,54%,.2)) padding-box;
   box-shadow: 0 1px 0 hsla(209,29%,72%,.15) inset, 0 0 0 1px hsla(209,29%,72%,.1) inset, 0 0 0 1px hsla(209,29%,72%,.1), 0 1px 0 hsla(210,16%,76%,.1);
   -moz-margin-end: 3px;
 }
 
+.devtools-toolbarbutton:-moz-focusring {
+  outline: 1px dotted hsla(210,30%,85%,0.4);
+  outline-offset: -4px;
+}
+
 .devtools-toolbarbutton > .toolbarbutton-icon {
   margin: 0;
 }
 
 .devtools-toolbarbutton:not([label]) {
   min-width: 32px;
 }
 
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2a8802216bf178bc05813fc592df30d91cd49415
GIT binary patch
literal 805
zc$@(x1KRwFP)<h;3K|Lk000e1NJLTq001Na000mO1^@s6e(T*M0000PbVXQnQ*UN;
zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!$4Nv%RCwCVR$FTmK@^_Z-89vNv{qwm
zODndik(x^}`U{GbLJ)lr5%fv)L45Of_%7H-U%enj2$F&~P-`(Ips`Au*oIzAYMV46
zo88&*oRE}pd&&6Lz{d=kbG|)u_M2~(a?Z&?`cX$mPdkjq03LwSBHjWN0eL`GTreKx
z95A>5$VXf7@J4<aQ^f@h;7i0O@1F^rJ|eS&^!K?xqhqlLFwOq6u=3M08XAZ@R3$3I
zV=!wbk(wVPC;SC_L7hWGLr#-fB^e@&#T<J<^!G1!$v73Q=}hX?v?(NtIowu|0=?5e
zewyBdxhVsT#he{a$feolW-)1+CXoTgV$Kc?X`Y@g+&H}T?ncM3104YOE}Aky9IG%c
z05(KTv1rUmsM1HYw&TF15T{Gy9`7O2?sgJIwP}ho(ssB&y8TPsf1xO9imIL-wNV6$
zl2S_g;*-yAgo0;XGOhpmEnT`ee(9mb$&$#!$M1EWyLS7j!{La?@R$~#na<|NG=&mu
zTSZtqIXpaUf~MAGhysBCZm5$@<gy|UO-*&ISFK2`QkB#ct!qlz<vi+d-@UIij$=z;
z>|UBGmWoNkFo+B=7IV;tniNex>IV$MLwpTQ?d3;XfCB6w1LF^KBG>F_-H+(p2QOJ&
z=qI(xQRtVtI}78ax~0>Nl`T>!E)#AoiVyU?eDUP^NF>}Y)8d&gNiBBy>KiD0o`@X+
zKN@h;xZ3NDl3s619>&;gX6pGB`uZ^*7F{f;GZYN!hRMn@1&xHm8XO%J4nbWvOz@*T
z@kMnecO^Mh8sUTIEo&lI=TSuM#9t*0SwF(qy|hy<SET(2V?{sG1U0YSx^s70@S}ZC
zDa0?sxMsh#XlAy=v#FcgezgCMW)ZWtM!8ku_JZIKbn6sM(vp`Wc2l<p($AA9cGT74
j|L|<*AGFVpzXA*Z|5Kl~6^V5Y00000NkvXXu0mjflR<IX
--- a/browser/themes/winstripe/jar.mn
+++ b/browser/themes/winstripe/jar.mn
@@ -125,16 +125,17 @@ browser.jar:
         skin/classic/browser/devtools/orion-container.css           (devtools/orion-container.css)
         skin/classic/browser/devtools/orion-task.png                (devtools/orion-task.png)
         skin/classic/browser/devtools/orion-breakpoint.png          (devtools/orion-breakpoint.png)
         skin/classic/browser/devtools/orion-debug-location.png      (devtools/orion-debug-location.png)
         skin/classic/browser/devtools/toolbarbutton-close.png       (devtools/toolbarbutton-close.png)
         skin/classic/browser/devtools/webconsole.css                  (devtools/webconsole.css)
         skin/classic/browser/devtools/webconsole_networkpanel.css     (devtools/webconsole_networkpanel.css)
         skin/classic/browser/devtools/webconsole.png                  (devtools/webconsole.png)
+        skin/classic/browser/devtools/breadcrumbs-scrollbutton.png                 (devtools/breadcrumbs-scrollbutton.png)
         skin/classic/browser/devtools/breadcrumbs/ltr-end-pressed.png              (devtools/breadcrumbs/ltr-end-pressed.png)
         skin/classic/browser/devtools/breadcrumbs/ltr-end-selected-pressed.png     (devtools/breadcrumbs/ltr-end-selected-pressed.png)
         skin/classic/browser/devtools/breadcrumbs/ltr-end-selected.png             (devtools/breadcrumbs/ltr-end-selected.png)
         skin/classic/browser/devtools/breadcrumbs/ltr-end.png                      (devtools/breadcrumbs/ltr-end.png)
         skin/classic/browser/devtools/breadcrumbs/ltr-middle-pressed.png           (devtools/breadcrumbs/ltr-middle-pressed.png)
         skin/classic/browser/devtools/breadcrumbs/ltr-middle-selected-pressed.png  (devtools/breadcrumbs/ltr-middle-selected-pressed.png)
         skin/classic/browser/devtools/breadcrumbs/ltr-middle-selected.png          (devtools/breadcrumbs/ltr-middle-selected.png)
         skin/classic/browser/devtools/breadcrumbs/ltr-middle.png                   (devtools/breadcrumbs/ltr-middle.png)
@@ -158,16 +159,17 @@ browser.jar:
         skin/classic/browser/devtools/styleeditor.css               (devtools/styleeditor.css)
         skin/classic/browser/devtools/debugger.css                  (devtools/debugger.css)
         skin/classic/browser/devtools/magnifying-glass.png          (devtools/magnifying-glass.png)
         skin/classic/browser/devtools/itemToggle.png                (devtools/itemToggle.png)
         skin/classic/browser/devtools/itemArrow-rtl.png             (devtools/itemArrow-rtl.png)
         skin/classic/browser/devtools/itemArrow-ltr.png             (devtools/itemArrow-ltr.png)
         skin/classic/browser/devtools/inspect-button.png            (devtools/inspect-button.png)
         skin/classic/browser/devtools/dropmarker.png                (devtools/dropmarker.png)
+        skin/classic/browser/devtools/treepanel-button.png          (devtools/treepanel-button.png)
 #ifdef MOZ_SERVICES_SYNC
         skin/classic/browser/sync-throbber.png
         skin/classic/browser/sync-16.png
         skin/classic/browser/sync-32.png
         skin/classic/browser/sync-128.png
         skin/classic/browser/sync-bg.png
         skin/classic/browser/sync-desktopIcon.png
         skin/classic/browser/sync-mobileIcon.png
@@ -303,16 +305,17 @@ browser.jar:
         skin/classic/aero/browser/devtools/orion-container.css       (devtools/orion-container.css)
         skin/classic/aero/browser/devtools/orion-task.png            (devtools/orion-task.png)
         skin/classic/aero/browser/devtools/orion-breakpoint.png      (devtools/orion-breakpoint.png)
         skin/classic/aero/browser/devtools/orion-debug-location.png  (devtools/orion-debug-location.png)
         skin/classic/aero/browser/devtools/toolbarbutton-close.png   (devtools/toolbarbutton-close.png)
         skin/classic/aero/browser/devtools/webconsole.css                  (devtools/webconsole.css)
         skin/classic/aero/browser/devtools/webconsole_networkpanel.css     (devtools/webconsole_networkpanel.css)
         skin/classic/aero/browser/devtools/webconsole.png                  (devtools/webconsole.png)
+        skin/classic/aero/browser/devtools/breadcrumbs-scrollbutton.png                 (devtools/breadcrumbs-scrollbutton.png)
         skin/classic/aero/browser/devtools/breadcrumbs/ltr-end-pressed.png              (devtools/breadcrumbs/ltr-end-pressed.png)
         skin/classic/aero/browser/devtools/breadcrumbs/ltr-end-selected-pressed.png     (devtools/breadcrumbs/ltr-end-selected-pressed.png)
         skin/classic/aero/browser/devtools/breadcrumbs/ltr-end-selected.png             (devtools/breadcrumbs/ltr-end-selected.png)
         skin/classic/aero/browser/devtools/breadcrumbs/ltr-end.png                      (devtools/breadcrumbs/ltr-end.png)
         skin/classic/aero/browser/devtools/breadcrumbs/ltr-middle-pressed.png           (devtools/breadcrumbs/ltr-middle-pressed.png)
         skin/classic/aero/browser/devtools/breadcrumbs/ltr-middle-selected-pressed.png  (devtools/breadcrumbs/ltr-middle-selected-pressed.png)
         skin/classic/aero/browser/devtools/breadcrumbs/ltr-middle-selected.png          (devtools/breadcrumbs/ltr-middle-selected.png)
         skin/classic/aero/browser/devtools/breadcrumbs/ltr-middle.png                   (devtools/breadcrumbs/ltr-middle.png)
@@ -336,16 +339,17 @@ browser.jar:
         skin/classic/aero/browser/devtools/styleeditor.css           (devtools/styleeditor.css)
         skin/classic/aero/browser/devtools/debugger.css              (devtools/debugger.css)
         skin/classic/aero/browser/devtools/magnifying-glass.png      (devtools/magnifying-glass.png)
         skin/classic/aero/browser/devtools/itemToggle.png            (devtools/itemToggle.png)
         skin/classic/aero/browser/devtools/itemArrow-rtl.png         (devtools/itemArrow-rtl.png)
         skin/classic/aero/browser/devtools/itemArrow-ltr.png         (devtools/itemArrow-ltr.png)
         skin/classic/aero/browser/devtools/inspect-button.png        (devtools/inspect-button.png)
         skin/classic/aero/browser/devtools/dropmarker.png            (devtools/dropmarker.png)
+        skin/classic/aero/browser/devtools/treepanel-button.png      (devtools/treepanel-button.png)
 #ifdef MOZ_SERVICES_SYNC
         skin/classic/aero/browser/sync-throbber.png
         skin/classic/aero/browser/sync-16.png
         skin/classic/aero/browser/sync-32.png
         skin/classic/aero/browser/sync-128.png
         skin/classic/aero/browser/sync-bg.png
         skin/classic/aero/browser/sync-desktopIcon.png
         skin/classic/aero/browser/sync-mobileIcon.png