mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
refactor: fork implementations in DebuggingOverlayRegistry (#41816)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/41816 Changelog: [Internal] Forking implementations for trace updates and element highlights from React DevTools: modern and legacy. Both implementations will later solve the same problem of highlighting the component only on a single AppContainer, but with different approaches: - Modern will be based on DOM Node APIs: `getBoundingClientRect` and `parentElement`. - Legacy will be based on `isChildInstance` from renderer and `measure`. All corresponding API call will be added in a separate diff later on top of these changes. Reviewed By: sammy-SC Differential Revision: D51713087 fbshipit-source-id: 1c840d711a541085b1075711f737a8dbc1e31637
This commit is contained in:
committed by
Facebook GitHub Bot
parent
5d68c908f1
commit
0300eb8c33
@@ -9,6 +9,7 @@
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import type ReactNativeElement from '../DOM/Nodes/ReactNativeElement';
|
||||
import type {
|
||||
AppContainerRootViewRef,
|
||||
DebuggingOverlayRef,
|
||||
@@ -35,6 +36,18 @@ export type DebuggingOverlayRegistrySubscriberProtocol = {
|
||||
debuggingOverlayRef: DebuggingOverlayRef,
|
||||
};
|
||||
|
||||
type ModernNodeUpdate = {
|
||||
id: number,
|
||||
instance: ReactNativeElement,
|
||||
color: string,
|
||||
};
|
||||
|
||||
type LegacyNodeUpdate = {
|
||||
id: number,
|
||||
instance: NativeMethods,
|
||||
color: string,
|
||||
};
|
||||
|
||||
class DebuggingOverlayRegistry {
|
||||
#registry: Set<DebuggingOverlayRegistrySubscriberProtocol> = new Set();
|
||||
#reactDevToolsAgent: ReactDevToolsAgent | null = null;
|
||||
@@ -99,22 +112,80 @@ class DebuggingOverlayRegistry {
|
||||
#onDrawTraceUpdates: (
|
||||
...ReactDevToolsAgentEvents['drawTraceUpdates']
|
||||
) => void = traceUpdates => {
|
||||
const promisesToResolve: Array<Promise<TraceUpdate>> = [];
|
||||
const modernNodesUpdates: Array<ModernNodeUpdate> = [];
|
||||
const legacyNodesUpdates: Array<LegacyNodeUpdate> = [];
|
||||
|
||||
traceUpdates.forEach(({node, color}) => {
|
||||
for (const {node, color} of traceUpdates) {
|
||||
const publicInstance = this.#getPublicInstanceFromInstance(node);
|
||||
|
||||
if (publicInstance == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const instanceReactTag = findNodeHandle(node);
|
||||
if (instanceReactTag == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Lazy import to avoid dependency cycle.
|
||||
const ReactNativeElementClass =
|
||||
require('../DOM/Nodes/ReactNativeElement').default;
|
||||
if (publicInstance instanceof ReactNativeElementClass) {
|
||||
modernNodesUpdates.push({
|
||||
id: instanceReactTag,
|
||||
instance: publicInstance,
|
||||
color,
|
||||
});
|
||||
} else {
|
||||
legacyNodesUpdates.push({
|
||||
id: instanceReactTag,
|
||||
instance: publicInstance,
|
||||
color,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (modernNodesUpdates.length > 0) {
|
||||
this.#drawTraceUpdatesModern(modernNodesUpdates);
|
||||
}
|
||||
|
||||
if (legacyNodesUpdates.length > 0) {
|
||||
this.#drawTraceUpdatesLegacy(legacyNodesUpdates);
|
||||
}
|
||||
};
|
||||
|
||||
#drawTraceUpdatesModern(updates: Array<ModernNodeUpdate>): void {
|
||||
const resolvedTraceUpdates: Array<TraceUpdate> = updates.map(
|
||||
({id, instance, color}) => {
|
||||
const {x, y, width, height} = instance.getBoundingClientRect();
|
||||
|
||||
return {
|
||||
id,
|
||||
rectangle: {x, y, width, height},
|
||||
color: processColor(color),
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
for (const {rootViewRef, debuggingOverlayRef} of this.#registry) {
|
||||
const rootViewReactTag = findNodeHandle(rootViewRef.current);
|
||||
if (rootViewReactTag == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
debuggingOverlayRef.current?.highlightTraceUpdates(resolvedTraceUpdates);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove once DOM Node APIs are opt-in by default and Paper is no longer supported.
|
||||
#drawTraceUpdatesLegacy(updates: Array<LegacyNodeUpdate>): void {
|
||||
const promisesToResolve: Array<Promise<TraceUpdate>> = [];
|
||||
|
||||
for (const {id, instance, color} of updates) {
|
||||
const frameToDrawPromise = new Promise<TraceUpdate>((resolve, reject) => {
|
||||
// TODO(T171095283): We should refactor this to use `getBoundingClientRect` when Paper is no longer supported.
|
||||
publicInstance.measure((x, y, width, height, left, top) => {
|
||||
const id = findNodeHandle(node);
|
||||
if (id == null) {
|
||||
reject();
|
||||
return;
|
||||
instance.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({
|
||||
@@ -126,21 +197,28 @@ class DebuggingOverlayRegistry {
|
||||
});
|
||||
|
||||
promisesToResolve.push(frameToDrawPromise);
|
||||
});
|
||||
}
|
||||
|
||||
Promise.all(promisesToResolve).then(
|
||||
updates => {
|
||||
for (const subscriber of this.#registry) {
|
||||
subscriber.debuggingOverlayRef.current?.highlightTraceUpdates(
|
||||
updates,
|
||||
Promise.all(promisesToResolve)
|
||||
.then(resolvedTraceUpdates => {
|
||||
for (const {rootViewRef, debuggingOverlayRef} of this.#registry) {
|
||||
const rootViewReactTag = findNodeHandle(rootViewRef.current);
|
||||
if (rootViewReactTag == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
debuggingOverlayRef.current?.highlightTraceUpdates(
|
||||
resolvedTraceUpdates,
|
||||
);
|
||||
}
|
||||
},
|
||||
err => {
|
||||
console.error(`Failed to measure updated traces. Error: ${err}`);
|
||||
},
|
||||
);
|
||||
};
|
||||
})
|
||||
.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.
|
||||
});
|
||||
}
|
||||
|
||||
#onHighlightElements: (
|
||||
...ReactDevToolsAgentEvents['showNativeHighlight']
|
||||
@@ -155,14 +233,41 @@ class DebuggingOverlayRegistry {
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
#onHighlightElementsModern(publicInstance: ReactNativeElement): void {
|
||||
const {x, y, width, height} = publicInstance.getBoundingClientRect();
|
||||
|
||||
for (const subscriber of this.#registry) {
|
||||
subscriber.debuggingOverlayRef.current?.highlightElements([
|
||||
{x, y, width, height},
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove once DOM Node APIs are opt-in by default and Paper is no longer supported.
|
||||
#onHighlightElementsLegacy(publicInstance: NativeMethods): void {
|
||||
publicInstance.measure((x, y, width, height, left, top) => {
|
||||
for (const subscriber of this.#registry) {
|
||||
subscriber.debuggingOverlayRef.current?.highlightElements([
|
||||
// measure can execute callback without any values provided to signal error.
|
||||
if (left == null || top == null || width == null || height == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const {debuggingOverlayRef} of this.#registry) {
|
||||
debuggingOverlayRef.current?.highlightElements([
|
||||
{x: left, y: top, width, height},
|
||||
]);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
#onClearElementsHighlights: (
|
||||
...ReactDevToolsAgentEvents['hideNativeHighlight']
|
||||
|
||||
Reference in New Issue
Block a user