From 0e25f074b6dff978b97fc8060b5a50454521e681 Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin Date: Mon, 15 Jan 2024 11:51:13 -0800 Subject: [PATCH] feat[DebuggingOverlayRegistry]: basic implementation (#41743) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/41743 Changelog: [Internal] There will be a single DebuggingRegistry instance per runtime, which will be responsible for finding lowest AppContainer ancestor for highligthed component. It will receive refs to root views (ancestors, AppContainers) as subscriptions and later will call all necessary methods. In the next series of diffs, subscriber will also provide reference to the DebuggingOverlay, on which DebuggingRegistry can call all necessary methods to highlight elements. Differential Revision: D51536787 Reviewed By: rshest fbshipit-source-id: a6a64ef5f2cec6eb10881275929272ce02038c20 --- .../Debugging/DebuggingOverlayRegistry.js | 38 +++++++++++++++++++ .../useSubscribeToDebuggingOverlayRegistry.js | 28 ++++++++++++++ .../Libraries/Inspector/Inspector.js | 3 +- .../Inspector/ReactDevToolsOverlay.js | 3 +- .../Libraries/ReactNative/AppContainer-dev.js | 18 +++++++-- .../__snapshots__/public-api-test.js.snap | 34 +++++++++++++++-- 6 files changed, 115 insertions(+), 9 deletions(-) create mode 100644 packages/react-native/Libraries/Debugging/DebuggingOverlayRegistry.js create mode 100644 packages/react-native/Libraries/Debugging/useSubscribeToDebuggingOverlayRegistry.js diff --git a/packages/react-native/Libraries/Debugging/DebuggingOverlayRegistry.js b/packages/react-native/Libraries/Debugging/DebuggingOverlayRegistry.js new file mode 100644 index 00000000000000..fd858f928de31f --- /dev/null +++ b/packages/react-native/Libraries/Debugging/DebuggingOverlayRegistry.js @@ -0,0 +1,38 @@ +/** + * 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. + * + * @flow strict-local + * @format + * @oncall react_native + */ + +import type {AppContainerRootViewRef} from '../ReactNative/AppContainer-dev'; + +export type DebuggingOverlayRegistrySubscriberProtocol = { + rootViewRef: AppContainerRootViewRef, +}; + +class DebuggingOverlayRegistry { + #registry: Set = new Set(); + + subscribe(subscriber: DebuggingOverlayRegistrySubscriberProtocol) { + this.#registry.add(subscriber); + } + + unsubscribe(subscriber: DebuggingOverlayRegistrySubscriberProtocol) { + const wasPresent = this.#registry.delete(subscriber); + if (!wasPresent) { + console.error( + '[DebuggingOverlayRegistry] Unexpected argument for unsubscription, which was not previously subscribed:', + subscriber, + ); + } + } +} + +const debuggingOverlayRegistryInstance: DebuggingOverlayRegistry = + new DebuggingOverlayRegistry(); +export default debuggingOverlayRegistryInstance; diff --git a/packages/react-native/Libraries/Debugging/useSubscribeToDebuggingOverlayRegistry.js b/packages/react-native/Libraries/Debugging/useSubscribeToDebuggingOverlayRegistry.js new file mode 100644 index 00000000000000..eae9392263c903 --- /dev/null +++ b/packages/react-native/Libraries/Debugging/useSubscribeToDebuggingOverlayRegistry.js @@ -0,0 +1,28 @@ +/** + * 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. + * + * @flow strict-local + * @format + * @oncall react_native + */ + +import type {AppContainerRootViewRef} from '../ReactNative/AppContainer-dev'; + +import DebuggingOverlayRegistry from './DebuggingOverlayRegistry'; +import {useEffect} from 'react'; + +const useSubscribeToDebuggingOverlayRegistry = ( + rootViewRef: AppContainerRootViewRef, +) => { + useEffect(() => { + const subscriber = {rootViewRef}; + + DebuggingOverlayRegistry.subscribe(subscriber); + return () => DebuggingOverlayRegistry.unsubscribe(subscriber); + }, [rootViewRef]); +}; + +export default useSubscribeToDebuggingOverlayRegistry; diff --git a/packages/react-native/Libraries/Inspector/Inspector.js b/packages/react-native/Libraries/Inspector/Inspector.js index dc274521388903..94c32994a781c6 100644 --- a/packages/react-native/Libraries/Inspector/Inspector.js +++ b/packages/react-native/Libraries/Inspector/Inspector.js @@ -10,6 +10,7 @@ 'use strict'; +import type {InspectedViewRef} from '../ReactNative/AppContainer-dev'; import type { InspectorData, TouchedViewDataAtPoint, @@ -46,7 +47,7 @@ export type InspectedElement = $ReadOnly<{ export type ElementsHierarchy = InspectorData['hierarchy']; type Props = { - inspectedViewRef: React.RefObject | null>, + inspectedViewRef: InspectedViewRef, onRequestRerenderApp: () => void, reactDevToolsAgent?: ReactDevToolsAgent, }; diff --git a/packages/react-native/Libraries/Inspector/ReactDevToolsOverlay.js b/packages/react-native/Libraries/Inspector/ReactDevToolsOverlay.js index cca9e7e8b7dc88..c62bc27b2ebe17 100644 --- a/packages/react-native/Libraries/Inspector/ReactDevToolsOverlay.js +++ b/packages/react-native/Libraries/Inspector/ReactDevToolsOverlay.js @@ -8,6 +8,7 @@ * @flow */ +import type {InspectedViewRef} from '../ReactNative/AppContainer-dev'; import type {PointerEvent} from '../Types/CoreEventTypes'; import type {PressEvent} from '../Types/CoreEventTypes'; import type { @@ -29,7 +30,7 @@ const getInspectorDataForViewAtPoint = require('./getInspectorDataForViewAtPoint const {useEffect, useState, useCallback} = React; type Props = { - inspectedViewRef: React.RefObject | null>, + inspectedViewRef: InspectedViewRef, reactDevToolsAgent: ReactDevToolsAgent, }; diff --git a/packages/react-native/Libraries/ReactNative/AppContainer-dev.js b/packages/react-native/Libraries/ReactNative/AppContainer-dev.js index 3e4fe8389610e7..e8f0c67be497d4 100644 --- a/packages/react-native/Libraries/ReactNative/AppContainer-dev.js +++ b/packages/react-native/Libraries/ReactNative/AppContainer-dev.js @@ -18,6 +18,7 @@ import type {Props} from './AppContainer'; import TraceUpdateOverlay from '../Components/TraceUpdateOverlay/TraceUpdateOverlay'; import ReactNativeStyleAttributes from '../Components/View/ReactNativeStyleAttributes'; import View from '../Components/View/View'; +import useSubscribeToDebuggingOverlayRegistry from '../Debugging/useSubscribeToDebuggingOverlayRegistry'; import RCTDeviceEventEmitter from '../EventEmitter/RCTDeviceEventEmitter'; import ReactDevToolsOverlay from '../Inspector/ReactDevToolsOverlay'; import LogBoxNotificationContainer from '../LogBox/LogBoxNotificationContainer'; @@ -40,7 +41,7 @@ if (reactDevToolsHook) { } type InspectorDeferredProps = { - inspectedViewRef: React.RefObject | null>, + inspectedViewRef: InspectedViewRef, onInspectedViewRerenderRequest: () => void, reactDevToolsAgent?: ReactDevToolsAgent, }; @@ -73,7 +74,9 @@ const AppContainer = ({ showArchitectureIndicator, WrapperComponent, }: Props): React.Node => { - const innerViewRef = React.useRef | null>(null); + const appContainerRootViewRef: AppContainerRootViewRef = React.useRef(null); + const innerViewRef: InspectedViewRef = React.useRef(null); + useSubscribeToDebuggingOverlayRegistry(appContainerRootViewRef); const [key, setKey] = useState(0); const [shouldRenderInspector, setShouldRenderInspector] = useState(false); @@ -138,7 +141,10 @@ const AppContainer = ({ return ( - + {innerView} {reactDevToolsAgent != null && ( @@ -169,4 +175,10 @@ const styles = StyleSheet.create({ container: {flex: 1}, }); +export type AppContainerRootViewRef = React.RefObject | null>; +export type InspectedViewRef = React.RefObject | null>; export default AppContainer; diff --git a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap index d17a2d04b0d0cc..0ba4cbb86aee03 100644 --- a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap +++ b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap @@ -4777,6 +4777,27 @@ declare export default HostComponent; " `; +exports[`public API should not change unintentionally Libraries/Debugging/DebuggingOverlayRegistry.js 1`] = ` +"export type DebuggingOverlayRegistrySubscriberProtocol = { + rootViewRef: AppContainerRootViewRef, +}; +declare class DebuggingOverlayRegistry { + subscribe(subscriber: DebuggingOverlayRegistrySubscriberProtocol): void; + unsubscribe(subscriber: DebuggingOverlayRegistrySubscriberProtocol): void; +} +declare const debuggingOverlayRegistryInstance: DebuggingOverlayRegistry; +declare export default typeof debuggingOverlayRegistryInstance; +" +`; + +exports[`public API should not change unintentionally Libraries/Debugging/useSubscribeToDebuggingOverlayRegistry.js 1`] = ` +"declare const useSubscribeToDebuggingOverlayRegistry: ( + rootViewRef: AppContainerRootViewRef +) => void; +declare export default typeof useSubscribeToDebuggingOverlayRegistry; +" +`; + exports[`public API should not change unintentionally Libraries/DevToolsSettings/DevToolsSettingsManager.android.js 1`] = ` "declare module.exports: { setConsolePatchSettings(newSettings: string): void, @@ -5406,8 +5427,7 @@ declare module.exports: resolveAssetSource; `; exports[`public API should not change unintentionally Libraries/Inspector/Inspector.js 1`] = ` -"declare const View: $FlowFixMe; -declare const React: $FlowFixMe; +"declare const React: $FlowFixMe; export type InspectedElementFrame = TouchedViewDataAtPoint[\\"frame\\"]; export type InspectedElementSource = InspectorData[\\"source\\"]; export type InspectedElement = $ReadOnly<{ @@ -5417,7 +5437,7 @@ export type InspectedElement = $ReadOnly<{ }>; export type ElementsHierarchy = InspectorData[\\"hierarchy\\"]; type Props = { - inspectedViewRef: React.RefObject | null>, + inspectedViewRef: InspectedViewRef, onRequestRerenderApp: () => void, reactDevToolsAgent?: ReactDevToolsAgent, }; @@ -5439,7 +5459,7 @@ declare module.exports: InspectorOverlay; exports[`public API should not change unintentionally Libraries/Inspector/ReactDevToolsOverlay.js 1`] = ` "type Props = { - inspectedViewRef: React.RefObject | null>, + inspectedViewRef: InspectedViewRef, reactDevToolsAgent: ReactDevToolsAgent, }; declare export default function ReactDevToolsOverlay(Props): React.Node; @@ -7520,6 +7540,12 @@ declare module.exports: AppContainer; exports[`public API should not change unintentionally Libraries/ReactNative/AppContainer-dev.js 1`] = ` "declare const AppContainer: (Props) => React.Node; +export type AppContainerRootViewRef = React.RefObject | null>; +export type InspectedViewRef = React.RefObject | null>; declare export default typeof AppContainer; " `;