From 430dd0be26c44821b2bdc2e818a81ac8a0b4b970 Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin Date: Tue, 16 Apr 2024 10:56:24 -0700 Subject: [PATCH] refactor[setUpReactDevTools]: support both RDT from Fusebox and standalone ws-based frontend (#44095) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/44095 # Changelog: [General][Added] - support opening React DevTools from both Chrome DevTools frontend (React Native DevTools) and `react-devtools` standalone app Reviewed By: robhogan Differential Revision: D54896251 fbshipit-source-id: d6156ca09578b67d53393f564a3a591fd13d145f --- .../Libraries/Core/setUpReactDevTools.js | 78 ++++++++++++++++--- .../setUpFuseboxReactDevToolsDispatcher.js | 2 +- 2 files changed, 68 insertions(+), 12 deletions(-) diff --git a/packages/react-native/Libraries/Core/setUpReactDevTools.js b/packages/react-native/Libraries/Core/setUpReactDevTools.js index 47ec03d1672..2c786a4c2d3 100644 --- a/packages/react-native/Libraries/Core/setUpReactDevTools.js +++ b/packages/react-native/Libraries/Core/setUpReactDevTools.js @@ -10,15 +10,52 @@ 'use strict'; +import type {Domain} from '../../src/private/fusebox/setUpFuseboxReactDevToolsDispatcher'; + if (__DEV__) { // Register dispatcher on global, which can be used later by Chrome DevTools frontend require('../../src/private/fusebox/setUpFuseboxReactDevToolsDispatcher'); + // Install hook before React is loaded. + const reactDevTools = require('react-devtools-core'); + // This should be defined in DEV, otherwise error is expected. + const fuseboxReactDevToolsDispatcher = + global.__FUSEBOX_REACT_DEVTOOLS_DISPATCHER__; + const reactDevToolsFuseboxGlobalBindingName = + fuseboxReactDevToolsDispatcher.BINDING_NAME; + + const ReactNativeStyleAttributes = require('../Components/View/ReactNativeStyleAttributes'); + const devToolsSettingsManager = require('../DevToolsSettings/DevToolsSettingsManager'); + const resolveRNStyle = require('../StyleSheet/flattenStyle'); + + let disconnect = null; + function disconnectBackendFromReactDevToolsInFuseboxIfNeeded() { + if (disconnect != null) { + disconnect(); + disconnect = null; + } + } + + function connectToReactDevToolsInFusebox(domain: Domain) { + disconnect = reactDevTools.connectWithCustomMessagingProtocol({ + onSubscribe: listener => { + domain.onMessage.addEventListener(listener); + }, + onUnsubscribe: listener => { + domain.onMessage.removeEventListener(listener); + }, + onMessage: (event, payload) => { + domain.sendMessage({event, payload}); + }, + settingsManager: devToolsSettingsManager, + nativeStyleEditorValidAttributes: Object.keys(ReactNativeStyleAttributes), + resolveRNStyle, + }); + } + let isWebSocketOpen = false; let ws = null; - - const reactDevTools = require('react-devtools-core'); - const connectToDevTools = () => { + function connectToWSBasedReactDevToolsFrontend() { if (ws !== null && isWebSocketOpen) { // If the DevTools backend is already connected, don't recreate the WebSocket. // This would break the connection. @@ -64,12 +101,9 @@ if (__DEV__) { isWebSocketOpen = true; }); - const ReactNativeStyleAttributes = require('../Components/View/ReactNativeStyleAttributes'); - const devToolsSettingsManager = require('../DevToolsSettings/DevToolsSettingsManager'); - reactDevTools.connectToDevTools({ isAppActive, - resolveRNStyle: require('../StyleSheet/flattenStyle'), + resolveRNStyle, nativeStyleEditorValidAttributes: Object.keys( ReactNativeStyleAttributes, ), @@ -77,9 +111,31 @@ if (__DEV__) { devToolsSettingsManager, }); } - }; + } - const RCTNativeAppEventEmitter = require('../EventEmitter/RCTNativeAppEventEmitter'); - RCTNativeAppEventEmitter.addListener('RCTDevMenuShown', connectToDevTools); - connectToDevTools(); // Try connecting once on load + // 1. If React DevTools has already been opened and initialized in Fusebox: + if (global[reactDevToolsFuseboxGlobalBindingName] != null) { + disconnectBackendFromReactDevToolsInFuseboxIfNeeded(); + const domain = + fuseboxReactDevToolsDispatcher.initializeDomain('react-devtools'); + connectToReactDevToolsInFusebox(domain); + } else { + // 2. If React DevTools panel in Fusebox was opened for the first time after the runtime has been created + global.__FUSEBOX_REACT_DEVTOOLS_DISPATCHER__.onDomainInitialization.addEventListener( + (domain: Domain) => { + if (domain.name === 'react-devtools') { + disconnectBackendFromReactDevToolsInFuseboxIfNeeded(); + connectToReactDevToolsInFusebox(domain); + } + }, + ); + + // 3. Fallback to attempting to connect WS-based RDT frontend + const RCTNativeAppEventEmitter = require('../EventEmitter/RCTNativeAppEventEmitter'); + RCTNativeAppEventEmitter.addListener( + 'RCTDevMenuShown', + connectToWSBasedReactDevToolsFrontend, + ); + connectToWSBasedReactDevToolsFrontend(); // Try connecting once on load + } } diff --git a/packages/react-native/src/private/fusebox/setUpFuseboxReactDevToolsDispatcher.js b/packages/react-native/src/private/fusebox/setUpFuseboxReactDevToolsDispatcher.js index e5638d8e6a7..376b1d6fb8d 100644 --- a/packages/react-native/src/private/fusebox/setUpFuseboxReactDevToolsDispatcher.js +++ b/packages/react-native/src/private/fusebox/setUpFuseboxReactDevToolsDispatcher.js @@ -37,7 +37,7 @@ class EventScope { } } -class Domain { +export class Domain { name: DomainName; onMessage: EventScope;