bug 560692 - Create a style panel for web page inspector. p=me, r=gavin
authorRob Campbell <rcampbell@mozilla.com>
Fri, 16 Jul 2010 11:12:39 -0300
changeset 47805 5575b3579d2f63bdb0197cb9fa81c4bb646a05cc
parent 47790 96de199027d7e7c09a98b68da0464d43327049ca
child 47806 66ed85eff8efe277fac5e24272a8a1bac8ce7a84
push id14426
push userrcampbell@mozilla.com
push dateFri, 16 Jul 2010 14:16:39 +0000
treeherdermozilla-central@cdeaa6dff0a7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgavin
bugs560692
milestone2.0b2pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 560692 - Create a style panel for web page inspector. p=me, r=gavin
browser/base/Makefile.in
browser/base/content/browser-sets.inc
browser/base/content/browser.css
browser/base/content/browser.xul
browser/base/content/inspector.js
browser/base/content/stylePanel.jsm
browser/base/content/test/Makefile.in
browser/base/content/test/browser_inspector_initialization.js
browser/base/content/test/browser_inspector_stylePanel.js
browser/locales/en-US/chrome/browser/browser.dtd
browser/locales/en-US/chrome/browser/inspector.properties
browser/locales/jar.mn
browser/themes/gnomestripe/browser/browser.css
browser/themes/pinstripe/browser/browser.css
browser/themes/winstripe/browser/browser.css
--- a/browser/base/Makefile.in
+++ b/browser/base/Makefile.in
@@ -51,16 +51,17 @@ CHROME_DEPS += $(abs_srcdir)/content/ove
 
 ifdef ENABLE_TESTS
 DIRS += content/test
 endif
 
 EXTRA_JS_MODULES = \
 	content/openLocationLastURL.jsm \
 	content/NetworkPrioritizer.jsm \
+	content/stylePanel.jsm \
 	$(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
 PRE_RELEASE_SUFFIX := $(shell $(PYTHON) $(topsrcdir)/config/printprereleasesuffix.py \
                         $(shell cat $(srcdir)/../config/version.txt))
 
 DEFINES += \
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -136,16 +136,29 @@
 
   <commandset id="placesCommands">
     <command id="Browser:ShowAllBookmarks"
              oncommand="PlacesCommandHook.showPlacesOrganizer('AllBookmarks');"/>
     <command id="Browser:ShowAllHistory"
              oncommand="PlacesCommandHook.showPlacesOrganizer('History');"/>
   </commandset>
 
+  <commandset id="inspectorCommands">
+    <command id="Inspector:Inspect"
+             oncommand="InspectorUI.toggleInspection();"/>
+    <command id="Inspector:Previous"
+             oncommand="InspectorUI.inspectPrevious();"
+             disabled="true"/>
+    <command id="Inspector:Next"
+             oncommand="InspectorUI.inspectNext();"
+             disabled="true"/>
+    <command id="Inspector:Style"
+             oncommand="InspectorUI.toggleStylePanel();"/>
+  </commandset>
+
   <broadcasterset id="mainBroadcasterSet">
     <broadcaster id="viewBookmarksSidebar" autoCheck="false" label="&bookmarksButton.label;"
                  type="checkbox" group="sidebar" sidebarurl="chrome://browser/content/bookmarks/bookmarksPanel.xul"
                  oncommand="toggleSidebar('viewBookmarksSidebar');"/>
 
     <!-- for both places and non-places, the sidebar lives at
          chrome://browser/content/history/history-panel.xul so there are no
          problems when switching between versions -->
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -314,22 +314,16 @@ window[chromehidden~="toolbar"] toolbar:
   -moz-binding: url("chrome://browser/content/browser-tabPreviews.xml#allTabs-preview");
 }
 
 #allTabs-tab-close-button {
   -moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-image");
   margin: 0;
 }
 
-/* Inspector / Highlighter */
-
-#highlighter-panel {
-  -moz-appearance: none;
-  -moz-window-shadow: none;
-}
 
 /* notification anchors should only be visible when their associated
    notifications are */
 .notification-anchor-icon {
   display: none;
 }
 
 #notification-popup-box[anchorid="geo-notification-icon"] > #geo-notification-icon,
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -228,29 +228,76 @@
            onmousemove="InspectorUI.highlighter.handleMouseMove(event);"/>
 
     <panel id="inspector-panel"
            orient="vertical"
            hidden="true"
            ignorekeys="true"
            noautofocus="true"
            noautohide="true"
-           level="top"
-           aria-labelledby="inspectPagePanelTitle">
-      <tree id="inspector-tree" class="plain" seltype="single" treelines="true"
-            onselect="InspectorUI.onTreeSelected()" flex="1">
+           level="top">
+      <toolbar id="inspector-toolbar"
+               nowindowdrag="true">
+        <toolbarbutton id="inspector-inspect-toolbutton"
+                       label="&inspectButton.label;"
+                       accesskey="&inspectButton.accesskey;"
+                       class="toolbarbutton-text"
+                       command="Inspector:Inspect"/>
+        <toolbarbutton id="inspector-previous-toolbutton"
+                       label="&inspectPreviousButton.label;"
+                       accesskey="&inspectPreviousButton.accesskey;"
+                       class="toolbarbutton-text"
+                       command="Inspector:Previous"/>
+        <toolbarbutton id="inspector-next-toolbutton"
+                       label="&inspectNextButton.label;"
+                       accesskey="&inspectNextButton.accesskey;"
+                       class="toolbarbutton-text"
+                       command="Inspector:Next"/>
+        <toolbarbutton id="inspector-style-toolbutton"
+                       label="&inspectStyleButton.label;"
+                       accesskey="&inspectStyleButton.accesskey;"
+                       class="toolbarbutton-text"
+                       command="Inspector:Style"/>
+      </toolbar>
+      <tree id="inspector-tree" class="plain"
+            seltype="single"
+            treelines="true"
+            onselect="InspectorUI.onTreeSelected()"
+            flex="1">
         <treecols>
           <treecol id="colNodeName" label="nodeName" primary="true"
                    persist="width,hidden,ordinal" flex="1"/>
           <splitter class="tree-splitter"/>
           <treecol id="colNodeValue" label="nodeValue"
                    persist="width,hidden,ordinal" flex="1"/>
         </treecols>
         <treechildren id="inspector-tree-body"/>
       </tree>
+      <hbox align="end">
+        <spacer flex="1" />
+        <resizer dir="bottomend" />
+      </hbox>
+    </panel>
+
+    <panel id="inspector-style-panel"
+           hidden="true"
+           orient="vertical"
+           ignorekeys="true"
+           noautofocus="true"
+           noautohide="true"
+           level="top">
+        <toolbar id="inspector-style-toolbar"
+                 nowindowdrag="true">
+          <label>&inspectStylePanelTitle.label;</label>
+        </toolbar>
+        <listbox id="inspector-style-listbox" flex="1"/>
+        <hbox align="end">
+          <spacer flex="1" />
+          <resizer dir="bottomend" />
+        </hbox>
     </panel>
 
     <menupopup id="toolbar-context-menu"
                onpopupshowing="onViewToolbarsPopupShowing(event);">
       <menuseparator/>
       <menuitem command="cmd_ToggleTabsOnTop"
                 type="checkbox"
                 label="&viewTabsOnTop.label;"
--- a/browser/base/content/inspector.js
+++ b/browser/base/content/inspector.js
@@ -122,25 +122,25 @@ PanelHighlighter.prototype = {
     } else {
       this.highlightVisibleRegion(rect);
     }
   },
 
   /**
    * Highlight the given node.
    *
-   * @param element
+   * @param aNode
    *        a DOM element to be highlighted
-   * @param params
+   * @param aParams
    *        extra parameters object
    */
-  highlightNode: function PanelHighlighter_highlightNode(element, params)
+  highlightNode: function PanelHighlighter_highlightNode(aNode, aParams)
   {
-    this.node = element;
-    this.highlight(params && params.scroll);
+    this.node = aNode;
+    this.highlight(aParams && aParams.scroll);
   },
 
   /**
    * Highlight the visible region of the region described by aRect, if any.
    *
    * @param aRect
    * @returns boolean
    *          was a region highlighted?
@@ -208,28 +208,28 @@ PanelHighlighter.prototype = {
   get isHighlighting()
   {
     return this.panel.state == "open";
   },
 
   /**
    * Return the midpoint of a line from pointA to pointB.
    *
-   * @param pointA
+   * @param aPointA
    *        An object with x and y properties.
-   * @param pointB
+   * @param aPointB
    *        An object with x and y properties.
    * @returns aPoint
    *          An object with x and y properties.
    */
-  midPoint: function PanelHighlighter_midPoint(pointA, pointB)
+  midPoint: function PanelHighlighter_midPoint(aPointA, aPointB)
   {
     let pointC = { };
-    pointC.x = (pointB.x - pointA.x) / 2 + pointA.x;
-    pointC.y = (pointB.y - pointA.y) / 2 + pointA.y;
+    pointC.x = (aPointB.x - aPointA.x) / 2 + aPointA.x;
+    pointC.y = (aPointB.y - aPointA.y) / 2 + aPointA.y;
     return pointC;
   },
 
   /**
    * Return the node under the highlighter rectangle. Useful for testing.
    * Calculation based on midpoint of diagonal from top left to bottom right
    * of panel.
    *
@@ -298,27 +298,27 @@ PanelHighlighter.prototype = {
   },
 
   /////////////////////////////////////////////////////////////////////////
   //// Event Handling
 
   /**
    * Handle mousemoves in panel when InspectorUI.inspecting is true.
    *
-   * @param event
+   * @param aEvent
    *        The MouseEvent triggering the method.
    */
-  handleMouseMove: function PanelHighlighter_handleMouseMove(event)
+  handleMouseMove: function PanelHighlighter_handleMouseMove(aEvent)
   {
     if (!InspectorUI.inspecting) {
       return;
     }
     let browserRect = this.browser.getBoundingClientRect();
-    let element = this.win.document.elementFromPoint(event.clientX -
-      browserRect.left, event.clientY - browserRect.top);
+    let element = this.win.document.elementFromPoint(aEvent.clientX -
+      browserRect.left, aEvent.clientY - browserRect.top);
     if (element && element != this.node) {
       InspectorUI.inspectNode(element);
     }
   },
 };
 
 ///////////////////////////////////////////////////////////////////////////
 //// InspectorTreeView
@@ -457,17 +457,17 @@ InspectorTreeView.prototype = {
       }
     }
 
     // We have all the ancestors, now open them one-by-one from the top
     // to bottom.
     let lastIndex;
     let view = this.tree.treeBoxObject.view;
 
-    for (let i = line.length - 1; i >= 0; i--) {
+    for (let i = line.length - 1; i >= 0; --i) {
       index = this.view.getRowIndexFromNode(line[i]);
       if (index < 0) {
         // Can't find the row, so stop trying to descend.
         break;
       }
       if (i > 0 && !view.isContainerOpen(index)) {
         view.toggleOpenState(index);
       }
@@ -487,53 +487,92 @@ InspectorTreeView.prototype = {
 //// InspectorUI
 
 /**
  * Main controller class for the Inspector.
  */
 var InspectorUI = {
   browser: null,
   _showTreePanel: true,
-  _showStylePanel: false,
+  _showStylePanel: true,
   _showDOMPanel: false,
   highlightColor: "#EEEE66",
   highlightThickness: 4,
   highlightOpacity: 0.4,
   selectEventsSuppressed: false,
   inspecting: false,
 
   /**
    * Toggle the inspector interface elements on or off.
    *
-   * @param event
+   * @param aEvent
    *        The event that requested the UI change. Toolbar button or menu.
    */
-  toggleInspectorUI: function InspectorUI_toggleInspectorUI()
+  toggleInspectorUI: function IUI_toggleInspectorUI(aEvent)
   {
     if (this.isPanelOpen) {
       this.closeInspectorUI();
     } else {
       this.openInspectorUI();
     }
   },
 
   /**
+   * Toggle the status of the inspector, starting or stopping it. Invoked
+   * from the toolbar's Inspect button.
+   */
+  toggleInspection: function IUI_toggleInspection()
+  {
+    if (this.inspecting) {
+      this.stopInspecting();
+    } else {
+      this.startInspecting();
+    }
+  },
+
+  /**
+   * Toggle the style panel. Invoked from the toolbar's Style button.
+   */
+  toggleStylePanel: function IUI_toggleStylePanel()
+  {
+    if (this._showStylePanel) {
+      this.stylePanel.hidePopup();
+    } else {
+      this.openStylePanel();
+      if (this.treeView.selectedNode) {
+        this.updateStylePanel(this.treeView.selectedNode);
+      }
+    }
+    this._showStylePanel = !this._showStylePanel;
+  },
+
+  /**
    * Is the tree panel open?
    *
    * @returns boolean
    */
   get isPanelOpen()
   {
     return this.treePanel && this.treePanel.state == "open";
   },
 
   /**
+   * Is the style panel open?
+   *
+   * @returns boolean
+   */
+  get isStylePanelOpen()
+  {
+    return this.stylePanel && this.stylePanel.state == "open";
+  },
+
+  /**
    * Open the inspector's tree panel and initialize it.
    */
-  openTreePanel: function InspectorUI_openTreePanel()
+  openTreePanel: function IUI_openTreePanel()
   {
     if (!this.treePanel) {
       this.treePanel = document.getElementById("inspector-panel");
       this.treePanel.hidden = false;
     }
     if (!this.isPanelOpen) {
       const panelWidthRatio = 7 / 8;
       const panelHeightRatio = 1 / 5;
@@ -541,126 +580,274 @@ var InspectorUI = {
       this.treePanel.openPopup(bar, "overlap", 120, -120, false, false);
       this.treePanel.sizeTo(this.win.outerWidth * panelWidthRatio, 
         this.win.outerHeight * panelHeightRatio);
       this.tree = document.getElementById("inspector-tree");
       this.createDocumentModel();
     }
   },
 
-  openStylePanel: function InspectorUI_openStylePanel()
+  /**
+   * Open the style panel if not already onscreen.
+   */
+  openStylePanel: function IUI_openStylePanel()
   {
-    // # todo
+    if (!this.stylePanel) {
+      this.stylePanel = document.getElementById("inspector-style-panel");
+      this.stylePanel.hidden = false;
+    }
+    if (!this.isStylePanelOpen) {
+      // open at top right of browser panel, offset by 20px from top.
+      this.stylePanel.openPopup(this.browser, "end_before", 0, 20, false, false);
+      // size panel to 200px wide by half browser height - 60.
+      this.stylePanel.sizeTo(200, this.win.outerHeight / 2 - 60);
+    }
   },
 
-  openDOMPanel: function InspectorUI_openDOMPanel()
+  /**
+   * Toggle the dimmed (semi-transparent) state for a panel by setting or
+   * removing a dimmed attribute.
+   *
+   * @param aDim
+   *        The panel to be dimmed.
+   */
+  toggleDimForPanel: function IUI_toggleDimForPanel(aDim)
   {
-    // # todo
+    if (aDim.hasAttribute("dimmed")) {
+      aDim.removeAttribute("dimmed");
+    } else {
+      aDim.setAttribute("dimmed", "true");
+    }
+  },
+
+  openDOMPanel: function IUI_openDOMPanel()
+  {
+    // # todo bug 561782
   },
 
   /**
    * Open inspector UI. tree, style and DOM panels if enabled. Add listeners for
-   * document scrolling and tabContainer.TabSelect.
+   * document scrolling, resize and tabContainer.TabSelect.
    */
-  openInspectorUI: function InspectorUI_openInspectorUI()
+  openInspectorUI: function IUI_openInspectorUI()
   {
     // initialization
     this.browser = gBrowser.selectedBrowser;
     this.win = this.browser.contentWindow;
+    if (!this.style) {
+      Cu.import("resource:///modules/stylePanel.jsm", this);
+      this.style.initialize();
+    }
 
     // open inspector UI
     if (this._showTreePanel) {
       this.openTreePanel();
     }
     if (this._showStylePanel) {
+      this.styleBox = document.getElementById("inspector-style-listbox");
+      this.clearStylePanel();
       this.openStylePanel();
     }
     if (this._showDOMPanel) {
       this.openDOMPanel();
     }
+    this.inspectorBundle = Services.strings.createBundle("chrome://browser/locale/inspector.properties");
     this.initializeHighlighter();
     this.startInspecting();
     this.win.document.addEventListener("scroll", this, false);
+    this.win.addEventListener("resize", this, false);
     gBrowser.tabContainer.addEventListener("TabSelect", this, false);
     this.inspectCmd.setAttribute("checked", true);
   },
 
   /**
    * Initialize highlighter.
    */
-  initializeHighlighter: function InspectorUI_initializeHighlighter()
+  initializeHighlighter: function IUI_initializeHighlighter()
   {
     this.highlighter = new PanelHighlighter(this.browser, this.highlightColor,
       this.highlightThickness, this.highlightOpacity);
   },
 
   /**
    * Close inspector UI and associated panels. Unhighlight and stop inspecting.
-   * Remove event listeners for document scrolling and
+   * Remove event listeners for document scrolling, resize and
    * tabContainer.TabSelect.
    */
-  closeInspectorUI: function InspectorUI_closeInspectorUI()
+  closeInspectorUI: function IUI_closeInspectorUI()
   {
     this.win.document.removeEventListener("scroll", this, false);
+    this.win.removeEventListener("resize", this, false);
     gBrowser.tabContainer.removeEventListener("TabSelect", this, false);
     this.stopInspecting();
     if (this.highlighter && this.highlighter.isHighlighting) {
       this.highlighter.unhighlight();
     }
     if (this.isPanelOpen) {
       this.treePanel.hidePopup();
       this.treeView.destroy();
     }
+    if (this.isStylePanelOpen) {
+      this.stylePanel.hidePopup();
+    }
     this.inspectCmd.setAttribute("checked", false);
     this.browser = this.win = null; // null out references to browser and window
   },
 
   /**
    * Begin inspecting webpage, attach page event listeners, activate
    * highlighter event listeners.
    */
-  startInspecting: function InspectorUI_startInspecting()
+  startInspecting: function IUI_startInspecting()
   {
     this.attachPageListeners();
     this.inspecting = true;
+    this.toggleDimForPanel(this.stylePanel);
   },
 
   /**
    * Stop inspecting webpage, detach page listeners, disable highlighter
    * event listeners.
    */
-  stopInspecting: function InspectorUI_stopInspecting()
+  stopInspecting: function IUI_stopInspecting()
   {
     if (!this.inspecting)
       return;
     this.detachPageListeners();
     this.inspecting = false;
+    this.toggleDimForPanel(this.stylePanel);
+    if (this.treeView.selection) {
+      this.updateStylePanel(this.treeView.selectedNode);
+    }
   },
 
   /////////////////////////////////////////////////////////////////////////
   //// Model Creation Methods
 
   /**
    * Create treeView object from content window.
    */
-  createDocumentModel: function InspectorUI_createDocumentModel()
+  createDocumentModel: function IUI_createDocumentModel()
   {
     this.treeView = new InspectorTreeView(this.win);
   },
 
+  /**
+   * add a new item to the listbox
+   *
+   * @param aLabel
+   *        A bit of text to put in the listitem's label attribute.
+   * @param aType
+   *        The type of item.
+   * @param content
+   *        Text content or value of the listitem.
+   */
+  addStyleItem: function IUI_addStyleItem(aLabel, aType, aContent)
+  {
+    let itemLabelString = this.inspectorBundle.GetStringFromName("style.styleItemLabel");
+    let item = document.createElement("listitem");
+
+    // Do not localize these strings
+    let label = aLabel;
+    item.className = "style-" + aType;
+    if (aContent) {
+      label = itemLabelString.replace("#1", aLabel);
+      label = label.replace("#2", aContent);
+    }
+    item.setAttribute("label", label);
+
+    this.styleBox.appendChild(item);
+  },
+
+  /**
+   * Create items for each rule included in the given array.
+   *
+   * @param aRules
+   *        an array of rule objects
+   */
+  createStyleRuleItems: function IUI_createStyleRuleItems(aRules)
+  {
+    let selectorLabel = this.inspectorBundle.GetStringFromName("style.selectorLabel");
+
+    aRules.forEach(function(rule) {
+      this.addStyleItem(selectorLabel, "selector", rule.id);
+      rule.properties.forEach(function(property) {
+        if (property.overridden)
+          return; // property marked overridden elsewhere
+        // Do not localize the strings below this line
+        let important = "";
+        if (property.important)
+          important += " !important";
+        this.addStyleItem(property.name, "property", property.value + important);
+      }, this);
+    }, this);
+  },
+
+  /**
+   * Create rule items for each section as well as the element's style rules,
+   * if any.
+   *
+   * @param aRules
+   *        Array of rules corresponding to the element's style object.
+   * @param aSections
+   *        Array of sections encapsulating the inherited rules for selectors
+   *        and elements.
+   */
+  createStyleItems: function IUI_createStyleItems(aRules, aSections)
+  {
+    this.createStyleRuleItems(aRules);
+    let inheritedString = 
+        this.inspectorBundle.GetStringFromName("style.inheritedFrom");
+    aSections.forEach(function(section) {
+      let sectionTitle = section.element.tagName;
+      if (section.element.id)
+        sectionTitle += "#" + section.element.id;
+      let replacedString = inheritedString.replace("#1", sectionTitle);
+      this.addStyleItem(replacedString, "section");
+      this.createStyleRuleItems(section.rules);
+    }, this);
+  },
+
+  /**
+   * Remove all items from the Style Panel's listbox.
+   */
+  clearStylePanel: function IUI_clearStylePanel()
+  {
+    for (let i = this.styleBox.childElementCount; i >= 0; --i)
+      this.styleBox.removeItemAt(i);
+  },
+
+  /**
+   * Update the contents of the style panel with styles for the currently
+   * inspected node.
+   *
+   * @param aNode
+   *        The highlighted node to get styles for.
+   */
+  updateStylePanel: function IUI_updateStylePanel(aNode)
+  {
+    if (this.inspecting || !this.isStylePanelOpen)
+      return;
+    let rules = [], styleSections = [], usedProperties = {};
+    this.style.getInheritedRules(aNode, styleSections, usedProperties);
+    this.style.getElementRules(aNode, rules, usedProperties);
+    this.clearStylePanel();
+    this.createStyleItems(rules, styleSections);
+  },
+
   /////////////////////////////////////////////////////////////////////////
   //// Event Handling
 
   /**
    * Main callback handler for events.
    *
    * @param event
    *        The event to be handled.
    */
-  handleEvent: function InspectorUI_handleEvent(event)
+  handleEvent: function IUI_handleEvent(event)
   {
     switch (event.type) {
       case "TabSelect":
         this.closeInspectorUI();
         break;
       case "keypress":
         switch (event.keyCode) {
           case KeyEvent.DOM_VK_RETURN:
@@ -675,75 +862,78 @@ var InspectorUI = {
         if (element && element != this.node) {
           this.inspectNode(element);
         }
         break;
       case "click":
         this.stopInspecting();
         break;
       case "scroll":
+      case "resize":
         this.highlighter.highlight();
         break;
     }
   },
 
   /**
    * Event fired when a tree row is selected in the tree panel.
    */
-  onTreeSelected: function InspectorUI_onTreeSelected()
+  onTreeSelected: function IUI_onTreeSelected()
   {
     if (this.selectEventsSuppressed) {
       return false;
     }
 
     let treeView = this.treeView;
     let node = treeView.selectedNode;
-    this.highlighter.highlightNode(node); // # todo scrolling causes issues
+    this.highlighter.highlightNode(node);
     this.stopInspecting();
+    this.updateStylePanel(node);
     return true;
   },
 
   /**
    * Attach event listeners to content window and child windows to enable 
    * highlighting and click to stop inspection.
    */
-  attachPageListeners: function InspectorUI_attachPageListeners()
+  attachPageListeners: function IUI_attachPageListeners()
   {
     this.win.addEventListener("keypress", this, true);
     this.browser.addEventListener("mousemove", this, true);
     this.browser.addEventListener("click", this, true);
   },
 
   /**
    * Detach event listeners from content window and child windows
    * to disable highlighting.
    */
-  detachPageListeners: function InspectorUI_detachPageListeners()
+  detachPageListeners: function IUI_detachPageListeners()
   {
     this.win.removeEventListener("keypress", this, true);
     this.browser.removeEventListener("mousemove", this, true);
     this.browser.removeEventListener("click", this, true);
   },
 
   /////////////////////////////////////////////////////////////////////////
   //// Utility Methods
 
   /**
    * inspect the given node, highlighting it on the page and selecting the
    * correct row in the tree panel
    *
-   * @param element
+   * @param aNode
    *        the element in the document to inspect
    */
-  inspectNode: function InspectorUI_inspectNode(element)
+  inspectNode: function IUI_inspectNode(aNode)
   {
-    this.highlighter.highlightNode(element);
+    this.highlighter.highlightNode(aNode);
     this.selectEventsSuppressed = true;
-    this.treeView.selectedNode = element;
+    this.treeView.selectedNode = aNode;
     this.selectEventsSuppressed = false;
+    this.updateStylePanel(aNode);
   },
 
   ///////////////////////////////////////////////////////////////////////////
   //// Utility functions
 
   /**
    * debug logging facility
    * @param msg
new file mode 100644
--- /dev/null
+++ b/browser/base/content/stylePanel.jsm
@@ -0,0 +1,311 @@
+/*
+ * Software License Agreement (BSD License)
+ *
+ * Copyright (c) 2007, Parakey Inc.
+ * All rights reserved.
+ * 
+ * Redistribution and use of this software in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ * 
+ * * Redistributions of source code must retain the above
+ *   copyright notice, this list of conditions and the
+ *   following disclaimer.
+ * 
+ * * Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the
+ *   following disclaimer in the documentation and/or other
+ *   materials provided with the distribution.
+ * 
+ * * Neither the name of Parakey Inc. nor the names of its
+ *   contributors may be used to endorse or promote products
+ *   derived from this software without specific prior
+ *   written permission of Parakey Inc.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Creator:
+ *  Joe Hewitt
+ * Contributors
+ *  John J. Barton (IBM Almaden)
+ *  Jan Odvarko (Mozilla Corp.)
+ *  Max Stepanov (Aptana Inc.)
+ *  Rob Campbell (Mozilla Corp.)
+ *  Hans Hillen (Paciello Group, Mozilla)
+ *  Curtis Bartley (Mozilla Corp.)
+ *  Mike Collins (IBM Almaden)
+ *  Kevin Decker
+ *  Mike Ratcliffe (Comartis AG)
+ *  Hernan Rodríguez Colmeiro
+ *  Austin Andrews
+ *  Christoph Dorn
+ *  Steven Roussey (AppCenter Inc, Network54)
+ */
+
+var EXPORTED_SYMBOLS = ["style"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+var style = {
+
+  /**
+   * initialize domUtils
+   */
+  initialize: function CSS_initialize()
+  {
+    this.domUtils = Cc["@mozilla.org/inspector/dom-utils;1"].
+      getService(Ci["inIDOMUtils"]);
+  },
+
+  /**
+   * Is the given property sheet a system (user agent) stylesheet?
+   *
+   * @param aSheet
+   *        a stylesheet
+   */
+  isSystemStyleSheet: function CSS_isSystemStyleSheet(aSheet)
+  {
+    if (!aSheet)
+      return true;
+
+    let url = aSheet.href;
+
+    if (!url)
+      return false;
+    if (url.length == 0)
+      return true;
+    if (url[0] == 'h') 
+      return false;
+    if (url.substr(0, 9) == "resource:")
+      return true;
+    if (url.substr(0, 7) == "chrome:")
+      return true;
+    if (url  == "XPCSafeJSObjectWrapper.cpp")
+      return true;
+    if (url.substr(0, 6) == "about:")
+      return true;
+
+    return false;
+  },
+
+  /**
+   * Parse properties from a given style object.
+   * Borrowed from Firebug's css.js.
+   *
+   * @param aStyle
+   *        a style object
+   */
+  parseCSSProperties: function CSS_parseCSSProps(aStyle)
+  {
+    let properties = [];
+    let lines = aStyle.cssText.match(/(?:[^;\(]*(?:\([^\)]*?\))?[^;\(]*)*;?/g);
+    let propRE = /\s*([^:\s]*)\s*:\s*(.*?)\s*(! important)?;?$/;
+    let line, i = 0;
+    while(line = lines[i++]) {
+      let match = propRE.exec(line);
+      if (!match)
+        continue;
+      let name = match[1];
+      let value = match[2];
+      let important = !!match[3]; // true if match[3] is non-empty
+      properties.unshift({name: name, value: value, important: important});
+    }
+
+    return properties;
+  },
+
+  /**
+   * Mark properties overridden further up the hierarchy.
+   *
+   * @param aProps
+   *        Array of properties.
+   * @param aUsedProps
+   *        Object of arrays keyed by property name.
+   * @param aInherit
+   *        Boolean of whether or not we are in inherited mode.
+   */
+  markOverriddenProperties: function CSS_markOverriddenProperties(aProps, aUsedProps, aInherit)
+  {
+    for (let i = 0; i < aProps.length; ++i) {
+      let prop = aProps[i];
+      if (aUsedProps.hasOwnProperty(prop.name)) {
+        // all previous occurrences of this property
+        let deadProps = aUsedProps[prop.name];
+        for (let j = 0; j < deadProps.length; ++j) {
+          let deadProp = deadProps[j];
+          if (!deadProp.disabled && !deadProp.wasInherited &&
+              deadProp.important && !prop.important) {
+            prop.overridden = true;  // new occurrence overridden
+          } else if (!prop.disabled) {
+            deadProp.overridden = true;  // previous occurrences overridden
+          } else {
+            aUsedProps[prop.name] = [];
+          }
+
+          prop.wasInherited = aInherit ? true : false;
+          // all occurrences of a property seen so far, by name
+          aUsedProps[prop.name].push(prop);
+        }
+      }
+    }
+  },
+
+  /**
+   * Sort given properties in lexical order by name.
+   *
+   * @param properties
+   *        An array of properties.
+   * @returns sorted array.
+   */
+  sortProperties: function CSS_sortProperties(properties)
+  {
+    properties.sort(function(a, b)
+    {
+      if (a.name < b.name) {
+        return -1;
+      }
+      if (a.name > b.name) {
+        return 1;
+      }
+      return 0;
+    });
+  },
+
+  /**
+   * Get properties for a given element and push them to the rules array.
+   *
+   * @param aNode
+   *        a DOM node
+   * @param rules
+   *        An array of rules to add properties to.
+   * @param usedProps
+   *        Object of arrays keyed by property name.
+   * @param inherit
+   *        boolean determining whether or not we're in inherit mode
+   */
+  getStyleProperties: function CSS_getStyleProperties(aNode, aRules, aUsedProps, aInherit)
+  {
+    let properties = this.parseCSSProperties(aNode.style, aInherit);
+
+    this.sortProperties(properties);
+    this.markOverriddenProperties(properties, aUsedProps, aInherit);
+
+    if (properties.length) {
+      aRules.push({rule: aNode, selector: "element.style",
+        properties: properties, inherited: aInherit});
+    }
+  },
+
+  /**
+   * Get properties for a given rule.
+   *
+   * @param aRule
+   *        A Rule from a stylesheet.
+   */
+  getRuleProperties: function CSS_getRuleProperties(aRule)
+  {
+    let style = aRule.style;
+    return this.parseCSSProperties(style);
+  },
+
+  /**
+   * Recursively get rules for an element's parents and add them to the
+   * sections array.
+   *
+   * @param aNode
+   *        an element in a DOM tree.
+   * @param sections
+   *        an array of sections
+   * @param usedProps
+   *        Object of arrays keyed by property name.
+   */
+  getInheritedRules: function CSS_getInheritedRules(aNode, aSections, aUsedProps)
+  {
+    let parent = aNode.parentNode;
+    if (parent && parent.nodeType == 1) {
+      this.getInheritedRules(parent, aSections, aUsedProps);
+
+      let rules = [];
+      this.getElementRules(parent, rules, aUsedProps, true);
+
+      if (rules.length) {
+        aSections.unshift({element: parent, rules: rules});
+      }
+    }
+  },
+
+  /**
+   * Get the CSS style rules for a given node in the DOM and append them to the
+   * rules array.
+   *
+   * @param aNode
+   *        an element in the DOM tree.
+   * @param aRules
+   *        an array of rules.
+   * @param aUsedProps
+   *        Object of arrays keyed by property name.
+   * @param aInherit
+   *        boolean indicating whether we are in an inherited mode or not
+   */
+  getElementRules: function CSS_getElementRules(aNode, aRules, aUsedProps, aInherit)
+  {
+    let inspectedRules;
+
+    try {
+      inspectedRules = this.domUtils.getCSSStyleRules(aNode);
+    } catch (ex) {
+      Services.console.logStringMessage(ex);
+    }
+
+    if (!inspectedRules)
+      return;
+
+    for (let i = 0; i < inspectedRules.Count(); ++i) {
+      let rule = inspectedRules.GetElementAt(i);
+      let href = rule.parentStyleSheet.href;
+
+      if (!href) {
+        // Null href means inline style.
+        href = aNode.ownerDocument.location.href;
+      }
+
+      let isSystemSheet = this.isSystemStyleSheet(rule.parentStyleSheet);
+
+      if (isSystemSheet)
+        continue;
+
+      let properties = this.getRuleProperties(rule, aInherit);
+      if (aInherit && !properties.length)
+        continue;
+
+      let line = this.domUtils.getRuleLine(rule);
+      let ruleId = rule.selectorText + " " + href + " (" + line + ")";
+
+      let sourceLink = "view-source:" + href + "#" + line;
+
+      this.markOverriddenProperties(properties, aUsedProps, aInherit);
+
+      aRules.unshift(
+        {rule: rule,
+         id: ruleId,
+         selector: rule.selectorText,
+         properties: properties,
+         inherited: aInherit,
+         sourceLink: sourceLink,
+         isSystemSheet: isSystemSheet});
+    }
+
+    if (aNode.style) {
+      this.getStyleProperties(aNode, aRules, aUsedProps, aInherit);
+    }
+  },
+};
--- a/browser/base/content/test/Makefile.in
+++ b/browser/base/content/test/Makefile.in
@@ -140,16 +140,17 @@ endif
                  browser_ctrlTab.js \
                  browser_discovery.js \
                  browser_drag.js \
                  browser_gestureSupport.js \
                  browser_getshortcutoruri.js \
                  browser_inspector_initialization.js \
                  browser_inspector_treeSelection.js \
                  browser_inspector_highlighter.js \
+                 browser_inspector_stylePanel.js \
                  browser_overflowScroll.js \
                  browser_pageInfo.js \
                  browser_page_style_menu.js \
                  browser_pinnedTabs.js \
                  browser_plainTextLinks.js \
                  browser_pluginnotification.js \
                  browser_popupUI.js \
                  browser_relatedTabs.js \
--- a/browser/base/content/test/browser_inspector_initialization.js
+++ b/browser/base/content/test/browser_inspector_initialization.js
@@ -49,24 +49,24 @@ function startInspectorTests()
 function runInspectorTests(evt)
 {
   if (evt.target.id != "inspector-panel")
     return true;
   document.removeEventListener("popupshown", runInspectorTests, false);
   document.addEventListener("popuphidden", finishInspectorTests, false);
   ok(InspectorUI.inspecting, "Inspector is highlighting");
   ok(InspectorUI.isPanelOpen, "Inspector Tree Panel is open");
-  todo(InspectorUI.isStylePanelOpen, "Inspector Style Panel is open");
+  ok(InspectorUI.isStylePanelOpen, "Inspector Style Panel is open");
   todo(InspectorUI.isDOMPanelOpen, "Inspector DOM Panel is open");
   InspectorUI.toggleInspectorUI();
 }
 
 function finishInspectorTests(evt)
 {
-  if (evt.target.id != "inspector-panel")
+  if (evt.target.id != "inspector-style-panel")
     return true;
   document.removeEventListener("popuphidden", finishInspectorTests, false);
   ok(!InspectorUI.isDOMPanelOpen, "Inspector DOM Panel is closed");
   ok(!InspectorUI.isStylePanelOpen, "Inspector Style Panel is closed");
   ok(!InspectorUI.isPanelOpen, "Inspector Tree Panel is closed");
   ok(!InspectorUI.inspecting, "Inspector is not highlighting");
   gBrowser.removeCurrentTab();
   finish();
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_inspector_stylePanel.js
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Inspector Style Panel Tests.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Rob Campbell <rcampbell@mozilla.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+let doc;
+let spans;
+let testGen;
+
+function createDocument()
+{
+  doc.body.innerHTML = '<div id="first" style="{ margin: 10em; ' +
+    'font-size: 14pt; font-family: helvetica, sans-serif; color: #AAA}">\n' +
+    '<h1>Some header text</h1>\n' +
+    '<p id="salutation" style="{font-size: 12pt}">hi.</p>\n' +
+    '<p id="body" style="{font-size: 12pt}">I am a test-case. This text exists ' +
+    'solely to provide some things to <span style="{color: yellow}">' +
+    'highlight</span> and <span style="{font-weight: bold}">count</span> ' +
+    'style list-items in the box at right. If you are reading this, ' +
+    'you should go do something else instead. Maybe read a book. Or better ' +
+    'yet, write some test-cases for another bit of code. ' +
+    '<span style="{font-style: italic}">Maybe more inspector test-cases!</span></p>\n' +
+    '<p id="closing">end transmission</p>\n' +
+    '</div>';
+  doc.title = "Inspector Style Test";
+  setupStyleTests();
+}
+
+function setupStyleTests()
+{
+  spans = doc.querySelectorAll("span");
+  ok(spans, "captain, we have the spans");
+  document.addEventListener("popupshown", runStyleTests, false);
+  InspectorUI.openInspectorUI();
+}
+
+function spanGenerator()
+{
+  for (var i = 0; i < spans.length; ++i) {
+    InspectorUI.inspectNode(spans[i]);
+    yield;
+  }
+}
+
+function runStyleTests(evt)
+{
+  if (evt.target.id != "inspector-style-panel")
+    return true;
+  document.removeEventListener("popupshown", runStyleTests, false);
+  document.addEventListener("popupshown", performTestComparisons, false);
+  InspectorUI.stopInspecting();
+  testGen = spanGenerator();
+  testGen.next();
+}
+
+function performTestComparisons(evt)
+{
+  if (evt.target.id != "highlighter-panel")
+    return true;
+
+  ok(InspectorUI.treeView.selectedNode, "selection");
+  ok(InspectorUI._showStylePanel, "_showStylePanel");
+  is(InspectorUI.isStylePanelOpen, InspectorUI._showStylePanel, "style panel matches _showStylePanel?");
+  ok(InspectorUI.highlighter.isHighlighting, "panel is highlighting");
+  ok(InspectorUI.styleBox.itemCount > 0, "styleBox has items");
+
+  try {
+    testGen.next();
+  } catch(StopIteration) {
+    document.removeEventListener("popupshown", performTestComparisons, false);
+    finishUp();
+  }
+}
+
+function finishUp() {
+  InspectorUI.closeInspectorUI();
+  gBrowser.removeCurrentTab();
+  finish();
+}
+
+function test()
+{
+  waitForExplicitFinish();
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function() {
+    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+    doc = content.document;
+    waitForFocus(createDocument, content);
+  }, true);
+  
+  content.location = "data:text/html,basic tests for inspector";
+}
+
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -172,16 +172,26 @@
 <!ENTITY hudConsoleCmd.label          "Heads Up Display">
 <!ENTITY hudConsoleCmd.accesskey      "U">
 <!ENTITY hudConsoleCmd.commandkey     "k">
 
 <!ENTITY inspectMenu.label            "Inspect">
 <!ENTITY inspectMenu.accesskey        "T">
 <!ENTITY inspectMenu.commandkey       "I">
 
+<!ENTITY inspectButton.label          "Inspect">
+<!ENTITY inspectButton.accesskey      "I">
+<!ENTITY inspectNextButton.label      "Next">
+<!ENTITY inspectNextButton.accesskey  "N">
+<!ENTITY inspectPreviousButton.label  "Previous">
+<!ENTITY inspectPreviousButton.accesskey "P">
+<!ENTITY inspectStyleButton.label     "Style">
+<!ENTITY inspectStyleButton.accesskey "S">
+<!ENTITY inspectStylePanelTitle.label  "Style">
+
 <!ENTITY fileMenu.label         "File"> 
 <!ENTITY fileMenu.accesskey       "F">
 <!ENTITY newNavigatorCmd.label        "New Window">
 <!ENTITY newNavigatorCmd.key        "N">
 <!ENTITY newNavigatorCmd.accesskey      "N">
 
 <!ENTITY editMenu.label         "Edit"> 
 <!ENTITY editMenu.accesskey       "E"> 
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/inspector.properties
@@ -0,0 +1,11 @@
+# LOCALIZATION NOTE  (style.selectorLabel): Used in the Inspector style panel
+#  to label a CSS Selector.
+style.selectorLabel=Selector
+
+# LOCALIZATION NOTE  (style.inheritedFrom): used in Style panel in
+#  inspector. Describes which tagname[#id] the properties are inherited from.
+style.inheritedFrom=Inherited from: #1
+
+# LOCALIZATION NOTE (style.styleItemLabel: used in Style panel in inspector.
+#  Used for construction of list items, #1 = label, #2 = content.
+style.styleItemLabel=#1: #2
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -6,16 +6,17 @@
     locale/browser/aboutDialog.dtd                 (%chrome/browser/aboutDialog.dtd)
     locale/browser/aboutPrivateBrowsing.dtd        (%chrome/browser/aboutPrivateBrowsing.dtd)
     locale/browser/aboutRobots.dtd                 (%chrome/browser/aboutRobots.dtd)
     locale/browser/aboutSessionRestore.dtd         (%chrome/browser/aboutSessionRestore.dtd)
     locale/browser/credits.dtd                     (%chrome/browser/credits.dtd)
 *   locale/browser/browser.dtd                     (%chrome/browser/browser.dtd)
     locale/browser/baseMenuOverlay.dtd             (%chrome/browser/baseMenuOverlay.dtd)
     locale/browser/browser.properties              (%chrome/browser/browser.properties)
+    locale/browser/inspector.properties            (%chrome/browser/inspector.properties)
     locale/browser/openLocation.dtd                (%chrome/browser/openLocation.dtd)
     locale/browser/openLocation.properties         (%chrome/browser/openLocation.properties)
 *   locale/browser/pageInfo.dtd                    (%chrome/browser/pageInfo.dtd)
     locale/browser/pageInfo.properties             (%chrome/browser/pageInfo.properties)
     locale/browser/pageReportFirstTime.dtd         (%chrome/browser/pageReportFirstTime.dtd)
     locale/browser/quitDialog.properties           (%chrome/browser/quitDialog.properties)
 *   locale/browser/safeMode.dtd                    (%chrome/browser/safeMode.dtd)
     locale/browser/sanitize.dtd                    (%chrome/browser/sanitize.dtd)
--- a/browser/themes/gnomestripe/browser/browser.css
+++ b/browser/themes/gnomestripe/browser/browser.css
@@ -1499,8 +1499,31 @@ toolbar[mode="text"] toolbarbutton.chevr
 #allTabs-filter {
   -moz-margin-start: 36px;
   -moz-margin-end: 0;
 }
 
 .allTabs-preview-label {
   -moz-transform: translate(0, 2px);
 }
+
+/* Inspector / Highlighter */
+
+#highlighter-panel {
+  -moz-appearance: none;
+  -moz-window-shadow: none;
+}
+
+listitem.style-selector {
+  background-color: DarkGray;
+  color: white;
+}
+
+listitem.style-section {
+  background-color: LightGray;
+  color: black;
+  font-weight: bold;
+}
+
+panel[dimmed="true"] {
+  opacity: 0.5;
+}
+
--- a/browser/themes/pinstripe/browser/browser.css
+++ b/browser/themes/pinstripe/browser/browser.css
@@ -2029,8 +2029,31 @@ toolbarbutton.chevron > .toolbarbutton-m
 
 .allTabs-preview:not(:hover):not([closebuttonhover]) > html|canvas {
   opacity: .8;
 }
 
 .allTabs-preview:focus > * > .allTabs-preview-inner {
   -moz-box-shadow: @focusRingShadow@;
 }
+
+/* Inspector / Highlighter */
+
+#highlighter-panel {
+  -moz-appearance: none;
+  -moz-window-shadow: none;
+}
+
+listitem.style-selector {
+  background-color: DarkGray;
+  color: white;
+}
+
+listitem.style-section {
+  background-color: LightGray;
+  color: black;
+  font-weight: bold;
+}
+
+panel[dimmed="true"] {
+  opacity: 0.5;
+}
+
--- a/browser/themes/winstripe/browser/browser.css
+++ b/browser/themes/winstripe/browser/browser.css
@@ -1786,8 +1786,31 @@ toolbarbutton.bookmark-item[dragover="tr
 
 .allTabs-preview:not(:hover):not([closebuttonhover]) > html|canvas {
   opacity: .8;
 }
 
 .allTabs-preview:focus > * > .allTabs-preview-inner {
   outline: 1px dotted -moz-dialogText;
 }
+
+/* Inspector / Highlighter */
+
+#highlighter-panel {
+  -moz-appearance: none;
+  -moz-window-shadow: none;
+}
+
+listitem.style-selector {
+  background-color: DarkGray;
+  color: white;
+}
+
+listitem.style-section {
+  background-color: LightGray;
+  color: black;
+  font-weight: bold;
+}
+
+panel[dimmed="true"] {
+  opacity: 0.5;
+}
+