247 Commits
Author SHA1 Message Date
Peter Abbondanzo 4ffc4a6523 Move sample TurboModule spec to RNTester (#58626)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/58626

Move `NativeSampleTurboModule` into RNTester so React Native core codegen no longer treats the sample as public API. Retarget the sample native implementations to RNTester's generated `AppSpecs` on Android, iOS, and macOS.

Changelog:
[General][Removed] - Remove the sample TurboModule from React Native's public API

Reviewed By: christophpurrer, javache

Differential Revision: D121032097

fbshipit-source-id: 865222ce4d4d4c8da323ae444eb43bec940ef000
2026-09-22 11:15:18 -07:00
Jakub Piasecki 900b717e83 Update branching implementation to default to the JS thread merge (#58615)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/58615

Changelog: [Internal]

The current behavior, merge on the main thread, is kept behind the new `enableFabricCommitBranchingMergeOnMainThread` flag.

Reviewed By: rubennorte

Differential Revision: D120981616

fbshipit-source-id: 47f00ddc150f154481cc908548f39662abf32cbf
2026-09-22 06:30:26 -07:00
Janic Duplessis b439ef7c3f Process synchronous event beats in the frame that requested them (#58530)
Summary:
`EventEmitter::experimental_flushSync` only *requests* an event beat; the beat is processed at the next `EventBeat::induce`. On iOS the run loop observer that induces the beat runs before Core Animation's commit observer, so a request made from `layoutSubviews` — inside CA's commit cycle — is only processed one frame later. Anything that reports layout-driven state to JS synchronously (`VirtualView` mode changes, and safe area insets in the PRs that build on this) renders a frame late in exactly the cases that matter.

`AppleEventBeat` now also schedules an induce in the **display phase of the current commit cycle**. Core Animation runs a commit as layout → display → commit, so a zero-sized layer marked as needing display during layout gets its `display` call after the whole layout pass and before the transaction is committed. That layer needs to live in the tree being committed, so the beat has to know which tree that is — and the emitter tells it:

- `experimental_flushSync` carries the **tag of the emitting view** through `EventDispatcher` and `EventQueue` to `EventBeat::requestSynchronous(Tag)`, with `kNoTag` (https://github.com/react/react-native/issues/58531) meaning no view attribution; a no-argument overload keeps unattributed requesters and the existing tests unchanged. The emitter reads the tag from its `ShadowNodeFamily` at flush time; `kNoTag` if the family is already gone.
- `AppleEventBeat` resolves the tag to the layer of the view's **window** through a resolver injected by `RCTSurfacePresenter` (`findComponentViewWithTag:` on the mounting registry — nullable, non-creating, main thread) and attaches its flusher layer there. The requesting view's window is by definition the root of the layer tree whose layout emitted the request, so the flusher is guaranteed a display phase in the current commit cycle — including for content UIKit mounts in a window of its own, like a full screen modal or LogBox. Requests within one cycle coalesce into a single induce.

One related fix in `EventBeat` itself: a synchronous request is no longer stranded behind an already-scheduled asynchronous beat (it would silently lose its this-frame guarantee, and the leftover flag would make an unrelated later beat blocking). `AppleEventBeat.cpp` becomes `.mm` for the Objective-C.

**Risk:** this changes when queued events are flushed on iOS for every `experimental_flushSync` caller — today `VirtualView`, and safe area insets with the PRs on top. The worst case is a beat processed a frame *earlier* than before, inside a Core Animation commit; the run loop observer path is untouched and still catches anything the display phase misses (an emitter with no tag, an unmounted view, a request off the main thread). Android ignores the tag. Revert is self-contained.

## Design Q&A:

**What happens when two views in different windows update at once?**
Each requesting window gets its own dirty flusher layer (the map is keyed by host layer), and the first `display` to fire induces the beat, which drains the whole event queue — every window's updates mount before that commit presents. The remaining flushers hit the `isEventBeatRequested_` guard and no-op, so it is one beat total, not one per window. If windows ever commit in separate transactions, each request still resolves within its own window's cycle, since its layer sits in the tree that emitted it. Only requesting windows carry a dirty layer.

**Can the tag point at the wrong view — after an unmount, or a recycled view?**
No. The tag comes from the emitter's `ShadowNodeFamily`, and a family keeps one tag for its whole life, across clones and state updates; if the family is already gone the flush carries `kNoTag` and skips the resolver. What changes over a view's life — its window — is read live: the tag resolves to a view at flush time and `view.window.layer` is looked up then, so a view that moved between windows targets its current tree. A view mid-unmount or recycled resolves to nil (the registry erases the entry and recycled views get tag `0`) and degrades to run-loop-observer timing. The tag only ever influences *where the induce is scheduled*, never what is delivered or to whom, so the blast radius of any staleness is one frame of timing, not correctness.

**Does `VirtualView` need changes to benefit?**
No — its sync mode-change flush goes through its own emitter, so the tag attribution is automatic. The case this improves is a mode change emitted during Core Animation layout (a resize pulling a virtualized item into view): on `main` that renders one frame late; here the induce lands in the display phase of the VirtualView's own window, including inside a full screen modal.

## Changelog:

[INTERNAL] - Process synchronous event beats in the frame that requested them on iOS, scheduling the induce on the requesting view's window

Pull Request resolved: https://github.com/react/react-native/pull/58530

Test Plan:
New unit tests in `EventBeatTest.cpp` cover the beat semantics: a synchronous request during an already-scheduled asynchronous beat, coalescing, and induce ordering. They drive the protected `induce` through a subclass standing in for the platform.

On device, with the safe area insets prop from the PRs above merged on top: an RNTester example renders a loud marker (yellow background) while a view observes the safe area but has not received an inset event yet, so any presented marker frame means the dispatch was not synchronous. The full apply → landscape → portrait sequence **inside a full screen modal** on an iPhone 17 Pro simulator, decomposed with ffmpeg into 982 frames and every frame scanned for the marker color — **zero marker frames**, and mid-rotation frames already carry the incoming orientation's insets, so the padding animates with the rotation. Scoped honestly: the first inset event after setting the prop is processed at the call site, so the marker primarily proves no regression; the same-frame path for layout-driven changes rests on the by-construction argument above plus the rotation frames.

https://github.com/user-attachments/assets/0f2db837-c9c0-4457-96c2-847b7aecf10e

`yarn fantom .../ViewSafeAreaInsets-itest.js` passes 4/4 with the prop merged on top. C++ API snapshots regenerated (`scripts/cxx-api/parser`, Doxygen 1.16.1): the deltas are the `requestSynchronous` overload pair, `EventEmitter::getTag`, the resolver type, and the `AppleEventBeat` constructor and destructor.

 ---

**Stack** — split out of https://github.com/react/react-native/issues/57967, which stays open as the prototype and design discussion. GitHub will not take a fork branch as a pull request base, so each of these targets `main` and its diff contains the ones below it until they merge. Each PR is one commit on top of the previous one.

This is the bottom of the stack, so its diff is already just this change.

👉 1. https://github.com/react/react-native/issues/58530 — Process synchronous event beats in the frame that requested them
    2. https://github.com/react/react-native/issues/58109 — Add an `experimental_onSafeAreaInsetsChange` view prop
    3. https://github.com/react/react-native/issues/58110 — Report the window safe area insets through Dimensions
    4. https://github.com/react/react-native/issues/58112 — Render the internal SafeAreaView from the safe area insets prop
    5. https://github.com/react/react-native/issues/58113 — Remove the native SafeAreaView and the deprecated public export

An earlier variant that targeted the surface's root view instead of the view's window was closed in https://github.com/react/react-native/issues/58528; its review thread carries the analysis behind the window-based resolution. https://github.com/react/react-native/issues/58108 was the per-window predecessor this supersedes.

Reviewed By: javache

Differential Revision: D120200496

Pulled By: Abbondanzo

fbshipit-source-id: b06ecb30935837abd6561f54674afef0cdf215a8
2026-09-21 02:10:25 -07:00
Calvin Liu af4d8eb08a Preserve unset accessibilityState.selected in C++ props (#58599)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/58599

`AccessibilityState::selected` was a plain `bool` defaulting to `false`, so the
native side could not tell a component that is selectable but currently
unselected (`accessibilityState={{selected: false}}`) from one that is not
selectable at all (`accessibilityState={{}}`). Both arrived as `false`. The JS
type is already `selected?: ?boolean`, so this is the bridge discarding a value
the public API accepts.

Make `selected` a `std::optional<bool>` defaulting to `std::nullopt`.

  JS accessibilityState      native selected  (before -> after)
  {}                         false -> undefined
  {selected: false}          false -> false
  {selected: true}           true  -> true

This aligns the representation with ARIA, which `accessibilityState` mirrors:
the bool / tri-state split now tracks which ARIA attributes admit an undefined
value.

  field      ARIA value type  admits undefined  representation
  disabled   boolean          no                bool
  busy       boolean          no                bool
  selected   boolean          yes               std::optional<bool>  (changed)
  expanded   boolean          yes               std::optional<bool>
  checked    tristate         yes               CheckedState (None = unset)

That is also why `disabled` and `busy` stay plain `bool`: ARIA gives them no
undefined value, so there is no unset state to preserve. `expanded` was made
optional for this same reason in
https://github.com/facebook/react-native/pull/40881 and `checked` has always
carried a `None`; `selected` was the outlier.

Nor is "unset" merely "absent" for this attribute. `testing-library/dom`
computes it as `boolean | undefined`, documented "false/true if (not)selected,
undefined if not selectable" -- the same shape, with the same meaning, that
this change introduces.

Host platforms need the distinction: on Windows a selectable component must
implement ISelectionItemProvider so UIA can report selection state, and with
the old representation every component carrying an accessibilityState looked
selectable.

iOS and Android rendering is unchanged. Trait derivation coalesces the optional
with `value_or(false)`, and the Android serializer omits the key when the value
is unset, which `BaseViewManager#setViewState` already handles by falling back
to `setSelected(false)`.

Reviewer note: `std::optional<bool>` is contextually convertible to `bool`, so a
bare `if (state.selected)` still compiles but tests engagement rather than
value, silently marking an explicitly unselected component as selected. There is
a regression test for that specific hazard.

Fixes https://github.com/facebook/react-native/issues/46988
Supersedes https://github.com/facebook/react-native/pull/47296, which went stale.

Changelog:
[General][Breaking] - `AccessibilityState::selected` is now `std::optional<bool>` in C++ props, preserving an unset `selected` instead of coercing it to `false`

Reviewed By: javache

Differential Revision: D120049025

fbshipit-source-id: 53ac440976f00daffb3f0599c8841ada2a1452a6
2026-09-18 15:42:35 -07:00
Christoph Purrer 562fe3433a Remove Fabric and TurboModule dead config (#58434)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/58434

Follow-up to D116318829, addressing rubennorte's review comment. Fabric and TurboModules shipped before bridgeless and are always on, so the toggles for them were hardcoded and read nowhere.

Android, `DefaultNewArchitectureEntryPoint` — now only selects the release channel and loads the SO:
- removed `fabricEnabled`, `turboModulesEnabled`, `concurrentReactEnabled`
- removed the deprecated `load(turboModulesEnabled)` and `load(turboModulesEnabled, fabricEnabled)` overloads
- removed `isConfigurationValid`, and with it `DefaultNewArchitectureEntryPointTest` (every test targeted it)
- updated the 8 in-repo call sites that passed `fabricEnabled` into the deprecated 3-arg `DefaultReactActivityDelegate` constructor, which discarded it

iOS:
- removed `fabricEnabled` / `turboModuleEnabled` from `RCTRootViewFactoryConfiguration`
- removed the corresponding `RCTDefaultReactNativeFactoryDelegate` stubs and the `RCTAppDelegate.h` doc references

`ReactAndroid.api` and the `ReactApple*Cxx.api` snapshots are regenerated.

One call site is not updated here: `users/zh/zhaogang/benchmarks/SimpleRN/android/app/src/main/java/com/simplern/MainActivity.kt` still imports `DefaultNewArchitectureEntryPoint.fabricEnabled`. It is a personal benchmark app under `users/` that is not materialized in this working copy, so it could not be edited.

Changelog:
[General][Breaking] - Remove the `fabricEnabled` / `turboModulesEnabled` / `concurrentReactEnabled` accessors and remaining deprecated `load` overloads from `DefaultNewArchitectureEntryPoint`, and the `fabricEnabled` / `turboModuleEnabled` properties from `RCTRootViewFactoryConfiguration`; Fabric and TurboModules are always enabled

https://www.internalfb.com/agent-home?session_id=dmh-2bfbb113-43fc-4bc4-819d-874c5101a1c8

Reviewed By: javache

Differential Revision: D119380472

fbshipit-source-id: 19c130c3dda5c1cd6d541f4e726d8041b515c05c
2026-09-16 10:44:28 -07:00
Conner Reimers 7e02fc7e68 Remove the unused maximumFontSize paragraph attribute (#58529)
Summary:
Fabric's `ParagraphAttributes` carries a `maximumFontSize` field, but it is not exposed by the `<Text>` or `<TextInput>` APIs, so normal JS usage leaves it as `NaN`. On iOS, `RCTTextLayoutManager` falls back to a 96pt maximum when it is `NaN`. On Android, the C++ side serializes it into MapBuffer key 7, but `TextLayoutManager` never reads that key.

This PR removes the attribute: the `ParagraphAttributes` field, its raw-prop parsing in `conversions.h`, the paragraph and text input prop entries, and MapBuffer key 7 on both the C++ and Kotlin sides.

- **iOS**: `RCTTextLayoutManager` now passes the largest font size in the attributed string to `scaleFontSizeToFitSize:` as the maximum instead of 96pt. `scaleFontSizeToFitSize:` returns early when the text already fits; otherwise `scaleFontSizeWithRatio:` applies `MIN(pointSize * ratio, maximumFontSize)` on each pass of a bisection over the ratio. With 96pt, fonts above 96pt were clamped to 96pt on every pass, and when that clamp alone made the text fit, the search went on to ratios above 1.0 and grew the remaining fonts up to 96pt. With the largest font size as the maximum the clamp is a no-op, the ratio-1.0 pass reproduces the original text, which is known not to fit, and every later pass shrinks. Fonts above 96pt are now scaled proportionally rather than capped, and text is never grown. Text at 96pt or smaller is unaffected.
- **Android**: `PA_KEY_MAXIMUM_FONT_SIZE` is removed. Nothing reads it, so there is no behavior change.

Split out of https://github.com/react/react-native/issues/58492. https://github.com/react/react-native/issues/58492 builds on this and reuses the largest-font-size helper for `minimumFontScale`.

## Changelog:

[GENERAL] [REMOVED] - Remove the unused `maximumFontSize` paragraph attribute

Pull Request resolved: https://github.com/react/react-native/pull/58529

Test Plan:
**Unit tests**

- Updated `ParagraphAttributesTest.cpp` for the removed field.
- Regenerated the C++ API snapshots (`yarn cxx-api-build`).

```sh
./gradlew :packages:react-native:ReactAndroid:testDebugUnitTest --tests 'com.facebook.react.views.text.*'
```

All 38 `com.facebook.react.views.text` tests pass.

Reviewed By: cipolleschi

Differential Revision: D120127100

Pulled By: javache

fbshipit-source-id: 44e7de2c21c95500e721d5c6f4b4e207da369713
2026-09-16 05:18:05 -07:00
Janic Duplessis a9473b5ed5 Name the no-tag sentinel and use it for the hardcoded -1 tags (#58531)
Summary:
React tags are positive; `-1` marks the absence of one in several places with a bare literal. This names the convention — `kNoTag` in `ReactPrimitives.h`, next to the `Tag` alias and mirroring `ViewUtil.NO_SURFACE_ID` on the Android side — and replaces the literals across the renderer: `ShadowViewMutation`'s `parentTag` default, factories and comparison, the stub view tree (folding its duplicate `NO_VIEW_TAG` constant into `kNoTag`), the `CppMountItem` mirrors on Android, `UIManagerViewTransitionDelegate`'s `nativeTag` and the view transition fallback that feeds it, and the pointer-events no-override comparisons.

Split out of the safe area insets stack: https://github.com/react/react-native/issues/58530 threads a `Tag` with no-tag semantics through `experimental_flushSync` and wants the named sentinel, but the cleanup stands on its own and can land regardless of that PR's fate.

## Changelog:

[INTERNAL] - Name the no-tag sentinel (`kNoTag`) and use it for the hardcoded `-1` tags

Pull Request resolved: https://github.com/react/react-native/pull/58531

Test Plan: Compiles via the Fantom tester build; `yarn fantom` mounting-heavy suite passes 224/224 (exercises the `StubViewTree` asserts). C++ API snapshots regenerated — the only delta is the new `kNoTag` constant.

Reviewed By: javache

Differential Revision: D120199347

Pulled By: Abbondanzo

fbshipit-source-id: 4198a33fa601edff53c54bce935814b3414c5820
2026-09-16 02:38:27 -07:00
Christoph Purrer 46662327fb Delete legacy RCTNativeAnimatedModule from RN iOS (#57904)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/57904

The Old Architecture has been deleted from React Native iOS, which makes the
legacy `RCTNativeAnimatedModule` unreachable:

- Its JS spec only requests `'NativeAnimatedModule'` when
  `shouldUseTurboAnimatedModule()` is false, but `ReactInstance.cpp` sets
  `RN$Bridgeless = true` unconditionally on iOS, so that branch never runs.
- Its only entry points were `-setBridge:` and `RCTUIManagerObserver`, and
  `RCTBridge` is now an all-nil stub with `RCTCxxBridge` deleted.
- Nothing referenced it by filename except a single `legacy = True` Buck
  plugin provider.

iOS Animated is served by `RCTNativeAnimatedTurboModule` (bridgeless ObjC) and
by the C++ `facebook::react::AnimatedModule` behind `cxxNativeAnimatedEnabled()`.

With the last bridge-dependent Animated consumer gone, also stripped the dead
bridge plumbing it was the only reason for:

- `RCTNativeAnimatedNodesManager -initWithBridge:surfacePresenter:` loses
  `bridge:`.
- `RCTPropsAnimatedNode -connectToView:viewName:bridge:surfacePresenter:` loses
  `bridge:` and the `RCTUIManager` fallback in `-updateView`, which collapses to
  the single `synchronouslyUpdateViewOnUIThread:props:` call.
- `viewName:` goes too, since it was only used to look up a legacy view
  manager. The C++ (`connectAnimatedNodeToView(Tag, Tag)`) and Kotlin
  (`connectAnimatedNodeToView(animatedNodeTag, viewTag)`) nodes managers already
  take two arguments, and the sole remaining ObjC caller passed `nil`.

`RCTNativeAnimatedTurboModule` is deliberately NOT renamed back to
`RCTNativeAnimatedModule` — that is a much wider rename, best done separately.

Changelog: [iOS][BREAKING] Delete legacy RCTNativeAnimatedModule from RN iOS

Reviewed By: zeyap

Differential Revision: D115663619

fbshipit-source-id: d9c703171c2121e8456d923e4db58e624c9040f5
2026-09-10 09:11:53 -07:00
Christoph Purrer 2a9eca81b8 Remove bridgelessEnabled from RCTRootViewFactoryConfiguration (#58416)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/58416

Follow-up to the Android change in the previous diff, applying the same cleanup
to iOS.

Bridgeless is the only supported mode, so the `bridgelessEnabled` surface on
`RCTRootViewFactoryConfiguration` was already deprecated and hardcoded: the
property was assigned `YES` in every initializer, the two deprecated
initializers ignored the argument entirely, and
`RCTDefaultReactNativeFactoryDelegate` returned `YES` unconditionally. Nothing
read the value.

Changes:

- Removed the `bridgelessEnabled` property from
  `RCTRootViewFactoryConfiguration`.
- Removed the two deprecated initializers
  `initWithBundleURLBlock:newArchEnabled:turboModuleEnabled:bridgelessEnabled:`
  and `initWithBundleURL:newArchEnabled:turboModuleEnabled:bridgelessEnabled:`.
  Both were marked `__deprecated` and discarded all arguments except the bundle
  URL, delegating to the designated initializer.
- Removed the `bridgelessEnabled` method from
  `RCTDefaultReactNativeFactoryDelegate`. It was not declared in any header or
  protocol.
- Updated the one caller in `RCTReactNativeFactory` to use
  `initWithBundleURLBlock:newArchEnabled:`.

Behavior is unchanged: bridgeless remains unconditionally enabled. Callers of
the designated `initWithBundleURLBlock:newArchEnabled:` and
`initWithBundleURL:newArchEnabled:` initializers are unaffected.

Changelog:
[iOS][Breaking] - Remove the deprecated `bridgelessEnabled` property and the deprecated `initWithBundleURLBlock:newArchEnabled:turboModuleEnabled:bridgelessEnabled:` / `initWithBundleURL:newArchEnabled:turboModuleEnabled:bridgelessEnabled:` initializers from `RCTRootViewFactoryConfiguration`; bridgeless is always enabled

Reviewed By: rubennorte

Differential Revision: D116318829

fbshipit-source-id: 9a8c232f22a9435934f848a7a80401841c2e29a2
2026-09-10 09:10:22 -07:00
Christoph Purrer 0e01003f53 Remove legacy RCTTimingModule (#57037)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/57037

## Changelog:
[IOS][Fixed] Remove legacy RCTTimingModule

Reviewed By: javache

Differential Revision: D107201906

fbshipit-source-id: 34e006cd046795c80bd234c13979110aadfa9f58
2026-09-10 08:55:38 -07:00
Edward Kimmel 5646c31dff fix: rootMargin ignored when IntersectionObserver root clips (#58177)
Summary:
Hit this using IntersectionObserver on a ScrollView with `rootMargin` to preload content below the fold. `rootBounds` grew by the margin, but `intersectionRatio` stayed 0 for anything past the clip.

We're clipping the target to the root's overflow (the ScrollView / `overflow: hidden` box) *before* intersecting with the margin-expanded rect. That's not what the spec does.

[Compute the intersection](https://w3c.github.io/IntersectionObserver/#calculate-intersection-rect-algo): walk containing blocks *while container is not root* and clip there, then intersect with the [root intersection rectangle](https://w3c.github.io/IntersectionObserver/#root-intersection-rectangle), which already includes `rootMargin`. The spec also calls out that `rootMargin` only applies to the root — clipping by any other ancestor is unchanged.

This stops overflow-clipping the observation root itself. Intermediate clippers stay as they are, so a viewport-rooted observer still can't see past a ScrollView. That's `scrollMargin`, which we don't implement.

--

Lots of words to say: This enables rootMargin to correctly extend the viewport of ScrollViews or any other view with overflow clipping when using that view as the root.  This also gives users a path around missing scrollMargin.

## Changelog:

[GENERAL] [FIXED] - IntersectionObserver rootMargin now affects intersection against a clipping root

Pull Request resolved: https://github.com/react/react-native/pull/58177

Test Plan:
- existing "clipping root with rootMargin" test: 10px margin on a 100x100 overflow:hidden root should intersect 60px of a child sitting at y=50, not 50px
- new ScrollView + `rootMargin: 150px` + `threshold: [0.01]` — target fully below the fold reports intersecting
- new viewport-root control — same layout, still not intersecting (intermediate ScrollView still clips)

Reviewed By: rubennorte

Differential Revision: D117876568

Pulled By: christophpurrer

fbshipit-source-id: 381991048593959748c44e76c17a06fd8b5c0fb3
2026-09-10 08:33:45 -07:00
Peter Abbondanzo 03ed284a2c Add experimental longest-line sizing for wrapped Text (#58351)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/58351

Add `experimental_textWidthMode` as a Text style for sizing wrapped text to its widest rendered line on Android and iOS. The `auto` value preserves the existing constrained measurement behavior, while `longest-line` removes unused horizontal space without changing the line count.

Changelog:
[General][Added] - Add experimental longest-line width sizing for wrapped `Text`

Reviewed By: javache

Differential Revision: D118718410

fbshipit-source-id: aa132bf509a286ab569cc0720867eeedab215bd1
2026-09-09 17:57:02 -07:00
dagur ca7acf62c5 Layout breaks when the first element has a different minWidth than the rest
Summary:
fixes https://github.com/react/yoga/issues/2006

`distributeFreeSpaceFirstPass` decrements `totalFlexGrowFactors` /
`totalFlexShrinkScaledFactors` as it freezes items, but only reduces `remainingFreeSpace`
after the loop. Items after the first frozen one get an inflated fair share and freeze
spuriously. When the first item clamps, the whole line freezes and `remainingFreeSpace`
drains to 0, so the second pass has nothing to distribute and everything falls back to its
flex basis — a 540px row of three `flexGrow: 1` items with `maxWidth: 180` and minWidths
60/30/30 lays out as 60/30/30 instead of 180/180/180.

Snapshot both totals before the loop and divide by the snapshot. The first pass is then
iteration 1 of CSS Flexbox §9.7, and independent of child order.

Layout only changes for configs that opt in: this sits behind
`Errata::FlexFirstPassUsesRunningTotals`, which new configs set by default, so existing
geometry is untouched until a config clears the bit. Same shape as
`MinSizeUndefinedInsteadOfAuto`, which gates the CSS §4.5 auto-min floor.

Changelog:
[General][Fixed] - Fix a flex line collapsing to its minimum sizes when the first item clamps to its min or max main size, behind the new `FlexFirstPassUsesRunningTotals` errata (set by default)

X-link: https://github.com/react/yoga/pull/2021

Reviewed By: javache

Differential Revision: D119142409

Pulled By: pasqualeanatriello

fbshipit-source-id: 9e5ad19e2ca818a5cccdcac68cfb8905a6f40894
2026-09-09 09:10:52 -07:00
ngocdevv 022458fbb6 Distinguish transparent colors from undefined props (#58093)
Summary:
Fixes https://github.com/react/react-native/issues/58085.

Android represents both an undefined color and explicit transparent black as ARGB `0`. `SharedColor` previously compared only that raw value, so Props 2.0 considered an explicitly supplied transparent color equal to an absent prop and omitted it from the mount diff.

This change tracks color presence separately from the platform value and includes it in equality, boolean conversion, and hashing. Platform parsers and the few call sites that intentionally produce an undefined color now preserve that state explicitly.

## Changelog:

[ANDROID] [FIXED] - Preserve explicitly transparent colors during Props 2.0 reconciliation.

Pull Request resolved: https://github.com/react/react-native/pull/58093

Test Plan:
- Added `ColorTest.testTransparentColorIsDistinctFromUndefined`.
- Compiled and ran a standalone regression harness against Android `Color.cpp`; it failed before this change and passes afterward.
- Built `ReactAndroid` CMake Debug for `arm64-v8a` successfully.
- Validated all 9 ReactCommon, ReactAndroid, and ReactApple C++ API snapshots against Doxygen 1.16.1.
- Ran targeted clang-format and `git diff --check`.

Reviewed By: javache

Differential Revision: D117292692

Pulled By: Abbondanzo

fbshipit-source-id: 08120b7eb805dbed06cd76b6bd14fe29a556d88e
2026-09-09 06:38:07 -07:00
Pieter De Baets e8f50cdf28 Tidy the bridgeless runtime executor plumbing (#58325)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/58325

Follow-ups to the buffered CallInvoker, kept out of that diff so the behavioural
change stays reviewable on its own. No behaviour change intended here.

- `BufferedRuntimeExecutor` gains `asRuntimeExecutor()` and
  `asWeakRuntimeExecutor()`. RuntimeExecutor is a std::function, so callers
  cannot hand it the object and instead each hand-rolled the same capturing
  lambda; the ownership question now has one answer per lifetime policy. The
  weak form is what `getBufferedRuntimeExecutor` needs, which must not keep the
  instance alive.
- `BufferedWork` becomes private. It was only ever an implementation detail, and
  being public put its fields in the C++ API snapshot.
- The buffer holds a `std::vector` sorted once at flush rather than a
  `std::priority_queue`. Indices are handed out before the lock, so arrival
  order can differ from submission order and something has to restore it — but a
  heap is the expensive way. `std::priority_queue::top()` returns a const
  reference, so every flushed item copied its `std::function` on the way out;
  that copy is now a move, and nothing sifts on the way in.
- `runtimeExecutorThatGoesThroughRuntimeScheduler` was a second copy of
  `getUnbufferedRuntimeExecutor()`; it now calls it.
- `RuntimeSchedulerCallInvoker` is marked deprecated. Its remaining users are
  migrated: `ReactCxxPlatform` holds a `ReactInstance` and can use
  `createJSCallInvoker()`, and four other files only `#include`d it without ever
  constructing one. The single remaining use is the flag-off branch, which is
  suppressed locally and goes when the flag is cleaned up.
- Drops a null check on `bufferedRuntimeExecutor_` in `callFunctionOnModule`
  that the constructor makes unreachable.

Changelog:
[Internal]

Reviewed By: christophpurrer

Differential Revision: D118616045

fbshipit-source-id: 884179f2c8a1f61185d44216a505fc75e5d103eb
2026-09-08 04:44:31 -07:00
Pieter De Baets 3ca6ea3eca Buffer async CallInvoker work with module calls (#58313)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/58313

In bridgeless, native reaches JS by two routes that end in the same
`RuntimeScheduler` queue but get there differently. `callFunctionOnModule` goes
through the instance's `BufferedRuntimeExecutor`; the `CallInvoker` goes
straight to `scheduleTask`. The CallInvoker therefore skips the buffer entirely
and can reach the runtime while a module call issued earlier is still parked,
unflushed, because the bundle is mid-evaluation. Native code that issues both
cannot rely on the order it issued them in, and `Task` is a min-heap on
`now() + timeout(priority)` with no insertion tiebreak, so equal priorities do
not settle it either.

Gives the two channels the same buffering. `BufferedRuntimeExecutor` gains a
priority-carrying `execute`, so work routed through it keeps the scheduler
priority it was submitted with instead of collapsing to the executor default,
and buffered work from both overloads stays in one submission-ordered stream.
`BufferedCallInvoker` sits on that executor and becomes the bridgeless
`jsCallInvoker` on Android, iOS and macOS.

`invokeSync` deliberately keeps going straight to the scheduler: a synchronous
call cannot wait for a flush that only happens once the bundle has run.

Behind `enableBufferedCallInvoker`, default true. `ReactInstance` picks between
the buffered invoker and the existing `RuntimeSchedulerCallInvoker` in one
place, so the platform call sites are identical either way and the change is
revertible at runtime — it moves when native-issued async work first reaches JS
during startup, which is the intended contract but affects every native module.

One lifetime hazard this surfaces, worth knowing about beyond this diff:
`BufferedRuntimeExecutor` reaches the scheduler through a raw pointer captured
at construction, which is safe only while the owning instance is alive. A
CallInvoker is routinely held across instance teardown, so `BufferedCallInvoker`
guards every async dispatch on a weak reference to the scheduler and drops the
work when it has expired — the same contract `RuntimeSchedulerCallInvoker` has.
Without that guard this reliably segfaults on a reload.

Changelog:
[General][Changed] - Async `CallInvoker` work is now buffered alongside callable module calls, so it no longer runs before the JS bundle has finished evaluating

Reviewed By: rubennorte

Differential Revision: D118456662

fbshipit-source-id: 7c6ccb595d69595721092eeb84b4724809140496
2026-09-04 12:39:12 -07:00
Rubén Norte 1a7c318ffc Make deprecated native module specs Flow strict-local (#57724)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/57724

Upgrade the legacy native module spec files from `flow` to `flow strict-local` to enforce stricter local type checking. Loose `Object` types were replaced with the codegen-equivalent `UnsafeObject`, and `Array<any>` parameters with `Array<unknown>`, both of which are codegen-identical so the generated native interfaces are unchanged.

Changelog: [Internal]

Reviewed By: javache

Differential Revision: D113763785

fbshipit-source-id: 085a0a570246eeda27b3c22a56a2696b04821fd5
2026-09-04 06:40:51 -07:00
nishan (o^▽^o) 63d118af72 Fix per-node memory regression caused by Grid styles (#58311)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/58311

# Why

Currently, grid style properties are stored in yoga style (`gridTemplateRows_`, `gridAutoColumns_` etc). These properties increase the size of style object from `152` bytes to `280` bytes (84% increase). The cost is added even when a node is not a grid container or a grid item.

# How

Move grid style properties behind a pointer that is lazily allocated, on the first grid property set. A node that doesn't use grid only adds the cost of this pointer (8 bytes). So style now costs 160 bytes (5% increase). The public API remains unchanged.

# Tests

A test is added to catch the style size regression and `tests/GridStyleTest.cpp` includes additional cases to assert unset style, copy and move behaviour.

Changelog: [Internal]

X-link: https://github.com/react/yoga/pull/2018

Reviewed By: rubennorte

Differential Revision: D118628661

Pulled By: javache

fbshipit-source-id: 185370e93bcf5b277b48c436ba3d06dada5a66fe
2026-09-04 05:54:27 -07:00
Pieter De Baets bf0e377c0a Reduce JNI allocations when importing native maps (#58274)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/58274

`ReadableNativeMap` materialization copied keys and created temporary JNI references for every imported type. Cache pointers to the stable native values and reuse global `ReadableType` references so importing maps and arrays does less allocation and lookup work.

Writable maps can continue mutating after materialization because `folly::dynamic` stores object entries in reference-stable `F14NodeMap` nodes.

Changelog: [Internal]

Reviewed By: christophpurrer, rubennorte

Differential Revision: D118277119

fbshipit-source-id: 955a60ef7f1eb4cb7549d64caa1238416e90b223
2026-09-04 05:02:48 -07:00
Peter Abbondanzo 8cf8e094f3 Fold optional TextAttributes fields into a presence mask when hashing (#57984)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/57984

`hash_combine` mixes each field into the previous seed, so it forms a dependency chain the CPU
cannot overlap and an unset optional still costs a full link. `hash_combine_optionals` folds a run
of optionals into one presence-mask link plus the engaged values, so a further optional costs a bit
in the mask rather than a link. The mask is what keeps it collision-free: skipping disengaged
fields alone would make the same value in two different slots hash identically.

Equality moves from `std::tie` to a short-circuit chain ordered cheapest first, with the string and
vector fields last, because the dominant caller is a successful cache lookup where the keys are
equal and every field has to be examined.

Changelog: [Internal]

Reviewed By: javache

Differential Revision: D115621401

fbshipit-source-id: f164c24f6b085ef8d32cafbeb2b61636eec3d0cf
2026-09-03 13:54:41 -07:00
Tomasz Żelawski 06eb1fefab fix: prevent programmatic scrolls from cancelling active touches on iOS (#57546)
Summary:
On iOS, a programmatic non-animated scroll — `scrollTo` / `scrollToOffset({ animated: false })`, or any library driving the offset frame-by-frame — cancels every active touch in enclosing scroll views. Two mechanisms combine into this:

1. `scrollToOffset:animated:` calls `_forceDispatchNextScrollEvent` and, for non-animated scrolls, `_handleFinishedScrolling` — so every call emits `onScroll` (twice) plus `onMomentumScrollEnd`, bypassing `scrollEventThrottle` entirely. A per-frame driver produces a continuous stream of unthrottled `topScroll` events (~60/s measured with `scrollEventThrottle={2000}`).
2. In the responder system, any `topScroll` event without `responderIgnoreScroll: true` starts a responder negotiation, and `ScrollView`'s `onScrollShouldSetResponder` answers `true` whenever a finger is down inside it. Each event therefore steals the responder from a pressed `Pressable`/`Touchable` and the press is cancelled — `onPressIn` fires, `onPress` never does.

On Android scroll events carry `responderIgnoreScroll: true`.

I added `responderIgnoreScroll` to the C++ `ScrollEvent` payload and set it to `!_isUserTriggeredScrolling` in `_scrollViewMetrics`. Programmatic scrolls no longer transfer the responder, while user-initiated scrolls (drag, deceleration, scroll-to-top) keep today's behavior.

## Changelog:

[IOS] [FIXED] - Programmatic (non-user-initiated) scrolls no longer cancel active touches in enclosing scroll views

Pull Request resolved: https://github.com/react/react-native/pull/57546

Test Plan:
Reproducible code — an endless marquee `FlatList` nested in a `ScrollView`, driven by `requestAnimationFrame` + `scrollToOffset({ animated: false })`, with a sibling `TouchableOpacity` and a counter proving the touches reach JS:

<details><summary>App.tsx</summary>

```tsx
import { useEffect, useMemo, useRef, useState } from 'react';
import {
  FlatList,
  ScrollView,
  Text,
  TouchableOpacity,
  View,
} from 'react-native';

const dpPerSecond = 20;
const size = 80;
const gap = 8;
const data = Array.from({ length: 6 }).map((_, i) => ({
  id: `id-${i}`,
  n: i + 1,
}));
const renderItem = ({ item }: { item: (typeof data)[0] }) => (
  <View
    style={{
      width: size,
      height: size,
      backgroundColor: 'red',
      justifyContent: 'center',
      alignItems: 'center',
    }}>
    <Text>#{item.n}</Text>
  </View>
);

const Carousel = ({ paused }: { paused: boolean }) => {
  const ref = useRef<FlatList>(null);
  const [width, setWidth] = useState(0);
  const offset = useRef(0);

  useEffect(() => {
    if (paused) return;
    const x = (size + gap) * data.length;
    let last = Date.now();
    let raf: number;
    const loop = () => {
      const now = Date.now();
      offset.current =
        (offset.current + (dpPerSecond * (now - last)) / 1000) % x;
      last = now;
      ref.current?.scrollToOffset({ offset: offset.current, animated: false });
      raf = requestAnimationFrame(loop);
    };
    raf = requestAnimationFrame(loop);
    return () => cancelAnimationFrame(raf);
  }, [paused]);

  const neededToFill = Math.ceil(width / (size + gap));
  const extendedData = useMemo(
    () => [
      ...data,
      ...data.slice(0, neededToFill).map((d) => ({ ...d, id: d.id + '-dup' })),
    ],
    [neededToFill]
  );

  return (
    <FlatList
      scrollEnabled={false}
      scrollEventThrottle={2000}
      showsHorizontalScrollIndicator={false}
      windowSize={3}
      onLayout={(e) => setWidth(e.nativeEvent.layout.width)}
      contentContainerStyle={{ gap }}
      ref={ref}
      horizontal
      data={extendedData}
      keyExtractor={(item) => item.id}
      renderItem={renderItem}
    />
  );
};

export default () => {
  const [paused, setPaused] = useState(true);
  const [touches, setTouches] = useState(0);

  return (
    <View style={{ flex: 1 }} onTouchStart={() => setTouches((t) => t + 1)}>
      <ScrollView contentContainerStyle={{ paddingVertical: 64, gap: 32 }}>
        <Carousel paused={paused} />
        <TouchableOpacity onPress={() => setPaused((p) => !p)}>
          <Text style={{ fontSize: 20 }}>
            Try tapping me {paused ? '▶️' : '⏸️'}
          </Text>
        </TouchableOpacity>
        <Text style={{ fontSize: 16 }}>touches seen by JS: {touches}</Text>
      </ScrollView>
    </View>
  );
};
```

</details>

Before this change, only the first tap works (it starts the marquee); every following tap increments the touch counter but never toggles the button — the press is cancelled by the responder transfer. After this change, every tap toggles the marquee.

Recordings of the repro above (every touch is marked with a blue ring and counted on screen):

Before:

https://github.com/user-attachments/assets/bc5a8764-efe3-40ba-a9cb-a5023d140369

After:

https://github.com/user-attachments/assets/b2c1eea1-dcab-422c-9a4a-90fd69061e9d

Reviewed By: cipolleschi

Differential Revision: D116453881

Pulled By: j-piasecki

fbshipit-source-id: eb72fb0f1a4ac9fdde15f621201009d855d63890
2026-08-20 01:04:12 -07:00
Kamil Paradowski b03652af78 ResizeObserver Web API implementation (#57723)
Summary:
> Stack 3/3 — parent: `feat/LayoutEventEmitter`. Review the two parents first.

Implements the [`ResizeObserver`](https://drafts.csswg.org/resize-observer/) Web API for the New Architecture, behind the `enableResizeObserverByDefault` flag (off by default).

The motivation is Web compatibility, and it's more capable than `onLayout`: callers pick which box to observe (`content-box`, `border-box`, `device-pixel-content-box`) and the sizes for those boxes are delivered in the notification.

The design is as follows: JS `ResizeObserver`/`ResizeObserverEntry`/`ResizeObserverSize` on top of a manager singleton and the `NativeResizeObserver` TurboModule, using the same notify + `takeRecords` pull model. In C++, `ResizeObserverManager` collects observed targets whose layout changed at commit time (via `shadowTreeDidCommit`) and then computes and delivers observations in the event loop's "update the rendering" step (`RuntimeSchedulerResizeObserverDelegate::runResizeObservations`), as the spec requires. Requires bridgeless + the modern event loop; the legacy scheduler gets a no-op delegate.

`runResizeObservations` implements the spec's depth-increasing gather/broadcast loop: each round gathers observations deeper than the shallowest target delivered in the previous round, so a callback that synchronously resizes or observes a shallower node is still delivered within the same tick.

Known deviations from the spec (each pinned by a test):
- Resizes triggered from a callback via a React state update are delivered on the next tick rather than within the current loop, because RN has no synchronous re-layout. Callbacks that resize or `observe()` synchronously do run further rounds in the same tick, and report `ResizeObserver loop completed with undelivered notifications` when an observation is left undelivered.
- The loop carries a hard cap of 100 iterations on top of the spec's depth rule. Reaching the cap reports the loop error and stops, so a pathological callback degrades to a log line instead of a frozen app.
- Re-observing a target with the same box is a no-op and does not re-deliver. This matches browsers, not the literal `observe()` algorithm.
- Callback order across observers follows first-`observe()` order rather than construction order.

## Changelog:

[INTERNAL] [ADDED] - Add the `ResizeObserver` API, behind the `enableResizeObserverByDefault` feature flag

Pull Request resolved: https://github.com/react/react-native/pull/57723

Test Plan:
- Fantom `ResizeObserver-itest.js` covers many test scenarios.
 - rn-tester has `ResizeObserver` examples (box sizes, text, visibility); verified initial, resize, and animation-driven delivery there.
 - `RuntimeSchedulerTest.cpp` new test scenarios for update rendering loop.

Reviewed By: christophpurrer

Differential Revision: D114045415

Pulled By: javache

fbshipit-source-id: a561d5321ba45adb35f46ffb0cf26ad78eb3bae6
2026-08-19 02:42:37 -07:00
Kamil Paradowski 11f9a7f449 RCTArrayBuffer zero-copy class for ObjC TM (#57879)
Summary:
iOS TurboModules mapped a JS `ArrayBuffer` to `NSData` on arguments and `NSMutableData` on returns, so every crossing copied — and `NSMutableData` cannot alias foreign memory, so there was no way to express "these bytes live somewhere else".

This adds `RCTArrayBuffer` (`packages/react-native/React/Base/`) as the ObjC representation of an `ArrayBuffer`. It carries an `isOwningBytes` flag: an owning buffer can be stored and read from any thread, a non-owning one aliases bytes valid only for the synchronous call that produced it.

Codegen now emits `RCTArrayBuffer *` for `ArrayBufferTypeAnnotation` params (was `NSData *`) and returns (was `NSMutableData *`).

## Changelog:

[IOS] [BREAKING] - Add `RCTArrayBuffer`, the ObjC representation of a JS `ArrayBuffer` for TurboModules, with an explicit byte-ownership contract

Pull Request resolved: https://github.com/react/react-native/pull/57879

Test Plan:
- `RCTTurboModuleArrayBufferTests` — 9 tests over the sync in-place path, `isOwningBytes` on a sync argument, returning one's own argument, the void/Promise copy paths, nesting, and a zero-length round trip.
 - `RCTTurboModuleTests.mm` adds `testNativeBackedArrayBufferIsAliasedAndKeepsBackingStoreAlive`.
 -  `RCTSampleTurboModule` doubles its argument in place and returns the same buffer, covering the path end to end.
 - Codegen and C++ API snapshots regenerated.

Reviewed By: cipolleschi

Differential Revision: D115629409

Pulled By: christophpurrer

fbshipit-source-id: a7ee6eadf99fd5f9f82fd2a95b0d3ff1369f4ddf
2026-08-17 17:57:28 -07:00
Christoph Purrer d84c13d511 Enforce the ArrayBuffer borrow contract for Java TurboModules (#57982)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/57982

Changelog: [ANDROID][FIXED]  Enforce the ArrayBuffer borrow contract for Java TurboModules

Follow-up hardening for the Android `ArrayBuffer` TurboModule type. Three problems:

**1. Borrowed JS-heap bytes outlived the call that lent them.** For a synchronous
method, `convertJSIArgsToJNIArgs` hands the module a `ByteBuffer` aliasing the JS
`ArrayBuffer`'s bytes without copying. Nothing stopped a module from stashing that
`ArrayBuffer` in a field and reading it later, after the JS heap may have moved,
freed, or reused the memory — a use-after-free that reads as intermittent data
corruption rather than a crash.

The borrow is now explicitly scoped to the call frame. `JNIArgs` records every
borrowed `ArrayBuffer` and revokes it in its destructor — including when the call
throws — via the new `JArrayBuffer::invalidate`, which drops the C++ side's
reference to the bytes. `ArrayBuffer.bytes` and `ArrayBuffer.size` then throw,
with a message pointing at `ArrayBuffer.arrayBufferWithCopiedBytes`, and
`JArrayBuffer::toJSBuffer` throws rather than aliasing revoked memory. Modules
that need the bytes past the call copy them; modules that don't keep the
zero-copy fast path.

Revocation lives entirely on the C++ side: the peer is the single source of
truth, and Kotlin asks it through `isBytesValid`. The destructor runs while the
stack unwinds, possibly with a Java exception pending, so it resolves each peer
pointer at borrow time — the `global_ref` alongside it keeps the Java object, and
therefore the peer, alive — and calls only the `noexcept`
`JArrayBuffer::invalidate`. No JNI calls are made from the destructor, which is
what lets it stay `noexcept` honestly.

**2. Argument conversion aborted under runtimes that refuse `tryGetMutableBuffer`.**
`jsi::Runtime::tryGetMutableBuffer` and `detached` are not universally
implemented: tracing and replay runtimes throw from `tryGetMutableBuffer`, and
`detached` throws a `JSINativeException` if the JS-side property isn't a bool.
`ArrayBuffer` argument conversion is not wrapped in a try/catch, so either throw
propagated out of a JNI frame. Both calls now go through exception-tolerant
helpers in `react/bridging/ArrayBuffer.h`; a runtime that refuses to answer is
treated as "no native buffer available", which selects the copy path. Routing
`AsyncArrayBuffer::acquire` and `::borrow` through the same helper fixes the
identical latent bug on the shared C++/ObjC path.

**3. A wrong return type from a module crashed instead of raising a JS error.**
The `ArrayBufferKind` return path cast the returned `jobject` to `JArrayBuffer`
unconditionally. A module returning any other object type produced undefined
behavior. The cast is now guarded by an `isInstanceOf` check that throws a
`jsi::JSError` naming the offending module and method.

Also in this change:
- `JByteBufferMutableBuffer::data()` reports null for a zero-capacity direct
  buffer instead of calling `getDirectBytes()`, which throws for one. That made
  `createArrayBuffer` throw for an empty `ArrayBuffer`.
- Dropped two dead zero-size branches in `JArrayBuffer`: `JByteBuffer::wrapBytes`
  already routes `size == 0` to an empty buffer.
- `JArrayBuffer.cpp` reuses the shared `detail::OwnedBytesBuffer` from
  `react/bridging/ArrayBuffer.h` instead of a second local copy.
- `ArrayBuffer.kt` KDoc corrected: the returned JS `ArrayBuffer` is a new object
  over the same bytes rather than the identical one, `size` is the capacity and
  not a view's remaining bytes, and `arrayBufferWithOwnedBytes` documents the
  caller's lifetime obligation.
- `ArrayBuffer.kt` moves from the `bridge` target to `native-types`, alongside the
  other JNI-backed bridge types.

Changelog:
[Android][Breaking] - TurboModule methods taking or returning an `ArrayBuffer`
now use `com.facebook.react.bridge.ArrayBuffer` instead of
`java.nio.ByteBuffer`, and an `ArrayBuffer` argument must not be retained past
the method that receives it unless its bytes are copied with
`ArrayBuffer.arrayBufferWithCopiedBytes()`.

Reviewed By: javache

Differential Revision: D115794808

fbshipit-source-id: 26f5d863469cc14a3f1bffc2cbc3302f3e983ecb
2026-08-17 17:57:28 -07:00
Kamil Paradowski 5bb9639594 JArrayBuffer zero-copy class for Java TM (#57897)
Summary:
Android TurboModules mapped a JS `ArrayBuffer` to `java.nio.ByteBuffer`, copying every argument into a direct buffer — and `ByteBuffer` carries no ownership contract, so there was no way to express aliased or borrowed bytes for synchronous in-place access.

This adds `ArrayBuffer` (`packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ArrayBuffer.kt`) as the Java representation of an `ArrayBuffer`. It carries an `isOwningBytes` flag: an owning buffer can be stored and returned to JS, a non-owning one aliases bytes valid only for the synchronous call that produced it.

Codegen now emits `ArrayBuffer` for `ArrayBufferTypeAnnotation` params (was `ByteBuffer`) and returns (was `ByteBuffer`).

## Changelog:

[ANDROID] [ADDED] - Add `ArrayBuffer`, the Java representation of a JS `ArrayBuffer` for TurboModules, with an explicit byte-ownership contract

Pull Request resolved: https://github.com/react/react-native/pull/57897

Test Plan:
- Codegen Java spec and JNI C++ snapshot tests updated for `ArrayBuffer` param/return signatures.
- `SampleTurboModule` doubles its sync argument in place and returns the same buffer, covering the zero-copy path end to end; `createNativeBuffer` allocates via `ArrayBuffer`.
- C++ API snapshots regenerated.

Reviewed By: javache

Differential Revision: D115755247

Pulled By: christophpurrer

fbshipit-source-id: de067789ad145b7202da721a02f838358c82f4d8
2026-08-17 15:48:23 -07:00
Gijs Weterings c467843ed0 Fix DOMHighResTimeStamp round-trip truncation in timing primitives (#57975)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/57975

`HighResDuration::fromDOMHighResTimeStamp` and `HighResTimeStamp::fromDOMHighResTimeStamp` converted milliseconds back to nanoseconds with `static_cast<int64_t>(units * 1e6)`, which truncates toward zero.

`toDOMHighResTimeStamp()` divides the nanosecond count by 1e6 (one rounding) and multiplying back by 1e6 rounds again, so the product frequently lands a hair below the original integer (e.g. `537648854729249.97`). Truncation then chops off a whole nanosecond, so `fromDOMHighResTimeStamp(toDOMHighResTimeStamp(x)) != x` for roughly 2% of random `now()` values.

That is the source of an intermittent `BridgingTest/highResTimeStampTest` failure, which reported:

```
Expected equality of these values:
  timestamp
    Which is: 8-byte object <22-02 00-21 FD-E8 01-00>
  bridging::fromJs<HighResTimeStamp>( rt, bridging::toJs(rt, timestamp), invoker)
    Which is: 8-byte object <21-02 00-21 FD-E8 01-00>
```

Round to the nearest nanosecond instead of truncating. Nanosecond values below 2^53 are exactly representable in a double, so the round trip is now exact for every value below 2.25e15 ns (26 days of monotonic clock); beyond that the double's ULP exceeds 0.5 ns and residual error is at most 2 ns, which is a floor of the DOM representation itself.

Rounding is also the correct semantic independently of the round trip — truncation gives a systematic downward bias and is asymmetric across zero, which affects the other callers (`RCTHighResTimeStampFromSeconds` for touch timestamps, `RuntimeTargetConsole` `console.timeStamp`, and `PerformanceTracer`).

The rounding is `std::llround`, which costs both overloads their `constexpr`: `<cmath>` rounding functions do not become usable in a constant expression until C++23 (P0533), and this header is compiled as C++20 everywhere (`-std=c++20` in `rn_defs.bzl`, `react-native-flags.cmake`, the podspecs and `Package.swift`). Both carry a `TODO` to restore `constexpr` once the C++23 rollout reaches them.

Dropping it is safe here. All 14 call sites are plain runtime calls — none is in a constant-expression context, none assigns to a `constexpr` variable or feeds a `static_assert` — and both functions are defined inside the class body, so they stay implicitly `inline` and neither linkage nor ABI changes. `fromDOMHighResTimeStamp` converts a `double` arriving from JS, so constant-evaluating it was never meaningful in the first place.

This does narrow the published API surface, so the C++ API snapshots are regenerated: the only change across all nine `.api` files is the `constexpr` keyword dropping off these two declarations, 18 lines in total.

`HighResTimeStamp::fromDOMHighResTimeStamp` now delegates to `HighResDuration`'s so there is a single implementation.

`highResTimeStampTest` previously asserted on `HighResTimeStamp::now()`, whose magnitude is host-uptime-dependent, so it only tripped the bug on about 2% of runs. It now round-trips five fixed nanosecond values (including the exact value from the failing run), which exercises the bug on every run. No test was skipped, disabled, or loosened.

Changelog:
[General][Fixed] - Round instead of truncate when converting a `DOMHighResTimeStamp` back to nanoseconds, so `HighResTimeStamp` and `HighResDuration` round trips are exact

Reviewed By: javache

Differential Revision: D116286868

fbshipit-source-id: 1adafd1b57b1185f51f08fa4dc55d96b88c176cd
2026-08-17 09:21:41 -07:00
Alex Hunt b74adf051f Add idle frame support to Performance timeline (#57952)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/57952

Implement Idle frame spans in React Native DevTools, by emitting synthetic `NeedsBeginFrameChanged` + `BeginFrame` events.

**Definition**

An idle frame is emitted whenever the gap between two consecutive frames exceeds one vsync interval, derived from the display's refresh rate (`CADisplayLink.duration` on iOS, `Display.refreshRate` on Android, falling back to 60 Hz).

```
   frame N                  gap > 1 vsync                 frame N+1
+------------+  +--------------------------------+  +------------+
| BeginFrame |  | NeedsBeginFrameChanged         |  | BeginFrame |
| DrawFrame  |  | BeginFrame     (no DrawFrame)  |  | DrawFrame  |
+------------+  +--------------------------------+  +------------+
                    rendered as an "Idle frame"
```

**Implementation notes**

- Drop frames that begin before the recording start — Android `FrameMetrics` can deliver frames from app startup, and the first iOS `CADisplayLink` callback reports the previous vsync.
- Sort frames by begin timestamp before serializing, since async screenshot encoding can deliver them out of order and break gap detection.
- iOS: skip the frame event when the screenshot is unchanged, letting the gap render as an idle frame.

Changelog: [Internal]

Reviewed By: hoxyq

Differential Revision: D97502569

fbshipit-source-id: 7cb19e0a462ad878f0b44b66f6b5de3f76426ffe
2026-08-14 07:54:22 -07:00
artus9033 7bfe32fd33 Add SceneDelegate support (#57700)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/57700

Add SceneDelegate lifecycle support to React Native iOS while retaining the AppDelegate path for backwards compatibility.

Update linking, window resolution, bootstrap APIs, RNTester, HelloWorld, and generated API snapshots for scene-based applications.

Changelog:
[iOS][Added] - Add SceneDelegate lifecycle support

Test Plan:
- `buck2 build --flagfile fbsource//fbobjc/mode/buck2/linux --config fbobjc.react_native_debug=development --flagfile fbsource//fbobjc/mode/iphonesimulator-arm64 --flagfile fbsource//fbobjc/mode/jest-coverage --config xplat.available_platforms=CXX,APPLE --config cxx.default_platform=iphonesimulator-arm64 --config user.sandcastle_build_mode=profile --config user.platform_flavor=iphonesimulator-arm64 //xplat/js/react-native-github/packages/rn-tester:RNTesterIOS`
- `xcodebuild -quiet -workspace RNTesterPods.xcworkspace -scheme RNTester -configuration Debug -destination "generic/platform=iOS Simulator" CODE_SIGNING_ALLOWED=NO build`
- `arc lint`
- Evaluate RNTester with the `RNTester` SceneDelegate scheme.
- Evaluate RNTester with the `RNTester (AppDelegate)` legacy scheme.
- Verify HelloWorld bootstrap and deep linking.

Reviewed By: cortinico, javache

Differential Revision: D113758228

Pulled By: cipolleschi

fbshipit-source-id: 31174c03a0936ca397fc1d061650fc0f582bdda7
2026-08-11 03:59:23 -07:00
Evan Katz 8415753e21 Add variable font settings support (#57815)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/57815

Apply the existing `fontVariationSettings` text style prop when Fabric
constructs fonts on iOS. Parse CSS-compatible axis settings into CoreText
variation dictionaries while preserving absent, explicit-clear, and invalid
value semantics for nested text.

The parser supports quoted four-character OpenType tags and finite numeric
values, rejects malformed settings as a complete unit, and applies normalized
variations after the base font and feature settings are resolved. This shared
attributed-text path covers Fabric `Text` and `TextInput`.

Changelog:
[iOS][Added] - Add `fontVariationSettings` support for Fabric text

Reviewed By: Abbondanzo, christophpurrer

Differential Revision: D114121940

fbshipit-source-id: d2b2fffd4fe723c5205e5279a466a125aa7edd38
2026-08-09 17:09:10 -07:00
Evan Katz 19f7d144b9 Add Android text font variation settings (#57804)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/57804

Add a `fontVariationSettings` text style prop and carry it through Fabric text attributes into Android text rendering. Android now deserializes the prop for `<Text>`, applies it to `Paint`, and includes it in text measurement cache identity because variable axes can affect layout.

Preserve the distinction between an absent setting and an explicitly empty setting so nested text can inherit or clear the parent variation axes. Apply high-level font properties before low-level variation settings so explicit axes take precedence, matching CSS font realization order.

Settings syntax is intentionally forwarded unchanged through common text attributes and validated only by the Android font variation parser. This avoids narrowing the grammar Android accepts. As a result, malformed child settings are outside the supported inheritance contract: they replace an inherited value before Android validation and are not guaranteed to fall back to the parent settings. Android also accepts `normal` and the React Native empty-string convention as explicit resets.

Changelog:
[Android][Added] - Add `fontVariationSettings` support for `<Text>`

Reviewed By: Abbondanzo, huntie

Differential Revision: D113580491

fbshipit-source-id: 46cea13ef5f06b33ed9f9d0b28b5c16f84193ea6
2026-08-09 17:09:10 -07:00
Pieter De Baets 85a81818c6 Avoid copying surface props when starting or updating a surface (#57813)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/57813

`SurfaceHandler` already takes a throwaway snapshot of its `Parameters` under `parametersMutex_` before handing them to the `UIManager`, but `UIManager::startSurface` and `UIManager::setSurfaceProps` took `moduleName` and `props` by const reference and then copy-captured them into the lambda posted to the `RuntimeExecutor`. That forced a second deep copy of the props tree — which for a real surface holds the initial route params and deep link data — on every surface start, prop update, and display mode change.

Take both by value and move them into the lambda, and move at the `SurfaceHandler` call sites, so the snapshot is handed off instead of duplicated. The snapshot is a local that is dead after the call, so there is nothing left to observe the moved-from state.

Changelog:
[General][Changed] - `UIManager::startSurface` and `UIManager::setSurfaceProps` now take `moduleName` and `props` by value

Reviewed By: zeyap, christophpurrer

Differential Revision: D114730310

fbshipit-source-id: 7891f6ac61abda43ff0da5f4336741af6c329ed2
2026-08-07 06:08:23 -07:00
Tomasz Żelawski e2a4c68449 refactor(iOS): persist bundle as local file on par with Android (#57751)
Summary:
On Android, in development, the JS bundle is downloaded and saved as a file - this is done to avoid passing it through JNI, but iOS could use similar approach for uniform behavior.

This allows libraries to be able to obtain the bundle from the file without re-downloading it from Metro.

It would impact iOS boot slightly, but only in development, so it should be negligible.

## Changelog:

[IOS] [CHANGED] - make iOS preserve dev bundle as a temp file on par with Android implementation

Pull Request resolved: https://github.com/react/react-native/pull/57751

Test Plan: rn-tester iOS dev works with this change

Reviewed By: cortinico, christophpurrer

Differential Revision: D114346244

Pulled By: coado

fbshipit-source-id: ebb4264fe8949cd766e71ca0363aa209a61370f7
2026-08-07 04:36:19 -07:00
Kamil Paradowski 5fb3ebce1a ArrayBuffer support to Java TurboModules
Summary:
Changelog:
[ANDROID] [ADDED] - Add ArrayBuffer support to Java TurboModules

- Codegen: ArrayBufferTypeAnnotation → ByteBuffer / ArrayBufferKind / Ljava/nio/ByteBuffer; in GenerateModuleJavaSpec and GenerateModuleJniCpp. Guard in Utils.js rejects Promise<ArrayBuffer> on Android at codegen time — drops async return to avoid CxxCallbackImpl.kt / JCallback / PromiseImpl.kt changes.

- Runtime: New JByteBufferMutableBuffer.h — jsi::MutableBuffer wrapping global_ref<JByteBuffer> direct ByteBuffer, holds Java object alive for JS ArrayBuffer lifetime; dtor uses ThreadScope to attach JNI thread (Hermes GC finalizes off-thread). Zero-copy native→JS return mirrors iOS NSMutableDataBuffer. Args JS→Java always copied into Java-owned direct ByteBuffer via allocateDirect+memcpy (mirrors iOS NSData copy), no sync/async branching — safe to retain/dispatch past GC.

- No changes to CxxCallbackImpl.kt, JCallback, PromiseImpl.kt. Nested ArrayBuffers still via folly::dynamic, deferred like ObjC/C++.

- Samples: NativeSampleTurboModule.js spec, SampleTurboModule.kt (getArrayBuffer/ createNativeBuffer/ processAsyncBuffer), RCTSampleTurboModule.mm. (NSData param / NSMutableData return), RNTester UI SampleTurboModuleExample.js.

X-link: https://github.com/facebook/react-native/pull/57062

Reviewed By: javache

Differential Revision: D107411163

Pulled By: christophpurrer

fbshipit-source-id: 67cc5da796a87f6918f0382debc5c5594f76661b
2026-08-06 12:28:37 -07:00
Christoph Purrer 3f3425e246 Remove unused NativeModalManager spec and all related code (#57806)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/57806

Changelog: [INTERNAL]

Remove the dead `NativeModalManager` TurboModule spec and all its dependents — 13 files, 192 deletions across JS spec, Modal.js usage, native registration, CXX API snapshots, blocklist entries, and test mocks. The spec was never implemented and the Modal component no longer needs the event subscription pattern since transitioning to the new renderer.

Reviewed By: cortinico

Differential Revision: D114652881

fbshipit-source-id: af939c002d4d58c662bacb840c805d2dd4f42eb4
2026-08-06 10:20:45 -07:00
Hanno J. Gödecke f535c97904 fix(android): props 2.0 image tintColor=transparent broken (#57668)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/57668

When enabling the props 2.0 feature flags I noticed that for an `<Image source={...} style={{ tintColor: 'transparent' }} />` the image is actually still showing, instead of becoming transparent.

### The underlying issue

All color props are defined as `SharedColor`, where a `SharedColor` has a default value of `0`:

https://github.com/facebook/react-native/blob/f3678f51d9873cb19602d7e36a4d8ed71562b9d0/packages/react-native/ReactCommon/react/renderer/graphics/platform/android/react/renderer/graphics/HostPlatformColor.h#L15-L18

https://github.com/facebook/react-native/blob/f3678f51d9873cb19602d7e36a4d8ed71562b9d0/packages/react-native/ReactCommon/react/renderer/graphics/Color.h#L30-L32

> [!NOTE]
> This is a bit confusing to me. `0` is not really an "undefined" color, but its actually "transparent". What we are really saying this way is that all color props have a default value of "transparent". The naming makes me unsure whether this has been intentional.

For other use cases, this seems to make sense. Ie. a user expects their text to have a background color of transparent/"nothing". However, we do not expect our image's tint color to have a default color of "transparent". This would hide all our images.

With props 2.0 we use the `getDiffProp` function, and when we pass `tintColor: 'transparent'` it will be passed to native as `tintColor: 0`. When we then compare the passed prop's value vs the default value here, it will not include the `tintColor`:

https://github.com/facebook/react-native/blob/f3678f51d9873cb19602d7e36a4d8ed71562b9d0/packages/react-native/ReactCommon/react/renderer/components/image/ImageProps.cpp#L249-L251

`tintColor` really is an optional color prop, and should be treated as such. The best fix I found was therefor making it really an `std::optional`. Let me know if you think otherwise!

## Changelog:

<!-- Help reviewers and the release process by writing your own changelog entry.

Pick one each for the category and type tags:

[ANDROID|GENERAL|IOS|INTERNAL] [BREAKING|ADDED|CHANGED|DEPRECATED|REMOVED|FIXED|SECURITY] - Message

For more details, see:
https://reactnative.dev/contributing/changelogs-in-pull-requests
-->

[ANDROID] [CHANGED] - ImageProps make `tintColor` an `std::optional` to support color `transparent` (`0`) with props 2.0

X-link: https://github.com/facebook/react-native/pull/55535

Test Plan:
- In the RNTester app change one of the Tint Color image examples to use tintColor of "transparent"
- The image should be invisible now as all visible pixels turned transparent
- Enable the props 2.0 feature flags
- Run the same example, notice that the tintColor has not been applied

Reviewed By: lenaic

Differential Revision: D93140534

Pulled By: coado

fbshipit-source-id: 0d8abf3583d87ad840b3f72603b4c29c775671ca
2026-07-31 13:49:09 -07:00
Kamil Paradowski 55195ca43f Layout event emitter (#57722)
Summary:
> Stack 2/3 — parent: `feat/shadowTreeDidCommit`. Review that first.

`onLayout` events are emitted inline from `ShadowTree::emitLayoutEvents`, which forces `ShadowTree` to depend on `ViewProps` and `BaseViewEventEmitter`.

This moves that logic into a standalone `LayoutEventEmitter` that consumes the `shadowTreeDidCommit` hook (added in the parent PR) and is registered as a commit hook by `Scheduler`. Same filter (nodes with an `onLayout` prop), same timing (during commit), same `BaseViewEventEmitter::onLayout` call — behavior is unchanged.

After this, `ShadowTree` no longer references view props or event emitters, and the per-commit layout-change signal is shared with `ResizeObserver` instead of being duplicated.

## Changelog:

[INTERNAL] [CHANGED] - Emit `onLayout` from a `LayoutEventEmitter` commit hook instead of inline in `ShadowTree`

Pull Request resolved: https://github.com/react/react-native/pull/57722

Test Plan: Behavior-preserving refactor. Existing `onLayout` tests pass and `ShadowTree` no longer includes `ViewShadowNode`/`ViewProps`. Verified in rn-tester that `onLayout` still fires on mount and on size changes.

Reviewed By: christophpurrer

Differential Revision: D114045065

Pulled By: javache

fbshipit-source-id: 5b887537ad3e947f3a352fb9dd10b7d124f669df
2026-07-30 11:44:31 -07:00
Kamil Paradowski e55da68478 Add new hook shadowTreeDidCommit (#57721)
Summary:
> Stack 1/3 — parent: `main`. Followed by `LayoutEventEmitter` → `ResizeObserver`.

`ShadowTree` already computes the set of nodes whose layout changed on every commit (`affectedLayoutableNodes`), but today it's only consumed internally by `ShadowTree::emitLayoutEvents`.

This adds a `shadowTreeDidCommit(shadowTree, rootShadowNode, affectedLayoutableNodes)` method to `ShadowTreeDelegate` and `UIManagerCommitHook` so other systems can react to layout changes after a commit. `ShadowTree` calls it once the new revision is installed, and `UIManager` forwards it to the registered commit hooks.

Both declarations have no-op defaults, so nothing observes the hook yet — this is inert on its own. It's the shared signal the next two PRs build on (extracting `onLayout` emission, and `ResizeObserver`), so neither has to re-derive which nodes changed layout.

## Changelog:

[INTERNAL] [ADDED] - Add `shadowTreeDidCommit` commit hook exposing the nodes with layout changes after each commit

Pull Request resolved: https://github.com/react/react-native/pull/57721

Test Plan: No behavior change: the hook has no-op defaults and no consumer in this PR. Existing Fabric commit/mounting tests pass.

Reviewed By: mdvacca

Differential Revision: D114044722

Pulled By: javache

fbshipit-source-id: 2a556006f7b66a967aea5559cf10273c61aabcbe
2026-07-30 06:22:41 -07:00
Dawid Małecki 9da1a01153 Wire the Android pull model in C++ behind the feature flag (#57579)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/57579

Makes `enableMountingCoordinatorPullModelAndroid` functional. With the flag on, the commit thread only signals transaction availability and the UI thread pulls and applies at mount time (matching iOS/macOS); with the flag off (default), behavior is byte-for-byte identical.
- `schedulerShouldRenderTransactions`: notifies via JNI (`FabricMountingManager::onTransactionAvailable`) instead of pulling and building the batch.
- `schedulerDidFinishTransaction`: no-op under the pull model.
- `FabricUIManagerBinding::pullAndExecuteTransaction` (new JNI method): pulls the surface's transaction on the UI thread and runs `executeMount`.
- `FabricMountingManager::executeMount`: gains a `synchronous` mode that executes the batch directly on the UI thread.
- The accumulation sites remain gated on `enableAccumulatedUpdatesInRawPropsAndroid`; the pull model requires that flag to be co-enabled, since a pull may collapse several commits into one diff and therefore needs complete accumulated rawProps.

## Changelog:
[Android] [Added] - Wire the pull-model mounting path in C++ behind `enableMountingCoordinatorPullModelAndroid`

Reviewed By: christophpurrer

Differential Revision: D112309053

fbshipit-source-id: 6e3ca5b8fbc8db647356ac555890c2e465890100
2026-07-29 08:01:12 -07:00
Peter Abbondanzo 00e27b7322 PlatformColor lazy fallback: honor a raw-color fallback on Android (native) (#57655)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/57655

An implementation for the RFC in https://github.com/react-native-community/discussions-and-proposals/pull/1008

Wires up the Android native color resolver (Fabric) to honor the lazy `{fallback}` carried by `PlatformColor(...)`. A later diff adds the JS argument that emits it, so on its own this change is a no-op for existing call sites.

To tell a genuine miss apart from a token that resolves to transparent black (ARGB 0):

- `FabricUIManager.getColor` now returns a boxed `Nullable Integer` — `null` means no resource path resolved. The Fabric C++ `PlatformColorParser.h` reads that boxed result over JNI, caches the explicit-miss signal, and on a miss parses the raw fallback string with the shared CSS color parser (`parseCSSProperty<CSSColor>`), matching the iOS Fabric path. The color object is now read as a `map<string, RawValue>` because it mixes an array (`resource_paths`) with an optional string (`fallback`).
- `NativeDrawable` carries an optional `colorFallback` alongside `resource_paths` so ripple drawables degrade to the fallback too.

Generated files (`ReactAndroid.api` and the `ReactAndroid*Cxx.api` snapshots) are regenerated to match the new public signatures.

Changelog:
[Internal] - PlatformColor: Android native support for a lazy raw-color fallback

Reviewed By: mdvacca, javache

Differential Revision: D113329136

fbshipit-source-id: ee15aea8dfed66364e99d427825e1b9ebcbdc483
2026-07-28 13:05:49 -07:00
Devan Buggay 2b992a0d4b Flush created animated nodes before handling events (#57656)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/57656

Changelog: [Internal]

View event handlers can run synchronously before NativeAnimated nodes created on the async path have been flushed into the active graph. Flush pending async-created nodes on the render thread before evaluating view-driven animation events, so focus-driven animations can resolve their value nodes on the first native focus event instead of needing another runloop/event.

Reviewed By: zeyap

Differential Revision: D111257551

fbshipit-source-id: d5b7322cfce90b820ccf06422f1c830255bea3b3
2026-07-24 13:56:48 -07:00
Christian Falch 38611186f5 fix(iOS): make jsinspector-modern tracing state types move-only for Swift C++ interop (#57605)
Summary:
The nightly-tests job `[ios] react-native-unistyles` fails on Xcode 26.3 with:

```
error: no matching function for call to '__construct_at'
note: in instantiation of member function 'std::vector<...RuntimeSamplingProfile>::vector' requested here
note: in implicit copy constructor for 'facebook::react::jsinspector_modern::tracing::TraceRecordingState' first required here
```

while compiling the **Swift** files of the Unistyles pod. The same failure hits any library built with Swift C++ interop (`-cxx-interoperability-mode=default`) — in practice, every Nitro-based library — against the prebuilt React Native core.

### The error

`TraceRecordingState` and `HostTracingProfile` hold `std::vector`s of move-only types (`RuntimeSamplingProfile` and `FrameTimingSequence` explicitly delete their copy constructors). Here's the C++ subtlety: `std::vector<T>`'s copy constructor is **declared for every `T`** — it only becomes ill-formed when *instantiated*. So the implicit copy constructors of these two structs are not implicitly deleted; they exist as declared-but-broken constructors that hard-error the moment anything asks for a copy.

### Why React Native compiles fine today

Nothing in RN ever asks. Every usage passes these types by reference; the single constructions move. A pure C++ (or ObjC++) build never instantiates the implicit copy constructors, so this code has always compiled — and always would, no matter how much C++ CI you throw at it. The defect is unobservable from within C++.

### What fails, and why now

The prebuilt-core headers now ship as real clang modules. A Swift target with C++ interop imports them (directly or transitively — e.g. via a module member whose `#ifdef __cplusplus` body opens because interop builds modules with C++ enabled), and Swift's ClangImporter surfaces the C++ value types to Swift as copyable. When the consumer's generated interop code then uses such a type as a Swift value — for a Nitro-based library, the nitrogen-generated `*_cxx.swift` bridging does exactly this — the compiler **synthesizes a copy of the type, instantiating the ill-formed implicit copy constructor**. That is the "ask" that plain C++ never makes; on Xcode 26.3 it hard-errors the entire module import, killing every Swift file in the consumer. (Newer Swift toolchains treat such types as non-copyable instead of failing.)

Before the prebuilt-modules work there was no Swift-visible module containing these headers, so no interop consumer ever imported these types — which is why this surfaces now despite the C++ being unchanged.

We deliberately did **not** fix this by removing headers from the module maps: the guarded-C++-in-modules pattern is shared by ~30 legitimately modular headers and is benign in all but this one shape, and experiments showed the type is reachable through multiple independent module surfaces (removing one member just moved the error to the next path).

### The fix

Declare the truth: make both types explicitly move-only.

```cpp
TraceRecordingState(const TraceRecordingState &) = delete;
TraceRecordingState &operator=(const TraceRecordingState &) = delete;
TraceRecordingState(TraceRecordingState &&) = default;
TraceRecordingState &operator=(TraceRecordingState &&) = default;
```

With the copy constructor explicitly deleted, Swift's importer sees a non-copyable type and imports it as such instead of instantiating a broken copy. It is also simply more correct C++: these types were never copyable in practice, and the explicit deletion turns any future accidental copy into a clear compile error at the call site instead of a template backtrace.

Declaring special members makes `HostTracingProfile` a non-aggregate, so its one designated-initializer construction site (`HostTargetTraceRecording.cpp`) is converted to member-wise assignment.

A sweep of the affected header surface (`std::vector`/`std::map`/`std::deque` of move-only element types) found exactly these two types; a follow-up adds a `headers-verify.js` gate that imports the shipped modules under Swift C++ interop at prebuild time, so the next type with this shape fails RN's own CI instead of community nightlies.

## Changelog:

[IOS] [FIXED] - Fix Swift C++-interop build failure (implicit copy constructor of TraceRecordingState/HostTracingProfile) for libraries using cxx interop with prebuilt React Native core

Pull Request resolved: https://github.com/react/react-native/pull/57605

Test Plan:
On a fresh RN-nightly app with stock `react-native-unistyles@3.3.0` + `react-native-nitro-modules`, prebuilt core (`RCT_USE_RN_DEP=1 RCT_USE_PREBUILT_RNCORE=1`), Xcode 26.3:

- **Red**: stock headers reproduce the CI failure exactly (`__construct_at` → `TraceRecordingState`). Fixing only `TraceRecordingState` then surfaces the identical failure on `HostTracingProfile` — confirming the shape, not the type, is the bug.
- **Green**: with both headers fixed (stock module maps, nothing else changed): BUILD SUCCEEDED — zero `__construct_at`, zero `shadowNodeFromValue`, zero module errors.
- All RN-internal usages audited: references and moves only; no behavior change. Plain C++/ObjC++ compilation unaffected by construction.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed By: fabriziocucci

Differential Revision: D112802806

Pulled By: cipolleschi

fbshipit-source-id: d2ae201c1fbb4de040f51ebc88263c7347b5979b
2026-07-21 03:45:29 -07:00
artus9033 566c7ac2d1 fix: stabilize CXX API generator on MacOS, make parser indempotent for nested enums (#57536)
Summary:
Fixes C++ API snapshot generation failures for nested enums and macOS/Linux output drift due to platform-dependent behavior, in the CXX API generator.

### Problems

1. Duplicate enums: Doxygen parses codegen `EventEmitters.h` twice (direct codegen input + again via `.mm` includes). The parser raised `RuntimeError: Identifier OnOrientationChangeOrientation already exists in scope ModalHostViewEventEmitter` on `ReactApple*` views.

2. Inconsistent behaviour on MacOS vs Linux:
  - for codegen component aliases (`ConcreteComponentDescriptor`, `ConcreteViewShadowNode`), macOS Doxygen emits hybrid XML definitions (`typedef Type Name = Type`) while Linux CI emits `using Name = Type`. The parser keyed off `definition.startswith("typedef")`, so identical source produced different `.api` output per platform.
  - `CASE_SENSE_NAMES = SYSTEM` follows the host OS default (case-insensitive on macOS, case-sensitive on Linux), causing inconsistent symbol resolution.

### Resolution

- `.doxygen.config.template` files: set `CASE_SENSE_NAMES = YES` for deterministic name matching across macOS and Linux.
- `snapshot.py`: `create_enum()` returns an existing enum scope instead of raising when the enum is already registered.
- `builders.py`: `create_enum_scope()` to skip enums that already exist; `get_typedef_member()` to normalize Doxygen’s `typedef ... = ...` form to `using` so the output format is unified.

## Changelog:

[INTERNAL] [FIXED] - Fix C++ API snapshot generation crash on duplicate codegen enums
[INTERNAL] [FIXED] - Fix platform-dependent inconsistent CXX API generator output

Pull Request resolved: https://github.com/react/react-native/pull/57536

Test Plan:
- [x] `yarn cxx-api-build` completes on macOS (tested locally)
- [x] `yarn cxx-api-validate` passes (tested locally on MacOS & on the Linux CI)
- [x] `validate-cxx-api-snapshots` passes on Linux (tested on CI)

Reviewed By: j-piasecki

Differential Revision: D112793182

Pulled By: coado

fbshipit-source-id: ada818451fb1207d965c1bed4fb5ce23ee2fe00f
2026-07-20 05:01:24 -07:00
Alex Hunt 9f7af3e088 Clean up network inspection feature flags (#57577)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/57577

Network Inspection has been Stage 4 / rolled out since 0.83. Circle back to remove feature flags from the codebase.

**Changes**

- Remove `enableNetworkEventReporting` and `fuseboxNetworkInspectionEnabled` flag definitions.
- Remove all feature guards in code.
- Update tests (add mocks).
- (fbsource) Remove all overrides.

Changelog: [Internal]

Reviewed By: cortinico

Differential Revision: D111696522

fbshipit-source-id: 4c6ba9a6d5abf2770b9b485556602059d1c24b59
2026-07-16 10:17:17 -07:00
Alex Hunt cfd0e359a6 Add CDP support for WebSocket events (Android) (#57541)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/57541

**Context**

This stack implements WebSocket event debugging for the Network panel in React Native DevTools.

**This diff**

Follows the iOS implementation: reports the same six `Network.webSocket*` CDP events through the shared `NetworkReporter` C++ core, gated behind the same `fuseboxWebSocketEventsEnabled` feature flag.

**Changes**

- `InspectorNetworkReporter` + `JInspectorNetworkReporter`: New JNI-backed `reportWebSocket*` methods bridging to the shared `NetworkReporter` C++ core. Payload conversion costs (base64) are deferred until a debugger is attached.
- `WebSocketModule`: Reports connection lifecycle, real handshake headers (from the OkHttp `Request`/`Response`), and sent/received messages, gated behind the same feature flags as iOS.

Changelog: [Internal]

Reviewed By: GijsWeterings

Differential Revision: D111561994

fbshipit-source-id: 735ad0e47fc4e9e943b1092fbc23846ff9baf963
2026-07-15 03:50:39 -07:00
Alex Hunt 020512bf15 Add CDP support for WebSocket events (iOS) (#57543)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/57543

**Context**

This stack implements WebSocket event debugging for the Network panel in React Native DevTools.

Discussed with Expo earlier this year, we want upstream parity before Expo adopts the 1P Network panel. This is a comparatively small surface: 6 CDP events, no CDP methods.

**Minimum goal**: Parity with Expo's current WS event coverage. We improve on this with Request Initiator support in D111561995.

**This diff**

Adds first-party reporting for six of the seven WebSocket CDP events (`webSocketCreated`, `webSocketWillSendHandshakeRequest`, `webSocketHandshakeResponseReceived`, `webSocketFrameSent`, `webSocketFrameReceived`, `webSocketClosed`), mirroring the layering of the existing HTTP pipeline:

- **C++ core**: WebSocket CDP types and reporting APIs through `CdpNetwork` → `NetworkHandler` → `NetworkReporter`. Compiled to no-ops in production builds; inert unless the Network domain is enabled.
- **iOS**: A new `RCTInspectorWebSocketReporter` bridges `RCTWebSocketModule` to `NetworkReporter`, reporting connection lifecycle, real handshake headers, and sent/received messages. Android follows separately.
- **Gating**: A new `fuseboxWebSocketEventsEnabled` feature flag (experimentation, default off), checked alongside `enableNetworkEventReporting`.

**Notes**

- **Omitted**: [`Network.webSocketFrameError`](https://chromedevtools.github.io/devtools-protocol/tot/Network/#event-webSocketFrameError). Neither SocketRocket nor OkHttp exposes a frame-scoped error; connection failures are terminal and already reported via `webSocketClosed`. Cheap to bolt on once a genuine per-message error source exists (e.g. OkHttp `send()` returning false).
- **Omitted**: Optional [`WebSocketResponse`](https://chromedevtools.github.io/devtools-protocol/tot/Network/#type-WebSocketResponse) fields (`headersText`, `requestHeaders`, `requestHeadersText`) — we report structured headers only; raw handshake text isn't available from OkHttp on Android.
- No `performance-timeline` integration — WebSocket events report to CDP only; unlike HTTP, there is no `PerformanceResourceTiming` counterpart.

**Rollout plan** (New `enableNetworkEventReporting` flag)

- 0.88 - Canary channel
- 0.89 - Stable channel

Changelog: [Internal]

Reviewed By: GijsWeterings, javache

Differential Revision: D111561998

fbshipit-source-id: ecd46ddb6baa91f1c1d77598ee62b2e410b45bd9
2026-07-15 03:50:39 -07:00
Pieter De Baets 07816cba56 Move ErrorUtils from cxxreact to jserrorhandler (#57236)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/57236

Move `handleJSError` from `cxxreact/ErrorUtils.h` (header-inline) to `jserrorhandler/ErrorUtils.{h,cpp}` (declared + linked). Inverts the cyclic dep so `jserrorhandler` becomes a standalone leaf and `cxxreact:bridge` can be narrowed out of more consumers in follow-ups.

The old `<cxxreact/ErrorUtils.h>` include path continues to work via a deprecated `#warning` forwarder header that includes the new location. `cxxreact:bridge` now depends on `jserrorhandler:jserrorhandler` so existing consumers of the deprecated path still link cleanly. `jserrorhandler/BUCK` drops its `cxxreact:bridge` dep (and the matching `React-cxxreact` podspec entry).

Changelog:
[Internal]

Reviewed By: christophpurrer

Differential Revision: D108786498

fbshipit-source-id: 6a34ec47665558ac9c91343253e027ed1df70e93
2026-07-06 07:43:37 -07:00
Pieter De Baets c1d8ae970d Route enableCppPropsIteratorSetter through a copy ctor + RawProps::forEachItem (#57328)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/57328

Today the iterator-setter path in `ConcreteComponentDescriptor::cloneProps` runs three sequential walks over the input — `RawProps::parse(parser)` (builds `keyIndexToValueIndex_` for `convertRawProp`), `static_cast<folly::dynamic>(rawProps)` (materializes a `folly::dynamic` via `jsi::dynamicFromValue` in JSI mode), and then `dynamic.items()` to dispatch `setProp`. Only the third is actually used: `convertRawProp` is never called on the iterator-setter branch, and the `folly::dynamic` materialization exists only as iteration scaffolding.

Restructure so the runtime flag picks one of two construction paths up front:

- **Iterator-setter** — copy-construct from `sourceProps` via the (re-enabled) `Props` copy ctor, then walk `rawProps` in-place via the new `RawProps::forEachItem` helper and route each entry through `setProp`. `parse()` is skipped entirely; the `folly::dynamic` materialization is skipped in `Mode::JSI`.
- **Classic** — unchanged: `parse()` + 3-arg `convertRawProp`-driven ctor.

`forEachItem` switches on `RawProps::Mode`:
- `Mode::JSI` — walks `value_.asObject(*runtime_).getPropertyNames(...)` and constructs `RawValue` from each `jsi::Value` directly, no `folly::dynamic` in between.
- `Mode::Dynamic` — iterates `dynamic_.items()` (same as today).
- `Mode::Empty` — no-op.

A new `HasIteratorSetterCtor<T>` concept (`std::copy_constructible<T>`) documents the contract and feeds a `static_assert` in `cloneProps`, so a future Props type that deletes its copy ctor fails at compile time rather than silently diverging at runtime between the two flag states.

The `RN_SERIALIZABLE_STATE` Props 2.0 accumulation branch keeps its existing dynamic-iteration shape — when `fallbackToDynamicRawPropsAccumulation` is true, `initializeDynamicProps` has already merged the source's rawProps with the input onto `shadowNodeProps->rawProps`, so we iterate that merged dynamic rather than the raw input.

The per-field `flag ? sourceProps.X : convertRawProp(...)` ternaries across every Props .cpp file become dead in the flag-on path (the copy ctor handles those fields) but are still functional in the flag-off path. They get removed in a follow-up cleanup; this diff is structurally non-breaking on either flag state.

Changelog:
[Internal]

Reviewed By: zeyap

Differential Revision: D109568749

fbshipit-source-id: eae20478418a7dbf7364c73d85d7694d99f1e8f1
2026-07-06 06:52:39 -07:00
Pieter De Baets 803456d699 Optimize MapBuffer representation (#57361)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/57361

Broadens the former header-shrink change into a single representation-optimization commit for MapBuffer. It bundles three layout optimizations that were previously split: (1) the header is reduced to a single 2-byte `count` field; (2) multi-byte values are read via `memcpy` so unaligned access is well-defined on all platforms; (3) every dynamic-data entry (`String`, `Map`, `MapBufferList`, `IntBuffer`, `DoubleBuffer`) packs its `[offset][byteLength]` into the bucket's 8-byte value instead of writing an in-band length prefix into the dynamic data section.

Net effect: 4 fewer bytes per dynamic entry, one fewer indirection on read (the length is already in the bucket), and every dynamic entry becomes self-delimiting from its bucket alone. No public API change — only the internal serialized representation.

Changelog: [Internal]

landed-with-radar-review

Reviewed By: lenaic, zeyap

Differential Revision: D109848478

fbshipit-source-id: 30749aaa1c2d9fc9b1f8684a3b67815ef44632e8
2026-07-03 01:41:41 -07:00
Pieter De Baets 6a957746f6 Add MapBufferList entry type to MapBuffer (#57360)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/57360

Introduces a dedicated `MapBufferList` `DataType` for an ordered array of nested MapBuffers, instead of overloading the `Map` type for lists. This makes a list of MapBuffers self-describing and distinguishable from a single nested `Map` (they were byte-distinct in payload but previously shared the `Map` type tag). Updates the C++ builder (`putMapBufferList`), the Kotlin `MapBuffer` interface, `ReadableMapBuffer`, and `WritableMapBuffer`, and adds cross-language JNI round-trip coverage in the serialization instrumentation test.

Changelog:
[Android][Added] - Add a dedicated `MapBufferList` type to `MapBuffer` for ordered lists of nested `MapBuffer`s

landed-with-radar-review

Reviewed By: zeyap

Differential Revision: D109848477

fbshipit-source-id: 7f590d5999d0cc4ee2d9d28cc34ae9220e442e18
2026-07-03 01:41:41 -07:00
Pieter De Baets e7cadaf9f3 Add IntBuffer and DoubleBuffer entry types to MapBuffer (#57359)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/57359

Adds two new MapBuffer entry types, `IntBuffer` and `DoubleBuffer`, for storing homogeneous arrays of ints and doubles compactly in the dynamic data section. Unlike `Map` / map lists, these carry no per-element key/type overhead: a batch of N values costs ~N*elementSize bytes plus a single 4-byte count prefix instead of N 12-byte buckets. The bucket value holds the offset of the array within the dynamic data section.

Covers the full surface: the C++ reader (`MapBuffer::getIntBuffer` / `getDoubleBuffer`), the C++ builder (`MapBufferBuilder::putIntBuffer` / `putDoubleBuffer`), and the Kotlin reader API (`MapBuffer.getIntBuffer` / `getDoubleBuffer`, `Entry.intBufferValue` / `doubleBufferValue`). The `DataType` enum gains `IntBuffer = 6` and `DoubleBuffer = 7`, kept in sync across C++ and Kotlin.

Changelog:
[General][Added] - Add `IntBuffer` and `DoubleBuffer` entry types to MapBuffer for compact homogeneous int/double arrays

landed-with-radar-review

Reviewed By: zeyap

Differential Revision: D109848476

fbshipit-source-id: f9e86b7c094dea796d9a8b725e53eb948c1390ca
2026-07-03 01:41:41 -07:00