From 79393ff52c8aa74f39c30bf6647c0cdc30a6ef90 Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin Date: Thu, 23 Oct 2025 04:55:38 -0700 Subject: [PATCH 1/3] Group sampling methods in header files (#54243) Summary: # Changelog: [Internal] Just formatting. Differential Revision: D85266409 --- .../inspector-modern/chrome/HermesRuntimeTargetDelegate.h | 2 -- .../ReactCommon/jsinspector-modern/RuntimeTarget.h | 6 ------ 2 files changed, 8 deletions(-) diff --git a/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeTargetDelegate.h b/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeTargetDelegate.h index 63662d9df270..9fc4f9c85a47 100644 --- a/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeTargetDelegate.h +++ b/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeTargetDelegate.h @@ -55,9 +55,7 @@ class HermesRuntimeTargetDelegate : public RuntimeTargetDelegate { size_t framesToSkip) override; void enableSamplingProfiler() override; - void disableSamplingProfiler() override; - tracing::RuntimeSamplingProfile collectSamplingProfile() override; std::optional serializeStackTrace( diff --git a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h index 451945bad1fa..61e579594866 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h @@ -99,12 +99,10 @@ class RuntimeTargetDelegate { * Start sampling profiler. */ virtual void enableSamplingProfiler() = 0; - /** * Stop sampling profiler. */ virtual void disableSamplingProfiler() = 0; - /** * Return recorded sampling profile for the previous sampling session. */ @@ -148,12 +146,10 @@ class RuntimeTargetController { * Start sampling profiler for the corresponding RuntimeTarget. */ void enableSamplingProfiler(); - /** * Stop sampling profiler for the corresponding RuntimeTarget. */ void disableSamplingProfiler(); - /** * Return recorded sampling profile for the previous sampling session. */ @@ -227,12 +223,10 @@ class JSINSPECTOR_EXPORT RuntimeTarget * Start sampling profiler for a particular JavaScript runtime. */ void enableSamplingProfiler(); - /** * Stop sampling profiler for a particular JavaScript runtime. */ void disableSamplingProfiler(); - /** * Return recorded sampling profile for the previous sampling session. */ From de9171905dc51e916a8965baf5baf75b7e7ff769 Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin Date: Thu, 23 Oct 2025 04:55:38 -0700 Subject: [PATCH 2/3] jsinspector: capture ReactNativeApplication domain notifications (#54244) Summary: # Changelog: [Internal] Refactors the logic a bit, and adds `ReactNativeApplication` domain to the ones that are currently being tracked. We will use it as a signal for installation of `console.createTask()` implementation. Differential Revision: D85274860 --- .../jsinspector-modern/HostAgent.cpp | 6 +- .../jsinspector-modern/RuntimeAgent.cpp | 19 +++++ .../jsinspector-modern/RuntimeTarget.cpp | 84 ++++++++++++++----- .../jsinspector-modern/RuntimeTarget.h | 25 +++++- 4 files changed, 109 insertions(+), 25 deletions(-) diff --git a/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.cpp b/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.cpp index bebb24a3bd7c..416244896b2e 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.cpp @@ -224,16 +224,18 @@ class HostAgent::Impl final { std::move(stashedTraceRecording.value())); } + // Percolate down to the RuntimeAgent. return { - .isFinishedHandlingRequest = true, + .isFinishedHandlingRequest = false, .shouldSendOKResponse = true, }; } if (req.method == "ReactNativeApplication.disable") { sessionState_.isReactNativeApplicationDomainEnabled = false; + // Percolate down to the RuntimeAgent. return { - .isFinishedHandlingRequest = true, + .isFinishedHandlingRequest = false, .shouldSendOKResponse = true, }; } diff --git a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeAgent.cpp b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeAgent.cpp index d1821f6b74b7..e632056b9378 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeAgent.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeAgent.cpp @@ -43,6 +43,11 @@ RuntimeAgent::RuntimeAgent( targetController_.notifyDomainStateChanged( RuntimeTargetController::Domain::Network, true, *this); } + + if (sessionState_.isReactNativeApplicationDomainEnabled) { + targetController_.notifyDomainStateChanged( + RuntimeTargetController::Domain::ReactNativeApplication, true, *this); + } } bool RuntimeAgent::handleRequest(const cdp::PreparsedRequest& req) { @@ -84,6 +89,16 @@ bool RuntimeAgent::handleRequest(const cdp::PreparsedRequest& req) { sessionState_.isNetworkDomainEnabled, *this); + // We are not responding to this request, just processing a side effect. + return false; + } else if ( + req.method == "ReactNativeApplication.enable" || + req.method == "ReactNativeApplication.disable") { + targetController_.notifyDomainStateChanged( + RuntimeTargetController::Domain::ReactNativeApplication, + sessionState_.isReactNativeApplicationDomainEnabled, + *this); + // We are not responding to this request, just processing a side effect. return false; } @@ -137,6 +152,10 @@ RuntimeAgent::~RuntimeAgent() { targetController_.notifyDomainStateChanged( RuntimeTargetController::Domain::Network, false, *this); } + if (sessionState_.isReactNativeApplicationDomainEnabled) { + targetController_.notifyDomainStateChanged( + RuntimeTargetController::Domain::ReactNativeApplication, false, *this); + } // TODO: Eventually, there may be more than one Runtime per Page, and we'll // need to store multiple agent states here accordingly. For now let's do diff --git a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.cpp b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.cpp index a42493ad55cc..2bf76fd2e8dc 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.cpp @@ -179,40 +179,80 @@ void RuntimeTarget::notifyDomainStateChanged( Domain domain, bool enabled, const RuntimeAgent& notifyingAgent) { - bool runtimeAndLogStatusBefore = false, runtimeAndLogStatusAfter = false; - if (domain == Domain::Log || domain == Domain::Runtime) { - runtimeAndLogStatusBefore = - agentsByEnabledDomain_[Domain::Runtime].contains(¬ifyingAgent) && - agentsByEnabledDomain_[Domain::Log].contains(¬ifyingAgent); - } - - if (enabled) { - agentsByEnabledDomain_[domain].insert(¬ifyingAgent); - } else { - agentsByEnabledDomain_[domain].erase(¬ifyingAgent); - } - threadSafeDomainStatus_[domain] = !agentsByEnabledDomain_[domain].empty(); - - if (domain == Domain::Log || domain == Domain::Runtime) { - runtimeAndLogStatusAfter = - agentsByEnabledDomain_[Domain::Runtime].contains(¬ifyingAgent) && - agentsByEnabledDomain_[Domain::Log].contains(¬ifyingAgent); + auto [domainStateChangedLocally, domainStateChangedGlobally] = + processDomainChange(domain, enabled, notifyingAgent); + + switch (domain) { + case Domain::Log: + case Domain::Runtime: { + auto otherDomain = domain == Domain::Log ? Domain::Runtime : Domain::Log; + // There should be an agent that enables both Log and Runtime domains. + if (!agentsByEnabledDomain_[otherDomain].contains(¬ifyingAgent)) { + break; + } - if (runtimeAndLogStatusBefore != runtimeAndLogStatusAfter) { - if (runtimeAndLogStatusAfter) { + if (domainStateChangedGlobally && enabled) { + assert(agentsWithRuntimeAndLogDomainsEnabled_ == 0); + emitDebuggerSessionCreated(); + ++agentsWithRuntimeAndLogDomainsEnabled_; + } else if (domainStateChangedGlobally) { + assert(agentsWithRuntimeAndLogDomainsEnabled_ == 1); + emitDebuggerSessionDestroyed(); + --agentsWithRuntimeAndLogDomainsEnabled_; + } else if (domainStateChangedLocally && enabled) { + // This is a case when given domain was already enabled by other Agent, + // so global state didn't change. if (++agentsWithRuntimeAndLogDomainsEnabled_ == 1) { emitDebuggerSessionCreated(); } - } else { - assert(agentsWithRuntimeAndLogDomainsEnabled_ > 0); + } else if (domainStateChangedLocally) { if (--agentsWithRuntimeAndLogDomainsEnabled_ == 0) { emitDebuggerSessionDestroyed(); } } + + break; + } + case Domain::ReactNativeApplication: { + if (domainStateChangedGlobally && enabled) { + // installConsoleCreateTask(); + } else if (domainStateChangedGlobally) { + // stubConsoleCreateTask(); + } + + break; + } + case Domain::Network: + break; + case Domain::kMaxValue: { + throw std::logic_error("Unexpected kMaxValue domain value provided"); } } } +std::pair RuntimeTarget::processDomainChange( + Domain domain, + bool enabled, + const RuntimeAgent& notifyingAgent) { + bool domainHadAgentsBefore = !agentsByEnabledDomain_[domain].empty(); + bool domainHasBeenEnabledBefore = + agentsByEnabledDomain_[domain].contains(¬ifyingAgent); + + if (enabled) { + agentsByEnabledDomain_[domain].insert(¬ifyingAgent); + } else { + agentsByEnabledDomain_[domain].erase(¬ifyingAgent); + } + threadSafeDomainStatus_[domain] = !agentsByEnabledDomain_[domain].empty(); + + bool domainHasAgentsAfter = !agentsByEnabledDomain_[domain].empty(); + + return { + domainHasBeenEnabledBefore ^ enabled, + domainHadAgentsBefore ^ domainHasAgentsAfter, + }; +} + bool RuntimeTarget::isDomainEnabled(Domain domain) const { return threadSafeDomainStatus_[domain]; } diff --git a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h index 61e579594866..2a2897f5b633 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h @@ -21,6 +21,7 @@ #include #include +#include #ifndef JSINSPECTOR_EXPORT #ifdef _MSC_VER @@ -122,7 +123,13 @@ class RuntimeTargetDelegate { */ class RuntimeTargetController { public: - enum class Domain { Network, Runtime, Log, kMaxValue }; + enum class Domain { + Log, + Network, + ReactNativeApplication, + Runtime, + kMaxValue + }; explicit RuntimeTargetController(RuntimeTarget& target); @@ -347,6 +354,22 @@ class JSINSPECTOR_EXPORT RuntimeTarget bool enabled, const RuntimeAgent& notifyingAgent); + /** + * Processes the changes to the state of a given domain. + * + * Returns a pair of booleans: + * 1. Returns true, if an only if the given domain state changed locally, + * for a given session. + * 2. Returns true, if and only if the given domain state changed globally: + * when the given Agent is the only Agent that enabled given domain across + * sessions, or when the only Agent that had this domain enabled has + * disconnected. + */ + std::pair processDomainChange( + Domain domain, + bool enabled, + const RuntimeAgent& notifyingAgent); + /** * Checks whether the given domain is enabled in at least one session * that is currently connected. This may be called from any thread, with From 9834e7f18afe650117a04a8da6e365ad70f79ce8 Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin Date: Thu, 23 Oct 2025 04:55:38 -0700 Subject: [PATCH 3/3] Scaffold implementation and lifecycle Summary: # Changelog: [Internal] `RuntimeTarget` will have 2 new methods: - `stubConsoleCreateTask` - installs a `console.createTask` stub, so it is always available. - `installConsoleCreateTask` - installs the actual implementation, when there is a Fusebox client The implementation for these methods will be provided in a separate diff. Similarly, the lifecycle for installation / stub during the background tracing will be provided in a separate diff. Differential Revision: D85274859 --- .../jsinspector-modern/RuntimeTarget.cpp | 6 ++++-- .../jsinspector-modern/RuntimeTarget.h | 17 +++++++++++++++++ .../jsinspector-modern/RuntimeTargetConsole.cpp | 4 ++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.cpp b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.cpp index 2bf76fd2e8dc..25fd02b9b349 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.cpp @@ -54,6 +54,8 @@ RuntimeTarget::RuntimeTarget( void RuntimeTarget::installGlobals() { // NOTE: RuntimeTarget::installConsoleHandler is in RuntimeTargetConsole.cpp installConsoleHandler(); + // NOTE: RuntimeTarget::stubConsoleCreateTask is in RuntimeTargetConsole.cpp + stubConsoleCreateTask(); // NOTE: RuntimeTarget::installDebuggerSessionObserver is in // RuntimeTargetDebuggerSessionObserver.cpp installDebuggerSessionObserver(); @@ -215,9 +217,9 @@ void RuntimeTarget::notifyDomainStateChanged( } case Domain::ReactNativeApplication: { if (domainStateChangedGlobally && enabled) { - // installConsoleCreateTask(); + installConsoleCreateTask(); } else if (domainStateChangedGlobally) { - // stubConsoleCreateTask(); + stubConsoleCreateTask(); } break; diff --git a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h index 2a2897f5b633..991d8f84a0ea 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h @@ -314,6 +314,23 @@ class JSINSPECTOR_EXPORT RuntimeTarget */ void installConsoleHandler(); + /* + * Installs console.createTask stub, which is essentially a no-op, but follows + * the semantics of actual API. + * + * The actual implementation is only installed when DevTools is opened or + * during tracing in the background. This aligns with Chromium's + * implementation. + */ + void stubConsoleCreateTask(); + + /* + * Installs the actual console.createTask implementation. This should only + * happen when DevTools is opened or during tracing in the background to avoid + * paying the cost of capturing unnecessary stack traces. + */ + void installConsoleCreateTask(); + /** * Installs __DEBUGGER_SESSION_OBSERVER__ object on the JavaScript's global * object, which later could be referenced from JavaScript side for diff --git a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTargetConsole.cpp b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTargetConsole.cpp index 25a6e694c17e..0f15d4fef8ed 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTargetConsole.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTargetConsole.cpp @@ -625,4 +625,8 @@ void RuntimeTarget::installConsoleHandler() { }); } +void RuntimeTarget::stubConsoleCreateTask() {} + +void RuntimeTarget::installConsoleCreateTask() {} + } // namespace facebook::react::jsinspector_modern