diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/StateWrapperImpl.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/StateWrapperImpl.kt index be9ac3869f6e..06280a065245 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/StateWrapperImpl.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/StateWrapperImpl.kt @@ -14,8 +14,10 @@ import com.facebook.proguard.annotations.DoNotStripAny import com.facebook.react.bridge.NativeMap import com.facebook.react.bridge.ReadableNativeMap import com.facebook.react.bridge.WritableMap +import com.facebook.react.common.annotations.UnstableReactNativeAPI import com.facebook.react.common.mapbuffer.ReadableMapBuffer import com.facebook.react.uimanager.ReferenceStateWrapper +import com.facebook.react.uimanager.StateWrapper /** * This class holds reference to the C++ EventEmitter object. Instances of this class are created on @@ -23,6 +25,7 @@ import com.facebook.react.uimanager.ReferenceStateWrapper */ @SuppressLint("MissingNativeLoadLibrary") @DoNotStripAny +@OptIn(UnstableReactNativeAPI::class) internal class StateWrapperImpl private constructor() : HybridClassBase(), ReferenceStateWrapper { private external fun initHybrid() @@ -33,7 +36,7 @@ internal class StateWrapperImpl private constructor() : HybridClassBase(), Refer private external fun getStateDataReferenceImpl(): Any? - external fun updateStateImpl(map: NativeMap) + external fun updateStateImpl(map: NativeMap, updateMode: Int) override val stateDataMapBuffer: ReadableMapBuffer? get() { @@ -67,11 +70,15 @@ internal class StateWrapperImpl private constructor() : HybridClassBase(), Refer } override fun updateState(map: WritableMap) { + updateState(map, StateWrapper.UpdateMode.Asynchronous) + } + + override fun updateState(map: WritableMap, updateMode: StateWrapper.UpdateMode) { if (!isValid) { FLog.e(TAG, "Race between StateWrapperImpl destruction and updateState") return } - updateStateImpl(map as NativeMap) + updateStateImpl(map as NativeMap, updateMode.value) } override fun destroyState() { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountItemDispatcher.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountItemDispatcher.kt index 72efd7c42058..dda030c43658 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountItemDispatcher.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountItemDispatcher.kt @@ -47,6 +47,7 @@ internal class MountItemDispatcher( } private var inDispatch: Boolean = false + private var followUpDispatchRequired: Boolean = false var batchedExecutionTime: Long = 0L private set @@ -93,24 +94,29 @@ internal class MountItemDispatcher( @UiThread @ThreadConfined(UI) fun tryDispatchMountItems() { - // If we're already dispatching, don't reenter. - // Reentrance can potentially happen a lot on Android in Fabric because `updateState` from the - // mounting layer causes mount items to be dispatched synchronously. We want to 1) make sure we - // don't reenter in those cases, but 2) still execute those queued instructions synchronously. - // This is a pretty blunt tool, but we might not have better options since we really don't want - // to execute anything out-of-order. + // If we're already dispatching, don't reenter but signal that a follow-up dispatch is + // needed. This follows the same pattern as iOS's RCTMountingManager::initiateTransaction, + // which uses _followUpTransactionRequired flag to ensure mount items + // enqueued during dispatch (e.g., from synchronous state updates triggered by view layout) + // are processed in the same frame rather than deferred to the next one. if (inDispatch) { + if (ReactNativeFeatureFlags.enableFollowUpMountItemDispatchAndroid()) { + followUpDispatchRequired = true + } return } - inDispatch = true + do { + followUpDispatchRequired = false + inDispatch = true - try { - dispatchMountItems() - } finally { - // Clean up after running dispatchMountItems - even if an exception was thrown - inDispatch = false - } + try { + dispatchMountItems() + } finally { + // Clean up after running dispatchMountItems - even if an exception was thrown + inDispatch = false + } + } while (followUpDispatchRequired) // We call didDispatchMountItems regardless of whether we actually dispatched anything, since // NativeAnimatedModule relies on this for executing any animations that may have been 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 b4cf39c69be8..73d925a6e087 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<<4171750cfc627709dcd32ccb67131dda>> */ /** @@ -192,6 +192,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun enableFlexboxAutoMinSizeInStrictMode(): Boolean = accessor.enableFlexboxAutoMinSizeInStrictMode() + /** + * When mount items are queued while MountItemDispatcher is already dispatching on Android (e.g. from a synchronous state update), dispatch them in the same frame instead of deferring them to the next one. + */ + @JvmStatic + public fun enableFollowUpMountItemDispatchAndroid(): Boolean = accessor.enableFollowUpMountItemDispatchAndroid() + /** * Enables font scale changes updating layout for measurable nodes. */ 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 352d4a2d3587..1170eaee7195 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<<7928e03a82c0fd05eb5a1623959402c4>> */ /** @@ -47,6 +47,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var enableFabricCommitBranchingMergeOnMainThreadCache: Boolean? = null private var enableFabricLogsCache: Boolean? = null private var enableFlexboxAutoMinSizeInStrictModeCache: Boolean? = null + private var enableFollowUpMountItemDispatchAndroidCache: Boolean? = null private var enableFontScaleChangesUpdatingLayoutCache: Boolean? = null private var enableIOSCompressedTextFrameAdjustmentCache: Boolean? = null private var enableIOSTextBaselineOffsetPerLineCache: Boolean? = null @@ -353,6 +354,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun enableFollowUpMountItemDispatchAndroid(): Boolean { + var cached = enableFollowUpMountItemDispatchAndroidCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.enableFollowUpMountItemDispatchAndroid() + enableFollowUpMountItemDispatchAndroidCache = cached + } + return cached + } + override fun enableFontScaleChangesUpdatingLayout(): Boolean { var cached = enableFontScaleChangesUpdatingLayoutCache 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 92be8ae14267..ac3e686de49e 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<> + * @generated SignedSource<<893c2c5612cff8fe044bead40c2fc603>> */ /** @@ -82,6 +82,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun enableFlexboxAutoMinSizeInStrictMode(): Boolean + @DoNotStrip @JvmStatic public external fun enableFollowUpMountItemDispatchAndroid(): Boolean + @DoNotStrip @JvmStatic public external fun enableFontScaleChangesUpdatingLayout(): Boolean @DoNotStrip @JvmStatic public external fun enableIOSCompressedTextFrameAdjustment(): 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 c9ea95e27635..d8e88001ac8d 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<<890e204b6cf8afef481f64de44766edf>> + * @generated SignedSource<<0093c0cd89ab032619b8a77146af32ab>> */ /** @@ -77,6 +77,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun enableFlexboxAutoMinSizeInStrictMode(): Boolean = false + override fun enableFollowUpMountItemDispatchAndroid(): Boolean = false + override fun enableFontScaleChangesUpdatingLayout(): Boolean = true override fun enableIOSCompressedTextFrameAdjustment(): Boolean = false diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt index 4cbaa0d49b11..67d8a8406453 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<<9044f0df865ba9d7668f9ff391d21911>> + * @generated SignedSource<<330c3dd9643d65d443d3fdc6ac71055e>> */ /** @@ -51,6 +51,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var enableFabricCommitBranchingMergeOnMainThreadCache: Boolean? = null private var enableFabricLogsCache: Boolean? = null private var enableFlexboxAutoMinSizeInStrictModeCache: Boolean? = null + private var enableFollowUpMountItemDispatchAndroidCache: Boolean? = null private var enableFontScaleChangesUpdatingLayoutCache: Boolean? = null private var enableIOSCompressedTextFrameAdjustmentCache: Boolean? = null private var enableIOSTextBaselineOffsetPerLineCache: Boolean? = null @@ -384,6 +385,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun enableFollowUpMountItemDispatchAndroid(): Boolean { + var cached = enableFollowUpMountItemDispatchAndroidCache + if (cached == null) { + cached = currentProvider.enableFollowUpMountItemDispatchAndroid() + accessedFeatureFlags.add("enableFollowUpMountItemDispatchAndroid") + enableFollowUpMountItemDispatchAndroidCache = cached + } + return cached + } + override fun enableFontScaleChangesUpdatingLayout(): Boolean { var cached = enableFontScaleChangesUpdatingLayoutCache 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 5fd66c91d1af..f6f3022a5087 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<> */ /** @@ -77,6 +77,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun enableFlexboxAutoMinSizeInStrictMode(): Boolean + @DoNotStrip public fun enableFollowUpMountItemDispatchAndroid(): Boolean + @DoNotStrip public fun enableFontScaleChangesUpdatingLayout(): Boolean @DoNotStrip public fun enableIOSCompressedTextFrameAdjustment(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/StateWrapper.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/StateWrapper.kt index 6e5d07abff3c..d28921073df5 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/StateWrapper.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/StateWrapper.kt @@ -9,6 +9,7 @@ package com.facebook.react.uimanager import com.facebook.react.bridge.ReadableNativeMap import com.facebook.react.bridge.WritableMap +import com.facebook.react.common.annotations.UnstableReactNativeAPI import com.facebook.react.common.mapbuffer.ReadableMapBuffer /** @@ -17,6 +18,15 @@ import com.facebook.react.common.mapbuffer.ReadableMapBuffer * by calling updateState, which communicates state back to the C++ layer. */ public interface StateWrapper { + /** + * Maps to EventQueue::UpdateMode in C++. Controls how state updates are flushed (Async or Sync). + */ + @UnstableReactNativeAPI + public enum class UpdateMode(public val value: Int) { + Asynchronous(0), + unstable_Immediate(1), + } + /** * Get a ReadableMapBuffer object from the C++ layer, which is a K/V map of short keys to values. * @@ -35,6 +45,12 @@ public interface StateWrapper { */ public fun updateState(map: WritableMap) + /** + * Pass a map of values back to the C++ layer. updateMode controls whether the update is queued + * asynchronously or flushed immediately. + */ + @UnstableReactNativeAPI public fun updateState(map: WritableMap, updateMode: UpdateMode) + /** * Mark state as unused and clean up in Java and in native. This should be called as early as * possible when you know a StateWrapper will no longer be used. If there's ANY chance of it being diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/StateWrapperImpl.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/StateWrapperImpl.cpp index 2459ad556d94..349780a367d2 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/StateWrapperImpl.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/StateWrapperImpl.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -50,12 +51,13 @@ jni::local_ref StateWrapperImpl::getStateDataReferenceImpl() { return nullptr; } -void StateWrapperImpl::updateStateImpl(NativeMap* map) { +void StateWrapperImpl::updateStateImpl(NativeMap* map, jint updateMode) { if (state_) { // Get folly::dynamic from map auto dynamicMap = map->consume(); // Set state - state_->updateState(std::move(dynamicMap)); + state_->updateState( + std::move(dynamicMap), static_cast(updateMode)); } } diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/StateWrapperImpl.h b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/StateWrapperImpl.h index 6e107180b555..388f2f067ce7 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/StateWrapperImpl.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/StateWrapperImpl.h @@ -27,7 +27,7 @@ class StateWrapperImpl : public jni::HybridClass jni::local_ref getStateMapBufferDataImpl(); jni::local_ref getStateDataImpl(); jni::local_ref getStateDataReferenceImpl(); - void updateStateImpl(NativeMap *map); + void updateStateImpl(NativeMap *map, jint updateMode); void setState(std::shared_ptr state); std::shared_ptr getState() const; 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 bb2a2fa0da10..4f0697464dc4 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<<251b3d06d3969eb89068403e3c07346e>> */ /** @@ -201,6 +201,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + bool enableFollowUpMountItemDispatchAndroid() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableFollowUpMountItemDispatchAndroid"); + return method(javaProvider_); + } + bool enableFontScaleChangesUpdatingLayout() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableFontScaleChangesUpdatingLayout"); @@ -712,6 +718,11 @@ bool JReactNativeFeatureFlagsCxxInterop::enableFlexboxAutoMinSizeInStrictMode( return ReactNativeFeatureFlags::enableFlexboxAutoMinSizeInStrictMode(); } +bool JReactNativeFeatureFlagsCxxInterop::enableFollowUpMountItemDispatchAndroid( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::enableFollowUpMountItemDispatchAndroid(); +} + bool JReactNativeFeatureFlagsCxxInterop::enableFontScaleChangesUpdatingLayout( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::enableFontScaleChangesUpdatingLayout(); @@ -1134,6 +1145,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "enableFlexboxAutoMinSizeInStrictMode", JReactNativeFeatureFlagsCxxInterop::enableFlexboxAutoMinSizeInStrictMode), + makeNativeMethod( + "enableFollowUpMountItemDispatchAndroid", + JReactNativeFeatureFlagsCxxInterop::enableFollowUpMountItemDispatchAndroid), makeNativeMethod( "enableFontScaleChangesUpdatingLayout", JReactNativeFeatureFlagsCxxInterop::enableFontScaleChangesUpdatingLayout), 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 c6ca35483ec0..9294dd45592e 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<<25d66101785e737822e86d218789912d>> + * @generated SignedSource<<913ddf4c8e4d0b4f897ff51e6d403b69>> */ /** @@ -111,6 +111,9 @@ class JReactNativeFeatureFlagsCxxInterop static bool enableFlexboxAutoMinSizeInStrictMode( facebook::jni::alias_ref); + static bool enableFollowUpMountItemDispatchAndroid( + facebook::jni::alias_ref); + static bool enableFontScaleChangesUpdatingLayout( facebook::jni::alias_ref); diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/mounting/MountItemDispatcherTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/mounting/MountItemDispatcherTest.kt new file mode 100644 index 000000000000..e0327690c416 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/mounting/MountItemDispatcherTest.kt @@ -0,0 +1,190 @@ +/* + * 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.fabric.mounting + +import com.facebook.react.fabric.mounting.MountItemDispatcher.ItemDispatchListener +import com.facebook.react.fabric.mounting.mountitems.MountItem +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.internal.tracing.PerformanceTracer +import com.facebook.react.uimanager.ViewManagerRegistry +import com.facebook.testutils.shadows.ShadowSoLoader +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import org.robolectric.annotation.Implementation +import org.robolectric.annotation.Implements + +@RunWith(RobolectricTestRunner::class) +@Config(shadows = [ShadowSoLoader::class, MountItemDispatcherTest.ShadowPerformanceTracer::class]) +class MountItemDispatcherTest { + + private lateinit var dispatcher: MountItemDispatcher + private lateinit var dispatchListener: RecordingItemDispatchListener + + @Before + fun setUp() { + ReactNativeFeatureFlagsForTests.setUp() + val mountingManager = + MountingManager(ViewManagerRegistry(emptyList()), MountingManager.MountItemExecutor {}) + dispatchListener = RecordingItemDispatchListener() + dispatcher = MountItemDispatcher(mountingManager, dispatchListener) + } + + @Test + fun tryDispatchMountItems_executesQueuedMountItem() { + val item = RecordingMountItem() + + dispatcher.addMountItem(item) + dispatcher.tryDispatchMountItems() + + assertThat(item.executed).isTrue() + assertThat(dispatchListener.didDispatchCount).isEqualTo(1) + } + + /** + * Regression test for synchronous (`unstable_Immediate`) state updates on Android. When a mount + * item's execution synchronously updates shadow node state, it can enqueue new mount items and + * re-enter [MountItemDispatcher.tryDispatchMountItems] while a dispatch is already in progress. + * + * The re-entrant call must not drop those items: they should be flushed in the same dispatch + * pass, rather than being deferred to the next frame. + */ + @Test + fun tryDispatchMountItems_reentrantDispatch_executesFollowUpItemInSamePass() { + enableFollowUpMountItemDispatch() + val followUpItem = RecordingMountItem() + // Simulates a synchronous state update triggered while the first item is being mounted: it + // enqueues another mount item and re-enters the dispatcher. + val initialItem = + RecordingMountItem( + onExecute = { + dispatcher.addMountItem(followUpItem) + dispatcher.tryDispatchMountItems() + } + ) + + dispatcher.addMountItem(initialItem) + dispatcher.tryDispatchMountItems() + + assertThat(initialItem.executed).isTrue() + assertThat(followUpItem.executed).isTrue() + } + + @Test + fun tryDispatchMountItems_reentrantDispatch_preservesExecutionOrder() { + enableFollowUpMountItemDispatch() + val executionOrder = mutableListOf() + val followUpItem = RecordingMountItem(onExecute = { executionOrder.add("followUp") }) + val initialItem = + RecordingMountItem( + onExecute = { + executionOrder.add("initial") + dispatcher.addMountItem(followUpItem) + dispatcher.tryDispatchMountItems() + } + ) + + dispatcher.addMountItem(initialItem) + dispatcher.tryDispatchMountItems() + + assertThat(executionOrder).containsExactly("initial", "followUp") + } + + @Test + fun tryDispatchMountItems_reentrantDispatch_invokesDidDispatchOnceForOuterCall() { + enableFollowUpMountItemDispatch() + val initialItem = + RecordingMountItem( + onExecute = { + dispatcher.addMountItem(RecordingMountItem()) + dispatcher.tryDispatchMountItems() + } + ) + + dispatcher.addMountItem(initialItem) + dispatcher.tryDispatchMountItems() + + // The re-entrant call returns early and must not notify the listener; only the outer call does, + // once, after the follow-up loop has drained everything. + assertThat(dispatchListener.didDispatchCount).isEqualTo(1) + } + + @Test + fun tryDispatchMountItems_reentrantDispatch_withFlagDisabled_defersFollowUpItem() { + val followUpItem = RecordingMountItem() + val initialItem = + RecordingMountItem( + onExecute = { + dispatcher.addMountItem(followUpItem) + dispatcher.tryDispatchMountItems() + } + ) + + dispatcher.addMountItem(initialItem) + dispatcher.tryDispatchMountItems() + + assertThat(initialItem.executed).isTrue() + assertThat(followUpItem.executed).isFalse() + + dispatcher.tryDispatchMountItems() + + assertThat(followUpItem.executed).isTrue() + } + + private fun enableFollowUpMountItemDispatch() { + ReactNativeFeatureFlags.override( + object : ReactNativeFeatureFlagsDefaults() { + override fun enableFollowUpMountItemDispatchAndroid(): Boolean = true + }, + ) + } + + private class RecordingMountItem( + private val surfaceId: Int = 1, + private val onExecute: () -> Unit = {}, + ) : MountItem { + var executed: Boolean = false + private set + + override fun execute(mountingManager: MountingManager) { + executed = true + onExecute() + } + + override fun getSurfaceId(): Int = surfaceId + } + + private class RecordingItemDispatchListener : ItemDispatchListener { + var didDispatchCount: Int = 0 + private set + + override fun willMountItems(mountItems: List?) = Unit + + override fun didMountItems(mountItems: List?) = Unit + + override fun didDispatchMountItems() { + didDispatchCount++ + } + + override fun onItemsQueued() = Unit + } + + // isTracing() is a native method; its JNI library isn't loaded in JVM tests, so return false and + // let trace() just run its block. ShadowSoLoader covers the SoLoader.loadLibrary init. + @Implements(PerformanceTracer::class) + class ShadowPerformanceTracer { + companion object { + @JvmStatic @Implementation fun isTracing(): Boolean = false + } + } +} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/StateWrapperUpdateModeTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/StateWrapperUpdateModeTest.kt new file mode 100644 index 000000000000..f3f12ff2de2d --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/StateWrapperUpdateModeTest.kt @@ -0,0 +1,36 @@ +/* + * 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 com.facebook.react.common.annotations.UnstableReactNativeAPI +import com.facebook.react.uimanager.StateWrapper.UpdateMode +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +/** + * UpdateMode.value crosses JNI and is cast straight to C++ EventQueue::UpdateMode, so the values + * must match its order (Asynchronous = 0, unstable_Immediate = 1). A mismatch silently flips + * async/sync state updates. + */ +@OptIn(UnstableReactNativeAPI::class) +@RunWith(RobolectricTestRunner::class) +class StateWrapperUpdateModeTest { + + @Test + fun updateModeValues_matchCxxEventQueueUpdateMode() { + assertThat(UpdateMode.Asynchronous.value).isEqualTo(0) + assertThat(UpdateMode.unstable_Immediate.value).isEqualTo(1) + } + + @Test + fun updateModeValues_areDistinct() { + assertThat(UpdateMode.entries.map { it.value }).doesNotHaveDuplicates() + } +} diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp index d5fd90438217..3caf4779d9cc 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<<8b8b80ac3744a205b20cfd0627856fba>> + * @generated SignedSource<<34a10b6009ba8ff100d238ccdb157e49>> */ /** @@ -134,6 +134,10 @@ bool ReactNativeFeatureFlags::enableFlexboxAutoMinSizeInStrictMode() { return getAccessor().enableFlexboxAutoMinSizeInStrictMode(); } +bool ReactNativeFeatureFlags::enableFollowUpMountItemDispatchAndroid() { + return getAccessor().enableFollowUpMountItemDispatchAndroid(); +} + bool ReactNativeFeatureFlags::enableFontScaleChangesUpdatingLayout() { return getAccessor().enableFontScaleChangesUpdatingLayout(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index 9f667efb1b52..9e5a78ce7993 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<<03873d0e7381921dc1ed1372eec991c0>> + * @generated SignedSource<> */ /** @@ -176,6 +176,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool enableFlexboxAutoMinSizeInStrictMode(); + /** + * When mount items are queued while MountItemDispatcher is already dispatching on Android (e.g. from a synchronous state update), dispatch them in the same frame instead of deferring them to the next one. + */ + RN_EXPORT static bool enableFollowUpMountItemDispatchAndroid(); + /** * Enables font scale changes updating layout for measurable nodes. */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index 9bb52b98e80a..75cba82ddcb4 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<<545c8d4044fd53f1fd585586a8d913a8>> */ /** @@ -515,6 +515,24 @@ bool ReactNativeFeatureFlagsAccessor::enableFlexboxAutoMinSizeInStrictMode() { return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::enableFollowUpMountItemDispatchAndroid() { + auto flagValue = enableFollowUpMountItemDispatchAndroid_.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(27, "enableFollowUpMountItemDispatchAndroid"); + + flagValue = currentProvider_->enableFollowUpMountItemDispatchAndroid(); + enableFollowUpMountItemDispatchAndroid_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::enableFontScaleChangesUpdatingLayout() { auto flagValue = enableFontScaleChangesUpdatingLayout_.load(); @@ -524,7 +542,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(27, "enableFontScaleChangesUpdatingLayout"); + markFlagAsAccessed(28, "enableFontScaleChangesUpdatingLayout"); flagValue = currentProvider_->enableFontScaleChangesUpdatingLayout(); enableFontScaleChangesUpdatingLayout_ = flagValue; @@ -542,7 +560,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(28, "enableIOSCompressedTextFrameAdjustment"); + markFlagAsAccessed(29, "enableIOSCompressedTextFrameAdjustment"); flagValue = currentProvider_->enableIOSCompressedTextFrameAdjustment(); enableIOSCompressedTextFrameAdjustment_ = flagValue; @@ -560,7 +578,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(29, "enableIOSTextBaselineOffsetPerLine"); + markFlagAsAccessed(30, "enableIOSTextBaselineOffsetPerLine"); flagValue = currentProvider_->enableIOSTextBaselineOffsetPerLine(); enableIOSTextBaselineOffsetPerLine_ = flagValue; @@ -578,7 +596,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(30, "enableIOSViewClipToPaddingBox"); + markFlagAsAccessed(31, "enableIOSViewClipToPaddingBox"); flagValue = currentProvider_->enableIOSViewClipToPaddingBox(); enableIOSViewClipToPaddingBox_ = flagValue; @@ -596,7 +614,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(31, "enableImagePrefetchingAndroid"); + markFlagAsAccessed(32, "enableImagePrefetchingAndroid"); flagValue = currentProvider_->enableImagePrefetchingAndroid(); enableImagePrefetchingAndroid_ = flagValue; @@ -614,7 +632,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(32, "enableImageTransparentTintColor"); + markFlagAsAccessed(33, "enableImageTransparentTintColor"); flagValue = currentProvider_->enableImageTransparentTintColor(); enableImageTransparentTintColor_ = flagValue; @@ -632,7 +650,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(33, "enableImmediateUpdateModeForContentOffsetChanges"); + markFlagAsAccessed(34, "enableImmediateUpdateModeForContentOffsetChanges"); flagValue = currentProvider_->enableImmediateUpdateModeForContentOffsetChanges(); enableImmediateUpdateModeForContentOffsetChanges_ = flagValue; @@ -650,7 +668,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(34, "enableImperativeEvents"); + markFlagAsAccessed(35, "enableImperativeEvents"); flagValue = currentProvider_->enableImperativeEvents(); enableImperativeEvents_ = flagValue; @@ -668,7 +686,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(35, "enableImperativeFocus"); + markFlagAsAccessed(36, "enableImperativeFocus"); flagValue = currentProvider_->enableImperativeFocus(); enableImperativeFocus_ = flagValue; @@ -686,7 +704,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(36, "enableInteropViewManagerClassLookUpOptimizationIOS"); + markFlagAsAccessed(37, "enableInteropViewManagerClassLookUpOptimizationIOS"); flagValue = currentProvider_->enableInteropViewManagerClassLookUpOptimizationIOS(); enableInteropViewManagerClassLookUpOptimizationIOS_ = flagValue; @@ -704,7 +722,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(37, "enableIntersectionObserverByDefault"); + markFlagAsAccessed(38, "enableIntersectionObserverByDefault"); flagValue = currentProvider_->enableIntersectionObserverByDefault(); enableIntersectionObserverByDefault_ = flagValue; @@ -722,7 +740,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(38, "enableKeyEvents"); + markFlagAsAccessed(39, "enableKeyEvents"); flagValue = currentProvider_->enableKeyEvents(); enableKeyEvents_ = flagValue; @@ -740,7 +758,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(39, "enableLayoutAnimationsOnAndroid"); + markFlagAsAccessed(40, "enableLayoutAnimationsOnAndroid"); flagValue = currentProvider_->enableLayoutAnimationsOnAndroid(); enableLayoutAnimationsOnAndroid_ = flagValue; @@ -758,7 +776,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(40, "enableLayoutAnimationsOnIOS"); + markFlagAsAccessed(41, "enableLayoutAnimationsOnIOS"); flagValue = currentProvider_->enableLayoutAnimationsOnIOS(); enableLayoutAnimationsOnIOS_ = flagValue; @@ -776,7 +794,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(41, "enableModuleArgumentNSNullConversionIOS"); + markFlagAsAccessed(42, "enableModuleArgumentNSNullConversionIOS"); flagValue = currentProvider_->enableModuleArgumentNSNullConversionIOS(); enableModuleArgumentNSNullConversionIOS_ = flagValue; @@ -794,7 +812,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(42, "enableMountingCoordinatorPullModelAndroid"); + markFlagAsAccessed(43, "enableMountingCoordinatorPullModelAndroid"); flagValue = currentProvider_->enableMountingCoordinatorPullModelAndroid(); enableMountingCoordinatorPullModelAndroid_ = flagValue; @@ -812,7 +830,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(43, "enableMutationObserverByDefault"); + markFlagAsAccessed(44, "enableMutationObserverByDefault"); flagValue = currentProvider_->enableMutationObserverByDefault(); enableMutationObserverByDefault_ = flagValue; @@ -830,7 +848,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(44, "enableNativeCSSParsing"); + markFlagAsAccessed(45, "enableNativeCSSParsing"); flagValue = currentProvider_->enableNativeCSSParsing(); enableNativeCSSParsing_ = flagValue; @@ -848,7 +866,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(45, "enablePreparedTextLayout"); + markFlagAsAccessed(46, "enablePreparedTextLayout"); flagValue = currentProvider_->enablePreparedTextLayout(); enablePreparedTextLayout_ = flagValue; @@ -866,7 +884,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(46, "enablePropsUpdateReconciliationAndroid"); + markFlagAsAccessed(47, "enablePropsUpdateReconciliationAndroid"); flagValue = currentProvider_->enablePropsUpdateReconciliationAndroid(); enablePropsUpdateReconciliationAndroid_ = flagValue; @@ -884,7 +902,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(47, "enableResizeObserverByDefault"); + markFlagAsAccessed(48, "enableResizeObserverByDefault"); flagValue = currentProvider_->enableResizeObserverByDefault(); enableResizeObserverByDefault_ = flagValue; @@ -902,7 +920,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(48, "enableSwiftUIBasedFilters"); + markFlagAsAccessed(49, "enableSwiftUIBasedFilters"); flagValue = currentProvider_->enableSwiftUIBasedFilters(); enableSwiftUIBasedFilters_ = flagValue; @@ -920,7 +938,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(49, "enableViewCulling"); + markFlagAsAccessed(50, "enableViewCulling"); flagValue = currentProvider_->enableViewCulling(); enableViewCulling_ = flagValue; @@ -938,7 +956,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(50, "enableViewRecycling"); + markFlagAsAccessed(51, "enableViewRecycling"); flagValue = currentProvider_->enableViewRecycling(); enableViewRecycling_ = flagValue; @@ -956,7 +974,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(51, "enableViewRecyclingForImage"); + markFlagAsAccessed(52, "enableViewRecyclingForImage"); flagValue = currentProvider_->enableViewRecyclingForImage(); enableViewRecyclingForImage_ = flagValue; @@ -974,7 +992,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(52, "enableViewRecyclingForScrollView"); + markFlagAsAccessed(53, "enableViewRecyclingForScrollView"); flagValue = currentProvider_->enableViewRecyclingForScrollView(); enableViewRecyclingForScrollView_ = flagValue; @@ -992,7 +1010,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(53, "enableViewRecyclingForText"); + markFlagAsAccessed(54, "enableViewRecyclingForText"); flagValue = currentProvider_->enableViewRecyclingForText(); enableViewRecyclingForText_ = flagValue; @@ -1010,7 +1028,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(54, "enableViewRecyclingForView"); + markFlagAsAccessed(55, "enableViewRecyclingForView"); flagValue = currentProvider_->enableViewRecyclingForView(); enableViewRecyclingForView_ = flagValue; @@ -1028,7 +1046,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(55, "enableVirtualViewContainerStateExperimental"); + markFlagAsAccessed(56, "enableVirtualViewContainerStateExperimental"); flagValue = currentProvider_->enableVirtualViewContainerStateExperimental(); enableVirtualViewContainerStateExperimental_ = flagValue; @@ -1046,7 +1064,7 @@ bool ReactNativeFeatureFlagsAccessor::fixMappingOfEventPrioritiesBetweenFabricAn // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(56, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); + markFlagAsAccessed(57, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); flagValue = currentProvider_->fixMappingOfEventPrioritiesBetweenFabricAndReact(); fixMappingOfEventPrioritiesBetweenFabricAndReact_ = flagValue; @@ -1064,7 +1082,7 @@ bool ReactNativeFeatureFlagsAccessor::fixYogaFlexBasisFitContentInMainAxis() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(57, "fixYogaFlexBasisFitContentInMainAxis"); + markFlagAsAccessed(58, "fixYogaFlexBasisFitContentInMainAxis"); flagValue = currentProvider_->fixYogaFlexBasisFitContentInMainAxis(); fixYogaFlexBasisFitContentInMainAxis_ = flagValue; @@ -1082,7 +1100,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxAssertSingleHostState() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(58, "fuseboxAssertSingleHostState"); + markFlagAsAccessed(59, "fuseboxAssertSingleHostState"); flagValue = currentProvider_->fuseboxAssertSingleHostState(); fuseboxAssertSingleHostState_ = flagValue; @@ -1100,7 +1118,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxEnabledRelease() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(59, "fuseboxEnabledRelease"); + markFlagAsAccessed(60, "fuseboxEnabledRelease"); flagValue = currentProvider_->fuseboxEnabledRelease(); fuseboxEnabledRelease_ = flagValue; @@ -1118,7 +1136,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxFrameRecordingEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(60, "fuseboxFrameRecordingEnabled"); + markFlagAsAccessed(61, "fuseboxFrameRecordingEnabled"); flagValue = currentProvider_->fuseboxFrameRecordingEnabled(); fuseboxFrameRecordingEnabled_ = flagValue; @@ -1136,7 +1154,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(61, "fuseboxScreenshotCaptureEnabled"); + markFlagAsAccessed(62, "fuseboxScreenshotCaptureEnabled"); flagValue = currentProvider_->fuseboxScreenshotCaptureEnabled(); fuseboxScreenshotCaptureEnabled_ = flagValue; @@ -1154,7 +1172,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(62, "fuseboxWebSocketEventsEnabled"); + markFlagAsAccessed(63, "fuseboxWebSocketEventsEnabled"); flagValue = currentProvider_->fuseboxWebSocketEventsEnabled(); fuseboxWebSocketEventsEnabled_ = flagValue; @@ -1172,7 +1190,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(63, "optimizedAnimatedPropUpdates"); + markFlagAsAccessed(64, "optimizedAnimatedPropUpdates"); flagValue = currentProvider_->optimizedAnimatedPropUpdates(); optimizedAnimatedPropUpdates_ = flagValue; @@ -1190,7 +1208,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(64, "overrideBySynchronousMountPropsAtMountingAndroid"); + markFlagAsAccessed(65, "overrideBySynchronousMountPropsAtMountingAndroid"); flagValue = currentProvider_->overrideBySynchronousMountPropsAtMountingAndroid(); overrideBySynchronousMountPropsAtMountingAndroid_ = flagValue; @@ -1208,7 +1226,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(65, "perfIssuesEnabled"); + markFlagAsAccessed(66, "perfIssuesEnabled"); flagValue = currentProvider_->perfIssuesEnabled(); perfIssuesEnabled_ = flagValue; @@ -1226,7 +1244,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(66, "perfMonitorV2Enabled"); + markFlagAsAccessed(67, "perfMonitorV2Enabled"); flagValue = currentProvider_->perfMonitorV2Enabled(); perfMonitorV2Enabled_ = flagValue; @@ -1244,7 +1262,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(67, "preparedTextCacheSize"); + markFlagAsAccessed(68, "preparedTextCacheSize"); flagValue = currentProvider_->preparedTextCacheSize(); preparedTextCacheSize_ = flagValue; @@ -1262,7 +1280,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(68, "preventShadowTreeCommitExhaustion"); + markFlagAsAccessed(69, "preventShadowTreeCommitExhaustion"); flagValue = currentProvider_->preventShadowTreeCommitExhaustion(); preventShadowTreeCommitExhaustion_ = flagValue; @@ -1280,7 +1298,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(69, "redBoxV2Android"); + markFlagAsAccessed(70, "redBoxV2Android"); flagValue = currentProvider_->redBoxV2Android(); redBoxV2Android_ = flagValue; @@ -1298,7 +1316,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(70, "redBoxV2IOS"); + markFlagAsAccessed(71, "redBoxV2IOS"); flagValue = currentProvider_->redBoxV2IOS(); redBoxV2IOS_ = flagValue; @@ -1316,7 +1334,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(71, "shouldPressibilityUseW3CPointerEventsForHover"); + markFlagAsAccessed(72, "shouldPressibilityUseW3CPointerEventsForHover"); flagValue = currentProvider_->shouldPressibilityUseW3CPointerEventsForHover(); shouldPressibilityUseW3CPointerEventsForHover_ = flagValue; @@ -1334,7 +1352,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(72, "shouldTriggerResponderTransferOnScrollAndroid"); + markFlagAsAccessed(73, "shouldTriggerResponderTransferOnScrollAndroid"); flagValue = currentProvider_->shouldTriggerResponderTransferOnScrollAndroid(); shouldTriggerResponderTransferOnScrollAndroid_ = flagValue; @@ -1352,7 +1370,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(73, "skipActivityIdentityAssertionOnHostPause"); + markFlagAsAccessed(74, "skipActivityIdentityAssertionOnHostPause"); flagValue = currentProvider_->skipActivityIdentityAssertionOnHostPause(); skipActivityIdentityAssertionOnHostPause_ = flagValue; @@ -1370,7 +1388,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(74, "syncAndroidClipBoundsWithOverflow"); + markFlagAsAccessed(75, "syncAndroidClipBoundsWithOverflow"); flagValue = currentProvider_->syncAndroidClipBoundsWithOverflow(); syncAndroidClipBoundsWithOverflow_ = flagValue; @@ -1388,7 +1406,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(75, "traceTurboModulePromiseRejectionsOnAndroid"); + markFlagAsAccessed(76, "traceTurboModulePromiseRejectionsOnAndroid"); flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid(); traceTurboModulePromiseRejectionsOnAndroid_ = flagValue; @@ -1406,7 +1424,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(76, "updateRuntimeShadowNodeReferencesOnCommit"); + markFlagAsAccessed(77, "updateRuntimeShadowNodeReferencesOnCommit"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommit(); updateRuntimeShadowNodeReferencesOnCommit_ = flagValue; @@ -1424,7 +1442,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(77, "updateRuntimeShadowNodeReferencesOnCommitThread"); + markFlagAsAccessed(78, "updateRuntimeShadowNodeReferencesOnCommitThread"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommitThread(); updateRuntimeShadowNodeReferencesOnCommitThread_ = flagValue; @@ -1442,7 +1460,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(78, "useAlwaysAvailableJSErrorHandling"); + markFlagAsAccessed(79, "useAlwaysAvailableJSErrorHandling"); flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling(); useAlwaysAvailableJSErrorHandling_ = flagValue; @@ -1460,7 +1478,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(79, "useFabricInterop"); + markFlagAsAccessed(80, "useFabricInterop"); flagValue = currentProvider_->useFabricInterop(); useFabricInterop_ = flagValue; @@ -1478,7 +1496,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(80, "useNativeViewConfigsInBridgelessMode"); + markFlagAsAccessed(81, "useNativeViewConfigsInBridgelessMode"); flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode(); useNativeViewConfigsInBridgelessMode_ = flagValue; @@ -1496,7 +1514,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(81, "useNestedScrollViewAndroid"); + markFlagAsAccessed(82, "useNestedScrollViewAndroid"); flagValue = currentProvider_->useNestedScrollViewAndroid(); useNestedScrollViewAndroid_ = flagValue; @@ -1514,7 +1532,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(82, "useSharedAnimatedBackend"); + markFlagAsAccessed(83, "useSharedAnimatedBackend"); flagValue = currentProvider_->useSharedAnimatedBackend(); useSharedAnimatedBackend_ = flagValue; @@ -1532,7 +1550,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(83, "useTraitHiddenOnAndroid"); + markFlagAsAccessed(84, "useTraitHiddenOnAndroid"); flagValue = currentProvider_->useTraitHiddenOnAndroid(); useTraitHiddenOnAndroid_ = flagValue; @@ -1550,7 +1568,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(84, "useTurboModuleInterop"); + markFlagAsAccessed(85, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = flagValue; @@ -1568,7 +1586,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(85, "viewCullingOutsetRatio"); + markFlagAsAccessed(86, "viewCullingOutsetRatio"); flagValue = currentProvider_->viewCullingOutsetRatio(); viewCullingOutsetRatio_ = flagValue; @@ -1586,7 +1604,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(86, "viewTransitionEnabled"); + markFlagAsAccessed(87, "viewTransitionEnabled"); flagValue = currentProvider_->viewTransitionEnabled(); viewTransitionEnabled_ = flagValue; @@ -1604,7 +1622,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(87, "viewTransitionUseHardwareBitmapAndroid"); + markFlagAsAccessed(88, "viewTransitionUseHardwareBitmapAndroid"); flagValue = currentProvider_->viewTransitionUseHardwareBitmapAndroid(); viewTransitionUseHardwareBitmapAndroid_ = flagValue; @@ -1622,7 +1640,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(88, "virtualViewPrerenderRatio"); + markFlagAsAccessed(89, "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 37b5bfe958f4..11e1f00f83ef 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<<021841c329e90b31fa5865c46c09186a>> */ /** @@ -61,6 +61,7 @@ class ReactNativeFeatureFlagsAccessor { bool enableFabricCommitBranchingMergeOnMainThread(); bool enableFabricLogs(); bool enableFlexboxAutoMinSizeInStrictMode(); + bool enableFollowUpMountItemDispatchAndroid(); bool enableFontScaleChangesUpdatingLayout(); bool enableIOSCompressedTextFrameAdjustment(); bool enableIOSTextBaselineOffsetPerLine(); @@ -134,7 +135,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 89> accessedFeatureFlags_; + std::array, 90> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> cdpInteractionMetricsEnabled_; @@ -163,6 +164,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> enableFabricCommitBranchingMergeOnMainThread_; std::atomic> enableFabricLogs_; std::atomic> enableFlexboxAutoMinSizeInStrictMode_; + std::atomic> enableFollowUpMountItemDispatchAndroid_; std::atomic> enableFontScaleChangesUpdatingLayout_; std::atomic> enableIOSCompressedTextFrameAdjustment_; std::atomic> enableIOSTextBaselineOffsetPerLine_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index 85fc19265b44..c8c411ec0c17 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<<7cd9353350b5e0d8b563f04e8ad17e7d>> */ /** @@ -137,6 +137,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return false; } + bool enableFollowUpMountItemDispatchAndroid() override { + return false; + } + bool enableFontScaleChangesUpdatingLayout() override { return true; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index 69054b7f1f7d..6e52268a636e 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<> */ /** @@ -290,6 +290,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::enableFlexboxAutoMinSizeInStrictMode(); } + bool enableFollowUpMountItemDispatchAndroid() override { + auto value = values_["enableFollowUpMountItemDispatchAndroid"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::enableFollowUpMountItemDispatchAndroid(); + } + bool enableFontScaleChangesUpdatingLayout() override { auto value = values_["enableFontScaleChangesUpdatingLayout"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index d7516f95a5ab..ba6efddd3294 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<<51165e8390c454fe7f3bb07bb3a69bb9>> + * @generated SignedSource<> */ /** @@ -54,6 +54,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool enableFabricCommitBranchingMergeOnMainThread() = 0; virtual bool enableFabricLogs() = 0; virtual bool enableFlexboxAutoMinSizeInStrictMode() = 0; + virtual bool enableFollowUpMountItemDispatchAndroid() = 0; virtual bool enableFontScaleChangesUpdatingLayout() = 0; virtual bool enableIOSCompressedTextFrameAdjustment() = 0; virtual bool enableIOSTextBaselineOffsetPerLine() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index fc948c8f01c2..6111bf862e29 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<<1bfe886891d4d195940b57548e3fcc6e>> + * @generated SignedSource<<25187c669178ccb0b5a67ba86af9f903>> */ /** @@ -179,6 +179,11 @@ bool NativeReactNativeFeatureFlags::enableFlexboxAutoMinSizeInStrictMode( return ReactNativeFeatureFlags::enableFlexboxAutoMinSizeInStrictMode(); } +bool NativeReactNativeFeatureFlags::enableFollowUpMountItemDispatchAndroid( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::enableFollowUpMountItemDispatchAndroid(); +} + bool NativeReactNativeFeatureFlags::enableFontScaleChangesUpdatingLayout( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::enableFontScaleChangesUpdatingLayout(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index bc2e6c36a912..308012eae5d8 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<> + * @generated SignedSource<<2b1eb67addbbb363d3008408d22e7e17>> */ /** @@ -92,6 +92,8 @@ class NativeReactNativeFeatureFlags bool enableFlexboxAutoMinSizeInStrictMode(jsi::Runtime& runtime); + bool enableFollowUpMountItemDispatchAndroid(jsi::Runtime& runtime); + bool enableFontScaleChangesUpdatingLayout(jsi::Runtime& runtime); bool enableIOSCompressedTextFrameAdjustment(jsi::Runtime& runtime); diff --git a/packages/react-native/ReactCommon/react/renderer/core/ConcreteState.h b/packages/react-native/ReactCommon/react/renderer/core/ConcreteState.h index f74a8c9f2416..1da50654a6c7 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ConcreteState.h +++ b/packages/react-native/ReactCommon/react/renderer/core/ConcreteState.h @@ -118,9 +118,10 @@ class ConcreteState : public State { return getData().getDynamic(); } - void updateState(folly::dynamic &&data) const override + void updateState(folly::dynamic &&data, EventQueue::UpdateMode updateMode = EventQueue::UpdateMode::Asynchronous) + const override { - updateState(Data(getData(), std::move(data))); + updateState(Data(getData(), std::move(data)), updateMode); } MapBuffer getMapBuffer() const override diff --git a/packages/react-native/ReactCommon/react/renderer/core/State.h b/packages/react-native/ReactCommon/react/renderer/core/State.h index 437409a119ef..585e018252d8 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/State.h +++ b/packages/react-native/ReactCommon/react/renderer/core/State.h @@ -12,6 +12,7 @@ #ifdef RN_SERIALIZABLE_STATE #include #include +#include #include #endif @@ -68,7 +69,9 @@ class State { virtual folly::dynamic getDynamic() const = 0; virtual MapBuffer getMapBuffer() const = 0; virtual jni::local_ref getJNIReference() const = 0; - virtual void updateState(folly::dynamic &&data) const = 0; + virtual void updateState( + folly::dynamic &&data, + EventQueue::UpdateMode updateMode = EventQueue::UpdateMode::Asynchronous) const = 0; #endif protected: diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index b7e56a770ebe..ef4ffecd25ae 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -337,6 +337,17 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'experimental', }, + enableFollowUpMountItemDispatchAndroid: { + defaultValue: false, + metadata: { + dateAdded: '2026-09-29', + description: + 'When mount items are queued while MountItemDispatcher is already dispatching on Android (e.g. from a synchronous state update), dispatch them in the same frame instead of deferring them to the next one.', + expectedReleaseValue: true, + purpose: 'experimentation', + }, + ossReleaseStage: 'none', + }, enableFontScaleChangesUpdatingLayout: { defaultValue: true, metadata: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index b06551330c29..8ac72c48ee94 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<<6e73a99aafb36218e1d679637bb80c82>> + * @generated SignedSource<<479d6b0198a9ce1c587b7bcb1ff8ec7d>> * @flow strict * @noformat */ @@ -76,6 +76,7 @@ export type ReactNativeFeatureFlags = Readonly<{ enableFabricCommitBranchingMergeOnMainThread: Getter, enableFabricLogs: Getter, enableFlexboxAutoMinSizeInStrictMode: Getter, + enableFollowUpMountItemDispatchAndroid: Getter, enableFontScaleChangesUpdatingLayout: Getter, enableIOSCompressedTextFrameAdjustment: Getter, enableIOSTextBaselineOffsetPerLine: Getter, @@ -322,6 +323,10 @@ export const enableFabricLogs: Getter = createNativeFlagGetter('enableF * Enables CSS Flexbox ยง4.5 automatic minimum sizing under strict layout conformance. When enabled, a flex item with an undefined main-axis `min-width`/`min-height` under strict conformance receives a content-derived minimum size (per spec) instead of an undefined (0) minimum. Defaults off so the behaviour can be ramped independently of strict conformance. */ export const enableFlexboxAutoMinSizeInStrictMode: Getter = createNativeFlagGetter('enableFlexboxAutoMinSizeInStrictMode', false); +/** + * When mount items are queued while MountItemDispatcher is already dispatching on Android (e.g. from a synchronous state update), dispatch them in the same frame instead of deferring them to the next one. + */ +export const enableFollowUpMountItemDispatchAndroid: Getter = createNativeFlagGetter('enableFollowUpMountItemDispatchAndroid', false); /** * Enables font scale changes updating layout for measurable nodes. */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index f843d04a95b4..a00cc2811527 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<> * @flow strict * @noformat */ @@ -52,6 +52,7 @@ export interface Spec extends TurboModule { readonly enableFabricCommitBranchingMergeOnMainThread?: () => boolean; readonly enableFabricLogs?: () => boolean; readonly enableFlexboxAutoMinSizeInStrictMode?: () => boolean; + readonly enableFollowUpMountItemDispatchAndroid?: () => boolean; readonly enableFontScaleChangesUpdatingLayout?: () => boolean; readonly enableIOSCompressedTextFrameAdjustment?: () => boolean; readonly enableIOSTextBaselineOffsetPerLine?: () => boolean; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index 52f451b09994..3289c0c25bf6 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -4906,7 +4906,7 @@ class facebook::react::State { public virtual facebook::react::MapBuffer getMapBuffer() const = 0; public virtual folly::dynamic getDynamic() const = 0; public virtual jni::local_ref getJNIReference() const = 0; - public virtual void updateState(folly::dynamic&& data) const = 0; + public virtual void updateState(folly::dynamic&& data, facebook::react::EventQueue::UpdateMode updateMode = facebook::react::EventQueue::UpdateMode::Asynchronous) const = 0; public virtual ~State() = default; } @@ -4930,7 +4930,7 @@ class facebook::react::StateWrapperImpl : public jni::HybridClass getState() const; public void setState(std::shared_ptr state); - public void updateStateImpl(facebook::react::NativeMap* map); + public void updateStateImpl(facebook::react::NativeMap* map, jint updateMode); } class facebook::react::StyleAnimatedNode : public facebook::react::AnimatedNode { @@ -8446,7 +8446,7 @@ class facebook::react::ConcreteState : public facebook::react::State { public virtual facebook::react::MapBuffer getMapBuffer() const override; public virtual folly::dynamic getDynamic() const override; public virtual jni::local_ref getJNIReference() const override; - public virtual void updateState(folly::dynamic&& data) const override; + public virtual void updateState(folly::dynamic&& data, facebook::react::EventQueue::UpdateMode updateMode = facebook::react::EventQueue::UpdateMode::Asynchronous) const override; public void updateState(facebook::react::ConcreteState::Data&& newData, facebook::react::EventQueue::UpdateMode updateMode = facebook::react::EventQueue::UpdateMode::Asynchronous) const; public void updateState(std::function callback, facebook::react::EventQueue::UpdateMode updateMode = facebook::react::EventQueue::UpdateMode::Asynchronous) const; public ~ConcreteState() override = default; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index 61a157f45c78..b92d30a6c9ce 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -4716,7 +4716,7 @@ class facebook::react::State { public virtual facebook::react::MapBuffer getMapBuffer() const = 0; public virtual folly::dynamic getDynamic() const = 0; public virtual jni::local_ref getJNIReference() const = 0; - public virtual void updateState(folly::dynamic&& data) const = 0; + public virtual void updateState(folly::dynamic&& data, facebook::react::EventQueue::UpdateMode updateMode = facebook::react::EventQueue::UpdateMode::Asynchronous) const = 0; public virtual ~State() = default; } @@ -4740,7 +4740,7 @@ class facebook::react::StateWrapperImpl : public jni::HybridClass getState() const; public void setState(std::shared_ptr state); - public void updateStateImpl(facebook::react::NativeMap* map); + public void updateStateImpl(facebook::react::NativeMap* map, jint updateMode); } class facebook::react::StyleAnimatedNode : public facebook::react::AnimatedNode { @@ -8206,7 +8206,7 @@ class facebook::react::ConcreteState : public facebook::react::State { public virtual facebook::react::MapBuffer getMapBuffer() const override; public virtual folly::dynamic getDynamic() const override; public virtual jni::local_ref getJNIReference() const override; - public virtual void updateState(folly::dynamic&& data) const override; + public virtual void updateState(folly::dynamic&& data, facebook::react::EventQueue::UpdateMode updateMode = facebook::react::EventQueue::UpdateMode::Asynchronous) const override; public void updateState(facebook::react::ConcreteState::Data&& newData, facebook::react::EventQueue::UpdateMode updateMode = facebook::react::EventQueue::UpdateMode::Asynchronous) const; public void updateState(std::function callback, facebook::react::EventQueue::UpdateMode updateMode = facebook::react::EventQueue::UpdateMode::Asynchronous) const; public ~ConcreteState() override = default; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index 12e08f58270a..df7f7d6b1777 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -4897,7 +4897,7 @@ class facebook::react::State { public virtual facebook::react::MapBuffer getMapBuffer() const = 0; public virtual folly::dynamic getDynamic() const = 0; public virtual jni::local_ref getJNIReference() const = 0; - public virtual void updateState(folly::dynamic&& data) const = 0; + public virtual void updateState(folly::dynamic&& data, facebook::react::EventQueue::UpdateMode updateMode = facebook::react::EventQueue::UpdateMode::Asynchronous) const = 0; public virtual ~State() = default; } @@ -4921,7 +4921,7 @@ class facebook::react::StateWrapperImpl : public jni::HybridClass getState() const; public void setState(std::shared_ptr state); - public void updateStateImpl(facebook::react::NativeMap* map); + public void updateStateImpl(facebook::react::NativeMap* map, jint updateMode); } class facebook::react::StyleAnimatedNode : public facebook::react::AnimatedNode { @@ -8437,7 +8437,7 @@ class facebook::react::ConcreteState : public facebook::react::State { public virtual facebook::react::MapBuffer getMapBuffer() const override; public virtual folly::dynamic getDynamic() const override; public virtual jni::local_ref getJNIReference() const override; - public virtual void updateState(folly::dynamic&& data) const override; + public virtual void updateState(folly::dynamic&& data, facebook::react::EventQueue::UpdateMode updateMode = facebook::react::EventQueue::UpdateMode::Asynchronous) const override; public void updateState(facebook::react::ConcreteState::Data&& newData, facebook::react::EventQueue::UpdateMode updateMode = facebook::react::EventQueue::UpdateMode::Asynchronous) const; public void updateState(std::function callback, facebook::react::EventQueue::UpdateMode updateMode = facebook::react::EventQueue::UpdateMode::Asynchronous) const; public ~ConcreteState() override = default;