diff --git a/browser/devtools/styleinspector/CssHtmlTree.jsm b/browser/devtools/styleinspector/CssHtmlTree.jsm index fb08ae27965e..1e831da6207e 100644 --- a/browser/devtools/styleinspector/CssHtmlTree.jsm +++ b/browser/devtools/styleinspector/CssHtmlTree.jsm @@ -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. diff --git a/browser/devtools/styleinspector/CssLogic.jsm b/browser/devtools/styleinspector/CssLogic.jsm index 0a5370885b3e..6856d60b8994 100644 --- a/browser/devtools/styleinspector/CssLogic.jsm +++ b/browser/devtools/styleinspector/CssLogic.jsm @@ -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. diff --git a/browser/devtools/styleinspector/csshtmltree.xhtml b/browser/devtools/styleinspector/csshtmltree.xhtml index 39b573e421ca..d8d019069c91 100644 --- a/browser/devtools/styleinspector/csshtmltree.xhtml +++ b/browser/devtools/styleinspector/csshtmltree.xhtml @@ -130,9 +130,7 @@ To visually debug the templates without running firefox, alter the display:none