mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
feat[React Native]: bump React DevTools to 5.0.0 and support highlighting multiple elements (#41842)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/41842 Changelog: [Internal] - bumps `react-devtools-*` packages to 5.0.0 across xplat - added support for highlighting multiple host components, when hovering over component, which is represented by multiple host fibers. See test plan, this can be reproduced with a named component, which renders multiple host components inside a React Fragment: ``` <> <View /> <View /> <View /> </> ``` Reviewed By: gsathya Differential Revision: D51888628 fbshipit-source-id: 2bd2d9fa50c24f478aa9406ee6bb42a47168bf13
This commit is contained in:
committed by
Facebook GitHub Bot
parent
9d846f47c2
commit
790d7fd24e
@@ -22,7 +22,10 @@ import type {
|
||||
ReactDevToolsAgentEvents,
|
||||
ReactDevToolsGlobalHook,
|
||||
} from '../Types/ReactDevToolsTypes';
|
||||
import type {TraceUpdate} from './DebuggingOverlayNativeComponent';
|
||||
import type {
|
||||
ElementRectangle,
|
||||
TraceUpdate,
|
||||
} from './DebuggingOverlayNativeComponent';
|
||||
|
||||
import {
|
||||
findNodeHandle,
|
||||
@@ -90,9 +93,9 @@ class DebuggingOverlayRegistry {
|
||||
agent.addListener('hideNativeHighlight', this.#onClearElementsHighlights);
|
||||
};
|
||||
|
||||
#getPublicInstanceFromInstance(
|
||||
#getPublicInstanceFromInstance = (
|
||||
instanceHandle: InstanceFromReactDevTools,
|
||||
): NativeMethods | null {
|
||||
): NativeMethods | null => {
|
||||
// `canonical.publicInstance` => Fabric
|
||||
if (instanceHandle.canonical?.publicInstance != null) {
|
||||
return instanceHandle.canonical?.publicInstance;
|
||||
@@ -111,7 +114,7 @@ class DebuggingOverlayRegistry {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
#findLowestParentFromRegistryForInstance(
|
||||
instance: ReactNativeElement,
|
||||
@@ -360,34 +363,63 @@ class DebuggingOverlayRegistry {
|
||||
|
||||
#onHighlightElements: (
|
||||
...ReactDevToolsAgentEvents['showNativeHighlight']
|
||||
) => void = node => {
|
||||
) => void = nodes => {
|
||||
// First clear highlights for every container
|
||||
for (const subscriber of this.#registry) {
|
||||
subscriber.debuggingOverlayRef.current?.clearElementsHighlight();
|
||||
}
|
||||
|
||||
const publicInstance = this.#getPublicInstanceFromInstance(node);
|
||||
if (publicInstance == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Lazy import to avoid dependency cycle.
|
||||
const ReactNativeElementClass =
|
||||
require('../DOM/Nodes/ReactNativeElement').default;
|
||||
if (publicInstance instanceof ReactNativeElementClass) {
|
||||
this.#onHighlightElementsModern(publicInstance);
|
||||
} else {
|
||||
this.#onHighlightElementsLegacy(publicInstance);
|
||||
|
||||
const reactNativeElements: Array<ReactNativeElement> = [];
|
||||
const legacyPublicInstances: Array<NativeMethods> = [];
|
||||
|
||||
for (const node of nodes) {
|
||||
const publicInstance = this.#getPublicInstanceFromInstance(node);
|
||||
if (publicInstance == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (publicInstance instanceof ReactNativeElementClass) {
|
||||
reactNativeElements.push(publicInstance);
|
||||
} else {
|
||||
legacyPublicInstances.push(publicInstance);
|
||||
}
|
||||
}
|
||||
|
||||
if (reactNativeElements.length > 0) {
|
||||
this.#onHighlightElementsModern(reactNativeElements);
|
||||
}
|
||||
|
||||
if (legacyPublicInstances.length > 0) {
|
||||
this.#onHighlightElementsLegacy(legacyPublicInstances);
|
||||
}
|
||||
};
|
||||
|
||||
#onHighlightElementsModern(publicInstance: ReactNativeElement): void {
|
||||
const {x, y, width, height} = publicInstance.getBoundingClientRect();
|
||||
#onHighlightElementsModern(elements: Array<ReactNativeElement>): void {
|
||||
const parentToElementsMap = new Map<
|
||||
DebuggingOverlayRegistrySubscriberProtocol,
|
||||
Array<ReactNativeElement>,
|
||||
>();
|
||||
|
||||
const parent =
|
||||
this.#findLowestParentFromRegistryForInstance(publicInstance);
|
||||
for (const element of elements) {
|
||||
const parent = this.#findLowestParentFromRegistryForInstance(element);
|
||||
if (parent == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (parent) {
|
||||
let childElementOfAParent = parentToElementsMap.get(parent);
|
||||
if (childElementOfAParent == null) {
|
||||
childElementOfAParent = [];
|
||||
parentToElementsMap.set(parent, childElementOfAParent);
|
||||
}
|
||||
|
||||
childElementOfAParent.push(element);
|
||||
}
|
||||
|
||||
for (const [parent, elementsToHighlight] of parentToElementsMap.entries()) {
|
||||
const rootViewInstance = parent.rootViewRef.current;
|
||||
if (rootViewInstance == null) {
|
||||
return;
|
||||
@@ -400,28 +432,70 @@ class DebuggingOverlayRegistry {
|
||||
// DebuggingOverlay will scale to the same size as a Root view. Substract Root view position from the element position
|
||||
// to calculate the element's position relatively to its parent DebuggingOverlay.
|
||||
// We can't call `getBoundingClientRect` on the debuggingOverlayRef, because its a ref for the native component, which doesn't have it, hopefully yet.
|
||||
parent.debuggingOverlayRef.current?.highlightElements([
|
||||
{x: x - parentX, y: y - parentY, width, height},
|
||||
]);
|
||||
const elementsRectangles = elementsToHighlight.map(element => {
|
||||
const {x, y, width, height} = element.getBoundingClientRect();
|
||||
return {x: x - parentX, y: y - parentY, width, height};
|
||||
});
|
||||
|
||||
parent.debuggingOverlayRef.current?.highlightElements(elementsRectangles);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove once DOM Node APIs are opt-in by default and Paper is no longer supported.
|
||||
#onHighlightElementsLegacy(publicInstance: NativeMethods): void {
|
||||
const container =
|
||||
this.#findLowestParentFromRegistryForInstanceLegacy(publicInstance);
|
||||
#onHighlightElementsLegacy(elements: Array<NativeMethods>): void {
|
||||
const parentToElementsMap = new Map<
|
||||
DebuggingOverlayRegistrySubscriberProtocol,
|
||||
Array<NativeMethods>,
|
||||
>();
|
||||
|
||||
if (container != null) {
|
||||
publicInstance.measure((x, y, width, height, left, top) => {
|
||||
// measure can execute callback without any values provided to signal error.
|
||||
if (left == null || top == null || width == null || height == null) {
|
||||
return;
|
||||
}
|
||||
for (const element of elements) {
|
||||
const parent =
|
||||
this.#findLowestParentFromRegistryForInstanceLegacy(element);
|
||||
if (parent == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
container.debuggingOverlayRef.current?.highlightElements([
|
||||
{x: left, y: top, width, height},
|
||||
]);
|
||||
});
|
||||
let childElementOfAParent = parentToElementsMap.get(parent);
|
||||
if (childElementOfAParent == null) {
|
||||
childElementOfAParent = [];
|
||||
parentToElementsMap.set(parent, childElementOfAParent);
|
||||
}
|
||||
|
||||
childElementOfAParent.push(element);
|
||||
}
|
||||
|
||||
for (const [parent, elementsToHighlight] of parentToElementsMap.entries()) {
|
||||
const promises = elementsToHighlight.map(
|
||||
element =>
|
||||
new Promise<ElementRectangle>((resolve, reject) => {
|
||||
element.measure((x, y, width, height, left, top) => {
|
||||
// measure can execute callback without any values provided to signal error.
|
||||
if (
|
||||
left == null ||
|
||||
top == null ||
|
||||
width == null ||
|
||||
height == null
|
||||
) {
|
||||
reject('Unexpectedly failed to call measure on an instance.');
|
||||
}
|
||||
|
||||
resolve({x: left, y: top, width, height});
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
Promise.all(promises)
|
||||
.then(resolvedElementsRectangles =>
|
||||
parent.debuggingOverlayRef.current?.highlightElements(
|
||||
resolvedElementsRectangles,
|
||||
),
|
||||
)
|
||||
.catch(() => {
|
||||
// noop. For legacy architecture (Paper) this can happen for root views or LogBox button.
|
||||
// LogBox case: it has a separate React root, so `measure` fails.
|
||||
// Calling `console.error` here would trigger rendering a new LogBox button, for which we will call measure again, this is a cycle.
|
||||
// Don't spam the UI with errors for such cases.
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ export type ReactDevToolsAgentEvents = {
|
||||
drawTraceUpdates: [Array<{node: InstanceFromReactDevTools, color: string}>],
|
||||
disableTraceUpdates: [],
|
||||
|
||||
showNativeHighlight: [node: InstanceFromReactDevTools],
|
||||
showNativeHighlight: [nodes: Array<InstanceFromReactDevTools>],
|
||||
hideNativeHighlight: [],
|
||||
shutdown: [],
|
||||
startInspectingNative: [],
|
||||
|
||||
@@ -9482,7 +9482,7 @@ export type InstanceFromReactDevTools =
|
||||
export type ReactDevToolsAgentEvents = {
|
||||
drawTraceUpdates: [Array<{ node: InstanceFromReactDevTools, color: string }>],
|
||||
disableTraceUpdates: [],
|
||||
showNativeHighlight: [node: InstanceFromReactDevTools],
|
||||
showNativeHighlight: [nodes: Array<InstanceFromReactDevTools>],
|
||||
hideNativeHighlight: [],
|
||||
shutdown: [],
|
||||
startInspectingNative: [],
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
"nullthrows": "^1.1.1",
|
||||
"pretty-format": "^26.5.2",
|
||||
"promise": "^8.3.0",
|
||||
"react-devtools-core": "^4.27.7",
|
||||
"react-devtools-core": "^5.0.0",
|
||||
"react-refresh": "^0.14.0",
|
||||
"react-shallow-renderer": "^16.15.0",
|
||||
"regenerator-runtime": "^0.13.2",
|
||||
|
||||
@@ -8100,10 +8100,10 @@ range-parser@~1.2.1:
|
||||
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
|
||||
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
|
||||
|
||||
react-devtools-core@^4.27.7:
|
||||
version "4.27.7"
|
||||
resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.27.7.tgz#458a6541483078d60a036c75bf88f54c478086ec"
|
||||
integrity sha512-12N0HrhCPbD76Z7SkyJdGdXdPGouUsgV6tlEsbSpAnLDO06tjXZP+irht4wPdYwJAJRQ85DxL48eQoz7UmrSuQ==
|
||||
react-devtools-core@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-5.0.0.tgz#50b04a4dbfa62badbe4d86529e9478c396988b31"
|
||||
integrity sha512-SAAMLacNDfFjMJjmbXURNWtrTyARi9xTqGkY48Btw5cIWlr1wgxfWYZKxoUZav1qqmhbpgTzSmmF+cpMHGHY3A==
|
||||
dependencies:
|
||||
shell-quote "^1.6.1"
|
||||
ws "^7"
|
||||
|
||||
Reference in New Issue
Block a user