Bug 582596 - Style view centered around answering common CSS questions, f=l10n, r=dolske, msucan
authorMike Ratcliffe <mratcliffe@mozilla.com>
Tue, 30 Aug 2011 09:12:02 -0300
changeset 76259 26bc47cdea9e1177b9cbc155dbe60b6e02fd1b6b
parent 76258 2470993f6287d3b594a542023ce1cb077fda9bff
child 76260 54685bf66136dc5e346e1e0873d2fc9afb4f85c0
push id21089
push userrcampbell@mozilla.com
push dateWed, 31 Aug 2011 12:23:58 +0000
treeherdermozilla-central@922f27baed98 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdolske, msucan
bugs582596
milestone9.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 582596 - Style view centered around answering common CSS questions, f=l10n, r=dolske, msucan
browser/app/profile/firefox.js
browser/devtools/Makefile.in
browser/devtools/jar.mn
browser/devtools/shared/Makefile.in
browser/devtools/shared/Templater.jsm
browser/devtools/styleinspector/CssHtmlTree.jsm
browser/devtools/styleinspector/CssLogic.jsm
browser/devtools/styleinspector/Makefile.in
browser/devtools/styleinspector/StyleInspector.jsm
browser/devtools/styleinspector/csshtmltree.xhtml
browser/devtools/styleinspector/test/Makefile.in
browser/devtools/styleinspector/test/browser/Makefile.in
browser/devtools/styleinspector/test/browser/browser_styleinspector.js
browser/devtools/styleinspector/test/browser/browser_styleinspector_webconsole.htm
browser/devtools/styleinspector/test/browser/browser_styleinspector_webconsole.js
browser/devtools/styleinspector/test/browser/head.js
browser/devtools/webconsole/HUDService.jsm
browser/locales/en-US/chrome/browser/styleinspector.dtd
browser/locales/en-US/chrome/browser/styleinspector.properties
browser/locales/jar.mn
browser/themes/gnomestripe/browser/devtools/arrows.png
browser/themes/gnomestripe/browser/devtools/csshtmltree.css
browser/themes/gnomestripe/browser/jar.mn
browser/themes/pinstripe/browser/devtools/arrows.png
browser/themes/pinstripe/browser/devtools/csshtmltree.css
browser/themes/pinstripe/browser/jar.mn
browser/themes/winstripe/browser/devtools/arrows.png
browser/themes/winstripe/browser/devtools/csshtmltree.css
browser/themes/winstripe/browser/jar.mn
toolkit/locales/en-US/chrome/global/headsUpDisplay.properties
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1010,16 +1010,19 @@ pref("services.sync.prefs.sync.xpinstall
 #endif
 
 // Disable the error console
 pref("devtools.errorconsole.enabled", false);
 
 // Enable the Inspector
 pref("devtools.inspector.enabled", true);
 
+// Enable the style inspector
+pref("devtools.styleinspector.enabled", true);
+
 // Enable the Scratchpad tool.
 pref("devtools.scratchpad.enabled", true);
 
 // Enable tools for Chrome development.
 pref("devtools.chrome.enabled", false);
 
 // The last Web Console height. This is initially 0 which means that the Web
 // Console will use the default height next time it shows.
--- a/browser/devtools/Makefile.in
+++ b/browser/devtools/Makefile.in
@@ -16,16 +16,17 @@
 #
 # The Initial Developer of the Original Code is
 # Netscape Communications Corporation.
 # Portions created by the Initial Developer are Copyright (C) 1998
 # the Initial Developer. All Rights Reserved.
 #
 # Contributor(s):
 #  Rob Campbell <rcampbell@mozilla.com>
+#  Mike Ratcliffe <mratcliffe@mozilla.com>
 #
 # Alternatively, the contents of this file may be used under the terms of
 # either the GNU General Public License Version 2 or later (the "GPL"), or
 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 # in which case the provisions of the GPL or the LGPL are applicable instead
 # of those above. If you wish to allow use of your version of this file only
 # under the terms of either the GPL or the LGPL, and not to allow others to
 # use your version of this file under the terms of the MPL, indicate your
@@ -44,15 +45,17 @@ VPATH   = @srcdir@
 include $(DEPTH)/config/autoconf.mk
 
 include $(topsrcdir)/config/config.mk
 
 DIRS = \
   webconsole \
   scratchpad \
   sourceeditor \
+  styleinspector \
+  shared \
   $(NULL)
 
 ifdef ENABLE_TESTS
 # DIRS += test # no tests yet
 endif
 
 include $(topsrcdir)/config/rules.mk
--- a/browser/devtools/jar.mn
+++ b/browser/devtools/jar.mn
@@ -1,6 +1,7 @@
 browser.jar:
     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)
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/Makefile.in
@@ -0,0 +1,55 @@
+#
+# ***** 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  HUDService code.
+#
+# The Initial Developer of the Original Code is Mozilla Corporation.
+# 
+# Portions created by the Initial Developer are Copyright (C) 2010
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#     Mike Ratcliffe <mratcliffe@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 *****
+
+DEPTH		= ../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+ifdef ENABLE_TESTS
+ifneq (mobile,$(MOZ_BUILD_APP))
+	# DIRS += test # no tests yet
+endif
+endif
+
+include $(topsrcdir)/config/rules.mk
+
+libs::
+	$(NSINSTALL) $(srcdir)/Templater.jsm $(FINAL_TARGET)/modules/devtools
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/Templater.jsm
@@ -0,0 +1,398 @@
+/* -*- 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 Bespin.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Joe Walker (jwalker@mozilla.com) (original author)
+ *   Mike Ratcliffe (mratcliffe@mozilla.com)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// WARNING: do not 'use_strict' without reading the notes in envEval;
+
+var EXPORTED_SYMBOLS = ["Templater"];
+
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+/**
+ * A templater that allows one to quickly template DOM nodes.
+ */
+function Templater() {
+  this.scope = [];
+}
+
+/**
+ * Recursive function to walk the tree processing the attributes as it goes.
+ * @param node the node to process.
+ * @param data the data to use for node processing.
+ */
+Templater.prototype.processNode = function(node, data) {
+  this.scope.push(node.nodeName + (node.id ? '#' + node.id : ''));
+  try {
+    // Process attributes
+    if (node.attributes && node.attributes.length) {
+      // We need to handle 'foreach' and 'if' first because they might stop
+      // some types of processing from happening, and foreach must come first
+      // because it defines new data on which 'if' might depend.
+      if (node.hasAttribute('foreach')) {
+        this.processForEach(node, data);
+        return;
+      }
+      if (node.hasAttribute('if')) {
+        if (!this.processIf(node, data)) {
+          return;
+        }
+      }
+      // Only make the node available once we know it's not going away
+      data.__element = node;
+      // It's good to clean up the attributes when we've processed them,
+      // but if we do it straight away, we mess up the array index
+      var attrs = Array.prototype.slice.call(node.attributes);
+      for (let i = 0, attLen = attrs.length; i < attLen; i++) {
+        var value = attrs[i].value;
+        var name = attrs[i].name;
+        this.scope.push(name);
+        try {
+          if (name === 'save') {
+            // Save attributes are a setter using the node
+            value = this.stripBraces(value);
+            this.property(value, data, node);
+            node.removeAttribute('save');
+          } else if (name.substring(0, 2) === 'on') {
+            // Event registration relies on property doing a bind
+            value = this.stripBraces(value);
+            var func = this.property(value, data);
+            if (typeof func !== 'function') {
+              this.handleError('Expected ' + value +
+                ' to resolve to a function, but got ' + typeof func);
+            }
+            node.removeAttribute(name);
+            var capture = node.hasAttribute('capture' + name.substring(2));
+            node.addEventListener(name.substring(2), func, capture);
+            if (capture) {
+              node.removeAttribute('capture' + name.substring(2));
+            }
+          } else {
+            // Replace references in all other attributes
+            var self = this;
+            var newValue = value.replace(/\$\{[^}]*\}/g, function(path) {
+              return self.envEval(path.slice(2, -1), data, value);
+            });
+            // Remove '_' prefix of attribute names so the DOM won't try
+            // to use them before we've processed the template
+            if (name.charAt(0) === '_') {
+              node.removeAttribute(name);
+              node.setAttribute(name.substring(1), newValue);
+            } else if (value !== newValue) {
+              attrs[i].value = newValue;
+            }
+          }
+        } finally {
+          this.scope.pop();
+        }
+      }
+    }
+
+    // Loop through our children calling processNode. First clone them, so the
+    // set of nodes that we visit will be unaffected by additions or removals.
+    var children = Array.prototype.slice.call(node.childNodes);
+    for (let j = 0, numChildren = children.length; j < numChildren; j++) {
+      this.processNode(children[j], data);
+    }
+
+    if (node.nodeType === Ci.nsIDOMNode.TEXT_NODE) {
+      this.processTextNode(node, data);
+    }
+  } finally {
+    this.scope.pop();
+  }
+};
+
+/**
+ * Handle <x if="${...}">
+ * @param node An element with an 'if' attribute
+ * @param data The data to use with envEval
+ * @returns true if processing should continue, false otherwise
+ */
+Templater.prototype.processIf = function(node, data) {
+  this.scope.push('if');
+  try {
+    var originalValue = node.getAttribute('if');
+    var value = this.stripBraces(originalValue);
+    var recurse = true;
+    try {
+      var reply = this.envEval(value, data, originalValue);
+      recurse = !!reply;
+    } catch (ex) {
+      this.handleError('Error with \'' + value + '\'', ex);
+      recurse = false;
+    }
+    if (!recurse) {
+      node.parentNode.removeChild(node);
+    }
+    node.removeAttribute('if');
+    return recurse;
+  } finally {
+    this.scope.pop();
+  }
+};
+
+/**
+ * Handle <x foreach="param in ${array}"> and the special case of
+ * <loop foreach="param in ${array}">
+ * @param node An element with a 'foreach' attribute
+ * @param data The data to use with envEval
+ */
+Templater.prototype.processForEach = function(node, data) {
+  this.scope.push('foreach');
+  try {
+    var originalValue = node.getAttribute('foreach');
+    var value = originalValue;
+
+    var paramName = 'param';
+    if (value.charAt(0) === '$') {
+      // No custom loop variable name. Use the default: 'param'
+      value = this.stripBraces(value);
+    } else {
+      // Extract the loop variable name from 'NAME in ${ARRAY}'
+      var nameArr = value.split(' in ');
+      paramName = nameArr[0].trim();
+      value = this.stripBraces(nameArr[1].trim());
+    }
+    node.removeAttribute('foreach');
+    try {
+      var self = this;
+      // Process a single iteration of a loop
+      var processSingle = function(member, node, ref) {
+        var clone = node.cloneNode(true);
+        clone.removeAttribute('foreach');
+        ref.parentNode.insertBefore(clone, ref);
+        data[paramName] = member;
+        self.processNode(clone, data);
+        delete data[paramName];
+      };
+
+      // processSingle is no good for <loop> nodes where we want to work on
+      // the children rather than the node itself
+      var processAll = function(scope, member) {
+        self.scope.push(scope);
+        try {
+          if (node.nodeName === 'loop') {
+            for (let i = 0, numChildren = node.children.length; i < numChildren; i++) {
+              processSingle(member, node.children[i], node);
+            }
+          } else {
+            processSingle(member, node, node);
+          }
+        } finally {
+          self.scope.pop();
+        }
+      };
+
+      let reply = this.envEval(value, data, originalValue);
+      if (Array.isArray(reply)) {
+        reply.forEach(function(data, i) {
+            processAll('' + i, data)
+        }, this);
+      } else {
+        for (let param in reply) {
+          if (reply.hasOwnProperty(param)) {
+            processAll(param, param);
+          }
+        }
+      }
+      node.parentNode.removeChild(node);
+    } catch (ex) {
+      this.handleError('Error with \'' + value + '\'', ex);
+    }
+  } finally {
+    this.scope.pop();
+  }
+};
+
+/**
+ * Take a text node and replace it with another text node with the ${...}
+ * sections parsed out. We replace the node by altering node.parentNode but
+ * we could probably use a DOM Text API to achieve the same thing.
+ * @param node The Text node to work on
+ * @param data The data to use in calls to envEval
+ */
+Templater.prototype.processTextNode = function(node, data) {
+  // Replace references in other attributes
+  var value = node.data;
+  // We can't use the string.replace() with function trick (see generic
+  // attribute processing in processNode()) because we need to support
+  // functions that return DOM nodes, so we can't have the conversion to a
+  // string.
+  // Instead we process the string as an array of parts. In order to split
+  // the string up, we first replace '${' with '\uF001$' and '}' with '\uF002'
+  // We can then split using \uF001 or \uF002 to get an array of strings
+  // where scripts are prefixed with $.
+  // \uF001 and \uF002 are just unicode chars reserved for private use.
+  value = value.replace(/\$\{([^}]*)\}/g, '\uF001$$$1\uF002');
+  var parts = value.split(/\uF001|\uF002/);
+  if (parts.length > 1) {
+    parts.forEach(function(part) {
+      if (part === null || part === undefined || part === '') {
+        return;
+      }
+      if (part.charAt(0) === '$') {
+        part = this.envEval(part.slice(1), data, node.data);
+      }
+      // It looks like this was done a few lines above but see envEval
+      if (part === null) {
+        part = "null";
+      }
+      if (part === undefined) {
+        part = "undefined";
+      }
+      // if (isDOMElement(part)) { ... }
+      if (typeof part.cloneNode !== 'function') {
+        part = node.ownerDocument.createTextNode(part.toString());
+      }
+      node.parentNode.insertBefore(part, node);
+    }, this);
+    node.parentNode.removeChild(node);
+  }
+};
+
+/**
+ * Warn of string does not begin '${' and end '}'
+ * @param str the string to check.
+ * @return The string stripped of ${ and }, or untouched if it does not match
+ */
+Templater.prototype.stripBraces = function(str) {
+  if (!str.match(/\$\{.*\}/g)) {
+    this.handleError('Expected ' + str + ' to match ${...}');
+    return str;
+  }
+  return str.slice(2, -1);
+};
+
+/**
+ * Combined getter and setter that works with a path through some data set.
+ * For example:
+ * <ul>
+ * <li>property('a.b', { a: { b: 99 }}); // returns 99
+ * <li>property('a', { a: { b: 99 }}); // returns { b: 99 }
+ * <li>property('a', { a: { b: 99 }}, 42); // returns 99 and alters the
+ * input data to be { a: { b: 42 }}
+ * </ul>
+ * @param path An array of strings indicating the path through the data, or
+ * a string to be cut into an array using <tt>split('.')</tt>
+ * @param data An object to look in for the <tt>path</tt> argument
+ * @param newValue (optional) If defined, this value will replace the
+ * original value for the data at the path specified.
+ * @return The value pointed to by <tt>path</tt> before any
+ * <tt>newValue</tt> is applied.
+ */
+Templater.prototype.property = function(path, data, newValue) {
+  this.scope.push(path);
+  try {
+    if (typeof path === 'string') {
+      path = path.split('.');
+    }
+    var value = data[path[0]];
+    if (path.length === 1) {
+      if (newValue !== undefined) {
+        data[path[0]] = newValue;
+      }
+      if (typeof value === 'function') {
+        return value.bind(data);
+      }
+      return value;
+    }
+    if (!value) {
+      this.handleError('Can\'t find path=' + path);
+      return null;
+    }
+    return this.property(path.slice(1), value, newValue);
+  } finally {
+    this.scope.pop();
+  }
+};
+
+/**
+ * Like eval, but that creates a context of the variables in <tt>env</tt> in
+ * which the script is evaluated.
+ * WARNING: This script uses 'with' which is generally regarded to be evil.
+ * The alternative is to create a Function at runtime that takes X parameters
+ * according to the X keys in the env object, and then call that function using
+ * the values in the env object. This is likely to be slow, but workable.
+ * @param script The string to be evaluated.
+ * @param env The environment in which to eval the script.
+ * @param context Optional debugging string in case of failure
+ * @return The return value of the script, or the error message if the script
+ * execution failed.
+ */
+Templater.prototype.envEval = function(script, env, context) {
+  with (env) {
+    try {
+      this.scope.push(context);
+      return eval(script);
+    } catch (ex) {
+      this.handleError('Template error evaluating \'' + script + '\'', ex);
+      return script;
+    } finally {
+      this.scope.pop();
+    }
+  }
+};
+
+/**
+ * A generic way of reporting errors, for easy overloading in different
+ * environments.
+ * @param message the error message to report.
+ * @param ex optional associated exception.
+ */
+Templater.prototype.handleError = function(message, ex) {
+  this.logError(message);
+  this.logError('In: ' + this.scope.join(' > '));
+  if (ex) {
+    this.logError(ex);
+  }
+};
+
+
+/**
+ * A generic way of reporting errors, for easy overloading in different
+ * environments.
+ * @param message the error message to report.
+ */
+Templater.prototype.logError = function(message) {
+  Services.console.logStringMessage(message);
+};
+
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/CssHtmlTree.jsm
@@ -0,0 +1,842 @@
+/* -*- 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 Inspector Module.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Joe Walker (jwalker@mozilla.com) (original author)
+ *   Mihai Șucan <mihai.sucan@gmail.com>
+ *   Michael Ratcliffe <mratcliffe@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/PluralForm.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource:///modules/devtools/CssLogic.jsm");
+Cu.import("resource:///modules/devtools/Templater.jsm");
+
+var EXPORTED_SYMBOLS = ["CssHtmlTree"];
+
+/**
+ * CssHtmlTree is a panel that manages the display of a table sorted by style.
+ * There should be one instance of CssHtmlTree per style display (of which there
+ * will generally only be one).
+ *
+ * @params {Document} aStyleWin The main XUL browser document
+ * @params {CssLogic} aCssLogic How we dig into the CSS. See CssLogic.jsm
+ * @constructor
+ */
+function CssHtmlTree(aStyleWin, aCssLogic, aPanel)
+{
+  this.styleWin = aStyleWin;
+  this.cssLogic = aCssLogic;
+  this.doc = aPanel.ownerDocument;
+  this.win = this.doc.defaultView;
+  this.getRTLAttr = CssHtmlTree.getRTLAttr;
+
+  // The document in which we display the results (csshtmltree.xhtml).
+  this.styleDocument = this.styleWin.contentWindow.document;
+
+  // Nodes used in templating
+  this.root = this.styleDocument.getElementById("root");
+  this.templateRoot = this.styleDocument.getElementById("templateRoot");
+  this.panel = aPanel;
+
+  // The element that we're inspecting, and the document that it comes from.
+  this.viewedElement = null;
+  this.viewedDocument = null;
+
+  this.createStyleGroupViews();
+}
+
+/**
+ * Memonized lookup of a l10n string from a string bundle.
+ * @param {string} aName The key to lookup.
+ * @returns A localized version of the given key.
+ */
+CssHtmlTree.l10n = function CssHtmlTree_l10n(aName)
+{
+  try {
+    return CssHtmlTree._strings.GetStringFromName(aName);
+  } catch (ex) {
+    Services.console.logStringMessage("Error reading '" + aName + "'");
+    throw new Error("l10n error with " + aName);
+  }
+};
+
+/**
+ * Clone the given template node, and process it by resolving ${} references
+ * in the template.
+ *
+ * @param {nsIDOMElement} aTemplate the template note to use.
+ * @param {nsIDOMElement} aDestination the destination node where the
+ * processed nodes will be displayed.
+ * @param {object} aData the data to pass to the template.
+ */
+CssHtmlTree.processTemplate = function CssHtmlTree_processTemplate(aTemplate, aDestination, aData)
+{
+  aDestination.innerHTML = "";
+
+  // All the templater does is to populate a given DOM tree with the given
+  // values, so we need to clone the template first.
+  let duplicated = aTemplate.cloneNode(true);
+  new Templater().processNode(duplicated, aData);
+  while (duplicated.firstChild) {
+    aDestination.appendChild(duplicated.firstChild);
+  }
+};
+
+/**
+ * Checks whether the UI is RTL
+ * @return {Boolean} true or false
+ */
+CssHtmlTree.isRTL = function CssHtmlTree_isRTL()
+{
+  return CssHtmlTree.getRTLAttr == "rtl";
+};
+
+/**
+ * Checks whether the UI is RTL
+ * @return {String} "ltr" or "rtl"
+ */
+XPCOMUtils.defineLazyGetter(CssHtmlTree, "getRTLAttr", function() {
+  let mainWindow = Services.wm.getMostRecentWindow("navigator:browser");
+  return mainWindow.getComputedStyle(mainWindow.gBrowser).direction;
+});
+
+XPCOMUtils.defineLazyGetter(CssHtmlTree, "_strings", function() Services.strings
+    .createBundle("chrome://browser/locale/styleinspector.properties"));
+
+CssHtmlTree.prototype = {
+  /**
+   * Focus the output display on a specific element.
+   * @param {nsIDOMElement} aElement The highlighted node to get styles for.
+   */
+  highlight: function CssHtmlTree_highlight(aElement)
+  {
+    this.viewedElement = aElement;
+
+    // Reset the style groups. Without this previously expanded groups
+    // will fail to expand when inspecting subsequent nodes
+    let close = !aElement;
+    this.styleGroups.forEach(function(group) group.reset(close));
+
+    if (this.viewedElement) {
+      this.viewedDocument = this.viewedElement.ownerDocument;
+      CssHtmlTree.processTemplate(this.templateRoot, this.root, this);
+    } else {
+      this.viewedDocument = null;
+      this.root.innerHTML = "";
+    }
+  },
+
+  /**
+   * Called when the user clicks on a parent element in the "current element"
+   * path.
+   *
+   * @param {Event} aEvent the DOM Event object.
+   */
+  pathClick: function CssHtmlTree_pathClick(aEvent)
+  {
+    aEvent.preventDefault();
+    if (aEvent.target && aEvent.target.pathElement) {
+      if (this.win.InspectorUI.selection) {
+        if (aEvent.target.pathElement != this.win.InspectorUI.selection) {
+          this.win.InspectorUI.inspectNode(aEvent.target.pathElement);
+        }
+      } else {
+        this.panel.selectNode(aEvent.target.pathElement);
+      }
+    }
+  },
+
+  /**
+   * Provide access to the path to get from document.body to the selected
+   * element.
+   *
+   * @return {array} the array holding the path from document.body to the
+   * selected element.
+   */
+  get pathElements()
+  {
+    return CssLogic.getShortNamePath(this.viewedElement);
+  },
+
+  /**
+   * Returns arrays of categorized properties.
+   */
+  _getPropertiesByGroup: function CssHtmlTree_getPropertiesByGroup()
+  {
+    return {
+      text: [
+        "color",                    // inherit http://www.w3.org/TR/CSS21/propidx.html
+        "color-interpolation",      //
+        "color-interpolation-filters", //
+        "direction",                // inherit http://www.w3.org/TR/CSS21/propidx.html
+        "fill",                     //
+        "fill-opacity",             //
+        "fill-rule",                //
+        "filter",                   //
+        "flood-color",              //
+        "flood-opacity",            //
+        "font-family",              // inherit http://www.w3.org/TR/CSS21/propidx.html
+        "font-size",                // inherit http://www.w3.org/TR/CSS21/propidx.html
+        "font-size-adjust",         // inherit http://www.w3.org/TR/WD-font/#font-size-props
+        "font-stretch",             // inherit http://www.w3.org/TR/WD-font/#font-stretch
+        "font-style",               // inherit http://www.w3.org/TR/CSS21/propidx.html
+        "font-variant",             // inherit http://www.w3.org/TR/CSS21/propidx.html
+        "font-weight",              // inherit http://www.w3.org/TR/CSS21/propidx.html
+        "ime-mode",                 //
+        "letter-spacing",           // inherit http://www.w3.org/TR/CSS21/propidx.html
+        "lighting-color",           //
+        "line-height",              // inherit http://www.w3.org/TR/CSS21/propidx.html
+        "opacity",                  // no      http://www.w3.org/TR/css3-color/#transparency
+        "quotes",                   // inherit http://www.w3.org/TR/CSS21/propidx.html
+        "stop-color",               //
+        "stop-opacity",             //
+        "stroke-opacity",           //
+        "text-align",               // inherit http://www.w3.org/TR/CSS21/propidx.html
+        "text-anchor",              //
+        "text-decoration",          // no      http://www.w3.org/TR/CSS21/propidx.html
+        "text-indent",              // inherit http://www.w3.org/TR/CSS21/propidx.html
+        "text-overflow",            //
+        "text-rendering",           // inherit http://www.w3.org/TR/SVG/painting.html#TextRenderingProperty !
+        "text-shadow",              // inherit http://www.w3.org/TR/css3-text/#text-shadow
+        "text-transform",           // inherit http://www.w3.org/TR/CSS21/propidx.html
+        "vertical-align",           // no      http://www.w3.org/TR/CSS21/propidx.html
+        "white-space",              // inherit http://www.w3.org/TR/CSS21/propidx.html
+        "word-spacing",             // inherit http://www.w3.org/TR/css3-text/#word-spacing
+        "word-wrap",                // inherit http://www.w3.org/TR/css3-text/#word-wrap
+        "-moz-column-count",        // no      http://www.w3.org/TR/css3-multicol/#column-count
+        "-moz-column-gap",          // no      http://www.w3.org/TR/css3-multicol/#column-gap
+        "-moz-column-rule-color",   // no      http://www.w3.org/TR/css3-multicol/#crc
+        "-moz-column-rule-style",   // no      http://www.w3.org/TR/css3-multicol/#column-rule-style
+        "-moz-column-rule-width",   // no      http://www.w3.org/TR/css3-multicol/#column-rule-width
+        "-moz-column-width",        // no      http://www.w3.org/TR/css3-multicol/#column-width
+        "-moz-font-feature-settings",  //
+        "-moz-font-language-override", //
+        "-moz-hyphens",                //
+        "-moz-text-decoration-color",  //
+        "-moz-text-decoration-style",  //
+        "-moz-text-decoration-line",   //
+        "-moz-text-blink",          //
+        "-moz-tab-size",            //
+      ],
+      list: [
+        "list-style-image",         // inherit http://www.w3.org/TR/CSS21/propidx.html
+        "list-style-position",      // inherit http://www.w3.org/TR/CSS21/propidx.html
+        "list-style-type",          // inherit http://www.w3.org/TR/CSS21/propidx.html
+        "marker-end",               //
+        "marker-mid",               //
+        "marker-offset",            //
+        "marker-start",             //
+      ],
+      background: [
+        "background-attachment",    // no      http://www.w3.org/TR/css3-background/#background-attachment
+        "background-clip",          // no      http://www.w3.org/TR/css3-background/#background-clip
+        "background-color",         // no      http://www.w3.org/TR/css3-background/#background-color
+        "background-image",         // no      http://www.w3.org/TR/css3-background/#background-image
+        "background-origin",        // no      http://www.w3.org/TR/css3-background/#background-origin
+        "background-position",      // no      http://www.w3.org/TR/css3-background/#background-position
+        "background-repeat",        // no      http://www.w3.org/TR/css3-background/#background-repeat
+        "background-size",          // no      http://www.w3.org/TR/css3-background/#background-size
+        "-moz-appearance",          //
+        "-moz-background-inline-policy", //
+      ],
+      dims: [
+        "width",                    // no      http://www.w3.org/TR/CSS21/propidx.html
+        "height",                   // no      http://www.w3.org/TR/CSS21/propidx.html
+        "max-width",                // no      http://www.w3.org/TR/CSS21/propidx.html
+        "max-height",               // no      http://www.w3.org/TR/CSS21/propidx.html
+        "min-width",                // no      http://www.w3.org/TR/CSS21/propidx.html
+        "min-height",               // no      http://www.w3.org/TR/CSS21/propidx.html
+        "margin-top",               // no      http://www.w3.org/TR/CSS21/propidx.html
+        "margin-right",             // no      http://www.w3.org/TR/CSS21/propidx.html
+        "margin-bottom",            // no      http://www.w3.org/TR/CSS21/propidx.html
+        "margin-left",              // no      http://www.w3.org/TR/CSS21/propidx.html
+        "padding-top",              // no      http://www.w3.org/TR/CSS21/propidx.html
+        "padding-right",            // no      http://www.w3.org/TR/CSS21/propidx.html
+        "padding-bottom",           // no      http://www.w3.org/TR/CSS21/propidx.html
+        "padding-left",             // no      http://www.w3.org/TR/CSS21/propidx.html
+        "clip",                     // no      http://www.w3.org/TR/CSS21/propidx.html
+        "clip-path",                //
+        "clip-rule",                //
+        "resize",                   // no      http://www.w3.org/TR/css3-ui/#resize
+        "stroke-width",             //
+        "-moz-box-flex",            //
+        "-moz-box-sizing",          // no      http://www.w3.org/TR/css3-ui/#box-sizing
+      ],
+      pos: [
+        "top",                      // no      http://www.w3.org/TR/CSS21/propidx.html
+        "right",                    // no      http://www.w3.org/TR/CSS21/propidx.html
+        "bottom",                   // no      http://www.w3.org/TR/CSS21/propidx.html
+        "left",                     // no      http://www.w3.org/TR/CSS21/propidx.html
+        "display",                  // no      http://www.w3.org/TR/CSS21/propidx.html
+        "float",                    // no      http://www.w3.org/TR/CSS21/propidx.html
+        "clear",                    // no      http://www.w3.org/TR/CSS21/propidx.html
+        "position",                 // no      http://www.w3.org/TR/CSS21/propidx.html
+        "visibility",               // inherit http://www.w3.org/TR/CSS21/propidx.html
+        "overflow",                 //
+        "overflow-x",               // no      http://www.w3.org/TR/CSS21/propidx.html
+        "overflow-y",               // no      http://www.w3.org/TR/CSS21/propidx.html
+        "z-index",                  // no      http://www.w3.org/TR/CSS21/propidx.html
+        "dominant-baseline",        //
+        "page-break-after",         //
+        "page-break-before",        //
+        "stroke-dashoffset",        //
+        "unicode-bidi",             //
+        "-moz-box-align",           //
+        "-moz-box-direction",       //
+        "-moz-box-ordinal-group",   //
+        "-moz-box-orient",          //
+        "-moz-box-pack",            //
+        "-moz-float-edge",          //
+        "-moz-orient",              //
+        "-moz-stack-sizing",        //
+      ],
+      border: [
+        "border-top-width",         // no      http://www.w3.org/TR/CSS21/propidx.html
+        "border-right-width",       // no      http://www.w3.org/TR/CSS21/propidx.html
+        "border-bottom-width",      // no      http://www.w3.org/TR/CSS21/propidx.html
+        "border-left-width",        // no      http://www.w3.org/TR/CSS21/propidx.html
+        "border-top-color",         // no      http://www.w3.org/TR/CSS21/propidx.html
+        "border-right-color",       // no      http://www.w3.org/TR/CSS21/propidx.html
+        "border-bottom-color",      // no      http://www.w3.org/TR/CSS21/propidx.html
+        "border-left-color",        // no      http://www.w3.org/TR/CSS21/propidx.html
+        "border-top-style",         // no      http://www.w3.org/TR/CSS21/propidx.html
+        "border-right-style",       // no      http://www.w3.org/TR/CSS21/propidx.html
+        "border-bottom-style",      // no      http://www.w3.org/TR/CSS21/propidx.html
+        "border-left-style",        // no      http://www.w3.org/TR/CSS21/propidx.html
+        "border-collapse",          // no      http://www.w3.org/TR/CSS21/propidx.html
+        "border-spacing",           // no      http://www.w3.org/TR/CSS21/propidx.html
+        "outline-offset",           // no      http://www.w3.org/TR/CSS21/propidx.html
+        "outline-style",            //
+        "outline-color",            //
+        "outline-width",            //
+        "border-top-left-radius",       // no http://www.w3.org/TR/css3-background/#border-radius
+        "border-top-right-radius",      // no http://www.w3.org/TR/css3-background/#border-radius
+        "border-bottom-right-radius",   // no http://www.w3.org/TR/css3-background/#border-radius
+        "border-bottom-left-radius",    // no http://www.w3.org/TR/css3-background/#border-radius
+        "-moz-border-bottom-colors",    //
+        "-moz-border-image",            //
+        "-moz-border-left-colors",      //
+        "-moz-border-right-colors",     //
+        "-moz-border-top-colors",       //
+        "-moz-outline-radius-topleft",      // no http://www.w3.org/TR/CSS2/ui.html#dynamic-outlines ?
+        "-moz-outline-radius-topright",     // no http://www.w3.org/TR/CSS2/ui.html#dynamic-outlines ?
+        "-moz-outline-radius-bottomright",  // no http://www.w3.org/TR/CSS2/ui.html#dynamic-outlines ?
+        "-moz-outline-radius-bottomleft",   // no http://www.w3.org/TR/CSS2/ui.html#dynamic-outlines ?
+      ],
+      other: [
+        "box-shadow",               // no      http://www.w3.org/TR/css3-background/#box-shadow
+        "caption-side",             // inherit http://www.w3.org/TR/CSS21/propidx.html
+        "content",                  // no      http://www.w3.org/TR/CSS21/propidx.html
+        "counter-increment",        // no      http://www.w3.org/TR/CSS21/propidx.html
+        "counter-reset",            // no      http://www.w3.org/TR/CSS21/propidx.html
+        "cursor",                   // inherit http://www.w3.org/TR/CSS21/propidx.html
+        "empty-cells",              // inherit http://www.w3.org/TR/CSS21/propidx.html
+        "image-rendering",          // inherit http://www.w3.org/TR/SVG/painting.html#ImageRenderingProperty
+        "mask",                     //
+        "pointer-events",           // inherit http://www.w3.org/TR/SVG11/interact.html#PointerEventsProperty
+        "shape-rendering",          //
+        "stroke",                   //
+        "stroke-dasharray",         //
+        "stroke-linecap",           //
+        "stroke-linejoin",          //
+        "stroke-miterlimit",        //
+        "table-layout",             // no      http://www.w3.org/TR/CSS21/propidx.html
+        "-moz-animation-delay",     //
+        "-moz-animation-direction", //
+        "-moz-animation-duration",  //
+        "-moz-animation-fill-mode", //
+        "-moz-animation-iteration-count", //
+        "-moz-animation-name",            //
+        "-moz-animation-play-state",      //
+        "-moz-animation-timing-function", //
+        "-moz-backface-visibility",       //
+        "-moz-binding",                   //
+        "-moz-force-broken-image-icon",   //
+        "-moz-image-region",        //
+        "-moz-perspective",         //
+        "-moz-perspective-origin",  //
+        "-moz-transform",           // no      http://www.w3.org/TR/css3-2d-transforms/#transform-property
+        "-moz-transform-origin",    //
+        "-moz-transition-delay",    //
+        "-moz-transition-duration", //
+        "-moz-transition-property", //
+        "-moz-transition-timing-function", //
+        "-moz-user-focus",          // inherit http://www.w3.org/TR/2000/WD-css3-userint-20000216#user-focus
+        "-moz-user-input",          // inherit http://www.w3.org/TR/2000/WD-css3-userint-20000216#user-input
+        "-moz-user-modify",         // inherit http://www.w3.org/TR/2000/WD-css3-userint-20000216#user-modify
+        "-moz-user-select",         // no      http://www.w3.org/TR/2000/WD-css3-userint-20000216#user-select
+        "-moz-window-shadow",       //
+      ],
+    };
+  },
+
+  /**
+   * The CSS groups as displayed by the UI.
+   */
+  createStyleGroupViews: function CssHtmlTree_createStyleGroupViews()
+  {
+    if (!CssHtmlTree.propertiesByGroup) {
+      let pbg = CssHtmlTree.propertiesByGroup = this._getPropertiesByGroup();
+
+      // Add any supported properties that are not categorized to the "other" group
+      let mergedArray = Array.concat(
+          pbg.text,
+          pbg.list,
+          pbg.background,
+          pbg.dims,
+          pbg.pos,
+          pbg.border,
+          pbg.other
+      );
+
+      // Here we build and cache a list of css properties supported by the browser
+      // and store a list to check against. We could use any element but let's
+      // use the inspector style panel
+      let styles = this.styleWin.contentWindow.getComputedStyle(this.styleDocument.body);
+      CssHtmlTree.supportedPropertyLookup = {};
+      for (let i = 0, numStyles = styles.length; i < numStyles; i++) {
+        let prop = styles.item(i);
+        CssHtmlTree.supportedPropertyLookup[prop] = true;
+
+        if (mergedArray.indexOf(prop) == -1) {
+          pbg.other.push(prop);
+        }
+      }
+
+      this.propertiesByGroup = CssHtmlTree.propertiesByGroup;
+    }
+
+    let pbg = CssHtmlTree.propertiesByGroup;
+
+    // These group titles are localized by their ID. See the styleinspector.properties file.
+    this.styleGroups = [
+      new StyleGroupView(this, "Text_Fonts_and_Color", pbg.text),
+      new StyleGroupView(this, "Lists", pbg.list),
+      new StyleGroupView(this, "Background", pbg.background),
+      new StyleGroupView(this, "Dimensions", pbg.dims),
+      new StyleGroupView(this, "Positioning_and_Page_Flow", pbg.pos),
+      new StyleGroupView(this, "Borders", pbg.border),
+      new StyleGroupView(this, "Effects_and_Other", pbg.other),
+    ];
+  },
+};
+
+/**
+ * A container to give easy access to style group data from the template engine.
+ *
+ * @constructor
+ * @param {CssHtmlTree} aTree the instance of the CssHtmlTree object that we are
+ * working with.
+ * @param {string} aId the style group ID.
+ * @param {array} aPropertyNames the list of property names associated to this
+ * style group view.
+ */
+function StyleGroupView(aTree, aId, aPropertyNames)
+{
+  this.tree = aTree;
+  this.id = aId;
+  this.getRTLAttr = CssHtmlTree.getRTLAttr;
+  this.localName = CssHtmlTree.l10n("group." + this.id);
+
+  this.propertyViews = [];
+  aPropertyNames.forEach(function(aPropertyName) {
+    if (this.isPropertySupported(aPropertyName)) {
+      this.propertyViews.push(new PropertyView(this.tree, this, aPropertyName));
+    }
+  }, this);
+
+  this.populated = false;
+
+  this.templateProperties = this.tree.styleDocument.getElementById("templateProperties");
+
+  // Populated by templater: parent element containing the open attribute
+  this.element = null;
+  // Destination for templateProperties.
+  this.properties = null;
+}
+
+StyleGroupView.prototype = {
+  /**
+   * The click event handler for the title of the style group view.
+   */
+  click: function StyleGroupView_click()
+  {
+    // TODO: Animate opening/closing. See bug 587752.
+    if (this.element.hasAttribute("open")) {
+      this.element.removeAttribute("open");
+      return;
+    }
+
+    if (!this.populated) {
+      CssHtmlTree.processTemplate(this.templateProperties, this.properties, this);
+      this.populated = true;
+    }
+
+    this.element.setAttribute("open", "");
+  },
+
+  /**
+   * Close the style group view.
+   */
+  close: function StyleGroupView_close()
+  {
+    if (this.element) {
+      this.element.removeAttribute("open");
+    }
+  },
+
+  /**
+   * Reset the style group view and its property views.
+   *
+   * @param {boolean} aClosePanel tells if the style panel is closing or not.
+   */
+  reset: function StyleGroupView_reset(aClosePanel)
+  {
+    this.close();
+    this.populated = false;
+    for (let i = 0, numViews = this.propertyViews.length; i < numViews; i++) {
+      this.propertyViews[i].reset();
+    }
+
+    if (this.properties) {
+      if (aClosePanel) {
+        if (this.element) {
+          this.element.removeChild(this.properties);
+        }
+
+        this.properties = null;
+      } else {
+        while (this.properties.hasChildNodes()) {
+          this.properties.removeChild(this.properties.firstChild);
+        }
+      }
+    }
+  },
+
+  /**
+   * Check if a CSS property is supported
+   *
+   * @param {string} aProperty the CSS property to check for
+   *
+   * @return {boolean} true or false
+   */
+  isPropertySupported: function(aProperty) {
+    return aProperty && aProperty in CssHtmlTree.supportedPropertyLookup;
+  },
+};
+
+/**
+ * A container to give easy access to property data from the template engine.
+ *
+ * @constructor
+ * @param {CssHtmlTree} aTree the CssHtmlTree instance we are working with.
+ * @param {StyleGroupView} aGroup the StyleGroupView instance we are working
+ * with.
+ * @param {string} aName the CSS property name for which this PropertyView
+ * instance will render the rules.
+ */
+function PropertyView(aTree, aGroup, aName)
+{
+  this.tree = aTree;
+  this.group = aGroup;
+  this.name = aName;
+  this.getRTLAttr = CssHtmlTree.getRTLAttr;
+
+  this.populated = false;
+  this.showUnmatched = false;
+
+  this.link = "https://developer.mozilla.org/en/CSS/" + aName;
+
+  this.templateRules = this.tree.styleDocument.getElementById("templateRules");
+
+  // The parent element which contains the open attribute
+  this.element = null;
+  // Destination for templateRules.
+  this.rules = null;
+
+  this.str = {};
+}
+
+PropertyView.prototype = {
+  /**
+   * The click event handler for the property name of the property view. If
+   * there are >0 rules then the rules are expanded. If there are 0 rules and
+   * >0 unmatched rules then the unmatched rules are expanded instead.
+   *
+   * @param {Event} aEvent the DOM event
+   */
+  click: function PropertyView_click(aEvent)
+  {
+    // Clicking on the property link itself is already handled
+    if (aEvent.target.tagName.toLowerCase() == "a") {
+      return;
+    }
+
+    // TODO: Animate opening/closing. See bug 587752.
+    if (this.element.hasAttribute("open")) {
+      this.element.removeAttribute("open");
+      return;
+    }
+
+    if (!this.populated) {
+      let matchedRuleCount = this.propertyInfo.matchedRuleCount;
+
+      if (matchedRuleCount == 0 && this.showUnmatchedLink) {
+        this.showUnmatchedLinkClick(aEvent);
+      } else {
+        CssHtmlTree.processTemplate(this.templateRules, this.rules, this);
+      }
+      this.populated = true;
+    }
+    this.element.setAttribute("open", "");
+  },
+
+  /**
+   * Get the computed style for the current property.
+   *
+   * @return {string} the computed style for the current property of the
+   * currently highlighted element.
+   */
+  get value()
+  {
+    return this.propertyInfo.value;
+  },
+
+  /**
+   * An easy way to access the CssPropertyInfo behind this PropertyView
+   */
+  get propertyInfo()
+  {
+    return this.tree.cssLogic.getPropertyInfo(this.name);
+  },
+
+  /**
+   * Compute the title of the property view. The title includes the number of
+   * rules that hold the current property.
+   *
+   * @param {nsIDOMElement} aElement reference to the DOM element where the rule
+   * title needs to be displayed.
+   * @return {string} The rule title.
+   */
+  ruleTitle: function PropertyView_ruleTitle(aElement)
+  {
+    let result = "";
+    let matchedRuleCount = this.propertyInfo.matchedRuleCount;
+
+    if (matchedRuleCount > 0) {
+      aElement.classList.add("rule-count");
+
+      let str = CssHtmlTree.l10n("property.numberOfRules");
+      result = PluralForm.get(matchedRuleCount, str).replace("#1", matchedRuleCount);
+    } else if (this.showUnmatchedLink) {
+      aElement.classList.add("rule-unmatched");
+
+      let unmatchedRuleCount = this.propertyInfo.unmatchedRuleCount;
+      let str = CssHtmlTree.l10n("property.numberOfUnmatchedRules");
+      result = PluralForm.get(unmatchedRuleCount, str).replace("#1", unmatchedRuleCount);
+    }
+    return result;
+  },
+
+  /**
+   * Close the property view.
+   */
+  close: function PropertyView_close()
+  {
+    if (this.rules && this.element) {
+      this.element.removeAttribute("open");
+    }
+  },
+
+  /**
+   * Reset the property view.
+   */
+  reset: function PropertyView_reset()
+  {
+    this.close();
+    this.populated = false;
+    this.showUnmatched = false;
+    this.element = false;
+  },
+
+  /**
+   * Provide access to the SelectorViews that we are currently displaying
+   */
+  get selectorViews()
+  {
+    var all = [];
+
+    function convert(aSelectorInfo) {
+      all.push(new SelectorView(aSelectorInfo));
+    }
+
+    this.propertyInfo.matchedSelectors.forEach(convert);
+    if (this.showUnmatched) {
+      this.propertyInfo.unmatchedSelectors.forEach(convert);
+    }
+
+    return all;
+  },
+
+  /**
+   * Should we display a 'X unmatched rules' link?
+   * @return {boolean} false if we are already showing the unmatched links or
+   * if there are none to display, true otherwise.
+   */
+  get showUnmatchedLink()
+  {
+    return !this.showUnmatched && this.propertyInfo.unmatchedRuleCount > 0;
+  },
+
+  /**
+   * The UI has a link to allow the user to display unmatched selectors.
+   * This provides localized link text.
+   */
+  get showUnmatchedLinkText()
+  {
+    let smur = CssHtmlTree.l10n("rule.showUnmatchedLink");
+    let plural = PluralForm.get(this.propertyInfo.unmatchedRuleCount, smur);
+    return plural.replace("#1", this.propertyInfo.unmatchedRuleCount);
+  },
+
+  /**
+   * The action when a user clicks the 'show unmatched' link.
+   */
+  showUnmatchedLinkClick: function PropertyView_showUnmatchedLinkClick(aEvent)
+  {
+    this.showUnmatched = true;
+    CssHtmlTree.processTemplate(this.templateRules, this.rules, this);
+    aEvent.preventDefault();
+  },
+};
+
+/**
+ * A container to view us easy access to display data from a CssRule
+ */
+function SelectorView(aSelectorInfo)
+{
+  this.selectorInfo = aSelectorInfo;
+  this._cacheStatusNames();
+}
+
+/**
+ * Decode for cssInfo.rule.status
+ * @see SelectorView.prototype._cacheStatusNames
+ * @see CssLogic.STATUS
+ */
+SelectorView.STATUS_NAMES = [
+  // "Unmatched", "Parent Match", "Matched", "Best Match"
+];
+
+SelectorView.CLASS_NAMES = [
+  "unmatched", "parentmatch", "matched", "bestmatch"
+];
+
+SelectorView.prototype = {
+  /**
+   * Cache localized status names.
+   *
+   * These statuses are localized inside the styleinspector.properties string bundle.
+   * @see CssLogic.jsm - the CssLogic.STATUS array.
+   *
+   * @return {void}
+   */
+  _cacheStatusNames: function SelectorView_cacheStatusNames()
+  {
+    if (SelectorView.STATUS_NAMES.length) {
+      return;
+    }
+
+    for (let status in CssLogic.STATUS) {
+      let i = CssLogic.STATUS[status];
+      if (i > -1) {
+        let value = CssHtmlTree.l10n("rule.status." + status);
+        // Replace normal spaces with non-breaking spaces
+        SelectorView.STATUS_NAMES[i] = value.replace(/ /g, '\u00A0');
+      }
+    }
+  },
+
+  /**
+   * A localized version of cssRule.status
+   */
+  get statusText()
+  {
+    return SelectorView.STATUS_NAMES[this.selectorInfo.status];
+  },
+
+  /**
+   * Get class name for selector depending on status
+   */
+  get statusClass()
+  {
+    return SelectorView.CLASS_NAMES[this.selectorInfo.status];
+  },
+
+  /**
+   * A localized Get localized human readable info
+   */
+  humanReadableText: function SelectorView_humanReadableText(aElement)
+  {
+    if (CssHtmlTree.isRTL()) {
+      return this.selectorInfo.value + " \u2190 " + this.text(aElement);
+    } else {
+      return this.text(aElement) + " \u2192 " + this.selectorInfo.value;
+    }
+  },
+
+  text: function SelectorView_text(aElement) {
+    let result = this.selectorInfo.selector.text;
+    if (this.selectorInfo.elementStyle) {
+      if (this.selectorInfo.sourceElement == this.win.InspectorUI.selection) {
+        result = "this";
+      } else {
+        result = CssLogic.getShortName(this.selectorInfo.sourceElement);
+        aElement.parentNode.querySelector(".rule-link > a").
+          addEventListener("click", function(aEvent) {
+            this.win.InspectorUI.inspectNode(this.selectorInfo.sourceElement);
+            aEvent.preventDefault();
+          }, false);
+      }
+
+      result += ".style";
+    }
+    return result;
+  },
+};
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/CssLogic.jsm
@@ -0,0 +1,1603 @@
+/* -*- 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 Inspector Module.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Joe Walker <jwalker@mozilla.com> (original author)
+ *   Mihai Șucan <mihai.sucan@gmail.com>
+ *   Michael Ratcliffe <mratcliffe@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * About the objects defined in this file:
+ * - CssLogic contains style information about a view context. It provides
+ *   access to 2 sets of objects: Css[Sheet|Rule|Selector] provide access to
+ *   information that does not change when the selected element changes while
+ *   Css[Property|Selector]Info provide information that is dependent on the
+ *   selected element.
+ *   Its key methods are highlight(), getPropertyInfo() and forEachSheet(), etc
+ *   It also contains a number of static methods for l10n, naming, etc
+ *
+ * - CssSheet provides a more useful API to a DOM CSSSheet for our purposes,
+ *   including shortSource and href.
+ * - CssRule a more useful API to a nsIDOMCSSRule including access to the group
+ *   of CssSelectors that the rule provides properties for
+ * - CssSelector A single selector - i.e. not a selector group. In other words
+ *   a CssSelector does not contain ','. This terminology is different from the
+ *   standard DOM API, but more inline with the definition in the spec.
+ *
+ * - CssPropertyInfo contains style information for a single property for the
+ *   highlighted element. It divides the CSS rules on the page into matched and
+ *   unmatched rules.
+ * - CssSelectorInfo is a wrapper around CssSelector, which adds sorting with
+ *   reference to the selected element.
+ */
+
+/**
+ * Provide access to the style information in a page.
+ * CssLogic uses the standard DOM API, and the Gecko inIDOMUtils API to access
+ * styling information in the page, and present this to the user in a way that
+ * helps them understand:
+ * - why their expectations may not have been fulfilled
+ * - how browsers process CSS
+ * @constructor
+ */
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var EXPORTED_SYMBOLS = ["CssLogic"];
+
+function CssLogic()
+{
+  // The cache of examined CSS properties.
+  _propertyInfos: {};
+}
+
+/**
+ * Special values for filter, in addition to an href these values can be used
+ */
+CssLogic.FILTER = {
+  ALL: "all", // show properties from all user style sheets.
+  UA: "ua",   // ALL, plus user-agent (i.e. browser) style sheets
+};
+
+/**
+ * Known media values. To distinguish "all" stylesheets (above) from "all" media
+ * The full list includes braille, embossed, handheld, print, projection,
+ * speech, tty, and tv, but this is only a hack because these are not defined
+ * in the DOM at all.
+ * @see http://www.w3.org/TR/CSS21/media.html#media-types
+ */
+CssLogic.MEDIA = {
+  ALL: "all",
+  SCREEN: "screen",
+};
+
+/**
+ * Each rule has a status, the bigger the number, the better placed it is to
+ * provide styling information.
+ *
+ * These statuses are localized inside the styleinspector.properties string bundle.
+ * @see csshtmltree.js RuleView._cacheStatusNames()
+ */
+CssLogic.STATUS = {
+  BEST: 3,
+  MATCHED: 2,
+  PARENT_MATCH: 1,
+  UNMATCHED: 0,
+  UNKNOWN: -1,
+};
+
+CssLogic.prototype = {
+  // Both setup by highlight().
+  viewedElement: null,
+  viewedDocument: null,
+
+  // The cache of the known sheets.
+  _sheets: null,
+
+  // Have the sheets been cached?
+  _sheetsCached: false,
+
+  // The total number of rules, in all stylesheets, after filtering.
+  _ruleCount: 0,
+
+  // The computed styles for the viewedElement.
+  _computedStyle: null,
+
+  // Source filter. Only display properties coming from the given source
+  _sourceFilter: CssLogic.FILTER.ALL,
+
+  // Used for tracking unique CssSheet/CssRule/CssSelector objects, in a run of
+  // processMatchedSelectors().
+  _passId: 0,
+
+  // Used for tracking matched CssSelector objects, such that we can skip them
+  // in processUnmatchedSelectors().
+  _matchId: 0,
+
+  _matchedSelectors: null,
+  _unmatchedSelectors: null,
+
+  domUtils: Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils),
+
+  /**
+   * Reset various properties
+   */
+  reset: function CssLogic_reset()
+  {
+    this._propertyInfos = {};
+    this._ruleCount = 0;
+    this._sheetIndex = 0;
+    this._sheets = {};
+    this._sheetsCached = false;
+    this._matchedSelectors = null;
+    this._unmatchedSelectors = null;
+  },
+
+  /**
+   * Focus on a new element - remove the style caches.
+   *
+   * @param {nsIDOMElement} aViewedElement the element the user has highlighted
+   * in the Inspector.
+   */
+  highlight: function CssLogic_highlight(aViewedElement)
+  {
+    if (!aViewedElement) {
+      this.viewedElement = null;
+      this.viewedDocument = null;
+      this._computedStyle = null;
+      this.reset();
+      return;
+    }
+
+    this.viewedElement = aViewedElement;
+
+    let doc = this.viewedElement.ownerDocument;
+    if (doc != this.viewedDocument) {
+      // New document: clear/rebuild the cache.
+      this.viewedDocument = doc;
+
+      // Hunt down top level stylesheets, and cache them.
+      this._cacheSheets();
+    } else {
+      // Clear cached data in the CssPropertyInfo objects.
+      this._propertyInfos = {};
+    }
+
+    this._matchedSelectors = null;
+    this._unmatchedSelectors = null;
+    let win = this.viewedDocument.defaultView;
+    this._computedStyle = win.getComputedStyle(this.viewedElement, "");
+  },
+
+  /**
+   * Get the source filter.
+   * @returns {string} The source filter being used.
+   */
+  get sourceFilter() {
+    return this._sourceFilter;
+  },
+
+  /**
+   * Source filter. Only display properties coming from the given source (web
+   * address).
+   * @see CssLogic.FILTER.*
+   */
+  set sourceFilter(aValue) {
+    let oldValue = this._sourceFilter;
+    this._sourceFilter = aValue;
+
+    let ruleCount = 0;
+
+    // Update the CssSheet objects.
+    this.forEachSheet(function(aSheet) {
+      aSheet._sheetAllowed = -1;
+      if (!aSheet.systemSheet && aSheet.sheetAllowed) {
+        ruleCount += aSheet.ruleCount;
+      }
+    }, this);
+
+    this._ruleCount = ruleCount;
+
+    // Full update is needed because the this.processMatchedSelectors() method
+    // skips UA stylesheets if the filter does not allow such sheets.
+    let needFullUpdate = (oldValue == CssLogic.FILTER.UA ||
+        aValue == CssLogic.FILTER.UA);
+
+    if (needFullUpdate) {
+      this._matchedSelectors = null;
+      this._unmatchedSelectors = null;
+      this._propertyInfos = {};
+    } else {
+      // Update the CssPropertyInfo objects.
+      for each (let propertyInfo in this._propertyInfos) {
+        propertyInfo.needRefilter = true;
+      }
+    }
+  },
+
+  /**
+   * Return a CssPropertyInfo data structure for the currently viewed element
+   * and the specified CSS property. If there is no currently viewed element we
+   * return an empty object.
+   *
+   * @param {string} aProperty The CSS property to look for.
+   * @return {CssPropertyInfo} a CssPropertyInfo structure for the given
+   * property.
+   */
+  getPropertyInfo: function CssLogic_getPropertyInfo(aProperty)
+  {
+    if (!this.viewedElement) {
+      return {};
+    }
+
+    let info = this._propertyInfos[aProperty];
+    if (!info) {
+      info = new CssPropertyInfo(this, aProperty);
+      this._propertyInfos[aProperty] = info;
+    }
+
+    return info;
+  },
+
+  /**
+   * Cache all the stylesheets in the inspected document
+   * @private
+   */
+  _cacheSheets: function CssLogic_cacheSheets()
+  {
+    this._passId++;
+    this.reset();
+
+    // styleSheets isn't an array, but forEach can work on it anyway
+    Array.prototype.forEach.call(this.viewedDocument.styleSheets,
+        this._cacheSheet, this);
+    
+    this._sheetsCached = true;
+  },
+
+  /**
+   * Cache a stylesheet if it falls within the requirements: if it's enabled,
+   * and if the @media is allowed. This method also walks through the stylesheet
+   * cssRules to find @imported rules, to cache the stylesheets of those rules
+   * as well.
+   *
+   * @private
+   * @param {CSSStyleSheet} aDomSheet the CSSStyleSheet object to cache.
+   */
+  _cacheSheet: function CssLogic_cacheSheet(aDomSheet)
+  {
+    if (aDomSheet.disabled) {
+      return;
+    }
+
+    // Only work with stylesheets that have their media allowed.
+    if (!CssLogic.sheetMediaAllowed(aDomSheet)) {
+      return;
+    }
+
+    // Cache the sheet.
+    let cssSheet = this.getSheet(aDomSheet, false, this._sheetIndex++);
+    if (cssSheet._passId != this._passId) {
+      cssSheet._passId = this._passId;
+
+      // Find import rules.
+      Array.prototype.forEach.call(aDomSheet.cssRules, function(aDomRule) {
+        if (aDomRule.type == Ci.nsIDOMCSSRule.IMPORT_RULE && aDomRule.styleSheet &&
+            CssLogic.sheetMediaAllowed(aDomRule)) {
+          this._cacheSheet(aDomRule.styleSheet);
+        }
+      }, this);
+    }
+  },
+
+  /**
+   * Retrieve the list of stylesheets in the document.
+   *
+   * @return {array} the list of stylesheets in the document.
+   */
+  get sheets()
+  {
+    if (!this._sheetsCached) {
+      this._cacheSheets();
+    }
+
+    let sheets = [];
+    this.forEachSheet(function (aSheet) {
+      if (!aSheet.systemSheet) {
+        sheets.push(aSheet);
+      }
+    }, this);
+
+    return sheets;
+  },
+
+  /**
+   * Retrieve a CssSheet object for a given a CSSStyleSheet object. If the
+   * stylesheet is already cached, you get the existing CssSheet object,
+   * otherwise the new CSSStyleSheet object is cached.
+   *
+   * @param {CSSStyleSheet} aDomSheet the CSSStyleSheet object you want.
+   * @param {boolean} aSystemSheet tells if the stylesheet is a browser-provided
+   * sheet or not.
+   * @param {number} aIndex the index, within the document, of the stylesheet.
+   *
+   * @return {CssSheet} the CssSheet object for the given CSSStyleSheet object.
+   */
+  getSheet: function CL_getSheet(aDomSheet, aSystemSheet, aIndex)
+  {
+    let cacheId = aSystemSheet ? "1" : "0";
+
+    if (aDomSheet.href) {
+      cacheId += aDomSheet.href;
+    } else if (aDomSheet.ownerNode && aDomSheet.ownerNode.ownerDocument) {
+      cacheId += aDomSheet.ownerNode.ownerDocument.location;
+    }
+
+    let sheet = null;
+    let sheetFound = false;
+
+    if (cacheId in this._sheets) {
+      for (let i = 0, numSheets = this._sheets[cacheId].length; i < numSheets; i++) {
+        sheet = this._sheets[cacheId][i];
+        if (sheet.domSheet == aDomSheet) {
+          sheet.index = aIndex;
+          sheetFound = true;
+          break;
+        }
+      }
+    }
+
+    if (!sheetFound) {
+      if (!(cacheId in this._sheets)) {
+        this._sheets[cacheId] = [];
+      }
+
+      sheet = new CssSheet(this, aDomSheet, aSystemSheet, aIndex);
+      if (sheet.sheetAllowed && !aSystemSheet) {
+        this._ruleCount += sheet.ruleCount;
+      }
+
+      this._sheets[cacheId].push(sheet);
+    }
+
+    return sheet;
+  },
+
+  /**
+   * Process each cached stylesheet in the document using your callback.
+   *
+   * @param {function} aCallback the function you want executed for each of the
+   * CssSheet objects cached.
+   * @param {object} aScope the scope you want for the callback function. aScope
+   * will be the this object when aCallback executes.
+   */
+  forEachSheet: function CssLogic_forEachSheet(aCallback, aScope)
+  {
+    for each (let sheet in this._sheets) {
+      sheet.forEach(aCallback, aScope);
+    }
+  },
+
+  /**
+   * Get the number nsIDOMCSSRule objects in the document, counted from all of
+   * the stylesheets. System sheets are excluded. If a filter is active, this
+   * tells only the number of nsIDOMCSSRule objects inside the selected
+   * CSSStyleSheet.
+   *
+   * WARNING: This only provides an estimate of the rule count, and the results
+   * could change at a later date. Todo remove this
+   *
+   * @return {number} the number of nsIDOMCSSRule (all rules).
+   */
+  get ruleCount()
+  {
+    if (!this._sheetsCached) {
+      this._cacheSheets();
+    }
+
+    return this._ruleCount;
+  },
+
+  /**
+   * Process the CssSelector objects that match the highlighted element and its
+   * parent elements. aScope.aCallback() is executed for each CssSelector
+   * object, being passed the CssSelector object and the match status.
+   *
+   * This method also includes all of the element.style properties, for each
+   * highlighted element parent and for the highlighted element itself.
+   *
+   * Note that the matched selectors are cached, such that next time your
+   * callback is invoked for the cached list of CssSelector objects.
+   *
+   * @param {function} aCallback the function you want to execute for each of
+   * the matched selectors.
+   * @param {object} aScope the scope you want for the callback function. aScope
+   * will be the this object when aCallback executes.
+   */
+  processMatchedSelectors: function CL_processMatchedSelectors(aCallback, aScope)
+  {
+    if (this._matchedSelectors) {
+      if (aCallback) {
+        this._passId++;
+        this._matchedSelectors.forEach(function(aValue) {
+          aCallback.call(aScope, aValue[0], aValue[1]);
+          aValue[0]._cssRule._passId = this._passId;
+        }, this);
+      }
+      return;
+    }
+
+    this._matchedSelectors = [];
+    this._unmatchedSelectors = null;
+    this._passId++;
+    this._matchId++;
+
+    let element = this.viewedElement;
+    let filter = this.sourceFilter;
+    let sheetIndex = 0;
+    let domRules = null;
+    do {
+      try {
+        domRules = this.domUtils.getCSSStyleRules(element);
+      } catch (ex) {
+        Services.console.
+            logStringMessage("CssLogic_processMatchedSelectors error: " + ex);
+        continue;
+      }
+
+      let status = (this.viewedElement == element) ?
+          CssLogic.STATUS.MATCHED : CssLogic.STATUS.PARENT_MATCH;
+
+      for (let i = 0, numRules = domRules.Count(); i < numRules; i++) {
+        let domRule = domRules.GetElementAt(i);
+        if (domRule.type !== Ci.nsIDOMCSSRule.STYLE_RULE) {
+          continue;
+        }
+
+        let domSheet = domRule.parentStyleSheet;
+        let systemSheet = CssLogic.isSystemStyleSheet(domSheet);
+        if (filter !== CssLogic.FILTER.UA && systemSheet) {
+          continue;
+        }
+
+        let sheet = this.getSheet(domSheet, systemSheet, sheetIndex);
+        let rule = sheet.getRule(domRule);
+
+        rule.selectors.forEach(function (aSelector) {
+          if (aSelector._matchId !== this._matchId &&
+              element.mozMatchesSelector(aSelector)) {
+            aSelector._matchId = this._matchId;
+            this._matchedSelectors.push([ aSelector, status ]);
+            if (aCallback) {
+              aCallback.call(aScope, aSelector, status);
+            }
+          }
+        }, this);
+
+        if (sheet._passId !== this._passId) {
+          sheetIndex++;
+          sheet._passId = this._passId;
+        }
+
+        if (rule._passId !== this._passId) {
+          rule._passId = this._passId;
+        }
+      }
+
+      // Add element.style information.
+      if (element.style.length > 0) {
+        let rule = new CssRule(null, { style: element.style }, element);
+        let selector = rule.selectors[0];
+        selector._matchId = this._matchId;
+
+        this._matchedSelectors.push([ selector, status ]);
+        if (aCallback) {
+          aCallback.call(aScope, selector, status);
+        }
+        rule._passId = this._passId;
+      }
+    } while ((element = element.parentNode) &&
+        element.nodeType === Ci.nsIDOMNode.ELEMENT_NODE);
+  },
+
+  /**
+   * Process the CssSelector object that do not match the highlighted elements,
+   * nor its parents. Your callback function is invoked for every such
+   * CssSelector object. You receive one argument: the CssSelector object.
+   *
+   * The list of unmatched selectors is cached.
+   *
+   * @param {function} aCallback the function you want to execute for each of
+   * the unmatched selectors.
+   * @param {object} aScope the scope you want for the callback function. aScope
+   * will be the this object when aCallback executes.
+   */
+  processUnmatchedSelectors: function CL_processUnmatchedSelectors(aCallback, aScope)
+  {
+    if (!this._matchedSelectors) {
+      this.processMatchedSelectors();
+    }
+
+    if (this._unmatchedSelectors) {
+      if (aCallback) {
+        this._unmatchedSelectors.forEach(aCallback, aScope);
+      }
+      return;
+    }
+
+    this._unmatchedSelectors = [];
+
+    this.forEachSheet(function (aSheet) {
+      aSheet.forEachRule(function (aRule) {
+        aRule.selectors.forEach(function (aSelector) {
+          if (aSelector._matchId != this._matchId) {
+            this._unmatchedSelectors.push(aSelector);
+            if (aCallback) {
+              aCallback.call(aScope, aSelector);
+            }
+          }
+        }, this);
+      }, this);
+    }, this);
+  },
+};
+
+/**
+ * If the element has an id, return '#id'. Otherwise return 'tagname[n]' where
+ * n is the index of this element in its siblings.
+ * <p>A technically more 'correct' output from the no-id case might be:
+ * 'tagname:nth-of-type(n)' however this is unlikely to be more understood
+ * and it is longer.
+ *
+ * @param {nsIDOMElement} aElement the element for which you want the short name.
+ * @return {string} the string to be displayed for aElement.
+ */
+CssLogic.getShortName = function CssLogic_getShortName(aElement)
+{
+  if (!aElement) {
+    return "null";
+  }
+  if (aElement.id) {
+    return "#" + aElement.id;
+  }
+  let priorSiblings = 0;
+  let temp = aElement;
+  while (temp = temp.previousElementSibling) {
+    priorSiblings++;
+  }
+  return aElement.tagName + "[" + priorSiblings + "]";
+};
+
+/**
+ * Get an array of short names from the given element to document.body.
+ *
+ * @param {nsIDOMElement} aElement the element for which you want the array of
+ * short names.
+ * @return {array} The array of elements.
+ * <p>Each element is an object of the form:
+ * <ul>
+ * <li>{ display: "what to display for the given (parent) element",
+ * <li>  element: referenceToTheElement }
+ * </ul>
+ */
+CssLogic.getShortNamePath = function CssLogic_getShortNamePath(aElement)
+{
+  let doc = aElement.ownerDocument;
+  let reply = [];
+
+  if (!aElement) {
+    return reply;
+  }
+
+  // We want to exclude nodes high up the tree (body/html) unless the user
+  // has selected that node, in which case we need to report something.
+  do {
+    reply.unshift({
+      display: CssLogic.getShortName(aElement),
+      element: aElement
+    });
+    aElement = aElement.parentNode;
+  } while (aElement && aElement != doc.body && aElement != doc.head && aElement != doc);
+
+  return reply;
+};
+
+/**
+ * Memonized lookup of a l10n string from a string bundle.
+ * @param {string} aName The key to lookup.
+ * @returns A localized version of the given key.
+ */
+CssLogic.l10n = function(aName) CssLogic._strings.GetStringFromName(aName);
+
+XPCOMUtils.defineLazyGetter(CssLogic, "_strings", function() Services.strings
+          .createBundle("chrome://browser/locale/styleinspector.properties"));
+
+/**
+ * Is the given property sheet a system (user agent) stylesheet?
+ *
+ * @param {CSSStyleSheet} aSheet a stylesheet
+ * @return {boolean} true if the given stylesheet is a system stylesheet or
+ * false otherwise.
+ */
+CssLogic.isSystemStyleSheet = function CssLogic_isSystemStyleSheet(aSheet)
+{
+  if (!aSheet) {
+    return true;
+  }
+
+  let url = aSheet.href;
+
+  if (!url) return false;
+  if (url.length === 0) return true;
+
+  // Check for http[s]
+  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;
+};
+
+/**
+ * Check if the given DOM CSS object holds an allowed media. Currently we only
+ * allow media screen or all.
+ *
+ * @param {CSSStyleSheet|CSSImportRule|CSSMediaRule} aDomObject the
+ * DOM object you want checked.
+ * @return {boolean} true if the media description is allowed, or false
+ * otherwise.
+ */
+CssLogic.sheetMediaAllowed = function CssLogic_sheetMediaAllowed(aDomObject)
+{
+  let result = false;
+  let media = aDomObject.media;
+
+  if (media.length > 0) {
+    let mediaItem = null;
+    for (let m = 0, mediaLen = media.length; m < mediaLen; m++) {
+      mediaItem = media.item(m).toLowerCase();
+      if (mediaItem === CssLogic.MEDIA.SCREEN ||
+          mediaItem === CssLogic.MEDIA.ALL) {
+        result = true;
+        break;
+      }
+    }
+  } else {
+    result = true;
+  }
+
+  return result;
+};
+
+/**
+ * A safe way to access cached bits of information about a stylesheet.
+ *
+ * @constructor
+ * @param {CssLogic} aCssLogic pointer to the CssLogic instance working with
+ * this CssSheet object.
+ * @param {CSSStyleSheet} aDomSheet reference to a DOM CSSStyleSheet object.
+ * @param {boolean} aSystemSheet tells if the stylesheet is system-provided.
+ * @param {number} aIndex tells the index/position of the stylesheet within the
+ * main document.
+ */
+function CssSheet(aCssLogic, aDomSheet, aSystemSheet, aIndex)
+{
+  this._cssLogic = aCssLogic;
+  this.domSheet = aDomSheet;
+  this.systemSheet = aSystemSheet;
+  this.index = this.systemSheet ? -100 * aIndex : aIndex;
+
+  // Cache of the sheets href. Cached by the getter.
+  this._href = null;
+  // Short version of href for use in select boxes etc. Cached by getter.
+  this._shortSource = null;
+
+  // null for uncached.
+  this._sheetAllowed = null;
+
+  // Cached CssRules from the given stylesheet.
+  this._rules = {};
+
+  this._ruleCount = -1;
+}
+
+CssSheet.prototype = {
+  /**
+   * Get a source for a stylesheet, taking into account embedded stylesheets
+   * for which we need to use document.defaultView.location.href rather than
+   * sheet.href
+   *
+   * @return {string} the address of the stylesheet.
+   */
+  get href()
+  {
+    if (!this._href) {
+      this._href = this.domSheet.href;
+      if (!this._href) {
+        this._href = this.domSheet.ownerNode.ownerDocument.location;
+      }
+    }
+
+    return this._href;
+  },
+
+  /**
+   * Create a shorthand version of the href of a stylesheet.
+   *
+   * @return {string} the shorthand source of the stylesheet.
+   */
+  get shortSource()
+  {
+    if (this._shortSource) {
+      return this._shortSource;
+    }
+
+    // Use a string like "inline" if there is no source href
+    if (!this.domSheet.href) {
+      this._shortSource = CssLogic.l10n("rule.sourceInline");
+      return this._shortSource;
+    }
+
+    // We try, in turn, the filename, filePath, query string, whole thing
+    let url = Services.io.newURI(this.domSheet.href, null, null);
+    url = url.QueryInterface(Ci.nsIURL);
+    if (url.fileName) {
+      this._shortSource = url.fileName;
+      return this._shortSource;
+    }
+
+    if (url.filePath) {
+      this._shortSource = url.filePath;
+      return this._shortSource;
+    }
+
+    if (url.query) {
+      this._shortSource = url.query;
+      return this._shortSource;
+    }
+
+    this._shortSource = this.domSheet.href;
+    return this._shortSource;
+  },
+
+  /**
+   * Tells if the sheet is allowed or not by the current CssLogic.sourceFilter.
+   *
+   * @return {boolean} true if the stylesheet is allowed by the sourceFilter, or
+   * false otherwise.
+   */
+  get sheetAllowed()
+  {
+    if (this._sheetAllowed !== null) {
+      return this._sheetAllowed;
+    }
+
+    this._sheetAllowed = true;
+
+    let filter = this._cssLogic.sourceFilter;
+    if (filter === CssLogic.FILTER.ALL && this.systemSheet) {
+      this._sheetAllowed = false;
+    }
+    if (filter !== CssLogic.FILTER.ALL && filter !== CssLogic.FILTER.UA) {
+      this._sheetAllowed = (filter === this.href);
+    }
+
+    return this._sheetAllowed;
+  },
+
+  /**
+   * Retrieve the number of rules in this stylesheet.
+   *
+   * @return {number} the number of nsIDOMCSSRule objects in this stylesheet.
+   */
+  get ruleCount()
+  {
+    return this._ruleCount > -1 ?
+        this._ruleCount :
+        this.domSheet.cssRules.length;
+  },
+
+  /**
+   * Retrieve a CssRule object for the given CSSStyleRule. The CssRule object is
+   * cached, such that subsequent retrievals return the same CssRule object for
+   * the same CSSStyleRule object.
+   *
+   * @param {CSSStyleRule} aDomRule the CSSStyleRule object for which you want a
+   * CssRule object.
+   * @return {CssRule} the cached CssRule object for the given CSSStyleRule
+   * object.
+   */
+  getRule: function CssSheet_getRule(aDomRule)
+  {
+    let cacheId = aDomRule.type + aDomRule.selectorText;
+
+    let rule = null;
+    let ruleFound = false;
+
+    if (cacheId in this._rules) {
+      for (let i = 0, rulesLen = this._rules[cacheId].length; i < rulesLen; i++) {
+        rule = this._rules[cacheId][i];
+        if (rule._domRule == aDomRule) {
+          ruleFound = true;
+          break;
+        }
+      }
+    }
+
+    if (!ruleFound) {
+      if (!(cacheId in this._rules)) {
+        this._rules[cacheId] = [];
+      }
+
+      rule = new CssRule(this, aDomRule);
+      this._rules[cacheId].push(rule);
+    }
+
+    return rule;
+  },
+
+  /**
+   * Process each rule in this stylesheet using your callback function. Your
+   * function receives one argument: the CssRule object for each CSSStyleRule
+   * inside the stylesheet.
+   *
+   * Note that this method also iterates through @media rules inside the
+   * stylesheet.
+   *
+   * @param {function} aCallback the function you want to execute for each of
+   * the style rules.
+   * @param {object} aScope the scope you want for the callback function. aScope
+   * will be the this object when aCallback executes.
+   */
+  forEachRule: function CssSheet_forEachRule(aCallback, aScope)
+  {
+    let ruleCount = 0;
+    let domRules = this.domSheet.cssRules;
+
+    function _iterator(aDomRule) {
+      if (aDomRule.type == Ci.nsIDOMCSSRule.STYLE_RULE) {
+        aCallback.call(aScope, this.getRule(aDomRule));
+        ruleCount++;
+      } else if (aDomRule.type == Ci.nsIDOMCSSRule.MEDIA_RULE &&
+          aDomRule.cssRules && CssLogic.sheetMediaAllowed(aDomRule)) {
+        Array.prototype.forEach.call(aDomRule.cssRules, _iterator, this);
+      }
+    }
+
+    Array.prototype.forEach.call(domRules, _iterator, this);
+
+    this._ruleCount = ruleCount;
+  },
+
+  toString: function CssSheet_toString()
+  {
+    return "CssSheet[" + this.shortSource + "]";
+  },
+};
+
+/**
+ * Information about a single CSSStyleRule.
+ *
+ * @param {CSSSheet|null} aCssSheet the CssSheet object of the stylesheet that
+ * holds the CSSStyleRule. If the rule comes from element.style, set this
+ * argument to null.
+ * @param {CSSStyleRule|object} aDomRule the DOM CSSStyleRule for which you want
+ * to cache data. If the rule comes from element.style, then provide
+ * an object of the form: {style: element.style}.
+ * @param {Element} [aElement] If the rule comes from element.style, then this
+ * argument must point to the element.
+ * @constructor
+ */
+function CssRule(aCssSheet, aDomRule, aElement)
+{
+  this._cssSheet = aCssSheet;
+  this._domRule = aDomRule;
+
+  if (this._cssSheet) {
+    // parse _domRule.selectorText on call to this.selectors
+    this._selectors = null;
+    this.line = this._cssSheet._cssLogic.domUtils.getRuleLine(this._domRule);
+    this.source = this._cssSheet.shortSource + ":" + this.line;
+    this.href = this._cssSheet.href;
+    this.systemRule = this._cssSheet.systemSheet;
+  } else if (aElement) {
+    this._selectors = [ new CssSelector(this, "@element.style") ];
+    this.line = -1;
+    this.source = CssLogic.l10n("rule.sourceElement");
+    this.href = "#";
+    this.systemRule = false;
+    this.sourceElement = aElement;
+  }
+}
+
+CssRule.prototype = {
+  /**
+   * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter.
+   *
+   * @return {boolean} true if the parent stylesheet is allowed by the current
+   * sourceFilter, or false otherwise.
+   */
+  get sheetAllowed()
+  {
+    return this._cssSheet ? this._cssSheet.sheetAllowed : true;
+  },
+
+  /**
+   * Retrieve the parent stylesheet index/position in the viewed document.
+   *
+   * @return {number} the parent stylesheet index/position in the viewed
+   * document.
+   */
+  get sheetIndex()
+  {
+    return this._cssSheet ? this._cssSheet.index : 0;
+  },
+
+  /**
+   * Retrieve the style property value from the current CSSStyleRule.
+   *
+   * @param {string} aProperty the CSS property name for which you want the
+   * value.
+   * @return {string} the property value.
+   */
+  getPropertyValue: function(aProperty)
+  {
+    return this._domRule.style.getPropertyValue(aProperty);
+  },
+
+  /**
+   * Retrieve the style property priority from the current CSSStyleRule.
+   *
+   * @param {string} aProperty the CSS property name for which you want the
+   * priority.
+   * @return {string} the property priority.
+   */
+  getPropertyPriority: function(aProperty)
+  {
+    return this._domRule.style.getPropertyPriority(aProperty);
+  },
+
+  /**
+   * Retrieve the list of CssSelector objects for each of the parsed selectors
+   * of the current CSSStyleRule.
+   *
+   * @return {array} the array hold the CssSelector objects.
+   */
+  get selectors()
+  {
+    if (this._selectors) {
+      return this._selectors;
+    }
+
+    // Parse the CSSStyleRule.selectorText string.
+    this._selectors = [];
+
+    if (!this._domRule.selectorText) {
+      return this._selectors;
+    }
+
+    let selector = this._domRule.selectorText.trim();
+    if (!selector) {
+      return this._selectors;
+    }
+
+    let nesting = 0;
+    let currentSelector = [];
+
+    // Parse a selector group into selectors. Normally we could just .split(',')
+    // however Gecko allows -moz-any(a, b, c) as a selector so we ignore commas
+    // inside brackets.
+    for (let i = 0, selLen = selector.length; i < selLen; i++) {
+      let c = selector.charAt(i);
+      switch (c) {
+        case ",":
+          if (nesting == 0 && currentSelector.length > 0) {
+            let selectorStr = currentSelector.join("").trim();
+            if (selectorStr) {
+              this._selectors.push(new CssSelector(this, selectorStr));
+            }
+            currentSelector = [];
+          } else {
+            currentSelector.push(c);
+          }
+          break;
+
+        case "(":
+          nesting++;
+          currentSelector.push(c);
+          break;
+
+        case ")":
+          nesting--;
+          currentSelector.push(c);
+          break;
+
+        default:
+          currentSelector.push(c);
+          break;
+      }
+    }
+
+    // Add the last selector.
+    if (nesting == 0 && currentSelector.length > 0) {
+      let selectorStr = currentSelector.join("").trim();
+      if (selectorStr) {
+        this._selectors.push(new CssSelector(this, selectorStr));
+      }
+    }
+
+    return this._selectors;
+  },
+
+  toString: function CssRule_toString()
+  {
+    return "[CssRule " + this._domRule.selectorText + "]";
+  },
+};
+
+/**
+ * The CSS selector class allows us to document the ranking of various CSS
+ * selectors.
+ *
+ * @constructor
+ * @param {CssRule} aCssRule the CssRule instance from where the selector comes.
+ * @param {string} aSelector The selector that we wish to investigate.
+ */
+function CssSelector(aCssRule, aSelector)
+{
+  this._cssRule = aCssRule;
+  this.text = aSelector;
+  this.elementStyle = this.text == "@element.style";
+}
+
+CssSelector.prototype = {
+  /**
+   * Retrieve the CssSelector source, which is the source of the CssSheet owning
+   * the selector.
+   *
+   * @return {string} the selector source.
+   */
+  get source()
+  {
+    return this._cssRule.source;
+  },
+
+  /**
+   * Retrieve the CssSelector source element, which is the source of the CssRule
+   * owning the selector. This is only available when the CssSelector comes from
+   * an element.style.
+   *
+   * @return {string} the source element selector.
+   */
+  get sourceElement()
+  {
+    return this._cssRule.sourceElement;
+  },
+
+  /**
+   * Retrieve the address of the CssSelector. This points to the address of the
+   * CssSheet owning this selector.
+   *
+   * @return {string} the address of the CssSelector.
+   */
+  get href()
+  {
+    return this._cssRule.href;
+  },
+
+  /**
+   * Check if the selector comes from a browser-provided stylesheet.
+   *
+   * @return {boolean} true if the selector comes from a browser-provided
+   * stylesheet, or false otherwise.
+   */
+  get systemRule()
+  {
+    return this._cssRule.systemRule;
+  },
+
+  /**
+   * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter.
+   *
+   * @return {boolean} true if the parent stylesheet is allowed by the current
+   * sourceFilter, or false otherwise.
+   */
+  get sheetAllowed()
+  {
+    return this._cssRule.sheetAllowed;
+  },
+
+  /**
+   * Retrieve the parent stylesheet index/position in the viewed document.
+   *
+   * @return {number} the parent stylesheet index/position in the viewed
+   * document.
+   */
+  get sheetIndex()
+  {
+    return this._cssRule.sheetIndex;
+  },
+
+  /**
+   * Retrieve the line of the parent CSSStyleRule in the parent CSSStyleSheet.
+   *
+   * @return {number} the line of the parent CSSStyleRule in the parent
+   * stylesheet.
+   */
+  get ruleLine()
+  {
+    return this._cssRule.line;
+  },
+
+  toString: function CssSelector_toString()
+  {
+    return this.text;
+  },
+};
+
+/**
+ * A cache of information about the matched rules, selectors and values attached
+ * to a CSS property, for the highlighted element.
+ *
+ * The heart of the CssPropertyInfo object is the _findMatchedSelectors() and
+ * _findUnmatchedSelectors() methods. These are invoked when the PropertyView
+ * tries to access the .matchedSelectors and .unmatchedSelectors arrays.
+ * Results are cached, for later reuse.
+ *
+ * @param {CssLogic} aCssLogic Reference to the parent CssLogic instance
+ * @param {string} aProperty The CSS property we are gathering information for
+ * @constructor
+ */
+function CssPropertyInfo(aCssLogic, aProperty)
+{
+  this._cssLogic = aCssLogic;
+  this.property = aProperty;
+  this._value = "";
+
+  // The number of matched rules holding the this.property style property.
+  // Additionally, only rules that come from allowed stylesheets are counted.
+  this._matchedRuleCount = 0;
+
+  // An array holding CssSelectorInfo objects for each of the matched selectors
+  // that are inside a CSS rule. Only rules that hold the this.property are
+  // counted. This includes rules that come from filtered stylesheets (those
+  // that have sheetAllowed = false).
+  this._matchedSelectors = null;
+}
+
+CssPropertyInfo.prototype = {
+  /**
+   * Retrieve the computed style value for the current property, for the
+   * highlighted element.
+   *
+   * @return {string} the computed style value for the current property, for the
+   * highlighted element.
+   */
+  get value()
+  {
+    if (!this._value && this._cssLogic._computedStyle) {
+      try {
+        this._value = this._cssLogic._computedStyle.getPropertyValue(this.property);
+      } catch (ex) {
+        Services.console.logStringMessage('Error reading computed style for ' +
+          this.property);
+        Services.console.logStringMessage(ex);
+      }
+    }
+
+    return this._value;
+  },
+
+  /**
+   * Retrieve the number of matched rules holding the this.property style
+   * property. Only rules that come from allowed stylesheets are counted.
+   *
+   * @return {number} the number of matched rules.
+   */
+  get matchedRuleCount()
+  {
+    if (!this._matchedSelectors) {
+      this._findMatchedSelectors();
+    } else if (this.needRefilter) {
+      this._refilterSelectors();
+    }
+
+    return this._matchedRuleCount;
+  },
+
+  /**
+   * Retrieve the number of unmatched rules.
+   *
+   * @return {number} the number of rules that do not match the highlighted
+   * element or its parents.
+   */
+  get unmatchedRuleCount()
+  {
+    if (!this._unmatchedSelectors) {
+      this._findUnmatchedSelectors();
+    } else if (this.needRefilter) {
+      this._refilterSelectors();
+    }
+
+    return this._unmatchedRuleCount;
+  },
+
+  /**
+   * Retrieve the array holding CssSelectorInfo objects for each of the matched
+   * selectors, from each of the matched rules. Only selectors coming from
+   * allowed stylesheets are included in the array.
+   *
+   * @return {array} the list of CssSelectorInfo objects of selectors that match
+   * the highlighted element and its parents.
+   */
+  get matchedSelectors()
+  {
+    if (!this._matchedSelectors) {
+      this._findMatchedSelectors();
+    } else if (this.needRefilter) {
+      this._refilterSelectors();
+    }
+
+    return this._matchedSelectors;
+  },
+
+  /**
+   * Retrieve the array holding CssSelectorInfo objects for each of the
+   * unmatched selectors, from each of the unmatched rules. Only selectors
+   * coming from allowed stylesheets are included in the array.
+   *
+   * @return {array} the list of CssSelectorInfo objects of selectors that do
+   * not match the highlighted element or its parents.
+   */
+  get unmatchedSelectors()
+  {
+    if (!this._unmatchedSelectors) {
+      this._findUnmatchedSelectors();
+    } else if (this.needRefilter) {
+      this._refilterSelectors();
+    }
+
+    return this._unmatchedSelectors;
+  },
+
+  /**
+   * Find the selectors that match the highlighted element and its parents.
+   * Uses CssLogic.processMatchedSelectors() to find the matched selectors,
+   * passing in a reference to CssPropertyInfo._processMatchedSelector() to
+   * create CssSelectorInfo objects, which we then sort
+   * @private
+   */
+  _findMatchedSelectors: function CssPropertyInfo_findMatchedSelectors()
+  {
+    this._matchedSelectors = [];
+    this._matchedRuleCount = 0;
+    this.needRefilter = false;
+
+    this._cssLogic.processMatchedSelectors(this._processMatchedSelector, this);
+
+    // Sort the selectors by how well they match the given element.
+    this._matchedSelectors.sort(function(aSelectorInfo1, aSelectorInfo2) {
+      if (aSelectorInfo1.status > aSelectorInfo2.status) {
+        return -1;
+      } else if (aSelectorInfo2.status > aSelectorInfo1.status) {
+        return 1;
+      } else {
+        return aSelectorInfo1.compareTo(aSelectorInfo2);
+      }
+    });
+
+    // Now we know which of the matches is best, we can mark it BEST_MATCH.
+    if (this._matchedSelectors.length > 0 &&
+        this._matchedSelectors[0].status > CssLogic.STATUS.UNMATCHED) {
+      this._matchedSelectors[0].status = CssLogic.STATUS.BEST;
+    }
+  },
+
+  /**
+   * Process a matched CssSelector object.
+   *
+   * @private
+   * @param {CssSelector} aSelector the matched CssSelector object.
+   * @param {CssLogic.STATUS} aStatus the CssSelector match status.
+   */
+  _processMatchedSelector: function CssPropertyInfo_processMatchedSelector(aSelector, aStatus)
+  {
+    let cssRule = aSelector._cssRule;
+    let value = cssRule.getPropertyValue(this.property);
+    if (value) {
+      let selectorInfo = new CssSelectorInfo(aSelector, this.property, value,
+          aStatus);
+      this._matchedSelectors.push(selectorInfo);
+      if (this._cssLogic._passId !== cssRule._passId && cssRule.sheetAllowed) {
+        this._matchedRuleCount++;
+      }
+    }
+  },
+
+  /**
+   * Find the selectors that do not match the highlighted element and its
+   * parents.
+   * @private
+   */
+  _findUnmatchedSelectors: function CssPropertyInfo_findUnmatchedSelectors()
+  {
+    this._unmatchedSelectors = [];
+    this._unmatchedRuleCount = 0;
+    this.needRefilter = false;
+    this._cssLogic._passId++;
+
+    this._cssLogic.processUnmatchedSelectors(this._processUnmatchedSelector,
+        this);
+
+    // Sort the selectors by specificity.
+    this._unmatchedSelectors.sort(function(aSelectorInfo1, aSelectorInfo2) {
+      return aSelectorInfo1.compareTo(aSelectorInfo2);
+    });
+  },
+
+  /**
+   * Process an unmatched CssSelector object.
+   *
+   * @private
+   * @param {CssSelector} aSelector the unmatched CssSelector object.
+   */
+  _processUnmatchedSelector: function CPI_processUnmatchedSelector(aSelector)
+  {
+    let cssRule = aSelector._cssRule;
+    if (cssRule.systemRule) {
+      return;
+    }
+
+    let value = cssRule.getPropertyValue(this.property);
+    if (value) {
+      let selectorInfo = new CssSelectorInfo(aSelector, this.property, value,
+          CssLogic.STATUS.UNMATCHED);
+      this._unmatchedSelectors.push(selectorInfo);
+      if (this._cssLogic._passId != cssRule._passId) {
+        if (cssRule.sheetAllowed) {
+          this._unmatchedRuleCount++;
+        }
+        cssRule._passId = this._cssLogic._passId;
+      }
+    }
+  },
+
+  /**
+   * Refilter the matched and unmatched selectors arrays when the
+   * CssLogic.sourceFilter changes. This allows for quick filter changes.
+   * @private
+   */
+  _refilterSelectors: function CssPropertyInfo_refilterSelectors()
+  {
+    let passId = ++this._cssLogic._passId;
+    let ruleCount = 0;
+
+    if (this._matchedSelectors) {
+      this._matchedSelectors.forEach(function(aSelectorInfo) {
+        let cssRule = aSelectorInfo.selector._cssRule;
+        if (cssRule._passId != passId) {
+          if (cssRule.sheetAllowed) {
+            ruleCount++;
+          }
+          cssRule._passId = passId;
+        }
+      });
+      this._matchedRuleCount = ruleCount;
+    }
+
+    if (this._unmatchedSelectors) {
+      ruleCount = 0;
+      this._unmatchedSelectors.forEach(function(aSelectorInfo) {
+        let cssRule = aSelectorInfo.selector._cssRule;
+        if (!cssRule.systemRule && cssRule._passId != passId) {
+          if (cssRule.sheetAllowed) {
+            ruleCount++;
+          }
+          cssRule._passId = passId;
+        }
+      });
+      this._unmatchedRuleCount = ruleCount;
+    }
+
+    this.needRefilter = false;
+  },
+
+  toString: function CssPropertyInfo_toString()
+  {
+    return "CssPropertyInfo[" + this.property + "]";
+  },
+};
+
+/**
+ * A class that holds information about a given CssSelector object.
+ *
+ * Instances of this class are given to CssHtmlTree in the arrays of matched and
+ * unmatched selectors. Each such object represents a displayable row in the
+ * PropertyView objects. The information given by this object blends data coming
+ * from the CssSheet, CssRule and from the CssSelector that own this object.
+ *
+ * @param {CssSelector} aSelector The CssSelector object for which to present information.
+ * @param {string} aProperty The property for which information should be retrieved.
+ * @param {string} aValue The property value from the CssRule that owns the selector.
+ * @param {CssLogic.STATUS} aStatus The selector match status.
+ * @constructor
+ */
+function CssSelectorInfo(aSelector, aProperty, aValue, aStatus)
+{
+  this.selector = aSelector;
+  this.property = aProperty;
+  this.value = aValue;
+  this.status = aStatus;
+
+  let priority = this.selector._cssRule.getPropertyPriority(this.property);
+  this.important = (priority === "important");
+}
+
+CssSelectorInfo.prototype = {
+  /**
+   * Retrieve the CssSelector source, which is the source of the CssSheet owning
+   * the selector.
+   *
+   * @return {string} the selector source.
+   */
+  get source()
+  {
+    return this.selector.source;
+  },
+
+  /**
+   * Retrieve the CssSelector source element, which is the source of the CssRule
+   * owning the selector. This is only available when the CssSelector comes from
+   * an element.style.
+   *
+   * @return {string} the source element selector.
+   */
+  get sourceElement()
+  {
+    return this.selector.sourceElement;
+  },
+
+  /**
+   * Retrieve the address of the CssSelector. This points to the address of the
+   * CssSheet owning this selector.
+   *
+   * @return {string} the address of the CssSelector.
+   */
+  get href()
+  {
+    return this.selector.href;
+  },
+
+  /**
+   * Check if the CssSelector comes from element.style or not.
+   *
+   * @return {boolean} true if the CssSelector comes from element.style, or
+   * false otherwise.
+   */
+  get elementStyle()
+  {
+    return this.selector.elementStyle;
+  },
+
+  /**
+   * Retrieve the parent stylesheet index/position in the viewed document.
+   *
+   * @return {number} the parent stylesheet index/position in the viewed
+   * document.
+   */
+  get sheetIndex()
+  {
+    return this.selector.sheetIndex;
+  },
+
+  /**
+   * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter.
+   *
+   * @return {boolean} true if the parent stylesheet is allowed by the current
+   * sourceFilter, or false otherwise.
+   */
+  get sheetAllowed()
+  {
+    return this.selector.sheetAllowed;
+  },
+
+  /**
+   * Retrieve the line of the parent CSSStyleRule in the parent CSSStyleSheet.
+   *
+   * @return {number} the line of the parent CSSStyleRule in the parent
+   * stylesheet.
+   */
+  get ruleLine()
+  {
+    return this.selector.ruleLine;
+  },
+
+  /**
+   * Check if the selector comes from a browser-provided stylesheet.
+   *
+   * @return {boolean} true if the selector comes from a browser-provided
+   * stylesheet, or false otherwise.
+   */
+  get systemRule()
+  {
+    return this.selector.systemRule;
+  },
+
+  /**
+   * Compare the current CssSelectorInfo instance to another instance, based on
+   * specificity information.
+   *
+   * @param {CssSelectorInfo} aThat The instance to compare ourselves against.
+   * @return number -1, 0, 1 depending on how aThat compares with this.
+   */
+  compareTo: function CssSelectorInfo_compareTo(aThat)
+  {
+    if (this.systemRule && !aThat.systemRule) return 1;
+    if (!this.systemRule && aThat.systemRule) return -1;
+
+    if (this.elementStyle && !aThat.elementStyle) {
+      if (!this.important && aThat.important) return 1;
+      else return -1;
+    }
+
+    if (!this.elementStyle && aThat.elementStyle) {
+      if (this.important && !aThat.important) return -1;
+      else return 1;
+    }
+
+    if (this.important && !aThat.important) return -1;
+    if (aThat.important && !this.important) return 1;
+
+    if (this.sheetIndex > aThat.sheetIndex) return -1;
+    if (aThat.sheetIndex > this.sheetIndex) return 1;
+
+    if (this.ruleLine > aThat.ruleLine) return -1;
+    if (aThat.ruleLine > this.ruleLine) return 1;
+
+    return 0;
+  },
+
+  toString: function CssSelectorInfo_toString()
+  {
+    return this.selector + " -> " + this.value;
+  },
+};
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/Makefile.in
@@ -0,0 +1,55 @@
+#
+# ***** 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  HUDService code.
+#
+# The Initial Developer of the Original Code is Mozilla Corporation.
+# 
+# Portions created by the Initial Developer are Copyright (C) 2010
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#     Mike Ratcliffe <mratcliffe@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 *****
+
+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
+
+libs::
+	$(NSINSTALL) $(srcdir)/*.jsm $(FINAL_TARGET)/modules/devtools
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/StyleInspector.jsm
@@ -0,0 +1,182 @@
+/* -*- 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 Inspector Module.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mike Ratcliffe <mratcliffe@mozilla.com>
+ *
+ * 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;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var EXPORTED_SYMBOLS = ["StyleInspector"];
+
+var StyleInspector = {
+  /**
+   * Is the Style Inspector enabled?
+   * @returns {Boolean} true or false
+   */
+  get isEnabled()
+  {
+    return Services.prefs.getBoolPref("devtools.styleinspector.enabled");
+  },
+
+  createPanel: function SI_createPanel()
+  {
+    let win = Services.wm.getMostRecentWindow("navigator:browser");
+    let popupSet = win.document.getElementById("mainPopupSet");
+    let ns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+    let panel = win.document.createElementNS(ns, "panel");
+
+    panel.setAttribute("orient", "vertical");
+    panel.setAttribute("ignorekeys", "true");
+    panel.setAttribute("noautofocus", "true");
+    panel.setAttribute("noautohide", "true");
+    panel.setAttribute("titlebar", "normal");
+    panel.setAttribute("close", "true");
+    panel.setAttribute("label", StyleInspector.l10n("panelTitle"));
+
+    // size panel to 200px wide by half browser height - 60.
+    let contentWindow = win.gBrowser.selectedBrowser.contentWindow;
+    panel.setAttribute("width", 200);
+    panel.setAttribute("height", contentWindow.outerHeight / 2 - 60);
+
+    let vbox = win.document.createElement("vbox");
+    vbox.setAttribute("flex", "1");
+    panel.appendChild(vbox);
+
+    let iframe = win.document.createElementNS(ns, "iframe");
+    iframe.setAttribute("flex", "1");
+    iframe.setAttribute("tooltip", "aHTMLTooltip");
+    iframe.setAttribute("src", "chrome://browser/content/csshtmltree.xhtml");
+    vbox.appendChild(iframe);
+
+    let hbox = win.document.createElement("hbox");
+    hbox.setAttribute("class", "resizerbox");
+    vbox.appendChild(hbox);
+
+    let spacer = win.document.createElement("spacer");
+    spacer.setAttribute("flex", "1");
+    hbox.appendChild(spacer);
+
+    let resizer = win.document.createElement("resizer");
+    resizer.setAttribute("dir", "bottomend");
+    hbox.appendChild(resizer);
+    popupSet.appendChild(panel);
+
+    panel.addEventListener("popupshown", function SI_popup_shown() {
+      if (!this.cssHtmlTree) {
+        this.cssLogic = new CssLogic();
+        this.cssHtmlTree = new CssHtmlTree(iframe, this.cssLogic, this);
+      }
+
+      this.cssLogic.highlight(this.selectedNode);
+      this.cssHtmlTree.highlight(this.selectedNode);
+      Services.obs.notifyObservers(null, "StyleInspector-opened", null);
+    }, false);
+
+    panel.addEventListener("popuphidden", function SI_popup_hidden() {
+      Services.obs.notifyObservers(null, "StyleInspector-closed", null);
+    }, false);
+    
+    /**
+     * Check if the style inspector is open
+     */
+    panel.isOpen = function SI_isOpen()
+    {
+      return this.state && this.state == "open";
+    };
+
+    /**
+     * Select a node to inspect in the Style Inspector panel
+     *
+     * @param aNode The node to inspect
+     */
+    panel.selectNode = function SI_selectNode(aNode)
+    {
+      this.selectedNode = aNode;
+      if (this.isOpen()) {
+        this.cssLogic.highlight(aNode);
+        this.cssHtmlTree.highlight(aNode);
+      } else {
+        let win = Services.wm.getMostRecentWindow("navigator:browser");
+        this.openPopup(win.gBrowser.selectedBrowser, "end_before", 0, 0, false, false);
+      }
+    };
+
+    /**
+     * Is the Style Inspector initialized?
+     * @returns {Boolean} true or false
+     */
+    function isInitialized()
+    {
+      return panel.cssLogic && panel.cssHtmlTree;
+    }
+
+    return panel;
+  },
+
+  /**
+   * Memonized lookup of a l10n string from a string bundle.
+   * @param {string} aName The key to lookup.
+   * @returns A localized version of the given key.
+   */
+  l10n: function SI_l10n(aName)
+  {
+    try {
+      return _strings.GetStringFromName(aName);
+    } catch (ex) {
+      Services.console.logStringMessage("Error reading '" + aName + "'");
+      throw new Error("l10n error with " + aName);
+    }
+  },
+};
+
+XPCOMUtils.defineLazyGetter(this, "_strings", function() Services.strings
+          .createBundle("chrome://browser/locale/styleinspector.properties"));
+
+XPCOMUtils.defineLazyGetter(this, "CssLogic", function() {
+  let tmp = {};
+  Cu.import("resource:///modules/devtools/CssLogic.jsm", tmp);
+  return tmp.CssLogic;
+});
+
+XPCOMUtils.defineLazyGetter(this, "CssHtmlTree", function() {
+  let tmp = {};
+  Cu.import("resource:///modules/devtools/CssHtmlTree.jsm", tmp);
+  return tmp.CssHtmlTree;
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/csshtmltree.xhtml
@@ -0,0 +1,158 @@
+<!DOCTYPE html [
+  <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
+  %htmlDTD;
+  <!ENTITY % inspectorDTD SYSTEM "chrome://browser/locale/styleinspector.dtd">
+  %inspectorDTD;
+  <!ELEMENT loop ANY>
+  <!ATTLIST li foreach CDATA #IMPLIED>
+  <!ATTLIST div foreach CDATA #IMPLIED>
+  <!ATTLIST loop foreach CDATA #IMPLIED>
+  <!ATTLIST a target CDATA #IMPLIED>
+  <!ATTLIST a __pathElement CDATA #IMPLIED>
+  <!ATTLIST div _id CDATA #IMPLIED>
+  <!ATTLIST div save CDATA #IMPLIED>
+  <!ATTLIST table save CDATA #IMPLIED>
+  <!ATTLIST loop if CDATA #IMPLIED>
+  <!ATTLIST tr if CDATA #IMPLIED>
+]>
+<!-- ***** 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 Inspector Module.
+   -
+   - The Initial Developer of the Original Code is The Mozilla Foundation.
+   - Portions created by the Initial Developer are Copyright (C) 2011
+   - the Initial Developer. All Rights Reserved.
+   -
+   - Contributor(s):
+   -   Joe Walker (jwalker@mozilla.com) (original author)
+   -   Mihai Șucan <mihai.sucan@gmail.com>
+   -   Michael Ratcliffe <mratcliffe@mozilla.com>
+   -
+   - Alternatively, the contents of this file may be used under the terms of
+   - either the GNU General Public License Version 2 or later (the "GPL"), or
+   - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+   - in which case the provisions of the GPL or the LGPL are applicable instead
+   - of those above. If you wish to allow use of your version of this file only
+   - under the terms of either the GPL or the LGPL, and not to allow others to
+   - use your version of this file under the terms of the MPL, indicate your
+   - decision by deleting the provisions above and replace them with the notice
+   - and other provisions required by the LGPL or the GPL. 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 ***** -->
+<html xmlns="http://www.w3.org/1999/xhtml"
+  xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<head>
+  <meta http-equiv="Content-Type"
+    content="application/xhtml+xml; charset=UTF-8" />
+  <link rel="stylesheet" type="text/css"
+    href="chrome://browser/skin/devtools/csshtmltree.css" />
+</head>
+<body role="application">
+
+<!-- The output from #templateRoot (below) is inserted here. -->
+<div id="root">
+</div>
+
+<!--
+To visually debug the templates without running firefox, alter the display:none
+-->
+<div style="display:none;">
+  <!--
+  templateRoot sits at the top of the window showing what we're looking at.
+  For data it needs an instance of CssHtmlTree.
+  -->
+  <div id="templateRoot">
+    <p class="path">
+      <label dir="${getRTLAttr}">&lookingAtLabel;</label>
+      <ol>
+        <li foreach="item in ${pathElements}" dir="${getRTLAttr}">
+          <a href="#" onclick="${pathClick}" __pathElement="${item.element}">
+            ${__element.pathElement = item.element; item.display}
+          </a>
+        </li>
+      </ol>
+    </p>
+
+    <div _id="groups">
+      <div foreach="group in ${styleGroups}" class="group" save="${group.element}" dir="${getRTLAttr}">
+        <h1 onclick="${group.click}" dir="${getRTLAttr}">
+          ${group.localName}
+          <div class="groupexpander"></div>
+        </h1>
+        <div save="${group.properties}"></div>
+      </div>
+    </div>
+  </div>
+
+  <!--
+  A templateProperties sits inside each templateGroups to show the properties
+  themselves. Each needs data like this:
+  {
+    property: [ ..., ] // Array of PropertyViews from CssHtmlTree.jsm
+  }
+  -->
+  <div id="templateProperties">
+    <div foreach="property in ${propertyViews}" class="property-view" save="${property.element}" dir="${getRTLAttr}">
+      <div class="property-header" onclick="${property.click}">
+        <span class="property-name" dir="${getRTLAttr}">
+          <a class="link" target="_blank" title="&helpLinkTitle;"
+              href="${property.link}">${property.name}</a>
+        </span>
+        <span class="property-value" dir="ltr">${property.value}</span>
+        <span class="link" dir="${getRTLAttr}">
+          ${property.ruleTitle(__element)}<div class="expander"></div>
+        </span>
+      </div>
+      <table class="rules" save="${property.rules}" dir="${getRTLAttr}"></table>
+    </div>
+  </div>
+
+  <!--
+  A templateRules sits inside each templateProperties showing the list of rules
+  that affect that property. Each needs data like this:
+  {
+    selectors: ..., // from cssLogic.getPropertyInfo(x).[un]matchedSelectors
+    statusText: function(status) {}, // convert rule.status to readable text
+    showUnmatchedRules: true / false, // show a "more unmatched rules" link
+    showUnmatchedRulesClick: function() {}, // click event handler for the
+        "show more unmatched rules"
+  }
+  This is a template so the parent does not need to be a table, except that
+  using a div as the parent causes the DOM to muck with the tr elements
+  -->
+  <table id="templateRules">
+    <loop foreach="selector in ${selectorViews}" if="${selector.selectorInfo.sheetAllowed}">
+      <tr>
+        <td dir="ltr" class="rule-text ${selector.statusClass}">
+          ${selector.humanReadableText(__element)}
+        </td>
+        <td class="rule-link">
+          <a target="_blank" href="view-source:${selector.selectorInfo.href}" class="link"
+              title="${selector.selectorInfo.href}">${selector.selectorInfo.source}</a>
+        </td>
+      </tr>
+    </loop>
+    <tr if="${showUnmatchedLink}">
+      <td colspan="4">
+        <a href="#" onclick="${showUnmatchedLinkClick}"
+            class="link">${showUnmatchedLinkText}</a>
+      </td>
+    </tr>
+  </table>
+</div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/Makefile.in
@@ -0,0 +1,48 @@
+#
+# ***** 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 Style Inspector code.
+#
+# The Initial Developer of the Original Code is
+# Mozilla Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2010
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#     Mike Ratcliffe <mratcliffe@mozilla.com>  (Original author)
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of 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 *****
+
+DEPTH		= ../../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+DIRS = browser
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser/Makefile.in
@@ -0,0 +1,62 @@
+#
+# ***** 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 Style Inspector code.
+#
+# The Initial Developer of the Original Code is
+# Mozilla Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2010
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#     Mike Ratcliffe <mratcliffe@mozilla.com>  (Original author)
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of 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 *****
+
+DEPTH     = ../../../../..
+topsrcdir = @top_srcdir@
+srcdir    = @srcdir@
+VPATH     = @srcdir@
+relativesrcdir  = browser/devtools/styleinspector/test/browser
+
+include $(DEPTH)/config/autoconf.mk
+include $(topsrcdir)/config/rules.mk
+
+_BROWSER_TEST_FILES = \
+  browser_styleinspector.js \
+  browser_styleinspector_webconsole.js \
+  head.js \
+  $(NULL)
+
+_BROWSER_TEST_PAGES = \
+  browser_styleinspector_webconsole.htm \
+  $(NULL)
+
+libs:: $(_BROWSER_TEST_FILES)
+	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
+
+libs:: $(_BROWSER_TEST_PAGES)
+	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser/browser_styleinspector.js
@@ -0,0 +1,145 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that the style inspector works properly
+
+let doc;
+let stylePanel;
+
+function createDocument()
+{
+  doc.body.innerHTML = '<style type="text/css"> ' +
+    'span { font-variant: small-caps; color: #000000; } ' +
+    '.nomatches {color: #ff0000;}</style> <div id="first" style="margin: 10em; ' +
+    'font-size: 14pt; font-family: helvetica, sans-serif; color: #AAA">\n' +
+    '<h1>Some header text</h1>\n' +
+    '<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' +
+    '<p>Inspect using inspectstyle(document.querySelectorAll("span")[0])</p>' +
+    '</div>';
+  doc.title = "Style Inspector Test";
+  ok(window.StyleInspector, "StyleInspector exists");
+  ok(StyleInspector.isEnabled, "style inspector preference is enabled");
+  stylePanel = StyleInspector.createPanel();
+  Services.obs.addObserver(runStyleInspectorTests, "StyleInspector-opened", false);
+  stylePanel.openPopup(gBrowser.selectedBrowser, "end_before", 0, 0, false, false);
+}
+
+function runStyleInspectorTests()
+{
+  Services.obs.removeObserver(runStyleInspectorTests, "StyleInspector-opened", false);
+
+  ok(stylePanel.isOpen(), "style inspector is open");
+
+  checkForNewProperties();
+
+  var spans = doc.querySelectorAll("span");
+  ok(spans, "captain, we have the spans");
+
+  let htmlTree = stylePanel.cssHtmlTree;
+
+  for (var i = 0, numSpans = spans.length; i < numSpans; i++) {
+    stylePanel.selectNode(spans[i]);
+
+    is(spans[i], htmlTree.viewedElement,
+      "style inspector node matches the selected node");
+    is(htmlTree.viewedElement, stylePanel.cssLogic.viewedElement,
+       "cssLogic node matches the cssHtmlTree node");
+
+    // The Fonts and Color group.
+    ok(groupRuleCount(0) > 0, "we have rules for the current span");
+  }
+
+  SI_CheckProperty();
+  Services.obs.addObserver(finishUp, "StyleInspector-closed", false);
+  stylePanel.hidePopup();
+}
+
+function checkForNewProperties()
+{
+  let htmlTree = stylePanel.cssHtmlTree;
+  htmlTree.createStyleGroupViews();
+  let otherProps = htmlTree._getPropertiesByGroup().other;
+  let otherPlusUnknownProps = htmlTree.propertiesByGroup.other;
+
+  let missingProps = [];
+  for each (let prop in otherPlusUnknownProps) {
+    if (otherProps.indexOf(prop) == -1) {
+      missingProps.push(prop);
+    }
+  }
+
+  if (missingProps.length > 0) {
+    let n = 1;
+    let msg = "The following css properties need to be categorized in " +
+              "CssHtmlTree.getPropertiesByGroup():\r\n";
+    missingProps.forEach(function BSI_buildMissingProps(aProp) {
+      msg += "  " + (n++) + ". " + aProp + "\n";
+    });
+    ok(false, msg);
+  }
+}
+
+function SI_CheckProperty()
+{
+  let group = stylePanel.cssHtmlTree.styleGroups[0];
+  let cssLogic = stylePanel.cssLogic;
+
+  let propertyInfo = cssLogic.getPropertyInfo("color");
+  ok(propertyInfo.matchedRuleCount > 0, "color property has matching rules");
+  ok(propertyInfo.unmatchedRuleCount > 0, "color property has unmatched rules");
+}
+
+function groupRuleCount(groupId)
+{
+  let groupRules = 0;
+  let group = stylePanel.cssHtmlTree.styleGroups[groupId];
+
+  ok(group, "we have a StyleGroupView");
+  ok(group.tree, "we have the CssHtmlTree object");
+
+  let cssLogic = stylePanel.cssLogic;
+
+  ok(cssLogic, "we have the CssLogic object");
+
+  // we use the click method to populate the groups properties
+  group.click();
+
+  ok(group.properties.childElementCount > 0, "the StyleGroupView has properties");
+
+  group.propertyViews.forEach(function(property) {
+    groupRules += cssLogic.getPropertyInfo(property.name).matchedRuleCount;
+  });
+
+  return groupRules;
+}
+
+function finishUp()
+{
+  Services.obs.removeObserver(finishUp, "StyleInspector-closed", false);
+  ok(!stylePanel.isOpen(), "style inspector is closed");
+  doc = stylePanel = null;
+  gBrowser.removeCurrentTab();
+  finish();
+}
+
+function test()
+{
+  waitForExplicitFinish();
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function(evt) {
+    gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
+    doc = content.document;
+    waitForFocus(createDocument, content);
+  }, true);
+
+  content.location = "data:text/html,basic style inspector tests";
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser/browser_styleinspector_webconsole.htm
@@ -0,0 +1,185 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US"><head>
+  <title>Style inspector test</title>
+  <style>
+    .text {
+      font-family: sans-serif;
+    }
+
+    .container > .text {
+      font-family: serif;
+    }
+
+    .text2 {
+      font-family: sans-serif;
+    }
+
+    .text3 {
+      font-size: 100px;
+    }
+
+    .text[dir=rtl] {
+      font-family: monospace;
+    }
+
+    .container .text {
+      font-family: fantasy;
+    }
+
+    #container .text {
+      font-family: fantasy;
+    }
+
+    .container > .text {
+      font-family: cursive;
+    }
+
+    #container > .text {
+      font-family: cursive;
+    }
+
+    #container > .dummy, #container > .dummy2 {
+      font-family: cursive;
+    }
+
+    div {
+      font-family: fantasy;
+    }
+
+    #container {
+      font-family: fantasy;
+    }
+
+    #container > span {
+      font-family: cursive;
+    }
+
+    #container .dummy {
+      font-family: fantasy;
+    }
+
+    html + .dummy {
+      font-family: fantasy;
+    }
+
+    span + span {
+      font-family: fantasy;
+    }
+
+    span[id=text] {
+      font-family: cursive;
+    }
+
+    span[att=glue] {
+      font-family: cursive;
+    }
+
+    span::before {
+      font-family: cursive;
+      content: "START ";
+    }
+
+    span::after {
+      font-family: cursive;
+      content: " END";
+    }
+
+    spawn::before {
+      font-family: cursive;
+      content: "START ";
+    }
+
+    spawn::after {
+      font-family: cursive;
+      content: " END";
+    }
+
+    a:link {
+      font-family: sans-serif;
+    }
+
+    .link:link {
+      font-family: fantasy;
+    }
+
+    a:visited {
+      font-family: sans-serif;
+    }
+
+    .link:visited {
+      font-family: fantasy;
+    }
+
+    a:active {
+      font-family: sans-serif;
+    }
+
+    .link:active {
+      font-family: fantasy;
+    }
+
+    a:hover {
+      font-family: sans-serif;
+    }
+
+    .link:hover {
+      font-family: fantasy;
+    }
+
+    a:focus {
+      font-family: sans-serif;
+      outline: 5px solid #0f0;
+    }
+
+    .link:focus {
+      font-family: fantasy;
+    }
+
+    span::first-letter {
+      font-family: sans-serif;
+    }
+
+    .text::first-letter {
+      font-family: fantasy;
+    }
+
+    span::first-line {
+      font-family: sans-serif;
+    }
+
+    .text::first-line {
+      font-family: fantasy;
+    }
+
+    #container:first-child {
+      font-family: sans-serif;
+    }
+
+    div:first-child {
+      font-family: fantasy;
+    }
+
+    span:lang(en) {
+      font-family: sans-serif;
+    }
+
+    span:lang(it) {
+      font-family: fantasy;
+    }
+
+    html::selection {
+      background-color: #f00;
+      font-family: fantasy;
+    }
+  </style>
+</head>
+<body>
+  <h2>font-size</h2>
+  <div id="container">
+    <span id="text" lang="en" class="text">Use inspectstyle($('text')) to inspect me</span><br />
+    <span id="text2" class="text2">Use inspectstyle($('text2'))</span><br />
+    <a class="link" href="#">Some Link</a>
+    <h2>font-family has a single unmatched rule</h2>
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser/browser_styleinspector_webconsole.js
@@ -0,0 +1,194 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** 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 DevTools test code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Michael Ratcliffe <mratcliffe@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// Tests that inspectstyle(node) works properly
+
+const TEST_URI = "http://example.com/browser/browser/devtools/styleinspector/test/browser/browser_styleinspector_webconsole.htm";
+
+let doc;
+let jsterm;
+let hudBox;
+let stylePanels = [];
+
+function test() {
+  addTab(TEST_URI);
+  browser.addEventListener("DOMContentLoaded", prepConsole, false);
+}
+
+function prepConsole() {
+  browser.removeEventListener("DOMContentLoaded", prepConsole, false);
+  doc = content.document;
+  openConsole();
+ 
+  ok(window.StyleInspector, "StyleInspector exists");
+ 
+  let hud = HUDService.getHudByWindow(content);
+  ok(hud, "we have a console");
+ 
+  hudBox = hud.HUDBox;
+  ok(hudBox, "we have the console display");
+ 
+  jsterm = hud.jsterm;
+  ok(jsterm, "we have a jsterm");
+ 
+  openStyleInspector1();
+}
+
+function openStyleInspector1() {
+  info("opening style inspector instance 1");
+  Services.obs.addObserver(openStyleInspector2, "StyleInspector-opened", false);
+  jsterm.execute("inspectstyle($('text'))");
+}
+
+function openStyleInspector2() {
+  Services.obs.removeObserver(openStyleInspector2, "StyleInspector-opened", false);
+  info("opening style inspector instance 2");
+  Services.obs.addObserver(openStyleInspector3, "StyleInspector-opened", false);
+  jsterm.execute("inspectstyle($('text2'))");
+}
+
+function openStyleInspector3() {
+  Services.obs.removeObserver(openStyleInspector3, "StyleInspector-opened", false);
+  info("opening style inspector instance 3");
+  Services.obs.addObserver(teststylePanels, "StyleInspector-opened", false);
+  jsterm.execute("inspectstyle($('container'))");
+}
+
+function teststylePanels() {
+  Services.obs.removeObserver(teststylePanels, "StyleInspector-opened", false);
+
+  info("adding style inspector instances to stylePanels array");
+  let popupSet = document.getElementById("mainPopupSet");
+  let len = popupSet.childNodes.length - 3;
+  stylePanels.push(popupSet.childNodes[len++]);
+  stylePanels.push(popupSet.childNodes[len++]);
+  stylePanels.push(popupSet.childNodes[len++]);
+
+  let eltArray = [
+    doc.getElementById("text"),
+    doc.getElementById("text2"),
+    doc.getElementById("container")
+  ];
+
+  info("looping through array to check initialization");
+  for (let i = 0, max = stylePanels.length; i < max; i++) {
+    ok(stylePanels[i], "style inspector instance " + i +
+       " correctly initialized");
+    ok(stylePanels[i].isOpen(), "style inspector " + i + " is open");
+
+    let htmlTree = stylePanels[i].cssHtmlTree;
+
+    is(eltArray[i], htmlTree.viewedElement,
+      "style inspector node matches the selected node (id=" +
+      eltArray[i].id + ")");
+    is(htmlTree.viewedElement, stylePanels[i].cssLogic.viewedElement,
+      "cssLogic node matches the cssHtmlTree node (id=" + eltArray[i].id + ")");
+
+    ok(groupRuleCount(0, stylePanels[i]) > 0,
+       "we have rules for the current node (id=" + eltArray[i].id + ")");
+  }
+
+  info("hiding stylePanels[1]");
+  Services.obs.addObserver(styleInspectorClosedByHide,
+                           "StyleInspector-closed", false);
+  stylePanels[1].hidePopup();
+}
+
+function styleInspectorClosedByHide()
+{
+  Services.obs.removeObserver(styleInspectorClosedByHide, "StyleInspector-closed", false);
+  ok(stylePanels[0].isOpen(), "instance stylePanels[0] is still open");
+  ok(!stylePanels[1].isOpen(), "instance stylePanels[1] is hidden");
+  ok(stylePanels[2].isOpen(), "instance stylePanels[2] is still open");
+
+  info("closing web console");
+  Services.obs.addObserver(styleInspectorClosedFromConsole1,
+                           "StyleInspector-closed", false);
+  closeConsole();
+}
+
+function styleInspectorClosedFromConsole1()
+{
+  Services.obs.removeObserver(styleInspectorClosedFromConsole1,
+                              "StyleInspector-closed", false);
+  info("Style Inspector 1 closed");
+  Services.obs.addObserver(styleInspectorClosedFromConsole2,
+                           "StyleInspector-closed", false);
+}
+
+function styleInspectorClosedFromConsole2()
+{
+  Services.obs.removeObserver(styleInspectorClosedFromConsole2,
+                              "StyleInspector-closed", false);
+  info("Style Inspector 2 closed");
+  executeSoon(cleanUp);
+}
+
+function cleanUp()
+{
+  let popupSet = document.getElementById("mainPopupSet");
+  ok(!popupSet.lastChild.hasAttribute("hudToolId"),
+     "all style inspector panels are now detached and ready for garbage collection");
+
+  info("cleaning up");
+  doc = hudBox = stylePanels = jsterm = null;
+  finishTest();
+}
+
+function groupRuleCount(groupId, aStylePanel)
+{
+  let groupRules = 0;
+  let group = aStylePanel.cssHtmlTree.styleGroups[groupId];
+
+  ok(group, "we have a StyleGroupView");
+  ok(group.tree, "we have the CssHtmlTree object");
+
+  let cssLogic = aStylePanel.cssLogic;
+
+  ok(cssLogic, "we have the CssLogic object");
+
+  // we use the click event to populate the groups properties
+  group.click();
+
+  ok(group.properties.childElementCount > 0, "the StyleGroupView has properties");
+
+  group.propertyViews.forEach(function(property) {
+    groupRules += cssLogic.getPropertyInfo(property.name).matchedRuleCount;
+  });
+
+  return groupRules;
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser/head.js
@@ -0,0 +1,185 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** 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 DevTools test code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Mike Ratcliffe <mratcliffe@mozilla.com>
+ *
+ * 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 ***** */
+
+Cu.import("resource:///modules/devtools/StyleInspector.jsm");
+Cu.import("resource://gre/modules/HUDService.jsm");
+
+function log(aMsg)
+{
+  dump("*** WebConsoleTest: " + aMsg + "\n");
+}
+
+function pprint(aObj)
+{
+  for (let prop in aObj) {
+    if (typeof aObj[prop] == "function") {
+      log("function " + prop);
+    }
+    else {
+      log(prop + ": " + aObj[prop]);
+    }
+  }
+}
+
+let tab, browser, hudId, hud, hudBox, filterBox, outputNode, cs;
+
+function addTab(aURL)
+{
+  gBrowser.selectedTab = gBrowser.addTab();
+  content.location = aURL;
+  tab = gBrowser.selectedTab;
+  browser = gBrowser.getBrowserForTab(tab);
+}
+
+function afterAllTabsLoaded(callback, win) {
+  win = win || window;
+
+  let stillToLoad = 0;
+
+  function onLoad() {
+    this.removeEventListener("load", onLoad, true);
+    stillToLoad--;
+    if (!stillToLoad)
+      callback();
+  }
+
+  for (let a = 0; a < win.gBrowser.tabs.length; a++) {
+    let browser = win.gBrowser.tabs[a].linkedBrowser;
+    if (browser.contentDocument.readyState != "complete") {
+      stillToLoad++;
+      browser.addEventListener("load", onLoad, true);
+    }
+  }
+
+  if (!stillToLoad)
+    callback();
+}
+
+/**
+ * Check if a log entry exists in the HUD output node.
+ *
+ * @param {Element} aOutputNode
+ *        the HUD output node.
+ * @param {string} aMatchString
+ *        the string you want to check if it exists in the output node.
+ * @param {string} aMsg
+ *        the message describing the test
+ * @param {boolean} [aOnlyVisible=false]
+ *        find only messages that are visible, not hidden by the filter.
+ * @param {boolean} [aFailIfFound=false]
+ *        fail the test if the string is found in the output node.
+ * @param {string} aClass [optional]
+ *        find only messages with the given CSS class.
+ */
+function testLogEntry(aOutputNode, aMatchString, aMsg, aOnlyVisible,
+                      aFailIfFound, aClass)
+{
+  let selector = ".hud-msg-node";
+  // Skip entries that are hidden by the filter.
+  if (aOnlyVisible) {
+    selector += ":not(.hud-filtered-by-type)";
+  }
+  if (aClass) {
+    selector += "." + aClass;
+  }
+
+  let msgs = aOutputNode.querySelectorAll(selector);
+  let found = false;
+  for (let i = 0, n = msgs.length; i < n; i++) {
+    let message = msgs[i].textContent.indexOf(aMatchString);
+    if (message > -1) {
+      found = true;
+      break;
+    }
+
+    // Search the labels too.
+    let labels = msgs[i].querySelectorAll("label");
+    for (let j = 0; j < labels.length; j++) {
+      if (labels[j].getAttribute("value").indexOf(aMatchString) > -1) {
+        found = true;
+        break;
+      }
+    }
+  }
+
+  is(found, !aFailIfFound, aMsg);
+}
+
+/**
+ * A convenience method to call testLogEntry().
+ *
+ * @param string aString
+ *        The string to find.
+ */
+function findLogEntry(aString)
+{
+  testLogEntry(outputNode, aString, "found " + aString);
+}
+
+function openConsole()
+{
+  HUDService.activateHUDForContext(tab);
+}
+
+function closeConsole()
+{
+  HUDService.deactivateHUDForContext(tab);
+}
+
+function finishTest()
+{
+  finish();
+}
+
+function tearDown()
+{
+  try {
+    HUDService.deactivateHUDForContext(gBrowser.selectedTab);
+  }
+  catch (ex) {
+    log(ex);
+  }
+  while (gBrowser.tabs.length > 1) {
+    gBrowser.removeCurrentTab();
+  }
+  tab = browser = hudId = hud = filterBox = outputNode = cs = null;
+}
+
+registerCleanupFunction(tearDown);
+
+waitForExplicitFinish();
+
--- a/browser/devtools/webconsole/HUDService.jsm
+++ b/browser/devtools/webconsole/HUDService.jsm
@@ -22,16 +22,17 @@
  *
  * Contributor(s):
  *   David Dahl <ddahl@mozilla.com> (original author)
  *   Rob Campbell <rcampbell@mozilla.com>
  *   Johnathan Nightingale <jnightingale@mozilla.com>
  *   Patrick Walton <pcwalton@mozilla.com>
  *   Julian Viereck <jviereck@mozilla.com>
  *   Mihai Șucan <mihai.sucan@gmail.com>
+ *   Michael Ratcliffe <mratcliffe@mozilla.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -66,16 +67,22 @@ XPCOMUtils.defineLazyServiceGetter(this,
 XPCOMUtils.defineLazyServiceGetter(this, "mimeService",
                                    "@mozilla.org/mime;1",
                                    "nsIMIMEService");
 
 XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
                                    "@mozilla.org/widget/clipboardhelper;1",
                                    "nsIClipboardHelper");
 
+XPCOMUtils.defineLazyGetter(this, "StyleInspector", function () {
+  var obj = {};
+  Cu.import("resource:///modules/devtools/StyleInspector.jsm", obj);
+  return obj.StyleInspector;
+});
+
 XPCOMUtils.defineLazyGetter(this, "NetUtil", function () {
   var obj = {};
   Cu.import("resource://gre/modules/NetUtil.jsm", obj);
   return obj.NetUtil;
 });
 
 XPCOMUtils.defineLazyGetter(this, "PropertyPanel", function () {
   var obj = {};
@@ -1771,16 +1778,21 @@ HUD_SERVICE.prototype =
 
     this.unregisterActiveContext(aHUDId);
 
     let popupset = hud.chromeDocument.getElementById("mainPopupSet");
     let panels = popupset.querySelectorAll("panel[hudId=" + aHUDId + "]");
     for (let i = 0; i < panels.length; i++) {
       panels[i].hidePopup();
     }
+    panels = popupset.querySelectorAll("panel[hudToolId=" + aHUDId + "]");
+    for (let i = 0; i < panels.length; i++) {
+      panels[i].hidePopup();
+      popupset.removeChild(panels[i]);
+    }
 
     let id = ConsoleUtils.supString(aHUDId);
     Services.obs.notifyObservers(id, "web-console-destroyed", null);
 
     if (Object.keys(this.hudReferences).length == 0) {
       let autocompletePopup = hud.chromeDocument.
                               getElementById("webConsole_autocompletePopup");
       if (autocompletePopup) {
@@ -4392,16 +4404,47 @@ function JSTermHelper(aJSTerm)
   aJSTerm.sandbox.inspect = function JSTH_inspect(aObject)
   {
     aJSTerm.helperEvaluated = true;
     let propPanel = aJSTerm.openPropertyPanel(null, unwrap(aObject));
     propPanel.panel.setAttribute("hudId", aJSTerm.hudId);
   };
 
   /**
+   * Inspects the passed aNode in the style inspector.
+   *
+   * @param object aNode
+   *        aNode to inspect.
+   * @returns void
+   */
+  aJSTerm.sandbox.inspectstyle = function JSTH_inspectstyle(aNode)
+  {
+    let errstr = null;
+    aJSTerm.helperEvaluated = true;
+
+    if (!Services.prefs.getBoolPref("devtools.styleinspector.enabled")) {
+      errstr = HUDService.getStr("inspectStyle.styleInspectorNotEnabled");
+    } else if (!aNode) {
+      errstr = HUDService.getStr("inspectStyle.nullObjectPassed");
+    } else if (!(aNode instanceof Ci.nsIDOMNode)) {
+      errstr = HUDService.getStr("inspectStyle.mustBeDomNode");
+    } else if (!(aNode.style instanceof Ci.nsIDOMCSSStyleDeclaration)) {
+      errstr = HUDService.getStr("inspectStyle.nodeHasNoStyleProps");
+    }
+
+    if (!errstr) {
+      let stylePanel = StyleInspector.createPanel();
+      stylePanel.setAttribute("hudToolId", aJSTerm.hudId);
+      stylePanel.selectNode(aNode, true);
+    } else {
+      aJSTerm.writeOutput(errstr + "\n", CATEGORY_OUTPUT, SEVERITY_ERROR);
+    }
+  };
+
+  /**
    * Prints aObject to the output.
    *
    * @param object aObject
    *        Object to print to the output.
    * @returns void
    */
   aJSTerm.sandbox.pprint = function JSTH_pprint(aObject)
   {
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/styleinspector.dtd
@@ -0,0 +1,10 @@
+<!-- LOCALIZATION NOTE (lookingAtLabel): This is the label for the path of
+  -  the highlighted element in the web page. This path is based on the document
+  -  tree. -->
+<!ENTITY lookingAtLabel        "Looking at:">
+
+<!-- LOCALIZATION NOTE (helpLinkTitle): For each style property
+  -  the user can hover it and get a help link button which allows one to
+  -  quickly jump to the documentation from the Mozilla Developer Network site.
+  -  This is the link title shown in the hover tooltip. -->
+<!ENTITY helpLinkTitle         "Read the documentation for this property">
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/styleinspector.properties
@@ -0,0 +1,53 @@
+# LOCALIZATION NOTE These strings are used inside the Style Inspector.
+
+# LOCALIZATION NOTE (panelTitle): This is the panel title
+panelTitle=Style Inspector
+
+# LOCALIZATION NOTE (property.numberOfRules): For each style property the panel
+# shows the number of rules which hold that specific property, counted from all
+# of the stylesheets in the web page inspected.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+property.numberOfRules=#1 rule;#1 rules
+
+# LOCALIZATION NOTE (property.numberOfUnmatchedRules): Each style property is
+# inside a rule. A rule is a selector that can match (or not) the highlighted
+# element in the web page. The property view shows no unmatched rules. If the
+# user wants to expand the property to view unmatched rules, he/she must click
+# this link displayed to the right of each property.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+property.numberOfUnmatchedRules=One unmatched rule;#1 unmatched rules
+
+# LOCALIZATION NOTE (rule.status): For each style property the panel shows
+# the rules which hold that specific property. For every rule, the rule status
+# is also displayed: a rule can be the best match, a match, a parent match, or a
+# rule did not match the element the user has highlighted.
+rule.status.BEST=Best Match
+rule.status.MATCHED=Matched
+rule.status.PARENT_MATCH=Parent Match
+rule.status.UNMATCHED=Unmatched
+
+# LOCALIZATION NOTE (rule.sourceElement, rule.sourceInline): For each
+# style property the panel shows the rules which hold that specific property.
+# For every rule, the rule source is also displayed: a rule can come from a
+# file, from the same page (inline), or from the element itself (element).
+rule.sourceInline=inline
+rule.sourceElement=element
+
+# LOCALIZATION NOTE (rule.showUnmatchedLink): Each style property
+# is inside a rule. A rule is a selector that can match (or not) the highlighted
+# element in the web page. The property view shows only a few of the unmatched
+# rules. If the user wants to see all of the unmatched rules, he/she must click
+# the link displayed at the bottom of the rules table. That link shows how many
+# rules are not displayed. This is the string used when the link is generated.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+rule.showUnmatchedLink=One unmatched rule...;#1 unmatched rules...
+
+# LOCALIZATION NOTE (group): Style properties are displayed in categories and
+# these are the category names.
+group.Text_Fonts_and_Color=Text, Fonts & Color
+group.Background=Background
+group.Dimensions=Dimensions
+group.Positioning_and_Page_Flow=Positioning and Page Flow
+group.Borders=Borders
+group.Lists=Lists
+group.Effects_and_Other=Effects and Other
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -9,16 +9,18 @@
     locale/browser/aboutHome.dtd                   (%chrome/browser/aboutHome.dtd)
     locale/browser/aboutSessionRestore.dtd         (%chrome/browser/aboutSessionRestore.dtd)
 #ifdef MOZ_SERVICES_SYNC
     locale/browser/aboutSyncTabs.dtd               (%chrome/browser/aboutSyncTabs.dtd)
 #endif
 *   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/styleinspector.properties       (%chrome/browser/styleinspector.properties)
+    locale/browser/styleinspector.dtd              (%chrome/browser/styleinspector.dtd)
     locale/browser/scratchpad.properties           (%chrome/browser/scratchpad.properties)
     locale/browser/scratchpad.dtd                  (%chrome/browser/scratchpad.dtd)
     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/quitDialog.properties           (%chrome/browser/quitDialog.properties)
new file mode 100644
index 0000000000000000000000000000000000000000..95e7217927854ed98dd7e55123709ad8baf126f9
GIT binary patch
literal 933
zc$@*H16urvP)<h;3K|Lk000e1NJLTq001xm000OG1^@s6zbkx400001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2ipe(
z2{9a0;%#{V00SjSL_t(I%Z-#zY!qb}#(&SZ1hxkRgceivVA94v7qr+KPFlDbf`^Xo
z8e^fxvlk=AizX%*!qKaQieA|8ZKH&EQQCTHw8f-}iRczRxj>1Pa&WiH&O9DwyKNIO
ze3?u#Gw=7#^XGj&(X;<F_Boo%ztPhNjb$EaZQu0@_BhZY=ou_i2nHEK3P8K{b9wCi
zKkfEfHOA8P*ntaF7t0USek`dRch)`{!nQS}rvU`EEu_52o(qs2LH>ba?<6n?K+pdF
z0lxbfJ9aG~O%n7h(pk{sKp&_V*oV~z3~+DtR6A{r0#}lxGS48R0B67VecLBVWjskL
z^&ow27y^AQ7?M2%9F#O97$Se33ON(qMH<DPr`UTAdmtyWccXLh;iK%>^CK{c)sJan
znbztR@b8@}b?(M|1uOxQgrtLkFr6fo`ENeG@Djk8Lw{@lf3@;vl2q!zdBh{w2o&RM
z7j56L*v#6%LX4$p(X;m_U|;98IzQh7u7XdIr3VPx-bD<6*190U;0BGALALKc224^Y
zG%*>3ok+tpmTO5;`9;!Gx#LEXv1ORYoCFUdeFQuz$)-CXr0KouQ&VpYBuQmFCnj=Y
zA|pFYjhO%z5P$+A00FrPJeh+pECRn`6@e@VND5@241m_1OGwu{5*V1Gv0NjUKkMq&
zf!2|-A3O-E0d;k?^(L7ufEY_(05=5ANqV6g%QMx*GJ!M`X&OjVk)}nmtd5zWxpIis
z-H1Ys=E?~uJcq+~5OzW#!-b8o=HNaC@5tTLC+j&f1Z<2umv17*V=O%utL4u|>Lar;
zmPS%%qqeUqH`w_kcFh3szHMPEbN1ePjsmg}MdUg#!P?&=G*>QnH#YuS2e?G@_FG89
z*fYovZAo8q<uYaoT&20ZMB^4)ls7kl<1v={<HgdK>z_`}JpsVW@04d_EDdL5hizYV
z?nKv)&YnLF^HbH=XWxEN9RiuJVNbBO`aG>Wm+pH!Yyhg!ygh;S0)%h3)MV{5;L}^e
zZ(qyo6ET+hJEQQQYaI|ZW@0Q214q&<o!e4me~9^DbNK%SaZ!ks&1VtM00000NkvXX
Hu0mjfz!{kU
new file mode 100644
--- /dev/null
+++ b/browser/themes/gnomestripe/browser/devtools/csshtmltree.css
@@ -0,0 +1,236 @@
+/* ***** 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 Inspector Module.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Joe Walker <jwalker@mozilla.com> (original author)
+ *   Mihai Șucan <mihai.sucan@gmail.com>
+ *   Michael Ratcliffe <mratcliffe@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+body {
+  font-family: Lucida Grande, sans-serif;
+  font-size: 11px;
+  background: #EEE;
+}
+
+.path {
+  font-size: 11px;
+  word-spacing: -1px;
+}
+.path ol {
+  list-style: none;
+  margin: 0;
+  padding: 0;
+}
+.path li {
+  border-radius: 3px;
+  padding: 2px 3px;
+  text-shadow: #FFF 0 1px 0;
+  font-weight: bold;
+  font-size: 11px;
+  background: -moz-linear-gradient(top, #F6F6FF, #E3E3FF);
+  display: inline-block;
+}
+.path li:after {
+  content: " > ";
+}
+.path li:last-child {
+  background: -moz-linear-gradient(top, #FFC, #DD8);
+}
+.path li:last-child:after {
+  color: red;
+  content: "";
+}
+
+#sheetList, #sheetList menuitem {
+  font-size: 1em;
+  font-weight: normal;
+}
+
+.sheet_line input {
+  vertical-align: middle;
+}
+
+.sheet_line label {
+  cursor: pointer;
+}
+
+#header, #footer {
+  padding: 5px;
+}
+
+#header label {
+  font-weight: bold;
+}
+#sheets {
+  -moz-margin-end: 10px;
+  margin-top: 5px;
+}
+h1 {
+  font-size: 13px;
+  padding: 2px 10px;
+  margin: 0;
+  background: -moz-linear-gradient(top, #CCC, #AAA);
+  border-radius: 3px;
+  text-shadow: #FFF 0 1px 0;
+  cursor: pointer;
+}
+
+.property-header {
+  padding: 2px 5px;
+  background: -moz-linear-gradient(top, #F8F8F8, #E8E8E8);
+  color: #666;
+}
+
+.property-name, .property-value, .rule-count, .rule-unmatched {
+  cursor: pointer;
+}
+
+/* Take away these two :visited rules to get a core dumper     */
+/* See https://bugzilla.mozilla.org/show_bug.cgi?id=575675#c30 */
+.link { color: #55A;  }
+.link:visited { color: #55A;  }
+a.link { text-decoration: none; cursor: pointer }
+a.link:visited { text-decoration: none; }
+.rule-count, .rule-unmatched {
+  float: right;
+}
+.rule-count[dir="rtl"], .rule-unmatched[dir="rtl"] {
+  float: left;
+}
+.property-view .rule-count .expander,
+.property-view .rule-unmatched .expander {
+  width: 8px;
+  height: 8px;
+  -moz-margin-start: 5px;
+  display: inline-block;
+  background: url("chrome://browser/skin/devtools/arrows.png");
+}
+
+.property-view .rule-count .expander,
+.property-view .rule-unmatched .expander {
+  background-position: 24px 0;
+}
+.property-view[dir="rtl"] .rule-count .expander,
+.property-view[dir="rtl"] .rule-unmatched .expander {
+  background-position: 16px 0;
+}
+.property-view[open] .rule-count .expander,
+.property-view[open] .rule-unmatched .expander {
+  background-position: 8px 0;
+}
+
+.property-name {
+  font-size: 12px;
+  font-weight: bold;
+  -moz-padding-end: 4px;
+  color: #000;
+}
+span.property-value {
+  -moz-padding-end: 5px;
+  font-size: 10px;
+}
+.group {
+  margin-top: 10px;
+}
+
+.group > h1 {
+  color: #333;
+  font-size: 11px;
+}
+.group .groupexpander {
+  width: 8px;
+  height: 8px;
+  margin-top: 2px;
+  background: url("chrome://browser/skin/devtools/arrows.png");
+}
+.group > div {
+  display: none;
+}
+.group[open] > div {
+  display: block;
+}
+.group .groupexpander {
+  background-position: 48px 0;
+  float: right;
+}
+.group[dir="rtl"] .groupexpander {
+  background-position: 40px 0;
+  float: left;
+}
+.group[open] .groupexpander {
+  background-position: 32px 0;
+  float: right;
+}
+.group[open][dir="rtl"] .groupexpander {
+  background-position: 32px 0;
+  float: left;
+}
+
+.group, #header, #footer {
+  background: #FFF;
+  border: 1px solid #E1E1E1;
+  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
+  border-radius: 4px 4px 4px 4px;
+}
+
+.property-view > .rules {
+  display: none;
+}
+.property-view[open] > .rules {
+  display: table;
+}
+.rules {
+  max-height: 350px;
+  overflow-y: auto;
+}
+.rule-specificty, .rule-status {
+  white-space: nowrap;
+}
+.rule-link {
+  text-align: end;
+}
+
+/* This rule is necessary because Templater.jsm breaks LTR TDs in RTL docs */
+.rule-text {
+  direction: ltr;
+}
+
+.resizerbox {
+  background-color: window;
+}
+
+.bestmatch { color: black; }
+.matched { text-decoration: line-through; }
+.parentmatch { color: #666; }
+.unmatched { color: brown; }
--- a/browser/themes/gnomestripe/browser/jar.mn
+++ b/browser/themes/gnomestripe/browser/jar.mn
@@ -79,16 +79,18 @@ browser.jar:
   skin/classic/browser/tabbrowser/tab.png             (tabbrowser/tab.png)
   skin/classic/browser/tabbrowser/tab-overflow-border.png (tabbrowser/tab-overflow-border.png)
   skin/classic/browser/tabbrowser/tabDragIndicator.png (tabbrowser/tabDragIndicator.png)
   skin/classic/browser/tabview/edit-light.png         (tabview/edit-light.png)
   skin/classic/browser/tabview/search.png             (tabview/search.png)  
   skin/classic/browser/tabview/stack-expander.png     (tabview/stack-expander.png)
   skin/classic/browser/tabview/tabview.png            (tabview/tabview.png)
   skin/classic/browser/tabview/tabview.css            (tabview/tabview.css)
+  skin/classic/browser/devtools/arrows.png            (devtools/arrows.png)
+  skin/classic/browser/devtools/csshtmltree.css       (devtools/csshtmltree.css)
 #ifdef MOZ_SERVICES_SYNC
   skin/classic/browser/sync-16-throbber.png
   skin/classic/browser/sync-16.png
   skin/classic/browser/sync-24-throbber.png
   skin/classic/browser/sync-32.png
   skin/classic/browser/sync-bg.png
   skin/classic/browser/sync-desktopIcon.png
   skin/classic/browser/sync-mobileIcon.png
new file mode 100644
index 0000000000000000000000000000000000000000..95e7217927854ed98dd7e55123709ad8baf126f9
GIT binary patch
literal 933
zc$@*H16urvP)<h;3K|Lk000e1NJLTq001xm000OG1^@s6zbkx400001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2ipe(
z2{9a0;%#{V00SjSL_t(I%Z-#zY!qb}#(&SZ1hxkRgceivVA94v7qr+KPFlDbf`^Xo
z8e^fxvlk=AizX%*!qKaQieA|8ZKH&EQQCTHw8f-}iRczRxj>1Pa&WiH&O9DwyKNIO
ze3?u#Gw=7#^XGj&(X;<F_Boo%ztPhNjb$EaZQu0@_BhZY=ou_i2nHEK3P8K{b9wCi
zKkfEfHOA8P*ntaF7t0USek`dRch)`{!nQS}rvU`EEu_52o(qs2LH>ba?<6n?K+pdF
z0lxbfJ9aG~O%n7h(pk{sKp&_V*oV~z3~+DtR6A{r0#}lxGS48R0B67VecLBVWjskL
z^&ow27y^AQ7?M2%9F#O97$Se33ON(qMH<DPr`UTAdmtyWccXLh;iK%>^CK{c)sJan
znbztR@b8@}b?(M|1uOxQgrtLkFr6fo`ENeG@Djk8Lw{@lf3@;vl2q!zdBh{w2o&RM
z7j56L*v#6%LX4$p(X;m_U|;98IzQh7u7XdIr3VPx-bD<6*190U;0BGALALKc224^Y
zG%*>3ok+tpmTO5;`9;!Gx#LEXv1ORYoCFUdeFQuz$)-CXr0KouQ&VpYBuQmFCnj=Y
zA|pFYjhO%z5P$+A00FrPJeh+pECRn`6@e@VND5@241m_1OGwu{5*V1Gv0NjUKkMq&
zf!2|-A3O-E0d;k?^(L7ufEY_(05=5ANqV6g%QMx*GJ!M`X&OjVk)}nmtd5zWxpIis
z-H1Ys=E?~uJcq+~5OzW#!-b8o=HNaC@5tTLC+j&f1Z<2umv17*V=O%utL4u|>Lar;
zmPS%%qqeUqH`w_kcFh3szHMPEbN1ePjsmg}MdUg#!P?&=G*>QnH#YuS2e?G@_FG89
z*fYovZAo8q<uYaoT&20ZMB^4)ls7kl<1v={<HgdK>z_`}JpsVW@04d_EDdL5hizYV
z?nKv)&YnLF^HbH=XWxEN9RiuJVNbBO`aG>Wm+pH!Yyhg!ygh;S0)%h3)MV{5;L}^e
zZ(qyo6ET+hJEQQQYaI|ZW@0Q214q&<o!e4me~9^DbNK%SaZ!ks&1VtM00000NkvXX
Hu0mjfz!{kU
new file mode 100644
--- /dev/null
+++ b/browser/themes/pinstripe/browser/devtools/csshtmltree.css
@@ -0,0 +1,236 @@
+/* ***** 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 Inspector Module.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Joe Walker <jwalker@mozilla.com> (original author)
+ *   Mihai Șucan <mihai.sucan@gmail.com>
+ *   Michael Ratcliffe <mratcliffe@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+body {
+  font-family: Lucida Grande, sans-serif;
+  font-size: 11px;
+  background: #EEE;
+}
+
+.path {
+  font-size: 11px;
+  word-spacing: -1px;
+}
+.path ol {
+  list-style: none;
+  margin: 0;
+  padding: 0;
+}
+.path li {
+  border-radius: 3px;
+  padding: 2px 3px;
+  text-shadow: #FFF 0 1px 0;
+  font-weight: bold;
+  font-size: 11px;
+  background: -moz-linear-gradient(top, #F6F6FF, #E3E3FF);
+  display: inline-block;
+}
+.path li:after {
+  content: " > ";
+}
+.path li:last-child {
+  background: -moz-linear-gradient(top, #FFC, #DD8);
+}
+.path li:last-child:after {
+  color: red;
+  content: "";
+}
+
+#sheetList, #sheetList menuitem {
+  font-size: 1em;
+  font-weight: normal;
+}
+
+.sheet_line input {
+  vertical-align: middle;
+}
+
+.sheet_line label {
+  cursor: pointer;
+}
+
+#header, #footer {
+  padding: 5px;
+}
+
+#header label {
+  font-weight: bold;
+}
+#sheets {
+  -moz-margin-end: 10px;
+  margin-top: 5px;
+}
+h1 {
+  font-size: 13px;
+  padding: 2px 10px;
+  margin: 0;
+  background: -moz-linear-gradient(top, #CCC, #AAA);
+  border-radius: 3px;
+  text-shadow: #FFF 0 1px 0;
+  cursor: pointer;
+}
+
+.property-header {
+  padding: 2px 5px;
+  background: -moz-linear-gradient(top, #F8F8F8, #E8E8E8);
+  color: #666;
+}
+
+.property-name, .property-value, .rule-count, .rule-unmatched {
+  cursor: pointer;
+}
+
+/* Take away these two :visited rules to get a core dumper     */
+/* See https://bugzilla.mozilla.org/show_bug.cgi?id=575675#c30 */
+.link { color: #55A;  }
+.link:visited { color: #55A;  }
+a.link { text-decoration: none; cursor: pointer }
+a.link:visited { text-decoration: none; }
+.rule-count, .rule-unmatched {
+  float: right;
+}
+.rule-count[dir="rtl"], .rule-unmatched[dir="rtl"] {
+  float: left;
+}
+.property-view .rule-count .expander,
+.property-view .rule-unmatched .expander {
+  width: 8px;
+  height: 8px;
+  -moz-margin-start: 5px;
+  display: inline-block;
+  background: url("chrome://browser/skin/devtools/arrows.png");
+}
+
+.property-view .rule-count .expander,
+.property-view .rule-unmatched .expander {
+  background-position: 24px 0;
+}
+.property-view[dir="rtl"] .rule-count .expander,
+.property-view[dir="rtl"] .rule-unmatched .expander {
+  background-position: 16px 0;
+}
+.property-view[open] .rule-count .expander,
+.property-view[open] .rule-unmatched .expander {
+  background-position: 8px 0;
+}
+
+.property-name {
+  font-size: 12px;
+  font-weight: bold;
+  -moz-padding-end: 4px;
+  color: #000;
+}
+span.property-value {
+  -moz-padding-end: 5px;
+  font-size: 10px;
+}
+.group {
+  margin-top: 10px;
+}
+
+.group > h1 {
+  color: #333;
+  font-size: 11px;
+}
+.group .groupexpander {
+  width: 8px;
+  height: 8px;
+  margin-top: 2px;
+  background: url("chrome://browser/skin/devtools/arrows.png");
+}
+.group > div {
+  display: none;
+}
+.group[open] > div {
+  display: block;
+}
+.group .groupexpander {
+  background-position: 48px 0;
+  float: right;
+}
+.group[dir="rtl"] .groupexpander {
+  background-position: 40px 0;
+  float: left;
+}
+.group[open] .groupexpander {
+  background-position: 32px 0;
+  float: right;
+}
+.group[open][dir="rtl"] .groupexpander {
+  background-position: 32px 0;
+  float: left;
+}
+
+.group, #header, #footer {
+  background: #FFF;
+  border: 1px solid #E1E1E1;
+  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
+  border-radius: 4px 4px 4px 4px;
+}
+
+.property-view > .rules {
+  display: none;
+}
+.property-view[open] > .rules {
+  display: table;
+}
+.rules {
+  max-height: 350px;
+  overflow-y: auto;
+}
+.rule-specificty, .rule-status {
+  white-space: nowrap;
+}
+.rule-link {
+  text-align: end;
+}
+
+/* This rule is necessary because Templater.jsm breaks LTR TDs in RTL docs */
+.rule-text {
+  direction: ltr;
+}
+
+.resizerbox {
+  background-color: window;
+}
+
+.bestmatch { color: black; }
+.matched { text-decoration: line-through; }
+.parentmatch { color: #666; }
+.unmatched { color: brown; }
--- a/browser/themes/pinstripe/browser/jar.mn
+++ b/browser/themes/pinstripe/browser/jar.mn
@@ -118,16 +118,18 @@ browser.jar:
   skin/classic/browser/tabbrowser/tab-overflow-border.png                (tabbrowser/tab-overflow-border.png)
   skin/classic/browser/tabbrowser/tabDragIndicator.png                   (tabbrowser/tabDragIndicator.png)
   skin/classic/browser/tabview/close.png                    (tabview/close.png)
   skin/classic/browser/tabview/edit-light.png               (tabview/edit-light.png)
   skin/classic/browser/tabview/search.png                   (tabview/search.png)
   skin/classic/browser/tabview/stack-expander.png           (tabview/stack-expander.png)
   skin/classic/browser/tabview/tabview.png                  (tabview/tabview.png)
   skin/classic/browser/tabview/tabview.css                  (tabview/tabview.css)
+  skin/classic/browser/devtools/arrows.png                  (devtools/arrows.png)
+  skin/classic/browser/devtools/csshtmltree.css             (devtools/csshtmltree.css)
 #ifdef MOZ_SERVICES_SYNC
   skin/classic/browser/sync-throbber.png
   skin/classic/browser/sync-16.png
   skin/classic/browser/sync-32.png
   skin/classic/browser/sync-bg.png
   skin/classic/browser/sync-desktopIcon.png
   skin/classic/browser/sync-mobileIcon.png
   skin/classic/browser/sync-notification-24.png
new file mode 100644
index 0000000000000000000000000000000000000000..95e7217927854ed98dd7e55123709ad8baf126f9
GIT binary patch
literal 933
zc$@*H16urvP)<h;3K|Lk000e1NJLTq001xm000OG1^@s6zbkx400001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2ipe(
z2{9a0;%#{V00SjSL_t(I%Z-#zY!qb}#(&SZ1hxkRgceivVA94v7qr+KPFlDbf`^Xo
z8e^fxvlk=AizX%*!qKaQieA|8ZKH&EQQCTHw8f-}iRczRxj>1Pa&WiH&O9DwyKNIO
ze3?u#Gw=7#^XGj&(X;<F_Boo%ztPhNjb$EaZQu0@_BhZY=ou_i2nHEK3P8K{b9wCi
zKkfEfHOA8P*ntaF7t0USek`dRch)`{!nQS}rvU`EEu_52o(qs2LH>ba?<6n?K+pdF
z0lxbfJ9aG~O%n7h(pk{sKp&_V*oV~z3~+DtR6A{r0#}lxGS48R0B67VecLBVWjskL
z^&ow27y^AQ7?M2%9F#O97$Se33ON(qMH<DPr`UTAdmtyWccXLh;iK%>^CK{c)sJan
znbztR@b8@}b?(M|1uOxQgrtLkFr6fo`ENeG@Djk8Lw{@lf3@;vl2q!zdBh{w2o&RM
z7j56L*v#6%LX4$p(X;m_U|;98IzQh7u7XdIr3VPx-bD<6*190U;0BGALALKc224^Y
zG%*>3ok+tpmTO5;`9;!Gx#LEXv1ORYoCFUdeFQuz$)-CXr0KouQ&VpYBuQmFCnj=Y
zA|pFYjhO%z5P$+A00FrPJeh+pECRn`6@e@VND5@241m_1OGwu{5*V1Gv0NjUKkMq&
zf!2|-A3O-E0d;k?^(L7ufEY_(05=5ANqV6g%QMx*GJ!M`X&OjVk)}nmtd5zWxpIis
z-H1Ys=E?~uJcq+~5OzW#!-b8o=HNaC@5tTLC+j&f1Z<2umv17*V=O%utL4u|>Lar;
zmPS%%qqeUqH`w_kcFh3szHMPEbN1ePjsmg}MdUg#!P?&=G*>QnH#YuS2e?G@_FG89
z*fYovZAo8q<uYaoT&20ZMB^4)ls7kl<1v={<HgdK>z_`}JpsVW@04d_EDdL5hizYV
z?nKv)&YnLF^HbH=XWxEN9RiuJVNbBO`aG>Wm+pH!Yyhg!ygh;S0)%h3)MV{5;L}^e
zZ(qyo6ET+hJEQQQYaI|ZW@0Q214q&<o!e4me~9^DbNK%SaZ!ks&1VtM00000NkvXX
Hu0mjfz!{kU
new file mode 100644
--- /dev/null
+++ b/browser/themes/winstripe/browser/devtools/csshtmltree.css
@@ -0,0 +1,236 @@
+/* ***** 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 Inspector Module.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Joe Walker <jwalker@mozilla.com> (original author)
+ *   Mihai Șucan <mihai.sucan@gmail.com>
+ *   Michael Ratcliffe <mratcliffe@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+body {
+  font-family: Lucida Grande, sans-serif;
+  font-size: 11px;
+  background: #EEE;
+}
+
+.path {
+  font-size: 11px;
+  word-spacing: -1px;
+}
+.path ol {
+  list-style: none;
+  margin: 0;
+  padding: 0;
+}
+.path li {
+  border-radius: 3px;
+  padding: 2px 3px;
+  text-shadow: #FFF 0 1px 0;
+  font-weight: bold;
+  font-size: 11px;
+  background: -moz-linear-gradient(top, #F6F6FF, #E3E3FF);
+  display: inline-block;
+}
+.path li:after {
+  content: " > ";
+}
+.path li:last-child {
+  background: -moz-linear-gradient(top, #FFC, #DD8);
+}
+.path li:last-child:after {
+  color: red;
+  content: "";
+}
+
+#sheetList, #sheetList menuitem {
+  font-size: 1em;
+  font-weight: normal;
+}
+
+.sheet_line input {
+  vertical-align: middle;
+}
+
+.sheet_line label {
+  cursor: pointer;
+}
+
+#header, #footer {
+  padding: 5px;
+}
+
+#header label {
+  font-weight: bold;
+}
+#sheets {
+  -moz-margin-end: 10px;
+  margin-top: 5px;
+}
+h1 {
+  font-size: 13px;
+  padding: 2px 10px;
+  margin: 0;
+  background: -moz-linear-gradient(top, #CCC, #AAA);
+  border-radius: 3px;
+  text-shadow: #FFF 0 1px 0;
+  cursor: pointer;
+}
+
+.property-header {
+  padding: 2px 5px;
+  background: -moz-linear-gradient(top, #F8F8F8, #E8E8E8);
+  color: #666;
+}
+
+.property-name, .property-value, .rule-count, .rule-unmatched {
+  cursor: pointer;
+}
+
+/* Take away these two :visited rules to get a core dumper     */
+/* See https://bugzilla.mozilla.org/show_bug.cgi?id=575675#c30 */
+.link { color: #55A;  }
+.link:visited { color: #55A;  }
+a.link { text-decoration: none; cursor: pointer }
+a.link:visited { text-decoration: none; }
+.rule-count, .rule-unmatched {
+  float: right;
+}
+.rule-count[dir="rtl"], .rule-unmatched[dir="rtl"] {
+  float: left;
+}
+.property-view .rule-count .expander,
+.property-view .rule-unmatched .expander {
+  width: 8px;
+  height: 8px;
+  -moz-margin-start: 5px;
+  display: inline-block;
+  background: url("chrome://browser/skin/devtools/arrows.png");
+}
+
+.property-view .rule-count .expander,
+.property-view .rule-unmatched .expander {
+  background-position: 24px 0;
+}
+.property-view[dir="rtl"] .rule-count .expander,
+.property-view[dir="rtl"] .rule-unmatched .expander {
+  background-position: 16px 0;
+}
+.property-view[open] .rule-count .expander,
+.property-view[open] .rule-unmatched .expander {
+  background-position: 8px 0;
+}
+
+.property-name {
+  font-size: 12px;
+  font-weight: bold;
+  -moz-padding-end: 4px;
+  color: #000;
+}
+span.property-value {
+  -moz-padding-end: 5px;
+  font-size: 10px;
+}
+.group {
+  margin-top: 10px;
+}
+
+.group > h1 {
+  color: #333;
+  font-size: 11px;
+}
+.group .groupexpander {
+  width: 8px;
+  height: 8px;
+  margin-top: 2px;
+  background: url("chrome://browser/skin/devtools/arrows.png");
+}
+.group > div {
+  display: none;
+}
+.group[open] > div {
+  display: block;
+}
+.group .groupexpander {
+  background-position: 48px 0;
+  float: right;
+}
+.group[dir="rtl"] .groupexpander {
+  background-position: 40px 0;
+  float: left;
+}
+.group[open] .groupexpander {
+  background-position: 32px 0;
+  float: right;
+}
+.group[open][dir="rtl"] .groupexpander {
+  background-position: 32px 0;
+  float: left;
+}
+
+.group, #header, #footer {
+  background: #FFF;
+  border: 1px solid #E1E1E1;
+  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
+  border-radius: 4px 4px 4px 4px;
+}
+
+.property-view > .rules {
+  display: none;
+}
+.property-view[open] > .rules {
+  display: table;
+}
+.rules {
+  max-height: 350px;
+  overflow-y: auto;
+}
+.rule-specificty, .rule-status {
+  white-space: nowrap;
+}
+.rule-link {
+  text-align: end;
+}
+
+/* This rule is necessary because Templater.jsm breaks LTR TDs in RTL docs */
+.rule-text {
+  direction: ltr;
+}
+
+.resizerbox {
+  background-color: window;
+}
+
+.bestmatch { color: black; }
+.matched { text-decoration: line-through; }
+.parentmatch { color: #666; }
+.unmatched { color: brown; }
--- a/browser/themes/winstripe/browser/jar.mn
+++ b/browser/themes/winstripe/browser/jar.mn
@@ -101,16 +101,18 @@ browser.jar:
         skin/classic/browser/tabview/close.png                      (tabview/close.png)
         skin/classic/browser/tabview/edit-light.png                 (tabview/edit-light.png)
         skin/classic/browser/tabview/grain.png                      (tabview/grain.png)
         skin/classic/browser/tabview/search.png                     (tabview/search.png)
         skin/classic/browser/tabview/stack-expander.png             (tabview/stack-expander.png)
         skin/classic/browser/tabview/tabview.png                    (tabview/tabview.png)
         skin/classic/browser/tabview/tabview-inverted.png           (tabview/tabview-inverted.png)
         skin/classic/browser/tabview/tabview.css                    (tabview/tabview.css)
+        skin/classic/browser/devtools/arrows.png                    (devtools/arrows.png)
+        skin/classic/browser/devtools/csshtmltree.css               (devtools/csshtmltree.css)
 #ifdef MOZ_SERVICES_SYNC
         skin/classic/browser/sync-throbber.png
         skin/classic/browser/sync-16.png
         skin/classic/browser/sync-32.png
         skin/classic/browser/sync-bg.png
         skin/classic/browser/sync-desktopIcon.png
         skin/classic/browser/sync-mobileIcon.png
         skin/classic/browser/sync-notification-24.png
--- a/toolkit/locales/en-US/chrome/global/headsUpDisplay.properties
+++ b/toolkit/locales/en-US/chrome/global/headsUpDisplay.properties
@@ -110,16 +110,33 @@ NetworkPanel.imageSizeDeltaDurationMS=%Sx%Spx, Δ%Sms
 # flash data received from the server can't be displayed.
 #
 # The %S is replaced by the content type, that can't be displayed, examples are
 #  o application/x-shockwave-flash
 #  o music/crescendo
 NetworkPanel.responseBodyUnableToDisplay.content=Unable to display responses of type "%S"
 ConsoleAPIDisabled=The Web Console logging API (console.log, console.info, console.warn, console.error) has been disabled by a script on this page.
 
+# LOCALIZATION NOTE (inspectStyle.nullObjectPassed):
+# This message is returned when a null object is passed in to inspectstyle()
+inspectStyle.nullObjectPassed=Object is null
+
+# LOCALIZATION NOTE (inspectStyle.mustBeDomNode):
+# This message is returned when a non-DOM node is passed in to inspectstyle()
+inspectStyle.mustBeDomNode=Object must be a valid DOM node
+
+# LOCALIZATION NOTE (inspectStyle.nodeHasNoStyleProps):
+# This message is returned when an unstyleable object is passed in to inspectstyle()
+inspectStyle.nodeHasNoStyleProps=Object cannot be styled
+
+# LOCALIZATION NOTE (inspectStyle.styleInspectorNotEnabled):
+# This message is returned when devtools.styleinspector.enabled is not set to
+# true
+inspectStyle.styleInspectorNotEnabled=The style inspector is not enabled. Please set the option devtools.styleinspector.enabled to true in about:config to use this command.
+
 # LOCALIZATION NOTE (webConsolePosition): The label shown for the menu which
 # allows the user to toggle between the Web Console positioning types.
 webConsolePosition=Position
 
 # LOCALIZATION NOTE (webConsolePositionTooltip): The tooltip shown when the user
 # hovers the Position button in the Web Console toolbar.
 webConsolePositionTooltip=Position the Web Console above or below the document