Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/devtools/server/actors/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 55 kB image not shown  

Quelle  page-style.js

  Sprache: JAVA
 

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


"use strict";

const { Actor } = require("resource://devtools/shared/protocol.js");
const {
  pageStyleSpec,
} = require("resource://devtools/shared/specs/page-style.js");

const {
  LongStringActor,
} = require("resource://devtools/server/actors/string.js");

const {
  style: { ELEMENT_STYLE },
} = require("resource://devtools/shared/constants.js");

const {
  toFixed,
} = require("resource://devtools/shared/inspector/font-utils.js");

loader.lazyRequireGetter(
  this,
  "StyleRuleActor",
  "resource://devtools/server/actors/style-rule.js",
  true
);
loader.lazyRequireGetter(
  this,
  "getFontPreviewData",
  "resource://devtools/server/actors/stylesheets/style-utils.js",
  true
);
loader.lazyRequireGetter(
  this,
  "CssLogic",
  "resource://devtools/server/actors/inspector/css-logic.js",
  true
);
loader.lazyRequireGetter(
  this,
  "SharedCssLogic",
  "resource://devtools/shared/inspector/css-logic.js"
);
loader.lazyRequireGetter(
  this,
  "getDefinedGeometryProperties",
  "resource://devtools/server/actors/highlighters/geometry-editor.js",
  true
);
loader.lazyRequireGetter(
  this,
  "UPDATE_GENERAL",
  "resource://devtools/server/actors/stylesheets/stylesheets-manager.js",
  true
);

loader.lazyGetter(this"PSEUDO_ELEMENTS", () => {
  return InspectorUtils.getCSSPseudoElementNames();
});
loader.lazyGetter(this"FONT_VARIATIONS_ENABLED", () => {
  return Services.prefs.getBoolPref("layout.css.font-variations.enabled");
});

const NORMAL_FONT_WEIGHT = 400;
const BOLD_FONT_WEIGHT = 700;

/**
 * The PageStyle actor lets the client look at the styles on a page, as
 * they are applied to a given node.
 */

class PageStyleActor extends Actor {
  /**
   * Create a PageStyleActor.
   *
   * @param inspector
   *    The InspectorActor that owns this PageStyleActor.
   *
   * @class
   */

  constructor(inspector) {
    super(inspector.conn, pageStyleSpec);
    this.inspector = inspector;
    if (!this.inspector.walker) {
      throw Error(
        "The inspector's WalkerActor must be created before " +
          "creating a PageStyleActor."
      );
    }
    this.walker = inspector.walker;
    this.cssLogic = new CssLogic();

    // Stores the association of DOM objects -> actors
    this.refMap = new Map();

    // Latest node queried for its applied styles.
    this.selectedElement = null;

    // Maps root node (document|ShadowRoot) to stylesheets, which are used to add new rules.
    this.styleSheetsByRootNode = new WeakMap();

    this.onFrameUnload = this.onFrameUnload.bind(this);

    this.inspector.targetActor.on("will-navigate"this.onFrameUnload);

    this.styleSheetsManager =
      this.inspector.targetActor.getStyleSheetsManager();

    this.styleSheetsManager.on("stylesheet-updated"this.#onStylesheetUpdated);
  }

  #observedRules = new Set();

  destroy() {
    if (!this.walker) {
      return;
    }
    super.destroy();
    this.inspector.targetActor.off("will-navigate"this.onFrameUnload);
    this.inspector = null;
    this.walker = null;
    this.refMap = null;
    this.selectedElement = null;
    this.cssLogic = null;
    this.styleSheetsByRootNode = null;

    this.#observedRules = null;
  }

  get ownerWindow() {
    return this.inspector.targetActor.window;
  }

  form() {
    // We need to use CSS from the inspected window in order to use CSS.supports() and
    // detect the right platform features from there.
    const CSS = this.inspector.targetActor.window.CSS;

    return {
      actor: this.actorID,
      traits: {
        // Whether the page supports values of font-stretch from CSS Fonts Level 4.
        fontStretchLevel4: CSS.supports("font-stretch: 100%"),
        // Whether the page supports values of font-style from CSS Fonts Level 4.
        fontStyleLevel4: CSS.supports("font-style: oblique 20deg"),
        // Whether getAllUsedFontFaces/getUsedFontFaces accepts the includeVariations
        // argument.
        fontVariations: FONT_VARIATIONS_ENABLED,
        // Whether the page supports values of font-weight from CSS Fonts Level 4.
        // font-weight at CSS Fonts Level 4 accepts values in increments of 1 rather
        // than 100. However, CSS.supports() returns false positives, so we guard with the
        // expected support of font-stretch at CSS Fonts Level 4.
        fontWeightLevel4:
          CSS.supports("font-weight: 1") && CSS.supports("font-stretch: 100%"),
      },
    };
  }

  /**
   * Called when a style sheet is updated.
   */

  #styleApplied = kind => {
    // No matter what kind of update is done, we need to invalidate
    // the keyframe cache.
    this.cssLogic.reset();
    if (kind === UPDATE_GENERAL) {
      this.emit("stylesheet-updated");
    }
  };

  /**
   * Return or create a StyleRuleActor for the given item.
   *
   * @param {CSSStyleRule|Element} item
   * @param {string} pseudoElement An optional pseudo-element type in cases when the CSS
   *        rule applies to a pseudo-element.
   * @param {boolean} userAdded: Optional boolean to distinguish rules added by the user.
   * @return {StyleRuleActor} The newly created, or cached, StyleRuleActor for this item.
   */

  styleRef(item, pseudoElement, userAdded = false) {
    if (this.refMap.has(item)) {
      const styleRuleActor = this.refMap.get(item);
      if (pseudoElement) {
        styleRuleActor.addPseudo(pseudoElement);
      }
      return styleRuleActor;
    }
    const actor = new StyleRuleActor({
      pageStyle: this,
      item,
      userAdded,
      pseudoElement,
    });
    this.manage(actor);
    this.refMap.set(item, actor);

    return actor;
  }

  /**
   * Update the association between a StyleRuleActor and its
   * corresponding item.  This is used when a StyleRuleActor updates
   * as style sheet and starts using a new rule.
   *
   * @param oldItem The old association; either a CSSStyleRule or a
   *                DOM element.
   * @param item Either a CSSStyleRule or a DOM element.
   * @param actor a StyleRuleActor
   */

  updateStyleRef(oldItem, item, actor) {
    this.refMap.delete(oldItem);
    this.refMap.set(item, actor);
  }

  /**
   * Get the StyleRuleActor matching the given rule id or null if no match is found.
   *
   * @param  {string} ruleId
   *         Actor ID of the StyleRuleActor
   * @return {StyleRuleActor|null}
   */

  getRule(ruleId) {
    let match = null;

    for (const actor of this.refMap.values()) {
      if (actor.actorID === ruleId) {
        match = actor;
        continue;
      }
    }

    return match;
  }

  /**
   * Get the computed style for a node.
   *
   * @param {NodeActor} node
   * @param {object} options
   * @param {string} options.filter: A string filter that affects the "matched" handling.
   * @param {Array<string>} options.filterProperties: An array of properties names that
   *        you would like returned.
   * @param {boolean} options.markMatched: true if you want the 'matched' property to be
   *        added when a computed property has been modified by a style included by `filter`.
   * @param {boolean} options.onlyMatched: true if unmatched properties shouldn't be included.
   * @param {boolean} options.clearCache: true if the cssLogic cache should be cleared.
   *
   * @returns a JSON blob with the following form:
   *   {
   *     "property-name": {
   *       value: "property-value",
   *       priority: "!important" <optional>
   *       matched: <true if there are matched selectors for this value>
   *     },
   *     ...
   *   }
   */

  getComputed(node, options) {
    const ret = Object.create(null);

    if (options.clearCache) {
      this.cssLogic.reset();
    }
    const filterProperties = Array.isArray(options.filterProperties)
      ? options.filterProperties
      : null;
    this.cssLogic.sourceFilter = options.filter || SharedCssLogic.FILTER.UA;
    this.cssLogic.highlight(node.rawNode);
    const computed = this.cssLogic.computedStyle || [];
    const targetDocument = this.inspector.targetActor.window.document;

    for (const name of computed) {
      if (filterProperties && !filterProperties.includes(name)) {
        continue;
      }
      ret[name] = {
        value: computed.getPropertyValue(name),
        priority: computed.getPropertyPriority(name) || undefined,
      };

      if (name.startsWith("--")) {
        const registeredProperty = InspectorUtils.getCSSRegisteredProperty(
          targetDocument,
          name
        );
        if (registeredProperty) {
          ret[name].registeredPropertyInitialValue =
            registeredProperty.initialValue;
          if (
            !InspectorUtils.valueMatchesSyntax(
              targetDocument,
              ret[name].value,
              registeredProperty.syntax
            )
          ) {
            ret[name].invalidAtComputedValueTime = true;
            ret[name].registeredPropertySyntax = registeredProperty.syntax;
          }
        }
      }
    }

    if (options.markMatched || options.onlyMatched) {
      const matched = this.cssLogic.hasMatchedSelectors(Object.keys(ret));
      for (const key in ret) {
        if (matched.has(key)) {
          ret[key].matched = options.markMatched ? true : undefined;
        } else if (options.onlyMatched) {
          delete ret[key];
        }
      }
    }

    return ret;
  }

  /**
   * Get all the fonts from a page.
   *
   * @param object options
   *   `includePreviews`: Whether to also return image previews of the fonts.
   *   `previewText`: The text to display in the previews.
   *   `previewFontSize`: The font size of the text in the previews.
   *
   * @returns object
   *   object with 'fontFaces', a list of fonts that apply to this node.
   */

  getAllUsedFontFaces(options) {
    const windows = this.inspector.targetActor.windows;
    let fontsList = [];
    for (const win of windows) {
      // Fall back to the documentElement for XUL documents.
      const node = win.document.body
        ? win.document.body
        : win.document.documentElement;
      fontsList = [...fontsList, ...this.getUsedFontFaces(node, options)];
    }

    return fontsList;
  }

  /**
   * Get the font faces used in an element.
   *
   * @param NodeActor node / actual DOM node
   *    The node to get fonts from.
   * @param object options
   *   `includePreviews`: Whether to also return image previews of the fonts.
   *   `previewText`: The text to display in the previews.
   *   `previewFontSize`: The font size of the text in the previews.
   *
   * @returns object
   *   object with 'fontFaces', a list of fonts that apply to this node.
   */

  getUsedFontFaces(node, options) {
    // node.rawNode is defined for NodeActor objects
    const actualNode = node.rawNode || node;
    const contentDocument = actualNode.ownerDocument;
    // We don't get fonts for a node, but for a range
    const rng = contentDocument.createRange();
    const isPseudoElement = Boolean(
      CssLogic.getBindingElementAndPseudo(actualNode).pseudo
    );
    if (isPseudoElement) {
      rng.selectNodeContents(actualNode);
    } else {
      rng.selectNode(actualNode);
    }
    const fonts = InspectorUtils.getUsedFontFaces(rng);
    const fontsArray = [];

    for (let i = 0; i < fonts.length; i++) {
      const font = fonts[i];
      const fontFace = {
        name: font.name,
        CSSFamilyName: font.CSSFamilyName,
        CSSGeneric: font.CSSGeneric || null,
        srcIndex: font.srcIndex,
        URI: font.URI,
        format: font.format,
        localName: font.localName,
        metadata: font.metadata,
        version: font.getNameString(InspectorFontFace.NAME_ID_VERSION),
        description: font.getNameString(InspectorFontFace.NAME_ID_DESCRIPTION),
        manufacturer: font.getNameString(
          InspectorFontFace.NAME_ID_MANUFACTURER
        ),
        vendorUrl: font.getNameString(InspectorFontFace.NAME_ID_VENDOR_URL),
        designer: font.getNameString(InspectorFontFace.NAME_ID_DESIGNER),
        designerUrl: font.getNameString(InspectorFontFace.NAME_ID_DESIGNER_URL),
        license: font.getNameString(InspectorFontFace.NAME_ID_LICENSE),
        licenseUrl: font.getNameString(InspectorFontFace.NAME_ID_LICENSE_URL),
        sampleText: font.getNameString(InspectorFontFace.NAME_ID_SAMPLE_TEXT),
      };

      // If this font comes from a @font-face rule
      if (font.rule) {
        const styleActor = new StyleRuleActor({
          pageStyle: this,
          item: font.rule,
        });
        this.manage(styleActor);
        fontFace.rule = styleActor;
        fontFace.ruleText = font.rule.cssText;
      }

      // Get the weight and style of this font for the preview and sort order
      let weight = NORMAL_FONT_WEIGHT,
        style = "";
      if (font.rule) {
        weight =
          font.rule.style.getPropertyValue("font-weight") || NORMAL_FONT_WEIGHT;
        if (weight == "bold") {
          weight = BOLD_FONT_WEIGHT;
        } else if (weight == "normal") {
          weight = NORMAL_FONT_WEIGHT;
        }
        style = font.rule.style.getPropertyValue("font-style") || "";
      }
      fontFace.weight = weight;
      fontFace.style = style;

      if (options.includePreviews) {
        const opts = {
          previewText: options.previewText,
          previewFontSize: options.previewFontSize,
          fontStyle: style,
          fontWeight: weight,
          fillStyle: options.previewFillStyle,
        };
        const { dataURL, size } = getFontPreviewData(
          font.CSSFamilyName,
          contentDocument,
          opts
        );
        fontFace.preview = {
          data: new LongStringActor(this.conn, dataURL),
          size,
        };
      }

      if (options.includeVariations && FONT_VARIATIONS_ENABLED) {
        // Round font variation axes values
        fontFace.variationAxes = font.getVariationAxes().map(axis => ({
          ...axis,
          minValue: toFixed(axis.minValue, 3),
          maxValue: toFixed(axis.maxValue, 3),
          defaultValue: toFixed(axis.defaultValue, 3),
        }));
        fontFace.variationInstances = font
          .getVariationInstances()
          .map(instance => ({
            ...instance,
            values: instance.values.map(variationValue => ({
              ...variationValue,
              value: toFixed(variationValue.value, 3),
            })),
          }));
      }

      fontsArray.push(fontFace);
    }

    // @font-face fonts at the top, then alphabetically, then by weight
    fontsArray.sort(function (a, b) {
      return a.weight > b.weight ? 1 : -1;
    });
    fontsArray.sort(function (a, b) {
      if (a.CSSFamilyName == b.CSSFamilyName) {
        return 0;
      }
      return a.CSSFamilyName > b.CSSFamilyName ? 1 : -1;
    });
    fontsArray.sort(function (a, b) {
      if ((a.rule && b.rule) || (!a.rule && !b.rule)) {
        return 0;
      }
      return !a.rule && b.rule ? 1 : -1;
    });

    return fontsArray;
  }

  /**
   * Get a list of selectors that match a given property for a node.
   *
   * @param NodeActor node
   * @param string property
   * @param object options
   *   `filter`: A string filter that affects the "matched" handling.
   *     'user': Include properties from user style sheets.
   *     'ua': Include properties from user and user-agent sheets.
   *     Default value is 'ua'
   *
   * @returns a JSON object with the following form:
   *   {
   *     // An ordered list of rules that apply
   *     matched: [{
   *       rule: <rule actorid>,
   *       sourceText: <string>, // The source of the selector, relative
   *                             // to the node in question.
   *       selector: <string>, // the selector ID that matched
   *       value: <string>, // the value of the property
   *       status: <int>,
   *         // The status of the match - high numbers are better placed
   *         // to provide styling information:
   *         // 3: Best match, was used.
   *         // 2: Matched, but was overridden.
   *         // 1: Rule from a parent matched.
   *         // 0: Unmatched (never returned in this API)
   *     }, ...],
   *
   *     // The full form of any domrule referenced.
   *     rules: [ <domrule>, ... ], // The full form of any domrule referenced
   *
   *     // The full form of any sheets referenced.
   *     sheets: [ <domsheet>, ... ]
   *  }
   */

  getMatchedSelectors(node, property, options) {
    this.cssLogic.sourceFilter = options.filter || SharedCssLogic.FILTER.UA;
    this.cssLogic.highlight(node.rawNode);

    const rules = new Set();
    const matched = [];

    const targetDocument = this.inspector.targetActor.window.document;
    let registeredProperty;
    if (property.startsWith("--")) {
      registeredProperty = InspectorUtils.getCSSRegisteredProperty(
        targetDocument,
        property
      );
    }

    const propInfo = this.cssLogic.getPropertyInfo(property);
    for (const selectorInfo of propInfo.matchedSelectors) {
      const cssRule = selectorInfo.selector.cssRule;
      const domRule = cssRule.sourceElement || cssRule.domRule;

      const rule = this.styleRef(domRule);
      rules.add(rule);

      const match = {
        rule,
        sourceText: this.getSelectorSource(selectorInfo, node.rawNode),
        selector: selectorInfo.selector.text,
        name: selectorInfo.property,
        value: selectorInfo.value,
        status: selectorInfo.status,
      };
      if (
        registeredProperty &&
        !InspectorUtils.valueMatchesSyntax(
          targetDocument,
          match.value,
          registeredProperty.syntax
        )
      ) {
        match.invalidAtComputedValueTime = true;
        match.registeredPropertySyntax = registeredProperty.syntax;
      }
      matched.push(match);
    }

    return {
      matched,
      rules: [...rules],
    };
  }

  // Get a selector source for a CssSelectorInfo relative to a given
  // node.
  getSelectorSource(selectorInfo, relativeTo) {
    let result = selectorInfo.selector.text;
    const ruleDeclarationOrigin =
      selectorInfo.selector.cssRule.domRule.declarationOrigin;
    if (
      ruleDeclarationOrigin === "style-attribute" ||
      ruleDeclarationOrigin === "pres-hints"
    ) {
      const source = selectorInfo.sourceElement;
      if (source === relativeTo) {
        result = "element";
      } else {
        result = CssLogic.getShortName(source);
      }

      if (ruleDeclarationOrigin === "pres-hints") {
        result += " attributes style";
      }
    }

    return result;
  }

  /**
   * @typedef {"user" | "ua" } GetAppliedFilterOption
   */


  /**
   * @typedef {object} GetAppliedOptions
   *
   * @property {GetAppliedFilterOption} filter - A string filter that affects the "matched" handling.
   *        Possible values are:
   *        - 'user': Include properties from user style sheets.
   *        - 'ua': Include properties from user and user-agent sheets.
   *        Default value is 'ua'
   * @property {boolean} inherited - Include styles inherited from parent nodes.
   * @property {boolean} matchedSelectors - Include an array of specific selectors that
   *        caused this rule to match its node.
   * @property {boolean} skipPseudo - Exclude styles applied to pseudo elements of the
   *        provided node.
   */


  /**
   * Get the set of styles that apply to a given node.
   *
   * @param {NodeActor} node
   * @param {GetAppliedOptions} options
   */

  async getApplied(node, options) {
    // Clear any previous references to StyleRuleActor instances for CSS rules.
    // Assume the consumer has switched context to a new node and no longer
    // interested in state changes of previous rules.
    this.#observedRules.clear();
    this.selectedElement = node?.rawNode || null;

    if (!node) {
      return { entries: [] };
    }

    this.cssLogic.highlight(node.rawNode);

    const entries = this.getAppliedProps(
      node,
      this.#getAllElementRules(node, {
        skipPseudo: options.skipPseudo,
        filter: options.filter,
      }),
      options
    );

    const promises = [];
    for (const entry of entries) {
      // Reference to instances of StyleRuleActor for CSS rules matching the node.
      // Assume these are used by a consumer which wants to be notified when their
      // state or declarations change either directly or indirectly.
      this.#observedRules.add(entry.rule);
      // We need to be sure that authoredText has been set before StyleRule#form is called.
      // This has to be treated specially, for now, because we cannot synchronously compute
      // the authored text and |form| can't return a promise.
      // See bug 1205868.
      promises.push(entry.rule.getAuthoredCssText());
    }

    await Promise.all(promises);

    return { entries };
  }

  #hasInheritedProps(style) {
    const doc = this.inspector.targetActor.window.document;
    return Array.prototype.some.call(style, prop =>
      InspectorUtils.isInheritedProperty(doc, prop)
    );
  }

  async isPositionEditable(node) {
    if (!node || node.rawNode.nodeType !== node.rawNode.ELEMENT_NODE) {
      return false;
    }

    const props = getDefinedGeometryProperties(node.rawNode);

    // Elements with only `width` and `height` are currently not considered
    // editable.
    return (
      props.has("top") ||
      props.has("right") ||
      props.has("left") ||
      props.has("bottom")
    );
  }

  /**
   * Helper function for getApplied, gets all the rules from a given
   * element. See getApplied for documentation on parameters.
   *
   * @param {NodeActor} node
   * @param {object} options
   * @param {boolean} options.isInherited - Set to true if we want to retrieve inherited rules,
   *        i.e. the passed node actor is an ancestor of the node we want to retrieved the
   *        applied rules for originally.
   * @param {boolean} options.skipPseudo - Exclude styles applied to pseudo elements of the
   *        provided node
   * @param {GetAppliedFilterOption} options.filter - will be passed to #getElementRules
   *
   * @return {Array{AppliedStyle}} The rules for a given element.
   *         See #getRuleItem for definition.
   */

  #getAllElementRules(node, { isInherited, skipPseudo, filter }) {
    const { rawNode } = node;
    const rules = [];

    // First add rule actors for element style defined via the DOM "style" attribute,
    // and/or via the JS "style" attribute (CSSOM API).
    if (rawNode.style) {
      const isPseudoElement = !!rawNode.implementedPseudoElement;
      // We only show element styles if:
      //  - we aren't processing a parent element for inherited rules, and that's not a pseudo element
      //    --or--
      //  - we are processing parent elements for inherited rules and we have at least one inherited rule
      const showElementStyles = !isInherited && !isPseudoElement;
      const showInheritedStyles =
        isInherited && this.#hasInheritedProps(rawNode.style);

      if (showElementStyles || showInheritedStyles) {
        const elementStyleActor = this.styleRef(
          rawNode,
          // We never try to fetch element styles for pseudo elements.
          null
        );

        if (showElementStyles) {
          rules.push(
            this.#getRuleItem(elementStyleActor, rawNode, {
              pseudoElement: null,
              isSystem: false,
              inherited: null,
            })
          );
        } else if (showInheritedStyles) {
          // at this point `isInherited` is true, so we want to put the NodeActor in the
          // `inherited` property so the client can show this information (for example in
          // the "Inherited from X" section in the Rules view).
          rules.push(
            this.#getRuleItem(elementStyleActor, rawNode, {
              pseudoElement: null,
              isSystem: false,
              inherited: node,
            })
          );
        }
      }
    }

    // Add normal rules matching exactly the DOM Element passed in (node).
    //
    // Note that when we are processing the special DOM Elements for pseudo elements
    // like ::before, ::marker, ::details-content,...
    // we pass an empty pseudo string  as we want to build the Rule for that DOM Element
    // and not a pseudo element Rule.
    // Pseudo element rule are build just after in this method and are for pseudo elements
    // created as children of the currently processed DOM Element.
    for (const oneRule of this.#getElementRules(
      rawNode,
      "",
      isInherited ? node : null,
      filter
    )) {
      rules.push(oneRule);
    }

    // If we don't want to check pseudo elements rules, we can stop here.
    if (skipPseudo) {
      return rules;
    }

    // Now retrieve any pseudo element rules.
    // We can have pseudo element that are children of other pseudo elements (e.g. with
    // ::before::marker , ::marker is a child of ::before).
    // In such case, we want to call #getElementRules with the actual pseudo element node,
    // not its binding element.

    const relevantPseudoElements = [];
    for (const readPseudo of PSEUDO_ELEMENTS) {
      if (!this.#pseudoIsRelevant(rawNode, readPseudo, isInherited)) {
        continue;
      }

      // FIXME: Bug 1909173. Need to handle view transitions peudo-elements.
      if (readPseudo === "::highlight") {
        InspectorUtils.getRegisteredCssHighlights(
          this.inspector.targetActor.window.document,
          // only active
          true
        ).forEach(name => {
          relevantPseudoElements.push(`::highlight(${name})`);
        });
      } else {
        relevantPseudoElements.push(readPseudo);
      }
    }

    for (const readPseudo of relevantPseudoElements) {
      const pseudoRules = this.#getElementRules(
        rawNode,
        readPseudo,
        isInherited ? node : null,
        filter
      );
      // inherited element backed pseudo element rules (e.g. `::details-content`) should
      // not be at the same "level" as rules inherited from the binding element (e.g. `<details>`),
      // so we need to put them before the "regular" rules.
      if (
        SharedCssLogic.ELEMENT_BACKED_PSEUDO_ELEMENTS.has(readPseudo) &&
        isInherited
      ) {
        rules.unshift(...pseudoRules);
      } else {
        rules.push(...pseudoRules);
      }
    }

    return rules;
  }

  /**
   * Create a new rule description ultimately returned by PageStyle.getApplied method.
   *
   * @param {StyleRuleActor} rule
   * @param {DOMNode | null} rawNode
   * @param {object} params
   * @param {NodeActor} params.inherited
   * @param {boolean} params.isSystem
   * @param {string | null} params.pseudoElement
   * @param {StyleRuleActor} params.keyframes
   * @returns {appliedstyle}
   */

  #getRuleItem(
    rule,
    rawNode = null,
    { inherited, isSystem, pseudoElement, keyframes } = {}
  ) {
    return {
      // /!\ Keep "appliedstyle" protocol.js type definition in sync with this object
      rule,
      pseudoElement,
      isSystem,
      inherited,
      // We can't compute the value for the whole document as the color scheme
      // can be set at the node level (e.g. with `color-scheme`)
      darkColorScheme: rawNode
        ? InspectorUtils.isUsedColorSchemeDark(rawNode)
        : undefined,
      keyframes,

      // May be later set from getAppliedProps.
      matchedSelectorIndexes: undefined,
    };
  }

  #nodeIsTextfieldLike(node) {
    if (node.nodeName == "TEXTAREA") {
      return true;
    }
    return (
      node.mozIsTextField &&
      (node.mozIsTextField(false) || node.type == "number")
    );
  }

  #nodeIsListItem(node) {
    const computed = CssLogic.getComputedStyle(node);
    if (!computed) {
      return false;
    }

    const display = computed.getPropertyValue("display");
    // This is written this way to handle `inline list-item` and such.
    return display.split(" ").includes("list-item");
  }

  /**
   * Returns whether or node the pseudo element is relevant for the passed node
   *
   * @param {DOMNode} node
   * @param {string} pseudo
   * @param {boolean} isInherited
   * @returns {boolean}
   */

  // eslint-disable-next-line complexity
  #pseudoIsRelevant(node, pseudo, isInherited = false) {
    switch (pseudo) {
      case "::after":
      case "::before":
      case "::first-letter":
      case "::first-line":
      case "::selection":
      case "::highlight":
      case "::target-text":
        return !isInherited;
      case "::marker":
        return !isInherited && this.#nodeIsListItem(node);
      case "::backdrop":
        return !isInherited && node.matches(":modal, :popover-open");
      case "::cue":
        return !isInherited && node.nodeName == "VIDEO";
      case "::file-selector-button":
        return !isInherited && node.nodeName == "INPUT" && node.type == "file";
      case "::details-content": {
        const isDetailsNode = node.nodeName == "DETAILS";
        if (!isDetailsNode) {
          return false;
        }

        if (!isInherited) {
          return true;
        }

        // If we're getting rules on a parent element, we need to check if the selected
        // element is inside the ::details-content of node
        // We traverse the flattened parent tree until we find the <slot> that implements
        // the pseudo element, as it's easier to handle edge cases like nested <details>,
        // multiple <summary>, etc …
        let traversedNode = this.selectedElement;
        while (traversedNode) {
          if (
            // if we found the <slot> implementing the pseudo element
            traversedNode.implementedPseudoElement === "::details-content" &&
            // and its parent <details> element is the element we're evaluating
            traversedNode.flattenedTreeParentNode === node
          ) {
            // then include the ::details-content rules from that element
            return true;
          }
          // otherwise keep looking up the tree
          traversedNode = traversedNode.flattenedTreeParentNode;
        }

        return false;
      }
      case "::placeholder":
      case "::-moz-placeholder":
        return !isInherited && this.#nodeIsTextfieldLike(node);
      case "::-moz-meter-bar":
        return !isInherited && node.nodeName == "METER";
      case "::-moz-progress-bar":
        return !isInherited && node.nodeName == "PROGRESS";
      case "::-moz-color-swatch":
        return !isInherited && node.nodeName == "INPUT" && node.type == "color";
      case "::-moz-range-progress":
      case "::-moz-range-thumb":
      case "::-moz-range-track":
      case "::slider-fill":
      case "::slider-thumb":
      case "::slider-track":
        return !isInherited && node.nodeName == "INPUT" && node.type == "range";
      case "::view-transition":
      case "::view-transition-group":
      case "::view-transition-image-pair":
      case "::view-transition-old":
      case "::view-transition-new":
        // FIXME: Bug 1909173. Need to handle view transitions peudo-elements
        // for DevTools. For now we skip them.
        return false;
      case "::picker-icon":
      case "::picker":
        // FIXME: Bug 2042839. Need to handle in DevTools.
        return !isInherited && node.nodeName == "SELECT";
      case "::checkmark":
        return !isInherited && node.nodeName == "OPTION";
      case "::-webkit-scrollbar":
        return false;
      default:
        console.error("Unhandled pseudo-element " + pseudo);
        return false;
    }
  }

  /**
   * Helper function for #getAllElementRules, returns the rules from a given
   * element. See getApplied for documentation on parameters.
   *
   * @param {DOMNode} node
   * @param {string} pseudo
   * @param {NodeActor} inherited
   * @param {GetAppliedFilterOption} filter
   *
   * @returns {Array<appliedstyle>}
   */

  #getElementRules(node, pseudo, inherited, filter) {
    if (!Element.isInstance(node)) {
      return [];
    }

    // we don't need to retrieve inherited starting style rules
    const includeStartingStyleRules = !inherited;
    const domRules = InspectorUtils.getMatchingCSSRules(
      node,
      pseudo,
      CssLogic.hasVisitedState(node),
      includeStartingStyleRules
    );

    if (!domRules) {
      return [];
    }

    const rules = [];

    const doc = this.inspector.targetActor.window.document;

    let hasClosestAppearanceBaseNode = null;

    // getMatchingCSSRules returns ordered from least-specific to
    // most-specific.
    for (let i = domRules.length - 1; i >= 0; i--) {
      const domRule = domRules[i];
      const isSystem =
        domRule.parentStyleSheet &&
        SharedCssLogic.isAgentStylesheet(domRule.parentStyleSheet);

      // For now, when dealing with InspectorDeclaration, we only care about presentational
      // hints style (e.g. <img height=100>).
      if (
        domRule.declarationOrigin &&
        domRule.declarationOrigin !== "pres-hints"
      ) {
        continue;
      }

      if (isSystem && filter != SharedCssLogic.FILTER.UA) {
        continue;
      }

      if (inherited) {
        // Don't include inherited rules if none of its properties
        // are inheritable.
        let hasInherited = false;
        // This can be on a hot path, so let's use a simple for rule instead of turning
        // domRule.style into an Array to use some on it.
        for (let j = 0, len = domRule.style.length; j < len; j++) {
          if (InspectorUtils.isInheritedProperty(doc, domRule.style[j])) {
            hasInherited = true;
            break;
          }
        }

        if (!hasInherited) {
          continue;
        }
      }

      if (isSystem) {
        // Rules inside @appearance-base only apply if the effective appearance value is
        // base / base-select in the element or any of its ancestors.
        // For now, if we don't match those condition, we're not going to show those rules
        // in the UI so it doesn't interfere with how we're handling overridden declarations.
        // Ideally, this should be done in C++ for better performance (see Bug 2028761).
        let currentRule = domRule;
        let hasClosestAppearanceBaseRule = false;
        while (currentRule) {
          if (
            ChromeUtils.getClassName(currentRule) === "CSSAppearanceBaseRule"
          ) {
            hasClosestAppearanceBaseRule = true;
            break;
          }
          currentRule = currentRule.parentRule;
        }

        if (hasClosestAppearanceBaseRule) {
          // lazyily compute this as this can be costly
          if (hasClosestAppearanceBaseNode === null) {
            hasClosestAppearanceBaseNode = false;
            let currentNode = node;
            while (currentNode) {
              const computed = CssLogic.getComputedStyle(currentNode);
              const appearance = computed
                ? computed.getPropertyValue("appearance")
                : null;
              if (appearance === "base" || appearance === "base-select") {
                hasClosestAppearanceBaseNode = true;
                break;
              }
              currentNode = currentNode.parentElement;
            }
          }

          if (!hasClosestAppearanceBaseNode) {
            // We don't want to display @appearance-base rules if they will be inactive
            continue;
          }
        }
      }

      const ruleActor = this.styleRef(domRule, pseudo);

      rules.push(
        this.#getRuleItem(ruleActor, node, {
          inherited,
          isSystem,
          pseudoElement: pseudo,
        })
      );
    }
    return rules;
  }

  /**
   * Given a node and a CSS rule, walk up the DOM looking for a matching element rule.
   *
   * @param {NodeActor} nodeActor the node
   * @param {CSSStyleRule} matchingRule the rule to find the entry for
   * @return {object | null} An entry as returned by #getAllElementRules, or null if no entry
   *                       matching the passed rule was find
   */

  findEntryMatchingRule(nodeActor, matchingRule) {
    let currentNodeActor = nodeActor;
    while (
      currentNodeActor &&
      currentNodeActor.rawNode.nodeType != Node.DOCUMENT_NODE
    ) {
      for (const entry of this.#getAllElementRules(currentNodeActor, {
        isInherited: nodeActor !== currentNodeActor,
      })) {
        if (entry.rule.rawRule === matchingRule) {
          return entry;
        }
      }

      currentNodeActor = this.walker.parentNode(currentNodeActor);
    }

    // If we reached the document node without finding the rule, return null
    return null;
  }

  /**
   * Helper function for getApplied that fetches a set of style properties that
   * apply to the given node and associated rules
   *
   * @param {NodeActor} node
   * @param {Array<appliedstyle>} entries
   *   List of appliedstyle objects that lists the rules that apply to the
   *   node. If adding a new rule to the stylesheet, only the new rule entry
   *   is provided and only the style properties that apply to the new
   *   rule is fetched.
   * @param {GetAppliedOptions} options
   * @returns {Array<appliedstyle>} of rule entries that applies to the given node and its associated rules.
   */

  getAppliedProps(node, entries, options) {
    if (options.inherited) {
      let parent = this.walker.parentNode(node);
      while (parent && parent.rawNode.nodeType != Node.DOCUMENT_NODE) {
        entries = entries.concat(
          this.#getAllElementRules(parent, {
            isInherited: true,
            skipPseudo: options.skipPseudo,
            filter: options.filter,
          })
        );
        parent = this.walker.parentNode(parent);
      }
    }

    if (options.matchedSelectors) {
      this.#computeSelectorIndexes(entries, node);
    }

    const computedStyle = this.cssLogic.computedStyle;
    if (computedStyle) {
      this.#getKeyFrameRules(entries, computedStyle);
      this.#getPositionTryRules(entries, computedStyle);
    }

    return entries;
  }

  /**
   * If requested by the client, populate the `matchedSelectorIndex` array in all `entries`.
   * This array contains the indexes of all selectors that are matching the selected DOM element.
   *
   * @param {Array<appliedstyle>} entries
   * @param {NodeActor} node
   */

  #computeSelectorIndexes(entries, node) {
    for (const entry of entries) {
      if (entry.rule.type === ELEMENT_STYLE) {
        continue;
      }
      entry.matchedSelectorIndexes = [];

      const domRule = entry.rule.rawRule;
      const element = entry.inherited ? entry.inherited.rawNode : node.rawNode;

      const pseudos = [];
      const { bindingElement, pseudo } =
        CssLogic.getBindingElementAndPseudo(element);

      // if we couldn't find a binding element, we can't call domRule.selectorMatchesElement,
      // so bail out
      if (!bindingElement) {
        continue;
      }

      if (pseudo) {
        pseudos.push(pseudo);
      } else if (entry.rule.pseudoElements.size) {
        // if `node` is not a pseudo element but the rule applies to some pseudo elements,
        // we need to pass those to CSSStyleRule#selectorMatchesElement
        pseudos.push(...entry.rule.pseudoElements);
      } else {
        // If the rule doesn't apply to any pseudo, set a null item so we'll still do
        // the proper check below
        pseudos.push(null);
      }

      const relevantLinkVisited = CssLogic.hasVisitedState(bindingElement);
      const len = domRule.selectorCount;
      for (let i = 0; i < len; i++) {
        for (const pseudoElementName of pseudos) {
          if (
            domRule.selectorMatchesElement(
              i,
              bindingElement,
              pseudoElementName,
              relevantLinkVisited
            )
          ) {
            entry.matchedSelectorIndexes.push(i);
            // if we matched the selector for one pseudo, no need to check the other ones
            break;
          }
        }
      }
    }
  }

  /**
   * Add all the keyframes rules associated with the element into `entries`
   * based on the applied declarations defined in `computedStyle`.
   *
   * @param {Array<appliedstyle>} entries
   * @param {CSSStyleDeclaration} computedStyle
   */

  #getKeyFrameRules(entries, computedStyle) {
    let animationNames = computedStyle.animationName.split(",");
    if (!animationNames.length) {
      return;
    }
    animationNames = animationNames.map(name => name.trim());

    // Traverse through all the available keyframes rule and add
    // the keyframes rule that matches the computed animation name
    for (const keyframesRule of this.cssLogic.keyframesRules) {
      if (!animationNames.includes(keyframesRule.name)) {
        continue;
      }

      for (const rule of keyframesRule.cssRules) {
        entries.push(
          this.#getRuleItem(this.styleRef(rule), null, {
            keyframes: this.styleRef(keyframesRule),
          })
        );
      }
    }
  }

  /**
   * Add all the @position-try associated with the element into `entries`
   * based on the applied declarations defined in `computedStyle`.
   *
   * @param {Array<appliedstyle>} entries
   * @param {CSSStyleDeclaration} computedStyle
   */

  #getPositionTryRules(entries, computedStyle) {
    const positionTryIdents = new Set();
    for (const part of computedStyle.positionTryFallbacks.split(",")) {
      const name = part.trim();
      if (name.startsWith("--")) {
        positionTryIdents.add(name);
      }
    }

    if (!positionTryIdents.size) {
      return;
    }

    for (const positionTryRule of this.cssLogic.positionTryRules) {
      if (!positionTryIdents.has(positionTryRule.name)) {
        continue;
      }
      entries.push(this.#getRuleItem(this.styleRef(positionTryRule)));
    }
  }

  /**
   * Get layout-related information about a node.
   * This method returns an object with properties giving information about
   * the node's margin, border, padding and content region sizes, as well
   * as information about the type of box, its position, z-index, etc...
   *
   * @param {NodeActor} node
   * @param {object} options The only available option is autoMargins.
   * If set to true, the element's margins will receive an extra check to see
   * whether they are set to "auto" (knowing that the computed-style in this
   * case would return "0px").
   * The returned object will contain an extra property (autoMargins) listing
   * all margins that are set to auto, e.g. {top: "auto", left: "auto"}.
   * @return {object}
   */

  getLayout(node, options) {
    this.cssLogic.highlight(node.rawNode);

    const layout = {};

    // First, we update the first part of the box model view, with
    // the size of the element.

    const clientRect = node.rawNode.getBoundingClientRect();
    layout.width = parseFloat(clientRect.width.toPrecision(6));
    layout.height = parseFloat(clientRect.height.toPrecision(6));

    // We compute and update the values of margins & co.
    const style = CssLogic.getComputedStyle(node.rawNode);
    for (const prop of [
      "position",
      "top",
      "right",
      "bottom",
      "left",
      "margin-top",
      "margin-right",
      "margin-bottom",
      "margin-left",
      "padding-top",
      "padding-right",
      "padding-bottom",
      "padding-left",
      "border-top-width",
      "border-right-width",
      "border-bottom-width",
      "border-left-width",
      "z-index",
      "box-sizing",
      "display",
      "float",
      "line-height",
    ]) {
      layout[prop] = style.getPropertyValue(prop);
    }

    if (options.autoMargins) {
      layout.autoMargins = this.processMargins(this.cssLogic);
    }

    for (const i in this.map) {
      const property = this.map[i].property;
      this.map[i].value = parseFloat(style.getPropertyValue(property));
    }

    return layout;
  }

  /**
   * Find 'auto' margin properties.
   */

  processMargins(cssLogic) {
    const margins = {};

    for (const prop of ["top""bottom""left""right"]) {
      const info = cssLogic.getPropertyInfo("margin-" + prop);
      const selectors = info.matchedSelectors;
      if (selectors && !!selectors.length && selectors[0].value == "auto") {
        margins[prop] = "auto";
      }
    }

    return margins;
  }

  /**
   * On page navigation, tidy up remaining objects.
   */

  onFrameUnload() {
    this.styleSheetsByRootNode = new WeakMap();
  }

  #onStylesheetUpdated = ({ resourceId, updateKind, updates = {} }) => {
    if (updateKind != "style-applied") {
      return;
    }
    const kind = updates.event.kind;
    // Duplicate refMap content before looping as onStyleApplied may mutate it
    for (const styleActor of [...this.refMap.values()]) {
      // Ignore StyleRuleActor that don't have a parent stylesheet.
      // i.e. actor whose type is ELEMENT_STYLE.
      if (!styleActor._parentSheet) {
        continue;
      }
      const resId = this.styleSheetsManager.getStyleSheetResourceId(
        styleActor._parentSheet
      );
      if (resId === resourceId) {
        styleActor.onStyleApplied(kind);
      }
    }
    this.#styleApplied(kind);
  };

  /**
   * Helper function for adding a new rule and getting its applied style
   * properties
   *
   * @param NodeActor node
   * @param CSSStyleRule rule
   * @returns Array containing its applied style properties
   */

  getNewAppliedProps(node, rule) {
    const ruleActor = this.styleRef(rule);
    return this.getAppliedProps(node, [this.#getRuleItem(ruleActor)], {
      matchedSelectors: true,
    });
  }

  /**
   * Adds a new rule, and returns the new StyleRuleActor.
   *
   * @param {NodeActor} node
   * @param {string} pseudoClasses The list of pseudo classes to append to the
   *        new selector.
   * @returns {StyleRuleActor} the new rule
   */

  async addNewRule(node, pseudoClasses) {
    let sheet = null;
    const doc = node.rawNode.ownerDocument;
    const rootNode = node.rawNode.getRootNode();

    if (
      this.styleSheetsByRootNode.has(rootNode) &&
      this.styleSheetsByRootNode.get(rootNode).ownerNode?.isConnected
    ) {
      sheet = this.styleSheetsByRootNode.get(rootNode);
    } else {
      sheet = await this.styleSheetsManager.addStyleSheet(
        doc,
        node.rawNode.containingShadowRoot || doc.documentElement
      );
      this.styleSheetsByRootNode.set(rootNode, sheet);
    }

    const cssRules = sheet.cssRules;

    // Get the binding element in case node is a pseudo element, so we can properly
    // build the selector
    const { bindingElement, pseudo } = CssLogic.getBindingElementAndPseudo(
      node.rawNode
    );
    const classes = [...bindingElement.classList];

    let selector;
    if (bindingElement.id) {
      selector = "#" + CSS.escape(bindingElement.id);
    } else if (classes.length) {
      selector = "." + classes.map(c => CSS.escape(c)).join(".");
    } else {
      selector = bindingElement.localName;
    }

    if (pseudo && pseudoClasses?.length) {
      throw new Error(
        `Can't set pseudo classes (${JSON.stringify(pseudoClasses)}) onto a pseudo element (${pseudo})`
      );
    }

    if (pseudo) {
      selector += pseudo;
    }
    if (pseudoClasses && pseudoClasses.length) {
      selector += pseudoClasses.join("");
    }

    const index = sheet.insertRule(selector + " {}", cssRules.length);

    const resourceId = this.styleSheetsManager.getStyleSheetResourceId(sheet);
    let authoredText = await this.styleSheetsManager.getText(resourceId);
    authoredText += "\n" + selector + " {\n" + "}";
    await this.styleSheetsManager.setStyleSheetText(resourceId, authoredText);

    const cssRule = sheet.cssRules.item(index);
    const ruleActor = this.styleRef(cssRule, nulltrue);

    this.inspector.targetActor.emit("track-css-change", {
      ...ruleActor.metadata,
      type: "rule-add",
      add: null,
      remove: null,
      selector,
    });

    return { entries: this.getNewAppliedProps(node, cssRule) };
  }

  /**
   * Cause all StyleRuleActor instances of observed CSS rules to check whether the
   * states of their declarations have changed.
   *
   * Observed rules are the latest rules returned by a call to PageStyleActor.getApplied()
   *
   * This is necessary because changes in one rule can cause the declarations in another
   * to not be applicable (inactive CSS). The observers of those rules should be notified.
   * Rules will fire a "rule-updated" event if any of their declarations changed state.
   *
   * Call this method whenever a CSS rule is mutated:
   * - a CSS declaration is added/changed/disabled/removed
   * - a selector is added/changed/removed
   *
   * @param {Array<StyleRuleActor>} rulesToForceRefresh: An array of rules that,
   *        if observed, should be refreshed even if the state of their declaration
   *        didn't change.
   */

  refreshObservedRules(rulesToForceRefresh) {
    for (const rule of this.#observedRules) {
      const force = rulesToForceRefresh && rulesToForceRefresh.includes(rule);
      rule.maybeRefresh(force);
    }
  }

  /**
   * Get an array of existing attribute values in a node document.
   *
   * @param {string} search: A string to filter attribute value on.
   * @param {string} attributeType: The type of attribute we want to retrieve the values.
   * @param {Element} node: The element we want to get possible attributes for. This will
   *        be used to get the document where the search is happening.
   * @returns {Array<string>} An array of strings
   */

  getAttributesInOwnerDocument(search, attributeType, node) {
    if (!search) {
      throw new Error("search is mandatory");
    }

    // In a non-fission world, a node from an iframe shares the same `rootNode` as a node
    // in the top-level document. So here we need to retrieve the document from the node
    // in parameter in order to retrieve the right document.
    // This may change once we have a dedicated walker for every target in a tab, as we'll
    // be able to directly talk to the "right" walker actor.
    const targetDocument = node.rawNode.ownerDocument;

    // We store the result in a Set which will contain the attribute value
    const result = new Set();
    const lcSearch = search.toLowerCase();
    this.#collectAttributesFromDocumentDOM(
      result,
      lcSearch,
      attributeType,
      targetDocument,
      node.rawNode
    );
    this.#collectAttributesFromDocumentStyleSheets(
      result,
      lcSearch,
      attributeType,
      targetDocument
    );

    return Array.from(result).sort();
  }

  /**
   * Collect attribute values from the document DOM tree, matching the passed filter and
   * type, to the result Set.
   *
   * @param {Set<string>} result: A Set to which the results will be added.
   * @param {string} search: A string to filter attribute value on.
   * @param {string} attributeType: The type of attribute we want to retrieve the values.
   * @param {Document} targetDocument: The document the search occurs in.
   * @param {Node} currentNode: The current element rawNode
   */

  #collectAttributesFromDocumentDOM(
    result,
    search,
    attributeType,
    targetDocument,
    nodeRawNode
  ) {
    // In order to retrieve attributes from DOM elements in the document, we're going to
    // do a query on the root node using attributes selector, to directly get the elements
    // matching the attributes we're looking for.

    // For classes, we need something a bit different as the className we're looking
    // for might not be the first in the attribute value, meaning we can't use the
    // "attribute starts with X" selector.
    const attributeSelectorPositionChar = attributeType === "class" ? "*" : "^";
    const selector = `[${attributeType}${attributeSelectorPositionChar}=${search} i]`;

    const matchingElements = targetDocument.querySelectorAll(selector);

    for (const element of matchingElements) {
      if (element === nodeRawNode) {
        return;
      }
      // For class attribute, we need to add the elements of the classList that match
      // the filter string.
      if (attributeType === "class") {
        for (const cls of element.classList) {
          if (!result.has(cls) && cls.toLowerCase().startsWith(search)) {
            result.add(cls);
          }
        }
      } else {
        const { value } = element.attributes[attributeType];
        // For other attributes, we can directly use the attribute value.
        result.add(value);
      }
    }
  }

  /**
   * Collect attribute values from the document stylesheets, matching the passed filter
   * and type, to the result Set.
   *
   * @param {Set<string>} result: A Set to which the results will be added.
   * @param {string} search: A string to filter attribute value on.
   * @param {string} attributeType: The type of attribute we want to retrieve the values.
   *                       It only supports "class" and "id" at the moment.
   * @param {Document} targetDocument: The document the search occurs in.
   */

  #collectAttributesFromDocumentStyleSheets(
    result,
    search,
    attributeType,
    targetDocument
  ) {
    if (attributeType !== "class" && attributeType !== "id") {
      return;
    }

    // We loop through all the stylesheets and their rules, recursively so we can go through
    // nested rules, and then use the lexer to only get the attributes we're looking for.
    const traverseRules = ruleList => {
      for (const rule of ruleList) {
        this.#collectAttributesFromRule(result, rule, search, attributeType);
        if (rule.cssRules) {
          traverseRules(rule.cssRules);
        }
      }
    };
    for (const styleSheet of targetDocument.styleSheets) {
      traverseRules(styleSheet.rules);
    }
  }

  /**
   * Collect attribute values from the rule, matching the passed filter and type, to the
   * result Set.
   *
   * @param {Set<string>} result: A Set to which the results will be added.
   * @param {Rule} rule: The rule the search occurs in.
   * @param {string} search: A string to filter attribute value on.
   * @param {string} attributeType: The type of attribute we want to retrieve the values.
   *                       It only supports "class" and "id" at the moment.
   */

  #collectAttributesFromRule(result, rule, search, attributeType) {
    const shouldRetrieveClasses = attributeType === "class";
    const shouldRetrieveIds = attributeType === "id";

    const { selectorText } = rule;
    // If there's no selectorText, or if the selectorText does not include the
    // filter, we can bail out.
    if (!selectorText || !selectorText.toLowerCase().includes(search)) {
      return;
    }

    // Check if we should parse the selectorText (do we need to check for class/id and
    // if so, does the selector contains class/id related chars).
    const parseForClasses =
      shouldRetrieveClasses &&
      selectorText.toLowerCase().includes(`.${search}`);
    const parseForIds =
      shouldRetrieveIds && selectorText.toLowerCase().includes(`#${search}`);

    if (!parseForClasses && !parseForIds) {
      return;
    }

    const lexer = new InspectorCSSParser(selectorText);
    let token;
    while ((token = lexer.nextToken())) {
      if (
        token.tokenType === "Delim" &&
        shouldRetrieveClasses &&
        token.text === "."
      ) {
        token = lexer.nextToken();
        if (
          token.tokenType === "Ident" &&
          token.text.toLowerCase().startsWith(search)
        ) {
          result.add(token.text);
        }
      }
      if (token.tokenType === "IDHash" && shouldRetrieveIds) {
        const idWithoutHash = token.value;
        if (idWithoutHash.startsWith(search)) {
          result.add(idWithoutHash);
        }
      }
    }
  }

  /**
   * Returns an array of valid anchor names for the selected node
   *
   * @param {NodeActor} node: The node for which we want anchor names
   * @return {Array<string>}
   */

  getAnchorNames(node) {
    return InspectorUtils.getAnchorNamesFor(node.rawNode);
  }
}
exports.PageStyleActor = PageStyleActor;

Messung V0.5 in Prozent
C=91 H=97 G=93

¤ Dauer der Verarbeitung: 0.40 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.