From 61c9d976dafde98dd6b81d6dc72cf34683b89311 Mon Sep 17 00:00:00 2001 From: Mike Ratcliffe Date: Tue, 30 Aug 2011 13:38:30 -0300 Subject: [PATCH] Bug 680111 - style inspector is not showing the correct selected rule, r=msucan --- browser/devtools/styleinspector/CssLogic.jsm | 91 +++++++++++++++++++ .../browser_styleinspector_webconsole.js | 40 +++++++- 2 files changed, 127 insertions(+), 4 deletions(-) diff --git a/browser/devtools/styleinspector/CssLogic.jsm b/browser/devtools/styleinspector/CssLogic.jsm index a2f535a83842..c301a3432758 100644 --- a/browser/devtools/styleinspector/CssLogic.jsm +++ b/browser/devtools/styleinspector/CssLogic.jsm @@ -1087,6 +1087,7 @@ function CssSelector(aCssRule, aSelector) this._cssRule = aCssRule; this.text = aSelector; this.elementStyle = this.text == "@element.style"; + this._specificity = null; } CssSelector.prototype = { @@ -1168,6 +1169,57 @@ CssSelector.prototype = { 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; @@ -1470,6 +1522,25 @@ function CssSelectorInfo(aSelector, aProperty, aValue, 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 = { @@ -1518,6 +1589,17 @@ CssSelectorInfo.prototype = { 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. * @@ -1587,6 +1669,15 @@ CssSelectorInfo.prototype = { 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; diff --git a/browser/devtools/styleinspector/test/browser/browser_styleinspector_webconsole.js b/browser/devtools/styleinspector/test/browser/browser_styleinspector_webconsole.js index 7917ccc06000..6255afa81307 100644 --- a/browser/devtools/styleinspector/test/browser/browser_styleinspector_webconsole.js +++ b/browser/devtools/styleinspector/test/browser/browser_styleinspector_webconsole.js @@ -104,6 +104,13 @@ function teststylePanels() { 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 + @@ -111,15 +118,40 @@ function teststylePanels() { 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]");