Skip to content

Commit 691b232

Browse files
motiz88huntierubennorte
authored
[0.85] Backport performance debugging improvements (#56637)
* Move base64Encode into react/utils (#55873) Summary: Pull Request resolved: #55873 Move `base64Encode` implementation (originally vendored in D58323859) into `ReactCommon/react/utils/` for reuse in other C++ packages. Changelog: [Internal] Reviewed By: cipolleschi Differential Revision: D95041544 fbshipit-source-id: d71376d03079a8cc4d0a6c3652c40d73374858ea * Move screenshot Base64 encoding to trace serialization (#55803) Summary: Pull Request resolved: #55803 Refactor Android FrameTimings to reduce memory usage in trace buffer. Binary data instead of Base64 should be ~25% smaller, and eliminates string copies during JNI transfer. Changelog: [Internal] Reviewed By: rubennorte Differential Revision: D94657907 fbshipit-source-id: c4a183ff00c519fd1545992340fd445530abab99 * Add tracing helper functions for RNDT traces in C++ and Kotlin (#55935) Summary: Pull Request resolved: #55935 Changelog: [internal] This diff adds helper functions to the PerformanceTracer class for tracing performance events in React Native Developer Tools traces. In Kotlin, new `trace` method overloads are added to `PerformanceTracer` that wrap a block of code and automatically measure and report its execution time. These use a try/finally pattern to ensure timing is captured correctly. In C++, a new `PerformanceTracerSection` RAII class is added that automatically reports timing when the section goes out of scope. It supports optional track, track group, and color parameters, as well as variadic key-value properties for additional metadata. Reviewed By: sammy-SC Differential Revision: D92061513 fbshipit-source-id: 92c745426c6a9e3a2eee1e85e2a2d7fd12d88ef6 * Introduce fuseboxFrameRecordingEnabled flag, gate existing code (#55941) Summary: Pull Request resolved: #55941 Replaces the previous `globalThis.enableTimelineFrames` setup with backend gating. Enables us to: - Iterate more safely by gating Android/iOS/C++ code - Have a per-app rollout at Meta Changelog: [Internal] Reviewed By: vzaidman Differential Revision: D95405444 fbshipit-source-id: 0c41f10059b28ed940a12a44707946baa310bdfb * Implement Performance frames and screenshots on iOS (#56015) Summary: Pull Request resolved: #56015 Implements CDP support for the Chrome DevTools Frames track on iOS during a performance trace, including screenshot capture. This initial version matches the corresponding implementation for Android: - Captures the key window. - Always emits an initial frame. - Resizes screenshots at 0.75 scale factor (normalized for device screen DPI) and 80% JPEG compression. - Uses a background thread queue for image resizing. **To dos** - [ ] Emit frames only when there is a visual update. - [ ] Dynamic frame sampling on slower devices (planned for Android). **Limitations** - Not a true screen recording, uses `UIGraphicsImageRenderer`. - Requires no permission prompt ✅ - This can mean empty data (a black screen) is sent during states such as system alert dialogs being presented. - Like Android, if the background queue builds (on slower hardware), screenshot data can be lost when the recording is ended. This feature is gated behind the `fuseboxFrameRecordingEnabled` flag. Changelog: [Internal] Reviewed By: sbuggay Differential Revision: D95566220 fbshipit-source-id: c87f1b6d8334b45e2eb67334a5f0f924d20901ec * Add pixel diffing to RCTFrameTimingsObserver (#56043) Summary: Pull Request resolved: #56043 Use pixel hash comparison to avoid encoding duplicate screenshots on iOS, reducing overhead when frames don't change. Matches Android behaviour. - Samples every 64th pixel with a fast FNV-1a hash (~0.1ms on main thread). - Skips JPEG encoding and CDP transport entirely for unchanged frames — the dominant per-frame cost. **Why this approach** Attempted `CATransaction` completion blocks, however these fire on commits that produce no visual change. No other public API on iOS. Changelog: [Internal] Reviewed By: sbuggay Differential Revision: D95981881 fbshipit-source-id: 76f492f0ceb6b3efea3fce822036ed6cb6067b83 * Add dynamic sampling to frame screenshots (#56048) Summary: Pull Request resolved: #56048 Update Android frame screenshot processing to skip screenshot capture when encoding is already in progress — now limited to a single background thread — while always emitting frame timing events. **Motivation** 1. Prevents truncated trace data on slower devices (e.g. missing screenshots for the last 1/3 of the trace), with the tradeoff of some intermediate frame screenshot loss. 2. Reduces total recording overhead by freeing up device threads - prevents excessive encoding work from blocking or slowing down the UI and other app threads. **Algorithm** Uses `encodingInProgress` atomic flag with single encoding thread and `lastFrameBuffer` storage for tail-capture of the last frame before idling (to capture settled animation states): - **Not encoding:** Frame passes directly to encoder → emits with screenshot when done - **Encoding busy:** Frame stored in `lastFrameBuffer` for tail-capture → any replaced frame emits without screenshot - **Encoding done:** Clears flag early, then opportunistically encodes tail frame without blocking new frames - **Failed captures:** Emit without screenshot immediately Result: Every frame emitted exactly once. Encoding adapts to device speed. Settled animation state guaranteed captured. **Remaining work** - ⚠️ This still does not yet solve crashes (OkHttp network chunk size overflow) for heavy frame data at a high FPS on fast devices (coming next). Changelog: [Internal] Reviewed By: rubennorte Differential Revision: D95987488 fbshipit-source-id: cda5758e3091edd227d3838638ba157dbcea52d8 * Switch trace event chunks from event count to size based (#56080) Summary: Pull Request resolved: #56080 Trace event chunks were previously split by a fixed event count (1000). This caused issues with Frame Timing events containing screenshot data, where a single chunk could produce CDP messages of tens or hundreds of MBs — exceeding the OkHttp WebSocket limit (app crash). Address this problem by replacing count-based chunking with size-based chunking (max 10 MiB per chunk) for `emitFrameTimings()` and `emitPerformanceTraceEvents()`. A new `TraceEventSerializer::estimateJsonSize()` utility recursively estimates the serialized byte size of a `folly::dynamic` value to avoid double-serialization overhead. **Notes** - RuntimeSamplingProfile chunks (separate code path) remain count-based (inherently small, no screenshot data). Changelog: [Internal] Reviewed By: rubennorte Differential Revision: D96206962 fbshipit-source-id: 6b6e4db76441f174c46bf34905292585d0e30899 * Increase trace screenshot scale factor to 1x (#56079) Summary: Pull Request resolved: #56079 Stacked on the current set of improvements, now further increase screenshot quality on Android. Changelog: [Internal] Reviewed By: rubennorte Differential Revision: D96382172 fbshipit-source-id: 1f850d249142a7438eb0064194fac16ce6c9ae89 * Add Page.captureScreenshot CDP support (#56307) Summary: Pull Request resolved: #56307 Implement the `Page.captureScreenshot` CDP command in the inspector backend, allowing DevTools to capture an on-demand screenshot of the current app view. This is a minimal implementation supporting only the `format` (jpeg/png/webp) and `quality` (0-100, jpeg only) parameters, returning base64-encoded image data. The feature is gated behind a new `fuseboxCaptureScreenshotEnabled` React Native feature flag, wired through `InspectorFlags` following the same pattern as frame recording. **Motivation** Improve agent verification / user feedback during AI sessions. We've proven that screenshots (in performance traces) are useful for understanding how components render on screen, and exposing this as an on-demand CDP method is relatively cheap today vs the larger task of modelling the DOM (Elements panel). **Changes** - New `fuseboxCaptureScreenshotEnabled` feature flag + `InspectorFlags` wiring (C++, Android JNI, Kotlin) - `HostTargetDelegate::captureScreenshot()` virtual method with async callback - C++ CDP handler in `HostAgent` — parses `format`/`quality`, delegates to platform, sends async CDP response (gated by flag) - iOS (`RCTHost.mm`): Captures key window via `UIGraphicsImageRenderer` + `drawViewHierarchyInRect`, encodes to PNG/JPEG - Android (`ReactHostImpl.kt`): Captures decor view via `Bitmap` + `Canvas` + `View.draw()`, encodes to PNG/JPEG/WebP - 4 C++ tests: success, failure, param forwarding, flag-disabled rejection Changelog: [Internal] Reviewed By: hoxyq Differential Revision: D99099930 fbshipit-source-id: d4d2ef86ba20d2ee230903e152dd21e11f29cbb8 * Fix data race on PerformanceObserver entry buffer (#56352) Summary: Pull Request resolved: #56352 Add mutex to protect `PerformanceObserver::buffer_` and `didScheduleFlushBuffer_`, which can be accessed concurrently from a background thread (`handleEntry`) and JS thread (`takeRecords`). Fixes T263429319. Changelog: [Internal] Reviewed By: jorge-cab Differential Revision: D99820359 fbshipit-source-id: 068fa1871b3a0feeb84b950b0c8f5fe7dcf38b7d * Add dynamic sampling to frame screenshots (#56135) Summary: Pull Request resolved: #56135 Ports the dynamic sampling algorithm from Android (D95987488) to iOS. Uses an `encodingInProgress` atomic flag with a single encoding thread and `lastFrameData` buffer to skip screenshot encoding when the encoder is busy, while always emitting frame timing events. This prevents truncated trace data on slower devices and reduces recording overhead. Changelog: [Internal] Reviewed By: rubennorte Differential Revision: D97131485 fbshipit-source-id: 5c30f1052d5b5a61f27f2ab90946ef47b8b6b279 * Increase trace screenshot scale factor to 1x (#56136) Summary: Pull Request resolved: #56136 Following D97131485, brings iOS screenshot recording to parity with Android. NOTE: Dynamic frame sampling (D97131485) becomes more load bearing after this change, as a 1x scale increases processing overhead significantly. Let's continue dogfooding. Changelog: [Internal] Reviewed By: rubennorte Differential Revision: D97131484 fbshipit-source-id: 4c03842ae22b905a50f989ff68fece7acc289a67 * Add missing debugger define for React-RuntimeApple (#56397) Summary: Pull Request resolved: #56397 The `React-RuntimeApple` pod/target was missing the `REACT_NATIVE_DEBUGGER_ENABLED` preprocessor define in both open source build configurations. Without this, the experimental-and-flagged `fuseboxFrameRecordingEnabled` feature (D95566220) was misbehaving on iOS — code paths in `RCTHost.mm` were unexpectedly compiled out. See #56372. Changelog: [Internal] Reviewed By: rubennorte Differential Revision: D100157250 fbshipit-source-id: 8c11287f34ac6029dd1153a01fed6ba93cc7d398 --------- Co-authored-by: Alex Hunt <huntie@meta.com> Co-authored-by: Rubén Norte <rubennorte@meta.com>
1 parent 5e3a3ba commit 691b232

62 files changed

Lines changed: 1394 additions & 152 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/react-native/Package.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,10 @@ let reactRuntimeApple = RNTarget(
398398
name: .reactRuntimeApple,
399399
path: "ReactCommon/react/runtime/platform/ios",
400400
excludedPaths: ["ReactCommon/RCTJscInstance.mm", "ReactCommon/metainternal"],
401-
dependencies: [.reactNativeDependencies, .jsi, .reactPerfLogger, .reactCxxReact, .rctDeprecation, .yoga, .reactRuntime, .reactRCTFabric, .reactCoreModules, .reactTurboModuleCore, .hermesPrebuilt, .reactUtils]
401+
dependencies: [.reactNativeDependencies, .jsi, .reactPerfLogger, .reactCxxReact, .rctDeprecation, .yoga, .reactRuntime, .reactRCTFabric, .reactCoreModules, .reactTurboModuleCore, .hermesPrebuilt, .reactUtils],
402+
defines: [
403+
CXXSetting.define("REACT_NATIVE_DEBUGGER_ENABLED", to: "1", .when(configuration: BuildConfiguration.debug))
404+
]
402405
)
403406

404407
let publicHeadersPathForReactCore: String = BUILD_FROM_SOURCE ? "includes" : "."
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#import <Foundation/Foundation.h>
9+
10+
#ifdef __cplusplus
11+
#import <jsinspector-modern/tracing/FrameTimingSequence.h>
12+
13+
using RCTFrameTimingCallback = void (^)(facebook::react::jsinspector_modern::tracing::FrameTimingSequence);
14+
#endif
15+
16+
@interface RCTFrameTimingsObserver : NSObject
17+
18+
#ifdef __cplusplus
19+
- (instancetype)initWithScreenshotsEnabled:(BOOL)screenshotsEnabled callback:(RCTFrameTimingCallback)callback;
20+
#endif
21+
- (void)start;
22+
- (void)stop;
23+
24+
@end
Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#import "RCTFrameTimingsObserver.h"
9+
10+
#import <UIKit/UIKit.h>
11+
12+
#import <mach/thread_act.h>
13+
#import <pthread.h>
14+
15+
#import <atomic>
16+
#import <chrono>
17+
#import <mutex>
18+
#import <optional>
19+
#import <vector>
20+
21+
#import <react/timing/primitives.h>
22+
23+
using namespace facebook::react;
24+
25+
static constexpr CGFloat kScreenshotScaleFactor = 1.0;
26+
static constexpr CGFloat kScreenshotJPEGQuality = 0.8;
27+
28+
namespace {
29+
30+
// Stores a captured frame screenshot and its associated metadata, used for
31+
// buffering frames during dynamic sampling.
32+
struct FrameData {
33+
UIImage *image;
34+
uint64_t frameId;
35+
jsinspector_modern::tracing::ThreadId threadId;
36+
HighResTimeStamp beginTimestamp;
37+
HighResTimeStamp endTimestamp;
38+
};
39+
40+
} // namespace
41+
42+
@implementation RCTFrameTimingsObserver {
43+
BOOL _screenshotsEnabled;
44+
RCTFrameTimingCallback _callback;
45+
CADisplayLink *_displayLink;
46+
uint64_t _frameCounter;
47+
// Serial queue for encoding work (single background thread). We limit to 1
48+
// thread to minimize the performance impact of screenshot recording.
49+
dispatch_queue_t _encodingQueue;
50+
std::atomic<bool> _running;
51+
uint64_t _lastScreenshotHash;
52+
53+
// Stores the most recently captured frame to opportunistically encode after
54+
// the current frame. Replaced frames are emitted as timings without
55+
// screenshots.
56+
std::mutex _lastFrameMutex;
57+
std::optional<FrameData> _lastFrameData;
58+
59+
std::atomic<bool> _encodingInProgress;
60+
}
61+
62+
- (instancetype)initWithScreenshotsEnabled:(BOOL)screenshotsEnabled callback:(RCTFrameTimingCallback)callback
63+
{
64+
if (self = [super init]) {
65+
_screenshotsEnabled = screenshotsEnabled;
66+
_callback = [callback copy];
67+
_frameCounter = 0;
68+
_encodingQueue = dispatch_queue_create("com.facebook.react.frame-timings-observer", DISPATCH_QUEUE_SERIAL);
69+
_running.store(false);
70+
_lastScreenshotHash = 0;
71+
_encodingInProgress.store(false);
72+
}
73+
return self;
74+
}
75+
76+
- (void)start
77+
{
78+
_running.store(true, std::memory_order_relaxed);
79+
_frameCounter = 0;
80+
_lastScreenshotHash = 0;
81+
_encodingInProgress.store(false, std::memory_order_relaxed);
82+
{
83+
std::lock_guard<std::mutex> lock(_lastFrameMutex);
84+
_lastFrameData.reset();
85+
}
86+
87+
// Emit initial frame event
88+
auto now = HighResTimeStamp::now();
89+
[self _emitFrameTimingWithBeginTimestamp:now endTimestamp:now];
90+
91+
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_displayLinkTick:)];
92+
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
93+
}
94+
95+
- (void)stop
96+
{
97+
_running.store(false, std::memory_order_relaxed);
98+
[_displayLink invalidate];
99+
_displayLink = nil;
100+
{
101+
std::lock_guard<std::mutex> lock(_lastFrameMutex);
102+
_lastFrameData.reset();
103+
}
104+
}
105+
106+
- (void)_displayLinkTick:(CADisplayLink *)sender
107+
{
108+
// CADisplayLink.timestamp and targetTimestamp are in the same timebase as
109+
// CACurrentMediaTime() / mach_absolute_time(), which on Apple platforms maps
110+
// to CLOCK_UPTIME_RAW — the same clock backing std::chrono::steady_clock.
111+
auto beginNanos = static_cast<int64_t>(sender.timestamp * 1e9);
112+
auto endNanos = static_cast<int64_t>(sender.targetTimestamp * 1e9);
113+
114+
auto beginTimestamp = HighResTimeStamp::fromChronoSteadyClockTimePoint(
115+
std::chrono::steady_clock::time_point(std::chrono::nanoseconds(beginNanos)));
116+
auto endTimestamp = HighResTimeStamp::fromChronoSteadyClockTimePoint(
117+
std::chrono::steady_clock::time_point(std::chrono::nanoseconds(endNanos)));
118+
119+
[self _emitFrameTimingWithBeginTimestamp:beginTimestamp endTimestamp:endTimestamp];
120+
}
121+
122+
- (void)_emitFrameTimingWithBeginTimestamp:(HighResTimeStamp)beginTimestamp endTimestamp:(HighResTimeStamp)endTimestamp
123+
{
124+
uint64_t frameId = _frameCounter++;
125+
auto threadId = static_cast<jsinspector_modern::tracing::ThreadId>(pthread_mach_thread_np(pthread_self()));
126+
127+
if (!_screenshotsEnabled) {
128+
// Screenshots disabled - emit without screenshot
129+
[self _emitFrameEventWithFrameId:frameId
130+
threadId:threadId
131+
beginTimestamp:beginTimestamp
132+
endTimestamp:endTimestamp
133+
screenshot:std::nullopt];
134+
return;
135+
}
136+
137+
UIImage *image = [self _captureScreenshot];
138+
if (image == nil) {
139+
// Failed to capture (e.g. no window, duplicate hash) - emit without screenshot
140+
[self _emitFrameEventWithFrameId:frameId
141+
threadId:threadId
142+
beginTimestamp:beginTimestamp
143+
endTimestamp:endTimestamp
144+
screenshot:std::nullopt];
145+
return;
146+
}
147+
148+
FrameData frameData{image, frameId, threadId, beginTimestamp, endTimestamp};
149+
150+
bool expected = false;
151+
if (_encodingInProgress.compare_exchange_strong(expected, true)) {
152+
// Not encoding - encode this frame immediately
153+
[self _encodeFrame:std::move(frameData)];
154+
} else {
155+
// Encoding thread busy - store current screenshot in buffer for tail-capture
156+
std::optional<FrameData> oldFrame;
157+
{
158+
std::lock_guard<std::mutex> lock(_lastFrameMutex);
159+
oldFrame = std::move(_lastFrameData);
160+
_lastFrameData = std::move(frameData);
161+
}
162+
if (oldFrame.has_value()) {
163+
// Skipped frame - emit event without screenshot
164+
[self _emitFrameEventWithFrameId:oldFrame->frameId
165+
threadId:oldFrame->threadId
166+
beginTimestamp:oldFrame->beginTimestamp
167+
endTimestamp:oldFrame->endTimestamp
168+
screenshot:std::nullopt];
169+
}
170+
}
171+
}
172+
173+
- (void)_emitFrameEventWithFrameId:(uint64_t)frameId
174+
threadId:(jsinspector_modern::tracing::ThreadId)threadId
175+
beginTimestamp:(HighResTimeStamp)beginTimestamp
176+
endTimestamp:(HighResTimeStamp)endTimestamp
177+
screenshot:(std::optional<std::vector<uint8_t>>)screenshot
178+
{
179+
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
180+
if (!self->_running.load(std::memory_order_relaxed)) {
181+
return;
182+
}
183+
jsinspector_modern::tracing::FrameTimingSequence sequence{
184+
frameId, threadId, beginTimestamp, endTimestamp, std::move(screenshot)};
185+
self->_callback(std::move(sequence));
186+
});
187+
}
188+
189+
- (void)_encodeFrame:(FrameData)frameData
190+
{
191+
dispatch_async(_encodingQueue, ^{
192+
if (!self->_running.load(std::memory_order_relaxed)) {
193+
return;
194+
}
195+
196+
auto screenshot = [self _encodeScreenshot:frameData.image];
197+
[self _emitFrameEventWithFrameId:frameData.frameId
198+
threadId:frameData.threadId
199+
beginTimestamp:frameData.beginTimestamp
200+
endTimestamp:frameData.endTimestamp
201+
screenshot:std::move(screenshot)];
202+
203+
// Clear encoding flag early, allowing new frames to start fresh encoding
204+
// sessions
205+
self->_encodingInProgress.store(false, std::memory_order_release);
206+
207+
// Opportunistically encode tail frame (if present) without blocking new
208+
// frames
209+
std::optional<FrameData> tailFrame;
210+
{
211+
std::lock_guard<std::mutex> lock(self->_lastFrameMutex);
212+
tailFrame = std::move(self->_lastFrameData);
213+
self->_lastFrameData.reset();
214+
}
215+
if (tailFrame.has_value()) {
216+
if (!self->_running.load(std::memory_order_relaxed)) {
217+
return;
218+
}
219+
auto tailScreenshot = [self _encodeScreenshot:tailFrame->image];
220+
[self _emitFrameEventWithFrameId:tailFrame->frameId
221+
threadId:tailFrame->threadId
222+
beginTimestamp:tailFrame->beginTimestamp
223+
endTimestamp:tailFrame->endTimestamp
224+
screenshot:std::move(tailScreenshot)];
225+
}
226+
});
227+
}
228+
229+
// Captures a screenshot of the current window. Must be called on the main
230+
// thread. Returns nil if capture fails or if the frame content is unchanged.
231+
- (UIImage *)_captureScreenshot
232+
{
233+
UIWindow *keyWindow = [self _getKeyWindow];
234+
if (keyWindow == nil) {
235+
return nil;
236+
}
237+
238+
UIView *rootView = keyWindow.rootViewController.view ?: keyWindow;
239+
CGSize viewSize = rootView.bounds.size;
240+
CGSize scaledSize = CGSizeMake(viewSize.width * kScreenshotScaleFactor, viewSize.height * kScreenshotScaleFactor);
241+
242+
UIGraphicsImageRendererFormat *format = [UIGraphicsImageRendererFormat defaultFormat];
243+
format.scale = 1.0;
244+
UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:scaledSize format:format];
245+
246+
UIImage *image = [renderer imageWithActions:^(UIGraphicsImageRendererContext *context) {
247+
[rootView drawViewHierarchyInRect:CGRectMake(0, 0, scaledSize.width, scaledSize.height) afterScreenUpdates:NO];
248+
}];
249+
250+
// Skip duplicate frames via sampled FNV-1a pixel hash
251+
CGImageRef cgImage = image.CGImage;
252+
CFDataRef pixelData = CGDataProviderCopyData(CGImageGetDataProvider(cgImage));
253+
uint64_t hash = 0xcbf29ce484222325ULL;
254+
const uint8_t *ptr = CFDataGetBytePtr(pixelData);
255+
CFIndex length = CFDataGetLength(pixelData);
256+
// Use prime stride to prevent row alignment on power-of-2 pixel widths
257+
for (CFIndex i = 0; i < length; i += 67) {
258+
hash ^= ptr[i];
259+
hash *= 0x100000001b3ULL;
260+
}
261+
CFRelease(pixelData);
262+
263+
if (hash == _lastScreenshotHash) {
264+
return nil;
265+
}
266+
_lastScreenshotHash = hash;
267+
268+
return image;
269+
}
270+
271+
- (std::optional<std::vector<uint8_t>>)_encodeScreenshot:(UIImage *)image
272+
{
273+
NSData *jpegData = UIImageJPEGRepresentation(image, kScreenshotJPEGQuality);
274+
if (jpegData == nil) {
275+
return std::nullopt;
276+
}
277+
278+
const auto *bytes = static_cast<const uint8_t *>(jpegData.bytes);
279+
return std::vector<uint8_t>(bytes, bytes + jpegData.length);
280+
}
281+
282+
- (UIWindow *)_getKeyWindow
283+
{
284+
for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
285+
if (scene.activationState == UISceneActivationStateForegroundActive &&
286+
[scene isKindOfClass:[UIWindowScene class]]) {
287+
auto windowScene = (UIWindowScene *)scene;
288+
for (UIWindow *window = nullptr in windowScene.windows) {
289+
if (window.isKeyWindow) {
290+
return window;
291+
}
292+
}
293+
}
294+
}
295+
return nil;
296+
}
297+
298+
@end

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/InspectorFlags.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ internal object InspectorFlags {
1717
SoLoader.loadLibrary("react_devsupportjni")
1818
}
1919

20+
@DoNotStrip @JvmStatic external fun getScreenshotCaptureEnabled(): Boolean
21+
2022
@DoNotStrip @JvmStatic external fun getFuseboxEnabled(): Boolean
2123

2224
@DoNotStrip @JvmStatic external fun getIsProfilingBuild(): Boolean
25+
26+
@DoNotStrip @JvmStatic external fun getFrameRecordingEnabled(): Boolean
2327
}

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/inspector/FrameTimingSequence.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ internal data class FrameTimingSequence(
1212
val threadId: Int,
1313
val beginTimestamp: Long,
1414
val endTimestamp: Long,
15-
val screenshot: String? = null,
15+
val screenshot: ByteArray? = null,
1616
)

0 commit comments

Comments
 (0)