Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/devtools/client/inspector/rules/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 80 kB image not shown  

Quelle  rules.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 flags = require("resource://devtools/shared/flags.js");
const { l10n } = require("resource://devtools/shared/inspector/css-logic.js");
const {
  style: { ELEMENT_STYLE },
} = require("resource://devtools/shared/constants.js");
const {
  PSEUDO_CLASSES,
} = require("resource://devtools/shared/css/constants.js");
const OutputParser = require("resource://devtools/client/shared/output-parser.js");
const { PrefObserver } = require("resource://devtools/client/shared/prefs.js");
const ElementStyle = require("resource://devtools/client/inspector/rules/models/element-style.js");
const RuleEditor = require("resource://devtools/client/inspector/rules/views/rule-editor.js");
const RegisteredPropertyEditor = require("resource://devtools/client/inspector/rules/views/registered-property-editor.js");
const TooltipsOverlay = require("resource://devtools/client/inspector/shared/tooltips-overlay.js");
const {
  createChild,
  promiseWarn,
} = require("resource://devtools/client/inspector/shared/utils.js");
const { debounce } = require("resource://devtools/shared/debounce.js");
const EventEmitter = require("resource://devtools/shared/event-emitter.js");

loader.lazyRequireGetter(
  this,
  ["flashElementOn""flashElementOff"],
  "resource://devtools/client/inspector/markup/utils.js",
  true
);
loader.lazyRequireGetter(
  this,
  "ClassListPreviewer",
  "resource://devtools/client/inspector/rules/views/class-list-previewer.js"
);
loader.lazyRequireGetter(
  this,
  ["getNodeInfo""getNodeCompatibilityInfo""getRuleFromNode"],
  "resource://devtools/client/inspector/rules/utils/utils.js",
  true
);
loader.lazyRequireGetter(
  this,
  "StyleInspectorMenu",
  "resource://devtools/client/inspector/shared/style-inspector-menu.js"
);
loader.lazyRequireGetter(
  this,
  "AutocompletePopup",
  "resource://devtools/client/shared/autocomplete-popup.js"
);
loader.lazyRequireGetter(
  this,
  "KeyShortcuts",
  "resource://devtools/client/shared/key-shortcuts.js"
);
loader.lazyRequireGetter(
  this,
  "clipboardHelper",
  "resource://devtools/shared/platform/clipboard.js"
);

const HTML_NS = "http://www.w3.org/1999/xhtml";
const PREF_UA_STYLES = "devtools.inspector.showUserAgentStyles";
const PREF_DEFAULT_COLOR_UNIT = "devtools.defaultColorUnit";
const PREF_DRAGGABLE = "devtools.inspector.draggable_properties";
const PREF_INPLACE_EDITOR_FOCUS_NEXT_ON_ENTER =
  "devtools.inspector.rule-view.focusNextOnEnter";
const FILTER_CHANGED_TIMEOUT = 150;
// Removes the flash-out class from an element after 1 second.
const PROPERTY_FLASHING_DURATION = 1000;

// This is used to parse user input when filtering.
const FILTER_PROP_RE = /\s*([^:\s]*)\s*:\s*(.*?)\s*;?$/;
// This is used to parse the filter search value to see if the filter
// should be strict or not
const FILTER_STRICT_RE = /\s*`(.*?)`\s*$/;

const RULE_VIEW_HEADER_CLASSNAME = "ruleview-header";
const PSEUDO_ELEMENTS_CONTAINER_ID = "pseudo-elements-container";
const REGISTERED_PROPERTIES_CONTAINER_ID = "registered-properties-container";

/**
 * Our model looks like this:
 *
 * ElementStyle:
 *   Responsible for keeping track of which properties are overridden.
 *   Maintains a list of Rule objects that apply to the element.
 * Rule:
 *   Manages a single style declaration or rule.
 *   Responsible for applying changes to the properties in a rule.
 *   Maintains a list of TextProperty objects.
 * TextProperty:
 *   Manages a single property from the authoredText attribute of the
 *     relevant declaration.
 *   Maintains a list of computed properties that come from this
 *     property declaration.
 *   Changes to the TextProperty are sent to its related Rule for
 *     application.
 *
 * View hierarchy mostly follows the model hierarchy.
 *
 * CssRuleView:
 *   Owns an ElementStyle and creates a list of RuleEditors for its
 *    Rules.
 * RuleEditor:
 *   Owns a Rule object and creates a list of TextPropertyEditors
 *     for its TextProperties.
 *   Manages creation of new text properties.
 * TextPropertyEditor:
 *   Owns a TextProperty object.
 *   Manages changes to the TextProperty.
 *   Can be expanded to display computed properties.
 *   Can mark a property disabled or enabled.
 */


/**
 * CssRuleView is a view of the style rules and declarations that
 * apply to a given element.  After construction, the 'element'
 * property will be available with the user interface.
 *
 * @param {Inspector} inspector
 *        Inspector toolbox panel
 * @param {Document} document
 *        The document that will contain the rule view.
 * @param {Object} store
 *        The CSS rule view can use this object to store metadata
 *        that might outlast the rule view, particularly the current
 *        set of disabled properties.
 */

function CssRuleView(inspector, document, store) {
  EventEmitter.decorate(this);

  this.inspector = inspector;
  this.cssProperties = inspector.cssProperties;
  this.styleDocument = document;
  this.styleWindow = this.styleDocument.defaultView;
  this.store = store || {};

  // Allow tests to override debouncing behavior, as this can cause intermittents.
  this.debounce = debounce;

  // Variable used to stop the propagation of mouse events to children
  // when we are updating a value by dragging the mouse and we then release it
  this.childHasDragged = false;

  this._outputParser = new OutputParser(document, this.cssProperties);
  this._abortController = new this.styleWindow.AbortController();

  this._onAddRule = this._onAddRule.bind(this);
  this._onContextMenu = this._onContextMenu.bind(this);
  this._onCopy = this._onCopy.bind(this);
  this._onFilterStyles = this._onFilterStyles.bind(this);
  this._onClearSearch = this._onClearSearch.bind(this);
  this._onTogglePseudoClassPanel = this._onTogglePseudoClassPanel.bind(this);
  this._onTogglePseudoClass = this._onTogglePseudoClass.bind(this);
  this._onToggleClassPanel = this._onToggleClassPanel.bind(this);
  this._onToggleLightColorSchemeSimulation =
    this._onToggleLightColorSchemeSimulation.bind(this);
  this._onToggleDarkColorSchemeSimulation =
    this._onToggleDarkColorSchemeSimulation.bind(this);
  this._onTogglePrintSimulation = this._onTogglePrintSimulation.bind(this);
  this.highlightElementRule = this.highlightElementRule.bind(this);
  this.highlightProperty = this.highlightProperty.bind(this);
  this.refreshPanel = this.refreshPanel.bind(this);

  const doc = this.styleDocument;
  // Delegate bulk handling of events happening within the DOM tree of the Rules view
  // to this.handleEvent(). Listening on the capture phase of the event bubbling to be
  // able to stop event propagation on a case-by-case basis and prevent event target
  // ancestor nodes from handling them.
  this.styleDocument.addEventListener("click"this, { capture: true });
  this.element = doc.getElementById("ruleview-container-focusable");
  this.addRuleButton = doc.getElementById("ruleview-add-rule-button");
  this.searchField = doc.getElementById("ruleview-searchbox");
  this.searchClearButton = doc.getElementById("ruleview-searchinput-clear");
  this.pseudoClassPanel = doc.getElementById("pseudo-class-panel");
  this.pseudoClassToggle = doc.getElementById("pseudo-class-panel-toggle");
  this.classPanel = doc.getElementById("ruleview-class-panel");
  this.classToggle = doc.getElementById("class-panel-toggle");
  this.colorSchemeLightSimulationButton = doc.getElementById(
    "color-scheme-simulation-light-toggle"
  );
  this.colorSchemeDarkSimulationButton = doc.getElementById(
    "color-scheme-simulation-dark-toggle"
  );
  this.printSimulationButton = doc.getElementById("print-simulation-toggle");

  this._initSimulationFeatures();

  this.searchClearButton.hidden = true;

  this.onHighlighterShown = data =>
    this.handleHighlighterEvent("highlighter-shown", data);
  this.onHighlighterHidden = data =>
    this.handleHighlighterEvent("highlighter-hidden", data);
  this.inspector.highlighters.on("highlighter-shown"this.onHighlighterShown);
  this.inspector.highlighters.on(
    "highlighter-hidden",
    this.onHighlighterHidden
  );

  this.shortcuts = new KeyShortcuts({ window: this.styleWindow });
  this._onShortcut = this._onShortcut.bind(this);
  this.shortcuts.on("Escape", event => this._onShortcut("Escape", event));
  this.shortcuts.on("Return", event => this._onShortcut("Return", event));
  this.shortcuts.on("Space", event => this._onShortcut("Space", event));
  this.shortcuts.on("CmdOrCtrl+F", event =>
    this._onShortcut("CmdOrCtrl+F", event)
  );
  this.element.addEventListener("copy"this._onCopy);
  this.element.addEventListener("contextmenu"this._onContextMenu);
  this.addRuleButton.addEventListener("click"this._onAddRule);
  this.searchField.addEventListener("input"this._onFilterStyles);
  this.searchClearButton.addEventListener("click"this._onClearSearch);
  this.pseudoClassToggle.addEventListener(
    "click",
    this._onTogglePseudoClassPanel
  );
  this.classToggle.addEventListener("click"this._onToggleClassPanel);
  // The "change" event bubbles up from checkbox inputs nested within the panel container.
  this.pseudoClassPanel.addEventListener("change"this._onTogglePseudoClass);

  if (flags.testing) {
    // In tests, we start listening immediately to avoid having to simulate a mousemove.
    this.highlighters.addToView(this);
  } else {
    this.element.addEventListener(
      "mousemove",
      () => {
        this.highlighters.addToView(this);
      },
      { once: true }
    );
  }

  this._handlePrefChange = this._handlePrefChange.bind(this);
  this._handleUAStylePrefChange = this._handleUAStylePrefChange.bind(this);
  this._handleDefaultColorUnitPrefChange =
    this._handleDefaultColorUnitPrefChange.bind(this);
  this._handleDraggablePrefChange = this._handleDraggablePrefChange.bind(this);
  this._handleInplaceEditorFocusNextOnEnterPrefChange =
    this._handleInplaceEditorFocusNextOnEnterPrefChange.bind(this);

  this._prefObserver = new PrefObserver("devtools.");
  this._prefObserver.on(PREF_UA_STYLES, this._handleUAStylePrefChange);
  this._prefObserver.on(
    PREF_DEFAULT_COLOR_UNIT,
    this._handleDefaultColorUnitPrefChange
  );
  this._prefObserver.on(PREF_DRAGGABLE, this._handleDraggablePrefChange);
  // Initialize value of this.draggablePropertiesEnabled
  this._handleDraggablePrefChange();

  this._prefObserver.on(
    PREF_INPLACE_EDITOR_FOCUS_NEXT_ON_ENTER,
    this._handleInplaceEditorFocusNextOnEnterPrefChange
  );
  // Initialize value of this.inplaceEditorFocusNextOnEnter
  this._handleInplaceEditorFocusNextOnEnterPrefChange();

  this.pseudoClassCheckboxes = this._createPseudoClassCheckboxes();
  this.showUserAgentStyles = Services.prefs.getBoolPref(PREF_UA_STYLES);

  // Add the tooltips and highlighters to the view
  this.tooltips = new TooltipsOverlay(this);

  this.cssRegisteredPropertiesByTarget = new Map();
}

CssRuleView.prototype = {
  // The element that we're inspecting.
  _viewedElement: null,

  // Used for cancelling timeouts in the style filter.
  _filterChangedTimeout: null,

  // Empty, unconnected element of the same type as this node, used
  // to figure out how shorthand properties will be parsed.
  _dummyElement: null,

  get popup() {
    if (!this._popup) {
      // The popup will be attached to the toolbox document.
      this._popup = new AutocompletePopup(this.inspector.toolbox.doc, {
        autoSelect: true,
      });
    }

    return this._popup;
  },

  get classListPreviewer() {
    if (!this._classListPreviewer) {
      this._classListPreviewer = new ClassListPreviewer(
        this.inspector,
        this.classPanel
      );
    }

    return this._classListPreviewer;
  },

  get contextMenu() {
    if (!this._contextMenu) {
      this._contextMenu = new StyleInspectorMenu(this, { isRuleView: true });
    }

    return this._contextMenu;
  },

  // Get the dummy elemenet.
  get dummyElement() {
    return this._dummyElement;
  },

  // Get the highlighters overlay from the Inspector.
  get highlighters() {
    if (!this._highlighters) {
      // highlighters is a lazy getter in the inspector.
      this._highlighters = this.inspector.highlighters;
    }

    return this._highlighters;
  },

  // Get the filter search value.
  get searchValue() {
    return this.searchField.value.toLowerCase();
  },

  get rules() {
    return this._elementStyle ? this._elementStyle.rules : [];
  },

  get currentTarget() {
    return this.inspector.toolbox.target;
  },

  /**
   * Highlight/unhighlight all the nodes that match a given rule's selector
   * inside the document of the current selected node.
   * Only one selector can be highlighted at a time, so calling the method a
   * second time with a different rule will first unhighlight the previously
   * highlighted nodes.
   * Calling the method a second time with the same rule will just
   * unhighlight the highlighted nodes.
   *
   * @param {Rule} rule
   * @param {String} selector
   *        Elements matching this selector will be highlighted on the page.
   * @param {Boolean} highlightFromRulesSelector
   */

  async toggleSelectorHighlighter(
    rule,
    selector,
    highlightFromRulesSelector = true
  ) {
    if (this.isSelectorHighlighted(selector)) {
      await this.inspector.highlighters.hideHighlighterType(
        this.inspector.highlighters.TYPES.SELECTOR
      );
    } else {
      const options = {
        hideInfoBar: true,
        hideGuides: true,
        // we still pass the selector (which can be the StyleRuleFront#computedSelector)
        // even if highlightFromRulesSelector is set to true, as it's how we keep track
        // of which selector is highlighted.
        selector,
      };
      if (highlightFromRulesSelector) {
        options.ruleActorID = rule.domRule.actorID;
      }
      await this.inspector.highlighters.showHighlighterTypeForNode(
        this.inspector.highlighters.TYPES.SELECTOR,
        this.inspector.selection.nodeFront,
        options
      );
    }
  },

  isPanelVisible() {
    return (
      this.inspector.toolbox &&
      this.inspector.sidebar &&
      this.inspector.toolbox.currentToolId === "inspector" &&
      (this.inspector.sidebar.getCurrentTabID() == "ruleview" ||
        this.inspector.is3PaneModeEnabled)
    );
  },

  /**
   * Check whether a SelectorHighlighter is active for the given selector text.
   *
   * @param {String} selector
   * @return {Boolean}
   */

  isSelectorHighlighted(selector) {
    const options = this.inspector.highlighters.getOptionsForActiveHighlighter(
      this.inspector.highlighters.TYPES.SELECTOR
    );

    return options?.selector === selector;
  },

  /**
   * Delegate handler for events happening within the DOM tree of the Rules view.
   * Itself delegates to specific handlers by event type.
   *
   * Use this instead of attaching specific event handlers when:
   * - there are many elements with the same event handler (eases memory pressure)
   * - you want to avoid having to remove event handlers manually
   * - elements are added/removed from the DOM tree arbitrarily over time
   *
   * @param {MouseEvent|UIEvent} event
   */

  handleEvent(event) {
    if (this.childHasDragged) {
      this.childHasDragged = false;
      event.stopPropagation();
      return;
    }
    switch (event.type) {
      case "click":
        this.handleClickEvent(event);
        break;
      default:
    }
  },

  /**
   * Delegate handler for click events happening within the DOM tree of the Rules view.
   * Stop propagation of click event wrapping a CSS rule or CSS declaration to avoid
   * triggering the prompt to add a new CSS declaration or to edit the existing one.
   *
   * @param {MouseEvent} event
   */

  async handleClickEvent(event) {
    const target = event.target;

    // Handle click on the icon next to a CSS selector.
    if (target.classList.contains("js-toggle-selector-highlighter")) {
      event.stopPropagation();
      let selector = target.dataset.computedSelector;
      const highlightFromRulesSelector =
        !!selector && !target.dataset.isUniqueSelector;
      // dataset.computedSelector will be initially empty for inline styles (inherited or not)
      // Rules associated with a regular selector should have this data-attribute
      // set in devtools/client/inspector/rules/views/rule-editor.js
      const rule = getRuleFromNode(target, this._elementStyle);
      if (selector === "") {
        try {
          if (rule.inherited) {
            // This is an inline style from an inherited rule. Need to resolve the
            // unique selector from the node which this rule is inherited from.
            selector = await rule.inherited.getUniqueSelector();
          } else {
            // This is an inline style from the current node.
            selector =
              await this.inspector.selection.nodeFront.getUniqueSelector();
          }

          // Now that the selector was computed, we can store it for subsequent usage.
          target.dataset.computedSelector = selector;
          target.dataset.isUniqueSelector = true;
        } finally {
          // Could not resolve a unique selector for the inline style.
        }
      }

      this.toggleSelectorHighlighter(
        rule,
        selector,
        highlightFromRulesSelector
      );
    }

    // Handle click on swatches next to flex and inline-flex CSS properties
    if (target.classList.contains("js-toggle-flexbox-highlighter")) {
      event.stopPropagation();
      this.inspector.highlighters.toggleFlexboxHighlighter(
        this.inspector.selection.nodeFront,
        "rule"
      );
    }

    // Handle click on swatches next to grid CSS properties
    if (target.classList.contains("js-toggle-grid-highlighter")) {
      event.stopPropagation();
      this.inspector.highlighters.toggleGridHighlighter(
        this.inspector.selection.nodeFront,
        "rule"
      );
    }
  },

  /**
   * Delegate handler for highlighter events.
   *
   * This is the place to observe for highlighter events, check the highlighter type and
   * event name, then react to specific events, for example by modifying the DOM.
   *
   * @param {String} eventName
   *        Highlighter event name. One of: "highlighter-hidden", "highlighter-shown"
   * @param {Object} data
   *        Object with data associated with the highlighter event.
   */

  handleHighlighterEvent(eventName, data) {
    switch (data.type) {
      // Toggle the "highlighted" class on selector icons in the Rules view when
      // the SelectorHighlighter is shown/hidden for a certain CSS selector.
      case this.inspector.highlighters.TYPES.SELECTOR:
        {
          const selector = data?.options?.selector;
          if (!selector) {
            return;
          }

          const query = `.js-toggle-selector-highlighter[data-computed-selector='${selector}']`;
          for (const node of this.styleDocument.querySelectorAll(query)) {
            const isHighlighterDisplayed = eventName == "highlighter-shown";
            node.classList.toggle("highlighted", isHighlighterDisplayed);
            node.setAttribute("aria-pressed", isHighlighterDisplayed);
          }
        }
        break;

      // Toggle the "aria-pressed" attribute on swatches next to flex and inline-flex CSS properties
      // when the FlexboxHighlighter is shown/hidden for the currently selected node.
      case this.inspector.highlighters.TYPES.FLEXBOX:
        {
          const query = ".js-toggle-flexbox-highlighter";
          for (const node of this.styleDocument.querySelectorAll(query)) {
            node.setAttribute("aria-pressed", eventName == "highlighter-shown");
          }
        }
        break;

      // Toggle the "aria-pressed" class on swatches next to grid CSS properties
      // when the GridHighlighter is shown/hidden for the currently selected node.
      case this.inspector.highlighters.TYPES.GRID:
        {
          const query = ".js-toggle-grid-highlighter";
          for (const node of this.styleDocument.querySelectorAll(query)) {
            // From the Layout panel, we can toggle grid highlighters for nodes which are
            // not currently selected. The Rules view shows `display: grid` declarations
            // only for the selected node. Avoid mistakenly marking them as "active".
            if (data.nodeFront === this.inspector.selection.nodeFront) {
              node.setAttribute(
                "aria-pressed",
                eventName == "highlighter-shown"
              );
            }

            // When the max limit of grid highlighters is reached (default 3),
            // mark inactive grid swatches as disabled.
            node.toggleAttribute(
              "disabled",
              !this.inspector.highlighters.canGridHighlighterToggle(
                this.inspector.selection.nodeFront
              )
            );
          }
        }
        break;
    }
  },

  /**
   * Enables the print and color scheme simulation only for local and remote tab debugging.
   */

  async _initSimulationFeatures() {
    if (!this.inspector.commands.descriptorFront.isTabDescriptor) {
      return;
    }
    this.colorSchemeLightSimulationButton.removeAttribute("hidden");
    this.colorSchemeDarkSimulationButton.removeAttribute("hidden");
    this.printSimulationButton.removeAttribute("hidden");
    this.printSimulationButton.addEventListener(
      "click",
      this._onTogglePrintSimulation
    );
    this.colorSchemeLightSimulationButton.addEventListener(
      "click",
      this._onToggleLightColorSchemeSimulation
    );
    this.colorSchemeDarkSimulationButton.addEventListener(
      "click",
      this._onToggleDarkColorSchemeSimulation
    );
    const { rfpCSSColorScheme } = this.inspector.walker;
    if (rfpCSSColorScheme) {
      this.colorSchemeLightSimulationButton.setAttribute("disabled"true);
      this.colorSchemeDarkSimulationButton.setAttribute("disabled"true);
      console.warn("Color scheme simulation is disabled in RFP mode.");
    }
  },

  /**
   * Get the type of a given node in the rule-view
   *
   * @param {DOMNode} node
   *        The node which we want information about
   * @return {Object|null} containing the following props:
   * - type {String} One of the VIEW_NODE_XXX_TYPE const in
   *   client/inspector/shared/node-types.
   * - rule {Rule} The Rule object.
   * - value {Object} Depends on the type of the node.
   * Otherwise, returns null if the node isn't anything we care about.
   */

  getNodeInfo(node) {
    return getNodeInfo(node, this._elementStyle);
  },

  /**
   * Get the node's compatibility issues
   *
   * @param {DOMNode} node
   *        The node which we want information about
   * @return {Object|null} containing the following props:
   * - type {String} Compatibility issue type.
   * - property {string} The incompatible rule
   * - alias {Array} The browser specific alias of rule
   * - url {string} Link to MDN documentation
   * - deprecated {bool} True if the rule is deprecated
   * - experimental {bool} True if rule is experimental
   * - unsupportedBrowsers {Array} Array of unsupported browser
   * Otherwise, returns null if the node has cross-browser compatible CSS
   */

  async getNodeCompatibilityInfo(node) {
    const compatibilityInfo = await getNodeCompatibilityInfo(
      node,
      this._elementStyle
    );

    return compatibilityInfo;
  },

  /**
   * Context menu handler.
   */

  _onContextMenu(event) {
    if (
      event.originalTarget.closest("input[type=text]") ||
      event.originalTarget.closest("input:not([type])") ||
      event.originalTarget.closest("textarea")
    ) {
      return;
    }

    event.stopPropagation();
    event.preventDefault();

    this.contextMenu.show(event);
  },

  /**
   * Callback for copy event. Copy the selected text.
   *
   * @param {Event} event
   *        copy event object.
   */

  _onCopy(event) {
    if (event) {
      this.copySelection(event.target);
      event.preventDefault();
      event.stopPropagation();
    }
  },

  /**
   * Copy the current selection. The current target is necessary
   * if the selection is inside an input or a textarea
   *
   * @param {DOMNode} target
   *        DOMNode target of the copy action
   */

  copySelection(target) {
    try {
      let text = "";

      const nodeName = target?.nodeName;
      const targetType = target?.type;

      if (
        // The target can be the enable/disable rule checkbox here (See Bug 1680893).
        (nodeName === "input" && targetType !== "checkbox") ||
        nodeName == "textarea"
      ) {
        const start = Math.min(target.selectionStart, target.selectionEnd);
        const end = Math.max(target.selectionStart, target.selectionEnd);
        const count = end - start;
        text = target.value.substr(start, count);
      } else {
        text = this.styleWindow.getSelection().toString();

        // Remove any double newlines.
        text = text.replace(/(\r?\n)\r?\n/g, "$1");
      }

      clipboardHelper.copyString(text);
    } catch (e) {
      console.error(e);
    }
  },

  /**
   * Add a new rule to the current element.
   */

  async _onAddRule() {
    const elementStyle = this._elementStyle;
    const element = elementStyle.element;
    const pseudoClasses = element.pseudoClassLocks;

    this._focusNextUserAddedRule = true;
    this.pageStyle.addNewRule(element, pseudoClasses);
  },

  /**
   * Disables add rule button when needed
   */

  refreshAddRuleButtonState() {
    const shouldBeDisabled =
      !this._viewedElement ||
      !this.inspector.selection.isElementNode() ||
      this.inspector.selection.isAnonymousNode();
    this.addRuleButton.disabled = shouldBeDisabled;
  },

  /**
   * Return {Boolean} true if the rule view currently has an input
   * editor visible.
   */

  get isEditing() {
    return (
      this.tooltips.isEditing ||
      !!this.element.querySelectorAll(".styleinspector-propertyeditor").length
    );
  },

  _handleUAStylePrefChange() {
    this.showUserAgentStyles = Services.prefs.getBoolPref(PREF_UA_STYLES);
    this._handlePrefChange(PREF_UA_STYLES);
  },

  _handleDefaultColorUnitPrefChange() {
    this._handlePrefChange(PREF_DEFAULT_COLOR_UNIT);
  },

  _handleDraggablePrefChange() {
    this.draggablePropertiesEnabled = Services.prefs.getBoolPref(
      PREF_DRAGGABLE,
      false
    );
    // This event is consumed by text-property-editor instances in order to
    // update their draggable behavior. Preferences observer are costly, so
    // we are forwarding the preference update via the EventEmitter.
    this.emit("draggable-preference-updated");
  },

  _handleInplaceEditorFocusNextOnEnterPrefChange() {
    this.inplaceEditorFocusNextOnEnter = Services.prefs.getBoolPref(
      PREF_INPLACE_EDITOR_FOCUS_NEXT_ON_ENTER,
      false
    );
    this._handlePrefChange(PREF_INPLACE_EDITOR_FOCUS_NEXT_ON_ENTER);
  },

  _handlePrefChange(pref) {
    // Reselect the currently selected element
    const refreshOnPrefs = [
      PREF_UA_STYLES,
      PREF_DEFAULT_COLOR_UNIT,
      PREF_INPLACE_EDITOR_FOCUS_NEXT_ON_ENTER,
    ];
    if (this._viewedElement && refreshOnPrefs.includes(pref)) {
      this.selectElement(this._viewedElement, true);
    }
  },

  /**
   * Set the filter style search value.
   * @param {String} value
   *        The search value.
   */

  setFilterStyles(value = "") {
    this.searchField.value = value;
    this.searchField.focus();
    this._onFilterStyles();
  },

  /**
   * Called when the user enters a search term in the filter style search box.
   */

  _onFilterStyles() {
    if (this._filterChangedTimeout) {
      clearTimeout(this._filterChangedTimeout);
    }

    const filterTimeout = this.searchValue.length ? FILTER_CHANGED_TIMEOUT : 0;
    this.searchClearButton.hidden = this.searchValue.length === 0;

    this._filterChangedTimeout = setTimeout(() => {
      this.searchData = {
        searchPropertyMatch: FILTER_PROP_RE.exec(this.searchValue),
        searchPropertyName: this.searchValue,
        searchPropertyValue: this.searchValue,
        strictSearchValue: "",
        strictSearchPropertyName: false,
        strictSearchPropertyValue: false,
        strictSearchAllValues: false,
      };

      if (this.searchData.searchPropertyMatch) {
        // Parse search value as a single property line and extract the
        // property name and value. If the parsed property name or value is
        // contained in backquotes (`), extract the value within the backquotes
        // and set the corresponding strict search for the property to true.
        if (FILTER_STRICT_RE.test(this.searchData.searchPropertyMatch[1])) {
          this.searchData.strictSearchPropertyName = true;
          this.searchData.searchPropertyName = FILTER_STRICT_RE.exec(
            this.searchData.searchPropertyMatch[1]
          )[1];
        } else {
          this.searchData.searchPropertyName =
            this.searchData.searchPropertyMatch[1];
        }

        if (FILTER_STRICT_RE.test(this.searchData.searchPropertyMatch[2])) {
          this.searchData.strictSearchPropertyValue = true;
          this.searchData.searchPropertyValue = FILTER_STRICT_RE.exec(
            this.searchData.searchPropertyMatch[2]
          )[1];
        } else {
          this.searchData.searchPropertyValue =
            this.searchData.searchPropertyMatch[2];
        }

        // Strict search for stylesheets will match the property line regex.
        // Extract the search value within the backquotes to be used
        // in the strict search for stylesheets in _highlightStyleSheet.
        if (FILTER_STRICT_RE.test(this.searchValue)) {
          this.searchData.strictSearchValue = FILTER_STRICT_RE.exec(
            this.searchValue
          )[1];
        }
      } else if (FILTER_STRICT_RE.test(this.searchValue)) {
        // If the search value does not correspond to a property line and
        // is contained in backquotes, extract the search value within the
        // backquotes and set the flag to perform a strict search for all
        // the values (selector, stylesheet, property and computed values).
        const searchValue = FILTER_STRICT_RE.exec(this.searchValue)[1];
        this.searchData.strictSearchAllValues = true;
        this.searchData.searchPropertyName = searchValue;
        this.searchData.searchPropertyValue = searchValue;
        this.searchData.strictSearchValue = searchValue;
      }

      this._clearHighlight(this.element);
      this._clearRules();
      this._createEditors();

      this.inspector.emit("ruleview-filtered");

      this._filterChangeTimeout = null;
    }, filterTimeout);
  },

  /**
   * Called when the user clicks on the clear button in the filter style search
   * box. Returns true if the search box is cleared and false otherwise.
   */

  _onClearSearch() {
    if (this.searchField.value) {
      this.setFilterStyles("");
      return true;
    }

    return false;
  },

  destroy() {
    this.isDestroyed = true;
    this.clear();

    this._dummyElement = null;
    // off handlers must have the same reference as their on handlers
    this._prefObserver.off(PREF_UA_STYLES, this._handleUAStylePrefChange);
    this._prefObserver.off(
      PREF_DEFAULT_COLOR_UNIT,
      this._handleDefaultColorUnitPrefChange
    );
    this._prefObserver.off(PREF_DRAGGABLE, this._handleDraggablePrefChange);
    this._prefObserver.off(
      PREF_INPLACE_EDITOR_FOCUS_NEXT_ON_ENTER,
      this._handleInplaceEditorFocusNextOnEnterPrefChange
    );
    this._prefObserver.destroy();

    this._outputParser = null;

    if (this._classListPreviewer) {
      this._classListPreviewer.destroy();
      this._classListPreviewer = null;
    }

    if (this._contextMenu) {
      this._contextMenu.destroy();
      this._contextMenu = null;
    }

    if (this._highlighters) {
      this._highlighters.removeFromView(this);
      this._highlighters = null;
    }

    // Clean-up for simulations.
    this.colorSchemeLightSimulationButton.removeEventListener(
      "click",
      this._onToggleLightColorSchemeSimulation
    );
    this.colorSchemeDarkSimulationButton.removeEventListener(
      "click",
      this._onToggleDarkColorSchemeSimulation
    );
    this.printSimulationButton.removeEventListener(
      "click",
      this._onTogglePrintSimulation
    );

    this.colorSchemeLightSimulationButton = null;
    this.colorSchemeDarkSimulationButton = null;
    this.printSimulationButton = null;

    this.tooltips.destroy();

    // Remove bound listeners
    this._abortController.abort();
    this._abortController = null;
    this.shortcuts.destroy();
    this.styleDocument.removeEventListener("click"this, { capture: true });
    this.element.removeEventListener("copy"this._onCopy);
    this.element.removeEventListener("contextmenu"this._onContextMenu);
    this.addRuleButton.removeEventListener("click"this._onAddRule);
    this.searchField.removeEventListener("input"this._onFilterStyles);
    this.searchClearButton.removeEventListener("click"this._onClearSearch);
    this.pseudoClassPanel.removeEventListener(
      "change",
      this._onTogglePseudoClass
    );
    this.pseudoClassToggle.removeEventListener(
      "click",
      this._onTogglePseudoClassPanel
    );
    this.classToggle.removeEventListener("click"this._onToggleClassPanel);
    this.inspector.highlighters.off(
      "highlighter-shown",
      this.onHighlighterShown
    );
    this.inspector.highlighters.off(
      "highlighter-hidden",
      this.onHighlighterHidden
    );

    this.searchField = null;
    this.searchClearButton = null;
    this.pseudoClassPanel = null;
    this.pseudoClassToggle = null;
    this.pseudoClassCheckboxes = null;
    this.classPanel = null;
    this.classToggle = null;

    this.inspector = null;
    this.styleDocument = null;
    this.styleWindow = null;

    if (this.element.parentNode) {
      this.element.remove();
    }

    if (this._elementStyle) {
      this._elementStyle.destroy();
    }

    if (this._popup) {
      this._popup.destroy();
      this._popup = null;
    }
  },

  /**
   * Mark the view as selecting an element, disabling all interaction, and
   * visually clearing the view after a few milliseconds to avoid confusion
   * about which element's styles the rule view shows.
   */

  _startSelectingElement() {
    this.element.classList.add("non-interactive");
  },

  /**
   * Mark the view as no longer selecting an element, re-enabling interaction.
   */

  _stopSelectingElement() {
    this.element.classList.remove("non-interactive");
  },

  /**
   * Update the view with a new selected element.
   *
   * @param {NodeActor} element
   *        The node whose style rules we'll inspect.
   * @param {Boolean} allowRefresh
   *        Update the view even if the element is the same as last time.
   */

  selectElement(element, allowRefresh = false) {
    const refresh = this._viewedElement === element;
    if (refresh && !allowRefresh) {
      return Promise.resolve(undefined);
    }

    if (this._popup && this.popup.isOpen) {
      this.popup.hidePopup();
    }

    this.clear(false);
    this._viewedElement = element;

    this.clearPseudoClassPanel();
    this.refreshAddRuleButtonState();

    if (!this._viewedElement) {
      this._stopSelectingElement();
      this._clearRules();
      this._showEmpty();
      this.refreshPseudoClassPanel();
      if (this.pageStyle) {
        this.pageStyle.off("stylesheet-updated"this.refreshPanel);
        this.pageStyle = null;
      }
      return Promise.resolve(undefined);
    }

    this.pageStyle = element.inspectorFront.pageStyle;
    this.pageStyle.on("stylesheet-updated"this.refreshPanel);

    // To figure out how shorthand properties are interpreted by the
    // engine, we will set properties on a dummy element and observe
    // how their .style attribute reflects them as computed values.
    const dummyElementPromise = Promise.resolve(this.styleDocument)
      .then(document => {
        // ::before and ::after do not have a namespaceURI
        const namespaceURI =
          this.element.namespaceURI || document.documentElement.namespaceURI;
        this._dummyElement = document.createElementNS(
          namespaceURI,
          this.element.tagName
        );
      })
      .catch(promiseWarn);

    const elementStyle = new ElementStyle(
      element,
      this,
      this.store,
      this.pageStyle,
      this.showUserAgentStyles
    );
    this._elementStyle = elementStyle;

    this._startSelectingElement();

    return dummyElementPromise
      .then(() => {
        if (this._elementStyle === elementStyle) {
          return this._populate();
        }
        return undefined;
      })
      .then(() => {
        if (this._elementStyle === elementStyle) {
          if (!refresh) {
            this.element.scrollTop = 0;
          }
          this._stopSelectingElement();
          this._elementStyle.onChanged = () => {
            this._changed();
          };
        }
      })
      .catch(e => {
        if (this._elementStyle === elementStyle) {
          this._stopSelectingElement();
          this._clearRules();
        }
        console.error(e);
      });
  },

  /**
   * Update the rules for the currently highlighted element.
   */

  refreshPanel() {
    // Ignore refreshes when the panel is hidden, or during editing or when no element is selected.
    if (!this.isPanelVisible() || this.isEditing || !this._elementStyle) {
      return Promise.resolve(undefined);
    }

    // Repopulate the element style once the current modifications are done.
    const promises = [];
    for (const rule of this._elementStyle.rules) {
      if (rule._applyingModifications) {
        promises.push(rule._applyingModifications);
      }
    }

    return Promise.all(promises).then(() => {
      return this._populate();
    });
  },

  /**
   * Clear the pseudo class options panel by removing the checked and disabled
   * attributes for each checkbox.
   */

  clearPseudoClassPanel() {
    this.pseudoClassCheckboxes.forEach(checkbox => {
      checkbox.checked = false;
      checkbox.disabled = false;
    });
  },

  /**
   * For each item in PSEUDO_CLASSES, create a checkbox input element for toggling a
   * pseudo-class on the selected element and append it to the pseudo-class panel.
   *
   * Returns an array with the checkbox input elements for pseudo-classes.
   *
   * @return {Array}
   */

  _createPseudoClassCheckboxes() {
    const doc = this.styleDocument;
    const fragment = doc.createDocumentFragment();

    for (const pseudo of PSEUDO_CLASSES) {
      const label = doc.createElement("label");
      const checkbox = doc.createElement("input");
      checkbox.setAttribute("tabindex""-1");
      checkbox.setAttribute("type""checkbox");
      checkbox.setAttribute("value", pseudo);

      label.append(checkbox, pseudo);
      fragment.append(label);
    }

    this.pseudoClassPanel.append(fragment);
    return Array.from(
      this.pseudoClassPanel.querySelectorAll("input[type=checkbox]")
    );
  },

  /**
   * Update the pseudo class options for the currently highlighted element.
   */

  refreshPseudoClassPanel() {
    if (!this._elementStyle || !this.inspector.selection.isElementNode()) {
      this.pseudoClassCheckboxes.forEach(checkbox => {
        checkbox.disabled = true;
      });
      return;
    }

    const pseudoClassLocks = this._elementStyle.element.pseudoClassLocks;
    this.pseudoClassCheckboxes.forEach(checkbox => {
      checkbox.disabled = false;
      checkbox.checked = pseudoClassLocks.includes(checkbox.value);
    });
  },

  _populate() {
    const elementStyle = this._elementStyle;
    return this._elementStyle
      .populate()
      .then(() => {
        if (this._elementStyle !== elementStyle || this.isDestroyed) {
          return null;
        }

        this._clearRules();
        const onEditorsReady = this._createEditors();
        this.refreshPseudoClassPanel();

        // Notify anyone that cares that we refreshed.
        return onEditorsReady.then(() => {
          this.emit("ruleview-refreshed");
        }, console.error);
      })
      .catch(promiseWarn);
  },

  /**
   * Show the user that the rule view has no node selected.
   */

  _showEmpty() {
    if (this.styleDocument.getElementById("ruleview-no-results")) {
      return;
    }

    createChild(this.element, "div", {
      id: "ruleview-no-results",
      class"devtools-sidepanel-no-result",
      textContent: l10n("rule.empty"),
    });
  },

  /**
   * Clear the rules.
   */

  _clearRules() {
    this.element.innerHTML = "";
  },

  /**
   * Clear the rule view.
   */

  clear(clearDom = true) {
    if (clearDom) {
      this._clearRules();
    }
    this._viewedElement = null;

    if (this._elementStyle) {
      this._elementStyle.destroy();
      this._elementStyle = null;
    }

    if (this.pageStyle) {
      this.pageStyle.off("stylesheet-updated"this.refreshPanel);
      this.pageStyle = null;
    }
  },

  /**
   * Called when the user has made changes to the ElementStyle.
   * Emits an event that clients can listen to.
   */

  _changed() {
    this.emit("ruleview-changed");
  },

  /**
   * Text for header that shows above rules for this element
   */

  get selectedElementLabel() {
    if (this._selectedElementLabel) {
      return this._selectedElementLabel;
    }
    this._selectedElementLabel = l10n("rule.selectedElement");
    return this._selectedElementLabel;
  },

  /**
   * Text for header that shows above rules for pseudo elements
   */

  get pseudoElementLabel() {
    if (this._pseudoElementLabel) {
      return this._pseudoElementLabel;
    }
    this._pseudoElementLabel = l10n("rule.pseudoElement");
    return this._pseudoElementLabel;
  },

  get showPseudoElements() {
    if (this._showPseudoElements === undefined) {
      this._showPseudoElements = Services.prefs.getBoolPref(
        "devtools.inspector.show_pseudo_elements"
      );
    }
    return this._showPseudoElements;
  },

  /**
   * Creates an expandable container in the rule view
   *
   * @param  {String} label
   *         The label for the container header
   * @param  {String} containerId
   *         The id that will be set on the container
   * @param  {Boolean} isPseudo
   *         Whether or not the container will hold pseudo element rules
   * @return {DOMNode} The container element
   */

  createExpandableContainer(label, containerId, isPseudo = false) {
    const header = this.styleDocument.createElementNS(HTML_NS, "div");
    header.classList.add(
      RULE_VIEW_HEADER_CLASSNAME,
      "ruleview-expandable-header"
    );
    header.setAttribute("role""heading");

    const toggleButton = this.styleDocument.createElementNS(HTML_NS, "button");
    toggleButton.setAttribute(
      "title",
      l10n("rule.expandableContainerToggleButton.title")
    );
    toggleButton.setAttribute("aria-expanded""true");
    toggleButton.setAttribute("aria-controls", containerId);

    const twisty = this.styleDocument.createElementNS(HTML_NS, "span");
    twisty.className = "ruleview-expander theme-twisty";

    toggleButton.append(twisty, this.styleDocument.createTextNode(label));
    header.append(toggleButton);

    const container = this.styleDocument.createElementNS(HTML_NS, "div");
    container.id = containerId;
    container.classList.add("ruleview-expandable-container");
    container.hidden = false;

    this.element.append(header, container);

    toggleButton.addEventListener("click", () => {
      this._toggleContainerVisibility(
        toggleButton,
        container,
        isPseudo,
        !this.showPseudoElements
      );
    });

    if (isPseudo) {
      this._toggleContainerVisibility(
        toggleButton,
        container,
        isPseudo,
        this.showPseudoElements
      );
    }

    return container;
  },

  /**
   * Create the `@property` expandable container
   *
   * @returns {Element}
   */

  createRegisteredPropertiesExpandableContainer() {
    const el = this.createExpandableContainer(
      "@property",
      REGISTERED_PROPERTIES_CONTAINER_ID
    );
    el.classList.add("registered-properties");
    return el;
  },

  /**
   * Return the RegisteredPropertyEditor element for a given property name
   *
   * @param {String} registeredPropertyName
   * @returns {Element|null}
   */

  getRegisteredPropertyElement(registeredPropertyName) {
    return this.styleDocument.querySelector(
      `#${REGISTERED_PROPERTIES_CONTAINER_ID} [data-name="${registeredPropertyName}"]`
    );
  },

  /**
   * Toggle the visibility of an expandable container
   *
   * @param  {DOMNode}  twisty
   *         Clickable toggle DOM Node
   * @param  {DOMNode}  container
   *         Expandable container DOM Node
   * @param  {Boolean}  isPseudo
   *         Whether or not the container will hold pseudo element rules
   * @param  {Boolean}  showPseudo
   *         Whether or not pseudo element rules should be displayed
   */

  _toggleContainerVisibility(toggleButton, container, isPseudo, showPseudo) {
    let isOpen = toggleButton.getAttribute("aria-expanded") === "true";

    if (isPseudo) {
      this._showPseudoElements = !!showPseudo;

      Services.prefs.setBoolPref(
        "devtools.inspector.show_pseudo_elements",
        this.showPseudoElements
      );

      container.hidden = !this.showPseudoElements;
      isOpen = !this.showPseudoElements;
    } else {
      container.hidden = !container.hidden;
    }

    toggleButton.setAttribute("aria-expanded", !isOpen);
  },

  /**
   * Creates editor UI for each of the rules in _elementStyle.
   */

  // eslint-disable-next-line complexity
  _createEditors() {
    // Run through the current list of rules, attaching
    // their editors in order.  Create editors if needed.
    let lastInheritedSource = "";
    let lastKeyframes = null;
    let seenPseudoElement = false;
    let seenNormalElement = false;
    let seenSearchTerm = false;
    let container = null;

    if (!this._elementStyle.rules) {
      return Promise.resolve();
    }

    const editorReadyPromises = [];
    for (const rule of this._elementStyle.rules) {
      if (rule.domRule.system) {
        continue;
      }

      // Initialize rule editor
      if (!rule.editor) {
        rule.editor = new RuleEditor(this, rule);
        editorReadyPromises.push(rule.editor.once("source-link-updated"));
      }

      // Filter the rules and highlight any matches if there is a search input
      if (this.searchValue && this.searchData) {
        if (this.highlightRule(rule)) {
          seenSearchTerm = true;
        } else if (rule.domRule.type !== ELEMENT_STYLE) {
          continue;
        }
      }

      // Only print header for this element if there are pseudo elements
      if (seenPseudoElement && !seenNormalElement && !rule.pseudoElement) {
        seenNormalElement = true;
        const div = this.styleDocument.createElementNS(HTML_NS, "div");
        div.className = RULE_VIEW_HEADER_CLASSNAME;
        div.setAttribute("role""heading");
        div.textContent = this.selectedElementLabel;
        this.element.appendChild(div);
      }

      const inheritedSource = rule.inherited;
      if (inheritedSource && inheritedSource !== lastInheritedSource) {
        const div = this.styleDocument.createElementNS(HTML_NS, "div");
        div.classList.add(
          RULE_VIEW_HEADER_CLASSNAME,
          "ruleview-header-inherited"
        );
        div.setAttribute("role""heading");
        div.setAttribute("aria-level""3");
        div.textContent = rule.inheritedSource;
        lastInheritedSource = inheritedSource;
        this.element.appendChild(div);
      }

      if (!seenPseudoElement && rule.pseudoElement) {
        seenPseudoElement = true;
        container = this.createExpandableContainer(
          this.pseudoElementLabel,
          PSEUDO_ELEMENTS_CONTAINER_ID,
          true
        );
      }

      const keyframes = rule.keyframes;
      if (keyframes && keyframes !== lastKeyframes) {
        lastKeyframes = keyframes;
        container = this.createExpandableContainer(
          rule.keyframesName,
          `keyframes-container-${keyframes.name}`
        );
      }

      rule.editor.element.setAttribute("role""article");
      if (container && (rule.pseudoElement || keyframes)) {
        container.appendChild(rule.editor.element);
      } else {
        this.element.appendChild(rule.editor.element);
      }

      // Automatically select the selector input when we are adding a user-added rule
      if (this._focusNextUserAddedRule && rule.domRule.userAdded) {
        this._focusNextUserAddedRule = null;
        rule.editor.selectorText.click();
        this.emitForTests("new-rule-added", rule);
      }
    }

    const targetRegisteredProperties =
      this.getRegisteredPropertiesForSelectedNodeTarget();
    if (targetRegisteredProperties?.size) {
      const registeredPropertiesContainer =
        this.createRegisteredPropertiesExpandableContainer();

      // Sort properties by their name, as we want to display them in alphabetical order
      const propertyDefinitions = Array.from(
        targetRegisteredProperties.values()
      ).sort((a, b) => (a.name < b.name ? -1 : 1));
      for (const propertyDefinition of propertyDefinitions) {
        const registeredPropertyEditor = new RegisteredPropertyEditor(
          this,
          propertyDefinition
        );

        registeredPropertiesContainer.appendChild(
          registeredPropertyEditor.element
        );
      }
    }

    const searchBox = this.searchField.parentNode;
    searchBox.classList.toggle(
      "devtools-searchbox-no-match",
      this.searchValue && !seenSearchTerm
    );

    return Promise.all(editorReadyPromises);
  },

  /**
   * Highlight rules that matches the filter search value and returns a
   * boolean indicating whether or not rules were highlighted.
   *
   * @param  {Rule} rule
   *         The rule object we're highlighting if its rule selectors or
   *         property values match the search value.
   * @return {Boolean} true if the rule was highlighted, false otherwise.
   */

  highlightRule(rule) {
    const isRuleSelectorHighlighted = this._highlightRuleSelector(rule);
    const isStyleSheetHighlighted = this._highlightStyleSheet(rule);
    const isAncestorRulesHighlighted = this._highlightAncestorRules(rule);
    let isHighlighted =
      isRuleSelectorHighlighted ||
      isStyleSheetHighlighted ||
      isAncestorRulesHighlighted;

    // Highlight search matches in the rule properties
    for (const textProp of rule.textProps) {
      if (!textProp.invisible && this._highlightProperty(textProp.editor)) {
        isHighlighted = true;
      }
    }

    return isHighlighted;
  },

  /**
   * Highlights the rule selector that matches the filter search value and
   * returns a boolean indicating whether or not the selector was highlighted.
   *
   * @param  {Rule} rule
   *         The Rule object.
   * @return {Boolean} true if the rule selector was highlighted,
   *         false otherwise.
   */

  _highlightRuleSelector(rule) {
    let isSelectorHighlighted = false;

    let selectorNodes = [...rule.editor.selectorText.childNodes];
    if (rule.domRule.type === CSSRule.KEYFRAME_RULE) {
      selectorNodes = [rule.editor.selectorText];
    } else if (rule.domRule.type === ELEMENT_STYLE) {
      selectorNodes = [];
    }

    // Highlight search matches in the rule selectors
    for (const selectorNode of selectorNodes) {
      const selector = selectorNode.textContent.toLowerCase();
      if (
        (this.searchData.strictSearchAllValues &&
          selector === this.searchData.strictSearchValue) ||
        (!this.searchData.strictSearchAllValues &&
          selector.includes(this.searchValue))
      ) {
        selectorNode.classList.add("ruleview-highlight");
        isSelectorHighlighted = true;
      }
    }

    return isSelectorHighlighted;
  },

  /**
   * Highlights the ancestor rules data (@media / @layer) that matches the filter search
   * value and returns a boolean indicating whether or not element was highlighted.
   *
   * @return {Boolean} true if the element was highlighted, false otherwise.
   */

  _highlightAncestorRules(rule) {
    const element = rule.editor.ancestorDataEl;
    if (!element) {
      return false;
    }

    const ancestorSelectors = element.querySelectorAll(
      ".ruleview-rule-ancestor-selectorcontainer"
    );

    let isHighlighted = false;
    for (const child of ancestorSelectors) {
      const dataText = child.innerText.toLowerCase();
      const matches = this.searchData.strictSearchValue
        ? dataText === this.searchData.strictSearchValue
        : dataText.includes(this.searchValue);
      if (matches) {
        isHighlighted = true;
        child.classList.add("ruleview-highlight");
      }
    }

    return isHighlighted;
  },

  /**
   * Highlights the stylesheet source that matches the filter search value and
   * returns a boolean indicating whether or not the stylesheet source was
   * highlighted.
   *
   * @return {Boolean} true if the stylesheet source was highlighted, false
   *         otherwise.
   */

  _highlightStyleSheet(rule) {
    const styleSheetSource = rule.title.toLowerCase();
    const isStyleSheetHighlighted = this.searchData.strictSearchValue
      ? styleSheetSource === this.searchData.strictSearchValue
      : styleSheetSource.includes(this.searchValue);

    if (isStyleSheetHighlighted) {
      rule.editor.source.classList.add("ruleview-highlight");
    }

    return isStyleSheetHighlighted;
  },

  /**
   * Highlights the rule properties and computed properties that match the
   * filter search value and returns a boolean indicating whether or not the
   * property or computed property was highlighted.
   *
   * @param  {TextPropertyEditor} editor
   *         The rule property TextPropertyEditor object.
   * @return {Boolean} true if the property or computed property was
   *         highlighted, false otherwise.
   */

  _highlightProperty(editor) {
    const isPropertyHighlighted = this._highlightRuleProperty(editor);
    const isComputedHighlighted = this._highlightComputedProperty(editor);

    // Expand the computed list if a computed property is highlighted and the
    // property rule is not highlighted
    if (
      !isPropertyHighlighted &&
      isComputedHighlighted &&
      !editor.computed.hasAttribute("user-open")
    ) {
      editor.expandForFilter();
    }

    return isPropertyHighlighted || isComputedHighlighted;
  },

  /**
   * Called when TextPropertyEditor is updated and updates the rule property
   * highlight.
   *
   * @param  {TextPropertyEditor} editor
   *         The rule property TextPropertyEditor object.
   */

  _updatePropertyHighlight(editor) {
    if (!this.searchValue || !this.searchData) {
      return;
    }

    this._clearHighlight(editor.element);

    if (this._highlightProperty(editor)) {
      this.searchField.classList.remove("devtools-style-searchbox-no-match");
    }
  },

  /**
   * Highlights the rule property that matches the filter search value
   * and returns a boolean indicating whether or not the property was
   * highlighted.
   *
   * @param  {TextPropertyEditor} editor
   *         The rule property TextPropertyEditor object.
   * @return {Boolean} true if the rule property was highlighted,
   *         false otherwise.
   */

  _highlightRuleProperty(editor) {
    // Get the actual property value displayed in the rule view
    const propertyName = editor.prop.name.toLowerCase();
    const propertyValue = editor.valueSpan.textContent.toLowerCase();

    return this._highlightMatches(
      editor.container,
      propertyName,
      propertyValue
    );
  },

  /**
   * Highlights the computed property that matches the filter search value and
   * returns a boolean indicating whether or not the computed property was
   * highlighted.
   *
   * @param  {TextPropertyEditor} editor
   *         The rule property TextPropertyEditor object.
   * @return {Boolean} true if the computed property was highlighted, false
   *         otherwise.
   */

  _highlightComputedProperty(editor) {
    let isComputedHighlighted = false;

    // Highlight search matches in the computed list of properties
    editor._populateComputed();
    for (const computed of editor.prop.computed) {
      if (computed.element) {
        // Get the actual property value displayed in the computed list
        const computedName = computed.name.toLowerCase();
        const computedValue = computed.parsedValue.toLowerCase();

        isComputedHighlighted = this._highlightMatches(
          computed.element,
          computedName,
          computedValue
        )
          ? true
          : isComputedHighlighted;
      }
    }

    return isComputedHighlighted;
  },

  /**
   * Helper function for highlightRules that carries out highlighting the given
   * element if the search terms match the property, and returns a boolean
   * indicating whether or not the search terms match.
   *
   * @param  {DOMNode} element
   *         The node to highlight if search terms match
   * @param  {String} propertyName
   *         The property name of a rule
   * @param  {String} propertyValue
   *         The property value of a rule
   * @return {Boolean} true if the given search terms match the property, false
   *         otherwise.
   */

  _highlightMatches(element, propertyName, propertyValue) {
    const {
      searchPropertyName,
      searchPropertyValue,
      searchPropertyMatch,
      strictSearchPropertyName,
      strictSearchPropertyValue,
      strictSearchAllValues,
    } = this.searchData;
    let matches = false;

    // If the inputted search value matches a property line like
    // `font-family: arial`, then check to make sure the name and value match.
    // Otherwise, just compare the inputted search string directly against the
    // name and value of the rule property.
    const hasNameAndValue =
      searchPropertyMatch && searchPropertyName && searchPropertyValue;
    const isMatch = (value, query, isStrict) => {
      return isStrict ? value === query : query && value.includes(query);
    };

    if (hasNameAndValue) {
      matches =
        isMatch(propertyName, searchPropertyName, strictSearchPropertyName) &&
        isMatch(propertyValue, searchPropertyValue, strictSearchPropertyValue);
    } else {
      matches =
        isMatch(
          propertyName,
          searchPropertyName,
          strictSearchPropertyName || strictSearchAllValues
        ) ||
        isMatch(
          propertyValue,
          searchPropertyValue,
          strictSearchPropertyValue || strictSearchAllValues
        );
    }

    if (matches) {
      element.classList.add("ruleview-highlight");
    }

    return matches;
  },

  /**
   * Clear all search filter highlights in the panel, and close the computed
   * list if toggled opened
   */

  _clearHighlight(element) {
    for (const el of element.querySelectorAll(".ruleview-highlight")) {
      el.classList.remove("ruleview-highlight");
    }

    for (const computed of element.querySelectorAll(
      ".ruleview-computedlist[filter-open]"
    )) {
      computed.parentNode._textPropertyEditor.collapseForFilter();
    }
  },

  /**
   * Called when the pseudo class panel button is clicked and toggles
   * the display of the pseudo class panel.
   */

  _onTogglePseudoClassPanel() {
    if (this.pseudoClassPanel.hidden) {
      this.showPseudoClassPanel();
    } else {
      this.hidePseudoClassPanel();
    }
  },

  showPseudoClassPanel() {
    this.hideClassPanel();

    this.pseudoClassToggle.setAttribute("aria-pressed""true");
    this.pseudoClassCheckboxes.forEach(checkbox => {
      checkbox.setAttribute("tabindex""0");
    });
    this.pseudoClassPanel.hidden = false;
  },

  hidePseudoClassPanel() {
    this.pseudoClassToggle.setAttribute("aria-pressed""false");
    this.pseudoClassCheckboxes.forEach(checkbox => {
      checkbox.setAttribute("tabindex""-1");
    });
    this.pseudoClassPanel.hidden = true;
  },

  /**
   * Called when a pseudo class checkbox is clicked and toggles
   * the pseudo class for the current selected element.
   */

  _onTogglePseudoClass(event) {
    const target = event.target;
    this.inspector.togglePseudoClass(target.value);
  },

  /**
   * Called when the class panel button is clicked and toggles the display of the class
   * panel.
   */

  _onToggleClassPanel() {
    if (this.classPanel.hidden) {
      this.showClassPanel();
    } else {
      this.hideClassPanel();
    }
  },

  showClassPanel() {
    this.hidePseudoClassPanel();

    this.classToggle.setAttribute("aria-pressed""true");
    this.classPanel.hidden = false;

    this.classListPreviewer.focusAddClassField();
  },

  hideClassPanel() {
    this.classToggle.setAttribute("aria-pressed""false");
    this.classPanel.hidden = true;
  },

  /**
   * Handle the keypress event in the rule view.
   */

  _onShortcut(name, event) {
    if (!event.target.closest("#sidebar-panel-ruleview")) {
      return;
    }

    if (name === "CmdOrCtrl+F") {
      this.searchField.focus();
      event.preventDefault();
    } else if (
      (name === "Return" || name === "Space") &&
      this.element.classList.contains("non-interactive")
    ) {
      event.preventDefault();
    } else if (
      name === "Escape" &&
      event.target === this.searchField &&
      this._onClearSearch()
    ) {
      // Handle the search box's keypress event. If the escape key is pressed,
      // clear the search box field.
      event.preventDefault();
      event.stopPropagation();
    }
  },

  async _onToggleLightColorSchemeSimulation() {
    const shouldSimulateLightScheme =
      this.colorSchemeLightSimulationButton.getAttribute("aria-pressed") !==
      "true";

    this.colorSchemeLightSimulationButton.setAttribute(
      "aria-pressed",
      shouldSimulateLightScheme
    );

    this.colorSchemeDarkSimulationButton.setAttribute("aria-pressed""false");

    await this.inspector.commands.targetConfigurationCommand.updateConfiguration(
      {
        colorSchemeSimulation: shouldSimulateLightScheme ? "light" : null,
      }
    );
    // Refresh the current element's rules in the panel.
    this.refreshPanel();
  },

  async _onToggleDarkColorSchemeSimulation() {
    const shouldSimulateDarkScheme =
      this.colorSchemeDarkSimulationButton.getAttribute("aria-pressed") !==
      "true";

    this.colorSchemeDarkSimulationButton.setAttribute(
      "aria-pressed",
      shouldSimulateDarkScheme
    );

    this.colorSchemeLightSimulationButton.setAttribute("aria-pressed""false");

    await this.inspector.commands.targetConfigurationCommand.updateConfiguration(
      {
        colorSchemeSimulation: shouldSimulateDarkScheme ? "dark" : null,
      }
    );
    // Refresh the current element's rules in the panel.
    this.refreshPanel();
  },

  async _onTogglePrintSimulation() {
    const enabled =
      this.printSimulationButton.getAttribute("aria-pressed") !== "true";
    this.printSimulationButton.setAttribute("aria-pressed", enabled);
    await this.inspector.commands.targetConfigurationCommand.updateConfiguration(
      {
        printSimulationEnabled: enabled,
      }
    );
    // Refresh the current element's rules in the panel.
    this.refreshPanel();
  },

  /**
   * Temporarily flash the given element.
   *
   * @param  {Element} element
   *         The element.
   */

  _flashElement(element) {
    flashElementOn(element, {
      backgroundClass: "theme-bg-contrast",
    });

    if (this._flashMutationTimer) {
      clearTimeout(this._removeFlashOutTimer);
      this._flashMutationTimer = null;
    }

    this._flashMutationTimer = setTimeout(() => {
      flashElementOff(element, {
        backgroundClass: "theme-bg-contrast",
      });

      // Emit "scrolled-to-property" for use by tests.
      this.emit("scrolled-to-element");
    }, PROPERTY_FLASHING_DURATION);
  },

  /**
   * Scrolls to the top of either the rule or declaration. The view will try to scroll to
   * the rule if both can fit in the viewport. If not, then scroll to the declaration.
   *
   * @param  {Element} rule
   *         The rule to scroll to.
   * @param  {Element|null} declaration
   *         Optional. The declaration to scroll to.
   * @param  {String} scrollBehavior
   *         Optional. The transition animation when scrolling. If prefers-reduced-motion
   *         system pref is set, then the scroll behavior will be overridden to "auto".
   */

  _scrollToElement(rule, declaration, scrollBehavior = "smooth") {
    let elementToScrollTo = rule;

    if (declaration) {
      const { offsetTop, offsetHeight } = declaration;
      // Get the distance between both the rule and declaration. If the distance is
      // greater than the height of the rule view, then only scroll to the declaration.
      const distance = offsetTop + offsetHeight - rule.offsetTop;

      if (this.element.parentNode.offsetHeight <= distance) {
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=92 H=97 G=94

¤ Dauer der Verarbeitung: 0.26 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

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.