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

Quelle  geometry-editor.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 {
  AutoRefreshHighlighter,
} = require("resource://devtools/server/actors/highlighters/auto-refresh.js");
const {
  CanvasFrameAnonymousContentHelper,
  getComputedStyle,
} = require("resource://devtools/server/actors/highlighters/utils/markup.js");
const {
  setIgnoreLayoutChanges,
  getAdjustedQuads,
  getCurrentZoom,
} = require("resource://devtools/shared/layout/utils.js");
const {
  getMatchingCSSRules,
} = require("resource://devtools/shared/inspector/css-logic.js");

const GEOMETRY_LABEL_SIZE = 6;

// List of all DOM Events subscribed directly to the document from the
// Geometry Editor highlighter
const DOM_EVENTS = ["mousemove""mouseup""pagehide"];

const _dragging = Symbol("geometry/dragging");

/**
 * Element geometry properties helper that gives names of position and size
 * properties.
 */

var GeoProp = {
  SIDES: ["top""right""bottom""left"],
  SIZES: ["width""height"],

  allProps() {
    return [...this.SIDES, ...this.SIZES];
  },

  isSide(name) {
    return this.SIDES.includes(name);
  },

  isSize(name) {
    return this.SIZES.includes(name);
  },

  containsSide(names) {
    return names.some(name => this.SIDES.includes(name));
  },

  containsSize(names) {
    return names.some(name => this.SIZES.includes(name));
  },

  isHorizontal(name) {
    return name === "left" || name === "right" || name === "width";
  },

  isInverted(name) {
    return name === "right" || name === "bottom";
  },

  mainAxisStart(name) {
    return this.isHorizontal(name) ? "left" : "top";
  },

  crossAxisStart(name) {
    return this.isHorizontal(name) ? "top" : "left";
  },

  mainAxisSize(name) {
    return this.isHorizontal(name) ? "width" : "height";
  },

  crossAxisSize(name) {
    return this.isHorizontal(name) ? "height" : "width";
  },

  axis(name) {
    return this.isHorizontal(name) ? "x" : "y";
  },

  crossAxis(name) {
    return this.isHorizontal(name) ? "y" : "x";
  },
};

/**
 * Get the provided node's offsetParent dimensions.
 * Returns an object with the {parent, dimension} properties.
 * Note that the returned parent will be null if the offsetParent is the
 * default, non-positioned, body or html node.
 *
 * node.offsetParent returns the nearest positioned ancestor but if it is
 * non-positioned itself, we just return null to let consumers know the node is
 * actually positioned relative to the viewport.
 *
 * @return {Object}
 */

function getOffsetParent(node) {
  const win = node.ownerGlobal;

  let offsetParent = node.offsetParent;
  if (offsetParent && getComputedStyle(offsetParent).position === "static") {
    offsetParent = null;
  }

  let width, height;
  if (!offsetParent) {
    height = win.innerHeight;
    width = win.innerWidth;
  } else {
    height = offsetParent.offsetHeight;
    width = offsetParent.offsetWidth;
  }

  return {
    element: offsetParent,
    dimension: { width, height },
  };
}

/**
 * Get the list of geometry properties that are actually set on the provided
 * node.
 *
 * @param {Node} node The node to analyze.
 * @return {Map} A map indexed by property name and where the value is an
 * object having the cssRule property.
 */

function getDefinedGeometryProperties(node) {
  const props = new Map();
  if (!node) {
    return props;
  }

  // Get the list of css rules applying to the current node.
  const cssRules = getMatchingCSSRules(node);
  for (let i = 0; i < cssRules.length; i++) {
    const rule = cssRules[i];
    for (const name of GeoProp.allProps()) {
      const value = rule.style.getPropertyValue(name);
      if (value && value !== "auto") {
        // getMatchingCSSRules returns rules ordered from least to most specific
        // so just override any previous properties we have set.
        props.set(name, {
          cssRule: rule,
        });
      }
    }
  }

  // Go through the inline styles last, only if the node supports inline style
  // (e.g. pseudo elements don't have a style property)
  if (node.style) {
    for (const name of GeoProp.allProps()) {
      const value = node.style.getPropertyValue(name);
      if (value && value !== "auto") {
        props.set(name, {
          // There's no cssRule to store here, so store the node instead since
          // node.style exists.
          cssRule: node,
        });
      }
    }
  }

  // Post-process the list for invalid properties. This is done after the fact
  // because of cases like relative positioning with both top and bottom where
  // only top will actually be used, but both exists in css rules and computed
  // styles.
  const { position } = getComputedStyle(node);
  for (const [name] of props) {
    // Top/left/bottom/right on static positioned elements have no effect.
    if (position === "static" && GeoProp.SIDES.includes(name)) {
      props.delete(name);
    }

    // Bottom/right on relative positioned elements are only used if top/left
    // are not defined.
    const hasRightAndLeft = name === "right" && props.has("left");
    const hasBottomAndTop = name === "bottom" && props.has("top");
    if (position === "relative" && (hasRightAndLeft || hasBottomAndTop)) {
      props.delete(name);
    }
  }

  return props;
}
exports.getDefinedGeometryProperties = getDefinedGeometryProperties;

/**
 * The GeometryEditor highlights an elements's top, left, bottom, right, width
 * and height dimensions, when they are set.
 *
 * To determine if an element has a set size and position, the highlighter lists
 * the CSS rules that apply to the element and checks for the top, left, bottom,
 * right, width and height properties.
 * The highlighter won't be shown if the element doesn't have any of these
 * properties set, but will be shown when at least 1 property is defined.
 *
 * The highlighter displays lines and labels for each of the defined properties
 * in and around the element (relative to the offset parent when one exists).
 * The highlighter also highlights the element itself and its offset parent if
 * there is one.
 *
 * Note that the class name contains the word Editor because the aim is for the
 * handles to be draggable in content to make the geometry editable.
 */

class GeometryEditorHighlighter extends AutoRefreshHighlighter {
  constructor(highlighterEnv) {
    super(highlighterEnv);

    this.ID_CLASS_PREFIX = "geometry-editor-";

    // The list of element geometry properties that can be set.
    this.definedProperties = new Map();

    this.markup = new CanvasFrameAnonymousContentHelper(
      highlighterEnv,
      this._buildMarkup.bind(this)
    );
    this.isReady = this.initialize();

    const { pageListenerTarget } = this.highlighterEnv;

    // Register the geometry editor instance to all events we're interested in.
    DOM_EVENTS.forEach(type => pageListenerTarget.addEventListener(type, this));

    this.onWillNavigate = this.onWillNavigate.bind(this);

    this.highlighterEnv.on("will-navigate"this.onWillNavigate);
  }

  async initialize() {
    await this.markup.initialize();
    // Register the mousedown event for each Geometry Editor's handler.
    // Those events are automatically removed when the markup is destroyed.
    const onMouseDown = this.handleEvent.bind(this);

    for (const side of GeoProp.SIDES) {
      this.getElement("handler-" + side).addEventListener(
        "mousedown",
        onMouseDown
      );
    }
  }

  _buildMarkup() {
    const container = this.markup.createNode({
      attributes: { class"highlighter-container" },
    });

    const root = this.markup.createNode({
      parent: container,
      attributes: {
        id: "root",
        class"root",
        hidden: "true",
      },
      prefix: this.ID_CLASS_PREFIX,
    });

    const svg = this.markup.createSVGNode({
      nodeType: "svg",
      parent: root,
      attributes: {
        id: "elements",
        width: "100%",
        height: "100%",
      },
      prefix: this.ID_CLASS_PREFIX,
    });

    // Offset parent node highlighter.
    this.markup.createSVGNode({
      nodeType: "polygon",
      parent: svg,
      attributes: {
        class"offset-parent",
        id: "offset-parent",
        hidden: "true",
      },
      prefix: this.ID_CLASS_PREFIX,
    });

    // Current node highlighter (margin box).
    this.markup.createSVGNode({
      nodeType: "polygon",
      parent: svg,
      attributes: {
        class"current-node",
        id: "current-node",
        hidden: "true",
      },
      prefix: this.ID_CLASS_PREFIX,
    });

    // Build the 4 side arrows, handlers and labels.
    for (const name of GeoProp.SIDES) {
      this.markup.createSVGNode({
        nodeType: "line",
        parent: svg,
        attributes: {
          class"arrow " + name,
          id: "arrow-" + name,
          hidden: "true",
        },
        prefix: this.ID_CLASS_PREFIX,
      });

      this.markup.createSVGNode({
        nodeType: "circle",
        parent: svg,
        attributes: {
          class"handler-" + name,
          id: "handler-" + name,
          r: "4",
          "data-side": name,
          hidden: "true",
        },
        prefix: this.ID_CLASS_PREFIX,
      });

      // Labels are positioned by using a translated <g>. This group contains
      // a path and text that are themselves positioned using another translated
      // <g>. This is so that the label arrow points at the 0,0 coordinates of
      // parent <g>.
      const labelG = this.markup.createSVGNode({
        nodeType: "g",
        parent: svg,
        attributes: {
          id: "label-" + name,
          hidden: "true",
        },
        prefix: this.ID_CLASS_PREFIX,
      });

      const subG = this.markup.createSVGNode({
        nodeType: "g",
        parent: labelG,
        attributes: {
          transform: GeoProp.isHorizontal(name)
            ? "translate(-30 -30)"
            : "translate(5 -10)",
        },
      });

      this.markup.createSVGNode({
        nodeType: "path",
        parent: subG,
        attributes: {
          class"label-bubble",
          d: GeoProp.isHorizontal(name)
            ? "M0 0 L60 0 L60 20 L35 20 L30 25 L25 20 L0 20z"
            : "M5 0 L65 0 L65 20 L5 20 L5 15 L0 10 L5 5z",
        },
        prefix: this.ID_CLASS_PREFIX,
      });

      this.markup.createSVGNode({
        nodeType: "text",
        parent: subG,
        attributes: {
          class"label-text",
          id: "label-text-" + name,
          x: GeoProp.isHorizontal(name) ? "30" : "35",
          y: "10",
        },
        prefix: this.ID_CLASS_PREFIX,
      });
    }

    return container;
  }

  destroy() {
    // Avoiding exceptions if `destroy` is called multiple times; and / or the
    // highlighter environment was already destroyed.
    if (!this.highlighterEnv) {
      return;
    }

    const { pageListenerTarget } = this.highlighterEnv;

    if (pageListenerTarget) {
      DOM_EVENTS.forEach(type =>
        pageListenerTarget.removeEventListener(type, this)
      );
    }

    AutoRefreshHighlighter.prototype.destroy.call(this);

    this.markup.destroy();
    this.definedProperties.clear();
    this.definedProperties = null;
    this.offsetParent = null;
  }

  handleEvent(event, id) {
    // No event handling if the highlighter is hidden
    if (this.getElement("root").hasAttribute("hidden")) {
      return;
    }

    const { target, type, pageX, pageY } = event;

    switch (type) {
      case "pagehide":
        // If a page hide event is triggered for current window's highlighter, hide the
        // highlighter.
        if (target.defaultView === this.win) {
          this.destroy();
        }

        break;
      case "mousedown":
        // The mousedown event is intended only for the handler
        if (!id) {
          return;
        }

        const handlerSide = this.markup
          .getElement(id)
          .getAttribute("data-side");

        if (handlerSide) {
          const side = handlerSide;
          const sideProp = this.definedProperties.get(side);

          if (!sideProp) {
            return;
          }

          let value = sideProp.cssRule.style.getPropertyValue(side);
          const computedValue = this.computedStyle.getPropertyValue(side);

          const [unit] = value.match(/[^\d]+$/) || [""];

          value = parseFloat(value);

          const ratio = value / parseFloat(computedValue) || 1;
          const dir = GeoProp.isInverted(side) ? -1 : 1;

          // Store all the initial values needed for drag & drop
          this[_dragging] = {
            side,
            value,
            unit,
            x: pageX,
            y: pageY,
            inc: ratio * dir,
          };

          this.getElement("handler-" + side).classList.add("dragging");
        }

        this.getElement("root").setAttribute("dragging""true");
        break;
      case "mouseup":
        // If we're dragging, drop it.
        if (this[_dragging]) {
          const { side } = this[_dragging];
          this.getElement("root").removeAttribute("dragging");
          this.getElement("handler-" + side).classList.remove("dragging");
          this[_dragging] = null;
        }
        break;
      case "mousemove":
        if (!this[_dragging]) {
          return;
        }

        const { side, x, y, value, unit, inc } = this[_dragging];
        const sideProps = this.definedProperties.get(side);

        if (!sideProps) {
          return;
        }

        const delta =
          (GeoProp.isHorizontal(side) ? pageX - x : pageY - y) * inc;

        // The inline style has usually the priority over any other CSS rule
        // set in stylesheets. However, if a rule has `!important` keyword,
        // it will override the inline style too. To ensure Geometry Editor
        // will always update the element, we have to add `!important` as
        // well.
        this.currentNode.style.setProperty(
          side,
          value + delta + unit,
          "important"
        );

        break;
    }
  }

  getElement(id) {
    return this.markup.getElement(this.ID_CLASS_PREFIX + id);
  }

  _show() {
    this.computedStyle = getComputedStyle(this.currentNode);
    const pos = this.computedStyle.position;
    // XXX: sticky positioning is ignored for now. To be implemented next.
    if (pos === "sticky") {
      this.hide();
      return false;
    }

    const hasUpdated = this._update();
    if (!hasUpdated) {
      this.hide();
      return false;
    }

    this.getElement("root").removeAttribute("hidden");

    return true;
  }

  _update() {
    // At each update, the position or/and size may have changed, so get the
    // list of defined properties, and re-position the arrows and highlighters.
    this.definedProperties = getDefinedGeometryProperties(this.currentNode);
    // We need the zoom factor to fix the original position of the node
    // as well as the arrows.
    this.zoomFactor = getCurrentZoom(this.currentNode);

    if (!this.definedProperties.size) {
      console.warn("The element does not have editable geometry properties");
      return false;
    }

    setIgnoreLayoutChanges(true);

    // Update the highlighters and arrows.
    this.updateOffsetParent();
    this.updateCurrentNode();
    this.updateArrows();

    // Avoid zooming the arrows when content is zoomed.
    const node = this.currentNode;
    this.markup.scaleRootElement(node, this.ID_CLASS_PREFIX + "root");

    setIgnoreLayoutChanges(falsethis.highlighterEnv.document.documentElement);
    return true;
  }

  /**
   * Update the offset parent rectangle.
   * There are 3 different cases covered here:
   * - the node is absolutely/fixed positioned, and an offsetParent is defined
   *   (i.e. it's not just positioned in the viewport): the offsetParent node
   *   is highlighted (i.e. the rectangle is shown),
   * - the node is relatively positioned: the rectangle is shown where the node
   *   would originally have been (because that's where the relative positioning
   *   is calculated from),
   * - the node has no offset parent at all: the offsetParent rectangle is
   *   hidden.
   */

  updateOffsetParent() {
    // Get the offsetParent, if any.
    this.offsetParent = getOffsetParent(this.currentNode);
    // And the offsetParent quads.
    this.parentQuads = getAdjustedQuads(
      this.win,
      this.offsetParent.element,
      "padding"
    );

    const el = this.getElement("offset-parent");

    const isPositioned =
      this.computedStyle.position === "absolute" ||
      this.computedStyle.position === "fixed";
    const isRelative = this.computedStyle.position === "relative";
    let isHighlighted = false;

    if (this.offsetParent.element && isPositioned) {
      const { p1, p2, p3, p4 } = this.parentQuads[0];
      const points =
        p1.x +
        "," +
        p1.y +
        " " +
        p2.x +
        "," +
        p2.y +
        " " +
        p3.x +
        "," +
        p3.y +
        " " +
        p4.x +
        "," +
        p4.y;
      el.setAttribute("points", points);
      isHighlighted = true;
    } else if (isRelative) {
      const xDelta = parseFloat(this.computedStyle.left) * this.zoomFactor;
      const yDelta = parseFloat(this.computedStyle.top) * this.zoomFactor;
      if (xDelta || yDelta) {
        const { p1, p2, p3, p4 } = this.currentQuads.margin[0];
        const points =
          p1.x -
          xDelta +
          "," +
          (p1.y - yDelta) +
          " " +
          (p2.x - xDelta) +
          "," +
          (p2.y - yDelta) +
          " " +
          (p3.x - xDelta) +
          "," +
          (p3.y - yDelta) +
          " " +
          (p4.x - xDelta) +
          "," +
          (p4.y - yDelta);
        el.setAttribute("points", points);
        isHighlighted = true;
      }
    }

    if (isHighlighted) {
      el.removeAttribute("hidden");
    } else {
      el.setAttribute("hidden""true");
    }
  }

  updateCurrentNode() {
    const box = this.getElement("current-node");
    const { p1, p2, p3, p4 } = this.currentQuads.margin[0];
    const attr =
      p1.x +
      "," +
      p1.y +
      " " +
      p2.x +
      "," +
      p2.y +
      " " +
      p3.x +
      "," +
      p3.y +
      " " +
      p4.x +
      "," +
      p4.y;
    box.setAttribute("points", attr);
    box.removeAttribute("hidden");
  }

  _hide() {
    setIgnoreLayoutChanges(true);

    this.getElement("root").setAttribute("hidden""true");
    this.getElement("current-node").setAttribute("hidden""true");
    this.getElement("offset-parent").setAttribute("hidden""true");
    this.hideArrows();

    this.definedProperties.clear();

    setIgnoreLayoutChanges(falsethis.highlighterEnv.document.documentElement);
  }

  hideArrows() {
    for (const side of GeoProp.SIDES) {
      this.getElement("arrow-" + side).setAttribute("hidden""true");
      this.getElement("label-" + side).setAttribute("hidden""true");
      this.getElement("handler-" + side).setAttribute("hidden""true");
    }
  }

  updateArrows() {
    this.hideArrows();

    // Position arrows always end at the node's margin box.
    const marginBox = this.currentQuads.margin[0].bounds;

    // Position the side arrows which need to be visible.
    // Arrows always start at the offsetParent edge, and end at the middle
    // position of the node's margin edge.
    // Note that for relative positioning, the offsetParent is considered to be
    // the node itself, where it would have been originally.
    // +------------------+----------------+
    // | offsetparent     | top            |
    // | or viewport      |                |
    // |         +--------+--------+       |
    // |         | node            |       |
    // +---------+                 +-------+
    // | left    |                 | right |
    // |         +--------+--------+       |
    // |                  | bottom         |
    // +------------------+----------------+
    const getSideArrowStartPos = side => {
      // In case of relative positioning.
      if (this.computedStyle.position === "relative") {
        if (GeoProp.isInverted(side)) {
          return (
            marginBox[side] +
            parseFloat(this.computedStyle[side]) * this.zoomFactor
          );
        }
        return (
          marginBox[side] -
          parseFloat(this.computedStyle[side]) * this.zoomFactor
        );
      }

      // In case an offsetParent exists and is highlighted.
      if (this.parentQuads && this.parentQuads.length) {
        return this.parentQuads[0].bounds[side];
      }

      // In case the element is positioned in the viewport.
      if (GeoProp.isInverted(side)) {
        return this.offsetParent.dimension[GeoProp.mainAxisSize(side)];
      }
      return (
        -1 *
        this.currentNode.ownerGlobal[
          "scroll" + GeoProp.axis(side).toUpperCase()
        ]
      );
    };

    for (const side of GeoProp.SIDES) {
      const sideProp = this.definedProperties.get(side);
      if (!sideProp) {
        continue;
      }

      const mainAxisStartPos = getSideArrowStartPos(side);
      const mainAxisEndPos = marginBox[side];
      const crossAxisPos =
        marginBox[GeoProp.crossAxisStart(side)] +
        marginBox[GeoProp.crossAxisSize(side)] / 2;

      this.updateArrow(
        side,
        mainAxisStartPos,
        mainAxisEndPos,
        crossAxisPos,
        sideProp.cssRule.style.getPropertyValue(side)
      );
    }
  }

  updateArrow(side, mainStart, mainEnd, crossPos, labelValue) {
    const arrowEl = this.getElement("arrow-" + side);
    const labelEl = this.getElement("label-" + side);
    const labelTextEl = this.getElement("label-text-" + side);
    const handlerEl = this.getElement("handler-" + side);

    // Position the arrow <line>.
    arrowEl.setAttribute(GeoProp.axis(side) + "1", mainStart);
    arrowEl.setAttribute(GeoProp.crossAxis(side) + "1", crossPos);
    arrowEl.setAttribute(GeoProp.axis(side) + "2", mainEnd);
    arrowEl.setAttribute(GeoProp.crossAxis(side) + "2", crossPos);
    arrowEl.removeAttribute("hidden");

    handlerEl.setAttribute("c" + GeoProp.axis(side), mainEnd);
    handlerEl.setAttribute("c" + GeoProp.crossAxis(side), crossPos);
    handlerEl.removeAttribute("hidden");

    // Position the label <text> in the middle of the arrow (making sure it's
    // not hidden below the fold).
    const capitalize = str => str[0].toUpperCase() + str.substring(1);
    const winMain = this.win["inner" + capitalize(GeoProp.mainAxisSize(side))];
    let labelMain = mainStart + (mainEnd - mainStart) / 2;
    if (
      (mainStart > 0 && mainStart < winMain) ||
      (mainEnd > 0 && mainEnd < winMain)
    ) {
      if (labelMain < GEOMETRY_LABEL_SIZE) {
        labelMain = GEOMETRY_LABEL_SIZE;
      } else if (labelMain > winMain - GEOMETRY_LABEL_SIZE) {
        labelMain = winMain - GEOMETRY_LABEL_SIZE;
      }
    }
    const labelCross = crossPos;
    labelEl.setAttribute(
      "transform",
      GeoProp.isHorizontal(side)
        ? "translate(" + labelMain + " " + labelCross + ")"
        : "translate(" + labelCross + " " + labelMain + ")"
    );
    labelEl.removeAttribute("hidden");
    labelTextEl.setTextContent(labelValue);
  }

  onWillNavigate({ isTopLevel }) {
    if (isTopLevel) {
      this.hide();
    }
  }
}

exports.GeometryEditorHighlighter = GeometryEditorHighlighter;

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

¤ Dauer der Verarbeitung: 0.4 Sekunden  (vorverarbeitet)  ¤

*© 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.