main
js 202 lines 5.93 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
11 import type {TouchedViewDataAtPoint, InspectorData} from './ReactNativeTypes';
12
13 import {
14 findCurrentHostFiber,
15 findCurrentFiberUsingSlowPath,
16 } from 'react-reconciler/src/ReactFiberTreeReflection';
17 import getComponentNameFromType from 'shared/getComponentNameFromType';
18 import {HostComponent} from 'react-reconciler/src/ReactWorkTags';
19 // Module provided by RN:
20 import {getNodeFromPublicInstance} from 'react-native/react-private-interface';
21 import {getNodeFromInternalInstanceHandle} from './ReactNativePublicCompat';
22 import {getStackByFiberInDevAndProd} from 'react-reconciler/src/ReactFiberComponentStack';
23
24 let getInspectorDataForInstance: (
25 closestInstance: Fiber | null,
26 ) => InspectorData;
27
28 if (__DEV__) {
29 const emptyObject = Object.freeze({});
30
31 // $FlowFixMe[missing-local-annot]
32 const createHierarchy = function (fiberHierarchy) {
33 return fiberHierarchy.map(fiber => ({
34 name: getComponentNameFromType(fiber.type),
35 getInspectorData: () => {
36 return {
37 props: getHostProps(fiber),
38 measure: callback => {
39 const hostFiber = findCurrentHostFiber(fiber);
40 const node =
41 hostFiber != null &&
42 hostFiber.stateNode !== null &&
43 hostFiber.stateNode.node;
44
45 if (node) {
46 nativeFabricUIManager.measure(node, callback);
47 }
48 },
49 };
50 },
51 }));
52 };
53
54 const getHostProps = function (fiber: Fiber) {
55 const host = findCurrentHostFiber(fiber);
56 if (host) {
57 return host.memoizedProps || emptyObject;
58 }
59 return emptyObject;
60 };
61
62 getInspectorDataForInstance = function (
63 closestInstance: Fiber | null,
64 ): InspectorData {
65 // Handle case where user clicks outside of ReactNative
66 if (!closestInstance) {
67 return {
68 hierarchy: [],
69 props: emptyObject,
70 selectedIndex: null,
71 componentStack: '',
72 };
73 }
74
75 const fiber = findCurrentFiberUsingSlowPath(closestInstance);
76 if (fiber === null) {
77 // Might not be currently mounted.
78 return {
79 hierarchy: [],
80 props: emptyObject,
81 selectedIndex: null,
82 componentStack: '',
83 };
84 }
85 const fiberHierarchy = getOwnerHierarchy(fiber);
86 const instance = lastNonHostInstance(fiberHierarchy);
87 const hierarchy = createHierarchy(fiberHierarchy);
88 const props = getHostProps(instance);
89 const selectedIndex = fiberHierarchy.indexOf(instance);
90 const componentStack = getStackByFiberInDevAndProd(fiber);
91
92 return {
93 closestInstance: instance,
94 hierarchy,
95 props,
96 selectedIndex,
97 componentStack,
98 };
99 };
100
101 const getOwnerHierarchy = function (instance: Fiber) {
102 const hierarchy: Array<$FlowFixMe> = [];
103 traverseOwnerTreeUp(hierarchy, instance);
104 return hierarchy;
105 };
106
107 // $FlowFixMe[missing-local-annot]
108 const lastNonHostInstance = function (hierarchy) {
109 for (let i = hierarchy.length - 1; i > 1; i--) {
110 const instance = hierarchy[i];
111
112 if (instance.tag !== HostComponent) {
113 return instance;
114 }
115 }
116 return hierarchy[0];
117 };
118
119 const traverseOwnerTreeUp = function (
120 hierarchy: Array<$FlowFixMe>,
121 instance: Fiber,
122 ): void {
123 hierarchy.unshift(instance);
124 const owner = instance._debugOwner;
125 if (owner != null && typeof owner.tag === 'number') {
126 traverseOwnerTreeUp(hierarchy, owner as any);
127 } else {
128 // TODO: Traverse Server Components owners.
129 }
130 };
131 }
132
133 function getInspectorDataForViewAtPoint(
134 inspectedView: Object,
135 locationX: number,
136 locationY: number,
137 callback: (viewData: TouchedViewDataAtPoint) => mixed,
138 ): void {
139 if (__DEV__) {
140 let closestInstance = null;
141
142 const fabricNode = getNodeFromPublicInstance(inspectedView);
143 if (fabricNode) {
144 // For Fabric we can look up the instance handle directly and measure it.
145 nativeFabricUIManager.findNodeAtPoint(
146 fabricNode,
147 locationX,
148 locationY,
149 internalInstanceHandle => {
150 const node =
151 internalInstanceHandle != null
152 ? getNodeFromInternalInstanceHandle(internalInstanceHandle)
153 : null;
154 if (internalInstanceHandle == null || node == null) {
155 callback({
156 pointerY: locationY,
157 frame: {left: 0, top: 0, width: 0, height: 0},
158 ...getInspectorDataForInstance(closestInstance),
159 });
160 return;
161 }
162
163 closestInstance =
164 internalInstanceHandle.stateNode.canonical.internalInstanceHandle;
165 const closestPublicInstance =
166 internalInstanceHandle.stateNode.canonical.publicInstance;
167
168 // Note: this is deprecated and we want to remove it ASAP. Keeping it here for React DevTools compatibility for now.
169 const nativeViewTag =
170 internalInstanceHandle.stateNode.canonical.nativeTag;
171
172 nativeFabricUIManager.measure(
173 node,
174 (x, y, width, height, pageX, pageY) => {
175 const inspectorData =
176 getInspectorDataForInstance(closestInstance);
177 callback({
178 ...inspectorData,
179 pointerY: locationY,
180 frame: {left: pageX, top: pageY, width, height},
181 touchedViewTag: nativeViewTag,
182 closestPublicInstance,
183 });
184 },
185 );
186 },
187 );
188 } else {
189 console.error(
190 'getInspectorDataForViewAtPoint expects to receive a host component',
191 );
192
193 return;
194 }
195 } else {
196 throw new Error(
197 'getInspectorDataForViewAtPoint() is not available in production.',
198 );
199 }
200 }
201
202 export {getInspectorDataForInstance, getInspectorDataForViewAtPoint};