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 84be6d08c390..1e4d29909741 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<> */ /** @@ -114,6 +114,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun enableAndroidTextMeasurementOptimizations(): Boolean = accessor.enableAndroidTextMeasurementOptimizations() + /** + * Runs multi-source image disk-cache checks asynchronously on Android. + */ + @JvmStatic + public fun enableAsyncDiskCacheCheckInMultiSourceImageAndroid(): Boolean = accessor.enableAsyncDiskCacheCheckInMultiSourceImageAndroid() + /** * Feature flag to enable the new bridgeless architecture. */ 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 8cc1c8822329..863ffce34ed4 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<<00e1e0529cbef727ab81a6a75174c87e>> + * @generated SignedSource<> */ /** @@ -34,6 +34,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var enableAccumulatedUpdatesInRawPropsAndroidCache: Boolean? = null private var enableAndroidAutoOffscreenCompositingForElevationCache: Boolean? = null private var enableAndroidTextMeasurementOptimizationsCache: Boolean? = null + private var enableAsyncDiskCacheCheckInMultiSourceImageAndroidCache: Boolean? = null private var enableBridgelessArchitectureCache: Boolean? = null private var enableBufferedCallInvokerCache: Boolean? = null private var enableCppPropsIteratorSetterCache: Boolean? = null @@ -235,6 +236,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun enableAsyncDiskCacheCheckInMultiSourceImageAndroid(): Boolean { + var cached = enableAsyncDiskCacheCheckInMultiSourceImageAndroidCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.enableAsyncDiskCacheCheckInMultiSourceImageAndroid() + enableAsyncDiskCacheCheckInMultiSourceImageAndroidCache = cached + } + return cached + } + override fun enableBridgelessArchitecture(): Boolean { var cached = enableBridgelessArchitectureCache 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 04ddf890d815..de896c451b40 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<<427c98d364e8a0aec3a0834b6a8117c8>> + * @generated SignedSource<> */ /** @@ -56,6 +56,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun enableAndroidTextMeasurementOptimizations(): Boolean + @DoNotStrip @JvmStatic public external fun enableAsyncDiskCacheCheckInMultiSourceImageAndroid(): Boolean + @DoNotStrip @JvmStatic public external fun enableBridgelessArchitecture(): Boolean @DoNotStrip @JvmStatic public external fun enableBufferedCallInvoker(): 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 8e2c60c4606c..6184e4feaced 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<<80a561079091b74aa3c682296ffe7da3>> */ /** @@ -51,6 +51,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun enableAndroidTextMeasurementOptimizations(): Boolean = false + override fun enableAsyncDiskCacheCheckInMultiSourceImageAndroid(): Boolean = false + override fun enableBridgelessArchitecture(): Boolean = true override fun enableBufferedCallInvoker(): 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 e8d343fe70bf..e9beb7831613 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<<20a908f1ef475bb5f51b631e2d0f2038>> + * @generated SignedSource<<3dd1a8c6227c3a1186ceaad7e3f0fc24>> */ /** @@ -38,6 +38,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var enableAccumulatedUpdatesInRawPropsAndroidCache: Boolean? = null private var enableAndroidAutoOffscreenCompositingForElevationCache: Boolean? = null private var enableAndroidTextMeasurementOptimizationsCache: Boolean? = null + private var enableAsyncDiskCacheCheckInMultiSourceImageAndroidCache: Boolean? = null private var enableBridgelessArchitectureCache: Boolean? = null private var enableBufferedCallInvokerCache: Boolean? = null private var enableCppPropsIteratorSetterCache: Boolean? = null @@ -253,6 +254,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun enableAsyncDiskCacheCheckInMultiSourceImageAndroid(): Boolean { + var cached = enableAsyncDiskCacheCheckInMultiSourceImageAndroidCache + if (cached == null) { + cached = currentProvider.enableAsyncDiskCacheCheckInMultiSourceImageAndroid() + accessedFeatureFlags.add("enableAsyncDiskCacheCheckInMultiSourceImageAndroid") + enableAsyncDiskCacheCheckInMultiSourceImageAndroidCache = cached + } + return cached + } + override fun enableBridgelessArchitecture(): Boolean { var cached = enableBridgelessArchitectureCache 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 b75a18108bd7..2cc30834e2b7 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<<7c81e9f6b6859f3bdc1ea8ad5539be5b>> */ /** @@ -51,6 +51,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun enableAndroidTextMeasurementOptimizations(): Boolean + @DoNotStrip public fun enableAsyncDiskCacheCheckInMultiSourceImageAndroid(): Boolean + @DoNotStrip public fun enableBridgelessArchitecture(): Boolean @DoNotStrip public fun enableBufferedCallInvoker(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt index 0b3e78db9b3e..6c41468a184e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt @@ -47,9 +47,11 @@ import com.facebook.imagepipeline.request.Postprocessor import com.facebook.react.bridge.ReactContext import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap +import com.facebook.react.bridge.UiThreadUtil.runOnUiThread import com.facebook.react.common.annotations.UnstableReactNativeAPI import com.facebook.react.common.annotations.VisibleForTesting import com.facebook.react.common.build.ReactBuildConfig +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags import com.facebook.react.internal.featureflags.ReactNativeNewArchitectureFeatureFlags import com.facebook.react.modules.fresco.ImageCacheControl import com.facebook.react.modules.fresco.ReactNetworkImageRequest @@ -72,6 +74,7 @@ import com.facebook.react.views.image.ImageResizeMode.defaultValue import com.facebook.react.views.image.MultiPostprocessor.Companion.from import com.facebook.react.views.imagehelper.ImageSource import com.facebook.react.views.imagehelper.ImageSource.Companion.getTransparentBitmapImageSource +import com.facebook.react.views.imagehelper.MultiSourceHelper import com.facebook.react.views.imagehelper.MultiSourceHelper.getBestSourceForSize import com.facebook.react.views.imagehelper.ResourceDrawableIdHelper import kotlin.math.abs @@ -106,6 +109,7 @@ public class ReactImageView( private var headers: ReadableMap? = null private var resizeMultiplier = 1.0f private var resizeMethod = ImageResizeMethod.AUTO + private var sourceGeneration = 0 init { // Workaround Android bug where ImageView visibility is not propagated to the Drawable, so you @@ -313,6 +317,7 @@ public class ReactImageView( this.sources.clear() this.sources.addAll(tmpSources) + sourceGeneration++ isDirty = true } @@ -544,6 +549,7 @@ public class ReactImageView( protected override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) if (w > 0 && h > 0) { + sourceGeneration++ isDirty = isDirty || hasMultipleSources() || isTiled maybeUpdateView() } @@ -555,13 +561,38 @@ public class ReactImageView( get() = tileMode != TileMode.CLAMP private fun setSourceImage() { + sourceGeneration++ imageSource = null + cachedImageSource = null if (sources.isEmpty()) { sources.add(getTransparentBitmapImageSource(context)) } else if (hasMultipleSources()) { val multiSource = getBestSourceForSize(width, height, sources) imageSource = multiSource.bestResult cachedImageSource = multiSource.bestResultInCache + val selectedSource = imageSource + if ( + selectedSource != null && + selectedSource.cacheControl != ImageCacheControl.RELOAD && + ReactNativeFeatureFlags.enableAsyncDiskCacheCheckInMultiSourceImageAndroid() + ) { + val generation = sourceGeneration + MultiSourceHelper.findBestCachedSourceFromDiskAsync(width, height, sources, 1.0) { result -> + runOnUiThread { + if ( + generation != sourceGeneration || + !isAttachedToWindow || + result == null || + result.source == cachedImageSource?.source || + imageSource?.source != selectedSource.source + ) { + return@runOnUiThread + } + cachedImageSource = result + maybeUpdateViewFromRequest(shouldResize(selectedSource)) + } + } + } return } imageSource = sources[0] diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/MultiSourceHelper.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/MultiSourceHelper.kt index 442ef575e87a..01d36a7b0160 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/MultiSourceHelper.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/MultiSourceHelper.kt @@ -7,7 +7,11 @@ package com.facebook.react.views.imagehelper +import com.facebook.common.executors.CallerThreadExecutor +import com.facebook.datasource.BaseDataSubscriber +import com.facebook.datasource.DataSource import com.facebook.imagepipeline.core.ImagePipelineFactory +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags import com.facebook.react.modules.fresco.ImageCacheControl import kotlin.math.abs @@ -33,50 +37,155 @@ internal object MultiSourceHelper { sources: List, multiplier: Double, ): MultiSourceResult { - // no sources if (sources.isEmpty()) { return MultiSourceResult(null, null) } - - // single source if (sources.size == 1) { return MultiSourceResult(sources[0], null) } - - // For multiple sources, we first need the view's size in order to determine the best source to - // load. If we haven't been measured yet, return null and wait for onSizeChanged. if (width <= 0 || height <= 0) { return MultiSourceResult(null, null) } + val imagePipeline = ImagePipelineFactory.getInstance().imagePipeline - var best: ImageSource? = null - var bestCached: ImageSource? = null + val best = findBestSourceForSize(width, height, sources, multiplier) + val checkDiskCache = + !ReactNativeFeatureFlags.enableAsyncDiskCacheCheckInMultiSourceImageAndroid() + val bestCached = + findBestCachedSourceForSize(width, height, sources, multiplier) { source -> + imagePipeline.isInBitmapMemoryCache(source.uri) || + (checkDiskCache && imagePipeline.isInDiskCacheSync(source.uri)) + } + + return MultiSourceResult(best, bestCached.takeUnless { it?.source == best?.source }) + } + + @JvmStatic + fun findBestCachedSourceFromDiskAsync( + width: Int, + height: Int, + sources: List, + multiplier: Double, + onResult: (ImageSource?) -> Unit, + ) { + val imagePipeline = ImagePipelineFactory.getInstance().imagePipeline + findBestCachedSourceFromDiskAsync( + width, + height, + sources, + multiplier, + isInMemoryCache = { source -> imagePipeline.isInBitmapMemoryCache(source.uri) }, + isInDiskCache = { source, callback -> + imagePipeline + .isInDiskCache(source.uri) + .subscribe( + object : BaseDataSubscriber() { + override fun onNewResultImpl(dataSource: DataSource) { + if (dataSource.isFinished) { + callback(dataSource.result == true) + } + } + + override fun onFailureImpl(dataSource: DataSource) { + callback(false) + } + }, + CallerThreadExecutor.getInstance(), + ) + }, + onResult, + ) + } + + internal fun findBestCachedSourceFromDiskAsync( + width: Int, + height: Int, + sources: List, + multiplier: Double, + isInMemoryCache: (ImageSource) -> Boolean, + isInDiskCache: (ImageSource, (Boolean) -> Unit) -> Unit, + onResult: (ImageSource?) -> Unit, + ) { + if (sources.size <= 1 || width <= 0 || height <= 0) { + onResult(null) + return + } + + val best = findBestSourceForSize(width, height, sources, multiplier) val viewArea = width * height * multiplier - var bestPrecision = Double.MAX_VALUE + val candidates = + sources + .filter { it.cacheControl != ImageCacheControl.RELOAD } + .sortedBy { abs(1.0 - it.size / viewArea) } + findFirstCachedSource(candidates, 0, isInMemoryCache, isInDiskCache) { source -> + onResult(source.takeUnless { it?.source == best?.source }) + } + } + + private fun findFirstCachedSource( + candidates: List, + index: Int, + isInMemoryCache: (ImageSource) -> Boolean, + isInDiskCache: (ImageSource, (Boolean) -> Unit) -> Unit, + onResult: (ImageSource?) -> Unit, + ) { + if (index >= candidates.size) { + onResult(null) + return + } + + val candidate = candidates[index] + if (isInMemoryCache(candidate)) { + onResult(candidate) + return + } + isInDiskCache(candidate) { isCached -> + if (isCached) { + onResult(candidate) + } else { + findFirstCachedSource( + candidates, + index + 1, + isInMemoryCache, + isInDiskCache, + onResult, + ) + } + } + } + + private fun findBestSourceForSize( + width: Int, + height: Int, + sources: List, + multiplier: Double, + ): ImageSource? { + val viewArea = width * height * multiplier + return sources.minByOrNull { source -> abs(1.0 - source.size / viewArea) } + } + + private fun findBestCachedSourceForSize( + width: Int, + height: Int, + sources: List, + multiplier: Double, + isCached: (ImageSource) -> Boolean, + ): ImageSource? { + val viewArea = width * height * multiplier + var bestCached: ImageSource? = null var bestCachePrecision = Double.MAX_VALUE for (source in sources) { val precision = abs(1.0 - source.size / viewArea) - if (precision < bestPrecision) { - bestPrecision = precision - best = source - } if ( precision < bestCachePrecision && source.cacheControl != ImageCacheControl.RELOAD && - (imagePipeline.isInBitmapMemoryCache(source.uri) || - // TODO: T206445115 isInDiskCacheSync is a blocking operation, we should move this - // to - // a separate thread - imagePipeline.isInDiskCacheSync(source.uri)) + isCached(source) ) { bestCachePrecision = precision bestCached = source } } - if (bestCached != null && best != null && bestCached.source == best.source) { - bestCached = null - } - return MultiSourceResult(best, bestCached) + return bestCached } class MultiSourceResult( 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 7377ae90c211..d8e51b808f1c 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<<13e3827487eba885b4de3746844f1942>> + * @generated SignedSource<<803863505a045e12dd54018811756e57>> */ /** @@ -123,6 +123,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + bool enableAsyncDiskCacheCheckInMultiSourceImageAndroid() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableAsyncDiskCacheCheckInMultiSourceImageAndroid"); + return method(javaProvider_); + } + bool enableBridgelessArchitecture() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableBridgelessArchitecture"); @@ -641,6 +647,11 @@ bool JReactNativeFeatureFlagsCxxInterop::enableAndroidTextMeasurementOptimizatio return ReactNativeFeatureFlags::enableAndroidTextMeasurementOptimizations(); } +bool JReactNativeFeatureFlagsCxxInterop::enableAsyncDiskCacheCheckInMultiSourceImageAndroid( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::enableAsyncDiskCacheCheckInMultiSourceImageAndroid(); +} + bool JReactNativeFeatureFlagsCxxInterop::enableBridgelessArchitecture( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::enableBridgelessArchitecture(); @@ -1084,6 +1095,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "enableAndroidTextMeasurementOptimizations", JReactNativeFeatureFlagsCxxInterop::enableAndroidTextMeasurementOptimizations), + makeNativeMethod( + "enableAsyncDiskCacheCheckInMultiSourceImageAndroid", + JReactNativeFeatureFlagsCxxInterop::enableAsyncDiskCacheCheckInMultiSourceImageAndroid), makeNativeMethod( "enableBridgelessArchitecture", JReactNativeFeatureFlagsCxxInterop::enableBridgelessArchitecture), 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 6cefb2837514..3a069656c1a4 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<2251eb4745e06db17b28edeee468bd73>> */ /** @@ -72,6 +72,9 @@ class JReactNativeFeatureFlagsCxxInterop static bool enableAndroidTextMeasurementOptimizations( facebook::jni::alias_ref); + static bool enableAsyncDiskCacheCheckInMultiSourceImageAndroid( + facebook::jni::alias_ref); + static bool enableBridgelessArchitecture( facebook::jni::alias_ref); diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/image/MultiSourceHelperTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/image/MultiSourceHelperTest.kt new file mode 100644 index 000000000000..919d129a1d38 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/image/MultiSourceHelperTest.kt @@ -0,0 +1,156 @@ +/* + * 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.views.image + +import com.facebook.react.modules.fresco.ImageCacheControl +import com.facebook.react.views.imagehelper.ImageSource +import com.facebook.react.views.imagehelper.MultiSourceHelper +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment + +@RunWith(RobolectricTestRunner::class) +class MultiSourceHelperTest { + + @Test + fun asyncDiskLookupChecksSourcesByPrecisionUntilItFindsCachedSource() { + val best = imageSource("best", 100.0) + val cached = imageSource("cached", 50.0) + val largest = imageSource("largest", 200.0) + val checkedSources = mutableListOf() + var result: ImageSource? = null + + MultiSourceHelper.findBestCachedSourceFromDiskAsync( + width = 100, + height = 100, + sources = listOf(largest, cached, best), + multiplier = 1.0, + isInMemoryCache = { false }, + isInDiskCache = { source, callback -> + checkedSources.add(source) + callback(source == cached) + }, + onResult = { result = it }, + ) + + assertThat(checkedSources).containsExactly(best, cached) + assertThat(result).isEqualTo(cached) + } + + @Test + fun asyncDiskLookupReturnsNullWhenBestSourceIsCached() { + val best = imageSource("best", 100.0) + val smaller = imageSource("smaller", 50.0) + val checkedSources = mutableListOf() + var result: ImageSource? = smaller + + MultiSourceHelper.findBestCachedSourceFromDiskAsync( + width = 100, + height = 100, + sources = listOf(smaller, best), + multiplier = 1.0, + isInMemoryCache = { false }, + isInDiskCache = { source, callback -> + checkedSources.add(source) + callback(true) + }, + onResult = { result = it }, + ) + + assertThat(checkedSources).containsExactly(best) + assertThat(result).isNull() + } + + @Test + fun asyncDiskLookupUsesMemoryCacheBeforeCheckingThatSourcesDiskCache() { + val best = imageSource("best", 100.0) + val cached = imageSource("cached", 50.0) + val checkedSources = mutableListOf() + var result: ImageSource? = null + + MultiSourceHelper.findBestCachedSourceFromDiskAsync( + width = 100, + height = 100, + sources = listOf(cached, best), + multiplier = 1.0, + isInMemoryCache = { source -> source == cached }, + isInDiskCache = { source, callback -> + checkedSources.add(source) + callback(false) + }, + onResult = { result = it }, + ) + + assertThat(checkedSources).containsExactly(best) + assertThat(result).isEqualTo(cached) + } + + @Test + fun asyncDiskLookupPrefersMorePreciseDiskSourceOverLessPreciseMemorySource() { + val best = imageSource("best", 100.0) + val diskCached = imageSource("disk", 90.0) + val memoryCached = imageSource("memory", 50.0) + val checkedSources = mutableListOf() + var result: ImageSource? = null + + MultiSourceHelper.findBestCachedSourceFromDiskAsync( + width = 100, + height = 100, + sources = listOf(memoryCached, diskCached, best), + multiplier = 1.0, + isInMemoryCache = { source -> source == memoryCached }, + isInDiskCache = { source, callback -> + checkedSources.add(source) + callback(source == diskCached) + }, + onResult = { result = it }, + ) + + assertThat(checkedSources).containsExactly(best, diskCached) + assertThat(result).isEqualTo(diskCached) + } + + @Test + fun asyncDiskLookupSkipsReloadSources() { + val best = imageSource("best", 100.0, ImageCacheControl.RELOAD) + val cached = imageSource("cached", 50.0) + val checkedSources = mutableListOf() + var result: ImageSource? = null + + MultiSourceHelper.findBestCachedSourceFromDiskAsync( + width = 100, + height = 100, + sources = listOf(cached, best), + multiplier = 1.0, + isInMemoryCache = { false }, + isInDiskCache = { source, callback -> + checkedSources.add(source) + callback(true) + }, + onResult = { result = it }, + ) + + assertThat(checkedSources).containsExactly(cached) + assertThat(result).isEqualTo(cached) + } + + private fun imageSource( + name: String, + side: Double, + cacheControl: ImageCacheControl = ImageCacheControl.DEFAULT, + ): ImageSource = + ImageSource( + RuntimeEnvironment.getApplication(), + "https://example.com/$name.png", + side, + side, + cacheControl, + ) +} diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp index 7bd58e4837a3..8f065e9eeebf 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<<32b83f89bbe2015f8e5dc52bbd621921>> + * @generated SignedSource<<7b98d6e75f9733b035a6f5a855048334>> */ /** @@ -82,6 +82,10 @@ bool ReactNativeFeatureFlags::enableAndroidTextMeasurementOptimizations() { return getAccessor().enableAndroidTextMeasurementOptimizations(); } +bool ReactNativeFeatureFlags::enableAsyncDiskCacheCheckInMultiSourceImageAndroid() { + return getAccessor().enableAsyncDiskCacheCheckInMultiSourceImageAndroid(); +} + bool ReactNativeFeatureFlags::enableBridgelessArchitecture() { return getAccessor().enableBridgelessArchitecture(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index 68aeb90becfb..7524aaeab041 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<<13ce24473fdf631a2689e2ad89904fcd>> + * @generated SignedSource<<193e17df99dab3eed7301cef20f50c1c>> */ /** @@ -111,6 +111,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool enableAndroidTextMeasurementOptimizations(); + /** + * Runs multi-source image disk-cache checks asynchronously on Android. + */ + RN_EXPORT static bool enableAsyncDiskCacheCheckInMultiSourceImageAndroid(); + /** * Feature flag to enable the new bridgeless architecture. */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index e8c097db5b75..34ff9172a319 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<<6045db442997c76cd83c0dd12126988c>> */ /** @@ -281,6 +281,24 @@ bool ReactNativeFeatureFlagsAccessor::enableAndroidTextMeasurementOptimizations( return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::enableAsyncDiskCacheCheckInMultiSourceImageAndroid() { + auto flagValue = enableAsyncDiskCacheCheckInMultiSourceImageAndroid_.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(14, "enableAsyncDiskCacheCheckInMultiSourceImageAndroid"); + + flagValue = currentProvider_->enableAsyncDiskCacheCheckInMultiSourceImageAndroid(); + enableAsyncDiskCacheCheckInMultiSourceImageAndroid_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::enableBridgelessArchitecture() { auto flagValue = enableBridgelessArchitecture_.load(); @@ -290,7 +308,7 @@ bool ReactNativeFeatureFlagsAccessor::enableBridgelessArchitecture() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(14, "enableBridgelessArchitecture"); + markFlagAsAccessed(15, "enableBridgelessArchitecture"); flagValue = currentProvider_->enableBridgelessArchitecture(); enableBridgelessArchitecture_ = flagValue; @@ -308,7 +326,7 @@ bool ReactNativeFeatureFlagsAccessor::enableBufferedCallInvoker() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(15, "enableBufferedCallInvoker"); + markFlagAsAccessed(16, "enableBufferedCallInvoker"); flagValue = currentProvider_->enableBufferedCallInvoker(); enableBufferedCallInvoker_ = flagValue; @@ -326,7 +344,7 @@ bool ReactNativeFeatureFlagsAccessor::enableCppPropsIteratorSetter() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(16, "enableCppPropsIteratorSetter"); + markFlagAsAccessed(17, "enableCppPropsIteratorSetter"); flagValue = currentProvider_->enableCppPropsIteratorSetter(); enableCppPropsIteratorSetter_ = flagValue; @@ -344,7 +362,7 @@ bool ReactNativeFeatureFlagsAccessor::enableCustomFocusSearchOnClippedElementsAn // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(17, "enableCustomFocusSearchOnClippedElementsAndroid"); + markFlagAsAccessed(18, "enableCustomFocusSearchOnClippedElementsAndroid"); flagValue = currentProvider_->enableCustomFocusSearchOnClippedElementsAndroid(); enableCustomFocusSearchOnClippedElementsAndroid_ = flagValue; @@ -362,7 +380,7 @@ bool ReactNativeFeatureFlagsAccessor::enableDestroyShadowTreeRevisionAsync() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(18, "enableDestroyShadowTreeRevisionAsync"); + markFlagAsAccessed(19, "enableDestroyShadowTreeRevisionAsync"); flagValue = currentProvider_->enableDestroyShadowTreeRevisionAsync(); enableDestroyShadowTreeRevisionAsync_ = flagValue; @@ -380,7 +398,7 @@ bool ReactNativeFeatureFlagsAccessor::enableDoubleMeasurementFixAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(19, "enableDoubleMeasurementFixAndroid"); + markFlagAsAccessed(20, "enableDoubleMeasurementFixAndroid"); flagValue = currentProvider_->enableDoubleMeasurementFixAndroid(); enableDoubleMeasurementFixAndroid_ = flagValue; @@ -398,7 +416,7 @@ bool ReactNativeFeatureFlagsAccessor::enableEagerRootViewAttachment() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(20, "enableEagerRootViewAttachment"); + markFlagAsAccessed(21, "enableEagerRootViewAttachment"); flagValue = currentProvider_->enableEagerRootViewAttachment(); enableEagerRootViewAttachment_ = flagValue; @@ -416,7 +434,7 @@ bool ReactNativeFeatureFlagsAccessor::enableExclusivePropsUpdateAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(21, "enableExclusivePropsUpdateAndroid"); + markFlagAsAccessed(22, "enableExclusivePropsUpdateAndroid"); flagValue = currentProvider_->enableExclusivePropsUpdateAndroid(); enableExclusivePropsUpdateAndroid_ = flagValue; @@ -434,7 +452,7 @@ bool ReactNativeFeatureFlagsAccessor::enableFabricCommitBranching() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(22, "enableFabricCommitBranching"); + markFlagAsAccessed(23, "enableFabricCommitBranching"); flagValue = currentProvider_->enableFabricCommitBranching(); enableFabricCommitBranching_ = flagValue; @@ -452,7 +470,7 @@ bool ReactNativeFeatureFlagsAccessor::enableFabricCommitBranchingMergeOnMainThre // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(23, "enableFabricCommitBranchingMergeOnMainThread"); + markFlagAsAccessed(24, "enableFabricCommitBranchingMergeOnMainThread"); flagValue = currentProvider_->enableFabricCommitBranchingMergeOnMainThread(); enableFabricCommitBranchingMergeOnMainThread_ = flagValue; @@ -470,7 +488,7 @@ bool ReactNativeFeatureFlagsAccessor::enableFabricLogs() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(24, "enableFabricLogs"); + markFlagAsAccessed(25, "enableFabricLogs"); flagValue = currentProvider_->enableFabricLogs(); enableFabricLogs_ = flagValue; @@ -488,7 +506,7 @@ bool ReactNativeFeatureFlagsAccessor::enableFlexboxAutoMinSizeInStrictMode() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(25, "enableFlexboxAutoMinSizeInStrictMode"); + markFlagAsAccessed(26, "enableFlexboxAutoMinSizeInStrictMode"); flagValue = currentProvider_->enableFlexboxAutoMinSizeInStrictMode(); enableFlexboxAutoMinSizeInStrictMode_ = flagValue; @@ -506,7 +524,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(26, "enableFontScaleChangesUpdatingLayout"); + markFlagAsAccessed(27, "enableFontScaleChangesUpdatingLayout"); flagValue = currentProvider_->enableFontScaleChangesUpdatingLayout(); enableFontScaleChangesUpdatingLayout_ = flagValue; @@ -524,7 +542,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(27, "enableIOSCompressedTextFrameAdjustment"); + markFlagAsAccessed(28, "enableIOSCompressedTextFrameAdjustment"); flagValue = currentProvider_->enableIOSCompressedTextFrameAdjustment(); enableIOSCompressedTextFrameAdjustment_ = flagValue; @@ -542,7 +560,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(28, "enableIOSTextBaselineOffsetPerLine"); + markFlagAsAccessed(29, "enableIOSTextBaselineOffsetPerLine"); flagValue = currentProvider_->enableIOSTextBaselineOffsetPerLine(); enableIOSTextBaselineOffsetPerLine_ = flagValue; @@ -560,7 +578,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(29, "enableIOSViewClipToPaddingBox"); + markFlagAsAccessed(30, "enableIOSViewClipToPaddingBox"); flagValue = currentProvider_->enableIOSViewClipToPaddingBox(); enableIOSViewClipToPaddingBox_ = flagValue; @@ -578,7 +596,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(30, "enableImagePrefetchingAndroid"); + markFlagAsAccessed(31, "enableImagePrefetchingAndroid"); flagValue = currentProvider_->enableImagePrefetchingAndroid(); enableImagePrefetchingAndroid_ = flagValue; @@ -596,7 +614,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(31, "enableImageTransparentTintColor"); + markFlagAsAccessed(32, "enableImageTransparentTintColor"); flagValue = currentProvider_->enableImageTransparentTintColor(); enableImageTransparentTintColor_ = flagValue; @@ -614,7 +632,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(32, "enableImmediateUpdateModeForContentOffsetChanges"); + markFlagAsAccessed(33, "enableImmediateUpdateModeForContentOffsetChanges"); flagValue = currentProvider_->enableImmediateUpdateModeForContentOffsetChanges(); enableImmediateUpdateModeForContentOffsetChanges_ = flagValue; @@ -632,7 +650,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(33, "enableImperativeEvents"); + markFlagAsAccessed(34, "enableImperativeEvents"); flagValue = currentProvider_->enableImperativeEvents(); enableImperativeEvents_ = flagValue; @@ -650,7 +668,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(34, "enableImperativeFocus"); + markFlagAsAccessed(35, "enableImperativeFocus"); flagValue = currentProvider_->enableImperativeFocus(); enableImperativeFocus_ = flagValue; @@ -668,7 +686,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(35, "enableInteropViewManagerClassLookUpOptimizationIOS"); + markFlagAsAccessed(36, "enableInteropViewManagerClassLookUpOptimizationIOS"); flagValue = currentProvider_->enableInteropViewManagerClassLookUpOptimizationIOS(); enableInteropViewManagerClassLookUpOptimizationIOS_ = flagValue; @@ -686,7 +704,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(36, "enableIntersectionObserverByDefault"); + markFlagAsAccessed(37, "enableIntersectionObserverByDefault"); flagValue = currentProvider_->enableIntersectionObserverByDefault(); enableIntersectionObserverByDefault_ = flagValue; @@ -704,7 +722,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(37, "enableKeyEvents"); + markFlagAsAccessed(38, "enableKeyEvents"); flagValue = currentProvider_->enableKeyEvents(); enableKeyEvents_ = flagValue; @@ -722,7 +740,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(38, "enableLayoutAnimationsOnAndroid"); + markFlagAsAccessed(39, "enableLayoutAnimationsOnAndroid"); flagValue = currentProvider_->enableLayoutAnimationsOnAndroid(); enableLayoutAnimationsOnAndroid_ = flagValue; @@ -740,7 +758,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(39, "enableLayoutAnimationsOnIOS"); + markFlagAsAccessed(40, "enableLayoutAnimationsOnIOS"); flagValue = currentProvider_->enableLayoutAnimationsOnIOS(); enableLayoutAnimationsOnIOS_ = flagValue; @@ -758,7 +776,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(40, "enableModuleArgumentNSNullConversionIOS"); + markFlagAsAccessed(41, "enableModuleArgumentNSNullConversionIOS"); flagValue = currentProvider_->enableModuleArgumentNSNullConversionIOS(); enableModuleArgumentNSNullConversionIOS_ = flagValue; @@ -776,7 +794,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(41, "enableMountingCoordinatorPullModelAndroid"); + markFlagAsAccessed(42, "enableMountingCoordinatorPullModelAndroid"); flagValue = currentProvider_->enableMountingCoordinatorPullModelAndroid(); enableMountingCoordinatorPullModelAndroid_ = flagValue; @@ -794,7 +812,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(42, "enableMutationObserverByDefault"); + markFlagAsAccessed(43, "enableMutationObserverByDefault"); flagValue = currentProvider_->enableMutationObserverByDefault(); enableMutationObserverByDefault_ = flagValue; @@ -812,7 +830,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(43, "enableNativeCSSParsing"); + markFlagAsAccessed(44, "enableNativeCSSParsing"); flagValue = currentProvider_->enableNativeCSSParsing(); enableNativeCSSParsing_ = flagValue; @@ -830,7 +848,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(44, "enablePreparedTextLayout"); + markFlagAsAccessed(45, "enablePreparedTextLayout"); flagValue = currentProvider_->enablePreparedTextLayout(); enablePreparedTextLayout_ = flagValue; @@ -848,7 +866,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(45, "enablePropsUpdateReconciliationAndroid"); + markFlagAsAccessed(46, "enablePropsUpdateReconciliationAndroid"); flagValue = currentProvider_->enablePropsUpdateReconciliationAndroid(); enablePropsUpdateReconciliationAndroid_ = flagValue; @@ -866,7 +884,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(46, "enableResizeObserverByDefault"); + markFlagAsAccessed(47, "enableResizeObserverByDefault"); flagValue = currentProvider_->enableResizeObserverByDefault(); enableResizeObserverByDefault_ = flagValue; @@ -884,7 +902,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(47, "enableSwiftUIBasedFilters"); + markFlagAsAccessed(48, "enableSwiftUIBasedFilters"); flagValue = currentProvider_->enableSwiftUIBasedFilters(); enableSwiftUIBasedFilters_ = flagValue; @@ -902,7 +920,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(48, "enableViewCulling"); + markFlagAsAccessed(49, "enableViewCulling"); flagValue = currentProvider_->enableViewCulling(); enableViewCulling_ = flagValue; @@ -920,7 +938,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(49, "enableViewRecycling"); + markFlagAsAccessed(50, "enableViewRecycling"); flagValue = currentProvider_->enableViewRecycling(); enableViewRecycling_ = flagValue; @@ -938,7 +956,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(50, "enableViewRecyclingForImage"); + markFlagAsAccessed(51, "enableViewRecyclingForImage"); flagValue = currentProvider_->enableViewRecyclingForImage(); enableViewRecyclingForImage_ = flagValue; @@ -956,7 +974,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(51, "enableViewRecyclingForScrollView"); + markFlagAsAccessed(52, "enableViewRecyclingForScrollView"); flagValue = currentProvider_->enableViewRecyclingForScrollView(); enableViewRecyclingForScrollView_ = flagValue; @@ -974,7 +992,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(52, "enableViewRecyclingForText"); + markFlagAsAccessed(53, "enableViewRecyclingForText"); flagValue = currentProvider_->enableViewRecyclingForText(); enableViewRecyclingForText_ = flagValue; @@ -992,7 +1010,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(53, "enableViewRecyclingForView"); + markFlagAsAccessed(54, "enableViewRecyclingForView"); flagValue = currentProvider_->enableViewRecyclingForView(); enableViewRecyclingForView_ = flagValue; @@ -1010,7 +1028,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(54, "enableVirtualViewContainerStateExperimental"); + markFlagAsAccessed(55, "enableVirtualViewContainerStateExperimental"); flagValue = currentProvider_->enableVirtualViewContainerStateExperimental(); enableVirtualViewContainerStateExperimental_ = flagValue; @@ -1028,7 +1046,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(55, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); + markFlagAsAccessed(56, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); flagValue = currentProvider_->fixMappingOfEventPrioritiesBetweenFabricAndReact(); fixMappingOfEventPrioritiesBetweenFabricAndReact_ = flagValue; @@ -1046,7 +1064,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(56, "fixYogaFlexBasisFitContentInMainAxis"); + markFlagAsAccessed(57, "fixYogaFlexBasisFitContentInMainAxis"); flagValue = currentProvider_->fixYogaFlexBasisFitContentInMainAxis(); fixYogaFlexBasisFitContentInMainAxis_ = flagValue; @@ -1064,7 +1082,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(57, "fuseboxAssertSingleHostState"); + markFlagAsAccessed(58, "fuseboxAssertSingleHostState"); flagValue = currentProvider_->fuseboxAssertSingleHostState(); fuseboxAssertSingleHostState_ = flagValue; @@ -1082,7 +1100,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(58, "fuseboxEnabledRelease"); + markFlagAsAccessed(59, "fuseboxEnabledRelease"); flagValue = currentProvider_->fuseboxEnabledRelease(); fuseboxEnabledRelease_ = flagValue; @@ -1100,7 +1118,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(59, "fuseboxFrameRecordingEnabled"); + markFlagAsAccessed(60, "fuseboxFrameRecordingEnabled"); flagValue = currentProvider_->fuseboxFrameRecordingEnabled(); fuseboxFrameRecordingEnabled_ = flagValue; @@ -1118,7 +1136,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(60, "fuseboxScreenshotCaptureEnabled"); + markFlagAsAccessed(61, "fuseboxScreenshotCaptureEnabled"); flagValue = currentProvider_->fuseboxScreenshotCaptureEnabled(); fuseboxScreenshotCaptureEnabled_ = flagValue; @@ -1136,7 +1154,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(61, "fuseboxWebSocketEventsEnabled"); + markFlagAsAccessed(62, "fuseboxWebSocketEventsEnabled"); flagValue = currentProvider_->fuseboxWebSocketEventsEnabled(); fuseboxWebSocketEventsEnabled_ = flagValue; @@ -1154,7 +1172,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(62, "optimizedAnimatedPropUpdates"); + markFlagAsAccessed(63, "optimizedAnimatedPropUpdates"); flagValue = currentProvider_->optimizedAnimatedPropUpdates(); optimizedAnimatedPropUpdates_ = flagValue; @@ -1172,7 +1190,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(63, "overrideBySynchronousMountPropsAtMountingAndroid"); + markFlagAsAccessed(64, "overrideBySynchronousMountPropsAtMountingAndroid"); flagValue = currentProvider_->overrideBySynchronousMountPropsAtMountingAndroid(); overrideBySynchronousMountPropsAtMountingAndroid_ = flagValue; @@ -1190,7 +1208,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(64, "perfIssuesEnabled"); + markFlagAsAccessed(65, "perfIssuesEnabled"); flagValue = currentProvider_->perfIssuesEnabled(); perfIssuesEnabled_ = flagValue; @@ -1208,7 +1226,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(65, "perfMonitorV2Enabled"); + markFlagAsAccessed(66, "perfMonitorV2Enabled"); flagValue = currentProvider_->perfMonitorV2Enabled(); perfMonitorV2Enabled_ = flagValue; @@ -1226,7 +1244,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(66, "preparedTextCacheSize"); + markFlagAsAccessed(67, "preparedTextCacheSize"); flagValue = currentProvider_->preparedTextCacheSize(); preparedTextCacheSize_ = flagValue; @@ -1244,7 +1262,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(67, "preventShadowTreeCommitExhaustion"); + markFlagAsAccessed(68, "preventShadowTreeCommitExhaustion"); flagValue = currentProvider_->preventShadowTreeCommitExhaustion(); preventShadowTreeCommitExhaustion_ = flagValue; @@ -1262,7 +1280,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(68, "redBoxV2Android"); + markFlagAsAccessed(69, "redBoxV2Android"); flagValue = currentProvider_->redBoxV2Android(); redBoxV2Android_ = flagValue; @@ -1280,7 +1298,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(69, "redBoxV2IOS"); + markFlagAsAccessed(70, "redBoxV2IOS"); flagValue = currentProvider_->redBoxV2IOS(); redBoxV2IOS_ = flagValue; @@ -1298,7 +1316,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(70, "shouldPressibilityUseW3CPointerEventsForHover"); + markFlagAsAccessed(71, "shouldPressibilityUseW3CPointerEventsForHover"); flagValue = currentProvider_->shouldPressibilityUseW3CPointerEventsForHover(); shouldPressibilityUseW3CPointerEventsForHover_ = flagValue; @@ -1316,7 +1334,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(71, "shouldTriggerResponderTransferOnScrollAndroid"); + markFlagAsAccessed(72, "shouldTriggerResponderTransferOnScrollAndroid"); flagValue = currentProvider_->shouldTriggerResponderTransferOnScrollAndroid(); shouldTriggerResponderTransferOnScrollAndroid_ = flagValue; @@ -1334,7 +1352,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(72, "skipActivityIdentityAssertionOnHostPause"); + markFlagAsAccessed(73, "skipActivityIdentityAssertionOnHostPause"); flagValue = currentProvider_->skipActivityIdentityAssertionOnHostPause(); skipActivityIdentityAssertionOnHostPause_ = flagValue; @@ -1352,7 +1370,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(73, "syncAndroidClipBoundsWithOverflow"); + markFlagAsAccessed(74, "syncAndroidClipBoundsWithOverflow"); flagValue = currentProvider_->syncAndroidClipBoundsWithOverflow(); syncAndroidClipBoundsWithOverflow_ = flagValue; @@ -1370,7 +1388,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(74, "traceTurboModulePromiseRejectionsOnAndroid"); + markFlagAsAccessed(75, "traceTurboModulePromiseRejectionsOnAndroid"); flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid(); traceTurboModulePromiseRejectionsOnAndroid_ = flagValue; @@ -1388,7 +1406,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(75, "updateRuntimeShadowNodeReferencesOnCommit"); + markFlagAsAccessed(76, "updateRuntimeShadowNodeReferencesOnCommit"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommit(); updateRuntimeShadowNodeReferencesOnCommit_ = flagValue; @@ -1406,7 +1424,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(76, "updateRuntimeShadowNodeReferencesOnCommitThread"); + markFlagAsAccessed(77, "updateRuntimeShadowNodeReferencesOnCommitThread"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommitThread(); updateRuntimeShadowNodeReferencesOnCommitThread_ = flagValue; @@ -1424,7 +1442,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(77, "useAlwaysAvailableJSErrorHandling"); + markFlagAsAccessed(78, "useAlwaysAvailableJSErrorHandling"); flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling(); useAlwaysAvailableJSErrorHandling_ = flagValue; @@ -1442,7 +1460,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(78, "useFabricInterop"); + markFlagAsAccessed(79, "useFabricInterop"); flagValue = currentProvider_->useFabricInterop(); useFabricInterop_ = flagValue; @@ -1460,7 +1478,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(79, "useNativeViewConfigsInBridgelessMode"); + markFlagAsAccessed(80, "useNativeViewConfigsInBridgelessMode"); flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode(); useNativeViewConfigsInBridgelessMode_ = flagValue; @@ -1478,7 +1496,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(80, "useNestedScrollViewAndroid"); + markFlagAsAccessed(81, "useNestedScrollViewAndroid"); flagValue = currentProvider_->useNestedScrollViewAndroid(); useNestedScrollViewAndroid_ = flagValue; @@ -1496,7 +1514,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(81, "useSharedAnimatedBackend"); + markFlagAsAccessed(82, "useSharedAnimatedBackend"); flagValue = currentProvider_->useSharedAnimatedBackend(); useSharedAnimatedBackend_ = flagValue; @@ -1514,7 +1532,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(82, "useTraitHiddenOnAndroid"); + markFlagAsAccessed(83, "useTraitHiddenOnAndroid"); flagValue = currentProvider_->useTraitHiddenOnAndroid(); useTraitHiddenOnAndroid_ = flagValue; @@ -1532,7 +1550,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(83, "useTurboModuleInterop"); + markFlagAsAccessed(84, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = flagValue; @@ -1550,7 +1568,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(84, "viewCullingOutsetRatio"); + markFlagAsAccessed(85, "viewCullingOutsetRatio"); flagValue = currentProvider_->viewCullingOutsetRatio(); viewCullingOutsetRatio_ = flagValue; @@ -1568,7 +1586,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(85, "viewTransitionEnabled"); + markFlagAsAccessed(86, "viewTransitionEnabled"); flagValue = currentProvider_->viewTransitionEnabled(); viewTransitionEnabled_ = flagValue; @@ -1586,7 +1604,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(86, "viewTransitionUseHardwareBitmapAndroid"); + markFlagAsAccessed(87, "viewTransitionUseHardwareBitmapAndroid"); flagValue = currentProvider_->viewTransitionUseHardwareBitmapAndroid(); viewTransitionUseHardwareBitmapAndroid_ = flagValue; @@ -1604,7 +1622,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(87, "virtualViewPrerenderRatio"); + markFlagAsAccessed(88, "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 659bb2a14752..a09cc0565d46 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<<75c8cd4da8fc81a393ba491373619d53>> + * @generated SignedSource<<22c9038896eaa78752021a6c31a24f36>> */ /** @@ -48,6 +48,7 @@ class ReactNativeFeatureFlagsAccessor { bool enableAccumulatedUpdatesInRawPropsAndroid(); bool enableAndroidAutoOffscreenCompositingForElevation(); bool enableAndroidTextMeasurementOptimizations(); + bool enableAsyncDiskCacheCheckInMultiSourceImageAndroid(); bool enableBridgelessArchitecture(); bool enableBufferedCallInvoker(); bool enableCppPropsIteratorSetter(); @@ -133,7 +134,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 88> accessedFeatureFlags_; + std::array, 89> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> cdpInteractionMetricsEnabled_; @@ -149,6 +150,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> enableAccumulatedUpdatesInRawPropsAndroid_; std::atomic> enableAndroidAutoOffscreenCompositingForElevation_; std::atomic> enableAndroidTextMeasurementOptimizations_; + std::atomic> enableAsyncDiskCacheCheckInMultiSourceImageAndroid_; std::atomic> enableBridgelessArchitecture_; std::atomic> enableBufferedCallInvoker_; std::atomic> enableCppPropsIteratorSetter_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index ce4a63de128e..c718aa9c1517 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<<5c0f5afb3f37733dd6fc94be39bae1b8>> + * @generated SignedSource<<98ccc88e34094ce7706487b45e7b387e>> */ /** @@ -85,6 +85,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return false; } + bool enableAsyncDiskCacheCheckInMultiSourceImageAndroid() override { + return false; + } + bool enableBridgelessArchitecture() override { return true; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index 26b1ae087320..cfb64728b59f 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<<5bf7fb7915acc1958df414702331e5fd>> + * @generated SignedSource<> */ /** @@ -173,6 +173,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::enableAndroidTextMeasurementOptimizations(); } + bool enableAsyncDiskCacheCheckInMultiSourceImageAndroid() override { + auto value = values_["enableAsyncDiskCacheCheckInMultiSourceImageAndroid"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::enableAsyncDiskCacheCheckInMultiSourceImageAndroid(); + } + bool enableBridgelessArchitecture() override { auto value = values_["enableBridgelessArchitecture"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index 3672759a7687..40540367d629 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<6e03c0769be8794de574ad225a5c50f9>> */ /** @@ -41,6 +41,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool enableAccumulatedUpdatesInRawPropsAndroid() = 0; virtual bool enableAndroidAutoOffscreenCompositingForElevation() = 0; virtual bool enableAndroidTextMeasurementOptimizations() = 0; + virtual bool enableAsyncDiskCacheCheckInMultiSourceImageAndroid() = 0; virtual bool enableBridgelessArchitecture() = 0; virtual bool enableBufferedCallInvoker() = 0; virtual bool enableCppPropsIteratorSetter() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index 04b9f8517103..d237052534af 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<<5c2c17d2dfe28c291757bc5d9b24b900>> + * @generated SignedSource<<06c87142f03f3d5420d28abbd9f6353d>> */ /** @@ -114,6 +114,11 @@ bool NativeReactNativeFeatureFlags::enableAndroidTextMeasurementOptimizations( return ReactNativeFeatureFlags::enableAndroidTextMeasurementOptimizations(); } +bool NativeReactNativeFeatureFlags::enableAsyncDiskCacheCheckInMultiSourceImageAndroid( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::enableAsyncDiskCacheCheckInMultiSourceImageAndroid(); +} + bool NativeReactNativeFeatureFlags::enableBridgelessArchitecture( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::enableBridgelessArchitecture(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index 868e3e8ffadd..daf169f49ea0 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<<31e836e5091c3a2140ebcfc4237dcd8c>> */ /** @@ -66,6 +66,8 @@ class NativeReactNativeFeatureFlags bool enableAndroidTextMeasurementOptimizations(jsi::Runtime& runtime); + bool enableAsyncDiskCacheCheckInMultiSourceImageAndroid(jsi::Runtime& runtime); + bool enableBridgelessArchitecture(jsi::Runtime& runtime); bool enableBufferedCallInvoker(jsi::Runtime& runtime); diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index 038f0412bfa7..38d9af8939c4 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -202,6 +202,17 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'none', }, + enableAsyncDiskCacheCheckInMultiSourceImageAndroid: { + defaultValue: false, + metadata: { + dateAdded: '2026-09-27', + description: + 'Runs multi-source image disk-cache checks asynchronously on Android.', + expectedReleaseValue: true, + purpose: 'experimentation', + }, + ossReleaseStage: 'none', + }, enableBridgelessArchitecture: { defaultValue: true, metadata: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index 3c8a00ee46d3..a21eb7438ccd 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<<6b2999150545a720138207180a81930e>> + * @generated SignedSource<> * @flow strict * @noformat */ @@ -63,6 +63,7 @@ export type ReactNativeFeatureFlags = Readonly<{ enableAccumulatedUpdatesInRawPropsAndroid: Getter, enableAndroidAutoOffscreenCompositingForElevation: Getter, enableAndroidTextMeasurementOptimizations: Getter, + enableAsyncDiskCacheCheckInMultiSourceImageAndroid: Getter, enableBridgelessArchitecture: Getter, enableBufferedCallInvoker: Getter, enableCppPropsIteratorSetter: Getter, @@ -269,6 +270,10 @@ export const enableAndroidAutoOffscreenCompositingForElevation: Getter * Enables various optimizations throughout the path of measuring text on Android. */ export const enableAndroidTextMeasurementOptimizations: Getter = createNativeFlagGetter('enableAndroidTextMeasurementOptimizations', false); +/** + * Runs multi-source image disk-cache checks asynchronously on Android. + */ +export const enableAsyncDiskCacheCheckInMultiSourceImageAndroid: Getter = createNativeFlagGetter('enableAsyncDiskCacheCheckInMultiSourceImageAndroid', false); /** * Feature flag to enable the new bridgeless architecture. */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index 7fafd47e123b..9d909779bd10 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<<7b46c51af70a529d0d1260906cef2ade>> + * @generated SignedSource<> * @flow strict * @noformat */ @@ -39,6 +39,7 @@ export interface Spec extends TurboModule { readonly enableAccumulatedUpdatesInRawPropsAndroid?: () => boolean; readonly enableAndroidAutoOffscreenCompositingForElevation?: () => boolean; readonly enableAndroidTextMeasurementOptimizations?: () => boolean; + readonly enableAsyncDiskCacheCheckInMultiSourceImageAndroid?: () => boolean; readonly enableBridgelessArchitecture?: () => boolean; readonly enableBufferedCallInvoker?: () => boolean; readonly enableCppPropsIteratorSetter?: () => boolean; diff --git a/packages/rn-tester/js/examples/FlatList/BaseFlatListExample.js b/packages/rn-tester/js/examples/FlatList/BaseFlatListExample.js index 1c77581facfd..ff6b3fb27798 100644 --- a/packages/rn-tester/js/examples/FlatList/BaseFlatListExample.js +++ b/packages/rn-tester/js/examples/FlatList/BaseFlatListExample.js @@ -60,6 +60,7 @@ const Item = ({item, separators}: ListRenderItemInfo) => { }; type Props = Readonly<{ + data?: ReadonlyArray, exampleProps: Partial>, exampleTestID?: ?string, onTest?: ?() => void, @@ -95,7 +96,7 @@ const BaseFlatListExample: component( ref={ref} testID="flat_list" // $FlowFixMe[incompatible-type] - data={DATA} + data={props.data ?? DATA} keyExtractor={(item, index) => item + index} style={styles.list} // $FlowFixMe[incompatible-type] diff --git a/packages/rn-tester/js/examples/FlatList/FlatList-onEndReached.js b/packages/rn-tester/js/examples/FlatList/FlatList-onEndReached.js index 132778d07b87..2a60d653160f 100644 --- a/packages/rn-tester/js/examples/FlatList/FlatList-onEndReached.js +++ b/packages/rn-tester/js/examples/FlatList/FlatList-onEndReached.js @@ -12,33 +12,129 @@ import type {RNTesterModuleExample} from '../../types/RNTesterTypes'; -import BaseFlatListExample from './BaseFlatListExample'; +import BaseFlatListExample, {ITEM_HEIGHT} from './BaseFlatListExample'; import * as React from 'react'; import {useRef, useState} from 'react'; +import {Button, FlatList} from 'react-native'; + +const DATA = Array.from({length: 20}, (_, index) => `Item ${index}`); +const SCROLL_TO_ITEM = DATA[18]; + +type EventCounts = { + dragEvents: number, + onEndReached: number, + onMomentumScrollEnd: number, + onStartReached: number, +}; export component FlatList_onEndReached() { - const [output, setOutput] = useState(''); - const exampleProps = { - onEndReached: (info: {distanceFromEnd: number, ...}) => - setOutput('onEndReached'), - onEndReachedThreshold: 0, + const [output, setOutput] = useState('ready'); + const listRef = useRef>(null); + const actionRef = useRef<'end' | 'start'>('end'); + const attemptsRef = useRef(0); + const eventCountsRef = useRef({ + dragEvents: 0, + onEndReached: 0, + onMomentumScrollEnd: 0, + onStartReached: 0, + }); + const recordEventsRef = useRef(false); + const momentumEndedRef = useRef(false); + + const report = () => { + const {dragEvents, onEndReached, onMomentumScrollEnd, onStartReached} = + eventCountsRef.current; + setOutput( + `${actionRef.current}: attempts=${attemptsRef.current}, ` + + `onEndReached=${onEndReached}, onStartReached=${onStartReached}, ` + + `onMomentumScrollEnd=${onMomentumScrollEnd}, dragEvents=${dragEvents}`, + ); + }; + + const onEndReached = () => { + if (recordEventsRef.current) { + eventCountsRef.current.onEndReached++; + if (momentumEndedRef.current) { + report(); + } + } + }; + + const onStartReached = () => { + if (recordEventsRef.current) { + eventCountsRef.current.onStartReached++; + if (momentumEndedRef.current) { + report(); + } + } }; - const ref = useRef(null); - const onTest = () => { - const scrollResponder = ref?.current?.getScrollResponder(); - if (scrollResponder != null) { - scrollResponder.scrollToEnd(); + const onMomentumScrollEnd = () => { + if (recordEventsRef.current) { + eventCountsRef.current.onMomentumScrollEnd++; + momentumEndedRef.current = true; + report(); } }; + const onDragEvent = () => { + if (recordEventsRef.current) { + eventCountsRef.current.dragEvents++; + } + }; + + const scrollToEnd = () => { + recordEventsRef.current = true; + momentumEndedRef.current = false; + actionRef.current = 'end'; + attemptsRef.current++; + setOutput('running'); + listRef.current?.scrollToItem({ + animated: true, + item: SCROLL_TO_ITEM, + viewOffset: -ITEM_HEIGHT, + }); + }; + + const scrollToStart = () => { + momentumEndedRef.current = false; + actionRef.current = 'start'; + attemptsRef.current++; + setOutput('running'); + listRef.current?.scrollToOffset({animated: true, offset: 0}); + }; + + const exampleProps = { + initialNumToRender: 19, + onEndReached, + onEndReachedThreshold: 0.2, + onMomentumScrollEnd, + onScrollBeginDrag: onDragEvent, + onScrollEndDrag: onDragEvent, + onStartReached, + onStartReachedThreshold: 0.1, + windowSize: 2, + }; + return ( + onTest={scrollToEnd} + testLabel="Scroll to item"> +