inactive-property-helper.js (55593B)
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 "use strict"; 6 7 loader.lazyRequireGetter( 8 this, 9 "CssLogic", 10 "resource://devtools/server/actors/inspector/css-logic.js", 11 true 12 ); 13 14 const TEXT_WRAP_BALANCE_LIMIT = Services.prefs.getIntPref( 15 "layout.css.text-wrap-balance.limit", 16 10 17 ); 18 19 const VISITED_MDN_LINK = 20 "https://developer.mozilla.org/docs/Web/CSS/Reference/Selectors/:visited"; 21 const VISITED_INVALID_PROPERTIES = allCssPropertiesExcept([ 22 "all", 23 "color", 24 "background", 25 "background-color", 26 "border", 27 "border-color", 28 "border-bottom-color", 29 "border-left-color", 30 "border-right-color", 31 "border-top-color", 32 "border-block", 33 "border-block-color", 34 "border-block-start-color", 35 "border-block-end-color", 36 "border-inline", 37 "border-inline-color", 38 "border-inline-start-color", 39 "border-inline-end-color", 40 "column-rule", 41 "column-rule-color", 42 "outline", 43 "outline-color", 44 "text-decoration-color", 45 "text-emphasis-color", 46 ]); 47 48 // Set of node names which are always treated as replaced elements: 49 const REPLACED_ELEMENTS_NAMES = new Set([ 50 "audio", 51 "br", 52 "button", 53 "canvas", 54 "embed", 55 "hr", 56 "iframe", 57 // Inputs are generally replaced elements. E.g. checkboxes and radios are replaced 58 // unless they have `appearance: none`. However unconditionally treating them 59 // as replaced is enough for our purpose here, and avoids extra complexity that 60 // will likely not be necessary in most cases. 61 "input", 62 "math", 63 "object", 64 "picture", 65 // Select is a replaced element if it has `size<=1` or no size specified, but 66 // unconditionally treating it as replaced is enough for our purpose here, and 67 // avoids extra complexity that will likely not be necessary in most cases. 68 "select", 69 "svg", 70 "textarea", 71 "video", 72 ]); 73 74 const CUE_PSEUDO_ELEMENT_STYLING_SPEC_URL = 75 "https://developer.mozilla.org/docs/Web/CSS/Reference/Selectors/::cue"; 76 77 const HIGHLIGHT_PSEUDO_ELEMENTS_STYLING_SPEC_URL = 78 "https://www.w3.org/TR/css-pseudo-4/#highlight-styling"; 79 const HIGHLIGHT_PSEUDO_ELEMENTS = [ 80 "::highlight", 81 "::selection", 82 "::target-text", 83 // Below are properties not yet implemented in Firefox (Bug 1694053) 84 "::grammar-error", 85 "::spelling-error", 86 ]; 87 const REGEXP_HIGHLIGHT_PSEUDO_ELEMENTS = new RegExp( 88 `${HIGHLIGHT_PSEUDO_ELEMENTS.join("|")}` 89 ); 90 const REGEXP_UPPERCASE_CHAR = /[A-Z]/; 91 92 const FIRST_LINE_PSEUDO_ELEMENT_STYLING_SPEC_URL = 93 "https://www.w3.org/TR/css-pseudo-4/#first-line-styling"; 94 95 const FIRST_LETTER_PSEUDO_ELEMENT_STYLING_SPEC_URL = 96 "https://www.w3.org/TR/css-pseudo-4/#first-letter-styling"; 97 98 const PLACEHOLDER_PSEUDO_ELEMENT_STYLING_SPEC_URL = 99 "https://www.w3.org/TR/css-pseudo-4/#placeholder-pseudo"; 100 101 const AT_POSITION_TRY_MDN_URL = 102 "https://developer.mozilla.org/docs/Web/CSS/Reference/At-rules/@position-try"; 103 104 class InactivePropertyHelper { 105 /** 106 * A list of rules for when CSS properties have no effect. 107 * 108 * In certain situations, CSS properties do not have any effect. A common 109 * example is trying to set a width on an inline element like a <span>. 110 * 111 * There are so many properties in CSS that it's difficult to remember which 112 * ones do and don't apply in certain situations. Some are straight-forward 113 * like `flex-wrap` only applying to an element that has `display:flex`. 114 * Others are less trivial like setting something other than a color on a 115 * `:visited` pseudo-class. 116 * 117 * This file contains "rules" in the form of objects with the following 118 * properties: 119 * { 120 * invalidProperties: 121 * Set of CSS property names that are inactive if the rule matches. 122 * when: 123 * The rule itself, a JS function used to identify the conditions 124 * indicating whether a property is valid or not. 125 * fixId: 126 * A Fluent id containing a suggested solution to the problem that is 127 * causing a property to be inactive. 128 * msgId: 129 * A Fluent id containing an error message explaining why a property is 130 * inactive in this situation. 131 * } 132 * 133 * If you add a new rule, also add a test for it in: 134 * server/tests/chrome/test_inspector-inactive-property-helper.html 135 * 136 * The main export is `getInactiveCssDataForProperty()`, which can be used to check if a 137 * property is inactive or not, and why. 138 * 139 * NOTE: We should generally *not* add rules here for any CSS properties that 140 * inherit by default, because it's hard for us to know whether such 141 * properties are truly "inactive". Web developers might legitimately set 142 * such a property on any arbitrary element, in order to concisely establish 143 * the default property-value throughout that element's subtree. For example, 144 * consider the "list-style-*" properties, which inherit by default and which 145 * only have a rendering effect on elements with "display:list-item" 146 * (e.g. <li>). It might superficially seem like we could add a rule here to 147 * warn about usages of these properties on non-"list-item" elements, but we 148 * shouldn't actually warn about that. A web developer may legitimately 149 * prefer to set these properties on an arbitrary container element (e.g. an 150 * <ol> element, or even the <html> element) in order to concisely adjust the 151 * rendering of a whole list (or all the lists in a document). 152 */ 153 get INVALID_PROPERTIES_VALIDATORS() { 154 return [ 155 // Flex container property used on non-flex container. 156 { 157 invalidProperties: ["flex-direction", "flex-flow", "flex-wrap"], 158 when: () => !this.flexContainer, 159 fixId: "inactive-css-not-flex-container-fix", 160 msgId: "inactive-css-not-flex-container", 161 }, 162 // Flex item property used on non-flex item. 163 { 164 invalidProperties: ["flex", "flex-basis", "flex-grow", "flex-shrink"], 165 when: () => !this.flexItem, 166 fixId: "inactive-css-not-flex-item-fix-2", 167 msgId: "inactive-css-not-flex-item", 168 }, 169 // Grid container property used on non-grid container. 170 { 171 invalidProperties: [ 172 "grid-auto-columns", 173 "grid-auto-flow", 174 "grid-auto-rows", 175 "grid-template", 176 "grid-template-areas", 177 "grid-template-columns", 178 "grid-template-rows", 179 "justify-items", 180 ], 181 when: () => !this.gridContainer, 182 fixId: "inactive-css-not-grid-container-fix", 183 msgId: "inactive-css-not-grid-container", 184 }, 185 // Grid/absolutely positioned item property used on non-grid/non-absolutely positioned item. 186 { 187 invalidProperties: [ 188 "grid-area", 189 "grid-column", 190 "grid-column-end", 191 "grid-column-start", 192 "grid-row", 193 "grid-row-end", 194 "grid-row-start", 195 ], 196 when: () => !this.gridItem && !this.isAbsPosGridElement(), 197 fixId: "inactive-css-not-grid-item-fix-2", 198 msgId: "inactive-css-not-grid-item", 199 }, 200 // Grid and flex item properties used on non-grid or non-flex item. 201 { 202 invalidProperties: ["order"], 203 when: () => !this.gridItem && !this.flexItem, 204 fixId: "inactive-css-not-grid-or-flex-item-fix-3", 205 msgId: "inactive-css-not-grid-or-flex-item", 206 }, 207 // Absolutely positioned, grid and flex item properties used on non absolutely positioned, 208 // non-grid or non-flex item. 209 { 210 invalidProperties: ["align-self", "place-self"], 211 when: () => 212 !this.gridItem && !this.flexItem && !this.isAbsolutelyPositioned, 213 fixId: 214 "inactive-css-not-grid-or-flex-or-absolutely-positioned-item-fix", 215 msgId: "inactive-css-not-grid-or-flex-or-absolutely-positioned-item", 216 }, 217 // Absolutely positioned and grid item properties used on non absolutely positioned, 218 // or non-grid item. 219 { 220 invalidProperties: ["justify-self"], 221 // This should be updated when justify-self support is added on block level boxes (see Bug 2005203) 222 when: () => !this.gridItem && !this.isAbsolutelyPositioned, 223 fixId: "inactive-css-not-grid-or-absolutely-positioned-item-fix", 224 msgId: "inactive-css-not-grid-or-absolutely-positioned-item", 225 }, 226 // Grid and flex container properties used on non-grid or non-flex container. 227 { 228 invalidProperties: [ 229 "align-items", 230 "justify-content", 231 "place-content", 232 "place-items", 233 "row-gap", 234 // grid-*-gap are supported legacy shorthands for the corresponding *-gap properties. 235 // See https://drafts.csswg.org/css-align-3/#gap-legacy for more information. 236 "grid-row-gap", 237 ], 238 when: () => !this.gridContainer && !this.flexContainer, 239 fixId: "inactive-css-not-grid-or-flex-container-fix", 240 msgId: "inactive-css-not-grid-or-flex-container", 241 }, 242 // align-content is special as align-content:baseline does have an effect on all 243 // grid items, flex items and table cells, regardless of what type of box they are. 244 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1598730 245 { 246 invalidProperties: ["align-content"], 247 when: () => { 248 if (this.style["align-content"].includes("baseline")) { 249 return false; 250 } 251 const supportedDisplay = [ 252 "flex", 253 "inline-flex", 254 "grid", 255 "inline-grid", 256 "block", 257 "inline-block", 258 // Uncomment table-cell when Bug 1883357 is fixed. 259 // "table-cell" 260 ]; 261 return !this.checkComputedStyle("display", supportedDisplay); 262 }, 263 fixId: "inactive-css-not-grid-or-flex-or-block-container-fix", 264 msgId: "inactive-css-property-because-of-display", 265 }, 266 // column-gap and shorthands used on non-grid or non-flex or non-multi-col container. 267 { 268 invalidProperties: [ 269 "column-gap", 270 "gap", 271 "grid-gap", 272 // grid-*-gap are supported legacy shorthands for the corresponding *-gap properties. 273 // See https://drafts.csswg.org/css-align-3/#gap-legacy for more information. 274 "grid-column-gap", 275 ], 276 when: () => 277 !this.gridContainer && !this.flexContainer && !this.multiColContainer, 278 fixId: 279 "inactive-css-not-grid-or-flex-container-or-multicol-container-fix", 280 msgId: "inactive-css-not-grid-or-flex-container-or-multicol-container", 281 }, 282 // Multi-column related properties used on non-multi-column container. 283 { 284 invalidProperties: [ 285 "column-fill", 286 "column-rule", 287 "column-rule-color", 288 "column-rule-style", 289 "column-rule-width", 290 ], 291 when: () => !this.multiColContainer, 292 fixId: "inactive-css-not-multicol-container-fix", 293 msgId: "inactive-css-not-multicol-container", 294 }, 295 // column-span used within non-multi-column container. 296 { 297 invalidProperties: ["column-span"], 298 when: () => !this.inMultiColContainer, 299 fixId: "inactive-css-column-span-fix", 300 msgId: "inactive-css-column-span", 301 }, 302 // Inline properties used on non-inline-level elements. 303 { 304 invalidProperties: ["vertical-align"], 305 when: () => 306 !this.isInlineLevel() && !this.isFirstLetter && !this.isFirstLine, 307 fixId: "inactive-css-not-inline-or-tablecell-fix", 308 msgId: "inactive-css-not-inline-or-tablecell", 309 }, 310 // Writing mode properties used on ::first-line pseudo-element. 311 { 312 invalidProperties: ["direction", "text-orientation", "writing-mode"], 313 when: () => this.isFirstLine, 314 fixId: "learn-more", 315 msgId: "inactive-css-first-line-pseudo-element-not-supported", 316 learnMoreURL: FIRST_LINE_PSEUDO_ELEMENT_STYLING_SPEC_URL, 317 }, 318 // Content modifying properties used on ::first-letter pseudo-element. 319 { 320 invalidProperties: ["content"], 321 when: () => this.isFirstLetter, 322 fixId: "learn-more", 323 msgId: "inactive-css-first-letter-pseudo-element-not-supported", 324 learnMoreURL: FIRST_LETTER_PSEUDO_ELEMENT_STYLING_SPEC_URL, 325 }, 326 // Writing mode or inline properties used on ::placeholder pseudo-element. 327 { 328 invalidProperties: [ 329 "baseline-source", 330 "direction", 331 "dominant-baseline", 332 "line-height", 333 "text-orientation", 334 "vertical-align", 335 "writing-mode", 336 // Below are properties not yet implemented in Firefox (Bug 1312611) 337 "alignment-baseline", 338 "baseline-shift", 339 "initial-letter", 340 "text-box-trim", 341 ], 342 when: () => { 343 const { selectorText } = this.cssRule; 344 return selectorText && selectorText.includes("::placeholder"); 345 }, 346 fixId: "learn-more", 347 msgId: "inactive-css-placeholder-pseudo-element-not-supported", 348 learnMoreURL: PLACEHOLDER_PSEUDO_ELEMENT_STYLING_SPEC_URL, 349 }, 350 // (max-|min-)width used on inline elements, table rows, or row groups. 351 { 352 invalidProperties: ["max-width", "min-width", "width"], 353 when: () => 354 this.nonReplacedInlineBox || 355 this.horizontalTableTrack || 356 this.horizontalTableTrackGroup, 357 fixId: "inactive-css-non-replaced-inline-or-table-row-or-row-group-fix", 358 msgId: "inactive-css-property-because-of-display", 359 }, 360 // (max-|min-)height used on inline elements, table columns, or column groups. 361 { 362 invalidProperties: ["max-height", "min-height", "height"], 363 when: () => 364 this.nonReplacedInlineBox || 365 this.verticalTableTrack || 366 this.verticalTableTrackGroup, 367 fixId: 368 "inactive-css-non-replaced-inline-or-table-column-or-column-group-fix", 369 msgId: "inactive-css-property-because-of-display", 370 }, 371 { 372 invalidProperties: ["display"], 373 when: () => 374 this.isFloated && 375 this.checkResolvedStyle("display", [ 376 "inline", 377 "inline-block", 378 "inline-table", 379 "inline-flex", 380 "inline-grid", 381 "table-cell", 382 "table-row", 383 "table-row-group", 384 "table-header-group", 385 "table-footer-group", 386 "table-column", 387 "table-column-group", 388 "table-caption", 389 ]), 390 fixId: "inactive-css-not-display-block-on-floated-fix", 391 msgId: "inactive-css-not-display-block-on-floated-2", 392 }, 393 // float property used on non-floating elements. 394 { 395 invalidProperties: ["float"], 396 when: () => this.gridItem || this.flexItem, 397 fixId: "inactive-css-only-non-grid-or-flex-item-fix", 398 msgId: "inactive-css-only-non-grid-or-flex-item", 399 }, 400 // clear property used on non-floating elements. 401 { 402 invalidProperties: ["clear"], 403 when: () => 404 !this.isBlockLevel() && 405 // The br element is a special case and allows clear for backwards compatibility to make its clear attribute work. 406 // https://html.spec.whatwg.org/multipage/rendering.html#phrasing-content-3 407 this.localName != "br", 408 fixId: "inactive-css-not-block-fix", 409 msgId: "inactive-css-not-block", 410 }, 411 // Block container properties used on non-block-container elements. 412 { 413 invalidProperties: ["text-overflow"], 414 when: () => !this.isBlockContainer(), 415 fixId: "inactive-css-not-block-container-fix", 416 msgId: "inactive-css-not-block-container", 417 }, 418 // Block, flex, and grid container properties used on non-block, non-flex or non-grid container elements. 419 { 420 invalidProperties: [ 421 "overflow", 422 "overflow-block", 423 "overflow-inline", 424 "overflow-x", 425 "overflow-y", 426 ], 427 when: () => 428 !this.isBlockContainer() && 429 !this.flexContainer && 430 !this.gridContainer, 431 fixId: "inactive-css-not-block-flex-grid-container-fix", 432 msgId: "inactive-css-not-block-flex-grid-container", 433 }, 434 // shape-image-threshold, shape-margin, shape-outside properties used on non-floated elements. 435 { 436 invalidProperties: [ 437 "shape-image-threshold", 438 "shape-margin", 439 "shape-outside", 440 ], 441 when: () => !this.isFloated, 442 fixId: "inactive-css-not-floated-fix", 443 msgId: "inactive-css-not-floated", 444 }, 445 // The property is impossible to override due to :visited restriction. 446 { 447 invalidProperties: VISITED_INVALID_PROPERTIES, 448 when: () => this.isVisitedRule(), 449 fixId: "learn-more", 450 msgId: "inactive-css-property-is-impossible-to-override-in-visited", 451 learnMoreURL: VISITED_MDN_LINK, 452 }, 453 // top, right, bottom, left properties used on non positioned boxes. 454 { 455 invalidProperties: ["top", "right", "bottom", "left"], 456 when: () => !this.isPositioned, 457 fixId: "inactive-css-position-property-on-unpositioned-box-fix", 458 msgId: "inactive-css-position-property-on-unpositioned-box", 459 }, 460 // z-index property used on non positioned boxes that are not grid/flex items. 461 { 462 invalidProperties: ["z-index"], 463 when: () => !this.isPositioned && !this.gridItem && !this.flexItem, 464 fixId: "inactive-css-position-property-on-unpositioned-box-fix", 465 msgId: "inactive-css-position-property-on-unpositioned-box", 466 }, 467 // object-fit or object-position property used on non-replaced elements. 468 { 469 invalidProperties: ["object-fit", "object-position"], 470 when: () => !this.replaced, 471 fixId: "inactive-css-only-replaced-elements-fix", 472 msgId: "inactive-css-only-replaced-elements", 473 }, 474 // text-overflow property used on elements for which 'overflow' is set to 'visible' 475 // (the initial value) in the inline axis. Note that this validator only checks if 476 // 'overflow-inline' computes to 'visible' on the element. 477 // In theory, we should also be checking if the element is a block as this doesn't 478 // normally work on inline element. However there are many edge cases that made it 479 // impossible for the JS code to determine whether the type of box would support 480 // text-overflow. So, rather than risking to show invalid warnings, we decided to 481 // only warn when 'overflow-inline: visible' was set. There is more information 482 // about this in this discussion https://phabricator.services.mozilla.com/D62407 and 483 // on the bug https://bugzilla.mozilla.org/show_bug.cgi?id=1551578 484 { 485 invalidProperties: ["text-overflow"], 486 when: () => this.checkComputedStyle("overflow-inline", ["visible"]), 487 fixId: "inactive-text-overflow-when-no-overflow-fix", 488 msgId: "inactive-text-overflow-when-no-overflow", 489 }, 490 // content-visibility used on elements for which size containment doesn't apply. 491 { 492 invalidProperties: ["content-visibility"], 493 when: () => 494 !this.hasPrincipalBox || 495 this.table || 496 this.internalTableElement || 497 this.rubyContainer || 498 this.internalRubyElement || 499 this.nonAtomicInlineBox, 500 fixId: "inactive-css-no-size-containment-fix", 501 msgId: "inactive-css-no-size-containment", 502 }, 503 // margin properties used on table internal elements. 504 { 505 invalidProperties: [ 506 "margin", 507 "margin-block", 508 "margin-block-end", 509 "margin-block-start", 510 "margin-bottom", 511 "margin-inline", 512 "margin-inline-end", 513 "margin-inline-start", 514 "margin-left", 515 "margin-right", 516 "margin-top", 517 ], 518 when: () => this.internalTableElement, 519 fixId: "inactive-css-not-for-internal-table-elements-fix", 520 msgId: "inactive-css-not-for-internal-table-elements", 521 }, 522 // padding properties used on table internal elements except table cells. 523 { 524 invalidProperties: [ 525 "padding", 526 "padding-block", 527 "padding-block-end", 528 "padding-block-start", 529 "padding-bottom", 530 "padding-inline", 531 "padding-inline-end", 532 "padding-inline-start", 533 "padding-left", 534 "padding-right", 535 "padding-top", 536 ], 537 when: () => 538 this.internalTableElement && 539 !this.checkComputedStyle("display", ["table-cell"]), 540 fixId: 541 "inactive-css-not-for-internal-table-elements-except-table-cells-fix", 542 msgId: 543 "inactive-css-not-for-internal-table-elements-except-table-cells", 544 }, 545 // table-related properties used on non-table elements. 546 { 547 invalidProperties: [ 548 "border-collapse", 549 "border-spacing", 550 "table-layout", 551 ], 552 when: () => 553 !this.checkComputedStyle("display", ["table", "inline-table"]), 554 fixId: "inactive-css-not-table-fix", 555 msgId: "inactive-css-not-table", 556 }, 557 // border-spacing property used on collapsed table borders. 558 { 559 invalidProperties: ["border-spacing"], 560 when: () => this.checkComputedStyle("border-collapse", ["collapse"]), 561 fixId: "inactive-css-collapsed-table-borders-fix", 562 msgId: "inactive-css-collapsed-table-borders", 563 }, 564 // empty-cells property used on non-table-cell elements. 565 { 566 invalidProperties: ["empty-cells"], 567 when: () => !this.checkComputedStyle("display", ["table-cell"]), 568 fixId: "inactive-css-not-table-cell-fix", 569 msgId: "inactive-css-not-table-cell", 570 }, 571 // scroll-padding-* properties used on non-scrollable elements. 572 { 573 invalidProperties: [ 574 "scroll-padding", 575 "scroll-padding-top", 576 "scroll-padding-right", 577 "scroll-padding-bottom", 578 "scroll-padding-left", 579 "scroll-padding-block", 580 "scroll-padding-block-end", 581 "scroll-padding-block-start", 582 "scroll-padding-inline", 583 "scroll-padding-inline-end", 584 "scroll-padding-inline-start", 585 ], 586 when: () => !this.isScrollContainer, 587 fixId: "inactive-scroll-padding-when-not-scroll-container-fix", 588 msgId: "inactive-scroll-padding-when-not-scroll-container", 589 }, 590 // border-image properties used on internal table with border collapse. 591 { 592 invalidProperties: [ 593 "border-image", 594 "border-image-outset", 595 "border-image-repeat", 596 "border-image-slice", 597 "border-image-source", 598 "border-image-width", 599 ], 600 when: () => 601 this.internalTableElement && 602 this.checkTableParentHasBorderCollapsed(), 603 fixId: "inactive-css-border-image-fix", 604 msgId: "inactive-css-border-image", 605 }, 606 // width & height properties used on ruby elements. 607 { 608 invalidProperties: [ 609 "height", 610 "min-height", 611 "max-height", 612 "width", 613 "min-width", 614 "max-width", 615 ], 616 when: () => this.checkComputedStyle("display", ["ruby", "ruby-text"]), 617 fixId: "inactive-css-ruby-element-fix", 618 msgId: "inactive-css-ruby-element", 619 }, 620 // resize property used on non-overflowing elements or replaced elements other than textarea. 621 { 622 invalidProperties: ["resize"], 623 when: () => !this.isScrollContainer && !this.isResizableReplacedElement, 624 fixId: "inactive-css-resize-fix", 625 msgId: "inactive-css-resize", 626 }, 627 // text-wrap: balance; used on elements exceeding the threshold line number 628 { 629 invalidProperties: ["text-wrap"], 630 when: () => { 631 if (!this.checkComputedStyle("text-wrap", ["balance"])) { 632 return false; 633 } 634 const blockLineCounts = InspectorUtils.getBlockLineCounts(this.node); 635 // We only check the number of lines within the first block 636 // because the text-wrap: balance; property only applies to 637 // the first block. And fragmented elements (with multiple 638 // blocks) are excluded from line balancing for the time being. 639 return ( 640 blockLineCounts && blockLineCounts[0] > TEXT_WRAP_BALANCE_LIMIT 641 ); 642 }, 643 fixId: "inactive-css-text-wrap-balance-lines-exceeded-fix", 644 msgId: "inactive-css-text-wrap-balance-lines-exceeded", 645 lineCount: TEXT_WRAP_BALANCE_LIMIT, 646 }, 647 // text-wrap: balance; used on fragmented elements 648 { 649 invalidProperties: ["text-wrap"], 650 when: () => { 651 if (!this.checkComputedStyle("text-wrap", ["balance"])) { 652 return false; 653 } 654 const blockLineCounts = InspectorUtils.getBlockLineCounts(this.node); 655 const isFragmented = blockLineCounts && blockLineCounts.length > 1; 656 return isFragmented; 657 }, 658 fixId: "inactive-css-text-wrap-balance-fragmented-fix", 659 msgId: "inactive-css-text-wrap-balance-fragmented", 660 }, 661 // box-sizing used on element ignoring width and height. 662 { 663 invalidProperties: ["box-sizing"], 664 when: () => this.nonReplacedInlineBox, 665 fixId: "learn-more", 666 msgId: "inactive-css-no-width-height", 667 }, 668 // anchor-name used on element not creating a principal box. 669 { 670 invalidProperties: ["anchor-name"], 671 when: () => !this.hasPrincipalBox, 672 fixId: "inactive-css-no-principal-box-fix", 673 msgId: "inactive-css-no-principal-box", 674 }, 675 ]; 676 } 677 678 /** 679 * A list of rules for when CSS properties have no effect, 680 * based on an allow list of properties. 681 * We're setting this as a different array than INVALID_PROPERTIES_VALIDATORS as we 682 * need to check every properties, which we don't do for invalid properties ( see check 683 * on this.invalidProperties). 684 * 685 * This file contains "rules" in the form of objects with the following 686 * properties: 687 * { 688 * acceptedProperties: 689 * Array of CSS property names that are the only one accepted if the rule matches. 690 * when: 691 * The rule itself, a JS function used to identify the conditions 692 * indicating whether a property is valid or not. 693 * fixId: 694 * A Fluent id containing a suggested solution to the problem that is 695 * causing a property to be inactive. 696 * msgId: 697 * A Fluent id containing an error message explaining why a property is 698 * inactive in this situation. 699 * } 700 * 701 * If you add a new rule, also add a test for it in: 702 * server/tests/chrome/test_inspector-inactive-property-helper.html 703 * 704 * The main export is `getInactiveCssDataForProperty()`, which can be used to check if a 705 * property is used or not, and why. 706 */ 707 ACCEPTED_PROPERTIES_VALIDATORS = [ 708 // Constrained set of properties on highlight pseudo-elements 709 { 710 acceptedProperties: new Set([ 711 // At the moment, for shorthand we don't look into each properties it covers, 712 // and so, although `background` might hold inactive values (e.g. background-image) 713 // we don't want to mark it as inactive if it sets a background-color (e.g. background: red). 714 "background", 715 "background-color", 716 "color", 717 "text-decoration", 718 "text-decoration-color", 719 "text-decoration-line", 720 "text-decoration-style", 721 "text-decoration-thickness", 722 "text-shadow", 723 "text-underline-offset", 724 "text-underline-position", 725 "-webkit-text-fill-color", 726 "-webkit-text-stroke-color", 727 "-webkit-text-stroke-width", 728 "-webkit-text-stroke", 729 ]), 730 when: () => { 731 const { selectorText } = this.cssRule; 732 return ( 733 selectorText && REGEXP_HIGHLIGHT_PSEUDO_ELEMENTS.test(selectorText) 734 ); 735 }, 736 msgId: "inactive-css-highlight-pseudo-elements-not-supported", 737 fixId: "learn-more", 738 learnMoreURL: HIGHLIGHT_PSEUDO_ELEMENTS_STYLING_SPEC_URL, 739 }, 740 // Constrained set of properties on ::cue pseudo-element 741 // 742 // Note that Gecko doesn't yet support the ::cue() pseudo-element 743 // taking a selector as argument. The properties accecpted by that 744 // partly differ from the ones accepted by the ::cue pseudo-element. 745 // See https://w3c.github.io/webvtt/#ref-for-selectordef-cue-selectorâ‘§. 746 // See https://bugzilla.mozilla.org/show_bug.cgi?id=865395 and its 747 // dependencies for the implementation status. 748 { 749 acceptedProperties: new Set([ 750 "background", 751 "background-attachment", 752 // The WebVTT spec. currently only allows all properties covered by 753 // the `background` shorthand and `background-blend-mode` is not 754 // part of that, though Gecko does support it, anyway. 755 // Therefore, there's also an issue pending to add it (and others) 756 // to the spec. See https://github.com/w3c/webvtt/issues/518. 757 "background-blend-mode", 758 "background-clip", 759 "background-color", 760 "background-image", 761 "background-origin", 762 "background-position", 763 "background-position-x", 764 "background-position-y", 765 "background-repeat", 766 "background-size", 767 "color", 768 "font", 769 "font-family", 770 "font-size", 771 "font-stretch", 772 "font-style", 773 "font-variant", 774 "font-variant-alternates", 775 "font-variant-caps", 776 "font-variant-east-asian", 777 "font-variant-ligatures", 778 "font-variant-numeric", 779 "font-variant-position", 780 "font-weight", 781 "line-height", 782 "opacity", 783 "outline", 784 "outline-color", 785 "outline-offset", 786 "outline-style", 787 "outline-width", 788 "ruby-position", 789 "text-combine-upright", 790 "text-decoration", 791 "text-decoration-color", 792 "text-decoration-line", 793 "text-decoration-style", 794 "text-decoration-thickness", 795 "text-shadow", 796 "visibility", 797 "white-space", 798 ]), 799 when: () => { 800 const { selectorText } = this.cssRule; 801 return selectorText && selectorText.includes("::cue"); 802 }, 803 msgId: "inactive-css-cue-pseudo-element-not-supported", 804 fixId: "learn-more", 805 learnMoreURL: CUE_PSEUDO_ELEMENT_STYLING_SPEC_URL, 806 }, 807 // Constrained set of properties on @position-try rules 808 { 809 acceptedProperties: new Set( 810 Object.keys(globalThis.CSSPositionTryDescriptors.prototype).filter( 811 // CSSPositionTryDescriptors.prototype gives us both css property names 812 // and their JS equivalent (e.g. `min-width` and `minWidth`). 813 // We can filter out the latter by checking if the property has an uppercase 814 p => !REGEXP_UPPERCASE_CHAR.test(p) 815 ) 816 ), 817 rejectCustomProperties: true, 818 when: () => 819 ChromeUtils.getClassName(this.cssRule) === "CSSPositionTryRule", 820 msgId: "inactive-css-at-position-try-not-supported", 821 fixId: "learn-more", 822 learnMoreURL: AT_POSITION_TRY_MDN_URL, 823 }, 824 ]; 825 826 /** 827 * Get a list of unique CSS property names for which there are checks 828 * for used/unused state. 829 * 830 * @return {Set} 831 * List of CSS properties 832 */ 833 get invalidProperties() { 834 if (!this._invalidProperties) { 835 const allProps = this.INVALID_PROPERTIES_VALIDATORS.map( 836 v => v.invalidProperties 837 ).flat(); 838 this._invalidProperties = new Set(allProps); 839 } 840 841 return this._invalidProperties; 842 } 843 844 /** 845 * Is this CSS property having any effect on this element? 846 * 847 * @param {DOMNode} el 848 * The DOM element. 849 * @param {Style} elStyle 850 * The computed style for this DOMNode. 851 * @param {DOMRule} cssRule 852 * The CSS rule the property is defined in. 853 * @param {string} property 854 * The CSS property name. 855 * 856 * @return {object | null} object 857 * if the property is active, this will return null 858 * @return {string} object.display 859 * The element computed display value. 860 * @return {string} object.fixId 861 * A Fluent id containing a suggested solution to the problem that is 862 * causing a property to be inactive. 863 * @return {string} object.msgId 864 * A Fluent id containing an error message explaining why a property 865 * is inactive in this situation. 866 * @return {string} object.property 867 * The inactive property name. 868 * @return {string} object.learnMoreURL 869 * An optional link if we need to open an other link than 870 * the default MDN property one. 871 */ 872 getInactiveCssDataForProperty(el, elStyle, cssRule, property) { 873 let fixId = ""; 874 let msgId = ""; 875 let learnMoreURL = null; 876 let lineCount = null; 877 let used = true; 878 879 const someFn = validator => { 880 // First check if this rule cares about this property. 881 let isRuleConcerned = false; 882 883 if (validator.invalidProperties) { 884 isRuleConcerned = validator.invalidProperties.includes(property); 885 } else if (validator.acceptedProperties) { 886 isRuleConcerned = 887 !validator.acceptedProperties.has(property) && 888 (!property.startsWith("--") || validator.rejectCustomProperties); 889 } 890 891 if (!isRuleConcerned) { 892 return false; 893 } 894 895 this.select(el, elStyle, cssRule, property); 896 897 // And then run the validator, gathering the error message if the 898 // validator passes. 899 if (validator.when()) { 900 fixId = validator.fixId; 901 msgId = validator.msgId; 902 learnMoreURL = validator.learnMoreURL; 903 lineCount = validator.lineCount; 904 used = false; 905 906 // We can bail out as soon as a validator reported an issue. 907 return true; 908 } 909 910 return false; 911 }; 912 913 // First run the accepted properties validators 914 const isNotAccepted = this.ACCEPTED_PROPERTIES_VALIDATORS.some(someFn); 915 916 // If the property is not in the list of properties to check and there was no issues 917 // in the accepted properties validators, assume the property is used. 918 if (!isNotAccepted && !this.invalidProperties.has(property)) { 919 this.unselect(); 920 return null; 921 } 922 923 // Otherwise, if there was no issue from the accepted properties validators, 924 // run the invalid properties validators. 925 if (!isNotAccepted) { 926 this.INVALID_PROPERTIES_VALIDATORS.some(someFn); 927 } 928 929 this.unselect(); 930 931 // Accessing elStyle might throws, we wrap it in a try/catch block to avoid test 932 // failures. 933 let display; 934 try { 935 display = elStyle ? elStyle.display : null; 936 } catch (e) {} 937 938 if (used) { 939 return null; 940 } 941 942 return { 943 display, 944 fixId, 945 msgId, 946 property, 947 learnMoreURL, 948 lineCount, 949 }; 950 } 951 952 /** 953 * Focus on a node. 954 * 955 * @param {DOMNode} node 956 * Node to focus on. 957 */ 958 select(node, style, cssRule, property) { 959 this._node = node; 960 this._cssRule = cssRule; 961 this._property = property; 962 this._style = style; 963 } 964 965 /** 966 * Clear references to avoid leaks. 967 */ 968 unselect() { 969 this._node = null; 970 this._cssRule = null; 971 this._property = null; 972 this._style = null; 973 } 974 975 /** 976 * Provide a public reference to node. 977 */ 978 get node() { 979 return this._node; 980 } 981 982 /** 983 * Cache and provide node's computed style. 984 */ 985 get style() { 986 return this._style; 987 } 988 989 /** 990 * Provide a public reference to the css rule. 991 */ 992 get cssRule() { 993 return this._cssRule; 994 } 995 996 /** 997 * Check if the current node's propName is set to one of the values passed in 998 * the values array. 999 * 1000 * @param {string} propName 1001 * Property name to check. 1002 * @param {Array} values 1003 * Values to compare against. 1004 */ 1005 checkComputedStyle(propName, values) { 1006 if (!this.style) { 1007 return false; 1008 } 1009 return values.some(value => this.style[propName] === value); 1010 } 1011 1012 /** 1013 * Check if a rule's propName is set to one of the values passed in the values 1014 * array. 1015 * 1016 * @param {string} propName 1017 * Property name to check. 1018 * @param {Array} values 1019 * Values to compare against. 1020 */ 1021 checkResolvedStyle(propName, values) { 1022 if (!(this.cssRule && this.cssRule.style)) { 1023 return false; 1024 } 1025 const { style } = this.cssRule; 1026 1027 return values.some(value => style[propName] === value); 1028 } 1029 1030 /** 1031 * Check if the current node is an block-level box. 1032 */ 1033 isBlockLevel() { 1034 return this.checkComputedStyle("display", [ 1035 "block", 1036 "flow-root", 1037 "flex", 1038 "grid", 1039 "table", 1040 ]); 1041 } 1042 1043 /** 1044 * Check if the current node is an block container. 1045 */ 1046 isBlockContainer() { 1047 return this.node ? InspectorUtils.isBlockContainer(this.node) : false; 1048 } 1049 1050 /** 1051 * Check if the current node is an inline-level box. 1052 */ 1053 isInlineLevel() { 1054 return this.checkComputedStyle("display", [ 1055 "inline", 1056 "inline-block", 1057 "inline-table", 1058 "inline-flex", 1059 "inline-grid", 1060 "table-cell", 1061 "table-row", 1062 "table-row-group", 1063 "table-header-group", 1064 "table-footer-group", 1065 ]); 1066 } 1067 1068 /** 1069 * Check if the current node is a flex container i.e. a node that has a style 1070 * of `display:flex` or `display:inline-flex`. 1071 */ 1072 get flexContainer() { 1073 return this.checkComputedStyle("display", ["flex", "inline-flex"]); 1074 } 1075 1076 /** 1077 * Check if the current node is a flex item. 1078 */ 1079 get flexItem() { 1080 return this.isFlexItem(this.node); 1081 } 1082 1083 /** 1084 * Check if the current node is a grid container i.e. a node that has a style 1085 * of `display:grid` or `display:inline-grid`. 1086 */ 1087 get gridContainer() { 1088 return this.checkComputedStyle("display", ["grid", "inline-grid"]); 1089 } 1090 1091 /** 1092 * Check if the current node is a grid item. 1093 */ 1094 get gridItem() { 1095 return this.isGridItem(this.node); 1096 } 1097 1098 /** 1099 * Check if the current node is a multi-column container, i.e. a node element whose 1100 * `column-width` or `column-count` property is not `auto`. 1101 */ 1102 get multiColContainer() { 1103 const autoColumnWidth = this.checkComputedStyle("column-width", ["auto"]); 1104 const autoColumnCount = this.checkComputedStyle("column-count", ["auto"]); 1105 1106 return !autoColumnWidth || !autoColumnCount; 1107 } 1108 1109 /** 1110 * Check if the current node is in a multi-column container, i.e. a node element 1111 * that has an ancestor with `column-width` or `column-count` property set to a value. 1112 */ 1113 get inMultiColContainer() { 1114 return !!this.getParentMultiColElement(this.node); 1115 } 1116 1117 /** 1118 * Check if the current node is a table. 1119 */ 1120 get table() { 1121 return this.checkComputedStyle("display", ["table", "inline-table"]); 1122 } 1123 1124 /** 1125 * Check if the current node is a table row. 1126 */ 1127 get tableRow() { 1128 return this.style && this.style.display === "table-row"; 1129 } 1130 1131 /** 1132 * Check if the current node is a table column. 1133 */ 1134 get tableColumn() { 1135 return this.style && this.style.display === "table-column"; 1136 } 1137 1138 /** 1139 * Check if the current node is an internal table element. 1140 */ 1141 get internalTableElement() { 1142 return this.checkComputedStyle("display", [ 1143 "table-cell", 1144 "table-row", 1145 "table-row-group", 1146 "table-header-group", 1147 "table-footer-group", 1148 "table-column", 1149 "table-column-group", 1150 ]); 1151 } 1152 1153 /** 1154 * Check if the current node is a horizontal table track. That is: either a table row 1155 * displayed in horizontal writing mode, or a table column displayed in vertical writing 1156 * mode. 1157 */ 1158 get horizontalTableTrack() { 1159 if (!this.tableRow && !this.tableColumn) { 1160 return false; 1161 } 1162 1163 const tableTrackParent = this.getTableTrackParent(); 1164 1165 return this.hasVerticalWritingMode(tableTrackParent) 1166 ? this.tableColumn 1167 : this.tableRow; 1168 } 1169 1170 /** 1171 * Check if the current node is a vertical table track. That is: either a table row 1172 * displayed in vertical writing mode, or a table column displayed in horizontal writing 1173 * mode. 1174 */ 1175 get verticalTableTrack() { 1176 if (!this.tableRow && !this.tableColumn) { 1177 return false; 1178 } 1179 1180 const tableTrackParent = this.getTableTrackParent(); 1181 1182 return this.hasVerticalWritingMode(tableTrackParent) 1183 ? this.tableRow 1184 : this.tableColumn; 1185 } 1186 1187 /** 1188 * Check if the current node is a row group. 1189 */ 1190 get rowGroup() { 1191 return this.isRowGroup(this.node); 1192 } 1193 1194 /** 1195 * Check if the current node is a table column group. 1196 */ 1197 get columnGroup() { 1198 return this.isColumnGroup(this.node); 1199 } 1200 1201 /** 1202 * Check if the current node is a horizontal table track group. That is: either a table 1203 * row group displayed in horizontal writing mode, or a table column group displayed in 1204 * vertical writing mode. 1205 */ 1206 get horizontalTableTrackGroup() { 1207 if (!this.rowGroup && !this.columnGroup) { 1208 return false; 1209 } 1210 1211 const tableTrackParent = this.getTableTrackParent(true); 1212 const isVertical = this.hasVerticalWritingMode(tableTrackParent); 1213 1214 const isHorizontalRowGroup = this.rowGroup && !isVertical; 1215 const isHorizontalColumnGroup = this.columnGroup && isVertical; 1216 1217 return isHorizontalRowGroup || isHorizontalColumnGroup; 1218 } 1219 1220 /** 1221 * Check if the current node is a vertical table track group. That is: either a table row 1222 * group displayed in vertical writing mode, or a table column group displayed in 1223 * horizontal writing mode. 1224 */ 1225 get verticalTableTrackGroup() { 1226 if (!this.rowGroup && !this.columnGroup) { 1227 return false; 1228 } 1229 1230 const tableTrackParent = this.getTableTrackParent(true); 1231 const isVertical = this.hasVerticalWritingMode(tableTrackParent); 1232 1233 const isVerticalRowGroup = this.rowGroup && isVertical; 1234 const isVerticalColumnGroup = this.columnGroup && !isVertical; 1235 1236 return isVerticalRowGroup || isVerticalColumnGroup; 1237 } 1238 1239 /** 1240 * Check if the current node is a ruby container. 1241 */ 1242 get rubyContainer() { 1243 return this.checkComputedStyle("display", ["ruby"]); 1244 } 1245 1246 /** 1247 * Check if the current node is an internal ruby element. 1248 */ 1249 get internalRubyElement() { 1250 return this.checkComputedStyle("display", [ 1251 "ruby-base", 1252 "ruby-text", 1253 "ruby-base-container", 1254 "ruby-text-container", 1255 ]); 1256 } 1257 1258 /** 1259 * Returns whether this element uses CSS layout. 1260 */ 1261 get hasCssLayout() { 1262 return !this.isSvg && !this.isMathMl; 1263 } 1264 1265 /** 1266 * Check if the current node is a non-replaced CSS inline box. 1267 */ 1268 get nonReplacedInlineBox() { 1269 return ( 1270 this.hasCssLayout && 1271 this.nonReplaced && 1272 this.style && 1273 this.style.display === "inline" 1274 ); 1275 } 1276 1277 /** 1278 * Check if the current node is a non-atomic CSS inline box. 1279 */ 1280 get nonAtomicInlineBox() { 1281 return ( 1282 this.hasCssLayout && 1283 this.nonReplaced && 1284 this.style && 1285 this.checkComputedStyle("display", ["inline", "inline list-item"]) 1286 ); 1287 } 1288 1289 /** 1290 * Check if the current node generates a principal box. 1291 */ 1292 get hasPrincipalBox() { 1293 return ( 1294 this.hasCssLayout && 1295 this.style && 1296 this.style.display !== "none" && 1297 this.style.display !== "contents" 1298 ); 1299 } 1300 1301 /** 1302 * Check if the current selector refers to a ::first-letter pseudo-element 1303 */ 1304 get isFirstLetter() { 1305 const { selectorText } = this.cssRule; 1306 return selectorText && selectorText.includes("::first-letter"); 1307 } 1308 1309 /** 1310 * Check if the current selector refers to a ::first-line pseudo-element 1311 */ 1312 get isFirstLine() { 1313 const { selectorText } = this.cssRule; 1314 return selectorText && selectorText.includes("::first-line"); 1315 } 1316 1317 /** 1318 * Check if the current node is a non-replaced element. See `replaced()` for 1319 * a description of what a replaced element is. 1320 */ 1321 get nonReplaced() { 1322 return !this.replaced; 1323 } 1324 1325 /** 1326 * Check if the current node is an absolutely-positioned element. 1327 */ 1328 get isAbsolutelyPositioned() { 1329 return this.checkComputedStyle("position", ["absolute", "fixed"]); 1330 } 1331 1332 /** 1333 * Check if the current node is positioned (i.e. its position property has a value other 1334 * than static). 1335 */ 1336 get isPositioned() { 1337 return this.checkComputedStyle("position", [ 1338 "relative", 1339 "absolute", 1340 "fixed", 1341 "sticky", 1342 ]); 1343 } 1344 1345 /** 1346 * Check if the current node is floated 1347 */ 1348 get isFloated() { 1349 return this.style && this.style.cssFloat !== "none"; 1350 } 1351 1352 /** 1353 * Check if the current node is scrollable 1354 */ 1355 get isScrollContainer() { 1356 // If `overflow` doesn't contain the values `visible` or `clip`, it is a scroll container. 1357 // While `hidden` doesn't allow scrolling via a user interaction, the element can 1358 // still be scrolled programmatically. 1359 // See https://www.w3.org/TR/css-overflow-3/#overflow-properties. 1360 const overflow = computedStyle(this.node).overflow; 1361 // `overflow` is a shorthand for `overflow-x` and `overflow-y` 1362 // (and with that also for `overflow-inline` and `overflow-block`), 1363 // so may hold two values. 1364 const overflowValues = overflow.split(" "); 1365 return !( 1366 overflowValues.includes("visible") || overflowValues.includes("clip") 1367 ); 1368 } 1369 1370 /** 1371 * Check if the current node is a replaced element that can be resized. 1372 */ 1373 get isResizableReplacedElement() { 1374 // There might be more replaced elements that can be resized in the future. 1375 // (See bug 1280920 and its dependencies.) 1376 return this.localName === "textarea"; 1377 } 1378 1379 /** 1380 * Check if the current node is a replaced element i.e. an element with 1381 * content that will be replaced e.g. <img>, <audio>, <video> or <object> 1382 * elements. 1383 */ 1384 get replaced() { 1385 if (REPLACED_ELEMENTS_NAMES.has(this.localName)) { 1386 return true; 1387 } 1388 1389 // img tags are replaced elements only when the image has finished loading. 1390 if (this.localName === "img" && this.node.complete) { 1391 return true; 1392 } 1393 1394 return false; 1395 } 1396 1397 /** 1398 * Return the current node's localName. 1399 * 1400 * @returns {string} 1401 */ 1402 get localName() { 1403 return this.node.localName; 1404 } 1405 1406 /** 1407 * Return whether the node is a MathML element. 1408 */ 1409 get isMathMl() { 1410 return this.node.namespaceURI === "http://www.w3.org/1998/Math/MathML"; 1411 } 1412 1413 /** 1414 * Return whether the node is an SVG element. 1415 */ 1416 get isSvg() { 1417 return this.node.namespaceURI === "http://www.w3.org/2000/svg"; 1418 } 1419 1420 /** 1421 * Check if the current node is an absolutely-positioned grid element. 1422 * See: https://drafts.csswg.org/css-grid/#abspos-items 1423 * 1424 * @return {boolean} whether or not the current node is absolutely-positioned by a 1425 * grid container. 1426 */ 1427 isAbsPosGridElement() { 1428 if (!this.isAbsolutelyPositioned) { 1429 return false; 1430 } 1431 1432 const containingBlock = this.getContainingBlock(); 1433 1434 return containingBlock !== null && this.isGridContainer(containingBlock); 1435 } 1436 1437 /** 1438 * Check if a node is a flex item. 1439 * 1440 * @param {DOMNode} node 1441 * The node to check. 1442 */ 1443 isFlexItem(node) { 1444 return !!node.parentFlexElement; 1445 } 1446 1447 /** 1448 * Check if a node is a flex container. 1449 * 1450 * @param {DOMNode} node 1451 * The node to check. 1452 */ 1453 isFlexContainer(node) { 1454 return !!node.getAsFlexContainer(); 1455 } 1456 1457 /** 1458 * Check if a node is a grid container. 1459 * 1460 * @param {DOMNode} node 1461 * The node to check. 1462 */ 1463 isGridContainer(node) { 1464 return node.hasGridFragments(); 1465 } 1466 1467 /** 1468 * Check if a node is a grid item. 1469 */ 1470 isGridItem() { 1471 return !!this.getParentGridElement(this.node); 1472 } 1473 1474 isVisitedRule() { 1475 if (!CssLogic.hasVisitedState(this.node)) { 1476 return false; 1477 } 1478 1479 const selectors = CssLogic.getSelectors(this.cssRule); 1480 if (!selectors.some(s => s.endsWith(":visited"))) { 1481 return false; 1482 } 1483 1484 const { bindingElement, pseudo } = CssLogic.getBindingElementAndPseudo( 1485 this.node 1486 ); 1487 1488 for (let i = 0; i < selectors.length; i++) { 1489 if ( 1490 !selectors[i].endsWith(":visited") && 1491 this.cssRule.selectorMatchesElement(i, bindingElement, pseudo, true) 1492 ) { 1493 // Match non :visited selector. 1494 return false; 1495 } 1496 } 1497 1498 return true; 1499 } 1500 1501 /** 1502 * Return the current node's ancestor that generates its containing block. 1503 */ 1504 getContainingBlock() { 1505 return this.node ? InspectorUtils.containingBlockOf(this.node) : null; 1506 } 1507 1508 getParentGridElement(node) { 1509 // The documentElement can't be a grid item, only a container, so bail out. 1510 if (node.flattenedTreeParentNode === node.ownerDocument) { 1511 return null; 1512 } 1513 1514 if (node.nodeType === node.ELEMENT_NODE) { 1515 const display = this.style ? this.style.display : null; 1516 1517 if (!display || display === "none" || display === "contents") { 1518 // Doesn't generate a box, not a grid item. 1519 return null; 1520 } 1521 if (this.isAbsolutelyPositioned) { 1522 // Out of flow, not a grid item. 1523 return null; 1524 } 1525 } else if (node.nodeType !== node.TEXT_NODE) { 1526 return null; 1527 } 1528 1529 for ( 1530 let p = node.flattenedTreeParentNode; 1531 p; 1532 p = p.flattenedTreeParentNode 1533 ) { 1534 if (this.isGridContainer(p)) { 1535 // It's a grid item! 1536 return p; 1537 } 1538 1539 const style = computedStyle(p, node.ownerGlobal); 1540 const display = style.display; 1541 1542 if (display !== "contents") { 1543 return null; // Not a grid item, for sure. 1544 } 1545 // display: contents, walk to the parent 1546 } 1547 return null; 1548 } 1549 1550 /** 1551 * Return the multi-column container for a node if it exists. 1552 * 1553 * @param {DOMNode} The node we want the container for 1554 * @param {DOMNode|null} The container element, or null if there is none. 1555 */ 1556 getParentMultiColElement(node) { 1557 // The documentElement can't be an element in a multi-column container, 1558 // only a container, so bail out. 1559 if (node.flattenedTreeParentNode === node.ownerDocument) { 1560 return null; 1561 } 1562 1563 // Ignore nodes that are not elements nor text nodes 1564 if ( 1565 node.nodeType !== node.ELEMENT_NODE && 1566 node.nodeType !== node.TEXT_NODE 1567 ) { 1568 return null; 1569 } 1570 1571 if (node.nodeType === node.ELEMENT_NODE) { 1572 const display = this.style ? this.style.display : null; 1573 1574 if (!display || display === "none" || display === "contents") { 1575 // Doesn't generate a box, not an element in a multi-column container. 1576 return null; 1577 } 1578 if (this.isAbsolutelyPositioned) { 1579 // Out of flow, not an element in a multi-column container. 1580 return null; 1581 } 1582 } 1583 1584 // Walk up the tree to find the nearest multi-column container. 1585 // Loop over flattenedTreeParentNode instead of parentNode to reach the 1586 // shadow host from the shadow DOM. 1587 for ( 1588 let p = node.flattenedTreeParentNode; 1589 p && p !== node.ownerDocument; 1590 p = p.flattenedTreeParentNode 1591 ) { 1592 const style = computedStyle(p, node.ownerGlobal); 1593 if (style.columnWidth !== "auto" || style.columnCount !== "auto") { 1594 // It's a multi-column container! 1595 return p; 1596 } 1597 } 1598 return null; 1599 } 1600 1601 isRowGroup(node) { 1602 const style = node === this.node ? this.style : computedStyle(node); 1603 1604 return ( 1605 style && 1606 (style.display === "table-row-group" || 1607 style.display === "table-header-group" || 1608 style.display === "table-footer-group") 1609 ); 1610 } 1611 1612 isColumnGroup(node) { 1613 const style = node === this.node ? this.style : computedStyle(node); 1614 1615 return style && style.display === "table-column-group"; 1616 } 1617 1618 /** 1619 * Check if the given node's writing mode is vertical 1620 */ 1621 hasVerticalWritingMode(node) { 1622 // Only 'horizontal-tb' has a horizontal writing mode. 1623 // See https://drafts.csswg.org/css-writing-modes-4/#propdef-writing-mode 1624 return computedStyle(node).writingMode !== "horizontal-tb"; 1625 } 1626 1627 /** 1628 * Assuming the current element is a table track (row or column) or table track group, 1629 * get the parent table. 1630 * This is either going to be the table element if there is one, or the parent element. 1631 * If the current element is not a table track, this returns the current element. 1632 * 1633 * @param {boolean} isGroup 1634 * Whether the element is a table track group, instead of a table track. 1635 * @return {DOMNode} 1636 * The parent table, the parent element, or the element itself. 1637 */ 1638 getTableTrackParent(isGroup) { 1639 let current = this.node.parentNode; 1640 1641 // Skip over unrendered elements. 1642 while (computedStyle(current).display === "contents") { 1643 current = current.parentNode; 1644 } 1645 1646 // Skip over groups if the initial element wasn't already one. 1647 if (!isGroup && (this.isRowGroup(current) || this.isColumnGroup(current))) { 1648 current = current.parentNode; 1649 } 1650 1651 // Once more over unrendered elements above the group. 1652 while (computedStyle(current).display === "contents") { 1653 current = current.parentNode; 1654 } 1655 1656 return current; 1657 } 1658 1659 /** 1660 * Get the parent table element of the current element. 1661 * 1662 * @return {DOMNode|null} 1663 * The closest table element or null if there are none. 1664 */ 1665 getTableParent() { 1666 let current = this.node.parentNode; 1667 1668 // Find the table parent 1669 while (current && computedStyle(current).display !== "table") { 1670 current = current.parentNode; 1671 1672 // If we reached the document element, stop. 1673 if (current == this.node.ownerDocument.documentElement) { 1674 return null; 1675 } 1676 } 1677 1678 return current; 1679 } 1680 1681 /** 1682 * Assuming the current element is an internal table element, 1683 * check wether its parent table element has `border-collapse` set to `collapse`. 1684 * 1685 * @returns {boolean} 1686 */ 1687 checkTableParentHasBorderCollapsed() { 1688 const parent = this.getTableParent(); 1689 if (!parent) { 1690 return false; 1691 } 1692 return computedStyle(parent).borderCollapse === "collapse"; 1693 } 1694 } 1695 1696 /** 1697 * Returns all CSS property names except given properties. 1698 * 1699 * @param {Array} - propertiesToIgnore 1700 * Array of property ignored. 1701 * @return {Array} 1702 * Array of all CSS property name except propertiesToIgnore. 1703 */ 1704 function allCssPropertiesExcept(propertiesToIgnore) { 1705 const properties = new Set( 1706 InspectorUtils.getCSSPropertyNames({ includeAliases: true }) 1707 ); 1708 1709 for (const name of propertiesToIgnore) { 1710 properties.delete(name); 1711 } 1712 1713 return [...properties]; 1714 } 1715 1716 /** 1717 * Helper for getting an element's computed styles. 1718 * 1719 * @param {DOMNode} node 1720 * The node to get the styles for. 1721 * @param {Window} window 1722 * Optional window object. If omitted, will get the node's window. 1723 * @return {object} 1724 */ 1725 function computedStyle(node, window = node.ownerGlobal) { 1726 return window.getComputedStyle(node); 1727 } 1728 1729 const inactivePropertyHelper = new InactivePropertyHelper(); 1730 1731 // The only public method from this module is `getInactiveCssDataForProperty`. 1732 exports.getInactiveCssDataForProperty = 1733 inactivePropertyHelper.getInactiveCssDataForProperty.bind( 1734 inactivePropertyHelper 1735 );