Files
react/packages/react-devtools-shared/src/backend/views/utils.js
Sebastian Markbåge 33e54fa252 [DevTools] Rename NativeElement to HostInstance in the Bridge (#30491)
Stacked on #30490.

This is in the same spirit but to clarify the difference between what is
React Native vs part of any generic Host. We used to use "Native" to
mean three different concepts. Now "Native" just means React Native.

E.g. from the frontend's perspective the Host can be
Highlighted/Inspected. However, that in turn can then be implemented as
either direct DOM manipulation or commands to React Native. So frontend
-> backend is "Host" but backend -> React Native is "Native" while
backend -> DOM is "Web".

Rename NativeElementsPanel to BuiltinElementsPanel. This isn't a React
Native panel but one part of the surrounding DevTools. We refer to Host
more as the thing running React itself. I.e. where the backend lives.
The runtime you're inspecting. The DevTools itself needs a third term.
So I went with "Builtin".
2024-07-30 09:12:12 -04:00

141 lines
4.4 KiB
JavaScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export interface Rect {
bottom: number;
height: number;
left: number;
right: number;
top: number;
width: number;
}
// Get the window object for the document that a node belongs to,
// or return null if it cannot be found (node not attached to DOM,
// etc).
export function getOwnerWindow(node: HTMLElement): typeof window | null {
if (!node.ownerDocument) {
return null;
}
return node.ownerDocument.defaultView;
}
// Get the iframe containing a node, or return null if it cannot
// be found (node not within iframe, etc).
export function getOwnerIframe(node: HTMLElement): HTMLElement | null {
const nodeWindow = getOwnerWindow(node);
if (nodeWindow) {
return nodeWindow.frameElement;
}
return null;
}
// Get a bounding client rect for a node, with an
// offset added to compensate for its border.
export function getBoundingClientRectWithBorderOffset(node: HTMLElement): Rect {
const dimensions = getElementDimensions(node);
return mergeRectOffsets([
node.getBoundingClientRect(),
{
top: dimensions.borderTop,
left: dimensions.borderLeft,
bottom: dimensions.borderBottom,
right: dimensions.borderRight,
// This width and height won't get used by mergeRectOffsets (since this
// is not the first rect in the array), but we set them so that this
// object type checks as a ClientRect.
width: 0,
height: 0,
},
]);
}
// Add together the top, left, bottom, and right properties of
// each ClientRect, but keep the width and height of the first one.
export function mergeRectOffsets(rects: Array<Rect>): Rect {
return rects.reduce((previousRect, rect) => {
if (previousRect == null) {
return rect;
}
return {
top: previousRect.top + rect.top,
left: previousRect.left + rect.left,
width: previousRect.width,
height: previousRect.height,
bottom: previousRect.bottom + rect.bottom,
right: previousRect.right + rect.right,
};
});
}
// Calculate a boundingClientRect for a node relative to boundaryWindow,
// taking into account any offsets caused by intermediate iframes.
export function getNestedBoundingClientRect(
node: HTMLElement,
boundaryWindow: typeof window,
): Rect {
const ownerIframe = getOwnerIframe(node);
if (ownerIframe && ownerIframe !== boundaryWindow) {
const rects: Array<Rect | ClientRect> = [node.getBoundingClientRect()];
let currentIframe: null | HTMLElement = ownerIframe;
let onlyOneMore = false;
while (currentIframe) {
const rect = getBoundingClientRectWithBorderOffset(currentIframe);
rects.push(rect);
currentIframe = getOwnerIframe(currentIframe);
if (onlyOneMore) {
break;
}
// We don't want to calculate iframe offsets upwards beyond
// the iframe containing the boundaryWindow, but we
// need to calculate the offset relative to the boundaryWindow.
if (currentIframe && getOwnerWindow(currentIframe) === boundaryWindow) {
onlyOneMore = true;
}
}
return mergeRectOffsets(rects);
} else {
return node.getBoundingClientRect();
}
}
export function getElementDimensions(domElement: HTMLElement): {
borderBottom: number,
borderLeft: number,
borderRight: number,
borderTop: number,
marginBottom: number,
marginLeft: number,
marginRight: number,
marginTop: number,
paddingBottom: number,
paddingLeft: number,
paddingRight: number,
paddingTop: number,
} {
const calculatedStyle = window.getComputedStyle(domElement);
return {
borderLeft: parseInt(calculatedStyle.borderLeftWidth, 10),
borderRight: parseInt(calculatedStyle.borderRightWidth, 10),
borderTop: parseInt(calculatedStyle.borderTopWidth, 10),
borderBottom: parseInt(calculatedStyle.borderBottomWidth, 10),
marginLeft: parseInt(calculatedStyle.marginLeft, 10),
marginRight: parseInt(calculatedStyle.marginRight, 10),
marginTop: parseInt(calculatedStyle.marginTop, 10),
marginBottom: parseInt(calculatedStyle.marginBottom, 10),
paddingLeft: parseInt(calculatedStyle.paddingLeft, 10),
paddingRight: parseInt(calculatedStyle.paddingRight, 10),
paddingTop: parseInt(calculatedStyle.paddingTop, 10),
paddingBottom: parseInt(calculatedStyle.paddingBottom, 10),
};
}