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 id21089
push userrcampbell@mozilla.com
push dateWed, 31 Aug 2011 12:23:58 +0000
treeherdermozilla-central@922f27baed98 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmsucan
bugs680111
milestone9.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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();
 }