Skip to content

Commit fa0e3ad

Browse files
javachefacebook-github-bot
authored andcommitted
Skip native prop metadata for delegate-backed managers
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. 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, 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. Differential Revision: D126630163
1 parent 87c3e8f commit fa0e3ad

22 files changed

Lines changed: 424 additions & 39 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<<cfa7aa53731059247d858fd8fb6e914b>>
7+
* @generated SignedSource<<c96fb296a907fe2b264b3a1a79ada34a>>
88
*/
99

1010
/**
@@ -498,6 +498,12 @@ public object ReactNativeFeatureFlags {
498498
@JvmStatic
499499
public fun skipActivityIdentityAssertionOnHostPause(): Boolean = accessor.skipActivityIdentityAssertionOnHostPause()
500500

501+
/**
502+
* Skip native prop metadata generation on Android for view managers with custom delegates. Requires their components to use static view configs.
503+
*/
504+
@JvmStatic
505+
public fun skipNativePropsForViewManagerWithDelegateAndroid(): Boolean = accessor.skipNativePropsForViewManagerWithDelegateAndroid()
506+
501507
/**
502508
* Override getClipBounds on Android views to return the padding box when overflow is hidden
503509
*/

‎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<<71d0554dc45df7d5365f81b7ec8c3d5b>>
7+
* @generated SignedSource<<758c59473ba39a89b5db675c217bc690>>
88
*/
99

1010
/**
@@ -98,6 +98,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
9898
private var shouldPressibilityUseW3CPointerEventsForHoverCache: Boolean? = null
9999
private var shouldTriggerResponderTransferOnScrollAndroidCache: Boolean? = null
100100
private var skipActivityIdentityAssertionOnHostPauseCache: Boolean? = null
101+
private var skipNativePropsForViewManagerWithDelegateAndroidCache: Boolean? = null
101102
private var syncAndroidClipBoundsWithOverflowCache: Boolean? = null
102103
private var traceTurboModulePromiseRejectionsOnAndroidCache: Boolean? = null
103104
private var updateRuntimeShadowNodeReferencesOnCommitCache: Boolean? = null
@@ -817,6 +818,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
817818
return cached
818819
}
819820

821+
override fun skipNativePropsForViewManagerWithDelegateAndroid(): Boolean {
822+
var cached = skipNativePropsForViewManagerWithDelegateAndroidCache
823+
if (cached == null) {
824+
cached = ReactNativeFeatureFlagsCxxInterop.skipNativePropsForViewManagerWithDelegateAndroid()
825+
skipNativePropsForViewManagerWithDelegateAndroidCache = cached
826+
}
827+
return cached
828+
}
829+
820830
override fun syncAndroidClipBoundsWithOverflow(): Boolean {
821831
var cached = syncAndroidClipBoundsWithOverflowCache
822832
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<<312cec99bd429d22e25f4e82d7fc4ee7>>
7+
* @generated SignedSource<<fd9a11754ecbf1aee959b43cb17d00a2>>
88
*/
99

1010
/**
@@ -184,6 +184,8 @@ public object ReactNativeFeatureFlagsCxxInterop {
184184

185185
@DoNotStrip @JvmStatic public external fun skipActivityIdentityAssertionOnHostPause(): Boolean
186186

187+
@DoNotStrip @JvmStatic public external fun skipNativePropsForViewManagerWithDelegateAndroid(): Boolean
188+
187189
@DoNotStrip @JvmStatic public external fun syncAndroidClipBoundsWithOverflow(): Boolean
188190

189191
@DoNotStrip @JvmStatic public external fun traceTurboModulePromiseRejectionsOnAndroid(): 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<<669f16e131d4a5ddc026ffecc6893575>>
7+
* @generated SignedSource<<d4d0349b560fe12cdcb91860e5f0359f>>
88
*/
99

1010
/**
@@ -179,6 +179,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
179179

180180
override fun skipActivityIdentityAssertionOnHostPause(): Boolean = false
181181

182+
override fun skipNativePropsForViewManagerWithDelegateAndroid(): Boolean = false
183+
182184
override fun syncAndroidClipBoundsWithOverflow(): Boolean = false
183185

184186
override fun traceTurboModulePromiseRejectionsOnAndroid(): 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<<ee4c0f9c1e7c6af3b0d15948ebc519b3>>
7+
* @generated SignedSource<<7f4c0ebf000e27e2bdb29d32a801cc80>>
88
*/
99

1010
/**
@@ -102,6 +102,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
102102
private var shouldPressibilityUseW3CPointerEventsForHoverCache: Boolean? = null
103103
private var shouldTriggerResponderTransferOnScrollAndroidCache: Boolean? = null
104104
private var skipActivityIdentityAssertionOnHostPauseCache: Boolean? = null
105+
private var skipNativePropsForViewManagerWithDelegateAndroidCache: Boolean? = null
105106
private var syncAndroidClipBoundsWithOverflowCache: Boolean? = null
106107
private var traceTurboModulePromiseRejectionsOnAndroidCache: Boolean? = null
107108
private var updateRuntimeShadowNodeReferencesOnCommitCache: Boolean? = null
@@ -899,6 +900,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
899900
return cached
900901
}
901902

903+
override fun skipNativePropsForViewManagerWithDelegateAndroid(): Boolean {
904+
var cached = skipNativePropsForViewManagerWithDelegateAndroidCache
905+
if (cached == null) {
906+
cached = currentProvider.skipNativePropsForViewManagerWithDelegateAndroid()
907+
accessedFeatureFlags.add("skipNativePropsForViewManagerWithDelegateAndroid")
908+
skipNativePropsForViewManagerWithDelegateAndroidCache = cached
909+
}
910+
return cached
911+
}
912+
902913
override fun syncAndroidClipBoundsWithOverflow(): Boolean {
903914
var cached = syncAndroidClipBoundsWithOverflowCache
904915
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<<18267103ef4f3eb22f23a15b808528c2>>
7+
* @generated SignedSource<<10d76cfc6241dbbb530c2cba99611c91>>
88
*/
99

1010
/**
@@ -179,6 +179,8 @@ public interface ReactNativeFeatureFlagsProvider {
179179

180180
@DoNotStrip public fun skipActivityIdentityAssertionOnHostPause(): Boolean
181181

182+
@DoNotStrip public fun skipNativePropsForViewManagerWithDelegateAndroid(): Boolean
183+
182184
@DoNotStrip public fun syncAndroidClipBoundsWithOverflow(): Boolean
183185

184186
@DoNotStrip public fun traceTurboModulePromiseRejectionsOnAndroid(): Boolean

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

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import com.facebook.react.uimanager.annotations.ReactPropGroup;
3131
import com.facebook.react.uimanager.annotations.ReactPropertyHolder;
3232
import com.facebook.yoga.YogaMeasureMode;
33+
import java.util.Collections;
3334
import java.util.HashMap;
3435
import java.util.Iterator;
3536
import java.util.Map;
@@ -46,7 +47,7 @@ public abstract class ViewManager<T extends View, C extends ReactShadowNode<?>>
4647

4748
private static final String TAG = "ViewManager";
4849

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

5152
/**
5253
* For View recycling: we store a Stack of unused, dead Views. This is null by default, and when
@@ -113,6 +114,11 @@ public void updateProperties(@NonNull T viewToUpdate, ReactStylesDiffMap props)
113114
* <p>By default, this methods returns a generic {@link ViewManagerDelegate} which uses {@link
114115
* ViewManagerSetter} to apply property updates.
115116
*
117+
* <p>When {@code skipNativePropsForViewManagerWithDelegateAndroid} is enabled, lazy delegate
118+
* initialisation may also occur during native prop metadata generation on the JavaScript thread.
119+
* The returned delegate is cached and shared with subsequent prop updates and commands. Overrides
120+
* must be safe to initialise on either thread.
121+
*
116122
* @return an instance of {@link ViewManagerDelegate} if the props of the view managed by this
117123
* view manager should be set via this delegate
118124
*/
@@ -131,8 +137,13 @@ protected ViewManagerDelegate<T> getDelegate() {
131137
private ViewManagerDelegate<T> getOrCreateViewManagerDelegate() {
132138
ViewManagerDelegate<T> delegate = mDelegate;
133139
if (delegate == null) {
134-
delegate = getDelegate();
135-
mDelegate = delegate;
140+
synchronized (this) {
141+
delegate = mDelegate;
142+
if (delegate == null) {
143+
delegate = getDelegate();
144+
mDelegate = delegate;
145+
}
146+
}
136147
}
137148
return delegate;
138149
}
@@ -404,8 +415,19 @@ public void receiveCommand(@NonNull T view, String commandId, ReadableArray args
404415
/**
405416
* Returns a {@link Map<String, String>} representing the native props of the view manager. The
406417
* Map contains the names (key) and types (value) of the ViewManager's props.
418+
*
419+
* <p>When {@code skipNativePropsForViewManagerWithDelegateAndroid} is enabled, managers with a
420+
* non-generic delegate return an empty map. Enable this only when those components use static
421+
* view configs rather than deriving their valid attributes from native prop metadata.
407422
*/
408423
public Map<String, String> getNativeProps() {
424+
if (ReactNativeFeatureFlags.skipNativePropsForViewManagerWithDelegateAndroid()) {
425+
ViewManagerDelegate<T> delegate = getOrCreateViewManagerDelegate();
426+
if (delegate != null
427+
&& !(delegate instanceof ViewManagerPropertyUpdater.GenericViewManagerDelegate)) {
428+
return Collections.emptyMap();
429+
}
430+
}
409431
if (ReactBuildConfig.UNSTABLE_ENABLE_MINIFY_LEGACY_ARCHITECTURE
410432
&& ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
411433
// 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<<568d6e4175fb51e09bc3d4441ffa9741>>
7+
* @generated SignedSource<<ec75566521a814c01401f86095e1ad73>>
88
*/
99

1010
/**
@@ -507,6 +507,12 @@ class ReactNativeFeatureFlagsJavaProvider
507507
return method(javaProvider_);
508508
}
509509

510+
bool skipNativePropsForViewManagerWithDelegateAndroid() override {
511+
static const auto method =
512+
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("skipNativePropsForViewManagerWithDelegateAndroid");
513+
return method(javaProvider_);
514+
}
515+
510516
bool syncAndroidClipBoundsWithOverflow() override {
511517
static const auto method =
512518
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("syncAndroidClipBoundsWithOverflow");
@@ -997,6 +1003,11 @@ bool JReactNativeFeatureFlagsCxxInterop::skipActivityIdentityAssertionOnHostPaus
9971003
return ReactNativeFeatureFlags::skipActivityIdentityAssertionOnHostPause();
9981004
}
9991005

1006+
bool JReactNativeFeatureFlagsCxxInterop::skipNativePropsForViewManagerWithDelegateAndroid(
1007+
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
1008+
return ReactNativeFeatureFlags::skipNativePropsForViewManagerWithDelegateAndroid();
1009+
}
1010+
10001011
bool JReactNativeFeatureFlagsCxxInterop::syncAndroidClipBoundsWithOverflow(
10011012
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
10021013
return ReactNativeFeatureFlags::syncAndroidClipBoundsWithOverflow();
@@ -1342,6 +1353,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
13421353
makeNativeMethod(
13431354
"skipActivityIdentityAssertionOnHostPause",
13441355
JReactNativeFeatureFlagsCxxInterop::skipActivityIdentityAssertionOnHostPause),
1356+
makeNativeMethod(
1357+
"skipNativePropsForViewManagerWithDelegateAndroid",
1358+
JReactNativeFeatureFlagsCxxInterop::skipNativePropsForViewManagerWithDelegateAndroid),
13451359
makeNativeMethod(
13461360
"syncAndroidClipBoundsWithOverflow",
13471361
JReactNativeFeatureFlagsCxxInterop::syncAndroidClipBoundsWithOverflow),

‎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<<2e0f936c003aafc456ab3f960ca4b2f3>>
7+
* @generated SignedSource<<ed562f74bbdb2b852383eef39cef1592>>
88
*/
99

1010
/**
@@ -264,6 +264,9 @@ class JReactNativeFeatureFlagsCxxInterop
264264
static bool skipActivityIdentityAssertionOnHostPause(
265265
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
266266

267+
static bool skipNativePropsForViewManagerWithDelegateAndroid(
268+
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
269+
267270
static bool syncAndroidClipBoundsWithOverflow(
268271
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
269272

0 commit comments

Comments
 (0)