tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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  );