From 42cb327bc2b5805f42c424e6db2d37a945304be6 Mon Sep 17 00:00:00 2001 From: hoxy Date: Thu, 21 Aug 2025 23:24:36 -0700 Subject: [PATCH 1/2] Add a mechanism for emitting stashed trace recording Summary: # Changelog: [Internal] When CDP session is created via `HostTarget::connect`, it will ask `HostTargetDelegate` is there is a previously recorded trace that Host wants to display in the Frontend. `TracingAgent` will serialize and send the recording at the initialization time in constructor. Differential Revision: D79672597 --- .../jsinspector-modern/HostAgent.cpp | 19 +++++--- .../jsinspector-modern/HostAgent.h | 5 ++- .../jsinspector-modern/HostTarget.cpp | 9 ++-- .../jsinspector-modern/HostTarget.h | 13 ++++++ .../jsinspector-modern/TracingAgent.cpp | 43 +++++++++++-------- .../jsinspector-modern/TracingAgent.h | 15 ++++++- 6 files changed, 76 insertions(+), 28 deletions(-) diff --git a/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.cpp b/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.cpp index 21c4fb8dbd77..989193dec95c 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.cpp @@ -41,14 +41,18 @@ class HostAgent::Impl final { HostTargetController& targetController, HostTargetMetadata hostMetadata, SessionState& sessionState, - VoidExecutor executor) + VoidExecutor executor, + std::optional traceRecordingToEmit) : frontendChannel_(frontendChannel), targetController_(targetController), hostMetadata_(std::move(hostMetadata)), sessionState_(sessionState), networkIOAgent_(NetworkIOAgent(frontendChannel, std::move(executor))), - tracingAgent_( - TracingAgent(frontendChannel, sessionState, targetController)) {} + tracingAgent_(TracingAgent( + frontendChannel, + sessionState, + targetController, + std::move(traceRecordingToEmit))) {} ~Impl() { if (isPausedInDebuggerOverlayVisible_) { @@ -428,7 +432,8 @@ class HostAgent::Impl final { HostTargetController& targetController, HostTargetMetadata hostMetadata, SessionState& sessionState, - VoidExecutor executor) {} + VoidExecutor executor, + std::optional traceRecordingToEmit) {} void handleRequest(const cdp::PreparsedRequest& req) {} void setCurrentInstanceAgent(std::shared_ptr agent) {} @@ -441,14 +446,16 @@ HostAgent::HostAgent( HostTargetController& targetController, HostTargetMetadata hostMetadata, SessionState& sessionState, - VoidExecutor executor) + VoidExecutor executor, + std::optional traceRecordingToEmit) : impl_(std::make_unique( *this, frontendChannel, targetController, std::move(hostMetadata), sessionState, - std::move(executor))) {} + std::move(executor), + std::move(traceRecordingToEmit))) {} HostAgent::~HostAgent() = default; diff --git a/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.h b/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.h index ac4728062903..cb87a600775a 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.h @@ -36,13 +36,16 @@ class HostAgent final { * \param hostMetadata Metadata about the host that created this agent. * \param sessionState The state of the session that created this agent. * \param executor A void executor to be used by async-aware handlers. + * \param traceRecordingToEmit If set, this is the trace that Host has + * requested to display in the Frontend. */ HostAgent( const FrontendChannel& frontendChannel, HostTargetController& targetController, HostTargetMetadata hostMetadata, SessionState& sessionState, - VoidExecutor executor); + VoidExecutor executor, + std::optional traceRecordingToEmit); HostAgent(const HostAgent&) = delete; HostAgent(HostAgent&&) = delete; diff --git a/packages/react-native/ReactCommon/jsinspector-modern/HostTarget.cpp b/packages/react-native/ReactCommon/jsinspector-modern/HostTarget.cpp index 190d61477da5..80eb2cdd5bda 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/HostTarget.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/HostTarget.cpp @@ -34,7 +34,8 @@ class HostTargetSession { std::unique_ptr remote, HostTargetController& targetController, HostTargetMetadata hostMetadata, - VoidExecutor executor) + VoidExecutor executor, + std::optional traceRecordingToEmit) : remote_(std::make_shared(std::move(remote))), frontendChannel_( [remoteWeak = std::weak_ptr(remote_)](std::string_view message) { @@ -47,7 +48,8 @@ class HostTargetSession { targetController, std::move(hostMetadata), state_, - std::move(executor)) {} + std::move(executor), + std::move(traceRecordingToEmit)) {} /** * Called by CallbackLocalConnection to send a message to this Session's @@ -206,7 +208,8 @@ std::unique_ptr HostTarget::connect( std::move(connectionToFrontend), controller_, delegate_.getMetadata(), - makeVoidExecutor(executorFromThis())); + makeVoidExecutor(executorFromThis()), + delegate_.unstable_getTraceRecordingThatWillBeEmittedOnInitialization()); session->setCurrentInstance(currentInstance_.get()); sessions_.insert(std::weak_ptr(session)); return std::make_unique( diff --git a/packages/react-native/ReactCommon/jsinspector-modern/HostTarget.h b/packages/react-native/ReactCommon/jsinspector-modern/HostTarget.h index 872af485d42e..8f4ad78e9c25 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/HostTarget.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/HostTarget.h @@ -146,6 +146,19 @@ class HostTargetDelegate : public LoadNetworkResourceDelegate { throw NotImplementedException( "LoadNetworkResourceDelegate.loadNetworkResource is not implemented by this host target delegate."); } + + /** + * [Experimental] Will be called at the CDP session initialization to get the + * trace recording that may have been stashed by the Host from the previous + * background session. + * + * \return the trace recording state if there is one that needs to be + * displayed, otherwise std::nullopt. + */ + virtual std::optional + unstable_getTraceRecordingThatWillBeEmittedOnInitialization() { + return std::nullopt; + } }; /** diff --git a/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.cpp b/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.cpp index 49568d70799a..dd84991722f0 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.cpp @@ -38,10 +38,15 @@ const uint16_t PROFILE_TRACE_EVENT_CHUNK_SIZE = 1; TracingAgent::TracingAgent( FrontendChannel frontendChannel, SessionState& sessionState, - HostTargetController& hostTargetController) + HostTargetController& hostTargetController, + std::optional traceRecordingToEmit) : frontendChannel_(std::move(frontendChannel)), sessionState_(sessionState), - hostTargetController_(hostTargetController) {} + hostTargetController_(hostTargetController) { + if (traceRecordingToEmit.has_value()) { + emitTraceRecording(std::move(traceRecordingToEmit.value())); + } +} TracingAgent::~TracingAgent() { // Agents are owned by the session. If the agent is destroyed, it means that @@ -86,25 +91,29 @@ bool TracingAgent::handleRequest(const cdp::PreparsedRequest& req) { // Send response to Tracing.end request. frontendChannel_(cdp::jsonResult(req.id)); - auto dataCollectedCallback = [this](folly::dynamic&& eventsChunk) { - frontendChannel_(cdp::jsonNotification( - "Tracing.dataCollected", - folly::dynamic::object("value", std::move(eventsChunk)))); - }; - tracing::TraceRecordingStateSerializer::emitAsDataCollectedChunks( - std::move(state), - dataCollectedCallback, - TRACE_EVENT_CHUNK_SIZE, - PROFILE_TRACE_EVENT_CHUNK_SIZE); - - frontendChannel_(cdp::jsonNotification( - "Tracing.tracingComplete", - folly::dynamic::object("dataLossOccurred", false))); - + emitTraceRecording(std::move(state)); return true; } return false; } +void TracingAgent::emitTraceRecording( + tracing::TraceRecordingState state) const { + auto dataCollectedCallback = [this](folly::dynamic&& eventsChunk) { + frontendChannel_(cdp::jsonNotification( + "Tracing.dataCollected", + folly::dynamic::object("value", std::move(eventsChunk)))); + }; + tracing::TraceRecordingStateSerializer::emitAsDataCollectedChunks( + std::move(state), + dataCollectedCallback, + TRACE_EVENT_CHUNK_SIZE, + PROFILE_TRACE_EVENT_CHUNK_SIZE); + + frontendChannel_(cdp::jsonNotification( + "Tracing.tracingComplete", + folly::dynamic::object("dataLossOccurred", false))); +} + } // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.h b/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.h index b46d9a78130d..ec686f9c5286 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.h @@ -24,11 +24,18 @@ class TracingAgent { /** * \param frontendChannel A channel used to send responses to the * frontend. + * \param sessionState The state of the session that created this agent. + * \param hostTargetController An interface to the HostTarget that this agent + * is attached to. The caller is responsible for ensuring that the + * HostTargetDelegate and underlying HostTarget both outlive the agent. + * \param traceRecordingToEmit If set, this is the trace that Host has + * requested to display in the Frontend. */ TracingAgent( FrontendChannel frontendChannel, SessionState& sessionState, - HostTargetController& hostTargetController); + HostTargetController& hostTargetController, + std::optional traceRecordingToEmit); ~TracingAgent(); @@ -48,6 +55,12 @@ class TracingAgent { SessionState& sessionState_; HostTargetController& hostTargetController_; + + /** + * Emits the captured Trace Recording state in a series of + * Tracing.dataCollected events, followed by a Tracing.tracingComplete event. + */ + void emitTraceRecording(tracing::TraceRecordingState state) const; }; } // namespace facebook::react::jsinspector_modern From 335393f6e6b676040678256c0d7333ad5c5fd517 Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin Date: Thu, 21 Aug 2025 23:28:55 -0700 Subject: [PATCH 2/2] Send custom CDP Event to Frontend to prepare for displaying a trace (#53079) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53079 # Changelog: [Internal] We need this to notify Frontend, so it updates the local state before receiving `Tracing.dataCollected` events. Corresponding change in CDT fork - https://github.com/facebook/react-native-devtools-frontend/pull/199. Reviewed By: sbuggay Differential Revision: D79672598 --- .../ReactCommon/jsinspector-modern/TracingAgent.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.cpp b/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.cpp index dd84991722f0..c6ef7037227b 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.cpp @@ -44,6 +44,8 @@ TracingAgent::TracingAgent( sessionState_(sessionState), hostTargetController_(hostTargetController) { if (traceRecordingToEmit.has_value()) { + frontendChannel_( + cdp::jsonNotification("ReactNativeApplication.traceRequested")); emitTraceRecording(std::move(traceRecordingToEmit.value())); } }