From a63204800a1aa23eb050b8f276be70f75057d1da Mon Sep 17 00:00:00 2001 From: Tianyu Yao Date: Tue, 16 Aug 2022 12:03:17 -0700 Subject: [PATCH] Show highlight when element is selected in React devtools Summary: Changelog: [General][Changed] - Copied and refactored the current devtools highlighting code from Inspector into its own module and add to the top level `AppContainer`. The effect is that the highlight stills shows without Inspector opened. This diff copies the current devtools highlighting logic from Inspector into a module and add to the top level `AppContainer`. The effect is without Inspector opened, the highlight will still show. ## Context This is the first diff for "Component Tab Automatically Highlight Elements". The idea is to replicate the behavior on Web to RN. ## Behavior on Web: - highlight shows whenever an element in the component list is hovered - Selecting an element doesn't keeps the highlight showing. on RN (before this diff): - when RN inspector opens: selecting an element keeps the highlight showing - when RN inspector closes: stop showing highlihgintg. on RN(this diff) - selecting an element keeps the highlight showing ## TODO - See if highlighting event can be sent on hover, instead of when an element is selected. Reviewed By: lunaruan Differential Revision: D38568135 fbshipit-source-id: d168874677d08a9c5526a7f896943579da804565 --- Libraries/Inspector/DevtoolsHighlighter.js | 80 ++++++++++++++++++++++ Libraries/Inspector/Inspector.js | 38 ---------- Libraries/ReactNative/AppContainer.js | 20 ++++-- 3 files changed, 93 insertions(+), 45 deletions(-) create mode 100644 Libraries/Inspector/DevtoolsHighlighter.js diff --git a/Libraries/Inspector/DevtoolsHighlighter.js b/Libraries/Inspector/DevtoolsHighlighter.js new file mode 100644 index 00000000000000..c3d6f1c3a3fbc3 --- /dev/null +++ b/Libraries/Inspector/DevtoolsHighlighter.js @@ -0,0 +1,80 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow + */ + +import ElementBox from './ElementBox'; +import * as React from 'react'; +const {useEffect, useState} = React; + +const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__; + +export default function DevtoolsHighlighter(): React.Node { + const [inspected, setInspected] = useState(null); + useEffect(() => { + let devToolsAgent = null; + let hideTimeoutId = null; + + function onAgentHideNativeHighlight() { + // we wait to actually hide in order to avoid flicker + clearTimeout(hideTimeoutId); + hideTimeoutId = setTimeout(() => { + setInspected(null); + }, 100); + } + + function onAgentShowNativeHighlight(node: any) { + clearTimeout(hideTimeoutId); + // Shape of `node` is different in Fabric. + const component = node.canonical ?? node; + + component.measure((x, y, width, height, left, top) => { + setInspected({ + frame: {left, top, width, height}, + }); + }); + } + + function cleanup() { + const currentAgent = devToolsAgent; + if (currentAgent != null) { + currentAgent.removeListener( + 'hideNativeHighlight', + onAgentHideNativeHighlight, + ); + currentAgent.removeListener( + 'showNativeHighlight', + onAgentShowNativeHighlight, + ); + currentAgent.removeListener('shutdown', cleanup); + devToolsAgent = null; + } + } + + function _attachToDevtools(agent: Object) { + devToolsAgent = agent; + agent.addListener('hideNativeHighlight', onAgentHideNativeHighlight); + agent.addListener('showNativeHighlight', onAgentShowNativeHighlight); + agent.addListener('shutdown', cleanup); + } + + hook.on('react-devtools', _attachToDevtools); + if (hook.reactDevtoolsAgent) { + _attachToDevtools(hook.reactDevtoolsAgent); + } + return () => { + hook.off('react-devtools', _attachToDevtools); + cleanup(); + }; + }, []); + + if (inspected != null) { + return ; + } + return null; +} diff --git a/Libraries/Inspector/Inspector.js b/Libraries/Inspector/Inspector.js index 1134e25429ff35..504c6963f5386a 100644 --- a/Libraries/Inspector/Inspector.js +++ b/Libraries/Inspector/Inspector.js @@ -144,8 +144,6 @@ class Inspector extends React.Component< } _attachToDevtools = (agent: Object) => { - agent.addListener('hideNativeHighlight', this._onAgentHideNativeHighlight); - agent.addListener('showNativeHighlight', this._onAgentShowNativeHighlight); agent.addListener('shutdown', this._onAgentShutdown); this.setState({ @@ -153,45 +151,9 @@ class Inspector extends React.Component< }); }; - _onAgentHideNativeHighlight = () => { - if (this.state.inspected === null) { - return; - } - // we wait to actually hide in order to avoid flicker - this._hideTimeoutID = setTimeout(() => { - this.setState({ - inspected: null, - }); - }, 100); - }; - - _onAgentShowNativeHighlight = (node: any) => { - clearTimeout(this._hideTimeoutID); - - // Shape of `node` is different in Fabric. - const component = node.canonical ?? node; - - component.measure((x, y, width, height, left, top) => { - this.setState({ - hierarchy: [], - inspected: { - frame: {left, top, width, height}, - }, - }); - }); - }; - _onAgentShutdown = () => { const agent = this.state.devtoolsAgent; if (agent != null) { - agent.removeListener( - 'hideNativeHighlight', - this._onAgentHideNativeHighlight, - ); - agent.removeListener( - 'showNativeHighlight', - this._onAgentShowNativeHighlight, - ); agent.removeListener('shutdown', this._onAgentShutdown); this.setState({devtoolsAgent: null}); diff --git a/Libraries/ReactNative/AppContainer.js b/Libraries/ReactNative/AppContainer.js index 8495893f8e3186..ccf04260152b34 100644 --- a/Libraries/ReactNative/AppContainer.js +++ b/Libraries/ReactNative/AppContainer.js @@ -77,14 +77,19 @@ class AppContainer extends React.Component { render(): React.Node { let logBox = null; + let devtoolsHighlighter = null; if (__DEV__) { - if ( - !global.__RCTProfileIsProfiling && - !this.props.internal_excludeLogBox - ) { - const LogBoxNotificationContainer = - require('../LogBox/LogBoxNotificationContainer').default; - logBox = ; + if (!global.__RCTProfileIsProfiling) { + if (!this.props.internal_excludeLogBox) { + const LogBoxNotificationContainer = + require('../LogBox/LogBoxNotificationContainer').default; + logBox = ; + } + if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__ != null) { + const DevtoolsHighlighter = + require('../Inspector/DevtoolsHighlighter').default; + devtoolsHighlighter = ; + } } } @@ -118,6 +123,7 @@ class AppContainer extends React.Component { {!this.state.hasError && innerView} + {devtoolsHighlighter} {this.state.inspector} {logBox}