diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 10730dda4a4b..46a95696a53f 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -6434,7 +6434,7 @@ public final class com/facebook/react/views/view/ReactDrawableHelper { public static final fun createDrawableFromJSDescription (Landroid/content/Context;Lcom/facebook/react/bridge/ReadableMap;)Landroid/graphics/drawable/Drawable; } -public class com/facebook/react/views/view/ReactViewGroup : android/view/ViewGroup, com/facebook/react/touch/ReactHitSlopView, com/facebook/react/touch/ReactInterceptingViewGroup, com/facebook/react/uimanager/ReactClippingViewGroup, com/facebook/react/uimanager/ReactOverflowViewWithInset, com/facebook/react/uimanager/ReactPointerEventsView { +public class com/facebook/react/views/view/ReactViewGroup : android/view/ViewGroup, com/facebook/react/touch/ReactHitSlopView, com/facebook/react/touch/ReactInterceptingViewGroup, com/facebook/react/uimanager/HasElevatedDescendantCache, com/facebook/react/uimanager/ReactClippingViewGroup, com/facebook/react/uimanager/ReactOverflowViewWithInset, com/facebook/react/uimanager/ReactPointerEventsView { public fun (Landroid/content/Context;)V public fun addChildrenForAccessibility (Ljava/util/ArrayList;)V public final fun cleanUpAxOrderListener ()V @@ -6455,6 +6455,7 @@ public class com/facebook/react/views/view/ReactViewGroup : android/view/ViewGro public fun getPointerEvents ()Lcom/facebook/react/uimanager/PointerEvents; public fun getRemoveClippedSubviews ()Z public fun hasOverlappingRendering ()Z + public fun invalidateElevatedDescendantCache ()Z protected fun onAttachedToWindow ()V protected fun onConfigurationChanged (Landroid/content/res/Configuration;)V public fun onHoverEvent (Landroid/view/MotionEvent;)Z 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 3408b7721a92..a7fca3aaf467 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<> + * @generated SignedSource<<7d7547b5f25dbe0d0e6722d2f2bf5baf>> */ /** @@ -102,6 +102,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun enableAccumulatedUpdatesInRawPropsAndroid(): Boolean = accessor.enableAccumulatedUpdatesInRawPropsAndroid() + /** + * When enabled, a View with reduced opacity that contains an elevated descendant is composited offscreen so the elevation shadow fades uniformly instead of rendering as banded per-primitive alpha. + */ + @JvmStatic + public fun enableAndroidAutoOffscreenCompositingForElevation(): Boolean = accessor.enableAndroidAutoOffscreenCompositingForElevation() + /** * Enables various optimizations throughout the path of measuring text on Android. */ 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 625d122b102d..747db742d6fa 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<> + * @generated SignedSource<> */ /** @@ -32,6 +32,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var disableViewPreallocationAndroidCache: Boolean? = null private var enableAccessibilityOrderCache: Boolean? = null private var enableAccumulatedUpdatesInRawPropsAndroidCache: Boolean? = null + private var enableAndroidAutoOffscreenCompositingForElevationCache: Boolean? = null private var enableAndroidTextMeasurementOptimizationsCache: Boolean? = null private var enableBridgelessArchitectureCache: Boolean? = null private var enableBufferedCallInvokerCache: Boolean? = null @@ -216,6 +217,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun enableAndroidAutoOffscreenCompositingForElevation(): Boolean { + var cached = enableAndroidAutoOffscreenCompositingForElevationCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.enableAndroidAutoOffscreenCompositingForElevation() + enableAndroidAutoOffscreenCompositingForElevationCache = cached + } + return cached + } + override fun enableAndroidTextMeasurementOptimizations(): Boolean { var cached = enableAndroidTextMeasurementOptimizationsCache 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 6ab4e2b8e8f6..fa8882f3f7f4 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<<8a0d1f134b175ada7550499896c09203>> + * @generated SignedSource<<7d6ce76213f08ecb356fa68e77edfe84>> */ /** @@ -52,6 +52,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun enableAccumulatedUpdatesInRawPropsAndroid(): Boolean + @DoNotStrip @JvmStatic public external fun enableAndroidAutoOffscreenCompositingForElevation(): Boolean + @DoNotStrip @JvmStatic public external fun enableAndroidTextMeasurementOptimizations(): Boolean @DoNotStrip @JvmStatic public external fun enableBridgelessArchitecture(): 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 c1c81a8d4109..b39ae04c511f 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<<891abee69433c105d062b45ed357fd5c>> + * @generated SignedSource<<0c228a0f53ced43d6744551a92d0ed86>> */ /** @@ -47,6 +47,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun enableAccumulatedUpdatesInRawPropsAndroid(): Boolean = false + override fun enableAndroidAutoOffscreenCompositingForElevation(): Boolean = false + override fun enableAndroidTextMeasurementOptimizations(): Boolean = false override fun enableBridgelessArchitecture(): Boolean = true 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 209ec0d5defb..ad6a4c2a5f90 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<<4c62625780d9f767714f8481c7496342>> + * @generated SignedSource<<37c9d46dfd98d868601390a9262f8d5a>> */ /** @@ -36,6 +36,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var disableViewPreallocationAndroidCache: Boolean? = null private var enableAccessibilityOrderCache: Boolean? = null private var enableAccumulatedUpdatesInRawPropsAndroidCache: Boolean? = null + private var enableAndroidAutoOffscreenCompositingForElevationCache: Boolean? = null private var enableAndroidTextMeasurementOptimizationsCache: Boolean? = null private var enableBridgelessArchitectureCache: Boolean? = null private var enableBufferedCallInvokerCache: Boolean? = null @@ -232,6 +233,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun enableAndroidAutoOffscreenCompositingForElevation(): Boolean { + var cached = enableAndroidAutoOffscreenCompositingForElevationCache + if (cached == null) { + cached = currentProvider.enableAndroidAutoOffscreenCompositingForElevation() + accessedFeatureFlags.add("enableAndroidAutoOffscreenCompositingForElevation") + enableAndroidAutoOffscreenCompositingForElevationCache = cached + } + return cached + } + override fun enableAndroidTextMeasurementOptimizations(): Boolean { var cached = enableAndroidTextMeasurementOptimizationsCache 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 05fc4e0b2bef..25cf6f42f310 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<> + * @generated SignedSource<<18ffa42c9d28304df99548a350b014ac>> */ /** @@ -47,6 +47,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun enableAccumulatedUpdatesInRawPropsAndroid(): Boolean + @DoNotStrip public fun enableAndroidAutoOffscreenCompositingForElevation(): Boolean + @DoNotStrip public fun enableAndroidTextMeasurementOptimizations(): Boolean @DoNotStrip public fun enableBridgelessArchitecture(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java index dd4198e4e50f..9affa257fa5f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java @@ -154,9 +154,6 @@ public BaseViewManager(@Nullable ReactApplicationContext reactContext) { view.setFocusable(false); view.setFocusableInTouchMode(false); - // https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-mainline-12.0.0_r96/core/java/android/view/View.java#5491 - view.setElevation(0); - // Predictably, alpha defaults to 1: // https://android.googlesource.com/platform/frameworks/base/+/a175a5b/core/java/android/view/View.java#2186 // This accounts for resetting mBackfaceOpacity and mBackfaceVisibility @@ -274,7 +271,12 @@ public void setOpacity(@NonNull T view, float opacity) { @ReactProp(name = ViewProps.ELEVATION) public void setElevation(@NonNull T view, float elevation) { - ViewCompat.setElevation(view, PixelUtil.toPixelFromDIP(elevation)); + float px = PixelUtil.toPixelFromDIP(elevation); + boolean wasElevated = view.getElevation() > 0f; + ViewCompat.setElevation(view, px); + if (wasElevated != (px > 0f)) { + HasElevatedDescendantCache.invalidateAncestors(view.getParent()); + } } @ReactProp(name = ViewProps.SHADOW_COLOR, defaultInt = Color.BLACK, customType = "Color") diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/HasElevatedDescendantCache.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/HasElevatedDescendantCache.kt new file mode 100644 index 000000000000..a40bd40606c5 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/HasElevatedDescendantCache.kt @@ -0,0 +1,74 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.uimanager + +import android.view.ViewParent +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags + +/** + * Implemented by views that cache whether their subtree contains a descendant with `elevation`, so + * the offscreen-compositing decision in `ReactViewGroup.hasOverlappingRendering()` is O(1) at draw + * time (see https://github.com/react/react-native/issues/23090). + * + * The cache is marked stale only from logical mount/unmount and elevation changes (via + * [invalidateAncestors]), never from the subview-clipping/scroll path, so scrolling a + * `removeClippedSubviews` list does no per-frame work. Invalidation is lazy -- it only flips a flag + * and the subtree is rescanned once, on the next query -- so mounting N children costs O(N) marks + * rather than O(N^2) rescans. The cache is self-correcting and cannot drift like a maintained + * counter. + * + * Mutation paths that change children without going through `ViewGroupManager` / + * `ReactClippingViewManager` or `BaseViewManager.setElevation` (a view manager overriding + * `addView`/`removeViewAt` without calling super, or re-routing children to another container) do + * not invalidate ancestor caches; the worst case is a briefly stale flag until the next tracked + * change, never a leak or drift. + * + * One known limitation: an invalidation that *originates inside a subtree currently clipped out of + * a `removeClippedSubviews` container* cannot reach that container. [invalidateAncestors] walks up + * via `View.getParent()`, which is null for a clipped-out (detached) node, so the walk stops at the + * detached subtree root before reaching the logical container that still holds it in `allChildren`. + * This affects both invalidation sources: `BaseViewManager.setElevation` (an elevation change on a + * clipped-out view or its descendant) and the `ViewGroupManager.addView`/`removeViewAt` mount hooks + * (mounting/unmounting an elevated descendant under a clipped-out sub-container). In both cases the + * container's cache stays briefly stale until the next tracked change to its own direct children -- + * which any sibling mount/unmount during scrolling provides -- so the shadow may only briefly fail + * to composite offscreen in this narrow scenario. + */ +internal interface HasElevatedDescendantCache { + /** + * Marks this view's cached elevated-descendant flag stale. The value is recomputed lazily on the + * next `hasOverlappingRendering()` query, so this stays O(1) on the mount/unmount path. Returns + * `true` if the cache was valid and is now newly invalidated, or `false` if it was already stale + * -- in which case its ancestors were already invalidated too, so callers walking up can stop. + */ + fun invalidateElevatedDescendantCache(): Boolean + + companion object { + /** + * Marks the elevated-descendant cache stale on [start] and each ancestor, walking through any + * intervening non-implementing container. This is O(depth) of flag writes; the actual subtree + * scan happens once, lazily, at the next draw. A no-op unless the feature flag is enabled, so + * the flag-off path costs nothing. + */ + @JvmStatic + fun invalidateAncestors(start: ViewParent?) { + if (!ReactNativeFeatureFlags.enableAndroidAutoOffscreenCompositingForElevation()) { + return + } + var node: ViewParent? = start + while (node != null) { + val cache = node as? HasElevatedDescendantCache + if (cache != null && !cache.invalidateElevatedDescendantCache()) { + // Already stale: whatever invalidated it already walked up and invalidated its ancestors. + break + } + node = node.parent + } + } + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewGroupManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewGroupManager.kt index ed2e2bb4d0b6..60ef17c2757d 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewGroupManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewGroupManager.kt @@ -25,8 +25,10 @@ constructor(reactContext: ReactApplicationContext? = null) : public override fun updateExtraData(root: T, extraData: Any): Unit = Unit - public override fun addView(parent: T, child: View, index: Int): Unit = - parent.addView(child, index) + public override fun addView(parent: T, child: View, index: Int) { + parent.addView(child, index) + HasElevatedDescendantCache.invalidateAncestors(parent) + } /** * Convenience method for batching a set of addView calls Note that this adds the views to the @@ -47,6 +49,7 @@ constructor(reactContext: ReactApplicationContext? = null) : public override fun removeViewAt(parent: T, index: Int) { UiThreadUtil.assertOnUiThread() parent.removeViewAt(index) + HasElevatedDescendantCache.invalidateAncestors(parent) } /** diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewManager.kt index 2f2453e97a1d..612aab52182f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewManager.kt @@ -9,6 +9,7 @@ package com.facebook.react.views.view import android.view.View import com.facebook.react.bridge.UiThreadUtil +import com.facebook.react.uimanager.HasElevatedDescendantCache import com.facebook.react.uimanager.ReactClippingViewGroupHelper import com.facebook.react.uimanager.ViewGroupManager import com.facebook.react.uimanager.annotations.ReactProp @@ -35,6 +36,7 @@ public abstract class ReactClippingViewManager : ViewGroupMa } else { parent.addView(child, index) } + HasElevatedDescendantCache.invalidateAncestors(parent) } override fun getChildCount(parent: T): Int { @@ -67,6 +69,7 @@ public abstract class ReactClippingViewManager : ViewGroupMa } else { parent.removeViewAt(index) } + HasElevatedDescendantCache.invalidateAncestors(parent) } override fun removeAllViews(parent: T) { @@ -78,5 +81,6 @@ public abstract class ReactClippingViewManager : ViewGroupMa } else { parent.removeAllViews() } + HasElevatedDescendantCache.invalidateAncestors(parent) } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt index ae2e36ca1ed6..363f1336d000 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt @@ -47,6 +47,7 @@ import com.facebook.react.uimanager.BackgroundStyleApplicator.setBorderStyle import com.facebook.react.uimanager.BackgroundStyleApplicator.setBorderWidth import com.facebook.react.uimanager.BackgroundStyleApplicator.setFeedbackUnderlay import com.facebook.react.uimanager.BlendModeHelper.needsIsolatedLayer +import com.facebook.react.uimanager.HasElevatedDescendantCache import com.facebook.react.uimanager.LengthPercentage import com.facebook.react.uimanager.LengthPercentageType import com.facebook.react.uimanager.MeasureSpecAssertions.assertExplicitMeasureSpec @@ -82,7 +83,8 @@ public open class ReactViewGroup public constructor(context: Context?) : ReactClippingViewGroup, ReactPointerEventsView, ReactHitSlopView, - ReactOverflowViewWithInset { + ReactOverflowViewWithInset, + HasElevatedDescendantCache { public override val overflowInset: Rect = Rect() @@ -161,6 +163,14 @@ public open class ReactViewGroup public constructor(context: Context?) : internal var nativeBackgroundMap: ReadableMap? = null internal var nativeForegroundMap: ReadableMap? = null + // Cached result of the descendant-elevation scan so the per-draw [hasOverlappingRendering] + // callback reads an O(1) value instead of walking the subtree each time. null means invalid + // (recomputed lazily on the next query); true/false is the memoized answer. Marked stale from the + // logical mount/unmount and elevation hooks via [HasElevatedDescendantCache.invalidateAncestors] + // -- never from the subview-clipping/scroll path. Declared before init { initView() } so + // initView()'s reset is not overwritten by the field initializer. + private var hasElevatedDescendantCache: Boolean? = null + init { initView() } @@ -185,6 +195,7 @@ public open class ReactViewGroup public constructor(context: Context?) : childrenLayoutChangeListener = null onInterceptTouchEventListener = null needsOffscreenAlphaCompositing = false + hasElevatedDescendantCache = null backfaceOpacity = 1f backfaceVisible = true childrenRemovedWhileTransitioning = null @@ -214,6 +225,9 @@ public open class ReactViewGroup public constructor(context: Context?) : // If the view is still attached to a parent, we need to remove it from the parent // before we can recycle it. if (parent != null) { + // Detaching via ViewGroup.removeView bypasses the ViewManager hooks, so invalidate the + // ancestor caches here. + HasElevatedDescendantCache.invalidateAncestors(parent) (parent as ViewGroup).removeView(this) } @@ -314,8 +328,74 @@ public open class ReactViewGroup public constructor(context: Context?) : /** * We override this to allow developers to determine whether they need offscreen alpha compositing * or not. See the documentation of needsOffscreenAlphaCompositing in View.js. + * + * When [ReactNativeFeatureFlags.enableAndroidAutoOffscreenCompositingForElevation] is enabled we + * also report overlapping rendering for a view drawn with reduced alpha that contains a + * descendant with elevation, so the framework composites the subtree offscreen before applying + * the alpha and the elevation shadow fades uniformly instead of rendering as banded per-primitive + * alpha (see https://github.com/facebook/react-native/issues/23090). We report this whenever an + * elevated descendant is present -- not only while currently faded -- so the decision is baked + * into the RenderNode at record time; alpha applied later (static, JS-driven, or a native-driver + * opacity animation that sets alpha directly on the RenderNode) then composites through the layer + * uniformly. A layer is only allocated when alpha < 1, so full opacity is unaffected. */ - override fun hasOverlappingRendering(): Boolean = needsOffscreenAlphaCompositing + override fun hasOverlappingRendering(): Boolean { + if (needsOffscreenAlphaCompositing) { + return true + } + // Report overlapping rendering whenever an elevated descendant is present -- not only while + // currently faded -- so the decision is baked into the RenderNode at record time. Alpha applied + // later, including native-driver opacity animations that set alpha directly on the RenderNode + // without re-recording, then composites through that layer and the shadow fades uniformly. A + // layer is only actually allocated when alpha < 1, so this costs nothing at full opacity. + return ReactNativeFeatureFlags.enableAndroidAutoOffscreenCompositingForElevation() && + hasElevatedDescendant() + } + + internal fun hasElevatedDescendant(): Boolean = + hasElevatedDescendantCache + ?: anyDescendantHasElevation(this).also { hasElevatedDescendantCache = it } + + private fun anyDescendantHasElevation(parent: ViewGroup): Boolean { + // For a clipping ReactViewGroup, scan the logical child list (allChildren) so an elevated + // descendant that is currently clipped out -- detached, present only in allChildren -- is still + // counted; otherwise re-attaching it during scroll would not refresh the cache and it would + // band. + val clipped = (parent as? ReactViewGroup)?.takeIf { it.removeClippedSubviews } + val count = clipped?.allChildrenCount ?: parent.childCount + for (i in 0 until count) { + val child = + (if (clipped != null) clipped.getChildAtWithSubviewClippingEnabled(i) + else parent.getChildAt(i)) ?: continue + if (child.elevation > 0f) { + return true + } + // Reuse a child ReactViewGroup's own memoized result instead of re-descending its subtree, so + // the scan is bounded at cached boundaries rather than O(subtree) on every recompute. A + // cached + // child that reports false must NOT fall through to a full re-descent below. + val cachedChild = child as? ReactViewGroup + if (cachedChild != null) { + if (cachedChild.hasElevatedDescendant()) { + return true + } + } else if (child is ViewGroup && anyDescendantHasElevation(child)) { + return true + } + } + return false + } + + override fun invalidateElevatedDescendantCache(): Boolean { + // Already stale: report so an ancestor walk can stop -- its ancestors were invalidated already. + if (hasElevatedDescendantCache == null) { + return false + } + // Only clear the value; the subtree is rescanned lazily on the next [hasElevatedDescendant] + // query, keeping mount/unmount O(1) instead of rescanning on every child change. + hasElevatedDescendantCache = null + return true + } /** See the documentation of needsOffscreenAlphaCompositing in View.js. */ public fun setNeedsOffscreenAlphaCompositing(needsOffscreenAlphaCompositing: 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 6aa1c470cb25..a41009b51f12 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<> + * @generated SignedSource<> */ /** @@ -111,6 +111,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + bool enableAndroidAutoOffscreenCompositingForElevation() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableAndroidAutoOffscreenCompositingForElevation"); + return method(javaProvider_); + } + bool enableAndroidTextMeasurementOptimizations() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableAndroidTextMeasurementOptimizations"); @@ -625,6 +631,11 @@ bool JReactNativeFeatureFlagsCxxInterop::enableAccumulatedUpdatesInRawPropsAndro return ReactNativeFeatureFlags::enableAccumulatedUpdatesInRawPropsAndroid(); } +bool JReactNativeFeatureFlagsCxxInterop::enableAndroidAutoOffscreenCompositingForElevation( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::enableAndroidAutoOffscreenCompositingForElevation(); +} + bool JReactNativeFeatureFlagsCxxInterop::enableAndroidTextMeasurementOptimizations( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::enableAndroidTextMeasurementOptimizations(); @@ -1067,6 +1078,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "enableAccumulatedUpdatesInRawPropsAndroid", JReactNativeFeatureFlagsCxxInterop::enableAccumulatedUpdatesInRawPropsAndroid), + makeNativeMethod( + "enableAndroidAutoOffscreenCompositingForElevation", + JReactNativeFeatureFlagsCxxInterop::enableAndroidAutoOffscreenCompositingForElevation), makeNativeMethod( "enableAndroidTextMeasurementOptimizations", JReactNativeFeatureFlagsCxxInterop::enableAndroidTextMeasurementOptimizations), 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 ddf10b023732..11becca9faee 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<> */ /** @@ -66,6 +66,9 @@ class JReactNativeFeatureFlagsCxxInterop static bool enableAccumulatedUpdatesInRawPropsAndroid( facebook::jni::alias_ref); + static bool enableAndroidAutoOffscreenCompositingForElevation( + facebook::jni::alias_ref); + static bool enableAndroidTextMeasurementOptimizations( facebook::jni::alias_ref); diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/view/ReactViewGroupTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/view/ReactViewGroupTest.kt index 588d92963e38..56383ff7efeb 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/view/ReactViewGroupTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/view/ReactViewGroupTest.kt @@ -12,7 +12,10 @@ import android.content.Context import android.view.View import android.view.ViewGroup import android.widget.FrameLayout +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsDefaults import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests +import com.facebook.react.uimanager.HasElevatedDescendantCache import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test @@ -63,6 +66,220 @@ class ReactViewGroupTest { rvg.updateClippingRect() assertThat(rvg.childCount).isEqualTo(20) } + + @Test + fun `auto offscreen compositing - true when flag on, alpha lt 1, and elevated descendant`() { + overrideAutoOffscreenFlag(true) + val rvg = ReactViewGroup(context) + mount(rvg, elevatedChild()) + rvg.alpha = 0.5f + assertThat(rvg.hasOverlappingRendering()).isTrue() + } + + @Test + fun `auto offscreen compositing - true even at alpha 1 so the layer is ready for a later alpha change`() { + // Returning true at full opacity is what bakes the offscreen decision into the RenderNode, so a + // later alpha change -- including a native-driver opacity animation -- composites uniformly. No + // layer is actually allocated until alpha < 1, so this costs nothing while opaque. + overrideAutoOffscreenFlag(true) + val rvg = ReactViewGroup(context) + mount(rvg, elevatedChild()) + rvg.alpha = 1f + assertThat(rvg.hasOverlappingRendering()).isTrue() + } + + @Test + fun `auto offscreen compositing - false when there is no elevated descendant`() { + overrideAutoOffscreenFlag(true) + val rvg = ReactViewGroup(context) + mount(rvg, View(context)) + rvg.alpha = 0.5f + assertThat(rvg.hasOverlappingRendering()).isFalse() + } + + @Test + fun `auto offscreen compositing - false when flag is off`() { + overrideAutoOffscreenFlag(false) + val rvg = ReactViewGroup(context) + mount(rvg, elevatedChild()) + rvg.alpha = 0.5f + assertThat(rvg.hasOverlappingRendering()).isFalse() + } + + @Test + fun `auto offscreen compositing - detects a nested elevated descendant`() { + overrideAutoOffscreenFlag(true) + val rvg = ReactViewGroup(context) + val inner = ReactViewGroup(context) + inner.addView(elevatedChild()) + mount(rvg, inner) + rvg.alpha = 0.5f + assertThat(rvg.hasOverlappingRendering()).isTrue() + } + + @Test + fun `auto offscreen compositing - detects an elevated view inside a non-ReactViewGroup container`() { + overrideAutoOffscreenFlag(true) + val rvg = ReactViewGroup(context) + val container = FrameLayout(context) + container.addView(elevatedChild()) + mount(rvg, container) + rvg.alpha = 0.5f + assertThat(rvg.hasOverlappingRendering()).isTrue() + } + + @Test + fun `auto offscreen compositing - invalidation bubbles through a non-ReactViewGroup ancestor`() { + overrideAutoOffscreenFlag(true) + val rvg = ReactViewGroup(context) + val container = FrameLayout(context) + val inner = ReactViewGroup(context) + rvg.addView(container) + container.addView(inner) + rvg.alpha = 0.5f + assertThat(rvg.hasOverlappingRendering()).isFalse() + // An elevated view mounted deep under a non-RVG container must still invalidate the RVG + // ancestor. + inner.addView(elevatedChild()) + HasElevatedDescendantCache.invalidateAncestors(inner) + assertThat(rvg.hasOverlappingRendering()).isTrue() + } + + @Test + fun `auto offscreen compositing - reflects an elevated child added after a prior query`() { + overrideAutoOffscreenFlag(true) + val rvg = ReactViewGroup(context) + rvg.alpha = 0.5f + assertThat(rvg.hasOverlappingRendering()).isFalse() + mount(rvg, elevatedChild()) + assertThat(rvg.hasOverlappingRendering()).isTrue() + } + + @Test + fun `auto offscreen compositing - false once the elevated descendant is removed`() { + overrideAutoOffscreenFlag(true) + val rvg = ReactViewGroup(context) + val child = elevatedChild() + mount(rvg, child) + rvg.alpha = 0.5f + assertThat(rvg.hasOverlappingRendering()).isTrue() + unmount(rvg, child) + assertThat(rvg.hasOverlappingRendering()).isFalse() + } + + @Test + fun `needsOffscreenAlphaCompositing forces overlapping rendering regardless of flag`() { + val rvg = ReactViewGroup(context) + rvg.setNeedsOffscreenAlphaCompositing(true) + assertThat(rvg.hasOverlappingRendering()).isTrue() + } + + @Test + fun `auto offscreen compositing - recycling a child invalidates the parent`() { + overrideAutoOffscreenFlag(true) + val rvg = ReactViewGroup(context) + val inner = ReactViewGroup(context) + inner.addView(elevatedChild()) + mount(rvg, inner) + rvg.alpha = 0.5f + assertThat(rvg.hasOverlappingRendering()).isTrue() + inner.recycleView() + assertThat(rvg.hasOverlappingRendering()).isFalse() + } + + @Test + fun `auto offscreen compositing - counts an elevated descendant that is currently clipped out`() { + overrideAutoOffscreenFlag(true) + val rvg = ReactViewGroup(context) + rvg.left = 0 + rvg.right = 100 + rvg.top = 0 + rvg.bottom = 100 + FrameLayout(context).addView(rvg) + rvg.removeClippedSubviews = true + // Elevated child positioned far below the viewport, so it is clipped out (detached, only in + // allChildren). The logical scan must still count it. + val elevated = + View(context).apply { + elevation = 10f + left = 0 + right = 100 + top = 1000 + bottom = 1010 + } + rvg.addViewWithSubviewClippingEnabled(elevated, 0) + rvg.updateClippingRect() + HasElevatedDescendantCache.invalidateAncestors(rvg) + rvg.alpha = 0.5f + assertThat(rvg.hasOverlappingRendering()).isTrue() + } + + @Test + fun `auto offscreen compositing - invalidateAncestors refreshes an outer ReactViewGroup across a nested one`() { + overrideAutoOffscreenFlag(true) + val outer = ReactViewGroup(context) + val inner = ReactViewGroup(context) + mount(outer, inner) + assertThat(outer.hasOverlappingRendering()).isFalse() + + // Add deep, then invalidate starting from the inner node: the walk must reach and refresh + // outer. + inner.addView(elevatedChild()) + HasElevatedDescendantCache.invalidateAncestors(inner) + assertThat(outer.hasOverlappingRendering()).isTrue() + } + + @Test + fun `auto offscreen compositing - invalidateElevatedDescendantCache reports whether it changed state`() { + overrideAutoOffscreenFlag(true) + val rvg = ReactViewGroup(context) + rvg.addView(elevatedChild()) + // Prime the cache. + assertThat(rvg.hasOverlappingRendering()).isTrue() + + // First invalidation clears a valid cache; the second finds it already stale. + assertThat(rvg.invalidateElevatedDescendantCache()).isTrue() + assertThat(rvg.invalidateElevatedDescendantCache()).isFalse() + } + + @Test + fun `auto offscreen compositing - invalidateAncestors short-circuits at an already-stale node`() { + overrideAutoOffscreenFlag(true) + val outer = ReactViewGroup(context) + val inner = ReactViewGroup(context) + mount(outer, elevatedChild()) // direct elevated child, scanned first + mount(outer, inner) // nested ReactViewGroup, scanned second + + // Querying outer returns true from its own elevated child and short-circuits before descending + // inner, so inner's cache is left stale. + assertThat(outer.hasOverlappingRendering()).isTrue() + + // Invalidating from inner finds inner already stale and stops there; outer's still-correct + // cached result must be unaffected. + HasElevatedDescendantCache.invalidateAncestors(inner) + assertThat(outer.hasOverlappingRendering()).isTrue() + } + + // Mirrors the ViewManager mount/unmount hooks: attach/detach the child, then refresh the cache. + private fun mount(parent: ReactViewGroup, child: View) { + parent.addView(child) + HasElevatedDescendantCache.invalidateAncestors(parent) + } + + private fun unmount(parent: ReactViewGroup, child: View) { + parent.removeView(child) + HasElevatedDescendantCache.invalidateAncestors(parent) + } + + private fun overrideAutoOffscreenFlag(enabled: Boolean) { + ReactNativeFeatureFlags.override( + object : ReactNativeFeatureFlagsDefaults() { + override fun enableAndroidAutoOffscreenCompositingForElevation(): Boolean = enabled + }, + ) + } + + private fun elevatedChild(): View = View(context).apply { elevation = 10f } } class TestView(context: Context, yPos: Int) : View(context) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp index 0b8ecc17507c..52ef2f7363e3 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<<023b0be9315450130aac118c65ac53eb>> + * @generated SignedSource<<37ceecaa4ef7ce8b076a46652b6cf0f0>> */ /** @@ -74,6 +74,10 @@ bool ReactNativeFeatureFlags::enableAccumulatedUpdatesInRawPropsAndroid() { return getAccessor().enableAccumulatedUpdatesInRawPropsAndroid(); } +bool ReactNativeFeatureFlags::enableAndroidAutoOffscreenCompositingForElevation() { + return getAccessor().enableAndroidAutoOffscreenCompositingForElevation(); +} + bool ReactNativeFeatureFlags::enableAndroidTextMeasurementOptimizations() { return getAccessor().enableAndroidTextMeasurementOptimizations(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index 6e383bdda111..63b53cb779bb 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<<0537b7ab2bf0250ddd0cfaec6c52111b>> + * @generated SignedSource<> */ /** @@ -101,6 +101,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool enableAccumulatedUpdatesInRawPropsAndroid(); + /** + * When enabled, a View with reduced opacity that contains an elevated descendant is composited offscreen so the elevation shadow fades uniformly instead of rendering as banded per-primitive alpha. + */ + RN_EXPORT static bool enableAndroidAutoOffscreenCompositingForElevation(); + /** * Enables various optimizations throughout the path of measuring text on Android. */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index 5a315e512cec..075a0c129a47 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<<3c52f26dcae83aedd2c3a5e79d1f91d4>> */ /** @@ -245,6 +245,24 @@ bool ReactNativeFeatureFlagsAccessor::enableAccumulatedUpdatesInRawPropsAndroid( return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::enableAndroidAutoOffscreenCompositingForElevation() { + auto flagValue = enableAndroidAutoOffscreenCompositingForElevation_.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(12, "enableAndroidAutoOffscreenCompositingForElevation"); + + flagValue = currentProvider_->enableAndroidAutoOffscreenCompositingForElevation(); + enableAndroidAutoOffscreenCompositingForElevation_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::enableAndroidTextMeasurementOptimizations() { auto flagValue = enableAndroidTextMeasurementOptimizations_.load(); @@ -254,7 +272,7 @@ bool ReactNativeFeatureFlagsAccessor::enableAndroidTextMeasurementOptimizations( // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(12, "enableAndroidTextMeasurementOptimizations"); + markFlagAsAccessed(13, "enableAndroidTextMeasurementOptimizations"); flagValue = currentProvider_->enableAndroidTextMeasurementOptimizations(); enableAndroidTextMeasurementOptimizations_ = flagValue; @@ -272,7 +290,7 @@ bool ReactNativeFeatureFlagsAccessor::enableBridgelessArchitecture() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(13, "enableBridgelessArchitecture"); + markFlagAsAccessed(14, "enableBridgelessArchitecture"); flagValue = currentProvider_->enableBridgelessArchitecture(); enableBridgelessArchitecture_ = flagValue; @@ -290,7 +308,7 @@ bool ReactNativeFeatureFlagsAccessor::enableBufferedCallInvoker() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(14, "enableBufferedCallInvoker"); + markFlagAsAccessed(15, "enableBufferedCallInvoker"); flagValue = currentProvider_->enableBufferedCallInvoker(); enableBufferedCallInvoker_ = flagValue; @@ -308,7 +326,7 @@ bool ReactNativeFeatureFlagsAccessor::enableCppPropsIteratorSetter() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(15, "enableCppPropsIteratorSetter"); + markFlagAsAccessed(16, "enableCppPropsIteratorSetter"); flagValue = currentProvider_->enableCppPropsIteratorSetter(); enableCppPropsIteratorSetter_ = flagValue; @@ -326,7 +344,7 @@ bool ReactNativeFeatureFlagsAccessor::enableCustomFocusSearchOnClippedElementsAn // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(16, "enableCustomFocusSearchOnClippedElementsAndroid"); + markFlagAsAccessed(17, "enableCustomFocusSearchOnClippedElementsAndroid"); flagValue = currentProvider_->enableCustomFocusSearchOnClippedElementsAndroid(); enableCustomFocusSearchOnClippedElementsAndroid_ = flagValue; @@ -344,7 +362,7 @@ bool ReactNativeFeatureFlagsAccessor::enableDestroyShadowTreeRevisionAsync() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(17, "enableDestroyShadowTreeRevisionAsync"); + markFlagAsAccessed(18, "enableDestroyShadowTreeRevisionAsync"); flagValue = currentProvider_->enableDestroyShadowTreeRevisionAsync(); enableDestroyShadowTreeRevisionAsync_ = flagValue; @@ -362,7 +380,7 @@ bool ReactNativeFeatureFlagsAccessor::enableDoubleMeasurementFixAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(18, "enableDoubleMeasurementFixAndroid"); + markFlagAsAccessed(19, "enableDoubleMeasurementFixAndroid"); flagValue = currentProvider_->enableDoubleMeasurementFixAndroid(); enableDoubleMeasurementFixAndroid_ = flagValue; @@ -380,7 +398,7 @@ bool ReactNativeFeatureFlagsAccessor::enableEagerRootViewAttachment() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(19, "enableEagerRootViewAttachment"); + markFlagAsAccessed(20, "enableEagerRootViewAttachment"); flagValue = currentProvider_->enableEagerRootViewAttachment(); enableEagerRootViewAttachment_ = flagValue; @@ -398,7 +416,7 @@ bool ReactNativeFeatureFlagsAccessor::enableExclusivePropsUpdateAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(20, "enableExclusivePropsUpdateAndroid"); + markFlagAsAccessed(21, "enableExclusivePropsUpdateAndroid"); flagValue = currentProvider_->enableExclusivePropsUpdateAndroid(); enableExclusivePropsUpdateAndroid_ = flagValue; @@ -416,7 +434,7 @@ bool ReactNativeFeatureFlagsAccessor::enableFabricCommitBranching() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(21, "enableFabricCommitBranching"); + markFlagAsAccessed(22, "enableFabricCommitBranching"); flagValue = currentProvider_->enableFabricCommitBranching(); enableFabricCommitBranching_ = flagValue; @@ -434,7 +452,7 @@ bool ReactNativeFeatureFlagsAccessor::enableFabricLogs() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(22, "enableFabricLogs"); + markFlagAsAccessed(23, "enableFabricLogs"); flagValue = currentProvider_->enableFabricLogs(); enableFabricLogs_ = flagValue; @@ -452,7 +470,7 @@ bool ReactNativeFeatureFlagsAccessor::enableFlexboxAutoMinSizeInStrictMode() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(23, "enableFlexboxAutoMinSizeInStrictMode"); + markFlagAsAccessed(24, "enableFlexboxAutoMinSizeInStrictMode"); flagValue = currentProvider_->enableFlexboxAutoMinSizeInStrictMode(); enableFlexboxAutoMinSizeInStrictMode_ = flagValue; @@ -470,7 +488,7 @@ bool ReactNativeFeatureFlagsAccessor::enableFontScaleChangesUpdatingLayout() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(24, "enableFontScaleChangesUpdatingLayout"); + markFlagAsAccessed(25, "enableFontScaleChangesUpdatingLayout"); flagValue = currentProvider_->enableFontScaleChangesUpdatingLayout(); enableFontScaleChangesUpdatingLayout_ = flagValue; @@ -488,7 +506,7 @@ bool ReactNativeFeatureFlagsAccessor::enableIOSCompressedTextFrameAdjustment() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(25, "enableIOSCompressedTextFrameAdjustment"); + markFlagAsAccessed(26, "enableIOSCompressedTextFrameAdjustment"); flagValue = currentProvider_->enableIOSCompressedTextFrameAdjustment(); enableIOSCompressedTextFrameAdjustment_ = flagValue; @@ -506,7 +524,7 @@ bool ReactNativeFeatureFlagsAccessor::enableIOSTextBaselineOffsetPerLine() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(26, "enableIOSTextBaselineOffsetPerLine"); + markFlagAsAccessed(27, "enableIOSTextBaselineOffsetPerLine"); flagValue = currentProvider_->enableIOSTextBaselineOffsetPerLine(); enableIOSTextBaselineOffsetPerLine_ = flagValue; @@ -524,7 +542,7 @@ bool ReactNativeFeatureFlagsAccessor::enableIOSViewClipToPaddingBox() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(27, "enableIOSViewClipToPaddingBox"); + markFlagAsAccessed(28, "enableIOSViewClipToPaddingBox"); flagValue = currentProvider_->enableIOSViewClipToPaddingBox(); enableIOSViewClipToPaddingBox_ = flagValue; @@ -542,7 +560,7 @@ bool ReactNativeFeatureFlagsAccessor::enableImagePrefetchingAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(28, "enableImagePrefetchingAndroid"); + markFlagAsAccessed(29, "enableImagePrefetchingAndroid"); flagValue = currentProvider_->enableImagePrefetchingAndroid(); enableImagePrefetchingAndroid_ = flagValue; @@ -560,7 +578,7 @@ bool ReactNativeFeatureFlagsAccessor::enableImageTransparentTintColor() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(29, "enableImageTransparentTintColor"); + markFlagAsAccessed(30, "enableImageTransparentTintColor"); flagValue = currentProvider_->enableImageTransparentTintColor(); enableImageTransparentTintColor_ = flagValue; @@ -578,7 +596,7 @@ bool ReactNativeFeatureFlagsAccessor::enableImmediateUpdateModeForContentOffsetC // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(30, "enableImmediateUpdateModeForContentOffsetChanges"); + markFlagAsAccessed(31, "enableImmediateUpdateModeForContentOffsetChanges"); flagValue = currentProvider_->enableImmediateUpdateModeForContentOffsetChanges(); enableImmediateUpdateModeForContentOffsetChanges_ = flagValue; @@ -596,7 +614,7 @@ bool ReactNativeFeatureFlagsAccessor::enableImperativeEvents() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(31, "enableImperativeEvents"); + markFlagAsAccessed(32, "enableImperativeEvents"); flagValue = currentProvider_->enableImperativeEvents(); enableImperativeEvents_ = flagValue; @@ -614,7 +632,7 @@ bool ReactNativeFeatureFlagsAccessor::enableImperativeFocus() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(32, "enableImperativeFocus"); + markFlagAsAccessed(33, "enableImperativeFocus"); flagValue = currentProvider_->enableImperativeFocus(); enableImperativeFocus_ = flagValue; @@ -632,7 +650,7 @@ bool ReactNativeFeatureFlagsAccessor::enableInteropViewManagerClassLookUpOptimiz // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(33, "enableInteropViewManagerClassLookUpOptimizationIOS"); + markFlagAsAccessed(34, "enableInteropViewManagerClassLookUpOptimizationIOS"); flagValue = currentProvider_->enableInteropViewManagerClassLookUpOptimizationIOS(); enableInteropViewManagerClassLookUpOptimizationIOS_ = flagValue; @@ -650,7 +668,7 @@ bool ReactNativeFeatureFlagsAccessor::enableIntersectionObserverByDefault() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(34, "enableIntersectionObserverByDefault"); + markFlagAsAccessed(35, "enableIntersectionObserverByDefault"); flagValue = currentProvider_->enableIntersectionObserverByDefault(); enableIntersectionObserverByDefault_ = flagValue; @@ -668,7 +686,7 @@ bool ReactNativeFeatureFlagsAccessor::enableKeyEvents() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(35, "enableKeyEvents"); + markFlagAsAccessed(36, "enableKeyEvents"); flagValue = currentProvider_->enableKeyEvents(); enableKeyEvents_ = flagValue; @@ -686,7 +704,7 @@ bool ReactNativeFeatureFlagsAccessor::enableLayoutAnimationsOnAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(36, "enableLayoutAnimationsOnAndroid"); + markFlagAsAccessed(37, "enableLayoutAnimationsOnAndroid"); flagValue = currentProvider_->enableLayoutAnimationsOnAndroid(); enableLayoutAnimationsOnAndroid_ = flagValue; @@ -704,7 +722,7 @@ bool ReactNativeFeatureFlagsAccessor::enableLayoutAnimationsOnIOS() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(37, "enableLayoutAnimationsOnIOS"); + markFlagAsAccessed(38, "enableLayoutAnimationsOnIOS"); flagValue = currentProvider_->enableLayoutAnimationsOnIOS(); enableLayoutAnimationsOnIOS_ = flagValue; @@ -722,7 +740,7 @@ bool ReactNativeFeatureFlagsAccessor::enableModuleArgumentNSNullConversionIOS() // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(38, "enableModuleArgumentNSNullConversionIOS"); + markFlagAsAccessed(39, "enableModuleArgumentNSNullConversionIOS"); flagValue = currentProvider_->enableModuleArgumentNSNullConversionIOS(); enableModuleArgumentNSNullConversionIOS_ = flagValue; @@ -740,7 +758,7 @@ bool ReactNativeFeatureFlagsAccessor::enableMountingCoordinatorPullModelAndroid( // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(39, "enableMountingCoordinatorPullModelAndroid"); + markFlagAsAccessed(40, "enableMountingCoordinatorPullModelAndroid"); flagValue = currentProvider_->enableMountingCoordinatorPullModelAndroid(); enableMountingCoordinatorPullModelAndroid_ = flagValue; @@ -758,7 +776,7 @@ bool ReactNativeFeatureFlagsAccessor::enableMutationObserverByDefault() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(40, "enableMutationObserverByDefault"); + markFlagAsAccessed(41, "enableMutationObserverByDefault"); flagValue = currentProvider_->enableMutationObserverByDefault(); enableMutationObserverByDefault_ = flagValue; @@ -776,7 +794,7 @@ bool ReactNativeFeatureFlagsAccessor::enableNativeCSSParsing() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(41, "enableNativeCSSParsing"); + markFlagAsAccessed(42, "enableNativeCSSParsing"); flagValue = currentProvider_->enableNativeCSSParsing(); enableNativeCSSParsing_ = flagValue; @@ -794,7 +812,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(42, "enablePreparedTextLayout"); + markFlagAsAccessed(43, "enablePreparedTextLayout"); flagValue = currentProvider_->enablePreparedTextLayout(); enablePreparedTextLayout_ = flagValue; @@ -812,7 +830,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(43, "enablePropsUpdateReconciliationAndroid"); + markFlagAsAccessed(44, "enablePropsUpdateReconciliationAndroid"); flagValue = currentProvider_->enablePropsUpdateReconciliationAndroid(); enablePropsUpdateReconciliationAndroid_ = flagValue; @@ -830,7 +848,7 @@ bool ReactNativeFeatureFlagsAccessor::enableResizeObserverByDefault() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(44, "enableResizeObserverByDefault"); + markFlagAsAccessed(45, "enableResizeObserverByDefault"); flagValue = currentProvider_->enableResizeObserverByDefault(); enableResizeObserverByDefault_ = flagValue; @@ -848,7 +866,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(45, "enableSwiftUIBasedFilters"); + markFlagAsAccessed(46, "enableSwiftUIBasedFilters"); flagValue = currentProvider_->enableSwiftUIBasedFilters(); enableSwiftUIBasedFilters_ = flagValue; @@ -866,7 +884,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(46, "enableViewCulling"); + markFlagAsAccessed(47, "enableViewCulling"); flagValue = currentProvider_->enableViewCulling(); enableViewCulling_ = flagValue; @@ -884,7 +902,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(47, "enableViewRecycling"); + markFlagAsAccessed(48, "enableViewRecycling"); flagValue = currentProvider_->enableViewRecycling(); enableViewRecycling_ = flagValue; @@ -902,7 +920,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(48, "enableViewRecyclingForImage"); + markFlagAsAccessed(49, "enableViewRecyclingForImage"); flagValue = currentProvider_->enableViewRecyclingForImage(); enableViewRecyclingForImage_ = flagValue; @@ -920,7 +938,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(49, "enableViewRecyclingForScrollView"); + markFlagAsAccessed(50, "enableViewRecyclingForScrollView"); flagValue = currentProvider_->enableViewRecyclingForScrollView(); enableViewRecyclingForScrollView_ = flagValue; @@ -938,7 +956,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(50, "enableViewRecyclingForText"); + markFlagAsAccessed(51, "enableViewRecyclingForText"); flagValue = currentProvider_->enableViewRecyclingForText(); enableViewRecyclingForText_ = flagValue; @@ -956,7 +974,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(51, "enableViewRecyclingForView"); + markFlagAsAccessed(52, "enableViewRecyclingForView"); flagValue = currentProvider_->enableViewRecyclingForView(); enableViewRecyclingForView_ = flagValue; @@ -974,7 +992,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(52, "enableVirtualViewContainerStateExperimental"); + markFlagAsAccessed(53, "enableVirtualViewContainerStateExperimental"); flagValue = currentProvider_->enableVirtualViewContainerStateExperimental(); enableVirtualViewContainerStateExperimental_ = flagValue; @@ -992,7 +1010,7 @@ bool ReactNativeFeatureFlagsAccessor::fixDifferentiatorParentTagForUnflattenCase // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(53, "fixDifferentiatorParentTagForUnflattenCase"); + markFlagAsAccessed(54, "fixDifferentiatorParentTagForUnflattenCase"); flagValue = currentProvider_->fixDifferentiatorParentTagForUnflattenCase(); fixDifferentiatorParentTagForUnflattenCase_ = flagValue; @@ -1010,7 +1028,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(54, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); + markFlagAsAccessed(55, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); flagValue = currentProvider_->fixMappingOfEventPrioritiesBetweenFabricAndReact(); fixMappingOfEventPrioritiesBetweenFabricAndReact_ = flagValue; @@ -1028,7 +1046,7 @@ bool ReactNativeFeatureFlagsAccessor::fixYogaFlexBasisFitContentInMainAxis() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(55, "fixYogaFlexBasisFitContentInMainAxis"); + markFlagAsAccessed(56, "fixYogaFlexBasisFitContentInMainAxis"); flagValue = currentProvider_->fixYogaFlexBasisFitContentInMainAxis(); fixYogaFlexBasisFitContentInMainAxis_ = flagValue; @@ -1046,7 +1064,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(56, "fuseboxAssertSingleHostState"); + markFlagAsAccessed(57, "fuseboxAssertSingleHostState"); flagValue = currentProvider_->fuseboxAssertSingleHostState(); fuseboxAssertSingleHostState_ = flagValue; @@ -1064,7 +1082,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(57, "fuseboxEnabledRelease"); + markFlagAsAccessed(58, "fuseboxEnabledRelease"); flagValue = currentProvider_->fuseboxEnabledRelease(); fuseboxEnabledRelease_ = flagValue; @@ -1082,7 +1100,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxFrameRecordingEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(58, "fuseboxFrameRecordingEnabled"); + markFlagAsAccessed(59, "fuseboxFrameRecordingEnabled"); flagValue = currentProvider_->fuseboxFrameRecordingEnabled(); fuseboxFrameRecordingEnabled_ = flagValue; @@ -1100,7 +1118,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxScreenshotCaptureEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(59, "fuseboxScreenshotCaptureEnabled"); + markFlagAsAccessed(60, "fuseboxScreenshotCaptureEnabled"); flagValue = currentProvider_->fuseboxScreenshotCaptureEnabled(); fuseboxScreenshotCaptureEnabled_ = flagValue; @@ -1118,7 +1136,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxWebSocketEventsEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(60, "fuseboxWebSocketEventsEnabled"); + markFlagAsAccessed(61, "fuseboxWebSocketEventsEnabled"); flagValue = currentProvider_->fuseboxWebSocketEventsEnabled(); fuseboxWebSocketEventsEnabled_ = flagValue; @@ -1136,7 +1154,7 @@ bool ReactNativeFeatureFlagsAccessor::optimizedAnimatedPropUpdates() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(61, "optimizedAnimatedPropUpdates"); + markFlagAsAccessed(62, "optimizedAnimatedPropUpdates"); flagValue = currentProvider_->optimizedAnimatedPropUpdates(); optimizedAnimatedPropUpdates_ = 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::redBoxV2Android() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(67, "redBoxV2Android"); + markFlagAsAccessed(68, "redBoxV2Android"); flagValue = currentProvider_->redBoxV2Android(); redBoxV2Android_ = flagValue; @@ -1262,7 +1280,7 @@ bool ReactNativeFeatureFlagsAccessor::redBoxV2IOS() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(68, "redBoxV2IOS"); + markFlagAsAccessed(69, "redBoxV2IOS"); flagValue = currentProvider_->redBoxV2IOS(); redBoxV2IOS_ = flagValue; @@ -1280,7 +1298,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(69, "shouldPressibilityUseW3CPointerEventsForHover"); + markFlagAsAccessed(70, "shouldPressibilityUseW3CPointerEventsForHover"); flagValue = currentProvider_->shouldPressibilityUseW3CPointerEventsForHover(); shouldPressibilityUseW3CPointerEventsForHover_ = flagValue; @@ -1298,7 +1316,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(70, "shouldTriggerResponderTransferOnScrollAndroid"); + markFlagAsAccessed(71, "shouldTriggerResponderTransferOnScrollAndroid"); flagValue = currentProvider_->shouldTriggerResponderTransferOnScrollAndroid(); shouldTriggerResponderTransferOnScrollAndroid_ = flagValue; @@ -1316,7 +1334,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(71, "skipActivityIdentityAssertionOnHostPause"); + markFlagAsAccessed(72, "skipActivityIdentityAssertionOnHostPause"); flagValue = currentProvider_->skipActivityIdentityAssertionOnHostPause(); skipActivityIdentityAssertionOnHostPause_ = flagValue; @@ -1334,7 +1352,7 @@ bool ReactNativeFeatureFlagsAccessor::syncAndroidClipBoundsWithOverflow() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(72, "syncAndroidClipBoundsWithOverflow"); + markFlagAsAccessed(73, "syncAndroidClipBoundsWithOverflow"); flagValue = currentProvider_->syncAndroidClipBoundsWithOverflow(); syncAndroidClipBoundsWithOverflow_ = flagValue; @@ -1352,7 +1370,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(73, "traceTurboModulePromiseRejectionsOnAndroid"); + markFlagAsAccessed(74, "traceTurboModulePromiseRejectionsOnAndroid"); flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid(); traceTurboModulePromiseRejectionsOnAndroid_ = flagValue; @@ -1370,7 +1388,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(74, "updateRuntimeShadowNodeReferencesOnCommit"); + markFlagAsAccessed(75, "updateRuntimeShadowNodeReferencesOnCommit"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommit(); updateRuntimeShadowNodeReferencesOnCommit_ = flagValue; @@ -1388,7 +1406,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(75, "updateRuntimeShadowNodeReferencesOnCommitThread"); + markFlagAsAccessed(76, "updateRuntimeShadowNodeReferencesOnCommitThread"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommitThread(); updateRuntimeShadowNodeReferencesOnCommitThread_ = flagValue; @@ -1406,7 +1424,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(76, "useAlwaysAvailableJSErrorHandling"); + markFlagAsAccessed(77, "useAlwaysAvailableJSErrorHandling"); flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling(); useAlwaysAvailableJSErrorHandling_ = flagValue; @@ -1424,7 +1442,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(77, "useFabricInterop"); + markFlagAsAccessed(78, "useFabricInterop"); flagValue = currentProvider_->useFabricInterop(); useFabricInterop_ = flagValue; @@ -1442,7 +1460,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(78, "useNativeViewConfigsInBridgelessMode"); + markFlagAsAccessed(79, "useNativeViewConfigsInBridgelessMode"); flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode(); useNativeViewConfigsInBridgelessMode_ = flagValue; @@ -1460,7 +1478,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(79, "useNestedScrollViewAndroid"); + markFlagAsAccessed(80, "useNestedScrollViewAndroid"); flagValue = currentProvider_->useNestedScrollViewAndroid(); useNestedScrollViewAndroid_ = flagValue; @@ -1478,7 +1496,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(80, "useSharedAnimatedBackend"); + markFlagAsAccessed(81, "useSharedAnimatedBackend"); flagValue = currentProvider_->useSharedAnimatedBackend(); useSharedAnimatedBackend_ = flagValue; @@ -1496,7 +1514,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(81, "useTraitHiddenOnAndroid"); + markFlagAsAccessed(82, "useTraitHiddenOnAndroid"); flagValue = currentProvider_->useTraitHiddenOnAndroid(); useTraitHiddenOnAndroid_ = flagValue; @@ -1514,7 +1532,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(82, "useTurboModuleInterop"); + markFlagAsAccessed(83, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = 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 @@ bool ReactNativeFeatureFlagsAccessor::viewTransitionUseHardwareBitmapAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(85, "viewTransitionUseHardwareBitmapAndroid"); + markFlagAsAccessed(86, "viewTransitionUseHardwareBitmapAndroid"); flagValue = currentProvider_->viewTransitionUseHardwareBitmapAndroid(); viewTransitionUseHardwareBitmapAndroid_ = flagValue; @@ -1586,7 +1604,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(86, "virtualViewPrerenderRatio"); + markFlagAsAccessed(87, "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 f8a58a3e5428..9dde21ed2d01 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<<0050c3fd04df99633d455ee88e0cf789>> */ /** @@ -46,6 +46,7 @@ class ReactNativeFeatureFlagsAccessor { bool disableViewPreallocationAndroid(); bool enableAccessibilityOrder(); bool enableAccumulatedUpdatesInRawPropsAndroid(); + bool enableAndroidAutoOffscreenCompositingForElevation(); bool enableAndroidTextMeasurementOptimizations(); bool enableBridgelessArchitecture(); bool enableBufferedCallInvoker(); @@ -132,7 +133,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 87> accessedFeatureFlags_; + std::array, 88> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> cdpInteractionMetricsEnabled_; @@ -146,6 +147,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> disableViewPreallocationAndroid_; std::atomic> enableAccessibilityOrder_; std::atomic> enableAccumulatedUpdatesInRawPropsAndroid_; + std::atomic> enableAndroidAutoOffscreenCompositingForElevation_; std::atomic> enableAndroidTextMeasurementOptimizations_; std::atomic> enableBridgelessArchitecture_; std::atomic> enableBufferedCallInvoker_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index 39bf7e4e6b25..c5f3c636d3ae 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<> */ /** @@ -77,6 +77,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return false; } + bool enableAndroidAutoOffscreenCompositingForElevation() override { + return false; + } + bool enableAndroidTextMeasurementOptimizations() override { return false; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index a29d9360ec56..a109bd9ea69f 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<> */ /** @@ -155,6 +155,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::enableAccumulatedUpdatesInRawPropsAndroid(); } + bool enableAndroidAutoOffscreenCompositingForElevation() override { + auto value = values_["enableAndroidAutoOffscreenCompositingForElevation"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::enableAndroidAutoOffscreenCompositingForElevation(); + } + bool enableAndroidTextMeasurementOptimizations() override { auto value = values_["enableAndroidTextMeasurementOptimizations"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index 215a8d846bf9..88d868442d0d 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<<1a6fb1d14362fdb64291a91d0b963ad6>> */ /** @@ -39,6 +39,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool disableViewPreallocationAndroid() = 0; virtual bool enableAccessibilityOrder() = 0; virtual bool enableAccumulatedUpdatesInRawPropsAndroid() = 0; + virtual bool enableAndroidAutoOffscreenCompositingForElevation() = 0; virtual bool enableAndroidTextMeasurementOptimizations() = 0; virtual bool enableBridgelessArchitecture() = 0; virtual bool enableBufferedCallInvoker() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index a0fd7464f0b1..85db7a161e90 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<<909f4afa6f96b4e77577432e4d98ba76>> + * @generated SignedSource<<11c7bb95a04471b81c5291b0d2decfe8>> */ /** @@ -104,6 +104,11 @@ bool NativeReactNativeFeatureFlags::enableAccumulatedUpdatesInRawPropsAndroid( return ReactNativeFeatureFlags::enableAccumulatedUpdatesInRawPropsAndroid(); } +bool NativeReactNativeFeatureFlags::enableAndroidAutoOffscreenCompositingForElevation( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::enableAndroidAutoOffscreenCompositingForElevation(); +} + bool NativeReactNativeFeatureFlags::enableAndroidTextMeasurementOptimizations( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::enableAndroidTextMeasurementOptimizations(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index 1a4b411746df..97b213eab5d0 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<<88f2554b7033d38f5b275eac1d52ae6e>> + * @generated SignedSource<> */ /** @@ -62,6 +62,8 @@ class NativeReactNativeFeatureFlags bool enableAccumulatedUpdatesInRawPropsAndroid(jsi::Runtime& runtime); + bool enableAndroidAutoOffscreenCompositingForElevation(jsi::Runtime& runtime); + bool enableAndroidTextMeasurementOptimizations(jsi::Runtime& runtime); bool enableBridgelessArchitecture(jsi::Runtime& runtime); diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index a28164193d10..f73aeb204b7c 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -180,6 +180,17 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'none', }, + enableAndroidAutoOffscreenCompositingForElevation: { + defaultValue: false, + metadata: { + dateAdded: '2026-08-31', + description: + 'When enabled, a View with reduced opacity that contains an elevated descendant is composited offscreen so the elevation shadow fades uniformly instead of rendering as banded per-primitive alpha.', + expectedReleaseValue: true, + purpose: 'experimentation', + }, + ossReleaseStage: 'none', + }, enableAndroidTextMeasurementOptimizations: { defaultValue: false, metadata: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index dc80a6d559c7..57ad939a51c9 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<<52d39a196023796eed43d419c5a0b855>> + * @generated SignedSource<<6ad9c99b513e5482b907ff75f6784818>> * @flow strict * @noformat */ @@ -61,6 +61,7 @@ export type ReactNativeFeatureFlags = Readonly<{ disableViewPreallocationAndroid: Getter, enableAccessibilityOrder: Getter, enableAccumulatedUpdatesInRawPropsAndroid: Getter, + enableAndroidAutoOffscreenCompositingForElevation: Getter, enableAndroidTextMeasurementOptimizations: Getter, enableBridgelessArchitecture: Getter, enableBufferedCallInvoker: Getter, @@ -260,6 +261,10 @@ export const enableAccessibilityOrder: Getter = createNativeFlagGetter( * When enabled, Android will accumulate updates in rawProps to reduce the number of mounting instructions for cascading re-renders. */ export const enableAccumulatedUpdatesInRawPropsAndroid: Getter = createNativeFlagGetter('enableAccumulatedUpdatesInRawPropsAndroid', false); +/** + * When enabled, a View with reduced opacity that contains an elevated descendant is composited offscreen so the elevation shadow fades uniformly instead of rendering as banded per-primitive alpha. + */ +export const enableAndroidAutoOffscreenCompositingForElevation: Getter = createNativeFlagGetter('enableAndroidAutoOffscreenCompositingForElevation', false); /** * Enables various optimizations throughout the path of measuring text on Android. */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index 5f1739e3cf29..61df827d78ec 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<<66e59b3ca65fca86249a6d81d7a6ae7b>> * @flow strict * @noformat */ @@ -37,6 +37,7 @@ export interface Spec extends TurboModule { readonly disableViewPreallocationAndroid?: () => boolean; readonly enableAccessibilityOrder?: () => boolean; readonly enableAccumulatedUpdatesInRawPropsAndroid?: () => boolean; + readonly enableAndroidAutoOffscreenCompositingForElevation?: () => boolean; readonly enableAndroidTextMeasurementOptimizations?: () => boolean; readonly enableBridgelessArchitecture?: () => boolean; readonly enableBufferedCallInvoker?: () => boolean;