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, _matchedRules: null,
_matchedSelectors: null, _matchedSelectors: null,
domUtils: Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils),
/** /**
* Reset various properties * Reset various properties
*/ */
@ -470,8 +468,9 @@ CssLogic.prototype = {
rule.selectors.forEach(function (aSelector) { rule.selectors.forEach(function (aSelector) {
if (aSelector._matchId !== this._matchId && if (aSelector._matchId !== this._matchId &&
(aSelector.elementStyle || (aSelector.elementStyle ||
this._selectorMatchesElement(aSelector))) { this.selectorMatchesElement(rule._domRule, aSelector.selectorIndex))) {
aSelector._matchId = this._matchId; aSelector._matchId = this._matchId;
this._matchedSelectors.push([ aSelector, status ]); this._matchedSelectors.push([ aSelector, status ]);
if (aCallback) { if (aCallback) {
@ -489,15 +488,19 @@ CssLogic.prototype = {
* parents. * parents.
* *
* @private * @private
* @param {string} aSelector the selector string you want to check. * @param {DOMRule} domRule
* @return {boolean} true if the given selector matches the highlighted * The DOM Rule containing the selector.
* element or any of its parents, otherwise false is returned. * @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; let element = this.viewedElement;
do { do {
if (element.mozMatchesSelector(aSelector)) { if (domUtils.selectorMatchesElement(element, domRule, idx)) {
return true; return true;
} }
} while ((element = element.parentNode) && } while ((element = element.parentNode) &&
@ -531,7 +534,7 @@ CssLogic.prototype = {
if (rule.getPropertyValue(aProperty) && if (rule.getPropertyValue(aProperty) &&
(status == CssLogic.STATUS.MATCHED || (status == CssLogic.STATUS.MATCHED ||
(status == CssLogic.STATUS.PARENT_MATCH && (status == CssLogic.STATUS.PARENT_MATCH &&
this.domUtils.isInheritedProperty(aProperty)))) { domUtils.isInheritedProperty(aProperty)))) {
result[aProperty] = true; result[aProperty] = true;
return false; return false;
} }
@ -569,7 +572,7 @@ CssLogic.prototype = {
CssLogic.STATUS.MATCHED : CssLogic.STATUS.PARENT_MATCH; CssLogic.STATUS.MATCHED : CssLogic.STATUS.PARENT_MATCH;
try { try {
domRules = this.domUtils.getCSSStyleRules(element); domRules = domUtils.getCSSStyleRules(element);
} catch (ex) { } catch (ex) {
Services.console. Services.console.
logStringMessage("CL__buildMatchedRules error: " + ex); 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. * @param {DOMRule} aDOMRule
* @return {array} An array of string selectors. * 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 selectors = [];
let selector = aSelectorText.trim(); let len = domUtils.getSelectorCount(aDOMRule);
if (!selector) { for (let i = 0; i < len; i++) {
return selectors; 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; return selectors;
} }
@ -1171,7 +1131,7 @@ function CssRule(aCssSheet, aDomRule, aElement)
if (this._cssSheet) { if (this._cssSheet) {
// parse _domRule.selectorText on call to this.selectors // parse _domRule.selectorText on call to this.selectors
this._selectors = null; 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; this.source = this._cssSheet.shortSource + ":" + this.line;
if (this.mediaText) { if (this.mediaText) {
this.source += " @media " + this.mediaText; this.source += " @media " + this.mediaText;
@ -1179,7 +1139,7 @@ function CssRule(aCssSheet, aDomRule, aElement)
this.href = this._cssSheet.href; this.href = this._cssSheet.href;
this.contentRule = this._cssSheet.contentSheet; this.contentRule = this._cssSheet.contentSheet;
} else if (aElement) { } else if (aElement) {
this._selectors = [ new CssSelector(this, "@element.style") ]; this._selectors = [ new CssSelector(this, "@element.style", 0) ];
this.line = -1; this.line = -1;
this.source = CssLogic.l10n("rule.sourceElement"); this.source = CssLogic.l10n("rule.sourceElement");
this.href = "#"; this.href = "#";
@ -1263,8 +1223,12 @@ CssRule.prototype = {
return this._selectors; return this._selectors;
} }
let selectors = CssLogic.getSelectors(this._domRule.selectorText); let selectors = CssLogic.getSelectors(this._domRule);
this._selectors = [new CssSelector(this, text) for (text of selectors)];
for (let i = 0, len = selectors.length; i < len; i++) {
this._selectors.push(new CssSelector(this, selectors[i], i));
}
return this._selectors; return this._selectors;
}, },
@ -1281,13 +1245,15 @@ CssRule.prototype = {
* @constructor * @constructor
* @param {CssRule} aCssRule the CssRule instance from where the selector comes. * @param {CssRule} aCssRule the CssRule instance from where the selector comes.
* @param {string} aSelector The selector that we wish to investigate. * @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._cssRule = aCssRule;
this.text = aSelector; this.text = aSelector;
this.elementStyle = this.text == "@element.style"; this.elementStyle = this.text == "@element.style";
this._specificity = null; this._specificity = null;
this.selectorIndex = aIndex;
} }
CssSelector.prototype = { CssSelector.prototype = {
@ -1402,8 +1368,7 @@ CssSelector.prototype = {
* @see http://www.w3.org/TR/css3-selectors/#specificity * @see http://www.w3.org/TR/css3-selectors/#specificity
* @see http://www.w3.org/TR/CSS2/selector.html * @see http://www.w3.org/TR/CSS2/selector.html
* *
* @return {object} an object holding specificity information for the current * @return {Number} The selector's specificity.
* selector.
*/ */
get specificity() get specificity()
{ {
@ -1411,59 +1376,8 @@ CssSelector.prototype = {
return this._specificity; return this._specificity;
} }
let specificity = { this._specificity = domUtils.getSpecificity(this._cssRule._domRule,
ids: 0, this.selectorIndex);
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;
return this._specificity; return this._specificity;
}, },
@ -1610,7 +1524,7 @@ CssPropertyInfo.prototype = {
if (value && if (value &&
(aStatus == CssLogic.STATUS.MATCHED || (aStatus == CssLogic.STATUS.MATCHED ||
(aStatus == CssLogic.STATUS.PARENT_MATCH && (aStatus == CssLogic.STATUS.PARENT_MATCH &&
this._cssLogic.domUtils.isInheritedProperty(this.property)))) { domUtils.isInheritedProperty(this.property)))) {
let selectorInfo = new CssSelectorInfo(aSelector, this.property, value, let selectorInfo = new CssSelectorInfo(aSelector, this.property, value,
aStatus); aStatus);
this._matchedSelectors.push(selectorInfo); this._matchedSelectors.push(selectorInfo);
@ -1677,25 +1591,6 @@ function CssSelectorInfo(aSelector, aProperty, aValue, aStatus)
let priority = this.selector._cssRule.getPropertyPriority(this.property); let priority = this.selector._cssRule.getPropertyPriority(this.property);
this.important = (priority === "important"); 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 = { CssSelectorInfo.prototype = {
@ -1824,14 +1719,8 @@ CssSelectorInfo.prototype = {
if (this.important && !aThat.important) return -1; if (this.important && !aThat.important) return -1;
if (aThat.important && !this.important) return 1; if (aThat.important && !this.important) return 1;
if (this.specificity.ids > aThat.specificity.ids) return -1; if (this.specificity > aThat.specificity) return -1;
if (aThat.specificity.ids > this.specificity.ids) return 1; if (aThat.specificity > this.specificity) 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 (this.sheetIndex > aThat.sheetIndex) return -1;
if (aThat.sheetIndex > this.sheetIndex) return 1; if (aThat.sheetIndex > this.sheetIndex) return 1;
@ -1847,3 +1736,7 @@ CssSelectorInfo.prototype = {
return this.selector + " -> " + this.value; 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. // to figure out how shorthand properties will be parsed.
dummyElement: null, 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 * Called by the Rule object when it has been changed through the
* setProperty* methods. * setProperty* methods.
@ -158,7 +156,7 @@ ElementStyle.prototype = {
}); });
// Get the styles that apply to the element. // 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 // getCSStyleRules returns ordered from least-specific to
// most-specific. // most-specific.
@ -413,7 +411,7 @@ Rule.prototype = {
// No stylesheet, no ruleLine // No stylesheet, no ruleLine
return null; return null;
} }
return this.elementStyle.domUtils.getRuleLine(this.domRule); return domUtils.getRuleLine(this.domRule);
}, },
/** /**
@ -576,8 +574,7 @@ Rule.prototype = {
continue; continue;
let name = matches[1]; let name = matches[1];
if (this.inherited && if (this.inherited && !domUtils.isInheritedProperty(name)) {
!this.elementStyle.domUtils.isInheritedProperty(name)) {
continue; continue;
} }
let value = store.userProperties.getProperty(this.style, name, matches[2]); 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' // actually match. For custom selector text (such as for the 'element'
// style, just show the text directly. // style, just show the text directly.
if (this.rule.domRule && this.rule.domRule.selectorText) { 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; let element = this.rule.inherited || this.ruleView._viewedElement;
for (let i = 0; i < selectors.length; i++) { for (let i = 0; i < selectors.length; i++) {
let selector = selectors[i]; let selector = selectors[i];
@ -1458,8 +1455,12 @@ RuleEditor.prototype = {
textContent: ", " textContent: ", "
}); });
} }
let cls = element.mozMatchesSelector(selector) ? "ruleview-selector-matched" : let cls;
"ruleview-selector-unmatched"; if (domUtils.selectorMatchesElement(element, this.rule.domRule, i)) {
cls = "ruleview-selector-matched";
} else {
cls = "ruleview-selector-unmatched";
}
createChild(this.selectorText, "span", { createChild(this.selectorText, "span", {
class: cls, class: cls,
textContent: selector textContent: selector
@ -2847,3 +2848,7 @@ XPCOMUtils.defineLazyGetter(this, "_strings", function() {
XPCOMUtils.defineLazyGetter(this, "osString", function() { XPCOMUtils.defineLazyGetter(this, "osString", function() {
return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS; 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 CssLogic = tempScope.CssLogic;
let CssSelector = tempScope.CssSelector; let CssSelector = tempScope.CssSelector;
function test() function createDocument()
{ {
let tests = [ let doc = content.document;
{text: "*", expected: "000"}, doc.body.innerHTML = getStylesheetText();
{text: "LI", expected: "001"}, doc.title = "Computed view specificity test";
{text: "UL LI", expected: "002"}, runTests(doc);
{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"},
];
tests.forEach(function(aTest) { function runTests(doc) {
let selector = new CssSelector(null, aTest.text); let cssLogic = new CssLogic();
let specificity = selector.specificity; cssLogic.highlight(doc.body);
let result = "" + specificity.ids + specificity.classes + specificity.tags; let tests = getTests();
is(result, aTest.expected, "selector \"" + aTest.text + let cssSheet = cssLogic.sheets[0];
"\" produces expected result"); 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(); 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() function finishUp()
{ {
CssLogic = CssSelector = null; CssLogic = CssSelector = tempScope = null;
finish(); 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";
}