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 id3
push userfelipc@gmail.com
push dateFri, 30 Sep 2011 20:09:13 +0000
reviewersdolske, msucan
bugs582596
milestone9.0a1
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 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..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 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..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 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..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