Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/calm-fabric-props.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@fluentui-react-native/native-core': patch
---

Document the repository-local RNmacOS native-props synchronization backport and its clean-build requirement for animated Fabric window resizing. Publishing Native Core does not install this renderer patch in a consumer.
202 changes: 202 additions & 0 deletions .yarn/patches/react-native-macos-npm-0.81.9-native-props.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
diff --git a/ReactCommon/react/renderer/core/ShadowNode.cpp b/ReactCommon/react/renderer/core/ShadowNode.cpp
--- a/ReactCommon/react/renderer/core/ShadowNode.cpp
+++ b/ReactCommon/react/renderer/core/ShadowNode.cpp
@@ -140,27 +140,32 @@
const ShadowNodeFragment& fragment) const {
const auto& family = *family_;
const auto& componentDescriptor = family.componentDescriptor_;
- if (family.nativeProps_DEPRECATED != nullptr) {
+
+ std::optional<RawProps> propsOverride;
+ {
+ std::lock_guard<std::mutex> lock(family.nativePropsMutex);
+ if (family.nativeProps_DEPRECATED != nullptr) {
+ if (fragment.props == ShadowNodeFragment::propsPlaceholder()) {
+ propsOverride.emplace(*family.nativeProps_DEPRECATED);
+ } else {
+ // TODO: We might need to merge fragment.props with
+ // `family.nativeProps_DEPRECATED`.
+ }
+ }
+ }
+
+ if (propsOverride) {
+ // Copy under the family lock, then parse and clone without holding it.
auto propsParserContext = PropsParserContext{family_->getSurfaceId(), {}};
- if (fragment.props == ShadowNodeFragment::propsPlaceholder()) {
- // Clone existing `props_` with `family.nativeProps_DEPRECATED` to apply
- // previously set props via `setNativeProps` API.
- auto props = componentDescriptor.cloneProps(
- propsParserContext, props_, RawProps(*family.nativeProps_DEPRECATED));
- auto clonedNode = componentDescriptor.cloneShadowNode(
- *this,
- {.props = props,
- .children = fragment.children,
- .state = fragment.state});
- return clonedNode;
- } else {
- // TODO: We might need to merge fragment.priops with
- // `family.nativeProps_DEPRECATED`.
- return componentDescriptor.cloneShadowNode(*this, fragment);
- }
- } else {
- return componentDescriptor.cloneShadowNode(*this, fragment);
- }
+ return componentDescriptor.cloneShadowNode(
+ *this,
+ {.props = componentDescriptor.cloneProps(
+ propsParserContext, props_, std::move(*propsOverride)),
+ .children = fragment.children,
+ .state = fragment.state});
+ }
+
+ return componentDescriptor.cloneShadowNode(*this, fragment);
}

ContextContainer::Shared ShadowNode::getContextContainer() const {
diff --git a/ReactCommon/react/renderer/core/ShadowNodeFamily.h b/ReactCommon/react/renderer/core/ShadowNodeFamily.h
--- a/ReactCommon/react/renderer/core/ShadowNodeFamily.h
+++ b/ReactCommon/react/renderer/core/ShadowNodeFamily.h
@@ -8,6 +8,7 @@
#pragma once

#include <memory>
+#include <mutex>
#include <shared_mutex>

#include <react/renderer/core/EventEmitter.h>
@@ -116,6 +117,7 @@
* architecture and will be removed in the future.
*/
mutable std::unique_ptr<folly::dynamic> nativeProps_DEPRECATED;
+ mutable std::mutex nativePropsMutex;

/**
* @return tag for the ShadowNodeFamily.
diff --git a/ReactCommon/react/renderer/uimanager/UIManager.cpp b/ReactCommon/react/renderer/uimanager/UIManager.cpp
--- a/ReactCommon/react/renderer/uimanager/UIManager.cpp
+++ b/ReactCommon/react/renderer/uimanager/UIManager.cpp
@@ -118,46 +118,48 @@
auto props = ShadowNodeFragment::propsPlaceholder();

if (!rawProps.isEmpty()) {
- if (family.nativeProps_DEPRECATED != nullptr) {
- // 1. update the nativeProps_DEPRECATED props.
- //
- // In this step, we want the most recent value for the props
- // managed by setNativeProps.
- // Values in `rawProps` patch (take precedence over)
- // `nativeProps_DEPRECATED`. For example, if both
- // `nativeProps_DEPRECATED` and `rawProps` contain key 'A'.
- // Value from `rawProps` overrides what was previously in
- // `nativeProps_DEPRECATED`. Notice that the `nativeProps_DEPRECATED`
- // patch will not get more props from `rawProps`: if the key is not
- // present in `nativeProps_DEPRECATED`, it will not be added.
- //
- // The result of this operation is the new `nativeProps_DEPRECATED`.
- family.nativeProps_DEPRECATED =
- std::make_unique<folly::dynamic>(mergeDynamicProps(
- *family.nativeProps_DEPRECATED, // source
- (folly::dynamic)rawProps, // patch
- NullValueStrategy::Ignore));
-
- // 2. Compute the final set of props.
- //
- // This step takes the new props handled by `setNativeProps` and
- // merges them in the `rawProps` managed by React.
- // The new props handled by `nativeProps` now takes precedence
- // on the props handled by React, as we want to make sure that
- // all the props are applied to the component.
- // We use these finalProps as source of truth for the component.
- auto finalProps = mergeDynamicProps(
- (folly::dynamic)rawProps, // source
- *family.nativeProps_DEPRECATED, // patch
- NullValueStrategy::Override);
-
- // 3. Clone the props by using finalProps.
- props = componentDescriptor.cloneProps(
- propsParserContext, shadowNode.getProps(), RawProps(finalProps));
- } else {
- props = componentDescriptor.cloneProps(
- propsParserContext, shadowNode.getProps(), std::move(rawProps));
+ std::optional<folly::dynamic> finalProps;
+ {
+ std::lock_guard<std::mutex> lock(family.nativePropsMutex);
+ if (family.nativeProps_DEPRECATED != nullptr) {
+ // 1. update the nativeProps_DEPRECATED props.
+ //
+ // In this step, we want the most recent value for the props
+ // managed by setNativeProps.
+ // Values in `rawProps` patch (take precedence over)
+ // `nativeProps_DEPRECATED`. For example, if both
+ // `nativeProps_DEPRECATED` and `rawProps` contain key 'A'.
+ // Value from `rawProps` overrides what was previously in
+ // `nativeProps_DEPRECATED`. Notice that the `nativeProps_DEPRECATED`
+ // patch will not get more props from `rawProps`: if the key is not
+ // present in `nativeProps_DEPRECATED`, it will not be added.
+ //
+ // The result of this operation is the new `nativeProps_DEPRECATED`.
+ family.nativeProps_DEPRECATED =
+ std::make_unique<folly::dynamic>(mergeDynamicProps(
+ *family.nativeProps_DEPRECATED, // source
+ (folly::dynamic)rawProps, // patch
+ NullValueStrategy::Ignore));
+
+ // 2. Compute the final set of props.
+ //
+ // This step takes the new props handled by `setNativeProps` and
+ // merges them in the `rawProps` managed by React.
+ // The new props handled by `nativeProps` now takes precedence
+ // on the props handled by React, as we want to make sure that
+ // all the props are applied to the component.
+ // We use these finalProps as source of truth for the component.
+ finalProps = mergeDynamicProps(
+ (folly::dynamic)rawProps, // source
+ *family.nativeProps_DEPRECATED, // patch
+ NullValueStrategy::Override);
+ }
}
+
+ props = componentDescriptor.cloneProps(
+ propsParserContext,
+ shadowNode.getProps(),
+ finalProps ? RawProps(std::move(*finalProps)) : std::move(rawProps));
}

auto clonedShadowNode = componentDescriptor.cloneShadowNode(
@@ -426,19 +428,22 @@
const std::shared_ptr<const ShadowNode>& shadowNode,
RawProps rawProps) const {
auto& family = shadowNode->getFamily();
- if (family.nativeProps_DEPRECATED) {
- // Values in `rawProps` patch (take precedence over)
- // `nativeProps_DEPRECATED`. For example, if both `nativeProps_DEPRECATED`
- // and `rawProps` contain key 'A'. Value from `rawProps` overrides what was
- // previously in `nativeProps_DEPRECATED`.
- family.nativeProps_DEPRECATED =
- std::make_unique<folly::dynamic>(mergeDynamicProps(
- *family.nativeProps_DEPRECATED,
- (folly::dynamic)rawProps,
- NullValueStrategy::Override));
- } else {
- family.nativeProps_DEPRECATED =
- std::make_unique<folly::dynamic>((folly::dynamic)rawProps);
+ {
+ std::lock_guard<std::mutex> lock(family.nativePropsMutex);
+ if (family.nativeProps_DEPRECATED) {
+ // Values in `rawProps` patch (take precedence over)
+ // `nativeProps_DEPRECATED`. For example, if both `nativeProps_DEPRECATED`
+ // and `rawProps` contain key 'A'. Value from `rawProps` overrides what was
+ // previously in `nativeProps_DEPRECATED`.
+ family.nativeProps_DEPRECATED =
+ std::make_unique<folly::dynamic>(mergeDynamicProps(
+ *family.nativeProps_DEPRECATED,
+ (folly::dynamic)rawProps,
+ NullValueStrategy::Override));
+ } else {
+ family.nativeProps_DEPRECATED =
+ std::make_unique<folly::dynamic>((folly::dynamic)rawProps);
+ }
}

shadowTreeRegistry_.visit(
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
"moment@npm:^2.24.0": "2.31.0",
"moment@npm:^2.29.4": "2.31.0",
"path-to-regexp@npm:8.3.0": "^8.4.2",
"react-native-macos": "patch:react-native-macos@npm%3A0.81.9#~/.yarn/patches/react-native-macos-npm-0.81.9-aa583a0648.patch",
"react-native-macos": "patch:react-native-macos@npm%3A0.81.9#~/.yarn/patches/react-native-macos-npm-0.81.9-aa583a0648.patch&~/.yarn/patches/react-native-macos-npm-0.81.9-native-props.patch",
"react-native-windows": "0.81.35",
"shell-quote": "1.11.0",
"smol-toml@npm:^1.6.1": "1.9.0",
Expand Down
5 changes: 4 additions & 1 deletion packages/agentic/native-core/references/remaining-work.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,12 @@ issue or pull request; compilation and skipped cases are not native qualificatio

## RNmacOS upstream replacement

- **Outcome:** consumers receive the macOS accessibility fixes without this repository's RNmacOS patch.
- **Outcome:** consumers receive the macOS accessibility and native-props synchronization fixes without this repository's RNmacOS patches.
- **Current state:** the [repository-local patch](rnmacos-patch.md) is applied only to this repository's native hosts;
publishing native-core or components does not install it in a consumer.
- **Done when:** the fixes land upstream or in an equivalent consuming-renderer integration, and the same native cases
pass against that replacement before the patch is removed. Checked-state actions, AX relationships, and VoiceOver
speech remain separate, still-unqualified semantics.
The native-props synchronization already has an upstream implementation; remove
that backport only after the pinned RNmacOS fork includes it and the animated
window-resize regression passes against the replacement.
36 changes: 36 additions & 0 deletions packages/agentic/native-core/references/rnmacos-patch.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,42 @@ text on macOS. RNmacOS otherwise rejects them unconditionally because its iOS
element provider is compiled out, which hides every Text label and `testID`
from accessibility clients.

## Native-props synchronization backport

The root also applies
`.yarn/patches/react-native-macos-npm-0.81.9-native-props.patch`, a minimal
backport of MIT-licensed React Native code from
[upstream commit `923e7ddcaf4d` by OrfeasZ, #52646](https://github.com/facebook/react-native/commit/923e7ddcaf4d2de45ca7dedafbb84c6b87dbffc4).
The upstream copyright and license in the patched source files remain intact.
Fabric's window-resize layout can clone animated nodes on the main thread while
JavaScript replaces their `nativeProps_DEPRECATED` map. This concurrent-access
cause is inferred from the failing reader, the unguarded source, and the upstream
fix; no ThreadSanitizer run established the concurrent writer. The backport guards
both writers and the cloning reader with one family-owned mutex, copying the
props before releasing the lock and parsing them afterward. This preserves
merge precedence without holding the mutex across shadow-tree commits.
Regenerate Pods and clean-build all native dependencies after changing this
patch: the family header's layout changes.

The race was exposed by the PGB-006 NativeSemantics window-resize callback
during Fabric Storybook qualification. A clean native rebuild still failed,
and an owned-process AddressSanitizer report recorded
`BUS` in `folly::dynamic::type()` while `ShadowNode::clone` copied the
unprotected native-props map during AppKit layout. This is a renderer
concurrency fix, not a click-through or driver retry workaround. PGB-006 runs
in the full Fabric physical lane as well as the accessible lane; the original
Checkbox, keyboard-clear, and Accordion reproductions passed without this
backport.

### Qualification

A clean Pods and Fabric Storybook native build with AddressSanitizer passed,
as did the PGB-006 resize callback and Native Core's CocoaPods-module and
Office-style bridging-header probes. This qualifies the Storybook host only:
macOS FluentTester and other native application hosts were not built locally.

## Scope and replacement

These fixes belong in RNmacOS, not in a MenuItem-specific native host or a
JavaScript accessibility-value workaround. The patch is used by this repository's
native hosts; publishing native-core or components does **not** install it in a
Expand Down
6 changes: 3 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -19597,9 +19597,9 @@ __metadata:
languageName: node
linkType: hard

"react-native-macos@patch:react-native-macos@npm%3A0.81.9#~/.yarn/patches/react-native-macos-npm-0.81.9-aa583a0648.patch":
"react-native-macos@patch:react-native-macos@npm%3A0.81.9#~/.yarn/patches/react-native-macos-npm-0.81.9-aa583a0648.patch&~/.yarn/patches/react-native-macos-npm-0.81.9-native-props.patch":
version: 0.81.9
resolution: "react-native-macos@patch:react-native-macos@npm%3A0.81.9#~/.yarn/patches/react-native-macos-npm-0.81.9-aa583a0648.patch::version=0.81.9&hash=fdc083"
resolution: "react-native-macos@patch:react-native-macos@npm%3A0.81.9#~/.yarn/patches/react-native-macos-npm-0.81.9-aa583a0648.patch&~/.yarn/patches/react-native-macos-npm-0.81.9-native-props.patch::version=0.81.9&hash=63578b"
dependencies:
"@jest/create-cache-key-function": "npm:^29.7.0"
"@react-native-macos/virtualized-lists": "npm:0.81.9"
Expand Down Expand Up @@ -19645,7 +19645,7 @@ __metadata:
bin:
react-native: cli.js
react-native-macos: cli.js
checksum: 10c0/c4dd2eda84eea704dc205b31bb5ddbc6f58f26a23fa99f14d66ebf6b4637407a0ebb66096df4c34f8923910426a326e586184e2efa62e006257b984608d39f86
checksum: 10c0/3d565e0d0a494b81519e5d86a8322aec279170317e7b5dc77ef1f3b8b786706cb342fc1626f5348bc3e3f67a501f46616b13c43f1f02842b21022f91597c8806
languageName: node
linkType: hard

Expand Down
Loading