Bug 834187 - [Computed view] Restore processing of namespaced type selectors e.g. :not(svg|a)

This commit is contained in:
Michael Ratcliffe 2013-02-04 16:29:30 +00:00
parent 35af0f7e6f
commit 8f982b54a0
3 changed files with 138 additions and 188 deletions

View File

@ -125,8 +125,6 @@ CssLogic.prototype = {
_matchedRules: null,
_matchedSelectors: null,
domUtils: Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils),
/**
* Reset various properties
*/
@ -471,7 +469,8 @@ CssLogic.prototype = {
rule.selectors.forEach(function (aSelector) {
if (aSelector._matchId !== this._matchId &&
(aSelector.elementStyle ||
this._selectorMatchesElement(aSelector))) {
this.selectorMatchesElement(rule._domRule, aSelector.selectorIndex))) {
aSelector._matchId = this._matchId;
this._matchedSelectors.push([ aSelector, status ]);
if (aCallback) {
@ -489,15 +488,19 @@ CssLogic.prototype = {
* parents.
*
* @private
* @param {string} aSelector the selector string you want to check.
* @return {boolean} true if the given selector matches the highlighted
* element or any of its parents, otherwise false is returned.
* @param {DOMRule} domRule
* The DOM Rule containing the selector.
* @param {Number} idx
* The index of the selector within the DOMRule.
* @return {boolean}
* true if the given selector matches the highlighted element or any
* of its parents, otherwise false is returned.
*/
_selectorMatchesElement: function CL__selectorMatchesElement(aSelector)
selectorMatchesElement: function CL_selectorMatchesElement2(domRule, idx)
{
let element = this.viewedElement;
do {
if (element.mozMatchesSelector(aSelector)) {
if (domUtils.selectorMatchesElement(element, domRule, idx)) {
return true;
}
} while ((element = element.parentNode) &&
@ -531,7 +534,7 @@ CssLogic.prototype = {
if (rule.getPropertyValue(aProperty) &&
(status == CssLogic.STATUS.MATCHED ||
(status == CssLogic.STATUS.PARENT_MATCH &&
this.domUtils.isInheritedProperty(aProperty)))) {
domUtils.isInheritedProperty(aProperty)))) {
result[aProperty] = true;
return false;
}
@ -569,7 +572,7 @@ CssLogic.prototype = {
CssLogic.STATUS.MATCHED : CssLogic.STATUS.PARENT_MATCH;
try {
domRules = this.domUtils.getCSSStyleRules(element);
domRules = domUtils.getCSSStyleRules(element);
} catch (ex) {
Services.console.
logStringMessage("CL__buildMatchedRules error: " + ex);
@ -690,65 +693,22 @@ CssLogic.getShortNamePath = function CssLogic_getShortNamePath(aElement)
};
/**
* Get a string list of selectors for a given CSSStyleRule.selectorText
* Get a string list of selectors for a given DOMRule.
*
* @param {string} aSelectorText The CSSStyleRule.selectorText to parse.
* @return {array} An array of string selectors.
* @param {DOMRule} aDOMRule
* The DOMRule to parse.
* @return {Array}
* An array of string selectors.
*/
CssLogic.getSelectors = function CssLogic_getSelectors(aSelectorText)
CssLogic.getSelectors = function CssLogic_getSelectors(aDOMRule)
{
let selectors = [];
let selector = aSelectorText.trim();
if (!selector) {
return selectors;
let len = domUtils.getSelectorCount(aDOMRule);
for (let i = 0; i < len; i++) {
let text = domUtils.getSelectorText(aDOMRule, i);
selectors.push(text);
}
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) {
selectors.push(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) {
selectors.push(selectorStr);
}
}
return selectors;
}
@ -1171,7 +1131,7 @@ function CssRule(aCssSheet, aDomRule, aElement)
if (this._cssSheet) {
// parse _domRule.selectorText on call to this.selectors
this._selectors = null;
this.line = this._cssSheet._cssLogic.domUtils.getRuleLine(this._domRule);
this.line = domUtils.getRuleLine(this._domRule);
this.source = this._cssSheet.shortSource + ":" + this.line;
if (this.mediaText) {
this.source += " @media " + this.mediaText;
@ -1179,7 +1139,7 @@ function CssRule(aCssSheet, aDomRule, aElement)
this.href = this._cssSheet.href;
this.contentRule = this._cssSheet.contentSheet;
} else if (aElement) {
this._selectors = [ new CssSelector(this, "@element.style") ];
this._selectors = [ new CssSelector(this, "@element.style", 0) ];
this.line = -1;
this.source = CssLogic.l10n("rule.sourceElement");
this.href = "#";
@ -1263,8 +1223,12 @@ CssRule.prototype = {
return this._selectors;
}
let selectors = CssLogic.getSelectors(this._domRule.selectorText);
this._selectors = [new CssSelector(this, text) for (text of selectors)];
let selectors = CssLogic.getSelectors(this._domRule);
for (let i = 0, len = selectors.length; i < len; i++) {
this._selectors.push(new CssSelector(this, selectors[i], i));
}
return this._selectors;
},
@ -1281,13 +1245,15 @@ CssRule.prototype = {
* @constructor
* @param {CssRule} aCssRule the CssRule instance from where the selector comes.
* @param {string} aSelector The selector that we wish to investigate.
* @param {Number} aIndex The index of the selector within it's rule.
*/
this.CssSelector = function CssSelector(aCssRule, aSelector)
this.CssSelector = function CssSelector(aCssRule, aSelector, aIndex)
{
this._cssRule = aCssRule;
this.text = aSelector;
this.elementStyle = this.text == "@element.style";
this._specificity = null;
this.selectorIndex = aIndex;
}
CssSelector.prototype = {
@ -1402,8 +1368,7 @@ CssSelector.prototype = {
* @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.
* @return {Number} The selector's specificity.
*/
get specificity()
{
@ -1411,59 +1376,8 @@ CssSelector.prototype = {
return this._specificity;
}
let specificity = {
ids: 0,
classes: 0,
tags: 0
};
let text = this.text;
if (!this.elementStyle) {
// Remove universal selectors as they are not relevant as far as specificity
// is concerned.
text = text.replace(RX_UNIVERSAL_SELECTOR, "");
// not() is ignored but any selectors contained by it are counted. Let's
// remove the not() and keep the contents.
text = text.replace(RX_NOT, " $1");
// Simplify remaining psuedo classes & elements.
text = text.replace(RX_PSEUDO_CLASS_OR_ELT, " $1)");
// Replace connectors with spaces
text = text.replace(RX_CONNECTORS, " ");
text.split(/\s/).forEach(function(aSimple) {
// Count IDs.
aSimple = aSimple.replace(RX_ID, function() {
specificity.ids++;
return "";
});
// Count class names and attribute matchers.
aSimple = aSimple.replace(RX_CLASS_OR_ATTRIBUTE, function() {
specificity.classes++;
return "";
});
aSimple = aSimple.replace(RX_PSEUDO, function(aDummy, aPseudoName) {
if (this.pseudoElements.has(aPseudoName)) {
// Pseudo elements count as tags.
specificity.tags++;
} else {
// Pseudo classes count as classes.
specificity.classes++;
}
return "";
}.bind(this));
if (aSimple) {
specificity.tags++;
}
}, this);
}
this._specificity = specificity;
this._specificity = domUtils.getSpecificity(this._cssRule._domRule,
this.selectorIndex);
return this._specificity;
},
@ -1610,7 +1524,7 @@ CssPropertyInfo.prototype = {
if (value &&
(aStatus == CssLogic.STATUS.MATCHED ||
(aStatus == CssLogic.STATUS.PARENT_MATCH &&
this._cssLogic.domUtils.isInheritedProperty(this.property)))) {
domUtils.isInheritedProperty(this.property)))) {
let selectorInfo = new CssSelectorInfo(aSelector, this.property, value,
aStatus);
this._matchedSelectors.push(selectorInfo);
@ -1677,25 +1591,6 @@ 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.contentRule ? 2 : 0;
if (this.elementStyle) {
scorePrefix++;
}
if (this.important) {
scorePrefix += this.contentRule ? 2 : 1;
}
this.specificityScore = "" + scorePrefix + this.specificity.ids +
this.specificity.classes + this.specificity.tags;
}
CssSelectorInfo.prototype = {
@ -1824,14 +1719,8 @@ 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.specificity > aThat.specificity) return -1;
if (aThat.specificity > this.specificity) return 1;
if (this.sheetIndex > aThat.sheetIndex) return -1;
if (aThat.sheetIndex > this.sheetIndex) return 1;
@ -1847,3 +1736,7 @@ CssSelectorInfo.prototype = {
return this.selector + " -> " + this.value;
},
};
XPCOMUtils.defineLazyGetter(this, "domUtils", function() {
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
});

View File

@ -108,8 +108,6 @@ ElementStyle.prototype = {
// to figure out how shorthand properties will be parsed.
dummyElement: null,
domUtils: Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils),
/**
* Called by the Rule object when it has been changed through the
* setProperty* methods.
@ -158,7 +156,7 @@ ElementStyle.prototype = {
});
// Get the styles that apply to the element.
var domRules = this.domUtils.getCSSStyleRules(aElement);
var domRules = domUtils.getCSSStyleRules(aElement);
// getCSStyleRules returns ordered from least-specific to
// most-specific.
@ -413,7 +411,7 @@ Rule.prototype = {
// No stylesheet, no ruleLine
return null;
}
return this.elementStyle.domUtils.getRuleLine(this.domRule);
return domUtils.getRuleLine(this.domRule);
},
/**
@ -576,8 +574,7 @@ Rule.prototype = {
continue;
let name = matches[1];
if (this.inherited &&
!this.elementStyle.domUtils.isInheritedProperty(name)) {
if (this.inherited && !domUtils.isInheritedProperty(name)) {
continue;
}
let value = store.userProperties.getProperty(this.style, name, matches[2]);
@ -1448,7 +1445,7 @@ RuleEditor.prototype = {
// actually match. For custom selector text (such as for the 'element'
// style, just show the text directly.
if (this.rule.domRule && this.rule.domRule.selectorText) {
let selectors = CssLogic.getSelectors(this.rule.selectorText);
let selectors = CssLogic.getSelectors(this.rule.domRule);
let element = this.rule.inherited || this.ruleView._viewedElement;
for (let i = 0; i < selectors.length; i++) {
let selector = selectors[i];
@ -1458,8 +1455,12 @@ RuleEditor.prototype = {
textContent: ", "
});
}
let cls = element.mozMatchesSelector(selector) ? "ruleview-selector-matched" :
"ruleview-selector-unmatched";
let cls;
if (domUtils.selectorMatchesElement(element, this.rule.domRule, i)) {
cls = "ruleview-selector-matched";
} else {
cls = "ruleview-selector-unmatched";
}
createChild(this.selectorText, "span", {
class: cls,
textContent: selector
@ -2847,3 +2848,7 @@ XPCOMUtils.defineLazyGetter(this, "_strings", function() {
XPCOMUtils.defineLazyGetter(this, "osString", function() {
return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
});
XPCOMUtils.defineLazyGetter(this, "domUtils", function() {
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
});

View File

@ -9,41 +9,93 @@ Cu.import("resource:///modules/devtools/CssLogic.jsm", tempScope);
let CssLogic = tempScope.CssLogic;
let CssSelector = tempScope.CssSelector;
function test()
function createDocument()
{
let tests = [
{text: "*", expected: "000"},
{text: "LI", expected: "001"},
{text: "UL LI", expected: "002"},
{text: "UL OL+LI", expected: "003"},
{text: "H1 + *[REL=up]", expected: "011"},
{text: "UL OL LI.red", expected: "013"},
{text: "LI.red.level", expected: "021"},
{text: ".red .level", expected: "020"},
{text: "#x34y", expected: "100"},
{text: "#s12:not(FOO)", expected: "101"},
{text: "body#home div#warning p.message", expected: "213"},
{text: "* body#home div#warning p.message", expected: "213"},
{text: "#footer *:not(nav) li", expected: "102"},
{text: "bar:nth-child(1n+0)", expected: "011"},
{text: "li::-moz-list-number", expected: "002"},
{text: "a:hover", expected: "011"},
];
let doc = content.document;
doc.body.innerHTML = getStylesheetText();
doc.title = "Computed view specificity test";
runTests(doc);
}
tests.forEach(function(aTest) {
let selector = new CssSelector(null, aTest.text);
let specificity = selector.specificity;
function runTests(doc) {
let cssLogic = new CssLogic();
cssLogic.highlight(doc.body);
let result = "" + specificity.ids + specificity.classes + specificity.tags;
is(result, aTest.expected, "selector \"" + aTest.text +
"\" produces expected result");
});
let tests = getTests();
let cssSheet = cssLogic.sheets[0];
let cssRule = cssSheet.domSheet.cssRules[0];
let selectors = CssLogic.getSelectors(cssRule);
for (let i = 0; i < selectors.length; i++) {
let selectorText = selectors[i];
let selector = new CssSelector(cssRule, selectorText, i);
let expected = getExpectedSpecificity(selectorText);
let specificity = selector.domUtils.getSpecificity(selector._cssRule,
selector.selectorIndex)
is(specificity, expected,
'selector "' + selectorText + '" has a specificity of ' + expected);
}
finishUp();
}
function getExpectedSpecificity(selectorText) {
let tests = getTests();
for (let test of tests) {
if (test.text == selectorText) {
return test.expected;
}
}
}
function getTests() {
return [
{text: "*", expected: 0},
{text: "LI", expected: 1},
{text: "UL LI", expected: 2},
{text: "UL OL + LI", expected: 3},
{text: "H1 + [REL=\"up\"]", expected: 257},
{text: "UL OL LI.red", expected: 259},
{text: "LI.red.level", expected: 513},
{text: ".red .level", expected: 512},
{text: "#x34y", expected: 65536},
{text: "#s12:not(FOO)", expected: 65537},
{text: "body#home div#warning p.message", expected: 131331},
{text: "* body#home div#warning p.message", expected: 131331},
{text: "#footer :not(nav) li", expected: 65538},
{text: "bar:nth-child(n)", expected: 257},
{text: "li::-moz-list-number", expected: 1},
{text: "a:hover", expected: 257},
];
}
function getStylesheetText() {
let tests = getTests();
let text = "";
tests.forEach(function(test) {
if (text.length > 0) {
text += ",";
}
text += test.text;
});
return '<style type="text/css">' + text + " {color:red;}</style>";
}
function finishUp()
{
CssLogic = CssSelector = null;
CssLogic = CssSelector = tempScope = null;
finish();
}
function test()
{
waitForExplicitFinish();
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) {
gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true);
waitForFocus(createDocument, content);
}, true);
content.location = "data:text/html,Computed view specificity test";
}