Add getInspectorDataForViewAtPoint (take two) (#18388)
* Add getInspectorDataForViewAtPoint (take two) * Updates from review * Add DEV to dev-only variable * Missed this rename
Ricky committed
Mar 30, 2020 at 15:42 UTC
dd7e5e4f5ac2ffac3171ef61daee2cb1edc69635
13 files changed
+260
-24
packages/react-dom/src/client/ReactDOMHostConfig.js
+1
@@ -145,6 +145,7 @@ export type UpdatePayload = Array<mixed>;
145
export type ChildSet = void; // Unused
146
export type TimeoutHandle = TimeoutID;
147
export type NoTimeout = -1;
148
+export type RendererInspectionConfig = $ReadOnly<{||}>;
149
150
type SelectionInformation = {|
151
activeElementDetached: null | HTMLElement,
packages/react-native-renderer/src/ReactFabric.js
+11
-3
@@ -34,8 +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';
38
-
37
+import {
38
+ getInspectorDataForViewTag,
39
+ getInspectorDataForViewAtPoint,
40
+} from './ReactNativeFiberInspector';
41
import {LegacyRoot} from 'react-reconciler/src/ReactRootTags';
42
import ReactSharedInternals from 'shared/ReactSharedInternals';
43
import getComponentName from 'shared/getComponentName';
@@ -232,8 +234,14 @@ export {
234
235
injectIntoDevTools({
236
findFiberByHostInstance: getClosestInstanceFromNode,
235
- getInspectorDataForViewTag: getInspectorDataForViewTag,
237
bundleType: __DEV__ ? 1 : 0,
238
version: ReactVersion,
239
rendererPackageName: 'react-native-renderer',
240
+ rendererConfig: {
241
+ getInspectorDataForViewTag: getInspectorDataForViewTag,
242
+ getInspectorDataForViewAtPoint: getInspectorDataForViewAtPoint.bind(
243
+ null,
244
+ findNodeHandle,
245
+ ),
246
+ },
247
});
packages/react-native-renderer/src/ReactFabricHostConfig.js
+12
@@ -15,6 +15,7 @@ import type {
15
MeasureOnSuccessCallback,
16
NativeMethods,
17
ReactNativeBaseComponentViewConfig,
18
+ TouchedViewDataAtPoint,
19
} from './ReactNativeTypes';
20
21
import {mountSafeCallback_NOT_REALLY_SAFE} from './NativeMethodsMixinUtils';
@@ -80,6 +81,17 @@ export type ReactListenerEvent = Object;
81
export type ReactListenerMap = Object;
82
export type ReactListener = Object;
83
84
+export type RendererInspectionConfig = $ReadOnly<{|
85
+ // Deprecated. Replaced with getInspectorDataForViewAtPoint.
86
+ getInspectorDataForViewTag?: (tag: number) => Object,
87
+ getInspectorDataForViewAtPoint?: (
88
+ inspectedView: Object,
89
+ locationX: number,
90
+ locationY: number,
91
+ callback: (viewData: TouchedViewDataAtPoint) => mixed,
92
+ ) => void,
93
+|}>;
94
+
95
// TODO: Remove this conditional once all changes have propagated.
96
if (registerEventHandler) {
97
/**
packages/react-native-renderer/src/ReactNativeFiberHostComponent.js
+9
-1
@@ -33,12 +33,20 @@ import {
33
class ReactNativeFiberHostComponent {
34
_children: Array<Instance | number>;
35
_nativeTag: number;
36
+ _internalFiberInstanceHandleDEV: Object;
37
viewConfig: ReactNativeBaseComponentViewConfig<>;
38
38
- constructor(tag: number, viewConfig: ReactNativeBaseComponentViewConfig<>) {
39
+ constructor(
40
+ tag: number,
41
+ viewConfig: ReactNativeBaseComponentViewConfig<>,
42
+ internalInstanceHandleDEV: Object,
43
+ ) {
44
this._nativeTag = tag;
45
this._children = [];
46
this.viewConfig = viewConfig;
47
+ if (__DEV__) {
48
+ this._internalFiberInstanceHandleDEV = internalInstanceHandleDEV;
49
+ }
50
}
51
52
blur() {
packages/react-native-renderer/src/ReactNativeFiberInspector.js
+133
-10
@@ -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,59 @@ 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, callback);
99
+ } else {
100
+ return UIManager.measure(
101
+ getHostNode(fiber, findNodeHandle),
102
+ callback,
103
+ );
104
+ }
105
+ },
106
+ };
107
+ },
108
}));
109
};
110
111
+ const getInspectorDataForInstance = function(closestInstance): InspectorData {
112
+ // Handle case where user clicks outside of ReactNative
113
+ if (!closestInstance) {
114
+ return {
115
+ hierarchy: [],
116
+ props: emptyObject,
117
+ selectedIndex: null,
118
+ source: null,
119
+ };
120
+ }
121
+
122
+ const fiber = findCurrentFiberUsingSlowPath(closestInstance);
123
+ const fiberHierarchy = getOwnerHierarchy(fiber);
124
+ const instance = lastNonHostInstance(fiberHierarchy);
125
+ const hierarchy = createHierarchy(fiberHierarchy);
126
+ const props = getHostProps(instance);
127
+ const source = instance._debugSource;
128
+ const selectedIndex = fiberHierarchy.indexOf(instance);
129
+
130
+ return {
131
+ hierarchy,
132
+ props,
133
+ selectedIndex,
134
+ source,
135
+ };
136
+ };
137
+
138
getInspectorDataForViewTag = function(viewTag: number): Object {
139
const closestInstance = getClosestInstanceFromNode(viewTag);
140
@@ -97,7 +143,7 @@ if (__DEV__) {
143
return {
144
hierarchy: [],
145
props: emptyObject,
100
- selection: null,
146
+ selectedIndex: null,
147
source: null,
148
};
149
}
@@ -108,15 +154,79 @@ if (__DEV__) {
154
const hierarchy = createHierarchy(fiberHierarchy);
155
const props = getHostProps(instance);
156
const source = instance._debugSource;
111
- const selection = fiberHierarchy.indexOf(instance);
157
+ const selectedIndex = fiberHierarchy.indexOf(instance);
158
159
return {
160
hierarchy,
161
props,
116
- selection,
162
+ selectedIndex,
163
source,
164
};
165
};
166
+
167
+ getInspectorDataForViewAtPoint = function(
168
+ findNodeHandle: (componentOrHandle: any) => ?number,
169
+ inspectedView: Object,
170
+ locationX: number,
171
+ locationY: number,
172
+ callback: (viewData: TouchedViewDataAtPoint) => mixed,
173
+ ): void {
174
+ let closestInstance = null;
175
+
176
+ if (inspectedView._internalInstanceHandle != null) {
177
+ // For Fabric we can look up the instance handle directly and measure it.
178
+ nativeFabricUIManager.findNodeAtPoint(
179
+ inspectedView._internalInstanceHandle.stateNode.node,
180
+ locationX,
181
+ locationY,
182
+ internalInstanceHandle => {
183
+ if (internalInstanceHandle == null) {
184
+ callback({
185
+ pointerY: locationY,
186
+ frame: {left: 0, top: 0, width: 0, height: 0},
187
+ ...getInspectorDataForInstance(closestInstance),
188
+ });
189
+ }
190
+
191
+ closestInstance =
192
+ internalInstanceHandle.stateNode.canonical._internalInstanceHandle;
193
+ nativeFabricUIManager.measure(
194
+ internalInstanceHandle.stateNode.node,
195
+ (x, y, width, height, pageX, pageY) => {
196
+ callback({
197
+ pointerY: locationY,
198
+ frame: {left: pageX, top: pageY, width, height},
199
+ ...getInspectorDataForInstance(closestInstance),
200
+ });
201
+ },
202
+ );
203
+ },
204
+ );
205
+ } else if (inspectedView._internalFiberInstanceHandleDEV != null) {
206
+ // For Paper we fall back to the old strategy using the React tag.
207
+ UIManager.findSubviewIn(
208
+ findNodeHandle(inspectedView),
209
+ [locationX, locationY],
210
+ (nativeViewTag, left, top, width, height) => {
211
+ const inspectorData = getInspectorDataForInstance(
212
+ getClosestInstanceFromNode(nativeViewTag),
213
+ );
214
+ callback({
215
+ ...inspectorData,
216
+ pointerY: locationY,
217
+ frame: {left, top, width, height},
218
+ touchedViewTag: nativeViewTag,
219
+ });
220
+ },
221
+ );
222
+ } else {
223
+ console.error(
224
+ 'getInspectorDataForViewAtPoint expects to receieve a host component',
225
+ );
226
+
227
+ return;
228
+ }
229
+ };
230
} else {
231
getInspectorDataForViewTag = () => {
232
invariant(
@@ -124,6 +234,19 @@ if (__DEV__) {
234
'getInspectorDataForViewTag() is not available in production',
235
);
236
};
237
+
238
+ getInspectorDataForViewAtPoint = (
239
+ findNodeHandle: (componentOrHandle: any) => ?number,
240
+ inspectedView: Object,
241
+ locationX: number,
242
+ locationY: number,
243
+ callback: (viewData: TouchedViewDataAtPoint) => mixed,
244
+ ): void => {
245
+ invariant(
246
+ false,
247
+ 'getInspectorDataForViewAtPoint() is not available in production.',
248
+ );
249
+ };
250
}
251
129
-export {getInspectorDataForViewTag};
252
+export {getInspectorDataForViewAtPoint, getInspectorDataForViewTag};
packages/react-native-renderer/src/ReactNativeHostConfig.js
+18
-1
@@ -7,6 +7,8 @@
7
* @flow
8
*/
9
10
+import type {TouchedViewDataAtPoint} from './ReactNativeTypes';
11
+
12
import invariant from 'shared/invariant';
13
14
// Modules provided by RN:
@@ -46,6 +48,17 @@ export type ChildSet = void; // Unused
48
export type TimeoutHandle = TimeoutID;
49
export type NoTimeout = -1;
50
51
+export type RendererInspectionConfig = $ReadOnly<{|
52
+ // Deprecated. Replaced with getInspectorDataForViewAtPoint.
53
+ getInspectorDataForViewTag?: (tag: number) => Object,
54
+ getInspectorDataForViewAtPoint?: (
55
+ inspectedView: Object,
56
+ locationX: number,
57
+ locationY: number,
58
+ callback: (viewData: TouchedViewDataAtPoint) => mixed,
59
+ ) => void,
60
+|}>;
61
+
62
const UPDATE_SIGNAL = {};
63
if (__DEV__) {
64
Object.freeze(UPDATE_SIGNAL);
@@ -112,7 +125,11 @@ export function createInstance(
125
updatePayload, // props
126
);
127
115
- const component = new ReactNativeFiberHostComponent(tag, viewConfig);
128
+ const component = new ReactNativeFiberHostComponent(
129
+ tag,
130
+ viewConfig,
131
+ internalInstanceHandle,
132
+ );
133
134
precacheFiberNode(internalInstanceHandle, tag);
135
updateFiberProps(tag, props);
packages/react-native-renderer/src/ReactNativeRenderer.js
+11
-3
@@ -36,8 +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';
40
-
39
+import {
40
+ getInspectorDataForViewTag,
41
+ getInspectorDataForViewAtPoint,
42
+} from './ReactNativeFiberInspector';
43
import {LegacyRoot} from 'react-reconciler/src/ReactRootTags';
44
import ReactSharedInternals from 'shared/ReactSharedInternals';
45
import getComponentName from 'shared/getComponentName';
@@ -246,8 +248,14 @@ export {
248
249
injectIntoDevTools({
250
findFiberByHostInstance: getClosestInstanceFromNode,
249
- getInspectorDataForViewTag: getInspectorDataForViewTag,
251
bundleType: __DEV__ ? 1 : 0,
252
version: ReactVersion,
253
rendererPackageName: 'react-native-renderer',
254
+ rendererConfig: {
255
+ getInspectorDataForViewTag: getInspectorDataForViewTag,
256
+ getInspectorDataForViewAtPoint: getInspectorDataForViewAtPoint.bind(
257
+ null,
258
+ findNodeHandle,
259
+ ),
260
+ },
261
});
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
+ selectedIndex: ?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.old.js
+3
-5
@@ -16,6 +16,7 @@ import type {
16
Container,
17
PublicInstance,
18
} from './ReactFiberHostConfig';
19
+import type {RendererInspectionConfig} from './ReactFiberHostConfig';
20
import {FundamentalComponent} from './ReactWorkTags';
21
import type {ReactNodeList, Thenable} from 'shared/ReactTypes';
22
import type {ExpirationTime} from './ReactFiberExpirationTime';
@@ -107,10 +108,7 @@ type DevToolsConfig = {|
108
// Note: this actually *does* depend on Fiber internal fields.
109
// Used by "inspect clicked DOM element" in React DevTools.
110
findFiberByHostInstance?: (instance: Instance | TextInstance) => Fiber | null,
110
- // Used by RN in-app inspector.
111
- // This API is unfortunately RN-specific.
112
- // TODO: Change it to accept Fiber instead and type it properly.
113
- getInspectorDataForViewTag?: (tag: number) => Object,
111
+ rendererConfig?: RendererInspectionConfig,
112
|};
113
114
let didWarnAboutNestedUpdates;
@@ -516,7 +514,7 @@ export function injectIntoDevTools(devToolsConfig: DevToolsConfig): boolean {
514
bundleType: devToolsConfig.bundleType,
515
version: devToolsConfig.version,
516
rendererPackageName: devToolsConfig.rendererPackageName,
519
- getInspectorDataForViewTag: devToolsConfig.getInspectorDataForViewTag,
517
+ rendererConfig: devToolsConfig.rendererConfig,
518
overrideHookState,
519
overrideProps,
520
setSuspenseHandler,
packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js
+1
@@ -37,6 +37,7 @@ export opaque type UpdatePayload = mixed; // eslint-disable-line no-undef
37
export opaque type ChildSet = mixed; // eslint-disable-line no-undef
38
export opaque type TimeoutHandle = mixed; // eslint-disable-line no-undef
39
export opaque type NoTimeout = mixed; // eslint-disable-line no-undef
40
+export opaque type RendererInspectionConfig = mixed; // eslint-disable-line no-undef
41
export type EventResponder = any;
42
export type ReactListenerEvent = Object;
43
export type ReactListenerMap = Object;
packages/react-test-renderer/src/ReactTestHostConfig.js
+1
@@ -48,6 +48,7 @@ export type EventResponder = any;
48
export type ReactListenerEvent = Object;
49
export type ReactListenerMap = Object;
50
export type ReactListener = Object;
51
+export type RendererInspectionConfig = $ReadOnly<{||}>;
52
53
export * from 'react-reconciler/src/ReactFiberHostConfigWithNoPersistence';
54
export * from 'react-reconciler/src/ReactFiberHostConfigWithNoHydration';
scripts/error-codes/codes.json
+2
-1
@@ -351,5 +351,6 @@
351
"350": "Cannot read from mutable source during the current render without tearing. This is a bug in React. Please file an issue.",
352
"351": "Unsupported type.",
353
"352": "React Blocks (and Lazy Components) are expected to be replaced by a compiler on the server. Try configuring your compiler set up and avoid using React.lazy inside of Blocks.",
354
- "353": "A server block should never encode any other slots. This is a bug in React."
354
+ "353": "A server block should never encode any other slots. This is a bug in React.",
355
+ "354": "getInspectorDataForViewAtPoint() is not available in production."
356
}
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