Inspector.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. /**
  2. * Copyright (c) Facebook, Inc. and its 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. * @format
  8. * @flow
  9. */
  10. 'use strict';
  11. const Dimensions = require('../Utilities/Dimensions');
  12. const InspectorOverlay = require('./InspectorOverlay');
  13. const InspectorPanel = require('./InspectorPanel');
  14. const Platform = require('../Utilities/Platform');
  15. const React = require('react');
  16. const ReactNative = require('../Renderer/shims/ReactNative');
  17. const StyleSheet = require('../StyleSheet/StyleSheet');
  18. const Touchable = require('../Components/Touchable/Touchable');
  19. const View = require('../Components/View/View');
  20. const invariant = require('invariant');
  21. import type {
  22. HostComponent,
  23. TouchedViewDataAtPoint,
  24. } from '../Renderer/shims/ReactNativeTypes';
  25. type HostRef = React.ElementRef<HostComponent<mixed>>;
  26. export type ReactRenderer = {
  27. rendererConfig: {
  28. getInspectorDataForViewAtPoint: (
  29. inspectedView: ?HostRef,
  30. locationX: number,
  31. locationY: number,
  32. callback: Function,
  33. ) => void,
  34. ...
  35. },
  36. };
  37. const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
  38. const renderers = findRenderers();
  39. // Required for React DevTools to view/edit React Native styles in Flipper.
  40. // Flipper doesn't inject these values when initializing DevTools.
  41. hook.resolveRNStyle = require('../StyleSheet/flattenStyle');
  42. const viewConfig = require('../Components/View/ReactNativeViewViewConfig');
  43. hook.nativeStyleEditorValidAttributes = Object.keys(
  44. viewConfig.validAttributes.style,
  45. );
  46. function findRenderers(): $ReadOnlyArray<ReactRenderer> {
  47. const allRenderers = Array.from(hook.renderers.values());
  48. invariant(
  49. allRenderers.length >= 1,
  50. 'Expected to find at least one React Native renderer on DevTools hook.',
  51. );
  52. return allRenderers;
  53. }
  54. function getInspectorDataForViewAtPoint(
  55. inspectedView: ?HostRef,
  56. locationX: number,
  57. locationY: number,
  58. callback: (viewData: TouchedViewDataAtPoint) => void,
  59. ) {
  60. // Check all renderers for inspector data.
  61. for (let i = 0; i < renderers.length; i++) {
  62. const renderer = renderers[i];
  63. if (renderer?.rendererConfig?.getInspectorDataForViewAtPoint != null) {
  64. renderer.rendererConfig.getInspectorDataForViewAtPoint(
  65. inspectedView,
  66. locationX,
  67. locationY,
  68. viewData => {
  69. // Only return with non-empty view data since only one renderer will have this view.
  70. if (viewData && viewData.hierarchy.length > 0) {
  71. callback(viewData);
  72. }
  73. },
  74. );
  75. }
  76. }
  77. }
  78. class Inspector extends React.Component<
  79. {
  80. inspectedView: ?HostRef,
  81. onRequestRerenderApp: (callback: (instance: ?HostRef) => void) => void,
  82. ...
  83. },
  84. {
  85. devtoolsAgent: ?Object,
  86. hierarchy: any,
  87. panelPos: string,
  88. inspecting: boolean,
  89. selection: ?number,
  90. perfing: boolean,
  91. inspected: any,
  92. inspectedView: ?HostRef,
  93. networking: boolean,
  94. ...
  95. },
  96. > {
  97. _hideTimeoutID: TimeoutID | null = null;
  98. _subs: ?Array<() => void>;
  99. _setTouchedViewData: ?(TouchedViewDataAtPoint) => void;
  100. constructor(props: Object) {
  101. super(props);
  102. this.state = {
  103. devtoolsAgent: null,
  104. hierarchy: null,
  105. panelPos: 'bottom',
  106. inspecting: true,
  107. perfing: false,
  108. inspected: null,
  109. selection: null,
  110. inspectedView: this.props.inspectedView,
  111. networking: false,
  112. };
  113. }
  114. componentDidMount() {
  115. hook.on('react-devtools', this._attachToDevtools);
  116. // if devtools is already started
  117. if (hook.reactDevtoolsAgent) {
  118. this._attachToDevtools(hook.reactDevtoolsAgent);
  119. }
  120. }
  121. componentWillUnmount() {
  122. if (this._subs) {
  123. this._subs.map(fn => fn());
  124. }
  125. hook.off('react-devtools', this._attachToDevtools);
  126. this._setTouchedViewData = null;
  127. }
  128. UNSAFE_componentWillReceiveProps(newProps: Object) {
  129. this.setState({inspectedView: newProps.inspectedView});
  130. }
  131. _attachToDevtools = (agent: Object) => {
  132. agent.addListener('hideNativeHighlight', this._onAgentHideNativeHighlight);
  133. agent.addListener('showNativeHighlight', this._onAgentShowNativeHighlight);
  134. agent.addListener('shutdown', this._onAgentShutdown);
  135. this.setState({
  136. devtoolsAgent: agent,
  137. });
  138. };
  139. _onAgentHideNativeHighlight = () => {
  140. if (this.state.inspected === null) {
  141. return;
  142. }
  143. // we wait to actually hide in order to avoid flicker
  144. this._hideTimeoutID = setTimeout(() => {
  145. this.setState({
  146. inspected: null,
  147. });
  148. }, 100);
  149. };
  150. _onAgentShowNativeHighlight = node => {
  151. clearTimeout(this._hideTimeoutID);
  152. node.measure((x, y, width, height, left, top) => {
  153. this.setState({
  154. hierarchy: [],
  155. inspected: {
  156. frame: {left, top, width, height},
  157. },
  158. });
  159. });
  160. };
  161. _onAgentShutdown = () => {
  162. const agent = this.state.devtoolsAgent;
  163. if (agent != null) {
  164. agent.removeListener(
  165. 'hideNativeHighlight',
  166. this._onAgentHideNativeHighlight,
  167. );
  168. agent.removeListener(
  169. 'showNativeHighlight',
  170. this._onAgentShowNativeHighlight,
  171. );
  172. agent.removeListener('shutdown', this._onAgentShutdown);
  173. this.setState({devtoolsAgent: null});
  174. }
  175. };
  176. setSelection(i: number) {
  177. const hierarchyItem = this.state.hierarchy[i];
  178. // we pass in ReactNative.findNodeHandle as the method is injected
  179. const {measure, props, source} = hierarchyItem.getInspectorData(
  180. ReactNative.findNodeHandle,
  181. );
  182. measure((x, y, width, height, left, top) => {
  183. this.setState({
  184. inspected: {
  185. frame: {left, top, width, height},
  186. style: props.style,
  187. source,
  188. },
  189. selection: i,
  190. });
  191. });
  192. }
  193. onTouchPoint(locationX: number, locationY: number) {
  194. this._setTouchedViewData = viewData => {
  195. const {
  196. hierarchy,
  197. props,
  198. selectedIndex,
  199. source,
  200. frame,
  201. pointerY,
  202. touchedViewTag,
  203. } = viewData;
  204. // Sync the touched view with React DevTools.
  205. // Note: This is Paper only. To support Fabric,
  206. // DevTools needs to be updated to not rely on view tags.
  207. if (this.state.devtoolsAgent && touchedViewTag) {
  208. this.state.devtoolsAgent.selectNode(
  209. ReactNative.findNodeHandle(touchedViewTag),
  210. );
  211. }
  212. this.setState({
  213. panelPos:
  214. pointerY > Dimensions.get('window').height / 2 ? 'top' : 'bottom',
  215. selection: selectedIndex,
  216. hierarchy,
  217. inspected: {
  218. style: props.style,
  219. frame,
  220. source,
  221. },
  222. });
  223. };
  224. getInspectorDataForViewAtPoint(
  225. this.state.inspectedView,
  226. locationX,
  227. locationY,
  228. viewData => {
  229. if (this._setTouchedViewData != null) {
  230. this._setTouchedViewData(viewData);
  231. this._setTouchedViewData = null;
  232. }
  233. },
  234. );
  235. }
  236. setPerfing(val: boolean) {
  237. this.setState({
  238. perfing: val,
  239. inspecting: false,
  240. inspected: null,
  241. networking: false,
  242. });
  243. }
  244. setInspecting(val: boolean) {
  245. this.setState({
  246. inspecting: val,
  247. inspected: null,
  248. });
  249. }
  250. setTouchTargeting(val: boolean) {
  251. Touchable.TOUCH_TARGET_DEBUG = val;
  252. this.props.onRequestRerenderApp(inspectedView => {
  253. this.setState({inspectedView});
  254. });
  255. }
  256. setNetworking(val: boolean) {
  257. this.setState({
  258. networking: val,
  259. perfing: false,
  260. inspecting: false,
  261. inspected: null,
  262. });
  263. }
  264. render(): React.Node {
  265. const panelContainerStyle =
  266. this.state.panelPos === 'bottom'
  267. ? {bottom: 0}
  268. : {top: Platform.OS === 'ios' ? 20 : 0};
  269. return (
  270. <View style={styles.container} pointerEvents="box-none">
  271. {this.state.inspecting && (
  272. <InspectorOverlay
  273. inspected={this.state.inspected}
  274. onTouchPoint={this.onTouchPoint.bind(this)}
  275. />
  276. )}
  277. <View style={[styles.panelContainer, panelContainerStyle]}>
  278. <InspectorPanel
  279. devtoolsIsOpen={!!this.state.devtoolsAgent}
  280. inspecting={this.state.inspecting}
  281. perfing={this.state.perfing}
  282. setPerfing={this.setPerfing.bind(this)}
  283. setInspecting={this.setInspecting.bind(this)}
  284. inspected={this.state.inspected}
  285. hierarchy={this.state.hierarchy}
  286. selection={this.state.selection}
  287. setSelection={this.setSelection.bind(this)}
  288. touchTargeting={Touchable.TOUCH_TARGET_DEBUG}
  289. setTouchTargeting={this.setTouchTargeting.bind(this)}
  290. networking={this.state.networking}
  291. setNetworking={this.setNetworking.bind(this)}
  292. />
  293. </View>
  294. </View>
  295. );
  296. }
  297. }
  298. const styles = StyleSheet.create({
  299. container: {
  300. position: 'absolute',
  301. backgroundColor: 'transparent',
  302. top: 0,
  303. left: 0,
  304. right: 0,
  305. bottom: 0,
  306. },
  307. panelContainer: {
  308. position: 'absolute',
  309. left: 0,
  310. right: 0,
  311. },
  312. });
  313. module.exports = Inspector;