tor-browser

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

rulers.js (8009B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 const {
      8  getCurrentZoom,
      9  setIgnoreLayoutChanges,
     10 } = require("resource://devtools/shared/layout/utils.js");
     11 const {
     12  CanvasFrameAnonymousContentHelper,
     13 } = require("resource://devtools/server/actors/highlighters/utils/markup.js");
     14 
     15 // Maximum size, in pixel, for the horizontal ruler and vertical ruler
     16 // used by RulersHighlighter
     17 const RULERS_MAX_X_AXIS = 10000;
     18 const RULERS_MAX_Y_AXIS = 15000;
     19 // Number of steps after we add a graduation, marker and text in
     20 // RulersHighliter; currently the unit is in pixel.
     21 const RULERS_GRADUATION_STEP = 5;
     22 const RULERS_MARKER_STEP = 50;
     23 const RULERS_TEXT_STEP = 100;
     24 
     25 /**
     26 * The RulersHighlighter is a class that displays both horizontal and
     27 * vertical rules on the page, along the top and left edges, with pixel
     28 * graduations, useful for users to quickly check distances
     29 */
     30 class RulersHighlighter {
     31  constructor(highlighterEnv) {
     32    this.env = highlighterEnv;
     33    this.markup = new CanvasFrameAnonymousContentHelper(
     34      highlighterEnv,
     35      this._buildMarkup.bind(this),
     36      {
     37        contentRootHostClassName: "devtools-highlighter-rulers",
     38      }
     39    );
     40    this.isReady = this.markup.initialize();
     41 
     42    const { pageListenerTarget } = highlighterEnv;
     43    pageListenerTarget.addEventListener("scroll", this);
     44    pageListenerTarget.addEventListener("pagehide", this);
     45  }
     46 
     47  _buildMarkup() {
     48    const createRuler = (axis, size) => {
     49      let width, height;
     50      let isHorizontal = true;
     51 
     52      if (axis === "x") {
     53        width = size;
     54        height = 16;
     55      } else if (axis === "y") {
     56        width = 16;
     57        height = size;
     58        isHorizontal = false;
     59      } else {
     60        throw new Error(
     61          `Invalid type of axis given; expected "x" or "y" but got "${axis}"`
     62        );
     63      }
     64 
     65      const g = this.markup.createSVGNode({
     66        nodeType: "g",
     67        attributes: {
     68          id: `rulers-highlighter-${axis}-axis`,
     69        },
     70        parent: svg,
     71      });
     72 
     73      this.markup.createSVGNode({
     74        nodeType: "rect",
     75        attributes: {
     76          y: isHorizontal ? 0 : 16,
     77          width,
     78          height,
     79        },
     80        parent: g,
     81      });
     82 
     83      const gRule = this.markup.createSVGNode({
     84        nodeType: "g",
     85        attributes: {
     86          id: `rulers-highlighter-${axis}-axis-ruler`,
     87        },
     88        parent: g,
     89      });
     90 
     91      const pathGraduations = this.markup.createSVGNode({
     92        nodeType: "path",
     93        attributes: {
     94          class: "rulers-highlighter-ruler-graduations",
     95          width,
     96          height,
     97        },
     98        parent: gRule,
     99      });
    100 
    101      const pathMarkers = this.markup.createSVGNode({
    102        nodeType: "path",
    103        attributes: {
    104          class: "rulers-highlighter-ruler-markers",
    105          width,
    106          height,
    107        },
    108        parent: gRule,
    109      });
    110 
    111      const gText = this.markup.createSVGNode({
    112        nodeType: "g",
    113        attributes: {
    114          id: `rulers-highlighter-${axis}-axis-text`,
    115          class: isHorizontal
    116            ? "rulers-highlighter-horizontal-labels"
    117            : "rulers-highlighter-vertical-labels",
    118        },
    119        parent: g,
    120      });
    121 
    122      let dGraduations = "";
    123      let dMarkers = "";
    124      let graduationLength;
    125 
    126      for (let i = 0; i < size; i += RULERS_GRADUATION_STEP) {
    127        if (i === 0) {
    128          continue;
    129        }
    130 
    131        graduationLength = i % 2 === 0 ? 6 : 4;
    132 
    133        if (i % RULERS_TEXT_STEP === 0) {
    134          graduationLength = 8;
    135          this.markup.createSVGNode({
    136            nodeType: "text",
    137            parent: gText,
    138            attributes: {
    139              x: isHorizontal ? 2 + i : -i - 1,
    140              y: 5,
    141            },
    142          }).textContent = i;
    143        }
    144 
    145        if (isHorizontal) {
    146          if (i % RULERS_MARKER_STEP === 0) {
    147            dMarkers += `M${i} 0 L${i} ${graduationLength}`;
    148          } else {
    149            dGraduations += `M${i} 0 L${i} ${graduationLength} `;
    150          }
    151        } else if (i % 50 === 0) {
    152          dMarkers += `M0 ${i} L${graduationLength} ${i}`;
    153        } else {
    154          dGraduations += `M0 ${i} L${graduationLength} ${i}`;
    155        }
    156      }
    157 
    158      pathGraduations.setAttribute("d", dGraduations);
    159      pathMarkers.setAttribute("d", dMarkers);
    160 
    161      return g;
    162    };
    163 
    164    const container = this.markup.createNode({
    165      attributes: { class: "highlighter-container" },
    166    });
    167 
    168    const root = this.markup.createNode({
    169      parent: container,
    170      attributes: {
    171        id: "rulers-highlighter-root",
    172        class: "rulers-highlighter-root",
    173      },
    174    });
    175 
    176    const svg = this.markup.createSVGNode({
    177      nodeType: "svg",
    178      parent: root,
    179      attributes: {
    180        id: "rulers-highlighter-elements",
    181        class: "rulers-highlighter-elements",
    182        width: "100%",
    183        height: "100%",
    184        hidden: "true",
    185      },
    186    });
    187 
    188    createRuler("x", RULERS_MAX_X_AXIS);
    189    createRuler("y", RULERS_MAX_Y_AXIS);
    190 
    191    return container;
    192  }
    193 
    194  handleEvent(event) {
    195    switch (event.type) {
    196      case "scroll":
    197        this._onScroll(event);
    198        break;
    199      case "pagehide":
    200        // If a page hide event is triggered for current window's highlighter, hide the
    201        // highlighter.
    202        if (event.target.defaultView === this.env.window) {
    203          this.destroy();
    204        }
    205        break;
    206    }
    207  }
    208 
    209  _onScroll(event) {
    210    const { scrollX, scrollY } = event.view;
    211 
    212    this.markup
    213      .getElement(`rulers-highlighter-x-axis-ruler`)
    214      .setAttribute("transform", `translate(${-scrollX})`);
    215    this.markup
    216      .getElement(`rulers-highlighter-x-axis-text`)
    217      .setAttribute("transform", `translate(${-scrollX})`);
    218    this.markup
    219      .getElement(`rulers-highlighter-y-axis-ruler`)
    220      .setAttribute("transform", `translate(0, ${-scrollY})`);
    221    this.markup
    222      .getElement(`rulers-highlighter-y-axis-text`)
    223      .setAttribute("transform", `translate(0, ${-scrollY})`);
    224  }
    225 
    226  _update() {
    227    const { window } = this.env;
    228 
    229    setIgnoreLayoutChanges(true);
    230 
    231    const zoom = getCurrentZoom(window);
    232    const isZoomChanged = zoom !== this._zoom;
    233 
    234    if (isZoomChanged) {
    235      this._zoom = zoom;
    236      this.updateViewport();
    237    }
    238 
    239    setIgnoreLayoutChanges(false, window.document.documentElement);
    240 
    241    this._rafID = window.requestAnimationFrame(() => this._update());
    242  }
    243 
    244  _cancelUpdate() {
    245    if (this._rafID) {
    246      this.env.window.cancelAnimationFrame(this._rafID);
    247      this._rafID = 0;
    248    }
    249  }
    250  updateViewport() {
    251    const { devicePixelRatio } = this.env.window;
    252 
    253    // Because `devicePixelRatio` is affected by zoom (see bug 809788),
    254    // in order to get the "real" device pixel ratio, we need divide by `zoom`
    255    const pixelRatio = devicePixelRatio / this._zoom;
    256 
    257    // The "real" device pixel ratio is used to calculate the max stroke
    258    // width we can actually assign: on retina, for instance, it would be 0.5,
    259    // where on non high dpi monitor would be 1.
    260    const minWidth = 1 / pixelRatio;
    261    const strokeWidth = Math.min(minWidth, minWidth / this._zoom);
    262 
    263    this.markup
    264      .getElement("rulers-highlighter-root")
    265      .setAttribute("style", `stroke-width:${strokeWidth};`);
    266  }
    267 
    268  destroy() {
    269    this.hide();
    270 
    271    const { pageListenerTarget } = this.env;
    272 
    273    if (pageListenerTarget) {
    274      pageListenerTarget.removeEventListener("scroll", this);
    275      pageListenerTarget.removeEventListener("pagehide", this);
    276    }
    277 
    278    this.markup.destroy();
    279  }
    280 
    281  show() {
    282    this.markup.removeAttributeForElement(
    283      "rulers-highlighter-elements",
    284      "hidden"
    285    );
    286 
    287    this._update();
    288 
    289    return true;
    290  }
    291 
    292  hide() {
    293    this.markup.setAttributeForElement(
    294      "rulers-highlighter-elements",
    295      "hidden",
    296      "true"
    297    );
    298 
    299    this._cancelUpdate();
    300  }
    301 }
    302 exports.RulersHighlighter = RulersHighlighter;