Bug 685902 - Improve csshtmltree speeds by adding a has[Un]matchedSelectors() method. r=msucan

This commit is contained in:
Michael Ratcliffe 2011-10-13 11:28:52 +02:00
parent 6639d315af
commit e52511a7b2
6 changed files with 222 additions and 101 deletions

View File

@ -284,7 +284,6 @@ CssHtmlTree.prototype = {
this.cssLogic.sourceFilter = this.showOnlyUserStyles ?
CssLogic.FILTER.ALL :
CssLogic.FILTER.UA;
this.refreshPanel();
},
@ -443,12 +442,28 @@ PropertyView.prototype = {
return this.tree.cssLogic.getPropertyInfo(this.name);
},
/**
* Does the property have any matched selectors?
*/
get hasMatchedSelectors()
{
return this.propertyInfo.hasMatchedSelectors();
},
/**
* Does the property have any unmatched selectors?
*/
get hasUnmatchedSelectors()
{
return this.propertyInfo.hasUnmatchedSelectors();
},
/**
* Should this property be visible?
*/
get visible()
{
if (this.tree.showOnlyUserStyles && this.matchedSelectorCount == 0) {
if (this.tree.showOnlyUserStyles && !this.hasMatchedSelectors) {
return false;
}
@ -469,22 +484,6 @@ PropertyView.prototype = {
return this.visible ? "property-view" : "property-view-hidden";
},
/**
* The number of matched selectors.
*/
get matchedSelectorCount()
{
return this.propertyInfo.matchedSelectors.length;
},
/**
* The number of unmatched selectors.
*/
get unmatchedSelectorCount()
{
return this.propertyInfo.unmatchedSelectors.length;
},
/**
* Refresh the panel's CSS property value.
*/
@ -520,10 +519,10 @@ PropertyView.prototype = {
*/
refreshMatchedSelectors: function PropertyView_refreshMatchedSelectors()
{
this.matchedSelectorsTitleNode.innerHTML = this.matchedSelectorTitle();
this.matchedSelectorsContainer.hidden = this.matchedSelectorCount == 0;
let hasMatchedSelectors = this.hasMatchedSelectors;
this.matchedSelectorsContainer.hidden = !hasMatchedSelectors;
if (this.matchedExpanded && this.matchedSelectorCount > 0) {
if (this.matchedExpanded && hasMatchedSelectors) {
CssHtmlTree.processTemplate(this.templateMatchedSelectors,
this.matchedSelectorTable, this);
this.matchedExpander.setAttribute("open", "");
@ -536,11 +535,12 @@ PropertyView.prototype = {
/**
* Refresh the panel unmatched rules.
*/
refreshUnmatchedSelectors: function PropertyView_refreshUnmatchedSelectors() {
this.unmatchedSelectorsTitleNode.innerHTML = this.unmatchedSelectorTitle();
this.unmatchedSelectorsContainer.hidden = this.unmatchedSelectorCount == 0;
refreshUnmatchedSelectors: function PropertyView_refreshUnmatchedSelectors()
{
let hasUnmatchedSelectors = this.hasUnmatchedSelectors;
this.unmatchedSelectorsContainer.hidden = !hasUnmatchedSelectors;
if (this.unmatchedExpanded && this.unmatchedSelectorCount > 0) {
if (this.unmatchedExpanded && hasUnmatchedSelectors) {
CssHtmlTree.processTemplate(this.templateUnmatchedSelectors,
this.unmatchedSelectorTable, this);
this.unmatchedExpander.setAttribute("open", "");
@ -550,42 +550,6 @@ PropertyView.prototype = {
}
},
/**
* Compute the title of the matched selector expander. The title includes the
* number of selectors that match the currently selected element.
*
* @return {string} The rule title.
*/
matchedSelectorTitle: function PropertyView_matchedSelectorTitle()
{
let result = "";
if (this.matchedSelectorCount > 0) {
let str = CssHtmlTree.l10n("property.numberOfMatchedSelectors");
result = PluralForm.get(this.matchedSelectorCount, str)
.replace("#1", this.matchedSelectorCount);
}
return result;
},
/**
* Compute the title of the unmatched selector expander. The title includes
* the number of selectors that match the currently selected element.
*
* @return {string} The rule title.
*/
unmatchedSelectorTitle: function PropertyView_unmatchedSelectorTitle()
{
let result = "";
if (this.unmatchedSelectorCount > 0) {
let str = CssHtmlTree.l10n("property.numberOfUnmatchedSelectors");
result = PluralForm.get(this.unmatchedSelectorCount, str)
.replace("#1", this.unmatchedSelectorCount);
}
return result;
},
/**
* Provide access to the matched SelectorViews that we are currently
* displaying.

View File

@ -216,7 +216,8 @@ CssLogic.prototype = {
/**
* Source filter. Only display properties coming from the given source (web
* address).
* address). Note that in order to avoid information overload we DO NOT show
* unmatched system rules.
* @see CssLogic.FILTER.*
*/
set sourceFilter(aValue) {
@ -415,6 +416,27 @@ CssLogic.prototype = {
}
},
/**
* Process *some* cached stylesheets in the document using your callback. The
* callback function should return true in order to halt processing.
*
* @param {function} aCallback the function you want executed for some of the
* CssSheet objects cached.
* @param {object} aScope the scope you want for the callback function. aScope
* will be the this object when aCallback executes.
* @return {Boolean} true if aCallback returns true during any iteration,
* otherwise false is returned.
*/
forSomeSheets: function CssLogic_forSomeSheets(aCallback, aScope)
{
for each (let sheets in this._sheets) {
if (sheets.some(aCallback, aScope)) {
return true;
}
}
return false;
},
/**
* Get the number nsIDOMCSSRule objects in the document, counted from all of
* the stylesheets. System sheets are excluded. If a filter is active, this
@ -554,7 +576,6 @@ CssLogic.prototype = {
if (!this._matchedSelectors) {
this.processMatchedSelectors();
}
if (this._unmatchedSelectors) {
if (aCallback) {
this._unmatchedSelectors.forEach(aCallback, aScope);
@ -565,6 +586,7 @@ CssLogic.prototype = {
this._unmatchedSelectors = [];
this.forEachSheet(function (aSheet) {
// We do not show unmatched selectors from system stylesheets
if (aSheet.systemSheet) {
return;
}
@ -581,6 +603,79 @@ CssLogic.prototype = {
}, this);
}, this);
},
/**
* Check if the highlighted element or it's parents have matched selectors.
* If aCallback is provided then the domRules for the element are passed to
* the callback function.
*
* @param {function} [aCallback] Simple callback method
* @return {Boolean} true if the current element or it's parents have
* matching CssSelector objects, false otherwise
*/
hasMatchedSelectors: function CL_hasMatchedSelectors(aCallback)
{
let domRules;
let element = this.viewedElement;
let matched = false;
do {
try {
domRules = this.domUtils.getCSSStyleRules(element);
} catch (ex) {
Services.console.
logStringMessage("CssLogic_hasMatchedSelectors error: " + ex);
continue;
}
if (domRules.Count() && (!aCallback || aCallback(domRules))) {
matched = true;
break;
}
} while ((element = element.parentNode) &&
element.nodeType === Ci.nsIDOMNode.ELEMENT_NODE);
return matched;
},
/**
* Check if the highlighted element or it's parents have unmatched selectors.
*
* @param {String} aProperty The CSS property to check against
* @return {Boolean} true if the current element or it's parents have
* unmatched CssSelector objects, false otherwise
*/
hasUnmatchedSelectors: function CL_hasUnmatchedSelectors(aProperty)
{
return this.forSomeSheets(function (aSheet) {
// We do not show unmatched selectors from system stylesheets
if (aSheet.systemSheet) {
return false;
}
return aSheet.forSomeRules(function (aRule) {
if (aRule.getPropertyValue(aProperty)) {
let element = this.viewedElement;
let selectorText = aRule._domRule.selectorText;
let matches = false;
do {
if (element.mozMatchesSelector(selectorText)) {
matches = true;
break;
}
} while ((element = element.parentNode) &&
element.nodeType === Ci.nsIDOMNode.ELEMENT_NODE);
if (!matches) {
// Now we know that there are rules but none match.
return true;
}
}
}, this);
}, this);
},
};
/**
@ -914,6 +1009,36 @@ CssSheet.prototype = {
this._ruleCount = ruleCount;
},
/**
* Process *some* rules in this stylesheet using your callback function. Your
* function receives one argument: the CssRule object for each CSSStyleRule
* inside the stylesheet. In order to stop processing the callback function
* needs to return a value.
*
* Note that this method also iterates through @media rules inside the
* stylesheet.
*
* @param {function} aCallback the function you want to execute for each of
* the style rules.
* @param {object} aScope the scope you want for the callback function. aScope
* will be the this object when aCallback executes.
* @return {Boolean} true if aCallback returns true during any iteration,
* otherwise false is returned.
*/
forSomeRules: function CssSheet_forSomeRules(aCallback, aScope)
{
let domRules = this.domSheet.cssRules;
function _iterator(aDomRule) {
if (aDomRule.type == Ci.nsIDOMCSSRule.STYLE_RULE) {
return aCallback.call(aScope, this.getRule(aDomRule));
} else if (aDomRule.type == Ci.nsIDOMCSSRule.MEDIA_RULE &&
aDomRule.cssRules && CssLogic.sheetMediaAllowed(aDomRule)) {
return Array.prototype.some.call(aDomRule.cssRules, _iterator, this);
}
}
return Array.prototype.some.call(domRules, _iterator, this);
},
toString: function CssSheet_toString()
{
return "CssSheet[" + this.shortSource + "]";
@ -1264,6 +1389,9 @@ function CssPropertyInfo(aCssLogic, aProperty)
// counted. This includes rules that come from filtered stylesheets (those
// that have sheetAllowed = false).
this._matchedSelectors = null;
this._unmatchedSelectors = null;
this._hasMatchedSelectors = null;
this._hasUnmatchedSelectors = null;
}
CssPropertyInfo.prototype = {
@ -1361,6 +1489,55 @@ CssPropertyInfo.prototype = {
return this._unmatchedSelectors;
},
/**
* Check if the property has any matched selectors.
*
* @return {Boolean} true if the current element or it's parents have
* matching CssSelector objects, false otherwise
*/
hasMatchedSelectors: function CssPropertyInfo_hasMatchedSelectors()
{
if (this._hasMatchedSelectors === null) {
this._hasMatchedSelectors = this._cssLogic.hasMatchedSelectors(function(aDomRules) {
for (let i = 0; i < aDomRules.Count(); i++) {
let domRule = aDomRules.GetElementAt(i);
if (domRule.type !== Ci.nsIDOMCSSRule.STYLE_RULE) {
continue;
}
let domSheet = domRule.parentStyleSheet;
let systemSheet = CssLogic.isSystemStyleSheet(domSheet);
let filter = this._cssLogic.sourceFilter;
if (filter !== CssLogic.FILTER.UA && systemSheet) {
continue;
}
if (domRule.style.getPropertyValue(this.property)) {
return true;
}
}
return false;
}.bind(this));
}
return this._hasMatchedSelectors;
},
/**
* Check if the property has any matched selectors.
*
* @return {Boolean} true if the current element or it's parents have
* unmatched CssSelector objects, false otherwise
*/
hasUnmatchedSelectors: function CssPropertyInfo_hasUnmatchedSelectors()
{
if (this._hasUnmatchedSelectors === null) {
this._hasUnmatchedSelectors = this._cssLogic.hasUnmatchedSelectors(this.property);
}
return this._hasUnmatchedSelectors;
},
/**
* Find the selectors that match the highlighted element and its parents.
* Uses CssLogic.processMatchedSelectors() to find the matched selectors,
@ -1437,7 +1614,8 @@ CssPropertyInfo.prototype = {
},
/**
* Process an unmatched CssSelector object.
* Process an unmatched CssSelector object. Note that in order to avoid
* information overload we DO NOT show unmatched system rules.
*
* @private
* @param {CssSelector} aSelector the unmatched CssSelector object.

View File

@ -130,9 +130,7 @@ To visually debug the templates without running firefox, alter the display:none
<div save="${matchedSelectorsContainer}" class="rulelink" dir="${getRTLAttr}">
<div onclick="${matchedSelectorsClick}" class="rule-matched">
<div save="${matchedExpander}" class="expander"></div>
<div save="${matchedSelectorsTitleNode}">
${matchedSelectorTitle(__element)}
</div>
<div save="${matchedSelectorsTitleNode}">&matchedSelectors;</div>
</div>
<table save="${matchedSelectorTable}" dir="${getRTLAttr}"></table>
</div>
@ -140,9 +138,7 @@ To visually debug the templates without running firefox, alter the display:none
<div save="${unmatchedSelectorsContainer}" class="rulelink" dir="${getRTLAttr}">
<div onclick="${unmatchedSelectorsClick}" class="rule-unmatched">
<div save="${unmatchedExpander}" class="expander"></div>
<div save="${unmatchedSelectorsTitleNode}">
${unmatchedSelectorTitle(__element)}
</div>
<div save="${unmatchedSelectorsTitleNode}">&unmatchedSelectors;</div>
</div>
<table save="${unmatchedSelectorTable}" dir="${getRTLAttr}"></table>
</div>

View File

@ -62,15 +62,8 @@ function testMatchedSelectors()
is(numMatchedSelectors, 6,
"CssLogic returns the correct number of matched selectors for div");
let returnedSelectorTitle = propertyView.matchedSelectorTitle();
let str = CssHtmlTree.l10n("property.numberOfMatchedSelectors");
let calculatedSelectorTitle = PluralForm.get(numMatchedSelectors, str)
.replace("#1", numMatchedSelectors);
info("returnedSelectorTitle: '" + returnedSelectorTitle + "'");
is(returnedSelectorTitle, calculatedSelectorTitle,
"returned title for matched selectors is correct");
is(propertyView.propertyInfo.hasMatchedSelectors(), true,
"hasMatchedSelectors returns true");
}
function testUnmatchedSelectors()
@ -93,15 +86,8 @@ function testUnmatchedSelectors()
is(numUnmatchedSelectors, 13,
"CssLogic returns the correct number of unmatched selectors for body");
let returnedSelectorTitle = propertyView.unmatchedSelectorTitle();
let str = CssHtmlTree.l10n("property.numberOfUnmatchedSelectors");
let calculatedSelectorTitle = PluralForm.get(numUnmatchedSelectors, str)
.replace("#1", numUnmatchedSelectors);
info("returnedSelectorTitle: '" + returnedSelectorTitle + "'");
is(returnedSelectorTitle, calculatedSelectorTitle,
"returned title for unmatched selectors is correct");
is(propertyView.propertyInfo.hasUnmatchedSelectors(), true,
"hasUnmatchedSelectors returns true");
}
function finishUp()

View File

@ -13,3 +13,13 @@
- quickly jump to the documentation from the Mozilla Developer Network site.
- This is the link title shown in the hover tooltip. -->
<!ENTITY helpLinkTitle "Read the documentation for this property">
<!-- LOCALIZATION NOTE (matchedSelectors): For each style property the
- panel shows whether there are any selectors that match the currently
- selected element. -->
<!ENTITY matchedSelectors "Matched selectors">
<!-- LOCALIZATION NOTE (unmatchedSelectors): For each style property
- the panel shows whether there are any selectors that do not match the
- currently selected element. -->
<!ENTITY unmatchedSelectors "Unmatched selectors">

View File

@ -3,19 +3,6 @@
# LOCALIZATION NOTE (panelTitle): This is the panel title
panelTitle=Style Inspector
# LOCALIZATION NOTE (property.numberOfMatchedSelectors): For each style property the
# panel shows the number of selectors which match the currently selected
# element, counted from all stylesheets in the web page inspected.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
property.numberOfMatchedSelectors=1 matched selector;#1 matched selectors
# LOCALIZATION NOTE (property.numberOfUnmatchedSelectors): For each style
# property the panel shows the number of selectors which do not match the
# currently selected element, counted from all stylesheets in the web page
# inspected.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
property.numberOfUnmatchedSelectors=1 unmatched selector;#1 unmatched selectors
# LOCALIZATION NOTE (rule.status): For each style property the panel shows
# the rules which hold that specific property. For every rule, the rule status
# is also displayed: a rule can be the best match, a match, a parent match, or a