tor-browser

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

utils.js (24906B)


      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 loader.lazyRequireGetter(
      8  this,
      9  "DevToolsUtils",
     10  "resource://devtools/shared/DevToolsUtils.js"
     11 );
     12 const lazy = {};
     13 ChromeUtils.defineESModuleGetters(
     14  lazy,
     15  {
     16    NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
     17  },
     18  { global: "contextual" }
     19 );
     20 
     21 const SHEET_TYPE = {
     22  agent: "AGENT_SHEET",
     23  user: "USER_SHEET",
     24  author: "AUTHOR_SHEET",
     25 };
     26 
     27 // eslint-disable-next-line no-unused-vars
     28 loader.lazyRequireGetter(
     29  this,
     30  "setIgnoreLayoutChanges",
     31  "resource://devtools/server/actors/reflow.js",
     32  true
     33 );
     34 exports.setIgnoreLayoutChanges = (...args) =>
     35  this.setIgnoreLayoutChanges(...args);
     36 
     37 /**
     38 * Check a window is part of the boundary window given.
     39 *
     40 * @param {DOMWindow} boundaryWindow
     41 * @param {DOMWindow} win
     42 * @return {boolean}
     43 */
     44 function isWindowIncluded(boundaryWindow, win) {
     45  if (win === boundaryWindow) {
     46    return true;
     47  }
     48 
     49  const parent = win.parent;
     50 
     51  if (!parent || parent === win) {
     52    return false;
     53  }
     54 
     55  return isWindowIncluded(boundaryWindow, parent);
     56 }
     57 exports.isWindowIncluded = isWindowIncluded;
     58 
     59 /**
     60 * like win.frameElement, but goes through mozbrowsers and mozapps iframes.
     61 *
     62 * @param {DOMWindow} win
     63 *        The window to get the frame for
     64 * @return {DOMNode}
     65 *         The element in which the window is embedded.
     66 */
     67 const getFrameElement = win => {
     68  const isTopWindow = win && DevToolsUtils.getTopWindow(win) === win;
     69  return isTopWindow ? null : win.browsingContext.embedderElement;
     70 };
     71 exports.getFrameElement = getFrameElement;
     72 
     73 /**
     74 * Get the x/y offsets for of all the parent frames of a given node, limited to
     75 * the boundary window given.
     76 *
     77 * @param {DOMWindow} boundaryWindow
     78 *        The window where to stop to iterate. If `null` is given, the top
     79 *        window is used.
     80 * @param {DOMNode} node
     81 *        The node for which we are to get the offset
     82 * @return {Array}
     83 *         The frame offset [x, y]
     84 */
     85 function getFrameOffsets(boundaryWindow, node) {
     86  let xOffset = 0;
     87  let yOffset = 0;
     88 
     89  let frameWin = getWindowFor(node);
     90  const scale = getCurrentZoom(node);
     91 
     92  if (boundaryWindow === null) {
     93    boundaryWindow = DevToolsUtils.getTopWindow(frameWin);
     94  } else if (typeof boundaryWindow === "undefined") {
     95    throw new Error("No boundaryWindow given. Use null for the default one.");
     96  }
     97 
     98  while (frameWin !== boundaryWindow) {
     99    const frameElement = getFrameElement(frameWin);
    100    if (!frameElement) {
    101      break;
    102    }
    103 
    104    // We are in an iframe.
    105    // We take into account the parent iframe position and its
    106    // offset (borders and padding).
    107    const frameRect = frameElement.getBoundingClientRect();
    108 
    109    const [offsetTop, offsetLeft] = getFrameContentOffset(frameElement);
    110 
    111    xOffset += frameRect.left + offsetLeft;
    112    yOffset += frameRect.top + offsetTop;
    113 
    114    frameWin = frameWin.parent;
    115  }
    116 
    117  return [xOffset * scale, yOffset * scale];
    118 }
    119 exports.getFrameOffsets = getFrameOffsets;
    120 
    121 /**
    122 * Get box quads adjusted for iframes and zoom level.
    123 *
    124 * Warning: this function returns things that look like DOMQuad objects but
    125 * aren't (they resemble an old version of the spec). Unlike the return value
    126 * of node.getBoxQuads, they have a .bounds property and not a .getBounds()
    127 * method.
    128 *
    129 * @param {DOMWindow} boundaryWindow
    130 *        The window where to stop to iterate. If `null` is given, the top
    131 *        window is used.
    132 * @param {DOMNode} node
    133 *        The node for which we are to get the box model region
    134 *        quads.
    135 * @param {string} region
    136 *        The box model region to return: "content", "padding", "border" or
    137 *        "margin".
    138 * @param {object} [options.ignoreZoom=false]
    139 *        Ignore zoom used in the context of e.g. canvas.
    140 * @return {Array}
    141 *        An array of objects that have the same structure as quads returned by
    142 *        getBoxQuads. An empty array if the node has no quads or is invalid.
    143 */
    144 function getAdjustedQuads(
    145  boundaryWindow,
    146  node,
    147  region,
    148  { ignoreZoom, ignoreScroll } = {}
    149 ) {
    150  if (!node || !node.getBoxQuads) {
    151    return [];
    152  }
    153 
    154  const quads = node.getBoxQuads({
    155    box: region,
    156    relativeTo: boundaryWindow.document,
    157    createFramesForSuppressedWhitespace: false,
    158  });
    159 
    160  if (!quads.length) {
    161    return [];
    162  }
    163 
    164  const scale = ignoreZoom ? 1 : getCurrentZoom(node);
    165  const { scrollX, scrollY } = ignoreScroll
    166    ? { scrollX: 0, scrollY: 0 }
    167    : boundaryWindow;
    168 
    169  const xOffset = scrollX * scale;
    170  const yOffset = scrollY * scale;
    171 
    172  const adjustedQuads = [];
    173  for (const quad of quads) {
    174    const bounds = quad.getBounds();
    175    adjustedQuads.push({
    176      p1: {
    177        w: quad.p1.w * scale,
    178        x: quad.p1.x * scale + xOffset,
    179        y: quad.p1.y * scale + yOffset,
    180        z: quad.p1.z * scale,
    181      },
    182      p2: {
    183        w: quad.p2.w * scale,
    184        x: quad.p2.x * scale + xOffset,
    185        y: quad.p2.y * scale + yOffset,
    186        z: quad.p2.z * scale,
    187      },
    188      p3: {
    189        w: quad.p3.w * scale,
    190        x: quad.p3.x * scale + xOffset,
    191        y: quad.p3.y * scale + yOffset,
    192        z: quad.p3.z * scale,
    193      },
    194      p4: {
    195        w: quad.p4.w * scale,
    196        x: quad.p4.x * scale + xOffset,
    197        y: quad.p4.y * scale + yOffset,
    198        z: quad.p4.z * scale,
    199      },
    200      bounds: {
    201        bottom: bounds.bottom * scale + yOffset,
    202        height: bounds.height * scale,
    203        left: bounds.left * scale + xOffset,
    204        right: bounds.right * scale + xOffset,
    205        top: bounds.top * scale + yOffset,
    206        width: bounds.width * scale,
    207        x: bounds.x * scale + xOffset,
    208        y: bounds.y * scale + yOffset,
    209      },
    210    });
    211  }
    212 
    213  return adjustedQuads;
    214 }
    215 exports.getAdjustedQuads = getAdjustedQuads;
    216 
    217 /**
    218 * Compute the absolute position and the dimensions of a node, relativalely
    219 * to the root window.
    220 
    221 * @param {DOMWindow} boundaryWindow
    222 *        The window where to stop to iterate. If `null` is given, the top
    223 *        window is used.
    224 * @param {DOMNode} node
    225 *        a DOM element to get the bounds for
    226 * @param {DOMWindow} contentWindow
    227 *        the content window holding the node
    228 * @return {object}
    229 *         A rect object with the {top, left, width, height} properties
    230 */
    231 function getRect(boundaryWindow, node, contentWindow) {
    232  let frameWin = node.ownerDocument.defaultView;
    233  const clientRect = node.getBoundingClientRect();
    234 
    235  if (boundaryWindow === null) {
    236    boundaryWindow = DevToolsUtils.getTopWindow(frameWin);
    237  } else if (typeof boundaryWindow === "undefined") {
    238    throw new Error("No boundaryWindow given. Use null for the default one.");
    239  }
    240 
    241  // Go up in the tree of frames to determine the correct rectangle.
    242  // clientRect is read-only, we need to be able to change properties.
    243  const rect = {
    244    top: clientRect.top + contentWindow.pageYOffset,
    245    left: clientRect.left + contentWindow.pageXOffset,
    246    width: clientRect.width,
    247    height: clientRect.height,
    248  };
    249 
    250  // We iterate through all the parent windows.
    251  while (frameWin !== boundaryWindow) {
    252    const frameElement = getFrameElement(frameWin);
    253    if (!frameElement) {
    254      break;
    255    }
    256 
    257    // We are in an iframe.
    258    // We take into account the parent iframe position and its
    259    // offset (borders and padding).
    260    const frameRect = frameElement.getBoundingClientRect();
    261 
    262    const [offsetTop, offsetLeft] = getFrameContentOffset(frameElement);
    263 
    264    rect.top += frameRect.top + offsetTop;
    265    rect.left += frameRect.left + offsetLeft;
    266 
    267    frameWin = frameWin.parent;
    268  }
    269 
    270  return rect;
    271 }
    272 exports.getRect = getRect;
    273 
    274 /**
    275 * Get the 4 bounding points for a node taking iframes into account.
    276 * Note that for transformed nodes, this will return the untransformed bound.
    277 *
    278 * @param {DOMWindow} boundaryWindow
    279 *        The window where to stop to iterate. If `null` is given, the top
    280 *        window is used.
    281 * @param {DOMNode} node
    282 * @return {object}
    283 *         An object with p1,p2,p3,p4 properties being {x,y} objects
    284 */
    285 function getNodeBounds(boundaryWindow, node) {
    286  if (!node) {
    287    return null;
    288  }
    289  const { scrollX, scrollY } = boundaryWindow;
    290  const scale = getCurrentZoom(node);
    291 
    292  // Find out the offset of the node in its current frame
    293  let offsetLeft = 0;
    294  let offsetTop = 0;
    295  let el = node;
    296  while (el?.parentNode) {
    297    offsetLeft += el.offsetLeft;
    298    offsetTop += el.offsetTop;
    299    el = el.offsetParent;
    300  }
    301 
    302  // Also take scrolled containers into account
    303  el = node;
    304  while (el?.parentNode) {
    305    if (el.scrollTop) {
    306      offsetTop -= el.scrollTop;
    307    }
    308    if (el.scrollLeft) {
    309      offsetLeft -= el.scrollLeft;
    310    }
    311    el = el.parentNode;
    312  }
    313 
    314  // And add the potential frame offset if the node is nested
    315  let [xOffset, yOffset] = getFrameOffsets(boundaryWindow, node);
    316  xOffset += (offsetLeft + scrollX) * scale;
    317  yOffset += (offsetTop + scrollY) * scale;
    318 
    319  // Get the width and height
    320  const width = node.offsetWidth * scale;
    321  const height = node.offsetHeight * scale;
    322 
    323  return {
    324    p1: { x: xOffset, y: yOffset },
    325    p2: { x: xOffset + width, y: yOffset },
    326    p3: { x: xOffset + width, y: yOffset + height },
    327    p4: { x: xOffset, y: yOffset + height },
    328    top: yOffset,
    329    right: xOffset + width,
    330    bottom: yOffset + height,
    331    left: xOffset,
    332    width,
    333    height,
    334  };
    335 }
    336 exports.getNodeBounds = getNodeBounds;
    337 
    338 /**
    339 * Same as doing iframe.contentWindow but works with all types of container
    340 * elements that act like frames (e.g. <embed>), where 'contentWindow' isn't a
    341 * property that can be accessed.
    342 * This uses the inIDeepTreeWalker instead.
    343 *
    344 * @param {DOMNode} frame
    345 * @return {Window}
    346 */
    347 function safelyGetContentWindow(frame) {
    348  if (frame.contentWindow) {
    349    return frame.contentWindow;
    350  }
    351 
    352  const walker = Cc["@mozilla.org/inspector/deep-tree-walker;1"].createInstance(
    353    Ci.inIDeepTreeWalker
    354  );
    355  walker.showSubDocuments = true;
    356  walker.showDocumentsAsNodes = true;
    357  walker.init(frame);
    358  walker.currentNode = frame;
    359 
    360  const document = walker.nextNode();
    361  if (!document || !document.defaultView) {
    362    throw new Error("Couldn't get the content window inside frame " + frame);
    363  }
    364 
    365  return document.defaultView;
    366 }
    367 
    368 /**
    369 * Returns a frame's content offset (frame border + padding).
    370 * Note: this function shouldn't need to exist, had the platform provided a
    371 * suitable API for determining the offset between the frame's content and
    372 * its bounding client rect. Bug 626359 should provide us with such an API.
    373 *
    374 * @param {DOMNode} frame
    375 *        The frame.
    376 * @return {Array} [offsetTop, offsetLeft]
    377 *         offsetTop is the distance from the top of the frame and the top of
    378 *         the content document.
    379 *         offsetLeft is the distance from the left of the frame and the left
    380 *         of the content document.
    381 */
    382 function getFrameContentOffset(frame) {
    383  const style = safelyGetContentWindow(frame).getComputedStyle(frame);
    384 
    385  // In some cases, the computed style is null
    386  if (!style) {
    387    return [0, 0];
    388  }
    389 
    390  const paddingTop = parseInt(style.getPropertyValue("padding-top"), 10);
    391  const paddingLeft = parseInt(style.getPropertyValue("padding-left"), 10);
    392 
    393  const borderTop = parseInt(style.getPropertyValue("border-top-width"), 10);
    394  const borderLeft = parseInt(style.getPropertyValue("border-left-width"), 10);
    395 
    396  return [borderTop + paddingTop, borderLeft + paddingLeft];
    397 }
    398 
    399 /**
    400 * Check if a node and its document are still alive
    401 * and attached to the window.
    402 *
    403 * @param {DOMNode} node
    404 * @return {boolean}
    405 */
    406 function isNodeConnected(node) {
    407  if (!node.ownerDocument || !node.ownerDocument.defaultView) {
    408    return false;
    409  }
    410 
    411  try {
    412    return !(
    413      node.compareDocumentPosition(node.ownerDocument.documentElement) &
    414      node.DOCUMENT_POSITION_DISCONNECTED
    415    );
    416  } catch (e) {
    417    // "can't access dead object" error
    418    return false;
    419  }
    420 }
    421 exports.isNodeConnected = isNodeConnected;
    422 
    423 /**
    424 * Determine whether a node is a template element.
    425 *
    426 * @param {DOMNode} node
    427 * @return {boolean}
    428 */
    429 function isTemplateElement(node) {
    430  return (
    431    node.ownerGlobal && node.ownerGlobal.HTMLTemplateElement.isInstance(node)
    432  );
    433 }
    434 exports.isTemplateElement = isTemplateElement;
    435 
    436 /**
    437 * Determine whether a node is a shadow root.
    438 *
    439 * @param {DOMNode} node
    440 * @return {boolean}
    441 */
    442 const isShadowRoot = node => node.containingShadowRoot == node;
    443 exports.isShadowRoot = isShadowRoot;
    444 
    445 /**
    446 * Gets the shadow root mode (open or closed).
    447 *
    448 * @param {DOMNode} node
    449 * @return {string | null}
    450 */
    451 function getShadowRootMode(node) {
    452  return isShadowRoot(node) ? node.mode : null;
    453 }
    454 exports.getShadowRootMode = getShadowRootMode;
    455 
    456 /**
    457 * Determine whether a node is a shadow host, ie. an element that has a shadowRoot
    458 * attached to itself.
    459 *
    460 * @param {DOMNode} node
    461 * @return {boolean}
    462 */
    463 function isShadowHost(node) {
    464  const shadowRoot = node.openOrClosedShadowRoot;
    465  return shadowRoot && shadowRoot.nodeType === Node.DOCUMENT_FRAGMENT_NODE;
    466 }
    467 exports.isShadowHost = isShadowHost;
    468 
    469 /**
    470 * Determine whether a node is a child of a shadow host. Even if the element has been
    471 * assigned to a slot in the attached shadow DOM, the parent node for this element is
    472 * still considered to be the "host" element, and we need to walk them differently.
    473 *
    474 * @param {DOMNode} node
    475 * @return {boolean}
    476 */
    477 function isDirectShadowHostChild(node) {
    478  // native anonymous elements (which includes pseudo elements) are always part of the
    479  // anonymous tree.
    480  if (node.isNativeAnonymous) {
    481    return false;
    482  }
    483 
    484  const parentNode = node.parentNode;
    485  return parentNode && !!parentNode.openOrClosedShadowRoot;
    486 }
    487 exports.isDirectShadowHostChild = isDirectShadowHostChild;
    488 
    489 /**
    490 * Get the current zoom factor applied to the container window of a given node.
    491 *
    492 * @param {DOMNode|DOMWindow}
    493 *        The node for which the zoom factor should be calculated, or its
    494 *        owner window.
    495 * @return {number}
    496 */
    497 function getCurrentZoom(node) {
    498  const win = getWindowFor(node);
    499 
    500  if (!win) {
    501    throw new Error("Unable to get the zoom from the given argument.");
    502  }
    503 
    504  return win.browsingContext?.fullZoom || 1.0;
    505 }
    506 exports.getCurrentZoom = getCurrentZoom;
    507 
    508 /**
    509 * Get the display pixel ratio for a given window.
    510 * The `devicePixelRatio` property is affected by the zoom (see bug 809788), so we have to
    511 * divide by the zoom value in order to get just the display density, expressed as pixel
    512 * ratio (the physical display pixel compares to a pixel on a “normal” density screen).
    513 *
    514 * @param {DOMNode|DOMWindow}
    515 *        The node for which the zoom factor should be calculated, or its
    516 *        owner window.
    517 * @return {number}
    518 */
    519 function getDisplayPixelRatio(node) {
    520  const win = getWindowFor(node);
    521  return win.devicePixelRatio / getCurrentZoom(node);
    522 }
    523 exports.getDisplayPixelRatio = getDisplayPixelRatio;
    524 
    525 /**
    526 * Returns the window's dimensions for the `window` given.
    527 *
    528 * @return {object} An object with `width` and `height` properties, representing the
    529 * number of pixels for the document's size.
    530 */
    531 function getWindowDimensions(window) {
    532  // First we'll try without flushing layout, because it's way faster.
    533  const { windowUtils } = window;
    534  let { width, height } = windowUtils.getRootBounds();
    535 
    536  if (!width || !height) {
    537    // We need a flush after all :'(
    538    width = window.innerWidth + window.scrollMaxX - window.scrollMinX;
    539    height = window.innerHeight + window.scrollMaxY - window.scrollMinY;
    540 
    541    const scrollbarHeight = {};
    542    const scrollbarWidth = {};
    543    windowUtils.getScrollbarSize(false, scrollbarWidth, scrollbarHeight);
    544    width -= scrollbarWidth.value;
    545    height -= scrollbarHeight.value;
    546  }
    547 
    548  return { width, height };
    549 }
    550 exports.getWindowDimensions = getWindowDimensions;
    551 
    552 /**
    553 * Returns the viewport's dimensions for the `window` given.
    554 *
    555 * @return {object} An object with `width` and `height` properties, representing the
    556 * number of pixels for the viewport's size.
    557 */
    558 function getViewportDimensions(window) {
    559  const { windowUtils } = window;
    560 
    561  const scrollbarHeight = {};
    562  const scrollbarWidth = {};
    563  windowUtils.getScrollbarSize(false, scrollbarWidth, scrollbarHeight);
    564 
    565  const width = window.innerWidth - scrollbarWidth.value;
    566  const height = window.innerHeight - scrollbarHeight.value;
    567 
    568  return { width, height };
    569 }
    570 exports.getViewportDimensions = getViewportDimensions;
    571 
    572 /**
    573 * Return the default view for a given node, where node can be:
    574 * - a DOM node
    575 * - the document node
    576 * - the window itself
    577 *
    578 * @param {DOMNode|DOMWindow|DOMDocument} node The node to get the window for.
    579 * @return {DOMWindow}
    580 */
    581 function getWindowFor(node) {
    582  if (Node.isInstance(node)) {
    583    if (node.nodeType === node.DOCUMENT_NODE) {
    584      return node.defaultView;
    585    }
    586    return node.ownerDocument.defaultView;
    587  } else if (node instanceof Ci.nsIDOMWindow) {
    588    return node;
    589  }
    590  return null;
    591 }
    592 
    593 /**
    594 * Synchronously loads a style sheet from `uri` and adds it to the list of
    595 * additional style sheets of the document.
    596 * The sheets added takes effect immediately, and only on the document of the
    597 * `window` given.
    598 *
    599 * @param {DOMWindow} window
    600 * @param {string} url
    601 * @param {string} [type="agent"]
    602 */
    603 function loadSheet(window, url, type = "agent") {
    604  if (!(type in SHEET_TYPE)) {
    605    type = "agent";
    606  }
    607 
    608  const { windowUtils } = window;
    609  try {
    610    windowUtils.loadSheetUsingURIString(url, windowUtils[SHEET_TYPE[type]]);
    611  } catch (e) {
    612    // The method fails if the url is already loaded.
    613  }
    614 }
    615 exports.loadSheet = loadSheet;
    616 
    617 /**
    618 * Remove the document style sheet at `sheetURI` from the list of additional
    619 * style sheets of the document. The removal takes effect immediately.
    620 *
    621 * @param {DOMWindow} window
    622 * @param {string} url
    623 * @param {string} [type="agent"]
    624 */
    625 function removeSheet(window, url, type = "agent") {
    626  if (!(type in SHEET_TYPE)) {
    627    type = "agent";
    628  }
    629 
    630  const { windowUtils } = window;
    631  try {
    632    windowUtils.removeSheetUsingURIString(url, windowUtils[SHEET_TYPE[type]]);
    633  } catch (e) {
    634    // The method fails if the url is already removed.
    635  }
    636 }
    637 exports.removeSheet = removeSheet;
    638 
    639 /**
    640 * Get the untransformed coordinates for a node.
    641 *
    642 * @param  {DOMNode} node
    643 *         The node for which the DOMQuad is to be returned.
    644 * @param  {string} region
    645 *         The box model region to return: "content", "padding", "border" or
    646 *         "margin".
    647 * @return {DOMQuad}
    648 *         A DOMQuad representation of the node.
    649 */
    650 function getUntransformedQuad(node, region = "border") {
    651  // Get the inverse transformation matrix for the node.
    652  const matrix = node.getTransformToViewport();
    653  const inverse = matrix.inverse();
    654  const win = node.ownerGlobal;
    655 
    656  // Get the adjusted quads for the node (including scroll offsets).
    657  const quads = getAdjustedQuads(win, node, region, {
    658    ignoreZoom: true,
    659  });
    660 
    661  // Create DOMPoints from the transformed node position.
    662  const p1 = new DOMPoint(quads[0].p1.x, quads[0].p1.y);
    663  const p2 = new DOMPoint(quads[0].p2.x, quads[0].p2.y);
    664  const p3 = new DOMPoint(quads[0].p3.x, quads[0].p3.y);
    665  const p4 = new DOMPoint(quads[0].p4.x, quads[0].p4.y);
    666 
    667  // Apply the inverse transformation matrix to the points to get the
    668  // untransformed points.
    669  const ip1 = inverse.transformPoint(p1);
    670  const ip2 = inverse.transformPoint(p2);
    671  const ip3 = inverse.transformPoint(p3);
    672  const ip4 = inverse.transformPoint(p4);
    673 
    674  // Save the results in a DOMQuad.
    675  const quad = new DOMQuad(
    676    { x: ip1.x, y: ip1.y },
    677    { x: ip2.x, y: ip2.y },
    678    { x: ip3.x, y: ip3.y },
    679    { x: ip4.x, y: ip4.y }
    680  );
    681 
    682  // Remove the border offsets because we include them when calculating
    683  // offsets in the while loop.
    684  const style = win.getComputedStyle(node);
    685  const leftAdjustment = parseInt(style.borderLeftWidth, 10) || 0;
    686  const topAdjustment = parseInt(style.borderTopWidth, 10) || 0;
    687 
    688  quad.p1.x -= leftAdjustment;
    689  quad.p2.x -= leftAdjustment;
    690  quad.p3.x -= leftAdjustment;
    691  quad.p4.x -= leftAdjustment;
    692  quad.p1.y -= topAdjustment;
    693  quad.p2.y -= topAdjustment;
    694  quad.p3.y -= topAdjustment;
    695  quad.p4.y -= topAdjustment;
    696 
    697  // Calculate offsets.
    698  while (node) {
    699    const nodeStyle = win.getComputedStyle(node);
    700    const borderLeftWidth = parseInt(nodeStyle.borderLeftWidth, 10) || 0;
    701    const borderTopWidth = parseInt(nodeStyle.borderTopWidth, 10) || 0;
    702    const leftOffset = node.offsetLeft - node.scrollLeft + borderLeftWidth;
    703    const topOffset = node.offsetTop - node.scrollTop + borderTopWidth;
    704 
    705    quad.p1.x += leftOffset;
    706    quad.p2.x += leftOffset;
    707    quad.p3.x += leftOffset;
    708    quad.p4.x += leftOffset;
    709    quad.p1.y += topOffset;
    710    quad.p2.y += topOffset;
    711    quad.p3.y += topOffset;
    712    quad.p4.y += topOffset;
    713 
    714    node = node.offsetParent;
    715  }
    716 
    717  return quad;
    718 }
    719 exports.getUntransformedQuad = getUntransformedQuad;
    720 
    721 /**
    722 * Calculate the total of the node and all of its ancestor's scrollTop and
    723 * scrollLeft values.
    724 *
    725 * @param  {DOMNode} node
    726 *         The node for which the absolute scroll offsets should be calculated.
    727 * @return {object} object
    728 *         An object containing scrollTop and scrollLeft values.
    729 * @return {number} object.scrollLeft
    730 *         The total scrollLeft values of the node and all of its ancestors.
    731 * @return {number} object.scrollTop
    732 *         The total scrollTop values of the node and all of its ancestors.
    733 */
    734 function getAbsoluteScrollOffsetsForNode(node) {
    735  const doc = node.ownerDocument;
    736 
    737  // Our walker will only iterate up to document.body so we start by saving the
    738  // scroll values for `document.documentElement`.
    739  let scrollTop = doc.documentElement.scrollTop;
    740  let scrollLeft = doc.documentElement.scrollLeft;
    741  const walker = doc.createTreeWalker(doc.body, NodeFilter.SHOW_ELEMENT);
    742  walker.currentNode = node;
    743  let currentNode = walker.currentNode;
    744 
    745  // Iterate from `node` up the tree to `document.body` adding scroll offsets
    746  // as we go.
    747  while (currentNode) {
    748    const nodeScrollTop = currentNode.scrollTop;
    749    const nodeScrollLeft = currentNode.scrollLeft;
    750 
    751    if (nodeScrollTop || nodeScrollLeft) {
    752      scrollTop += nodeScrollTop;
    753      scrollLeft += nodeScrollLeft;
    754    }
    755 
    756    currentNode = walker.parentNode();
    757  }
    758 
    759  return {
    760    scrollLeft,
    761    scrollTop,
    762  };
    763 }
    764 exports.getAbsoluteScrollOffsetsForNode = getAbsoluteScrollOffsetsForNode;
    765 
    766 /**
    767 * Check if the provided node is a <frame> or <iframe> element.
    768 *
    769 * @param {DOMNode} node
    770 * @returns {boolean}
    771 */
    772 function isFrame(node) {
    773  const className = ChromeUtils.getClassName(node);
    774  return className == "HTMLIFrameElement" || className == "HTMLFrameElement";
    775 }
    776 
    777 /**
    778 * Check if the provided node is representing a remote <browser> element.
    779 *
    780 * @param  {DOMNode} node
    781 * @return {boolean}
    782 */
    783 function isRemoteBrowserElement(node) {
    784  return (
    785    ChromeUtils.getClassName(node) == "XULFrameElement" &&
    786    !node.childNodes.length &&
    787    node.getAttribute("remote") == "true"
    788  );
    789 }
    790 exports.isRemoteBrowserElement = isRemoteBrowserElement;
    791 
    792 /**
    793 * Check if the provided node is representing a remote frame.
    794 *
    795 * - In the context of the browser toolbox, a remote frame can be the <browser remote>
    796 * element found inside each tab.
    797 * - In the context of the content toolbox, a remote frame can be a <iframe> that contains
    798 * a different origin document.
    799 *
    800 * @param  {DOMNode} node
    801 * @return {boolean}
    802 */
    803 function isRemoteFrame(node) {
    804  if (isFrame(node)) {
    805    return node.frameLoader?.isRemoteFrame;
    806  }
    807 
    808  if (isRemoteBrowserElement(node)) {
    809    return true;
    810  }
    811 
    812  return false;
    813 }
    814 exports.isRemoteFrame = isRemoteFrame;
    815 
    816 /**
    817 * Check if the provided node is representing a frame that has its own dedicated child target.
    818 *
    819 * @param {BrowsingContextTargetActor} targetActor
    820 * @param {DOMNode} node
    821 * @returns {boolean}
    822 */
    823 function isFrameWithChildTarget(targetActor, node) {
    824  // If the iframe is blocked because of CSP, it won't have a document (and no associated targets)
    825  if (isFrameBlockedByCSP(node)) {
    826    return false;
    827  }
    828 
    829  return isRemoteFrame(node) || (isFrame(node) && targetActor.ignoreSubFrames);
    830 }
    831 
    832 exports.isFrameWithChildTarget = isFrameWithChildTarget;
    833 
    834 /**
    835 * Check if the provided node is representing a frame that is blocked by CSP.
    836 *
    837 * @param {DOMNode} node
    838 * @returns {boolean}
    839 */
    840 function isFrameBlockedByCSP(node) {
    841  if (!isFrame(node)) {
    842    return false;
    843  }
    844 
    845  if (!node.src) {
    846    return false;
    847  }
    848 
    849  let uri;
    850  try {
    851    uri = lazy.NetUtil.newURI(node.src);
    852  } catch (e) {
    853    return false;
    854  }
    855 
    856  const res = node.ownerDocument.policyContainer.csp.shouldLoad(
    857    Ci.nsIContentPolicy.TYPE_SUBDOCUMENT,
    858    null, // nsICSPEventListener
    859    null, // nsILoadInfo
    860    uri,
    861    null, // aOriginalURIIfRedirect
    862    false // aSendViolationReports
    863  );
    864 
    865  return res !== Ci.nsIContentPolicy.ACCEPT;
    866 }
    867 
    868 exports.isFrameBlockedByCSP = isFrameBlockedByCSP;