react-native-tvos 0.83.4-1 → 0.83.6-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/Libraries/Components/ScrollView/AndroidHorizontalScrollViewNativeComponent.js +1 -0
  2. package/Libraries/Components/ScrollView/ScrollView.d.ts +7 -0
  3. package/Libraries/Components/ScrollView/ScrollView.js +6 -0
  4. package/Libraries/Components/ScrollView/ScrollViewNativeComponent.js +2 -0
  5. package/Libraries/Components/ScrollView/ScrollViewNativeComponentType.js +1 -0
  6. package/Libraries/Core/ReactNativeVersion.js +2 -2
  7. package/Libraries/Utilities/Appearance.js +6 -1
  8. package/Libraries/Utilities/HMRClient.js +28 -1
  9. package/README.md +52 -0
  10. package/React/Base/RCTVersion.m +2 -2
  11. package/React/CoreModules/RCTDevLoadingView.mm +17 -0
  12. package/React/DevSupport/RCTFrameTimingsObserver.h +24 -0
  13. package/React/DevSupport/RCTFrameTimingsObserver.mm +298 -0
  14. package/React/FBReactNativeSpec/FBReactNativeSpecJSI.h +16 -0
  15. package/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.h +1 -0
  16. package/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm +78 -0
  17. package/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm +32 -6
  18. package/React/Views/ScrollView/RCTScrollViewManager.m +1 -0
  19. package/ReactAndroid/api/ReactAndroid.api +0 -9
  20. package/ReactAndroid/gradle.properties +1 -1
  21. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgelessDevSupportManager.kt +2 -2
  22. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.kt +7 -7
  23. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/InspectorFlags.kt +4 -0
  24. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/inspector/FrameTimingSequence.kt +16 -0
  25. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/inspector/FrameTimingsObserver.kt +275 -0
  26. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/inspector/TracingState.kt +17 -0
  27. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/inspector/TracingStateListener.kt +15 -0
  28. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/{interfaces → inspector}/TracingStateProvider.kt +1 -1
  29. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/perfmonitor/PerfMonitorInspectorTargetBinding.kt +1 -1
  30. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/perfmonitor/PerfMonitorOverlayManager.kt +4 -4
  31. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/perfmonitor/PerfMonitorOverlayView.kt +3 -3
  32. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/perfmonitor/PerfMonitorUpdateListener.kt +1 -1
  33. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt +13 -1
  34. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt +21 -1
  35. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt +5 -1
  36. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt +6 -2
  37. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt +23 -1
  38. package/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt +5 -1
  39. package/ReactAndroid/src/main/java/com/facebook/react/internal/tracing/PerformanceTracer.kt +39 -0
  40. package/ReactAndroid/src/main/java/com/facebook/react/modules/blob/BlobModule.kt +1 -1
  41. package/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkEventUtil.kt +20 -19
  42. package/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.kt +6 -12
  43. package/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.kt +2 -2
  44. package/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.kt +86 -4
  45. package/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImplDevHelper.kt +3 -3
  46. package/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostInspectorTarget.kt +10 -6
  47. package/ReactAndroid/src/main/java/com/facebook/react/views/common/UiModeUtils.kt +20 -0
  48. package/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java +70 -8
  49. package/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.kt +5 -0
  50. package/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java +48 -2
  51. package/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.kt +5 -0
  52. package/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java +28 -3
  53. package/ReactAndroid/src/main/jni/react/devsupport/JInspectorFlags.cpp +22 -0
  54. package/ReactAndroid/src/main/jni/react/devsupport/JInspectorFlags.h +2 -0
  55. package/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp +29 -1
  56. package/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h +7 -1
  57. package/ReactAndroid/src/main/jni/react/runtime/jni/JReactHostInspectorTarget.cpp +196 -17
  58. package/ReactAndroid/src/main/jni/react/runtime/jni/JReactHostInspectorTarget.h +168 -18
  59. package/ReactAndroid/src/main/jni/third-party/folly/CMakeLists.txt +1 -0
  60. package/ReactCommon/cxxreact/ReactNativeVersion.h +3 -3
  61. package/ReactCommon/hermes/inspector-modern/chrome/Registration.cpp +44 -2
  62. package/ReactCommon/jsinspector-modern/HostAgent.cpp +45 -10
  63. package/ReactCommon/jsinspector-modern/HostAgent.h +2 -2
  64. package/ReactCommon/jsinspector-modern/HostTarget.cpp +14 -7
  65. package/ReactCommon/jsinspector-modern/HostTarget.h +101 -14
  66. package/ReactCommon/jsinspector-modern/HostTargetTraceRecording.cpp +39 -8
  67. package/ReactCommon/jsinspector-modern/HostTargetTraceRecording.h +42 -5
  68. package/ReactCommon/jsinspector-modern/HostTargetTracing.cpp +54 -21
  69. package/ReactCommon/jsinspector-modern/HostTargetTracing.h +89 -0
  70. package/ReactCommon/jsinspector-modern/InspectorFlags.cpp +12 -0
  71. package/ReactCommon/jsinspector-modern/InspectorFlags.h +12 -0
  72. package/ReactCommon/jsinspector-modern/InspectorInterfaces.cpp +3 -7
  73. package/ReactCommon/jsinspector-modern/InstanceAgent.cpp +2 -11
  74. package/ReactCommon/jsinspector-modern/NetworkIOAgent.cpp +1 -1
  75. package/ReactCommon/jsinspector-modern/RuntimeAgent.cpp +19 -0
  76. package/ReactCommon/jsinspector-modern/RuntimeAgent.h +7 -0
  77. package/ReactCommon/jsinspector-modern/RuntimeTarget.cpp +33 -0
  78. package/ReactCommon/jsinspector-modern/RuntimeTarget.h +6 -0
  79. package/ReactCommon/jsinspector-modern/TracingAgent.cpp +29 -13
  80. package/ReactCommon/jsinspector-modern/TracingAgent.h +5 -4
  81. package/ReactCommon/jsinspector-modern/tests/HostTargetTest.cpp +65 -0
  82. package/ReactCommon/jsinspector-modern/tests/InspectorMocks.h +23 -2
  83. package/ReactCommon/jsinspector-modern/tests/JsiIntegrationTest.cpp +1 -0
  84. package/ReactCommon/jsinspector-modern/tests/NetworkReporterTest.cpp +1 -0
  85. package/ReactCommon/jsinspector-modern/tests/TracingTest.cpp +335 -0
  86. package/ReactCommon/jsinspector-modern/tests/TracingTest.h +95 -0
  87. package/ReactCommon/jsinspector-modern/tests/utils/InspectorFlagOverridesGuard.cpp +10 -0
  88. package/ReactCommon/jsinspector-modern/tests/utils/InspectorFlagOverridesGuard.h +3 -1
  89. package/ReactCommon/jsinspector-modern/tracing/CMakeLists.txt +1 -0
  90. package/ReactCommon/jsinspector-modern/tracing/FrameTimingSequence.h +61 -0
  91. package/ReactCommon/jsinspector-modern/tracing/HostTracingProfile.h +43 -0
  92. package/ReactCommon/jsinspector-modern/tracing/HostTracingProfileSerializer.cpp +165 -0
  93. package/ReactCommon/jsinspector-modern/tracing/HostTracingProfileSerializer.h +50 -0
  94. package/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp +16 -14
  95. package/ReactCommon/jsinspector-modern/tracing/PerformanceTracerSection.h +113 -0
  96. package/ReactCommon/jsinspector-modern/tracing/React-jsinspectortracing.podspec +1 -0
  97. package/ReactCommon/jsinspector-modern/tracing/TimeWindowedBuffer.h +158 -0
  98. package/ReactCommon/jsinspector-modern/tracing/TraceEvent.h +2 -1
  99. package/ReactCommon/jsinspector-modern/tracing/TraceEventGenerator.cpp +100 -0
  100. package/ReactCommon/jsinspector-modern/tracing/TraceEventGenerator.h +60 -0
  101. package/ReactCommon/jsinspector-modern/tracing/TraceEventSerializer.cpp +44 -1
  102. package/ReactCommon/jsinspector-modern/tracing/TraceEventSerializer.h +7 -0
  103. package/ReactCommon/jsinspector-modern/tracing/TraceRecordingState.h +18 -7
  104. package/ReactCommon/jsinspector-modern/tracing/TracingCategory.h +136 -0
  105. package/ReactCommon/jsinspector-modern/tracing/tests/TimeWindowedBufferTest.cpp +352 -0
  106. package/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp +9 -1
  107. package/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h +11 -1
  108. package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp +65 -29
  109. package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h +6 -2
  110. package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h +10 -2
  111. package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h +19 -1
  112. package/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h +3 -1
  113. package/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm +3 -1
  114. package/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp +11 -1
  115. package/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h +5 -1
  116. package/ReactCommon/react/performance/timeline/PerformanceObserver.cpp +18 -6
  117. package/ReactCommon/react/performance/timeline/PerformanceObserver.h +2 -0
  118. package/ReactCommon/react/renderer/components/scrollview/BaseScrollViewProps.cpp +10 -0
  119. package/ReactCommon/react/renderer/components/scrollview/BaseScrollViewProps.h +1 -0
  120. package/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm +115 -0
  121. package/ReactCommon/{jsinspector-modern → react/utils}/Base64.h +2 -2
  122. package/gradle/libs.versions.toml +1 -1
  123. package/package.json +10 -10
  124. package/scripts/cocoapods/utils.rb +1 -0
  125. package/src/private/featureflags/ReactNativeFeatureFlags.js +12 -2
  126. package/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +3 -1
  127. package/third-party-podspecs/RCT-Folly.podspec +1 -1
  128. package/third-party-podspecs/fmt.podspec +2 -2
  129. package/types/public/ReactNativeTVTypes.d.ts +8 -0
  130. package/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/TracingState.kt +0 -19
  131. package/ReactCommon/jsinspector-modern/tracing/TraceRecordingStateSerializer.cpp +0 -68
  132. package/ReactCommon/jsinspector-modern/tracing/TraceRecordingStateSerializer.h +0 -42
  133. package/ReactCommon/jsinspector-modern/tracing/TracingState.h +0 -24
@@ -59,6 +59,7 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig = {
59
59
  process: require('../../StyleSheet/processColor').default,
60
60
  },
61
61
  snapToItemPadding: true,
62
+ scrollAnimationEnabled: true,
62
63
  pointerEvents: true,
63
64
  },
64
65
  };
@@ -745,6 +745,13 @@ export interface ScrollViewProps
745
745
  */
746
746
  removeClippedSubviews?: boolean | undefined;
747
747
 
748
+ /**
749
+ * (TV only)
750
+ * When false, the scroll view will jump to the correct offset without animation
751
+ * when focus changes. Defaults to true.
752
+ */
753
+ scrollAnimationEnabled?: boolean | undefined;
754
+
748
755
  /**
749
756
  * When true, shows a horizontal scroll indicator.
750
757
  */
@@ -662,6 +662,12 @@ type ScrollViewBaseProps = $ReadOnly<{
662
662
  * true.
663
663
  */
664
664
  removeClippedSubviews?: ?boolean,
665
+ /**
666
+ * (TV only)
667
+ * When false, the scroll view will jump to the correct offset without animation
668
+ * when focus changes. Defaults to true.
669
+ */
670
+ scrollAnimationEnabled?: ?boolean,
665
671
  /**
666
672
  * A RefreshControl component, used to provide pull-to-refresh
667
673
  * functionality for the ScrollView. Only works for vertical ScrollViews
@@ -87,6 +87,7 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig =
87
87
  process: require('../../StyleSheet/processColor').default,
88
88
  },
89
89
  snapToItemPadding: true,
90
+ scrollAnimationEnabled: true,
90
91
  pointerEvents: true,
91
92
  isInvertedVirtualizedList: true,
92
93
  },
@@ -152,6 +153,7 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig =
152
153
  scrollsToTop: true,
153
154
  showsHorizontalScrollIndicator: true,
154
155
  showsVerticalScrollIndicator: true,
156
+ scrollAnimationEnabled: true,
155
157
  showsScrollIndex: true,
156
158
  snapToItemPadding: true,
157
159
  snapToAlignment: true,
@@ -72,6 +72,7 @@ export type ScrollViewNativeProps = $ReadOnly<{
72
72
  snapToItemPadding?: ?number,
73
73
  sendMomentumEvents?: ?boolean,
74
74
  showsHorizontalScrollIndicator?: ?boolean,
75
+ scrollAnimationEnabled?: ?boolean,
75
76
  showsScrollIndex?: ?boolean,
76
77
  showsVerticalScrollIndicator?: ?boolean,
77
78
  snapToAlignment?: ?('start' | 'center' | 'end' | 'item'),
@@ -28,8 +28,8 @@
28
28
  export default class ReactNativeVersion {
29
29
  static major: number = 0;
30
30
  static minor: number = 83;
31
- static patch: number = 4;
32
- static prerelease: string | null = '1';
31
+ static patch: number = 6;
32
+ static prerelease: string | null = '0';
33
33
 
34
34
  static getVersionString(): string {
35
35
  return `${this.major}.${this.minor}.${this.patch}${this.prerelease != null ? `-${this.prerelease}` : ''}`;
@@ -99,7 +99,12 @@ export function setColorScheme(colorScheme: ColorSchemeName): void {
99
99
  if (NativeAppearance != null) {
100
100
  NativeAppearance.setColorScheme(colorScheme);
101
101
  state.appearance = {
102
- colorScheme,
102
+ // When setting to 'unspecified', get the actual system color scheme.
103
+ // Fall back to the passed value if getColorScheme() returns null.
104
+ colorScheme:
105
+ colorScheme === 'unspecified'
106
+ ? (NativeAppearance.getColorScheme() ?? colorScheme)
107
+ : colorScheme,
103
108
  };
104
109
  }
105
110
  }
@@ -26,6 +26,7 @@ let hmrUnavailableReason: string | null = null;
26
26
  let hmrOrigin: string | null = null;
27
27
  let currentCompileErrorMessage: string | null = null;
28
28
  let didConnect: boolean = false;
29
+ let lastMarkerChangeId: ?string = null;
29
30
  let pendingLogs: Array<[LogLevel, $ReadOnlyArray<mixed>]> = [];
30
31
 
31
32
  type LogLevel =
@@ -229,10 +230,15 @@ Error: ${e.message}`;
229
230
  }
230
231
  });
231
232
 
232
- client.on('update-done', () => {
233
+ client.on('update-done', body => {
233
234
  pendingUpdatesCount--;
234
235
  if (pendingUpdatesCount === 0) {
235
236
  DevLoadingView.hide();
237
+ const changeId = body?.changeId;
238
+ if (changeId != null && changeId !== lastMarkerChangeId) {
239
+ lastMarkerChangeId = changeId;
240
+ emitFastRefreshCompleteEvents();
241
+ }
236
242
  }
237
243
  });
238
244
 
@@ -378,4 +384,25 @@ function showCompileError() {
378
384
  throw error;
379
385
  }
380
386
 
387
+ function emitFastRefreshCompleteEvents() {
388
+ // Add marker entry in performance timeline
389
+ performance.mark('Fast Refresh - Update done', {
390
+ detail: {
391
+ devtools: {
392
+ dataType: 'marker',
393
+ color: 'primary',
394
+ tooltipText: 'Fast Refresh \u269b',
395
+ },
396
+ },
397
+ });
398
+
399
+ // Notify CDP clients via internal binding
400
+ if (
401
+ // $FlowFixMe[prop-missing] - Injected by RuntimeTarget
402
+ typeof globalThis.__notifyFastRefreshComplete === 'function'
403
+ ) {
404
+ globalThis.__notifyFastRefreshComplete();
405
+ }
406
+ }
407
+
381
408
  export default HMRClient;
package/README.md CHANGED
@@ -262,6 +262,58 @@ class Game2048 extends React.Component {
262
262
 
263
263
  - _TVTextScrollView_: On Apple TV, a ScrollView will not scroll unless there are focusable items inside it or above/below it. This component works on both Apple TV and Android TV, using native code to allow scrolling using swipe gestures from the remote control.
264
264
 
265
+ - _ScrollView snap alignment_: The existing `snapToAlignment` prop has been extended with a new `'item'` value that enables per-item snap alignment on TV. Instead of snapping all items uniformly to `'start'`, `'center'`, or `'end'`, each child can specify its own alignment via the `scrollSnapAlign` View prop. Optional padding can be applied with `snapToItemPadding` on the ScrollView.
266
+
267
+ | Prop (ScrollView) | Value | Description |
268
+ |---|---|---|
269
+ | snapToAlignment | `'item'` | Enables per-item snap alignment (TV only). Requires `snapToInterval` to be set. |
270
+ | snapToItemPadding | number? | Extra padding (in dp/pt) applied around items when snapping. Only used with `snapToAlignment="item"`. |
271
+
272
+ | Prop (View) | Value | Description |
273
+ |---|---|---|
274
+ | scrollSnapAlign | `'start'` \| `'center'` \| `'end'` | Controls where this item snaps inside its parent ScrollView. Only used when the parent has `snapToAlignment="item"`. |
275
+
276
+ ```jsx
277
+ <ScrollView
278
+ horizontal
279
+ snapToInterval={300}
280
+ snapToAlignment="item"
281
+ snapToItemPadding={20}>
282
+ <Pressable scrollSnapAlign="start" style={{width: 300}}>
283
+ <Text>Snaps to start</Text>
284
+ </Pressable>
285
+ <Pressable scrollSnapAlign="center" style={{width: 200}}>
286
+ <Text>Snaps to center</Text>
287
+ </Pressable>
288
+ <Pressable scrollSnapAlign="end" style={{width: 300}}>
289
+ <Text>Snaps to end</Text>
290
+ </Pressable>
291
+ </ScrollView>
292
+ ```
293
+
294
+ - _ScrollView animation control_: The `scrollAnimationEnabled` prop allows disabling scroll animations when focus changes on TV. When set to `false`, the scroll view jumps instantly to the focused item instead of animating. This only affects TV platforms and has no effect on mobile.
295
+
296
+ | Prop (ScrollView) | Value | Description |
297
+ |---|---|---|
298
+ | scrollAnimationEnabled | boolean? | When `false`, disables animated scrolling on focus change. Defaults to `true`. TV only. |
299
+
300
+ ```jsx
301
+ <ScrollView horizontal scrollAnimationEnabled={false}>
302
+ <Pressable style={{width: 300}}><Text>Item 1</Text></Pressable>
303
+ <Pressable style={{width: 300}}><Text>Item 2</Text></Pressable>
304
+ <Pressable style={{width: 300}}><Text>Item 3</Text></Pressable>
305
+ </ScrollView>
306
+ ```
307
+
308
+ - _Interaction with existing ScrollView props_: The TV scroll props build on top of existing React Native ScrollView behavior. Here is how they interact:
309
+
310
+ - `snapToAlignment="item"` does not require `snapToInterval` to be set. It works as a standalone snapping mode where each child's `scrollSnapAlign` determines the snap position. If `snapToInterval` is also set, the interval is applied as an additional constraint after the item offset is computed.
311
+ - `snapToAlignment="item"` should not be combined with `pagingEnabled`. Both attempt to control scroll positioning independently, which leads to unpredictable behavior. Use one or the other.
312
+ - `snapToStart` and `snapToEnd` work independently of `snapToAlignment="item"`. They control edge behavior during swipe/drag momentum (whether the scroll view snaps to the first or last position), but do not affect focus driven item snapping.
313
+ - `scrollAnimationEnabled={false}` disables all scroll animations, including programmatic `scrollTo({animated: true})` calls. When disabled, all scrolling is instant.
314
+ - `decelerationRate` has no effect when `scrollAnimationEnabled={false}`, since there is no animation to decelerate.
315
+ - `scrollAnimationEnabled` and `snapToAlignment="item"` work well together. Snapping still occurs, but instantly instead of animated.
316
+
265
317
  - _VirtualizedList_: We extend `VirtualizedList` to make virtualization work well with focus management in mind. All of the improvements that we made are automatically available to all the VirtualizedList based components such as `FlatList`.
266
318
  - Defaults: VirtualizeList contents are automatically wrapped with a `TVFocusGuideView` with `trapFocus*` properties enabled depending on the orientation of the list. This default makes sure that focus doesn't leave the list accidentally due to a virtualization issue etc. until reaching the beginning or the end of the list.
267
319
  - New Props:
@@ -23,8 +23,8 @@ NSDictionary* RCTGetReactNativeVersion(void)
23
23
  __rnVersion = @{
24
24
  RCTVersionMajor: @(0),
25
25
  RCTVersionMinor: @(83),
26
- RCTVersionPatch: @(4),
27
- RCTVersionPrerelease: @"1",
26
+ RCTVersionPatch: @(6),
27
+ RCTVersionPrerelease: @"0",
28
28
  };
29
29
  });
30
30
  return __rnVersion;
@@ -50,10 +50,27 @@ RCT_EXPORT_MODULE()
50
50
  selector:@selector(hide)
51
51
  name:RCTJavaScriptDidFailToLoadNotification
52
52
  object:nil];
53
+ [[NSNotificationCenter defaultCenter] addObserver:self
54
+ selector:@selector(hide)
55
+ name:@"RCTInstanceDidLoadBundle"
56
+ object:nil];
53
57
  }
54
58
  return self;
55
59
  }
56
60
 
61
+ - (void)dealloc
62
+ {
63
+ [self clearInitialMessageDelay];
64
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
65
+ UIWindow *window = _window;
66
+ _window = nil;
67
+ if (window) {
68
+ RCTExecuteOnMainQueue(^{
69
+ window.hidden = YES;
70
+ });
71
+ }
72
+ }
73
+
57
74
  + (void)setEnabled:(BOOL)enabled
58
75
  {
59
76
  RCTDevLoadingViewSetEnabled(enabled);
@@ -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
@@ -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
@@ -281,7 +281,9 @@ protected:
281
281
  methodMap_["fixMappingOfEventPrioritiesBetweenFabricAndReact"] = MethodMetadata {.argCount = 0, .invoker = __fixMappingOfEventPrioritiesBetweenFabricAndReact};
282
282
  methodMap_["fuseboxAssertSingleHostState"] = MethodMetadata {.argCount = 0, .invoker = __fuseboxAssertSingleHostState};
283
283
  methodMap_["fuseboxEnabledRelease"] = MethodMetadata {.argCount = 0, .invoker = __fuseboxEnabledRelease};
284
+ methodMap_["fuseboxFrameRecordingEnabled"] = MethodMetadata {.argCount = 0, .invoker = __fuseboxFrameRecordingEnabled};
284
285
  methodMap_["fuseboxNetworkInspectionEnabled"] = MethodMetadata {.argCount = 0, .invoker = __fuseboxNetworkInspectionEnabled};
286
+ methodMap_["fuseboxScreenshotCaptureEnabled"] = MethodMetadata {.argCount = 0, .invoker = __fuseboxScreenshotCaptureEnabled};
285
287
  methodMap_["hideOffscreenVirtualViewsOnIOS"] = MethodMetadata {.argCount = 0, .invoker = __hideOffscreenVirtualViewsOnIOS};
286
288
  methodMap_["overrideBySynchronousMountPropsAtMountingAndroid"] = MethodMetadata {.argCount = 0, .invoker = __overrideBySynchronousMountPropsAtMountingAndroid};
287
289
  methodMap_["perfIssuesEnabled"] = MethodMetadata {.argCount = 0, .invoker = __perfIssuesEnabled};
@@ -718,6 +720,13 @@ private:
718
720
  return bridging::callFromJs<bool>(rt, &T::fuseboxEnabledRelease, static_cast<NativeReactNativeFeatureFlagsCxxSpec*>(&turboModule)->jsInvoker_, static_cast<T*>(&turboModule));
719
721
  }
720
722
 
723
+ static jsi::Value __fuseboxFrameRecordingEnabled(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* /*args*/, size_t /*count*/) {
724
+ static_assert(
725
+ bridging::getParameterCount(&T::fuseboxFrameRecordingEnabled) == 1,
726
+ "Expected fuseboxFrameRecordingEnabled(...) to have 1 parameters");
727
+ return bridging::callFromJs<bool>(rt, &T::fuseboxFrameRecordingEnabled, static_cast<NativeReactNativeFeatureFlagsCxxSpec*>(&turboModule)->jsInvoker_, static_cast<T*>(&turboModule));
728
+ }
729
+
721
730
  static jsi::Value __fuseboxNetworkInspectionEnabled(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* /*args*/, size_t /*count*/) {
722
731
  static_assert(
723
732
  bridging::getParameterCount(&T::fuseboxNetworkInspectionEnabled) == 1,
@@ -725,6 +734,13 @@ private:
725
734
  return bridging::callFromJs<bool>(rt, &T::fuseboxNetworkInspectionEnabled, static_cast<NativeReactNativeFeatureFlagsCxxSpec*>(&turboModule)->jsInvoker_, static_cast<T*>(&turboModule));
726
735
  }
727
736
 
737
+ static jsi::Value __fuseboxScreenshotCaptureEnabled(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* /*args*/, size_t /*count*/) {
738
+ static_assert(
739
+ bridging::getParameterCount(&T::fuseboxScreenshotCaptureEnabled) == 1,
740
+ "Expected fuseboxScreenshotCaptureEnabled(...) to have 1 parameters");
741
+ return bridging::callFromJs<bool>(rt, &T::fuseboxScreenshotCaptureEnabled, static_cast<NativeReactNativeFeatureFlagsCxxSpec*>(&turboModule)->jsInvoker_, static_cast<T*>(&turboModule));
742
+ }
743
+
728
744
  static jsi::Value __hideOffscreenVirtualViewsOnIOS(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* /*args*/, size_t /*count*/) {
729
745
  static_assert(
730
746
  bridging::getParameterCount(&T::hideOffscreenVirtualViewsOnIOS) == 1,
@@ -52,6 +52,7 @@ NS_ASSUME_NONNULL_BEGIN
52
52
  @property (nonatomic, assign) BOOL snapToEnd;
53
53
  @property (nonatomic, copy) NSArray<NSNumber *> *snapToOffsets;
54
54
  @property (nonatomic, assign) BOOL scrollSnapEnabled;
55
+ @property (nonatomic, assign) BOOL scrollAnimationEnabled;
55
56
 
56
57
  /*
57
58
  * Makes `setContentOffset:` method no-op when given `block` is executed.