Skip to content

Commit 5f2e5a5

Browse files
sammy-SCfacebook-github-bot
authored andcommitted
Move View.js prop transformations to C++ prop parsing (facebook#55853)
Summary: Changelog: [Internal] `View.js` previously transformed `aria-*` props to `accessibility*` equivalents, `id` to `nativeID`, and `tabIndex` to `focusable` in JavaScript before passing to the native component. This diff moves all those transformations to the C++ prop parsing layer (`AccessibilityProps::setProp`, `Props::setProp`, and `HostPlatformViewProps::setProp`), making the JS wrapper thinner and faster. ## Benchmark Results (View vs ViewNativeComponent) | Component | Before (median ns) | After (median ns) | Change | | View | 666,750 | 546,917 | **-18%** | This change is gated for safe rollout. Reviewed By: NickGerleman Differential Revision: D94219577
1 parent 2d81437 commit 5f2e5a5

27 files changed

+690
-143
lines changed

packages/react-native/Libraries/Components/View/View.js

Lines changed: 82 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import type {ViewProps} from './ViewPropTypes';
1212

13+
import * as ReactNativeFeatureFlags from '../../../src/private/featureflags/ReactNativeFeatureFlags';
1314
import TextAncestorContext from '../../Text/TextAncestorContext';
1415
import ViewNativeComponent from './ViewNativeComponent';
1516
import * as React from 'react';
@@ -28,96 +29,100 @@ component View(
2829
) {
2930
const hasTextAncestor = use(TextAncestorContext);
3031

31-
const {
32-
accessibilityState,
33-
accessibilityValue,
34-
'aria-busy': ariaBusy,
35-
'aria-checked': ariaChecked,
36-
'aria-disabled': ariaDisabled,
37-
'aria-expanded': ariaExpanded,
38-
'aria-hidden': ariaHidden,
39-
'aria-label': ariaLabel,
40-
'aria-labelledby': ariaLabelledBy,
41-
'aria-live': ariaLive,
42-
'aria-selected': ariaSelected,
43-
'aria-valuemax': ariaValueMax,
44-
'aria-valuemin': ariaValueMin,
45-
'aria-valuenow': ariaValueNow,
46-
'aria-valuetext': ariaValueText,
47-
id,
48-
tabIndex,
49-
...otherProps
50-
} = props;
51-
52-
// Since we destructured props, we can now treat it as mutable
53-
const processedProps = otherProps as {...ViewProps};
54-
55-
const parsedAriaLabelledBy = ariaLabelledBy?.split(/\s*,\s*/g);
56-
if (parsedAriaLabelledBy !== undefined) {
57-
processedProps.accessibilityLabelledBy = parsedAriaLabelledBy;
58-
}
32+
let resolvedProps = props;
33+
if (!ReactNativeFeatureFlags.enableNativeViewPropTransformations()) {
34+
const {
35+
accessibilityState,
36+
accessibilityValue,
37+
'aria-busy': ariaBusy,
38+
'aria-checked': ariaChecked,
39+
'aria-disabled': ariaDisabled,
40+
'aria-expanded': ariaExpanded,
41+
'aria-hidden': ariaHidden,
42+
'aria-label': ariaLabel,
43+
'aria-labelledby': ariaLabelledBy,
44+
'aria-live': ariaLive,
45+
'aria-selected': ariaSelected,
46+
'aria-valuemax': ariaValueMax,
47+
'aria-valuemin': ariaValueMin,
48+
'aria-valuenow': ariaValueNow,
49+
'aria-valuetext': ariaValueText,
50+
id,
51+
tabIndex,
52+
...otherProps
53+
} = props;
54+
55+
const processedProps = otherProps as {...ViewProps};
56+
57+
const parsedAriaLabelledBy = ariaLabelledBy?.split(/\s*,\s*/g);
58+
if (parsedAriaLabelledBy !== undefined) {
59+
processedProps.accessibilityLabelledBy = parsedAriaLabelledBy;
60+
}
5961

60-
if (ariaLabel !== undefined) {
61-
processedProps.accessibilityLabel = ariaLabel;
62-
}
62+
if (ariaLabel !== undefined) {
63+
processedProps.accessibilityLabel = ariaLabel;
64+
}
6365

64-
if (ariaLive !== undefined) {
65-
processedProps.accessibilityLiveRegion =
66-
ariaLive === 'off' ? 'none' : ariaLive;
67-
}
66+
if (ariaLive !== undefined) {
67+
processedProps.accessibilityLiveRegion =
68+
ariaLive === 'off' ? 'none' : ariaLive;
69+
}
6870

69-
if (ariaHidden !== undefined) {
70-
processedProps.accessibilityElementsHidden = ariaHidden;
71-
if (ariaHidden === true) {
72-
processedProps.importantForAccessibility = 'no-hide-descendants';
71+
if (ariaHidden !== undefined) {
72+
processedProps.accessibilityElementsHidden = ariaHidden;
73+
if (ariaHidden === true) {
74+
processedProps.importantForAccessibility = 'no-hide-descendants';
75+
}
7376
}
74-
}
7577

76-
if (id !== undefined) {
77-
processedProps.nativeID = id;
78-
}
78+
if (id !== undefined) {
79+
processedProps.nativeID = id;
80+
}
7981

80-
if (tabIndex !== undefined) {
81-
processedProps.focusable = !tabIndex;
82-
}
82+
if (tabIndex !== undefined) {
83+
processedProps.focusable = !tabIndex;
84+
}
8385

84-
if (
85-
accessibilityState != null ||
86-
ariaBusy != null ||
87-
ariaChecked != null ||
88-
ariaDisabled != null ||
89-
ariaExpanded != null ||
90-
ariaSelected != null
91-
) {
92-
processedProps.accessibilityState = {
93-
busy: ariaBusy ?? accessibilityState?.busy,
94-
checked: ariaChecked ?? accessibilityState?.checked,
95-
disabled: ariaDisabled ?? accessibilityState?.disabled,
96-
expanded: ariaExpanded ?? accessibilityState?.expanded,
97-
selected: ariaSelected ?? accessibilityState?.selected,
98-
};
99-
}
86+
if (
87+
accessibilityState != null ||
88+
ariaBusy != null ||
89+
ariaChecked != null ||
90+
ariaDisabled != null ||
91+
ariaExpanded != null ||
92+
ariaSelected != null
93+
) {
94+
processedProps.accessibilityState = {
95+
busy: ariaBusy ?? accessibilityState?.busy,
96+
checked: ariaChecked ?? accessibilityState?.checked,
97+
disabled: ariaDisabled ?? accessibilityState?.disabled,
98+
expanded: ariaExpanded ?? accessibilityState?.expanded,
99+
selected: ariaSelected ?? accessibilityState?.selected,
100+
};
101+
}
102+
103+
if (
104+
accessibilityValue != null ||
105+
ariaValueMax != null ||
106+
ariaValueMin != null ||
107+
ariaValueNow != null ||
108+
ariaValueText != null
109+
) {
110+
processedProps.accessibilityValue = {
111+
max: ariaValueMax ?? accessibilityValue?.max,
112+
min: ariaValueMin ?? accessibilityValue?.min,
113+
now: ariaValueNow ?? accessibilityValue?.now,
114+
text: ariaValueText ?? accessibilityValue?.text,
115+
};
116+
}
100117

101-
if (
102-
accessibilityValue != null ||
103-
ariaValueMax != null ||
104-
ariaValueMin != null ||
105-
ariaValueNow != null ||
106-
ariaValueText != null
107-
) {
108-
processedProps.accessibilityValue = {
109-
max: ariaValueMax ?? accessibilityValue?.max,
110-
min: ariaValueMin ?? accessibilityValue?.min,
111-
now: ariaValueNow ?? accessibilityValue?.now,
112-
text: ariaValueText ?? accessibilityValue?.text,
113-
};
118+
resolvedProps = processedProps;
114119
}
115120

116121
const actualView =
117122
ref == null ? (
118-
<ViewNativeComponent {...processedProps} />
123+
<ViewNativeComponent {...resolvedProps} />
119124
) : (
120-
<ViewNativeComponent {...processedProps} ref={ref} />
125+
<ViewNativeComponent {...resolvedProps} ref={ref} />
121126
);
122127

123128
if (hasTextAncestor) {

packages/react-native/Libraries/Components/View/__tests__/View-itest.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* LICENSE file in the root directory of this source tree.
66
*
77
* @flow strict-local
8-
* @fantom_flags enableNativeCSSParsing:*
8+
* @fantom_flags enableNativeCSSParsing:* enableNativeViewPropTransformations:*
99
* @format
1010
*/
1111

@@ -403,7 +403,11 @@ describe('<View>', () => {
403403
root.render(<View aria-hidden={true} collapsable={false} />);
404404
});
405405

406-
expect(root.getRenderedOutput().toJSX()).toEqual(
406+
expect(
407+
root
408+
.getRenderedOutput({props: ['importantForAccessibility']})
409+
.toJSX(),
410+
).toEqual(
407411
<rn-view importantForAccessibility="no-hide-descendants" />,
408412
);
409413
});

packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ const validAttributesForNonEventProps = {
214214
renderToHardwareTextureAndroid: true,
215215
testID: true,
216216
nativeID: true,
217+
id: true,
217218
accessibilityLabelledBy: true,
218219
accessibilityLabel: true,
219220
accessibilityHint: true,
@@ -226,6 +227,19 @@ const validAttributesForNonEventProps = {
226227
experimental_accessibilityOrder: true,
227228
importantForAccessibility: true,
228229
screenReaderFocusable: true,
230+
'aria-busy': true,
231+
'aria-checked': true,
232+
'aria-disabled': true,
233+
'aria-expanded': true,
234+
'aria-hidden': true,
235+
'aria-label': true,
236+
'aria-labelledby': true,
237+
'aria-live': true,
238+
'aria-selected': true,
239+
'aria-valuemax': true,
240+
'aria-valuemin': true,
241+
'aria-valuenow': true,
242+
'aria-valuetext': true,
229243
role: true,
230244
rotation: true,
231245
scaleX: true,
@@ -368,6 +382,7 @@ const validAttributesForNonEventProps = {
368382
borderBlockEndColor: colorAttribute,
369383
borderBlockStartColor: colorAttribute,
370384
focusable: true,
385+
tabIndex: true,
371386
backfaceVisibility: true,
372387
} as const;
373388

packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,22 @@ const validAttributesForNonEventProps = {
218218
transformOrigin: true,
219219
accessibilityRole: true,
220220
accessibilityState: true,
221+
'aria-busy': true,
222+
'aria-checked': true,
223+
'aria-disabled': true,
224+
'aria-expanded': true,
225+
'aria-hidden': true,
226+
'aria-label': true,
227+
'aria-labelledby': true,
228+
'aria-live': true,
229+
'aria-selected': true,
230+
'aria-valuemax': true,
231+
'aria-valuemin': true,
232+
'aria-valuenow': true,
233+
'aria-valuetext': true,
234+
tabIndex: true,
221235
nativeID: true,
236+
id: true,
222237
pointerEvents: true,
223238
removeClippedSubviews: true,
224239
role: true,

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<156d4f5f35037184b6fc61ff1d856028>>
7+
* @generated SignedSource<<c0397bf7655a9312a2ba3913a1cdffba>>
88
*/
99

1010
/**
@@ -282,6 +282,12 @@ public object ReactNativeFeatureFlags {
282282
@JvmStatic
283283
public fun enableNativeCSSParsing(): Boolean = accessor.enableNativeCSSParsing()
284284

285+
/**
286+
* When enabled, View.js passes aria-*, id, and tabIndex props directly to native, relying on C++ prop parsing instead of JS-side transformations.
287+
*/
288+
@JvmStatic
289+
public fun enableNativeViewPropTransformations(): Boolean = accessor.enableNativeViewPropTransformations()
290+
285291
/**
286292
* 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.
287293
*/

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<0875d5e54d884a26d37bb4eb2acc57d5>>
7+
* @generated SignedSource<<2a8a856b7377787e68d961e8458a6c43>>
88
*/
99

1010
/**
@@ -62,6 +62,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
6262
private var enableMainQueueCoordinatorOnIOSCache: Boolean? = null
6363
private var enableModuleArgumentNSNullConversionIOSCache: Boolean? = null
6464
private var enableNativeCSSParsingCache: Boolean? = null
65+
private var enableNativeViewPropTransformationsCache: Boolean? = null
6566
private var enableNetworkEventReportingCache: Boolean? = null
6667
private var enablePreparedTextLayoutCache: Boolean? = null
6768
private var enablePropsUpdateReconciliationAndroidCache: Boolean? = null
@@ -484,6 +485,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
484485
return cached
485486
}
486487

488+
override fun enableNativeViewPropTransformations(): Boolean {
489+
var cached = enableNativeViewPropTransformationsCache
490+
if (cached == null) {
491+
cached = ReactNativeFeatureFlagsCxxInterop.enableNativeViewPropTransformations()
492+
enableNativeViewPropTransformationsCache = cached
493+
}
494+
return cached
495+
}
496+
487497
override fun enableNetworkEventReporting(): Boolean {
488498
var cached = enableNetworkEventReportingCache
489499
if (cached == null) {

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<948a9beebe2ff00791a03455eb774eee>>
7+
* @generated SignedSource<<d814a10acb2930742c7c72b76456add9>>
88
*/
99

1010
/**
@@ -112,6 +112,8 @@ public object ReactNativeFeatureFlagsCxxInterop {
112112

113113
@DoNotStrip @JvmStatic public external fun enableNativeCSSParsing(): Boolean
114114

115+
@DoNotStrip @JvmStatic public external fun enableNativeViewPropTransformations(): Boolean
116+
115117
@DoNotStrip @JvmStatic public external fun enableNetworkEventReporting(): Boolean
116118

117119
@DoNotStrip @JvmStatic public external fun enablePreparedTextLayout(): Boolean

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<89c61520177334f93c65ff92c2fc74a6>>
7+
* @generated SignedSource<<0c4b222b00f570d13ff4161083d23770>>
88
*/
99

1010
/**
@@ -107,6 +107,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
107107

108108
override fun enableNativeCSSParsing(): Boolean = false
109109

110+
override fun enableNativeViewPropTransformations(): Boolean = false
111+
110112
override fun enableNetworkEventReporting(): Boolean = true
111113

112114
override fun enablePreparedTextLayout(): Boolean = false

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<669708c311abe9ffc8f7783219e2baad>>
7+
* @generated SignedSource<<d60ffbba04e5f5b2c3a1fb942b5e051e>>
88
*/
99

1010
/**
@@ -66,6 +66,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
6666
private var enableMainQueueCoordinatorOnIOSCache: Boolean? = null
6767
private var enableModuleArgumentNSNullConversionIOSCache: Boolean? = null
6868
private var enableNativeCSSParsingCache: Boolean? = null
69+
private var enableNativeViewPropTransformationsCache: Boolean? = null
6970
private var enableNetworkEventReportingCache: Boolean? = null
7071
private var enablePreparedTextLayoutCache: Boolean? = null
7172
private var enablePropsUpdateReconciliationAndroidCache: Boolean? = null
@@ -530,6 +531,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
530531
return cached
531532
}
532533

534+
override fun enableNativeViewPropTransformations(): Boolean {
535+
var cached = enableNativeViewPropTransformationsCache
536+
if (cached == null) {
537+
cached = currentProvider.enableNativeViewPropTransformations()
538+
accessedFeatureFlags.add("enableNativeViewPropTransformations")
539+
enableNativeViewPropTransformationsCache = cached
540+
}
541+
return cached
542+
}
543+
533544
override fun enableNetworkEventReporting(): Boolean {
534545
var cached = enableNetworkEventReportingCache
535546
if (cached == null) {

0 commit comments

Comments
 (0)