Bug 696180 - Add support for inherited properties to the rule view. r=robcee
authorDave Camp <dcamp@mozilla.com>
Sat, 05 Nov 2011 08:45:22 -0700
changeset 79826 3246d984607166392829d1abee89a51150bb715c
parent 79825 56e7358abdb3c4f8bb82b064bf749081b181f802
child 79827 a6508712707aa560c32ad79532552f853577911d
push id293
push userdcamp@campd.org
push dateSat, 05 Nov 2011 16:02:42 +0000
treeherderfx-team@a6508712707a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrobcee
bugs696180
milestone10.0a1
Bug 696180 - Add support for inherited properties to the rule view. r=robcee
browser/devtools/styleinspector/CssRuleView.jsm
browser/devtools/styleinspector/test/browser/Makefile.in
browser/devtools/styleinspector/test/browser/browser_ruleview_inherit.js
browser/locales/en-US/chrome/browser/devtools/styleinspector.properties
--- a/browser/devtools/styleinspector/CssRuleView.jsm
+++ b/browser/devtools/styleinspector/CssRuleView.jsm
@@ -120,49 +120,88 @@ ElementStyle.prototype = {
   /**
    * Refresh the list of rules to be displayed for the active element.
    * Upon completion, this.rules[] will hold a list of Rule objects.
    */
   _populate: function ElementStyle_populate()
   {
     this.rules = [];
 
+    let element = this.element;
+    do {
+      this._addElementRules(element);
+    } while ((element = element.parentNode) &&
+             element.nodeType === Ci.nsIDOMNode.ELEMENT_NODE);
+
+    // Mark overridden computed styles.
+    this.markOverridden();
+  },
+
+  _addElementRules: function ElementStyle_addElementRules(aElement)
+  {
+    let inherited = aElement !== this.element ? aElement : null;
+
     // Include the element's style first.
-    this.rules.push(new Rule(this, {
-      style: this.element.style,
-      selectorText: CssLogic.l10n("rule.sourceElement")
-    }));
+    this._maybeAddRule({
+      style: aElement.style,
+      selectorText: CssLogic.l10n("rule.sourceElement"),
+      inherited: inherited
+    });
 
     // Get the styles that apply to the element.
-    try {
-      var domRules = this.domUtils.getCSSStyleRules(this.element);
-    } catch (ex) {
-      Services.console.logStringMessage("ElementStyle_populate error: " + ex);
-      return;
-    }
+    var domRules = this.domUtils.getCSSStyleRules(aElement);
 
     // getCSStyleRules returns ordered from least-specific to
     // most-specific.
     for (let i = domRules.Count() - 1; i >= 0; i--) {
       let domRule = domRules.GetElementAt(i);
 
       // XXX: Optionally provide access to system sheets.
       let systemSheet = CssLogic.isSystemStyleSheet(domRule.parentStyleSheet);
       if (systemSheet) {
         continue;
       }
 
-      // XXX: non-style rules.
-      if (domRule.type === Ci.nsIDOMCSSRule.STYLE_RULE) {
-        this.rules.push(new Rule(this, { domRule: domRule }));
+      if (domRule.type !== Ci.nsIDOMCSSRule.STYLE_RULE) {
+        continue;
       }
+
+      this._maybeAddRule({
+        domRule: domRule,
+        inherited: inherited
+      });
+    }
+  },
+
+  /**
+   * Add a rule if it's one we care about.  Filters out duplicates and
+   * inherited styles with no inherited properties.
+   *
+   * @param {object} aOptions
+   *        Options for creating the Rule, see the Rule constructor.
+   *
+   * @return true if we added the rule.
+   */
+  _maybeAddRule: function ElementStyle_maybeAddRule(aOptions)
+  {
+    // If we've already included this domRule (for example, when a
+    // common selector is inherited), ignore it.
+    if (aOptions.domRule &&
+        this.rules.some(function(rule) rule.domRule === aOptions.domRule)) {
+      return false;
     }
 
-    // Mark overridden computed styles.
-    this.markOverridden();
+    let rule = new Rule(this, aOptions);
+
+    // Ignore inherited rules with no properties.
+    if (aOptions.inherited && rule.textProps.length == 0) {
+      return false;
+    }
+
+    this.rules.push(rule);
   },
 
   /**
    * Mark the properties listed in this.rules with an overridden flag
    * if an earlier property overrides it.
    */
   markOverridden: function ElementStyle_markOverridden()
   {
@@ -173,17 +212,17 @@ ElementStyle.prototype = {
       textProps = textProps.concat(rule.textProps.slice(0).reverse());
     }
 
     // Gather all the computed properties applied by those text
     // properties.
     let computedProps = [];
     for each (let textProp in textProps) {
       computedProps = computedProps.concat(textProp.computed);
-    };
+    }
 
     // Walk over the computed properties.  As we see a property name
     // for the first time, mark that property's name as taken by this
     // property.
     //
     // If we come across a property whose name is already taken, check
     // its priority against the property that was found first:
     //
@@ -268,40 +307,53 @@ ElementStyle.prototype = {
  *        The ElementStyle to which this rule belongs.
  * @param {object} aOptions
  *        The information used to construct this rule.  Properties include:
  *          domRule: the nsIDOMCSSStyleRule to view, if any.
  *          style: the nsIDOMCSSStyleDeclaration to view.  If omitted,
  *            the domRule's style will be used.
  *          selectorText: selector text to display.  If omitted, the domRule's
  *            selectorText will be used.
+ *          inherited: An element this rule was inherited from.  If omitted,
+ *            the rule applies directly to the current element.
  * @constructor
  */
 function Rule(aElementStyle, aOptions)
 {
   this.elementStyle = aElementStyle;
   this.domRule = aOptions.domRule || null;
   this.style = aOptions.style || this.domRule.style;
   this.selectorText = aOptions.selectorText || this.domRule.selectorText;
-
+  this.inherited = aOptions.inherited || null;
   this._getTextProperties();
 }
 
 Rule.prototype = {
   get title()
   {
     if (this._title) {
       return this._title;
     }
     let sheet = this.domRule ? this.domRule.parentStyleSheet : null;
     this._title = CssLogic.shortSource(sheet);
     if (this.domRule) {
       let line = this.elementStyle.domUtils.getRuleLine(this.domRule);
       this._title += ":" + line;
     }
+
+    if (this.inherited) {
+      let eltText = this.inherited.tagName.toLowerCase();
+      if (this.inherited.id) {
+        eltText += "#" + this.inherited.id;
+      }
+      let args = [eltText, this._title];
+      this._title = CssLogic._strings.formatStringFromName("rule.inheritedSource",
+                                                           args, args.length);
+    }
+
     return this._title;
   },
 
   /**
    * Create a new TextProperty to include in the rule.
    *
    * @param {string} aName
    *        The text property name (such as "background" or "border-top").
@@ -411,17 +463,23 @@ Rule.prototype = {
   {
     this.textProps = [];
     let lines = this.style.cssText.match(CSS_LINE_RE);
     for each (let line in lines) {
       let matches = CSS_PROP_RE.exec(line);
       if(!matches || !matches[2])
         continue;
 
-      let prop = new TextProperty(this, matches[1], matches[2], matches[3] || "");
+      let name = matches[1];
+      if (this.inherited &&
+          !this.elementStyle.domUtils.isInheritedProperty(name)) {
+        continue;
+      }
+
+      let prop = new TextProperty(this, name, matches[2], matches[3] || "");
       this.textProps.push(prop);
     }
   },
 }
 
 /**
  * A single property in a rule's cssText.
  *
--- a/browser/devtools/styleinspector/test/browser/Makefile.in
+++ b/browser/devtools/styleinspector/test/browser/Makefile.in
@@ -48,16 +48,17 @@ include $(topsrcdir)/config/rules.mk
 _BROWSER_TEST_FILES = \
   browser_styleinspector.js \
   browser_styleinspector_webconsole.js \
   browser_bug683672.js \
   browser_styleinspector_bug_672746_default_styles.js \
   browser_styleinspector_bug_672744_search_filter.js \
   browser_bug_692400_element_style.js \
   browser_ruleview_editor.js \
+  browser_ruleview_inherit.js \
   browser_ruleview_manipulation.js \
   browser_ruleview_override.js \
   browser_ruleview_ui.js \
   head.js \
   $(NULL)
 
 _BROWSER_TEST_PAGES = \
   browser_styleinspector_webconsole.htm \
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser/browser_ruleview_inherit.js
@@ -0,0 +1,101 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Cu.import("resource:///modules/devtools/CssRuleView.jsm");
+
+let doc;
+
+function simpleInherit()
+{
+  let style = '' +
+    '#test2 {' +
+    '  background-color: green;' +
+    '  color: purple;' +
+    '}';
+
+  let styleNode = addStyle(doc, style);
+  doc.body.innerHTML = '<div id="test2"><div id="test1">Styled Node</div></div>';
+
+  let elementStyle = new _ElementStyle(doc.getElementById("test1"));
+
+  is(elementStyle.rules.length, 2, "Should have 2 rules.");
+
+  let elementRule = elementStyle.rules[0];
+  ok(!elementRule.inherited, "Element style attribute should not consider itself inherited.");
+
+  let inheritRule = elementStyle.rules[1];
+  is(inheritRule.selectorText, "#test2", "Inherited rule should be the one that includes inheritable properties.");
+  ok(!!inheritRule.inherited, "Rule should consider itself inherited.");
+  is(inheritRule.textProps.length, 1, "Should only display one inherited style");
+  let inheritProp = inheritRule.textProps[0];
+  is(inheritProp.name, "color", "color should have been inherited.");
+
+  styleNode.parentNode.removeChild(styleNode);
+
+  emptyInherit();
+}
+
+function emptyInherit()
+{
+  // No inheritable styles, this rule shouldn't show up.
+  let style = '' +
+    '#test2 {' +
+    '  background-color: green;' +
+    '}';
+
+  let styleNode = addStyle(doc, style);
+  doc.body.innerHTML = '<div id="test2"><div id="test1">Styled Node</div></div>';
+
+  let elementStyle = new _ElementStyle(doc.getElementById("test1"));
+
+  is(elementStyle.rules.length, 1, "Should have 1 rule.");
+
+  let elementRule = elementStyle.rules[0];
+  ok(!elementRule.inherited, "Element style attribute should not consider itself inherited.");
+
+  styleNode.parentNode.removeChild(styleNode);
+
+  elementStyleInherit();
+}
+
+function elementStyleInherit()
+{
+  doc.body.innerHTML = '<div id="test2" style="color: red"><div id="test1">Styled Node</div></div>';
+
+  let elementStyle = new _ElementStyle(doc.getElementById("test1"));
+
+  is(elementStyle.rules.length, 2, "Should have 2 rules.");
+
+  let elementRule = elementStyle.rules[0];
+  ok(!elementRule.inherited, "Element style attribute should not consider itself inherited.");
+
+  let inheritRule = elementStyle.rules[1];
+  ok(!inheritRule.domRule, "Inherited rule should be an element style, not a rule.");
+  ok(!!inheritRule.inherited, "Rule should consider itself inherited.");
+  is(inheritRule.textProps.length, 1, "Should only display one inherited style");
+  let inheritProp = inheritRule.textProps[0];
+  is(inheritProp.name, "color", "color should have been inherited.");
+
+  finishTest();
+}
+
+function finishTest()
+{
+  doc = 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(simpleInherit, content);
+  }, true);
+
+  content.location = "data:text/html,basic style inspector tests";
+}
--- a/browser/locales/en-US/chrome/browser/devtools/styleinspector.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/styleinspector.properties
@@ -14,23 +14,30 @@ 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.inheritedSource): Shown for CSS rules
+# that were inherited from a parent node.  Will be passed a node
+# identifier and a source location.
+# e.g "Inherited from body#bodyID (styles.css:20)"
+rule.inheritedSource=Inherited from %S (%S)
+
 # 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
 
 # LOCALIZATION NOTE (style.highlighter.button): These strings are used inside
 # sidebar of the Highlighter for the style inspector button
 style.highlighter.button.label1=Properties
 style.highlighter.accesskey1=P
 style.highlighter.button.tooltip=Inspect element styles
+