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/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..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(); @@ -179,40 +181,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 451945bad1fa..991d8f84a0ea 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 @@ -99,12 +100,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. */ @@ -124,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); @@ -148,12 +153,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 +230,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. */ @@ -313,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 @@ -353,6 +371,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 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