Bug 650794 - Disable HTML panel and make it use registerTools API; r=msucan, gavin
☠☠ backed out by aa17cb6e4d1b ☠ ☠
authorRob Campbell <rcampbell@mozilla.com>
Fri, 23 Sep 2011 13:51:51 -0300
changeset 77399 121518f3df44e8837ef64e7536717a8fdeb36006
parent 77398 f1409901573ab504ffa35030198084679c0d8154
child 77400 b1c0b12a5f6512e335685f6dcf75694e5ed8d81c
child 77445 aa17cb6e4d1bae2453ecd78013bc0b18f5d4fa60
push id3
push userfelipc@gmail.com
push dateFri, 30 Sep 2011 20:09:13 +0000
reviewersmsucan, gavin
bugs650794
milestone9.0a1
Bug 650794 - Disable HTML panel and make it use registerTools API; r=msucan, gavin
browser/base/content/browser.xul
browser/devtools/highlighter/InsideOutBox.jsm
browser/devtools/highlighter/Makefile.in
browser/devtools/highlighter/TreePanel.jsm
browser/devtools/highlighter/insideOutBox.js
browser/devtools/highlighter/inspector.js
browser/devtools/highlighter/test/Makefile.in
browser/devtools/highlighter/test/browser_inspector_bug_566084_location_changed.js
browser/devtools/highlighter/test/browser_inspector_editor.js
browser/devtools/highlighter/test/browser_inspector_iframeTest.js
browser/devtools/highlighter/test/browser_inspector_initialization.js
browser/devtools/highlighter/test/browser_inspector_tab_switch.js
browser/devtools/highlighter/test/browser_inspector_treePanel_click.js
browser/devtools/highlighter/test/browser_inspector_treePanel_output.js
browser/devtools/jar.mn
browser/devtools/scratchpad/Makefile.in
browser/locales/en-US/chrome/browser/inspector.properties
--- 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
@@ -41,15 +41,17 @@ DEPTH		= ../../..
 topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 EXTRA_JS_MODULES = \
 	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,694 @@
+/* -*- 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"];
+
+/**
+ * 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
+
+  /**
+   * container.
+   * @returns xul:panel element
+   */
+  get container()
+  {
+    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.container,
+      unregister: this.destroy,
+    };
+    this.editingEvents = {};
+
+    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.window.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;
+    this.container.hidden = false;
+
+    this.treeIFrame = this.document.getElementById("inspector-tree-iframe");
+    if (!this.treeIFrame) {
+      let resizerBox = this.document.getElementById("tree-panel-resizer-box");
+      this.treeIFrame = this.document.createElement("iframe");
+      this.treeIFrame.setAttribute("id", "inspector-tree-iframe");
+      this.treeIFrame.setAttribute("flex", "1");
+      this.treeIFrame.setAttribute("type", "content");
+      this.treeIFrame = this.container.insertBefore(this.treeIFrame, resizerBox);
+    }
+
+    let self = this;
+    this.container.addEventListener("popupshown", function treePanelShown() {
+      self.container.removeEventListener("popupshown",
+        treePanelShown, false);
+
+        self.treeIFrame.addEventListener("load",
+          function loadedInitializeTreePanel() {
+            self.treeIFrame.removeEventListener("load",
+              loadedInitializeTreePanel, true);
+            self.initializeIFrame();
+          }, true);
+
+      let src = self.treeIFrame.getAttribute("src");
+      if (src != "chrome://browser/content/inspector.html") {
+        self.treeIFrame.setAttribute("src",
+          "chrome://browser/content/inspector.html");
+      } else {
+        self.treeIFrame.contentWindow.location.reload();
+      }
+    }, 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);
+  },
+
+  /**
+   * Close the TreePanel.
+   */
+  close: function TP_close()
+  {
+    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;
+    this.container.hidePopup();
+  },
+
+  /**
+   * Is the TreePanel open?
+   * @returns boolean
+   */
+  isOpen: function TP_isOpen()
+  {
+    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.window.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.window.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.window.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.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;
+    }
+
+    this.container.removeEventListener("popuphiding", this._boundClose, false);
+    delete this._boundClose;
+  }
+};
+
--- a/browser/devtools/highlighter/inspector.js
+++ b/browser/devtools/highlighter/inspector.js
@@ -21,35 +21,33 @@
  * 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
 
-#include insideOutBox.js
-
 const INSPECTOR_INVISIBLE_ELEMENTS = {
   "head": true,
   "base": true,
   "basefont": true,
   "isindex": true,
   "link": true,
   "meta": true,
   "script": true,
@@ -67,16 +65,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
@@ -549,34 +550,33 @@ Highlighter.prototype = {
 
 /**
  * Main controller class for the Inspector.
  */
 var InspectorUI = {
   browser: null,
   tools: {},
   toolEvents: {},
-  showTextNodesWithWhitespace: false,
   inspecting: false,
-  treeLoaded: false,
+  treePanelEnabled: true,
   get enabled()
   {
     return gPrefService.getBoolPref("devtools.inspector.enabled");
   },
   isDirty: false,
 
   /**
    * 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
@@ -587,216 +587,46 @@ 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;
   },
 
   /**
    * 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.treeLoaded && this.highlighter && aNode) {
+    if (this.highlighter && 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,
@@ -816,27 +646,30 @@ var InspectorUI = {
     // Start initialization.
     this.browser = gBrowser.selectedBrowser;
     this.win = this.browser.contentWindow;
     this.winID = this.getWindowID(this.win);
     this.toolbar = document.getElementById("inspector-toolbar");
 
     this.initTools();
 
-    if (!this.domplate) {
-      Cu.import("resource:///modules/domplate.jsm", this);
-      this.domplateUtils.setDOM(window);
+    if (!this.TreePanel && this.treePanelEnabled) {
+      Cu.import("resource:///modules/TreePanel.jsm", this);
+      this.treePanel = new this.TreePanel(window, this);
     }
 
-    this.openTreePanel();
+    this.toolbar.hidden = false;
+    this.inspectMenuitem.setAttribute("checked", true);
 
-    this.toolbar.hidden = false;
-    this.inspectCmd.setAttribute("checked", true);
+    this.isDirty = false;
 
     gBrowser.addProgressListener(InspectorProgressListener);
+
+    // initialize the highlighter
+    this.initializeHighlighter();
   },
 
   /**
    * Register and initialize any included tools.
    */
   initTools: function IUI_initTools()
   {
     // Style inspector
@@ -880,21 +713,23 @@ var InspectorUI = {
       gBrowser.tabContainer.addEventListener("TabSelect", this, false);
 
     // Has this windowID been inspected before?
     if (InspectorStore.hasID(this.winID)) {
       let selectedNode = InspectorStore.getValue(this.winID, "selectedNode");
       if (selectedNode) {
         this.inspectNode(selectedNode);
       }
+      this.isDirty = InspectorStore.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);
+      InspectorStore.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.
@@ -903,18 +738,18 @@ 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;
 
@@ -925,16 +760,17 @@ var InspectorUI = {
       this.win.removeEventListener("pagehide", this, true);
     } else {
       // Update the store before closing.
       if (this.selection) {
         InspectorStore.setValue(this.winID, "selectedNode",
           this.selection);
       }
       InspectorStore.setValue(this.winID, "inspecting", this.inspecting);
+      InspectorStore.setValue(this.winID, "isDirty", this.isDirty);
     }
 
     if (InspectorStore.isEmpty()) {
       gBrowser.tabContainer.removeEventListener("TabSelect", this, false);
     }
 
     this.stopInspecting();
 
@@ -943,68 +779,42 @@ var InspectorUI = {
       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.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
@@ -1013,17 +823,17 @@ var InspectorUI = {
    *        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);
     }
@@ -1031,38 +841,37 @@ var InspectorUI = {
   },
 
   /**
    * 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.toolsSelect();
+    this.toolsSelect(aScroll);
   },
 
   /////////////////////////////////////////////////////////////////////////
   //// Event Handling
 
   highlighterReady: function IUI_highlighterReady()
   {
     // Setup the InspectorStore or restore state
@@ -1088,17 +897,17 @@ var InspectorUI = {
   {
     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) {
+        if (this.isInspectorOpen && winID != this.winID) {
           this.closeInspectorUI(true);
           inspectorClosed = true;
         }
 
         if (winID && InspectorStore.hasID(winID)) {
           if (inspectorClosed && this.closing) {
             Services.obs.addObserver(function reopenInspectorForTab() {
               Services.obs.removeObserver(reopenInspectorForTab,
@@ -1144,275 +953,16 @@ var InspectorUI = {
             }
             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.toolsSelect();
-          this.stopInspecting(true);
-        } else {
-          this.select(node, true, false);
-          this.highlighter.highlightNode(node);
-          this.toolsSelect();
-        }
-      }
-    }
-  },
-
-  /**
-   * 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();
   },
@@ -1507,57 +1057,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) {
@@ -1570,42 +1079,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);
   },
 
@@ -1803,22 +1286,24 @@ var InspectorUI = {
         }
       }.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()
+  toolsSelect: function IUI_toolsSelect(aScroll)
   {
     this.toolsDo(function IUI_toolsOnSelect(aTool) {
       if (aTool.isOpen) {
-        aTool.onSelect.call(aTool.context, InspectorUI.selection);
+        aTool.onSelect.call(aTool.context, InspectorUI.selection, aScroll);
       }
     });
   },
 
   /**
    * Dim or undim each tool in the tools collection
    * @param aState true = dim, false = undim
    */
@@ -1986,17 +1471,17 @@ var InspectorStore = {
  * confirm page navigation, such that he's given the chance to prevent the loss
  * of edits.
  */
 var InspectorProgressListener = {
   onStateChange:
   function IPL_onStateChange(aProgress, aRequest, aFlag, aStatus)
   {
     // Remove myself if the Inspector is no longer open.
-    if (!InspectorUI.isTreePanelOpen) {
+    if (!InspectorUI.isInspectorOpen) {
       gBrowser.removeProgressListener(InspectorProgressListener);
       return;
     }
 
     // Skip non-start states.
     if (!(aFlag & Ci.nsIWebProgressListener.STATE_START)) {
       return;
     }
@@ -2084,20 +1569,24 @@ var InspectorProgressListener = {
     // transient notification removal.
     notification.persistence = -1;
   },
 };
 
 /////////////////////////////////////////////////////////////////////////
 //// Initializers
 
-XPCOMUtils.defineLazyGetter(InspectorUI, "inspectCmd", function () {
+XPCOMUtils.defineLazyGetter(InspectorUI, "inspectMenuitem", function () {
   return document.getElementById("Tools:Inspect");
 });
 
+XPCOMUtils.defineLazyGetter(InspectorUI, "inspectToolbutton", function () {
+  return document.getElementById("inspector-inspect-toolbutton");
+});
+
 XPCOMUtils.defineLazyGetter(InspectorUI, "strings", function () {
   return Services.strings.
          createBundle("chrome://browser/locale/inspector.properties");
 });
 
 XPCOMUtils.defineLazyGetter(InspectorUI, "StyleInspector", function () {
   var obj = {};
   Cu.import("resource:///modules/devtools/StyleInspector.jsm", obj);
--- a/browser/devtools/highlighter/test/Makefile.in
+++ b/browser/devtools/highlighter/test/Makefile.in
@@ -56,10 +56,12 @@ 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)
 
+# 		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
@@ -12,17 +12,17 @@ function startLocationTests() {
 function runInspectorTests() {
   Services.obs.removeObserver(runInspectorTests, 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_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, INSPECTOR_NOTIFICATIONS.OPENED, false);
   InspectorUI.toggleInspectorUI();
 }
 
+function setupHTMLPanel()
+{
+  Services.obs.removeObserver(setupHTMLPanel, INSPECTOR_NOTIFICATIONS.OPENED);
+  Services.obs.addObserver(runEditorTests, INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
+  InspectorUI.treePanel.open();
+}
+
 function runEditorTests()
 {
-  Services.obs.removeObserver(runEditorTests, INSPECTOR_NOTIFICATIONS.OPENED, false);
+  Services.obs.removeObserver(runEditorTests, 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);
 
   // 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,93 +127,93 @@ 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);
 
   executeSoon(finishUp);
--- 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 () {
@@ -94,17 +93,16 @@ function setupIframeTests()
 function runIframeTests()
 {
   Services.obs.removeObserver(runIframeTests,
     INSPECTOR_NOTIFICATIONS.OPENED, false);
 
   Services.obs.addObserver(performTestComparisons1,
     INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
 
-  highlighterFrame = InspectorUI.highlighter.iframe;
   executeSoon(moveMouseOver.bind(this, div1));
 }
 
 function performTestComparisons1()
 {
   Services.obs.removeObserver(performTestComparisons1,
     INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
   Services.obs.addObserver(performTestComparisons2,
--- a/browser/devtools/highlighter/test/browser_inspector_initialization.js
+++ b/browser/devtools/highlighter/test/browser_inspector_initialization.js
@@ -63,26 +63,37 @@ function startInspectorTests()
   Services.obs.addObserver(runInspectorTests,
     INSPECTOR_NOTIFICATIONS.OPENED, false);
   InspectorUI.toggleInspectorUI();
 }
 
 function runInspectorTests()
 {
   Services.obs.removeObserver(runInspectorTests,
-    INSPECTOR_NOTIFICATIONS.OPENED, false);
+    INSPECTOR_NOTIFICATIONS.OPENED);
+  Services.obs.addObserver(treePanelTests,
+    INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
+
+  ok(InspectorUI.toolbar, "we have the toolbar.");
+  ok(!InspectorUI.toolbar.hidden, "toolbar is visible");
+  ok(InspectorUI.inspecting, "Inspector is inspecting");
+  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,
+    INSPECTOR_NOTIFICATIONS.TREEPANELREADY);
   Services.obs.addObserver(runContextMenuTest,
     INSPECTOR_NOTIFICATIONS.CLOSED, false);
 
-  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.highlighter, "Highlighter is up");
+  ok(InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is open");
 
   executeSoon(function() {
     InspectorUI.closeInspectorUI();
   });
 }
 
 function runContextMenuTest()
 {
@@ -105,17 +116,17 @@ function runContextMenuTest()
 }
 
 function inspectNodesFromContextTest()
 {
   Services.obs.removeObserver(inspectNodesFromContextTest, INSPECTOR_NOTIFICATIONS.OPENED, false);
   Services.obs.addObserver(openInspectorForContextTest, 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");
   executeSoon(function() {
     InspectorUI.closeInspectorUI(true);
   });
 }
 
 function openInspectorForContextTest()
@@ -157,19 +168,19 @@ function inspectNodesFromContextTestTrap
 }
 
 function finishInspectorTests()
 {
   Services.obs.removeObserver(finishInspectorTests,
     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_tab_switch.js
+++ b/browser/devtools/highlighter/test/browser_inspector_tab_switch.js
@@ -56,17 +56,17 @@ function inspectorTabOpen1()
 
 function inspectorUIOpen1()
 {
   Services.obs.removeObserver(inspectorUIOpen1,
     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(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
   ok(!InspectorStore.isEmpty(), "InspectorStore is not empty");
   is(InspectorStore.length, 1, "InspectorStore.length = 1");
 
   // Highlight a node.
   div = content.document.getElementsByTagName("div")[0];
   InspectorUI.inspectNode(div);
   is(InspectorUI.selection, div, "selection matches the div element");
 
@@ -82,17 +82,17 @@ 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");
+  ok(!InspectorUI.treePanel, "Inspector Tree Panel is closed");
   is(InspectorStore.length, 1, "InspectorStore.length = 1");
 
   // Activate the inspector again.
   executeSoon(function() {
     Services.obs.addObserver(inspectorUIOpen2,
       INSPECTOR_NOTIFICATIONS.OPENED, false);
     InspectorUI.openInspectorUI();
   });
@@ -100,17 +100,17 @@ function inspectorTabOpen2()
 
 function inspectorUIOpen2()
 {
   Services.obs.removeObserver(inspectorUIOpen2,
     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(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
   is(InspectorStore.length, 2, "InspectorStore.length = 2");
 
   // Disable highlighting.
   InspectorUI.toggleInspection();
   ok(!InspectorUI.inspecting, "Inspector is not highlighting");
 
   // Switch back to tab 1.
   executeSoon(function() {
@@ -122,51 +122,100 @@ function inspectorUIOpen2()
 
 function inspectorFocusTab1()
 {
   Services.obs.removeObserver(inspectorFocusTab1,
     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");
+  ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
+  is(InspectorStore.length, 2, "InspectorStore.length = 2");
+  is(InspectorUI.selection, div, "selection matches the div element");
+
+  Services.obs.addObserver(inspectorOpenTreePanelTab1,
+    INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
+
+  InspectorUI.treePanel.open();
+}
+
+function inspectorOpenTreePanelTab1()
+{
+  Services.obs.removeObserver(inspectorOpenTreePanelTab1,
+    INSPECTOR_NOTIFICATIONS.TREEPANELREADY);
+
+  ok(InspectorUI.inspecting, "Inspector is highlighting");
+  ok(InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is open");
   is(InspectorStore.length, 2, "InspectorStore.length = 2");
   is(InspectorUI.selection, div, "selection matches the div element");
 
   // Switch back to tab 2.
   Services.obs.addObserver(inspectorFocusTab2,
     INSPECTOR_NOTIFICATIONS.OPENED, false);
   gBrowser.selectedTab = tab2;
 }
 
 function inspectorFocusTab2()
 {
   Services.obs.removeObserver(inspectorFocusTab2,
     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");
+  ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
+  is(InspectorStore.length, 2, "InspectorStore.length = 2");
+  isnot(InspectorUI.selection, div, "selection does not match the div element");
+
+  // Switch back to tab 1.
+  Services.obs.addObserver(inspectorSecondFocusTab1,
+    INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
+  gBrowser.selectedTab = tab1;
+}
+
+function inspectorSecondFocusTab1()
+{
+  Services.obs.removeObserver(inspectorSecondFocusTab1,
+    INSPECTOR_NOTIFICATIONS.TREEPANELREADY);
+
+  ok(InspectorUI.inspecting, "Inspector is highlighting");
+  ok(InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is open");
+  is(InspectorStore.length, 2, "InspectorStore.length = 2");
+  is(InspectorUI.selection, div, "selection matches the div element");
+
+  // Switch back to tab 2.
+  Services.obs.addObserver(inspectorSecondFocusTab2,
+    INSPECTOR_NOTIFICATIONS.OPENED, false);
+  gBrowser.selectedTab = tab2;
+}
+
+function inspectorSecondFocusTab2()
+{
+  Services.obs.removeObserver(inspectorSecondFocusTab2,
+    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(InspectorStore.length, 2, "InspectorStore.length = 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");
+  ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
   is(InspectorStore.length, 1, "InspectorStore.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,49 +12,53 @@ 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);
     InspectorUI.toggleInspectorUI();
   }
 
   function runTests() {
     Services.obs.removeObserver(runTests, INSPECTOR_NOTIFICATIONS.OPENED);
-    testNode1();
+    Services.obs.addObserver(testNode1, 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, 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, INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, false);
+    InspectorUI.treePanelSelect("node2");
+  }
+
+  function testHighlightingNode2() {
+    dump("testHighlightingNode2\n")
+    Services.obs.removeObserver(testHighlightingNode2, INSPECTOR_NOTIFICATIONS.HIGHLIGHTING);
+    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();
   }
 
   function finishUp() {
     Services.obs.removeObserver(finishUp, 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
@@ -66,53 +66,62 @@ function xhr_onReadyStateChange() {
   Services.obs.addObserver(inspectorOpened,
     INSPECTOR_NOTIFICATIONS.OPENED, false);
   InspectorUI.openInspectorUI();
 }
 
 function inspectorOpened()
 {
   Services.obs.removeObserver(inspectorOpened,
-    INSPECTOR_NOTIFICATIONS.OPENED, false);
+    INSPECTOR_NOTIFICATIONS.OPENED);
+
+  Services.obs.addObserver(treePanelOpened, INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
+  InspectorUI.treePanel.open();
+}
+
+function treePanelOpened()
+{
+  Services.obs.removeObserver(treePanelOpened,
+    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.closeInspectorUI();
 }
 
 function inspectorClosed()
 {
   Services.obs.removeObserver(inspectorClosed,
     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/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/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): A button label that appears on the
+# InspectorUI's toolbar.
+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
+