1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/devtools/styleinspector/css-logic.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1803 @@ 1.4 +/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +/* 1.11 + * About the objects defined in this file: 1.12 + * - CssLogic contains style information about a view context. It provides 1.13 + * access to 2 sets of objects: Css[Sheet|Rule|Selector] provide access to 1.14 + * information that does not change when the selected element changes while 1.15 + * Css[Property|Selector]Info provide information that is dependent on the 1.16 + * selected element. 1.17 + * Its key methods are highlight(), getPropertyInfo() and forEachSheet(), etc 1.18 + * It also contains a number of static methods for l10n, naming, etc 1.19 + * 1.20 + * - CssSheet provides a more useful API to a DOM CSSSheet for our purposes, 1.21 + * including shortSource and href. 1.22 + * - CssRule a more useful API to a nsIDOMCSSRule including access to the group 1.23 + * of CssSelectors that the rule provides properties for 1.24 + * - CssSelector A single selector - i.e. not a selector group. In other words 1.25 + * a CssSelector does not contain ','. This terminology is different from the 1.26 + * standard DOM API, but more inline with the definition in the spec. 1.27 + * 1.28 + * - CssPropertyInfo contains style information for a single property for the 1.29 + * highlighted element. 1.30 + * - CssSelectorInfo is a wrapper around CssSelector, which adds sorting with 1.31 + * reference to the selected element. 1.32 + */ 1.33 + 1.34 +/** 1.35 + * Provide access to the style information in a page. 1.36 + * CssLogic uses the standard DOM API, and the Gecko inIDOMUtils API to access 1.37 + * styling information in the page, and present this to the user in a way that 1.38 + * helps them understand: 1.39 + * - why their expectations may not have been fulfilled 1.40 + * - how browsers process CSS 1.41 + * @constructor 1.42 + */ 1.43 + 1.44 +const {Cc, Ci, Cu} = require("chrome"); 1.45 + 1.46 +const RX_UNIVERSAL_SELECTOR = /\s*\*\s*/g; 1.47 +const RX_NOT = /:not\((.*?)\)/g; 1.48 +const RX_PSEUDO_CLASS_OR_ELT = /(:[\w-]+\().*?\)/g; 1.49 +const RX_CONNECTORS = /\s*[\s>+~]\s*/g; 1.50 +const RX_ID = /\s*#\w+\s*/g; 1.51 +const RX_CLASS_OR_ATTRIBUTE = /\s*(?:\.\w+|\[.+?\])\s*/g; 1.52 +const RX_PSEUDO = /\s*:?:([\w-]+)(\(?\)?)\s*/g; 1.53 + 1.54 +Cu.import("resource://gre/modules/Services.jsm"); 1.55 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.56 + 1.57 +function CssLogic() 1.58 +{ 1.59 + // The cache of examined CSS properties. 1.60 + _propertyInfos: {}; 1.61 +} 1.62 + 1.63 +exports.CssLogic = CssLogic; 1.64 + 1.65 +/** 1.66 + * Special values for filter, in addition to an href these values can be used 1.67 + */ 1.68 +CssLogic.FILTER = { 1.69 + USER: "user", // show properties for all user style sheets. 1.70 + UA: "ua", // USER, plus user-agent (i.e. browser) style sheets 1.71 +}; 1.72 + 1.73 +/** 1.74 + * Known media values. To distinguish "all" stylesheets (above) from "all" media 1.75 + * The full list includes braille, embossed, handheld, print, projection, 1.76 + * speech, tty, and tv, but this is only a hack because these are not defined 1.77 + * in the DOM at all. 1.78 + * @see http://www.w3.org/TR/CSS21/media.html#media-types 1.79 + */ 1.80 +CssLogic.MEDIA = { 1.81 + ALL: "all", 1.82 + SCREEN: "screen", 1.83 +}; 1.84 + 1.85 +/** 1.86 + * Each rule has a status, the bigger the number, the better placed it is to 1.87 + * provide styling information. 1.88 + * 1.89 + * These statuses are localized inside the styleinspector.properties string bundle. 1.90 + * @see csshtmltree.js RuleView._cacheStatusNames() 1.91 + */ 1.92 +CssLogic.STATUS = { 1.93 + BEST: 3, 1.94 + MATCHED: 2, 1.95 + PARENT_MATCH: 1, 1.96 + UNMATCHED: 0, 1.97 + UNKNOWN: -1, 1.98 +}; 1.99 + 1.100 +CssLogic.prototype = { 1.101 + // Both setup by highlight(). 1.102 + viewedElement: null, 1.103 + viewedDocument: null, 1.104 + 1.105 + // The cache of the known sheets. 1.106 + _sheets: null, 1.107 + 1.108 + // Have the sheets been cached? 1.109 + _sheetsCached: false, 1.110 + 1.111 + // The total number of rules, in all stylesheets, after filtering. 1.112 + _ruleCount: 0, 1.113 + 1.114 + // The computed styles for the viewedElement. 1.115 + _computedStyle: null, 1.116 + 1.117 + // Source filter. Only display properties coming from the given source 1.118 + _sourceFilter: CssLogic.FILTER.USER, 1.119 + 1.120 + // Used for tracking unique CssSheet/CssRule/CssSelector objects, in a run of 1.121 + // processMatchedSelectors(). 1.122 + _passId: 0, 1.123 + 1.124 + // Used for tracking matched CssSelector objects. 1.125 + _matchId: 0, 1.126 + 1.127 + _matchedRules: null, 1.128 + _matchedSelectors: null, 1.129 + 1.130 + /** 1.131 + * Reset various properties 1.132 + */ 1.133 + reset: function CssLogic_reset() 1.134 + { 1.135 + this._propertyInfos = {}; 1.136 + this._ruleCount = 0; 1.137 + this._sheetIndex = 0; 1.138 + this._sheets = {}; 1.139 + this._sheetsCached = false; 1.140 + this._matchedRules = null; 1.141 + this._matchedSelectors = null; 1.142 + }, 1.143 + 1.144 + /** 1.145 + * Focus on a new element - remove the style caches. 1.146 + * 1.147 + * @param {nsIDOMElement} aViewedElement the element the user has highlighted 1.148 + * in the Inspector. 1.149 + */ 1.150 + highlight: function CssLogic_highlight(aViewedElement) 1.151 + { 1.152 + if (!aViewedElement) { 1.153 + this.viewedElement = null; 1.154 + this.viewedDocument = null; 1.155 + this._computedStyle = null; 1.156 + this.reset(); 1.157 + return; 1.158 + } 1.159 + 1.160 + this.viewedElement = aViewedElement; 1.161 + 1.162 + let doc = this.viewedElement.ownerDocument; 1.163 + if (doc != this.viewedDocument) { 1.164 + // New document: clear/rebuild the cache. 1.165 + this.viewedDocument = doc; 1.166 + 1.167 + // Hunt down top level stylesheets, and cache them. 1.168 + this._cacheSheets(); 1.169 + } else { 1.170 + // Clear cached data in the CssPropertyInfo objects. 1.171 + this._propertyInfos = {}; 1.172 + } 1.173 + 1.174 + this._matchedRules = null; 1.175 + this._matchedSelectors = null; 1.176 + let win = this.viewedDocument.defaultView; 1.177 + this._computedStyle = win.getComputedStyle(this.viewedElement, ""); 1.178 + }, 1.179 + 1.180 + /** 1.181 + * Get the source filter. 1.182 + * @returns {string} The source filter being used. 1.183 + */ 1.184 + get sourceFilter() { 1.185 + return this._sourceFilter; 1.186 + }, 1.187 + 1.188 + /** 1.189 + * Source filter. Only display properties coming from the given source (web 1.190 + * address). Note that in order to avoid information overload we DO NOT show 1.191 + * unmatched system rules. 1.192 + * @see CssLogic.FILTER.* 1.193 + */ 1.194 + set sourceFilter(aValue) { 1.195 + let oldValue = this._sourceFilter; 1.196 + this._sourceFilter = aValue; 1.197 + 1.198 + let ruleCount = 0; 1.199 + 1.200 + // Update the CssSheet objects. 1.201 + this.forEachSheet(function(aSheet) { 1.202 + aSheet._sheetAllowed = -1; 1.203 + if (aSheet.contentSheet && aSheet.sheetAllowed) { 1.204 + ruleCount += aSheet.ruleCount; 1.205 + } 1.206 + }, this); 1.207 + 1.208 + this._ruleCount = ruleCount; 1.209 + 1.210 + // Full update is needed because the this.processMatchedSelectors() method 1.211 + // skips UA stylesheets if the filter does not allow such sheets. 1.212 + let needFullUpdate = (oldValue == CssLogic.FILTER.UA || 1.213 + aValue == CssLogic.FILTER.UA); 1.214 + 1.215 + if (needFullUpdate) { 1.216 + this._matchedRules = null; 1.217 + this._matchedSelectors = null; 1.218 + this._propertyInfos = {}; 1.219 + } else { 1.220 + // Update the CssPropertyInfo objects. 1.221 + for each (let propertyInfo in this._propertyInfos) { 1.222 + propertyInfo.needRefilter = true; 1.223 + } 1.224 + } 1.225 + }, 1.226 + 1.227 + /** 1.228 + * Return a CssPropertyInfo data structure for the currently viewed element 1.229 + * and the specified CSS property. If there is no currently viewed element we 1.230 + * return an empty object. 1.231 + * 1.232 + * @param {string} aProperty The CSS property to look for. 1.233 + * @return {CssPropertyInfo} a CssPropertyInfo structure for the given 1.234 + * property. 1.235 + */ 1.236 + getPropertyInfo: function CssLogic_getPropertyInfo(aProperty) 1.237 + { 1.238 + if (!this.viewedElement) { 1.239 + return {}; 1.240 + } 1.241 + 1.242 + let info = this._propertyInfos[aProperty]; 1.243 + if (!info) { 1.244 + info = new CssPropertyInfo(this, aProperty); 1.245 + this._propertyInfos[aProperty] = info; 1.246 + } 1.247 + 1.248 + return info; 1.249 + }, 1.250 + 1.251 + /** 1.252 + * Cache all the stylesheets in the inspected document 1.253 + * @private 1.254 + */ 1.255 + _cacheSheets: function CssLogic_cacheSheets() 1.256 + { 1.257 + this._passId++; 1.258 + this.reset(); 1.259 + 1.260 + // styleSheets isn't an array, but forEach can work on it anyway 1.261 + Array.prototype.forEach.call(this.viewedDocument.styleSheets, 1.262 + this._cacheSheet, this); 1.263 + 1.264 + this._sheetsCached = true; 1.265 + }, 1.266 + 1.267 + /** 1.268 + * Cache a stylesheet if it falls within the requirements: if it's enabled, 1.269 + * and if the @media is allowed. This method also walks through the stylesheet 1.270 + * cssRules to find @imported rules, to cache the stylesheets of those rules 1.271 + * as well. 1.272 + * 1.273 + * @private 1.274 + * @param {CSSStyleSheet} aDomSheet the CSSStyleSheet object to cache. 1.275 + */ 1.276 + _cacheSheet: function CssLogic_cacheSheet(aDomSheet) 1.277 + { 1.278 + if (aDomSheet.disabled) { 1.279 + return; 1.280 + } 1.281 + 1.282 + // Only work with stylesheets that have their media allowed. 1.283 + if (!this.mediaMatches(aDomSheet)) { 1.284 + return; 1.285 + } 1.286 + 1.287 + // Cache the sheet. 1.288 + let cssSheet = this.getSheet(aDomSheet, this._sheetIndex++); 1.289 + if (cssSheet._passId != this._passId) { 1.290 + cssSheet._passId = this._passId; 1.291 + 1.292 + // Find import rules. 1.293 + Array.prototype.forEach.call(aDomSheet.cssRules, function(aDomRule) { 1.294 + if (aDomRule.type == Ci.nsIDOMCSSRule.IMPORT_RULE && aDomRule.styleSheet && 1.295 + this.mediaMatches(aDomRule)) { 1.296 + this._cacheSheet(aDomRule.styleSheet); 1.297 + } 1.298 + }, this); 1.299 + } 1.300 + }, 1.301 + 1.302 + /** 1.303 + * Retrieve the list of stylesheets in the document. 1.304 + * 1.305 + * @return {array} the list of stylesheets in the document. 1.306 + */ 1.307 + get sheets() 1.308 + { 1.309 + if (!this._sheetsCached) { 1.310 + this._cacheSheets(); 1.311 + } 1.312 + 1.313 + let sheets = []; 1.314 + this.forEachSheet(function (aSheet) { 1.315 + if (aSheet.contentSheet) { 1.316 + sheets.push(aSheet); 1.317 + } 1.318 + }, this); 1.319 + 1.320 + return sheets; 1.321 + }, 1.322 + 1.323 + /** 1.324 + * Retrieve a CssSheet object for a given a CSSStyleSheet object. If the 1.325 + * stylesheet is already cached, you get the existing CssSheet object, 1.326 + * otherwise the new CSSStyleSheet object is cached. 1.327 + * 1.328 + * @param {CSSStyleSheet} aDomSheet the CSSStyleSheet object you want. 1.329 + * @param {number} aIndex the index, within the document, of the stylesheet. 1.330 + * 1.331 + * @return {CssSheet} the CssSheet object for the given CSSStyleSheet object. 1.332 + */ 1.333 + getSheet: function CL_getSheet(aDomSheet, aIndex) 1.334 + { 1.335 + let cacheId = ""; 1.336 + 1.337 + if (aDomSheet.href) { 1.338 + cacheId = aDomSheet.href; 1.339 + } else if (aDomSheet.ownerNode && aDomSheet.ownerNode.ownerDocument) { 1.340 + cacheId = aDomSheet.ownerNode.ownerDocument.location; 1.341 + } 1.342 + 1.343 + let sheet = null; 1.344 + let sheetFound = false; 1.345 + 1.346 + if (cacheId in this._sheets) { 1.347 + for (let i = 0, numSheets = this._sheets[cacheId].length; i < numSheets; i++) { 1.348 + sheet = this._sheets[cacheId][i]; 1.349 + if (sheet.domSheet === aDomSheet) { 1.350 + if (aIndex != -1) { 1.351 + sheet.index = aIndex; 1.352 + } 1.353 + sheetFound = true; 1.354 + break; 1.355 + } 1.356 + } 1.357 + } 1.358 + 1.359 + if (!sheetFound) { 1.360 + if (!(cacheId in this._sheets)) { 1.361 + this._sheets[cacheId] = []; 1.362 + } 1.363 + 1.364 + sheet = new CssSheet(this, aDomSheet, aIndex); 1.365 + if (sheet.sheetAllowed && sheet.contentSheet) { 1.366 + this._ruleCount += sheet.ruleCount; 1.367 + } 1.368 + 1.369 + this._sheets[cacheId].push(sheet); 1.370 + } 1.371 + 1.372 + return sheet; 1.373 + }, 1.374 + 1.375 + /** 1.376 + * Process each cached stylesheet in the document using your callback. 1.377 + * 1.378 + * @param {function} aCallback the function you want executed for each of the 1.379 + * CssSheet objects cached. 1.380 + * @param {object} aScope the scope you want for the callback function. aScope 1.381 + * will be the this object when aCallback executes. 1.382 + */ 1.383 + forEachSheet: function CssLogic_forEachSheet(aCallback, aScope) 1.384 + { 1.385 + for each (let sheets in this._sheets) { 1.386 + for (let i = 0; i < sheets.length; i ++) { 1.387 + // We take this as an opportunity to clean dead sheets 1.388 + try { 1.389 + let sheet = sheets[i]; 1.390 + sheet.domSheet; // If accessing domSheet raises an exception, then the 1.391 + // style sheet is a dead object 1.392 + aCallback.call(aScope, sheet, i, sheets); 1.393 + } catch (e) { 1.394 + sheets.splice(i, 1); 1.395 + i --; 1.396 + } 1.397 + } 1.398 + } 1.399 + }, 1.400 + 1.401 + /** 1.402 + * Process *some* cached stylesheets in the document using your callback. The 1.403 + * callback function should return true in order to halt processing. 1.404 + * 1.405 + * @param {function} aCallback the function you want executed for some of the 1.406 + * CssSheet objects cached. 1.407 + * @param {object} aScope the scope you want for the callback function. aScope 1.408 + * will be the this object when aCallback executes. 1.409 + * @return {Boolean} true if aCallback returns true during any iteration, 1.410 + * otherwise false is returned. 1.411 + */ 1.412 + forSomeSheets: function CssLogic_forSomeSheets(aCallback, aScope) 1.413 + { 1.414 + for each (let sheets in this._sheets) { 1.415 + if (sheets.some(aCallback, aScope)) { 1.416 + return true; 1.417 + } 1.418 + } 1.419 + return false; 1.420 + }, 1.421 + 1.422 + /** 1.423 + * Get the number nsIDOMCSSRule objects in the document, counted from all of 1.424 + * the stylesheets. System sheets are excluded. If a filter is active, this 1.425 + * tells only the number of nsIDOMCSSRule objects inside the selected 1.426 + * CSSStyleSheet. 1.427 + * 1.428 + * WARNING: This only provides an estimate of the rule count, and the results 1.429 + * could change at a later date. Todo remove this 1.430 + * 1.431 + * @return {number} the number of nsIDOMCSSRule (all rules). 1.432 + */ 1.433 + get ruleCount() 1.434 + { 1.435 + if (!this._sheetsCached) { 1.436 + this._cacheSheets(); 1.437 + } 1.438 + 1.439 + return this._ruleCount; 1.440 + }, 1.441 + 1.442 + /** 1.443 + * Process the CssSelector objects that match the highlighted element and its 1.444 + * parent elements. aScope.aCallback() is executed for each CssSelector 1.445 + * object, being passed the CssSelector object and the match status. 1.446 + * 1.447 + * This method also includes all of the element.style properties, for each 1.448 + * highlighted element parent and for the highlighted element itself. 1.449 + * 1.450 + * Note that the matched selectors are cached, such that next time your 1.451 + * callback is invoked for the cached list of CssSelector objects. 1.452 + * 1.453 + * @param {function} aCallback the function you want to execute for each of 1.454 + * the matched selectors. 1.455 + * @param {object} aScope the scope you want for the callback function. aScope 1.456 + * will be the this object when aCallback executes. 1.457 + */ 1.458 + processMatchedSelectors: function CL_processMatchedSelectors(aCallback, aScope) 1.459 + { 1.460 + if (this._matchedSelectors) { 1.461 + if (aCallback) { 1.462 + this._passId++; 1.463 + this._matchedSelectors.forEach(function(aValue) { 1.464 + aCallback.call(aScope, aValue[0], aValue[1]); 1.465 + aValue[0].cssRule._passId = this._passId; 1.466 + }, this); 1.467 + } 1.468 + return; 1.469 + } 1.470 + 1.471 + if (!this._matchedRules) { 1.472 + this._buildMatchedRules(); 1.473 + } 1.474 + 1.475 + this._matchedSelectors = []; 1.476 + this._passId++; 1.477 + 1.478 + for (let i = 0; i < this._matchedRules.length; i++) { 1.479 + let rule = this._matchedRules[i][0]; 1.480 + let status = this._matchedRules[i][1]; 1.481 + 1.482 + rule.selectors.forEach(function (aSelector) { 1.483 + if (aSelector._matchId !== this._matchId && 1.484 + (aSelector.elementStyle || 1.485 + this.selectorMatchesElement(rule.domRule, aSelector.selectorIndex))) { 1.486 + 1.487 + aSelector._matchId = this._matchId; 1.488 + this._matchedSelectors.push([ aSelector, status ]); 1.489 + if (aCallback) { 1.490 + aCallback.call(aScope, aSelector, status); 1.491 + } 1.492 + } 1.493 + }, this); 1.494 + 1.495 + rule._passId = this._passId; 1.496 + } 1.497 + }, 1.498 + 1.499 + /** 1.500 + * Check if the given selector matches the highlighted element or any of its 1.501 + * parents. 1.502 + * 1.503 + * @private 1.504 + * @param {DOMRule} domRule 1.505 + * The DOM Rule containing the selector. 1.506 + * @param {Number} idx 1.507 + * The index of the selector within the DOMRule. 1.508 + * @return {boolean} 1.509 + * true if the given selector matches the highlighted element or any 1.510 + * of its parents, otherwise false is returned. 1.511 + */ 1.512 + selectorMatchesElement: function CL_selectorMatchesElement2(domRule, idx) 1.513 + { 1.514 + let element = this.viewedElement; 1.515 + do { 1.516 + if (domUtils.selectorMatchesElement(element, domRule, idx)) { 1.517 + return true; 1.518 + } 1.519 + } while ((element = element.parentNode) && 1.520 + element.nodeType === Ci.nsIDOMNode.ELEMENT_NODE); 1.521 + 1.522 + return false; 1.523 + }, 1.524 + 1.525 + /** 1.526 + * Check if the highlighted element or it's parents have matched selectors. 1.527 + * 1.528 + * @param {array} aProperties The list of properties you want to check if they 1.529 + * have matched selectors or not. 1.530 + * @return {object} An object that tells for each property if it has matched 1.531 + * selectors or not. Object keys are property names and values are booleans. 1.532 + */ 1.533 + hasMatchedSelectors: function CL_hasMatchedSelectors(aProperties) 1.534 + { 1.535 + if (!this._matchedRules) { 1.536 + this._buildMatchedRules(); 1.537 + } 1.538 + 1.539 + let result = {}; 1.540 + 1.541 + this._matchedRules.some(function(aValue) { 1.542 + let rule = aValue[0]; 1.543 + let status = aValue[1]; 1.544 + aProperties = aProperties.filter(function(aProperty) { 1.545 + // We just need to find if a rule has this property while it matches 1.546 + // the viewedElement (or its parents). 1.547 + if (rule.getPropertyValue(aProperty) && 1.548 + (status == CssLogic.STATUS.MATCHED || 1.549 + (status == CssLogic.STATUS.PARENT_MATCH && 1.550 + domUtils.isInheritedProperty(aProperty)))) { 1.551 + result[aProperty] = true; 1.552 + return false; 1.553 + } 1.554 + return true; // Keep the property for the next rule. 1.555 + }.bind(this)); 1.556 + return aProperties.length == 0; 1.557 + }, this); 1.558 + 1.559 + return result; 1.560 + }, 1.561 + 1.562 + /** 1.563 + * Build the array of matched rules for the currently highlighted element. 1.564 + * The array will hold rules that match the viewedElement and its parents. 1.565 + * 1.566 + * @private 1.567 + */ 1.568 + _buildMatchedRules: function CL__buildMatchedRules() 1.569 + { 1.570 + let domRules; 1.571 + let element = this.viewedElement; 1.572 + let filter = this.sourceFilter; 1.573 + let sheetIndex = 0; 1.574 + 1.575 + this._matchId++; 1.576 + this._passId++; 1.577 + this._matchedRules = []; 1.578 + 1.579 + if (!element) { 1.580 + return; 1.581 + } 1.582 + 1.583 + do { 1.584 + let status = this.viewedElement === element ? 1.585 + CssLogic.STATUS.MATCHED : CssLogic.STATUS.PARENT_MATCH; 1.586 + 1.587 + try { 1.588 + domRules = domUtils.getCSSStyleRules(element); 1.589 + } catch (ex) { 1.590 + Services.console. 1.591 + logStringMessage("CL__buildMatchedRules error: " + ex); 1.592 + continue; 1.593 + } 1.594 + 1.595 + for (let i = 0, n = domRules.Count(); i < n; i++) { 1.596 + let domRule = domRules.GetElementAt(i); 1.597 + if (domRule.type !== Ci.nsIDOMCSSRule.STYLE_RULE) { 1.598 + continue; 1.599 + } 1.600 + 1.601 + let sheet = this.getSheet(domRule.parentStyleSheet, -1); 1.602 + if (sheet._passId !== this._passId) { 1.603 + sheet.index = sheetIndex++; 1.604 + sheet._passId = this._passId; 1.605 + } 1.606 + 1.607 + if (filter === CssLogic.FILTER.USER && !sheet.contentSheet) { 1.608 + continue; 1.609 + } 1.610 + 1.611 + let rule = sheet.getRule(domRule); 1.612 + if (rule._passId === this._passId) { 1.613 + continue; 1.614 + } 1.615 + 1.616 + rule._matchId = this._matchId; 1.617 + rule._passId = this._passId; 1.618 + this._matchedRules.push([rule, status]); 1.619 + } 1.620 + 1.621 + 1.622 + // Add element.style information. 1.623 + if (element.style && element.style.length > 0) { 1.624 + let rule = new CssRule(null, { style: element.style }, element); 1.625 + rule._matchId = this._matchId; 1.626 + rule._passId = this._passId; 1.627 + this._matchedRules.push([rule, status]); 1.628 + } 1.629 + } while ((element = element.parentNode) && 1.630 + element.nodeType === Ci.nsIDOMNode.ELEMENT_NODE); 1.631 + }, 1.632 + 1.633 + /** 1.634 + * Tells if the given DOM CSS object matches the current view media. 1.635 + * 1.636 + * @param {object} aDomObject The DOM CSS object to check. 1.637 + * @return {boolean} True if the DOM CSS object matches the current view 1.638 + * media, or false otherwise. 1.639 + */ 1.640 + mediaMatches: function CL_mediaMatches(aDomObject) 1.641 + { 1.642 + let mediaText = aDomObject.media.mediaText; 1.643 + return !mediaText || this.viewedDocument.defaultView. 1.644 + matchMedia(mediaText).matches; 1.645 + }, 1.646 +}; 1.647 + 1.648 +/** 1.649 + * If the element has an id, return '#id'. Otherwise return 'tagname[n]' where 1.650 + * n is the index of this element in its siblings. 1.651 + * <p>A technically more 'correct' output from the no-id case might be: 1.652 + * 'tagname:nth-of-type(n)' however this is unlikely to be more understood 1.653 + * and it is longer. 1.654 + * 1.655 + * @param {nsIDOMElement} aElement the element for which you want the short name. 1.656 + * @return {string} the string to be displayed for aElement. 1.657 + */ 1.658 +CssLogic.getShortName = function CssLogic_getShortName(aElement) 1.659 +{ 1.660 + if (!aElement) { 1.661 + return "null"; 1.662 + } 1.663 + if (aElement.id) { 1.664 + return "#" + aElement.id; 1.665 + } 1.666 + let priorSiblings = 0; 1.667 + let temp = aElement; 1.668 + while (temp = temp.previousElementSibling) { 1.669 + priorSiblings++; 1.670 + } 1.671 + return aElement.tagName + "[" + priorSiblings + "]"; 1.672 +}; 1.673 + 1.674 +/** 1.675 + * Get an array of short names from the given element to document.body. 1.676 + * 1.677 + * @param {nsIDOMElement} aElement the element for which you want the array of 1.678 + * short names. 1.679 + * @return {array} The array of elements. 1.680 + * <p>Each element is an object of the form: 1.681 + * <ul> 1.682 + * <li>{ display: "what to display for the given (parent) element", 1.683 + * <li> element: referenceToTheElement } 1.684 + * </ul> 1.685 + */ 1.686 +CssLogic.getShortNamePath = function CssLogic_getShortNamePath(aElement) 1.687 +{ 1.688 + let doc = aElement.ownerDocument; 1.689 + let reply = []; 1.690 + 1.691 + if (!aElement) { 1.692 + return reply; 1.693 + } 1.694 + 1.695 + // We want to exclude nodes high up the tree (body/html) unless the user 1.696 + // has selected that node, in which case we need to report something. 1.697 + do { 1.698 + reply.unshift({ 1.699 + display: CssLogic.getShortName(aElement), 1.700 + element: aElement 1.701 + }); 1.702 + aElement = aElement.parentNode; 1.703 + } while (aElement && aElement != doc.body && aElement != doc.head && aElement != doc); 1.704 + 1.705 + return reply; 1.706 +}; 1.707 + 1.708 +/** 1.709 + * Get a string list of selectors for a given DOMRule. 1.710 + * 1.711 + * @param {DOMRule} aDOMRule 1.712 + * The DOMRule to parse. 1.713 + * @return {Array} 1.714 + * An array of string selectors. 1.715 + */ 1.716 +CssLogic.getSelectors = function CssLogic_getSelectors(aDOMRule) 1.717 +{ 1.718 + let selectors = []; 1.719 + 1.720 + let len = domUtils.getSelectorCount(aDOMRule); 1.721 + for (let i = 0; i < len; i++) { 1.722 + let text = domUtils.getSelectorText(aDOMRule, i); 1.723 + selectors.push(text); 1.724 + } 1.725 + return selectors; 1.726 +} 1.727 + 1.728 +/** 1.729 + * Memonized lookup of a l10n string from a string bundle. 1.730 + * @param {string} aName The key to lookup. 1.731 + * @returns A localized version of the given key. 1.732 + */ 1.733 +CssLogic.l10n = function(aName) CssLogic._strings.GetStringFromName(aName); 1.734 + 1.735 +XPCOMUtils.defineLazyGetter(CssLogic, "_strings", function() Services.strings 1.736 + .createBundle("chrome://global/locale/devtools/styleinspector.properties")); 1.737 + 1.738 +/** 1.739 + * Is the given property sheet a content stylesheet? 1.740 + * 1.741 + * @param {CSSStyleSheet} aSheet a stylesheet 1.742 + * @return {boolean} true if the given stylesheet is a content stylesheet, 1.743 + * false otherwise. 1.744 + */ 1.745 +CssLogic.isContentStylesheet = function CssLogic_isContentStylesheet(aSheet) 1.746 +{ 1.747 + // All sheets with owner nodes have been included by content. 1.748 + if (aSheet.ownerNode) { 1.749 + return true; 1.750 + } 1.751 + 1.752 + // If the sheet has a CSSImportRule we need to check the parent stylesheet. 1.753 + if (aSheet.ownerRule instanceof Ci.nsIDOMCSSImportRule) { 1.754 + return CssLogic.isContentStylesheet(aSheet.parentStyleSheet); 1.755 + } 1.756 + 1.757 + return false; 1.758 +}; 1.759 + 1.760 +/** 1.761 + * Get a source for a stylesheet, taking into account embedded stylesheets 1.762 + * for which we need to use document.defaultView.location.href rather than 1.763 + * sheet.href 1.764 + * 1.765 + * @param {CSSStyleSheet} aSheet the DOM object for the style sheet. 1.766 + * @return {string} the address of the stylesheet. 1.767 + */ 1.768 +CssLogic.href = function CssLogic_href(aSheet) 1.769 +{ 1.770 + let href = aSheet.href; 1.771 + if (!href) { 1.772 + href = aSheet.ownerNode.ownerDocument.location; 1.773 + } 1.774 + 1.775 + return href; 1.776 +}; 1.777 + 1.778 +/** 1.779 + * Return a shortened version of a style sheet's source. 1.780 + * 1.781 + * @param {CSSStyleSheet} aSheet the DOM object for the style sheet. 1.782 + */ 1.783 +CssLogic.shortSource = function CssLogic_shortSource(aSheet) 1.784 +{ 1.785 + // Use a string like "inline" if there is no source href 1.786 + if (!aSheet || !aSheet.href) { 1.787 + return CssLogic.l10n("rule.sourceInline"); 1.788 + } 1.789 + 1.790 + // We try, in turn, the filename, filePath, query string, whole thing 1.791 + let url = {}; 1.792 + try { 1.793 + url = Services.io.newURI(aSheet.href, null, null); 1.794 + url = url.QueryInterface(Ci.nsIURL); 1.795 + } catch (ex) { 1.796 + // Some UA-provided stylesheets are not valid URLs. 1.797 + } 1.798 + 1.799 + if (url.fileName) { 1.800 + return url.fileName; 1.801 + } 1.802 + 1.803 + if (url.filePath) { 1.804 + return url.filePath; 1.805 + } 1.806 + 1.807 + if (url.query) { 1.808 + return url.query; 1.809 + } 1.810 + 1.811 + let dataUrl = aSheet.href.match(/^(data:[^,]*),/); 1.812 + return dataUrl ? dataUrl[1] : aSheet.href; 1.813 +} 1.814 + 1.815 +/** 1.816 + * Extract the background image URL (if any) from a property value. 1.817 + * Used, for example, for the preview tooltip in the rule view and 1.818 + * computed view. 1.819 + * 1.820 + * @param {String} aProperty 1.821 + * @param {String} aSheetHref 1.822 + * @return {string} a image URL 1.823 + */ 1.824 +CssLogic.getBackgroundImageUriFromProperty = function(aProperty, aSheetHref) { 1.825 + let startToken = "url(", start = aProperty.indexOf(startToken), end; 1.826 + if (start === -1) { 1.827 + return null; 1.828 + } 1.829 + 1.830 + aProperty = aProperty.substring(start + startToken.length).trim(); 1.831 + let quote = aProperty.substring(0, 1); 1.832 + if (quote === "'" || quote === '"') { 1.833 + end = aProperty.search(new RegExp(quote + "\\s*\\)")); 1.834 + start = 1; 1.835 + } else { 1.836 + end = aProperty.indexOf(")"); 1.837 + start = 0; 1.838 + } 1.839 + 1.840 + let uri = aProperty.substring(start, end).trim(); 1.841 + if (aSheetHref) { 1.842 + let IOService = Cc["@mozilla.org/network/io-service;1"] 1.843 + .getService(Ci.nsIIOService); 1.844 + let sheetUri = IOService.newURI(aSheetHref, null, null); 1.845 + uri = sheetUri.resolve(uri); 1.846 + } 1.847 + 1.848 + return uri; 1.849 +} 1.850 + 1.851 +/** 1.852 + * Find the position of [element] in [nodeList]. 1.853 + * @returns an index of the match, or -1 if there is no match 1.854 + */ 1.855 +function positionInNodeList(element, nodeList) { 1.856 + for (var i = 0; i < nodeList.length; i++) { 1.857 + if (element === nodeList[i]) { 1.858 + return i; 1.859 + } 1.860 + } 1.861 + return -1; 1.862 +} 1.863 + 1.864 +/** 1.865 + * Find a unique CSS selector for a given element 1.866 + * @returns a string such that ele.ownerDocument.querySelector(reply) === ele 1.867 + * and ele.ownerDocument.querySelectorAll(reply).length === 1 1.868 + */ 1.869 +CssLogic.findCssSelector = function CssLogic_findCssSelector(ele) { 1.870 + var document = ele.ownerDocument; 1.871 + if (ele.id && document.getElementById(ele.id) === ele) { 1.872 + return '#' + ele.id; 1.873 + } 1.874 + 1.875 + // Inherently unique by tag name 1.876 + var tagName = ele.tagName.toLowerCase(); 1.877 + if (tagName === 'html') { 1.878 + return 'html'; 1.879 + } 1.880 + if (tagName === 'head') { 1.881 + return 'head'; 1.882 + } 1.883 + if (tagName === 'body') { 1.884 + return 'body'; 1.885 + } 1.886 + 1.887 + if (ele.parentNode == null) { 1.888 + console.log('danger: ' + tagName); 1.889 + } 1.890 + 1.891 + // We might be able to find a unique class name 1.892 + var selector, index, matches; 1.893 + if (ele.classList.length > 0) { 1.894 + for (var i = 0; i < ele.classList.length; i++) { 1.895 + // Is this className unique by itself? 1.896 + selector = '.' + ele.classList.item(i); 1.897 + matches = document.querySelectorAll(selector); 1.898 + if (matches.length === 1) { 1.899 + return selector; 1.900 + } 1.901 + // Maybe it's unique with a tag name? 1.902 + selector = tagName + selector; 1.903 + matches = document.querySelectorAll(selector); 1.904 + if (matches.length === 1) { 1.905 + return selector; 1.906 + } 1.907 + // Maybe it's unique using a tag name and nth-child 1.908 + index = positionInNodeList(ele, ele.parentNode.children) + 1; 1.909 + selector = selector + ':nth-child(' + index + ')'; 1.910 + matches = document.querySelectorAll(selector); 1.911 + if (matches.length === 1) { 1.912 + return selector; 1.913 + } 1.914 + } 1.915 + } 1.916 + 1.917 + // So we can be unique w.r.t. our parent, and use recursion 1.918 + index = positionInNodeList(ele, ele.parentNode.children) + 1; 1.919 + selector = CssLogic_findCssSelector(ele.parentNode) + ' > ' + 1.920 + tagName + ':nth-child(' + index + ')'; 1.921 + 1.922 + return selector; 1.923 +}; 1.924 + 1.925 +/** 1.926 + * A safe way to access cached bits of information about a stylesheet. 1.927 + * 1.928 + * @constructor 1.929 + * @param {CssLogic} aCssLogic pointer to the CssLogic instance working with 1.930 + * this CssSheet object. 1.931 + * @param {CSSStyleSheet} aDomSheet reference to a DOM CSSStyleSheet object. 1.932 + * @param {number} aIndex tells the index/position of the stylesheet within the 1.933 + * main document. 1.934 + */ 1.935 +function CssSheet(aCssLogic, aDomSheet, aIndex) 1.936 +{ 1.937 + this._cssLogic = aCssLogic; 1.938 + this.domSheet = aDomSheet; 1.939 + this.index = this.contentSheet ? aIndex : -100 * aIndex; 1.940 + 1.941 + // Cache of the sheets href. Cached by the getter. 1.942 + this._href = null; 1.943 + // Short version of href for use in select boxes etc. Cached by getter. 1.944 + this._shortSource = null; 1.945 + 1.946 + // null for uncached. 1.947 + this._sheetAllowed = null; 1.948 + 1.949 + // Cached CssRules from the given stylesheet. 1.950 + this._rules = {}; 1.951 + 1.952 + this._ruleCount = -1; 1.953 +} 1.954 + 1.955 +CssSheet.prototype = { 1.956 + _passId: null, 1.957 + _contentSheet: null, 1.958 + _mediaMatches: null, 1.959 + 1.960 + /** 1.961 + * Tells if the stylesheet is provided by the browser or not. 1.962 + * 1.963 + * @return {boolean} false if this is a browser-provided stylesheet, or true 1.964 + * otherwise. 1.965 + */ 1.966 + get contentSheet() 1.967 + { 1.968 + if (this._contentSheet === null) { 1.969 + this._contentSheet = CssLogic.isContentStylesheet(this.domSheet); 1.970 + } 1.971 + return this._contentSheet; 1.972 + }, 1.973 + 1.974 + /** 1.975 + * Tells if the stylesheet is disabled or not. 1.976 + * @return {boolean} true if this stylesheet is disabled, or false otherwise. 1.977 + */ 1.978 + get disabled() 1.979 + { 1.980 + return this.domSheet.disabled; 1.981 + }, 1.982 + 1.983 + /** 1.984 + * Tells if the stylesheet matches the current browser view media. 1.985 + * @return {boolean} true if this stylesheet matches the current browser view 1.986 + * media, or false otherwise. 1.987 + */ 1.988 + get mediaMatches() 1.989 + { 1.990 + if (this._mediaMatches === null) { 1.991 + this._mediaMatches = this._cssLogic.mediaMatches(this.domSheet); 1.992 + } 1.993 + return this._mediaMatches; 1.994 + }, 1.995 + 1.996 + /** 1.997 + * Get a source for a stylesheet, using CssLogic.href 1.998 + * 1.999 + * @return {string} the address of the stylesheet. 1.1000 + */ 1.1001 + get href() 1.1002 + { 1.1003 + if (this._href) { 1.1004 + return this._href; 1.1005 + } 1.1006 + 1.1007 + this._href = CssLogic.href(this.domSheet); 1.1008 + return this._href; 1.1009 + }, 1.1010 + 1.1011 + /** 1.1012 + * Create a shorthand version of the href of a stylesheet. 1.1013 + * 1.1014 + * @return {string} the shorthand source of the stylesheet. 1.1015 + */ 1.1016 + get shortSource() 1.1017 + { 1.1018 + if (this._shortSource) { 1.1019 + return this._shortSource; 1.1020 + } 1.1021 + 1.1022 + this._shortSource = CssLogic.shortSource(this.domSheet); 1.1023 + return this._shortSource; 1.1024 + }, 1.1025 + 1.1026 + /** 1.1027 + * Tells if the sheet is allowed or not by the current CssLogic.sourceFilter. 1.1028 + * 1.1029 + * @return {boolean} true if the stylesheet is allowed by the sourceFilter, or 1.1030 + * false otherwise. 1.1031 + */ 1.1032 + get sheetAllowed() 1.1033 + { 1.1034 + if (this._sheetAllowed !== null) { 1.1035 + return this._sheetAllowed; 1.1036 + } 1.1037 + 1.1038 + this._sheetAllowed = true; 1.1039 + 1.1040 + let filter = this._cssLogic.sourceFilter; 1.1041 + if (filter === CssLogic.FILTER.USER && !this.contentSheet) { 1.1042 + this._sheetAllowed = false; 1.1043 + } 1.1044 + if (filter !== CssLogic.FILTER.USER && filter !== CssLogic.FILTER.UA) { 1.1045 + this._sheetAllowed = (filter === this.href); 1.1046 + } 1.1047 + 1.1048 + return this._sheetAllowed; 1.1049 + }, 1.1050 + 1.1051 + /** 1.1052 + * Retrieve the number of rules in this stylesheet. 1.1053 + * 1.1054 + * @return {number} the number of nsIDOMCSSRule objects in this stylesheet. 1.1055 + */ 1.1056 + get ruleCount() 1.1057 + { 1.1058 + return this._ruleCount > -1 ? 1.1059 + this._ruleCount : 1.1060 + this.domSheet.cssRules.length; 1.1061 + }, 1.1062 + 1.1063 + /** 1.1064 + * Retrieve a CssRule object for the given CSSStyleRule. The CssRule object is 1.1065 + * cached, such that subsequent retrievals return the same CssRule object for 1.1066 + * the same CSSStyleRule object. 1.1067 + * 1.1068 + * @param {CSSStyleRule} aDomRule the CSSStyleRule object for which you want a 1.1069 + * CssRule object. 1.1070 + * @return {CssRule} the cached CssRule object for the given CSSStyleRule 1.1071 + * object. 1.1072 + */ 1.1073 + getRule: function CssSheet_getRule(aDomRule) 1.1074 + { 1.1075 + let cacheId = aDomRule.type + aDomRule.selectorText; 1.1076 + 1.1077 + let rule = null; 1.1078 + let ruleFound = false; 1.1079 + 1.1080 + if (cacheId in this._rules) { 1.1081 + for (let i = 0, rulesLen = this._rules[cacheId].length; i < rulesLen; i++) { 1.1082 + rule = this._rules[cacheId][i]; 1.1083 + if (rule.domRule === aDomRule) { 1.1084 + ruleFound = true; 1.1085 + break; 1.1086 + } 1.1087 + } 1.1088 + } 1.1089 + 1.1090 + if (!ruleFound) { 1.1091 + if (!(cacheId in this._rules)) { 1.1092 + this._rules[cacheId] = []; 1.1093 + } 1.1094 + 1.1095 + rule = new CssRule(this, aDomRule); 1.1096 + this._rules[cacheId].push(rule); 1.1097 + } 1.1098 + 1.1099 + return rule; 1.1100 + }, 1.1101 + 1.1102 + /** 1.1103 + * Process each rule in this stylesheet using your callback function. Your 1.1104 + * function receives one argument: the CssRule object for each CSSStyleRule 1.1105 + * inside the stylesheet. 1.1106 + * 1.1107 + * Note that this method also iterates through @media rules inside the 1.1108 + * stylesheet. 1.1109 + * 1.1110 + * @param {function} aCallback the function you want to execute for each of 1.1111 + * the style rules. 1.1112 + * @param {object} aScope the scope you want for the callback function. aScope 1.1113 + * will be the this object when aCallback executes. 1.1114 + */ 1.1115 + forEachRule: function CssSheet_forEachRule(aCallback, aScope) 1.1116 + { 1.1117 + let ruleCount = 0; 1.1118 + let domRules = this.domSheet.cssRules; 1.1119 + 1.1120 + function _iterator(aDomRule) { 1.1121 + if (aDomRule.type == Ci.nsIDOMCSSRule.STYLE_RULE) { 1.1122 + aCallback.call(aScope, this.getRule(aDomRule)); 1.1123 + ruleCount++; 1.1124 + } else if (aDomRule.type == Ci.nsIDOMCSSRule.MEDIA_RULE && 1.1125 + aDomRule.cssRules && this._cssLogic.mediaMatches(aDomRule)) { 1.1126 + Array.prototype.forEach.call(aDomRule.cssRules, _iterator, this); 1.1127 + } 1.1128 + } 1.1129 + 1.1130 + Array.prototype.forEach.call(domRules, _iterator, this); 1.1131 + 1.1132 + this._ruleCount = ruleCount; 1.1133 + }, 1.1134 + 1.1135 + /** 1.1136 + * Process *some* rules in this stylesheet using your callback function. Your 1.1137 + * function receives one argument: the CssRule object for each CSSStyleRule 1.1138 + * inside the stylesheet. In order to stop processing the callback function 1.1139 + * needs to return a value. 1.1140 + * 1.1141 + * Note that this method also iterates through @media rules inside the 1.1142 + * stylesheet. 1.1143 + * 1.1144 + * @param {function} aCallback the function you want to execute for each of 1.1145 + * the style rules. 1.1146 + * @param {object} aScope the scope you want for the callback function. aScope 1.1147 + * will be the this object when aCallback executes. 1.1148 + * @return {Boolean} true if aCallback returns true during any iteration, 1.1149 + * otherwise false is returned. 1.1150 + */ 1.1151 + forSomeRules: function CssSheet_forSomeRules(aCallback, aScope) 1.1152 + { 1.1153 + let domRules = this.domSheet.cssRules; 1.1154 + function _iterator(aDomRule) { 1.1155 + if (aDomRule.type == Ci.nsIDOMCSSRule.STYLE_RULE) { 1.1156 + return aCallback.call(aScope, this.getRule(aDomRule)); 1.1157 + } else if (aDomRule.type == Ci.nsIDOMCSSRule.MEDIA_RULE && 1.1158 + aDomRule.cssRules && this._cssLogic.mediaMatches(aDomRule)) { 1.1159 + return Array.prototype.some.call(aDomRule.cssRules, _iterator, this); 1.1160 + } 1.1161 + } 1.1162 + return Array.prototype.some.call(domRules, _iterator, this); 1.1163 + }, 1.1164 + 1.1165 + toString: function CssSheet_toString() 1.1166 + { 1.1167 + return "CssSheet[" + this.shortSource + "]"; 1.1168 + } 1.1169 +}; 1.1170 + 1.1171 +/** 1.1172 + * Information about a single CSSStyleRule. 1.1173 + * 1.1174 + * @param {CSSSheet|null} aCssSheet the CssSheet object of the stylesheet that 1.1175 + * holds the CSSStyleRule. If the rule comes from element.style, set this 1.1176 + * argument to null. 1.1177 + * @param {CSSStyleRule|object} aDomRule the DOM CSSStyleRule for which you want 1.1178 + * to cache data. If the rule comes from element.style, then provide 1.1179 + * an object of the form: {style: element.style}. 1.1180 + * @param {Element} [aElement] If the rule comes from element.style, then this 1.1181 + * argument must point to the element. 1.1182 + * @constructor 1.1183 + */ 1.1184 +function CssRule(aCssSheet, aDomRule, aElement) 1.1185 +{ 1.1186 + this._cssSheet = aCssSheet; 1.1187 + this.domRule = aDomRule; 1.1188 + 1.1189 + let parentRule = aDomRule.parentRule; 1.1190 + if (parentRule && parentRule.type == Ci.nsIDOMCSSRule.MEDIA_RULE) { 1.1191 + this.mediaText = parentRule.media.mediaText; 1.1192 + } 1.1193 + 1.1194 + if (this._cssSheet) { 1.1195 + // parse domRule.selectorText on call to this.selectors 1.1196 + this._selectors = null; 1.1197 + this.line = domUtils.getRuleLine(this.domRule); 1.1198 + this.source = this._cssSheet.shortSource + ":" + this.line; 1.1199 + if (this.mediaText) { 1.1200 + this.source += " @media " + this.mediaText; 1.1201 + } 1.1202 + this.href = this._cssSheet.href; 1.1203 + this.contentRule = this._cssSheet.contentSheet; 1.1204 + } else if (aElement) { 1.1205 + this._selectors = [ new CssSelector(this, "@element.style", 0) ]; 1.1206 + this.line = -1; 1.1207 + this.source = CssLogic.l10n("rule.sourceElement"); 1.1208 + this.href = "#"; 1.1209 + this.contentRule = true; 1.1210 + this.sourceElement = aElement; 1.1211 + } 1.1212 +} 1.1213 + 1.1214 +CssRule.prototype = { 1.1215 + _passId: null, 1.1216 + 1.1217 + mediaText: "", 1.1218 + 1.1219 + get isMediaRule() 1.1220 + { 1.1221 + return !!this.mediaText; 1.1222 + }, 1.1223 + 1.1224 + /** 1.1225 + * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter. 1.1226 + * 1.1227 + * @return {boolean} true if the parent stylesheet is allowed by the current 1.1228 + * sourceFilter, or false otherwise. 1.1229 + */ 1.1230 + get sheetAllowed() 1.1231 + { 1.1232 + return this._cssSheet ? this._cssSheet.sheetAllowed : true; 1.1233 + }, 1.1234 + 1.1235 + /** 1.1236 + * Retrieve the parent stylesheet index/position in the viewed document. 1.1237 + * 1.1238 + * @return {number} the parent stylesheet index/position in the viewed 1.1239 + * document. 1.1240 + */ 1.1241 + get sheetIndex() 1.1242 + { 1.1243 + return this._cssSheet ? this._cssSheet.index : 0; 1.1244 + }, 1.1245 + 1.1246 + /** 1.1247 + * Retrieve the style property value from the current CSSStyleRule. 1.1248 + * 1.1249 + * @param {string} aProperty the CSS property name for which you want the 1.1250 + * value. 1.1251 + * @return {string} the property value. 1.1252 + */ 1.1253 + getPropertyValue: function(aProperty) 1.1254 + { 1.1255 + return this.domRule.style.getPropertyValue(aProperty); 1.1256 + }, 1.1257 + 1.1258 + /** 1.1259 + * Retrieve the style property priority from the current CSSStyleRule. 1.1260 + * 1.1261 + * @param {string} aProperty the CSS property name for which you want the 1.1262 + * priority. 1.1263 + * @return {string} the property priority. 1.1264 + */ 1.1265 + getPropertyPriority: function(aProperty) 1.1266 + { 1.1267 + return this.domRule.style.getPropertyPriority(aProperty); 1.1268 + }, 1.1269 + 1.1270 + /** 1.1271 + * Retrieve the list of CssSelector objects for each of the parsed selectors 1.1272 + * of the current CSSStyleRule. 1.1273 + * 1.1274 + * @return {array} the array hold the CssSelector objects. 1.1275 + */ 1.1276 + get selectors() 1.1277 + { 1.1278 + if (this._selectors) { 1.1279 + return this._selectors; 1.1280 + } 1.1281 + 1.1282 + // Parse the CSSStyleRule.selectorText string. 1.1283 + this._selectors = []; 1.1284 + 1.1285 + if (!this.domRule.selectorText) { 1.1286 + return this._selectors; 1.1287 + } 1.1288 + 1.1289 + let selectors = CssLogic.getSelectors(this.domRule); 1.1290 + 1.1291 + for (let i = 0, len = selectors.length; i < len; i++) { 1.1292 + this._selectors.push(new CssSelector(this, selectors[i], i)); 1.1293 + } 1.1294 + 1.1295 + return this._selectors; 1.1296 + }, 1.1297 + 1.1298 + toString: function CssRule_toString() 1.1299 + { 1.1300 + return "[CssRule " + this.domRule.selectorText + "]"; 1.1301 + }, 1.1302 +}; 1.1303 + 1.1304 +/** 1.1305 + * The CSS selector class allows us to document the ranking of various CSS 1.1306 + * selectors. 1.1307 + * 1.1308 + * @constructor 1.1309 + * @param {CssRule} aCssRule the CssRule instance from where the selector comes. 1.1310 + * @param {string} aSelector The selector that we wish to investigate. 1.1311 + * @param {Number} aIndex The index of the selector within it's rule. 1.1312 + */ 1.1313 +function CssSelector(aCssRule, aSelector, aIndex) 1.1314 +{ 1.1315 + this.cssRule = aCssRule; 1.1316 + this.text = aSelector; 1.1317 + this.elementStyle = this.text == "@element.style"; 1.1318 + this._specificity = null; 1.1319 + this.selectorIndex = aIndex; 1.1320 +} 1.1321 + 1.1322 +exports.CssSelector = CssSelector; 1.1323 + 1.1324 +CssSelector.prototype = { 1.1325 + _matchId: null, 1.1326 + 1.1327 + /** 1.1328 + * Retrieve the CssSelector source, which is the source of the CssSheet owning 1.1329 + * the selector. 1.1330 + * 1.1331 + * @return {string} the selector source. 1.1332 + */ 1.1333 + get source() 1.1334 + { 1.1335 + return this.cssRule.source; 1.1336 + }, 1.1337 + 1.1338 + /** 1.1339 + * Retrieve the CssSelector source element, which is the source of the CssRule 1.1340 + * owning the selector. This is only available when the CssSelector comes from 1.1341 + * an element.style. 1.1342 + * 1.1343 + * @return {string} the source element selector. 1.1344 + */ 1.1345 + get sourceElement() 1.1346 + { 1.1347 + return this.cssRule.sourceElement; 1.1348 + }, 1.1349 + 1.1350 + /** 1.1351 + * Retrieve the address of the CssSelector. This points to the address of the 1.1352 + * CssSheet owning this selector. 1.1353 + * 1.1354 + * @return {string} the address of the CssSelector. 1.1355 + */ 1.1356 + get href() 1.1357 + { 1.1358 + return this.cssRule.href; 1.1359 + }, 1.1360 + 1.1361 + /** 1.1362 + * Check if the selector comes from a browser-provided stylesheet. 1.1363 + * 1.1364 + * @return {boolean} true if the selector comes from a content-provided 1.1365 + * stylesheet, or false otherwise. 1.1366 + */ 1.1367 + get contentRule() 1.1368 + { 1.1369 + return this.cssRule.contentRule; 1.1370 + }, 1.1371 + 1.1372 + /** 1.1373 + * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter. 1.1374 + * 1.1375 + * @return {boolean} true if the parent stylesheet is allowed by the current 1.1376 + * sourceFilter, or false otherwise. 1.1377 + */ 1.1378 + get sheetAllowed() 1.1379 + { 1.1380 + return this.cssRule.sheetAllowed; 1.1381 + }, 1.1382 + 1.1383 + /** 1.1384 + * Retrieve the parent stylesheet index/position in the viewed document. 1.1385 + * 1.1386 + * @return {number} the parent stylesheet index/position in the viewed 1.1387 + * document. 1.1388 + */ 1.1389 + get sheetIndex() 1.1390 + { 1.1391 + return this.cssRule.sheetIndex; 1.1392 + }, 1.1393 + 1.1394 + /** 1.1395 + * Retrieve the line of the parent CSSStyleRule in the parent CSSStyleSheet. 1.1396 + * 1.1397 + * @return {number} the line of the parent CSSStyleRule in the parent 1.1398 + * stylesheet. 1.1399 + */ 1.1400 + get ruleLine() 1.1401 + { 1.1402 + return this.cssRule.line; 1.1403 + }, 1.1404 + 1.1405 + /** 1.1406 + * Retrieve the pseudo-elements that we support. This list should match the 1.1407 + * elements specified in layout/style/nsCSSPseudoElementList.h 1.1408 + */ 1.1409 + get pseudoElements() 1.1410 + { 1.1411 + if (!CssSelector._pseudoElements) { 1.1412 + let pseudos = CssSelector._pseudoElements = new Set(); 1.1413 + pseudos.add("after"); 1.1414 + pseudos.add("before"); 1.1415 + pseudos.add("first-letter"); 1.1416 + pseudos.add("first-line"); 1.1417 + pseudos.add("selection"); 1.1418 + pseudos.add("-moz-color-swatch"); 1.1419 + pseudos.add("-moz-focus-inner"); 1.1420 + pseudos.add("-moz-focus-outer"); 1.1421 + pseudos.add("-moz-list-bullet"); 1.1422 + pseudos.add("-moz-list-number"); 1.1423 + pseudos.add("-moz-math-anonymous"); 1.1424 + pseudos.add("-moz-math-stretchy"); 1.1425 + pseudos.add("-moz-progress-bar"); 1.1426 + pseudos.add("-moz-selection"); 1.1427 + } 1.1428 + return CssSelector._pseudoElements; 1.1429 + }, 1.1430 + 1.1431 + /** 1.1432 + * Retrieve specificity information for the current selector. 1.1433 + * 1.1434 + * @see http://www.w3.org/TR/css3-selectors/#specificity 1.1435 + * @see http://www.w3.org/TR/CSS2/selector.html 1.1436 + * 1.1437 + * @return {Number} The selector's specificity. 1.1438 + */ 1.1439 + get specificity() 1.1440 + { 1.1441 + if (this._specificity) { 1.1442 + return this._specificity; 1.1443 + } 1.1444 + 1.1445 + this._specificity = domUtils.getSpecificity(this.cssRule.domRule, 1.1446 + this.selectorIndex); 1.1447 + 1.1448 + return this._specificity; 1.1449 + }, 1.1450 + 1.1451 + toString: function CssSelector_toString() 1.1452 + { 1.1453 + return this.text; 1.1454 + }, 1.1455 +}; 1.1456 + 1.1457 +/** 1.1458 + * A cache of information about the matched rules, selectors and values attached 1.1459 + * to a CSS property, for the highlighted element. 1.1460 + * 1.1461 + * The heart of the CssPropertyInfo object is the _findMatchedSelectors() 1.1462 + * method. This are invoked when the PropertyView tries to access the 1.1463 + * .matchedSelectors array. 1.1464 + * Results are cached, for later reuse. 1.1465 + * 1.1466 + * @param {CssLogic} aCssLogic Reference to the parent CssLogic instance 1.1467 + * @param {string} aProperty The CSS property we are gathering information for 1.1468 + * @constructor 1.1469 + */ 1.1470 +function CssPropertyInfo(aCssLogic, aProperty) 1.1471 +{ 1.1472 + this._cssLogic = aCssLogic; 1.1473 + this.property = aProperty; 1.1474 + this._value = ""; 1.1475 + 1.1476 + // The number of matched rules holding the this.property style property. 1.1477 + // Additionally, only rules that come from allowed stylesheets are counted. 1.1478 + this._matchedRuleCount = 0; 1.1479 + 1.1480 + // An array holding CssSelectorInfo objects for each of the matched selectors 1.1481 + // that are inside a CSS rule. Only rules that hold the this.property are 1.1482 + // counted. This includes rules that come from filtered stylesheets (those 1.1483 + // that have sheetAllowed = false). 1.1484 + this._matchedSelectors = null; 1.1485 +} 1.1486 + 1.1487 +CssPropertyInfo.prototype = { 1.1488 + /** 1.1489 + * Retrieve the computed style value for the current property, for the 1.1490 + * highlighted element. 1.1491 + * 1.1492 + * @return {string} the computed style value for the current property, for the 1.1493 + * highlighted element. 1.1494 + */ 1.1495 + get value() 1.1496 + { 1.1497 + if (!this._value && this._cssLogic._computedStyle) { 1.1498 + try { 1.1499 + this._value = this._cssLogic._computedStyle.getPropertyValue(this.property); 1.1500 + } catch (ex) { 1.1501 + Services.console.logStringMessage('Error reading computed style for ' + 1.1502 + this.property); 1.1503 + Services.console.logStringMessage(ex); 1.1504 + } 1.1505 + } 1.1506 + return this._value; 1.1507 + }, 1.1508 + 1.1509 + /** 1.1510 + * Retrieve the number of matched rules holding the this.property style 1.1511 + * property. Only rules that come from allowed stylesheets are counted. 1.1512 + * 1.1513 + * @return {number} the number of matched rules. 1.1514 + */ 1.1515 + get matchedRuleCount() 1.1516 + { 1.1517 + if (!this._matchedSelectors) { 1.1518 + this._findMatchedSelectors(); 1.1519 + } else if (this.needRefilter) { 1.1520 + this._refilterSelectors(); 1.1521 + } 1.1522 + 1.1523 + return this._matchedRuleCount; 1.1524 + }, 1.1525 + 1.1526 + /** 1.1527 + * Retrieve the array holding CssSelectorInfo objects for each of the matched 1.1528 + * selectors, from each of the matched rules. Only selectors coming from 1.1529 + * allowed stylesheets are included in the array. 1.1530 + * 1.1531 + * @return {array} the list of CssSelectorInfo objects of selectors that match 1.1532 + * the highlighted element and its parents. 1.1533 + */ 1.1534 + get matchedSelectors() 1.1535 + { 1.1536 + if (!this._matchedSelectors) { 1.1537 + this._findMatchedSelectors(); 1.1538 + } else if (this.needRefilter) { 1.1539 + this._refilterSelectors(); 1.1540 + } 1.1541 + 1.1542 + return this._matchedSelectors; 1.1543 + }, 1.1544 + 1.1545 + /** 1.1546 + * Find the selectors that match the highlighted element and its parents. 1.1547 + * Uses CssLogic.processMatchedSelectors() to find the matched selectors, 1.1548 + * passing in a reference to CssPropertyInfo._processMatchedSelector() to 1.1549 + * create CssSelectorInfo objects, which we then sort 1.1550 + * @private 1.1551 + */ 1.1552 + _findMatchedSelectors: function CssPropertyInfo_findMatchedSelectors() 1.1553 + { 1.1554 + this._matchedSelectors = []; 1.1555 + this._matchedRuleCount = 0; 1.1556 + this.needRefilter = false; 1.1557 + 1.1558 + this._cssLogic.processMatchedSelectors(this._processMatchedSelector, this); 1.1559 + 1.1560 + // Sort the selectors by how well they match the given element. 1.1561 + this._matchedSelectors.sort(function(aSelectorInfo1, aSelectorInfo2) { 1.1562 + if (aSelectorInfo1.status > aSelectorInfo2.status) { 1.1563 + return -1; 1.1564 + } else if (aSelectorInfo2.status > aSelectorInfo1.status) { 1.1565 + return 1; 1.1566 + } else { 1.1567 + return aSelectorInfo1.compareTo(aSelectorInfo2); 1.1568 + } 1.1569 + }); 1.1570 + 1.1571 + // Now we know which of the matches is best, we can mark it BEST_MATCH. 1.1572 + if (this._matchedSelectors.length > 0 && 1.1573 + this._matchedSelectors[0].status > CssLogic.STATUS.UNMATCHED) { 1.1574 + this._matchedSelectors[0].status = CssLogic.STATUS.BEST; 1.1575 + } 1.1576 + }, 1.1577 + 1.1578 + /** 1.1579 + * Process a matched CssSelector object. 1.1580 + * 1.1581 + * @private 1.1582 + * @param {CssSelector} aSelector the matched CssSelector object. 1.1583 + * @param {CssLogic.STATUS} aStatus the CssSelector match status. 1.1584 + */ 1.1585 + _processMatchedSelector: function CssPropertyInfo_processMatchedSelector(aSelector, aStatus) 1.1586 + { 1.1587 + let cssRule = aSelector.cssRule; 1.1588 + let value = cssRule.getPropertyValue(this.property); 1.1589 + if (value && 1.1590 + (aStatus == CssLogic.STATUS.MATCHED || 1.1591 + (aStatus == CssLogic.STATUS.PARENT_MATCH && 1.1592 + domUtils.isInheritedProperty(this.property)))) { 1.1593 + let selectorInfo = new CssSelectorInfo(aSelector, this.property, value, 1.1594 + aStatus); 1.1595 + this._matchedSelectors.push(selectorInfo); 1.1596 + if (this._cssLogic._passId !== cssRule._passId && cssRule.sheetAllowed) { 1.1597 + this._matchedRuleCount++; 1.1598 + } 1.1599 + } 1.1600 + }, 1.1601 + 1.1602 + /** 1.1603 + * Refilter the matched selectors array when the CssLogic.sourceFilter 1.1604 + * changes. This allows for quick filter changes. 1.1605 + * @private 1.1606 + */ 1.1607 + _refilterSelectors: function CssPropertyInfo_refilterSelectors() 1.1608 + { 1.1609 + let passId = ++this._cssLogic._passId; 1.1610 + let ruleCount = 0; 1.1611 + 1.1612 + let iterator = function(aSelectorInfo) { 1.1613 + let cssRule = aSelectorInfo.selector.cssRule; 1.1614 + if (cssRule._passId != passId) { 1.1615 + if (cssRule.sheetAllowed) { 1.1616 + ruleCount++; 1.1617 + } 1.1618 + cssRule._passId = passId; 1.1619 + } 1.1620 + }; 1.1621 + 1.1622 + if (this._matchedSelectors) { 1.1623 + this._matchedSelectors.forEach(iterator); 1.1624 + this._matchedRuleCount = ruleCount; 1.1625 + } 1.1626 + 1.1627 + this.needRefilter = false; 1.1628 + }, 1.1629 + 1.1630 + toString: function CssPropertyInfo_toString() 1.1631 + { 1.1632 + return "CssPropertyInfo[" + this.property + "]"; 1.1633 + }, 1.1634 +}; 1.1635 + 1.1636 +/** 1.1637 + * A class that holds information about a given CssSelector object. 1.1638 + * 1.1639 + * Instances of this class are given to CssHtmlTree in the array of matched 1.1640 + * selectors. Each such object represents a displayable row in the PropertyView 1.1641 + * objects. The information given by this object blends data coming from the 1.1642 + * CssSheet, CssRule and from the CssSelector that own this object. 1.1643 + * 1.1644 + * @param {CssSelector} aSelector The CssSelector object for which to present information. 1.1645 + * @param {string} aProperty The property for which information should be retrieved. 1.1646 + * @param {string} aValue The property value from the CssRule that owns the selector. 1.1647 + * @param {CssLogic.STATUS} aStatus The selector match status. 1.1648 + * @constructor 1.1649 + */ 1.1650 +function CssSelectorInfo(aSelector, aProperty, aValue, aStatus) 1.1651 +{ 1.1652 + this.selector = aSelector; 1.1653 + this.property = aProperty; 1.1654 + this.status = aStatus; 1.1655 + this.value = aValue; 1.1656 + let priority = this.selector.cssRule.getPropertyPriority(this.property); 1.1657 + this.important = (priority === "important"); 1.1658 +} 1.1659 + 1.1660 +CssSelectorInfo.prototype = { 1.1661 + /** 1.1662 + * Retrieve the CssSelector source, which is the source of the CssSheet owning 1.1663 + * the selector. 1.1664 + * 1.1665 + * @return {string} the selector source. 1.1666 + */ 1.1667 + get source() 1.1668 + { 1.1669 + return this.selector.source; 1.1670 + }, 1.1671 + 1.1672 + /** 1.1673 + * Retrieve the CssSelector source element, which is the source of the CssRule 1.1674 + * owning the selector. This is only available when the CssSelector comes from 1.1675 + * an element.style. 1.1676 + * 1.1677 + * @return {string} the source element selector. 1.1678 + */ 1.1679 + get sourceElement() 1.1680 + { 1.1681 + return this.selector.sourceElement; 1.1682 + }, 1.1683 + 1.1684 + /** 1.1685 + * Retrieve the address of the CssSelector. This points to the address of the 1.1686 + * CssSheet owning this selector. 1.1687 + * 1.1688 + * @return {string} the address of the CssSelector. 1.1689 + */ 1.1690 + get href() 1.1691 + { 1.1692 + return this.selector.href; 1.1693 + }, 1.1694 + 1.1695 + /** 1.1696 + * Check if the CssSelector comes from element.style or not. 1.1697 + * 1.1698 + * @return {boolean} true if the CssSelector comes from element.style, or 1.1699 + * false otherwise. 1.1700 + */ 1.1701 + get elementStyle() 1.1702 + { 1.1703 + return this.selector.elementStyle; 1.1704 + }, 1.1705 + 1.1706 + /** 1.1707 + * Retrieve specificity information for the current selector. 1.1708 + * 1.1709 + * @return {object} an object holding specificity information for the current 1.1710 + * selector. 1.1711 + */ 1.1712 + get specificity() 1.1713 + { 1.1714 + return this.selector.specificity; 1.1715 + }, 1.1716 + 1.1717 + /** 1.1718 + * Retrieve the parent stylesheet index/position in the viewed document. 1.1719 + * 1.1720 + * @return {number} the parent stylesheet index/position in the viewed 1.1721 + * document. 1.1722 + */ 1.1723 + get sheetIndex() 1.1724 + { 1.1725 + return this.selector.sheetIndex; 1.1726 + }, 1.1727 + 1.1728 + /** 1.1729 + * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter. 1.1730 + * 1.1731 + * @return {boolean} true if the parent stylesheet is allowed by the current 1.1732 + * sourceFilter, or false otherwise. 1.1733 + */ 1.1734 + get sheetAllowed() 1.1735 + { 1.1736 + return this.selector.sheetAllowed; 1.1737 + }, 1.1738 + 1.1739 + /** 1.1740 + * Retrieve the line of the parent CSSStyleRule in the parent CSSStyleSheet. 1.1741 + * 1.1742 + * @return {number} the line of the parent CSSStyleRule in the parent 1.1743 + * stylesheet. 1.1744 + */ 1.1745 + get ruleLine() 1.1746 + { 1.1747 + return this.selector.ruleLine; 1.1748 + }, 1.1749 + 1.1750 + /** 1.1751 + * Check if the selector comes from a browser-provided stylesheet. 1.1752 + * 1.1753 + * @return {boolean} true if the selector comes from a browser-provided 1.1754 + * stylesheet, or false otherwise. 1.1755 + */ 1.1756 + get contentRule() 1.1757 + { 1.1758 + return this.selector.contentRule; 1.1759 + }, 1.1760 + 1.1761 + /** 1.1762 + * Compare the current CssSelectorInfo instance to another instance, based on 1.1763 + * specificity information. 1.1764 + * 1.1765 + * @param {CssSelectorInfo} aThat The instance to compare ourselves against. 1.1766 + * @return number -1, 0, 1 depending on how aThat compares with this. 1.1767 + */ 1.1768 + compareTo: function CssSelectorInfo_compareTo(aThat) 1.1769 + { 1.1770 + if (!this.contentRule && aThat.contentRule) return 1; 1.1771 + if (this.contentRule && !aThat.contentRule) return -1; 1.1772 + 1.1773 + if (this.elementStyle && !aThat.elementStyle) { 1.1774 + if (!this.important && aThat.important) return 1; 1.1775 + else return -1; 1.1776 + } 1.1777 + 1.1778 + if (!this.elementStyle && aThat.elementStyle) { 1.1779 + if (this.important && !aThat.important) return -1; 1.1780 + else return 1; 1.1781 + } 1.1782 + 1.1783 + if (this.important && !aThat.important) return -1; 1.1784 + if (aThat.important && !this.important) return 1; 1.1785 + 1.1786 + if (this.specificity > aThat.specificity) return -1; 1.1787 + if (aThat.specificity > this.specificity) return 1; 1.1788 + 1.1789 + if (this.sheetIndex > aThat.sheetIndex) return -1; 1.1790 + if (aThat.sheetIndex > this.sheetIndex) return 1; 1.1791 + 1.1792 + if (this.ruleLine > aThat.ruleLine) return -1; 1.1793 + if (aThat.ruleLine > this.ruleLine) return 1; 1.1794 + 1.1795 + return 0; 1.1796 + }, 1.1797 + 1.1798 + toString: function CssSelectorInfo_toString() 1.1799 + { 1.1800 + return this.selector + " -> " + this.value; 1.1801 + }, 1.1802 +}; 1.1803 + 1.1804 +XPCOMUtils.defineLazyGetter(this, "domUtils", function() { 1.1805 + return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils); 1.1806 +});