Bug 680111 - style inspector is not showing the correct selected rule, r=msucan
authorMike Ratcliffe <mratcliffe@mozilla.com>
Tue, 30 Aug 2011 13:38:30 -0300
changeset 76260 54685bf66136dc5e346e1e0873d2fc9afb4f85c0
parent 76259 26bc47cdea9e1177b9cbc155dbe60b6e02fd1b6b
child 76261 c6309c9aa79a70c0b92ca335cd8d4340c796d4e9
push id3
push userfelipc@gmail.com
push dateFri, 30 Sep 2011 20:09:13 +0000
reviewersmsucan
bugs680111
milestone9.0a1
Bug 680111 - style inspector is not showing the correct selected rule, r=msucan
browser/devtools/styleinspector/CssLogic.jsm
browser/devtools/styleinspector/test/browser/browser_styleinspector_webconsole.js
--- a/browser/devtools/styleinspector/CssLogic.jsm
+++ b/browser/devtools/styleinspector/CssLogic.jsm
@@ -1082,16 +1082,17 @@ CssRule.prototype = {
  * @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";
+  this._specificity = null;
 }
 
 CssSelector.prototype = {
   /**
    * Retrieve the CssSelector source, which is the source of the CssSheet owning
    * the selector.
    *
    * @return {string} the selector source.
@@ -1163,16 +1164,67 @@ CssSelector.prototype = {
    * @return {number} the line of the parent CSSStyleRule in the parent
    * stylesheet.
    */
   get ruleLine()
   {
     return this._cssRule.line;
   },
 
+  /**
+   * Retrieve specificity information for the current selector.
+   *
+   * @see http://www.w3.org/TR/css3-selectors/#specificity
+   * @see http://www.w3.org/TR/CSS2/selector.html
+   *
+   * @return {object} an object holding specificity information for the current
+   * selector.
+   */
+  get specificity()
+  {
+    if (this._specificity) {
+      return this._specificity;
+    }
+
+    let specificity = {};
+
+    specificity.ids = 0;
+    specificity.classes = 0;
+    specificity.tags = 0;
+
+    // Split on CSS combinators (section 5.2).
+    // TODO: We need to properly parse the selector. See bug 592743.
+    if (!this.elementStyle) {
+      this.text.split(/[ >+]/).forEach(function(aSimple) {
+        // The regex leaves empty nodes combinators like ' > '
+        if (!aSimple) {
+          return;
+        }
+        // See http://www.w3.org/TR/css3-selectors/#specificity
+        // We can count the IDs by counting the '#' marks.
+        specificity.ids += (aSimple.match(/#/g) || []).length;
+        // Similar with class names and attribute matchers
+        specificity.classes += (aSimple.match(/\./g) || []).length;
+        specificity.classes += (aSimple.match(/\[/g) || []).length;
+        // Pseudo elements count as elements.
+        specificity.tags += (aSimple.match(/:/g) || []).length;
+        // If we have anything of substance before we get into ids/classes/etc
+        // then it must be a tag if it isn't '*'.
+        let tag = aSimple.split(/[#.[:]/)[0];
+        if (tag && tag != "*") {
+          specificity.tags++;
+        }
+      }, this);
+    }
+
+    this._specificity = specificity;
+
+    return this._specificity;
+  },
+
   toString: function CssSelector_toString()
   {
     return this.text;
   },
 };
 
 /**
  * A cache of information about the matched rules, selectors and values attached
@@ -1465,16 +1517,35 @@ function CssSelectorInfo(aSelector, aPro
 {
   this.selector = aSelector;
   this.property = aProperty;
   this.value = aValue;
   this.status = aStatus;
 
   let priority = this.selector._cssRule.getPropertyPriority(this.property);
   this.important = (priority === "important");
+
+  /* Score prefix:
+  0 UA normal property
+  1 UA important property
+  2 normal property
+  3 inline (element.style)
+  4 important
+  5 inline important
+  */
+  let scorePrefix = this.systemRule ? 0 : 2;
+  if (this.elementStyle) {
+    scorePrefix++;
+  }
+  if (this.important) {
+    scorePrefix += this.systemRule ? 1 : 2;
+  }
+
+  this.specificityScore = "" + scorePrefix + this.specificity.ids +
+      this.specificity.classes + this.specificity.tags;
 }
 
 CssSelectorInfo.prototype = {
   /**
    * Retrieve the CssSelector source, which is the source of the CssSheet owning
    * the selector.
    *
    * @return {string} the selector source.
@@ -1514,16 +1585,27 @@ CssSelectorInfo.prototype = {
    * false otherwise.
    */
   get elementStyle()
   {
     return this.selector.elementStyle;
   },
 
   /**
+   * Retrieve specificity information for the current selector.
+   *
+   * @return {object} an object holding specificity information for the current
+   * selector.
+   */
+  get specificity()
+  {
+    return this.selector.specificity;
+  },
+
+  /**
    * 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;
@@ -1582,16 +1664,25 @@ CssSelectorInfo.prototype = {
     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.specificity.ids > aThat.specificity.ids) return -1;
+    if (aThat.specificity.ids > this.specificity.ids) return 1;
+
+    if (this.specificity.classes > aThat.specificity.classes) return -1;
+    if (aThat.specificity.classes > this.specificity.classes) return 1;
+
+    if (this.specificity.tags > aThat.specificity.tags) return -1;
+    if (aThat.specificity.tags > this.specificity.tags) 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;
   },
--- a/browser/devtools/styleinspector/test/browser/browser_styleinspector_webconsole.js
+++ b/browser/devtools/styleinspector/test/browser/browser_styleinspector_webconsole.js
@@ -99,32 +99,64 @@ function teststylePanels() {
   stylePanels.push(popupSet.childNodes[len++]);
 
   let eltArray = [
     doc.getElementById("text"),
     doc.getElementById("text2"),
     doc.getElementById("container")
   ];
 
+  // We have 3 style inspector instances, each with an element selected:
+  // 1. #text
+  // 2. #text2
+  // 3. #container
+  //
+  // We will loop through each instance and check that the correct node is
+  // selected and that the correct css selector has been selected as active
   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;
+    let cssLogic = htmlTree.cssLogic;
+    let elt = eltArray[i];
+    let eltId = elt.id;
 
-    is(eltArray[i], htmlTree.viewedElement,
-      "style inspector node matches the selected node (id=" +
-      eltArray[i].id + ")");
+    // Check that the correct node is selected
+    is(elt, htmlTree.viewedElement,
+      "style inspector node matches the selected node (id=" + eltId + ")");
     is(htmlTree.viewedElement, stylePanels[i].cssLogic.viewedElement,
-      "cssLogic node matches the cssHtmlTree node (id=" + eltArray[i].id + ")");
+      "cssLogic node matches the cssHtmlTree node (id=" + eltId + ")");
 
     ok(groupRuleCount(0, stylePanels[i]) > 0,
        "we have rules for the current node (id=" + eltArray[i].id + ")");
+
+    // Check that the correct css selector has been selected as active
+    let matchedSelectors = cssLogic.getPropertyInfo("font-family").matchedSelectors;
+    let sel = matchedSelectors[0];
+    let selector = sel.selector.text;
+    let value = sel.value;
+
+    // Because we know which selectors should be the best match and what their
+    // values should be we can check them
+    switch(eltId) {
+      case "text":
+        is(selector, "#container > .text", "correct best match for #text");
+        is(value, "cursive", "correct css property value for #" + eltId);
+        break;
+      case "text2":
+        is(selector, "#container > span", "correct best match for #text2");
+        is(value, "cursive", "correct css property value for #" + eltId);
+        break;
+      case "container":
+        is(selector, "#container", "correct best match for #container");
+        is(value, "fantasy", "correct css property value for #" + eltId);
+    }
   }
 
   info("hiding stylePanels[1]");
   Services.obs.addObserver(styleInspectorClosedByHide,
                            "StyleInspector-closed", false);
   stylePanels[1].hidePopup();
 }