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

Quelle  breadcrumbs.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 { ELLIPSIS } = require("resource://devtools/shared/l10n.js");
const EventEmitter = require("resource://devtools/shared/event-emitter.js");

loader.lazyRequireGetter(
  this,
  "KeyShortcuts",
  "resource://devtools/client/shared/key-shortcuts.js"
);

const MAX_LABEL_LENGTH = 40;

const NS_XHTML = "http://www.w3.org/1999/xhtml";
const SCROLL_REPEAT_MS = 100;

// Some margin may be required for visible element detection.
const SCROLL_MARGIN = 1;

const SHADOW_ROOT_TAGNAME = "#shadow-root";

/**
 * Component to replicate functionality of XUL arrowscrollbox
 * for breadcrumbs
 *
 * @param {Window} win The window containing the breadcrumbs
 * @parem {DOMNode} container The element in which to put the scroll box
 */

function ArrowScrollBox(win, container) {
  this.win = win;
  this.doc = win.document;
  this.container = container;
  EventEmitter.decorate(this);
  this.init();
}

ArrowScrollBox.prototype = {
  // Scroll behavior, exposed for testing
  scrollBehavior: "smooth",

  /**
   * Build the HTML, add to the DOM and start listening to
   * events
   */

  init() {
    this.constructHtml();

    this.onScroll = this.onScroll.bind(this);
    this.onStartBtnClick = this.onStartBtnClick.bind(this);
    this.onEndBtnClick = this.onEndBtnClick.bind(this);
    this.onStartBtnDblClick = this.onStartBtnDblClick.bind(this);
    this.onEndBtnDblClick = this.onEndBtnDblClick.bind(this);
    this.onUnderflow = this.onUnderflow.bind(this);
    this.onOverflow = this.onOverflow.bind(this);

    this.inner.addEventListener("scroll"this.onScroll);
    this.startBtn.addEventListener("mousedown"this.onStartBtnClick);
    this.endBtn.addEventListener("mousedown"this.onEndBtnClick);
    this.startBtn.addEventListener("dblclick"this.onStartBtnDblClick);
    this.endBtn.addEventListener("dblclick"this.onEndBtnDblClick);

    // Overflow and underflow are moz specific events
    this.inner.addEventListener("underflow"this.onUnderflow);
    this.inner.addEventListener("overflow"this.onOverflow);
  },

  /**
   * Scroll to the specified element using the current scroll behavior
   * @param {Element} element element to scroll
   * @param {String} block desired alignment of element after scrolling
   */

  scrollToElement(element, block) {
    element.scrollIntoView({ block, behavior: this.scrollBehavior });
  },

  /**
   * Call the given function once; then continuously
   * while the mouse button is held
   * @param {Function} repeatFn the function to repeat while the button is held
   */

  clickOrHold(repeatFn) {
    let timer;
    const container = this.container;

    function handleClick() {
      cancelHold();
      repeatFn();
    }

    const window = this.win;
    function cancelHold() {
      window.clearTimeout(timer);
      container.removeEventListener("mouseout", cancelHold);
      container.removeEventListener("mouseup", handleClick);
    }

    function repeated() {
      repeatFn();
      timer = window.setTimeout(repeated, SCROLL_REPEAT_MS);
    }

    container.addEventListener("mouseout", cancelHold);
    container.addEventListener("mouseup", handleClick);
    timer = window.setTimeout(repeated, SCROLL_REPEAT_MS);
  },

  /**
   * When start button is dbl clicked scroll to first element
   */

  onStartBtnDblClick() {
    const children = this.inner.childNodes;
    if (children.length < 1) {
      return;
    }

    const element = this.inner.childNodes[0];
    this.scrollToElement(element, "start");
  },

  /**
   * When end button is dbl clicked scroll to last element
   */

  onEndBtnDblClick() {
    const children = this.inner.childNodes;
    if (children.length < 1) {
      return;
    }

    const element = children[children.length - 1];
    this.scrollToElement(element, "start");
  },

  /**
   * When start arrow button is clicked scroll towards first element
   */

  onStartBtnClick() {
    const scrollToStart = () => {
      const element = this.getFirstInvisibleElement();
      if (!element) {
        return;
      }

      this.scrollToElement(element, "start");
    };

    this.clickOrHold(scrollToStart);
  },

  /**
   * When end arrow button is clicked scroll towards last element
   */

  onEndBtnClick() {
    const scrollToEnd = () => {
      const element = this.getLastInvisibleElement();
      if (!element) {
        return;
      }

      this.scrollToElement(element, "end");
    };

    this.clickOrHold(scrollToEnd);
  },

  /**
   * Event handler for scrolling, update the
   * enabled/disabled status of the arrow buttons
   */

  onScroll() {
    const first = this.getFirstInvisibleElement();
    if (!first) {
      this.startBtn.setAttribute("disabled""true");
    } else {
      this.startBtn.removeAttribute("disabled");
    }

    const last = this.getLastInvisibleElement();
    if (!last) {
      this.endBtn.setAttribute("disabled""true");
    } else {
      this.endBtn.removeAttribute("disabled");
    }
  },

  /**
   * On underflow, make the arrow buttons invisible
   */

  onUnderflow() {
    this.startBtn.style.visibility = "collapse";
    this.endBtn.style.visibility = "collapse";
    this.emit("underflow");
  },

  /**
   * On overflow, show the arrow buttons
   */

  onOverflow() {
    this.startBtn.style.visibility = "visible";
    this.endBtn.style.visibility = "visible";
    this.emit("overflow");
  },

  /**
   * Check whether the element is to the left of its container but does
   * not also span the entire container.
   * @param {Number} left the left scroll point of the container
   * @param {Number} right the right edge of the container
   * @param {Number} elementLeft the left edge of the element
   * @param {Number} elementRight the right edge of the element
   */

  elementLeftOfContainer(left, right, elementLeft, elementRight) {
    return (
      elementLeft < left - SCROLL_MARGIN && elementRight < right - SCROLL_MARGIN
    );
  },

  /**
   * Check whether the element is to the right of its container but does
   * not also span the entire container.
   * @param {Number} left the left scroll point of the container
   * @param {Number} right the right edge of the container
   * @param {Number} elementLeft the left edge of the element
   * @param {Number} elementRight the right edge of the element
   */

  elementRightOfContainer(left, right, elementLeft, elementRight) {
    return (
      elementLeft > left + SCROLL_MARGIN && elementRight > right + SCROLL_MARGIN
    );
  },

  /**
   * Get the first (i.e. furthest left for LTR)
   * non or partly visible element in the scroll box
   */

  getFirstInvisibleElement() {
    const elementsList = Array.from(this.inner.childNodes).reverse();

    const predicate = this.elementLeftOfContainer;
    return this.findFirstWithBounds(elementsList, predicate);
  },

  /**
   * Get the last (i.e. furthest right for LTR)
   * non or partly visible element in the scroll box
   */

  getLastInvisibleElement() {
    const predicate = this.elementRightOfContainer;
    return this.findFirstWithBounds(this.inner.childNodes, predicate);
  },

  /**
   * Find the first element that matches the given predicate, called with bounds
   * information
   * @param {Array} elements an ordered list of elements
   * @param {Function} predicate a function to be called with bounds
   * information
   */

  findFirstWithBounds(elements, predicate) {
    const left = this.inner.scrollLeft;
    const right = left + this.inner.clientWidth;
    for (const element of elements) {
      const elementLeft = element.offsetLeft - element.parentElement.offsetLeft;
      const elementRight = elementLeft + element.offsetWidth;

      // Check that the starting edge of the element is out of the visible area
      // and that the ending edge does not span the whole container
      if (predicate(left, right, elementLeft, elementRight)) {
        return element;
      }
    }

    return null;
  },

  /**
   * Build the HTML for the scroll box and insert it into the DOM
   */

  constructHtml() {
    this.startBtn = this.createElement(
      "div",
      "scrollbutton-up",
      this.container
    );
    this.createElement("div""toolbarbutton-icon"this.startBtn);

    this.createElement(
      "div",
      "arrowscrollbox-overflow-start-indicator",
      this.container
    );
    this.inner = this.createElement(
      "div",
      "html-arrowscrollbox-inner",
      this.container
    );
    this.createElement(
      "div",
      "arrowscrollbox-overflow-end-indicator",
      this.container
    );

    this.endBtn = this.createElement(
      "div",
      "scrollbutton-down",
      this.container
    );
    this.createElement("div""toolbarbutton-icon"this.endBtn);
  },

  /**
   * Create an XHTML element with the given class name, and append it to the
   * parent.
   * @param {String} tagName name of the tag to create
   * @param {String} className class of the element
   * @param {DOMNode} parent the parent node to which it should be appended
   * @return {DOMNode} The new element
   */

  createElement(tagName, className, parent) {
    const el = this.doc.createElementNS(NS_XHTML, tagName);
    el.className = className;
    if (parent) {
      parent.appendChild(el);
    }

    return el;
  },

  /**
   * Remove event handlers and clean up
   */

  destroy() {
    this.inner.removeEventListener("scroll"this.onScroll);
    this.startBtn.removeEventListener("mousedown"this.onStartBtnClick);
    this.endBtn.removeEventListener("mousedown"this.onEndBtnClick);
    this.startBtn.removeEventListener("dblclick"this.onStartBtnDblClick);
    this.endBtn.removeEventListener("dblclick"this.onRightBtnDblClick);

    // Overflow and underflow are moz specific events
    this.inner.removeEventListener("underflow"this.onUnderflow);
    this.inner.removeEventListener("overflow"this.onOverflow);
  },
};

/**
 * Display the ancestors of the current node and its children.
 * Only one "branch" of children are displayed (only one line).
 *
 * Mechanism:
 * - If no nodes displayed yet:
 *   then display the ancestor of the selected node and the selected node;
 *   else select the node;
 * - If the selected node is the last node displayed, append its first (if any).
 *
 * @param {InspectorPanel} inspector The inspector hosting this widget.
 */

function HTMLBreadcrumbs(inspector) {
  this.inspector = inspector;
  this.selection = this.inspector.selection;
  this.win = this.inspector.panelWin;
  this.doc = this.inspector.panelDoc;
  this._init();
}

exports.HTMLBreadcrumbs = HTMLBreadcrumbs;

HTMLBreadcrumbs.prototype = {
  get walker() {
    return this.inspector.walker;
  },

  _init() {
    this.outer = this.doc.getElementById("inspector-breadcrumbs");
    this.arrowScrollBox = new ArrowScrollBox(this.win, this.outer);

    this.container = this.arrowScrollBox.inner;
    this.scroll = this.scroll.bind(this);
    this.arrowScrollBox.on("overflow"this.scroll);

    this.outer.addEventListener("click"thistrue);
    this.outer.addEventListener("mouseover"thistrue);
    this.outer.addEventListener("mouseout"thistrue);
    this.outer.addEventListener("focus"thistrue);

    this.handleShortcut = this.handleShortcut.bind(this);

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

    // We will save a list of already displayed nodes in this array.
    this.nodeHierarchy = [];

    // Last selected node in nodeHierarchy.
    this.currentIndex = -1;

    // Used to build a unique breadcrumb button Id.
    this.breadcrumbsWidgetItemId = 0;

    this.update = this.update.bind(this);
    this.updateWithMutations = this.updateWithMutations.bind(this);
    this.updateSelectors = this.updateSelectors.bind(this);
    this.selection.on("new-node-front"this.update);
    this.selection.on("pseudoclass"this.updateSelectors);
    this.selection.on("attribute-changed"this.updateSelectors);
    this.inspector.on("markupmutation"this.updateWithMutations);
    this.update();
  },

  initKeyShortcuts() {
    this.shortcuts = new KeyShortcuts({ window: this.win, target: this.outer });
    this.shortcuts.on("Right"this.handleShortcut);
    this.shortcuts.on("Left"this.handleShortcut);
  },

  /**
   * Build a string that represents the node: tagName#id.class1.class2.
   * @param {NodeFront} node The node to pretty-print
   * @return {String}
   */

  prettyPrintNodeAsText(node) {
    let text = node.isShadowRoot ? SHADOW_ROOT_TAGNAME : node.displayName;
    if (node.isMarkerPseudoElement) {
      text = "::marker";
    } else if (node.isBeforePseudoElement) {
      text = "::before";
    } else if (node.isAfterPseudoElement) {
      text = "::after";
    }

    if (node.id) {
      text += "#" + node.id;
    }

    if (node.className) {
      const classList = node.className.split(/\s+/);
      for (let i = 0; i < classList.length; i++) {
        text += "." + classList[i];
      }
    }

    for (const pseudo of node.pseudoClassLocks) {
      text += pseudo;
    }

    return text;
  },

  /**
   * Build <span>s that represent the node:
   *   <span class="breadcrumbs-widget-item-tag">tagName</span>
   *   <span class="breadcrumbs-widget-item-id">#id</span>
   *   <span class="breadcrumbs-widget-item-classes">.class1.class2</span>
   * @param {NodeFront} node The node to pretty-print
   * @returns {DocumentFragment}
   */

  prettyPrintNodeAsXHTML(node) {
    const tagLabel = this.doc.createElementNS(NS_XHTML, "span");
    tagLabel.className = "breadcrumbs-widget-item-tag";

    const idLabel = this.doc.createElementNS(NS_XHTML, "span");
    idLabel.className = "breadcrumbs-widget-item-id";

    const classesLabel = this.doc.createElementNS(NS_XHTML, "span");
    classesLabel.className = "breadcrumbs-widget-item-classes";

    const pseudosLabel = this.doc.createElementNS(NS_XHTML, "span");
    pseudosLabel.className = "breadcrumbs-widget-item-pseudo-classes";

    let tagText = node.isShadowRoot ? SHADOW_ROOT_TAGNAME : node.displayName;
    if (node.isMarkerPseudoElement) {
      tagText = "::marker";
    } else if (node.isBeforePseudoElement) {
      tagText = "::before";
    } else if (node.isAfterPseudoElement) {
      tagText = "::after";
    }
    let idText = node.id ? "#" + node.id : "";
    let classesText = "";

    if (node.className) {
      const classList = node.className.split(/\s+/);
      for (let i = 0; i < classList.length; i++) {
        classesText += "." + classList[i];
      }
    }

    // Figure out which element (if any) needs ellipsing.
    // Substring for that element, then clear out any extras
    // (except for pseudo elements).
    const maxTagLength = MAX_LABEL_LENGTH;
    const maxIdLength = MAX_LABEL_LENGTH - tagText.length;
    const maxClassLength = MAX_LABEL_LENGTH - tagText.length - idText.length;

    if (tagText.length > maxTagLength) {
      tagText = tagText.substr(0, maxTagLength) + ELLIPSIS;
      idText = classesText = "";
    } else if (idText.length > maxIdLength) {
      idText = idText.substr(0, maxIdLength) + ELLIPSIS;
      classesText = "";
    } else if (classesText.length > maxClassLength) {
      classesText = classesText.substr(0, maxClassLength) + ELLIPSIS;
    }

    tagLabel.textContent = tagText;
    idLabel.textContent = idText;
    classesLabel.textContent = classesText;
    pseudosLabel.textContent = node.pseudoClassLocks.join("");

    const fragment = this.doc.createDocumentFragment();
    fragment.appendChild(tagLabel);
    fragment.appendChild(idLabel);
    fragment.appendChild(classesLabel);
    fragment.appendChild(pseudosLabel);

    return fragment;
  },

  /**
   * Generic event handler.
   * @param {DOMEvent} event.
   */

  handleEvent(event) {
    if (event.type == "click" && event.button == 0) {
      this.handleClick(event);
    } else if (event.type == "mouseover") {
      this.handleMouseOver(event);
    } else if (event.type == "mouseout") {
      this.handleMouseOut(event);
    } else if (event.type == "focus") {
      this.handleFocus(event);
    }
  },

  /**
   * Focus event handler. When breadcrumbs container gets focus,
   * aria-activedescendant needs to be updated to currently selected
   * breadcrumb. Ensures that the focus stays on the container at all times.
   * @param {DOMEvent} event.
   */

  handleFocus(event) {
    event.stopPropagation();

    const node = this.nodeHierarchy[this.currentIndex];
    if (node) {
      this.outer.setAttribute("aria-activedescendant", node.button.id);
    } else {
      this.outer.removeAttribute("aria-activedescendant");
    }

    this.outer.focus();
  },

  /**
   * On click navigate to the correct node.
   * @param {DOMEvent} event.
   */

  handleClick(event) {
    const target = event.originalTarget;
    if (target.tagName == "button") {
      target.onBreadcrumbsClick();
    }
  },

  /**
   * On mouse over, highlight the corresponding content DOM Node.
   * @param {DOMEvent} event.
   */

  handleMouseOver(event) {
    const target = event.originalTarget;
    if (target.tagName == "button") {
      target.onBreadcrumbsHover();
    }
  },

  /**
   * On mouse out, make sure to unhighlight.
   */

  handleMouseOut() {
    this.inspector.highlighters.hideHighlighterType(
      this.inspector.highlighters.TYPES.BOXMODEL
    );
  },

  /**
   * Handle a keyboard shortcut supported by the breadcrumbs widget.
   *
   * @param {String} name
   *        Name of the keyboard shortcut received.
   * @param {DOMEvent} event
   *        Original event that triggered the shortcut.
   */

  handleShortcut(event) {
    if (!this.selection.isElementNode()) {
      return;
    }

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

    this.keyPromise = (this.keyPromise || Promise.resolve(null)).then(() => {
      let currentnode;

      const isLeft = event.code === "ArrowLeft";
      const isRight = event.code === "ArrowRight";

      if (isLeft && this.currentIndex != 0) {
        currentnode = this.nodeHierarchy[this.currentIndex - 1];
      } else if (isRight && this.currentIndex < this.nodeHierarchy.length - 1) {
        currentnode = this.nodeHierarchy[this.currentIndex + 1];
      } else {
        return null;
      }

      this.outer.setAttribute("aria-activedescendant", currentnode.button.id);
      return this.selection.setNodeFront(currentnode.node, {
        reason: "breadcrumbs",
      });
    });
  },

  /**
   * Remove nodes and clean up.
   */

  destroy() {
    this.selection.off("new-node-front"this.update);
    this.selection.off("pseudoclass"this.updateSelectors);
    this.selection.off("attribute-changed"this.updateSelectors);
    this.inspector.off("markupmutation"this.updateWithMutations);

    this.container.removeEventListener("click"thistrue);
    this.container.removeEventListener("mouseover"thistrue);
    this.container.removeEventListener("mouseout"thistrue);
    this.container.removeEventListener("focus"thistrue);

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

    this.empty();

    this.arrowScrollBox.off("overflow"this.scroll);
    this.arrowScrollBox.destroy();
    this.arrowScrollBox = null;
    this.outer = null;
    this.container = null;
    this.nodeHierarchy = null;

    this.isDestroyed = true;
  },

  /**
   * Empty the breadcrumbs container.
   */

  empty() {
    while (this.container.hasChildNodes()) {
      this.container.firstChild.remove();
    }
  },

  /**
   * Set which button represent the selected node.
   * @param {Number} index Index of the displayed-button to select.
   */

  setCursor(index) {
    // Unselect the previously selected button
    if (
      this.currentIndex > -1 &&
      this.currentIndex < this.nodeHierarchy.length
    ) {
      this.nodeHierarchy[this.currentIndex].button.setAttribute(
        "aria-pressed",
        "false"
      );
    }
    if (index > -1) {
      this.nodeHierarchy[index].button.setAttribute("aria-pressed""true");
    } else {
      // Unset active active descendant when all buttons are unselected.
      this.outer.removeAttribute("aria-activedescendant");
    }
    this.currentIndex = index;
  },

  /**
   * Get the index of the node in the cache.
   * @param {NodeFront} node.
   * @returns {Number} The index for this node or -1 if not found.
   */

  indexOf(node) {
    for (let i = this.nodeHierarchy.length - 1; i >= 0; i--) {
      if (this.nodeHierarchy[i].node === node) {
        return i;
      }
    }
    return -1;
  },

  /**
   * Remove all the buttons and their references in the cache after a given
   * index.
   * @param {Number} index.
   */

  cutAfter(index) {
    while (this.nodeHierarchy.length > index + 1) {
      const toRemove = this.nodeHierarchy.pop();
      this.container.removeChild(toRemove.button);
    }
  },

  /**
   * Build a button representing the node.
   * @param {NodeFront} node The node from the page.
   * @return {DOMNode} The <button> for this node.
   */

  buildButton(node) {
    const button = this.doc.createElementNS(NS_XHTML, "button");
    button.appendChild(this.prettyPrintNodeAsXHTML(node));
    button.className = "breadcrumbs-widget-item";
    button.id = "breadcrumbs-widget-item-" + this.breadcrumbsWidgetItemId++;

    button.setAttribute("tabindex""-1");
    button.setAttribute("title"this.prettyPrintNodeAsText(node));

    button.onclick = () => {
      button.focus();
    };

    button.onBreadcrumbsClick = () => {
      this.selection.setNodeFront(node, { reason: "breadcrumbs" });
    };

    button.onBreadcrumbsHover = () => {
      this.inspector.highlighters.showHighlighterTypeForNode(
        this.inspector.highlighters.TYPES.BOXMODEL,
        node
      );
    };

    return button;
  },

  /**
   * Connecting the end of the breadcrumbs to a node.
   * @param {NodeFront} node The node to reach.
   */

  expand(node) {
    const fragment = this.doc.createDocumentFragment();
    let lastButtonInserted = null;
    const originalLength = this.nodeHierarchy.length;
    let stopNode = null;
    if (originalLength > 0) {
      stopNode = this.nodeHierarchy[originalLength - 1].node;
    }
    while (node && node != stopNode) {
      if (node.tagName || node.isShadowRoot) {
        const button = this.buildButton(node);
        fragment.insertBefore(button, lastButtonInserted);
        lastButtonInserted = button;
        this.nodeHierarchy.splice(originalLength, 0, {
          node,
          button,
          currentPrettyPrintText: this.prettyPrintNodeAsText(node),
        });
      }
      node = node.parentOrHost();
    }
    this.container.appendChild(fragment, this.container.firstChild);
  },

  /**
   * Find the "youngest" ancestor of a node which is already in the breadcrumbs.
   * @param {NodeFront} node.
   * @return {Number} Index of the ancestor in the cache, or -1 if not found.
   */

  getCommonAncestor(node) {
    while (node) {
      const idx = this.indexOf(node);
      if (idx > -1) {
        return idx;
      }
      node = node.parentNode();
    }
    return -1;
  },

  /**
   * Ensure the selected node is visible.
   */

  scroll() {
    // FIXME bug 684352: make sure its immediate neighbors are visible too.
    if (!this.isDestroyed) {
      const element = this.nodeHierarchy[this.currentIndex].button;
      this.arrowScrollBox.scrollToElement(element, "end");
    }
  },

  /**
   * Update all button outputs.
   */

  updateSelectors() {
    if (this.isDestroyed) {
      return;
    }

    for (let i = this.nodeHierarchy.length - 1; i >= 0; i--) {
      const { node, button, currentPrettyPrintText } = this.nodeHierarchy[i];

      // If the output of the node doesn't change, skip the update.
      const textOutput = this.prettyPrintNodeAsText(node);
      if (currentPrettyPrintText === textOutput) {
        continue;
      }

      // Otherwise, update the whole markup for the button.
      while (button.hasChildNodes()) {
        button.firstChild.remove();
      }
      button.appendChild(this.prettyPrintNodeAsXHTML(node));
      button.setAttribute("title", textOutput);

      this.nodeHierarchy[i].currentPrettyPrintText = textOutput;
    }
  },

  /**
   * Given a list of mutation changes (passed by the markupmutation event),
   * decide whether or not they are "interesting" to the current state of the
   * breadcrumbs widget, i.e. at least one of them should cause part of the
   * widget to be updated.
   * @param {Array} mutations The mutations array.
   * @return {Boolean}
   */

  _hasInterestingMutations(mutations) {
    if (!mutations || !mutations.length) {
      return false;
    }

    for (const mutation of mutations) {
      if (this._isInterestingMutation(mutation)) {
        return true;
      }
    }

    return false;
  },

  /**
   * Check if the provided mutation (from a markupmutation event) is relevant
   * for the current breadcrumbs.
   *
   * @param {Object} mutation The mutation to check.
   * @return {Boolean} true if the mutation is relevant, false otherwise.
   */

  _isInterestingMutation(mutation) {
    const { type, added, removed, target, attributeName } = mutation;
    if (type === "childList") {
      // Only interested in childList mutations if the added or removed
      // nodes are currently displayed.
      return (
        added.some(node => this.indexOf(node) > -1) ||
        removed.some(node => this.indexOf(node) > -1)
      );
    } else if (type === "attributes" && this.indexOf(target) > -1) {
      // Only interested in attributes mutations if the target is
      // currently displayed, and the attribute is either id or class.
      return attributeName === "class" || attributeName === "id";
    }
    return false;
  },

  /**
   * Update the breadcrumbs display when a new node is selected and there are
   * mutations.
   * @param {Array} mutations An array of mutations in case this was called as
   * the "markupmutation" event listener.
   */

  updateWithMutations(mutations) {
    return this.update("markupmutation", mutations);
  },

  /**
   * Update the breadcrumbs display when a new node is selected.
   * @param {String} reason The reason for the update, if any.
   * @param {Array} mutations An array of mutations in case this was called as
   * the "markupmutation" event listener.
   */

  update(reason, mutations) {
    if (this.isDestroyed) {
      return;
    }

    const hasInterestingMutations = this._hasInterestingMutations(mutations);
    if (reason === "markupmutation" && !hasInterestingMutations) {
      return;
    }

    if (!this.selection.isConnected()) {
      // remove all the crumbs
      this.cutAfter(-1);
      return;
    }

    // If this was an interesting deletion; then trim the breadcrumb trail
    let trimmed = false;
    if (reason === "markupmutation") {
      for (const { type, removed } of mutations) {
        if (type !== "childList") {
          continue;
        }

        for (const node of removed) {
          const removedIndex = this.indexOf(node);
          if (removedIndex > -1) {
            this.cutAfter(removedIndex - 1);
            trimmed = true;
          }
        }
      }
    }

    if (!this.selection.isElementNode() && !this.selection.isShadowRootNode()) {
      // no selection
      this.setCursor(-1);
      if (trimmed) {
        // Since something changed, notify the interested parties.
        this.inspector.emit("breadcrumbs-updated"this.selection.nodeFront);
      }
      return;
    }

    let idx = this.indexOf(this.selection.nodeFront);

    // Is the node already displayed in the breadcrumbs?
    // (and there are no mutations that need re-display of the crumbs)
    if (idx > -1 && !hasInterestingMutations) {
      // Yes. We select it.
      this.setCursor(idx);
    } else {
      // No. Is the breadcrumbs display empty?
      if (this.nodeHierarchy.length) {
        // No. We drop all the element that are not direct ancestors
        // of the selection
        const parent = this.selection.nodeFront.parentNode();
        const ancestorIdx = this.getCommonAncestor(parent);
        this.cutAfter(ancestorIdx);
      }
      // we append the missing button between the end of the breadcrumbs display
      // and the current node.
      this.expand(this.selection.nodeFront);

      // we select the current node button
      idx = this.indexOf(this.selection.nodeFront);
      this.setCursor(idx);
    }

    const doneUpdating = this.inspector.updating("breadcrumbs");

    this.updateSelectors();

    // Make sure the selected node and its neighbours are visible.
    setTimeout(() => {
      try {
        this.scroll();
        this.inspector.emit("breadcrumbs-updated"this.selection.nodeFront);
        doneUpdating();
      } catch (e) {
        // Only log this as an error if we haven't been destroyed in the meantime.
        if (!this.isDestroyed) {
          console.error(e);
        }
      }
    }, 0);
  },
};

Messung V0.5
C=94 H=99 G=96

¤ Dauer der Verarbeitung: 0.12 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.