Skip to content

Commit

Permalink
refactor: DebuggingRegistry to handle native highlights from React De…
Browse files Browse the repository at this point in the history
…vTools (#41746)

Summary:

Changelog: [Internal]

Now using previously added `highlightElements` and `clearElementsHighlights` commands.

[Improvement] Since DebuggingRegistry is a singleton, it will only subscribe to the React DevTools events once and not *number-of-rendered-AppContainers* times.

All required functionality for highlighting elements on a single AppContainer will be added in one of the next diffs of this stack, changes are incremental.

Differential Revision: D51708053
  • Loading branch information
Ruslan Lesiutin authored and facebook-github-bot committed Dec 6, 2023
1 parent 608d169 commit ba56187
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 53 deletions.
28 changes: 27 additions & 1 deletion packages/react-native/Libraries/Debugging/DebuggingOverlay.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
* @format
*/

import type {Overlay} from './DebuggingOverlayNativeComponent';
import type {
ElementRectangle,
Overlay,
} from './DebuggingOverlayNativeComponent';

import View from '../Components/View/View';
import UIManager from '../ReactNative/UIManager';
Expand All @@ -24,6 +27,8 @@ const isNativeComponentReady =

type DebuggingOverlayHandle = {
highlightTraceUpdates(updates: Overlay[]): void,
highlightElements(elements: ElementRectangle[]): void,
clearElementsHighlight(): void,
};

function DebuggingOverlay(
Expand All @@ -49,6 +54,27 @@ function DebuggingOverlay(
);
}
},
highlightElements(elements) {
if (!isNativeComponentReady) {
return;
}

if (nativeComponentRef.current != null) {
Commands.highlightElements(
nativeComponentRef.current,
JSON.stringify(elements),
);
}
},
clearElementsHighlight() {
if (!isNativeComponentReady) {
return;
}

if (nativeComponentRef.current != null) {
Commands.clearElementsHighlights(nativeComponentRef.current);
}
},
}),
[],
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ export type Overlay = {
color: ?ProcessedColorValue,
};

export type ElementRectangle = {
x: number,
y: number,
width: number,
height: number,
};

interface NativeCommands {
+draw: (
viewRef: React.ElementRef<DebuggingOverlayNativeComponentType>,
Expand Down
32 changes: 32 additions & 0 deletions packages/react-native/Libraries/Debugging/DebuggingRegistry.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ class DebuggingRegistry {
this.#reactDevToolsAgent = agent;

agent.addListener('drawTraceUpdates', this.#onDrawTraceUpdates);
agent.addListener('showNativeHighlight', this.#onHighlightElements);
agent.addListener('hideNativeHighlight', this.#onClearElementsHighlights);
};

#getPublicInstanceFromInstance(
Expand Down Expand Up @@ -131,6 +133,36 @@ class DebuggingRegistry {
},
);
};

#onHighlightElements: (
...ReactDevToolsAgentEvents['showNativeHighlight']
) => void = node => {
// 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;
}

publicInstance.measure((x, y, width, height, left, top) => {
for (const subscriber of this.#registry) {
subscriber.debuggingOverlayRef.current?.highlightElements([
{x: left, y: top, width, height},
]);
}
});
};

#onClearElementsHighlights: (
...ReactDevToolsAgentEvents['hideNativeHighlight']
) => void = () => {
for (const subscriber of this.#registry) {
subscriber.debuggingOverlayRef.current?.clearElementsHighlight();
}
};
}

const debuggingRegistryInstance: DebuggingRegistry = new DebuggingRegistry();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@
import type {InspectedViewRef} from '../ReactNative/AppContainer-dev';
import type {PointerEvent} from '../Types/CoreEventTypes';
import type {PressEvent} from '../Types/CoreEventTypes';
import type {
InstanceFromReactDevTools,
ReactDevToolsAgent,
} from '../Types/ReactDevToolsTypes';
import type {ReactDevToolsAgent} from '../Types/ReactDevToolsTypes';
import type {InspectedElement} from './Inspector';

import View from '../Components/View/View';
Expand Down Expand Up @@ -42,47 +39,7 @@ export default function ReactDevToolsOverlay({
const [isInspecting, setIsInspecting] = useState(false);

useEffect(() => {
let hideTimeoutId = null;

function onAgentHideNativeHighlight() {
// we wait to actually hide in order to avoid flicker
clearTimeout(hideTimeoutId);
hideTimeoutId = setTimeout(() => {
setInspected(null);
}, 100);
}

function onAgentShowNativeHighlight(node?: InstanceFromReactDevTools) {
clearTimeout(hideTimeoutId);

// `canonical.publicInstance` => Fabric
// `canonical` => Legacy Fabric
// `node` => Legacy renderer
const component =
(node?.canonical && node.canonical.publicInstance) ??
// TODO: remove this check when syncing the new version of the renderer from React to React Native.
node?.canonical ??
node;
if (!component || !component.measure) {
return;
}

component.measure((x, y, width, height, left, top) => {
setInspected({
frame: {left, top, width, height},
});
});
}

function cleanup() {
reactDevToolsAgent.removeListener(
'hideNativeHighlight',
onAgentHideNativeHighlight,
);
reactDevToolsAgent.removeListener(
'showNativeHighlight',
onAgentShowNativeHighlight,
);
reactDevToolsAgent.removeListener('shutdown', cleanup);
reactDevToolsAgent.removeListener(
'startInspectingNative',
Expand All @@ -102,14 +59,6 @@ export default function ReactDevToolsOverlay({
setIsInspecting(false);
}

reactDevToolsAgent.addListener(
'hideNativeHighlight',
onAgentHideNativeHighlight,
);
reactDevToolsAgent.addListener(
'showNativeHighlight',
onAgentShowNativeHighlight,
);
reactDevToolsAgent.addListener('shutdown', cleanup);
reactDevToolsAgent.addListener(
'startInspectingNative',
Expand Down

0 comments on commit ba56187

Please sign in to comment.