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 243a3eae3fa8..f187a2f8dfa4 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<<39edde759ee98ddbe1a3723f114ceada>> */ /** @@ -438,6 +438,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun fuseboxWebSocketEventsEnabled(): Boolean = accessor.fuseboxWebSocketEventsEnabled() + /** + * Keep JS timers running on Android while the React host is paused, stopping them only when the React instance is destroyed. Matches iOS, where timers do not depend on host lifecycle. + */ + @JvmStatic + public fun keepTimersRunningWhileHostPausedAndroid(): Boolean = accessor.keepTimersRunningWhileHostPausedAndroid() + /** * When enabled, uses optimized platform-specific paths to apply animated props synchronously. On Android, this uses a batched int/double buffer protocol with a single JNI call. Other platforms apply the props of each view through the existing synchronous update. */ 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 0f110b756115..6fbe8910a293 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<<8012f449a876977a418c640e8537aee9>> */ /** @@ -88,6 +88,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var fuseboxFrameRecordingEnabledCache: Boolean? = null private var fuseboxScreenshotCaptureEnabledCache: Boolean? = null private var fuseboxWebSocketEventsEnabledCache: Boolean? = null + private var keepTimersRunningWhileHostPausedAndroidCache: Boolean? = null private var optimizedAnimatedPropUpdatesCache: Boolean? = null private var overrideBySynchronousMountPropsAtMountingAndroidCache: Boolean? = null private var perfIssuesEnabledCache: Boolean? = null @@ -729,6 +730,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun keepTimersRunningWhileHostPausedAndroid(): Boolean { + var cached = keepTimersRunningWhileHostPausedAndroidCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.keepTimersRunningWhileHostPausedAndroid() + keepTimersRunningWhileHostPausedAndroidCache = cached + } + return cached + } + override fun optimizedAnimatedPropUpdates(): Boolean { var cached = optimizedAnimatedPropUpdatesCache 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 fc4b23624bbd..ab022aab2987 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<<52b31eabb2d0590572c8a8c9a772a158>> */ /** @@ -164,6 +164,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun fuseboxWebSocketEventsEnabled(): Boolean + @DoNotStrip @JvmStatic public external fun keepTimersRunningWhileHostPausedAndroid(): Boolean + @DoNotStrip @JvmStatic public external fun optimizedAnimatedPropUpdates(): Boolean @DoNotStrip @JvmStatic public external fun overrideBySynchronousMountPropsAtMountingAndroid(): 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 380e9ec4c213..dc53838014fc 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<> + * @generated SignedSource<<931601ba571d0666c5920205cf40eb9f>> */ /** @@ -159,6 +159,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun fuseboxWebSocketEventsEnabled(): Boolean = false + override fun keepTimersRunningWhileHostPausedAndroid(): Boolean = false + override fun optimizedAnimatedPropUpdates(): Boolean = false override fun overrideBySynchronousMountPropsAtMountingAndroid(): 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 073cc2b90a73..dcda32200203 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<> + * @generated SignedSource<<13cf2ff00b943a9a15d55a6ef105cdcd>> */ /** @@ -92,6 +92,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var fuseboxFrameRecordingEnabledCache: Boolean? = null private var fuseboxScreenshotCaptureEnabledCache: Boolean? = null private var fuseboxWebSocketEventsEnabledCache: Boolean? = null + private var keepTimersRunningWhileHostPausedAndroidCache: Boolean? = null private var optimizedAnimatedPropUpdatesCache: Boolean? = null private var overrideBySynchronousMountPropsAtMountingAndroidCache: Boolean? = null private var perfIssuesEnabledCache: Boolean? = null @@ -801,6 +802,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun keepTimersRunningWhileHostPausedAndroid(): Boolean { + var cached = keepTimersRunningWhileHostPausedAndroidCache + if (cached == null) { + cached = currentProvider.keepTimersRunningWhileHostPausedAndroid() + accessedFeatureFlags.add("keepTimersRunningWhileHostPausedAndroid") + keepTimersRunningWhileHostPausedAndroidCache = cached + } + return cached + } + override fun optimizedAnimatedPropUpdates(): Boolean { var cached = optimizedAnimatedPropUpdatesCache 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 a639fd19fa5e..06479b15add7 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<> */ /** @@ -159,6 +159,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun fuseboxWebSocketEventsEnabled(): Boolean + @DoNotStrip public fun keepTimersRunningWhileHostPausedAndroid(): Boolean + @DoNotStrip public fun optimizedAnimatedPropUpdates(): Boolean @DoNotStrip public fun overrideBySynchronousMountPropsAtMountingAndroid(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.kt index f10f0efc419a..438249be0e72 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.kt @@ -20,6 +20,7 @@ import com.facebook.react.common.SystemClock.nanoTime import com.facebook.react.common.SystemClock.uptimeMillis import com.facebook.react.common.annotations.internal.LegacyArchitecture import com.facebook.react.devsupport.interfaces.DevSupportManager +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags import com.facebook.react.jstasks.HeadlessJsTaskContext import com.facebook.react.jstasks.HeadlessJsTaskEventListener import java.util.PriorityQueue @@ -50,6 +51,11 @@ public open class JavaTimerManager( private val idleCallbackGuard = Any() private val timerIdsToTimers: SparseArray = SparseArray() private val isPaused = AtomicBoolean(true) + private val isDestroyed = AtomicBoolean(false) + // Like iOS: timers follow the instance lifetime instead of the host's pause/resume, so JS + // work keeps progressing while no React screen is resumed. + private val keepTimersRunningWhileHostPaused = + ReactNativeFeatureFlags.keepTimersRunningWhileHostPausedAndroid() private val isRunningTasks = AtomicBoolean(false) private val timerFrameCallback = TimerFrameCallback() private val idleFrameCallback = IdleFrameCallback() @@ -65,6 +71,15 @@ public open class JavaTimerManager( init { reactApplicationContext.addLifecycleEventListener(this) HeadlessJsTaskContext.getInstance(reactApplicationContext).addTaskEventListener(this) + if (keepTimersRunningWhileHostPaused) { + // The host may never resume (e.g. no React screen is shown), so start timers here. + UiThreadUtil.runOnUiThread { + if (!isDestroyed.get()) { + setChoreographerCallback() + maybeSetChoreographerIdleCallback() + } + } + } } override fun onHostPause() { @@ -105,10 +120,14 @@ public open class JavaTimerManager( public open fun onInstanceDestroy() { HeadlessJsTaskContext.getInstance(reactApplicationContext).removeTaskEventListener(this) reactApplicationContext.removeLifecycleEventListener(this) + isDestroyed.set(true) clearFrameCallback() clearChoreographerIdleCallback() } + private fun isStopped(): Boolean = + if (keepTimersRunningWhileHostPaused) isDestroyed.get() else isPaused.get() + @LegacyArchitecture private fun maybeSetChoreographerIdleCallback() { synchronized(idleCallbackGuard) { @@ -120,7 +139,7 @@ public open class JavaTimerManager( @LegacyArchitecture private fun maybeIdleCallback() { - if (isPaused.get() && !isRunningTasks.get()) { + if (isStopped() && !isRunningTasks.get()) { clearFrameCallback() } } @@ -137,7 +156,7 @@ public open class JavaTimerManager( private fun clearFrameCallback() { val headlessJsTaskContext = HeadlessJsTaskContext.getInstance(reactApplicationContext) - if (frameCallbackPosted && isPaused.get() && !headlessJsTaskContext.hasActiveTasks()) { + if (frameCallbackPosted && isStopped() && !headlessJsTaskContext.hasActiveTasks()) { reactChoreographer.removeFrameCallback( ReactChoreographer.CallbackType.TIMERS_EVENTS, timerFrameCallback, @@ -286,7 +305,7 @@ public open class JavaTimerManager( /** Calls all timers that have expired since the last time this frame callback was called. */ override fun doFrame(frameTimeNanos: Long) { - if (isPaused.get() && !isRunningTasks.get()) { + if (isStopped() && !isRunningTasks.get()) { return } val frameTimeMillis = frameTimeNanos / 1000000 @@ -318,7 +337,7 @@ public open class JavaTimerManager( private inner class IdleFrameCallback : Choreographer.FrameCallback { override fun doFrame(frameTimeNanos: Long) { - if (isPaused.get() && !isRunningTasks.get()) { + if (isStopped() && !isRunningTasks.get()) { return } 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 882566ee95ea..a6e0a3b8b0f8 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<<1ab2eec7b9c5bdd181ec5fbfd37149ab>> + * @generated SignedSource<<1a0f644a9504d1279e892fc4f9174b10>> */ /** @@ -447,6 +447,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + bool keepTimersRunningWhileHostPausedAndroid() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("keepTimersRunningWhileHostPausedAndroid"); + return method(javaProvider_); + } + bool optimizedAnimatedPropUpdates() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("optimizedAnimatedPropUpdates"); @@ -959,6 +965,11 @@ bool JReactNativeFeatureFlagsCxxInterop::fuseboxWebSocketEventsEnabled( return ReactNativeFeatureFlags::fuseboxWebSocketEventsEnabled(); } +bool JReactNativeFeatureFlagsCxxInterop::keepTimersRunningWhileHostPausedAndroid( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::keepTimersRunningWhileHostPausedAndroid(); +} + bool JReactNativeFeatureFlagsCxxInterop::optimizedAnimatedPropUpdates( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::optimizedAnimatedPropUpdates(); @@ -1334,6 +1345,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "fuseboxWebSocketEventsEnabled", JReactNativeFeatureFlagsCxxInterop::fuseboxWebSocketEventsEnabled), + makeNativeMethod( + "keepTimersRunningWhileHostPausedAndroid", + JReactNativeFeatureFlagsCxxInterop::keepTimersRunningWhileHostPausedAndroid), makeNativeMethod( "optimizedAnimatedPropUpdates", JReactNativeFeatureFlagsCxxInterop::optimizedAnimatedPropUpdates), 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 8ae17b1dc3b6..8f116d545eda 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<<2c8f9205f1de55591ede3845ba2fa877>> + * @generated SignedSource<<156a320a8a7e9509922dbf14bdab255e>> */ /** @@ -234,6 +234,9 @@ class JReactNativeFeatureFlagsCxxInterop static bool fuseboxWebSocketEventsEnabled( facebook::jni::alias_ref); + static bool keepTimersRunningWhileHostPausedAndroid( + facebook::jni::alias_ref); + static bool optimizedAnimatedPropUpdates( facebook::jni::alias_ref); diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/core/JavaTimerManagerTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/core/JavaTimerManagerTest.kt new file mode 100644 index 000000000000..fbcef15b80d0 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/core/JavaTimerManagerTest.kt @@ -0,0 +1,99 @@ +/* + * 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.modules.core + +import android.os.Looper +import android.view.Choreographer +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.devsupport.interfaces.DevSupportManager +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsDefaults +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests +import com.facebook.testutils.shadows.ShadowArguments +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify +import org.robolectric.RobolectricTestRunner +import org.robolectric.Shadows.shadowOf +import org.robolectric.annotation.Config + +@Config(shadows = [ShadowArguments::class]) +@RunWith(RobolectricTestRunner::class) +class JavaTimerManagerTest { + private lateinit var reactChoreographer: ReactChoreographer + private lateinit var timerExecutor: JavaScriptTimerExecutor + + @Before + fun setUp() { + ReactNativeFeatureFlagsForTests.setUp() + reactChoreographer = mock() + timerExecutor = mock() + } + + @After + fun tearDown() { + ReactNativeFeatureFlags.dangerouslyReset() + } + + private fun newTimerManager(keepTimersRunningWhileHostPaused: Boolean): JavaTimerManager { + ReactNativeFeatureFlags.dangerouslyReset() + ReactNativeFeatureFlags.override( + object : ReactNativeFeatureFlagsDefaults() { + override fun keepTimersRunningWhileHostPausedAndroid(): Boolean = + keepTimersRunningWhileHostPaused + } + ) + val timerManager = + JavaTimerManager( + mock(), + timerExecutor, + reactChoreographer, + mock(), + ) + shadowOf(Looper.getMainLooper()).idle() + return timerManager + } + + private fun postedTimerCallback(): Choreographer.FrameCallback { + val captor = argumentCaptor() + verify(reactChoreographer) + .postFrameCallback(eq(ReactChoreographer.CallbackType.TIMERS_EVENTS), captor.capture()) + return captor.firstValue + } + + @Test + fun `timers run from creation, survive host pause, and stop on instance destroy`() { + val timerManager = newTimerManager(keepTimersRunningWhileHostPaused = true) + timerManager.onHostPause() + timerManager.createTimer(timerId = 1, delay = 0, repeat = false) + + val frameCallback = postedTimerCallback() + frameCallback.doFrame(Long.MAX_VALUE) + verify(timerExecutor).callTimers(any()) + + timerManager.onInstanceDestroy() + timerManager.createTimer(timerId = 2, delay = 0, repeat = false) + frameCallback.doFrame(Long.MAX_VALUE) + verify(timerExecutor).callTimers(any()) + } + + @Test + fun `timers wait for host resume without the flag`() { + newTimerManager(keepTimersRunningWhileHostPaused = false) + + verify(reactChoreographer, never()) + .postFrameCallback(eq(ReactChoreographer.CallbackType.TIMERS_EVENTS), any()) + } +} diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp index c2457ce7afc5..6f387044dd67 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<<7542508e6a3b0dd942557748186527b3>> + * @generated SignedSource<<2ab4512e00e9fb73996f0437ceeedafa>> */ /** @@ -298,6 +298,10 @@ bool ReactNativeFeatureFlags::fuseboxWebSocketEventsEnabled() { return getAccessor().fuseboxWebSocketEventsEnabled(); } +bool ReactNativeFeatureFlags::keepTimersRunningWhileHostPausedAndroid() { + return getAccessor().keepTimersRunningWhileHostPausedAndroid(); +} + bool ReactNativeFeatureFlags::optimizedAnimatedPropUpdates() { return getAccessor().optimizedAnimatedPropUpdates(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index 9668de64aaee..830ed4f508a6 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<> + * @generated SignedSource<<60ce3e98148a564855b62bf499658731>> */ /** @@ -381,6 +381,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool fuseboxWebSocketEventsEnabled(); + /** + * Keep JS timers running on Android while the React host is paused, stopping them only when the React instance is destroyed. Matches iOS, where timers do not depend on host lifecycle. + */ + RN_EXPORT static bool keepTimersRunningWhileHostPausedAndroid(); + /** * When enabled, uses optimized platform-specific paths to apply animated props synchronously. On Android, this uses a batched int/double buffer protocol with a single JNI call. Other platforms apply the props of each view through the existing synchronous update. */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index 418d452984b9..c9c9cccef17c 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<<3c7ff59afd1e9e05051e888b51a39ac0>> + * @generated SignedSource<> */ /** @@ -1253,6 +1253,24 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxWebSocketEventsEnabled() { return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::keepTimersRunningWhileHostPausedAndroid() { + auto flagValue = keepTimersRunningWhileHostPausedAndroid_.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(68, "keepTimersRunningWhileHostPausedAndroid"); + + flagValue = currentProvider_->keepTimersRunningWhileHostPausedAndroid(); + keepTimersRunningWhileHostPausedAndroid_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::optimizedAnimatedPropUpdates() { auto flagValue = optimizedAnimatedPropUpdates_.load(); @@ -1262,7 +1280,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(68, "optimizedAnimatedPropUpdates"); + markFlagAsAccessed(69, "optimizedAnimatedPropUpdates"); flagValue = currentProvider_->optimizedAnimatedPropUpdates(); optimizedAnimatedPropUpdates_ = flagValue; @@ -1280,7 +1298,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(69, "overrideBySynchronousMountPropsAtMountingAndroid"); + markFlagAsAccessed(70, "overrideBySynchronousMountPropsAtMountingAndroid"); flagValue = currentProvider_->overrideBySynchronousMountPropsAtMountingAndroid(); overrideBySynchronousMountPropsAtMountingAndroid_ = flagValue; @@ -1298,7 +1316,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(70, "perfIssuesEnabled"); + markFlagAsAccessed(71, "perfIssuesEnabled"); flagValue = currentProvider_->perfIssuesEnabled(); perfIssuesEnabled_ = flagValue; @@ -1316,7 +1334,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(71, "perfMonitorV2Enabled"); + markFlagAsAccessed(72, "perfMonitorV2Enabled"); flagValue = currentProvider_->perfMonitorV2Enabled(); perfMonitorV2Enabled_ = flagValue; @@ -1334,7 +1352,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(72, "preparedTextCacheSize"); + markFlagAsAccessed(73, "preparedTextCacheSize"); flagValue = currentProvider_->preparedTextCacheSize(); preparedTextCacheSize_ = flagValue; @@ -1352,7 +1370,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(73, "preventShadowTreeCommitExhaustion"); + markFlagAsAccessed(74, "preventShadowTreeCommitExhaustion"); flagValue = currentProvider_->preventShadowTreeCommitExhaustion(); preventShadowTreeCommitExhaustion_ = flagValue; @@ -1370,7 +1388,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(74, "redBoxV2Android"); + markFlagAsAccessed(75, "redBoxV2Android"); flagValue = currentProvider_->redBoxV2Android(); redBoxV2Android_ = flagValue; @@ -1388,7 +1406,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(75, "redBoxV2IOS"); + markFlagAsAccessed(76, "redBoxV2IOS"); flagValue = currentProvider_->redBoxV2IOS(); redBoxV2IOS_ = flagValue; @@ -1406,7 +1424,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(76, "shouldPressibilityUseW3CPointerEventsForHover"); + markFlagAsAccessed(77, "shouldPressibilityUseW3CPointerEventsForHover"); flagValue = currentProvider_->shouldPressibilityUseW3CPointerEventsForHover(); shouldPressibilityUseW3CPointerEventsForHover_ = flagValue; @@ -1424,7 +1442,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(77, "shouldTriggerResponderTransferOnScrollAndroid"); + markFlagAsAccessed(78, "shouldTriggerResponderTransferOnScrollAndroid"); flagValue = currentProvider_->shouldTriggerResponderTransferOnScrollAndroid(); shouldTriggerResponderTransferOnScrollAndroid_ = flagValue; @@ -1442,7 +1460,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(78, "skipActivityIdentityAssertionOnHostPause"); + markFlagAsAccessed(79, "skipActivityIdentityAssertionOnHostPause"); flagValue = currentProvider_->skipActivityIdentityAssertionOnHostPause(); skipActivityIdentityAssertionOnHostPause_ = flagValue; @@ -1460,7 +1478,7 @@ bool ReactNativeFeatureFlagsAccessor::skipUnchangedChildrenInDifferentiator() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(79, "skipUnchangedChildrenInDifferentiator"); + markFlagAsAccessed(80, "skipUnchangedChildrenInDifferentiator"); flagValue = currentProvider_->skipUnchangedChildrenInDifferentiator(); skipUnchangedChildrenInDifferentiator_ = flagValue; @@ -1478,7 +1496,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(80, "syncAndroidClipBoundsWithOverflow"); + markFlagAsAccessed(81, "syncAndroidClipBoundsWithOverflow"); flagValue = currentProvider_->syncAndroidClipBoundsWithOverflow(); syncAndroidClipBoundsWithOverflow_ = flagValue; @@ -1496,7 +1514,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(81, "traceTurboModulePromiseRejectionsOnAndroid"); + markFlagAsAccessed(82, "traceTurboModulePromiseRejectionsOnAndroid"); flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid(); traceTurboModulePromiseRejectionsOnAndroid_ = flagValue; @@ -1514,7 +1532,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(82, "updateRuntimeShadowNodeReferencesOnCommit"); + markFlagAsAccessed(83, "updateRuntimeShadowNodeReferencesOnCommit"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommit(); updateRuntimeShadowNodeReferencesOnCommit_ = flagValue; @@ -1532,7 +1550,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(83, "updateRuntimeShadowNodeReferencesOnCommitThread"); + markFlagAsAccessed(84, "updateRuntimeShadowNodeReferencesOnCommitThread"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommitThread(); updateRuntimeShadowNodeReferencesOnCommitThread_ = flagValue; @@ -1550,7 +1568,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(84, "useAlwaysAvailableJSErrorHandling"); + markFlagAsAccessed(85, "useAlwaysAvailableJSErrorHandling"); flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling(); useAlwaysAvailableJSErrorHandling_ = flagValue; @@ -1568,7 +1586,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(85, "useFabricInterop"); + markFlagAsAccessed(86, "useFabricInterop"); flagValue = currentProvider_->useFabricInterop(); useFabricInterop_ = flagValue; @@ -1586,7 +1604,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(86, "useNativeViewConfigsInBridgelessMode"); + markFlagAsAccessed(87, "useNativeViewConfigsInBridgelessMode"); flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode(); useNativeViewConfigsInBridgelessMode_ = flagValue; @@ -1604,7 +1622,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(87, "useNestedScrollViewAndroid"); + markFlagAsAccessed(88, "useNestedScrollViewAndroid"); flagValue = currentProvider_->useNestedScrollViewAndroid(); useNestedScrollViewAndroid_ = flagValue; @@ -1622,7 +1640,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(88, "useSharedAnimatedBackend"); + markFlagAsAccessed(89, "useSharedAnimatedBackend"); flagValue = currentProvider_->useSharedAnimatedBackend(); useSharedAnimatedBackend_ = flagValue; @@ -1640,7 +1658,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(89, "useTraitHiddenOnAndroid"); + markFlagAsAccessed(90, "useTraitHiddenOnAndroid"); flagValue = currentProvider_->useTraitHiddenOnAndroid(); useTraitHiddenOnAndroid_ = flagValue; @@ -1658,7 +1676,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(90, "useTurboModuleInterop"); + markFlagAsAccessed(91, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = flagValue; @@ -1676,7 +1694,7 @@ bool ReactNativeFeatureFlagsAccessor::useWindowDensityForDisplayMetricsAndroid() // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(91, "useWindowDensityForDisplayMetricsAndroid"); + markFlagAsAccessed(92, "useWindowDensityForDisplayMetricsAndroid"); flagValue = currentProvider_->useWindowDensityForDisplayMetricsAndroid(); useWindowDensityForDisplayMetricsAndroid_ = flagValue; @@ -1694,7 +1712,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(92, "viewCullingOutsetRatio"); + markFlagAsAccessed(93, "viewCullingOutsetRatio"); flagValue = currentProvider_->viewCullingOutsetRatio(); viewCullingOutsetRatio_ = flagValue; @@ -1712,7 +1730,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(93, "viewTransitionEnabled"); + markFlagAsAccessed(94, "viewTransitionEnabled"); flagValue = currentProvider_->viewTransitionEnabled(); viewTransitionEnabled_ = flagValue; @@ -1730,7 +1748,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(94, "viewTransitionUseHardwareBitmapAndroid"); + markFlagAsAccessed(95, "viewTransitionUseHardwareBitmapAndroid"); flagValue = currentProvider_->viewTransitionUseHardwareBitmapAndroid(); viewTransitionUseHardwareBitmapAndroid_ = flagValue; @@ -1748,7 +1766,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(95, "virtualViewPrerenderRatio"); + markFlagAsAccessed(96, "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 a6ba9a46d70f..53dd3d93b5fa 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<<0b7f6d7766d1e038b3909bb7ea634f83>> + * @generated SignedSource<<8710e5ce1e7de3effe347d0f96646ce3>> */ /** @@ -102,6 +102,7 @@ class ReactNativeFeatureFlagsAccessor { bool fuseboxFrameRecordingEnabled(); bool fuseboxScreenshotCaptureEnabled(); bool fuseboxWebSocketEventsEnabled(); + bool keepTimersRunningWhileHostPausedAndroid(); bool optimizedAnimatedPropUpdates(); bool overrideBySynchronousMountPropsAtMountingAndroid(); bool perfIssuesEnabled(); @@ -141,7 +142,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 96> accessedFeatureFlags_; + std::array, 97> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> cdpInteractionMetricsEnabled_; @@ -211,6 +212,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> fuseboxFrameRecordingEnabled_; std::atomic> fuseboxScreenshotCaptureEnabled_; std::atomic> fuseboxWebSocketEventsEnabled_; + std::atomic> keepTimersRunningWhileHostPausedAndroid_; std::atomic> optimizedAnimatedPropUpdates_; std::atomic> overrideBySynchronousMountPropsAtMountingAndroid_; std::atomic> perfIssuesEnabled_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index 76bb504681e3..6e77133dc786 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<<46e48274f8b27b9c6136b13048682252>> + * @generated SignedSource<> */ /** @@ -301,6 +301,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return false; } + bool keepTimersRunningWhileHostPausedAndroid() override { + return false; + } + bool optimizedAnimatedPropUpdates() override { return false; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index bda4465a9160..eb7f2305d6d3 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<> */ /** @@ -659,6 +659,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::fuseboxWebSocketEventsEnabled(); } + bool keepTimersRunningWhileHostPausedAndroid() override { + auto value = values_["keepTimersRunningWhileHostPausedAndroid"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::keepTimersRunningWhileHostPausedAndroid(); + } + bool optimizedAnimatedPropUpdates() override { auto value = values_["optimizedAnimatedPropUpdates"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index bc1459bef441..52477bddef74 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<<7626d25a1450564a30de026870f4d094>> + * @generated SignedSource<<76a8f199d164c0e8f18cb4351cff07cf>> */ /** @@ -95,6 +95,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool fuseboxFrameRecordingEnabled() = 0; virtual bool fuseboxScreenshotCaptureEnabled() = 0; virtual bool fuseboxWebSocketEventsEnabled() = 0; + virtual bool keepTimersRunningWhileHostPausedAndroid() = 0; virtual bool optimizedAnimatedPropUpdates() = 0; virtual bool overrideBySynchronousMountPropsAtMountingAndroid() = 0; virtual bool perfIssuesEnabled() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index 9d8c414b2686..ec158bdcd7a1 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<> + * @generated SignedSource<> */ /** @@ -384,6 +384,11 @@ bool NativeReactNativeFeatureFlags::fuseboxWebSocketEventsEnabled( return ReactNativeFeatureFlags::fuseboxWebSocketEventsEnabled(); } +bool NativeReactNativeFeatureFlags::keepTimersRunningWhileHostPausedAndroid( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::keepTimersRunningWhileHostPausedAndroid(); +} + bool NativeReactNativeFeatureFlags::optimizedAnimatedPropUpdates( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::optimizedAnimatedPropUpdates(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index 4014c7ac6551..131f88b6fed0 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<<4d3151d0ce9a022c5b9b74e2a910229d>> + * @generated SignedSource<<8b89d1dcfc0f03eb1baf434aa05c43b4>> */ /** @@ -174,6 +174,8 @@ class NativeReactNativeFeatureFlags bool fuseboxWebSocketEventsEnabled(jsi::Runtime& runtime); + bool keepTimersRunningWhileHostPausedAndroid(jsi::Runtime& runtime); + bool optimizedAnimatedPropUpdates(jsi::Runtime& runtime); bool overrideBySynchronousMountPropsAtMountingAndroid(jsi::Runtime& runtime); diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index ac6c41e0a8e3..24159561a8ea 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -782,6 +782,17 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'canary', }, + keepTimersRunningWhileHostPausedAndroid: { + defaultValue: false, + metadata: { + dateAdded: '2026-10-22', + description: + 'Keep JS timers running on Android while the React host is paused, stopping them only when the React instance is destroyed. Matches iOS, where timers do not depend on host lifecycle.', + expectedReleaseValue: true, + purpose: 'experimentation', + }, + ossReleaseStage: 'none', + }, optimizedAnimatedPropUpdates: { defaultValue: false, metadata: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index c1c6f65c21d1..e577f3f450a2 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<<5a79107fda7ef89a1d68594cad01b8e7>> + * @generated SignedSource<<81f80b2e53ff814f6331de11e1f983f0>> * @flow strict * @noformat */ @@ -117,6 +117,7 @@ export type ReactNativeFeatureFlags = Readonly<{ fuseboxFrameRecordingEnabled: Getter, fuseboxScreenshotCaptureEnabled: Getter, fuseboxWebSocketEventsEnabled: Getter, + keepTimersRunningWhileHostPausedAndroid: Getter, optimizedAnimatedPropUpdates: Getter, overrideBySynchronousMountPropsAtMountingAndroid: Getter, perfIssuesEnabled: Getter, @@ -493,6 +494,10 @@ export const fuseboxScreenshotCaptureEnabled: Getter = createNativeFlag * Enable reporting of WebSocket network events (`Network.webSocket*` CDP events) to the React Native DevTools CDP backend. */ export const fuseboxWebSocketEventsEnabled: Getter = createNativeFlagGetter('fuseboxWebSocketEventsEnabled', false); +/** + * Keep JS timers running on Android while the React host is paused, stopping them only when the React instance is destroyed. Matches iOS, where timers do not depend on host lifecycle. + */ +export const keepTimersRunningWhileHostPausedAndroid: Getter = createNativeFlagGetter('keepTimersRunningWhileHostPausedAndroid', false); /** * When enabled, uses optimized platform-specific paths to apply animated props synchronously. On Android, this uses a batched int/double buffer protocol with a single JNI call. Other platforms apply the props of each view through the existing synchronous update. */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index 48355705857f..824052a68fdc 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<<8a60267bdb9494ecfe79748fce0e89cc>> * @flow strict * @noformat */ @@ -93,6 +93,7 @@ export interface Spec extends TurboModule { readonly fuseboxFrameRecordingEnabled?: () => boolean; readonly fuseboxScreenshotCaptureEnabled?: () => boolean; readonly fuseboxWebSocketEventsEnabled?: () => boolean; + readonly keepTimersRunningWhileHostPausedAndroid?: () => boolean; readonly optimizedAnimatedPropUpdates?: () => boolean; readonly overrideBySynchronousMountPropsAtMountingAndroid?: () => boolean; readonly perfIssuesEnabled?: () => boolean;