[React Native] Add getInspectorDataForViewAtPoint (#18233)
Ricky committed
Mar 11, 2020 at 16:12 UTC
bf351089a0ee27405b3d5c0f474941a4946f2f37
9 files changed
+235
-12
packages/react-native-renderer/src/ReactFabric.js
+8
-1
@@ -34,7 +34,10 @@ import ReactVersion from 'shared/ReactVersion';
34
import {UIManager} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
35
36
import {getClosestInstanceFromNode} from './ReactFabricComponentTree';
37
-import {getInspectorDataForViewTag} from './ReactNativeFiberInspector';
37
+import {
38
+ getInspectorDataForViewAtPoint,
39
+ getInspectorDataForViewTag,
40
+} from './ReactNativeFiberInspector';
41
42
import {LegacyRoot} from 'shared/ReactRootTags';
43
import ReactSharedInternals from 'shared/ReactSharedInternals';
@@ -233,6 +236,10 @@ export {
236
injectIntoDevTools({
237
findFiberByHostInstance: getClosestInstanceFromNode,
238
getInspectorDataForViewTag: getInspectorDataForViewTag,
239
+ getInspectorDataForViewAtPoint: getInspectorDataForViewAtPoint.bind(
240
+ null,
241
+ findNodeHandle,
242
+ ),
243
bundleType: __DEV__ ? 1 : 0,
244
version: ReactVersion,
245
rendererPackageName: 'react-native-renderer',
packages/react-native-renderer/src/ReactNativeFiberHostComponent.js
+7
-1
@@ -34,11 +34,17 @@ class ReactNativeFiberHostComponent {
34
_children: Array<Instance | number>;
35
_nativeTag: number;
36
viewConfig: ReactNativeBaseComponentViewConfig<>;
37
+ _internalFiberInstanceHandle: Object;
38
38
- constructor(tag: number, viewConfig: ReactNativeBaseComponentViewConfig<>) {
39
+ constructor(
40
+ tag: number,
41
+ viewConfig: ReactNativeBaseComponentViewConfig<>,
42
+ internalInstanceHandle: Object,
43
+ ) {
44
this._nativeTag = tag;
45
this._children = [];
46
this.viewConfig = viewConfig;
47
+ this._internalFiberInstanceHandle = internalInstanceHandle;
48
}
49
50
blur() {
packages/react-native-renderer/src/ReactNativeFiberInspector.js
+139
-7
@@ -8,6 +8,7 @@
8
*/
9
10
import type {Fiber} from 'react-reconciler/src/ReactFiber';
11
+import type {TouchedViewDataAtPoint, InspectorData} from './ReactNativeTypes';
12
13
import {
14
findCurrentHostFiber,
@@ -27,6 +28,7 @@ if (__DEV__) {
28
}
29
30
let getInspectorDataForViewTag;
31
+let getInspectorDataForViewAtPoint;
32
33
if (__DEV__) {
34
const traverseOwnerTreeUp = function(hierarchy, instance: any) {
@@ -80,15 +82,68 @@ if (__DEV__) {
82
const createHierarchy = function(fiberHierarchy) {
83
return fiberHierarchy.map(fiber => ({
84
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
+ },
117
}));
118
};
119
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
+
147
getInspectorDataForViewTag = function(viewTag: number): Object {
148
const closestInstance = getClosestInstanceFromNode(viewTag);
149
@@ -117,6 +172,70 @@ if (__DEV__) {
172
source,
173
};
174
};
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
+ };
239
} else {
240
getInspectorDataForViewTag = () => {
241
invariant(
@@ -124,6 +243,19 @@ if (__DEV__) {
243
'getInspectorDataForViewTag() is not available in production',
244
);
245
};
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
+ };
259
}
260
129
-export {getInspectorDataForViewTag};
261
+export {getInspectorDataForViewAtPoint, getInspectorDataForViewTag};
packages/react-native-renderer/src/ReactNativeHostConfig.js
+5
-1
@@ -112,7 +112,11 @@ export function createInstance(
112
updatePayload, // props
113
);
114
115
- const component = new ReactNativeFiberHostComponent(tag, viewConfig);
115
+ const component = new ReactNativeFiberHostComponent(
116
+ tag,
117
+ viewConfig,
118
+ internalInstanceHandle,
119
+ );
120
121
precacheFiberNode(internalInstanceHandle, tag);
122
updateFiberProps(tag, props);
packages/react-native-renderer/src/ReactNativeRenderer.js
+8
-1
@@ -36,7 +36,10 @@ import ReactVersion from 'shared/ReactVersion';
36
import {UIManager} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
37
38
import {getClosestInstanceFromNode} from './ReactNativeComponentTree';
39
-import {getInspectorDataForViewTag} from './ReactNativeFiberInspector';
39
+import {
40
+ getInspectorDataForViewTag,
41
+ getInspectorDataForViewAtPoint,
42
+} from './ReactNativeFiberInspector';
43
44
import {LegacyRoot} from 'shared/ReactRootTags';
45
import ReactSharedInternals from 'shared/ReactSharedInternals';
@@ -247,6 +250,10 @@ export {
250
injectIntoDevTools({
251
findFiberByHostInstance: getClosestInstanceFromNode,
252
getInspectorDataForViewTag: getInspectorDataForViewTag,
253
+ getInspectorDataForViewAtPoint: getInspectorDataForViewAtPoint.bind(
254
+ null,
255
+ findNodeHandle,
256
+ ),
257
bundleType: __DEV__ ? 1 : 0,
258
version: ReactVersion,
259
rendererPackageName: 'react-native-renderer',
packages/react-native-renderer/src/ReactNativeTypes.js
+40
@@ -100,6 +100,46 @@ type SecretInternalsType = {
100
...
101
};
102
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
+
143
/**
144
* Flat ReactNative renderer bundles are too big for Flow to parse efficiently.
145
* Provide minimal Flow typing for the high-level RN API and call it a day.
packages/react-reconciler/src/ReactFiberReconciler.js
+8
@@ -10,6 +10,7 @@
10
import type {Fiber} from './ReactFiber';
11
import type {FiberRoot} from './ReactFiberRoot';
12
import type {RootTag} from 'shared/ReactRootTags';
13
+import type {TouchedViewDataAtPoint} from 'react-native-renderer/src/ReactNativeTypes';
14
import type {
15
Instance,
16
TextInstance,
@@ -109,6 +110,13 @@ type DevToolsConfig = {|
110
// This API is unfortunately RN-specific.
111
// TODO: Change it to accept Fiber instead and type it properly.
112
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,
120
|};
121
122
let didWarnAboutNestedUpdates;
scripts/error-codes/codes.json
+2
-1
@@ -346,5 +346,6 @@
346
"345": "Root did not complete. This is a bug in React.",
347
"346": "An event responder context was used outside of an event cycle.",
348
"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."
351
}
scripts/flow/react-native-host-hooks.js
+18
@@ -18,6 +18,7 @@ import type {
18
} from 'react-native-renderer/src/ReactNativeTypes';
19
import type {RNTopLevelEventType} from 'legacy-events/TopLevelEventTypes';
20
import type {CapturedError} from 'react-reconciler/src/ReactCapturedValue';
21
+import type {Fiber} from 'react-reconciler/src/ReactFiber';
22
23
type DeepDifferOptions = {|+unsafelyIgnoreFunctions?: boolean|};
24
@@ -96,6 +97,17 @@ declare module 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface'
97
) => Promise<any>,
98
setJSResponder: (reactTag: number, blockNativeResponder: boolean) => void,
99
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,
111
...
112
};
113
declare export var BatchedBridge: {
@@ -156,6 +168,12 @@ declare var nativeFabricUIManager: {
168
onFail: () => void,
169
onSuccess: MeasureLayoutOnSuccessCallback,
170
) => void,
171
+ findNodeAtPoint: (
172
+ node: Node,
173
+ locationX: number,
174
+ locationY: number,
175
+ callback: (Fiber) => void,
176
+ ) => void,
177
...
178
};
179