tor-browser

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

box-model.js (26080B)


      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  AutoRefreshHighlighter,
      9 } = require("resource://devtools/server/actors/highlighters/auto-refresh.js");
     10 const {
     11  CanvasFrameAnonymousContentHelper,
     12  getBindingElementAndPseudo,
     13  hasPseudoClassLock,
     14  isNodeValid,
     15  moveInfobar,
     16 } = require("resource://devtools/server/actors/highlighters/utils/markup.js");
     17 const {
     18  PSEUDO_CLASSES,
     19 } = require("resource://devtools/shared/css/constants.js");
     20 const {
     21  getCurrentZoom,
     22  setIgnoreLayoutChanges,
     23 } = require("resource://devtools/shared/layout/utils.js");
     24 const {
     25  getNodeDisplayName,
     26  getNodeGridFlexType,
     27 } = require("resource://devtools/server/actors/inspector/utils.js");
     28 const nodeConstants = require("resource://devtools/shared/dom-node-constants.js");
     29 loader.lazyGetter(this, "HighlightersBundle", () => {
     30  return new Localization(["devtools/shared/highlighters.ftl"], true);
     31 });
     32 loader.lazyRequireGetter(this, "flags", "resource://devtools/shared/flags.js");
     33 
     34 // Note that the order of items in this array is important because it is used
     35 // for drawing the BoxModelHighlighter's path elements correctly.
     36 const BOX_MODEL_REGIONS = ["margin", "border", "padding", "content"];
     37 const BOX_MODEL_SIDES = ["top", "right", "bottom", "left"];
     38 // Width of boxmodelhighlighter guides
     39 const GUIDE_STROKE_WIDTH = 1;
     40 
     41 /**
     42 * The BoxModelHighlighter draws the box model regions on top of a node.
     43 * If the node is a block box, then each region will be displayed as 1 polygon.
     44 * If the node is an inline box though, each region may be represented by 1 or
     45 * more polygons, depending on how many line boxes the inline element has.
     46 *
     47 * Usage example:
     48 *
     49 * let h = new BoxModelHighlighter(env);
     50 * h.show(node, options);
     51 * h.hide();
     52 * h.destroy();
     53 *
     54 * @param {string} options.region
     55 *        Specifies the region that the guides should outline:
     56 *          "content" (default), "padding", "border" or "margin".
     57 * @param {boolean} options.hideGuides
     58 *        Defaults to false
     59 * @param {boolean} options.hideInfoBar
     60 *        Defaults to false
     61 * @param {string} options.showOnly
     62 *        If set, only this region will be highlighted. Use with onlyRegionArea
     63 *        to only highlight the area of the region:
     64 *        "content", "padding", "border" or "margin"
     65 * @param {boolean} options.onlyRegionArea
     66 *        This can be set to true to make each region's box only highlight the
     67 *        area of the corresponding region rather than the area of nested
     68 *        regions too. This is useful when used with showOnly.
     69 *
     70 * Structure:
     71 * <div class="highlighter-container" aria-hidden="true">
     72 *   <div class="box-model-root">
     73 *     <svg class="box-model-elements" hidden="true">
     74 *       <g class="box-model-regions">
     75 *         <path class="box-model-margin" points="..." />
     76 *         <path class="box-model-border" points="..." />
     77 *         <path class="box-model-padding" points="..." />
     78 *         <path class="box-model-content" points="..." />
     79 *       </g>
     80 *       <line class="box-model-guide-top" x1="..." y1="..." x2="..." y2="..." />
     81 *       <line class="box-model-guide-right" x1="..." y1="..." x2="..." y2="..." />
     82 *       <line class="box-model-guide-bottom" x1="..." y1="..." x2="..." y2="..." />
     83 *       <line class="box-model-guide-left" x1="..." y1="..." x2="..." y2="..." />
     84 *     </svg>
     85 *     <div class="box-model-infobar-container">
     86 *       <div class="box-model-infobar-arrow highlighter-infobar-arrow-top" />
     87 *       <div class="box-model-infobar">
     88 *         <div class="box-model-infobar-text" align="center">
     89 *           <span class="box-model-infobar-tagname">Node name</span>
     90 *           <span class="box-model-infobar-id">Node id</span>
     91 *           <span class="box-model-infobar-classes">.someClass</span>
     92 *           <span class="box-model-infobar-pseudo-classes">:hover</span>
     93 *           <span class="box-model-infobar-grid-type">Grid Type</span>
     94 *           <span class="box-model-infobar-flex-type">Flex Type</span>
     95 *         </div>
     96 *       </div>
     97 *       <div class="box-model-infobar-arrow box-model-infobar-arrow-bottom"/>
     98 *     </div>
     99 *   </div>
    100 * </div>
    101 */
    102 class BoxModelHighlighter extends AutoRefreshHighlighter {
    103  constructor(highlighterEnv) {
    104    super(highlighterEnv);
    105 
    106    this.markup = new CanvasFrameAnonymousContentHelper(
    107      this.highlighterEnv,
    108      this._buildMarkup.bind(this),
    109      {
    110        contentRootHostClassName: "devtools-highlighter-box-model",
    111      }
    112    );
    113    this.isReady = this.markup.initialize();
    114 
    115    this.onPageHide = this.onPageHide.bind(this);
    116    this.onWillNavigate = this.onWillNavigate.bind(this);
    117 
    118    this.highlighterEnv.on("will-navigate", this.onWillNavigate);
    119 
    120    const { pageListenerTarget } = highlighterEnv;
    121    pageListenerTarget.addEventListener("pagehide", this.onPageHide);
    122  }
    123 
    124  /**
    125   * Static getter that indicates that BoxModelHighlighter supports
    126   * highlighting in XUL windows.
    127   */
    128  static get XULSupported() {
    129    return true;
    130  }
    131 
    132  get supportsSimpleHighlighters() {
    133    return true;
    134  }
    135 
    136  _buildMarkup() {
    137    const highlighterContainer =
    138      this.markup.anonymousContentDocument.createElement("div");
    139    highlighterContainer.className = "highlighter-container box-model";
    140 
    141    this.highlighterContainer = highlighterContainer;
    142    // We need a better solution for how to handle the highlighter from the
    143    // accessibility standpoint. For now, in order to avoid displaying it in the
    144    // accessibility tree lets hide it altogether. See bug 1598667 for more
    145    // context.
    146    highlighterContainer.setAttribute("aria-hidden", "true");
    147 
    148    // Build the root wrapper, used to adapt to the page zoom.
    149    this.rootEl = this.markup.createNode({
    150      parent: highlighterContainer,
    151      attributes: {
    152        id: "box-model-root",
    153        class:
    154          "box-model-root" +
    155          (this.highlighterEnv.useSimpleHighlightersForReducedMotion
    156            ? " use-simple-highlighters"
    157            : ""),
    158        role: "presentation",
    159      },
    160    });
    161 
    162    // Building the SVG element with its polygons and lines
    163 
    164    const svg = this.markup.createSVGNode({
    165      nodeType: "svg",
    166      parent: this.rootEl,
    167      attributes: {
    168        id: "box-model-elements",
    169        width: "100%",
    170        height: "100%",
    171        hidden: "true",
    172        role: "presentation",
    173      },
    174    });
    175 
    176    const regions = this.markup.createSVGNode({
    177      nodeType: "g",
    178      parent: svg,
    179      attributes: {
    180        class: "box-model-regions",
    181        role: "presentation",
    182      },
    183    });
    184 
    185    for (const region of BOX_MODEL_REGIONS) {
    186      this.markup.createSVGNode({
    187        nodeType: "path",
    188        parent: regions,
    189        attributes: {
    190          class: "box-model-" + region,
    191          id: "box-model-" + region,
    192          role: "presentation",
    193        },
    194      });
    195    }
    196 
    197    for (const side of BOX_MODEL_SIDES) {
    198      this.markup.createSVGNode({
    199        nodeType: "line",
    200        parent: svg,
    201        attributes: {
    202          class: "box-model-guide-" + side,
    203          id: "box-model-guide-" + side,
    204          "stroke-width": GUIDE_STROKE_WIDTH,
    205          role: "presentation",
    206        },
    207      });
    208    }
    209 
    210    // Building the nodeinfo bar markup
    211 
    212    const infobarContainer = this.markup.createNode({
    213      parent: this.rootEl,
    214      attributes: {
    215        class: "box-model-infobar-container",
    216        id: "box-model-infobar-container",
    217        position: "top",
    218        hidden: "true",
    219      },
    220    });
    221 
    222    const infobar = this.markup.createNode({
    223      parent: infobarContainer,
    224      attributes: {
    225        class: "box-model-infobar",
    226      },
    227    });
    228 
    229    const texthbox = this.markup.createNode({
    230      parent: infobar,
    231      attributes: {
    232        class: "box-model-infobar-text",
    233      },
    234    });
    235    this.markup.createNode({
    236      nodeType: "span",
    237      parent: texthbox,
    238      attributes: {
    239        class: "box-model-infobar-tagname",
    240        id: "box-model-infobar-tagname",
    241      },
    242    });
    243    this.markup.createNode({
    244      nodeType: "span",
    245      parent: texthbox,
    246      attributes: {
    247        class: "box-model-infobar-id",
    248        id: "box-model-infobar-id",
    249      },
    250    });
    251    this.markup.createNode({
    252      nodeType: "span",
    253      parent: texthbox,
    254      attributes: {
    255        class: "box-model-infobar-classes",
    256        id: "box-model-infobar-classes",
    257      },
    258    });
    259    this.markup.createNode({
    260      nodeType: "span",
    261      parent: texthbox,
    262      attributes: {
    263        class: "box-model-infobar-pseudo-classes",
    264        id: "box-model-infobar-pseudo-classes",
    265      },
    266    });
    267    this.markup.createNode({
    268      nodeType: "span",
    269      parent: texthbox,
    270      attributes: {
    271        class: "box-model-infobar-dimensions",
    272        id: "box-model-infobar-dimensions",
    273      },
    274    });
    275 
    276    this.markup.createNode({
    277      nodeType: "span",
    278      parent: texthbox,
    279      attributes: {
    280        class: "box-model-infobar-grid-type",
    281        id: "box-model-infobar-grid-type",
    282      },
    283    });
    284 
    285    this.markup.createNode({
    286      nodeType: "span",
    287      parent: texthbox,
    288      attributes: {
    289        class: "box-model-infobar-flex-type",
    290        id: "box-model-infobar-flex-type",
    291      },
    292    });
    293 
    294    return highlighterContainer;
    295  }
    296 
    297  /**
    298   * Destroy the nodes. Remove listeners.
    299   */
    300  destroy() {
    301    this.highlighterEnv.off("will-navigate", this.onWillNavigate);
    302 
    303    const { pageListenerTarget } = this.highlighterEnv;
    304    if (pageListenerTarget) {
    305      pageListenerTarget.removeEventListener("pagehide", this.onPageHide);
    306    }
    307 
    308    this.markup.destroy();
    309    this.rootEl = null;
    310 
    311    AutoRefreshHighlighter.prototype.destroy.call(this);
    312  }
    313 
    314  getElement(id) {
    315    return this.markup.getElement(id);
    316  }
    317 
    318  /**
    319   * Override the AutoRefreshHighlighter's _isNodeValid method to also return true for
    320   * text nodes since these can also be highlighted.
    321   *
    322   * @param {DOMNode} node
    323   * @return {boolean}
    324   */
    325  _isNodeValid(node) {
    326    return (
    327      node && (isNodeValid(node) || isNodeValid(node, nodeConstants.TEXT_NODE))
    328    );
    329  }
    330 
    331  /**
    332   * Show the highlighter on a given node
    333   */
    334  _show() {
    335    if (!BOX_MODEL_REGIONS.includes(this.options.region)) {
    336      this.options.region = "content";
    337    }
    338 
    339    const shown = this._update();
    340    this._trackMutations();
    341    return shown;
    342  }
    343 
    344  /**
    345   * Track the current node markup mutations so that the node info bar can be
    346   * updated to reflects the node's attributes
    347   */
    348  _trackMutations() {
    349    if (isNodeValid(this.currentNode)) {
    350      const win = this.currentNode.ownerGlobal;
    351      this.currentNodeObserver = new win.MutationObserver(this.update);
    352      this.currentNodeObserver.observe(this.currentNode, { attributes: true });
    353    }
    354  }
    355 
    356  _untrackMutations() {
    357    if (isNodeValid(this.currentNode) && this.currentNodeObserver) {
    358      this.currentNodeObserver.disconnect();
    359      this.currentNodeObserver = null;
    360    }
    361  }
    362 
    363  /**
    364   * Update the highlighter on the current highlighted node (the one that was
    365   * passed as an argument to show(node)).
    366   * Should be called whenever node size or attributes change
    367   */
    368  _update() {
    369    const node = this.currentNode;
    370    let shown = false;
    371    setIgnoreLayoutChanges(true);
    372 
    373    if (this._updateBoxModel()) {
    374      const isElementOrTextNode =
    375        node.nodeType === node.ELEMENT_NODE || node.nodeType === node.TEXT_NODE;
    376      // Show the infobar only if configured to do so and the node is an element or a text
    377      // node.
    378      if (isElementOrTextNode) {
    379        if (!this.options.hideInfoBar) {
    380          this._showInfobar();
    381        } else if (flags.testing) {
    382          // Even if we don't want to show the info bar, update the infobar data (tag, id,
    383          // classes, …) anyway so it's easier to debug/test
    384          this._updateInfobarNodeData();
    385        } else {
    386          this._hideInfobar();
    387        }
    388      } else {
    389        this._hideInfobar();
    390      }
    391 
    392      this._updateSimpleHighlighters();
    393      this._showBoxModel();
    394 
    395      shown = true;
    396    } else {
    397      // Nothing to highlight (0px rectangle like a <script> tag for instance)
    398      this._hide();
    399    }
    400 
    401    setIgnoreLayoutChanges(
    402      false,
    403      this.highlighterEnv.window.document.documentElement
    404    );
    405 
    406    return shown;
    407  }
    408 
    409  _scrollUpdate() {
    410    this._moveInfobar();
    411  }
    412 
    413  /**
    414   * Hide the highlighter, the outline and the infobar.
    415   */
    416  _hide() {
    417    setIgnoreLayoutChanges(true);
    418 
    419    this._untrackMutations();
    420    this._hideBoxModel();
    421    this._hideInfobar();
    422 
    423    setIgnoreLayoutChanges(
    424      false,
    425      this.highlighterEnv.window.document.documentElement
    426    );
    427  }
    428 
    429  /**
    430   * Hide the infobar
    431   */
    432  _hideInfobar() {
    433    this.getElement("box-model-infobar-container").setAttribute(
    434      "hidden",
    435      "true"
    436    );
    437  }
    438 
    439  /**
    440   * Show the infobar
    441   */
    442  _showInfobar() {
    443    this.getElement("box-model-infobar-container").removeAttribute("hidden");
    444    this._updateInfobar();
    445  }
    446 
    447  /**
    448   * Hide the box model
    449   */
    450  _hideBoxModel() {
    451    this.getElement("box-model-elements").setAttribute("hidden", "true");
    452  }
    453 
    454  /**
    455   * Show the box model
    456   */
    457  _showBoxModel() {
    458    this.getElement("box-model-elements").removeAttribute("hidden");
    459  }
    460 
    461  /**
    462   * Calculate an outer quad based on the quads returned by getAdjustedQuads.
    463   * The BoxModelHighlighter may highlight more than one boxes, so in this case
    464   * create a new quad that "contains" all of these quads.
    465   * This is useful to position the guides and infobar.
    466   * This may happen if the BoxModelHighlighter is used to highlight an inline
    467   * element that spans line breaks.
    468   *
    469   * @param {string} region The box-model region to get the outer quad for.
    470   * @return {object} A quad-like object {p1,p2,p3,p4,bounds}
    471   */
    472  _getOuterQuad(region) {
    473    const quads = this.currentQuads[region];
    474    if (!quads || !quads.length) {
    475      return null;
    476    }
    477 
    478    const quad = {
    479      p1: { x: Infinity, y: Infinity },
    480      p2: { x: -Infinity, y: Infinity },
    481      p3: { x: -Infinity, y: -Infinity },
    482      p4: { x: Infinity, y: -Infinity },
    483      bounds: {
    484        bottom: -Infinity,
    485        height: 0,
    486        left: Infinity,
    487        right: -Infinity,
    488        top: Infinity,
    489        width: 0,
    490        x: 0,
    491        y: 0,
    492      },
    493    };
    494 
    495    for (const q of quads) {
    496      quad.p1.x = Math.min(quad.p1.x, q.p1.x);
    497      quad.p1.y = Math.min(quad.p1.y, q.p1.y);
    498      quad.p2.x = Math.max(quad.p2.x, q.p2.x);
    499      quad.p2.y = Math.min(quad.p2.y, q.p2.y);
    500      quad.p3.x = Math.max(quad.p3.x, q.p3.x);
    501      quad.p3.y = Math.max(quad.p3.y, q.p3.y);
    502      quad.p4.x = Math.min(quad.p4.x, q.p4.x);
    503      quad.p4.y = Math.max(quad.p4.y, q.p4.y);
    504 
    505      quad.bounds.bottom = Math.max(quad.bounds.bottom, q.bounds.bottom);
    506      quad.bounds.top = Math.min(quad.bounds.top, q.bounds.top);
    507      quad.bounds.left = Math.min(quad.bounds.left, q.bounds.left);
    508      quad.bounds.right = Math.max(quad.bounds.right, q.bounds.right);
    509    }
    510    quad.bounds.x = quad.bounds.left;
    511    quad.bounds.y = quad.bounds.top;
    512    quad.bounds.width = quad.bounds.right - quad.bounds.left;
    513    quad.bounds.height = quad.bounds.bottom - quad.bounds.top;
    514 
    515    return quad;
    516  }
    517 
    518  /**
    519   * Update the box model as per the current node.
    520   *
    521   * @return {boolean}
    522   *         True if the current node has a box model to be highlighted
    523   */
    524  _updateBoxModel() {
    525    const options = this.options;
    526    options.region = options.region || "content";
    527 
    528    if (!this._nodeNeedsHighlighting()) {
    529      this._hideBoxModel();
    530      return false;
    531    }
    532 
    533    for (let i = 0; i < BOX_MODEL_REGIONS.length; i++) {
    534      const boxType = BOX_MODEL_REGIONS[i];
    535      const nextBoxType = BOX_MODEL_REGIONS[i + 1];
    536      const box = this.getElement("box-model-" + boxType);
    537 
    538      // Highlight all quads for this region by setting the "d" attribute of the
    539      // corresponding <path>.
    540      const path = [];
    541      for (let j = 0; j < this.currentQuads[boxType].length; j++) {
    542        const boxQuad = this.currentQuads[boxType][j];
    543        const nextBoxQuad = this.currentQuads[nextBoxType]
    544          ? this.currentQuads[nextBoxType][j]
    545          : null;
    546        path.push(this._getBoxPathCoordinates(boxQuad, nextBoxQuad));
    547      }
    548 
    549      box.setAttribute("d", path.join(" "));
    550      box.removeAttribute("faded");
    551 
    552      // If showOnly is defined, either hide the other regions, or fade them out
    553      // if onlyRegionArea is set too.
    554      if (options.showOnly && options.showOnly !== boxType) {
    555        if (options.onlyRegionArea) {
    556          box.setAttribute("faded", "true");
    557        } else {
    558          box.removeAttribute("d");
    559        }
    560      }
    561 
    562      if (boxType === options.region && !options.hideGuides) {
    563        this._showGuides(boxType);
    564      } else if (options.hideGuides) {
    565        this._hideGuides();
    566      }
    567    }
    568 
    569    // Un-zoom the root wrapper if the page was zoomed.
    570    this.markup.scaleRootElement(this.currentNode, "box-model-elements");
    571 
    572    return true;
    573  }
    574 
    575  _getBoxPathCoordinates(boxQuad, nextBoxQuad) {
    576    const { p1, p2, p3, p4 } = boxQuad;
    577 
    578    let path;
    579    if (!nextBoxQuad || !this.options.onlyRegionArea) {
    580      // If this is the content box (inner-most box) or if we're not being asked
    581      // to highlight only region areas, then draw a simple rectangle.
    582      path =
    583        "M" +
    584        p1.x +
    585        "," +
    586        p1.y +
    587        " " +
    588        "L" +
    589        p2.x +
    590        "," +
    591        p2.y +
    592        " " +
    593        "L" +
    594        p3.x +
    595        "," +
    596        p3.y +
    597        " " +
    598        "L" +
    599        p4.x +
    600        "," +
    601        p4.y +
    602        " " +
    603        "L" +
    604        p1.x +
    605        "," +
    606        p1.y;
    607    } else {
    608      // Otherwise, just draw the region itself, not a filled rectangle.
    609      const { p1: np1, p2: np2, p3: np3, p4: np4 } = nextBoxQuad;
    610      path =
    611        "M" +
    612        p1.x +
    613        "," +
    614        p1.y +
    615        " " +
    616        "L" +
    617        p2.x +
    618        "," +
    619        p2.y +
    620        " " +
    621        "L" +
    622        p3.x +
    623        "," +
    624        p3.y +
    625        " " +
    626        "L" +
    627        p4.x +
    628        "," +
    629        p4.y +
    630        " " +
    631        "L" +
    632        p1.x +
    633        "," +
    634        p1.y +
    635        " " +
    636        "L" +
    637        np1.x +
    638        "," +
    639        np1.y +
    640        " " +
    641        "L" +
    642        np4.x +
    643        "," +
    644        np4.y +
    645        " " +
    646        "L" +
    647        np3.x +
    648        "," +
    649        np3.y +
    650        " " +
    651        "L" +
    652        np2.x +
    653        "," +
    654        np2.y +
    655        " " +
    656        "L" +
    657        np1.x +
    658        "," +
    659        np1.y;
    660    }
    661 
    662    return path;
    663  }
    664 
    665  /**
    666   * Can the current node be highlighted? Does it have quads.
    667   *
    668   * @return {boolean}
    669   */
    670  _nodeNeedsHighlighting() {
    671    return (
    672      this.currentQuads.margin.length ||
    673      this.currentQuads.border.length ||
    674      this.currentQuads.padding.length ||
    675      this.currentQuads.content.length
    676    );
    677  }
    678 
    679  _getOuterBounds() {
    680    for (const region of ["margin", "border", "padding", "content"]) {
    681      const quad = this._getOuterQuad(region);
    682 
    683      if (!quad) {
    684        // Invisible element such as a script tag.
    685        break;
    686      }
    687 
    688      const { bottom, height, left, right, top, width, x, y } = quad.bounds;
    689 
    690      if (width > 0 || height > 0) {
    691        return { bottom, height, left, right, top, width, x, y };
    692      }
    693    }
    694 
    695    return {
    696      bottom: 0,
    697      height: 0,
    698      left: 0,
    699      right: 0,
    700      top: 0,
    701      width: 0,
    702      x: 0,
    703      y: 0,
    704    };
    705  }
    706 
    707  /**
    708   * We only want to show guides for horizontal and vertical edges as this helps
    709   * to line them up. This method finds these edges and displays a guide there.
    710   *
    711   * @param {string} region The region around which the guides should be shown.
    712   */
    713  _showGuides(region) {
    714    const quad = this._getOuterQuad(region);
    715 
    716    if (!quad) {
    717      // Invisible element such as a script tag.
    718      return;
    719    }
    720 
    721    const { p1, p2, p3, p4 } = quad;
    722 
    723    const allX = [p1.x, p2.x, p3.x, p4.x].sort((a, b) => a - b);
    724    const allY = [p1.y, p2.y, p3.y, p4.y].sort((a, b) => a - b);
    725    const toShowX = [];
    726    const toShowY = [];
    727 
    728    for (const arr of [allX, allY]) {
    729      for (let i = 0; i < arr.length; i++) {
    730        const val = arr[i];
    731 
    732        if (i !== arr.lastIndexOf(val)) {
    733          if (arr === allX) {
    734            toShowX.push(val);
    735          } else {
    736            toShowY.push(val);
    737          }
    738          arr.splice(arr.lastIndexOf(val), 1);
    739        }
    740      }
    741    }
    742 
    743    // Move guide into place or hide it if no valid co-ordinate was found.
    744    this._updateGuide("top", Math.round(toShowY[0]));
    745    this._updateGuide("right", Math.round(toShowX[1]) - 1);
    746    this._updateGuide("bottom", Math.round(toShowY[1]) - 1);
    747    this._updateGuide("left", Math.round(toShowX[0]));
    748  }
    749 
    750  _hideGuides() {
    751    for (const side of BOX_MODEL_SIDES) {
    752      this.getElement("box-model-guide-" + side).setAttribute("hidden", "true");
    753    }
    754  }
    755 
    756  /**
    757   * Move a guide to the appropriate position and display it. If no point is
    758   * passed then the guide is hidden.
    759   *
    760   * @param  {string} side
    761   *         The guide to update
    762   * @param  {Integer} point
    763   *         x or y co-ordinate. If this is undefined we hide the guide.
    764   */
    765  _updateGuide(side, point) {
    766    const guide = this.getElement("box-model-guide-" + side);
    767 
    768    if (!point || point <= 0) {
    769      guide.setAttribute("hidden", "true");
    770      return false;
    771    }
    772 
    773    if (side === "top" || side === "bottom") {
    774      guide.setAttribute("x1", "0");
    775      guide.setAttribute("y1", point + "");
    776      guide.setAttribute("x2", "100%");
    777      guide.setAttribute("y2", point + "");
    778    } else {
    779      guide.setAttribute("x1", point + "");
    780      guide.setAttribute("y1", "0");
    781      guide.setAttribute("x2", point + "");
    782      guide.setAttribute("y2", "100%");
    783    }
    784 
    785    guide.removeAttribute("hidden");
    786 
    787    return true;
    788  }
    789 
    790  /**
    791   * Update node information (displayName#id.class)
    792   */
    793  _updateInfobar() {
    794    if (!this.currentNode) {
    795      return;
    796    }
    797 
    798    const { bindingElement: node } = getBindingElementAndPseudo(
    799      this.currentNode
    800    );
    801 
    802    // We want to display the original `width` and `height`, instead of the ones affected
    803    // by any zoom. Since the infobar can be displayed also for text nodes, we can't
    804    // access the computed style for that, and this is why we recalculate them here.
    805    const zoom = getCurrentZoom(this.win);
    806    const quad = this._getOuterQuad("border");
    807 
    808    if (!quad) {
    809      return;
    810    }
    811 
    812    const { width, height } = quad.bounds;
    813    const dim =
    814      parseFloat((width / zoom).toPrecision(6)) +
    815      " \u00D7 " +
    816      parseFloat((height / zoom).toPrecision(6));
    817 
    818    const { grid: gridType, flex: flexType } = getNodeGridFlexType(node);
    819    const gridLayoutTextType = this._getLayoutTextType("gridtype", gridType);
    820    const flexLayoutTextType = this._getLayoutTextType("flextype", flexType);
    821 
    822    // Update the tag, id, classes, pseudo-classes
    823    this._updateInfobarNodeData();
    824    this.getElement("box-model-infobar-dimensions").setTextContent(dim);
    825    this.getElement("box-model-infobar-grid-type").setTextContent(
    826      gridLayoutTextType
    827    );
    828    this.getElement("box-model-infobar-flex-type").setTextContent(
    829      flexLayoutTextType
    830    );
    831 
    832    this._moveInfobar();
    833  }
    834 
    835  _updateInfobarNodeData() {
    836    if (!this.currentNode) {
    837      return;
    838    }
    839 
    840    // The binding element of a pseudo element can also be a pseudo element (for example
    841    // ::before::marker), so walk up through the tree until we get a non pseudo binding
    842    // element.
    843    let node = this.currentNode,
    844      pseudo = "";
    845    while (true) {
    846      const res = getBindingElementAndPseudo(node);
    847 
    848      // Stop as soon as the binding element is the same as the passed node, meaning we
    849      // found the ultimate originating element (https://drafts.csswg.org/selectors-4/#ultimate-originating-element).
    850      if (res.bindingElement === node) {
    851        break;
    852      }
    853 
    854      node = res.bindingElement;
    855      pseudo = res.pseudo + pseudo;
    856    }
    857 
    858    // Update the tag, id, classes, pseudo-classes and dimensions
    859    const displayName = getNodeDisplayName(node);
    860 
    861    const id = node.id ? "#" + node.id : "";
    862 
    863    const classList = node.classList?.length
    864      ? "." + [...node.classList].join(".")
    865      : "";
    866 
    867    let pseudos = this._getPseudoClasses(node).join("");
    868    if (pseudo) {
    869      pseudos += pseudo;
    870    }
    871 
    872    this.getElement("box-model-infobar-tagname").setTextContent(displayName);
    873    this.getElement("box-model-infobar-id").setTextContent(id);
    874    this.getElement("box-model-infobar-classes").setTextContent(classList);
    875    this.getElement("box-model-infobar-pseudo-classes").setTextContent(pseudos);
    876  }
    877 
    878  _getLayoutTextType(layoutTypeKey, { isContainer, isItem }) {
    879    if (!isContainer && !isItem) {
    880      return "";
    881    }
    882    if (isContainer && !isItem) {
    883      return HighlightersBundle.formatValueSync(`${layoutTypeKey}-container`);
    884    }
    885    if (!isContainer && isItem) {
    886      return HighlightersBundle.formatValueSync(`${layoutTypeKey}-item`);
    887    }
    888    return HighlightersBundle.formatValueSync(`${layoutTypeKey}-dual`);
    889  }
    890 
    891  _getPseudoClasses(node) {
    892    if (node.nodeType !== nodeConstants.ELEMENT_NODE) {
    893      // hasPseudoClassLock can only be used on Elements.
    894      return [];
    895    }
    896 
    897    return PSEUDO_CLASSES.filter(pseudo => hasPseudoClassLock(node, pseudo));
    898  }
    899 
    900  /**
    901   * Move the Infobar to the right place in the highlighter.
    902   */
    903  _moveInfobar() {
    904    const bounds = this._getOuterBounds();
    905    const container = this.getElement("box-model-infobar-container");
    906 
    907    moveInfobar(container, bounds, this.win);
    908  }
    909 
    910  onPageHide({ target }) {
    911    // If a pagehide event is triggered for current window's highlighter, hide the
    912    // highlighter.
    913    if (target.defaultView === this.win) {
    914      this.hide();
    915    }
    916  }
    917 
    918  onWillNavigate({ isTopLevel }) {
    919    if (isTopLevel) {
    920      this.hide();
    921    }
    922  }
    923 }
    924 
    925 exports.BoxModelHighlighter = BoxModelHighlighter;