From 8a71c80d23cf75ffee4169bc614f9f3727f4bb85 Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Thu, 4 Dec 2025 11:23:55 -0800 Subject: [PATCH] Exclude legacy Hermes targets from single host assertion (#54784) Summary: ### Context This is a follow-up to D86201689 following user feedback on `0.83.0-rc.2`: https://github.com/react-native-community/discussions-and-proposals/discussions/954#discussioncomment-15138992. The problem was that we were incorrectly attributing any and all `IInspector::addPage()` calls to represent a new React Native Host being registered. However, this API also remains common to the legacy Hermes `ConnectionDemux` runtime target setup (which does not represent a host target). ### This diff We now fork this code path internally based on `InspectorTargetCapabilities.prefersFuseboxFrontend`, in order to track `registeredHostsCount` accurately (i.e. **only** for HostTarget page registrations). Changelog: [General][Fixed] - React Native DevTools: Fix a bug where we would incorrectly flag apps using additonal Hermes runtimes (e.g. Reanimated) as being multi-host Reviewed By: hoxyq Differential Revision: D88386623 --- .../ReactCommon/jsinspector-modern/HostAgent.cpp | 4 ++-- .../jsinspector-modern/InspectorInterfaces.cpp | 15 ++++++++++----- .../jsinspector-modern/InspectorInterfaces.h | 4 ++-- .../jsinspector-modern/TracingAgent.cpp | 2 +- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.cpp b/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.cpp index 813770d51ad5..95b86ad8cfbd 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.cpp @@ -147,7 +147,7 @@ class HostAgent::Impl final { if (InspectorFlags::getInstance().getNetworkInspectionEnabled()) { if (req.method == "Network.enable") { auto& inspector = getInspectorInstance(); - if (inspector.getSystemState().registeredPagesCount > 1) { + if (inspector.getSystemState().registeredHostsCount > 1) { frontendChannel_( cdp::jsonError( req.id, @@ -231,7 +231,7 @@ class HostAgent::Impl final { "ReactNativeApplication.metadataUpdated", createHostMetadataPayload(hostMetadata_))); auto& inspector = getInspectorInstance(); - bool isSingleHost = inspector.getSystemState().registeredPagesCount <= 1; + bool isSingleHost = inspector.getSystemState().registeredHostsCount <= 1; if (!isSingleHost) { emitSystemStateChanged(isSingleHost); } diff --git a/packages/react-native/ReactCommon/jsinspector-modern/InspectorInterfaces.cpp b/packages/react-native/ReactCommon/jsinspector-modern/InspectorInterfaces.cpp index b0d2f7a5ffd4..065dd0e53c3b 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/InspectorInterfaces.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/InspectorInterfaces.cpp @@ -67,8 +67,8 @@ class InspectorImpl : public IInspector { public: explicit SystemStateListener(InspectorSystemState& state) : state_(state) {} - void onPageAdded(int /*pageId*/) override { - state_.registeredPagesCount++; + void unstable_onHostTargetAdded() override { + state_.registeredHostsCount++; } private: @@ -94,6 +94,7 @@ class InspectorImpl : public IInspector { ConnectFunc connectFunc_; InspectorTargetCapabilities capabilities_; }; + mutable std::mutex mutex_; int nextPageId_{1}; std::map pages_; @@ -142,9 +143,13 @@ int InspectorImpl::addPage( pageId, Page{pageId, description, vm, std::move(connectFunc), capabilities}); - for (const auto& listenerWeak : listeners_) { - if (auto listener = listenerWeak.lock()) { - listener->onPageAdded(pageId); + // Strong assumption: If prefersFuseboxFrontend is set, the page added is a + // HostTarget and not a legacy Hermes runtime target. + if (capabilities.prefersFuseboxFrontend) { + for (const auto& listenerWeak : listeners_) { + if (auto listener = listenerWeak.lock()) { + listener->unstable_onHostTargetAdded(); + } } } diff --git a/packages/react-native/ReactCommon/jsinspector-modern/InspectorInterfaces.h b/packages/react-native/ReactCommon/jsinspector-modern/InspectorInterfaces.h index 3117b216ac2d..aa256a4bf7d0 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/InspectorInterfaces.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/InspectorInterfaces.h @@ -53,7 +53,7 @@ using InspectorPage = InspectorPageDescription; struct InspectorSystemState { /** The total count of pages registered during the app lifetime. */ - int registeredPagesCount; + int registeredHostsCount; }; /// IRemoteConnection allows the VM to send debugger messages to the client. @@ -83,7 +83,7 @@ class JSINSPECTOR_EXPORT ILocalConnection : public IDestructible { class JSINSPECTOR_EXPORT IPageStatusListener : public IDestructible { public: virtual ~IPageStatusListener() = 0; - virtual void onPageAdded(int /*pageId*/) {} + virtual void unstable_onHostTargetAdded() {} virtual void onPageRemoved(int /*pageId*/) {} }; diff --git a/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.cpp b/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.cpp index 823c56ecfaa9..b8cc1cc1ab1b 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.cpp @@ -54,7 +54,7 @@ TracingAgent::~TracingAgent() { bool TracingAgent::handleRequest(const cdp::PreparsedRequest& req) { if (req.method == "Tracing.start") { auto& inspector = getInspectorInstance(); - if (inspector.getSystemState().registeredPagesCount > 1) { + if (inspector.getSystemState().registeredHostsCount > 1) { frontendChannel_( cdp::jsonError( req.id,