Skip to content

fix(0.81, fabric): synchronize native props across shadow node clones - #3177

Open
Jason Morse (JasonVMo) wants to merge 5 commits into
0.81/user/jasonvmo/safe-area-callbackfrom
0.81/user/jasonvmo/fabric-native-props-sync
Open

Jason Morse (JasonVMo) wants to merge 5 commits into
0.81/user/jasonvmo/safe-area-callbackfrom
0.81/user/jasonvmo/fabric-native-props-sync

Conversation

@JasonVMo

@JasonVMo Jason Morse (JasonVMo) commented Nov 12, 2026 •

Copy link
Copy Markdown

Summary:

Backport the MIT-licensed upstream implementation by OrfeasZ,
react/react-native@923e7ddcaf4d2de45ca7dedafbb84c6b87dbffc4
(react#52646), adapting only branch-specific APIs, tests and
snapshots. This resolves the missing native-props synchronization exposed by
microsoft/fluentui-react-native#4499's Fabric window-resize qualification.

  • A single family-owned mutable mutex guards all ten native-props map
    accesses: the clone reader and both UIManager writer paths.
  • Copy owned snapshots while locked; unlock before parsing props, cloning
    through descriptors, visiting registries, or committing shadow trees.
  • Preserve Ignore/Override merge precedence, null handling, and explicit
    fragment props behavior. Production logic matches upstream.
  • Port upstream FabricUIManagerTest coverage with focused merge/clone and
    bounded concurrency cases. Add a bounded real-NSWindow resize/native-props
    regression to the existing native test host.
  • Update only the snapshot file that exists on the target branch. Main/0.83
    use scripts/cxx-api; 0.81 uses private/cxx-public-api. Each snapshot adds
    exactly the new mutex declaration, not unrelated generator churn.

ABI: ShadowNodeFamily layout changes. Regenerate Pods and clean-build all
affected native libraries and C++ consumers together; do not combine new
headers with old renderer/prebuilt binaries.

Branch/package findings and release metadata

Main, 0.81-stable and 0.83-stable lack an equivalent fix. All ten published
0.81.x tarballs (0.81.0–0.81.9) and published 0.83.0 were downloaded,
integrity-verified and source-inspected; all are missing it. Upgrading to
0.83.0 does not fix this gap.

Do not duplicate future work: 0.87-merge contains the original upstream fix;
existing #3036's release head contains equivalent upstream stable backport
0b941bc51a740670470c05e08bca5898d1a6b78c.

The owner explicitly approved these incident-driven stable backports. This is
not a claim that upstream React Native still supports 0.81/0.83, or a new
macOS maintenance commitment. Metadata follows each branch: no bump record
on main; an Nx patch plan on 0.81, whose inert Changeset was removed after
review; a Changeset on 0.83.

Separate prerequisites; no PRs merged

The native test host was already broken by UIKit-only image tests and gzip
C++ linkage. Main reuses existing #3095. Separate stable repairs are #3180
(0.81) and #3179 (0.83).

Stable initializer CI also exposed an existing missing UIKit-compatible
safe-area callback. It is fixed separately in #3181/#3182; main has #3183.
No dependency, animation, assertion, resize check, or integration job was
removed to conceal either failure.

With explicit owner approval, stable PRs form native GitHub stacks:

Ordinary merge commits preserve all existing history; no force-push or PR
merge occurred. The reviewed three production-file blobs are unchanged
after integrating prerequisites.

Test Plan:

Paired native reproduction, not just ordinary test success

The same committed XCTest fixture, source hash
03915e9f4ae6e255b5682006250dd12b8e3aaf35a95cb1e8bba540aa5b9609e0,
runs against separately clean-built original and repaired 0.81 libraries:
120 owned-window resize callbacks, 3,840 native writes and 3,840 reader
clones, a fixed deadline, asserted final state, and joined worker teardown.

Instrumented native run Actual result
Original 0.81, ASan Expected failure: heap-use-after-free copying folly::dynamic in ShadowNode::clone, reached through Yoga layout, SurfaceHandler::constraintLayout, and windowDidResize:.
Repaired 0.81, ASan Passed: 1 testcase, 0 failures, no ASan diagnostic.
Original 0.81, TSan Expected failure: same family pointer read in ShadowNode::clone concurrently with replacement in UIManager::setNativeProps_DEPRECATED.
Repaired 0.81, TSan Passed: 1 testcase, 0 failures, no TSan diagnostic.

This establishes the race in the controlled RNmacOS native fixture.
It does not establish the exact JavaScript writer in the historical FURN
incident; that remains inferred. One bounded passing run is not a claim of
global race freedom.

Actual instrumented compiler response files for ShadowNode.cpp,
ShadowNodeFamily.cpp and UIManager.cpp were checked, as were linked sanitizer
runtimes and source hashes. Baseline/repaired archives are not mixed.

The existing target's pre-existing image-test compile failure initially
blocked execution. Qualification therefore also built the exact same
fixture source
as a focused XCTest bundle against the fresh matching
archives, using the existing XCTest runtime. It does not read the repository
test plan or retry failed tests.

Exact runner commands (NATIVE_PROPS_EVIDENCE denotes the retained local
artifact directory; its scripts contain full compiler/link argv):

python3 "$NATIVE_PROPS_EVIDENCE/run-native-props-xctest.py" baseline081-asan address direct-host
# Expected native failure: ASan heap-use-after-free; testcase started.
python3 "$NATIVE_PROPS_EVIDENCE/run-native-props-xctest.py" baseline081-tsan thread
# Expected native failure: TSan reader/writer data race; testcase started.
python3 "$NATIVE_PROPS_EVIDENCE/run-native-props-xctest.py" patched081-tsan thread
# Passed: one case, no failures or race diagnostic.
bash "$NATIVE_PROPS_EVIDENCE/build-native-props-xctest.sh" "$PATCHED081_WORKTREE" "$NATIVE_PROPS_EVIDENCE/build-patched081-asan-app/Build/Products/Debug" address patched081-asan
python3 "$NATIVE_PROPS_EVIDENCE/run-native-props-xctest.py" patched081-asan address
# Passed: one case, no failures or ASan diagnostic.

The first ASan launcher used the xcrun wrapper, which stripped runtime
preloading and aborted before a testcase started. Resolving and invoking
the actual XCTest executable corrected that launch error. The subsequent
native baseline failure was retained, not retried until green. Likewise,
Xcode's rejected -test-iterations 1 invocation and the early zero-test
safe-area invocation were not counted as coverage.

Existing native host, Paper, C++ and snapshots

  • Fresh main renderer/support libraries: 4/4 actual
    FabricUIManagerTest GTests pass
    , including merge and concurrency cases.
  • All three branch-specific production, GTest and native fixture syntax/API
    checks pass. Stock API generators ran; only the intended member delta was
    committed. Scoped native format/project/whitespace checks pass.
  • Proper-cwd Pods regeneration and clean native app builds pass on main,
    0.83, and original/repaired 0.81 ASan/TSan configurations.
  • Repaired Paper app clean-builds; 9 existing layout/gzip tests pass.
  • After separate host repair, the existing 0.83 XCTest target builds, links
    and executes the committed resize fixture: 1/1 passed.
  • Separate callback compatibility regressions: 2/2 modern, and
    11/11 legacy including the nine Paper regressions.

Representative exact existing-host qualification commands:

RCT_NO_LAUNCH_PACKAGER=1 xcodebuild -workspace packages/rn-tester/RNTesterPods.xcworkspace -scheme RNTester-macOS -sdk macosx -destination 'platform=macOS,arch=arm64' -configuration Debug -jobs 2 -derivedDataPath "$NATIVE_PROPS_EVIDENCE/build-patched083-clean" -resultBundlePath "$NATIVE_PROPS_EVIDENCE/results-patched083-existing-host.xcresult" CODE_SIGNING_ALLOWED=NO COMPILER_INDEX_STORE_ENABLE=NO -enableAddressSanitizer NO -enableThreadSanitizer NO -enableUndefinedBehaviorSanitizer NO -parallel-testing-enabled NO -only-testing:RNTester-macOSUnitTests/RCTFabricNativePropsTests/testWindowResizeWhileUpdatingNativeProps test-without-building
# Passed: TEST EXECUTE SUCCEEDED, 1 case.
NX_DAEMON=false yarn nx release plan:check --base=origin/0.81-stable
# Passed for the 0.81 release-plan remediation.

Ordinary native-host qualification uses an explicitly local single-run
profile. No test-plan retry/sanitizer-policy changes are committed, and no
ordinary build/unit pass is used as the race-freedom argument.

One Opus 5.5 review and remediation

Exactly one requested Opus 5.5 review found no production synchronization
correctness defects
across the three branch adapters. Its two findings
were remediated by the implementation role:

  • Unrunnable pre-existing native host: separately repaired/reused and the
    exact existing-host fixture actually executed.
  • Inert 0.81 Changeset: replaced with the branch-native Nx plan; validation
    passes. The pre-existing stable-dist-tag resolver mismatch was also corrected
    in a separate commit and checked with the actual resolver.

The original implementing sub-agent later encountered a provider error.
The owner authorized direct implementation takeover; the failed request was
not retried/rephrased and no second review was launched.

Not run: FURN's full physical/accessible suites against an upstream release
artifact, iOS/Android/device sanitizer execution, the entire legacy native
unit suite, or publication. CI status is tracked separately; no merge is
requested or performed.

FURN consumption / removing only the temporary patch

No inspected published 0.81/0.83 package contains this fix yet. FURN should
consume the next source-verified 0.81 patch artifact containing the final
#3177 head (including required prerequisites), retain its React Native peer
alignment and minimum-package-age gate, and clean-rebuild Pods, renderer and
all native consumers. Prebuilt renderer binaries must contain the same fix.

Remove only .yarn/patches/react-native-macos-npm-0.81.9-native-props.patch
and its segment of the Yarn patch chain after verifying the artifact's
family mutex/read/write implementation and rerunning PGB-006 plus the owned
physical/accessible Fabric qualification. Preserve/rebase the independent
RNmacOS patch ending in aa583a0648.patch and all other unrelated patches.

Port the merge regression from MIT-licensed react/react-native commit 923e7dd by OrfeasZ (react#52646), preserving its license headers. Add native/React precedence and placeholder clone cases, a bounded concurrent clone case, and an in-process AppKit XCTest using the real Scheduler, SurfaceHandler and UIManager. The fixture never accesses the new mutex and can run unchanged against an unpatched baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 33ea5c72-8bbd-448d-aa21-c9b99dd03c96
Backport MIT-licensed react/react-native commit 923e7dd by OrfeasZ (react#52646), preserving upstream copyright and license headers. Use one family-owned mutex for both UIManager writers and ShadowNode cloning, releasing it before parsing, descriptor callbacks and commits. Retain the 0.81 context-container API, update its existing private C++ API snapshot and add a patch Changeset. Rebuild all native consumers because ShadowNodeFamily layout changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 33ea5c72-8bbd-448d-aa21-c9b99dd03c96
Correct the pre-existing 0.81 NPM dry-run blocker after 0.83 became latest. The unchanged branch-aware prepublish rules require 0.81-stable for an older minor. Match that existing rule rather than bypassing validation or altering publication policy. The installed Nx registry resolver now reads 0.81.9 for both react-native-macos and @react-native-macos/virtualized-lists. The original validator failed against unchanged baseline metadata; the corrected validator and actual resolver pass without authentication, versioning or publishing. Only nx.json changes; renderer/test commits reviewed through e3492a9 remain unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 33ea5c72-8bbd-448d-aa21-c9b99dd03c96
Address the Opus review finding that 0.81-stable has Nx version plans, not Changesets. Use the existing fixed release-group convention and remove the inert Changeset. No version is published or bumped by this commit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 33ea5c72-8bbd-448d-aa21-c9b99dd03c96
…tory

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 33ea5c72-8bbd-448d-aa21-c9b99dd03c96

# Conflicts:
#	packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj
@JasonVMo
Jason Morse (JasonVMo) changed the base branch from 0.81-stable to 0.81/user/jasonvmo/safe-area-callback November 13, 2026 01:50
@JasonVMo
Jason Morse (JasonVMo) marked this pull request as ready for review November 13, 2026 01:53
@JasonVMo
Jason Morse (JasonVMo) requested a review from a team as a code owner November 13, 2026 01:53
@JasonVMo

Copy link
Copy Markdown
Author

Final-base CI run 43348256508 completed successfully at aeaf54f: 20 jobs passed, with no failed jobs. This includes the generated-app initialization path, native old/new architecture builds, integration and prebuild coverage. The separate host and compatibility prerequisites remain unmerged in native stack 3184; no force-push or PR merge occurred. Paired ASan/TSan native evidence and the exact downstream patch-removal gate remain in the Test Plan. This does not claim FURN historical-writer TSan proof or a published fixed npm release.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant