toolkit/devtools/styleinspector/css-logic.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

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

mercurial