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