mirror of
https://github.com/react/react-native.git
synced 2026-09-28 13:23:09 +08:00
main
247
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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
|
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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
|
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |