@samitouri / QOS-React-2 / commits / bf351089a0

[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