css-logic.js (51248B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 /* 6 * About the objects defined in this file: 7 * - CssLogic contains style information about a view context. It provides 8 * access to 2 sets of objects: Css[Sheet|Rule|Selector] provide access to 9 * information that does not change when the selected element changes while 10 * Css[Property|Selector]Info provide information that is dependent on the 11 * selected element. 12 * Its key methods are highlight(), getPropertyInfo() and forEachSheet(), etc 13 * 14 * - CssSheet provides a more useful API to a DOM CSSSheet for our purposes, 15 * including shortSource and href. 16 * - CssRule a more useful API to a DOM CSSRule including access to the group 17 * of CssSelectors that the rule provides properties for 18 * - CssSelector A single selector - i.e. not a selector group. In other words 19 * a CssSelector does not contain ','. This terminology is different from the 20 * standard DOM API, but more inline with the definition in the spec. 21 * 22 * - CssPropertyInfo contains style information for a single property for the 23 * highlighted element. 24 * - CssSelectorInfo is a wrapper around CssSelector, which adds sorting with 25 * reference to the selected element. 26 */ 27 28 "use strict"; 29 30 const nodeConstants = require("resource://devtools/shared/dom-node-constants.js"); 31 const { 32 getBindingElementAndPseudo, 33 getMatchingCSSRules, 34 hasVisitedState, 35 isAgentStylesheet, 36 isAuthorStylesheet, 37 isUserStylesheet, 38 shortSource, 39 ELEMENT_BACKED_PSEUDO_ELEMENTS, 40 FILTER, 41 STATUS, 42 } = require("resource://devtools/shared/inspector/css-logic.js"); 43 44 const COMPAREMODE = { 45 BOOLEAN: "bool", 46 INTEGER: "int", 47 }; 48 49 class CssLogic { 50 /** 51 * If the element has an id, return '#id'. Otherwise return 'tagname[n]' where 52 * n is the index of this element in its siblings. 53 * <p>A technically more 'correct' output from the no-id case might be: 54 * 'tagname:nth-of-type(n)' however this is unlikely to be more understood 55 * and it is longer. 56 * 57 * @param {Element} element the element for which you want the short name. 58 * @return {string} the string to be displayed for element. 59 */ 60 static getShortName(element) { 61 if (!element) { 62 return "null"; 63 } 64 if (element.id) { 65 return "#" + element.id; 66 } 67 let priorSiblings = 0; 68 let temp = element; 69 while ((temp = temp.previousElementSibling)) { 70 priorSiblings++; 71 } 72 return element.tagName + "[" + priorSiblings + "]"; 73 } 74 75 /** 76 * Get a string list of selectors for a given DOMRule. 77 * 78 * @param {DOMRule} domRule 79 * The DOMRule to parse. 80 * @param {boolean} desugared 81 * Set to true to get the desugared selector (see https://drafts.csswg.org/css-nesting-1/#nest-selector) 82 * @return {Array} 83 * An array of string selectors. 84 */ 85 static getSelectors(domRule, desugared = false) { 86 if (ChromeUtils.getClassName(domRule) !== "CSSStyleRule") { 87 // Return empty array since CSSRule#selectorCount assumes only STYLE_RULE type. 88 return []; 89 } 90 91 const selectors = []; 92 93 const len = domRule.selectorCount; 94 for (let i = 0; i < len; i++) { 95 selectors.push(domRule.selectorTextAt(i, desugared)); 96 } 97 return selectors; 98 } 99 100 /** 101 * Given a node, check to see if it is a ::before or ::after element. 102 * If so, return the node that is accessible from within the document 103 * (the parent of the anonymous node), along with which pseudo element 104 * it was. Otherwise, return the node itself. 105 * 106 * @returns {object} 107 * - {DOMNode} node The non-anonymous node 108 * - {string} pseudo One of ':marker', ':before', ':after', or null. 109 */ 110 static getBindingElementAndPseudo = getBindingElementAndPseudo; 111 112 /** 113 * Get the computed style on a node. Automatically handles reading 114 * computed styles on a ::before/::after element by reading on the 115 * parent node with the proper pseudo argument. 116 * 117 * @param {Node} 118 * @returns {CSSStyleDeclaration} 119 */ 120 static getComputedStyle(node) { 121 if ( 122 !node || 123 Cu.isDeadWrapper(node) || 124 node.nodeType !== nodeConstants.ELEMENT_NODE || 125 !node.ownerGlobal 126 ) { 127 return null; 128 } 129 130 const { bindingElement, pseudo } = 131 CssLogic.getBindingElementAndPseudo(node); 132 133 // For reasons that still escape us, pseudo-elements can sometimes be "unattached" (i.e. 134 // not have a parentNode defined). This seems to happen when a page is reloaded while 135 // the inspector is open. Bailing out here ensures that the inspector does not fail at 136 // presenting DOM nodes and CSS styles when this happens. This is a temporary measure. 137 // See bug 1506792. 138 if (!bindingElement) { 139 return null; 140 } 141 142 return node.ownerGlobal.getComputedStyle(bindingElement, pseudo); 143 } 144 145 /** 146 * Get a source for a stylesheet, taking into account embedded stylesheets 147 * for which we need to use document.defaultView.location.href rather than 148 * sheet.href 149 * 150 * @param {CSSStyleSheet} sheet the DOM object for the style sheet. 151 * @return {string} the address of the stylesheet. 152 */ 153 static href(sheet) { 154 return sheet.href || sheet.associatedDocument.location; 155 } 156 157 /** 158 * Returns true if the given node has visited state. 159 */ 160 static hasVisitedState = hasVisitedState; 161 162 // Used for tracking matched CssSelector objects. 163 matchId = 0; 164 165 // Used for tracking unique CssSheet/CssRule/CssSelector objects, in a run of 166 // processMatchedSelectors(). 167 passId = 0; 168 169 // Both setup by highlight(). 170 viewedElement = null; 171 viewedDocument = null; 172 173 #propertyInfos = {}; 174 175 // The cache of the known sheets. 176 #sheets = null; 177 178 // Have the sheets been cached? 179 #sheetsCached = false; 180 181 #sheetIndex = 0; 182 183 // The total number of rules, in all stylesheets, after filtering. 184 #ruleCount = 0; 185 186 // The computed styles for the viewedElement. 187 #computedStyle = null; 188 189 // Source filter. Only display properties coming from the given source 190 #sourceFilter = FILTER.USER; 191 192 #matchedRules = null; 193 #matchedSelectors = null; 194 195 // Cached keyframes rules in all stylesheets 196 #keyframesRules = null; 197 198 // Cached @position-try rules in all stylesheets 199 #positionTryRules = null; 200 201 /** 202 * Reset various properties 203 */ 204 reset() { 205 this.#propertyInfos = {}; 206 this.#ruleCount = 0; 207 this.#sheetIndex = 0; 208 this.#sheets = {}; 209 this.#sheetsCached = false; 210 this.#matchedRules = null; 211 this.#matchedSelectors = null; 212 this.#keyframesRules = []; 213 this.#positionTryRules = []; 214 } 215 216 /** 217 * Focus on a new element - remove the style caches. 218 * 219 * @param {Element} aViewedElement the element the user has highlighted 220 * in the Inspector. 221 */ 222 highlight(viewedElement) { 223 if (!viewedElement) { 224 this.viewedElement = null; 225 this.viewedDocument = null; 226 this.#computedStyle = null; 227 this.reset(); 228 return; 229 } 230 231 if (viewedElement === this.viewedElement) { 232 return; 233 } 234 235 this.viewedElement = viewedElement; 236 237 const doc = this.viewedElement.ownerDocument; 238 if (doc != this.viewedDocument) { 239 // New document: clear/rebuild the cache. 240 this.viewedDocument = doc; 241 242 // Hunt down top level stylesheets, and cache them. 243 this.#cacheSheets(); 244 } else { 245 // Clear cached data in the CssPropertyInfo objects. 246 this.#propertyInfos = {}; 247 } 248 249 this.#matchedRules = null; 250 this.#matchedSelectors = null; 251 this.#computedStyle = CssLogic.getComputedStyle(this.viewedElement); 252 } 253 254 /** 255 * Get the values of all the computed CSS properties for the highlighted 256 * element. 257 * 258 * @returns {object} The computed CSS properties for a selected element 259 */ 260 get computedStyle() { 261 return this.#computedStyle; 262 } 263 264 /** 265 * Get the source filter. 266 * 267 * @returns {string} The source filter being used. 268 */ 269 get sourceFilter() { 270 return this.#sourceFilter; 271 } 272 273 /** 274 * Source filter. Only display properties coming from the given source (web 275 * address). Note that in order to avoid information overload we DO NOT show 276 * unmatched system rules. 277 * 278 * @see FILTER.* 279 */ 280 set sourceFilter(value) { 281 const oldValue = this.#sourceFilter; 282 this.#sourceFilter = value; 283 284 let ruleCount = 0; 285 286 // Update the CssSheet objects. 287 this.forEachSheet(function (sheet) { 288 if (sheet.authorSheet && sheet.sheetAllowed) { 289 ruleCount += sheet.ruleCount; 290 } 291 }, this); 292 293 this.#ruleCount = ruleCount; 294 295 // Full update is needed because the this.processMatchedSelectors() method 296 // skips UA stylesheets if the filter does not allow such sheets. 297 const needFullUpdate = oldValue == FILTER.UA || value == FILTER.UA; 298 299 if (needFullUpdate) { 300 this.#matchedRules = null; 301 this.#matchedSelectors = null; 302 this.#propertyInfos = {}; 303 } else { 304 // Update the CssPropertyInfo objects. 305 for (const property in this.#propertyInfos) { 306 this.#propertyInfos[property].needRefilter = true; 307 } 308 } 309 } 310 311 /** 312 * Return a CssPropertyInfo data structure for the currently viewed element 313 * and the specified CSS property. If there is no currently viewed element we 314 * return an empty object. 315 * 316 * @param {string} property The CSS property to look for. 317 * @return {CssPropertyInfo} a CssPropertyInfo structure for the given 318 * property. 319 */ 320 getPropertyInfo(property) { 321 if (!this.viewedElement) { 322 return {}; 323 } 324 325 let info = this.#propertyInfos[property]; 326 if (!info) { 327 info = new CssPropertyInfo(this, property); 328 this.#propertyInfos[property] = info; 329 } 330 331 return info; 332 } 333 334 /** 335 * Cache all the stylesheets in the inspected document 336 * 337 * @private 338 */ 339 #cacheSheets() { 340 this.passId++; 341 this.reset(); 342 343 // styleSheets isn't an array, but forEach can work on it anyway 344 const styleSheets = InspectorUtils.getAllStyleSheets( 345 this.viewedDocument, 346 true 347 ); 348 Array.prototype.forEach.call(styleSheets, this.#cacheSheet, this); 349 350 this.#sheetsCached = true; 351 } 352 353 /** 354 * Cache a stylesheet if it falls within the requirements: if it's enabled, 355 * and if the @media is allowed. This method also walks through the stylesheet 356 * cssRules to find @imported rules, to cache the stylesheets of those rules 357 * as well. In addition, @keyframes and @position-try rules in the stylesheet are cached. 358 * 359 * @private 360 * @param {CSSStyleSheet} domSheet the CSSStyleSheet object to cache. 361 */ 362 #cacheSheet(domSheet) { 363 if (domSheet.disabled) { 364 return; 365 } 366 367 // Only work with stylesheets that have their media allowed. 368 if (!this.mediaMatches(domSheet)) { 369 return; 370 } 371 372 // Cache the sheet. 373 const cssSheet = this.getSheet(domSheet, this.#sheetIndex++); 374 if (cssSheet.passId != this.passId) { 375 cssSheet.passId = this.passId; 376 377 // Find import, keyframes and position-try rules. We loop through all the stylesheet 378 // recursively, so we can go through nested rules. 379 const traverseRules = ruleList => { 380 for (const aDomRule of ruleList) { 381 const ruleClassName = ChromeUtils.getClassName(aDomRule); 382 if ( 383 ruleClassName === "CSSImportRule" && 384 aDomRule.styleSheet && 385 this.mediaMatches(aDomRule) 386 ) { 387 this.#cacheSheet(aDomRule.styleSheet); 388 } else if (ruleClassName === "CSSKeyframesRule") { 389 this.#keyframesRules.push(aDomRule); 390 } else if (ruleClassName === "CSSPositionTryRule") { 391 this.#positionTryRules.push(aDomRule); 392 } 393 394 if (aDomRule.cssRules) { 395 traverseRules(aDomRule.cssRules); 396 } 397 } 398 }; 399 400 traverseRules(cssSheet.getCssRules()); 401 } 402 } 403 404 /** 405 * Retrieve the list of stylesheets in the document. 406 * 407 * @return {Array} the list of stylesheets in the document. 408 */ 409 get sheets() { 410 if (!this.#sheetsCached) { 411 this.#cacheSheets(); 412 } 413 414 const sheets = []; 415 this.forEachSheet(function (sheet) { 416 if (sheet.authorSheet) { 417 sheets.push(sheet); 418 } 419 }, this); 420 421 return sheets; 422 } 423 424 /** 425 * Retrieve the list of keyframes rules in the document. 426 * 427 * @ return {array} the list of keyframes rules in the document. 428 */ 429 get keyframesRules() { 430 if (!this.#sheetsCached) { 431 this.#cacheSheets(); 432 } 433 return this.#keyframesRules; 434 } 435 436 /** 437 * Retrieve the list of @position-try rules in the document. 438 * 439 * @returns {CSSPositionTryRule[]} the list of @position-try rules in the document. 440 */ 441 get positionTryRules() { 442 if (!this.#sheetsCached) { 443 this.#cacheSheets(); 444 } 445 return this.#positionTryRules; 446 } 447 448 /** 449 * Retrieve a CssSheet object for a given a CSSStyleSheet object. If the 450 * stylesheet is already cached, you get the existing CssSheet object, 451 * otherwise the new CSSStyleSheet object is cached. 452 * 453 * @param {CSSStyleSheet} domSheet the CSSStyleSheet object you want. 454 * @param {number} index the index, within the document, of the stylesheet. 455 * 456 * @return {CssSheet} the CssSheet object for the given CSSStyleSheet object. 457 */ 458 getSheet(domSheet, index) { 459 let cacheId = ""; 460 461 if (domSheet.href) { 462 cacheId = domSheet.href; 463 } else if (domSheet.associatedDocument) { 464 cacheId = domSheet.associatedDocument.location; 465 } 466 467 let sheet = null; 468 let sheetFound = false; 469 470 if (cacheId in this.#sheets) { 471 for (sheet of this.#sheets[cacheId]) { 472 if (sheet.domSheet === domSheet) { 473 if (index != -1) { 474 sheet.index = index; 475 } 476 sheetFound = true; 477 break; 478 } 479 } 480 } 481 482 if (!sheetFound) { 483 if (!(cacheId in this.#sheets)) { 484 this.#sheets[cacheId] = []; 485 } 486 487 sheet = new CssSheet(this, domSheet, index); 488 if (sheet.sheetAllowed && sheet.authorSheet) { 489 this.#ruleCount += sheet.ruleCount; 490 } 491 492 this.#sheets[cacheId].push(sheet); 493 } 494 495 return sheet; 496 } 497 498 /** 499 * Process each cached stylesheet in the document using your callback. 500 * 501 * @param {function} callback the function you want executed for each of the 502 * CssSheet objects cached. 503 * @param {object} scope the scope you want for the callback function. scope 504 * will be the this object when callback executes. 505 */ 506 forEachSheet(callback, scope) { 507 for (const cacheId in this.#sheets) { 508 const sheets = this.#sheets[cacheId]; 509 for (let i = 0; i < sheets.length; i++) { 510 // We take this as an opportunity to clean dead sheets 511 try { 512 const sheet = sheets[i]; 513 // If accessing domSheet raises an exception, then the style 514 // sheet is a dead object. 515 sheet.domSheet; 516 callback.call(scope, sheet, i, sheets); 517 } catch (e) { 518 sheets.splice(i, 1); 519 i--; 520 } 521 } 522 } 523 } 524 525 /** 526 527 /** 528 * Get the number CSSRule objects in the document, counted from all of 529 * the stylesheets. System sheets are excluded. If a filter is active, this 530 * tells only the number of CSSRule objects inside the selected 531 * CSSStyleSheet. 532 * 533 * WARNING: This only provides an estimate of the rule count, and the results 534 * could change at a later date. Todo remove this 535 * 536 * @return {number} the number of CSSRule (all rules). 537 */ 538 get ruleCount() { 539 if (!this.#sheetsCached) { 540 this.#cacheSheets(); 541 } 542 543 return this.#ruleCount; 544 } 545 546 /** 547 * Process the CssSelector objects that match the highlighted element and its 548 * parent elements. scope.callback() is executed for each CssSelector 549 * object, being passed the CssSelector object and the match status. 550 * 551 * This method also includes all of the element.style properties, for each 552 * highlighted element parent and for the highlighted element itself. 553 * 554 * Note that the matched selectors are cached, such that next time your 555 * callback is invoked for the cached list of CssSelector objects. 556 * 557 * @param {function} callback the function you want to execute for each of 558 * the matched selectors. 559 * @param {object} scope the scope you want for the callback function. scope 560 * will be the this object when callback executes. 561 */ 562 processMatchedSelectors(callback, scope) { 563 if (this.#matchedSelectors) { 564 if (callback) { 565 this.passId++; 566 this.#matchedSelectors.forEach(function (value) { 567 callback.call(scope, value[0], value[1]); 568 value[0].cssRule.passId = this.passId; 569 }, this); 570 } 571 return; 572 } 573 574 if (!this.#matchedRules) { 575 this.#buildMatchedRules(); 576 } 577 578 this.#matchedSelectors = []; 579 this.passId++; 580 581 for (const matchedRule of this.#matchedRules) { 582 const [rule, status, distance] = matchedRule; 583 584 rule.selectors.forEach(function (selector) { 585 if ( 586 selector.matchId !== this.matchId && 587 (rule.domRule.declarationOrigin === "style-attribute" || 588 rule.domRule.declarationOrigin === "pres-hints" || 589 this.selectorMatchesElement(rule.domRule, selector.selectorIndex)) 590 ) { 591 selector.matchId = this.matchId; 592 this.#matchedSelectors.push([selector, status, distance]); 593 if (callback) { 594 callback.call(scope, selector, status, distance); 595 } 596 } 597 }, this); 598 599 rule.passId = this.passId; 600 } 601 } 602 603 /** 604 * Check if the given selector matches the highlighted element or any of its 605 * parents. 606 * 607 * @private 608 * @param {DOMRule} domRule 609 * The DOM Rule containing the selector. 610 * @param {number} idx 611 * The index of the selector within the DOMRule. 612 * @return {boolean} 613 * true if the given selector matches the highlighted element or any 614 * of its parents, otherwise false is returned. 615 */ 616 selectorMatchesElement(domRule, idx) { 617 let element = this.viewedElement; 618 do { 619 const { bindingElement, pseudo } = 620 CssLogic.getBindingElementAndPseudo(element); 621 if (domRule.selectorMatchesElement(idx, bindingElement, pseudo)) { 622 return true; 623 } 624 625 for (const pseudoElement of ELEMENT_BACKED_PSEUDO_ELEMENTS) { 626 if (domRule.selectorMatchesElement(idx, element, pseudoElement)) { 627 return true; 628 } 629 } 630 } while ( 631 // Loop on flattenedTreeParentNode instead of parentNode to reach the 632 // shadow host from the shadow dom. 633 (element = element.flattenedTreeParentNode) && 634 element.nodeType === nodeConstants.ELEMENT_NODE 635 ); 636 637 return false; 638 } 639 640 /** 641 * Check if the highlighted element or its parents have matched selectors. 642 * 643 * @param {Array<string>} properties: The list of properties you want to check if they 644 * have matched selectors or not. For CSS variables, this will check if the variable 645 * is set OR used in a matching rule. 646 * @return {Set<string>} A Set containing the properties that do have matched selectors. 647 */ 648 hasMatchedSelectors(properties) { 649 if (!this.#matchedRules) { 650 this.#buildMatchedRules(); 651 } 652 653 const result = new Set(); 654 655 for (const [rule, status] of this.#matchedRules) { 656 // Getting the rule cssText can be costly, so cache it 657 let cssText; 658 const getCssText = () => { 659 if (cssText === undefined) { 660 cssText = rule.domRule.cssText; 661 } 662 return cssText; 663 }; 664 665 // Loop through properties in reverse as we're removing items from it and we don't 666 // want to mess with the iteration. 667 for (let i = properties.length - 1; i >= 0; i--) { 668 const property = properties[i]; 669 // We just need to find if a rule has this property while it matches 670 // the viewedElement (or its parents). 671 if ( 672 // check if the property is assigned 673 (rule.isPropertyAssigned(property) || 674 // or if this is a css variable, if it's being used in the rule. 675 (property.startsWith("--") && 676 // we may have false positive for dashed ident or the variable being 677 // used in comment/string, but the tradeoff seems okay, as we would have 678 // to parse the value of each declaration, which could be costly. 679 new RegExp(`${property}[^A-Za-z0-9_-]`).test(getCssText()))) && 680 (status == STATUS.MATCHED || 681 (status == STATUS.PARENT_MATCH && 682 InspectorUtils.isInheritedProperty( 683 this.viewedDocument, 684 property 685 ))) 686 ) { 687 result.add(property); 688 // Once the property has a matched selector, we can remove it from the array 689 properties.splice(i, 1); 690 } 691 } 692 693 if (!properties.length) { 694 return result; 695 } 696 } 697 698 return result; 699 } 700 701 /** 702 * Build the array of matched rules for the currently highlighted element. 703 * The array will hold rules that match the viewedElement and its parents. 704 * 705 * @private 706 */ 707 #buildMatchedRules() { 708 let domRules; 709 let element = this.viewedElement; 710 const filter = this.sourceFilter; 711 let sheetIndex = 0; 712 713 // distance is used to tell us how close an ancestor is to an element e.g. 714 // 0: The rule is directly applied to the current element. 715 // -1: The rule is inherited from the current element's first parent. 716 // -2: The rule is inherited from the current element's second parent. 717 // etc. 718 let distance = 0; 719 720 this.matchId++; 721 this.passId++; 722 this.#matchedRules = []; 723 724 if (!element) { 725 return; 726 } 727 728 do { 729 const status = 730 this.viewedElement === element ? STATUS.MATCHED : STATUS.PARENT_MATCH; 731 732 try { 733 domRules = getMatchingCSSRules(element); 734 } catch (ex) { 735 console.log("CL__buildMatchedRules error: " + ex); 736 continue; 737 } 738 739 // getMatchingCSSRules can return null with a shadow DOM element. 740 if (domRules !== null) { 741 // getMatchingCSSRules returns ordered from least-specific to most-specific, 742 // but we do want them from most-specific to least specific, so we need to loop 743 // through the rules backward. 744 for (let i = domRules.length - 1; i >= 0; i--) { 745 const domRule = domRules[i]; 746 if (domRule.declarationOrigin) { 747 // We only consume element.style and pres hint declarations for now 748 if ( 749 domRule.declarationOrigin !== "style-attribute" && 750 domRule.declarationOrigin !== "pres-hints" 751 ) { 752 continue; 753 } 754 755 const rule = new CssRule( 756 null, 757 { 758 style: domRule.style, 759 declarationOrigin: domRule.declarationOrigin, 760 }, 761 element 762 ); 763 rule.matchId = this.matchId; 764 rule.passId = this.passId; 765 this.#matchedRules.push([rule, status, distance]); 766 767 continue; 768 } 769 const sheet = this.getSheet(domRule.parentStyleSheet, -1); 770 if (sheet.passId !== this.passId) { 771 sheet.index = sheetIndex++; 772 sheet.passId = this.passId; 773 } 774 775 if (filter === FILTER.USER && !sheet.authorSheet) { 776 continue; 777 } 778 779 const rule = sheet.getRule(domRule); 780 if (rule.passId === this.passId) { 781 continue; 782 } 783 784 rule.matchId = this.matchId; 785 rule.passId = this.passId; 786 this.#matchedRules.push([rule, status, distance]); 787 } 788 } 789 790 distance--; 791 } while ( 792 // Loop on flattenedTreeParentNode instead of parentNode to reach the 793 // shadow host from the shadow dom. 794 (element = element.flattenedTreeParentNode) && 795 element.nodeType === nodeConstants.ELEMENT_NODE 796 ); 797 } 798 799 /** 800 * Tells if the given DOM CSS object matches the current view media. 801 * 802 * @param {object} domObject The DOM CSS object to check. 803 * @return {boolean} True if the DOM CSS object matches the current view 804 * media, or false otherwise. 805 */ 806 mediaMatches(domObject) { 807 const mediaText = domObject.media.mediaText; 808 return ( 809 !mediaText || 810 this.viewedDocument.defaultView.matchMedia(mediaText).matches 811 ); 812 } 813 } 814 815 class CssSheet { 816 /** 817 * A safe way to access cached bits of information about a stylesheet. 818 * 819 * @class 820 * @param {CssLogic} cssLogic pointer to the CssLogic instance working with 821 * this CssSheet object. 822 * @param {CSSStyleSheet} domSheet reference to a DOM CSSStyleSheet object. 823 * @param {number} index tells the index/position of the stylesheet within the 824 * main document. 825 */ 826 constructor(cssLogic, domSheet, index) { 827 this.#cssLogic = cssLogic; 828 this.domSheet = domSheet; 829 this.index = this.authorSheet ? index : -100 * index; 830 } 831 832 #cssLogic; 833 // Cache of the sheets href. Cached by the getter. 834 #href = null; 835 836 // Short version of href for use in select boxes etc. Cached by getter. 837 #shortSource = null; 838 839 // Cached CssRules from the given stylesheet. 840 #rules = {}; 841 842 // null for uncached. 843 #sheetAllowed = null; 844 845 #ruleCount = -1; 846 847 passId = null; 848 #agentSheet = null; 849 #authorSheet = null; 850 #userSheet = null; 851 852 /** 853 * Check if the stylesheet is an agent stylesheet (provided by the browser). 854 * 855 * @return {boolean} true if this is an agent stylesheet, false otherwise. 856 */ 857 get agentSheet() { 858 if (this.#agentSheet === null) { 859 this.#agentSheet = isAgentStylesheet(this.domSheet); 860 } 861 return this.#agentSheet; 862 } 863 864 /** 865 * Check if the stylesheet is an author stylesheet (provided by the content page). 866 * 867 * @return {boolean} true if this is an author stylesheet, false otherwise. 868 */ 869 get authorSheet() { 870 if (this.#authorSheet === null) { 871 this.#authorSheet = isAuthorStylesheet(this.domSheet); 872 } 873 return this.#authorSheet; 874 } 875 876 /** 877 * Check if the stylesheet is a user stylesheet (provided by userChrome.css or 878 * userContent.css). 879 * 880 * @return {boolean} true if this is a user stylesheet, false otherwise. 881 */ 882 get userSheet() { 883 if (this.#userSheet === null) { 884 this.#userSheet = isUserStylesheet(this.domSheet); 885 } 886 return this.#userSheet; 887 } 888 889 /** 890 * Check if the stylesheet is disabled or not. 891 * 892 * @return {boolean} true if this stylesheet is disabled, or false otherwise. 893 */ 894 get disabled() { 895 return this.domSheet.disabled; 896 } 897 898 /** 899 * Get a source for a stylesheet, using CssLogic.href 900 * 901 * @return {string} the address of the stylesheet. 902 */ 903 get href() { 904 if (this.#href) { 905 return this.#href; 906 } 907 908 this.#href = CssLogic.href(this.domSheet); 909 return this.#href; 910 } 911 912 /** 913 * Create a shorthand version of the href of a stylesheet. 914 * 915 * @return {string} the shorthand source of the stylesheet. 916 */ 917 get shortSource() { 918 if (this.#shortSource) { 919 return this.#shortSource; 920 } 921 922 this.#shortSource = shortSource(this.domSheet); 923 return this.#shortSource; 924 } 925 926 /** 927 * Tells if the sheet is allowed or not by the current CssLogic.sourceFilter. 928 * 929 * @return {boolean} true if the stylesheet is allowed by the sourceFilter, or 930 * false otherwise. 931 */ 932 get sheetAllowed() { 933 if (this.#sheetAllowed !== null) { 934 return this.#sheetAllowed; 935 } 936 937 this.#sheetAllowed = true; 938 939 const filter = this.#cssLogic.sourceFilter; 940 if (filter === FILTER.USER && !this.authorSheet) { 941 this.#sheetAllowed = false; 942 } 943 if (filter !== FILTER.USER && filter !== FILTER.UA) { 944 this.#sheetAllowed = filter === this.href; 945 } 946 947 return this.#sheetAllowed; 948 } 949 950 /** 951 * Retrieve the number of rules in this stylesheet. 952 * 953 * @return {number} the number of CSSRule objects in this stylesheet. 954 */ 955 get ruleCount() { 956 try { 957 return this.#ruleCount > -1 ? this.#ruleCount : this.getCssRules().length; 958 } catch (e) { 959 return 0; 960 } 961 } 962 963 /** 964 * Retrieve the array of css rules for this stylesheet. 965 * 966 * Accessing cssRules on a stylesheet that is not completely loaded can throw a 967 * DOMException (Bug 625013). This wrapper will return an empty array instead. 968 * 969 * @return {Array} array of css rules. 970 */ 971 getCssRules() { 972 try { 973 return this.domSheet.cssRules; 974 } catch (e) { 975 return []; 976 } 977 } 978 979 /** 980 * Retrieve a CssRule object for the given CSSStyleRule. The CssRule object is 981 * cached, such that subsequent retrievals return the same CssRule object for 982 * the same CSSStyleRule object. 983 * 984 * @param {CSSStyleRule} aDomRule the CSSStyleRule object for which you want a 985 * CssRule object. 986 * @return {CssRule} the cached CssRule object for the given CSSStyleRule 987 * object. 988 */ 989 getRule(domRule) { 990 const cacheId = domRule.type + domRule.selectorText; 991 992 let rule = null; 993 let ruleFound = false; 994 995 if (cacheId in this.#rules) { 996 for (rule of this.#rules[cacheId]) { 997 if (rule.domRule === domRule) { 998 ruleFound = true; 999 break; 1000 } 1001 } 1002 } 1003 1004 if (!ruleFound) { 1005 if (!(cacheId in this.#rules)) { 1006 this.#rules[cacheId] = []; 1007 } 1008 1009 rule = new CssRule(this, domRule); 1010 this.#rules[cacheId].push(rule); 1011 } 1012 1013 return rule; 1014 } 1015 1016 toString() { 1017 return "CssSheet[" + this.shortSource + "]"; 1018 } 1019 } 1020 1021 class CssRule { 1022 /** 1023 * Information about a single CSSStyleRule. 1024 * 1025 * @param {CSSStyleSheet|null} cssSheet the CssSheet object of the stylesheet that 1026 * holds the CSSStyleRule. If the rule comes from element.style, set this 1027 * argument to null. 1028 * @param {CSSStyleRule|InspectorDeclaration} domRule the DOM CSSStyleRule for which you want 1029 * to cache data. If the rule comes from element.style or presentational attributes, it 1030 * will be an InspectorDeclaration (object of the form {style: element.style, declarationOrigin: string}). 1031 * @param {Element} [element] If the rule comes from element.style, then this 1032 * argument must point to the element. 1033 * @class 1034 */ 1035 constructor(cssSheet, domRule, element) { 1036 this.#cssSheet = cssSheet; 1037 this.domRule = domRule; 1038 1039 if (this.#cssSheet) { 1040 // parse domRule.selectorText on call to this.selectors 1041 this.#selectors = null; 1042 this.line = InspectorUtils.getRelativeRuleLine(this.domRule); 1043 this.column = InspectorUtils.getRuleColumn(this.domRule); 1044 this.href = this.#cssSheet.href; 1045 this.authorRule = this.#cssSheet.authorSheet; 1046 this.userRule = this.#cssSheet.userSheet; 1047 this.agentRule = this.#cssSheet.agentSheet; 1048 } else if (element) { 1049 let selector = ""; 1050 if (domRule.declarationOrigin === "style-attribute") { 1051 selector = "@element.style"; 1052 } else if (domRule.declarationOrigin === "pres-hints") { 1053 selector = "@element.attributesStyle"; 1054 } 1055 1056 this.#selectors = [new CssSelector(this, selector, 0)]; 1057 this.line = -1; 1058 this.href = "#"; 1059 this.authorRule = true; 1060 this.userRule = false; 1061 this.agentRule = false; 1062 this.sourceElement = element; 1063 } 1064 } 1065 1066 passId = null; 1067 1068 #cssSheet; 1069 #selectors; 1070 1071 /** 1072 * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter. 1073 * 1074 * @return {boolean} true if the parent stylesheet is allowed by the current 1075 * sourceFilter, or false otherwise. 1076 */ 1077 get sheetAllowed() { 1078 return this.#cssSheet ? this.#cssSheet.sheetAllowed : true; 1079 } 1080 1081 /** 1082 * Retrieve the parent stylesheet index/position in the viewed document. 1083 * 1084 * @return {number} the parent stylesheet index/position in the viewed 1085 * document. 1086 */ 1087 get sheetIndex() { 1088 return this.#cssSheet ? this.#cssSheet.index : 0; 1089 } 1090 1091 /** 1092 * Returns the underlying CSSRule style 1093 * 1094 * @returns CSS2Properties 1095 */ 1096 getStyle() { 1097 // When dealing with a style attribute "rule", this.domRule is a InspectorDeclaration 1098 // and its style property might not reflect the current declarations, so we need to 1099 // retrieve the source element style instead. 1100 return this.domRule.declarationOrigin === "style-attribute" 1101 ? this.sourceElement.style 1102 : this.domRule.style; 1103 } 1104 1105 /** 1106 * Retrieve the style property value from the current CSSStyleRule. 1107 * 1108 * @param {string} property the CSS property name for which you want the 1109 * value. 1110 * @return {string} the property value. 1111 */ 1112 getPropertyValue(property) { 1113 return this.getStyle().getPropertyValue(property); 1114 } 1115 1116 /** 1117 * Returns whether or not the given property is set in the current CSSStyleRule. 1118 * 1119 * @param {string} property the CSS property name 1120 * @return {boolean} 1121 */ 1122 isPropertyAssigned(property) { 1123 return this.getStyle().hasLonghandProperty(property); 1124 } 1125 1126 /** 1127 * Retrieve the style property priority from the current CSSStyleRule. 1128 * 1129 * @param {string} property the CSS property name for which you want the 1130 * priority. 1131 * @return {string} the property priority. 1132 */ 1133 getPropertyPriority(property) { 1134 return this.getStyle().getPropertyPriority(property); 1135 } 1136 1137 /** 1138 * Retrieve the list of CssSelector objects for each of the parsed selectors 1139 * of the current CSSStyleRule. 1140 * 1141 * @return {Array} the array hold the CssSelector objects. 1142 */ 1143 get selectors() { 1144 if (this.#selectors) { 1145 return this.#selectors; 1146 } 1147 1148 // Parse the CSSStyleRule.selectorText string. 1149 this.#selectors = []; 1150 1151 if (!this.domRule.selectorText) { 1152 return this.#selectors; 1153 } 1154 1155 const selectors = CssLogic.getSelectors(this.domRule); 1156 1157 for (let i = 0, len = selectors.length; i < len; i++) { 1158 this.#selectors.push(new CssSelector(this, selectors[i], i)); 1159 } 1160 1161 return this.#selectors; 1162 } 1163 1164 toString() { 1165 return "[CssRule " + this.domRule.selectorText + "]"; 1166 } 1167 } 1168 1169 class CssSelector { 1170 /** 1171 * The CSS selector class allows us to document the ranking of various CSS 1172 * selectors. 1173 * 1174 * @class 1175 * @param {CssRule} cssRule the CssRule instance from where the selector comes. 1176 * @param {string} selector The selector that we wish to investigate. 1177 * @param {number} index The index of the selector within it's rule. 1178 */ 1179 constructor(cssRule, selector, index) { 1180 this.cssRule = cssRule; 1181 this.text = selector; 1182 this.inlineStyle = cssRule.domRule?.declarationOrigin === "style-attribute"; 1183 this.selectorIndex = index; 1184 } 1185 1186 matchId = null; 1187 1188 #specificity = null; 1189 1190 /** 1191 * Retrieve the CssSelector source element, which is the source of the CssRule 1192 * owning the selector. This is only available when the CssSelector comes from 1193 * an element.style. 1194 * 1195 * @return {string} the source element selector. 1196 */ 1197 get sourceElement() { 1198 return this.cssRule.sourceElement; 1199 } 1200 1201 /** 1202 * Retrieve the address of the CssSelector. This points to the address of the 1203 * CssSheet owning this selector. 1204 * 1205 * @return {string} the address of the CssSelector. 1206 */ 1207 get href() { 1208 return this.cssRule.href; 1209 } 1210 1211 /** 1212 * Check if the selector comes from an agent stylesheet (provided by the browser). 1213 * 1214 * @return {boolean} true if this is an agent stylesheet, false otherwise. 1215 */ 1216 get agentRule() { 1217 return this.cssRule.agentRule; 1218 } 1219 1220 /** 1221 * Check if the selector comes from an author stylesheet (provided by the content page). 1222 * 1223 * @return {boolean} true if this is an author stylesheet, false otherwise. 1224 */ 1225 get authorRule() { 1226 return this.cssRule.authorRule; 1227 } 1228 1229 /** 1230 * Check if the selector comes from a user stylesheet (provided by userChrome.css or 1231 * userContent.css). 1232 * 1233 * @return {boolean} true if this is a user stylesheet, false otherwise. 1234 */ 1235 get userRule() { 1236 return this.cssRule.userRule; 1237 } 1238 1239 /** 1240 * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter. 1241 * 1242 * @return {boolean} true if the parent stylesheet is allowed by the current 1243 * sourceFilter, or false otherwise. 1244 */ 1245 get sheetAllowed() { 1246 return this.cssRule.sheetAllowed; 1247 } 1248 1249 /** 1250 * Retrieve the parent stylesheet index/position in the viewed document. 1251 * 1252 * @return {number} the parent stylesheet index/position in the viewed 1253 * document. 1254 */ 1255 get sheetIndex() { 1256 return this.cssRule.sheetIndex; 1257 } 1258 1259 /** 1260 * Retrieve the line of the parent CSSStyleRule in the parent CSSStyleSheet. 1261 * 1262 * @return {number} the line of the parent CSSStyleRule in the parent 1263 * stylesheet. 1264 */ 1265 get ruleLine() { 1266 return this.cssRule.line; 1267 } 1268 1269 /** 1270 * Retrieve the column of the parent CSSStyleRule in the parent CSSStyleSheet. 1271 * 1272 * @return {number} the column of the parent CSSStyleRule in the parent 1273 * stylesheet. 1274 */ 1275 get ruleColumn() { 1276 return this.cssRule.column; 1277 } 1278 1279 /** 1280 * Retrieve specificity information for the current selector. 1281 * 1282 * @see http://www.w3.org/TR/css3-selectors/#specificity 1283 * @see http://www.w3.org/TR/CSS2/selector.html 1284 * 1285 * @return {number} The selector's specificity. 1286 */ 1287 get specificity() { 1288 if (this.inlineStyle) { 1289 // We don't have an actual rule to call selectorSpecificityAt for element styles. 1290 // However, specificity of element style is constant, 1,0,0,0 or 0x40000000, 1291 // so just return the constant directly. 1292 // @see http://www.w3.org/TR/CSS2/cascade.html#specificity 1293 return 0x40000000; 1294 } 1295 1296 if (this.cssRule.declarationOrigin === "pres-hints") { 1297 // As for element styles, we don't have an actual rule to call selectorSpecificityAt 1298 // on for pres-hints styles. 1299 // However, specificity of such "rule" is constant, 0,0,0,0, just return the constant 1300 // directly. @see https://www.w3.org/TR/CSS2/cascade.html#preshint 1301 return 0; 1302 } 1303 1304 if (typeof this.#specificity !== "number") { 1305 this.#specificity = this.cssRule.domRule.selectorSpecificityAt( 1306 this.selectorIndex 1307 ); 1308 } 1309 1310 return this.#specificity; 1311 } 1312 1313 toString() { 1314 return this.text; 1315 } 1316 } 1317 1318 class CssPropertyInfo { 1319 /** 1320 * A cache of information about the matched rules, selectors and values attached 1321 * to a CSS property, for the highlighted element. 1322 * 1323 * The heart of the CssPropertyInfo object is the #findMatchedSelectors() 1324 * method. This are invoked when the PropertyView tries to access the 1325 * .matchedSelectors array. 1326 * Results are cached, for later reuse. 1327 * 1328 * @param {CssLogic} cssLogic Reference to the parent CssLogic instance 1329 * @param {string} property The CSS property we are gathering information for 1330 * @class 1331 */ 1332 constructor(cssLogic, property) { 1333 this.#cssLogic = cssLogic; 1334 this.property = property; 1335 } 1336 1337 // An array holding CssSelectorInfo objects for each of the matched selectors 1338 // that are inside a CSS rule. Only rules that hold the this.property are 1339 // counted. This includes rules that come from filtered stylesheets (those 1340 // that have sheetAllowed = false). 1341 #matchedSelectors = null; 1342 1343 #cssLogic; 1344 #value = ""; 1345 1346 /** 1347 * Retrieve the computed style value for the current property, for the 1348 * highlighted element. 1349 * 1350 * @return {string} the computed style value for the current property, for the 1351 * highlighted element. 1352 */ 1353 get value() { 1354 if (!this.#value && this.#cssLogic.computedStyle) { 1355 try { 1356 this.#value = this.#cssLogic.computedStyle.getPropertyValue( 1357 this.property 1358 ); 1359 } catch (ex) { 1360 console.log("Error reading computed style for " + this.property); 1361 console.log(ex); 1362 } 1363 } 1364 return this.#value; 1365 } 1366 1367 /** 1368 * Retrieve the array holding CssSelectorInfo objects for each of the matched 1369 * selectors, from each of the matched rules. Only selectors coming from 1370 * allowed stylesheets are included in the array. 1371 * 1372 * @return {Array} the list of CssSelectorInfo objects of selectors that match 1373 * the highlighted element and its parents. 1374 */ 1375 get matchedSelectors() { 1376 if (!this.#matchedSelectors) { 1377 this.#findMatchedSelectors(); 1378 } else if (this.needRefilter) { 1379 this.#refilterSelectors(); 1380 } 1381 1382 return this.#matchedSelectors; 1383 } 1384 1385 /** 1386 * Find the selectors that match the highlighted element and its parents. 1387 * Uses CssLogic.processMatchedSelectors() to find the matched selectors, 1388 * passing in a reference to CssPropertyInfo.#processMatchedSelector() to 1389 * create CssSelectorInfo objects, which we then sort 1390 * 1391 * @private 1392 */ 1393 #findMatchedSelectors() { 1394 this.#matchedSelectors = []; 1395 this.needRefilter = false; 1396 1397 this.#cssLogic.processMatchedSelectors(this.#processMatchedSelector, this); 1398 1399 // Sort the selectors by how well they match the given element. 1400 this.#matchedSelectors.sort((selectorInfo1, selectorInfo2) => 1401 selectorInfo1.compareTo(selectorInfo2, this.#matchedSelectors) 1402 ); 1403 1404 // Now we know which of the matches is best, we can mark it BEST_MATCH. 1405 if ( 1406 this.#matchedSelectors.length && 1407 this.#matchedSelectors[0].status > STATUS.UNMATCHED 1408 ) { 1409 this.#matchedSelectors[0].status = STATUS.BEST; 1410 } 1411 } 1412 1413 /** 1414 * Process a matched CssSelector object. 1415 * 1416 * @private 1417 * @param {CssSelector} selector: the matched CssSelector object. 1418 * @param {STATUS} status: the CssSelector match status. 1419 * @param {Int} distance: See CssLogic.#buildMatchedRules for definition. 1420 */ 1421 #processMatchedSelector(selector, status, distance) { 1422 const cssRule = selector.cssRule; 1423 if ( 1424 cssRule.isPropertyAssigned(this.property) && 1425 (status == STATUS.MATCHED || 1426 (status == STATUS.PARENT_MATCH && 1427 InspectorUtils.isInheritedProperty( 1428 this.#cssLogic.viewedDocument, 1429 this.property 1430 ))) 1431 ) { 1432 const selectorInfo = new CssSelectorInfo( 1433 selector, 1434 this.property, 1435 // FIXME: If this is a property that is coming from a longhand property which is 1436 // using CSS variables, we would get an empty string at this point. 1437 // It would be nice to try to display a value that would make sense to the user. 1438 // See Bug 2003264 1439 cssRule.getPropertyValue(this.property), 1440 status, 1441 distance 1442 ); 1443 this.#matchedSelectors.push(selectorInfo); 1444 } 1445 } 1446 1447 /** 1448 * Refilter the matched selectors array when the CssLogic.sourceFilter 1449 * changes. This allows for quick filter changes. 1450 * 1451 * @private 1452 */ 1453 #refilterSelectors() { 1454 const passId = ++this.#cssLogic.passId; 1455 1456 const iterator = function (selectorInfo) { 1457 const cssRule = selectorInfo.selector.cssRule; 1458 if (cssRule.passId != passId) { 1459 cssRule.passId = passId; 1460 } 1461 }; 1462 1463 if (this.#matchedSelectors) { 1464 this.#matchedSelectors.forEach(iterator); 1465 } 1466 1467 this.needRefilter = false; 1468 } 1469 1470 toString() { 1471 return "CssPropertyInfo[" + this.property + "]"; 1472 } 1473 } 1474 1475 class CssSelectorInfo { 1476 /** 1477 * A class that holds information about a given CssSelector object. 1478 * 1479 * Instances of this class are given to CssHtmlTree in the array of matched 1480 * selectors. Each such object represents a displayable row in the PropertyView 1481 * objects. The information given by this object blends data coming from the 1482 * CssSheet, CssRule and from the CssSelector that own this object. 1483 * 1484 * @param {CssSelector} selector The CssSelector object for which to 1485 * present information. 1486 * @param {string} property The property for which information should 1487 * be retrieved. 1488 * @param {string} value The property value from the CssRule that owns 1489 * the selector. 1490 * @param {STATUS} status The selector match status. 1491 * @param {number} distance See CssLogic.#buildMatchedRules for definition. 1492 */ 1493 constructor(selector, property, value, status, distance) { 1494 this.selector = selector; 1495 this.property = property; 1496 this.status = status; 1497 this.distance = distance; 1498 this.value = value; 1499 const priority = this.selector.cssRule.getPropertyPriority(this.property); 1500 this.important = priority === "important"; 1501 1502 // Array<string|CSSLayerBlockRule> 1503 this.parentLayers = []; 1504 1505 // Go through all parent rules to populate this.parentLayers 1506 let rule = selector.cssRule.domRule; 1507 while (rule) { 1508 const className = ChromeUtils.getClassName(rule); 1509 if (className == "CSSLayerBlockRule") { 1510 // If the layer has a name, it's enough to uniquely identify it 1511 // If the layer does not have a name. We put the actual rule here, so we'll 1512 // be able to compare actual rule instances in `compareTo` 1513 this.parentLayers.push(rule.name || rule); 1514 } else if (className == "CSSImportRule" && rule.layerName !== null) { 1515 // Same reasoning for @import rule + layer 1516 this.parentLayers.push(rule.layerName || rule); 1517 } 1518 1519 // Get the parent rule (could be the parent stylesheet owner rule 1520 // for `@import url(path/to/file.css) layer`) 1521 rule = rule.parentRule || rule.parentStyleSheet?.ownerRule; 1522 } 1523 } 1524 1525 /** 1526 * Retrieve the CssSelector source element, which is the source of the CssRule 1527 * owning the selector. This is only available when the CssSelector comes from 1528 * an element.style. 1529 * 1530 * @return {string} the source element selector. 1531 */ 1532 get sourceElement() { 1533 return this.selector.sourceElement; 1534 } 1535 1536 /** 1537 * Retrieve the address of the CssSelector. This points to the address of the 1538 * CssSheet owning this selector. 1539 * 1540 * @return {string} the address of the CssSelector. 1541 */ 1542 get href() { 1543 return this.selector.href; 1544 } 1545 1546 /** 1547 * Check if the CssSelector comes from element.style or not. 1548 * 1549 * @return {boolean} true if the CssSelector comes from element.style, or 1550 * false otherwise. 1551 */ 1552 get inlineStyle() { 1553 return this.selector.inlineStyle; 1554 } 1555 1556 /** 1557 * Retrieve specificity information for the current selector. 1558 * 1559 * @return {object} an object holding specificity information for the current 1560 * selector. 1561 */ 1562 get specificity() { 1563 return this.selector.specificity; 1564 } 1565 1566 /** 1567 * Retrieve the parent stylesheet index/position in the viewed document. 1568 * 1569 * @return {number} the parent stylesheet index/position in the viewed 1570 * document. 1571 */ 1572 get sheetIndex() { 1573 return this.selector.sheetIndex; 1574 } 1575 1576 /** 1577 * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter. 1578 * 1579 * @return {boolean} true if the parent stylesheet is allowed by the current 1580 * sourceFilter, or false otherwise. 1581 */ 1582 get sheetAllowed() { 1583 return this.selector.sheetAllowed; 1584 } 1585 1586 /** 1587 * Retrieve the line of the parent CSSStyleRule in the parent CSSStyleSheet. 1588 * 1589 * @return {number} the line of the parent CSSStyleRule in the parent 1590 * stylesheet. 1591 */ 1592 get ruleLine() { 1593 return this.selector.ruleLine; 1594 } 1595 1596 /** 1597 * Retrieve the column of the parent CSSStyleRule in the parent CSSStyleSheet. 1598 * 1599 * @return {number} the column of the parent CSSStyleRule in the parent 1600 * stylesheet. 1601 */ 1602 get ruleColumn() { 1603 return this.selector.ruleColumn; 1604 } 1605 1606 /** 1607 * Check if the selector comes from a browser-provided stylesheet. 1608 * 1609 * @return {boolean} true if the selector comes from a browser-provided 1610 * stylesheet, or false otherwise. 1611 */ 1612 get agentRule() { 1613 return this.selector.agentRule; 1614 } 1615 1616 /** 1617 * Check if the selector comes from a webpage-provided stylesheet. 1618 * 1619 * @return {boolean} true if the selector comes from a webpage-provided 1620 * stylesheet, or false otherwise. 1621 */ 1622 get authorRule() { 1623 return this.selector.authorRule; 1624 } 1625 1626 /** 1627 * Check if the selector comes from a user stylesheet (userChrome.css or 1628 * userContent.css). 1629 * 1630 * @return {boolean} true if the selector comes from a webpage-provided 1631 * stylesheet, or false otherwise. 1632 */ 1633 get userRule() { 1634 return this.selector.userRule; 1635 } 1636 1637 /** 1638 * Compare the current CssSelectorInfo instance to another instance. 1639 * Since selectorInfos is computed from `InspectorUtils.getMatchingCSSRules`, 1640 * it's already sorted for regular cases. We only need to handle important values. 1641 * 1642 * @param {CssSelectorInfo} that 1643 * The instance to compare ourselves against. 1644 * @param {Array<CssSelectorInfo>} selectorInfos 1645 * The list of CssSelectorInfo we are currently ordering 1646 * @return {number} 1647 * -1, 0, 1 depending on how that compares with this. 1648 */ 1649 compareTo(that, selectorInfos) { 1650 const originalOrder = 1651 selectorInfos.indexOf(this) < selectorInfos.indexOf(that) ? -1 : 1; 1652 1653 // If both properties are not important, we can keep the original order 1654 if (!this.important && !that.important) { 1655 return originalOrder; 1656 } 1657 1658 // If one of the property is important and the other is not, the important one wins 1659 if (this.important !== that.important) { 1660 return this.important ? -1 : 1; 1661 } 1662 1663 // At this point, this and that are both important 1664 1665 const thisIsInLayer = !!this.parentLayers.length; 1666 const thatIsInLayer = !!that.parentLayers.length; 1667 1668 // If they're not in layers, we can keep the original rule order 1669 if (!thisIsInLayer && !thatIsInLayer) { 1670 return originalOrder; 1671 } 1672 1673 // If one of the rule is the style attribute, it wins 1674 if (this.selector.inlineStyle || that.selector.inlineStyle) { 1675 return this.selector.inlineStyle ? -1 : 1; 1676 } 1677 1678 // If one of the rule is not in a layer, then the rule in a layer wins. 1679 if (!thisIsInLayer || !thatIsInLayer) { 1680 return thisIsInLayer ? -1 : 1; 1681 } 1682 1683 const inSameLayers = 1684 this.parentLayers.length === that.parentLayers.length && 1685 this.parentLayers.every((layer, i) => layer === that.parentLayers[i]); 1686 // If both rules are in the same layer, we keep the original order 1687 if (inSameLayers) { 1688 return originalOrder; 1689 } 1690 1691 // When comparing declarations that belong to different layers, then for 1692 // important rules the declaration whose cascade layer is first wins. 1693 // We get the rules in the most-specific to least-specific order, meaning we'll have 1694 // rules in layers in the reverse order of the order of declarations of layers. 1695 // We can reverse that again to get the order of declarations of layers. 1696 return originalOrder * -1; 1697 } 1698 1699 compare(that, propertyName, type) { 1700 switch (type) { 1701 case COMPAREMODE.BOOLEAN: 1702 if (this[propertyName] && !that[propertyName]) { 1703 return -1; 1704 } 1705 if (!this[propertyName] && that[propertyName]) { 1706 return 1; 1707 } 1708 break; 1709 case COMPAREMODE.INTEGER: 1710 if (this[propertyName] > that[propertyName]) { 1711 return -1; 1712 } 1713 if (this[propertyName] < that[propertyName]) { 1714 return 1; 1715 } 1716 break; 1717 } 1718 return 0; 1719 } 1720 1721 toString() { 1722 return this.selector + " -> " + this.value; 1723 } 1724 } 1725 1726 exports.CssLogic = CssLogic; 1727 exports.CssSelector = CssSelector;