Skip to content

Commit bf35108

Browse files
authored
[React Native] Add getInspectorDataForViewAtPoint (#18233)
1 parent 99d7371 commit bf35108

File tree

9 files changed

+235
-12
lines changed

9 files changed

+235
-12
lines changed

packages/react-native-renderer/src/ReactFabric.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ import ReactVersion from 'shared/ReactVersion';
3434
import {UIManager} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
3535

3636
import {getClosestInstanceFromNode} from './ReactFabricComponentTree';
37-
import {getInspectorDataForViewTag} from './ReactNativeFiberInspector';
37+
import {
38+
getInspectorDataForViewAtPoint,
39+
getInspectorDataForViewTag,
40+
} from './ReactNativeFiberInspector';
3841

3942
import {LegacyRoot} from 'shared/ReactRootTags';
4043
import ReactSharedInternals from 'shared/ReactSharedInternals';
@@ -233,6 +236,10 @@ export {
233236
injectIntoDevTools({
234237
findFiberByHostInstance: getClosestInstanceFromNode,
235238
getInspectorDataForViewTag: getInspectorDataForViewTag,
239+
getInspectorDataForViewAtPoint: getInspectorDataForViewAtPoint.bind(
240+
null,
241+
findNodeHandle,
242+
),
236243
bundleType: __DEV__ ? 1 : 0,
237244
version: ReactVersion,
238245
rendererPackageName: 'react-native-renderer',

packages/react-native-renderer/src/ReactNativeFiberHostComponent.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,17 @@ class ReactNativeFiberHostComponent {
3434
_children: Array<Instance | number>;
3535
_nativeTag: number;
3636
viewConfig: ReactNativeBaseComponentViewConfig<>;
37+
_internalFiberInstanceHandle: Object;
3738

38-
constructor(tag: number, viewConfig: ReactNativeBaseComponentViewConfig<>) {
39+
constructor(
40+
tag: number,
41+
viewConfig: ReactNativeBaseComponentViewConfig<>,
42+
internalInstanceHandle: Object,
43+
) {
3944
this._nativeTag = tag;
4045
this._children = [];
4146
this.viewConfig = viewConfig;
47+
this._internalFiberInstanceHandle = internalInstanceHandle;
4248
}
4349

4450
blur() {

packages/react-native-renderer/src/ReactNativeFiberInspector.js

Lines changed: 139 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99

1010
import type {Fiber} from 'react-reconciler/src/ReactFiber';
11+
import type {TouchedViewDataAtPoint, InspectorData} from './ReactNativeTypes';
1112

1213
import {
1314
findCurrentHostFiber,
@@ -27,6 +28,7 @@ if (__DEV__) {
2728
}
2829

2930
let getInspectorDataForViewTag;
31+
let getInspectorDataForViewAtPoint;
3032

3133
if (__DEV__) {
3234
const traverseOwnerTreeUp = function(hierarchy, instance: any) {
@@ -80,15 +82,68 @@ if (__DEV__) {
8082
const createHierarchy = function(fiberHierarchy) {
8183
return fiberHierarchy.map(fiber => ({
8284
name: getComponentName(fiber.type),
83-
getInspectorData: findNodeHandle => ({
84-
measure: callback =>
85-
UIManager.measure(getHostNode(fiber, findNodeHandle), callback),
86-
props: getHostProps(fiber),
87-
source: fiber._debugSource,
88-
}),
85+
getInspectorData: findNodeHandle => {
86+
return {
87+
props: getHostProps(fiber),
88+
source: fiber._debugSource,
89+
measure: callback => {
90+
// If this is Fabric, we'll find a ShadowNode and use that to measure.
91+
const hostFiber = findCurrentHostFiber(fiber);
92+
const shadowNode =
93+
hostFiber != null &&
94+
hostFiber.stateNode !== null &&
95+
hostFiber.stateNode.node;
96+
97+
if (shadowNode) {
98+
nativeFabricUIManager.measure(shadowNode, function(
99+
x,
100+
y,
101+
width,
102+
height,
103+
pageX,
104+
pageY,
105+
) {
106+
callback(x, y, width, height, pageX, pageY);
107+
});
108+
} else {
109+
return UIManager.measure(
110+
getHostNode(fiber, findNodeHandle),
111+
callback,
112+
);
113+
}
114+
},
115+
};
116+
},
89117
}));
90118
};
91119

120+
const getInspectorDataForInstance = function(closestInstance): InspectorData {
121+
// Handle case where user clicks outside of ReactNative
122+
if (!closestInstance) {
123+
return {
124+
hierarchy: [],
125+
props: emptyObject,
126+
selection: null,
127+
source: null,
128+
};
129+
}
130+
131+
const fiber = findCurrentFiberUsingSlowPath(closestInstance);
132+
const fiberHierarchy = getOwnerHierarchy(fiber);
133+
const instance = lastNonHostInstance(fiberHierarchy);
134+
const hierarchy = createHierarchy(fiberHierarchy);
135+
const props = getHostProps(instance);
136+
const source = instance._debugSource;
137+
const selection = fiberHierarchy.indexOf(instance);
138+
139+
return {
140+
hierarchy,
141+
props,
142+
selection,
143+
source,
144+
};
145+
};
146+
92147
getInspectorDataForViewTag = function(viewTag: number): Object {
93148
const closestInstance = getClosestInstanceFromNode(viewTag);
94149

@@ -117,13 +172,90 @@ if (__DEV__) {
117172
source,
118173
};
119174
};
175+
176+
getInspectorDataForViewAtPoint = function(
177+
findNodeHandle: (componentOrHandle: any) => ?number,
178+
inspectedView: Object,
179+
locationX: number,
180+
locationY: number,
181+
callback: (viewData: TouchedViewDataAtPoint) => mixed,
182+
): void {
183+
let closestInstance = null;
184+
185+
if (inspectedView._internalInstanceHandle != null) {
186+
// For Fabric we can look up the instance handle directly and measure it.
187+
nativeFabricUIManager.findNodeAtPoint(
188+
inspectedView._internalInstanceHandle.stateNode.node,
189+
locationX,
190+
locationY,
191+
internalInstanceHandle => {
192+
if (internalInstanceHandle == null) {
193+
callback({
194+
pointerY: locationY,
195+
frame: {left: 0, top: 0, width: 0, height: 0},
196+
...getInspectorDataForInstance(closestInstance),
197+
});
198+
}
199+
200+
closestInstance =
201+
internalInstanceHandle.stateNode.canonical._internalInstanceHandle;
202+
nativeFabricUIManager.measure(
203+
internalInstanceHandle.stateNode.node,
204+
(x, y, width, height, pageX, pageY) => {
205+
callback({
206+
pointerY: locationY,
207+
frame: {left: pageX, top: pageY, width, height},
208+
...getInspectorDataForInstance(closestInstance),
209+
});
210+
},
211+
);
212+
},
213+
);
214+
} else if (inspectedView._internalFiberInstanceHandle != null) {
215+
// For Paper we fall back to the old strategy using the React tag.
216+
UIManager.findSubviewIn(
217+
findNodeHandle(inspectedView),
218+
[locationX, locationY],
219+
(nativeViewTag, left, top, width, height) => {
220+
const inspectorData = getInspectorDataForInstance(
221+
getClosestInstanceFromNode(nativeViewTag),
222+
);
223+
callback({
224+
...inspectorData,
225+
pointerY: locationY,
226+
frame: {left, top, width, height},
227+
touchedViewTag: nativeViewTag,
228+
});
229+
},
230+
);
231+
} else if (__DEV__) {
232+
console.error(
233+
'getInspectorDataForViewAtPoint expects to receieve a host component',
234+
);
235+
236+
return;
237+
}
238+
};
120239
} else {
121240
getInspectorDataForViewTag = () => {
122241
invariant(
123242
false,
124243
'getInspectorDataForViewTag() is not available in production',
125244
);
126245
};
246+
247+
getInspectorDataForViewAtPoint = (
248+
findNodeHandle: (componentOrHandle: any) => ?number,
249+
inspectedView: Object,
250+
locationX: number,
251+
locationY: number,
252+
callback: (viewData: TouchedViewDataAtPoint) => mixed,
253+
): void => {
254+
invariant(
255+
false,
256+
'getInspectorDataForViewAtPoint() is not available in production.',
257+
);
258+
};
127259
}
128260

129-
export {getInspectorDataForViewTag};
261+
export {getInspectorDataForViewAtPoint, getInspectorDataForViewTag};

packages/react-native-renderer/src/ReactNativeHostConfig.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,11 @@ export function createInstance(
112112
updatePayload, // props
113113
);
114114

115-
const component = new ReactNativeFiberHostComponent(tag, viewConfig);
115+
const component = new ReactNativeFiberHostComponent(
116+
tag,
117+
viewConfig,
118+
internalInstanceHandle,
119+
);
116120

117121
precacheFiberNode(internalInstanceHandle, tag);
118122
updateFiberProps(tag, props);

packages/react-native-renderer/src/ReactNativeRenderer.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ import ReactVersion from 'shared/ReactVersion';
3636
import {UIManager} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
3737

3838
import {getClosestInstanceFromNode} from './ReactNativeComponentTree';
39-
import {getInspectorDataForViewTag} from './ReactNativeFiberInspector';
39+
import {
40+
getInspectorDataForViewTag,
41+
getInspectorDataForViewAtPoint,
42+
} from './ReactNativeFiberInspector';
4043

4144
import {LegacyRoot} from 'shared/ReactRootTags';
4245
import ReactSharedInternals from 'shared/ReactSharedInternals';
@@ -247,6 +250,10 @@ export {
247250
injectIntoDevTools({
248251
findFiberByHostInstance: getClosestInstanceFromNode,
249252
getInspectorDataForViewTag: getInspectorDataForViewTag,
253+
getInspectorDataForViewAtPoint: getInspectorDataForViewAtPoint.bind(
254+
null,
255+
findNodeHandle,
256+
),
250257
bundleType: __DEV__ ? 1 : 0,
251258
version: ReactVersion,
252259
rendererPackageName: 'react-native-renderer',

packages/react-native-renderer/src/ReactNativeTypes.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,46 @@ type SecretInternalsType = {
100100
...
101101
};
102102

103+
type InspectorDataProps = $ReadOnly<{
104+
[propName: string]: string,
105+
...,
106+
}>;
107+
108+
type InspectorDataSource = $ReadOnly<{|
109+
fileName?: string,
110+
lineNumber?: number,
111+
|}>;
112+
113+
type InspectorDataGetter = (
114+
(componentOrHandle: any) => ?number,
115+
) => $ReadOnly<{|
116+
measure: Function,
117+
props: InspectorDataProps,
118+
source: InspectorDataSource,
119+
|}>;
120+
121+
export type InspectorData = $ReadOnly<{|
122+
hierarchy: Array<{|
123+
name: ?string,
124+
getInspectorData: InspectorDataGetter,
125+
|}>,
126+
selection: ?number,
127+
props: InspectorDataProps,
128+
source: ?InspectorDataSource,
129+
|}>;
130+
131+
export type TouchedViewDataAtPoint = $ReadOnly<{|
132+
pointerY: number,
133+
touchedViewTag?: number,
134+
frame: $ReadOnly<{|
135+
top: number,
136+
left: number,
137+
width: number,
138+
height: number,
139+
|}>,
140+
...InspectorData,
141+
|}>;
142+
103143
/**
104144
* Flat ReactNative renderer bundles are too big for Flow to parse efficiently.
105145
* Provide minimal Flow typing for the high-level RN API and call it a day.

packages/react-reconciler/src/ReactFiberReconciler.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import type {Fiber} from './ReactFiber';
1111
import type {FiberRoot} from './ReactFiberRoot';
1212
import type {RootTag} from 'shared/ReactRootTags';
13+
import type {TouchedViewDataAtPoint} from 'react-native-renderer/src/ReactNativeTypes';
1314
import type {
1415
Instance,
1516
TextInstance,
@@ -109,6 +110,13 @@ type DevToolsConfig = {|
109110
// This API is unfortunately RN-specific.
110111
// TODO: Change it to accept Fiber instead and type it properly.
111112
getInspectorDataForViewTag?: (tag: number) => Object,
113+
// Used by RN in-app inspector.
114+
getInspectorDataForViewAtPoint?: (
115+
inspectedView: Object,
116+
locationX: number,
117+
locationY: number,
118+
callback: (viewData: TouchedViewDataAtPoint) => mixed,
119+
) => void,
112120
|};
113121

114122
let didWarnAboutNestedUpdates;

scripts/error-codes/codes.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,5 +346,6 @@
346346
"345": "Root did not complete. This is a bug in React.",
347347
"346": "An event responder context was used outside of an event cycle.",
348348
"347": "Maps are not valid as a React child (found: %s). Consider converting children to an array of keyed ReactElements instead.",
349-
"348": "ensureListeningTo(): received a container that was not an element node. This is likely a bug in React."
349+
"348": "ensureListeningTo(): received a container that was not an element node. This is likely a bug in React.",
350+
"349": "getInspectorDataForViewAtPoint() is not available in production."
350351
}

scripts/flow/react-native-host-hooks.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import type {
1818
} from 'react-native-renderer/src/ReactNativeTypes';
1919
import type {RNTopLevelEventType} from 'legacy-events/TopLevelEventTypes';
2020
import type {CapturedError} from 'react-reconciler/src/ReactCapturedValue';
21+
import type {Fiber} from 'react-reconciler/src/ReactFiber';
2122

2223
type DeepDifferOptions = {|+unsafelyIgnoreFunctions?: boolean|};
2324

@@ -96,6 +97,17 @@ declare module 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface'
9697
) => Promise<any>,
9798
setJSResponder: (reactTag: number, blockNativeResponder: boolean) => void,
9899
clearJSResponder: () => void,
100+
findSubviewIn: (
101+
reactTag: ?number,
102+
point: Array<number>,
103+
callback: (
104+
nativeViewTag: number,
105+
left: number,
106+
top: number,
107+
width: number,
108+
height: number,
109+
) => void,
110+
) => void,
99111
...
100112
};
101113
declare export var BatchedBridge: {
@@ -156,6 +168,12 @@ declare var nativeFabricUIManager: {
156168
onFail: () => void,
157169
onSuccess: MeasureLayoutOnSuccessCallback,
158170
) => void,
171+
findNodeAtPoint: (
172+
node: Node,
173+
locationX: number,
174+
locationY: number,
175+
callback: (Fiber) => void,
176+
) => void,
159177
...
160178
};
161179

0 commit comments

Comments
 (0)