diff --git a/packages/react-native/Libraries/Components/View/View.js b/packages/react-native/Libraries/Components/View/View.js index be74cb04c0de..7bf5f641944b 100644 --- a/packages/react-native/Libraries/Components/View/View.js +++ b/packages/react-native/Libraries/Components/View/View.js @@ -10,6 +10,7 @@ import type {ViewProps} from './ViewPropTypes'; +import * as ReactNativeFeatureFlags from '../../../src/private/featureflags/ReactNativeFeatureFlags'; import TextAncestorContext from '../../Text/TextAncestorContext'; import ViewNativeComponent from './ViewNativeComponent'; import * as React from 'react'; @@ -28,96 +29,100 @@ component View( ) { const hasTextAncestor = use(TextAncestorContext); - const { - accessibilityState, - accessibilityValue, - 'aria-busy': ariaBusy, - 'aria-checked': ariaChecked, - 'aria-disabled': ariaDisabled, - 'aria-expanded': ariaExpanded, - 'aria-hidden': ariaHidden, - 'aria-label': ariaLabel, - 'aria-labelledby': ariaLabelledBy, - 'aria-live': ariaLive, - 'aria-selected': ariaSelected, - 'aria-valuemax': ariaValueMax, - 'aria-valuemin': ariaValueMin, - 'aria-valuenow': ariaValueNow, - 'aria-valuetext': ariaValueText, - id, - tabIndex, - ...otherProps - } = props; - - // Since we destructured props, we can now treat it as mutable - const processedProps = otherProps as {...ViewProps}; - - const parsedAriaLabelledBy = ariaLabelledBy?.split(/\s*,\s*/g); - if (parsedAriaLabelledBy !== undefined) { - processedProps.accessibilityLabelledBy = parsedAriaLabelledBy; - } + let resolvedProps = props; + if (!ReactNativeFeatureFlags.enableNativeViewPropTransformations()) { + const { + accessibilityState, + accessibilityValue, + 'aria-busy': ariaBusy, + 'aria-checked': ariaChecked, + 'aria-disabled': ariaDisabled, + 'aria-expanded': ariaExpanded, + 'aria-hidden': ariaHidden, + 'aria-label': ariaLabel, + 'aria-labelledby': ariaLabelledBy, + 'aria-live': ariaLive, + 'aria-selected': ariaSelected, + 'aria-valuemax': ariaValueMax, + 'aria-valuemin': ariaValueMin, + 'aria-valuenow': ariaValueNow, + 'aria-valuetext': ariaValueText, + id, + tabIndex, + ...otherProps + } = props; + + const processedProps = otherProps as {...ViewProps}; + + const parsedAriaLabelledBy = ariaLabelledBy?.split(/\s*,\s*/g); + if (parsedAriaLabelledBy !== undefined) { + processedProps.accessibilityLabelledBy = parsedAriaLabelledBy; + } - if (ariaLabel !== undefined) { - processedProps.accessibilityLabel = ariaLabel; - } + if (ariaLabel !== undefined) { + processedProps.accessibilityLabel = ariaLabel; + } - if (ariaLive !== undefined) { - processedProps.accessibilityLiveRegion = - ariaLive === 'off' ? 'none' : ariaLive; - } + if (ariaLive !== undefined) { + processedProps.accessibilityLiveRegion = + ariaLive === 'off' ? 'none' : ariaLive; + } - if (ariaHidden !== undefined) { - processedProps.accessibilityElementsHidden = ariaHidden; - if (ariaHidden === true) { - processedProps.importantForAccessibility = 'no-hide-descendants'; + if (ariaHidden !== undefined) { + processedProps.accessibilityElementsHidden = ariaHidden; + if (ariaHidden === true) { + processedProps.importantForAccessibility = 'no-hide-descendants'; + } } - } - if (id !== undefined) { - processedProps.nativeID = id; - } + if (id !== undefined) { + processedProps.nativeID = id; + } - if (tabIndex !== undefined) { - processedProps.focusable = !tabIndex; - } + if (tabIndex !== undefined) { + processedProps.focusable = !tabIndex; + } - if ( - accessibilityState != null || - ariaBusy != null || - ariaChecked != null || - ariaDisabled != null || - ariaExpanded != null || - ariaSelected != null - ) { - processedProps.accessibilityState = { - busy: ariaBusy ?? accessibilityState?.busy, - checked: ariaChecked ?? accessibilityState?.checked, - disabled: ariaDisabled ?? accessibilityState?.disabled, - expanded: ariaExpanded ?? accessibilityState?.expanded, - selected: ariaSelected ?? accessibilityState?.selected, - }; - } + if ( + accessibilityState != null || + ariaBusy != null || + ariaChecked != null || + ariaDisabled != null || + ariaExpanded != null || + ariaSelected != null + ) { + processedProps.accessibilityState = { + busy: ariaBusy ?? accessibilityState?.busy, + checked: ariaChecked ?? accessibilityState?.checked, + disabled: ariaDisabled ?? accessibilityState?.disabled, + expanded: ariaExpanded ?? accessibilityState?.expanded, + selected: ariaSelected ?? accessibilityState?.selected, + }; + } + + if ( + accessibilityValue != null || + ariaValueMax != null || + ariaValueMin != null || + ariaValueNow != null || + ariaValueText != null + ) { + processedProps.accessibilityValue = { + max: ariaValueMax ?? accessibilityValue?.max, + min: ariaValueMin ?? accessibilityValue?.min, + now: ariaValueNow ?? accessibilityValue?.now, + text: ariaValueText ?? accessibilityValue?.text, + }; + } - if ( - accessibilityValue != null || - ariaValueMax != null || - ariaValueMin != null || - ariaValueNow != null || - ariaValueText != null - ) { - processedProps.accessibilityValue = { - max: ariaValueMax ?? accessibilityValue?.max, - min: ariaValueMin ?? accessibilityValue?.min, - now: ariaValueNow ?? accessibilityValue?.now, - text: ariaValueText ?? accessibilityValue?.text, - }; + resolvedProps = processedProps; } const actualView = ref == null ? ( - + ) : ( - + ); if (hasTextAncestor) { diff --git a/packages/react-native/Libraries/Components/View/__tests__/View-itest.js b/packages/react-native/Libraries/Components/View/__tests__/View-itest.js index 0ad79c3d02d8..267d683170ff 100644 --- a/packages/react-native/Libraries/Components/View/__tests__/View-itest.js +++ b/packages/react-native/Libraries/Components/View/__tests__/View-itest.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. * * @flow strict-local - * @fantom_flags enableNativeCSSParsing:* + * @fantom_flags enableNativeCSSParsing:* enableNativeViewPropTransformations:* * @format */ @@ -578,6 +578,140 @@ describe('', () => { }); }); + describe('overlapping aria-label and accessibilityLabel', () => { + it('preserves accessibilityLabel when aria-label is removed', () => { + const root = Fantom.createRoot(); + + // Set both aria-label and accessibilityLabel + Fantom.runTask(() => { + root.render( + , + ); + }); + + // aria-label should take precedence + expect( + root.getRenderedOutput({props: ['accessibilityLabel']}).toJSX(), + ).toEqual(); + + // Remove aria-label but keep accessibilityLabel + Fantom.runTask(() => { + root.render( + , + ); + }); + + // accessibilityLabel should still be "native value" + expect( + root.getRenderedOutput({props: ['accessibilityLabel']}).toJSX(), + ).toEqual(); + }); + }); + + describe('overlapping aria-hidden and importantForAccessibility', () => { + it('preserves importantForAccessibility when aria-hidden is removed', () => { + const root = Fantom.createRoot(); + + // Set both aria-hidden and importantForAccessibility + Fantom.runTask(() => { + root.render( + , + ); + }); + + expect( + root + .getRenderedOutput({props: ['importantForAccessibility']}) + .toJSX(), + ).toEqual( + , + ); + + // Remove aria-hidden but keep importantForAccessibility + Fantom.runTask(() => { + root.render( + , + ); + }); + + // importantForAccessibility should still be "no-hide-descendants" + expect( + root + .getRenderedOutput({props: ['importantForAccessibility']}) + .toJSX(), + ).toEqual( + , + ); + }); + }); + + describe('aria-hidden={false} with importantForAccessibility', () => { + it('does not overwrite explicit importantForAccessibility', () => { + const root = Fantom.createRoot(); + + // Set importantForAccessibility="yes" and aria-hidden={false}. + // aria-hidden={false} should NOT reset importantForAccessibility + // to Auto, it should preserve the explicit "yes" value. + Fantom.runTask(() => { + root.render( + , + ); + }); + + expect( + root + .getRenderedOutput({props: ['importantForAccessibility']}) + .toJSX(), + ).toEqual(); + }); + }); + + describe('overlapping aria-live and accessibilityLiveRegion', () => { + it('preserves accessibilityLiveRegion when aria-live is removed', () => { + const root = Fantom.createRoot(); + + // Set both aria-live and accessibilityLiveRegion + Fantom.runTask(() => { + root.render( + , + ); + }); + + // Remove aria-live but keep accessibilityLiveRegion + Fantom.runTask(() => { + root.render( + , + ); + }); + + // accessibilityLiveRegion should still be "assertive" + expect( + root + .getRenderedOutput({props: ['accessibilityLiveRegion']}) + .toJSX(), + ).toEqual(); + }); + }); + describe('aria-live', () => { it('is mapped to accessibilityLiveRegion', () => { const root = Fantom.createRoot(); diff --git a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js index f2c8c78798d3..cdd9eceaead2 100644 --- a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js +++ b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js @@ -214,6 +214,7 @@ const validAttributesForNonEventProps = { renderToHardwareTextureAndroid: true, testID: true, nativeID: true, + id: true, accessibilityLabelledBy: true, accessibilityLabel: true, accessibilityHint: true, @@ -226,6 +227,19 @@ const validAttributesForNonEventProps = { experimental_accessibilityOrder: true, importantForAccessibility: true, screenReaderFocusable: true, + 'aria-busy': true, + 'aria-checked': true, + 'aria-disabled': true, + 'aria-expanded': true, + 'aria-hidden': true, + 'aria-label': true, + 'aria-labelledby': true, + 'aria-live': true, + 'aria-selected': true, + 'aria-valuemax': true, + 'aria-valuemin': true, + 'aria-valuenow': true, + 'aria-valuetext': true, role: true, rotation: true, scaleX: true, @@ -368,6 +382,7 @@ const validAttributesForNonEventProps = { borderBlockEndColor: colorAttribute, borderBlockStartColor: colorAttribute, focusable: true, + tabIndex: true, backfaceVisibility: true, } as const; diff --git a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js index 913ebc34b534..2c0ca3437148 100644 --- a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js +++ b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js @@ -218,7 +218,22 @@ const validAttributesForNonEventProps = { transformOrigin: true, accessibilityRole: true, accessibilityState: true, + 'aria-busy': true, + 'aria-checked': true, + 'aria-disabled': true, + 'aria-expanded': true, + 'aria-hidden': true, + 'aria-label': true, + 'aria-labelledby': true, + 'aria-live': true, + 'aria-selected': true, + 'aria-valuemax': true, + 'aria-valuemin': true, + 'aria-valuenow': true, + 'aria-valuetext': true, + tabIndex: true, nativeID: true, + id: true, pointerEvents: true, removeClippedSubviews: true, role: true, diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt index a66fb344c78a..eeec9f2a911e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<2ca6b65f1103a486e4e5a006de629e76>> + * @generated SignedSource<<38f50776ad2b88fa69b710dbd67315e2>> */ /** @@ -288,6 +288,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun enableNativeCSSParsing(): Boolean = accessor.enableNativeCSSParsing() + /** + * When enabled, View.js passes aria-*, id, and tabIndex props directly to native, relying on C++ prop parsing instead of JS-side transformations. + */ + @JvmStatic + public fun enableNativeViewPropTransformations(): Boolean = accessor.enableNativeViewPropTransformations() + /** * Enable network event reporting hooks in each native platform through `NetworkReporter` (Web Perf APIs + CDP). This flag should be combined with `fuseboxNetworkInspectionEnabled` to enable Network CDP debugging. */ diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt index 164b90ee3e9e..214d1d356bf0 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<202662ab1c26ed104cfe837162d4f9a2>> + * @generated SignedSource<> */ /** @@ -63,6 +63,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var enableMainQueueCoordinatorOnIOSCache: Boolean? = null private var enableModuleArgumentNSNullConversionIOSCache: Boolean? = null private var enableNativeCSSParsingCache: Boolean? = null + private var enableNativeViewPropTransformationsCache: Boolean? = null private var enableNetworkEventReportingCache: Boolean? = null private var enablePreparedTextLayoutCache: Boolean? = null private var enablePropsUpdateReconciliationAndroidCache: Boolean? = null @@ -494,6 +495,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun enableNativeViewPropTransformations(): Boolean { + var cached = enableNativeViewPropTransformationsCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.enableNativeViewPropTransformations() + enableNativeViewPropTransformationsCache = cached + } + return cached + } + override fun enableNetworkEventReporting(): Boolean { var cached = enableNetworkEventReportingCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt index 0ba312d6258b..6ead7879cc7f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<03f5b76fefda14757a43414f6624601a>> + * @generated SignedSource<<85d23b5f33a6a09c1e42b976a3a99f2f>> */ /** @@ -114,6 +114,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun enableNativeCSSParsing(): Boolean + @DoNotStrip @JvmStatic public external fun enableNativeViewPropTransformations(): Boolean + @DoNotStrip @JvmStatic public external fun enableNetworkEventReporting(): Boolean @DoNotStrip @JvmStatic public external fun enablePreparedTextLayout(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt index 01469ff2539f..1e6f48d7c16d 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<7713ee7b0947f0ae8c66b73413a7f226>> + * @generated SignedSource<<4adf4e6f40b726bf9d3680301fb92e1f>> */ /** @@ -109,6 +109,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun enableNativeCSSParsing(): Boolean = false + override fun enableNativeViewPropTransformations(): Boolean = false + override fun enableNetworkEventReporting(): Boolean = true override fun enablePreparedTextLayout(): Boolean = false diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt index 1ce18fbaf475..5b1fd8c2a1b7 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<79cf67c605a76059f010cf2ccd0ec64b>> + * @generated SignedSource<<2115ff3be1f3944d499090d3089d185b>> */ /** @@ -67,6 +67,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var enableMainQueueCoordinatorOnIOSCache: Boolean? = null private var enableModuleArgumentNSNullConversionIOSCache: Boolean? = null private var enableNativeCSSParsingCache: Boolean? = null + private var enableNativeViewPropTransformationsCache: Boolean? = null private var enableNetworkEventReportingCache: Boolean? = null private var enablePreparedTextLayoutCache: Boolean? = null private var enablePropsUpdateReconciliationAndroidCache: Boolean? = null @@ -541,6 +542,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun enableNativeViewPropTransformations(): Boolean { + var cached = enableNativeViewPropTransformationsCache + if (cached == null) { + cached = currentProvider.enableNativeViewPropTransformations() + accessedFeatureFlags.add("enableNativeViewPropTransformations") + enableNativeViewPropTransformationsCache = cached + } + return cached + } + override fun enableNetworkEventReporting(): Boolean { var cached = enableNetworkEventReportingCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt index 59b5af3095b8..f574fa643c0f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<655d1ff3caeb3d8e95d44176d65b951b>> + * @generated SignedSource<<1bbbe280815927e0f977f1d1e7787937>> */ /** @@ -109,6 +109,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun enableNativeCSSParsing(): Boolean + @DoNotStrip public fun enableNativeViewPropTransformations(): Boolean + @DoNotStrip public fun enableNetworkEventReporting(): Boolean @DoNotStrip public fun enablePreparedTextLayout(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp index 5467c7072e81..4a2a9b544184 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<175e48107d9bd1d713a4da0a252a58bf>> + * @generated SignedSource<> */ /** @@ -297,6 +297,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + bool enableNativeViewPropTransformations() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableNativeViewPropTransformations"); + return method(javaProvider_); + } + bool enableNetworkEventReporting() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableNetworkEventReporting"); @@ -774,6 +780,11 @@ bool JReactNativeFeatureFlagsCxxInterop::enableNativeCSSParsing( return ReactNativeFeatureFlags::enableNativeCSSParsing(); } +bool JReactNativeFeatureFlagsCxxInterop::enableNativeViewPropTransformations( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::enableNativeViewPropTransformations(); +} + bool JReactNativeFeatureFlagsCxxInterop::enableNetworkEventReporting( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::enableNetworkEventReporting(); @@ -1149,6 +1160,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "enableNativeCSSParsing", JReactNativeFeatureFlagsCxxInterop::enableNativeCSSParsing), + makeNativeMethod( + "enableNativeViewPropTransformations", + JReactNativeFeatureFlagsCxxInterop::enableNativeViewPropTransformations), makeNativeMethod( "enableNetworkEventReporting", JReactNativeFeatureFlagsCxxInterop::enableNetworkEventReporting), diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h index 69cb7c48e47e..e2a03055ab86 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<78e35f9316b9568e7a44153d542b6ebd>> */ /** @@ -159,6 +159,9 @@ class JReactNativeFeatureFlagsCxxInterop static bool enableNativeCSSParsing( facebook::jni::alias_ref); + static bool enableNativeViewPropTransformations( + facebook::jni::alias_ref); + static bool enableNetworkEventReporting( facebook::jni::alias_ref); diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp index 08bfc040f3de..6de9c614fc18 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<5208616bc5d84f040ad0fa5e85acd6c4>> + * @generated SignedSource<<8643070722a65532f6c928ed82ca2aa5>> */ /** @@ -198,6 +198,10 @@ bool ReactNativeFeatureFlags::enableNativeCSSParsing() { return getAccessor().enableNativeCSSParsing(); } +bool ReactNativeFeatureFlags::enableNativeViewPropTransformations() { + return getAccessor().enableNativeViewPropTransformations(); +} + bool ReactNativeFeatureFlags::enableNetworkEventReporting() { return getAccessor().enableNetworkEventReporting(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index 731038a34561..fdbcfc434f8e 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<1cbe2158c5242b2980ddafafe9084d7c>> + * @generated SignedSource<<47cc177671195fd67010ecdba712596a>> */ /** @@ -254,6 +254,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool enableNativeCSSParsing(); + /** + * When enabled, View.js passes aria-*, id, and tabIndex props directly to native, relying on C++ prop parsing instead of JS-side transformations. + */ + RN_EXPORT static bool enableNativeViewPropTransformations(); + /** * Enable network event reporting hooks in each native platform through `NetworkReporter` (Web Perf APIs + CDP). This flag should be combined with `fuseboxNetworkInspectionEnabled` to enable Network CDP debugging. */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index 90f98c98f1da..cf9c347b5c3e 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -803,6 +803,24 @@ bool ReactNativeFeatureFlagsAccessor::enableNativeCSSParsing() { return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::enableNativeViewPropTransformations() { + auto flagValue = enableNativeViewPropTransformations_.load(); + + if (!flagValue.has_value()) { + // This block is not exclusive but it is not necessary. + // If multiple threads try to initialize the feature flag, we would only + // be accessing the provider multiple times but the end state of this + // instance and the returned flag value would be the same. + + markFlagAsAccessed(43, "enableNativeViewPropTransformations"); + + flagValue = currentProvider_->enableNativeViewPropTransformations(); + enableNativeViewPropTransformations_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::enableNetworkEventReporting() { auto flagValue = enableNetworkEventReporting_.load(); @@ -812,7 +830,7 @@ bool ReactNativeFeatureFlagsAccessor::enableNetworkEventReporting() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(43, "enableNetworkEventReporting"); + markFlagAsAccessed(44, "enableNetworkEventReporting"); flagValue = currentProvider_->enableNetworkEventReporting(); enableNetworkEventReporting_ = flagValue; @@ -830,7 +848,7 @@ bool ReactNativeFeatureFlagsAccessor::enablePreparedTextLayout() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(44, "enablePreparedTextLayout"); + markFlagAsAccessed(45, "enablePreparedTextLayout"); flagValue = currentProvider_->enablePreparedTextLayout(); enablePreparedTextLayout_ = flagValue; @@ -848,7 +866,7 @@ bool ReactNativeFeatureFlagsAccessor::enablePropsUpdateReconciliationAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(45, "enablePropsUpdateReconciliationAndroid"); + markFlagAsAccessed(46, "enablePropsUpdateReconciliationAndroid"); flagValue = currentProvider_->enablePropsUpdateReconciliationAndroid(); enablePropsUpdateReconciliationAndroid_ = flagValue; @@ -866,7 +884,7 @@ bool ReactNativeFeatureFlagsAccessor::enableSwiftUIBasedFilters() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(46, "enableSwiftUIBasedFilters"); + markFlagAsAccessed(47, "enableSwiftUIBasedFilters"); flagValue = currentProvider_->enableSwiftUIBasedFilters(); enableSwiftUIBasedFilters_ = flagValue; @@ -884,7 +902,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewCulling() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(47, "enableViewCulling"); + markFlagAsAccessed(48, "enableViewCulling"); flagValue = currentProvider_->enableViewCulling(); enableViewCulling_ = flagValue; @@ -902,7 +920,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecycling() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(48, "enableViewRecycling"); + markFlagAsAccessed(49, "enableViewRecycling"); flagValue = currentProvider_->enableViewRecycling(); enableViewRecycling_ = flagValue; @@ -920,7 +938,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForImage() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(49, "enableViewRecyclingForImage"); + markFlagAsAccessed(50, "enableViewRecyclingForImage"); flagValue = currentProvider_->enableViewRecyclingForImage(); enableViewRecyclingForImage_ = flagValue; @@ -938,7 +956,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForScrollView() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(50, "enableViewRecyclingForScrollView"); + markFlagAsAccessed(51, "enableViewRecyclingForScrollView"); flagValue = currentProvider_->enableViewRecyclingForScrollView(); enableViewRecyclingForScrollView_ = flagValue; @@ -956,7 +974,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForText() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(51, "enableViewRecyclingForText"); + markFlagAsAccessed(52, "enableViewRecyclingForText"); flagValue = currentProvider_->enableViewRecyclingForText(); enableViewRecyclingForText_ = flagValue; @@ -974,7 +992,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForView() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(52, "enableViewRecyclingForView"); + markFlagAsAccessed(53, "enableViewRecyclingForView"); flagValue = currentProvider_->enableViewRecyclingForView(); enableViewRecyclingForView_ = flagValue; @@ -992,7 +1010,7 @@ bool ReactNativeFeatureFlagsAccessor::enableVirtualViewContainerStateExperimenta // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(53, "enableVirtualViewContainerStateExperimental"); + markFlagAsAccessed(54, "enableVirtualViewContainerStateExperimental"); flagValue = currentProvider_->enableVirtualViewContainerStateExperimental(); enableVirtualViewContainerStateExperimental_ = flagValue; @@ -1010,7 +1028,7 @@ bool ReactNativeFeatureFlagsAccessor::enableVirtualViewDebugFeatures() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(54, "enableVirtualViewDebugFeatures"); + markFlagAsAccessed(55, "enableVirtualViewDebugFeatures"); flagValue = currentProvider_->enableVirtualViewDebugFeatures(); enableVirtualViewDebugFeatures_ = flagValue; @@ -1028,7 +1046,7 @@ bool ReactNativeFeatureFlagsAccessor::fixFindShadowNodeByTagRaceCondition() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(55, "fixFindShadowNodeByTagRaceCondition"); + markFlagAsAccessed(56, "fixFindShadowNodeByTagRaceCondition"); flagValue = currentProvider_->fixFindShadowNodeByTagRaceCondition(); fixFindShadowNodeByTagRaceCondition_ = flagValue; @@ -1046,7 +1064,7 @@ bool ReactNativeFeatureFlagsAccessor::fixMappingOfEventPrioritiesBetweenFabricAn // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(56, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); + markFlagAsAccessed(57, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); flagValue = currentProvider_->fixMappingOfEventPrioritiesBetweenFabricAndReact(); fixMappingOfEventPrioritiesBetweenFabricAndReact_ = flagValue; @@ -1064,7 +1082,7 @@ bool ReactNativeFeatureFlagsAccessor::fixTextClippingAndroid15useBoundsForWidth( // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(57, "fixTextClippingAndroid15useBoundsForWidth"); + markFlagAsAccessed(58, "fixTextClippingAndroid15useBoundsForWidth"); flagValue = currentProvider_->fixTextClippingAndroid15useBoundsForWidth(); fixTextClippingAndroid15useBoundsForWidth_ = flagValue; @@ -1082,7 +1100,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxAssertSingleHostState() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(58, "fuseboxAssertSingleHostState"); + markFlagAsAccessed(59, "fuseboxAssertSingleHostState"); flagValue = currentProvider_->fuseboxAssertSingleHostState(); fuseboxAssertSingleHostState_ = flagValue; @@ -1100,7 +1118,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxEnabledRelease() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(59, "fuseboxEnabledRelease"); + markFlagAsAccessed(60, "fuseboxEnabledRelease"); flagValue = currentProvider_->fuseboxEnabledRelease(); fuseboxEnabledRelease_ = flagValue; @@ -1118,7 +1136,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxNetworkInspectionEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(60, "fuseboxNetworkInspectionEnabled"); + markFlagAsAccessed(61, "fuseboxNetworkInspectionEnabled"); flagValue = currentProvider_->fuseboxNetworkInspectionEnabled(); fuseboxNetworkInspectionEnabled_ = flagValue; @@ -1136,7 +1154,7 @@ bool ReactNativeFeatureFlagsAccessor::hideOffscreenVirtualViewsOnIOS() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(61, "hideOffscreenVirtualViewsOnIOS"); + markFlagAsAccessed(62, "hideOffscreenVirtualViewsOnIOS"); flagValue = currentProvider_->hideOffscreenVirtualViewsOnIOS(); hideOffscreenVirtualViewsOnIOS_ = flagValue; @@ -1154,7 +1172,7 @@ bool ReactNativeFeatureFlagsAccessor::overrideBySynchronousMountPropsAtMountingA // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(62, "overrideBySynchronousMountPropsAtMountingAndroid"); + markFlagAsAccessed(63, "overrideBySynchronousMountPropsAtMountingAndroid"); flagValue = currentProvider_->overrideBySynchronousMountPropsAtMountingAndroid(); overrideBySynchronousMountPropsAtMountingAndroid_ = flagValue; @@ -1172,7 +1190,7 @@ bool ReactNativeFeatureFlagsAccessor::perfIssuesEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(63, "perfIssuesEnabled"); + markFlagAsAccessed(64, "perfIssuesEnabled"); flagValue = currentProvider_->perfIssuesEnabled(); perfIssuesEnabled_ = flagValue; @@ -1190,7 +1208,7 @@ bool ReactNativeFeatureFlagsAccessor::perfMonitorV2Enabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(64, "perfMonitorV2Enabled"); + markFlagAsAccessed(65, "perfMonitorV2Enabled"); flagValue = currentProvider_->perfMonitorV2Enabled(); perfMonitorV2Enabled_ = flagValue; @@ -1208,7 +1226,7 @@ double ReactNativeFeatureFlagsAccessor::preparedTextCacheSize() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(65, "preparedTextCacheSize"); + markFlagAsAccessed(66, "preparedTextCacheSize"); flagValue = currentProvider_->preparedTextCacheSize(); preparedTextCacheSize_ = flagValue; @@ -1226,7 +1244,7 @@ bool ReactNativeFeatureFlagsAccessor::preventShadowTreeCommitExhaustion() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(66, "preventShadowTreeCommitExhaustion"); + markFlagAsAccessed(67, "preventShadowTreeCommitExhaustion"); flagValue = currentProvider_->preventShadowTreeCommitExhaustion(); preventShadowTreeCommitExhaustion_ = flagValue; @@ -1244,7 +1262,7 @@ bool ReactNativeFeatureFlagsAccessor::shouldPressibilityUseW3CPointerEventsForHo // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(67, "shouldPressibilityUseW3CPointerEventsForHover"); + markFlagAsAccessed(68, "shouldPressibilityUseW3CPointerEventsForHover"); flagValue = currentProvider_->shouldPressibilityUseW3CPointerEventsForHover(); shouldPressibilityUseW3CPointerEventsForHover_ = flagValue; @@ -1262,7 +1280,7 @@ bool ReactNativeFeatureFlagsAccessor::shouldTriggerResponderTransferOnScrollAndr // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(68, "shouldTriggerResponderTransferOnScrollAndroid"); + markFlagAsAccessed(69, "shouldTriggerResponderTransferOnScrollAndroid"); flagValue = currentProvider_->shouldTriggerResponderTransferOnScrollAndroid(); shouldTriggerResponderTransferOnScrollAndroid_ = flagValue; @@ -1280,7 +1298,7 @@ bool ReactNativeFeatureFlagsAccessor::skipActivityIdentityAssertionOnHostPause() // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(69, "skipActivityIdentityAssertionOnHostPause"); + markFlagAsAccessed(70, "skipActivityIdentityAssertionOnHostPause"); flagValue = currentProvider_->skipActivityIdentityAssertionOnHostPause(); skipActivityIdentityAssertionOnHostPause_ = flagValue; @@ -1298,7 +1316,7 @@ bool ReactNativeFeatureFlagsAccessor::syncAndroidClipToPaddingWithOverflow() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(70, "syncAndroidClipToPaddingWithOverflow"); + markFlagAsAccessed(71, "syncAndroidClipToPaddingWithOverflow"); flagValue = currentProvider_->syncAndroidClipToPaddingWithOverflow(); syncAndroidClipToPaddingWithOverflow_ = flagValue; @@ -1316,7 +1334,7 @@ bool ReactNativeFeatureFlagsAccessor::traceTurboModulePromiseRejectionsOnAndroid // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(71, "traceTurboModulePromiseRejectionsOnAndroid"); + markFlagAsAccessed(72, "traceTurboModulePromiseRejectionsOnAndroid"); flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid(); traceTurboModulePromiseRejectionsOnAndroid_ = flagValue; @@ -1334,7 +1352,7 @@ bool ReactNativeFeatureFlagsAccessor::updateRuntimeShadowNodeReferencesOnCommit( // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(72, "updateRuntimeShadowNodeReferencesOnCommit"); + markFlagAsAccessed(73, "updateRuntimeShadowNodeReferencesOnCommit"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommit(); updateRuntimeShadowNodeReferencesOnCommit_ = flagValue; @@ -1352,7 +1370,7 @@ bool ReactNativeFeatureFlagsAccessor::updateRuntimeShadowNodeReferencesOnCommitT // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(73, "updateRuntimeShadowNodeReferencesOnCommitThread"); + markFlagAsAccessed(74, "updateRuntimeShadowNodeReferencesOnCommitThread"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommitThread(); updateRuntimeShadowNodeReferencesOnCommitThread_ = flagValue; @@ -1370,7 +1388,7 @@ bool ReactNativeFeatureFlagsAccessor::useAlwaysAvailableJSErrorHandling() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(74, "useAlwaysAvailableJSErrorHandling"); + markFlagAsAccessed(75, "useAlwaysAvailableJSErrorHandling"); flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling(); useAlwaysAvailableJSErrorHandling_ = flagValue; @@ -1388,7 +1406,7 @@ bool ReactNativeFeatureFlagsAccessor::useFabricInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(75, "useFabricInterop"); + markFlagAsAccessed(76, "useFabricInterop"); flagValue = currentProvider_->useFabricInterop(); useFabricInterop_ = flagValue; @@ -1406,7 +1424,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeViewConfigsInBridgelessMode() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(76, "useNativeViewConfigsInBridgelessMode"); + markFlagAsAccessed(77, "useNativeViewConfigsInBridgelessMode"); flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode(); useNativeViewConfigsInBridgelessMode_ = flagValue; @@ -1424,7 +1442,7 @@ bool ReactNativeFeatureFlagsAccessor::useNestedScrollViewAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(77, "useNestedScrollViewAndroid"); + markFlagAsAccessed(78, "useNestedScrollViewAndroid"); flagValue = currentProvider_->useNestedScrollViewAndroid(); useNestedScrollViewAndroid_ = flagValue; @@ -1442,7 +1460,7 @@ bool ReactNativeFeatureFlagsAccessor::useSharedAnimatedBackend() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(78, "useSharedAnimatedBackend"); + markFlagAsAccessed(79, "useSharedAnimatedBackend"); flagValue = currentProvider_->useSharedAnimatedBackend(); useSharedAnimatedBackend_ = flagValue; @@ -1460,7 +1478,7 @@ bool ReactNativeFeatureFlagsAccessor::useTraitHiddenOnAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(79, "useTraitHiddenOnAndroid"); + markFlagAsAccessed(80, "useTraitHiddenOnAndroid"); flagValue = currentProvider_->useTraitHiddenOnAndroid(); useTraitHiddenOnAndroid_ = flagValue; @@ -1478,7 +1496,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModuleInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(80, "useTurboModuleInterop"); + markFlagAsAccessed(81, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = flagValue; @@ -1496,7 +1514,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModules() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(81, "useTurboModules"); + markFlagAsAccessed(82, "useTurboModules"); flagValue = currentProvider_->useTurboModules(); useTurboModules_ = flagValue; @@ -1514,7 +1532,7 @@ bool ReactNativeFeatureFlagsAccessor::useUnorderedMapInDifferentiator() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(82, "useUnorderedMapInDifferentiator"); + markFlagAsAccessed(83, "useUnorderedMapInDifferentiator"); flagValue = currentProvider_->useUnorderedMapInDifferentiator(); useUnorderedMapInDifferentiator_ = flagValue; @@ -1532,7 +1550,7 @@ double ReactNativeFeatureFlagsAccessor::viewCullingOutsetRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(83, "viewCullingOutsetRatio"); + markFlagAsAccessed(84, "viewCullingOutsetRatio"); flagValue = currentProvider_->viewCullingOutsetRatio(); viewCullingOutsetRatio_ = flagValue; @@ -1550,7 +1568,7 @@ bool ReactNativeFeatureFlagsAccessor::viewTransitionEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(84, "viewTransitionEnabled"); + markFlagAsAccessed(85, "viewTransitionEnabled"); flagValue = currentProvider_->viewTransitionEnabled(); viewTransitionEnabled_ = flagValue; @@ -1568,7 +1586,7 @@ double ReactNativeFeatureFlagsAccessor::virtualViewPrerenderRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(85, "virtualViewPrerenderRatio"); + markFlagAsAccessed(86, "virtualViewPrerenderRatio"); flagValue = currentProvider_->virtualViewPrerenderRatio(); virtualViewPrerenderRatio_ = flagValue; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h index 454d483bf89b..754cf0afa06e 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<63a0714c9c06697555b9b66f2bd77288>> */ /** @@ -75,6 +75,7 @@ class ReactNativeFeatureFlagsAccessor { bool enableMainQueueCoordinatorOnIOS(); bool enableModuleArgumentNSNullConversionIOS(); bool enableNativeCSSParsing(); + bool enableNativeViewPropTransformations(); bool enableNetworkEventReporting(); bool enablePreparedTextLayout(); bool enablePropsUpdateReconciliationAndroid(); @@ -129,7 +130,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 86> accessedFeatureFlags_; + std::array, 87> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> cdpInteractionMetricsEnabled_; @@ -174,6 +175,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> enableMainQueueCoordinatorOnIOS_; std::atomic> enableModuleArgumentNSNullConversionIOS_; std::atomic> enableNativeCSSParsing_; + std::atomic> enableNativeViewPropTransformations_; std::atomic> enableNetworkEventReporting_; std::atomic> enablePreparedTextLayout_; std::atomic> enablePropsUpdateReconciliationAndroid_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index 1807361bc1fa..aa3675116ca4 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<613758ace44bd00d1156d05f365f1119>> */ /** @@ -199,6 +199,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return false; } + bool enableNativeViewPropTransformations() override { + return false; + } + bool enableNetworkEventReporting() override { return true; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index 9b51cd8ea334..97a94b58dcb2 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<0ed8edbf386bc66719f23b2514d05074>> */ /** @@ -432,6 +432,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::enableNativeCSSParsing(); } + bool enableNativeViewPropTransformations() override { + auto value = values_["enableNativeViewPropTransformations"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::enableNativeViewPropTransformations(); + } + bool enableNetworkEventReporting() override { auto value = values_["enableNetworkEventReporting"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index 0c6e9ae47a47..b9cc111cd84a 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<37ac0b0a8ecb4ee585cd6c0d1b14bfce>> */ /** @@ -68,6 +68,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool enableMainQueueCoordinatorOnIOS() = 0; virtual bool enableModuleArgumentNSNullConversionIOS() = 0; virtual bool enableNativeCSSParsing() = 0; + virtual bool enableNativeViewPropTransformations() = 0; virtual bool enableNetworkEventReporting() = 0; virtual bool enablePreparedTextLayout() = 0; virtual bool enablePropsUpdateReconciliationAndroid() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index bb1057121cec..701e31305c76 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<11d5240e0fd4bce14794689d7521cda3>> */ /** @@ -259,6 +259,11 @@ bool NativeReactNativeFeatureFlags::enableNativeCSSParsing( return ReactNativeFeatureFlags::enableNativeCSSParsing(); } +bool NativeReactNativeFeatureFlags::enableNativeViewPropTransformations( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::enableNativeViewPropTransformations(); +} + bool NativeReactNativeFeatureFlags::enableNetworkEventReporting( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::enableNetworkEventReporting(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index 8e919af8df5a..72a9b5568d17 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<567578eba7383f26c5d745c57d110e61>> + * @generated SignedSource<> */ /** @@ -122,6 +122,8 @@ class NativeReactNativeFeatureFlags bool enableNativeCSSParsing(jsi::Runtime& runtime); + bool enableNativeViewPropTransformations(jsi::Runtime& runtime); + bool enableNetworkEventReporting(jsi::Runtime& runtime); bool enablePreparedTextLayout(jsi::Runtime& runtime); diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityProps.cpp index cc39827f8eff..9bf1bb2b65b8 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityProps.cpp @@ -15,6 +15,24 @@ namespace facebook::react { +static AccessibilityLabelledBy parseCommaSeparatedList(const std::string& str) { + AccessibilityLabelledBy result; + size_t pos = 0; + while (pos < str.size()) { + auto commaPos = str.find(',', pos); + if (commaPos == std::string::npos) { + commaPos = str.size(); + } + auto start = str.find_first_not_of(' ', pos); + if (start < commaPos) { + auto end = str.find_last_not_of(' ', commaPos - 1); + result.value.push_back(str.substr(start, end - start + 1)); + } + pos = commaPos + 1; + } + return result; +} + AccessibilityProps::AccessibilityProps( const PropsParserContext& context, const AccessibilityProps& sourceProps, @@ -216,7 +234,14 @@ AccessibilityProps::AccessibilityProps( rawProps, "testID", sourceProps.testId, - "")) { + "")), + canonicalAccessibilityLabel_(sourceProps.canonicalAccessibilityLabel_), + canonicalAccessibilityLiveRegion_( + sourceProps.canonicalAccessibilityLiveRegion_), + canonicalImportantForAccessibility_( + sourceProps.canonicalImportantForAccessibility_), + canonicalAccessibilityElementsHidden_( + sourceProps.canonicalAccessibilityElementsHidden_) { // It is a (severe!) perf deoptimization to request props out-of-order. // Thus, since we need to request the same prop twice here // (accessibilityRole) we "must" do them subsequently here to prevent @@ -254,6 +279,244 @@ AccessibilityProps::AccessibilityProps( } else { fromRawValue(context, *precedentRoleValue, accessibilityTraits); } + + if (ReactNativeFeatureFlags::enableNativeViewPropTransformations()) { + static auto defaults = AccessibilityProps{}; + + // Update canonical values from explicit props if present. + // These track the user-set accessibility* prop values separately + // from any aria-* override, so we can restore them when an + // aria-* alias is cleared. + auto* explicitLabel = rawProps.at("accessibilityLabel", nullptr, nullptr); + if (explicitLabel != nullptr) { + canonicalAccessibilityLabel_ = explicitLabel->hasValue() + ? accessibilityLabel + : defaults.accessibilityLabel; + } + + auto* explicitLiveRegion = + rawProps.at("accessibilityLiveRegion", nullptr, nullptr); + if (explicitLiveRegion != nullptr) { + canonicalAccessibilityLiveRegion_ = explicitLiveRegion->hasValue() + ? accessibilityLiveRegion + : defaults.accessibilityLiveRegion; + } + + auto* explicitIFA = + rawProps.at("importantForAccessibility", nullptr, nullptr); + if (explicitIFA != nullptr) { + canonicalImportantForAccessibility_ = explicitIFA->hasValue() + ? importantForAccessibility + : defaults.importantForAccessibility; + } + + auto* explicitAEH = + rawProps.at("accessibilityElementsHidden", nullptr, nullptr); + if (explicitAEH != nullptr) { + canonicalAccessibilityElementsHidden_ = explicitAEH->hasValue() + ? accessibilityElementsHidden + : defaults.accessibilityElementsHidden; + } + + // aria-label -> accessibilityLabel + auto* ariaLabel = rawProps.at("aria-label", nullptr, nullptr); + if (ariaLabel != nullptr) { + if (ariaLabel->hasValue()) { + fromRawValue(context, *ariaLabel, accessibilityLabel); + } else { + accessibilityLabel = canonicalAccessibilityLabel_; + } + } + + // aria-labelledby -> accessibilityLabelledBy (comma-split string -> + // array) + auto* ariaLabelledBy = rawProps.at("aria-labelledby", nullptr, nullptr); + if (ariaLabelledBy != nullptr) { + if (ariaLabelledBy->hasValue()) { + if (ariaLabelledBy->hasType()) { + accessibilityLabelledBy = + parseCommaSeparatedList((std::string)*ariaLabelledBy); + } else { + fromRawValue(context, *ariaLabelledBy, accessibilityLabelledBy); + } + } else { + accessibilityLabelledBy = defaults.accessibilityLabelledBy; + } + } + + // aria-live -> accessibilityLiveRegion (map "off" -> None) + auto* ariaLive = rawProps.at("aria-live", nullptr, nullptr); + if (ariaLive != nullptr) { + if (ariaLive->hasValue()) { + if (ariaLive->hasType()) { + auto str = (std::string)*ariaLive; + if (str == "off") { + accessibilityLiveRegion = AccessibilityLiveRegion::None; + } else { + fromRawValue(context, *ariaLive, accessibilityLiveRegion); + } + } + } else { + accessibilityLiveRegion = canonicalAccessibilityLiveRegion_; + } + } + + // aria-hidden -> accessibilityElementsHidden + + // importantForAccessibility + auto* ariaHidden = rawProps.at("aria-hidden", nullptr, nullptr); + if (ariaHidden != nullptr) { + if (ariaHidden->hasValue()) { + fromRawValue(context, *ariaHidden, accessibilityElementsHidden); + if (accessibilityElementsHidden) { + importantForAccessibility = + ImportantForAccessibility::NoHideDescendants; + } else { + importantForAccessibility = canonicalImportantForAccessibility_; + } + } else { + accessibilityElementsHidden = canonicalAccessibilityElementsHidden_; + importantForAccessibility = canonicalImportantForAccessibility_; + } + } + + // aria-busy -> accessibilityState.busy + auto* ariaBusy = rawProps.at("aria-busy", nullptr, nullptr); + if (ariaBusy != nullptr) { + if (ariaBusy->hasValue()) { + if (!accessibilityState.has_value()) { + accessibilityState = AccessibilityState{}; + } + fromRawValue(context, *ariaBusy, accessibilityState->busy); + } else { + if (accessibilityState.has_value()) { + accessibilityState->busy = AccessibilityState{}.busy; + } + } + } + + // aria-checked -> accessibilityState.checked + auto* ariaChecked = rawProps.at("aria-checked", nullptr, nullptr); + if (ariaChecked != nullptr) { + if (ariaChecked->hasValue()) { + if (!accessibilityState.has_value()) { + accessibilityState = AccessibilityState{}; + } + if (ariaChecked->hasType()) { + if ((std::string)*ariaChecked == "mixed") { + accessibilityState->checked = AccessibilityState::Mixed; + } + } else if (ariaChecked->hasType()) { + accessibilityState->checked = (bool)*ariaChecked + ? AccessibilityState::Checked + : AccessibilityState::Unchecked; + } + } else { + if (accessibilityState.has_value()) { + accessibilityState->checked = AccessibilityState{}.checked; + } + } + } + + // aria-disabled -> accessibilityState.disabled + auto* ariaDisabled = rawProps.at("aria-disabled", nullptr, nullptr); + if (ariaDisabled != nullptr) { + if (ariaDisabled->hasValue()) { + if (!accessibilityState.has_value()) { + accessibilityState = AccessibilityState{}; + } + fromRawValue(context, *ariaDisabled, accessibilityState->disabled); + } else { + if (accessibilityState.has_value()) { + accessibilityState->disabled = AccessibilityState{}.disabled; + } + } + } + + // aria-expanded -> accessibilityState.expanded + auto* ariaExpanded = rawProps.at("aria-expanded", nullptr, nullptr); + if (ariaExpanded != nullptr) { + if (ariaExpanded->hasValue()) { + if (!accessibilityState.has_value()) { + accessibilityState = AccessibilityState{}; + } + fromRawValue(context, *ariaExpanded, accessibilityState->expanded); + } else { + if (accessibilityState.has_value()) { + accessibilityState->expanded = AccessibilityState{}.expanded; + } + } + } + + // aria-selected -> accessibilityState.selected + auto* ariaSelected = rawProps.at("aria-selected", nullptr, nullptr); + if (ariaSelected != nullptr) { + if (ariaSelected->hasValue()) { + if (!accessibilityState.has_value()) { + accessibilityState = AccessibilityState{}; + } + fromRawValue(context, *ariaSelected, accessibilityState->selected); + } else { + if (accessibilityState.has_value()) { + accessibilityState->selected = AccessibilityState{}.selected; + } + } + } + + // If all aria-state fields have been reset to defaults, clear the + // optional entirely so the view reports no accessibilityState. + if (accessibilityState.has_value() && + *accessibilityState == AccessibilityState{}) { + accessibilityState = std::nullopt; + } + + // aria-valuemax -> accessibilityValue.max + auto* ariaValueMax = rawProps.at("aria-valuemax", nullptr, nullptr); + if (ariaValueMax != nullptr) { + if (ariaValueMax->hasValue()) { + if (ariaValueMax->hasType()) { + accessibilityValue.max = (int)*ariaValueMax; + } + } else { + accessibilityValue.max = std::nullopt; + } + } + + // aria-valuemin -> accessibilityValue.min + auto* ariaValueMin = rawProps.at("aria-valuemin", nullptr, nullptr); + if (ariaValueMin != nullptr) { + if (ariaValueMin->hasValue()) { + if (ariaValueMin->hasType()) { + accessibilityValue.min = (int)*ariaValueMin; + } + } else { + accessibilityValue.min = std::nullopt; + } + } + + // aria-valuenow -> accessibilityValue.now + auto* ariaValueNow = rawProps.at("aria-valuenow", nullptr, nullptr); + if (ariaValueNow != nullptr) { + if (ariaValueNow->hasValue()) { + if (ariaValueNow->hasType()) { + accessibilityValue.now = (int)*ariaValueNow; + } + } else { + accessibilityValue.now = std::nullopt; + } + } + + // aria-valuetext -> accessibilityValue.text + auto* ariaValueText = rawProps.at("aria-valuetext", nullptr, nullptr); + if (ariaValueText != nullptr) { + if (ariaValueText->hasValue()) { + if (ariaValueText->hasType()) { + accessibilityValue.text = (std::string)*ariaValueText; + } + } else { + accessibilityValue.text = std::nullopt; + } + } + } } } @@ -267,26 +530,56 @@ void AccessibilityProps::setProp( switch (hash) { RAW_SET_PROP_SWITCH_CASE_BASIC(accessible); RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityState); - RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityLabel); - RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityOrder); - RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityLabelledBy); - RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityHint); - RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityLanguage); - RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityShowsLargeContentViewer); - RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityLargeContentTitle); - RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityValue); - RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityActions); - RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityViewIsModal); - RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityElementsHidden); - RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityIgnoresInvertColors); - RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityRespondsToUserInteraction); - RAW_SET_PROP_SWITCH_CASE_BASIC(onAccessibilityTap); - RAW_SET_PROP_SWITCH_CASE_BASIC(onAccessibilityMagicTap); - RAW_SET_PROP_SWITCH_CASE_BASIC(onAccessibilityEscape); - RAW_SET_PROP_SWITCH_CASE_BASIC(onAccessibilityAction); - RAW_SET_PROP_SWITCH_CASE_BASIC(importantForAccessibility); - RAW_SET_PROP_SWITCH_CASE_BASIC(role); - RAW_SET_PROP_SWITCH_CASE(testId, "testID"); + case CONSTEXPR_RAW_PROPS_KEY_HASH("accessibilityLabel"): { + fromRawValue( + context, value, accessibilityLabel, defaults.accessibilityLabel); + canonicalAccessibilityLabel_ = accessibilityLabel; + return; + } + RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityOrder); + RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityLabelledBy); + case CONSTEXPR_RAW_PROPS_KEY_HASH("accessibilityLiveRegion"): { + fromRawValue( + context, + value, + accessibilityLiveRegion, + defaults.accessibilityLiveRegion); + canonicalAccessibilityLiveRegion_ = accessibilityLiveRegion; + return; + } + RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityHint); + RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityLanguage); + RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityShowsLargeContentViewer); + RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityLargeContentTitle); + RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityValue); + RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityActions); + RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityViewIsModal); + case CONSTEXPR_RAW_PROPS_KEY_HASH("accessibilityElementsHidden"): { + fromRawValue( + context, + value, + accessibilityElementsHidden, + defaults.accessibilityElementsHidden); + canonicalAccessibilityElementsHidden_ = accessibilityElementsHidden; + return; + } + RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityIgnoresInvertColors); + RAW_SET_PROP_SWITCH_CASE_BASIC(accessibilityRespondsToUserInteraction); + RAW_SET_PROP_SWITCH_CASE_BASIC(onAccessibilityTap); + RAW_SET_PROP_SWITCH_CASE_BASIC(onAccessibilityMagicTap); + RAW_SET_PROP_SWITCH_CASE_BASIC(onAccessibilityEscape); + RAW_SET_PROP_SWITCH_CASE_BASIC(onAccessibilityAction); + case CONSTEXPR_RAW_PROPS_KEY_HASH("importantForAccessibility"): { + fromRawValue( + context, + value, + importantForAccessibility, + defaults.importantForAccessibility); + canonicalImportantForAccessibility_ = importantForAccessibility; + return; + } + RAW_SET_PROP_SWITCH_CASE_BASIC(role); + RAW_SET_PROP_SWITCH_CASE(testId, "testID"); case CONSTEXPR_RAW_PROPS_KEY_HASH("accessibilityRole"): { AccessibilityTraits traits = AccessibilityTraits::None; std::string roleString; @@ -299,6 +592,223 @@ void AccessibilityProps::setProp( accessibilityRole = roleString; return; } + case CONSTEXPR_RAW_PROPS_KEY_HASH("aria-label"): { + if (!ReactNativeFeatureFlags::enableNativeViewPropTransformations()) { + return; + } + if (value.hasValue()) { + fromRawValue(context, value, accessibilityLabel); + } else { + accessibilityLabel = canonicalAccessibilityLabel_; + } + return; + } + case CONSTEXPR_RAW_PROPS_KEY_HASH("aria-labelledby"): { + if (!ReactNativeFeatureFlags::enableNativeViewPropTransformations()) { + return; + } + if (value.hasValue()) { + if (value.hasType()) { + accessibilityLabelledBy = parseCommaSeparatedList((std::string)value); + } else { + fromRawValue(context, value, accessibilityLabelledBy); + } + } else { + accessibilityLabelledBy = defaults.accessibilityLabelledBy; + } + return; + } + case CONSTEXPR_RAW_PROPS_KEY_HASH("aria-live"): { + if (!ReactNativeFeatureFlags::enableNativeViewPropTransformations()) { + return; + } + if (value.hasValue()) { + if (value.hasType()) { + auto str = (std::string)value; + if (str == "off") { + accessibilityLiveRegion = AccessibilityLiveRegion::None; + } else { + fromRawValue(context, value, accessibilityLiveRegion); + } + } + } else { + accessibilityLiveRegion = canonicalAccessibilityLiveRegion_; + } + return; + } + case CONSTEXPR_RAW_PROPS_KEY_HASH("aria-hidden"): { + if (!ReactNativeFeatureFlags::enableNativeViewPropTransformations()) { + return; + } + if (value.hasValue()) { + fromRawValue(context, value, accessibilityElementsHidden); + if (accessibilityElementsHidden) { + importantForAccessibility = + ImportantForAccessibility::NoHideDescendants; + } else { + importantForAccessibility = canonicalImportantForAccessibility_; + } + } else { + accessibilityElementsHidden = canonicalAccessibilityElementsHidden_; + importantForAccessibility = canonicalImportantForAccessibility_; + } + return; + } + case CONSTEXPR_RAW_PROPS_KEY_HASH("aria-busy"): { + if (!ReactNativeFeatureFlags::enableNativeViewPropTransformations()) { + return; + } + if (value.hasValue()) { + if (!accessibilityState.has_value()) { + accessibilityState = AccessibilityState{}; + } + fromRawValue(context, value, accessibilityState->busy); + } else { + if (accessibilityState.has_value()) { + accessibilityState->busy = AccessibilityState{}.busy; + if (*accessibilityState == AccessibilityState{}) { + accessibilityState = std::nullopt; + } + } + } + return; + } + case CONSTEXPR_RAW_PROPS_KEY_HASH("aria-checked"): { + if (!ReactNativeFeatureFlags::enableNativeViewPropTransformations()) { + return; + } + if (value.hasValue()) { + if (!accessibilityState.has_value()) { + accessibilityState = AccessibilityState{}; + } + if (value.hasType()) { + if ((std::string)value == "mixed") { + accessibilityState->checked = AccessibilityState::Mixed; + } + } else if (value.hasType()) { + accessibilityState->checked = (bool)value + ? AccessibilityState::Checked + : AccessibilityState::Unchecked; + } + } else { + if (accessibilityState.has_value()) { + accessibilityState->checked = AccessibilityState{}.checked; + if (*accessibilityState == AccessibilityState{}) { + accessibilityState = std::nullopt; + } + } + } + return; + } + case CONSTEXPR_RAW_PROPS_KEY_HASH("aria-disabled"): { + if (!ReactNativeFeatureFlags::enableNativeViewPropTransformations()) { + return; + } + if (value.hasValue()) { + if (!accessibilityState.has_value()) { + accessibilityState = AccessibilityState{}; + } + fromRawValue(context, value, accessibilityState->disabled); + } else { + if (accessibilityState.has_value()) { + accessibilityState->disabled = AccessibilityState{}.disabled; + if (*accessibilityState == AccessibilityState{}) { + accessibilityState = std::nullopt; + } + } + } + return; + } + case CONSTEXPR_RAW_PROPS_KEY_HASH("aria-expanded"): { + if (!ReactNativeFeatureFlags::enableNativeViewPropTransformations()) { + return; + } + if (value.hasValue()) { + if (!accessibilityState.has_value()) { + accessibilityState = AccessibilityState{}; + } + fromRawValue(context, value, accessibilityState->expanded); + } else { + if (accessibilityState.has_value()) { + accessibilityState->expanded = AccessibilityState{}.expanded; + if (*accessibilityState == AccessibilityState{}) { + accessibilityState = std::nullopt; + } + } + } + return; + } + case CONSTEXPR_RAW_PROPS_KEY_HASH("aria-selected"): { + if (!ReactNativeFeatureFlags::enableNativeViewPropTransformations()) { + return; + } + if (value.hasValue()) { + if (!accessibilityState.has_value()) { + accessibilityState = AccessibilityState{}; + } + fromRawValue(context, value, accessibilityState->selected); + } else { + if (accessibilityState.has_value()) { + accessibilityState->selected = AccessibilityState{}.selected; + if (*accessibilityState == AccessibilityState{}) { + accessibilityState = std::nullopt; + } + } + } + return; + } + case CONSTEXPR_RAW_PROPS_KEY_HASH("aria-valuemax"): { + if (!ReactNativeFeatureFlags::enableNativeViewPropTransformations()) { + return; + } + if (value.hasValue()) { + if (value.hasType()) { + accessibilityValue.max = (int)value; + } + } else { + accessibilityValue.max = std::nullopt; + } + return; + } + case CONSTEXPR_RAW_PROPS_KEY_HASH("aria-valuemin"): { + if (!ReactNativeFeatureFlags::enableNativeViewPropTransformations()) { + return; + } + if (value.hasValue()) { + if (value.hasType()) { + accessibilityValue.min = (int)value; + } + } else { + accessibilityValue.min = std::nullopt; + } + return; + } + case CONSTEXPR_RAW_PROPS_KEY_HASH("aria-valuenow"): { + if (!ReactNativeFeatureFlags::enableNativeViewPropTransformations()) { + return; + } + if (value.hasValue()) { + if (value.hasType()) { + accessibilityValue.now = (int)value; + } + } else { + accessibilityValue.now = std::nullopt; + } + return; + } + case CONSTEXPR_RAW_PROPS_KEY_HASH("aria-valuetext"): { + if (!ReactNativeFeatureFlags::enableNativeViewPropTransformations()) { + return; + } + if (value.hasValue()) { + if (value.hasType()) { + accessibilityValue.text = (std::string)value; + } + } else { + accessibilityValue.text = std::nullopt; + } + return; + } } } diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityProps.h b/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityProps.h index c664b8461e2f..625b71707174 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityProps.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/AccessibilityProps.h @@ -62,6 +62,15 @@ class AccessibilityProps { #if RN_DEBUG_STRING_CONVERTIBLE SharedDebugStringConvertibleList getDebugProps() const; #endif + + private: + // Canonical prop values for overlap detection between aria-* and + // accessibility* props. These track the values from the explicit + // accessibility* props, separate from any aria-* override. + std::string canonicalAccessibilityLabel_; + AccessibilityLiveRegion canonicalAccessibilityLiveRegion_{AccessibilityLiveRegion::None}; + ImportantForAccessibility canonicalImportantForAccessibility_{ImportantForAccessibility::Auto}; + bool canonicalAccessibilityElementsHidden_{false}; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp index 3237f54cbdce..b47d7f1beec6 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp @@ -141,7 +141,23 @@ HostPlatformViewProps::HostPlatformViewProps( rawProps, "nextFocusUp", sourceProps.nextFocusUp, - {})) {} + {})) { + if (!ReactNativeFeatureFlags::enableCppPropsIteratorSetter()) { + if (ReactNativeFeatureFlags::enableNativeViewPropTransformations()) { + // tabIndex -> focusable + auto* tabIndexValue = rawProps.at("tabIndex", nullptr, nullptr); + if (tabIndexValue != nullptr) { + if (tabIndexValue->hasValue()) { + int tabIndex = 0; + fromRawValue(context, *tabIndexValue, tabIndex); + focusable = tabIndex == 0; + } else { + focusable = {}; + } + } + } + } +} #define VIEW_EVENT_CASE(eventType) \ case CONSTEXPR_RAW_PROPS_KEY_HASH("on" #eventType): { \ @@ -181,6 +197,19 @@ void HostPlatformViewProps::setProp( RAW_SET_PROP_SWITCH_CASE_BASIC(nextFocusLeft); RAW_SET_PROP_SWITCH_CASE_BASIC(nextFocusRight); RAW_SET_PROP_SWITCH_CASE_BASIC(nextFocusUp); + case CONSTEXPR_RAW_PROPS_KEY_HASH("tabIndex"): { + if (!ReactNativeFeatureFlags::enableNativeViewPropTransformations()) { + return; + } + if (value.hasValue()) { + int tabIndex = 0; + fromRawValue(context, value, tabIndex); + focusable = tabIndex == 0; + } else { + focusable = defaults.focusable; + } + return; + } } } diff --git a/packages/react-native/ReactCommon/react/renderer/core/Props.cpp b/packages/react-native/ReactCommon/react/renderer/core/Props.cpp index 1f86cfb08665..3b7d126bb6d7 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/Props.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/Props.cpp @@ -32,6 +32,20 @@ void Props::initialize( nativeId = ReactNativeFeatureFlags::enableCppPropsIteratorSetter() ? sourceProps.nativeId : convertRawProp(context, rawProps, "nativeID", sourceProps.nativeId, {}); + + if (!ReactNativeFeatureFlags::enableCppPropsIteratorSetter()) { + if (ReactNativeFeatureFlags::enableNativeViewPropTransformations()) { + // id -> nativeId + auto* idValue = rawProps.at("id", nullptr, nullptr); + if (idValue != nullptr) { + if (idValue->hasValue()) { + fromRawValue(context, *idValue, nativeId); + } else { + nativeId = {}; + } + } + } + } #ifdef RN_SERIALIZABLE_STATE if (!ReactNativeFeatureFlags::enableExclusivePropsUpdateAndroid()) { initializeDynamicProps(sourceProps, rawProps, filterObjectKeys); @@ -48,6 +62,12 @@ void Props::setProp( case CONSTEXPR_RAW_PROPS_KEY_HASH("nativeID"): fromRawValue(context, value, nativeId, {}); return; + case CONSTEXPR_RAW_PROPS_KEY_HASH("id"): + if (!ReactNativeFeatureFlags::enableNativeViewPropTransformations()) { + return; + } + fromRawValue(context, value, nativeId, {}); + return; } } diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index 957047b5bcba..4a3fafab275d 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -507,6 +507,17 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'none', }, + enableNativeViewPropTransformations: { + defaultValue: false, + metadata: { + dateAdded: '2026-02-26', + description: + 'When enabled, View.js passes aria-*, id, and tabIndex props directly to native, relying on C++ prop parsing instead of JS-side transformations.', + expectedReleaseValue: true, + purpose: 'experimentation', + }, + ossReleaseStage: 'none', + }, enableNetworkEventReporting: { defaultValue: true, metadata: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index ed2200b5c999..824e851550d6 100644 --- a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<6c49874d2e5f3f9dfa6c64945026f4b7>> + * @generated SignedSource<<5072c12941f3173be27ac422320c3caa>> * @flow strict * @noformat */ @@ -91,6 +91,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{ enableMainQueueCoordinatorOnIOS: Getter, enableModuleArgumentNSNullConversionIOS: Getter, enableNativeCSSParsing: Getter, + enableNativeViewPropTransformations: Getter, enableNetworkEventReporting: Getter, enablePreparedTextLayout: Getter, enablePropsUpdateReconciliationAndroid: Getter, @@ -377,6 +378,10 @@ export const enableModuleArgumentNSNullConversionIOS: Getter = createNa * Parse CSS strings using the Fabric CSS parser instead of ViewConfig processing */ export const enableNativeCSSParsing: Getter = createNativeFlagGetter('enableNativeCSSParsing', false); +/** + * When enabled, View.js passes aria-*, id, and tabIndex props directly to native, relying on C++ prop parsing instead of JS-side transformations. + */ +export const enableNativeViewPropTransformations: Getter = createNativeFlagGetter('enableNativeViewPropTransformations', false); /** * Enable network event reporting hooks in each native platform through `NetworkReporter` (Web Perf APIs + CDP). This flag should be combined with `fuseboxNetworkInspectionEnabled` to enable Network CDP debugging. */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index 6c01f3b3bba5..1cc964cdf0f2 100644 --- a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<3fd91ee5bd5c4d53241b3946d8e0b056>> * @flow strict * @noformat */ @@ -68,6 +68,7 @@ export interface Spec extends TurboModule { +enableMainQueueCoordinatorOnIOS?: () => boolean; +enableModuleArgumentNSNullConversionIOS?: () => boolean; +enableNativeCSSParsing?: () => boolean; + +enableNativeViewPropTransformations?: () => boolean; +enableNetworkEventReporting?: () => boolean; +enablePreparedTextLayout?: () => boolean; +enablePropsUpdateReconciliationAndroid?: () => boolean;