Skip to content

Instantly share code, notes, and snippets.

@huntie
Last active April 9, 2026 10:28
Show Gist options
  • Select an option

  • Save huntie/9b034059245205f425da600cdbb38f23 to your computer and use it in GitHub Desktop.

Select an option

Save huntie/9b034059245205f425da600cdbb38f23 to your computer and use it in GitHub Desktop.
backport-perf-trace-vs-main.diff
diff --git a/packages/react-native/React/DevSupport/RCTFrameTimingsObserver.mm b/packages/react-native/React/DevSupport/RCTFrameTimingsObserver.mm
index ec9a0ae01fb..112bba1fc06 100644
--- a/packages/react-native/React/DevSupport/RCTFrameTimingsObserver.mm
+++ b/packages/react-native/React/DevSupport/RCTFrameTimingsObserver.mm
@@ -14,7 +14,6 @@
#import <atomic>
#import <chrono>
-#import <mutex>
#import <optional>
#import <vector>
@@ -22,41 +21,17 @@
using namespace facebook::react;
-static constexpr CGFloat kScreenshotScaleFactor = 1.0;
+static constexpr CGFloat kScreenshotScaleFactor = 0.75;
static constexpr CGFloat kScreenshotJPEGQuality = 0.8;
-namespace {
-
-// Stores a captured frame screenshot and its associated metadata, used for
-// buffering frames during dynamic sampling.
-struct FrameData {
- UIImage *image;
- uint64_t frameId;
- jsinspector_modern::tracing::ThreadId threadId;
- HighResTimeStamp beginTimestamp;
- HighResTimeStamp endTimestamp;
-};
-
-} // namespace
-
@implementation RCTFrameTimingsObserver {
BOOL _screenshotsEnabled;
RCTFrameTimingCallback _callback;
CADisplayLink *_displayLink;
uint64_t _frameCounter;
- // Serial queue for encoding work (single background thread). We limit to 1
- // thread to minimize the performance impact of screenshot recording.
dispatch_queue_t _encodingQueue;
std::atomic<bool> _running;
uint64_t _lastScreenshotHash;
-
- // Stores the most recently captured frame to opportunistically encode after
- // the current frame. Replaced frames are emitted as timings without
- // screenshots.
- std::mutex _lastFrameMutex;
- std::optional<FrameData> _lastFrameData;
-
- std::atomic<bool> _encodingInProgress;
}
- (instancetype)initWithScreenshotsEnabled:(BOOL)screenshotsEnabled callback:(RCTFrameTimingCallback)callback
@@ -68,7 +43,6 @@ struct FrameData {
_encodingQueue = dispatch_queue_create("com.facebook.react.frame-timings-observer", DISPATCH_QUEUE_SERIAL);
_running.store(false);
_lastScreenshotHash = 0;
- _encodingInProgress.store(false);
}
return self;
}
@@ -78,13 +52,9 @@ struct FrameData {
_running.store(true, std::memory_order_relaxed);
_frameCounter = 0;
_lastScreenshotHash = 0;
- _encodingInProgress.store(false, std::memory_order_relaxed);
- {
- std::lock_guard<std::mutex> lock(_lastFrameMutex);
- _lastFrameData.reset();
- }
- // Emit initial frame event
+ // Emit an initial frame timing to ensure at least one frame is captured at the
+ // start of tracing, even if no UI changes occur.
auto now = HighResTimeStamp::now();
[self _emitFrameTimingWithBeginTimestamp:now endTimestamp:now];
@@ -97,10 +67,6 @@ struct FrameData {
_running.store(false, std::memory_order_relaxed);
[_displayLink invalidate];
_displayLink = nil;
- {
- std::lock_guard<std::mutex> lock(_lastFrameMutex);
- _lastFrameData.reset();
- }
}
- (void)_displayLinkTick:(CADisplayLink *)sender
@@ -124,115 +90,32 @@ struct FrameData {
uint64_t frameId = _frameCounter++;
auto threadId = static_cast<jsinspector_modern::tracing::ThreadId>(pthread_mach_thread_np(pthread_self()));
- if (!_screenshotsEnabled) {
- // Screenshots disabled - emit without screenshot
- [self _emitFrameEventWithFrameId:frameId
- threadId:threadId
- beginTimestamp:beginTimestamp
- endTimestamp:endTimestamp
- screenshot:std::nullopt];
- return;
- }
-
- UIImage *image = [self _captureScreenshot];
- if (image == nil) {
- // Failed to capture (e.g. no window, duplicate hash) - emit without screenshot
- [self _emitFrameEventWithFrameId:frameId
- threadId:threadId
- beginTimestamp:beginTimestamp
- endTimestamp:endTimestamp
- screenshot:std::nullopt];
- return;
- }
-
- FrameData frameData{image, frameId, threadId, beginTimestamp, endTimestamp};
-
- bool expected = false;
- if (_encodingInProgress.compare_exchange_strong(expected, true)) {
- // Not encoding - encode this frame immediately
- [self _encodeFrame:std::move(frameData)];
+ if (_screenshotsEnabled) {
+ [self _captureScreenshotWithCompletion:^(std::optional<std::vector<uint8_t>> screenshotData) {
+ if (!self->_running.load()) {
+ return;
+ }
+ jsinspector_modern::tracing::FrameTimingSequence sequence{
+ frameId, threadId, beginTimestamp, endTimestamp, std::move(screenshotData)};
+ self->_callback(std::move(sequence));
+ }];
} else {
- // Encoding thread busy - store current screenshot in buffer for tail-capture
- std::optional<FrameData> oldFrame;
- {
- std::lock_guard<std::mutex> lock(_lastFrameMutex);
- oldFrame = std::move(_lastFrameData);
- _lastFrameData = std::move(frameData);
- }
- if (oldFrame.has_value()) {
- // Skipped frame - emit event without screenshot
- [self _emitFrameEventWithFrameId:oldFrame->frameId
- threadId:oldFrame->threadId
- beginTimestamp:oldFrame->beginTimestamp
- endTimestamp:oldFrame->endTimestamp
- screenshot:std::nullopt];
- }
- }
-}
-
-- (void)_emitFrameEventWithFrameId:(uint64_t)frameId
- threadId:(jsinspector_modern::tracing::ThreadId)threadId
- beginTimestamp:(HighResTimeStamp)beginTimestamp
- endTimestamp:(HighResTimeStamp)endTimestamp
- screenshot:(std::optional<std::vector<uint8_t>>)screenshot
-{
- dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
- if (!self->_running.load(std::memory_order_relaxed)) {
- return;
- }
- jsinspector_modern::tracing::FrameTimingSequence sequence{
- frameId, threadId, beginTimestamp, endTimestamp, std::move(screenshot)};
- self->_callback(std::move(sequence));
- });
-}
-
-- (void)_encodeFrame:(FrameData)frameData
-{
- dispatch_async(_encodingQueue, ^{
- if (!self->_running.load(std::memory_order_relaxed)) {
- return;
- }
-
- auto screenshot = [self _encodeScreenshot:frameData.image];
- [self _emitFrameEventWithFrameId:frameData.frameId
- threadId:frameData.threadId
- beginTimestamp:frameData.beginTimestamp
- endTimestamp:frameData.endTimestamp
- screenshot:std::move(screenshot)];
-
- // Clear encoding flag early, allowing new frames to start fresh encoding
- // sessions
- self->_encodingInProgress.store(false, std::memory_order_release);
-
- // Opportunistically encode tail frame (if present) without blocking new
- // frames
- std::optional<FrameData> tailFrame;
- {
- std::lock_guard<std::mutex> lock(self->_lastFrameMutex);
- tailFrame = std::move(self->_lastFrameData);
- self->_lastFrameData.reset();
- }
- if (tailFrame.has_value()) {
+ dispatch_async(_encodingQueue, ^{
if (!self->_running.load(std::memory_order_relaxed)) {
return;
}
- auto tailScreenshot = [self _encodeScreenshot:tailFrame->image];
- [self _emitFrameEventWithFrameId:tailFrame->frameId
- threadId:tailFrame->threadId
- beginTimestamp:tailFrame->beginTimestamp
- endTimestamp:tailFrame->endTimestamp
- screenshot:std::move(tailScreenshot)];
- }
- });
+ jsinspector_modern::tracing::FrameTimingSequence sequence{frameId, threadId, beginTimestamp, endTimestamp};
+ self->_callback(std::move(sequence));
+ });
+ }
}
-// Captures a screenshot of the current window. Must be called on the main
-// thread. Returns nil if capture fails or if the frame content is unchanged.
-- (UIImage *)_captureScreenshot
+- (void)_captureScreenshotWithCompletion:(void (^)(std::optional<std::vector<uint8_t>>))completion
{
UIWindow *keyWindow = [self _getKeyWindow];
- if (keyWindow == nil) {
- return nil;
+ if (keyWindow == nullptr) {
+ completion(std::nullopt);
+ return;
}
UIView *rootView = keyWindow.rootViewController.view ?: keyWindow;
@@ -261,22 +144,24 @@ struct FrameData {
CFRelease(pixelData);
if (hash == _lastScreenshotHash) {
- return nil;
+ return;
}
_lastScreenshotHash = hash;
- return image;
-}
-
-- (std::optional<std::vector<uint8_t>>)_encodeScreenshot:(UIImage *)image
-{
- NSData *jpegData = UIImageJPEGRepresentation(image, kScreenshotJPEGQuality);
- if (jpegData == nil) {
- return std::nullopt;
- }
+ dispatch_async(_encodingQueue, ^{
+ if (!self->_running.load(std::memory_order_relaxed)) {
+ return;
+ }
+ NSData *jpegData = UIImageJPEGRepresentation(image, kScreenshotJPEGQuality);
+ if (jpegData == nullptr) {
+ completion(std::nullopt);
+ return;
+ }
- const auto *bytes = static_cast<const uint8_t *>(jpegData.bytes);
- return std::vector<uint8_t>(bytes, bytes + jpegData.length);
+ const auto *bytes = static_cast<const uint8_t *>(jpegData.bytes);
+ std::vector<uint8_t> screenshotBytes(bytes, bytes + jpegData.length);
+ completion(std::move(screenshotBytes));
+ });
}
- (UIWindow *)_getKeyWindow
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgelessDevSupportManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgelessDevSupportManager.kt
index 06aeeea9fc3..9679ea67c2f 100644
--- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgelessDevSupportManager.kt
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgelessDevSupportManager.kt
@@ -23,8 +23,9 @@ import com.facebook.react.packagerconnection.RequestHandler
* [DevSupportManagerBase] with some additional, more flexible APIs for asynchronously loading the
* JS bundle.
*
- * @constructor The primary constructor mirrors the same constructor we had for
- * `BridgeDevSupportManager` and is kept for backward compatibility.
+ * @constructor The primary constructor mirrors the same constructor we have for
+ * [BridgeDevSupportManager] and
+ * * is kept for backward compatibility.
*/
internal class BridgelessDevSupportManager(
applicationContext: Context,
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.kt
index 0608c07b78b..543ed13f937 100644
--- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.kt
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.kt
@@ -789,8 +789,8 @@ public abstract class DevSupportManagerBase(
callback.onSuccess(bundleLoader)
}
- override fun onProgress(status: String?, done: Int?, total: Int?, percent: Int?) {
- devLoadingViewManager?.updateProgress(status, done, total, percent)
+ override fun onProgress(status: String?, done: Int?, total: Int?) {
+ devLoadingViewManager?.updateProgress(status, done, total)
}
override fun onFailure(cause: Exception) {
@@ -856,9 +856,9 @@ public abstract class DevSupportManagerBase(
callback.onSuccess()
}
- override fun onProgress(status: String?, done: Int?, total: Int?, percent: Int?) {
- devLoadingViewManager?.updateProgress(status, done, total, percent)
- devBundleDownloadListener?.onProgress(status, done, total, percent)
+ override fun onProgress(status: String?, done: Int?, total: Int?) {
+ devLoadingViewManager?.updateProgress(status, done, total)
+ devBundleDownloadListener?.onProgress(status, done, total)
}
override fun onFailure(cause: Exception) {
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/InspectorFlags.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/InspectorFlags.kt
index f9e38dda2c5..c7ffcff620a 100644
--- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/InspectorFlags.kt
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/InspectorFlags.kt
@@ -9,10 +9,8 @@ package com.facebook.react.devsupport
import com.facebook.proguard.annotations.DoNotStrip
import com.facebook.soloader.SoLoader
-import com.facebook.soloader.annotation.SoLoaderLibrary
/** JNI wrapper for `jsinspector_modern::InspectorFlags`. */
-@SoLoaderLibrary("react_devsupportjni")
@DoNotStrip
internal object InspectorFlags {
init {
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/inspector/FrameTimingsObserver.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/inspector/FrameTimingsObserver.kt
index a88ef73aa95..a6233999023 100644
--- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/inspector/FrameTimingsObserver.kt
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/inspector/FrameTimingsObserver.kt
@@ -97,15 +97,16 @@ internal class FrameTimingsObserver(
}
}
- private val frameMetricsListener = Window.OnFrameMetricsAvailableListener { _, frameMetrics, _ ->
- // Guard against calls after stop()
- if (!isTracing) {
- return@OnFrameMetricsAvailableListener
- }
- val beginTimestamp = frameMetrics.getMetric(FrameMetrics.VSYNC_TIMESTAMP)
- val endTimestamp = beginTimestamp + frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION)
- emitFrameTiming(beginTimestamp, endTimestamp)
- }
+ private val frameMetricsListener =
+ Window.OnFrameMetricsAvailableListener { _, frameMetrics, _ ->
+ // Guard against calls after stop()
+ if (!isTracing) {
+ return@OnFrameMetricsAvailableListener
+ }
+ val beginTimestamp = frameMetrics.getMetric(FrameMetrics.VSYNC_TIMESTAMP)
+ val endTimestamp = beginTimestamp + frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION)
+ emitFrameTiming(beginTimestamp, endTimestamp)
+ }
private fun emitFrameTiming(beginTimestamp: Long, endTimestamp: Long) {
val frameId = frameCounter++
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/perfmonitor/PerfMonitorOverlayManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/perfmonitor/PerfMonitorOverlayManager.kt
index 6c550c5a17d..87fe0a92943 100644
--- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/perfmonitor/PerfMonitorOverlayManager.kt
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/perfmonitor/PerfMonitorOverlayManager.kt
@@ -7,8 +7,6 @@
package com.facebook.react.devsupport.perfmonitor
-import android.os.Handler
-import android.os.Looper
import com.facebook.react.bridge.UiThreadUtil
import com.facebook.react.devsupport.inspector.TracingState
@@ -25,7 +23,6 @@ internal class PerfMonitorOverlayManager(
private var view: PerfMonitorOverlayView? = null
private var tracingState: TracingState = TracingState.ENABLED_IN_CDP_MODE
private var perfIssueCount: Int = 0
- private val handler = Handler(Looper.getMainLooper())
/** Enable the Perf Monitor overlay. */
fun enable() {
@@ -78,37 +75,19 @@ internal class PerfMonitorOverlayManager(
tracingState = state
if (state != TracingState.DISABLED) {
perfIssueCount = 0
- handler.removeCallbacksAndMessages(null)
}
UiThreadUtil.runOnUiThread {
view?.updateRecordingState(state)
view?.updatePerfIssueCount(perfIssueCount)
- if (state == TracingState.ENABLED_IN_CDP_MODE) {
- view?.hide()
- } else {
- view?.show()
- }
+ view?.show()
}
}
override fun onPerfIssueAdded(name: String) {
- perfIssueCount++
-
UiThreadUtil.runOnUiThread {
- view?.updatePerfIssueCount(perfIssueCount)
+ view?.updatePerfIssueCount(++perfIssueCount)
view?.show()
}
-
- handler.postDelayed(
- {
- perfIssueCount--
- UiThreadUtil.runOnUiThread {
- view?.updatePerfIssueCount(perfIssueCount)
- view?.show()
- }
- },
- PERF_ISSUE_EXPIRY_MS,
- )
}
private fun handleRecordingButtonPress() {
@@ -126,8 +105,4 @@ internal class PerfMonitorOverlayManager(
TracingState.ENABLED_IN_CDP_MODE -> Unit
}
}
-
- companion object {
- private const val PERF_ISSUE_EXPIRY_MS = 20_000L
- }
}
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/perfmonitor/PerfMonitorOverlayView.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/perfmonitor/PerfMonitorOverlayView.kt
index 63d5fa858d4..5f1ce5a89c5 100644
--- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/perfmonitor/PerfMonitorOverlayView.kt
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/perfmonitor/PerfMonitorOverlayView.kt
@@ -120,12 +120,12 @@ internal class PerfMonitorOverlayView(
setTextColor(Color.WHITE)
typeface = TYPEFACE_BOLD
val alertDrawable =
- context.getDrawable(R.drawable.ic_perf_issue)?.apply {
+ context.getDrawable(android.R.drawable.ic_dialog_alert)?.apply {
setBounds(
0,
1,
- dpToPx(ISSUE_ICON_SIZE).toInt(),
- dpToPx(ISSUE_ICON_SIZE).toInt() + 1,
+ dpToPx(TEXT_SIZE_PRIMARY).toInt(),
+ dpToPx(TEXT_SIZE_PRIMARY).toInt() + 1,
)
}
setCompoundDrawables(alertDrawable, null, null, null)
@@ -142,9 +142,8 @@ internal class PerfMonitorOverlayView(
val dialog =
createAnchoredDialog(dpToPx(12f), dpToPx(12f)).apply { setContentView(containerLayout) }
dialog.window?.apply {
- attributes = attributes?.apply {
- flags = flags or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- }
+ attributes =
+ attributes?.apply { flags = flags or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE }
}
return dialog
@@ -159,13 +158,14 @@ internal class PerfMonitorOverlayView(
setCancelable(false)
}
dialog.window?.apply {
- attributes = attributes?.apply {
- width = WindowManager.LayoutParams.WRAP_CONTENT
- height = WindowManager.LayoutParams.WRAP_CONTENT
- gravity = Gravity.TOP or Gravity.END
- x = offsetX.toInt()
- y = offsetY.toInt()
- }
+ attributes =
+ attributes?.apply {
+ width = WindowManager.LayoutParams.WRAP_CONTENT
+ height = WindowManager.LayoutParams.WRAP_CONTENT
+ gravity = Gravity.TOP or Gravity.END
+ x = offsetX.toInt()
+ y = offsetY.toInt()
+ }
}
dialog.window?.decorView?.let { decorView ->
ViewCompat.setOnApplyWindowInsetsListener(decorView) { view, windowInsets ->
@@ -214,7 +214,6 @@ internal class PerfMonitorOverlayView(
private val COLOR_OVERLAY_BORDER = Color.parseColor("#6C6C6C")
private val TEXT_SIZE_PRIMARY = 12f
private val TEXT_SIZE_ACCESSORY = 10f
- private val ISSUE_ICON_SIZE = 15f
private val TYPEFACE_BOLD = Typeface.create("sans-serif", Typeface.BOLD)
}
}
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/tracing/PerformanceTracer.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/tracing/PerformanceTracer.kt
index c8abc7bb385..8bac4f57c38 100644
--- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/tracing/PerformanceTracer.kt
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/tracing/PerformanceTracer.kt
@@ -20,7 +20,7 @@ import com.facebook.soloader.SoLoader
@DoNotStrip
public object PerformanceTracer {
init {
- SoLoader.loadLibrary("react_tracingjni")
+ SoLoader.loadLibrary("react_performancetracerjni")
}
public fun <T> trace(name: String, block: () -> T): T {
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.kt
index 5f8b3dd9c18..48ae99d9335 100644
--- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.kt
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.kt
@@ -1027,10 +1027,11 @@ public class ReactHostImpl(
jsBundleLoader.onSuccess(
{ task ->
val bundleLoader = checkNotNull(task.getResult())
- val reactContext = bridgelessReactContextRef.getOrCreate {
- stateTracker.enterState(method, "Creating BridgelessReactContext")
- BridgelessReactContext(context, this)
- }
+ val reactContext =
+ bridgelessReactContextRef.getOrCreate {
+ stateTracker.enterState(method, "Creating BridgelessReactContext")
+ BridgelessReactContext(context, this)
+ }
reactContext.jsExceptionHandler = devSupportManager
stateTracker.enterState(method, "Creating ReactInstance")
@@ -1305,6 +1306,10 @@ public class ReactHostImpl(
val method = "getOrCreateReloadTask()"
stateTracker.enterState(method)
+ // Log how React Native is destroyed
+ // TODO(T136397487): Remove after Venice is shipped to 100%
+ raiseSoftException(method, reason)
+
reloadTask?.let {
return it
}
@@ -1456,6 +1461,10 @@ public class ReactHostImpl(
val method = "getOrCreateDestroyTask()"
stateTracker.enterState(method)
+ // Log how React Native is destroyed
+ // TODO(T136397487): Remove after Venice is shipped to 100%
+ raiseSoftException(method, reason, ex)
+
destroyTask?.let {
return it
}
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImplDevHelper.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImplDevHelper.kt
index d293887d6c6..322bbb81376 100644
--- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImplDevHelper.kt
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImplDevHelper.kt
@@ -70,14 +70,7 @@ internal class ReactHostImplDevHelper(private val delegate: ReactHostImpl) :
}
override fun destroyRootView(rootView: View) {
- val surface = (rootView as? ReactSurfaceView)?.surface ?: return
- // stop() synchronously removes the surface from ReactHostImpl.attachedSurfaces via
- // detachSurface(), then asynchronously tears down the React component tree.
- // detach() severs the surface's back-reference to the host.
- // clear() removes child views from the ReactSurfaceView.
- surface.stop()
- surface.detach()
- surface.clear()
+ // Not implemented, only referenced by BridgeDevSupportManager
}
override fun reload(reason: String) {
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostInspectorTarget.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostInspectorTarget.kt
index 936bcac115c..68fb8a8945d 100644
--- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostInspectorTarget.kt
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostInspectorTarget.kt
@@ -41,7 +41,7 @@ internal class ReactHostInspectorTarget(reactHostImpl: ReactHostImpl) :
external fun stopAndMaybeEmitBackgroundTrace(): Boolean
- external fun stopTracing()
+ external fun stopAndDiscardBackgroundTrace()
external override fun getTracingState(): TracingState
@@ -53,19 +53,29 @@ internal class ReactHostInspectorTarget(reactHostImpl: ReactHostImpl) :
override fun addPerfMonitorListener(listener: PerfMonitorUpdateListener) {
perfMonitorListeners.add(listener)
- registerTracingStateListener { state, _ -> listener.onRecordingStateChanged(state) }
}
override fun pauseAndAnalyzeBackgroundTrace(): Boolean {
- return stopAndMaybeEmitBackgroundTrace()
+ val emitted = stopAndMaybeEmitBackgroundTrace()
+ perfMonitorListeners.forEach { listener ->
+ listener.onRecordingStateChanged(TracingState.DISABLED)
+ }
+
+ return emitted
}
override fun resumeBackgroundTrace() {
startBackgroundTrace()
+ perfMonitorListeners.forEach { listener ->
+ listener.onRecordingStateChanged(TracingState.ENABLED_IN_BACKGROUND_MODE)
+ }
}
override fun stopBackgroundTrace() {
- stopTracing()
+ stopAndDiscardBackgroundTrace()
+ perfMonitorListeners.forEach { listener ->
+ listener.onRecordingStateChanged(TracingState.DISABLED)
+ }
}
fun handleNativePerfIssueAdded(
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 d8498aeea98..eeafc2085f5 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
@@ -17,6 +17,35 @@ using namespace facebook::react::jsinspector_modern;
namespace facebook::react {
+namespace {
+jni::local_ref<JTracingState::javaobject> convertCPPTracingStateToJava(
+ TracingState tracingState) {
+ auto tracingStateClass = jni::findClassLocal(
+ "com/facebook/react/devsupport/inspector/TracingState");
+ auto valueOfMethod =
+ tracingStateClass->getStaticMethod<JTracingState(jstring)>("valueOf");
+
+ switch (tracingState) {
+ case TracingState::Disabled:
+ return valueOfMethod(
+ tracingStateClass, jni::make_jstring("DISABLED").get());
+
+ case TracingState::EnabledInBackgroundMode:
+ return valueOfMethod(
+ tracingStateClass,
+ jni::make_jstring("ENABLED_IN_BACKGROUND_MODE").get());
+
+ case TracingState::EnabledInCDPMode:
+ return valueOfMethod(
+ tracingStateClass, jni::make_jstring("ENABLED_IN_CDP_MODE").get());
+
+ default:
+ jni::throwNewJavaException(
+ "java/lang/IllegalStateException", "Unexpected new TracingState.");
+ }
+}
+} // namespace
+
JReactHostInspectorTarget::JReactHostInspectorTarget(
alias_ref<JReactHostInspectorTarget::javaobject> jobj,
alias_ref<JReactHostImpl> reactHostImpl,
@@ -48,7 +77,7 @@ JReactHostInspectorTarget::JReactHostInspectorTarget(
// Reject the connection.
return nullptr;
},
- {.nativePageReloads = true});
+ {.nativePageReloads = true, .prefersFuseboxFrontend = true});
}
}
@@ -73,16 +102,13 @@ JReactHostInspectorTarget::initHybrid(
}
void JReactHostInspectorTarget::sendDebuggerResumeCommand() {
- inspectorTarget().sendCommand(HostCommand::DebuggerResume);
-}
-
-jsinspector_modern::HostTarget& JReactHostInspectorTarget::inspectorTarget() {
if (inspectorTarget_) {
- return *inspectorTarget_;
+ inspectorTarget_->sendCommand(HostCommand::DebuggerResume);
+ } else {
+ jni::throwNewJavaException(
+ "java/lang/IllegalStateException",
+ "Cannot send command while the Fusebox backend is not enabled");
}
- jni::throwNewJavaException(
- "java/lang/IllegalStateException",
- "Inspector method called while the Fusebox backend is not enabled.");
}
jsinspector_modern::HostTargetMetadata
@@ -164,22 +190,58 @@ HostTarget* JReactHostInspectorTarget::getInspectorTarget() {
}
bool JReactHostInspectorTarget::startBackgroundTrace() {
- return inspectorTarget().startTracing(
- tracing::Mode::Background,
- {
- tracing::Category::HiddenTimeline,
- tracing::Category::RuntimeExecution,
- tracing::Category::Timeline,
- tracing::Category::UserTiming,
- });
+ if (inspectorTarget_) {
+ return inspectorTarget_->startTracing(
+ tracing::Mode::Background,
+ {
+ tracing::Category::HiddenTimeline,
+ tracing::Category::RuntimeExecution,
+ tracing::Category::Timeline,
+ tracing::Category::UserTiming,
+ });
+ } else {
+ jni::throwNewJavaException(
+ "java/lang/IllegalStateException",
+ "Cannot start Tracing session while the Fusebox backend is not enabled.");
+ }
}
-void JReactHostInspectorTarget::stopTracing() {
- inspectorTarget().stopTracing();
+tracing::HostTracingProfile JReactHostInspectorTarget::stopTracing() {
+ if (inspectorTarget_) {
+ return inspectorTarget_->stopTracing();
+ } else {
+ jni::throwNewJavaException(
+ "java/lang/IllegalStateException",
+ "Cannot stop Tracing session while the Fusebox backend is not enabled.");
+ }
}
jboolean JReactHostInspectorTarget::stopAndMaybeEmitBackgroundTrace() {
- return jboolean(inspectorTarget().stopAndMaybeEmitBackgroundTrace());
+ auto capturedTrace = inspectorTarget_->stopTracing();
+ if (inspectorTarget_->hasActiveSessionWithFuseboxClient()) {
+ inspectorTarget_->emitTracingProfileForFirstFuseboxClient(
+ std::move(capturedTrace));
+ return jboolean(true);
+ }
+
+ stashTracingProfile(std::move(capturedTrace));
+ return jboolean(false);
+}
+
+void JReactHostInspectorTarget::stopAndDiscardBackgroundTrace() {
+ inspectorTarget_->stopTracing();
+}
+
+void JReactHostInspectorTarget::stashTracingProfile(
+ tracing::HostTracingProfile&& hostTracingProfile) {
+ stashedTracingProfile_ = std::move(hostTracingProfile);
+}
+
+std::optional<tracing::HostTracingProfile> JReactHostInspectorTarget::
+ unstable_getHostTracingProfileThatWillBeEmittedOnInitialization() {
+ auto tracingProfile = std::move(stashedTracingProfile_);
+ stashedTracingProfile_.reset();
+ return tracingProfile;
}
void JReactHostInspectorTarget::registerNatives() {
@@ -194,7 +256,9 @@ void JReactHostInspectorTarget::registerNatives() {
makeNativeMethod(
"stopAndMaybeEmitBackgroundTrace",
JReactHostInspectorTarget::stopAndMaybeEmitBackgroundTrace),
- makeNativeMethod("stopTracing", JReactHostInspectorTarget::stopTracing),
+ makeNativeMethod(
+ "stopAndDiscardBackgroundTrace",
+ JReactHostInspectorTarget::stopAndDiscardBackgroundTrace),
makeNativeMethod(
"getTracingState", JReactHostInspectorTarget::getTracingState),
makeNativeMethod(
@@ -216,8 +280,17 @@ JReactHostInspectorTarget::getTracingState() {
jlong JReactHostInspectorTarget::registerTracingStateListener(
jni::alias_ref<JTracingStateListener::javaobject> listener) {
auto cppListener = [globalRef = make_global(listener)](
- TracingState tracingState, bool screenshotsEnabled) {
- globalRef->onStateChanged(tracingState, screenshotsEnabled);
+ TracingState state, bool screenshotsEnabled) {
+ static auto method =
+ globalRef->getClass()
+ ->getMethod<void(
+ jni::local_ref<JTracingState::javaobject>, jboolean)>(
+ "onStateChanged");
+
+ method(
+ globalRef,
+ convertCPPTracingStateToJava(state),
+ static_cast<jboolean>(screenshotsEnabled));
};
return static_cast<jlong>(
@@ -235,7 +308,7 @@ HostTargetTracingDelegate* JReactHostInspectorTarget::getTracingDelegate() {
void JReactHostInspectorTarget::recordFrameTimings(
jni::alias_ref<JFrameTimingSequence::javaobject> frameTimingSequence) {
- inspectorTarget().recordFrameTimings({
+ inspectorTarget_->recordFrameTimings({
frameTimingSequence->getId(),
frameTimingSequence->getThreadId(),
frameTimingSequence->getBeginTimestamp(),
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 d2389940308..6b0beae1e86 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
@@ -21,53 +21,16 @@
namespace facebook::react {
-struct JTracingState : public jni::JavaClass<JTracingState> {
- static constexpr auto kJavaDescriptor = "Lcom/facebook/react/devsupport/inspector/TracingState;";
-};
-
-namespace {
-
-enum class TracingState {
- Disabled,
- EnabledInBackgroundMode,
- EnabledInCDPMode,
-};
-
-jni::local_ref<JTracingState::javaobject> convertCPPTracingStateToJava(TracingState tracingState)
-{
- auto tracingStateClass = jni::findClassLocal("com/facebook/react/devsupport/inspector/TracingState");
- auto valueOfMethod = tracingStateClass->getStaticMethod<JTracingState(jstring)>("valueOf");
-
- switch (tracingState) {
- case TracingState::Disabled:
- return valueOfMethod(tracingStateClass, jni::make_jstring("DISABLED").get());
-
- case TracingState::EnabledInBackgroundMode:
- return valueOfMethod(tracingStateClass, jni::make_jstring("ENABLED_IN_BACKGROUND_MODE").get());
-
- case TracingState::EnabledInCDPMode:
- return valueOfMethod(tracingStateClass, jni::make_jstring("ENABLED_IN_CDP_MODE").get());
-
- default:
- jni::throwNewJavaException("java/lang/IllegalStateException", "Unexpected new TracingState.");
- }
-}
-
-} // namespace
-
struct JTaskInterface : public jni::JavaClass<JTaskInterface> {
static constexpr auto kJavaDescriptor = "Lcom/facebook/react/interfaces/TaskInterface;";
};
+struct JTracingState : public jni::JavaClass<JTracingState> {
+ static constexpr auto kJavaDescriptor = "Lcom/facebook/react/devsupport/inspector/TracingState;";
+};
+
struct JTracingStateListener : public jni::JavaClass<JTracingStateListener> {
static constexpr auto kJavaDescriptor = "Lcom/facebook/react/devsupport/inspector/TracingStateListener;";
-
- void onStateChanged(TracingState tracingState, bool screenshotsEnabled) const
- {
- static auto method =
- javaClassStatic()->getMethod<void(jni::local_ref<JTracingState::javaobject>, jboolean)>("onStateChanged");
- return method(self(), convertCPPTracingStateToJava(tracingState), static_cast<jboolean>(screenshotsEnabled));
- }
};
struct JFrameTimingSequence : public jni::JavaClass<JFrameTimingSequence> {
@@ -154,6 +117,12 @@ struct JReactHostImpl : public jni::JavaClass<JReactHostImpl> {
}
};
+enum class TracingState {
+ Disabled,
+ EnabledInBackgroundMode,
+ EnabledInCDPMode,
+};
+
/**
* A callback that will be invoked when tracing state has changed.
*/
@@ -245,7 +214,7 @@ class JReactHostInspectorTarget : public jni::HybridClass<JReactHostInspectorTar
/**
* Stops previously started trace recording and discards the captured trace.
*/
- void stopTracing();
+ void stopAndDiscardBackgroundTrace();
jsinspector_modern::HostTarget *getInspectorTarget();
@@ -284,6 +253,8 @@ class JReactHostInspectorTarget : public jni::HybridClass<JReactHostInspectorTar
jsinspector_modern::ScopedExecutor<jsinspector_modern::NetworkRequestListener> executor) override;
std::optional<std::string> captureScreenshot(
const jsinspector_modern::HostTargetDelegate::PageCaptureScreenshotRequest &request) override;
+ std::optional<jsinspector_modern::tracing::HostTracingProfile>
+ unstable_getHostTracingProfileThatWillBeEmittedOnInitialization() override;
jsinspector_modern::HostTargetTracingDelegate *getTracingDelegate() override;
private:
@@ -291,13 +262,6 @@ class JReactHostInspectorTarget : public jni::HybridClass<JReactHostInspectorTar
jni::alias_ref<JReactHostInspectorTarget::javaobject> jobj,
jni::alias_ref<JReactHostImpl> reactHostImpl,
jni::alias_ref<JExecutor::javaobject> javaExecutor);
-
- /**
- * Returns a reference to the HostTarget, throwing a Java IllegalStateException
- * if the Fusebox backend is not enabled (i.e., inspectorTarget_ is null).
- */
- jsinspector_modern::HostTarget &inspectorTarget();
-
jni::global_ref<JReactHostInspectorTarget::javaobject> jobj_;
// This weak reference breaks the cycle between the C++ HostTarget and the
// Java ReactHostImpl, preventing memory leaks in apps that create multiple
@@ -308,6 +272,20 @@ class JReactHostInspectorTarget : public jni::HybridClass<JReactHostInspectorTar
std::shared_ptr<jsinspector_modern::HostTarget> inspectorTarget_;
std::optional<int> inspectorPageId_;
+ /**
+ * Stops previously started trace recording and returns the captured HostTracingProfile.
+ */
+ jsinspector_modern::tracing::HostTracingProfile stopTracing();
+ /**
+ * Stashes previously recorded HostTracingProfile that will be emitted when
+ * CDP session is created. Once emitted, the value will be cleared from this
+ * instance.
+ */
+ void stashTracingProfile(jsinspector_modern::tracing::HostTracingProfile &&hostTracingProfile);
+ /**
+ * Previously recorded HostTracingProfile that will be emitted when CDP session is created.
+ */
+ std::optional<jsinspector_modern::tracing::HostTracingProfile> stashedTracingProfile_;
/**
* Encapsulates the logic around tracing for this HostInspectorTarget.
*/
diff --git a/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.cpp b/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.cpp
index d1ef2ab54f0..d535b7496d1 100644
--- a/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.cpp
+++ b/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.cpp
@@ -272,11 +272,13 @@ class HostAgent::Impl final {
emitSystemStateChanged(isSingleHost);
}
- auto emitted = targetController_.maybeEmitStashedBackgroundTrace();
- assert(
- emitted &&
- "Expected to find at least one session eligible to receive a background trace after ReactNativeApplication.enable");
- (void)emitted;
+ auto stashedTraceRecording =
+ targetController_.getDelegate()
+ .unstable_getHostTracingProfileThatWillBeEmittedOnInitialization();
+ if (stashedTraceRecording.has_value()) {
+ tracingAgent_.emitExternalHostTracingProfile(
+ std::move(stashedTraceRecording.value()));
+ }
return {
.isFinishedHandlingRequest = true,
@@ -415,8 +417,16 @@ class HostAgent::Impl final {
}
}
- bool isEligibleForBackgroundTrace() const {
- return sessionState_.isReactNativeApplicationDomainEnabled;
+ bool hasFuseboxClientConnected() const {
+ return fuseboxClientType_ == FuseboxClientType::Fusebox;
+ }
+
+ void emitExternalTracingProfile(
+ tracing::HostTracingProfile tracingProfile) const {
+ assert(
+ hasFuseboxClientConnected() &&
+ "Attempted to emit a trace recording to a non-Fusebox client");
+ tracingAgent_.emitExternalHostTracingProfile(std::move(tracingProfile));
}
void emitSystemStateChanged(bool isSingleHost) {
@@ -529,9 +539,10 @@ class HostAgent::Impl final {
void handleRequest(const cdp::PreparsedRequest& req) {}
void setCurrentInstanceAgent(std::shared_ptr<InstanceAgent> agent) {}
- bool isEligibleForBackgroundTrace() const {
+ bool hasFuseboxClientConnected() const {
return false;
}
+ void emitExternalTracingProfile(tracing::HostTracingProfile tracingProfile) {}
void emitSystemStateChanged(bool isSingleHost) {}
};
@@ -563,8 +574,17 @@ void HostAgent::setCurrentInstanceAgent(
impl_->setCurrentInstanceAgent(std::move(instanceAgent));
}
-bool HostAgent::isEligibleForBackgroundTrace() const {
- return impl_->isEligibleForBackgroundTrace();
+bool HostAgent::hasFuseboxClientConnected() const {
+ return impl_->hasFuseboxClientConnected();
+}
+
+void HostAgent::emitExternalTracingProfile(
+ tracing::HostTracingProfile tracingProfile) const {
+ impl_->emitExternalTracingProfile(std::move(tracingProfile));
+}
+
+void HostAgent::emitSystemStateChanged(bool isSingleHost) const {
+ impl_->emitSystemStateChanged(isSingleHost);
}
#pragma mark - Tracing
diff --git a/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.h b/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.h
index 083632667f1..8789cb5d32c 100644
--- a/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.h
+++ b/packages/react-native/ReactCommon/jsinspector-modern/HostAgent.h
@@ -67,16 +67,23 @@ class HostAgent final {
void setCurrentInstanceAgent(std::shared_ptr<InstanceAgent> agent);
/**
- * Returns whether this HostAgent is eligible to receive notifications about
- * background traces.
+ * Returns whether this HostAgent is part of the session that has an active
+ * Fusebox client connecte, i.e. with Chrome DevTools Frontend fork for React
+ * Native.
*/
- bool isEligibleForBackgroundTrace() const;
+ bool hasFuseboxClientConnected() const;
+
+ /**
+ * Emits the HostTracingProfile that was captured externally, not via the
+ * CDP-initiated request.
+ */
+ void emitExternalTracingProfile(tracing::HostTracingProfile tracingProfile) const;
/**
* Emits a system state changed event when the number of ReactHost instances
* changes.
*/
- void emitSystemStateChanged(bool isSingleHost);
+ void emitSystemStateChanged(bool isSingleHost) const;
private:
// We use the private implementation idiom to ensure this class has the same
diff --git a/packages/react-native/ReactCommon/jsinspector-modern/HostTarget.cpp b/packages/react-native/ReactCommon/jsinspector-modern/HostTarget.cpp
index 637198d2a57..26c7e1ca849 100644
--- a/packages/react-native/ReactCommon/jsinspector-modern/HostTarget.cpp
+++ b/packages/react-native/ReactCommon/jsinspector-modern/HostTarget.cpp
@@ -8,7 +8,6 @@
#include "HostTarget.h"
#include "HostAgent.h"
#include "HostTargetTraceRecording.h"
-#include "HostTargetTracing.h"
#include "InspectorInterfaces.h"
#include "InspectorUtilities.h"
#include "InstanceTarget.h"
@@ -103,12 +102,18 @@ class HostTargetSession {
}
}
- HostAgent& agent() {
- return hostAgent_;
+ /**
+ * Returns whether the ReactNativeApplication CDP domain is enabled.
+ *
+ * Chrome DevTools Frontend enables this domain as a client.
+ */
+ bool hasFuseboxClient() const {
+ return hostAgent_.hasFuseboxClientConnected();
}
- FrontendChannel dangerouslyGetFrontendChannel() {
- return frontendChannel_;
+ void emitHostTracingProfile(
+ tracing::HostTracingProfile tracingProfile) const {
+ hostAgent_.emitExternalTracingProfile(std::move(tracingProfile));
}
private:
@@ -319,10 +324,6 @@ bool HostTargetController::decrementPauseOverlayCounter() {
return --pauseOverlayCounter_ != 0;
}
-bool HostTargetController::maybeEmitStashedBackgroundTrace() {
- return target_.maybeEmitStashedBackgroundTrace();
-}
-
namespace {
struct StaticHostTargetMetadata {
@@ -380,33 +381,32 @@ folly::dynamic createHostMetadataPayload(const HostTargetMetadata& metadata) {
return result;
}
-bool HostTarget::maybeEmitStashedBackgroundTrace() {
- std::vector<FrontendChannel> eligibleFrontendChannels;
- eligibleFrontendChannels.reserve(sessions_.size());
- sessions_.forEach([&eligibleFrontendChannels](auto& session) {
- if (session.agent().isEligibleForBackgroundTrace()) {
- eligibleFrontendChannels.push_back(
- session.dangerouslyGetFrontendChannel());
- }
+bool HostTarget::hasActiveSessionWithFuseboxClient() const {
+ bool hasActiveFuseboxSession = false;
+ sessions_.forEach([&](HostTargetSession& session) {
+ hasActiveFuseboxSession |= session.hasFuseboxClient();
});
-
- if (eligibleFrontendChannels.empty()) {
- return false;
- }
-
- auto stashedTrace = std::exchange(stashedTracingProfile_, std::nullopt);
- if (stashedTrace) {
- emitNotificationsForTracingProfile(
- std::move(*stashedTrace),
- eligibleFrontendChannels,
- /* isBackgroundTrace */ true);
- }
- return true;
+ return hasActiveFuseboxSession;
}
-bool HostTarget::stopAndMaybeEmitBackgroundTrace() {
- stashedTracingProfile_ = stopTracing();
- return maybeEmitStashedBackgroundTrace();
+void HostTarget::emitTracingProfileForFirstFuseboxClient(
+ tracing::HostTracingProfile tracingProfile) const {
+ bool emitted = false;
+ sessions_.forEach([&](HostTargetSession& session) {
+ if (emitted) {
+ /**
+ * HostTracingProfile object is not copiable for performance reasons,
+ * because it could contain large Runtime sampling profile object.
+ *
+ * This approach would not work with multi-client debugger setup.
+ */
+ return;
+ }
+ if (session.hasFuseboxClient()) {
+ session.emitHostTracingProfile(std::move(tracingProfile));
+ emitted = true;
+ }
+ });
}
} // namespace facebook::react::jsinspector_modern
diff --git a/packages/react-native/ReactCommon/jsinspector-modern/HostTarget.h b/packages/react-native/ReactCommon/jsinspector-modern/HostTarget.h
index 1030b90879f..798fc6d8dc0 100644
--- a/packages/react-native/ReactCommon/jsinspector-modern/HostTarget.h
+++ b/packages/react-native/ReactCommon/jsinspector-modern/HostTarget.h
@@ -206,6 +206,19 @@ class HostTargetDelegate : public LoadNetworkResourceDelegate {
return std::nullopt;
}
+ /**
+ * [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 HostTracingProfile if there is one that needs to be
+ * displayed, otherwise std::nullopt.
+ */
+ virtual std::optional<tracing::HostTracingProfile> unstable_getHostTracingProfileThatWillBeEmittedOnInitialization()
+ {
+ return std::nullopt;
+ }
+
/**
* An optional delegate that will be used by HostTarget to notify about tracing-related events.
*/
@@ -268,12 +281,6 @@ class HostTargetController final {
*/
tracing::HostTracingProfile stopTracing();
- /**
- * If there is a stashed background trace, emit it to all eligible sessions.
- * \return true if an eligible session is found (even if there was no stashed background trace).
- */
- bool maybeEmitStashedBackgroundTrace();
-
private:
HostTarget &target_;
size_t pauseOverlayCounter_{0};
@@ -370,13 +377,23 @@ class JSINSPECTOR_EXPORT HostTarget : public EnableExecutorFromThis<HostTarget>
tracing::HostTracingProfile stopTracing();
/**
- * Stops previously started trace recording and:
- * - If there is an active CDP session with ReactNativeApplication domain
- * enabled, emits the trace and returns true.
- * - Otherwise, stashes the captured trace, that will be emitted when a CDP
- * session enables ReactNativeApplication. Returns false.
+ * Returns whether there is an active session with the Fusebox client, i.e.
+ * with Chrome DevTools Frontend fork for React Native.
*/
- bool stopAndMaybeEmitBackgroundTrace();
+ bool hasActiveSessionWithFuseboxClient() const;
+
+ /**
+ * Emits the HostTracingProfile for the first active session with the Fusebox
+ * client.
+ *
+ * @see \c hasActiveFrontendSession
+ */
+ void emitTracingProfileForFirstFuseboxClient(tracing::HostTracingProfile tracingProfile) const;
+
+ /**
+ * Emits a system state changed event to all active sessions.
+ */
+ void emitSystemStateChanged(bool isSingleHost) const;
/**
* An endpoint for the Host to report frame timings that will be recorded if and only if there is currently an active
@@ -416,11 +433,6 @@ class JSINSPECTOR_EXPORT HostTarget : public EnableExecutorFromThis<HostTarget>
* Should only be allocated when there is an active tracing session.
*/
std::unique_ptr<HostTargetTraceRecording> traceRecording_{nullptr};
- /**
- * Previously recorded HostTracingProfile that will be emitted when CDP session is created
- * and enables ReactNativeApplication. Once emitted, the value will be cleared.
- */
- std::optional<tracing::HostTracingProfile> stashedTracingProfile_;
/**
* Protects the state inside traceRecording_.
*
@@ -447,12 +459,6 @@ class JSINSPECTOR_EXPORT HostTarget : public EnableExecutorFromThis<HostTarget>
*/
void installPerfIssuesBinding();
- /**
- * If there is a stashed background trace, emit it to the first eligible session.
- * \return true if an eligible session is found (even if there was no stashed background trace).
- */
- bool maybeEmitStashedBackgroundTrace();
-
// 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/InspectorInterfaces.cpp b/packages/react-native/ReactCommon/jsinspector-modern/InspectorInterfaces.cpp
index 6a08e59e39e..ab5a973f12c 100644
--- a/packages/react-native/ReactCommon/jsinspector-modern/InspectorInterfaces.cpp
+++ b/packages/react-native/ReactCommon/jsinspector-modern/InspectorInterfaces.cpp
@@ -29,7 +29,8 @@ folly::dynamic targetCapabilitiesToDynamic(
const InspectorTargetCapabilities& capabilities) {
return folly::dynamic::object(
"nativePageReloads", capabilities.nativePageReloads)(
- "nativeSourceCodeFetching", capabilities.nativeSourceCodeFetching);
+ "nativeSourceCodeFetching", capabilities.nativeSourceCodeFetching)(
+ "prefersFuseboxFrontend", capabilities.prefersFuseboxFrontend);
}
namespace {
diff --git a/packages/react-native/ReactCommon/jsinspector-modern/NetworkIOAgent.cpp b/packages/react-native/ReactCommon/jsinspector-modern/NetworkIOAgent.cpp
index ba7447baa23..53dcc0a3469 100644
--- a/packages/react-native/ReactCommon/jsinspector-modern/NetworkIOAgent.cpp
+++ b/packages/react-native/ReactCommon/jsinspector-modern/NetworkIOAgent.cpp
@@ -259,13 +259,6 @@ class Stream : public NetworkRequestListener,
};
} // namespace
-NetworkIOAgent::~NetworkIOAgent() {
- if (networkAgentId_) {
- NetworkHandler::getInstance().disableAgent(*networkAgentId_);
- networkAgentId_ = std::nullopt;
- }
-}
-
bool NetworkIOAgent::handleRequest(
const cdp::PreparsedRequest& req,
LoadNetworkResourceDelegate& delegate) {
@@ -285,17 +278,15 @@ bool NetworkIOAgent::handleRequest(
// @cdp Network.enable support is experimental.
if (req.method == "Network.enable") {
- networkAgentId_ = networkHandler.enableAgent(frontendChannel_);
+ networkHandler.setFrontendChannel(frontendChannel_);
+ networkHandler.enable();
// NOTE: Domain enable/disable responses are sent by HostAgent.
return false;
}
// @cdp Network.disable support is experimental.
if (req.method == "Network.disable") {
- if (networkAgentId_) {
- networkHandler.disableAgent(*networkAgentId_);
- networkAgentId_ = std::nullopt;
- }
+ networkHandler.disable();
// NOTE: Domain enable/disable responses are sent by HostAgent.
return false;
}
diff --git a/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.cpp b/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.cpp
index d20203d0e88..2c8b2030a7a 100644
--- a/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.cpp
+++ b/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.cpp
@@ -6,8 +6,8 @@
*/
#include "TracingAgent.h"
-#include "HostTargetTracing.h"
+#include <jsinspector-modern/tracing/HostTracingProfileSerializer.h>
#include <jsinspector-modern/tracing/PerformanceTracer.h>
#include <jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.h>
#include <jsinspector-modern/tracing/TraceEventSerializer.h>
@@ -15,6 +15,26 @@
namespace facebook::react::jsinspector_modern {
+namespace {
+
+/**
+ * Threshold for the size Trace Event chunk, that will be flushed out with
+ * Tracing.dataCollected event.
+ */
+const uint16_t TRACE_EVENT_CHUNK_SIZE = 1000;
+
+/**
+ * The maximum number of ProfileChunk trace events
+ * that will be sent in a single CDP Tracing.dataCollected message.
+ * TODO(T219394401): Increase the size once we manage the queue on OkHTTP
+ side
+ * properly and avoid WebSocket disconnections when sending a message larger
+ * than 16MB.
+ */
+const uint16_t PROFILE_TRACE_EVENT_CHUNK_SIZE = 1;
+
+} // namespace
+
TracingAgent::TracingAgent(
FrontendChannel frontendChannel,
SessionState& sessionState,
@@ -74,8 +94,6 @@ bool TracingAgent::handleRequest(const cdp::PreparsedRequest& req) {
bool didNotHaveAlreadyRunningRecording = hostTargetController_.startTracing(
tracing::Mode::CDP, std::move(enabledCategories));
if (!didNotHaveAlreadyRunningRecording) {
- // @cdp Tracing.start fails if there is a tracing session already running
- // in the current target. This matches Chrome's behavior.
frontendChannel_(
cdp::jsonError(
req.id,
@@ -96,14 +114,38 @@ bool TracingAgent::handleRequest(const cdp::PreparsedRequest& req) {
// Send response to Tracing.end request.
frontendChannel_(cdp::jsonResult(req.id));
- emitNotificationsForTracingProfile(
- std::move(tracingProfile),
- frontendChannel_,
- /* isBackgroundTrace */ false);
+ emitHostTracingProfile(std::move(tracingProfile));
return true;
}
return false;
}
+void TracingAgent::emitExternalHostTracingProfile(
+ tracing::HostTracingProfile tracingProfile) const {
+ frontendChannel_(
+ cdp::jsonNotification("ReactNativeApplication.traceRequested"));
+ emitHostTracingProfile(std::move(tracingProfile));
+}
+
+void TracingAgent::emitHostTracingProfile(
+ tracing::HostTracingProfile tracingProfile) const {
+ auto dataCollectedCallback = [this](folly::dynamic&& eventsChunk) {
+ frontendChannel_(
+ cdp::jsonNotification(
+ "Tracing.dataCollected",
+ folly::dynamic::object("value", std::move(eventsChunk))));
+ };
+ tracing::HostTracingProfileSerializer::emitAsDataCollectedChunks(
+ std::move(tracingProfile),
+ 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 2fe4c8ad666..d0f474f1234 100644
--- a/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.h
+++ b/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.h
@@ -41,6 +41,11 @@ class TracingAgent {
*/
bool handleRequest(const cdp::PreparsedRequest &req);
+ /**
+ * Emits the HostTracingProfile that was stashed externally by the HostTarget.
+ */
+ void emitExternalHostTracingProfile(tracing::HostTracingProfile tracingProfile) const;
+
private:
/**
* A channel used to send responses and events to the frontend.
@@ -50,6 +55,12 @@ class TracingAgent {
SessionState &sessionState_;
HostTargetController &hostTargetController_;
+
+ /**
+ * Emits captured HostTracingProfile in a series of
+ * Tracing.dataCollected events, followed by a Tracing.tracingComplete event.
+ */
+ void emitHostTracingProfile(tracing::HostTracingProfile tracingProfile) const;
};
} // namespace facebook::react::jsinspector_modern
diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp
index 14ddcafac2a..5769ef2f60c 100644
--- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp
+++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp
@@ -189,8 +189,7 @@ void PerformanceTracer::reportMeasure(
const std::string& name,
HighResTimeStamp start,
HighResDuration duration,
- folly::dynamic&& detail,
- std::optional<folly::dynamic> stackTrace) {
+ folly::dynamic&& detail) {
if (!tracingAtomic_) {
return;
}
@@ -207,7 +206,6 @@ void PerformanceTracer::reportMeasure(
.duration = duration,
.detail = std::move(detail),
.threadId = getCurrentThreadId(),
- .stackTrace = std::move(stackTrace),
});
}
@@ -218,8 +216,7 @@ void PerformanceTracer::reportTimeStamp(
std::optional<std::string> trackName,
std::optional<std::string> trackGroup,
std::optional<ConsoleTimeStampColor> color,
- std::optional<folly::dynamic> detail,
- std::optional<folly::dynamic> stackTrace) {
+ std::optional<folly::dynamic> detail) {
if (!tracingAtomic_) {
return;
}
@@ -238,7 +235,6 @@ void PerformanceTracer::reportTimeStamp(
.trackGroup = std::move(trackGroup),
.color = std::move(color),
.detail = std::move(detail),
- .stackTrace = std::move(stackTrace),
.threadId = getCurrentThreadId(),
});
}
@@ -312,27 +308,6 @@ void PerformanceTracer::reportResourceSendRequest(
});
}
-void PerformanceTracer::reportResourceReceivedData(
- const std::string& devtoolsRequestId,
- HighResTimeStamp start,
- int encodedDataLength) {
- if (!tracingAtomic_) {
- return;
- };
-
- std::lock_guard<std::mutex> lock(mutex_);
- if (!tracingAtomic_) {
- return;
- }
-
- enqueueEvent(
- PerformanceTracerResourceReceivedData{
- .requestId = devtoolsRequestId,
- .start = start,
- .encodedDataLength = encodedDataLength,
- .threadId = getCurrentThreadId()});
-}
-
void PerformanceTracer::reportResourceReceiveResponse(
const std::string& devtoolsRequestId,
HighResTimeStamp start,
@@ -598,10 +573,6 @@ void PerformanceTracer::enqueueTraceEventsFromPerformanceTracerEvent(
beginEventArgs =
folly::dynamic::object("detail", folly::toJson(event.detail));
}
- if (event.stackTrace) {
- beginEventArgs["data"] = folly::dynamic::object(
- "rnStackTrace", std::move(*event.stackTrace));
- }
auto eventId = ++performanceMeasureCount_;
@@ -691,9 +662,6 @@ void PerformanceTracer::enqueueTraceEventsFromPerformanceTracerEvent(
}
data["devtools"] = folly::toJson(devtoolsDetail);
}
- if (event.stackTrace) {
- data["rnStackTrace"] = std::move(*event.stackTrace);
- }
events.emplace_back(
TraceEvent{
@@ -727,23 +695,6 @@ void PerformanceTracer::enqueueTraceEventsFromPerformanceTracerEvent(
.args = folly::dynamic::object("data", std::move(data)),
});
},
- [&](PerformanceTracerResourceReceivedData&& event) {
- folly::dynamic data = folly::dynamic::object(
- "encodedDataLength", event.encodedDataLength)(
- "requestId", std::move(event.requestId));
-
- events.emplace_back(
- TraceEvent{
- .name = "ResourceReceivedData",
- .cat = {Category::Timeline},
- .ph = 'I',
- .ts = event.start,
- .pid = processId_,
- .s = 't',
- .tid = event.threadId,
- .args = folly::dynamic::object("data", std::move(data)),
- });
- },
[&](PerformanceTracerResourceReceiveResponse&& event) {
folly::dynamic headersEntries = folly::dynamic::array;
for (const auto& [key, value] : event.headers) {
diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/TracingCategory.h b/packages/react-native/ReactCommon/jsinspector-modern/tracing/TracingCategory.h
index 24c1880b9df..011c05fd1c0 100644
--- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/TracingCategory.h
+++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/TracingCategory.h
@@ -42,8 +42,6 @@ inline std::string tracingCategoryToString(const Category &category)
return "disabled-by-default-devtools.timeline.frame";
case Category::Screenshot:
return "disabled-by-default-devtools.screenshot";
- default:
- folly::assume_unreachable();
}
}
diff --git a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm
index 5ff41abd148..207e602260b 100644
--- a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm
+++ b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm
@@ -267,6 +267,10 @@ class RCTHostHostTargetDelegate : public facebook::react::jsinspector_modern::Ho
launchOptions:launchOptions];
}
+/**
+ Host initialization should not be resource intensive. A host may be created before any intention of using React Native
+ has been expressed.
+ */
- (instancetype)initWithBundleURLProvider:(RCTHostBundleURLProvider)provider
hostDelegate:(id<RCTHostDelegate>)hostDelegate
turboModuleManagerDelegate:(id<RCTTurboModuleManagerDelegate>)turboModuleManagerDelegate
@@ -278,32 +282,24 @@ class RCTHostHostTargetDelegate : public facebook::react::jsinspector_modern::Ho
turboModuleManagerDelegate:turboModuleManagerDelegate
jsEngineProvider:jsEngineProvider
launchOptions:launchOptions
- bundleConfiguration:[RCTBundleConfiguration defaultConfiguration]
devMenuConfiguration:[RCTDevMenuConfiguration defaultConfiguration]];
}
-/**
- Host initialization should not be resource intensive. A host may be created before any intention of using React Native
- has been expressed.
- */
- (instancetype)initWithBundleURLProvider:(RCTHostBundleURLProvider)provider
hostDelegate:(id<RCTHostDelegate>)hostDelegate
turboModuleManagerDelegate:(id<RCTTurboModuleManagerDelegate>)turboModuleManagerDelegate
jsEngineProvider:(RCTHostJSEngineProvider)jsEngineProvider
launchOptions:(nullable NSDictionary *)launchOptions
- bundleConfiguration:(RCTBundleConfiguration *)bundleConfiguration
devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration
{
if (self = [super init]) {
_hostDelegate = hostDelegate;
_turboModuleManagerDelegate = turboModuleManagerDelegate;
- _bundleManager = [[RCTBundleManager alloc] initWithBundleConfig:bundleConfiguration];
+ _bundleManager = [RCTBundleManager new];
_moduleRegistry = [RCTModuleRegistry new];
_jsEngineProvider = [jsEngineProvider copy];
_launchOptions = [launchOptions copy];
- [self setBundleURLProvider:provider];
-
__weak RCTHost *weakSelf = self;
auto bundleURLGetter = ^NSURL *() {
RCTHost *strongSelf = weakSelf;
@@ -370,7 +366,7 @@ class RCTHostHostTargetDelegate : public facebook::react::jsinspector_modern::Ho
}
return strongSelf->_inspectorTarget->connect(std::move(remote));
},
- {.nativePageReloads = true});
+ {.nativePageReloads = true, .prefersFuseboxFrontend = true});
}
if (_instance) {
RCTLogWarn(
@@ -486,14 +482,7 @@ class RCTHostHostTargetDelegate : public facebook::react::jsinspector_modern::Ho
- (void)instance:(RCTInstance *)instance didInitializeRuntime:(facebook::jsi::Runtime &)runtime
{
- if ([_hostDelegate respondsToSelector:@selector(host:didInitializeRuntime:)]) {
- [_hostDelegate host:self didInitializeRuntime:runtime];
- }
- // Runtime delegate is deprecated as of 0.84
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[self.runtimeDelegate host:self didInitializeRuntime:runtime];
-#pragma clang diagnostic pop
}
- (void)loadBundleAtURL:(NSURL *)sourceURL
@@ -576,7 +565,7 @@ class RCTHostHostTargetDelegate : public facebook::react::jsinspector_modern::Ho
// Sanitize the bundle URL
_bundleURL = [RCTConvert NSURL:_bundleURL.absoluteString];
- // Update the global bundle URL
+ // Update the global bundle URLq
RCTReloadCommandSetBundleURL(_bundleURL);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment