Backed out 2 changesets (bug 1943998) for causing wd failures @ phases.py CLOSED...
[gecko.git] / devtools / shared / layout / utils.js
blob06cf9b02fe45c349c0abbafe4a5f1db04185df2a
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(
14 lazy,
16 NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
18 { global: "contextual" }
21 const SHEET_TYPE = {
22 agent: "AGENT_SHEET",
23 user: "USER_SHEET",
24 author: "AUTHOR_SHEET",
27 // eslint-disable-next-line no-unused-vars
28 loader.lazyRequireGetter(
29 this,
30 "setIgnoreLayoutChanges",
31 "resource://devtools/server/actors/reflow.js",
32 true
34 exports.setIgnoreLayoutChanges = (...args) =>
35 this.setIgnoreLayoutChanges(...args);
37 /**
38 * Check a window is part of the boundary window given.
40 * @param {DOMWindow} boundaryWindow
41 * @param {DOMWindow} win
42 * @return {Boolean}
44 function isWindowIncluded(boundaryWindow, win) {
45 if (win === boundaryWindow) {
46 return true;
49 const parent = win.parent;
51 if (!parent || parent === win) {
52 return false;
55 return isWindowIncluded(boundaryWindow, parent);
57 exports.isWindowIncluded = isWindowIncluded;
59 /**
60 * like win.frameElement, but goes through mozbrowsers and mozapps iframes.
62 * @param {DOMWindow} win
63 * The window to get the frame for
64 * @return {DOMNode}
65 * The element in which the window is embedded.
67 const getFrameElement = win => {
68 const isTopWindow = win && DevToolsUtils.getTopWindow(win) === win;
69 return isTopWindow ? null : win.browsingContext.embedderElement;
71 exports.getFrameElement = getFrameElement;
73 /**
74 * Get the x/y offsets for of all the parent frames of a given node, limited to
75 * the boundary window given.
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]
85 function getFrameOffsets(boundaryWindow, node) {
86 let xOffset = 0;
87 let yOffset = 0;
89 let frameWin = getWindowFor(node);
90 const scale = getCurrentZoom(node);
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.");
98 while (frameWin !== boundaryWindow) {
99 const frameElement = getFrameElement(frameWin);
100 if (!frameElement) {
101 break;
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();
109 const [offsetTop, offsetLeft] = getFrameContentOffset(frameElement);
111 xOffset += frameRect.left + offsetLeft;
112 yOffset += frameRect.top + offsetTop;
114 frameWin = frameWin.parent;
117 return [xOffset * scale, yOffset * scale];
119 exports.getFrameOffsets = getFrameOffsets;
122 * Get box quads adjusted for iframes and zoom level.
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.
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.
144 function getAdjustedQuads(
145 boundaryWindow,
146 node,
147 region,
148 { ignoreZoom, ignoreScroll } = {}
150 if (!node || !node.getBoxQuads) {
151 return [];
154 const quads = node.getBoxQuads({
155 box: region,
156 relativeTo: boundaryWindow.document,
157 createFramesForSuppressedWhitespace: false,
160 if (!quads.length) {
161 return [];
164 const scale = ignoreZoom ? 1 : getCurrentZoom(node);
165 const { scrollX, scrollY } = ignoreScroll
166 ? { scrollX: 0, scrollY: 0 }
167 : boundaryWindow;
169 const xOffset = scrollX * scale;
170 const yOffset = scrollY * scale;
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,
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,
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,
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,
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,
213 return adjustedQuads;
215 exports.getAdjustedQuads = getAdjustedQuads;
218 * Compute the absolute position and the dimensions of a node, relativalely
219 * to the root window.
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
231 function getRect(boundaryWindow, node, contentWindow) {
232 let frameWin = node.ownerDocument.defaultView;
233 const clientRect = node.getBoundingClientRect();
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.");
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,
250 // We iterate through all the parent windows.
251 while (frameWin !== boundaryWindow) {
252 const frameElement = getFrameElement(frameWin);
253 if (!frameElement) {
254 break;
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();
262 const [offsetTop, offsetLeft] = getFrameContentOffset(frameElement);
264 rect.top += frameRect.top + offsetTop;
265 rect.left += frameRect.left + offsetLeft;
267 frameWin = frameWin.parent;
270 return rect;
272 exports.getRect = getRect;
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.
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
285 function getNodeBounds(boundaryWindow, node) {
286 if (!node) {
287 return null;
289 const { scrollX, scrollY } = boundaryWindow;
290 const scale = getCurrentZoom(node);
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;
302 // Also take scrolled containers into account
303 el = node;
304 while (el?.parentNode) {
305 if (el.scrollTop) {
306 offsetTop -= el.scrollTop;
308 if (el.scrollLeft) {
309 offsetLeft -= el.scrollLeft;
311 el = el.parentNode;
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;
319 // Get the width and height
320 const width = node.offsetWidth * scale;
321 const height = node.offsetHeight * scale;
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,
336 exports.getNodeBounds = getNodeBounds;
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 * @param {DOMNode} frame
344 * @return {Window}
346 function safelyGetContentWindow(frame) {
347 if (frame.contentWindow) {
348 return frame.contentWindow;
351 const walker = Cc["@mozilla.org/inspector/deep-tree-walker;1"].createInstance(
352 Ci.inIDeepTreeWalker
354 walker.showSubDocuments = true;
355 walker.showDocumentsAsNodes = true;
356 walker.init(frame);
357 walker.currentNode = frame;
359 const document = walker.nextNode();
360 if (!document || !document.defaultView) {
361 throw new Error("Couldn't get the content window inside frame " + frame);
364 return document.defaultView;
368 * Returns a frame's content offset (frame border + padding).
369 * Note: this function shouldn't need to exist, had the platform provided a
370 * suitable API for determining the offset between the frame's content and
371 * its bounding client rect. Bug 626359 should provide us with such an API.
373 * @param {DOMNode} frame
374 * The frame.
375 * @return {Array} [offsetTop, offsetLeft]
376 * offsetTop is the distance from the top of the frame and the top of
377 * the content document.
378 * offsetLeft is the distance from the left of the frame and the left
379 * of the content document.
381 function getFrameContentOffset(frame) {
382 const style = safelyGetContentWindow(frame).getComputedStyle(frame);
384 // In some cases, the computed style is null
385 if (!style) {
386 return [0, 0];
389 const paddingTop = parseInt(style.getPropertyValue("padding-top"), 10);
390 const paddingLeft = parseInt(style.getPropertyValue("padding-left"), 10);
392 const borderTop = parseInt(style.getPropertyValue("border-top-width"), 10);
393 const borderLeft = parseInt(style.getPropertyValue("border-left-width"), 10);
395 return [borderTop + paddingTop, borderLeft + paddingLeft];
399 * Check if a node and its document are still alive
400 * and attached to the window.
402 * @param {DOMNode} node
403 * @return {Boolean}
405 function isNodeConnected(node) {
406 if (!node.ownerDocument || !node.ownerDocument.defaultView) {
407 return false;
410 try {
411 return !(
412 node.compareDocumentPosition(node.ownerDocument.documentElement) &
413 node.DOCUMENT_POSITION_DISCONNECTED
415 } catch (e) {
416 // "can't access dead object" error
417 return false;
420 exports.isNodeConnected = isNodeConnected;
423 * Determine whether a node is anonymous.
425 * @param {DOMNode} node
426 * @return {Boolean}
428 * FIXME(bug 1597411): Remove one of these (or both, as
429 * `node.isNativeAnonymous` is quite clear).
431 const isAnonymous = node => node.isNativeAnonymous;
432 exports.isAnonymous = isAnonymous;
433 exports.isNativeAnonymous = isAnonymous;
436 * Determine whether a node is a template element.
438 * @param {DOMNode} node
439 * @return {Boolean}
441 function isTemplateElement(node) {
442 return (
443 node.ownerGlobal && node.ownerGlobal.HTMLTemplateElement.isInstance(node)
446 exports.isTemplateElement = isTemplateElement;
449 * Determine whether a node is a shadow root.
451 * @param {DOMNode} node
452 * @return {Boolean}
454 const isShadowRoot = node => node.containingShadowRoot == node;
455 exports.isShadowRoot = isShadowRoot;
458 * Gets the shadow root mode (open or closed).
460 * @param {DOMNode} node
461 * @return {String|null}
463 function getShadowRootMode(node) {
464 return isShadowRoot(node) ? node.mode : null;
466 exports.getShadowRootMode = getShadowRootMode;
469 * Determine whether a node is a shadow host, ie. an element that has a shadowRoot
470 * attached to itself.
472 * @param {DOMNode} node
473 * @return {Boolean}
475 function isShadowHost(node) {
476 const shadowRoot = node.openOrClosedShadowRoot;
477 return shadowRoot && shadowRoot.nodeType === Node.DOCUMENT_FRAGMENT_NODE;
479 exports.isShadowHost = isShadowHost;
482 * Determine whether a node is a child of a shadow host. Even if the element has been
483 * assigned to a slot in the attached shadow DOM, the parent node for this element is
484 * still considered to be the "host" element, and we need to walk them differently.
486 * @param {DOMNode} node
487 * @return {Boolean}
489 function isDirectShadowHostChild(node) {
490 // Pseudo elements and native anonymous elements are always part of the anonymous tree.
491 if (
492 isMarkerPseudoElement(node) ||
493 isBeforePseudoElement(node) ||
494 isAfterPseudoElement(node) ||
495 node.isNativeAnonymous
497 return false;
500 const parentNode = node.parentNode;
501 return parentNode && !!parentNode.openOrClosedShadowRoot;
503 exports.isDirectShadowHostChild = isDirectShadowHostChild;
506 * Determine whether a node is a ::marker pseudo.
508 * @param {DOMNode} node
509 * @return {Boolean}
511 function isMarkerPseudoElement(node) {
512 return node.nodeName === "_moz_generated_content_marker";
514 exports.isMarkerPseudoElement = isMarkerPseudoElement;
517 * Determine whether a node is a ::before pseudo.
519 * @param {DOMNode} node
520 * @return {Boolean}
522 function isBeforePseudoElement(node) {
523 return node.nodeName === "_moz_generated_content_before";
525 exports.isBeforePseudoElement = isBeforePseudoElement;
528 * Determine whether a node is a ::after pseudo.
530 * @param {DOMNode} node
531 * @return {Boolean}
533 function isAfterPseudoElement(node) {
534 return node.nodeName === "_moz_generated_content_after";
536 exports.isAfterPseudoElement = isAfterPseudoElement;
539 * Get the current zoom factor applied to the container window of a given node.
540 * @param {DOMNode|DOMWindow}
541 * The node for which the zoom factor should be calculated, or its
542 * owner window.
543 * @return {Number}
545 function getCurrentZoom(node) {
546 const win = getWindowFor(node);
548 if (!win) {
549 throw new Error("Unable to get the zoom from the given argument.");
552 return win.browsingContext?.fullZoom || 1.0;
554 exports.getCurrentZoom = getCurrentZoom;
557 * Get the display pixel ratio for a given window.
558 * The `devicePixelRatio` property is affected by the zoom (see bug 809788), so we have to
559 * divide by the zoom value in order to get just the display density, expressed as pixel
560 * ratio (the physical display pixel compares to a pixel on a “normal” density screen).
562 * @param {DOMNode|DOMWindow}
563 * The node for which the zoom factor should be calculated, or its
564 * owner window.
565 * @return {Number}
567 function getDisplayPixelRatio(node) {
568 const win = getWindowFor(node);
569 return win.devicePixelRatio / getCurrentZoom(node);
571 exports.getDisplayPixelRatio = getDisplayPixelRatio;
574 * Returns the window's dimensions for the `window` given.
576 * @return {Object} An object with `width` and `height` properties, representing the
577 * number of pixels for the document's size.
579 function getWindowDimensions(window) {
580 // First we'll try without flushing layout, because it's way faster.
581 const { windowUtils } = window;
582 let { width, height } = windowUtils.getRootBounds();
584 if (!width || !height) {
585 // We need a flush after all :'(
586 width = window.innerWidth + window.scrollMaxX - window.scrollMinX;
587 height = window.innerHeight + window.scrollMaxY - window.scrollMinY;
589 const scrollbarHeight = {};
590 const scrollbarWidth = {};
591 windowUtils.getScrollbarSize(false, scrollbarWidth, scrollbarHeight);
592 width -= scrollbarWidth.value;
593 height -= scrollbarHeight.value;
596 return { width, height };
598 exports.getWindowDimensions = getWindowDimensions;
601 * Returns the viewport's dimensions for the `window` given.
603 * @return {Object} An object with `width` and `height` properties, representing the
604 * number of pixels for the viewport's size.
606 function getViewportDimensions(window) {
607 const { windowUtils } = window;
609 const scrollbarHeight = {};
610 const scrollbarWidth = {};
611 windowUtils.getScrollbarSize(false, scrollbarWidth, scrollbarHeight);
613 const width = window.innerWidth - scrollbarWidth.value;
614 const height = window.innerHeight - scrollbarHeight.value;
616 return { width, height };
618 exports.getViewportDimensions = getViewportDimensions;
621 * Return the default view for a given node, where node can be:
622 * - a DOM node
623 * - the document node
624 * - the window itself
625 * @param {DOMNode|DOMWindow|DOMDocument} node The node to get the window for.
626 * @return {DOMWindow}
628 function getWindowFor(node) {
629 if (Node.isInstance(node)) {
630 if (node.nodeType === node.DOCUMENT_NODE) {
631 return node.defaultView;
633 return node.ownerDocument.defaultView;
634 } else if (node instanceof Ci.nsIDOMWindow) {
635 return node;
637 return null;
641 * Synchronously loads a style sheet from `uri` and adds it to the list of
642 * additional style sheets of the document.
643 * The sheets added takes effect immediately, and only on the document of the
644 * `window` given.
646 * @param {DOMWindow} window
647 * @param {String} url
648 * @param {String} [type="agent"]
650 function loadSheet(window, url, type = "agent") {
651 if (!(type in SHEET_TYPE)) {
652 type = "agent";
655 const { windowUtils } = window;
656 try {
657 windowUtils.loadSheetUsingURIString(url, windowUtils[SHEET_TYPE[type]]);
658 } catch (e) {
659 // The method fails if the url is already loaded.
662 exports.loadSheet = loadSheet;
665 * Remove the document style sheet at `sheetURI` from the list of additional
666 * style sheets of the document. The removal takes effect immediately.
668 * @param {DOMWindow} window
669 * @param {String} url
670 * @param {String} [type="agent"]
672 function removeSheet(window, url, type = "agent") {
673 if (!(type in SHEET_TYPE)) {
674 type = "agent";
677 const { windowUtils } = window;
678 try {
679 windowUtils.removeSheetUsingURIString(url, windowUtils[SHEET_TYPE[type]]);
680 } catch (e) {
681 // The method fails if the url is already removed.
684 exports.removeSheet = removeSheet;
687 * Get the untransformed coordinates for a node.
689 * @param {DOMNode} node
690 * The node for which the DOMQuad is to be returned.
691 * @param {String} region
692 * The box model region to return: "content", "padding", "border" or
693 * "margin".
694 * @return {DOMQuad}
695 * A DOMQuad representation of the node.
697 function getUntransformedQuad(node, region = "border") {
698 // Get the inverse transformation matrix for the node.
699 const matrix = node.getTransformToViewport();
700 const inverse = matrix.inverse();
701 const win = node.ownerGlobal;
703 // Get the adjusted quads for the node (including scroll offsets).
704 const quads = getAdjustedQuads(win, node, region, {
705 ignoreZoom: true,
708 // Create DOMPoints from the transformed node position.
709 const p1 = new DOMPoint(quads[0].p1.x, quads[0].p1.y);
710 const p2 = new DOMPoint(quads[0].p2.x, quads[0].p2.y);
711 const p3 = new DOMPoint(quads[0].p3.x, quads[0].p3.y);
712 const p4 = new DOMPoint(quads[0].p4.x, quads[0].p4.y);
714 // Apply the inverse transformation matrix to the points to get the
715 // untransformed points.
716 const ip1 = inverse.transformPoint(p1);
717 const ip2 = inverse.transformPoint(p2);
718 const ip3 = inverse.transformPoint(p3);
719 const ip4 = inverse.transformPoint(p4);
721 // Save the results in a DOMQuad.
722 const quad = new DOMQuad(
723 { x: ip1.x, y: ip1.y },
724 { x: ip2.x, y: ip2.y },
725 { x: ip3.x, y: ip3.y },
726 { x: ip4.x, y: ip4.y }
729 // Remove the border offsets because we include them when calculating
730 // offsets in the while loop.
731 const style = win.getComputedStyle(node);
732 const leftAdjustment = parseInt(style.borderLeftWidth, 10) || 0;
733 const topAdjustment = parseInt(style.borderTopWidth, 10) || 0;
735 quad.p1.x -= leftAdjustment;
736 quad.p2.x -= leftAdjustment;
737 quad.p3.x -= leftAdjustment;
738 quad.p4.x -= leftAdjustment;
739 quad.p1.y -= topAdjustment;
740 quad.p2.y -= topAdjustment;
741 quad.p3.y -= topAdjustment;
742 quad.p4.y -= topAdjustment;
744 // Calculate offsets.
745 while (node) {
746 const nodeStyle = win.getComputedStyle(node);
747 const borderLeftWidth = parseInt(nodeStyle.borderLeftWidth, 10) || 0;
748 const borderTopWidth = parseInt(nodeStyle.borderTopWidth, 10) || 0;
749 const leftOffset = node.offsetLeft - node.scrollLeft + borderLeftWidth;
750 const topOffset = node.offsetTop - node.scrollTop + borderTopWidth;
752 quad.p1.x += leftOffset;
753 quad.p2.x += leftOffset;
754 quad.p3.x += leftOffset;
755 quad.p4.x += leftOffset;
756 quad.p1.y += topOffset;
757 quad.p2.y += topOffset;
758 quad.p3.y += topOffset;
759 quad.p4.y += topOffset;
761 node = node.offsetParent;
764 return quad;
766 exports.getUntransformedQuad = getUntransformedQuad;
769 * Calculate the total of the node and all of its ancestor's scrollTop and
770 * scrollLeft values.
772 * @param {DOMNode} node
773 * The node for which the absolute scroll offsets should be calculated.
774 * @return {Object} object
775 * An object containing scrollTop and scrollLeft values.
776 * @return {Number} object.scrollLeft
777 * The total scrollLeft values of the node and all of its ancestors.
778 * @return {Number} object.scrollTop
779 * The total scrollTop values of the node and all of its ancestors.
781 function getAbsoluteScrollOffsetsForNode(node) {
782 const doc = node.ownerDocument;
784 // Our walker will only iterate up to document.body so we start by saving the
785 // scroll values for `document.documentElement`.
786 let scrollTop = doc.documentElement.scrollTop;
787 let scrollLeft = doc.documentElement.scrollLeft;
788 const walker = doc.createTreeWalker(doc.body, NodeFilter.SHOW_ELEMENT);
789 walker.currentNode = node;
790 let currentNode = walker.currentNode;
792 // Iterate from `node` up the tree to `document.body` adding scroll offsets
793 // as we go.
794 while (currentNode) {
795 const nodeScrollTop = currentNode.scrollTop;
796 const nodeScrollLeft = currentNode.scrollLeft;
798 if (nodeScrollTop || nodeScrollLeft) {
799 scrollTop += nodeScrollTop;
800 scrollLeft += nodeScrollLeft;
803 currentNode = walker.parentNode();
806 return {
807 scrollLeft,
808 scrollTop,
811 exports.getAbsoluteScrollOffsetsForNode = getAbsoluteScrollOffsetsForNode;
814 * Check if the provided node is a <frame> or <iframe> element.
816 * @param {DOMNode} node
817 * @returns {Boolean}
819 function isFrame(node) {
820 const className = ChromeUtils.getClassName(node);
821 return className == "HTMLIFrameElement" || className == "HTMLFrameElement";
825 * Check if the provided node is representing a remote <browser> element.
827 * @param {DOMNode} node
828 * @return {Boolean}
830 function isRemoteBrowserElement(node) {
831 return (
832 ChromeUtils.getClassName(node) == "XULFrameElement" &&
833 !node.childNodes.length &&
834 node.getAttribute("remote") == "true"
837 exports.isRemoteBrowserElement = isRemoteBrowserElement;
840 * Check if the provided node is representing a remote frame.
842 * - In the context of the browser toolbox, a remote frame can be the <browser remote>
843 * element found inside each tab.
844 * - In the context of the content toolbox, a remote frame can be a <iframe> that contains
845 * a different origin document.
847 * @param {DOMNode} node
848 * @return {Boolean}
850 function isRemoteFrame(node) {
851 if (isFrame(node)) {
852 return node.frameLoader?.isRemoteFrame;
855 if (isRemoteBrowserElement(node)) {
856 return true;
859 return false;
861 exports.isRemoteFrame = isRemoteFrame;
864 * Check if the provided node is representing a frame that has its own dedicated child target.
866 * @param {BrowsingContextTargetActor} targetActor
867 * @param {DOMNode} node
868 * @returns {Boolean}
870 function isFrameWithChildTarget(targetActor, node) {
871 // If the iframe is blocked because of CSP, it won't have a document (and no associated targets)
872 if (isFrameBlockedByCSP(node)) {
873 return false;
876 return isRemoteFrame(node) || (isFrame(node) && targetActor.ignoreSubFrames);
879 exports.isFrameWithChildTarget = isFrameWithChildTarget;
882 * Check if the provided node is representing a frame that is blocked by CSP.
884 * @param {DOMNode} node
885 * @returns {Boolean}
887 function isFrameBlockedByCSP(node) {
888 if (!isFrame(node)) {
889 return false;
892 if (!node.src) {
893 return false;
896 let uri;
897 try {
898 uri = lazy.NetUtil.newURI(node.src);
899 } catch (e) {
900 return false;
903 const res = node.ownerDocument.csp.shouldLoad(
904 Ci.nsIContentPolicy.TYPE_SUBDOCUMENT,
905 null, // nsICSPEventListener
906 null, // nsILoadInfo
907 uri,
908 null, // aOriginalURIIfRedirect
909 false // aSendViolationReports
912 return res !== Ci.nsIContentPolicy.ACCEPT;
915 exports.isFrameBlockedByCSP = isFrameBlockedByCSP;