Skip to content

Commit c52df02

Browse files
tyao1facebook-github-bot
authored andcommitted
Enable inspection from devtools
Summary: Changelog: [General][Added] - Added an overlay similar to Inspector.js that allows directly selecting elements on RN from React DevTools This diff updates DevToolsHighlighter into DevToolsOverlay. It now also allows DevTools user to select an element to inspect directly from DevTools. Depends on facebook/react#25111 to work. TODOs: - Currently once an element selected on RN, the inspector toggle isn't turned off automatically. - Fabric support depends on facebook/react#25118 Reviewed By: lunaruan Differential Revision: D38815494 fbshipit-source-id: 7e1e3a78f6594960b5dfaec142bafd3ca4b146af
1 parent 3db19b4 commit c52df02

File tree

5 files changed

+254
-143
lines changed

5 files changed

+254
-143
lines changed

Libraries/Inspector/DevtoolsHighlighter.js

-80
This file was deleted.
+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @format
8+
* @flow
9+
*/
10+
11+
import ElementBox from './ElementBox';
12+
import * as React from 'react';
13+
import type {PressEvent} from '../Types/CoreEventTypes';
14+
import View from '../Components/View/View';
15+
import StyleSheet from '../StyleSheet/StyleSheet';
16+
import Dimensions from '../Utilities/Dimensions';
17+
const getInspectorDataForViewAtPoint = require('./getInspectorDataForViewAtPoint');
18+
const ReactNative = require('../Renderer/shims/ReactNative');
19+
20+
import type {HostRef} from './getInspectorDataForViewAtPoint';
21+
22+
const {useEffect, useState, useCallback, useRef} = React;
23+
24+
const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
25+
26+
export default function DevtoolsOverlay({
27+
inspectedView,
28+
}: {
29+
inspectedView: ?HostRef,
30+
}): React.Node {
31+
const [inspected, setInspected] = useState(null);
32+
const [isInspecting, setIsInspecting] = useState(false);
33+
const devToolsAgentRef = useRef(null);
34+
35+
useEffect(() => {
36+
let devToolsAgent = null;
37+
let hideTimeoutId = null;
38+
39+
function onAgentHideNativeHighlight() {
40+
// we wait to actually hide in order to avoid flicker
41+
clearTimeout(hideTimeoutId);
42+
hideTimeoutId = setTimeout(() => {
43+
setInspected(null);
44+
}, 100);
45+
}
46+
47+
function onAgentShowNativeHighlight(node: any) {
48+
clearTimeout(hideTimeoutId);
49+
// Shape of `node` is different in Fabric.
50+
const component = node.canonical ?? node;
51+
52+
component.measure((x, y, width, height, left, top) => {
53+
setInspected({
54+
frame: {left, top, width, height},
55+
});
56+
});
57+
}
58+
59+
function cleanup() {
60+
const currentAgent = devToolsAgent;
61+
if (currentAgent != null) {
62+
currentAgent.removeListener(
63+
'hideNativeHighlight',
64+
onAgentHideNativeHighlight,
65+
);
66+
currentAgent.removeListener(
67+
'showNativeHighlight',
68+
onAgentShowNativeHighlight,
69+
);
70+
currentAgent.removeListener('shutdown', cleanup);
71+
currentAgent.removeListener(
72+
'startInspectingNative',
73+
onStartInspectingNative,
74+
);
75+
currentAgent.removeListener(
76+
'stopInspectingNative',
77+
onStopInspectingNative,
78+
);
79+
devToolsAgent = null;
80+
}
81+
devToolsAgentRef.current = null;
82+
}
83+
84+
function onStartInspectingNative() {
85+
setIsInspecting(true);
86+
}
87+
88+
function onStopInspectingNative() {
89+
setIsInspecting(false);
90+
}
91+
92+
function _attachToDevtools(agent: Object) {
93+
devToolsAgent = agent;
94+
devToolsAgentRef.current = agent;
95+
agent.addListener('hideNativeHighlight', onAgentHideNativeHighlight);
96+
agent.addListener('showNativeHighlight', onAgentShowNativeHighlight);
97+
agent.addListener('shutdown', cleanup);
98+
agent.addListener('startInspectingNative', onStartInspectingNative);
99+
agent.addListener('stopInspectingNative', onStopInspectingNative);
100+
}
101+
102+
hook.on('react-devtools', _attachToDevtools);
103+
if (hook.reactDevtoolsAgent) {
104+
_attachToDevtools(hook.reactDevtoolsAgent);
105+
}
106+
return () => {
107+
hook.off('react-devtools', _attachToDevtools);
108+
cleanup();
109+
};
110+
}, []);
111+
112+
const findViewForTouchEvent = useCallback(
113+
(e: PressEvent) => {
114+
const agent = devToolsAgentRef.current;
115+
if (agent == null) {
116+
return;
117+
}
118+
const {locationX, locationY} = e.nativeEvent.touches[0];
119+
getInspectorDataForViewAtPoint(
120+
inspectedView,
121+
locationX,
122+
locationY,
123+
viewData => {
124+
const {touchedViewTag} = viewData;
125+
if (touchedViewTag != null) {
126+
agent.selectNode(ReactNative.findNodeHandle(touchedViewTag));
127+
return true;
128+
}
129+
return false;
130+
},
131+
);
132+
},
133+
[inspectedView],
134+
);
135+
136+
const shouldSetResponser = useCallback(
137+
(e: PressEvent): boolean => {
138+
findViewForTouchEvent(e);
139+
return true;
140+
},
141+
[findViewForTouchEvent],
142+
);
143+
144+
let highlight = inspected ? <ElementBox frame={inspected.frame} /> : null;
145+
if (isInspecting) {
146+
return (
147+
<View
148+
onStartShouldSetResponder={shouldSetResponser}
149+
onResponderMove={findViewForTouchEvent}
150+
nativeID="devToolsInspectorOverlay"
151+
style={[styles.inspector, {height: Dimensions.get('window').height}]}>
152+
{highlight}
153+
</View>
154+
);
155+
}
156+
return highlight;
157+
}
158+
159+
const styles = StyleSheet.create({
160+
inspector: {
161+
backgroundColor: 'transparent',
162+
position: 'absolute',
163+
left: 0,
164+
top: 0,
165+
right: 0,
166+
},
167+
});

Libraries/Inspector/Inspector.js

+4-55
Original file line numberDiff line numberDiff line change
@@ -20,70 +20,18 @@ const ReactNative = require('../Renderer/shims/ReactNative');
2020
const StyleSheet = require('../StyleSheet/StyleSheet');
2121
const View = require('../Components/View/View');
2222
const ReactNativeStyleAttributes = require('../Components/View/ReactNativeStyleAttributes');
23+
const getInspectorDataForViewAtPoint = require('./getInspectorDataForViewAtPoint');
2324

24-
const invariant = require('invariant');
25-
26-
import type {
27-
HostComponent,
28-
TouchedViewDataAtPoint,
29-
} from '../Renderer/shims/ReactNativeTypes';
30-
31-
type HostRef = React.ElementRef<HostComponent<mixed>>;
32-
33-
export type ReactRenderer = {
34-
rendererConfig: {
35-
getInspectorDataForViewAtPoint: (
36-
inspectedView: ?HostRef,
37-
locationX: number,
38-
locationY: number,
39-
callback: Function,
40-
) => void,
41-
...
42-
},
43-
};
25+
import type {TouchedViewDataAtPoint} from '../Renderer/shims/ReactNativeTypes';
26+
import type {HostRef} from './getInspectorDataForViewAtPoint';
4427

4528
const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
46-
const renderers = findRenderers();
4729

4830
// Required for React DevTools to view/edit React Native styles in Flipper.
4931
// Flipper doesn't inject these values when initializing DevTools.
5032
hook.resolveRNStyle = require('../StyleSheet/flattenStyle');
5133
hook.nativeStyleEditorValidAttributes = Object.keys(ReactNativeStyleAttributes);
5234

53-
function findRenderers(): $ReadOnlyArray<ReactRenderer> {
54-
const allRenderers = Array.from(hook.renderers.values());
55-
invariant(
56-
allRenderers.length >= 1,
57-
'Expected to find at least one React Native renderer on DevTools hook.',
58-
);
59-
return allRenderers;
60-
}
61-
62-
function getInspectorDataForViewAtPoint(
63-
inspectedView: ?HostRef,
64-
locationX: number,
65-
locationY: number,
66-
callback: (viewData: TouchedViewDataAtPoint) => void,
67-
) {
68-
// Check all renderers for inspector data.
69-
for (let i = 0; i < renderers.length; i++) {
70-
const renderer = renderers[i];
71-
if (renderer?.rendererConfig?.getInspectorDataForViewAtPoint != null) {
72-
renderer.rendererConfig.getInspectorDataForViewAtPoint(
73-
inspectedView,
74-
locationX,
75-
locationY,
76-
viewData => {
77-
// Only return with non-empty view data since only one renderer will have this view.
78-
if (viewData && viewData.hierarchy.length > 0) {
79-
callback(viewData);
80-
}
81-
},
82-
);
83-
}
84-
}
85-
}
86-
8735
class Inspector extends React.Component<
8836
{
8937
inspectedView: ?HostRef,
@@ -221,6 +169,7 @@ class Inspector extends React.Component<
221169
this._setTouchedViewData(viewData);
222170
this._setTouchedViewData = null;
223171
}
172+
return false;
224173
},
225174
);
226175
}

0 commit comments

Comments
 (0)