@samitouri / QOS-React / commits / dd7e5e4f5a

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