diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactHostInspectorTarget.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactHostInspectorTarget.cpp index 0843ee7b4ac7..fb3795c62676 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactHostInspectorTarget.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactHostInspectorTarget.cpp @@ -127,6 +127,9 @@ void JReactHostInspectorTarget::onSetPausedInDebuggerMessage( } } +void JReactHostInspectorTarget::unstable_onPerfMonitorUpdate( + const PerfMonitorUpdateRequest& /* unused */) {} + void JReactHostInspectorTarget::loadNetworkResource( const jsinspector_modern::LoadNetworkResourceRequest& params, jsinspector_modern::ScopedExecutor< diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactHostInspectorTarget.h b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactHostInspectorTarget.h index 1f6cf1e80c86..dda6073e7d5f 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactHostInspectorTarget.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactHostInspectorTarget.h @@ -84,6 +84,8 @@ class JReactHostInspectorTarget void onReload(const PageReloadRequest& request) override; void onSetPausedInDebuggerMessage( const OverlaySetPausedInDebuggerMessageRequest&) override; + void unstable_onPerfMonitorUpdate( + const PerfMonitorUpdateRequest& /* unused */) override; void loadNetworkResource( const jsinspector_modern::LoadNetworkResourceRequest& params, jsinspector_modern::ScopedExecutor< diff --git a/packages/react-native/ReactCommon/jsinspector-modern/HostTarget.cpp b/packages/react-native/ReactCommon/jsinspector-modern/HostTarget.cpp index ee6d29ed6bae..ae4618de612c 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/HostTarget.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/HostTarget.cpp @@ -145,11 +145,53 @@ class HostCommandSender { std::unique_ptr connection_; }; +/** + * Enables the caller to install and subscribe to a named CDP runtime binding + * on the HostTarget via a callback. Note: Per CDP spec, this does not need to + * check if the `Runtime` domain is enabled. + */ +class HostRuntimeBinding { + public: + explicit HostRuntimeBinding( + HostTarget& target, + std::string name, + std::function callback) + : connection_(target.connect(std::make_unique( + [callback = std::move(callback)](const std::string& message) { + auto parsedMessage = folly::parseJson(message); + + // Ignore initial Runtime.addBinding response + if (parsedMessage["id"] == 0 && + parsedMessage["result"].isObject() && + parsedMessage["result"].empty()) { + return; + } + + // Assert that we only intercept bindingCalled responses + assert( + parsedMessage["method"].asString() == + "Runtime.bindingCalled"); + callback(parsedMessage["params"]["payload"].asString()); + }))) { + // Install runtime binding + connection_->sendMessage(cdp::jsonRequest( + 0, + "Runtime.addBinding", + folly::dynamic::object("name", std::move(name)))); + } + + private: + std::unique_ptr connection_; +}; + std::shared_ptr HostTarget::create( HostTargetDelegate& delegate, VoidExecutor executor) { std::shared_ptr hostTarget{new HostTarget(delegate)}; hostTarget->setExecutor(std::move(executor)); + if (InspectorFlags::getInstance().getPerfMonitorV2Enabled()) { + hostTarget->installPerfMetricsBinding(); + } return hostTarget; } @@ -212,6 +254,19 @@ void HostTarget::sendCommand(HostCommand command) { }); } +void HostTarget::installPerfMetricsBinding() { + perfMetricsBinding_ = std::make_unique( + *this, // Used immediately + "__chromium_devtools_metrics_reporter", + [this](const std::string& message) { + auto payload = folly::parseJson(message); + HostTargetDelegate::PerfMonitorUpdateRequest request{ + .interactionName = payload["eventName"].asString(), + .durationMs = static_cast(payload["duration"].asInt())}; + delegate_.unstable_onPerfMonitorUpdate(request); + }); +} + HostTargetController::HostTargetController(HostTarget& target) : target_(target) {} diff --git a/packages/react-native/ReactCommon/jsinspector-modern/HostTarget.h b/packages/react-native/ReactCommon/jsinspector-modern/HostTarget.h index 938271857198..682ef6ba0c97 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/HostTarget.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/HostTarget.h @@ -35,6 +35,7 @@ namespace facebook::react::jsinspector_modern { class HostTargetSession; class HostAgent; class HostCommandSender; +class HostRuntimeBinding; class HostTarget; struct HostTargetMetadata { @@ -93,6 +94,11 @@ class HostTargetDelegate : public LoadNetworkResourceDelegate { } }; + struct PerfMonitorUpdateRequest { + std::string interactionName; + uint16_t durationMs; + }; + virtual ~HostTargetDelegate() override; /** @@ -121,6 +127,13 @@ class HostTargetDelegate : public LoadNetworkResourceDelegate { virtual void onSetPausedInDebuggerMessage( const OverlaySetPausedInDebuggerMessageRequest& request) = 0; + /** + * [Experimental] Called when the runtime has new data for the V2 Perf + * Monitor overlay. This is called on the inspector thread. + */ + virtual void unstable_onPerfMonitorUpdate( + const PerfMonitorUpdateRequest& /*request*/) {} + /** * Called by NetworkIOAgent on handling a `Network.loadNetworkResource` CDP * request. Platform implementations should override this to perform a @@ -256,6 +269,7 @@ class JSINSPECTOR_EXPORT HostTarget std::shared_ptr executionContextManager_; std::shared_ptr currentInstance_{nullptr}; std::unique_ptr commandSender_; + std::unique_ptr perfMetricsBinding_; inline HostTargetDelegate& getDelegate() { return delegate_; @@ -265,6 +279,13 @@ class JSINSPECTOR_EXPORT HostTarget return currentInstance_ != nullptr; } + /** + * Install a runtime binding subscribing to the Interaction to Next Paint + * (INP) live metric, which we broadcast to the V2 Perf Monitor overlay + * via \ref HostTargetDelegate::unstable_onPerfMonitorUpdate. + */ + void installPerfMetricsBinding(); + // Necessary to allow HostAgent to access HostTarget's internals in a // controlled way (i.e. only HostTargetController gets friend access, while // HostAgent itself doesn't). diff --git a/packages/react-native/ReactCommon/jsinspector-modern/InspectorFlags.cpp b/packages/react-native/ReactCommon/jsinspector-modern/InspectorFlags.cpp index bd949687b040..df4dcbaafc95 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/InspectorFlags.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/InspectorFlags.cpp @@ -33,6 +33,10 @@ bool InspectorFlags::getNetworkInspectionEnabled() const { return loadFlagsAndAssertUnchanged().networkInspectionEnabled; } +bool InspectorFlags::getPerfMonitorV2Enabled() const { + return loadFlagsAndAssertUnchanged().perfMonitorV2Enabled; +} + void InspectorFlags::dangerouslyResetFlags() { *this = InspectorFlags{}; } @@ -59,6 +63,9 @@ const InspectorFlags::Values& InspectorFlags::loadFlagsAndAssertUnchanged() .networkInspectionEnabled = ReactNativeFeatureFlags::enableBridgelessArchitecture() && ReactNativeFeatureFlags::fuseboxNetworkInspectionEnabled(), + .perfMonitorV2Enabled = + ReactNativeFeatureFlags::enableBridgelessArchitecture() && + ReactNativeFeatureFlags::perfMonitorV2Enabled(), }; if (cachedValues_.has_value() && !inconsistentFlagsStateLogged_) { diff --git a/packages/react-native/ReactCommon/jsinspector-modern/InspectorFlags.h b/packages/react-native/ReactCommon/jsinspector-modern/InspectorFlags.h index a644f17663c3..975793bd37ab 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/InspectorFlags.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/InspectorFlags.h @@ -35,6 +35,11 @@ class InspectorFlags { */ bool getNetworkInspectionEnabled() const; + /** + * Flag determining if the V2 in-app Performance Monitor is enabled. + */ + bool getPerfMonitorV2Enabled() const; + /** * Forcibly disable the main `getFuseboxEnabled()` flag. This should ONLY be * used by `ReactInstanceIntegrationTest`. @@ -52,6 +57,7 @@ class InspectorFlags { bool fuseboxEnabled; bool isProfilingBuild; bool networkInspectionEnabled; + bool perfMonitorV2Enabled; bool operator==(const Values&) const = default; }; diff --git a/packages/react-native/ReactCommon/jsinspector-modern/InspectorUtilities.cpp b/packages/react-native/ReactCommon/jsinspector-modern/InspectorUtilities.cpp index 0c4c99ca6cea..f073c61a4475 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/InspectorUtilities.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/InspectorUtilities.cpp @@ -24,6 +24,14 @@ void CallbackLocalConnection::disconnect() { handler_ = nullptr; } +CallbackRemoteConnection::CallbackRemoteConnection( + std::function handler) + : handler_(std::move(handler)) {} + +void CallbackRemoteConnection::onMessage(std::string message) { + handler_(std::move(message)); +} + RAIIRemoteConnection::RAIIRemoteConnection( std::unique_ptr remote) : remote_(std::move(remote)) {} diff --git a/packages/react-native/ReactCommon/jsinspector-modern/InspectorUtilities.h b/packages/react-native/ReactCommon/jsinspector-modern/InspectorUtilities.h index 9c14d1be9809..1168edc8c8b5 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/InspectorUtilities.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/InspectorUtilities.h @@ -33,6 +33,25 @@ class CallbackLocalConnection : public ILocalConnection { std::function handler_; }; +/** + * Wraps a callback function in IRemoteConnection. + */ +class CallbackRemoteConnection : public IRemoteConnection { + public: + /** + * Creates a new Connection that uses the given callback to receive messages + * from the backend. + */ + explicit CallbackRemoteConnection(std::function handler); + + void onMessage(std::string message) override; + + void onDisconnect() override {} + + private: + std::function handler_; +}; + /** * Wraps an IRemoteConnection in a simpler interface that calls `onDisconnect` * implicitly upon destruction.