toolkit/devtools/styleinspector/css-logic.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim: set ts=2 et sw=2 tw=80: */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 /*
     8  * About the objects defined in this file:
     9  * - CssLogic contains style information about a view context. It provides
    10  *   access to 2 sets of objects: Css[Sheet|Rule|Selector] provide access to
    11  *   information that does not change when the selected element changes while
    12  *   Css[Property|Selector]Info provide information that is dependent on the
    13  *   selected element.
    14  *   Its key methods are highlight(), getPropertyInfo() and forEachSheet(), etc
    15  *   It also contains a number of static methods for l10n, naming, etc
    16  *
    17  * - CssSheet provides a more useful API to a DOM CSSSheet for our purposes,
    18  *   including shortSource and href.
    19  * - CssRule a more useful API to a nsIDOMCSSRule including access to the group
    20  *   of CssSelectors that the rule provides properties for
    21  * - CssSelector A single selector - i.e. not a selector group. In other words
    22  *   a CssSelector does not contain ','. This terminology is different from the
    23  *   standard DOM API, but more inline with the definition in the spec.
    24  *
    25  * - CssPropertyInfo contains style information for a single property for the
    26  *   highlighted element.
    27  * - CssSelectorInfo is a wrapper around CssSelector, which adds sorting with
    28  *   reference to the selected element.
    29  */
    31 /**
    32  * Provide access to the style information in a page.
    33  * CssLogic uses the standard DOM API, and the Gecko inIDOMUtils API to access
    34  * styling information in the page, and present this to the user in a way that
    35  * helps them understand:
    36  * - why their expectations may not have been fulfilled
    37  * - how browsers process CSS
    38  * @constructor
    39  */
    41 const {Cc, Ci, Cu} = require("chrome");
    43 const RX_UNIVERSAL_SELECTOR = /\s*\*\s*/g;
    44 const RX_NOT = /:not\((.*?)\)/g;
    45 const RX_PSEUDO_CLASS_OR_ELT = /(:[\w-]+\().*?\)/g;
    46 const RX_CONNECTORS = /\s*[\s>+~]\s*/g;
    47 const RX_ID = /\s*#\w+\s*/g;
    48 const RX_CLASS_OR_ATTRIBUTE = /\s*(?:\.\w+|\[.+?\])\s*/g;
    49 const RX_PSEUDO = /\s*:?:([\w-]+)(\(?\)?)\s*/g;
    51 Cu.import("resource://gre/modules/Services.jsm");
    52 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    54 function CssLogic()
    55 {
    56   // The cache of examined CSS properties.
    57   _propertyInfos: {};
    58 }
    60 exports.CssLogic = CssLogic;
    62 /**
    63  * Special values for filter, in addition to an href these values can be used
    64  */
    65 CssLogic.FILTER = {
    66   USER: "user", // show properties for all user style sheets.
    67   UA: "ua",    // USER, plus user-agent (i.e. browser) style sheets
    68 };
    70 /**
    71  * Known media values. To distinguish "all" stylesheets (above) from "all" media
    72  * The full list includes braille, embossed, handheld, print, projection,
    73  * speech, tty, and tv, but this is only a hack because these are not defined
    74  * in the DOM at all.
    75  * @see http://www.w3.org/TR/CSS21/media.html#media-types
    76  */
    77 CssLogic.MEDIA = {
    78   ALL: "all",
    79   SCREEN: "screen",
    80 };
    82 /**
    83  * Each rule has a status, the bigger the number, the better placed it is to
    84  * provide styling information.
    85  *
    86  * These statuses are localized inside the styleinspector.properties string bundle.
    87  * @see csshtmltree.js RuleView._cacheStatusNames()
    88  */
    89 CssLogic.STATUS = {
    90   BEST: 3,
    91   MATCHED: 2,
    92   PARENT_MATCH: 1,
    93   UNMATCHED: 0,
    94   UNKNOWN: -1,
    95 };
    97 CssLogic.prototype = {
    98   // Both setup by highlight().
    99   viewedElement: null,
   100   viewedDocument: null,
   102   // The cache of the known sheets.
   103   _sheets: null,
   105   // Have the sheets been cached?
   106   _sheetsCached: false,
   108   // The total number of rules, in all stylesheets, after filtering.
   109   _ruleCount: 0,
   111   // The computed styles for the viewedElement.
   112   _computedStyle: null,
   114   // Source filter. Only display properties coming from the given source
   115   _sourceFilter: CssLogic.FILTER.USER,
   117   // Used for tracking unique CssSheet/CssRule/CssSelector objects, in a run of
   118   // processMatchedSelectors().
   119   _passId: 0,
   121   // Used for tracking matched CssSelector objects.
   122   _matchId: 0,
   124   _matchedRules: null,
   125   _matchedSelectors: null,
   127   /**
   128    * Reset various properties
   129    */
   130   reset: function CssLogic_reset()
   131   {
   132     this._propertyInfos = {};
   133     this._ruleCount = 0;
   134     this._sheetIndex = 0;
   135     this._sheets = {};
   136     this._sheetsCached = false;
   137     this._matchedRules = null;
   138     this._matchedSelectors = null;
   139   },
   141   /**
   142    * Focus on a new element - remove the style caches.
   143    *
   144    * @param {nsIDOMElement} aViewedElement the element the user has highlighted
   145    * in the Inspector.
   146    */
   147   highlight: function CssLogic_highlight(aViewedElement)
   148   {
   149     if (!aViewedElement) {
   150       this.viewedElement = null;
   151       this.viewedDocument = null;
   152       this._computedStyle = null;
   153       this.reset();
   154       return;
   155     }
   157     this.viewedElement = aViewedElement;
   159     let doc = this.viewedElement.ownerDocument;
   160     if (doc != this.viewedDocument) {
   161       // New document: clear/rebuild the cache.
   162       this.viewedDocument = doc;
   164       // Hunt down top level stylesheets, and cache them.
   165       this._cacheSheets();
   166     } else {
   167       // Clear cached data in the CssPropertyInfo objects.
   168       this._propertyInfos = {};
   169     }
   171     this._matchedRules = null;
   172     this._matchedSelectors = null;
   173     let win = this.viewedDocument.defaultView;
   174     this._computedStyle = win.getComputedStyle(this.viewedElement, "");
   175   },
   177   /**
   178    * Get the source filter.
   179    * @returns {string} The source filter being used.
   180    */
   181   get sourceFilter() {
   182     return this._sourceFilter;
   183   },
   185   /**
   186    * Source filter. Only display properties coming from the given source (web
   187    * address). Note that in order to avoid information overload we DO NOT show
   188    * unmatched system rules.
   189    * @see CssLogic.FILTER.*
   190    */
   191   set sourceFilter(aValue) {
   192     let oldValue = this._sourceFilter;
   193     this._sourceFilter = aValue;
   195     let ruleCount = 0;
   197     // Update the CssSheet objects.
   198     this.forEachSheet(function(aSheet) {
   199       aSheet._sheetAllowed = -1;
   200       if (aSheet.contentSheet && aSheet.sheetAllowed) {
   201         ruleCount += aSheet.ruleCount;
   202       }
   203     }, this);
   205     this._ruleCount = ruleCount;
   207     // Full update is needed because the this.processMatchedSelectors() method
   208     // skips UA stylesheets if the filter does not allow such sheets.
   209     let needFullUpdate = (oldValue == CssLogic.FILTER.UA ||
   210         aValue == CssLogic.FILTER.UA);
   212     if (needFullUpdate) {
   213       this._matchedRules = null;
   214       this._matchedSelectors = null;
   215       this._propertyInfos = {};
   216     } else {
   217       // Update the CssPropertyInfo objects.
   218       for each (let propertyInfo in this._propertyInfos) {
   219         propertyInfo.needRefilter = true;
   220       }
   221     }
   222   },
   224   /**
   225    * Return a CssPropertyInfo data structure for the currently viewed element
   226    * and the specified CSS property. If there is no currently viewed element we
   227    * return an empty object.
   228    *
   229    * @param {string} aProperty The CSS property to look for.
   230    * @return {CssPropertyInfo} a CssPropertyInfo structure for the given
   231    * property.
   232    */
   233   getPropertyInfo: function CssLogic_getPropertyInfo(aProperty)
   234   {
   235     if (!this.viewedElement) {
   236       return {};
   237     }
   239     let info = this._propertyInfos[aProperty];
   240     if (!info) {
   241       info = new CssPropertyInfo(this, aProperty);
   242       this._propertyInfos[aProperty] = info;
   243     }
   245     return info;
   246   },
   248   /**
   249    * Cache all the stylesheets in the inspected document
   250    * @private
   251    */
   252   _cacheSheets: function CssLogic_cacheSheets()
   253   {
   254     this._passId++;
   255     this.reset();
   257     // styleSheets isn't an array, but forEach can work on it anyway
   258     Array.prototype.forEach.call(this.viewedDocument.styleSheets,
   259         this._cacheSheet, this);
   261     this._sheetsCached = true;
   262   },
   264   /**
   265    * Cache a stylesheet if it falls within the requirements: if it's enabled,
   266    * and if the @media is allowed. This method also walks through the stylesheet
   267    * cssRules to find @imported rules, to cache the stylesheets of those rules
   268    * as well.
   269    *
   270    * @private
   271    * @param {CSSStyleSheet} aDomSheet the CSSStyleSheet object to cache.
   272    */
   273   _cacheSheet: function CssLogic_cacheSheet(aDomSheet)
   274   {
   275     if (aDomSheet.disabled) {
   276       return;
   277     }
   279     // Only work with stylesheets that have their media allowed.
   280     if (!this.mediaMatches(aDomSheet)) {
   281       return;
   282     }
   284     // Cache the sheet.
   285     let cssSheet = this.getSheet(aDomSheet, this._sheetIndex++);
   286     if (cssSheet._passId != this._passId) {
   287       cssSheet._passId = this._passId;
   289       // Find import rules.
   290       Array.prototype.forEach.call(aDomSheet.cssRules, function(aDomRule) {
   291         if (aDomRule.type == Ci.nsIDOMCSSRule.IMPORT_RULE && aDomRule.styleSheet &&
   292             this.mediaMatches(aDomRule)) {
   293           this._cacheSheet(aDomRule.styleSheet);
   294         }
   295       }, this);
   296     }
   297   },
   299   /**
   300    * Retrieve the list of stylesheets in the document.
   301    *
   302    * @return {array} the list of stylesheets in the document.
   303    */
   304   get sheets()
   305   {
   306     if (!this._sheetsCached) {
   307       this._cacheSheets();
   308     }
   310     let sheets = [];
   311     this.forEachSheet(function (aSheet) {
   312       if (aSheet.contentSheet) {
   313         sheets.push(aSheet);
   314       }
   315     }, this);
   317     return sheets;
   318   },
   320   /**
   321    * Retrieve a CssSheet object for a given a CSSStyleSheet object. If the
   322    * stylesheet is already cached, you get the existing CssSheet object,
   323    * otherwise the new CSSStyleSheet object is cached.
   324    *
   325    * @param {CSSStyleSheet} aDomSheet the CSSStyleSheet object you want.
   326    * @param {number} aIndex the index, within the document, of the stylesheet.
   327    *
   328    * @return {CssSheet} the CssSheet object for the given CSSStyleSheet object.
   329    */
   330   getSheet: function CL_getSheet(aDomSheet, aIndex)
   331   {
   332     let cacheId = "";
   334     if (aDomSheet.href) {
   335       cacheId = aDomSheet.href;
   336     } else if (aDomSheet.ownerNode && aDomSheet.ownerNode.ownerDocument) {
   337       cacheId = aDomSheet.ownerNode.ownerDocument.location;
   338     }
   340     let sheet = null;
   341     let sheetFound = false;
   343     if (cacheId in this._sheets) {
   344       for (let i = 0, numSheets = this._sheets[cacheId].length; i < numSheets; i++) {
   345         sheet = this._sheets[cacheId][i];
   346         if (sheet.domSheet === aDomSheet) {
   347           if (aIndex != -1) {
   348             sheet.index = aIndex;
   349           }
   350           sheetFound = true;
   351           break;
   352         }
   353       }
   354     }
   356     if (!sheetFound) {
   357       if (!(cacheId in this._sheets)) {
   358         this._sheets[cacheId] = [];
   359       }
   361       sheet = new CssSheet(this, aDomSheet, aIndex);
   362       if (sheet.sheetAllowed && sheet.contentSheet) {
   363         this._ruleCount += sheet.ruleCount;
   364       }
   366       this._sheets[cacheId].push(sheet);
   367     }
   369     return sheet;
   370   },
   372   /**
   373    * Process each cached stylesheet in the document using your callback.
   374    *
   375    * @param {function} aCallback the function you want executed for each of the
   376    * CssSheet objects cached.
   377    * @param {object} aScope the scope you want for the callback function. aScope
   378    * will be the this object when aCallback executes.
   379    */
   380   forEachSheet: function CssLogic_forEachSheet(aCallback, aScope)
   381   {
   382     for each (let sheets in this._sheets) {
   383       for (let i = 0; i < sheets.length; i ++) {
   384         // We take this as an opportunity to clean dead sheets
   385         try {
   386           let sheet = sheets[i];
   387           sheet.domSheet; // If accessing domSheet raises an exception, then the
   388           // style sheet is a dead object
   389           aCallback.call(aScope, sheet, i, sheets);
   390         } catch (e) {
   391           sheets.splice(i, 1);
   392           i --;
   393         }
   394       }
   395     }
   396   },
   398   /**
   399    * Process *some* cached stylesheets in the document using your callback. The
   400    * callback function should return true in order to halt processing.
   401    *
   402    * @param {function} aCallback the function you want executed for some of the
   403    * CssSheet objects cached.
   404    * @param {object} aScope the scope you want for the callback function. aScope
   405    * will be the this object when aCallback executes.
   406    * @return {Boolean} true if aCallback returns true during any iteration,
   407    * otherwise false is returned.
   408    */
   409   forSomeSheets: function CssLogic_forSomeSheets(aCallback, aScope)
   410   {
   411     for each (let sheets in this._sheets) {
   412       if (sheets.some(aCallback, aScope)) {
   413         return true;
   414       }
   415     }
   416     return false;
   417   },
   419   /**
   420    * Get the number nsIDOMCSSRule objects in the document, counted from all of
   421    * the stylesheets. System sheets are excluded. If a filter is active, this
   422    * tells only the number of nsIDOMCSSRule objects inside the selected
   423    * CSSStyleSheet.
   424    *
   425    * WARNING: This only provides an estimate of the rule count, and the results
   426    * could change at a later date. Todo remove this
   427    *
   428    * @return {number} the number of nsIDOMCSSRule (all rules).
   429    */
   430   get ruleCount()
   431   {
   432     if (!this._sheetsCached) {
   433       this._cacheSheets();
   434     }
   436     return this._ruleCount;
   437   },
   439   /**
   440    * Process the CssSelector objects that match the highlighted element and its
   441    * parent elements. aScope.aCallback() is executed for each CssSelector
   442    * object, being passed the CssSelector object and the match status.
   443    *
   444    * This method also includes all of the element.style properties, for each
   445    * highlighted element parent and for the highlighted element itself.
   446    *
   447    * Note that the matched selectors are cached, such that next time your
   448    * callback is invoked for the cached list of CssSelector objects.
   449    *
   450    * @param {function} aCallback the function you want to execute for each of
   451    * the matched selectors.
   452    * @param {object} aScope the scope you want for the callback function. aScope
   453    * will be the this object when aCallback executes.
   454    */
   455   processMatchedSelectors: function CL_processMatchedSelectors(aCallback, aScope)
   456   {
   457     if (this._matchedSelectors) {
   458       if (aCallback) {
   459         this._passId++;
   460         this._matchedSelectors.forEach(function(aValue) {
   461           aCallback.call(aScope, aValue[0], aValue[1]);
   462           aValue[0].cssRule._passId = this._passId;
   463         }, this);
   464       }
   465       return;
   466     }
   468     if (!this._matchedRules) {
   469       this._buildMatchedRules();
   470     }
   472     this._matchedSelectors = [];
   473     this._passId++;
   475     for (let i = 0; i < this._matchedRules.length; i++) {
   476       let rule = this._matchedRules[i][0];
   477       let status = this._matchedRules[i][1];
   479       rule.selectors.forEach(function (aSelector) {
   480         if (aSelector._matchId !== this._matchId &&
   481            (aSelector.elementStyle ||
   482             this.selectorMatchesElement(rule.domRule, aSelector.selectorIndex))) {
   484           aSelector._matchId = this._matchId;
   485           this._matchedSelectors.push([ aSelector, status ]);
   486           if (aCallback) {
   487             aCallback.call(aScope, aSelector, status);
   488           }
   489         }
   490       }, this);
   492       rule._passId = this._passId;
   493     }
   494   },
   496   /**
   497    * Check if the given selector matches the highlighted element or any of its
   498    * parents.
   499    *
   500    * @private
   501    * @param {DOMRule} domRule
   502    *        The DOM Rule containing the selector.
   503    * @param {Number} idx
   504    *        The index of the selector within the DOMRule.
   505    * @return {boolean}
   506    *         true if the given selector matches the highlighted element or any
   507    *         of its parents, otherwise false is returned.
   508    */
   509   selectorMatchesElement: function CL_selectorMatchesElement2(domRule, idx)
   510   {
   511     let element = this.viewedElement;
   512     do {
   513       if (domUtils.selectorMatchesElement(element, domRule, idx)) {
   514         return true;
   515       }
   516     } while ((element = element.parentNode) &&
   517              element.nodeType === Ci.nsIDOMNode.ELEMENT_NODE);
   519     return false;
   520   },
   522   /**
   523    * Check if the highlighted element or it's parents have matched selectors.
   524    *
   525    * @param {array} aProperties The list of properties you want to check if they
   526    * have matched selectors or not.
   527    * @return {object} An object that tells for each property if it has matched
   528    * selectors or not. Object keys are property names and values are booleans.
   529    */
   530   hasMatchedSelectors: function CL_hasMatchedSelectors(aProperties)
   531   {
   532     if (!this._matchedRules) {
   533       this._buildMatchedRules();
   534     }
   536     let result = {};
   538     this._matchedRules.some(function(aValue) {
   539       let rule = aValue[0];
   540       let status = aValue[1];
   541       aProperties = aProperties.filter(function(aProperty) {
   542         // We just need to find if a rule has this property while it matches
   543         // the viewedElement (or its parents).
   544         if (rule.getPropertyValue(aProperty) &&
   545             (status == CssLogic.STATUS.MATCHED ||
   546              (status == CssLogic.STATUS.PARENT_MATCH &&
   547               domUtils.isInheritedProperty(aProperty)))) {
   548           result[aProperty] = true;
   549           return false;
   550         }
   551         return true; // Keep the property for the next rule.
   552       }.bind(this));
   553       return aProperties.length == 0;
   554     }, this);
   556     return result;
   557   },
   559   /**
   560    * Build the array of matched rules for the currently highlighted element.
   561    * The array will hold rules that match the viewedElement and its parents.
   562    *
   563    * @private
   564    */
   565   _buildMatchedRules: function CL__buildMatchedRules()
   566   {
   567     let domRules;
   568     let element = this.viewedElement;
   569     let filter = this.sourceFilter;
   570     let sheetIndex = 0;
   572     this._matchId++;
   573     this._passId++;
   574     this._matchedRules = [];
   576     if (!element) {
   577       return;
   578     }
   580     do {
   581       let status = this.viewedElement === element ?
   582                    CssLogic.STATUS.MATCHED : CssLogic.STATUS.PARENT_MATCH;
   584       try {
   585         domRules = domUtils.getCSSStyleRules(element);
   586       } catch (ex) {
   587         Services.console.
   588           logStringMessage("CL__buildMatchedRules error: " + ex);
   589         continue;
   590       }
   592       for (let i = 0, n = domRules.Count(); i < n; i++) {
   593         let domRule = domRules.GetElementAt(i);
   594         if (domRule.type !== Ci.nsIDOMCSSRule.STYLE_RULE) {
   595           continue;
   596         }
   598         let sheet = this.getSheet(domRule.parentStyleSheet, -1);
   599         if (sheet._passId !== this._passId) {
   600           sheet.index = sheetIndex++;
   601           sheet._passId = this._passId;
   602         }
   604         if (filter === CssLogic.FILTER.USER && !sheet.contentSheet) {
   605           continue;
   606         }
   608         let rule = sheet.getRule(domRule);
   609         if (rule._passId === this._passId) {
   610           continue;
   611         }
   613         rule._matchId = this._matchId;
   614         rule._passId = this._passId;
   615         this._matchedRules.push([rule, status]);
   616       }
   619       // Add element.style information.
   620       if (element.style && element.style.length > 0) {
   621         let rule = new CssRule(null, { style: element.style }, element);
   622         rule._matchId = this._matchId;
   623         rule._passId = this._passId;
   624         this._matchedRules.push([rule, status]);
   625       }
   626     } while ((element = element.parentNode) &&
   627               element.nodeType === Ci.nsIDOMNode.ELEMENT_NODE);
   628   },
   630   /**
   631    * Tells if the given DOM CSS object matches the current view media.
   632    *
   633    * @param {object} aDomObject The DOM CSS object to check.
   634    * @return {boolean} True if the DOM CSS object matches the current view
   635    * media, or false otherwise.
   636    */
   637   mediaMatches: function CL_mediaMatches(aDomObject)
   638   {
   639     let mediaText = aDomObject.media.mediaText;
   640     return !mediaText || this.viewedDocument.defaultView.
   641                          matchMedia(mediaText).matches;
   642    },
   643 };
   645 /**
   646  * If the element has an id, return '#id'. Otherwise return 'tagname[n]' where
   647  * n is the index of this element in its siblings.
   648  * <p>A technically more 'correct' output from the no-id case might be:
   649  * 'tagname:nth-of-type(n)' however this is unlikely to be more understood
   650  * and it is longer.
   651  *
   652  * @param {nsIDOMElement} aElement the element for which you want the short name.
   653  * @return {string} the string to be displayed for aElement.
   654  */
   655 CssLogic.getShortName = function CssLogic_getShortName(aElement)
   656 {
   657   if (!aElement) {
   658     return "null";
   659   }
   660   if (aElement.id) {
   661     return "#" + aElement.id;
   662   }
   663   let priorSiblings = 0;
   664   let temp = aElement;
   665   while (temp = temp.previousElementSibling) {
   666     priorSiblings++;
   667   }
   668   return aElement.tagName + "[" + priorSiblings + "]";
   669 };
   671 /**
   672  * Get an array of short names from the given element to document.body.
   673  *
   674  * @param {nsIDOMElement} aElement the element for which you want the array of
   675  * short names.
   676  * @return {array} The array of elements.
   677  * <p>Each element is an object of the form:
   678  * <ul>
   679  * <li>{ display: "what to display for the given (parent) element",
   680  * <li>  element: referenceToTheElement }
   681  * </ul>
   682  */
   683 CssLogic.getShortNamePath = function CssLogic_getShortNamePath(aElement)
   684 {
   685   let doc = aElement.ownerDocument;
   686   let reply = [];
   688   if (!aElement) {
   689     return reply;
   690   }
   692   // We want to exclude nodes high up the tree (body/html) unless the user
   693   // has selected that node, in which case we need to report something.
   694   do {
   695     reply.unshift({
   696       display: CssLogic.getShortName(aElement),
   697       element: aElement
   698     });
   699     aElement = aElement.parentNode;
   700   } while (aElement && aElement != doc.body && aElement != doc.head && aElement != doc);
   702   return reply;
   703 };
   705 /**
   706  * Get a string list of selectors for a given DOMRule.
   707  *
   708  * @param {DOMRule} aDOMRule
   709  *        The DOMRule to parse.
   710  * @return {Array}
   711  *         An array of string selectors.
   712  */
   713 CssLogic.getSelectors = function CssLogic_getSelectors(aDOMRule)
   714 {
   715   let selectors = [];
   717   let len = domUtils.getSelectorCount(aDOMRule);
   718   for (let i = 0; i < len; i++) {
   719     let text = domUtils.getSelectorText(aDOMRule, i);
   720     selectors.push(text);
   721   }
   722   return selectors;
   723 }
   725 /**
   726  * Memonized lookup of a l10n string from a string bundle.
   727  * @param {string} aName The key to lookup.
   728  * @returns A localized version of the given key.
   729  */
   730 CssLogic.l10n = function(aName) CssLogic._strings.GetStringFromName(aName);
   732 XPCOMUtils.defineLazyGetter(CssLogic, "_strings", function() Services.strings
   733         .createBundle("chrome://global/locale/devtools/styleinspector.properties"));
   735 /**
   736  * Is the given property sheet a content stylesheet?
   737  *
   738  * @param {CSSStyleSheet} aSheet a stylesheet
   739  * @return {boolean} true if the given stylesheet is a content stylesheet,
   740  * false otherwise.
   741  */
   742 CssLogic.isContentStylesheet = function CssLogic_isContentStylesheet(aSheet)
   743 {
   744   // All sheets with owner nodes have been included by content.
   745   if (aSheet.ownerNode) {
   746     return true;
   747   }
   749   // If the sheet has a CSSImportRule we need to check the parent stylesheet.
   750   if (aSheet.ownerRule instanceof Ci.nsIDOMCSSImportRule) {
   751     return CssLogic.isContentStylesheet(aSheet.parentStyleSheet);
   752   }
   754   return false;
   755 };
   757 /**
   758  * Get a source for a stylesheet, taking into account embedded stylesheets
   759  * for which we need to use document.defaultView.location.href rather than
   760  * sheet.href
   761  *
   762  * @param {CSSStyleSheet} aSheet the DOM object for the style sheet.
   763  * @return {string} the address of the stylesheet.
   764  */
   765 CssLogic.href = function CssLogic_href(aSheet)
   766 {
   767   let href = aSheet.href;
   768   if (!href) {
   769     href = aSheet.ownerNode.ownerDocument.location;
   770   }
   772   return href;
   773 };
   775 /**
   776  * Return a shortened version of a style sheet's source.
   777  *
   778  * @param {CSSStyleSheet} aSheet the DOM object for the style sheet.
   779  */
   780 CssLogic.shortSource = function CssLogic_shortSource(aSheet)
   781 {
   782   // Use a string like "inline" if there is no source href
   783   if (!aSheet || !aSheet.href) {
   784     return CssLogic.l10n("rule.sourceInline");
   785   }
   787   // We try, in turn, the filename, filePath, query string, whole thing
   788   let url = {};
   789   try {
   790     url = Services.io.newURI(aSheet.href, null, null);
   791     url = url.QueryInterface(Ci.nsIURL);
   792   } catch (ex) {
   793     // Some UA-provided stylesheets are not valid URLs.
   794   }
   796   if (url.fileName) {
   797     return url.fileName;
   798   }
   800   if (url.filePath) {
   801     return url.filePath;
   802   }
   804   if (url.query) {
   805     return url.query;
   806   }
   808   let dataUrl = aSheet.href.match(/^(data:[^,]*),/);
   809   return dataUrl ? dataUrl[1] : aSheet.href;
   810 }
   812 /**
   813  * Extract the background image URL (if any) from a property value.
   814  * Used, for example, for the preview tooltip in the rule view and
   815  * computed view.
   816  *
   817  * @param {String} aProperty
   818  * @param {String} aSheetHref
   819  * @return {string} a image URL
   820  */
   821 CssLogic.getBackgroundImageUriFromProperty = function(aProperty, aSheetHref) {
   822   let startToken = "url(", start = aProperty.indexOf(startToken), end;
   823   if (start === -1) {
   824     return null;
   825   }
   827   aProperty = aProperty.substring(start + startToken.length).trim();
   828   let quote = aProperty.substring(0, 1);
   829   if (quote === "'" || quote === '"') {
   830     end = aProperty.search(new RegExp(quote + "\\s*\\)"));
   831     start = 1;
   832   } else {
   833     end = aProperty.indexOf(")");
   834     start = 0;
   835   }
   837   let uri = aProperty.substring(start, end).trim();
   838   if (aSheetHref) {
   839     let IOService = Cc["@mozilla.org/network/io-service;1"]
   840       .getService(Ci.nsIIOService);
   841     let sheetUri = IOService.newURI(aSheetHref, null, null);
   842     uri = sheetUri.resolve(uri);
   843   }
   845   return uri;
   846 }
   848 /**
   849  * Find the position of [element] in [nodeList].
   850  * @returns an index of the match, or -1 if there is no match
   851  */
   852 function positionInNodeList(element, nodeList) {
   853   for (var i = 0; i < nodeList.length; i++) {
   854     if (element === nodeList[i]) {
   855       return i;
   856     }
   857   }
   858   return -1;
   859 }
   861 /**
   862  * Find a unique CSS selector for a given element
   863  * @returns a string such that ele.ownerDocument.querySelector(reply) === ele
   864  * and ele.ownerDocument.querySelectorAll(reply).length === 1
   865  */
   866 CssLogic.findCssSelector = function CssLogic_findCssSelector(ele) {
   867   var document = ele.ownerDocument;
   868   if (ele.id && document.getElementById(ele.id) === ele) {
   869     return '#' + ele.id;
   870   }
   872   // Inherently unique by tag name
   873   var tagName = ele.tagName.toLowerCase();
   874   if (tagName === 'html') {
   875     return 'html';
   876   }
   877   if (tagName === 'head') {
   878     return 'head';
   879   }
   880   if (tagName === 'body') {
   881     return 'body';
   882   }
   884   if (ele.parentNode == null) {
   885     console.log('danger: ' + tagName);
   886   }
   888   // We might be able to find a unique class name
   889   var selector, index, matches;
   890   if (ele.classList.length > 0) {
   891     for (var i = 0; i < ele.classList.length; i++) {
   892       // Is this className unique by itself?
   893       selector = '.' + ele.classList.item(i);
   894       matches = document.querySelectorAll(selector);
   895       if (matches.length === 1) {
   896         return selector;
   897       }
   898       // Maybe it's unique with a tag name?
   899       selector = tagName + selector;
   900       matches = document.querySelectorAll(selector);
   901       if (matches.length === 1) {
   902         return selector;
   903       }
   904       // Maybe it's unique using a tag name and nth-child
   905       index = positionInNodeList(ele, ele.parentNode.children) + 1;
   906       selector = selector + ':nth-child(' + index + ')';
   907       matches = document.querySelectorAll(selector);
   908       if (matches.length === 1) {
   909         return selector;
   910       }
   911     }
   912   }
   914   // So we can be unique w.r.t. our parent, and use recursion
   915   index = positionInNodeList(ele, ele.parentNode.children) + 1;
   916   selector = CssLogic_findCssSelector(ele.parentNode) + ' > ' +
   917           tagName + ':nth-child(' + index + ')';
   919   return selector;
   920 };
   922 /**
   923  * A safe way to access cached bits of information about a stylesheet.
   924  *
   925  * @constructor
   926  * @param {CssLogic} aCssLogic pointer to the CssLogic instance working with
   927  * this CssSheet object.
   928  * @param {CSSStyleSheet} aDomSheet reference to a DOM CSSStyleSheet object.
   929  * @param {number} aIndex tells the index/position of the stylesheet within the
   930  * main document.
   931  */
   932 function CssSheet(aCssLogic, aDomSheet, aIndex)
   933 {
   934   this._cssLogic = aCssLogic;
   935   this.domSheet = aDomSheet;
   936   this.index = this.contentSheet ? aIndex : -100 * aIndex;
   938   // Cache of the sheets href. Cached by the getter.
   939   this._href = null;
   940   // Short version of href for use in select boxes etc. Cached by getter.
   941   this._shortSource = null;
   943   // null for uncached.
   944   this._sheetAllowed = null;
   946   // Cached CssRules from the given stylesheet.
   947   this._rules = {};
   949   this._ruleCount = -1;
   950 }
   952 CssSheet.prototype = {
   953   _passId: null,
   954   _contentSheet: null,
   955   _mediaMatches: null,
   957   /**
   958    * Tells if the stylesheet is provided by the browser or not.
   959    *
   960    * @return {boolean} false if this is a browser-provided stylesheet, or true
   961    * otherwise.
   962    */
   963   get contentSheet()
   964   {
   965     if (this._contentSheet === null) {
   966       this._contentSheet = CssLogic.isContentStylesheet(this.domSheet);
   967     }
   968     return this._contentSheet;
   969   },
   971   /**
   972    * Tells if the stylesheet is disabled or not.
   973    * @return {boolean} true if this stylesheet is disabled, or false otherwise.
   974    */
   975   get disabled()
   976   {
   977     return this.domSheet.disabled;
   978   },
   980   /**
   981    * Tells if the stylesheet matches the current browser view media.
   982    * @return {boolean} true if this stylesheet matches the current browser view
   983    * media, or false otherwise.
   984    */
   985   get mediaMatches()
   986   {
   987     if (this._mediaMatches === null) {
   988       this._mediaMatches = this._cssLogic.mediaMatches(this.domSheet);
   989     }
   990     return this._mediaMatches;
   991   },
   993   /**
   994    * Get a source for a stylesheet, using CssLogic.href
   995    *
   996    * @return {string} the address of the stylesheet.
   997    */
   998   get href()
   999   {
  1000     if (this._href) {
  1001       return this._href;
  1004     this._href = CssLogic.href(this.domSheet);
  1005     return this._href;
  1006   },
  1008   /**
  1009    * Create a shorthand version of the href of a stylesheet.
  1011    * @return {string} the shorthand source of the stylesheet.
  1012    */
  1013   get shortSource()
  1015     if (this._shortSource) {
  1016       return this._shortSource;
  1019     this._shortSource = CssLogic.shortSource(this.domSheet);
  1020     return this._shortSource;
  1021   },
  1023   /**
  1024    * Tells if the sheet is allowed or not by the current CssLogic.sourceFilter.
  1026    * @return {boolean} true if the stylesheet is allowed by the sourceFilter, or
  1027    * false otherwise.
  1028    */
  1029   get sheetAllowed()
  1031     if (this._sheetAllowed !== null) {
  1032       return this._sheetAllowed;
  1035     this._sheetAllowed = true;
  1037     let filter = this._cssLogic.sourceFilter;
  1038     if (filter === CssLogic.FILTER.USER && !this.contentSheet) {
  1039       this._sheetAllowed = false;
  1041     if (filter !== CssLogic.FILTER.USER && filter !== CssLogic.FILTER.UA) {
  1042       this._sheetAllowed = (filter === this.href);
  1045     return this._sheetAllowed;
  1046   },
  1048   /**
  1049    * Retrieve the number of rules in this stylesheet.
  1051    * @return {number} the number of nsIDOMCSSRule objects in this stylesheet.
  1052    */
  1053   get ruleCount()
  1055     return this._ruleCount > -1 ?
  1056       this._ruleCount :
  1057       this.domSheet.cssRules.length;
  1058   },
  1060   /**
  1061    * Retrieve a CssRule object for the given CSSStyleRule. The CssRule object is
  1062    * cached, such that subsequent retrievals return the same CssRule object for
  1063    * the same CSSStyleRule object.
  1065    * @param {CSSStyleRule} aDomRule the CSSStyleRule object for which you want a
  1066    * CssRule object.
  1067    * @return {CssRule} the cached CssRule object for the given CSSStyleRule
  1068    * object.
  1069    */
  1070   getRule: function CssSheet_getRule(aDomRule)
  1072     let cacheId = aDomRule.type + aDomRule.selectorText;
  1074     let rule = null;
  1075     let ruleFound = false;
  1077     if (cacheId in this._rules) {
  1078       for (let i = 0, rulesLen = this._rules[cacheId].length; i < rulesLen; i++) {
  1079         rule = this._rules[cacheId][i];
  1080         if (rule.domRule === aDomRule) {
  1081           ruleFound = true;
  1082           break;
  1087     if (!ruleFound) {
  1088       if (!(cacheId in this._rules)) {
  1089         this._rules[cacheId] = [];
  1092       rule = new CssRule(this, aDomRule);
  1093       this._rules[cacheId].push(rule);
  1096     return rule;
  1097   },
  1099   /**
  1100    * Process each rule in this stylesheet using your callback function. Your
  1101    * function receives one argument: the CssRule object for each CSSStyleRule
  1102    * inside the stylesheet.
  1104    * Note that this method also iterates through @media rules inside the
  1105    * stylesheet.
  1107    * @param {function} aCallback the function you want to execute for each of
  1108    * the style rules.
  1109    * @param {object} aScope the scope you want for the callback function. aScope
  1110    * will be the this object when aCallback executes.
  1111    */
  1112   forEachRule: function CssSheet_forEachRule(aCallback, aScope)
  1114     let ruleCount = 0;
  1115     let domRules = this.domSheet.cssRules;
  1117     function _iterator(aDomRule) {
  1118       if (aDomRule.type == Ci.nsIDOMCSSRule.STYLE_RULE) {
  1119         aCallback.call(aScope, this.getRule(aDomRule));
  1120         ruleCount++;
  1121       } else if (aDomRule.type == Ci.nsIDOMCSSRule.MEDIA_RULE &&
  1122           aDomRule.cssRules && this._cssLogic.mediaMatches(aDomRule)) {
  1123         Array.prototype.forEach.call(aDomRule.cssRules, _iterator, this);
  1127     Array.prototype.forEach.call(domRules, _iterator, this);
  1129     this._ruleCount = ruleCount;
  1130   },
  1132   /**
  1133    * Process *some* rules in this stylesheet using your callback function. Your
  1134    * function receives one argument: the CssRule object for each CSSStyleRule
  1135    * inside the stylesheet. In order to stop processing the callback function
  1136    * needs to return a value.
  1138    * Note that this method also iterates through @media rules inside the
  1139    * stylesheet.
  1141    * @param {function} aCallback the function you want to execute for each of
  1142    * the style rules.
  1143    * @param {object} aScope the scope you want for the callback function. aScope
  1144    * will be the this object when aCallback executes.
  1145    * @return {Boolean} true if aCallback returns true during any iteration,
  1146    * otherwise false is returned.
  1147    */
  1148   forSomeRules: function CssSheet_forSomeRules(aCallback, aScope)
  1150     let domRules = this.domSheet.cssRules;
  1151     function _iterator(aDomRule) {
  1152       if (aDomRule.type == Ci.nsIDOMCSSRule.STYLE_RULE) {
  1153         return aCallback.call(aScope, this.getRule(aDomRule));
  1154       } else if (aDomRule.type == Ci.nsIDOMCSSRule.MEDIA_RULE &&
  1155           aDomRule.cssRules && this._cssLogic.mediaMatches(aDomRule)) {
  1156         return Array.prototype.some.call(aDomRule.cssRules, _iterator, this);
  1159     return Array.prototype.some.call(domRules, _iterator, this);
  1160   },
  1162   toString: function CssSheet_toString()
  1164     return "CssSheet[" + this.shortSource + "]";
  1166 };
  1168 /**
  1169  * Information about a single CSSStyleRule.
  1171  * @param {CSSSheet|null} aCssSheet the CssSheet object of the stylesheet that
  1172  * holds the CSSStyleRule. If the rule comes from element.style, set this
  1173  * argument to null.
  1174  * @param {CSSStyleRule|object} aDomRule the DOM CSSStyleRule for which you want
  1175  * to cache data. If the rule comes from element.style, then provide
  1176  * an object of the form: {style: element.style}.
  1177  * @param {Element} [aElement] If the rule comes from element.style, then this
  1178  * argument must point to the element.
  1179  * @constructor
  1180  */
  1181 function CssRule(aCssSheet, aDomRule, aElement)
  1183   this._cssSheet = aCssSheet;
  1184   this.domRule = aDomRule;
  1186   let parentRule = aDomRule.parentRule;
  1187   if (parentRule && parentRule.type == Ci.nsIDOMCSSRule.MEDIA_RULE) {
  1188     this.mediaText = parentRule.media.mediaText;
  1191   if (this._cssSheet) {
  1192     // parse domRule.selectorText on call to this.selectors
  1193     this._selectors = null;
  1194     this.line = domUtils.getRuleLine(this.domRule);
  1195     this.source = this._cssSheet.shortSource + ":" + this.line;
  1196     if (this.mediaText) {
  1197       this.source += " @media " + this.mediaText;
  1199     this.href = this._cssSheet.href;
  1200     this.contentRule = this._cssSheet.contentSheet;
  1201   } else if (aElement) {
  1202     this._selectors = [ new CssSelector(this, "@element.style", 0) ];
  1203     this.line = -1;
  1204     this.source = CssLogic.l10n("rule.sourceElement");
  1205     this.href = "#";
  1206     this.contentRule = true;
  1207     this.sourceElement = aElement;
  1211 CssRule.prototype = {
  1212   _passId: null,
  1214   mediaText: "",
  1216   get isMediaRule()
  1218     return !!this.mediaText;
  1219   },
  1221   /**
  1222    * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter.
  1224    * @return {boolean} true if the parent stylesheet is allowed by the current
  1225    * sourceFilter, or false otherwise.
  1226    */
  1227   get sheetAllowed()
  1229     return this._cssSheet ? this._cssSheet.sheetAllowed : true;
  1230   },
  1232   /**
  1233    * Retrieve the parent stylesheet index/position in the viewed document.
  1235    * @return {number} the parent stylesheet index/position in the viewed
  1236    * document.
  1237    */
  1238   get sheetIndex()
  1240     return this._cssSheet ? this._cssSheet.index : 0;
  1241   },
  1243   /**
  1244    * Retrieve the style property value from the current CSSStyleRule.
  1246    * @param {string} aProperty the CSS property name for which you want the
  1247    * value.
  1248    * @return {string} the property value.
  1249    */
  1250   getPropertyValue: function(aProperty)
  1252     return this.domRule.style.getPropertyValue(aProperty);
  1253   },
  1255   /**
  1256    * Retrieve the style property priority from the current CSSStyleRule.
  1258    * @param {string} aProperty the CSS property name for which you want the
  1259    * priority.
  1260    * @return {string} the property priority.
  1261    */
  1262   getPropertyPriority: function(aProperty)
  1264     return this.domRule.style.getPropertyPriority(aProperty);
  1265   },
  1267   /**
  1268    * Retrieve the list of CssSelector objects for each of the parsed selectors
  1269    * of the current CSSStyleRule.
  1271    * @return {array} the array hold the CssSelector objects.
  1272    */
  1273   get selectors()
  1275     if (this._selectors) {
  1276       return this._selectors;
  1279     // Parse the CSSStyleRule.selectorText string.
  1280     this._selectors = [];
  1282     if (!this.domRule.selectorText) {
  1283       return this._selectors;
  1286     let selectors = CssLogic.getSelectors(this.domRule);
  1288     for (let i = 0, len = selectors.length; i < len; i++) {
  1289       this._selectors.push(new CssSelector(this, selectors[i], i));
  1292     return this._selectors;
  1293   },
  1295   toString: function CssRule_toString()
  1297     return "[CssRule " + this.domRule.selectorText + "]";
  1298   },
  1299 };
  1301 /**
  1302  * The CSS selector class allows us to document the ranking of various CSS
  1303  * selectors.
  1305  * @constructor
  1306  * @param {CssRule} aCssRule the CssRule instance from where the selector comes.
  1307  * @param {string} aSelector The selector that we wish to investigate.
  1308  * @param {Number} aIndex The index of the selector within it's rule.
  1309  */
  1310 function CssSelector(aCssRule, aSelector, aIndex)
  1312   this.cssRule = aCssRule;
  1313   this.text = aSelector;
  1314   this.elementStyle = this.text == "@element.style";
  1315   this._specificity = null;
  1316   this.selectorIndex = aIndex;
  1319 exports.CssSelector = CssSelector;
  1321 CssSelector.prototype = {
  1322   _matchId: null,
  1324   /**
  1325    * Retrieve the CssSelector source, which is the source of the CssSheet owning
  1326    * the selector.
  1328    * @return {string} the selector source.
  1329    */
  1330   get source()
  1332     return this.cssRule.source;
  1333   },
  1335   /**
  1336    * Retrieve the CssSelector source element, which is the source of the CssRule
  1337    * owning the selector. This is only available when the CssSelector comes from
  1338    * an element.style.
  1340    * @return {string} the source element selector.
  1341    */
  1342   get sourceElement()
  1344     return this.cssRule.sourceElement;
  1345   },
  1347   /**
  1348    * Retrieve the address of the CssSelector. This points to the address of the
  1349    * CssSheet owning this selector.
  1351    * @return {string} the address of the CssSelector.
  1352    */
  1353   get href()
  1355     return this.cssRule.href;
  1356   },
  1358   /**
  1359    * Check if the selector comes from a browser-provided stylesheet.
  1361    * @return {boolean} true if the selector comes from a content-provided
  1362    * stylesheet, or false otherwise.
  1363    */
  1364   get contentRule()
  1366     return this.cssRule.contentRule;
  1367   },
  1369   /**
  1370    * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter.
  1372    * @return {boolean} true if the parent stylesheet is allowed by the current
  1373    * sourceFilter, or false otherwise.
  1374    */
  1375   get sheetAllowed()
  1377     return this.cssRule.sheetAllowed;
  1378   },
  1380   /**
  1381    * Retrieve the parent stylesheet index/position in the viewed document.
  1383    * @return {number} the parent stylesheet index/position in the viewed
  1384    * document.
  1385    */
  1386   get sheetIndex()
  1388     return this.cssRule.sheetIndex;
  1389   },
  1391   /**
  1392    * Retrieve the line of the parent CSSStyleRule in the parent CSSStyleSheet.
  1394    * @return {number} the line of the parent CSSStyleRule in the parent
  1395    * stylesheet.
  1396    */
  1397   get ruleLine()
  1399     return this.cssRule.line;
  1400   },
  1402   /**
  1403    * Retrieve the pseudo-elements that we support. This list should match the
  1404    * elements specified in layout/style/nsCSSPseudoElementList.h
  1405    */
  1406   get pseudoElements()
  1408     if (!CssSelector._pseudoElements) {
  1409       let pseudos = CssSelector._pseudoElements = new Set();
  1410       pseudos.add("after");
  1411       pseudos.add("before");
  1412       pseudos.add("first-letter");
  1413       pseudos.add("first-line");
  1414       pseudos.add("selection");
  1415       pseudos.add("-moz-color-swatch");
  1416       pseudos.add("-moz-focus-inner");
  1417       pseudos.add("-moz-focus-outer");
  1418       pseudos.add("-moz-list-bullet");
  1419       pseudos.add("-moz-list-number");
  1420       pseudos.add("-moz-math-anonymous");
  1421       pseudos.add("-moz-math-stretchy");
  1422       pseudos.add("-moz-progress-bar");
  1423       pseudos.add("-moz-selection");
  1425     return CssSelector._pseudoElements;
  1426   },
  1428   /**
  1429    * Retrieve specificity information for the current selector.
  1431    * @see http://www.w3.org/TR/css3-selectors/#specificity
  1432    * @see http://www.w3.org/TR/CSS2/selector.html
  1434    * @return {Number} The selector's specificity.
  1435    */
  1436   get specificity()
  1438     if (this._specificity) {
  1439       return this._specificity;
  1442     this._specificity = domUtils.getSpecificity(this.cssRule.domRule,
  1443                                                 this.selectorIndex);
  1445     return this._specificity;
  1446   },
  1448   toString: function CssSelector_toString()
  1450     return this.text;
  1451   },
  1452 };
  1454 /**
  1455  * A cache of information about the matched rules, selectors and values attached
  1456  * to a CSS property, for the highlighted element.
  1458  * The heart of the CssPropertyInfo object is the _findMatchedSelectors()
  1459  * method. This are invoked when the PropertyView tries to access the
  1460  * .matchedSelectors array.
  1461  * Results are cached, for later reuse.
  1463  * @param {CssLogic} aCssLogic Reference to the parent CssLogic instance
  1464  * @param {string} aProperty The CSS property we are gathering information for
  1465  * @constructor
  1466  */
  1467 function CssPropertyInfo(aCssLogic, aProperty)
  1469   this._cssLogic = aCssLogic;
  1470   this.property = aProperty;
  1471   this._value = "";
  1473   // The number of matched rules holding the this.property style property.
  1474   // Additionally, only rules that come from allowed stylesheets are counted.
  1475   this._matchedRuleCount = 0;
  1477   // An array holding CssSelectorInfo objects for each of the matched selectors
  1478   // that are inside a CSS rule. Only rules that hold the this.property are
  1479   // counted. This includes rules that come from filtered stylesheets (those
  1480   // that have sheetAllowed = false).
  1481   this._matchedSelectors = null;
  1484 CssPropertyInfo.prototype = {
  1485   /**
  1486    * Retrieve the computed style value for the current property, for the
  1487    * highlighted element.
  1489    * @return {string} the computed style value for the current property, for the
  1490    * highlighted element.
  1491    */
  1492   get value()
  1494     if (!this._value && this._cssLogic._computedStyle) {
  1495       try {
  1496         this._value = this._cssLogic._computedStyle.getPropertyValue(this.property);
  1497       } catch (ex) {
  1498         Services.console.logStringMessage('Error reading computed style for ' +
  1499           this.property);
  1500         Services.console.logStringMessage(ex);
  1503     return this._value;
  1504   },
  1506   /**
  1507    * Retrieve the number of matched rules holding the this.property style
  1508    * property. Only rules that come from allowed stylesheets are counted.
  1510    * @return {number} the number of matched rules.
  1511    */
  1512   get matchedRuleCount()
  1514     if (!this._matchedSelectors) {
  1515       this._findMatchedSelectors();
  1516     } else if (this.needRefilter) {
  1517       this._refilterSelectors();
  1520     return this._matchedRuleCount;
  1521   },
  1523   /**
  1524    * Retrieve the array holding CssSelectorInfo objects for each of the matched
  1525    * selectors, from each of the matched rules. Only selectors coming from
  1526    * allowed stylesheets are included in the array.
  1528    * @return {array} the list of CssSelectorInfo objects of selectors that match
  1529    * the highlighted element and its parents.
  1530    */
  1531   get matchedSelectors()
  1533     if (!this._matchedSelectors) {
  1534       this._findMatchedSelectors();
  1535     } else if (this.needRefilter) {
  1536       this._refilterSelectors();
  1539     return this._matchedSelectors;
  1540   },
  1542   /**
  1543    * Find the selectors that match the highlighted element and its parents.
  1544    * Uses CssLogic.processMatchedSelectors() to find the matched selectors,
  1545    * passing in a reference to CssPropertyInfo._processMatchedSelector() to
  1546    * create CssSelectorInfo objects, which we then sort
  1547    * @private
  1548    */
  1549   _findMatchedSelectors: function CssPropertyInfo_findMatchedSelectors()
  1551     this._matchedSelectors = [];
  1552     this._matchedRuleCount = 0;
  1553     this.needRefilter = false;
  1555     this._cssLogic.processMatchedSelectors(this._processMatchedSelector, this);
  1557     // Sort the selectors by how well they match the given element.
  1558     this._matchedSelectors.sort(function(aSelectorInfo1, aSelectorInfo2) {
  1559       if (aSelectorInfo1.status > aSelectorInfo2.status) {
  1560         return -1;
  1561       } else if (aSelectorInfo2.status > aSelectorInfo1.status) {
  1562         return 1;
  1563       } else {
  1564         return aSelectorInfo1.compareTo(aSelectorInfo2);
  1566     });
  1568     // Now we know which of the matches is best, we can mark it BEST_MATCH.
  1569     if (this._matchedSelectors.length > 0 &&
  1570         this._matchedSelectors[0].status > CssLogic.STATUS.UNMATCHED) {
  1571       this._matchedSelectors[0].status = CssLogic.STATUS.BEST;
  1573   },
  1575   /**
  1576    * Process a matched CssSelector object.
  1578    * @private
  1579    * @param {CssSelector} aSelector the matched CssSelector object.
  1580    * @param {CssLogic.STATUS} aStatus the CssSelector match status.
  1581    */
  1582   _processMatchedSelector: function CssPropertyInfo_processMatchedSelector(aSelector, aStatus)
  1584     let cssRule = aSelector.cssRule;
  1585     let value = cssRule.getPropertyValue(this.property);
  1586     if (value &&
  1587         (aStatus == CssLogic.STATUS.MATCHED ||
  1588          (aStatus == CssLogic.STATUS.PARENT_MATCH &&
  1589           domUtils.isInheritedProperty(this.property)))) {
  1590       let selectorInfo = new CssSelectorInfo(aSelector, this.property, value,
  1591           aStatus);
  1592       this._matchedSelectors.push(selectorInfo);
  1593       if (this._cssLogic._passId !== cssRule._passId && cssRule.sheetAllowed) {
  1594         this._matchedRuleCount++;
  1597   },
  1599   /**
  1600    * Refilter the matched selectors array when the CssLogic.sourceFilter
  1601    * changes. This allows for quick filter changes.
  1602    * @private
  1603    */
  1604   _refilterSelectors: function CssPropertyInfo_refilterSelectors()
  1606     let passId = ++this._cssLogic._passId;
  1607     let ruleCount = 0;
  1609     let iterator = function(aSelectorInfo) {
  1610       let cssRule = aSelectorInfo.selector.cssRule;
  1611       if (cssRule._passId != passId) {
  1612         if (cssRule.sheetAllowed) {
  1613           ruleCount++;
  1615         cssRule._passId = passId;
  1617     };
  1619     if (this._matchedSelectors) {
  1620       this._matchedSelectors.forEach(iterator);
  1621       this._matchedRuleCount = ruleCount;
  1624     this.needRefilter = false;
  1625   },
  1627   toString: function CssPropertyInfo_toString()
  1629     return "CssPropertyInfo[" + this.property + "]";
  1630   },
  1631 };
  1633 /**
  1634  * A class that holds information about a given CssSelector object.
  1636  * Instances of this class are given to CssHtmlTree in the array of matched
  1637  * selectors. Each such object represents a displayable row in the PropertyView
  1638  * objects. The information given by this object blends data coming from the
  1639  * CssSheet, CssRule and from the CssSelector that own this object.
  1641  * @param {CssSelector} aSelector The CssSelector object for which to present information.
  1642  * @param {string} aProperty The property for which information should be retrieved.
  1643  * @param {string} aValue The property value from the CssRule that owns the selector.
  1644  * @param {CssLogic.STATUS} aStatus The selector match status.
  1645  * @constructor
  1646  */
  1647 function CssSelectorInfo(aSelector, aProperty, aValue, aStatus)
  1649   this.selector = aSelector;
  1650   this.property = aProperty;
  1651   this.status = aStatus;
  1652   this.value = aValue;
  1653   let priority = this.selector.cssRule.getPropertyPriority(this.property);
  1654   this.important = (priority === "important");
  1657 CssSelectorInfo.prototype = {
  1658   /**
  1659    * Retrieve the CssSelector source, which is the source of the CssSheet owning
  1660    * the selector.
  1662    * @return {string} the selector source.
  1663    */
  1664   get source()
  1666     return this.selector.source;
  1667   },
  1669   /**
  1670    * Retrieve the CssSelector source element, which is the source of the CssRule
  1671    * owning the selector. This is only available when the CssSelector comes from
  1672    * an element.style.
  1674    * @return {string} the source element selector.
  1675    */
  1676   get sourceElement()
  1678     return this.selector.sourceElement;
  1679   },
  1681   /**
  1682    * Retrieve the address of the CssSelector. This points to the address of the
  1683    * CssSheet owning this selector.
  1685    * @return {string} the address of the CssSelector.
  1686    */
  1687   get href()
  1689     return this.selector.href;
  1690   },
  1692   /**
  1693    * Check if the CssSelector comes from element.style or not.
  1695    * @return {boolean} true if the CssSelector comes from element.style, or
  1696    * false otherwise.
  1697    */
  1698   get elementStyle()
  1700     return this.selector.elementStyle;
  1701   },
  1703   /**
  1704    * Retrieve specificity information for the current selector.
  1706    * @return {object} an object holding specificity information for the current
  1707    * selector.
  1708    */
  1709   get specificity()
  1711     return this.selector.specificity;
  1712   },
  1714   /**
  1715    * Retrieve the parent stylesheet index/position in the viewed document.
  1717    * @return {number} the parent stylesheet index/position in the viewed
  1718    * document.
  1719    */
  1720   get sheetIndex()
  1722     return this.selector.sheetIndex;
  1723   },
  1725   /**
  1726    * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter.
  1728    * @return {boolean} true if the parent stylesheet is allowed by the current
  1729    * sourceFilter, or false otherwise.
  1730    */
  1731   get sheetAllowed()
  1733     return this.selector.sheetAllowed;
  1734   },
  1736   /**
  1737    * Retrieve the line of the parent CSSStyleRule in the parent CSSStyleSheet.
  1739    * @return {number} the line of the parent CSSStyleRule in the parent
  1740    * stylesheet.
  1741    */
  1742   get ruleLine()
  1744     return this.selector.ruleLine;
  1745   },
  1747   /**
  1748    * Check if the selector comes from a browser-provided stylesheet.
  1750    * @return {boolean} true if the selector comes from a browser-provided
  1751    * stylesheet, or false otherwise.
  1752    */
  1753   get contentRule()
  1755     return this.selector.contentRule;
  1756   },
  1758   /**
  1759    * Compare the current CssSelectorInfo instance to another instance, based on
  1760    * specificity information.
  1762    * @param {CssSelectorInfo} aThat The instance to compare ourselves against.
  1763    * @return number -1, 0, 1 depending on how aThat compares with this.
  1764    */
  1765   compareTo: function CssSelectorInfo_compareTo(aThat)
  1767     if (!this.contentRule && aThat.contentRule) return 1;
  1768     if (this.contentRule && !aThat.contentRule) return -1;
  1770     if (this.elementStyle && !aThat.elementStyle) {
  1771       if (!this.important && aThat.important) return 1;
  1772       else return -1;
  1775     if (!this.elementStyle && aThat.elementStyle) {
  1776       if (this.important && !aThat.important) return -1;
  1777       else return 1;
  1780     if (this.important && !aThat.important) return -1;
  1781     if (aThat.important && !this.important) return 1;
  1783     if (this.specificity > aThat.specificity) return -1;
  1784     if (aThat.specificity > this.specificity) return 1;
  1786     if (this.sheetIndex > aThat.sheetIndex) return -1;
  1787     if (aThat.sheetIndex > this.sheetIndex) return 1;
  1789     if (this.ruleLine > aThat.ruleLine) return -1;
  1790     if (aThat.ruleLine > this.ruleLine) return 1;
  1792     return 0;
  1793   },
  1795   toString: function CssSelectorInfo_toString()
  1797     return this.selector + " -> " + this.value;
  1798   },
  1799 };
  1801 XPCOMUtils.defineLazyGetter(this, "domUtils", function() {
  1802   return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
  1803 });

mercurial