Skip to content

Commit 8639725

Browse files
javachefacebook-github-bot
authored andcommitted
Skip native prop metadata for delegate-backed managers (#59296)
Summary: Add the default-off `skipNativePropsForViewManagerWithDelegateAndroid` feature flag. When enabled, `ViewManager.getNativeProps()` returns an empty map for managers with a non-null, non-generic delegate, avoiding unused manager and shadow-node prop metadata generation. Return a fresh mutable map so subclasses can safely extend `super.getNativeProps()`. The constants helper already omits `NativeProps` for an empty map; commands, events and custom constants are preserved. Null and generic delegates keep the existing metadata path, including its legacy-architecture shadow-node handling. The flag must only be enabled when delegate-backed components use static view configs rather than runtime native prop metadata. NativeProps queries reuse `getOrCreateViewManagerDelegate()`, sharing the same delegate with prop updates and commands. Make its lazy initialisation thread-safe with a volatile cache and double-checked synchronisation, leaving warm reads lock-free. Document that initialisation may occur on the JavaScript thread when this flag is enabled. Regenerate all feature-flag bindings and cover enabled/disabled paths, generic and null delegates, marker-only managers, preserved constants, subclass prop extensions, independent mutable maps, existing-cache reuse and concurrent metadata/command initialisation. Changelog: [Android][Changed] - Add an opt-in optimisation to skip native prop metadata for delegate-backed view managers using static view configs. Reviewed By: andrewdacenko Differential Revision: D126630163
1 parent caf9cbc commit 8639725

22 files changed

Lines changed: 485 additions & 42 deletions

‎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<<a89ede36c4631b7a7709899858a7f9c9>>
7+
* @generated SignedSource<<251ba53185bdf10220816b4c239ef6d5>>
88
*/
99

1010
/**
@@ -510,6 +510,12 @@ public object ReactNativeFeatureFlags {
510510
@JvmStatic
511511
public fun skipActivityIdentityAssertionOnHostPause(): Boolean = accessor.skipActivityIdentityAssertionOnHostPause()
512512

513+
/**
514+
* Skip native prop metadata generation on Android for view managers with custom delegates. Requires their components to use static view configs.
515+
*/
516+
@JvmStatic
517+
public fun skipNativePropsForViewManagerWithDelegateAndroid(): Boolean = accessor.skipNativePropsForViewManagerWithDelegateAndroid()
518+
513519
/**
514520
* When a node changes, the differ leaves out its children that are unchanged in both trees instead of describing and comparing them.
515521
*/

‎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<<cd3b4f6a7fb4e616d3f56ab8c9eb9658>>
7+
* @generated SignedSource<<1bb0fee5a17f70b91f33850d97479cc1>>
88
*/
99

1010
/**
@@ -100,6 +100,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
100100
private var shouldPressibilityUseW3CPointerEventsForHoverCache: Boolean? = null
101101
private var shouldTriggerResponderTransferOnScrollAndroidCache: Boolean? = null
102102
private var skipActivityIdentityAssertionOnHostPauseCache: Boolean? = null
103+
private var skipNativePropsForViewManagerWithDelegateAndroidCache: Boolean? = null
103104
private var skipUnchangedChildrenInDifferentiatorCache: Boolean? = null
104105
private var syncAndroidClipBoundsWithOverflowCache: Boolean? = null
105106
private var traceTurboModulePromiseRejectionsOnAndroidCache: Boolean? = null
@@ -838,6 +839,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
838839
return cached
839840
}
840841

842+
override fun skipNativePropsForViewManagerWithDelegateAndroid(): Boolean {
843+
var cached = skipNativePropsForViewManagerWithDelegateAndroidCache
844+
if (cached == null) {
845+
cached = ReactNativeFeatureFlagsCxxInterop.skipNativePropsForViewManagerWithDelegateAndroid()
846+
skipNativePropsForViewManagerWithDelegateAndroidCache = cached
847+
}
848+
return cached
849+
}
850+
841851
override fun skipUnchangedChildrenInDifferentiator(): Boolean {
842852
var cached = skipUnchangedChildrenInDifferentiatorCache
843853
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<<6d86d4adbc261c9271b5bed72bd12dcf>>
7+
* @generated SignedSource<<8686d1f71576023f0cd22aca37b2e8ba>>
88
*/
99

1010
/**
@@ -188,6 +188,8 @@ public object ReactNativeFeatureFlagsCxxInterop {
188188

189189
@DoNotStrip @JvmStatic public external fun skipActivityIdentityAssertionOnHostPause(): Boolean
190190

191+
@DoNotStrip @JvmStatic public external fun skipNativePropsForViewManagerWithDelegateAndroid(): Boolean
192+
191193
@DoNotStrip @JvmStatic public external fun skipUnchangedChildrenInDifferentiator(): Boolean
192194

193195
@DoNotStrip @JvmStatic public external fun syncAndroidClipBoundsWithOverflow(): 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<<a1368426dd823245c54b4312ae7b14e6>>
7+
* @generated SignedSource<<09945e5bc899957424ce20441022830c>>
88
*/
99

1010
/**
@@ -183,6 +183,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
183183

184184
override fun skipActivityIdentityAssertionOnHostPause(): Boolean = false
185185

186+
override fun skipNativePropsForViewManagerWithDelegateAndroid(): Boolean = false
187+
186188
override fun skipUnchangedChildrenInDifferentiator(): Boolean = false
187189

188190
override fun syncAndroidClipBoundsWithOverflow(): 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<<c305274be2fc6720b6c0eb75e78bd6b0>>
7+
* @generated SignedSource<<12dd3ead4fa496af154f770cabc252f1>>
88
*/
99

1010
/**
@@ -104,6 +104,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
104104
private var shouldPressibilityUseW3CPointerEventsForHoverCache: Boolean? = null
105105
private var shouldTriggerResponderTransferOnScrollAndroidCache: Boolean? = null
106106
private var skipActivityIdentityAssertionOnHostPauseCache: Boolean? = null
107+
private var skipNativePropsForViewManagerWithDelegateAndroidCache: Boolean? = null
107108
private var skipUnchangedChildrenInDifferentiatorCache: Boolean? = null
108109
private var syncAndroidClipBoundsWithOverflowCache: Boolean? = null
109110
private var traceTurboModulePromiseRejectionsOnAndroidCache: Boolean? = null
@@ -922,6 +923,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
922923
return cached
923924
}
924925

926+
override fun skipNativePropsForViewManagerWithDelegateAndroid(): Boolean {
927+
var cached = skipNativePropsForViewManagerWithDelegateAndroidCache
928+
if (cached == null) {
929+
cached = currentProvider.skipNativePropsForViewManagerWithDelegateAndroid()
930+
accessedFeatureFlags.add("skipNativePropsForViewManagerWithDelegateAndroid")
931+
skipNativePropsForViewManagerWithDelegateAndroidCache = cached
932+
}
933+
return cached
934+
}
935+
925936
override fun skipUnchangedChildrenInDifferentiator(): Boolean {
926937
var cached = skipUnchangedChildrenInDifferentiatorCache
927938
if (cached == null) {

‎packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.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<<da45e41e9a58711ee3b68a38965938fb>>
7+
* @generated SignedSource<<8227961bd327cc2851437304b21dee6a>>
88
*/
99

1010
/**
@@ -183,6 +183,8 @@ public interface ReactNativeFeatureFlagsProvider {
183183

184184
@DoNotStrip public fun skipActivityIdentityAssertionOnHostPause(): Boolean
185185

186+
@DoNotStrip public fun skipNativePropsForViewManagerWithDelegateAndroid(): Boolean
187+
186188
@DoNotStrip public fun skipUnchangedChildrenInDifferentiator(): Boolean
187189

188190
@DoNotStrip public fun syncAndroidClipBoundsWithOverflow(): Boolean

‎packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManager.java‎

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public abstract class ViewManager<T extends View, C extends ReactShadowNode<?>>
4646

4747
private static final String TAG = "ViewManager";
4848

49-
private @Nullable ViewManagerDelegate<T> mDelegate = null;
49+
private volatile @Nullable ViewManagerDelegate<T> mDelegate = null;
5050

5151
/**
5252
* For View recycling: we store a Stack of unused, dead Views. This is null by default, and when
@@ -113,6 +113,11 @@ public void updateProperties(@NonNull T viewToUpdate, ReactStylesDiffMap props)
113113
* <p>By default, this methods returns a generic {@link ViewManagerDelegate} which uses {@link
114114
* ViewManagerSetter} to apply property updates.
115115
*
116+
* <p>When {@code skipNativePropsForViewManagerWithDelegateAndroid} is enabled, lazy delegate
117+
* initialisation may also occur during native prop metadata generation on the JavaScript thread.
118+
* The returned delegate is cached and shared with subsequent prop updates and commands. Overrides
119+
* must be safe to initialise on either thread.
120+
*
116121
* @return an instance of {@link ViewManagerDelegate} if the props of the view managed by this
117122
* view manager should be set via this delegate
118123
*/
@@ -131,8 +136,13 @@ protected ViewManagerDelegate<T> getDelegate() {
131136
private ViewManagerDelegate<T> getOrCreateViewManagerDelegate() {
132137
ViewManagerDelegate<T> delegate = mDelegate;
133138
if (delegate == null) {
134-
delegate = getDelegate();
135-
mDelegate = delegate;
139+
synchronized (this) {
140+
delegate = mDelegate;
141+
if (delegate == null) {
142+
delegate = getDelegate();
143+
mDelegate = delegate;
144+
}
145+
}
136146
}
137147
return delegate;
138148
}
@@ -402,10 +412,21 @@ public void receiveCommand(@NonNull T view, String commandId, ReadableArray args
402412
}
403413

404414
/**
405-
* Returns a {@link Map<String, String>} representing the native props of the view manager. The
406-
* Map contains the names (key) and types (value) of the ViewManager's props.
415+
* Returns a mutable {@link Map<String, String>} representing the native props of the view
416+
* manager. The Map contains the names (key) and types (value) of the ViewManager's props.
417+
*
418+
* <p>When {@code skipNativePropsForViewManagerWithDelegateAndroid} is enabled, managers with a
419+
* non-generic delegate return an empty map. Enable this only when those components use static
420+
* view configs rather than deriving their valid attributes from native prop metadata.
407421
*/
408422
public Map<String, String> getNativeProps() {
423+
if (ReactNativeFeatureFlags.skipNativePropsForViewManagerWithDelegateAndroid()) {
424+
ViewManagerDelegate<T> delegate = getOrCreateViewManagerDelegate();
425+
if (delegate != null
426+
&& !(delegate instanceof ViewManagerPropertyUpdater.GenericViewManagerDelegate)) {
427+
return new HashMap<>();
428+
}
429+
}
409430
if (ReactBuildConfig.UNSTABLE_ENABLE_MINIFY_LEGACY_ARCHITECTURE
410431
&& ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
411432
// TODO: review if we need to check fabricInterop here

‎packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp‎

Lines changed: 15 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<<944781e7a547e601f0637869a3a3ec5e>>
7+
* @generated SignedSource<<223e38a173e3762fa0034ed47f96a84d>>
88
*/
99

1010
/**
@@ -519,6 +519,12 @@ class ReactNativeFeatureFlagsJavaProvider
519519
return method(javaProvider_);
520520
}
521521

522+
bool skipNativePropsForViewManagerWithDelegateAndroid() override {
523+
static const auto method =
524+
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("skipNativePropsForViewManagerWithDelegateAndroid");
525+
return method(javaProvider_);
526+
}
527+
522528
bool skipUnchangedChildrenInDifferentiator() override {
523529
static const auto method =
524530
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("skipUnchangedChildrenInDifferentiator");
@@ -1025,6 +1031,11 @@ bool JReactNativeFeatureFlagsCxxInterop::skipActivityIdentityAssertionOnHostPaus
10251031
return ReactNativeFeatureFlags::skipActivityIdentityAssertionOnHostPause();
10261032
}
10271033

1034+
bool JReactNativeFeatureFlagsCxxInterop::skipNativePropsForViewManagerWithDelegateAndroid(
1035+
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
1036+
return ReactNativeFeatureFlags::skipNativePropsForViewManagerWithDelegateAndroid();
1037+
}
1038+
10281039
bool JReactNativeFeatureFlagsCxxInterop::skipUnchangedChildrenInDifferentiator(
10291040
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
10301041
return ReactNativeFeatureFlags::skipUnchangedChildrenInDifferentiator();
@@ -1381,6 +1392,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
13811392
makeNativeMethod(
13821393
"skipActivityIdentityAssertionOnHostPause",
13831394
JReactNativeFeatureFlagsCxxInterop::skipActivityIdentityAssertionOnHostPause),
1395+
makeNativeMethod(
1396+
"skipNativePropsForViewManagerWithDelegateAndroid",
1397+
JReactNativeFeatureFlagsCxxInterop::skipNativePropsForViewManagerWithDelegateAndroid),
13841398
makeNativeMethod(
13851399
"skipUnchangedChildrenInDifferentiator",
13861400
JReactNativeFeatureFlagsCxxInterop::skipUnchangedChildrenInDifferentiator),

‎packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h‎

Lines changed: 4 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<<ad9275a6afb74520bec7d446faafd1bc>>
7+
* @generated SignedSource<<fbe2d936391c1d25f9741753c952dfdb>>
88
*/
99

1010
/**
@@ -270,6 +270,9 @@ class JReactNativeFeatureFlagsCxxInterop
270270
static bool skipActivityIdentityAssertionOnHostPause(
271271
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
272272

273+
static bool skipNativePropsForViewManagerWithDelegateAndroid(
274+
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
275+
273276
static bool skipUnchangedChildrenInDifferentiator(
274277
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
275278

0 commit comments

Comments
 (0)