merge the last green changeset on m-c to fx-team
authorTim Taubert <tim.taubert@gmx.de>
Tue, 27 Sep 2011 04:08:27 +0200
changeset 77672 ee20c375e6e63e7467f54c4fa766b47437b8b294
parent 77591 d3e0e80e39205cbe4106d1f347534f964faa1952 (current diff)
parent 77671 5607a7df7a938779aa212d9c9d36cb634b0e63f1 (diff)
child 77673 a9edf67c89547f37836e69ea744e87ab6708c3e2
push id3
push userfelipc@gmail.com
push dateFri, 30 Sep 2011 20:09:13 +0000
milestone9.0a1
merge the last green changeset on m-c to fx-team
browser/devtools/highlighter/insideOutBox.js
browser/devtools/highlighter/inspector.js
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -168,24 +168,29 @@ XPCOMUtils.defineLazyGetter(this, "Popup
     return new tmp.PopupNotifications(gBrowser,
                                       document.getElementById("notification-popup"),
                                       document.getElementById("notification-popup-box"));
   } catch (ex) {
     Cu.reportError(ex);
   }
 });
 
+XPCOMUtils.defineLazyGetter(this, "InspectorUI", function() {
+  let tmp = {};
+  Cu.import("resource:///modules/inspector.jsm", tmp);
+  return new tmp.InspectorUI(window);
+});
+
 let gInitialPages = [
   "about:blank",
   "about:privatebrowsing",
   "about:sessionrestore"
 ];
 
 #include browser-fullZoom.js
-#include ../../devtools/highlighter/inspector.js
 #include browser-places.js
 #include browser-tabPreviews.js
 #include browser-tabview.js
 
 #ifdef MOZ_SERVICES_SYNC
 #include browser-syncui.js
 #endif
 
@@ -1671,17 +1676,18 @@ function delayedStartup(isLoadingBlank, 
 #ifdef MOZ_SERVICES_SYNC
   // initialize the sync UI
   gSyncUI.init();
 #endif
 
   TabView.init();
 
   // Enable Inspector?
-  if (InspectorUI.enabled) {
+  let enabled = gPrefService.getBoolPref("devtools.inspector.enabled");
+  if (enabled) {
     document.getElementById("menu_pageinspect").hidden = false;
     document.getElementById("Tools:Inspect").removeAttribute("disabled");
 #ifdef MENUBAR_CAN_AUTOHIDE
     document.getElementById("appmenu_pageInspect").hidden = false;
 #endif
   }
 
   // Enable Error Console?
@@ -1719,16 +1725,19 @@ function delayedStartup(isLoadingBlank, 
 
 function BrowserShutdown() {
   // In certain scenarios it's possible for unload to be fired before onload,
   // (e.g. if the window is being closed after browser.js loads but before the
   // load completes). In that case, there's nothing to do here.
   if (!gStartupRan)
     return;
 
+  if (!__lookupGetter__("InspectorUI"))
+    InspectorUI.destroy();
+
   // First clean up services initialized in BrowserStartup (or those whose
   // uninit methods don't depend on the services having been initialized).
   allTabs.uninit();
 
   CombinedStopReload.uninit();
 
   gGestureSupport.init(false);
 
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -227,17 +227,16 @@
     <panel id="inspector-tree-panel"
            orient="vertical"
            hidden="true"
            ignorekeys="true"
            noautofocus="true"
            noautohide="true"
            titlebar="normal"
            close="true"
-           onpopuphiding="InspectorUI.closeInspectorUI();"
            label="&inspectPanelTitle.label;">
       <hbox id="tree-panel-resizer-box" align="end">
         <spacer flex="1" />
         <resizer dir="bottomend" />
       </hbox>
     </panel>
 
     <menupopup id="toolbar-context-menu"
rename from browser/devtools/highlighter/insideOutBox.js
rename to browser/devtools/highlighter/InsideOutBox.jsm
--- a/browser/devtools/highlighter/insideOutBox.js
+++ b/browser/devtools/highlighter/InsideOutBox.jsm
@@ -120,16 +120,19 @@ InsideOutBoxView = {
  *
  * Constructor
  * @param aView
  *        The view requiring the InsideOutBox.
  * @param aBox
  *        The box object containing the InsideOutBox. Required to add/remove
  *        children during box manipulation (toggling opened or closed).
  */
+
+var EXPORTED_SYMBOLS = ["InsideOutBox"];
+
 function InsideOutBox(aView, aBox)
 {
   this.view = aView;
   this.box = aBox;
 
   this.rootObject = null;
 
   this.rootObjectBox = null;
@@ -445,17 +448,17 @@ InsideOutBox.prototype =
       return null;
 
     if (aObject == aRootObject) {
       if (!this.rootObjectBox || this.rootObjectBox.repObject != aRootObject) {
         if (this.rootObjectBox) {
           try {
             this.box.removeChild(this.rootObjectBox);
           } catch (exc) {
-            InspectorUI._log("this.box.removeChild(this.rootObjectBox) FAILS " +
+            this.view._log("this.box.removeChild(this.rootObjectBox) FAILS " +
               this.box + " must not contain " + this.rootObjectBox);
           }
         }
 
         this.highlightedObjectBox = null;
         this.selectedObjectBox = null;
         this.rootObjectBox = this.view.createObjectBox(aObject, true);
         this.box.appendChild(this.rootObjectBox);
@@ -638,9 +641,23 @@ InsideOutBox.prototype =
   {
     let node = aNode;
     let tmpNode;
     while ((tmpNode = this.view.getParentObject(node)))
       node = tmpNode;
 
     return node;
   },
+
+  /**
+   * Clean up our mess.
+   */
+  destroy: function IOBox_destroy()
+  {
+    delete this.view;
+    delete this.box;
+    delete this.rootObject;
+    delete this.rootObjectBox;
+    delete this.selectedObjectBox;
+    delete this.highlightedObjectBox;
+    delete this.scrollIntoView;
+  }
 };
--- a/browser/devtools/highlighter/Makefile.in
+++ b/browser/devtools/highlighter/Makefile.in
@@ -40,16 +40,19 @@
 DEPTH		= ../../..
 topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 EXTRA_JS_MODULES = \
+	inspector.jsm \
 	domplate.jsm \
+	InsideOutBox.jsm \
+	TreePanel.jsm \
 	$(NULL)
 
 ifdef ENABLE_TESTS
  	DIRS += test
 endif
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/browser/devtools/highlighter/TreePanel.jsm
@@ -0,0 +1,779 @@
+/* -*- 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 the Mozilla Tree Panel.
+ *
+ * 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):
+ *   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>
+ *
+ * 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 ***** */
+
+const Cu = Components.utils;
+
+Cu.import("resource:///modules/domplate.jsm");
+Cu.import("resource:///modules/InsideOutBox.jsm");
+Cu.import("resource:///modules/Services.jsm");
+
+var EXPORTED_SYMBOLS = ["TreePanel"];
+
+const INSPECTOR_URI = "chrome://browser/content/inspector.html";
+
+/**
+ * TreePanel
+ * A container for the Inspector's HTML Tree Panel widget constructor function.
+ * @param aContext nsIDOMWindow (xulwindow)
+ * @param aIUI global InspectorUI object
+ */
+function TreePanel(aContext, aIUI) {
+  this._init(aContext, aIUI);
+};
+
+TreePanel.prototype = {
+  showTextNodesWithWhitespace: false,
+  id: "treepanel", // DO NOT LOCALIZE
+  openInDock: true,
+
+  /**
+   * The tree panel container element.
+   * @returns xul:panel|xul:vbox|null
+   *          xul:panel is returned when the tree panel is not docked, or
+   *          xul:vbox when when the tree panel is docked.
+   *          null is returned when no container is available.
+   */
+  get container()
+  {
+    if (this.openInDock) {
+      return this.document.getElementById("inspector-tree-box");
+    }
+
+    return this.document.getElementById("inspector-tree-panel");
+  },
+
+  /**
+   * Main TreePanel boot-strapping method. Initialize the TreePanel with the
+   * originating context and the InspectorUI global.
+   * @param aContext nsIDOMWindow (xulwindow)
+   * @param aIUI global InspectorUI object
+   */
+  _init: function TP__init(aContext, aIUI)
+  {
+    this.IUI = aIUI;
+    this.window = aContext;
+    this.document = this.window.document;
+
+    domplateUtils.setDOM(this.window);
+
+    let isOpen = this.isOpen.bind(this);
+
+    this.registrationObject = {
+      id: this.id,
+      label: this.IUI.strings.GetStringFromName("htmlPanel.label"),
+      tooltiptext: this.IUI.strings.GetStringFromName("htmlPanel.tooltiptext"),
+      accesskey: this.IUI.strings.GetStringFromName("htmlPanel.accesskey"),
+      context: this,
+      get isOpen() isOpen(),
+      show: this.open,
+      hide: this.close,
+      onSelect: this.select,
+      panel: this.openInDock ? null : this.container,
+      unregister: this.destroy,
+    };
+    this.editingEvents = {};
+
+    if (!this.openInDock) {
+      this._boundClose = this.close.bind(this);
+      this.container.addEventListener("popuphiding", this._boundClose, false);
+    }
+
+    // Register the HTML panel with the highlighter
+    this.IUI.registerTool(this.registrationObject);
+  },
+
+  /**
+   * Initialization function for the TreePanel.
+   */
+  initializeIFrame: function TP_initializeIFrame()
+  {
+    if (!this.initializingTreePanel || this.treeLoaded) {
+      return;
+    }
+    this.treeBrowserDocument = this.treeIFrame.contentDocument;
+    this.treePanelDiv = this.treeBrowserDocument.createElement("div");
+    this.treeBrowserDocument.body.appendChild(this.treePanelDiv);
+    this.treePanelDiv.ownerPanel = this;
+    this.ioBox = new InsideOutBox(this, this.treePanelDiv);
+    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);
+    delete this.initializingTreePanel;
+    Services.obs.notifyObservers(null,
+      this.IUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, null);
+    if (this.IUI.selection)
+      this.select(this.IUI.selection, true);
+  },
+
+  /**
+   * Open the inspector's tree panel and initialize it.
+   */
+  open: function TP_open()
+  {
+    if (this.initializingTreePanel && !this.treeLoaded) {
+      return;
+    }
+
+    this.initializingTreePanel = true;
+    if (!this.openInDock)
+      this.container.hidden = false;
+
+    this.treeIFrame = this.document.getElementById("inspector-tree-iframe");
+    if (!this.treeIFrame) {
+      this.treeIFrame = this.document.createElement("iframe");
+      this.treeIFrame.setAttribute("id", "inspector-tree-iframe");
+      this.treeIFrame.flex = 1;
+      this.treeIFrame.setAttribute("type", "content");
+    }
+
+    if (this.openInDock) { // Create vbox
+      this.openDocked();
+      return;
+    }
+
+    let resizerBox = this.document.getElementById("tree-panel-resizer-box");
+    this.treeIFrame = this.container.insertBefore(this.treeIFrame, resizerBox);
+
+    let boundLoadedInitializeTreePanel = function loadedInitializeTreePanel()
+    {
+      this.treeIFrame.removeEventListener("load",
+        boundLoadedInitializeTreePanel, true);
+      this.initializeIFrame();
+    }.bind(this);
+
+    let boundTreePanelShown = function treePanelShown()
+    {
+      this.container.removeEventListener("popupshown",
+        boundTreePanelShown, false);
+
+      this.treeIFrame.addEventListener("load",
+        boundLoadedInitializeTreePanel, true);
+
+      let src = this.treeIFrame.getAttribute("src");
+      if (src != INSPECTOR_URI) {
+        this.treeIFrame.setAttribute("src", INSPECTOR_URI);
+      } else {
+        this.treeIFrame.contentWindow.location.reload();
+      }
+    }.bind(this);
+
+    this.container.addEventListener("popupshown", boundTreePanelShown, false);
+
+    const panelWidthRatio = 7 / 8;
+    const panelHeightRatio = 1 / 5;
+
+    let width = parseInt(this.IUI.win.outerWidth * panelWidthRatio);
+    let height = parseInt(this.IUI.win.outerHeight * panelHeightRatio);
+    let y = Math.min(this.document.defaultView.screen.availHeight - height,
+      this.IUI.win.innerHeight);
+
+    this.container.openPopup(this.browser, "overlap", 0, 0,
+      false, false);
+
+    this.container.moveTo(80, y);
+    this.container.sizeTo(width, height);
+  },
+
+  openDocked: function TP_openDocked()
+  {
+    let treeBox = null;
+    let toolbar = this.IUI.toolbar.nextSibling; // Addons bar, typically
+    let toolbarParent =
+      this.IUI.browser.ownerDocument.getElementById("browser-bottombox");
+    treeBox = this.document.createElement("vbox");
+    treeBox.id = "inspector-tree-box";
+    treeBox.state = "open"; // for the registerTools API.
+    treeBox.minHeight = 10;
+    treeBox.flex = 1;
+    toolbarParent.insertBefore(treeBox, toolbar);
+    this.createResizer();
+    treeBox.appendChild(this.treeIFrame);
+
+    let boundLoadedInitializeTreePanel = function loadedInitializeTreePanel()
+    {
+      this.treeIFrame.removeEventListener("load",
+        boundLoadedInitializeTreePanel, true);
+      this.initializeIFrame();
+    }.bind(this);
+
+    this.treeIFrame.addEventListener("load",
+      boundLoadedInitializeTreePanel, true);
+
+    let src = this.treeIFrame.getAttribute("src");
+    if (src != INSPECTOR_URI) {
+      this.treeIFrame.setAttribute("src", INSPECTOR_URI);
+    } else {
+      this.treeIFrame.contentWindow.location.reload();
+    }
+  },
+
+  /**
+   * Lame resizer on the toolbar.
+   */
+  createResizer: function TP_createResizer()
+  {
+    let resizer = this.document.createElement("resizer");
+    resizer.id = "inspector-horizontal-splitter";
+    resizer.setAttribute("dir", "top");
+    resizer.flex = 1;
+    resizer.setAttribute("element", "inspector-tree-box");
+    resizer.height = 24;
+    this.IUI.toolbar.appendChild(resizer);
+    this.resizer = resizer;
+  },
+
+  /**
+   * Close the TreePanel.
+   */
+  close: function TP_close()
+  {
+    if (this.openInDock) {
+      this.IUI.toolbar.removeChild(this.resizer);
+      let treeBox = this.container;
+      let treeBoxParent = treeBox.parentNode;
+      treeBoxParent.removeChild(treeBox);
+    } else {
+      this.container.hidePopup();
+    }
+
+    if (this.treePanelDiv) {
+      this.treePanelDiv.ownerPanel = null;
+      let parent = this.treePanelDiv.parentNode;
+      parent.removeChild(this.treePanelDiv);
+      delete this.treePanelDiv;
+      delete this.treeBrowserDocument;
+    }
+
+    this.treeLoaded = false;
+  },
+
+  /**
+   * Is the TreePanel open?
+   * @returns boolean
+   */
+  isOpen: function TP_isOpen()
+  {
+    if (this.openInDock)
+      return this.treeLoaded && this.container;
+
+    return this.treeLoaded && this.container.state == "open";
+  },
+
+  /**
+   * Create the ObjectBox for the given object.
+   * @param object nsIDOMNode
+   * @param isRoot boolean - Is this the root object?
+   * @returns InsideOutBox
+   */
+  createObjectBox: function TP_createObjectBox(object, isRoot)
+  {
+    let tag = domplateUtils.getNodeTag(object);
+    if (tag)
+      return tag.replace({object: object}, this.treeBrowserDocument);
+  },
+
+  getParentObject: function TP_getParentObject(node)
+  {
+    let parentNode = node ? node.parentNode : null;
+
+    if (!parentNode) {
+      // Documents have no parentNode; Attr, Document, DocumentFragment, Entity,
+      // and Notation. top level windows have no parentNode
+      if (node && node == this.window.Node.DOCUMENT_NODE) {
+        // document type
+        if (node.defaultView) {
+          let embeddingFrame = node.defaultView.frameElement;
+          if (embeddingFrame)
+            return embeddingFrame.parentNode;
+        }
+      }
+      // a Document object without a parentNode or window
+      return null;  // top level has no parent
+    }
+
+    if (parentNode.nodeType == this.window.Node.DOCUMENT_NODE) {
+      if (parentNode.defaultView) {
+        return parentNode.defaultView.frameElement;
+      }
+      // parent is document element, but no window at defaultView.
+      return null;
+    }
+
+    if (!parentNode.localName)
+      return null;
+
+    return parentNode;
+  },
+
+  getChildObject: function TP_getChildObject(node, index, previousSibling)
+  {
+    if (!node)
+      return null;
+
+    if (node.contentDocument) {
+      // then the node is a frame
+      if (index == 0) {
+        return node.contentDocument.documentElement;  // the node's HTMLElement
+      }
+      return null;
+    }
+
+    if (node instanceof this.window.GetSVGDocument) {
+      let svgDocument = node.getSVGDocument();
+      if (svgDocument) {
+        // then the node is a frame
+        if (index == 0) {
+          return svgDocument.documentElement;  // the node's SVGElement
+        }
+        return null;
+      }
+    }
+
+    let child = null;
+    if (previousSibling)  // then we are walking
+      child = this.getNextSibling(previousSibling);
+    else
+      child = this.getFirstChild(node);
+
+    if (this.showTextNodesWithWhitespace)
+      return child;
+
+    for (; child; child = this.getNextSibling(child)) {
+      if (!domplateUtils.isWhitespaceText(child))
+        return child;
+    }
+
+    return null;  // we have no children worth showing.
+  },
+
+  getFirstChild: function TP_getFirstChild(node)
+  {
+    this.treeWalker = node.ownerDocument.createTreeWalker(node,
+      this.window.NodeFilter.SHOW_ALL, null, false);
+    return this.treeWalker.firstChild();
+  },
+
+  getNextSibling: function TP_getNextSibling(node)
+  {
+    let next = this.treeWalker.nextSibling();
+
+    if (!next)
+      delete this.treeWalker;
+
+    return next;
+  },
+
+  /////////////////////////////////////////////////////////////////////
+  // Event Handling
+
+  /**
+   * Handle click events in the html tree panel.
+   * @param aEvent
+   *        The mouse event.
+   */
+  onTreeClick: function TP_onTreeClick(aEvent)
+  {
+    let node;
+    let target = aEvent.target;
+    let hitTwisty = false;
+    if (this.hasClass(target, "twisty")) {
+      node = this.getRepObject(aEvent.target.nextSibling);
+      hitTwisty = true;
+    } else {
+      node = this.getRepObject(aEvent.target);
+    }
+
+    if (node) {
+      if (hitTwisty) {
+        this.ioBox.toggleObject(node);
+      } else {
+        if (this.IUI.inspecting) {
+          this.IUI.stopInspecting(true);
+        } else {
+          this.IUI.select(node, true, false);
+          this.IUI.highlighter.highlightNode(node);
+        }
+      }
+    }
+  },
+
+  /**
+   * Handle double-click events in the html tree panel.
+   * (double-clicking an attribute value allows it to be edited)
+   * @param aEvent
+   *        The mouse event.
+   */
+  onTreeDblClick: function TP_onTreeDblClick(aEvent)
+  {
+    // if already editing an attribute value, double-clicking elsewhere
+    // in the tree is the same as a click, which dismisses the editor
+    if (this.editingContext)
+      this.closeEditor();
+
+    let target = aEvent.target;
+
+    if (this.hasClass(target, "nodeValue")) {
+      let repObj = this.getRepObject(target);
+      let attrName = target.getAttribute("data-attributeName");
+      let attrVal = target.innerHTML;
+
+      this.editAttributeValue(target, repObj, attrName, attrVal);
+    }
+  },
+
+  /**
+   * Starts the editor for an attribute value.
+   * @param aAttrObj
+   *        The DOM object representing the attribute value in the HTML Tree
+   * @param aRepObj
+   *        The original DOM (target) object being inspected/edited
+   * @param aAttrName
+   *        The name of the attribute being edited
+   * @param aAttrVal
+   *        The current value of the attribute being edited
+   */
+  editAttributeValue:
+  function TP_editAttributeValue(aAttrObj, aRepObj, aAttrName, aAttrVal)
+  {
+    let editor = this.treeBrowserDocument.getElementById("attribute-editor");
+    let editorInput =
+      this.treeBrowserDocument.getElementById("attribute-editor-input");
+    let attrDims = aAttrObj.getBoundingClientRect();
+    // figure out actual viewable viewport dimensions (sans scrollbars)
+    let viewportWidth = this.treeBrowserDocument.documentElement.clientWidth;
+    let viewportHeight = this.treeBrowserDocument.documentElement.clientHeight;
+
+    // saves the editing context for use when the editor is saved/closed
+    this.editingContext = {
+      attrObj: aAttrObj,
+      repObj: aRepObj,
+      attrName: aAttrName
+    };
+
+    // highlight attribute-value node in tree while editing
+    this.addClass(aAttrObj, "editingAttributeValue");
+
+    // show the editor
+    this.addClass(editor, "editing");
+
+    // offset the editor below the attribute-value node being edited
+    let editorVeritcalOffset = 2;
+
+    // keep the editor comfortably within the bounds of the viewport
+    let editorViewportBoundary = 5;
+
+    // outer editor is sized based on the <input> box inside it
+    editorInput.style.width = Math.min(attrDims.width, viewportWidth -
+                                editorViewportBoundary) + "px";
+    editorInput.style.height = Math.min(attrDims.height, viewportHeight -
+                                editorViewportBoundary) + "px";
+    let editorDims = editor.getBoundingClientRect();
+
+    // calculate position for the editor according to the attribute node
+    let editorLeft = attrDims.left + this.treeIFrame.contentWindow.scrollX -
+                    // center the editor against the attribute value
+                    ((editorDims.width - attrDims.width) / 2);
+    let editorTop = attrDims.top + this.treeIFrame.contentWindow.scrollY +
+                    attrDims.height + editorVeritcalOffset;
+
+    // but, make sure the editor stays within the visible viewport
+    editorLeft = Math.max(0, Math.min(
+                                      (this.treeIFrame.contentWindow.scrollX +
+                                          viewportWidth - editorDims.width),
+                                      editorLeft)
+                          );
+    editorTop = Math.max(0, Math.min(
+                                      (this.treeIFrame.contentWindow.scrollY +
+                                          viewportHeight - editorDims.height),
+                                      editorTop)
+                          );
+
+    // position the editor
+    editor.style.left = editorLeft + "px";
+    editor.style.top = editorTop + "px";
+
+    // set and select the text
+    editorInput.value = aAttrVal;
+    editorInput.select();
+
+    // listen for editor specific events
+    this.bindEditorEvent(editor, "click", function(aEvent) {
+      aEvent.stopPropagation();
+    });
+    this.bindEditorEvent(editor, "dblclick", function(aEvent) {
+      aEvent.stopPropagation();
+    });
+    this.bindEditorEvent(editor, "keypress",
+                          this.handleEditorKeypress.bind(this));
+
+    // event notification
+    Services.obs.notifyObservers(null, this.IUI.INSPECTOR_NOTIFICATIONS.EDITOR_OPENED,
+                                  null);
+  },
+
+  /**
+   * Handle binding an event handler for the editor.
+   * (saves the callback for easier unbinding later)
+   * @param aEditor
+   *        The DOM object for the editor
+   * @param aEventName
+   *        The name of the event to listen for
+   * @param aEventCallback
+   *        The callback to bind to the event (and also to save for later
+   *          unbinding)
+   */
+  bindEditorEvent:
+  function TP_bindEditorEvent(aEditor, aEventName, aEventCallback)
+  {
+    this.editingEvents[aEventName] = aEventCallback;
+    aEditor.addEventListener(aEventName, aEventCallback, false);
+  },
+
+  /**
+   * Handle unbinding an event handler from the editor.
+   * (unbinds the previously bound and saved callback)
+   * @param aEditor
+   *        The DOM object for the editor
+   * @param aEventName
+   *        The name of the event being listened for
+   */
+  unbindEditorEvent: function TP_unbindEditorEvent(aEditor, aEventName)
+  {
+    aEditor.removeEventListener(aEventName, this.editingEvents[aEventName],
+                                  false);
+    this.editingEvents[aEventName] = null;
+  },
+
+  /**
+   * Handle keypress events in the editor.
+   * @param aEvent
+   *        The keyboard event.
+   */
+  handleEditorKeypress: function TP_handleEditorKeypress(aEvent)
+  {
+    if (aEvent.which == this.window.KeyEvent.DOM_VK_RETURN) {
+      this.saveEditor();
+    } else if (aEvent.keyCode == this.window.KeyEvent.DOM_VK_ESCAPE) {
+      this.closeEditor();
+    }
+  },
+
+  /**
+   * Close the editor and cleanup.
+   */
+  closeEditor: function TP_closeEditor()
+  {
+    let editor = this.treeBrowserDocument.getElementById("attribute-editor");
+    let editorInput =
+      this.treeBrowserDocument.getElementById("attribute-editor-input");
+
+    // remove highlight from attribute-value node in tree
+    this.removeClass(this.editingContext.attrObj, "editingAttributeValue");
+
+    // hide editor
+    this.removeClass(editor, "editing");
+
+    // stop listening for editor specific events
+    this.unbindEditorEvent(editor, "click");
+    this.unbindEditorEvent(editor, "dblclick");
+    this.unbindEditorEvent(editor, "keypress");
+
+    // clean up after the editor
+    editorInput.value = "";
+    editorInput.blur();
+    this.editingContext = null;
+    this.editingEvents = {};
+
+    // event notification
+    Services.obs.notifyObservers(null, this.IUI.INSPECTOR_NOTIFICATIONS.EDITOR_CLOSED,
+                                  null);
+  },
+
+  /**
+   * Commit the edits made in the editor, then close it.
+   */
+  saveEditor: function TP_saveEditor()
+  {
+    let editorInput =
+      this.treeBrowserDocument.getElementById("attribute-editor-input");
+
+    // set the new attribute value on the original target DOM element
+    this.editingContext.repObj.setAttribute(this.editingContext.attrName,
+                                              editorInput.value);
+
+    // update the HTML tree attribute value
+    this.editingContext.attrObj.innerHTML = editorInput.value;
+
+    this.IUI.isDirty = true;
+
+    // event notification
+    Services.obs.notifyObservers(null, this.IUI.INSPECTOR_NOTIFICATIONS.EDITOR_SAVED,
+                                  null);
+
+    this.closeEditor();
+  },
+
+  /**
+   * 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)
+      this.ioBox.select(aNode, true, true, aScroll);
+  },
+
+  ///////////////////////////////////////////////////////////////////////////
+  //// Utility functions
+
+  /**
+   * Does the given object have a class attribute?
+   * @param aNode
+   *        the DOM node.
+   * @param aClass
+   *        The class string.
+   * @returns boolean
+   */
+  hasClass: function TP_hasClass(aNode, aClass)
+  {
+    if (!(aNode instanceof this.window.Element))
+      return false;
+    return aNode.classList.contains(aClass);
+  },
+
+  /**
+   * Add the class name to the given object.
+   * @param aNode
+   *        the DOM node.
+   * @param aClass
+   *        The class string.
+   */
+  addClass: function TP_addClass(aNode, aClass)
+  {
+    if (aNode instanceof this.window.Element)
+      aNode.classList.add(aClass);
+  },
+
+  /**
+   * Remove the class name from the given object
+   * @param aNode
+   *        the DOM node.
+   * @param aClass
+   *        The class string.
+   */
+  removeClass: function TP_removeClass(aNode, aClass)
+  {
+    if (aNode instanceof this.window.Element)
+      aNode.classList.remove(aClass);
+  },
+
+  /**
+   * Get the "repObject" from the HTML panel's domplate-constructed DOM node.
+   * In this system, a "repObject" is the Object being Represented by the box
+   * object. It is the "real" object that we're building our facade around.
+   *
+   * @param element
+   *        The element in the HTML panel the user clicked.
+   * @returns either a real node or null
+   */
+  getRepObject: function TP_getRepObject(element)
+  {
+    let target = null;
+    for (let child = element; child; child = child.parentNode) {
+      if (this.hasClass(child, "repTarget"))
+        target = child;
+
+      if (child.repObject) {
+        if (!target && this.hasClass(child.repObject, "repIgnore"))
+          break;
+        else
+          return child.repObject;
+      }
+    }
+    return null;
+  },
+
+  /**
+   * Destructor function. Cleanup.
+   */
+  destroy: function TP_destroy()
+  {
+    if (this.isOpen()) {
+      this.close();
+    }
+
+    domplateUtils.setDOM(null);
+
+    delete this.resizer;
+    delete this.treeWalker;
+
+    if (this.treePanelDiv) {
+      this.treePanelDiv.ownerPanel = null;
+      let parent = this.treePanelDiv.parentNode;
+      parent.removeChild(this.treePanelDiv);
+      delete this.treePanelDiv;
+      delete this.treeBrowserDocument;
+    }
+
+    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;
+    }
+
+    if (!this.openInDock) {
+      this.container.removeEventListener("popuphiding", this._boundClose, false);
+      delete this._boundClose;
+    }
+  }
+};
+
rename from browser/devtools/highlighter/inspector.js
rename to browser/devtools/highlighter/inspector.jsm
--- a/browser/devtools/highlighter/inspector.js
+++ b/browser/devtools/highlighter/inspector.jsm
@@ -1,11 +1,10 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
-#ifdef 0
 /* ***** 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/
  *
@@ -21,34 +20,40 @@
  * Portions created by the Initial Developer are Copyright (C) 2010
  * 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 <ksimpson@mozilla.com> 
+ *   Kyle Simpson <ksimpson@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 ***** */
-#endif
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+const Cr = Components.results;
 
-#include insideOutBox.js
+var EXPORTED_SYMBOLS = ["InspectorUI"];
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 const INSPECTOR_INVISIBLE_ELEMENTS = {
   "head": true,
   "base": true,
   "basefont": true,
   "isindex": true,
   "link": true,
   "meta": true,
@@ -67,16 +72,19 @@ const INSPECTOR_NOTIFICATIONS = {
 
   // Fires once the Inspector completes the initialization and opens up on
   // screen.
   OPENED: "inspector-opened",
 
   // Fires once the Inspector is closed.
   CLOSED: "inspector-closed",
 
+  // Fires when the Tree Panel is opened and initialized.
+  TREEPANELREADY: "inspector-treepanel-ready",
+
   // Event notifications for the attribute-value editor
   EDITOR_OPENED: "inspector-editor-opened",
   EDITOR_CLOSED: "inspector-editor-closed",
   EDITOR_SAVED: "inspector-editor-saved",
 };
 
 ///////////////////////////////////////////////////////////////////////////
 //// Highlighter
@@ -85,40 +93,42 @@ const INSPECTOR_NOTIFICATIONS = {
  * A highlighter mechanism.
  *
  * The highlighter is built dynamically once the Inspector is invoked:
  * <stack id="highlighter-container">
  *   <vbox id="highlighter-veil-container">...</vbox>
  *   <box id="highlighter-controls>...</vbox>
  * </stack>
  *
- * @param nsIDOMNode aBrowser
- *        The xul:browser object for the content window being highlighted.
+ * @param object aInspector
+ *        The InspectorUI instance.
  */
-function Highlighter(aBrowser)
+function Highlighter(aInspector)
 {
-  this._init(aBrowser);
+  this.IUI = aInspector;
+  this._init();
 }
 
 Highlighter.prototype = {
+  _init: function Highlighter__init()
+  {
+    this.browser = this.IUI.browser;
+    this.chromeDoc = this.IUI.chromeDoc;
 
-  _init: function Highlighter__init(aBrowser)
-  {
-    this.browser = aBrowser;
     let stack = this.browser.parentNode;
     this.win = this.browser.contentWindow;
     this._highlighting = false;
 
-    this.highlighterContainer = document.createElement("stack");
+    this.highlighterContainer = this.chromeDoc.createElement("stack");
     this.highlighterContainer.id = "highlighter-container";
 
-    this.veilContainer = document.createElement("vbox");
+    this.veilContainer = this.chromeDoc.createElement("vbox");
     this.veilContainer.id = "highlighter-veil-container";
 
-    let controlsBox = document.createElement("box");
+    let controlsBox = this.chromeDoc.createElement("box");
     controlsBox.id = "highlighter-controls";
 
     // The veil will make the whole page darker except
     // for the region of the selected box.
     this.buildVeil(this.veilContainer);
 
     // The controlsBox will host the different interactive
     // elements of the highlighter (buttons, toolbars, ...).
@@ -130,17 +140,16 @@ Highlighter.prototype = {
     stack.appendChild(this.highlighterContainer);
 
     this.browser.addEventListener("resize", this, true);
     this.browser.addEventListener("scroll", this, true);
 
     this.handleResize();
   },
 
-
   /**
    * Build the veil:
    *
    * <vbox id="highlighter-veil-container">
    *   <box id="highlighter-veil-topbox" class="highlighter-veil"/>
    *   <hbox id="highlighter-veil-middlebox">
    *     <box id="highlighter-veil-leftbox" class="highlighter-veil"/>
    *     <box id="highlighter-veil-transparentbox"/>
@@ -148,42 +157,41 @@ Highlighter.prototype = {
    *   </hbox>
    *   <box id="highlighter-veil-bottombox" class="highlighter-veil"/>
    * </vbox>
    *
    * @param nsIDOMNode aParent
    */
   buildVeil: function Highlighter_buildVeil(aParent)
   {
-
     // We will need to resize these boxes to surround a node.
     // See highlightRectangle().
 
-    this.veilTopBox = document.createElement("box");
+    this.veilTopBox = this.chromeDoc.createElement("box");
     this.veilTopBox.id = "highlighter-veil-topbox";
     this.veilTopBox.className = "highlighter-veil";
 
-    this.veilMiddleBox = document.createElement("hbox");
+    this.veilMiddleBox = this.chromeDoc.createElement("hbox");
     this.veilMiddleBox.id = "highlighter-veil-middlebox";
 
-    this.veilLeftBox = document.createElement("box");
+    this.veilLeftBox = this.chromeDoc.createElement("box");
     this.veilLeftBox.id = "highlighter-veil-leftbox";
     this.veilLeftBox.className = "highlighter-veil";
 
-    this.veilTransparentBox = document.createElement("box");
+    this.veilTransparentBox = this.chromeDoc.createElement("box");
     this.veilTransparentBox.id = "highlighter-veil-transparentbox";
 
     // We don't need any references to veilRightBox and veilBottomBox.
     // These boxes are automatically resized (flex=1)
 
-    let veilRightBox = document.createElement("box");
+    let veilRightBox = this.chromeDoc.createElement("box");
     veilRightBox.id = "highlighter-veil-rightbox";
     veilRightBox.className = "highlighter-veil";
 
-    let veilBottomBox = document.createElement("box");
+    let veilBottomBox = this.chromeDoc.createElement("box");
     veilBottomBox.id = "highlighter-veil-bottombox";
     veilBottomBox.className = "highlighter-veil";
 
     this.veilMiddleBox.appendChild(this.veilLeftBox);
     this.veilMiddleBox.appendChild(this.veilTransparentBox);
     this.veilMiddleBox.appendChild(veilRightBox);
 
     aParent.appendChild(this.veilTopBox);
@@ -195,26 +203,26 @@ Highlighter.prototype = {
    * Build the controls:
    *
    * <box id="highlighter-close-button"/>
    *
    * @param nsIDOMNode aParent
    */
   buildControls: function Highlighter_buildControls(aParent)
   {
-    let closeButton = document.createElement("box");
+    let closeButton = this.chromeDoc.createElement("box");
     closeButton.id = "highlighter-close-button";
-    closeButton.appendChild(document.createElement("image"));
+    closeButton.appendChild(this.chromeDoc.createElement("image"));
 
-    closeButton.setAttribute("onclick", "InspectorUI.closeInspectorUI(false);");
+    closeButton.addEventListener("click",
+      this.IUI.closeInspectorUI.bind(this.IUI), false);
 
     aParent.appendChild(closeButton);
   },
 
-
   /**
    * Destroy the nodes.
    */
   destroy: function Highlighter_destroy()
   {
     this.browser.removeEventListener("scroll", this, true);
     this.browser.removeEventListener("resize", this, true);
     this._highlightRect = null;
@@ -224,17 +232,18 @@ Highlighter.prototype = {
     this.veilMiddleBox = null;
     this.veilTransparentBox = null;
     this.veilContainer = null;
     this.node = null;
     this.highlighterContainer.parentNode.removeChild(this.highlighterContainer);
     this.highlighterContainer = null;
     this.win = null
     this.browser = null;
-    this.toolbar = null;
+    this.chromeDoc = null;
+    this.IUI = null;
   },
 
   /**
    * Is the highlighter highlighting? Public method for querying the state
    * of the highlighter.
    */
   get isHighlighting() {
     return this._highlighting;
@@ -303,17 +312,17 @@ Highlighter.prototype = {
       }
 
       // We are in an iframe.
       // We take into account the parent iframe position and its
       // offset (borders and padding).
       let frameRect = frameWin.frameElement.getBoundingClientRect();
 
       let [offsetTop, offsetLeft] =
-        InspectorUI.getIframeContentOffset(frameWin.frameElement);
+        this.IUI.getIframeContentOffset(frameWin.frameElement);
 
       rect.top += frameRect.top + offsetTop;
       rect.left += frameRect.left + offsetLeft;
 
       frameWin = frameWin.parent;
     }
 
     this.highlightRectangle(rect);
@@ -428,29 +437,29 @@ Highlighter.prototype = {
     let b = {
       x: a.x + this._highlightRect.width,
       y: a.y + this._highlightRect.height
     };
 
     // Get midpoint of diagonal line.
     let midpoint = this.midPoint(a, b);
 
-    return InspectorUI.elementFromPoint(this.win.document, midpoint.x,
+    return this.IUI.elementFromPoint(this.win.document, midpoint.x,
       midpoint.y);
   },
 
   /**
    * Is this.node highlightable?
    *
    * @returns boolean
    *          True if the node is highlightable or false otherwise.
    */
   isNodeHighlightable: function Highlighter_isNodeHighlightable()
   {
-    if (!this.node || this.node.nodeType != Node.ELEMENT_NODE) {
+    if (!this.node || this.node.nodeType != this.node.ELEMENT_NODE) {
       return false;
     }
     let nodeName = this.node.nodeName.toLowerCase();
     return !INSPECTOR_INVISIBLE_ELEMENTS[nodeName];
   },
 
   /////////////////////////////////////////////////////////////////////////
   //// Event Handling
@@ -510,35 +519,35 @@ Highlighter.prototype = {
    * @param nsIDOMEvent aEvent
    *        The DOM event.
    */
   handleClick: function Highlighter_handleClick(aEvent)
   {
     // Stop inspection when the user clicks on a node.
     if (aEvent.button == 0) {
       let win = aEvent.target.ownerDocument.defaultView;
-      InspectorUI.stopInspecting();
+      this.IUI.stopInspecting();
       win.focus();
     }
     aEvent.preventDefault();
     aEvent.stopPropagation();
   },
 
   /**
    * Handle mousemoves in panel when InspectorUI.inspecting is true.
    *
    * @param nsiDOMEvent aEvent
    *        The MouseEvent triggering the method.
    */
   handleMouseMove: function Highlighter_handleMouseMove(aEvent)
   {
-    let element = InspectorUI.elementFromPoint(aEvent.target.ownerDocument,
+    let element = this.IUI.elementFromPoint(aEvent.target.ownerDocument,
       aEvent.clientX, aEvent.clientY);
     if (element && element != this.node) {
-      InspectorUI.inspectNode(element);
+      this.IUI.inspectNode(element);
     }
   },
 
   /**
    * Handle window resize events.
    */
   handleResize: function Highlighter_handleResize()
   {
@@ -546,38 +555,50 @@ Highlighter.prototype = {
   },
 };
 
 ///////////////////////////////////////////////////////////////////////////
 //// InspectorUI
 
 /**
  * Main controller class for the Inspector.
+ *
+ * @constructor
+ * @param nsIDOMWindow aWindow
+ *        The chrome window for which the Inspector instance is created.
  */
-var InspectorUI = {
+function InspectorUI(aWindow)
+{
+  this.chromeWin = aWindow;
+  this.chromeDoc = aWindow.document;
+  this.tabbrowser = aWindow.gBrowser;
+  this.tools = {};
+  this.toolEvents = {};
+  this.store = new InspectorStore();
+  this.INSPECTOR_NOTIFICATIONS = INSPECTOR_NOTIFICATIONS;
+}
+
+InspectorUI.prototype = {
   browser: null,
-  tools: {},
-  showTextNodesWithWhitespace: false,
+  tools: null,
+  toolEvents: null,
   inspecting: false,
-  treeLoaded: false,
-  get enabled()
-  {
-    return gPrefService.getBoolPref("devtools.inspector.enabled");
-  },
+  treePanelEnabled: true,
   isDirty: false,
+  store: null,
 
   /**
    * 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.isTreePanelOpen) {
+    if (this.isInspectorOpen) {
       this.closeInspectorUI();
     } else {
       this.openInspectorUI();
     }
   },
 
   /**
    * Toggle the status of the inspector, starting or stopping it. Invoked
@@ -588,279 +609,156 @@ var InspectorUI = {
     if (this.inspecting) {
       this.stopInspecting();
     } else {
       this.startInspecting();
     }
   },
 
   /**
-   * Is the tree panel open?
+   * Is the inspector UI open? Simply check if the toolbar is visible or not.
    *
    * @returns boolean
    */
-  get isTreePanelOpen()
+  get isInspectorOpen()
   {
-    return this.treePanel && this.treePanel.state == "open";
+    return this.toolbar && !this.toolbar.hidden && this.highlighter;
   },
 
   /**
    * Return the default selection element for the inspected document.
    */
   get defaultSelection()
   {
     let doc = this.win.document;
     return doc.documentElement ? doc.documentElement.lastElementChild : null;
   },
 
-  initializeTreePanel: function IUI_initializeTreePanel()
-  {
-    this.treeBrowserDocument = this.treeIFrame.contentDocument;
-    this.treePanelDiv = this.treeBrowserDocument.createElement("div");
-    this.treeBrowserDocument.body.appendChild(this.treePanelDiv);
-    this.treePanelDiv.ownerPanel = this;
-    this.ioBox = new InsideOutBox(this, this.treePanelDiv);
-    this.ioBox.createObjectBox(this.win.document.documentElement);
-    this.treeLoaded = true;
-    this.editingContext = null;
-    this.editingEvents = {};
-
-    // initialize the highlighter
-    this.initializeHighlighter();
-  },
-
-  /**
-   * Open the inspector's tree panel and initialize it.
-   */
-  openTreePanel: function IUI_openTreePanel()
-  {
-    if (!this.treePanel) {
-      this.treePanel = document.getElementById("inspector-tree-panel");
-      this.treePanel.hidden = false;
-    }
-
-    this.treeIFrame = document.getElementById("inspector-tree-iframe");
-    if (!this.treeIFrame) {
-      let resizerBox = document.getElementById("tree-panel-resizer-box");
-      this.treeIFrame = document.createElement("iframe");
-      this.treeIFrame.setAttribute("id", "inspector-tree-iframe");
-      this.treeIFrame.setAttribute("flex", "1");
-      this.treeIFrame.setAttribute("type", "content");
-      this.treeIFrame.setAttribute("onclick", "InspectorUI.onTreeClick(event)");
-      this.treeIFrame.setAttribute("ondblclick", "InspectorUI.onTreeDblClick(event);");
-      this.treeIFrame = this.treePanel.insertBefore(this.treeIFrame, resizerBox);
-    }
-
-    this.treePanel.addEventListener("popupshown", function treePanelShown() {
-      InspectorUI.treePanel.removeEventListener("popupshown",
-        treePanelShown, false);
-
-      InspectorUI.treeIFrame.addEventListener("load",
-        function loadedInitializeTreePanel() {
-          InspectorUI.treeIFrame.removeEventListener("load",
-            loadedInitializeTreePanel, true);
-          InspectorUI.initializeTreePanel();
-        }, true);
-
-      let src = InspectorUI.treeIFrame.getAttribute("src");
-      if (src != "chrome://browser/content/inspector.html") {
-        InspectorUI.treeIFrame.setAttribute("src",
-          "chrome://browser/content/inspector.html");
-      } else {
-        InspectorUI.treeIFrame.contentWindow.location.reload();
-      }
-
-    }, false);
-
-    const panelWidthRatio = 7 / 8;
-    const panelHeightRatio = 1 / 5;
-
-    let width = parseInt(this.win.outerWidth * panelWidthRatio);
-    let height = parseInt(this.win.outerHeight * panelHeightRatio);
-    let y = Math.min(window.screen.availHeight - height, this.win.innerHeight);
-
-    this.treePanel.openPopup(this.browser, "overlap", 0, 0,
-      false, false);
-
-    this.treePanel.moveTo(80, y);
-    this.treePanel.sizeTo(width, height);
-  },
-
-  createObjectBox: function IUI_createObjectBox(object, isRoot)
-  {
-    let tag = this.domplateUtils.getNodeTag(object);
-    if (tag)
-      return tag.replace({object: object}, this.treeBrowserDocument);
-  },
-
-  getParentObject: function IUI_getParentObject(node)
-  {
-    let parentNode = node ? node.parentNode : null;
-
-    if (!parentNode) {
-      // Documents have no parentNode; Attr, Document, DocumentFragment, Entity,
-      // and Notation. top level windows have no parentNode
-      if (node && node == Node.DOCUMENT_NODE) {
-        // document type
-        if (node.defaultView) {
-          let embeddingFrame = node.defaultView.frameElement;
-          if (embeddingFrame)
-            return embeddingFrame.parentNode;
-        }
-      }
-      // a Document object without a parentNode or window
-      return null;  // top level has no parent
-    }
-
-    if (parentNode.nodeType == Node.DOCUMENT_NODE) {
-      if (parentNode.defaultView) {
-        return parentNode.defaultView.frameElement;
-      }
-      // parent is document element, but no window at defaultView.
-      return null;
-    }
-    if (!parentNode.localName) {
-      return null;
-    }
-    return parentNode;
-  },
-
-  getChildObject: function IUI_getChildObject(node, index, previousSibling)
-  {
-    if (!node)
-      return null;
-
-    if (node.contentDocument) {
-      // then the node is a frame
-      if (index == 0) {
-        return node.contentDocument.documentElement;  // the node's HTMLElement
-      }
-      return null;
-    }
-
-    if (node instanceof GetSVGDocument) {
-      let svgDocument = node.getSVGDocument();
-      if (svgDocument) {
-        // then the node is a frame
-        if (index == 0) {
-          return svgDocument.documentElement;  // the node's SVGElement
-        }
-        return null;
-      }
-    }
-
-    let child = null;
-    if (previousSibling)  // then we are walking
-      child = this.getNextSibling(previousSibling);
-    else
-      child = this.getFirstChild(node);
-
-    if (this.showTextNodesWithWhitespace)
-      return child;
-
-    for (; child; child = this.getNextSibling(child)) {
-      if (!this.domplateUtils.isWhitespaceText(child))
-        return child;
-    }
-
-    return null;  // we have no children worth showing.
-  },
-
-  getFirstChild: function IUI_getFirstChild(node)
-  {
-    this.treeWalker = node.ownerDocument.createTreeWalker(node,
-      NodeFilter.SHOW_ALL, null, false);
-    return this.treeWalker.firstChild();
-  },
-
-  getNextSibling: function IUI_getNextSibling(node)
-  {
-    let next = this.treeWalker.nextSibling();
-
-    if (!next)
-      delete this.treeWalker;
-
-    return next;
-  },
-
   /**
    * Open inspector UI and HTML tree. Add listeners for document scrolling,
    * resize, tabContainer.TabSelect and others. If a node is provided, then
    * start inspecting it.
    *
    * @param [optional] aNode
    *        The node to inspect.
    */
   openInspectorUI: function IUI_openInspectorUI(aNode)
   {
+    // InspectorUI is already up and running. Lock a node if asked (via context).
+    if (this.isInspectorOpen && aNode) {
+      this.inspectNode(aNode);
+      this.stopInspecting();
+      return;
+    }
+
     // Observer used to inspect the specified element from content after the
     // inspector UI has been opened.
     function inspectObserver(aElement) {
       Services.obs.removeObserver(boundInspectObserver,
                                   INSPECTOR_NOTIFICATIONS.OPENED,
                                   false);
       this.inspectNode(aElement);
       this.stopInspecting();
     };
+
     var boundInspectObserver = inspectObserver.bind(this, aNode);
 
     if (aNode) {
       // Add the observer to inspect the node after initialization finishes.
       Services.obs.addObserver(boundInspectObserver,
                                INSPECTOR_NOTIFICATIONS.OPENED,
                                false);
     }
     // Start initialization.
-    this.browser = gBrowser.selectedBrowser;
+    this.browser = this.tabbrowser.selectedBrowser;
     this.win = this.browser.contentWindow;
     this.winID = this.getWindowID(this.win);
-    this.toolbar = document.getElementById("inspector-toolbar");
+    this.toolbar = this.chromeDoc.getElementById("inspector-toolbar");
+    this.inspectMenuitem = this.chromeDoc.getElementById("Tools:Inspect");
+    this.inspectToolbutton =
+      this.chromeDoc.getElementById("inspector-inspect-toolbutton");
 
-    if (!this.domplate) {
-      Cu.import("resource:///modules/domplate.jsm", this);
-      this.domplateUtils.setDOM(window);
+    this.initTools();
+
+    if (!this.TreePanel && this.treePanelEnabled) {
+      Cu.import("resource:///modules/TreePanel.jsm", this);
+      this.treePanel = new this.TreePanel(this.chromeWin, this);
     }
 
-    this.openTreePanel();
+    this.toolbar.hidden = false;
+    this.inspectMenuitem.setAttribute("checked", true);
+
+    this.isDirty = false;
+
+    this.progressListener = new InspectorProgressListener(this);
+
+    // initialize the highlighter
+    this.initializeHighlighter();
+  },
 
-    this.toolbar.hidden = false;
-    this.inspectCmd.setAttribute("checked", true);
-
-    gBrowser.addProgressListener(InspectorProgressListener);
+  /**
+   * Register and initialize any included tools.
+   */
+  initTools: function IUI_initTools()
+  {
+    // Style inspector
+    // XXX bug 689164, remove /false &&/ from below when bug 689160 fixed.
+    if (false && Services.prefs.getBoolPref("devtools.styleinspector.enabled") &&
+        !this.toolRegistered("styleinspector")) {
+      let stylePanel = StyleInspector.createPanel(true);
+      this.registerTool({
+        id: "styleinspector",
+        label: StyleInspector.l10n("style.highlighter.button.label"),
+        tooltiptext: StyleInspector.l10n("style.highlighter.button.tooltip"),
+        accesskey: StyleInspector.l10n("style.highlighter.accesskey"),
+        context: stylePanel,
+        get isOpen() stylePanel.isOpen(),
+        onSelect: stylePanel.selectNode,
+        show: stylePanel.showTool,
+        hide: stylePanel.hideTool,
+        dim: stylePanel.dimTool,
+        panel: stylePanel,
+        unregister: stylePanel.destroy,
+      });
+      this.stylePanel = stylePanel;
+    }
   },
 
   /**
    * Initialize highlighter.
    */
   initializeHighlighter: function IUI_initializeHighlighter()
   {
-    this.highlighter = new Highlighter(this.browser);
+    this.highlighter = new Highlighter(this);
     this.highlighterReady();
   },
 
   /**
    * Initialize the InspectorStore.
    */
   initializeStore: function IUI_initializeStore()
   {
     // First time opened, add the TabSelect listener
-    if (InspectorStore.isEmpty())
-      gBrowser.tabContainer.addEventListener("TabSelect", this, false);
+    if (this.store.isEmpty()) {
+      this.tabbrowser.tabContainer.addEventListener("TabSelect", this, false);
+    }
 
     // Has this windowID been inspected before?
-    if (InspectorStore.hasID(this.winID)) {
-      let selectedNode = InspectorStore.getValue(this.winID, "selectedNode");
+    if (this.store.hasID(this.winID)) {
+      let selectedNode = this.store.getValue(this.winID, "selectedNode");
       if (selectedNode) {
         this.inspectNode(selectedNode);
       }
+      this.isDirty = this.store.getValue(this.winID, "isDirty");
     } else {
       // First time inspecting, set state to no selection + live inspection.
-      InspectorStore.addStore(this.winID);
-      InspectorStore.setValue(this.winID, "selectedNode", null);
-      InspectorStore.setValue(this.winID, "inspecting", true);
+      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);
       this.win.addEventListener("pagehide", this, true);
     }
   },
 
   /**
    * Close inspector UI and associated panels. Unhighlight and stop inspecting.
    * Remove event listeners for document scrolling, resize,
    * tabContainer.TabSelect and others.
@@ -869,181 +767,157 @@ var InspectorUI = {
    *        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.
    */
   closeInspectorUI: function IUI_closeInspectorUI(aKeepStore)
   {
     // if currently editing an attribute value, closing the
     // highlighter/HTML panel dismisses the editor
-    if (this.editingContext)
-      this.closeEditor();
+    if (this.treePanel && this.treePanel.editingContext)
+      this.treePanel.closeEditor();
 
     if (this.closing || !this.win || !this.browser) {
       return;
     }
 
     this.closing = true;
     this.toolbar.hidden = true;
 
-    gBrowser.removeProgressListener(InspectorProgressListener);
+    this.progressListener.destroy();
+    delete this.progressListener;
 
     if (!aKeepStore) {
-      InspectorStore.deleteStore(this.winID);
+      this.store.deleteStore(this.winID);
       this.win.removeEventListener("pagehide", this, true);
     } else {
       // Update the store before closing.
       if (this.selection) {
-        InspectorStore.setValue(this.winID, "selectedNode",
+        this.store.setValue(this.winID, "selectedNode",
           this.selection);
       }
-      InspectorStore.setValue(this.winID, "inspecting", this.inspecting);
+      this.store.setValue(this.winID, "inspecting", this.inspecting);
+      this.store.setValue(this.winID, "isDirty", this.isDirty);
     }
 
-    if (InspectorStore.isEmpty()) {
-      gBrowser.tabContainer.removeEventListener("TabSelect", this, false);
+    if (this.store.isEmpty()) {
+      this.tabbrowser.tabContainer.removeEventListener("TabSelect", this, false);
     }
 
     this.stopInspecting();
+
+    this.saveToolState(this.winID);
+    this.toolsDo(function IUI_toolsHide(aTool) {
+      this.unregisterTool(aTool);
+    }.bind(this));
+
     if (this.highlighter) {
       this.highlighter.destroy();
       this.highlighter = null;
     }
 
-    if (this.treePanelDiv) {
-      this.treePanelDiv.ownerPanel = null;
-      let parent = this.treePanelDiv.parentNode;
-      parent.removeChild(this.treePanelDiv);
-      delete this.treePanelDiv;
-      delete this.treeBrowserDocument;
-    }
-
-    if (this.treeIFrame) {
-      let parent = this.treeIFrame.parentNode;
-      parent.removeChild(this.treeIFrame);
-      delete this.treeIFrame;
-    }
-    delete this.ioBox;
-
-    if (this.domplate) {
-      this.domplateUtils.setDOM(null);
-      delete this.domplate;
-      delete this.HTMLTemplates;
-      delete this.domplateUtils;
-    }
-
-    this.saveToolState(this.winID);
-    this.toolsDo(function IUI_toolsHide(aTool) {
-      if (aTool.panel) {
-        aTool.panel.hidePopup();
-      }
-    });
-
-    this.inspectCmd.setAttribute("checked", false);
+    this.inspectMenuitem.setAttribute("checked", false);
     this.browser = this.win = null; // null out references to browser and window
     this.winID = null;
     this.selection = null;
-    this.treeLoaded = false;
-
-    this.treePanel.addEventListener("popuphidden", function treePanelHidden() {
-      this.removeEventListener("popuphidden", treePanelHidden, false);
+    this.closing = false;
+    this.isDirty = false;
 
-      InspectorUI.closing = false;
-      Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.CLOSED, null);
-    }, false);
-
-    this.treePanel.hidePopup();
     delete this.treePanel;
+    delete this.stylePanel;
+    delete this.toolbar;
+    delete this.TreePanel;
+    Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.CLOSED, null);
   },
 
   /**
    * Begin inspecting webpage, attach page event listeners, activate
    * highlighter event listeners.
    */
   startInspecting: function IUI_startInspecting()
   {
     // if currently editing an attribute value, starting
     // "live inspection" mode closes the editor
-    if (this.editingContext)
-      this.closeEditor();
+    if (this.treePanel && this.treePanel.editingContext)
+      this.treePanel.closeEditor();
 
-    document.getElementById("inspector-inspect-toolbutton").checked = true;
+    this.inspectToolbutton.checked = true;
     this.attachPageListeners();
     this.inspecting = true;
+    this.toolsDim(true);
     this.highlighter.veilContainer.removeAttribute("locked");
   },
 
   /**
    * Stop inspecting webpage, detach page listeners, disable highlighter
    * event listeners.
    * @param aPreventScroll
    *        Prevent scroll in the HTML tree?
    */
   stopInspecting: function IUI_stopInspecting(aPreventScroll)
   {
     if (!this.inspecting) {
       return;
     }
 
-    document.getElementById("inspector-inspect-toolbutton").checked = false;
+    this.inspectToolbutton.checked = false;
     this.detachPageListeners();
     this.inspecting = false;
+    this.toolsDim(false);
     if (this.highlighter.node) {
       this.select(this.highlighter.node, true, true, !aPreventScroll);
     } else {
       this.select(null, true, true);
     }
     this.highlighter.veilContainer.setAttribute("locked", true);
   },
 
   /**
    * Select an object in the tree view.
    * @param aNode
    *        node to inspect
    * @param forceUpdate
    *        force an update?
-   * @param aScroll
-   *        force scroll?
+   * @param aScroll boolean
+   *        scroll the tree panel?
    */
   select: function IUI_select(aNode, forceUpdate, aScroll)
   {
     // if currently editing an attribute value, using the
     // highlighter dismisses the editor
-    if (this.editingContext)
-      this.closeEditor();
+    if (this.treePanel && this.treePanel.editingContext)
+      this.treePanel.closeEditor();
 
     if (!aNode)
       aNode = this.defaultSelection;
 
     if (forceUpdate || aNode != this.selection) {
       this.selection = aNode;
       if (!this.inspecting) {
         this.highlighter.highlightNode(this.selection);
       }
-      this.ioBox.select(this.selection, true, true, aScroll);
     }
-    this.toolsDo(function IUI_toolsOnSelect(aTool) {
-      if (aTool.panel.state == "open") {
-        aTool.onSelect.apply(aTool.context, [aNode]);
-      }
-    });
+
+    this.toolsSelect(aScroll);
   },
 
   /////////////////////////////////////////////////////////////////////////
   //// Event Handling
 
   highlighterReady: function IUI_highlighterReady()
   {
     // Setup the InspectorStore or restore state
     this.initializeStore();
 
-    if (InspectorStore.getValue(this.winID, "inspecting")) {
+    if (this.store.getValue(this.winID, "inspecting")) {
       this.startInspecting();
     }
 
+    this.restoreToolState(this.winID);
+
     this.win.focus();
     Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.OPENED, null);
   },
 
   /**
    * Main callback handler for events.
    *
    * @param event
@@ -1052,331 +926,75 @@ var InspectorUI = {
   handleEvent: function IUI_handleEvent(event)
   {
     let winID = null;
     let win = null;
     let inspectorClosed = false;
 
     switch (event.type) {
       case "TabSelect":
-        winID = this.getWindowID(gBrowser.selectedBrowser.contentWindow);
-        if (this.isTreePanelOpen && winID != this.winID) {
+        winID = this.getWindowID(this.tabbrowser.selectedBrowser.contentWindow);
+        if (this.isInspectorOpen && winID != this.winID) {
           this.closeInspectorUI(true);
           inspectorClosed = true;
         }
 
-        if (winID && InspectorStore.hasID(winID)) {
+        if (winID && this.store.hasID(winID)) {
           if (inspectorClosed && this.closing) {
             Services.obs.addObserver(function reopenInspectorForTab() {
               Services.obs.removeObserver(reopenInspectorForTab,
                 INSPECTOR_NOTIFICATIONS.CLOSED, false);
 
-              InspectorUI.openInspectorUI();
-            }, INSPECTOR_NOTIFICATIONS.CLOSED, false);
+              this.openInspectorUI();
+            }.bind(this), INSPECTOR_NOTIFICATIONS.CLOSED, false);
           } else {
             this.openInspectorUI();
-            this.restoreToolState(winID);
           }
         }
 
-        if (InspectorStore.isEmpty()) {
-          gBrowser.tabContainer.removeEventListener("TabSelect", this, false);
+        if (this.store.isEmpty()) {
+          this.tabbrowser.tabContainer.removeEventListener("TabSelect", this,
+                                                         false);
         }
         break;
       case "pagehide":
         win = event.originalTarget.defaultView;
         // Skip iframes/frames.
         if (!win || win.frameElement || win.top != win) {
           break;
         }
 
         win.removeEventListener(event.type, this, true);
 
         winID = this.getWindowID(win);
         if (winID && winID != this.winID) {
-          InspectorStore.deleteStore(winID);
+          this.store.deleteStore(winID);
         }
 
-        if (InspectorStore.isEmpty()) {
-          gBrowser.tabContainer.removeEventListener("TabSelect", this, false);
+        if (this.store.isEmpty()) {
+          this.tabbrowser.tabContainer.removeEventListener("TabSelect", this,
+                                                         false);
         }
         break;
       case "keypress":
         switch (event.keyCode) {
-          case KeyEvent.DOM_VK_RETURN:
-          case KeyEvent.DOM_VK_ESCAPE:
+          case this.chromeWin.KeyEvent.DOM_VK_RETURN:
+          case this.chromeWin.KeyEvent.DOM_VK_ESCAPE:
             if (this.inspecting) {
               this.stopInspecting();
               event.preventDefault();
               event.stopPropagation();
             }
             break;
         }
         break;
     }
   },
 
   /**
-   * Handle click events in the html tree panel.
-   * @param aEvent
-   *        The mouse event.
-   */
-  onTreeClick: function IUI_onTreeClick(aEvent)
-  {
-    // if currently editing an attribute value, clicking outside
-    // the editor dismisses the editor
-    if (this.editingContext) {
-      this.closeEditor();
-
-      // clicking outside the editor ONLY closes the editor
-      // so, cancel the rest of the processing of this event
-      aEvent.preventDefault();
-      return;
-    }
-
-    let node;
-    let target = aEvent.target;
-    let hitTwisty = false;
-    if (this.hasClass(target, "twisty")) {
-      node = this.getRepObject(aEvent.target.nextSibling);
-      hitTwisty = true;
-    } else {
-      node = this.getRepObject(aEvent.target);
-    }
-
-    if (node) {
-      if (hitTwisty) {
-        this.ioBox.toggleObject(node);
-      } else {
-        if (this.inspecting) {
-          this.stopInspecting(true);
-        } else {
-          this.select(node, true, false);
-          this.highlighter.highlightNode(node);
-        }
-      }
-    }
-  },
-
-  /**
-   * Handle double-click events in the html tree panel.
-   * (double-clicking an attribute value allows it to be edited)
-   * @param aEvent
-   *        The mouse event.
-   */
-  onTreeDblClick: function IUI_onTreeDblClick(aEvent)
-  {
-    // if already editing an attribute value, double-clicking elsewhere
-    // in the tree is the same as a click, which dismisses the editor
-    if (this.editingContext)
-      this.closeEditor();
-
-    let target = aEvent.target;
-    if (this.hasClass(target, "nodeValue")) {
-      let repObj = this.getRepObject(target);
-      let attrName = target.getAttribute("data-attributeName");
-      let attrVal = target.innerHTML;
-
-      this.editAttributeValue(target, repObj, attrName, attrVal);
-    }
-  },
-
-  /**
-   * Starts the editor for an attribute value.
-   * @param aAttrObj
-   *        The DOM object representing the attribute value in the HTML Tree
-   * @param aRepObj
-   *        The original DOM (target) object being inspected/edited
-   * @param aAttrName
-   *        The name of the attribute being edited
-   * @param aAttrVal
-   *        The current value of the attribute being edited
-   */
-  editAttributeValue: 
-  function IUI_editAttributeValue(aAttrObj, aRepObj, aAttrName, aAttrVal)
-  {
-    let editor = this.treeBrowserDocument.getElementById("attribute-editor");
-    let editorInput = 
-      this.treeBrowserDocument.getElementById("attribute-editor-input");
-    let attrDims = aAttrObj.getBoundingClientRect();
-    // figure out actual viewable viewport dimensions (sans scrollbars)
-    let viewportWidth = this.treeBrowserDocument.documentElement.clientWidth;
-    let viewportHeight = this.treeBrowserDocument.documentElement.clientHeight;
-
-    // saves the editing context for use when the editor is saved/closed
-    this.editingContext = {
-      attrObj: aAttrObj,
-      repObj: aRepObj,
-      attrName: aAttrName
-    };
-
-    // highlight attribute-value node in tree while editing
-    this.addClass(aAttrObj, "editingAttributeValue");
-
-    // show the editor
-    this.addClass(editor, "editing");
-
-    // offset the editor below the attribute-value node being edited
-    let editorVeritcalOffset = 2;
-
-    // keep the editor comfortably within the bounds of the viewport
-    let editorViewportBoundary = 5;
-
-    // outer editor is sized based on the <input> box inside it
-    editorInput.style.width = Math.min(attrDims.width, viewportWidth - 
-                                editorViewportBoundary) + "px";
-    editorInput.style.height = Math.min(attrDims.height, viewportHeight - 
-                                editorViewportBoundary) + "px";
-    let editorDims = editor.getBoundingClientRect();
-
-    // calculate position for the editor according to the attribute node
-    let editorLeft = attrDims.left + this.treeIFrame.contentWindow.scrollX -
-                    // center the editor against the attribute value    
-                    ((editorDims.width - attrDims.width) / 2); 
-    let editorTop = attrDims.top + this.treeIFrame.contentWindow.scrollY + 
-                    attrDims.height + editorVeritcalOffset;
-
-    // but, make sure the editor stays within the visible viewport
-    editorLeft = Math.max(0, Math.min(
-                                      (this.treeIFrame.contentWindow.scrollX + 
-                                          viewportWidth - editorDims.width),
-                                      editorLeft)
-                          );
-    editorTop = Math.max(0, Math.min(
-                                      (this.treeIFrame.contentWindow.scrollY + 
-                                          viewportHeight - editorDims.height),
-                                      editorTop)
-                          );
-
-    // position the editor
-    editor.style.left = editorLeft + "px";
-    editor.style.top = editorTop + "px";
-
-    // set and select the text
-    editorInput.value = aAttrVal;
-    editorInput.select();
-
-    // listen for editor specific events
-    this.bindEditorEvent(editor, "click", function(aEvent) {
-      aEvent.stopPropagation();
-    });
-    this.bindEditorEvent(editor, "dblclick", function(aEvent) {
-      aEvent.stopPropagation();
-    });
-    this.bindEditorEvent(editor, "keypress", 
-                          this.handleEditorKeypress.bind(this));
-
-    // event notification    
-    Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.EDITOR_OPENED, 
-                                  null);
-  },
-
-  /**
-   * Handle binding an event handler for the editor.
-   * (saves the callback for easier unbinding later)   
-   * @param aEditor
-   *        The DOM object for the editor
-   * @param aEventName
-   *        The name of the event to listen for
-   * @param aEventCallback
-   *        The callback to bind to the event (and also to save for later 
-   *          unbinding)
-   */
-  bindEditorEvent: 
-  function IUI_bindEditorEvent(aEditor, aEventName, aEventCallback)
-  {
-    this.editingEvents[aEventName] = aEventCallback;
-    aEditor.addEventListener(aEventName, aEventCallback, false);
-  },
-
-  /**
-   * Handle unbinding an event handler from the editor.
-   * (unbinds the previously bound and saved callback)   
-   * @param aEditor
-   *        The DOM object for the editor
-   * @param aEventName
-   *        The name of the event being listened for
-   */
-  unbindEditorEvent: function IUI_unbindEditorEvent(aEditor, aEventName)
-  {
-    aEditor.removeEventListener(aEventName, this.editingEvents[aEventName], 
-                                  false);
-    this.editingEvents[aEventName] = null;
-  },
-
-  /**
-   * Handle keypress events in the editor.
-   * @param aEvent
-   *        The keyboard event.
-   */
-  handleEditorKeypress: function IUI_handleEditorKeypress(aEvent)
-  {
-    if (aEvent.which == KeyEvent.DOM_VK_RETURN) {
-      this.saveEditor();
-    } else if (aEvent.keyCode == KeyEvent.DOM_VK_ESCAPE) {
-      this.closeEditor();
-    }
-  },
-
-  /**
-   * Close the editor and cleanup.
-   */
-  closeEditor: function IUI_closeEditor()
-  {
-    let editor = this.treeBrowserDocument.getElementById("attribute-editor");
-    let editorInput = 
-      this.treeBrowserDocument.getElementById("attribute-editor-input");
-
-    // remove highlight from attribute-value node in tree
-    this.removeClass(this.editingContext.attrObj, "editingAttributeValue");
-
-    // hide editor
-    this.removeClass(editor, "editing");
-
-    // stop listening for editor specific events
-    this.unbindEditorEvent(editor, "click");
-    this.unbindEditorEvent(editor, "dblclick");
-    this.unbindEditorEvent(editor, "keypress");
-
-    // clean up after the editor
-    editorInput.value = "";
-    editorInput.blur();
-    this.editingContext = null;
-    this.editingEvents = {};
-
-    // event notification    
-    Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.EDITOR_CLOSED, 
-                                  null);
-  },
-
-  /**
-   * Commit the edits made in the editor, then close it.
-   */
-  saveEditor: function IUI_saveEditor()
-  {
-    let editorInput = 
-      this.treeBrowserDocument.getElementById("attribute-editor-input");
-
-    // set the new attribute value on the original target DOM element
-    this.editingContext.repObj.setAttribute(this.editingContext.attrName, 
-                                              editorInput.value);
-
-    // update the HTML tree attribute value
-    this.editingContext.attrObj.innerHTML = editorInput.value;
-
-    this.isDirty = true;
-
-    // event notification    
-    Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.EDITOR_SAVED, 
-                                  null);
-    
-    this.closeEditor();
-  },
-
-  /**
    * Attach event listeners to content window and child windows to enable
    * highlighting and click to stop inspection.
    */
   attachPageListeners: function IUI_attachPageListeners()
   {
     this.browser.addEventListener("keypress", this, true);
     this.highlighter.attachInspectListeners();
   },
@@ -1415,32 +1033,32 @@ var InspectorUI = {
    * @param integer aX
    * @param integer aY
    * @returns Node|null the element node found at the given coordinates.
    */
   elementFromPoint: function IUI_elementFromPoint(aDocument, aX, aY)
   {
     let node = aDocument.elementFromPoint(aX, aY);
     if (node && node.contentDocument) {
-      if (node instanceof HTMLIFrameElement) {
+      if (node instanceof Ci.nsIDOMHTMLIFrameElement) {
         let rect = node.getBoundingClientRect();
 
         // Gap between the iframe and its content window.
         let [offsetTop, offsetLeft] = this.getIframeContentOffset(node);
 
         aX -= rect.left + offsetLeft;
         aY -= rect.top + offsetTop;
 
         if (aX < 0 || aY < 0) {
           // Didn't reach the content document, still over the iframe.
           return node;
         }
       }
-      if (node instanceof HTMLIFrameElement ||
-          node instanceof HTMLFrameElement) {
+      if (node instanceof Ci.nsIDOMHTMLIFrameElement ||
+          node instanceof Ci.nsIDOMHTMLFrameElement) {
         let subnode = this.elementFromPoint(node.contentDocument, aX, aY);
         if (subnode) {
           node = subnode;
         }
       }
     }
     return node;
   },
@@ -1471,57 +1089,16 @@ var InspectorUI = {
 
     let borderTop = parseInt(style.getPropertyValue("border-top-width"));
     let borderLeft = parseInt(style.getPropertyValue("border-left-width"));
 
     return [borderTop + paddingTop, borderLeft + paddingLeft];
   },
 
   /**
-   * Does the given object have a class attribute?
-   * @param aNode
-   *        the DOM node.
-   * @param aClass
-   *        The class string.
-   * @returns boolean
-   */
-  hasClass: function IUI_hasClass(aNode, aClass)
-  {
-    if (!(aNode instanceof Element))
-      return false;
-    return aNode.classList.contains(aClass);
-  },
-
-  /**
-   * Add the class name to the given object.
-   * @param aNode
-   *        the DOM node.
-   * @param aClass
-   *        The class string.
-   */
-  addClass: function IUI_addClass(aNode, aClass)
-  {
-    if (aNode instanceof Element)
-      aNode.classList.add(aClass);
-  },
-
-  /**
-   * Remove the class name from the given object
-   * @param aNode
-   *        the DOM node.
-   * @param aClass
-   *        The class string.
-   */
-  removeClass: function IUI_removeClass(aNode, aClass)
-  {
-    if (aNode instanceof Element)
-      aNode.classList.remove(aClass);
-  },
-
-  /**
    * Retrieve the unique ID of a window object.
    *
    * @param nsIDOMWindow aWindow
    * @returns integer ID
    */
   getWindowID: function IUI_getWindowID(aWindow)
   {
     if (!aWindow) {
@@ -1534,42 +1111,16 @@ var InspectorUI = {
       util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
         getInterface(Ci.nsIDOMWindowUtils);
     } catch (ex) { }
 
     return util.currentInnerWindowID;
   },
 
   /**
-   * Get the "repObject" from the HTML panel's domplate-constructed DOM node.
-   * In this system, a "repObject" is the Object being Represented by the box
-   * object. It is the "real" object that we're building our facade around.
-   *
-   * @param element
-   *        The element in the HTML panel the user clicked.
-   * @returns either a real node or null
-   */
-  getRepObject: function IUI_getRepObject(element)
-  {
-    let target = null;
-    for (let child = element; child; child = child.parentNode) {
-      if (this.hasClass(child, "repTarget"))
-        target = child;
-
-      if (child.repObject) {
-        if (!target && this.hasClass(child.repObject, "repIgnore"))
-          break;
-        else
-          return child.repObject;
-      }
-    }
-    return null;
-  },
-
-  /**
    * @param msg
    *        text message to send to the log
    */
   _log: function LOG(msg)
   {
     Services.console.logStringMessage(msg);
   },
 
@@ -1588,127 +1139,263 @@ var InspectorUI = {
         this._log("filename: " + frame.filename + " lineNumber: " + frame.lineNumber +
           " functionName: " + frame.name);
       }
     }
     this._log("END TRACE");
   },
 
   /**
+   * Get the toolbar button name for a given id string. Used by the
+   * registerTools API to retrieve a consistent name for toolbar buttons
+   * based on the ID of the tool.
+   * @param anId String
+   *        id of the tool to be buttonized
+   * @returns String
+   */
+  getToolbarButtonId: function IUI_createButtonId(anId)
+  {
+    return "inspector-" + anId + "-toolbutton";
+  },
+
+  /**
    * Register an external tool with the inspector.
    *
    * aRegObj = {
    *   id: "toolname",
    *   context: myTool,
    *   label: "Button label",
    *   icon: "chrome://somepath.png",
    *   tooltiptext: "Button tooltip",
    *   accesskey: "S",
+   *   isOpen: object.property, (getter) returning true if tool is open.
    *   onSelect: object.method,
-   *   onShow: object.method,
-   *   onHide: 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
    * }
    *
-   * @param aRegObj
+   * @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.tools[aRegObj.id]) {
+  registerTool: function IUI_registerTool(aRegObj)
+  {
+    if (this.toolRegistered(aRegObj.id)) {
       return;
-    } else {
-      let id = aRegObj.id;
-      let buttonId = "inspector-" + id + "-toolbutton";
-      aRegObj.buttonId = buttonId;
-
-      aRegObj.panel.addEventListener("popuphiding",
-        function IUI_toolPanelHiding() {
-          btn.setAttribute("checked", "false");
-        }, false);
-      aRegObj.panel.addEventListener("popupshowing",
-        function IUI_toolPanelShowing() {
-          btn.setAttribute("checked", "true");
-        }, false);
-
-      this.tools[id] = aRegObj;
     }
 
-    let toolbox = document.getElementById("inspector-tools");
-    let btn = document.createElement("toolbarbutton");
-    btn.setAttribute("id", aRegObj.buttonId);
+    this.tools[aRegObj.id] = aRegObj;
+
+    let buttonContainer = this.chromeDoc.getElementById("inspector-tools");
+    let btn = this.chromeDoc.createElement("toolbarbutton");
+    let buttonId = this.getToolbarButtonId(aRegObj.id);
+    btn.setAttribute("id", buttonId);
     btn.setAttribute("label", aRegObj.label);
     btn.setAttribute("tooltiptext", aRegObj.tooltiptext);
     btn.setAttribute("accesskey", aRegObj.accesskey);
-    btn.setAttribute("class", "toolbarbutton-text");
     btn.setAttribute("image", aRegObj.icon || "");
-    toolbox.appendChild(btn);
+    buttonContainer.appendChild(btn);
+
+    /**
+     * 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
+     */
+    let toolEvents = this.toolEvents;
+    function bindToolEvent(aWidget, aEvent, aCallback) {
+      toolEvents[aWidget.id + "_" + aEvent] = aCallback;
+      aWidget.addEventListener(aEvent, aCallback, false);
+    }
 
-    btn.addEventListener("click",
-      function IUI_ToolButtonClick(aEvent) {
-        if (btn.getAttribute("checked") == "true") {
-          aRegObj.onHide.apply(aRegObj.context);
+    bindToolEvent(btn, "click",
+      function IUI_toolButtonClick(aEvent) {
+        if (btn.checked) {
+          this.toolHide(aRegObj);
         } else {
-          aRegObj.onShow.apply(aRegObj.context, [InspectorUI.selection]);
-          aRegObj.onSelect.apply(aRegObj.context, [InspectorUI.selection]);
+          this.toolShow(aRegObj);
         }
-      }, false);
+      }.bind(this));
+
+    if (aRegObj.panel) {
+      bindToolEvent(aRegObj.panel, "popuphiding",
+        function IUI_toolPanelHiding() {
+          btn.checked = false;
+        });
+    }
+  },
+
+  /**
+   * Show the specified tool.
+   * @param aTool Object (see comment for IUI_registerTool)
+   */
+  toolShow: function IUI_toolShow(aTool)
+  {
+    aTool.show.call(aTool.context, this.selection);
+    this.chromeDoc.getElementById(this.getToolbarButtonId(aTool.id)).checked = true;
   },
 
-/**
- * Save a list of open tools to the inspector store.
- *
- * @param aWinID The ID of the window used to save the associated tools
- */
+  /**
+   * Hide the specified tool.
+   * @param aTool Object (see comment for IUI_registerTool)
+   */
+  toolHide: function IUI_toolHide(aTool)
+  {
+    aTool.hide.call(aTool.context);
+    this.chromeDoc.getElementById(this.getToolbarButtonId(aTool.id)).checked = false;
+  },
+
+  /**
+   * 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)
+  {
+    let button = this.chromeDoc.getElementById(this.getToolbarButtonId(aRegObj.id));
+
+    /**
+     * Unregister the events associated with the registered tool's widget.
+     * @param aWidget XUL:widget (toolbarbutton|panel).
+     * @param aEvent a DOM event.
+     */
+    let toolEvents = this.toolEvents;
+    function unbindToolEvent(aWidget, aEvent) {
+      let toolEvent = aWidget.id + "_" + aEvent;
+      aWidget.removeEventListener(aEvent, toolEvents[toolEvent], false);
+      delete toolEvents[toolEvent]
+    };
+
+    let buttonContainer = this.chromeDoc.getElementById("inspector-tools");
+    unbindToolEvent(button, "click");
+
+    if (aRegObj.panel)
+      unbindToolEvent(aRegObj.panel, "popuphiding");
+
+    buttonContainer.removeChild(button);
+
+    if (aRegObj.unregister)
+      aRegObj.unregister.call(aRegObj.context);
+
+    delete this.tools[aRegObj.id];
+  },
+
+  /**
+   * Save a list of open tools to the inspector store.
+   *
+   * @param aWinID The ID of the window used to save the associated tools
+   */
   saveToolState: function IUI_saveToolState(aWinID)
   {
     let openTools = {};
     this.toolsDo(function IUI_toolsSetId(aTool) {
-      if (aTool.panel.state == "open") {
+      if (aTool.isOpen) {
         openTools[aTool.id] = true;
       }
     });
-    InspectorStore.setValue(aWinID, "openTools", openTools);
+    this.store.setValue(aWinID, "openTools", openTools);
   },
 
-/**
- * Restore tools previously save using saveToolState().
- *
- * @param aWinID The ID of the window to which the associated tools are to be
- *               restored.
- */
+  /**
+   * Restore tools previously save using saveToolState().
+   *
+   * @param aWinID The ID of the window to which the associated tools are to be
+   *               restored.
+   */
   restoreToolState: function IUI_restoreToolState(aWinID)
   {
-    let openTools = InspectorStore.getValue(aWinID, "openTools");
-    InspectorUI.selection = InspectorUI.selection;
+    let openTools = this.store.getValue(aWinID, "openTools");
     if (openTools) {
       this.toolsDo(function IUI_toolsOnShow(aTool) {
         if (aTool.id in openTools) {
-          aTool.onShow.apply(aTool.context, [InspectorUI.selection]);
+          this.toolShow(aTool);
         }
-      });
+      }.bind(this));
     }
   },
-  
+
+  /**
+   * 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_toolsOnSelect(aTool) {
+      if (aTool.isOpen && "dim" in aTool) {
+        aTool.dim.call(aTool.context, aState);
+      }
+    });
+  },
+
   /**
    * 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);
     }
   },
+
+  /**
+   * 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();
+    }
+
+    delete this.store;
+    delete this.chromeDoc;
+    delete this.chromeWin;
+    delete this.tabbrowser;
+  },
 };
 
 /**
  * The Inspector store is used for storing data specific to each tab window.
+ * @constructor
  */
-var InspectorStore = {
-  store: {},
+function InspectorStore()
+{
+  this.store = {};
+}
+InspectorStore.prototype = {
   length: 0,
 
   /**
    * Check if there is any data recorded for any tab/window.
    *
    * @returns boolean True if there are no stores for any window/tab, or false
    * otherwise.
    */
@@ -1827,58 +1514,68 @@ var InspectorStore = {
 };
 
 /**
  * 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.
+ *
+ * @constructor
+ * @param object aInspector
+ *        InspectorUI instance object.
  */
-var InspectorProgressListener = {
+function InspectorProgressListener(aInspector)
+{
+  this.IUI = aInspector;
+  this.IUI.tabbrowser.addProgressListener(this);
+}
+
+InspectorProgressListener.prototype = {
   onStateChange:
   function IPL_onStateChange(aProgress, aRequest, aFlag, aStatus)
   {
     // Remove myself if the Inspector is no longer open.
-    if (!InspectorUI.isTreePanelOpen) {
-      gBrowser.removeProgressListener(InspectorProgressListener);
+    if (!this.IUI.isInspectorOpen) {
+      this.destroy();
       return;
     }
 
     // Skip non-start states.
     if (!(aFlag & Ci.nsIWebProgressListener.STATE_START)) {
       return;
     }
 
     // If the request is about to happen in a new window, we are not concerned
     // about the request.
-    if (aProgress.DOMWindow != InspectorUI.win) {
+    if (aProgress.DOMWindow != this.IUI.win) {
       return;
     }
 
-    if (InspectorUI.isDirty) {
+    if (this.IUI.isDirty) {
       this.showNotification(aRequest);
     } else {
-      InspectorUI.closeInspectorUI();
+      this.IUI.closeInspectorUI();
     }
   },
 
   /**
    * Show an asynchronous notification which asks the user to confirm or cancel
    * the page navigation request.
    *
    * @param nsIRequest aRequest
    *        The request initiated by the user or by the page itself.
    * @returns void
    */
   showNotification: function IPL_showNotification(aRequest)
   {
     aRequest.suspend();
 
-    let notificationBox = gBrowser.getNotificationBox(InspectorUI.browser);
+    let notificationBox = this.IUI.tabbrowser.getNotificationBox(this.IUI.browser);
     let notification = notificationBox.
       getNotificationWithValue("inspector-page-navigation");
 
     if (notification) {
       notificationBox.removeNotification(notification, true);
     }
 
     let cancelRequest = function onCancelRequest() {
@@ -1893,55 +1590,76 @@ var InspectorProgressListener = {
       if (aEvent == "removed") {
         cancelRequest();
       }
     };
 
     let buttons = [
       {
         id: "inspector.confirmNavigationAway.buttonLeave",
-        label: InspectorUI.strings.
+        label: this.IUI.strings.
           GetStringFromName("confirmNavigationAway.buttonLeave"),
-        accessKey: InspectorUI.strings.
+        accessKey: this.IUI.strings.
           GetStringFromName("confirmNavigationAway.buttonLeaveAccesskey"),
         callback: function onButtonLeave() {
           if (aRequest) {
             aRequest.resume();
             aRequest = null;
-            InspectorUI.closeInspectorUI();
+            this.IUI.closeInspectorUI();
           }
-        },
+        }.bind(this),
       },
       {
         id: "inspector.confirmNavigationAway.buttonStay",
-        label: InspectorUI.strings.
+        label: this.IUI.strings.
           GetStringFromName("confirmNavigationAway.buttonStay"),
-        accessKey: InspectorUI.strings.
+        accessKey: this.IUI.strings.
           GetStringFromName("confirmNavigationAway.buttonStayAccesskey"),
         callback: cancelRequest
       },
     ];
 
-    let message = InspectorUI.strings.
+    let message = this.IUI.strings.
       GetStringFromName("confirmNavigationAway.message");
 
     notification = notificationBox.appendNotification(message,
       "inspector-page-navigation", "chrome://browser/skin/Info.png",
       notificationBox.PRIORITY_WARNING_HIGH, buttons, eventCallback);
 
     // Make sure this not a transient notification, to avoid the automatic
     // transient notification removal.
     notification.persistence = -1;
   },
+
+  /**
+   * Destroy the progress listener instance.
+   */
+  destroy: function IPL_destroy()
+  {
+    this.IUI.tabbrowser.removeProgressListener(this);
+
+    let notificationBox = this.IUI.tabbrowser.getNotificationBox(this.IUI.browser);
+    let notification = notificationBox.
+      getNotificationWithValue("inspector-page-navigation");
+
+    if (notification) {
+      notificationBox.removeNotification(notification, true);
+    }
+
+    delete this.IUI;
+  },
 };
 
 /////////////////////////////////////////////////////////////////////////
 //// Initializers
 
-XPCOMUtils.defineLazyGetter(InspectorUI, "inspectCmd", function () {
-  return document.getElementById("Tools:Inspect");
+XPCOMUtils.defineLazyGetter(InspectorUI.prototype, "strings",
+  function () {
+    return Services.strings.
+           createBundle("chrome://browser/locale/inspector.properties");
+  });
+
+XPCOMUtils.defineLazyGetter(this, "StyleInspector", function () {
+  var obj = {};
+  Cu.import("resource:///modules/devtools/StyleInspector.jsm", obj);
+  return obj.StyleInspector;
 });
 
-XPCOMUtils.defineLazyGetter(InspectorUI, "strings", function () {
-  return Services.strings.
-         createBundle("chrome://browser/locale/inspector.properties");
-});
-
--- a/browser/devtools/highlighter/test/Makefile.in
+++ b/browser/devtools/highlighter/test/Makefile.in
@@ -56,10 +56,13 @@ include $(topsrcdir)/config/rules.mk
 		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_bug_566084_location_changed.js \
 		$(NULL)
 
+# Disabled due to constant failures
+# 		browser_inspector_treePanel_click.js \
+
 libs::	$(_BROWSER_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
--- a/browser/devtools/highlighter/test/browser_inspector_bug_566084_location_changed.js
+++ b/browser/devtools/highlighter/test/browser_inspector_bug_566084_location_changed.js
@@ -1,28 +1,28 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 let notificationBox = null;
 
 function startLocationTests() {
   ok(window.InspectorUI, "InspectorUI variable exists");
-  Services.obs.addObserver(runInspectorTests, INSPECTOR_NOTIFICATIONS.OPENED, null);
+  Services.obs.addObserver(runInspectorTests, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, null);
   InspectorUI.toggleInspectorUI();
 }
 
 function runInspectorTests() {
-  Services.obs.removeObserver(runInspectorTests, INSPECTOR_NOTIFICATIONS.OPENED, null);
+  Services.obs.removeObserver(runInspectorTests, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, null);
 
   let para = content.document.querySelector("p");
   ok(para, "found the paragraph element");
   is(para.textContent, "init", "paragraph content is correct");
 
   ok(InspectorUI.inspecting, "Inspector is highlighting");
-  ok(InspectorUI.isTreePanelOpen, "Inspector Panel is open");
+  ok(InspectorUI.isInspectorOpen, "Inspector is open");
 
   InspectorUI.isDirty = true;
 
   notificationBox = gBrowser.getNotificationBox(gBrowser.selectedBrowser);
   notificationBox.addEventListener("AlertActive", alertActive1, false);
 
   gBrowser.selectedBrowser.addEventListener("load", onPageLoad, true);
 
@@ -51,29 +51,29 @@ function onPageLoad() {
   isnot(content.location.href.indexOf("test2"), -1,
         "page navigated to the correct location");
 
   let para = content.document.querySelector("p");
   ok(para, "found the paragraph element, third time");
   is(para.textContent, "test2", "paragraph content is correct");
 
   ok(!InspectorUI.inspecting, "Inspector is not highlighting");
-  ok(!InspectorUI.isTreePanelOpen, "Inspector Panel is not open");
+  ok(!InspectorUI.isInspectorOpen, "Inspector Panel is not open");
 
   testEnd();
 }
 
 function locationTest2() {
   // Location did not change.
   let para = content.document.querySelector("p");
   ok(para, "found the paragraph element, second time");
   is(para.textContent, "init", "paragraph content is correct");
 
   ok(InspectorUI.inspecting, "Inspector is highlighting");
-  ok(InspectorUI.isTreePanelOpen, "Inspector Panel is open");
+  ok(InspectorUI.isInspectorOpen, "Inspector Panel is open");
 
   notificationBox.addEventListener("AlertActive", alertActive2, false);
 
   content.location = "data:text/html,<div>location change test 2 for " +
     "inspector</div><p>test2</p>";
 }
 
 function alertActive2() {
@@ -97,17 +97,16 @@ function alertActive2() {
   // Accept page navigation.
   executeSoon(function(){
     buttonLeave.doCommand();
   });
 }
 
 function testEnd() {
   notificationBox = null;
-  InspectorUI.isDirty = false;
   gBrowser.removeCurrentTab();
   executeSoon(finish);
 }
 
 function test() {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
--- a/browser/devtools/highlighter/test/browser_inspector_bug_665880.js
+++ b/browser/devtools/highlighter/test/browser_inspector_bug_665880.js
@@ -18,45 +18,45 @@ function test()
 
   content.location = "data:text/html,<object style='padding: 100px'><p>foobar</p></object>";
 
   function setupObjectInspectionTest()
   {
     objectNode = doc.querySelector("object");
     ok(objectNode, "we have the object node");
     Services.obs.addObserver(runObjectInspectionTest,
-      INSPECTOR_NOTIFICATIONS.OPENED, false);
+      InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
     InspectorUI.toggleInspectorUI();
   }
 
   function runObjectInspectionTest()
   {
     Services.obs.removeObserver(runObjectInspectionTest,
-      INSPECTOR_NOTIFICATIONS.OPENED);
+      InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
 
     executeSoon(function() {
       Services.obs.addObserver(performTestComparison,
-        INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+        InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
 
       InspectorUI.inspectNode(objectNode);
     });
   }
 
   function performTestComparison()
   {
     Services.obs.removeObserver(performTestComparison,
-      INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
+      InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
 
     is(InspectorUI.selection, objectNode, "selection matches node");
 
     Services.obs.addObserver(finishUp,
-      INSPECTOR_NOTIFICATIONS.CLOSED, false);
+      InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
     InspectorUI.closeInspectorUI();
   }
 
 
   function finishUp() {
-    Services.obs.removeObserver(finishUp, INSPECTOR_NOTIFICATIONS.CLOSED);
+    Services.obs.removeObserver(finishUp, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
     doc = objectNode = null;
     gBrowser.removeCurrentTab();
     finish();
   }
 }
--- a/browser/devtools/highlighter/test/browser_inspector_bug_674871.js
+++ b/browser/devtools/highlighter/test/browser_inspector_bug_674871.js
@@ -42,67 +42,67 @@ function test()
 
   function setupTest()
   {
     iframeNode = doc.querySelector("iframe");
     iframeBodyNode = iframeNode.contentDocument.querySelector("body");
     ok(iframeNode, "we have the iframe node");
     ok(iframeBodyNode, "we have the body node");
     Services.obs.addObserver(runTests,
-      INSPECTOR_NOTIFICATIONS.OPENED, false);
+      InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
     InspectorUI.toggleInspectorUI();
   }
 
   function runTests()
   {
     Services.obs.removeObserver(runTests,
-      INSPECTOR_NOTIFICATIONS.OPENED);
+      InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
 
     executeSoon(function() {
       Services.obs.addObserver(isTheIframeSelected,
-        INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+        InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
 
       moveMouseOver(iframeNode, 1, 1);
     });
   }
 
   function isTheIframeSelected()
   {
     Services.obs.removeObserver(isTheIframeSelected,
-      INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
+      InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
 
     is(InspectorUI.selection, iframeNode, "selection matches node");
     iframeNode.style.marginBottom = doc.defaultView.innerHeight + "px";
     doc.defaultView.scrollBy(0, 40);
 
     executeSoon(function() {
       Services.obs.addObserver(isTheIframeContentSelected,
-        INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+        InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
 
       moveMouseOver(iframeNode, 40, 40);
     });
   }
 
   function isTheIframeContentSelected()
   {
     Services.obs.removeObserver(isTheIframeContentSelected,
-      INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
+      InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
 
     is(InspectorUI.selection, iframeBodyNode, "selection matches node");
     // 184 == 200 + 11(border) + 13(padding) - 40(scroll)
     is(InspectorUI.highlighter._highlightRect.height, 184,
       "highlighter height");
 
     Services.obs.addObserver(finishUp,
-      INSPECTOR_NOTIFICATIONS.CLOSED, false);
+      InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
     InspectorUI.closeInspectorUI();
   }
 
   function finishUp() {
-    Services.obs.removeObserver(finishUp, INSPECTOR_NOTIFICATIONS.CLOSED);
+    Services.obs.removeObserver(finishUp, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
     doc = iframeNode = iframeBodyNode = null;
     gBrowser.removeCurrentTab();
     finish();
   }
 
 
   function moveMouseOver(aElement, x, y)
   {
--- a/browser/devtools/highlighter/test/browser_inspector_editor.js
+++ b/browser/devtools/highlighter/test/browser_inspector_editor.js
@@ -1,18 +1,18 @@
 /* -*- 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 *****
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
- * 
+ *
  * Contributor(s):
  *   Rob Campbell <rcampbell@mozilla.com>
  *   Mihai Sucan <mihai.sucan@gmail.com>
- *   Kyle Simpson <ksimpson@mozilla.com> 
+ *   Kyle Simpson <ksimpson@mozilla.com>
  *
  * ***** END LICENSE BLOCK ***** */
 
 let doc;
 let div;
 let editorTestSteps;
 
 function doNextStep() {
@@ -20,61 +20,69 @@ function doNextStep() {
 }
 
 function setupEditorTests()
 {
   div = doc.createElement("div");
   div.setAttribute("id", "foobar");
   div.setAttribute("class", "barbaz");
   doc.body.appendChild(div);
-  
-  Services.obs.addObserver(runEditorTests, INSPECTOR_NOTIFICATIONS.OPENED, false);
+
+  Services.obs.addObserver(setupHTMLPanel, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
   InspectorUI.toggleInspectorUI();
 }
 
+function setupHTMLPanel()
+{
+  Services.obs.removeObserver(setupHTMLPanel, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
+  Services.obs.addObserver(runEditorTests, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
+  InspectorUI.treePanel.open();
+}
+
 function runEditorTests()
 {
-  Services.obs.removeObserver(runEditorTests, INSPECTOR_NOTIFICATIONS.OPENED, false);
+  Services.obs.removeObserver(runEditorTests, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY);
   InspectorUI.stopInspecting();
-  
+
   // setup generator for async test steps
   editorTestSteps = doEditorTestSteps();
-  
+
   // add step listeners
-  Services.obs.addObserver(doNextStep, INSPECTOR_NOTIFICATIONS.EDITOR_OPENED, false);
-  Services.obs.addObserver(doNextStep, INSPECTOR_NOTIFICATIONS.EDITOR_CLOSED, false);
-  Services.obs.addObserver(doNextStep, INSPECTOR_NOTIFICATIONS.EDITOR_SAVED, false);
+  Services.obs.addObserver(doNextStep, InspectorUI.INSPECTOR_NOTIFICATIONS.EDITOR_OPENED, false);
+  Services.obs.addObserver(doNextStep, InspectorUI.INSPECTOR_NOTIFICATIONS.EDITOR_CLOSED, false);
+  Services.obs.addObserver(doNextStep, InspectorUI.INSPECTOR_NOTIFICATIONS.EDITOR_SAVED, false);
 
   // start the tests
   doNextStep();
 }
 
 function doEditorTestSteps()
 {
-  let editor = InspectorUI.treeBrowserDocument.getElementById("attribute-editor");
-  let editorInput = InspectorUI.treeBrowserDocument.getElementById("attribute-editor-input");
+  let treePanel = InspectorUI.treePanel;
+  let editor = treePanel.treeBrowserDocument.getElementById("attribute-editor");
+  let editorInput = treePanel.treeBrowserDocument.getElementById("attribute-editor-input");
 
   // Step 1: grab and test the attribute-value nodes in the HTML panel, then open editor
-  let attrValNode_id = InspectorUI.treeBrowserDocument.querySelectorAll(".nodeValue.editable[data-attributeName='id']")[0];
-  let attrValNode_class = InspectorUI.treeBrowserDocument.querySelectorAll(".nodeValue.editable[data-attributeName='class']")[0];
+  let attrValNode_id = treePanel.treeBrowserDocument.querySelectorAll(".nodeValue.editable[data-attributeName='id']")[0];
+  let attrValNode_class = treePanel.treeBrowserDocument.querySelectorAll(".nodeValue.editable[data-attributeName='class']")[0];
 
   is(attrValNode_id.innerHTML, "foobar", "Step 1: we have the correct `id` attribute-value node in the HTML panel");
   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.editingContext, "Step 2: editor session started");
+  ok(InspectorUI.treePanel.editingContext, "Step 2: editor session started");
 
   let editorVisible = editor.classList.contains("editing");
   ok(editorVisible, "editor popup visible");
 
   // check if the editor popup is "near" the correct position
   let editorDims = editor.getBoundingClientRect();
   let attrValNodeDims = attrValNode_id.getBoundingClientRect();
   let editorPositionOK = (editorDims.left >= (attrValNodeDims.left - editorDims.width - 5)) &&
@@ -83,35 +91,35 @@ function doEditorTestSteps()
                           (editorDims.bottom <= (attrValNodeDims.bottom + editorDims.height + 5));
 
   ok(editorPositionOK, "editor position acceptable");
 
   // check to make sure the attribute-value node being edited is properly highlighted
   let attrValNodeHighlighted = attrValNode_id.classList.contains("editingAttributeValue");
   ok(attrValNodeHighlighted, "`id` attribute-value node is editor-highlighted");
 
-  is(InspectorUI.editingContext.repObj, div, "editor session has correct reference to div");
-  is(InspectorUI.editingContext.attrObj, attrValNode_id, "editor session has correct reference to `id` attribute-value node in HTML panel");
-  is(InspectorUI.editingContext.attrName, "id", "editor session knows correct attribute-name");
+  is(treePanel.editingContext.repObj, div, "editor session has correct reference to div");
+  is(treePanel.editingContext.attrObj, attrValNode_id, "editor session has correct reference to `id` attribute-value node in HTML panel");
+  is(treePanel.editingContext.attrName, "id", "editor session knows correct attribute-name");
 
   editorInput.value = "Hello World";
   editorInput.focus();
-  
+
   // hit <enter> to save the inputted value
   executeSoon(function() {
     EventUtils.synthesizeKey("VK_RETURN", {}, attrValNode_id.ownerDocument.defaultView);
   });
 
   // two `yield` statements, to trap both the "SAVED" and "CLOSED" events that will be triggered
   yield;
   yield; // End of Step 2
 
 
   // Step 3: validate that the previous editing session saved correctly, then open editor on `class` attribute value
-  ok(!InspectorUI.editingContext, "Step 3: editor session ended");
+  ok(!treePanel.editingContext, "Step 3: editor session ended");
   editorVisible = editor.classList.contains("editing");
   ok(!editorVisible, "editor popup hidden");
   attrValNodeHighlighted = attrValNode_id.classList.contains("editingAttributeValue");
   ok(!attrValNodeHighlighted, "`id` attribute-value node is no longer editor-highlighted");
   is(div.getAttribute("id"), "Hello World", "`id` attribute-value successfully updated");
   is(attrValNode_id.innerHTML, "Hello World", "attribute-value node in HTML panel successfully updated");
 
   // double-click the `class` attribute-value node to open the editor
@@ -119,99 +127,99 @@ function doEditorTestSteps()
     // firing 2 clicks right in a row to simulate a double-click
     EventUtils.synthesizeMouse(attrValNode_class, 2, 2, {clickCount: 2}, attrValNode_class.ownerDocument.defaultView);
   });
 
   yield; // End of Step 3
 
 
   // Step 4: enter value into editor, then hit <escape> to discard it
-  ok(InspectorUI.editingContext, "Step 4: editor session started");
+  ok(treePanel.editingContext, "Step 4: editor session started");
   editorVisible = editor.classList.contains("editing");
   ok(editorVisible, "editor popup visible");
-  
-  is(InspectorUI.editingContext.attrObj, attrValNode_class, "editor session has correct reference to `class` attribute-value node in HTML panel");
-  is(InspectorUI.editingContext.attrName, "class", "editor session knows correct attribute-name");
+
+  is(treePanel.editingContext.attrObj, attrValNode_class, "editor session has correct reference to `class` attribute-value node in HTML panel");
+  is(treePanel.editingContext.attrName, "class", "editor session knows correct attribute-name");
 
   editorInput.value = "Hello World";
   editorInput.focus();
-  
+
   // hit <escape> to discard the inputted value
   executeSoon(function() {
     EventUtils.synthesizeKey("VK_ESCAPE", {}, attrValNode_class.ownerDocument.defaultView);
   });
 
   yield; // End of Step 4
 
 
   // Step 5: validate that the previous editing session discarded correctly, then open editor on `id` attribute value again
-  ok(!InspectorUI.editingContext, "Step 5: editor session ended");
+  ok(!treePanel.editingContext, "Step 5: editor session ended");
   editorVisible = editor.classList.contains("editing");
   ok(!editorVisible, "editor popup hidden");
   is(div.getAttribute("class"), "barbaz", "`class` attribute-value *not* updated");
   is(attrValNode_class.innerHTML, "barbaz", "attribute-value node in HTML panel *not* updated");
 
   // 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 5
 
 
   // Step 6: validate that editor opened again, then test double-click inside of editor (should do nothing)
-  ok(InspectorUI.editingContext, "Step 6: editor session started");
+  ok(treePanel.editingContext, "Step 6: editor session started");
   editorVisible = editor.classList.contains("editing");
   ok(editorVisible, "editor popup visible");
-  
+
   // double-click on the editor input box
   executeSoon(function() {
     // firing 2 clicks right in a row to simulate a double-click
     EventUtils.synthesizeMouse(editorInput, 2, 2, {clickCount: 2}, editorInput.ownerDocument.defaultView);
-    
+
     // since the previous double-click is supposed to do nothing,
     // wait a brief moment, then move on to the next step
     executeSoon(function() {
       doNextStep();
     });
   });
 
   yield; // End of Step 6
 
 
-  // Step 7: validate that editing session is still correct, then enter a value and try a click 
+  // Step 7: validate that editing session is still correct, then enter a value and try a click
   //         outside of editor (should cancel the editing session)
-  ok(InspectorUI.editingContext, "Step 7: editor session still going");
+  ok(treePanel.editingContext, "Step 7: editor session still going");
   editorVisible = editor.classList.contains("editing");
   ok(editorVisible, "editor popup still visible");
-  
+
   editorInput.value = "all your base are belong to us";
 
   // single-click the `class` attribute-value node
   executeSoon(function() {
     EventUtils.synthesizeMouse(attrValNode_class, 2, 2, {}, attrValNode_class.ownerDocument.defaultView);
   });
 
   yield; // End of Step 7
 
 
   // Step 8: validate that the editor was closed and that the editing was not saved
-  ok(!InspectorUI.editingContext, "Step 8: editor session ended");
+  ok(!treePanel.editingContext, "Step 8: editor session ended");
   editorVisible = editor.classList.contains("editing");
   ok(!editorVisible, "editor popup hidden");
   is(div.getAttribute("id"), "Hello World", "`id` attribute-value *not* updated");
   is(attrValNode_id.innerHTML, "Hello World", "attribute-value node in HTML panel *not* updated");
-  
+
   // End of Step 8
 
   // end of all steps, so clean up
-  Services.obs.removeObserver(doNextStep, INSPECTOR_NOTIFICATIONS.EDITOR_OPENED, false);
-  Services.obs.removeObserver(doNextStep, INSPECTOR_NOTIFICATIONS.EDITOR_CLOSED, false);
-  Services.obs.removeObserver(doNextStep, INSPECTOR_NOTIFICATIONS.EDITOR_SAVED, false);
+  Services.obs.removeObserver(doNextStep, InspectorUI.INSPECTOR_NOTIFICATIONS.EDITOR_OPENED, false);
+  Services.obs.removeObserver(doNextStep, InspectorUI.INSPECTOR_NOTIFICATIONS.EDITOR_CLOSED, false);
+  Services.obs.removeObserver(doNextStep, InspectorUI.INSPECTOR_NOTIFICATIONS.EDITOR_SAVED, false);
 
   executeSoon(finishUp);
 }
 
 function finishUp() {
   doc = div = null;
   InspectorUI.closeInspectorUI();
   gBrowser.removeCurrentTab();
--- a/browser/devtools/highlighter/test/browser_inspector_highlighter.js
+++ b/browser/devtools/highlighter/test/browser_inspector_highlighter.js
@@ -75,36 +75,36 @@ function createDocument()
   setupHighlighterTests();
 }
 
 function setupHighlighterTests()
 {
   h1 = doc.querySelectorAll("h1")[0];
   ok(h1, "we have the header node");
   Services.obs.addObserver(runSelectionTests,
-    INSPECTOR_NOTIFICATIONS.OPENED, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
   InspectorUI.toggleInspectorUI();
 }
 
 function runSelectionTests()
 {
   Services.obs.removeObserver(runSelectionTests,
-    INSPECTOR_NOTIFICATIONS.OPENED, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
 
   executeSoon(function() {
     Services.obs.addObserver(performTestComparisons,
-      INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+      InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
     EventUtils.synthesizeMouse(h1, 2, 2, {type: "mousemove"}, content);
   });
 }
 
 function performTestComparisons(evt)
 {
   Services.obs.removeObserver(performTestComparisons,
-    INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
 
   InspectorUI.stopInspecting();
   ok(InspectorUI.highlighter.isHighlighting, "highlighter is highlighting");
   is(InspectorUI.highlighter.highlitNode, h1, "highlighter matches selection")
   is(InspectorUI.selection, h1, "selection matches node");
   is(InspectorUI.selection, InspectorUI.highlighter.highlitNode, "selection matches highlighter");
 
   doc = h1 = null;
--- a/browser/devtools/highlighter/test/browser_inspector_iframeTest.js
+++ b/browser/devtools/highlighter/test/browser_inspector_iframeTest.js
@@ -38,17 +38,16 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 let doc;
 let div1;
 let div2;
 let iframe1;
 let iframe2;
-let highlighterFrame;
 
 function createDocument()
 {
   doc.title = "Inspector iframe Tests";
 
   iframe1 = doc.createElement('iframe');
 
   iframe1.addEventListener("load", function () {
@@ -82,49 +81,48 @@ function moveMouseOver(aElement)
 {
   EventUtils.synthesizeMouse(aElement, 2, 2, {type: "mousemove"},
     aElement.ownerDocument.defaultView);
 }
 
 function setupIframeTests()
 {
   Services.obs.addObserver(runIframeTests,
-    INSPECTOR_NOTIFICATIONS.OPENED, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
   InspectorUI.openInspectorUI();
 }
 
 function runIframeTests()
 {
   Services.obs.removeObserver(runIframeTests,
-    INSPECTOR_NOTIFICATIONS.OPENED, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
 
   Services.obs.addObserver(performTestComparisons1,
-    INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
 
-  highlighterFrame = InspectorUI.highlighter.iframe;
   executeSoon(moveMouseOver.bind(this, div1));
 }
 
 function performTestComparisons1()
 {
   Services.obs.removeObserver(performTestComparisons1,
-    INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
   Services.obs.addObserver(performTestComparisons2,
-    INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
 
   is(InspectorUI.selection, div1, "selection matches div1 node");
   is(InspectorUI.highlighter.highlitNode, div1, "highlighter matches selection");
 
   executeSoon(moveMouseOver.bind(this, div2));
 }
 
 function performTestComparisons2()
 {
   Services.obs.removeObserver(performTestComparisons2,
-    INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
 
   is(InspectorUI.selection, div2, "selection matches div2 node");
   is(InspectorUI.highlighter.highlitNode, div2, "highlighter matches selection");
 
   finish();
 }
 
 function test() {
--- a/browser/devtools/highlighter/test/browser_inspector_initialization.js
+++ b/browser/devtools/highlighter/test/browser_inspector_initialization.js
@@ -33,16 +33,17 @@
  * 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 salutation;
+let closing;
 
 function createDocument()
 {
   doc.body.innerHTML = '<div id="first" style="{ margin: 10em; ' +
     'font-size: 14pt; font-family: helvetica, sans-serif; color: #AAA}">\n' +
     '<h1>Some header text</h1>\n' +
     '<p id="salutation" style="{font-size: 12pt}">hi.</p>\n' +
     '<p id="body" style="{font-size: 12pt}">I am a test-case. This text exists ' +
@@ -55,81 +56,131 @@ function createDocument()
   doc.title = "Inspector Initialization Test";
   startInspectorTests();
 }
 
 function startInspectorTests()
 {
   ok(InspectorUI, "InspectorUI variable exists");
   Services.obs.addObserver(runInspectorTests,
-    INSPECTOR_NOTIFICATIONS.OPENED, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
   InspectorUI.toggleInspectorUI();
 }
 
 function runInspectorTests()
 {
   Services.obs.removeObserver(runInspectorTests,
-    INSPECTOR_NOTIFICATIONS.OPENED, false);
-  Services.obs.addObserver(runContextMenuTest,
-    INSPECTOR_NOTIFICATIONS.CLOSED, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
+  Services.obs.addObserver(treePanelTests,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
 
+  ok(InspectorUI.toolbar, "we have the toolbar.");
   ok(!InspectorUI.toolbar.hidden, "toolbar is visible");
-  let iframe = document.getElementById("inspector-tree-iframe");
-  is(InspectorUI.treeIFrame, iframe, "Inspector IFrame matches");
   ok(InspectorUI.inspecting, "Inspector is inspecting");
-  ok(InspectorUI.isTreePanelOpen, "Inspector Tree Panel is open");
+  ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
   ok(InspectorUI.highlighter, "Highlighter is up");
 
+  InspectorUI.treePanel.open();
+}
+
+function treePanelTests()
+{
+  Services.obs.removeObserver(treePanelTests,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY);
+  Services.obs.addObserver(runContextMenuTest,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
+
+  ok(InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is open");
+
   executeSoon(function() {
     InspectorUI.closeInspectorUI();
   });
 }
 
 function runContextMenuTest()
 {
-  Services.obs.removeObserver(runContextMenuTest, INSPECTOR_NOTIFICATIONS.CLOSED, false);
-  Services.obs.addObserver(inspectNodesFromContextTest, INSPECTOR_NOTIFICATIONS.OPENED, false);
+  Services.obs.removeObserver(runContextMenuTest, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
+  Services.obs.addObserver(inspectNodesFromContextTest, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
   salutation = doc.getElementById("salutation");
   ok(salutation, "hello, context menu test!");
   let eventDeets = { type : "contextmenu", button : 2 };
   let contextMenu = document.getElementById("contentAreaContextMenu");
   ok(contextMenu, "we have the context menu");
   let contextInspectMenuItem = document.getElementById("context-inspect");
   ok(contextInspectMenuItem, "we have the inspect context menu item");
   EventUtils.synthesizeMouse(salutation, 2, 2, eventDeets);
   is(contextMenu.state, "showing", "context menu is open");
-  is(contextInspectMenuItem.hidden, !InspectorUI.enabled, "is context menu item enabled?");
+  is(!contextInspectMenuItem.hidden, gPrefService.getBoolPref("devtools.inspector.enabled"), "is context menu item enabled?");
   contextMenu.hidePopup();
   executeSoon(function() {
     InspectorUI.openInspectorUI(salutation);
   });
 }
 
 function inspectNodesFromContextTest()
 {
-  Services.obs.removeObserver(inspectNodesFromContextTest, INSPECTOR_NOTIFICATIONS.OPENED, false);
-  Services.obs.addObserver(finishInspectorTests, INSPECTOR_NOTIFICATIONS.CLOSED, false);
+  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.isTreePanelOpen, "Inspector Tree Panel is open");
+  ok(!InspectorUI.isTreePanelOpen, "Inspector Tree Panel is closed");
   // TODO: These tests depend on the style inspector patches.
   todo(InspectorUI.isStylePanelOpen, "Inspector Style Panel is open");
-  todo(InspectorUI.isDOMPanelOpen, "Inspector DOM Panel is open");
-  InspectorUI.closeInspectorUI(true);
+  executeSoon(function() {
+    InspectorUI.closeInspectorUI(true);
+  });
+}
+
+function openInspectorForContextTest()
+{
+  Services.obs.removeObserver(openInspectorForContextTest, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
+  Services.obs.addObserver(inspectNodesFromContextTestWhileOpen, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
+  executeSoon(function() {
+    InspectorUI.openInspectorUI(salutation);
+  });
+}
+
+function inspectNodesFromContextTestWhileOpen()
+{
+  Services.obs.removeObserver(inspectNodesFromContextTestWhileOpen, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
+  Services.obs.addObserver(inspectNodesFromContextTestTrap, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
+  Services.obs.addObserver(inspectNodesFromContextTestHighlight, InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+  is(InspectorUI.selection, salutation, "Inspector is highlighting salutation");
+  closing = doc.getElementById("closing");
+  ok(closing, "we have the closing statement");
+  executeSoon(function() {
+    InspectorUI.openInspectorUI(closing);
+  });
+}
+
+function inspectNodesFromContextTestHighlight()
+{
+  Services.obs.removeObserver(inspectNodesFromContextTestHighlight, InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
+  Services.obs.addObserver(finishInspectorTests, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
+  is(InspectorUI.selection, closing, "InspectorUI.selection is header");
+  executeSoon(function() {
+    InspectorUI.closeInspectorUI(true);
+  });
+}
+
+function inspectNodesFromContextTestTrap()
+{
+  Services.obs.removeObserver(inspectNodesFromContextTestTrap, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
+  ok(false, "Inspector UI has been opened again. We Should Not Be Here!");
 }
 
 function finishInspectorTests()
 {
   Services.obs.removeObserver(finishInspectorTests,
-    INSPECTOR_NOTIFICATIONS.CLOSED, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
 
   ok(!InspectorUI.highlighter, "Highlighter is gone");
-  ok(!InspectorUI.isTreePanelOpen, "Inspector Tree Panel is closed");
+  ok(!InspectorUI.treePanel, "Inspector Tree Panel is closed");
   ok(!InspectorUI.inspecting, "Inspector is not inspecting");
-  ok(InspectorUI.toolbar.hidden, "toolbar is hidden");
+  ok(!InspectorUI.toolbar, "toolbar is hidden");
 
   gBrowser.removeCurrentTab();
   finish();
 }
 
 function test()
 {
   waitForExplicitFinish();
--- a/browser/devtools/highlighter/test/browser_inspector_registertools.js
+++ b/browser/devtools/highlighter/test/browser_inspector_registertools.js
@@ -34,26 +34,30 @@
  * 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");
-  let h1 = doc.createElement("h1");
+  h1 = doc.createElement("h1");
   let p1 = doc.createElement("p");
-  let p2 = 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 " +
@@ -74,202 +78,215 @@ function createDocument()
   div2.appendChild(p3);
   doc.body.appendChild(div);
   doc.body.appendChild(div2);
   setupHighlighterTests();
 }
 
 function setupHighlighterTests()
 {
-  h1 = doc.querySelectorAll("h1")[0];
   ok(h1, "we have the header node");
-  Services.obs.addObserver(inspectorOpen, "inspector-opened", false);
+  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, "inspector-opened", false);
-  Services.obs.addObserver(startToolTests, "inspector-highlighting", false);
-  let rect = h1.getBoundingClientRect();
-  executeSoon(function() {
-    EventUtils.synthesizeMouse(h1, 2, 2, {type: "mousemove"}, content);
-  });
+  Services.obs.removeObserver(inspectorOpen, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
+  toolsLength = InspectorUI.tools.length;
+  toolEvents = InspectorUI.toolEvents.length;
+  info("tools registered");
+  Services.obs.addObserver(startToolTests, InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+  InspectorUI.inspectNode(h1);
 }
 
 function startToolTests(evt)
 {
-  info("we received the inspector-highlighting notification");
-  Services.obs.removeObserver(startToolTests, "inspector-highlighting", false);
+  Services.obs.removeObserver(startToolTests, InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
   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.context.panelIsClosed, "Panel 1 is closed");
-  ok(tool2.context.panelIsClosed, "Panel 2 is closed");
-  ok(tool3.context.panelIsClosed, "Panel 3 is closed");
+  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");
-  tool1.onShow.apply(tool1.context, [h1]);
-  tool2.onShow.apply(tool2.context, [h1]);
-  tool3.onShow.apply(tool3.context, [h1]);
+  InspectorUI.toolShow(tool1);
+  InspectorUI.toolShow(tool2);
+  InspectorUI.toolShow(tool3);
 
   info("Checking panel states 2");
-  ok(tool1.context.panelIsOpen, "Panel 1 is open");
-  ok(tool2.context.panelIsOpen, "Panel 2 is open");
-  ok(tool3.context.panelIsOpen, "Panel 3 is open");
+  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");
-  tool1.onSelect.apply(tool1.context, [h1]);
-  tool2.onSelect.apply(tool2.context, [h1]);
-  tool3.onSelect.apply(tool3.context, [h1]);
+  info("Calling selectNode method for all tools, should see 3 selects");
+  InspectorUI.inspectNode(p2);
 
   info("Calling hide method for all tools");
-  tool1.onHide.apply(tool1.context, [h1]);
-  tool2.onHide.apply(tool2.context, [h1]);
-  tool3.onHide.apply(tool3.context, [h1]);
-
+  InspectorUI.toolHide(tool1);
+  InspectorUI.toolHide(tool2);
+  InspectorUI.toolHide(tool3);
+  
   info("Checking panel states 3");
-  ok(tool1.context.panelIsClosed, "Panel 1 is closed");
-  ok(tool2.context.panelIsClosed, "Panel 2 is closed");
-  ok(tool3.context.panelIsClosed, "Panel 3 is closed");
+  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");
-  tool1.onShow.apply(tool1.context, [h1]);
-  tool3.onShow.apply(tool3.context, [h1]);
+  InspectorUI.toolShow(tool1);
+  InspectorUI.toolShow(tool3);
 
   info("Checking panel states 4");
-  ok(tool1.context.panelIsOpen, "Panel 1 is open");
-  ok(tool2.context.panelIsClosed, "Panel 2 is closed");
-  ok(tool3.context.panelIsOpen, "Panel 3 is open");
+  ok(tool1.isOpen, "Panel 1 is open");
+  ok(!tool2.isOpen, "Panel 2 is closed");
+  ok(tool3.isOpen, "Panel 3 is open");
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function() {
     gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
     waitForFocus(testSecondTab, content);
   }, true);
 
   content.location = "data:text/html,registertool new tab test for inspector";
 }
 
 function testSecondTab()
 {
   info("Opened second tab");
   info("Checking panel states 5");
-  ok(tool1.context.panelIsClosed, "Panel 1 is closed");
-  ok(tool2.context.panelIsClosed, "Panel 2 is closed");
-  ok(tool3.context.panelIsClosed, "Panel 3 is closed");
+
+  let tools = InspectorUI.tools;
+  ok(!(tool1 in tools), "Panel 1 not in tools");
+  ok(!(tool2 in tools), "Panel 2 not in tools");
+  ok(!(tool3 in tools), "Panel 3 not in tools");
 
   info("Closing current tab");
+  Services.obs.addObserver(testOriginalTab, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
   gBrowser.removeCurrentTab();
+}
 
+function testOriginalTab()
+{
+  Services.obs.removeObserver(testOriginalTab, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
   info("Checking panel states 6");
-  ok(tool1.context.panelIsOpen, "Panel 1 is open");
-  ok(tool2.context.panelIsClosed, "Panel 2 is closed");
-  ok(tool3.context.panelIsOpen, "Panel 3 is open");
+
+  info("Tools: " + InspectorUI.tools);
+  // reacquaint ourselves with our tools
+  tool1 = InspectorUI.tools["tool_1"];
+  tool2 = InspectorUI.tools["tool_2"];
+  tool3 = InspectorUI.tools["tool_3"];
+
+  ok(tool1.isOpen, "Panel 1 is open after reactivation");
+  ok(!tool2.isOpen, "Panel 2 is closed after reactivation");
+  ok(tool3.isOpen, "Panel 3 is open after reactivation");
 
-  executeSoon(finishUp);
+  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() {
-  InspectorUI.closeInspectorUI(true);
   gBrowser.removeCurrentTab();
+  InspectorUI.initTools = initToolsMethod;
   finish();
 }
 
 function test()
 {
   waitForExplicitFinish();
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function() {
     gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
     doc = content.document;
-    waitForFocus(registerTools, content);
+    waitForFocus(createDocument, content);
   }, true);
   
   content.location = "data:text/html,registertool tests for inspector";
 }
 
 function registerTools()
 {
-  createDocument();
-  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"));
+  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,
-    onShow: aTool.show,
-    onHide: aTool.hide,
-    panel: aTool.panel
+    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.panel = this.createPanel();
+  this.accesskey = aAccesskey;
+  this._isOpen = false;
 }
 
 testTool.prototype = {
-  get panelIsOpen()
-  {
-    return this.panel.state == "open" || this.panel.state == "showing";
-  },
-
-  get panelIsClosed()
-  {
-    return this.panel.state == "closed" || this.panel.state == "hiding";
+  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.panel.openPopup(gBrowser.selectedBrowser,
-                         "end_before", 0, 20, false, false);
+    this._isOpen = true;
     is(InspectorUI.selection, aNode,
        "show: currently selected node was passed: " + this.id);
   },
 
   hide: function BIR_hide() {
     info(this.id + " hide");
-    this.panel.hidePopup();
+    this._isOpen = false;
   },
 
-  createPanel: function BIR_createPanel() {
-    let popupSet = document.getElementById("mainPopupSet");
-    let ns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-    let panel = this.panel = document.createElementNS(ns, "panel");
-    panel.setAttribute("orient", "vertical");
-    panel.setAttribute("noautofocus", "true");
-    panel.setAttribute("noautohide", "true");
-    panel.setAttribute("titlebar", "normal");
-    panel.setAttribute("close", "true");
-    panel.setAttribute("label", "Panel for " + this.id);
-    panel.setAttribute("width", 200);
-    panel.setAttribute("height", 400);
-    popupSet.appendChild(panel);
-
-    ok(panel.parentNode == popupSet, "Panel created and appended successfully");
-    return panel;
+  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_scrolling.js
+++ b/browser/devtools/highlighter/test/browser_inspector_scrolling.js
@@ -60,36 +60,36 @@ function createDocument()
   }, false);
 
   iframe.src = "data:text/html,foo bar";
   doc.body.appendChild(iframe);
 }
 
 function toggleInspector()
 {
-  Services.obs.addObserver(inspectNode, INSPECTOR_NOTIFICATIONS.OPENED, false);
+  Services.obs.addObserver(inspectNode, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
   InspectorUI.toggleInspectorUI();
 }
 
 function inspectNode()
 {
   Services.obs.removeObserver(inspectNode,
-    INSPECTOR_NOTIFICATIONS.OPENED, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
   Services.obs.addObserver(performScrollingTest,
-    INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
 
   executeSoon(function() {
     InspectorUI.inspectNode(div);
   });
 }
 
 function performScrollingTest()
 {
   Services.obs.removeObserver(performScrollingTest,
-    INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
 
   EventUtils.synthesizeMouseScroll(div, 10, 10,
     {axis:"vertical", delta:50, type:"MozMousePixelScroll"},
     iframe.contentWindow);
 
   gBrowser.selectedBrowser.addEventListener("scroll", function() {
     gBrowser.selectedBrowser.removeEventListener("scroll", arguments.callee,
       false);
--- a/browser/devtools/highlighter/test/browser_inspector_store.js
+++ b/browser/devtools/highlighter/test/browser_inspector_store.js
@@ -34,18 +34,18 @@
  * 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()
 {
-  ok(window.InspectorUI, "InspectorUI variable exists");
-  ok(!InspectorUI.inspecting, "Inspector is not highlighting");
+  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");
--- a/browser/devtools/highlighter/test/browser_inspector_tab_switch.js
+++ b/browser/devtools/highlighter/test/browser_inspector_tab_switch.js
@@ -42,33 +42,33 @@ let div;
 let tab1;
 let tab2;
 let tab1window;
 
 function inspectorTabOpen1()
 {
   ok(window.InspectorUI, "InspectorUI variable exists");
   ok(!InspectorUI.inspecting, "Inspector is not highlighting");
-  ok(InspectorStore.isEmpty(), "InspectorStore is empty");
+  ok(InspectorUI.store.isEmpty(), "Inspector.store is empty");
 
   Services.obs.addObserver(inspectorUIOpen1,
-    INSPECTOR_NOTIFICATIONS.OPENED, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
   InspectorUI.openInspectorUI();
 }
 
 function inspectorUIOpen1()
 {
   Services.obs.removeObserver(inspectorUIOpen1,
-    INSPECTOR_NOTIFICATIONS.OPENED, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
 
   // Make sure the inspector is open.
   ok(InspectorUI.inspecting, "Inspector is highlighting");
-  ok(InspectorUI.isTreePanelOpen, "Inspector Tree Panel is open");
-  ok(!InspectorStore.isEmpty(), "InspectorStore is not empty");
-  is(InspectorStore.length, 1, "InspectorStore.length = 1");
+  ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel 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");
 
   // Open the second tab.
   tab2 = gBrowser.addTab();
@@ -82,92 +82,141 @@ 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.isPanelOpen, "Inspector Tree Panel is closed");
-  is(InspectorStore.length, 1, "InspectorStore.length = 1");
+  ok(!InspectorUI.treePanel, "Inspector Tree Panel is closed");
+  is(InspectorUI.store.length, 1, "Inspector.store.length = 1");
 
   // Activate the inspector again.
   executeSoon(function() {
     Services.obs.addObserver(inspectorUIOpen2,
-      INSPECTOR_NOTIFICATIONS.OPENED, false);
+      InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
     InspectorUI.openInspectorUI();
   });
 }
 
 function inspectorUIOpen2()
 {
   Services.obs.removeObserver(inspectorUIOpen2,
-    INSPECTOR_NOTIFICATIONS.OPENED, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
 
   // Make sure the inspector is open.
   ok(InspectorUI.inspecting, "Inspector is highlighting");
-  ok(InspectorUI.isTreePanelOpen, "Inspector Tree Panel is open");
-  is(InspectorStore.length, 2, "InspectorStore.length = 2");
+  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,
-      INSPECTOR_NOTIFICATIONS.OPENED, false);
+      InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
     gBrowser.selectedTab = tab1;
   });
 }
 
 function inspectorFocusTab1()
 {
   Services.obs.removeObserver(inspectorFocusTab1,
-    INSPECTOR_NOTIFICATIONS.OPENED, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
 
   // Make sure the inspector is still open.
   ok(InspectorUI.inspecting, "Inspector is highlighting");
-  ok(InspectorUI.isTreePanelOpen, "Inspector Tree Panel is open");
-  is(InspectorStore.length, 2, "InspectorStore.length = 2");
+  ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
+  is(InspectorUI.store.length, 2, "Inspector.store.length = 2");
+  is(InspectorUI.selection, div, "selection matches the div element");
+
+  Services.obs.addObserver(inspectorOpenTreePanelTab1,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
+
+  InspectorUI.treePanel.open();
+}
+
+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");
 
   // Switch back to tab 2.
   Services.obs.addObserver(inspectorFocusTab2,
-    INSPECTOR_NOTIFICATIONS.OPENED, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
   gBrowser.selectedTab = tab2;
 }
 
 function inspectorFocusTab2()
 {
   Services.obs.removeObserver(inspectorFocusTab2,
-    INSPECTOR_NOTIFICATIONS.OPENED, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
 
   // Make sure the inspector is still open.
   ok(!InspectorUI.inspecting, "Inspector is not highlighting");
-  ok(InspectorUI.isTreePanelOpen, "Inspector Tree Panel is open");
-  is(InspectorStore.length, 2, "InspectorStore.length = 2");
+  ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
+  is(InspectorUI.store.length, 2, "Inspector.store.length is 2");
+  isnot(InspectorUI.selection, div, "selection does not match the div element");
+
+  // Switch back to tab 1.
+  Services.obs.addObserver(inspectorSecondFocusTab1,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
+  gBrowser.selectedTab = tab1;
+}
+
+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");
+
+  // Switch back to tab 2.
+  Services.obs.addObserver(inspectorSecondFocusTab2,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
+  gBrowser.selectedTab = tab2;
+}
+
+function inspectorSecondFocusTab2()
+{
+  Services.obs.removeObserver(inspectorSecondFocusTab2,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
+
+  // Make sure the inspector is still open.
+  ok(!InspectorUI.inspecting, "Inspector is not highlighting");
+  ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
+  is(InspectorUI.store.length, 2, "Inspector.store.length is 2");
   isnot(InspectorUI.selection, div, "selection does not match the div element");
 
   // Remove tab 1.
   tab1window = gBrowser.getBrowserForTab(tab1).contentWindow;
   tab1window.addEventListener("pagehide", inspectorTabUnload1, false);
   gBrowser.removeTab(tab1);
 }
 
 function inspectorTabUnload1(evt)
 {
   tab1window.removeEventListener(evt.type, arguments.callee, false);
   tab1window = tab1 = tab2 = div = null;
 
   // Make sure the Inspector is still open and that the state is correct.
   ok(!InspectorUI.inspecting, "Inspector is not highlighting");
-  ok(InspectorUI.isTreePanelOpen, "Inspector Tree Panel is open");
-  is(InspectorStore.length, 1, "InspectorStore.length = 1");
+  ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
+  is(InspectorUI.store.length, 1, "Inspector.store.length = 1");
 
   InspectorUI.closeInspectorUI();
   gBrowser.removeCurrentTab();
   finish();
 }
 
 function test()
 {
--- a/browser/devtools/highlighter/test/browser_inspector_treePanel_click.js
+++ b/browser/devtools/highlighter/test/browser_inspector_treePanel_click.js
@@ -12,50 +12,54 @@ function test() {
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function onload() {
     gBrowser.selectedBrowser.removeEventListener("load", onload, true);
     doc = content.document;
     waitForFocus(setupTest, content);
   }, true);
 
-  content.location = "data:text/html,<div><p></p></div>";
+  content.location = 'data:text/html,<div style="width: 200px; height: 200px"><p></p></div>';
 
   function setupTest() {
     node1 = doc.querySelector("div");
     node2 = doc.querySelector("p");
-    Services.obs.addObserver(runTests, INSPECTOR_NOTIFICATIONS.OPENED, false);
+    Services.obs.addObserver(runTests, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
     InspectorUI.toggleInspectorUI();
   }
 
   function runTests() {
-    Services.obs.removeObserver(runTests, INSPECTOR_NOTIFICATIONS.OPENED);
-    testNode1();
+    Services.obs.removeObserver(runTests, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
+    Services.obs.addObserver(testNode1, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
+    InspectorUI.select(node1, true, true, true);
+    InspectorUI.openTreePanel();
   }
 
   function testNode1() {
-    let box = InspectorUI.ioBox.createObjectBox(node1);
-    box.click();
-    executeSoon(function() {
-      is(InspectorUI.selection, node1, "selection matches node");
-      is(InspectorUI.highlighter.node, node1, "selection matches node");
-      testNode2();
-    });
+    dump("testNode1\n");
+    Services.obs.removeObserver(testNode1, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY);
+    is(InspectorUI.selection, node1, "selection matches node");
+    is(InspectorUI.highlighter.node, node1, "selection matches node");
+    testNode2();
   }
 
   function testNode2() {
-    let box = InspectorUI.ioBox.createObjectBox(node2);
-    box.click();
-    executeSoon(function() {
-      is(InspectorUI.selection, node2, "selection matches node");
-      is(InspectorUI.highlighter.node, node2, "selection matches node");
-      Services.obs.addObserver(finishUp, INSPECTOR_NOTIFICATIONS.CLOSED, false);
-      InspectorUI.closeInspectorUI();
-    });
+    dump("testNode2\n")
+    Services.obs.addObserver(testHighlightingNode2, InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+    InspectorUI.treePanelSelect("node2");
+  }
+
+  function testHighlightingNode2() {
+    dump("testHighlightingNode2\n")
+    Services.obs.removeObserver(testHighlightingNode2, InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
+    is(InspectorUI.selection, node2, "selection matches node");
+    is(InspectorUI.highlighter.node, node2, "selection matches node");
+    Services.obs.addObserver(finishUp, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
+    InspectorUI.closeInspectorUI();
   }
 
   function finishUp() {
-    Services.obs.removeObserver(finishUp, INSPECTOR_NOTIFICATIONS.CLOSED);
+    Services.obs.removeObserver(finishUp, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
     doc = node1 = node2 = null;
     gBrowser.removeCurrentTab();
     finish();
   }
 }
--- a/browser/devtools/highlighter/test/browser_inspector_treePanel_output.js
+++ b/browser/devtools/highlighter/test/browser_inspector_treePanel_output.js
@@ -59,60 +59,69 @@ function xhr_onReadyStateChange() {
   }
 
   is(xhr.status, 200, "xhr.status is 200");
   ok(!!xhr.responseText, "xhr.responseText is available");
   expectedResult = xhr.responseText.replace(/^\s+|\s+$/mg, '');
   xhr = null;
 
   Services.obs.addObserver(inspectorOpened,
-    INSPECTOR_NOTIFICATIONS.OPENED, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
   InspectorUI.openInspectorUI();
 }
 
 function inspectorOpened()
 {
   Services.obs.removeObserver(inspectorOpened,
-    INSPECTOR_NOTIFICATIONS.OPENED, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
+
+  Services.obs.addObserver(treePanelOpened, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
+  InspectorUI.treePanel.open();
+}
+
+function treePanelOpened()
+{
+  Services.obs.removeObserver(treePanelOpened,
+    InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY);
 
   ok(InspectorUI.inspecting, "Inspector is highlighting");
-  ok(InspectorUI.isTreePanelOpen, "Inspector Tree Panel is open");
+  ok(InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is open");
   InspectorUI.stopInspecting();
   ok(!InspectorUI.inspecting, "Inspector is not highlighting");
 
   let elements = doc.querySelectorAll("meta, script, style, p[unknownAttribute]");
   for (let i = 0; i < elements.length; i++) {
     InspectorUI.inspectNode(elements[i]);
   }
 
   let iframe = doc.querySelector("iframe");
   ok(iframe, "Found the iframe tag");
   ok(iframe.contentDocument, "Found the iframe.contentDocument");
 
   let iframeDiv = iframe.contentDocument.querySelector("div");
   ok(iframeDiv, "Found the div element inside the iframe");
   InspectorUI.inspectNode(iframeDiv);
 
-  ok(InspectorUI.treePanelDiv, "InspectorUI.treePanelDiv is available");
-  is(InspectorUI.treePanelDiv.innerHTML.replace(/^\s+|\s+$/mg, ''),
+  ok(InspectorUI.treePanel.treePanelDiv, "InspectorUI.treePanelDiv is available");
+  is(InspectorUI.treePanel.treePanelDiv.innerHTML.replace(/^\s+|\s+$/mg, ''),
     expectedResult, "treePanelDiv.innerHTML is correct");
   expectedResult = null;
 
   Services.obs.addObserver(inspectorClosed,
-    INSPECTOR_NOTIFICATIONS.CLOSED, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
   InspectorUI.closeInspectorUI();
 }
 
 function inspectorClosed()
 {
   Services.obs.removeObserver(inspectorClosed,
-    INSPECTOR_NOTIFICATIONS.CLOSED, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
 
   ok(!InspectorUI.inspecting, "Inspector is not highlighting");
-  ok(!InspectorUI.isTreePanelOpen, "Inspector Tree Panel is not open");
+  ok(!InspectorUI.treePanel, "Inspector Tree Panel is not open");
 
   gBrowser.removeCurrentTab();
   finish();
 }
 
 function test()
 {
   waitForExplicitFinish();
--- a/browser/devtools/highlighter/test/browser_inspector_treeSelection.js
+++ b/browser/devtools/highlighter/test/browser_inspector_treeSelection.js
@@ -63,35 +63,35 @@ function createDocument()
   setupSelectionTests();
 }
 
 function setupSelectionTests()
 {
   h1 = doc.querySelectorAll("h1")[0];
   ok(h1, "we have the header node");
   Services.obs.addObserver(runSelectionTests,
-    INSPECTOR_NOTIFICATIONS.OPENED, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
   InspectorUI.openInspectorUI();
 }
 
 function runSelectionTests()
 {
   Services.obs.removeObserver(runSelectionTests,
-    INSPECTOR_NOTIFICATIONS.OPENED, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
   Services.obs.addObserver(performTestComparisons,
-    INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
   executeSoon(function() {
     InspectorUI.inspectNode(h1);
   });
 }
 
 function performTestComparisons(evt)
 {
   Services.obs.removeObserver(performTestComparisons,
-    INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
 
   is(h1, InspectorUI.selection, "selection matches node");
   ok(InspectorUI.highlighter.isHighlighting, "highlighter is highlighting");
   is(InspectorUI.highlighter.highlitNode, h1, "highlighter highlighting correct node");
 
   finishUp();
 }
 
--- a/browser/devtools/jar.mn
+++ b/browser/devtools/jar.mn
@@ -1,8 +1,9 @@
 browser.jar:
+*   content/browser/inspector.html                (highlighter/inspector.html)
     content/browser/NetworkPanel.xhtml            (webconsole/NetworkPanel.xhtml)
 *   content/browser/scratchpad.xul                (scratchpad/scratchpad.xul)
 *   content/browser/scratchpad.js                 (scratchpad/scratchpad.js)
     content/browser/csshtmltree.xhtml             (styleinspector/csshtmltree.xhtml)
     content/browser/orion.js                      (sourceeditor/orion/orion.js)
     content/browser/orion.css                     (sourceeditor/orion/orion.css)
 *   content/browser/inspector.html                (highlighter/inspector.html)
--- a/browser/devtools/scratchpad/Makefile.in
+++ b/browser/devtools/scratchpad/Makefile.in
@@ -7,21 +7,21 @@
 # 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  HUDService code.
+# The Original Code is Scratchpad Build Code.
 #
-# The Initial Developer of the Original Code is Mozilla Corporation.
-# 
-# Portions created by the Initial Developer are Copyright (C) 2010
+# 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):
 #   Rob Campbell <rcampbell@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"),
@@ -39,14 +39,12 @@
 DEPTH		= ../../..
 topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 ifdef ENABLE_TESTS
-ifneq (mobile,$(MOZ_BUILD_APP))
 	DIRS += test
 endif
-endif
 
 include $(topsrcdir)/config/rules.mk
--- a/browser/devtools/styleinspector/CssHtmlTree.jsm
+++ b/browser/devtools/styleinspector/CssHtmlTree.jsm
@@ -16,19 +16,20 @@
  * The Original Code is the Mozilla Inspector Module.
  *
  * 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):
- *   Joe Walker (jwalker@mozilla.com) (original author)
+ *   Joe Walker (jwalker@mozilla.com) (Original Author)
  *   Mihai Șucan <mihai.sucan@gmail.com>
  *   Michael Ratcliffe <mratcliffe@mozilla.com>
+ *   Rob Campbell <rcampbell@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
--- a/browser/devtools/styleinspector/StyleInspector.jsm
+++ b/browser/devtools/styleinspector/StyleInspector.jsm
@@ -16,17 +16,18 @@
  * The Original Code is the Mozilla Inspector Module.
  *
  * 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>
+ *   Mike Ratcliffe <mratcliffe@mozilla.com> (Original Author)
+ *   Rob Campbell <rcampbell@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
@@ -104,31 +105,33 @@ var StyleInspector = {
     popupSet.appendChild(panel);
 
     /**
      * Iframe's onload event
      */
     let iframeReady = false;
     function SI_iframeOnload() {
       iframe.removeEventListener("load", SI_iframeOnload, true);
-      panel.cssLogic = new CssLogic();
-      panel.cssHtmlTree = new CssHtmlTree(iframe, panel.cssLogic, panel);
       iframeReady = true;
       if (panelReady) {
         SI_popupShown.call(panel);
       }
     }
 
     /**
      * Initialize the popup when it is first shown
      */
     let panelReady = false;
     function SI_popupShown() {
       panelReady = true;
       if (iframeReady) {
+        if (!this.cssLogic) {
+          this.cssLogic = new CssLogic();
+          this.cssHtmlTree = new CssHtmlTree(iframe, this.cssLogic, this);
+        }
         let selectedNode = this.selectedNode || null;
         this.cssLogic.highlight(selectedNode);
         this.cssHtmlTree.highlight(selectedNode);
         Services.obs.notifyObservers(null, "StyleInspector-opened", null);
       }
     }
 
     /**
@@ -157,39 +160,71 @@ var StyleInspector = {
     /**
      * Select a node to inspect in the Style Inspector panel
      *
      * @param aNode The node to inspect
      */
     panel.selectNode = function SI_selectNode(aNode)
     {
       this.selectedNode = aNode;
-      if (this.isOpen()) {
+      if (this.isOpen() && !this.hasAttribute("dimmed")) {
         this.cssLogic.highlight(aNode);
         this.cssHtmlTree.highlight(aNode);
-      } else {
-        let win = Services.wm.getMostRecentWindow("navigator:browser");
-        this.openPopup(win.gBrowser.selectedBrowser, "end_before", 0, 0, false, false);
       }
     };
 
     /**
      * Destroy the style panel, remove listeners etc.
      */
     panel.destroy = function SI_destroy()
     {
+      if (!this.cssLogic)
+        return;
+      if (this.isOpen())
+        this.hideTool();
       this.cssLogic = null;
       this.cssHtmlTree = null;
       this.removeEventListener("popupshown", SI_popupShown);
       this.removeEventListener("popuphidden", SI_popupHidden);
       this.parentNode.removeChild(this);
       Services.obs.notifyObservers(null, "StyleInspector-closed", null);
     };
 
     /**
+     * Dim or undim a panel by setting or removing a dimmed attribute.
+     *
+     * @param aState
+     *        true = dim, false = undim
+     */
+    panel.dimTool = function SI_dimTool(aState)
+    {
+      if (!this.isOpen())
+        return;
+
+      if (aState) {
+        this.setAttribute("dimmed", "true");
+      } else if (this.hasAttribute("dimmed")) {
+        this.removeAttribute("dimmed");
+      }
+    };
+
+    panel.showTool = function SI_showTool(aSelection)
+    {
+      this.selectNode(aSelection);
+      let win = Services.wm.getMostRecentWindow("navigator:browser");
+      this.openPopup(win.gBrowser.selectedBrowser, "end_before", 0, 0,
+        false, false);
+    };
+
+    panel.hideTool = function SI_hideTool()
+    {
+      this.hidePopup();
+    };
+
+    /**
      * Is the Style Inspector initialized?
      * @returns {Boolean} true or false
      */
     function isInitialized()
     {
       return panel.cssLogic && panel.cssHtmlTree;
     }
 
--- a/browser/devtools/styleinspector/test/browser/browser_styleinspector_webconsole.js
+++ b/browser/devtools/styleinspector/test/browser/browser_styleinspector_webconsole.js
@@ -169,31 +169,23 @@ function styleInspectorClosedByHide()
                            "StyleInspector-closed", false);
   closeConsole();
 }
 
 function styleInspectorClosedFromConsole1()
 {
   Services.obs.removeObserver(styleInspectorClosedFromConsole1,
                               "StyleInspector-closed", false);
-  info("Style Inspector 1 closed");
-  Services.obs.addObserver(styleInspectorClosedFromConsole2,
-                           "StyleInspector-closed", false);
-}
-
-function styleInspectorClosedFromConsole2()
-{
-  Services.obs.removeObserver(styleInspectorClosedFromConsole2,
-                              "StyleInspector-closed", false);
-  info("Style Inspector 2 closed");
+  info("Style Inspector 1 and 2 closed");
   executeSoon(cleanUp);
 }
 
 function cleanUp()
 {
-  let popupSet = document.getElementById("mainPopupSet");
-  ok(!popupSet.lastChild.hasAttribute("hudToolId"),
+  let panels = document.querySelector("panel[hudToolId]");
+  ok(!panels,
      "all style inspector panels are now detached and ready for garbage collection");
 
   info("cleaning up");
+
   doc = hudBox = stylePanels = jsterm = null;
   finishTest();
 }
--- a/browser/devtools/webconsole/HUDService.jsm
+++ b/browser/devtools/webconsole/HUDService.jsm
@@ -160,17 +160,20 @@ const SEVERITY_LOG = 3;
 // A mapping from the console API log event levels to the Web Console
 // severities.
 const LEVELS = {
   error: SEVERITY_ERROR,
   warn: SEVERITY_WARNING,
   info: SEVERITY_INFO,
   log: SEVERITY_LOG,
   trace: SEVERITY_LOG,
-  dir: SEVERITY_LOG
+  dir: SEVERITY_LOG,
+  group: SEVERITY_LOG,
+  groupCollapsed: SEVERITY_LOG,
+  groupEnd: SEVERITY_LOG
 };
 
 // The lowest HTTP response code (inclusive) that is considered an error.
 const MIN_HTTP_ERROR_CODE = 400;
 // The highest HTTP response code (exclusive) that is considered an error.
 const MAX_HTTP_ERROR_CODE = 600;
 
 // HTTP status codes.
@@ -247,16 +250,19 @@ const TYPEOF_FUNCTION = "function";
 
 const ERRORS = { LOG_MESSAGE_MISSING_ARGS:
                  "Missing arguments: aMessage, aConsoleNode and aMessageNode are required.",
                  CANNOT_GET_HUD: "Cannot getHeads Up Display with provided ID",
                  MISSING_ARGS: "Missing arguments",
                  LOG_OUTPUT_FAILED: "Log Failure: Could not append messageNode to outputNode",
 };
 
+// The indent of a console group in pixels.
+const GROUP_INDENT = 12;
+
 /**
  * Implements the nsIStreamListener and nsIRequestObserver interface. Used
  * within the HS_httpObserverFactory function to get the response body of
  * requests.
  *
  * The code is mostly based on code listings from:
  *
  *   http://www.softwareishard.com/blog/firebug/
@@ -2000,25 +2006,40 @@ HUD_SERVICE.prototype =
 
       case "dir":
         body = unwrap(args[0]);
         clipboardText = body.toString();
         sourceURL = aMessage.filename;
         sourceLine = aMessage.lineNumber;
         break;
 
+      case "group":
+      case "groupCollapsed":
+        clipboardText = body = formatResult(args);
+        sourceURL = aMessage.filename;
+        sourceLine = aMessage.lineNumber;
+        hud.groupDepth++;
+        break;
+
+      case "groupEnd":
+        if (hud.groupDepth > 0) {
+          hud.groupDepth--;
+        }
+        return;
+
       default:
         Cu.reportError("Unknown Console API log level: " + level);
         return;
     }
 
     let node = ConsoleUtils.createMessageNode(hud.outputNode.ownerDocument,
                                               CATEGORY_WEBDEV,
                                               LEVELS[level],
                                               body,
+                                              aHUDId,
                                               sourceURL,
                                               sourceLine,
                                               clipboardText,
                                               level);
 
     // Make the node bring up the property panel, to allow the user to inspect
     // the stack trace.
     if (level == "trace") {
@@ -2071,17 +2092,18 @@ HUD_SERVICE.prototype =
    */
   logWarningAboutReplacedAPI:
   function HS_logWarningAboutReplacedAPI(aHUDId)
   {
     let hud = this.hudReferences[aHUDId];
     let chromeDocument = hud.HUDBox.ownerDocument;
     let message = stringBundle.GetStringFromName("ConsoleAPIDisabled");
     let node = ConsoleUtils.createMessageNode(chromeDocument, CATEGORY_JS,
-                                              SEVERITY_WARNING, message);
+                                              SEVERITY_WARNING, message,
+                                              aHUDId);
     ConsoleUtils.outputMessageNode(node, aHUDId);
   },
 
   /**
    * Reports an error in the page source, either JavaScript or CSS.
    *
    * @param number aCategory
    *        The category of the message; either CATEGORY_CSS or CATEGORY_JS.
@@ -2111,16 +2133,17 @@ HUD_SERVICE.prototype =
       if (hudId) {
         let outputNode = this.hudReferences[hudId].outputNode;
         let chromeDocument = outputNode.ownerDocument;
 
         let node = ConsoleUtils.createMessageNode(chromeDocument,
                                                   aCategory,
                                                   severity,
                                                   aScriptError.errorMessage,
+                                                  hudId,
                                                   aScriptError.sourceName,
                                                   aScriptError.lineNumber);
 
         ConsoleUtils.outputMessageNode(node, hudId);
       }
     }
   },
 
@@ -2571,16 +2594,17 @@ HUD_SERVICE.prototype =
     linkNode.appendChild(statusNode);
 
     let clipboardText = aActivityObject.method + " " + aActivityObject.url;
 
     let messageNode = ConsoleUtils.createMessageNode(chromeDocument,
                                                      CATEGORY_NETWORK,
                                                      SEVERITY_LOG,
                                                      msgNode,
+                                                     hudId,
                                                      null,
                                                      null,
                                                      clipboardText);
 
     ConsoleUtils.outputMessageNode(messageNode, aActivityObject.hudId);
     return messageNode;
   },
 
@@ -3076,16 +3100,21 @@ function HeadsUpDisplay(aConfig)
   // A cache for tracking repeated CSS Nodes.
   this.cssNodes = {};
 }
 
 HeadsUpDisplay.prototype = {
 
   consolePanel: null,
 
+  /**
+   * The nesting depth of the currently active console group.
+   */
+  groupDepth: 0,
+
   get mainPopupSet()
   {
     return this.chromeDocument.getElementById("mainPopupSet");
   },
 
   /**
    * Get the tab associated to the HeadsUpDisplay object.
    */
@@ -4433,17 +4462,17 @@ function JSTermHelper(aJSTerm)
       errstr = HUDService.getStr("inspectStyle.mustBeDomNode");
     } else if (!(aNode.style instanceof Ci.nsIDOMCSSStyleDeclaration)) {
       errstr = HUDService.getStr("inspectStyle.nodeHasNoStyleProps");
     }
 
     if (!errstr) {
       let stylePanel = StyleInspector.createPanel();
       stylePanel.setAttribute("hudToolId", aJSTerm.hudId);
-      stylePanel.selectNode(aNode);
+      stylePanel.showTool(aNode);
     } else {
       aJSTerm.writeOutput(errstr + "\n", CATEGORY_OUTPUT, SEVERITY_ERROR);
     }
   };
 
   /**
    * Prints aObject to the output.
    *
@@ -4747,17 +4776,18 @@ JSTerm.prototype = {
    * @param object aOutputString
    *        The output string to be written to the outputNode.
    */
   writeOutputJS: function JST_writeOutputJS(aEvalString, aOutputObject, aOutputString)
   {
     let node = ConsoleUtils.createMessageNode(this.parentNode.ownerDocument,
                                               CATEGORY_OUTPUT,
                                               SEVERITY_LOG,
-                                              aOutputString);
+                                              aOutputString,
+                                              this.hudId);
 
     let linkNode = node.querySelector(".webconsole-msg-body");
 
     linkNode.classList.add("hud-clickable");
     linkNode.setAttribute("aria-haspopup", "true");
 
     // Make the object bring up the property panel.
     node.addEventListener("mousedown", function(aEvent) {
@@ -4794,17 +4824,17 @@ JSTerm.prototype = {
    * @param number aSeverity
    *        The severity of message: one of the SEVERITY_ constants.
    * @returns void
    */
   writeOutput: function JST_writeOutput(aOutputMessage, aCategory, aSeverity)
   {
     let node = ConsoleUtils.createMessageNode(this.parentNode.ownerDocument,
                                               aCategory, aSeverity,
-                                              aOutputMessage);
+                                              aOutputMessage, this.hudId);
 
     ConsoleUtils.outputMessageNode(node, this.hudId);
   },
 
   /**
    * Format the jsterm execution result based on its type.
    *
    * @param mixed aResult
@@ -5535,16 +5565,18 @@ ConsoleUtils = {
    * @param nsIDOMDocument aDocument
    *        The document in which to create the node.
    * @param number aCategory
    *        The category of the message: one of the CATEGORY_* constants.
    * @param number aSeverity
    *        The severity of the message: one of the SEVERITY_* constants;
    * @param string|nsIDOMNode aBody
    *        The body of the message, either a simple string or a DOM node.
+   * @param number aHUDId
+   *        The HeadsUpDisplay ID.
    * @param string aSourceURL [optional]
    *        The URL of the source file that emitted the error.
    * @param number aSourceLine [optional]
    *        The line number on which the error occurred. If zero or omitted,
    *        there is no line number associated with this message.
    * @param string aClipboardText [optional]
    *        The text that should be copied to the clipboard when this node is
    *        copied. If omitted, defaults to the body text. If `aBody` is not
@@ -5552,28 +5584,31 @@ ConsoleUtils = {
    * @param number aLevel [optional]
    *        The level of the console API message.
    * @return nsIDOMNode
    *         The message node: a XUL richlistitem ready to be inserted into
    *         the Web Console output node.
    */
   createMessageNode:
   function ConsoleUtils_createMessageNode(aDocument, aCategory, aSeverity,
-                                          aBody, aSourceURL, aSourceLine,
-                                          aClipboardText, aLevel) {
+                                          aBody, aHUDId, aSourceURL,
+                                          aSourceLine, aClipboardText, aLevel) {
     if (aBody instanceof Ci.nsIDOMNode && aClipboardText == null) {
       throw new Error("HUDService.createMessageNode(): DOM node supplied " +
                       "without any clipboard text");
     }
 
     // Make the icon container, which is a vertical box. Its purpose is to
     // ensure that the icon stays anchored at the top of the message even for
     // long multi-line messages.
     let iconContainer = aDocument.createElementNS(XUL_NS, "vbox");
     iconContainer.classList.add("webconsole-msg-icon-container");
+    // Apply the curent group by indenting appropriately.
+    let hud = HUDService.getHudReferenceById(aHUDId);
+    iconContainer.style.marginLeft = hud.groupDepth * GROUP_INDENT + "px";
 
     // Make the icon node. It's sprited and the actual region of the image is
     // determined by CSS rules.
     let iconNode = aDocument.createElementNS(XUL_NS, "image");
     iconNode.classList.add("webconsole-msg-icon");
     iconContainer.appendChild(iconNode);
 
     // Make the spacer that positions the icon.
@@ -6664,16 +6699,17 @@ ConsoleProgressListener.prototype = {
     }, false);
 
     msgNode.appendChild(linkNode);
 
     let messageNode = ConsoleUtils.createMessageNode(chromeDocument,
                                                      CATEGORY_NETWORK,
                                                      SEVERITY_LOG,
                                                      msgNode,
+                                                     this.hudId,
                                                      null,
                                                      null,
                                                      uri.spec);
 
     ConsoleUtils.outputMessageNode(messageNode, this.hudId);
   },
 
   onLocationChange: function() {},
--- a/browser/devtools/webconsole/test/browser/Makefile.in
+++ b/browser/devtools/webconsole/test/browser/Makefile.in
@@ -140,16 +140,17 @@ include $(topsrcdir)/config/rules.mk
 	browser_webconsole_bug_585991_autocomplete_popup.js \
 	browser_webconsole_bug_585991_autocomplete_keys.js \
 	browser_webconsole_bug_663443_panel_title.js \
 	browser_webconsole_bug_660806_history_nav.js \
 	browser_webconsole_bug_651501_document_body_autocomplete.js \
 	browser_webconsole_bug_653531_highlighter_console_helper.js \
 	browser_webconsole_bug_659907_console_dir.js \
 	browser_webconsole_bug_678816.js \
+	browser_webconsole_bug_664131_console_group.js \
 	head.js \
 	$(NULL)
 
 _BROWSER_TEST_PAGES = \
 	test-console.html \
 	test-network.html \
 	test-network-request.html \
 	test-mutation.html \
--- a/browser/devtools/webconsole/test/browser/browser_webconsole_bug_642108_pruneTest.js
+++ b/browser/devtools/webconsole/test/browser/browser_webconsole_bug_642108_pruneTest.js
@@ -20,30 +20,32 @@ function test() {
 
 function populateConsoleRepeats(aHudRef) {
   let hud = aHudRef.HUDBox;
 
   for (i = 0; i < 5; i++) {
     let node = ConsoleUtils.createMessageNode(hud.ownerDocument,
                                               CATEGORY_CSS,
                                               SEVERITY_WARNING,
-                                              "css log x");
+                                              "css log x",
+                                              aHudRef.hudId);
     ConsoleUtils.outputMessageNode(node, aHudRef.hudId);
   }
 }
 
 
 function populateConsole(aHudRef) {
   let hud = aHudRef.HUDBox;
 
   for (i = 0; i < LOG_LIMIT + 5; i++) {
     let node = ConsoleUtils.createMessageNode(hud.ownerDocument,
                                               CATEGORY_CSS,
                                               SEVERITY_WARNING,
-                                              "css log " + i);
+                                              "css log " + i,
+                                              aHudRef.hudId);
     ConsoleUtils.outputMessageNode(node, aHudRef.hudId);
   }
 }
 
 function testCSSPruning() {
   let prefBranch = Services.prefs.getBranch("devtools.hud.loglimit.");
   prefBranch.setIntPref("cssparser", LOG_LIMIT);
 
--- a/browser/devtools/webconsole/test/browser/browser_webconsole_bug_653531_highlighter_console_helper.js
+++ b/browser/devtools/webconsole/test/browser/browser_webconsole_bug_653531_highlighter_console_helper.js
@@ -77,36 +77,36 @@ function createDocument()
   setupHighlighterTests();
 }
 
 function setupHighlighterTests()
 {
   h1 = doc.querySelectorAll("h1")[0];
   ok(h1, "we have the header node");
   Services.obs.addObserver(runSelectionTests,
-    INSPECTOR_NOTIFICATIONS.OPENED, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
   InspectorUI.toggleInspectorUI();
 }
 
 function runSelectionTests()
 {
   Services.obs.removeObserver(runSelectionTests,
-    INSPECTOR_NOTIFICATIONS.OPENED, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
 
   executeSoon(function() {
     Services.obs.addObserver(performTestComparisons,
-      INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+      InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
     EventUtils.synthesizeMouse(h1, 2, 2, {type: "mousemove"}, content);
   });
 }
 
 function performTestComparisons(evt)
 {
   Services.obs.removeObserver(performTestComparisons,
-    INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
 
   InspectorUI.stopInspecting();
   ok(InspectorUI.highlighter.isHighlighting, "highlighter is highlighting");
   is(InspectorUI.selection, h1, "selection matches node");
 
   HUDService.activateHUDForContext(gBrowser.selectedTab);
   let hudId = HUDService.getHudIdByWindow(content);
   let hud = HUDService.hudReferences[hudId];
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser/browser_webconsole_bug_664131_console_group.js
@@ -0,0 +1,48 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Tests that console.group/groupEnd works as intended.
+const GROUP_INDENT = 12;
+
+function test() {
+  addTab("data:text/html,Web Console test for bug 664131: Expand console " +
+         "object with group methods");
+  browser.addEventListener("load", onLoad, true);
+}
+
+function onLoad(aEvent) {
+  browser.removeEventListener(aEvent.type, arguments.callee, true);
+
+  openConsole();
+  let hudId = HUDService.getHudIdByWindow(content);
+  let hud = HUDService.hudReferences[hudId];
+  outputNode = hud.outputNode;
+
+  content.console.group("a");
+  findLogEntry("a");
+  let msg = outputNode.querySelectorAll(".webconsole-msg-icon-container");
+  is(msg.length, 1, "one message node displayed");
+  is(msg[0].style.marginLeft, GROUP_INDENT + "px", "correct group indent found");
+  content.console.log("inside");
+  findLogEntry("inside");
+  let msg = outputNode.querySelectorAll(".webconsole-msg-icon-container");
+  is(msg.length, 2, "two message nodes displayed");
+  is(msg[1].style.marginLeft, GROUP_INDENT + "px", "correct group indent found");
+  content.console.groupEnd("a");
+  content.console.log("outside");
+  findLogEntry("outside");
+  let msg = outputNode.querySelectorAll(".webconsole-msg-icon-container");
+  is(msg.length, 3, "three message nodes displayed");
+  is(msg[2].style.marginLeft, "0px", "correct group indent found");
+  content.console.groupCollapsed("b");
+  findLogEntry("b");
+  let msg = outputNode.querySelectorAll(".webconsole-msg-icon-container");
+  is(msg.length, 4, "four message nodes displayed");
+  is(msg[3].style.marginLeft, GROUP_INDENT + "px", "correct group indent found");
+
+  finishTest();
+}
+
--- a/browser/devtools/webconsole/test/browser/test-console-extras.html
+++ b/browser/devtools/webconsole/test/browser/test-console-extras.html
@@ -5,19 +5,16 @@
       function test() {
         console.log("start");
         console.time();
         console.timeEnd()
         console.exception()
         console.assert()
         console.clear()
         console.dirxml()
-        console.group()
-        console.groupCollapsed()
-        console.groupEnd()
         console.profile()
         console.profileEnd()
         console.count()
         console.table()
         console.log("end");
       }
     </script>
   </head>
--- a/browser/locales/en-US/chrome/browser/inspector.properties
+++ b/browser/locales/en-US/chrome/browser/inspector.properties
@@ -2,8 +2,23 @@
 # the user tries to navigate away from a web page, to confirm the change of
 # page.
 confirmNavigationAway.message=Leaving this page will close the Inspector and the changes you have made will be lost.
 confirmNavigationAway.buttonLeave=Leave Page
 confirmNavigationAway.buttonLeaveAccesskey=L
 confirmNavigationAway.buttonStay=Stay on Page
 confirmNavigationAway.buttonStayAccesskey=S
 
+# LOCALIZATION NOTE (htmlPanel): Used in the Inspector tool's openInspectorUI
+# method when registering the HTML panel.
+
+# LOCALIZATION NOTE (htmlPanel.label): This is a lable for a button that
+# activates the Web Developer->Inspect UI's HTML Tree Panel.
+htmlPanel.label=HTML
+
+# LOCALIZATION NOTE (htmlPanel.tooltiptext): The text that appears when a user
+# hovers over the HTML panel's toolbar button.
+htmlPanel.tooltiptext=HTML panel
+
+# LOCALIZATION NOTE (htmlPanel.accesskey): The key bound to the HTML panel's
+# toolbar button.
+htmlPanel.accesskey=H
+
--- a/browser/locales/en-US/chrome/browser/styleinspector.properties
+++ b/browser/locales/en-US/chrome/browser/styleinspector.properties
@@ -36,8 +36,14 @@ rule.sourceElement=element
 # these are the category names.
 group.Text_Fonts_and_Color=Text, Fonts & Color
 group.Background=Background
 group.Dimensions=Dimensions
 group.Positioning_and_Page_Flow=Positioning and Page Flow
 group.Borders=Borders
 group.Lists=Lists
 group.Effects_and_Other=Effects and Other
+
+# LOCALIZATION NOTE (style.highlighter.button): These strings are used inside
+# html tree of the highlighter for the style inspector button
+style.highlighter.button.label=Style
+style.highlighter.accesskey=S
+style.highlighter.button.tooltip=Inspect element styles
--- a/browser/themes/gnomestripe/browser/browser.css
+++ b/browser/themes/gnomestripe/browser/browser.css
@@ -1937,8 +1937,17 @@ panel[dimmed="true"] {
   outline: 1px dashed rgba(255,255,255,0.5);
   outline-offset: -1px;
 }
 
 #highlighter-veil-container[locked] > #highlighter-veil-middlebox > #highlighter-veil-transparentbox {
   box-shadow: 0 0 0 1px black;
   outline-color: white;
 }
+
+/*
+ * need a "bumpy" background image for this!
+ */
+#inspector-horizontal-splitter {
+  background: none !important;
+  -moz-appearance: none;
+  cursor: n-resize;
+}
--- a/browser/themes/pinstripe/browser/browser.css
+++ b/browser/themes/pinstripe/browser/browser.css
@@ -2597,8 +2597,18 @@ panel[dimmed="true"] {
   background: -moz-linear-gradient(hsla(220,6%,10%,.6), hsla(210,11%,18%,.45) 75%, hsla(210,11%,30%,.4));
   box-shadow: 0 1px 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-inspect-toolbutton[checked]:hover:active,
 #inspector-tools > toolbarbutton[checked]:hover:active {
   background-color: hsla(210,8%,5%,.2);
 }
+
+/*
+ * need a "bumpy" background image for this!
+ */
+#inspector-horizontal-splitter {
+  background: none !important;
+  -moz-appearance: none;
+  cursor: n-resize;
+}
+
--- a/browser/themes/winstripe/browser/browser.css
+++ b/browser/themes/winstripe/browser/browser.css
@@ -2553,8 +2553,18 @@ panel[dimmed="true"] {
   outline: 1px dashed rgba(255,255,255,0.5);
   outline-offset: -1px;
 }
 
 #highlighter-veil-container[locked] > #highlighter-veil-middlebox > #highlighter-veil-transparentbox {
   box-shadow: 0 0 0 1px black;
   outline-color: white;
 }
+
+/*
+ * need a "bumpy" background image for this!
+ */
+#inspector-horizontal-splitter {
+  background: none !important;
+  -moz-appearance: none;
+  cursor: n-resize;
+}
+
--- a/dom/base/ConsoleAPI.js
+++ b/dom/base/ConsoleAPI.js
@@ -89,24 +89,36 @@ ConsoleAPI.prototype = {
       },
       trace: function CA_trace() {
         self.notifyObservers(outerID, innerID, "trace", self.getStackTrace());
       },
       // Displays an interactive listing of all the properties of an object.
       dir: function CA_dir() {
         self.notifyObservers(outerID, innerID, "dir", arguments);
       },
+      group: function CA_group() {
+        self.notifyObservers(outerID, innerID, "group", self.beginGroup(arguments));
+      },
+      groupCollapsed: function CA_groupCollapsed() {
+        self.notifyObservers(outerID, innerID, "groupCollapsed", self.beginGroup(arguments));
+      },
+      groupEnd: function CA_groupEnd() {
+        self.notifyObservers(outerID, innerID, "groupEnd", arguments);
+      },
       __exposedProps__: {
         log: "r",
         info: "r",
         warn: "r",
         error: "r",
         debug: "r",
         trace: "r",
-        dir: "r"
+        dir: "r",
+        group: "r",
+        groupCollapsed: "r",
+        groupEnd: "r"
       }
     };
 
     // We need to return an actual content object here, instead of a wrapped
     // chrome object. This allows things like console.log.bind() to work.
     let contentObj = Cu.createObjectIn(aWindow);
     function genPropDesc(fun) {
       return { enumerable: true, configurable: true, writable: true,
@@ -115,16 +127,19 @@ ConsoleAPI.prototype = {
     const properties = {
       log: genPropDesc('log'),
       info: genPropDesc('info'),
       warn: genPropDesc('warn'),
       error: genPropDesc('error'),
       debug: genPropDesc('debug'),
       trace: genPropDesc('trace'),
       dir: genPropDesc('dir'),
+      group: genPropDesc('group'),
+      groupCollapsed: genPropDesc('groupCollapsed'),
+      groupEnd: genPropDesc('groupEnd'),
       __noSuchMethod__: { enumerable: true, configurable: true, writable: true,
                           value: function() {} },
       __mozillaConsole__: { value: true }
     };
 
     Object.defineProperties(contentObj, properties);
     Cu.makeObjectPropsNormal(contentObj);
 
@@ -224,12 +239,19 @@ ConsoleAPI.prototype = {
           lineNumber: frame.lineNumber,
           functionName: frame.name,
           language: frame.language,
         });
       }
     }
 
     return stack;
+  },
+
+  /**
+   * Begin a new group for logging output together.
+   **/
+  beginGroup: function CA_beginGroup() {
+    return Array.prototype.join.call(arguments[0], " ");
   }
 };
 
 let NSGetFactory = XPCOMUtils.generateNSGetFactory([ConsoleAPI]);
--- a/dom/tests/browser/browser_ConsoleAPITests.js
+++ b/dom/tests/browser/browser_ConsoleAPITests.js
@@ -103,19 +103,62 @@ function testLocationData(aMessageObject
   is(aMessageObject.filename, gArgs[0].filename, "filename matches");
   is(aMessageObject.lineNumber, gArgs[0].lineNumber, "lineNumber matches");
   is(aMessageObject.functionName, gArgs[0].functionName, "functionName matches");
   is(aMessageObject.arguments.length, gArgs[0].arguments.length, "arguments.length matches");
   gArgs[0].arguments.forEach(function (a, i) {
     is(aMessageObject.arguments[i], a, "correct arg " + i);
   });
 
-  // Test finished
-  ConsoleObserver.destroy();
-  finish();
+  startGroupTest();
+}
+
+function startGroupTest() {
+  // Reset the observer function to cope with the fabricated test data.
+  ConsoleObserver.observe = function CO_observe(aSubject, aTopic, aData) {
+    try {
+      testConsoleGroup(aSubject.wrappedJSObject);
+    } catch (ex) {
+      // XXX Exceptions in this function currently aren't reported, because of
+      // some XPConnect weirdness, so report them manually
+      ok(false, "Exception thrown in CO_observe: " + ex);
+    }
+  };
+  let button = gWindow.document.getElementById("test-groups");
+  ok(button, "found #test-groups button");
+  EventUtils.synthesizeMouse(button, 2, 2, {}, gWindow);
+}
+
+function testConsoleGroup(aMessageObject) {
+  let messageWindow = getWindowByWindowId(aMessageObject.ID);
+  is(messageWindow, gWindow, "found correct window by window ID");
+
+  ok(aMessageObject.level == "group" ||
+     aMessageObject.level == "groupCollapsed" ||
+     aMessageObject.level == "groupEnd",
+     "expected level received");
+
+  is(aMessageObject.functionName, "testGroups", "functionName matches");
+  ok(aMessageObject.lineNumber >= 32 && aMessageObject.lineNumber <= 34,
+     "lineNumber matches");
+  if (aMessageObject.level == "groupCollapsed") {
+    ok(aMessageObject.arguments == "a group", "groupCollapsed arguments matches");
+  }
+  else if (aMessageObject.level == "group") {
+    ok(aMessageObject.arguments == "b group", "group arguments matches");
+  }
+  else if (aMessageObject.level == "groupEnd") {
+    ok(Array.prototype.join.call(aMessageObject.arguments, " ") == "b group", "groupEnd arguments matches");
+  }
+
+  if (aMessageObject.level == "groupEnd") {
+    // Test finished
+    ConsoleObserver.destroy();
+    finish();
+  }
 }
 
 function startTraceTest() {
   gLevel = "trace";
   gArgs = [
     {filename: TEST_URI, lineNumber: 6, functionName: null, language: 2},
     {filename: TEST_URI, lineNumber: 11, functionName: "foobar585956b", language: 2},
     {filename: TEST_URI, lineNumber: 15, functionName: "foobar585956a", language: 2},
@@ -190,16 +233,19 @@ function consoleAPISanityTest() {
   ok(win.console, "we have a console attached, 2nd attempt");
 
   ok(win.console.log, "console.log is here");
   ok(win.console.info, "console.info is here");
   ok(win.console.warn, "console.warn is here");
   ok(win.console.error, "console.error is here");
   ok(win.console.trace, "console.trace is here");
   ok(win.console.dir, "console.dir is here");
+  ok(win.console.group, "console.group is here");
+  ok(win.console.groupCollapsed, "console.groupCollapsed is here");
+  ok(win.console.groupEnd, "console.groupEnd is here");
 }
 
 var ConsoleObserver = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
 
   init: function CO_init() {
     Services.obs.addObserver(this, "console-api-log-event", false);
   },
--- a/dom/tests/browser/test-console-api.html
+++ b/dom/tests/browser/test-console-api.html
@@ -22,17 +22,24 @@
       function test() {
         var str = "Test Message."
         console.foobar(str); // if this throws, we don't execute following funcs
         console.log(str);
         console.info(str);
         console.warn(str);
         console.error(str);
       }
+
+      function testGroups() {
+        console.groupCollapsed("a", "group");
+        console.group("b", "group");
+        console.groupEnd("b", "group");
+      }
     </script>
   </head>
   <body>
     <h1>Console API Test Page</h1>
     <button onclick="test();">Log stuff</button>
     <button id="test-trace" onclick="foobar585956a('omg');">Test trace</button>
     <button id="test-location" onclick="foobar646025('omg');">Test location</button>
+    <button id="test-groups" onclick="testGroups();">Test groups</button>
   </body>
 </html>
--- a/dom/tests/mochitest/general/test_consoleAPI.html
+++ b/dom/tests/mochitest/general/test_consoleAPI.html
@@ -22,16 +22,19 @@ function doTest() {
   var expectedProps = {
     "log": "function",
     "info": "function",
     "warn": "function",
     "error": "function",
     "debug": "function",
     "trace": "function",
     "dir": "function",
+    "group": "function",
+    "groupCollapsed": "function",
+    "groupEnd": "function",
     "__noSuchMethod__": "function"
   };
 
   var foundProps = 0;
   for (var prop in console) {
     foundProps++;
     is(typeof(console[prop]), expectedProps[prop], "expect console prop " + prop + " exists");
   }
--- a/toolkit/mozapps/extensions/content/selectAddons.xml
+++ b/toolkit/mozapps/extensions/content/selectAddons.xml
@@ -48,16 +48,17 @@
 <bindings xmlns="http://www.mozilla.org/xbl"
           xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
           xmlns:xbl="http://www.mozilla.org/xbl">
 
   <binding id="addon-select">
     <content>
       <xul:hbox class="select-keep select-cell">
         <xul:checkbox class="addon-keep-checkbox" anonid="keep"
+                      xbl:inherits="tooltiptext=name"
                       oncommand="document.getBindingParent(this).keepChanged();"/>
       </xul:hbox>
       <xul:hbox class="select-icon select-cell">
         <xul:image class="addon-icon" xbl:inherits="type"/>
       </xul:hbox>
       <xul:hbox class="select-name select-cell">
         <xul:label class="addon-name" crop="end" style="&select.name.style;"
                    xbl:inherits="xbl:text=name"/>
--- a/toolkit/themes/gnomestripe/mozapps/extensions/selectAddons.css
+++ b/toolkit/themes/gnomestripe/mozapps/extensions/selectAddons.css
@@ -109,16 +109,20 @@
 .select-keep {
   -moz-box-pack: center;
 }
 
 .select-keep .checkbox-label-box {
   display: none;
 }
 
+.select-keep .addon-keep-checkbox:-moz-focusring {
+  outline: 1px dotted ThreeDDarkShadow;
+}
+
 .select-icon {
   width: 20px;
 }
 
 #select-grid separator {
   display: none;
 }
 
--- a/toolkit/themes/winstripe/mozapps/extensions/selectAddons.css
+++ b/toolkit/themes/winstripe/mozapps/extensions/selectAddons.css
@@ -119,16 +119,20 @@
 }
 
 .select-keep .addon-keep-checkbox {
   margin: 0;
   padding: 0;
   width: 13px;
 }
 
+.select-keep .addon-keep-checkbox:-moz-focusring {
+  outline: 1px dotted ThreeDDarkShadow;
+}
+
 .select-keep .checkbox-label-box {
   display: none;
 }
 
 .addon-name,
 .addon-action-message,
 .addon-action-update {
   -moz-box-sizing: border-box;