update expo dependencies
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
# ***This second part of this patch is load bearing, do not remove.***
|
||||
|
||||
## Scheduler delegate invalidation - iOS use-after-free
|
||||
|
||||
Fixes Sentry issue APP-T28X: an `EXC_BAD_ACCESS` in
|
||||
`Scheduler::uiManagerDidFinishTransaction` or
|
||||
`Scheduler::uiManagerDidDispatchCommand` after a queued rendering update
|
||||
outlives its captured raw `SchedulerDelegate` pointer.
|
||||
|
||||
React Native 0.86 contains the invalidation-token guard from
|
||||
facebook/react-native#56680, but `enableSchedulerDelegateInvalidation` is false
|
||||
for the stable release level used by Expo. Override only this flag in
|
||||
`ReactNativeFeatureFlagsOverridesOSSStable` instead of opting the app into all
|
||||
experimental React Native flags.
|
||||
|
||||
**TODO: Remove after upgrading to a React Native release that closes the
|
||||
queued Scheduler delegate lifetime race by default.**
|
||||
|
||||
## UIManager.cpp Patch - Fabric focus navigation use-after-free
|
||||
|
||||
Fixes Sentry issue APP-T4H9: a SIGSEGV in
|
||||
`FabricUIManagerBinding::findNextFocusableElement` during focus navigation.
|
||||
|
||||
React Native 0.86 contains the safe implementation behind
|
||||
`fixFindShadowNodeByTagRaceCondition`, but the public default is false. The
|
||||
fallback captures a raw root shadow-node pointer in `tryCommit` and dereferences
|
||||
it after the lock is released, allowing a concurrent commit or surface stop to
|
||||
free the node first. This backports the final upstream implementation, which
|
||||
holds the current revision's `shared_ptr` for the entire traversal.
|
||||
|
||||
**TODO: Remove after bumping React Native to a release containing
|
||||
facebook/react-native#56850.**
|
||||
|
||||
## RefreshControl Patch - iOS 17.4 Haptic Regression
|
||||
|
||||
Patching `RCTRefreshControl.mm` temporarily to play an impact haptic on refresh when using iOS 17.4 or higher. Since
|
||||
17.4, there has been a regression somewhere causing haptics to not play on iOS on refresh. Should monitor for an update
|
||||
in the RN repo: https://github.com/facebook/react-native/issues/43388
|
||||
|
||||
## RCTPullToRefreshViewComponentView.mm Patch - iOS 17.4+ haptic regression and iOS 26 progressViewOffset cancellation on New Arch
|
||||
|
||||
Both bugs share one root cause, established by instrumented frame-logging runs on the iOS 26
|
||||
simulator (Aug 2026): **writes to a detached `UIRefreshControl` are hazardous, because the
|
||||
control's `_UIRefreshControlModernContentView` bakes in the state it observes at its own
|
||||
creation.** Facts proven by the logs:
|
||||
|
||||
- `scrollView.refreshControl` assignment inserts the control and creates its content view
|
||||
**synchronously** on iOS 26 (the "UIKit inserts lazily on a later layout pass" folklore is
|
||||
false there).
|
||||
- The content view can also be materialized **earlier** by a pre-attach property write (observed
|
||||
with `tintColor`) while the control is still detached.
|
||||
- The content view positions itself at whatever `bounds.origin` exists at its creation and keeps
|
||||
that y forever - width tracks on later layouts, y never re-pins.
|
||||
|
||||
Consequences:
|
||||
|
||||
**1. progressViewOffset.** Stock Fabric writes the offset as a `bounds.origin` shift in
|
||||
`updateProps`, pre-attach. The content view is then created (at insertion) already inside the
|
||||
shifted bounds, pins to it, and cancels the shift exactly - spinner hidden behind the floating
|
||||
home header (home is the only screen passing a non-zero offset). Stock RN appeared to work only
|
||||
by accident: its own pre-attach `tintColor` write materialized the content view at origin 0
|
||||
*before* the offset write. Possibly related upstream: react-native#54183.
|
||||
|
||||
**2. Haptic (react-native#43388).** The Paper fix above does not cover Fabric: `updateProps`
|
||||
writes `tintColor` pre-attach, and a tint write on a detached control materializes the content
|
||||
view outside the scroll view, permanently suppressing the trigger haptic on iOS 17.4+ (the
|
||||
creation-time-state story likely explains this too, though the haptic wiring itself is not
|
||||
observable in logs).
|
||||
|
||||
**The fix**: both `tintColor` and `progressViewOffset` are parked in the component view
|
||||
(`_pendingTintColor` / `_pendingProgressViewOffset`, no `UIRefreshControl` subclass) and applied
|
||||
only once `_refreshControl.superview` is the scroll view - by then the content view exists,
|
||||
was created at origin 0, and a bounds shift lands visibly. Application points: immediately in
|
||||
`_updateX` for runtime changes while attached; in `_attach` right after the assignment (insertion
|
||||
is synchronous); and from `layoutSubviews` with a `setNeedsLayout` re-arm as a fallback should
|
||||
insertion ever be deferred.
|
||||
|
||||
Supporting changes:
|
||||
|
||||
- `shouldBeRecycled = NO`: recycled instances get all props force-applied in `updateProps` before
|
||||
the new control is attached, which would re-trigger the pre-attach hazards; opting out keeps
|
||||
every mount on the untouched-before-attach path.
|
||||
- `_updateTitle` no longer writes `attributedTitle = nil` when there is nothing to clear - even a
|
||||
nil write before attach suppresses the haptic.
|
||||
|
||||
History: an earlier iteration fixed the offset by porting Paper's frame-offset trick into an
|
||||
`RCTHapticCompatibleRefreshControl` subclass (worked, verified on device) - replaced by the
|
||||
deferral once the root cause was understood. The control's `didMoveToSuperview` appeared broken
|
||||
as a tint application point in early non-rigorous testing; unproven, not disproven.
|
||||
|
||||
Upstream issue #43388 still open as of Aug 2026. Haptics cannot be verified on the simulator -
|
||||
physical device only. Spinner position verified via frame logs; haptic on this variant NOT yet
|
||||
device-verified.
|
||||
|
||||
Opened issue in RN repo: https://github.com/react/react-native/issues/57843
|
||||
|
||||
## RCTEnhancedScrollView.mm / RCTScrollViewComponentView.mm Patch - centerContent insets stale after content resize on New Arch
|
||||
|
||||
**TODO: Remove after bumping React Native to 0.87+** (fixed upstream by facebook/react-native#56832,
|
||||
commit d50c1b5207; first shipped in 0.87.0-rc.0).
|
||||
|
||||
On Fabric, `centerContent` centers by computing `contentInset` in `centerContentIfNeeded`, but that
|
||||
recompute only ran on `setFrame`/`didAddSubview`/`scrollViewDidZoom` - not when a state update assigns a
|
||||
new `contentSize` in `updateState`. Any content that resizes after mount inside a `centerContent`
|
||||
ScrollView (e.g. the lightbox image crop view getting its real aspect ratio from `onLoad` when the embed
|
||||
has no aspectRatio metadata) keeps the old insets: content rests off-center and the excess inset creates
|
||||
phantom scroll range, so the image can be dragged and parked off-center and the native scroll steals the
|
||||
swipe-down-to-dismiss pan. The old architecture paired every `contentSize` update with re-centering in
|
||||
`RCTScrollView.updateContentSizeIfNeeded`; Fabric dropped that link.
|
||||
|
||||
Backport of the upstream fix: `setContentSize:`/`setCenterContent:` overrides on `RCTEnhancedScrollView`
|
||||
that call `centerContentIfNeeded`, plus the `updateProps` guards so the `contentInset` prop does not
|
||||
fight the computed centering inset.
|
||||
|
||||
Issue: https://github.com/facebook/react-native/issues/55090
|
||||
|
||||
## RCTScrollViewComponentView.mm Patch - ScrollView pinch/pan ignored outside content area on New Arch
|
||||
|
||||
**TODO: Remove after bumping React Native to 0.87+** (fixed upstream by facebook/react-native#56747,
|
||||
commit efcab20908; first shipped in 0.87.0-rc.0).
|
||||
|
||||
On Fabric, `betterHitTest` in `RCTScrollViewComponentView` deliberately skips the `_containerView`
|
||||
and hit-tests its grandchildren, returning `self` (the wrapper component view) when the touch lands
|
||||
inside the scroll view bounds but outside any content. UIKit only delivers touches to a gesture
|
||||
recognizer when the hit view is the recognizer's view or a descendant of it, and the `UIScrollView`
|
||||
is a *child* of the wrapper - so its native pinch/pan recognizers never see those touches. In the
|
||||
lightbox this means pinch-to-zoom and pan-while-zoomed only respond when the fingers are over the
|
||||
image itself, not over the letterbox bars above/below it. On the old architecture, default UIKit
|
||||
hit-testing returns the `UIScrollView` itself for those touches, so everything works.
|
||||
|
||||
Backport of the upstream one-liner: return `_scrollView` instead of `self` so touches in the
|
||||
content-less area are attributed to the `UIScrollView`.
|
||||
|
||||
Issue: https://github.com/facebook/react-native/issues/54123
|
||||
PR: https://github.com/react/react-native/pull/56747
|
||||
|
||||
## ReactViewGroup.kt Patch - Fatal "Required value was null" during subview clipping on Android
|
||||
|
||||
Fixes Sentry issue APP-T20Q: `IllegalStateException: Required value was null` thrown by
|
||||
`checkNotNull(allChildren?.get(idx))` in `updateSubviewClipStatus`, reached from
|
||||
`ReactScrollView.onScrollChanged -> updateClippingRect` during an animated smooth scroll
|
||||
(New Architecture, `removeClippedSubviews`).
|
||||
|
||||
The clipping loop in `updateClippingToRect` captures its bound once, but clipping a view
|
||||
(`removeViewsInLayout`) can synchronously trigger reentrant child removal (layout-change
|
||||
listeners, animation-end callbacks, Fabric mounting on the UI thread), which compacts
|
||||
`allChildren` and nulls the tail mid-loop. Upstream already catches the
|
||||
`IndexOutOfBoundsException` variant of this corruption with diagnostics, but the null-child
|
||||
variant throws `IllegalStateException` and escapes as a fatal crash. A null entry means the
|
||||
view is already detached, so we skip it and count it as clipped to keep index math aligned.
|
||||
|
||||
Not fixed upstream as of July 2026 (identical `checkNotNull` on `main`); the sibling fix
|
||||
attempt facebook/react-native#57365 for the same bookkeeping corruption (different stack)
|
||||
was abandoned. Re-check when bumping React Native.
|
||||
|
||||
Note on build modes: production Android builds compile react-android from source
|
||||
(`buildReactNativeFromSource: IS_PRODUCTION` via expo-build-properties in app.config.js
|
||||
injects the includeBuild/dependency-substitution block at prebuild), so this hunk IS
|
||||
active in production releases. Local dev builds prebuilt in a non-production env consume
|
||||
the prebuilt AAR from Maven Central instead, where this hunk (like any ReactAndroid
|
||||
source change) has no effect - do not expect to see the fix in a local debug build unless
|
||||
you prebuild with EXPO_PUBLIC_ENV=production or add the substitution block manually.
|
||||
|
||||
## RCTTextLayoutManager.mm Patch - Text overflows instead of wrapping on the last line
|
||||
|
||||
Issue: https://github.com/react/react-native/issues/53450#issuecomment-3298157830
|
||||
Bandaid fix taken from: https://github.com/react/react-native/commit/581d643a9e59fd88f93757f80194e1efd11bd0e5
|
||||
|
||||
## RCTViewComponentView.mm Patch - Hairline border strokes dropped at certain subpixel Y offsets on New Arch
|
||||
|
||||
Symptom: dividers built as `borderTopWidth: hairlineWidth` vanish on some screens and not
|
||||
others, deterministically by the view's absolute subpixel Y. A background fill of the same
|
||||
geometry always renders.
|
||||
|
||||
Cause: Fabric draws borders as a stretched 9-slice image. The consumer
|
||||
(`RCTAddContourEffectToLayer`) hardcodes the stretchable middle as a 1pt band, which matched
|
||||
the image the producer built until RN 0.81. facebook/react-native#54237 changed the image
|
||||
size to `ceil(insets) + 1 + ceil(insets)` without updating that formula, so for fractional
|
||||
(hairline) insets the labels no longer match the image: transparent filler is treated as a
|
||||
rigid cap, and when squeezed into a one-pixel-tall layer the sampling can land on it instead
|
||||
of the stroke - no line.
|
||||
|
||||
Fix: compute the middle from the cap insets (`size - caps`) instead of assuming 1pt.
|
||||
|
||||
Upstream issue: https://github.com/react/react-native/issues/58054 (repro:
|
||||
https://github.com/abulenok/HairlineBorderRepro, fails identically on 0.86.0 and 0.87.0).
|
||||
Reference in New Issue
Block a user