diff --git a/packages/react-native/Libraries/Image/__tests__/Image-requestPriority-itest.js b/packages/react-native/Libraries/Image/__tests__/Image-requestPriority-itest.js new file mode 100644 index 000000000000..ae6a64f0977c --- /dev/null +++ b/packages/react-native/Libraries/Image/__tests__/Image-requestPriority-itest.js @@ -0,0 +1,455 @@ +/** + * 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. + * + * @fantom_flags enableImageRequestDowngradingForNonVisibleImages:true + * @flow strict-local + * @format + */ + +import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; + +import type {RootConfig} from '@react-native/fantom'; + +import * as Fantom from '@react-native/fantom'; +import * as React from 'react'; +import {Image, ScrollView, View} from 'react-native'; +import NativeFantom from 'react-native/src/private/testing/fantom/specs/NativeFantom'; + +const IMAGE_SOURCE = {uri: 'https://reactnative.dev/img/tiny_logo.png'}; +const UPDATED_IMAGE_SOURCE = { + uri: 'https://reactnative.dev/img/header_logo.svg', +}; + +type ImageRequestPriority = 'immediate' | 'prefetch'; + +function expectLatestImageRequestPriority( + element: React.MixedElement, + expectedPriority: ImageRequestPriority, + rootConfig?: RootConfig, +) { + const root = Fantom.createRoot({ + viewportWidth: 100, + viewportHeight: 100, + ...rootConfig, + }); + + Fantom.runTask(() => { + root.render(element); + }); + + expect(NativeFantom.getImageRequestCount(IMAGE_SOURCE.uri)).toBe(1); + expect(NativeFantom.getImageRequestPriority(IMAGE_SOURCE.uri)).toBe( + expectedPriority, + ); +} + +describe(' request priority', () => { + beforeEach(() => { + NativeFantom.clearImageRequests(); + }); + + afterEach(() => { + NativeFantom.clearImageRequests(); + }); + + it('requests visible images at immediate priority', () => { + expectLatestImageRequestPriority( + , + 'immediate', + ); + }); + + it('requests images below the viewport at prefetch priority', () => { + expectLatestImageRequestPriority( + , + 'prefetch', + ); + }); + + it('requests images above the viewport at prefetch priority', () => { + expectLatestImageRequestPriority( + , + 'prefetch', + ); + }); + + it('requests images left of the viewport at prefetch priority', () => { + expectLatestImageRequestPriority( + , + 'prefetch', + ); + }); + + it('requests images right of the viewport at prefetch priority', () => { + expectLatestImageRequestPriority( + , + 'prefetch', + ); + }); + + it('requests edge-touching images at prefetch priority', () => { + expectLatestImageRequestPriority( + , + 'prefetch', + ); + }); + + it('requests one-pixel-overlapping images at immediate priority', () => { + expectLatestImageRequestPriority( + , + 'immediate', + ); + }); + + it('uses nested layout offsets when calculating priority', () => { + expectLatestImageRequestPriority( + + + , + 'prefetch', + ); + }); + + it('uses viewport offsets when calculating priority', () => { + expectLatestImageRequestPriority( + , + 'prefetch', + { + viewportOffsetY: 25, + }, + ); + }); + + it('uses ScrollView content offsets when calculating priority', () => { + expectLatestImageRequestPriority( + + + , + 'immediate', + ); + }); + + it('requests images above the ScrollView viewport at prefetch priority', () => { + expectLatestImageRequestPriority( + + + , + 'prefetch', + ); + }); + + it('requests images below the ScrollView viewport at prefetch priority', () => { + expectLatestImageRequestPriority( + + + , + 'prefetch', + ); + }); + + it('uses smaller ScrollView content offsets when calculating priority', () => { + expectLatestImageRequestPriority( + + + , + 'immediate', + ); + }); + + it('uses horizontal ScrollView content offsets when calculating priority', () => { + expectLatestImageRequestPriority( + + + , + 'immediate', + ); + }); + + it('requests images left of a horizontal ScrollView viewport at prefetch priority', () => { + expectLatestImageRequestPriority( + + + , + 'prefetch', + ); + }); + + it('requests images right of a horizontal ScrollView viewport at prefetch priority', () => { + expectLatestImageRequestPriority( + + + , + 'prefetch', + ); + }); + + it('uses image transforms when calculating priority', () => { + expectLatestImageRequestPriority( + , + 'immediate', + ); + }); + + it('uses image transforms that move images out of the viewport when calculating priority', () => { + expectLatestImageRequestPriority( + , + 'prefetch', + ); + }); + + it('uses image scale transforms when calculating priority', () => { + expectLatestImageRequestPriority( + , + 'immediate', + ); + }); + + it('uses ancestor transforms when calculating priority', () => { + expectLatestImageRequestPriority( + + + , + 'immediate', + ); + }); + + it('uses ancestor transforms that move images out of the viewport when calculating priority', () => { + expectLatestImageRequestPriority( + + + , + 'prefetch', + ); + }); + + it('updates priority when layout moves an image onscreen', () => { + const root = Fantom.createRoot({ + viewportWidth: 100, + viewportHeight: 100, + }); + + Fantom.runTask(() => { + root.render( + , + ); + }); + + expect(NativeFantom.getImageRequestCount(IMAGE_SOURCE.uri)).toBe(1); + expect(NativeFantom.getImageRequestPriority(IMAGE_SOURCE.uri)).toBe( + 'prefetch', + ); + + Fantom.runTask(() => { + root.render( + , + ); + }); + + expect(NativeFantom.getImageRequestCount(IMAGE_SOURCE.uri)).toBe(2); + expect(NativeFantom.getImageRequestPriority(IMAGE_SOURCE.uri)).toBe( + 'immediate', + ); + }); + + it('keeps offscreen priority when the source changes without a layout change', () => { + const root = Fantom.createRoot({ + viewportWidth: 100, + viewportHeight: 100, + }); + + const offscreenStyle = { + height: 50, + left: 0, + position: 'absolute', + top: 150, + width: 50, + } as const; + + Fantom.runTask(() => { + root.render(); + }); + + expect(NativeFantom.getImageRequestCount(IMAGE_SOURCE.uri)).toBe(1); + expect(NativeFantom.getImageRequestPriority(IMAGE_SOURCE.uri)).toBe( + 'prefetch', + ); + + NativeFantom.clearImageRequests(); + + Fantom.runTask(() => { + root.render( + , + ); + }); + + expect(NativeFantom.getImageRequestCount(UPDATED_IMAGE_SOURCE.uri)).toBe(1); + expect(NativeFantom.getImageRequestPriority(UPDATED_IMAGE_SOURCE.uri)).toBe( + 'prefetch', + ); + }); +}); diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/Image/RCTImageComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/Image/RCTImageComponentView.mm index 99104f0ec8fc..8f60235ca3b6 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/Image/RCTImageComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/Image/RCTImageComponentView.mm @@ -11,6 +11,7 @@ #import #import #import +#import #import #import #import @@ -19,9 +20,40 @@ using namespace facebook::react; +static NSString *const RCTImageRequestPriorityDebugOverlayEnabledEnvironmentVariable = + @"RCT_IMAGE_REQUEST_PRIORITY_DEBUG_OVERLAY"; + +static BOOL RCTImageRequestPriorityDebugOverlayEnabled() +{ + if (ReactNativeFeatureFlags::enableImageRequestDowngradingForNonVisibleImages()) { + static BOOL enabled = NO; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSDictionary *environment = [[NSProcessInfo processInfo] environment]; + enabled = [environment[RCTImageRequestPriorityDebugOverlayEnabledEnvironmentVariable] boolValue]; + }); + return enabled; + } else { + return NO; + } +} + +static NSString *RCTImageRequestPriorityDebugLabel(ImageRequestPriority priority) +{ + switch (priority) { + case ImageRequestPriority::Immediate: + return @"immediate"; + case ImageRequestPriority::Prefetch: + return @"offscreen"; + default: + return @"unknown"; + } +} + @implementation RCTImageComponentView { ImageShadowNode::ConcreteState::Shared _state; std::shared_ptr _imageResponseObserverProxy; + UILabel *_requestPriorityLabel; } - (instancetype)initWithFrame:(CGRect)frame @@ -85,6 +117,7 @@ - (void)updateState:(const State::Shared &)state oldState:(const State::Shared & auto newImageState = std::static_pointer_cast(state); [self _setStateAndResubscribeImageResponseObserver:newImageState]; + [self _updateRequestPriorityLabelWithState:newImageState]; bool havePreviousData = oldImageState && oldImageState->getData().getImageSource() != ImageSource{}; @@ -115,10 +148,53 @@ - (void)_setStateAndResubscribeImageResponseObserver:(const ImageShadowNode::Con } } +- (UILabel *)_requestPriorityLabel +{ + if (!_requestPriorityLabel) { + _requestPriorityLabel = [UILabel new]; + _requestPriorityLabel.accessibilityElementsHidden = YES; + _requestPriorityLabel.backgroundColor = [UIColor colorWithWhite:0 alpha:0.65]; + _requestPriorityLabel.clipsToBounds = YES; + _requestPriorityLabel.font = [UIFont systemFontOfSize:10 weight:UIFontWeightSemibold]; + _requestPriorityLabel.hidden = YES; + _requestPriorityLabel.isAccessibilityElement = NO; + _requestPriorityLabel.layer.cornerRadius = 3; + _requestPriorityLabel.textAlignment = NSTextAlignmentCenter; + _requestPriorityLabel.textColor = UIColor.whiteColor; + [_imageView addSubview:_requestPriorityLabel]; + } + + return _requestPriorityLabel; +} + +- (void)_updateRequestPriorityLabelWithState:(const ImageShadowNode::ConcreteState::Shared &)state +{ + if (!state || !RCTImageRequestPriorityDebugOverlayEnabled()) { + if (_requestPriorityLabel) { + _requestPriorityLabel.hidden = YES; + _requestPriorityLabel.text = nil; + } + return; + } + + UILabel *requestPriorityLabel = [self _requestPriorityLabel]; + requestPriorityLabel.text = RCTImageRequestPriorityDebugLabel(state->getData().getImageRequestParams().priority); + [requestPriorityLabel sizeToFit]; + + CGRect frame = requestPriorityLabel.frame; + frame.origin = CGPointMake(2, 2); + frame.size.width += 8; + frame.size.height += 4; + requestPriorityLabel.frame = frame; + requestPriorityLabel.hidden = NO; + [_imageView bringSubviewToFront:requestPriorityLabel]; +} + - (void)prepareForRecycle { [super prepareForRecycle]; [self _setStateAndResubscribeImageResponseObserver:nullptr]; + [self _updateRequestPriorityLabelWithState:nullptr]; _imageView.image = nil; } 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 166a429df92c..136683e07728 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<> */ /** @@ -192,6 +192,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun enableImagePrefetchingAndroid(): Boolean = accessor.enableImagePrefetchingAndroid() + /** + * When enabled, ImageShadowNode downgrades image requests to prefetch priority when layout determines that the image does not intersect the viewport. + */ + @JvmStatic + public fun enableImageRequestDowngradingForNonVisibleImages(): Boolean = accessor.enableImageRequestDowngradingForNonVisibleImages() + /** * Dispatches state updates for content offset changes synchronously on the main thread. */ 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 fb4e124a3c26..79106a62dee3 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<<5072b83512e1a86dc1382268f18916f4>> + * @generated SignedSource<> */ /** @@ -47,6 +47,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var enableIOSTextBaselineOffsetPerLineCache: Boolean? = null private var enableIOSViewClipToPaddingBoxCache: Boolean? = null private var enableImagePrefetchingAndroidCache: Boolean? = null + private var enableImageRequestDowngradingForNonVisibleImagesCache: Boolean? = null private var enableImmediateUpdateModeForContentOffsetChangesCache: Boolean? = null private var enableImperativeFocusCache: Boolean? = null private var enableInteropViewManagerClassLookUpOptimizationIOSCache: Boolean? = null @@ -351,6 +352,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun enableImageRequestDowngradingForNonVisibleImages(): Boolean { + var cached = enableImageRequestDowngradingForNonVisibleImagesCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.enableImageRequestDowngradingForNonVisibleImages() + enableImageRequestDowngradingForNonVisibleImagesCache = cached + } + return cached + } + override fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean { var cached = enableImmediateUpdateModeForContentOffsetChangesCache 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 e24858b67e2b..bd0fea2f8851 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<<9110ccc69266f7a51738ca533df3a150>> + * @generated SignedSource<<9d3495c774e4367973974a4f83ea7d16>> */ /** @@ -82,6 +82,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun enableImagePrefetchingAndroid(): Boolean + @DoNotStrip @JvmStatic public external fun enableImageRequestDowngradingForNonVisibleImages(): Boolean + @DoNotStrip @JvmStatic public external fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean @DoNotStrip @JvmStatic public external fun enableImperativeFocus(): 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 fbe4ad98e4d6..20c4ff5060ab 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<<811ccbada43f15c9418e0177041c5747>> */ /** @@ -77,6 +77,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun enableImagePrefetchingAndroid(): Boolean = false + override fun enableImageRequestDowngradingForNonVisibleImages(): Boolean = false + override fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean = false override fun enableImperativeFocus(): Boolean = false diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt index 6bc21fdf7735..5da3dce91ea9 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<<135694971d264d19c84eb6aea5fc8425>> + * @generated SignedSource<<69c333993720f22c6db80f3aceae32cd>> */ /** @@ -51,6 +51,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var enableIOSTextBaselineOffsetPerLineCache: Boolean? = null private var enableIOSViewClipToPaddingBoxCache: Boolean? = null private var enableImagePrefetchingAndroidCache: Boolean? = null + private var enableImageRequestDowngradingForNonVisibleImagesCache: Boolean? = null private var enableImmediateUpdateModeForContentOffsetChangesCache: Boolean? = null private var enableImperativeFocusCache: Boolean? = null private var enableInteropViewManagerClassLookUpOptimizationIOSCache: Boolean? = null @@ -382,6 +383,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun enableImageRequestDowngradingForNonVisibleImages(): Boolean { + var cached = enableImageRequestDowngradingForNonVisibleImagesCache + if (cached == null) { + cached = currentProvider.enableImageRequestDowngradingForNonVisibleImages() + accessedFeatureFlags.add("enableImageRequestDowngradingForNonVisibleImages") + enableImageRequestDowngradingForNonVisibleImagesCache = cached + } + return cached + } + override fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean { var cached = enableImmediateUpdateModeForContentOffsetChangesCache 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 5869b6b50483..0143ec3c230f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -77,6 +77,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun enableImagePrefetchingAndroid(): Boolean + @DoNotStrip public fun enableImageRequestDowngradingForNonVisibleImages(): Boolean + @DoNotStrip public fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean @DoNotStrip public fun enableImperativeFocus(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp index 6d738fea9d7b..76b86eb1b566 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -201,6 +201,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + bool enableImageRequestDowngradingForNonVisibleImages() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableImageRequestDowngradingForNonVisibleImages"); + return method(javaProvider_); + } + bool enableImmediateUpdateModeForContentOffsetChanges() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableImmediateUpdateModeForContentOffsetChanges"); @@ -700,6 +706,11 @@ bool JReactNativeFeatureFlagsCxxInterop::enableImagePrefetchingAndroid( return ReactNativeFeatureFlags::enableImagePrefetchingAndroid(); } +bool JReactNativeFeatureFlagsCxxInterop::enableImageRequestDowngradingForNonVisibleImages( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::enableImageRequestDowngradingForNonVisibleImages(); +} + bool JReactNativeFeatureFlagsCxxInterop::enableImmediateUpdateModeForContentOffsetChanges( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::enableImmediateUpdateModeForContentOffsetChanges(); @@ -1112,6 +1123,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "enableImagePrefetchingAndroid", JReactNativeFeatureFlagsCxxInterop::enableImagePrefetchingAndroid), + makeNativeMethod( + "enableImageRequestDowngradingForNonVisibleImages", + JReactNativeFeatureFlagsCxxInterop::enableImageRequestDowngradingForNonVisibleImages), makeNativeMethod( "enableImmediateUpdateModeForContentOffsetChanges", JReactNativeFeatureFlagsCxxInterop::enableImmediateUpdateModeForContentOffsetChanges), 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 9dcca78323bd..abdfa5f815be 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<<1655eb2f217476e64b03c97bce083192>> + * @generated SignedSource<> */ /** @@ -111,6 +111,9 @@ class JReactNativeFeatureFlagsCxxInterop static bool enableImagePrefetchingAndroid( facebook::jni::alias_ref); + static bool enableImageRequestDowngradingForNonVisibleImages( + facebook::jni::alias_ref); + static bool enableImmediateUpdateModeForContentOffsetChanges( facebook::jni::alias_ref); diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp index 6b774e1d8d9d..174194e58e20 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<> + * @generated SignedSource<<177c370caf828494ce42486af0d8c698>> */ /** @@ -134,6 +134,10 @@ bool ReactNativeFeatureFlags::enableImagePrefetchingAndroid() { return getAccessor().enableImagePrefetchingAndroid(); } +bool ReactNativeFeatureFlags::enableImageRequestDowngradingForNonVisibleImages() { + return getAccessor().enableImageRequestDowngradingForNonVisibleImages(); +} + bool ReactNativeFeatureFlags::enableImmediateUpdateModeForContentOffsetChanges() { return getAccessor().enableImmediateUpdateModeForContentOffsetChanges(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index c1a0d8f0b8db..3a28a8257d7d 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<<7299ac6603eb90d63d6cfb21e7ef3893>> + * @generated SignedSource<<5ff6ec567d516d966cb2fadfc5cc93b3>> */ /** @@ -174,6 +174,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool enableImagePrefetchingAndroid(); + /** + * When enabled, ImageShadowNode downgrades image requests to prefetch priority when layout determines that the image does not intersect the viewport. + */ + RN_EXPORT static bool enableImageRequestDowngradingForNonVisibleImages(); + /** * Dispatches state updates for content offset changes synchronously on the main thread. */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index fc51ef901f7a..d12da714c54f 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<<5ce3ec2efef77ca4cf69fa6c82097f58>> + * @generated SignedSource<<015535e3b014cc48e24628deeb6209de>> */ /** @@ -515,6 +515,24 @@ bool ReactNativeFeatureFlagsAccessor::enableImagePrefetchingAndroid() { return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::enableImageRequestDowngradingForNonVisibleImages() { + auto flagValue = enableImageRequestDowngradingForNonVisibleImages_.load(); + + if (!flagValue.has_value()) { + // This block is not exclusive but it is not necessary. + // If multiple threads try to initialize the feature flag, we would only + // be accessing the provider multiple times but the end state of this + // instance and the returned flag value would be the same. + + markFlagAsAccessed(27, "enableImageRequestDowngradingForNonVisibleImages"); + + flagValue = currentProvider_->enableImageRequestDowngradingForNonVisibleImages(); + enableImageRequestDowngradingForNonVisibleImages_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::enableImmediateUpdateModeForContentOffsetChanges() { auto flagValue = enableImmediateUpdateModeForContentOffsetChanges_.load(); @@ -524,7 +542,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(27, "enableImmediateUpdateModeForContentOffsetChanges"); + markFlagAsAccessed(28, "enableImmediateUpdateModeForContentOffsetChanges"); flagValue = currentProvider_->enableImmediateUpdateModeForContentOffsetChanges(); enableImmediateUpdateModeForContentOffsetChanges_ = flagValue; @@ -542,7 +560,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(28, "enableImperativeFocus"); + markFlagAsAccessed(29, "enableImperativeFocus"); flagValue = currentProvider_->enableImperativeFocus(); enableImperativeFocus_ = flagValue; @@ -560,7 +578,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(29, "enableInteropViewManagerClassLookUpOptimizationIOS"); + markFlagAsAccessed(30, "enableInteropViewManagerClassLookUpOptimizationIOS"); flagValue = currentProvider_->enableInteropViewManagerClassLookUpOptimizationIOS(); enableInteropViewManagerClassLookUpOptimizationIOS_ = flagValue; @@ -578,7 +596,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(30, "enableIntersectionObserverByDefault"); + markFlagAsAccessed(31, "enableIntersectionObserverByDefault"); flagValue = currentProvider_->enableIntersectionObserverByDefault(); enableIntersectionObserverByDefault_ = flagValue; @@ -596,7 +614,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(31, "enableKeyEvents"); + markFlagAsAccessed(32, "enableKeyEvents"); flagValue = currentProvider_->enableKeyEvents(); enableKeyEvents_ = flagValue; @@ -614,7 +632,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(32, "enableLayoutAnimationsOnAndroid"); + markFlagAsAccessed(33, "enableLayoutAnimationsOnAndroid"); flagValue = currentProvider_->enableLayoutAnimationsOnAndroid(); enableLayoutAnimationsOnAndroid_ = flagValue; @@ -632,7 +650,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(33, "enableLayoutAnimationsOnIOS"); + markFlagAsAccessed(34, "enableLayoutAnimationsOnIOS"); flagValue = currentProvider_->enableLayoutAnimationsOnIOS(); enableLayoutAnimationsOnIOS_ = flagValue; @@ -650,7 +668,7 @@ bool ReactNativeFeatureFlagsAccessor::enableMainQueueCoordinatorOnIOS() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(34, "enableMainQueueCoordinatorOnIOS"); + markFlagAsAccessed(35, "enableMainQueueCoordinatorOnIOS"); flagValue = currentProvider_->enableMainQueueCoordinatorOnIOS(); enableMainQueueCoordinatorOnIOS_ = flagValue; @@ -668,7 +686,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(35, "enableModuleArgumentNSNullConversionIOS"); + markFlagAsAccessed(36, "enableModuleArgumentNSNullConversionIOS"); flagValue = currentProvider_->enableModuleArgumentNSNullConversionIOS(); enableModuleArgumentNSNullConversionIOS_ = flagValue; @@ -686,7 +704,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(36, "enableMutationObserverByDefault"); + markFlagAsAccessed(37, "enableMutationObserverByDefault"); flagValue = currentProvider_->enableMutationObserverByDefault(); enableMutationObserverByDefault_ = flagValue; @@ -704,7 +722,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(37, "enableNativeCSSParsing"); + markFlagAsAccessed(38, "enableNativeCSSParsing"); flagValue = currentProvider_->enableNativeCSSParsing(); enableNativeCSSParsing_ = flagValue; @@ -722,7 +740,7 @@ bool ReactNativeFeatureFlagsAccessor::enableNetworkEventReporting() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(38, "enableNetworkEventReporting"); + markFlagAsAccessed(39, "enableNetworkEventReporting"); flagValue = currentProvider_->enableNetworkEventReporting(); enableNetworkEventReporting_ = flagValue; @@ -740,7 +758,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(39, "enablePreparedTextLayout"); + markFlagAsAccessed(40, "enablePreparedTextLayout"); flagValue = currentProvider_->enablePreparedTextLayout(); enablePreparedTextLayout_ = flagValue; @@ -758,7 +776,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(40, "enablePropsUpdateReconciliationAndroid"); + markFlagAsAccessed(41, "enablePropsUpdateReconciliationAndroid"); flagValue = currentProvider_->enablePropsUpdateReconciliationAndroid(); enablePropsUpdateReconciliationAndroid_ = flagValue; @@ -776,7 +794,7 @@ bool ReactNativeFeatureFlagsAccessor::enableRuntimeSchedulerQueueClearingOnError // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(41, "enableRuntimeSchedulerQueueClearingOnError"); + markFlagAsAccessed(42, "enableRuntimeSchedulerQueueClearingOnError"); flagValue = currentProvider_->enableRuntimeSchedulerQueueClearingOnError(); enableRuntimeSchedulerQueueClearingOnError_ = flagValue; @@ -794,7 +812,7 @@ bool ReactNativeFeatureFlagsAccessor::enableSchedulerDelegateInvalidation() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(42, "enableSchedulerDelegateInvalidation"); + markFlagAsAccessed(43, "enableSchedulerDelegateInvalidation"); flagValue = currentProvider_->enableSchedulerDelegateInvalidation(); enableSchedulerDelegateInvalidation_ = flagValue; @@ -812,7 +830,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(43, "enableSwiftUIBasedFilters"); + markFlagAsAccessed(44, "enableSwiftUIBasedFilters"); flagValue = currentProvider_->enableSwiftUIBasedFilters(); enableSwiftUIBasedFilters_ = flagValue; @@ -830,7 +848,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(44, "enableViewCulling"); + markFlagAsAccessed(45, "enableViewCulling"); flagValue = currentProvider_->enableViewCulling(); enableViewCulling_ = flagValue; @@ -848,7 +866,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(45, "enableViewRecycling"); + markFlagAsAccessed(46, "enableViewRecycling"); flagValue = currentProvider_->enableViewRecycling(); enableViewRecycling_ = flagValue; @@ -866,7 +884,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(46, "enableViewRecyclingForImage"); + markFlagAsAccessed(47, "enableViewRecyclingForImage"); flagValue = currentProvider_->enableViewRecyclingForImage(); enableViewRecyclingForImage_ = flagValue; @@ -884,7 +902,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(47, "enableViewRecyclingForScrollView"); + markFlagAsAccessed(48, "enableViewRecyclingForScrollView"); flagValue = currentProvider_->enableViewRecyclingForScrollView(); enableViewRecyclingForScrollView_ = flagValue; @@ -902,7 +920,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(48, "enableViewRecyclingForText"); + markFlagAsAccessed(49, "enableViewRecyclingForText"); flagValue = currentProvider_->enableViewRecyclingForText(); enableViewRecyclingForText_ = flagValue; @@ -920,7 +938,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(49, "enableViewRecyclingForView"); + markFlagAsAccessed(50, "enableViewRecyclingForView"); flagValue = currentProvider_->enableViewRecyclingForView(); enableViewRecyclingForView_ = flagValue; @@ -938,7 +956,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(50, "enableVirtualViewContainerStateExperimental"); + markFlagAsAccessed(51, "enableVirtualViewContainerStateExperimental"); flagValue = currentProvider_->enableVirtualViewContainerStateExperimental(); enableVirtualViewContainerStateExperimental_ = flagValue; @@ -956,7 +974,7 @@ bool ReactNativeFeatureFlagsAccessor::fixDifferentiatorParentTagForUnflattenCase // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(51, "fixDifferentiatorParentTagForUnflattenCase"); + markFlagAsAccessed(52, "fixDifferentiatorParentTagForUnflattenCase"); flagValue = currentProvider_->fixDifferentiatorParentTagForUnflattenCase(); fixDifferentiatorParentTagForUnflattenCase_ = flagValue; @@ -974,7 +992,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(52, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); + markFlagAsAccessed(53, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); flagValue = currentProvider_->fixMappingOfEventPrioritiesBetweenFabricAndReact(); fixMappingOfEventPrioritiesBetweenFabricAndReact_ = flagValue; @@ -992,7 +1010,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(53, "fixYogaFlexBasisFitContentInMainAxis"); + markFlagAsAccessed(54, "fixYogaFlexBasisFitContentInMainAxis"); flagValue = currentProvider_->fixYogaFlexBasisFitContentInMainAxis(); fixYogaFlexBasisFitContentInMainAxis_ = flagValue; @@ -1010,7 +1028,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(54, "fuseboxAssertSingleHostState"); + markFlagAsAccessed(55, "fuseboxAssertSingleHostState"); flagValue = currentProvider_->fuseboxAssertSingleHostState(); fuseboxAssertSingleHostState_ = flagValue; @@ -1028,7 +1046,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(55, "fuseboxEnabledRelease"); + markFlagAsAccessed(56, "fuseboxEnabledRelease"); flagValue = currentProvider_->fuseboxEnabledRelease(); fuseboxEnabledRelease_ = flagValue; @@ -1046,7 +1064,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(56, "fuseboxFrameRecordingEnabled"); + markFlagAsAccessed(57, "fuseboxFrameRecordingEnabled"); flagValue = currentProvider_->fuseboxFrameRecordingEnabled(); fuseboxFrameRecordingEnabled_ = flagValue; @@ -1064,7 +1082,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxNetworkInspectionEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(57, "fuseboxNetworkInspectionEnabled"); + markFlagAsAccessed(58, "fuseboxNetworkInspectionEnabled"); flagValue = currentProvider_->fuseboxNetworkInspectionEnabled(); fuseboxNetworkInspectionEnabled_ = flagValue; @@ -1082,7 +1100,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(58, "fuseboxScreenshotCaptureEnabled"); + markFlagAsAccessed(59, "fuseboxScreenshotCaptureEnabled"); flagValue = currentProvider_->fuseboxScreenshotCaptureEnabled(); fuseboxScreenshotCaptureEnabled_ = flagValue; @@ -1100,7 +1118,7 @@ bool ReactNativeFeatureFlagsAccessor::hideOffscreenVirtualViewsOnIOS() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(59, "hideOffscreenVirtualViewsOnIOS"); + markFlagAsAccessed(60, "hideOffscreenVirtualViewsOnIOS"); flagValue = currentProvider_->hideOffscreenVirtualViewsOnIOS(); hideOffscreenVirtualViewsOnIOS_ = flagValue; @@ -1118,7 +1136,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(60, "optimizedAnimatedPropUpdates"); + markFlagAsAccessed(61, "optimizedAnimatedPropUpdates"); flagValue = currentProvider_->optimizedAnimatedPropUpdates(); optimizedAnimatedPropUpdates_ = flagValue; @@ -1136,7 +1154,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(61, "overrideBySynchronousMountPropsAtMountingAndroid"); + markFlagAsAccessed(62, "overrideBySynchronousMountPropsAtMountingAndroid"); flagValue = currentProvider_->overrideBySynchronousMountPropsAtMountingAndroid(); overrideBySynchronousMountPropsAtMountingAndroid_ = flagValue; @@ -1154,7 +1172,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(62, "perfIssuesEnabled"); + markFlagAsAccessed(63, "perfIssuesEnabled"); flagValue = currentProvider_->perfIssuesEnabled(); perfIssuesEnabled_ = flagValue; @@ -1172,7 +1190,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(63, "perfMonitorV2Enabled"); + markFlagAsAccessed(64, "perfMonitorV2Enabled"); flagValue = currentProvider_->perfMonitorV2Enabled(); perfMonitorV2Enabled_ = flagValue; @@ -1190,7 +1208,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(64, "preparedTextCacheSize"); + markFlagAsAccessed(65, "preparedTextCacheSize"); flagValue = currentProvider_->preparedTextCacheSize(); preparedTextCacheSize_ = flagValue; @@ -1208,7 +1226,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(65, "preventShadowTreeCommitExhaustion"); + markFlagAsAccessed(66, "preventShadowTreeCommitExhaustion"); flagValue = currentProvider_->preventShadowTreeCommitExhaustion(); preventShadowTreeCommitExhaustion_ = flagValue; @@ -1226,7 +1244,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(66, "redBoxV2Android"); + markFlagAsAccessed(67, "redBoxV2Android"); flagValue = currentProvider_->redBoxV2Android(); redBoxV2Android_ = flagValue; @@ -1244,7 +1262,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(67, "redBoxV2IOS"); + markFlagAsAccessed(68, "redBoxV2IOS"); flagValue = currentProvider_->redBoxV2IOS(); redBoxV2IOS_ = flagValue; @@ -1262,7 +1280,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(68, "shouldPressibilityUseW3CPointerEventsForHover"); + markFlagAsAccessed(69, "shouldPressibilityUseW3CPointerEventsForHover"); flagValue = currentProvider_->shouldPressibilityUseW3CPointerEventsForHover(); shouldPressibilityUseW3CPointerEventsForHover_ = flagValue; @@ -1280,7 +1298,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(69, "shouldTriggerResponderTransferOnScrollAndroid"); + markFlagAsAccessed(70, "shouldTriggerResponderTransferOnScrollAndroid"); flagValue = currentProvider_->shouldTriggerResponderTransferOnScrollAndroid(); shouldTriggerResponderTransferOnScrollAndroid_ = flagValue; @@ -1298,7 +1316,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(70, "skipActivityIdentityAssertionOnHostPause"); + markFlagAsAccessed(71, "skipActivityIdentityAssertionOnHostPause"); flagValue = currentProvider_->skipActivityIdentityAssertionOnHostPause(); skipActivityIdentityAssertionOnHostPause_ = flagValue; @@ -1316,7 +1334,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(71, "syncAndroidClipBoundsWithOverflow"); + markFlagAsAccessed(72, "syncAndroidClipBoundsWithOverflow"); flagValue = currentProvider_->syncAndroidClipBoundsWithOverflow(); syncAndroidClipBoundsWithOverflow_ = flagValue; @@ -1334,7 +1352,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(72, "traceTurboModulePromiseRejectionsOnAndroid"); + markFlagAsAccessed(73, "traceTurboModulePromiseRejectionsOnAndroid"); flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid(); traceTurboModulePromiseRejectionsOnAndroid_ = flagValue; @@ -1352,7 +1370,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(73, "updateRuntimeShadowNodeReferencesOnCommit"); + markFlagAsAccessed(74, "updateRuntimeShadowNodeReferencesOnCommit"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommit(); updateRuntimeShadowNodeReferencesOnCommit_ = flagValue; @@ -1370,7 +1388,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(74, "updateRuntimeShadowNodeReferencesOnCommitThread"); + markFlagAsAccessed(75, "updateRuntimeShadowNodeReferencesOnCommitThread"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommitThread(); updateRuntimeShadowNodeReferencesOnCommitThread_ = flagValue; @@ -1388,7 +1406,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(75, "useAlwaysAvailableJSErrorHandling"); + markFlagAsAccessed(76, "useAlwaysAvailableJSErrorHandling"); flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling(); useAlwaysAvailableJSErrorHandling_ = flagValue; @@ -1406,7 +1424,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(76, "useFabricInterop"); + markFlagAsAccessed(77, "useFabricInterop"); flagValue = currentProvider_->useFabricInterop(); useFabricInterop_ = flagValue; @@ -1424,7 +1442,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(77, "useNativeViewConfigsInBridgelessMode"); + markFlagAsAccessed(78, "useNativeViewConfigsInBridgelessMode"); flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode(); useNativeViewConfigsInBridgelessMode_ = flagValue; @@ -1442,7 +1460,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(78, "useNestedScrollViewAndroid"); + markFlagAsAccessed(79, "useNestedScrollViewAndroid"); flagValue = currentProvider_->useNestedScrollViewAndroid(); useNestedScrollViewAndroid_ = flagValue; @@ -1460,7 +1478,7 @@ bool ReactNativeFeatureFlagsAccessor::useOptimizedViewRegistryOnAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(79, "useOptimizedViewRegistryOnAndroid"); + markFlagAsAccessed(80, "useOptimizedViewRegistryOnAndroid"); flagValue = currentProvider_->useOptimizedViewRegistryOnAndroid(); useOptimizedViewRegistryOnAndroid_ = flagValue; @@ -1478,7 +1496,7 @@ bool ReactNativeFeatureFlagsAccessor::useSharedAnimatedBackend() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(80, "useSharedAnimatedBackend"); + markFlagAsAccessed(81, "useSharedAnimatedBackend"); flagValue = currentProvider_->useSharedAnimatedBackend(); useSharedAnimatedBackend_ = flagValue; @@ -1496,7 +1514,7 @@ bool ReactNativeFeatureFlagsAccessor::useTraitHiddenOnAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(81, "useTraitHiddenOnAndroid"); + markFlagAsAccessed(82, "useTraitHiddenOnAndroid"); flagValue = currentProvider_->useTraitHiddenOnAndroid(); useTraitHiddenOnAndroid_ = flagValue; @@ -1514,7 +1532,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModuleInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(82, "useTurboModuleInterop"); + markFlagAsAccessed(83, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = flagValue; @@ -1532,7 +1550,7 @@ double ReactNativeFeatureFlagsAccessor::viewCullingOutsetRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(83, "viewCullingOutsetRatio"); + markFlagAsAccessed(84, "viewCullingOutsetRatio"); flagValue = currentProvider_->viewCullingOutsetRatio(); viewCullingOutsetRatio_ = flagValue; @@ -1550,7 +1568,7 @@ bool ReactNativeFeatureFlagsAccessor::viewTransitionEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(84, "viewTransitionEnabled"); + markFlagAsAccessed(85, "viewTransitionEnabled"); flagValue = currentProvider_->viewTransitionEnabled(); viewTransitionEnabled_ = flagValue; @@ -1568,7 +1586,7 @@ bool ReactNativeFeatureFlagsAccessor::viewTransitionUseHardwareBitmapAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(85, "viewTransitionUseHardwareBitmapAndroid"); + markFlagAsAccessed(86, "viewTransitionUseHardwareBitmapAndroid"); flagValue = currentProvider_->viewTransitionUseHardwareBitmapAndroid(); viewTransitionUseHardwareBitmapAndroid_ = flagValue; @@ -1586,7 +1604,7 @@ double ReactNativeFeatureFlagsAccessor::virtualViewPrerenderRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(86, "virtualViewPrerenderRatio"); + markFlagAsAccessed(87, "virtualViewPrerenderRatio"); flagValue = currentProvider_->virtualViewPrerenderRatio(); virtualViewPrerenderRatio_ = flagValue; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h index 3a6c47ac2e4b..a82fc14eb190 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<<2f027dd5571effd4ee39326db7131eb7>> + * @generated SignedSource<<4b9621fa4f180c100cece906c0d4db34>> */ /** @@ -59,6 +59,7 @@ class ReactNativeFeatureFlagsAccessor { bool enableIOSTextBaselineOffsetPerLine(); bool enableIOSViewClipToPaddingBox(); bool enableImagePrefetchingAndroid(); + bool enableImageRequestDowngradingForNonVisibleImages(); bool enableImmediateUpdateModeForContentOffsetChanges(); bool enableImperativeFocus(); bool enableInteropViewManagerClassLookUpOptimizationIOS(); @@ -130,7 +131,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 87> accessedFeatureFlags_; + std::array, 88> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> cdpInteractionMetricsEnabled_; @@ -159,6 +160,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> enableIOSTextBaselineOffsetPerLine_; std::atomic> enableIOSViewClipToPaddingBox_; std::atomic> enableImagePrefetchingAndroid_; + std::atomic> enableImageRequestDowngradingForNonVisibleImages_; std::atomic> enableImmediateUpdateModeForContentOffsetChanges_; std::atomic> enableImperativeFocus_; std::atomic> enableInteropViewManagerClassLookUpOptimizationIOS_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index 2c457f1f516f..6a6c5b05f9db 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<<0dff76292f3f765d95454717d3a61c5f>> + * @generated SignedSource<> */ /** @@ -135,6 +135,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return false; } + bool enableImageRequestDowngradingForNonVisibleImages() override { + return false; + } + bool enableImmediateUpdateModeForContentOffsetChanges() override { return false; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index 146ac1affb60..2a9314874921 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<<6d7237856953bacb6de35ec63e859d65>> + * @generated SignedSource<<50d5c4ac14b1abc6065af8dc82945f93>> */ /** @@ -288,6 +288,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::enableImagePrefetchingAndroid(); } + bool enableImageRequestDowngradingForNonVisibleImages() override { + auto value = values_["enableImageRequestDowngradingForNonVisibleImages"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::enableImageRequestDowngradingForNonVisibleImages(); + } + bool enableImmediateUpdateModeForContentOffsetChanges() override { auto value = values_["enableImmediateUpdateModeForContentOffsetChanges"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index 85b4175d3050..93105df09908 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<<07a0d8f1c703685916bce8682f5b4967>> + * @generated SignedSource<<5ba9185aeb77b3bb3bfd258fb20b7e13>> */ /** @@ -52,6 +52,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool enableIOSTextBaselineOffsetPerLine() = 0; virtual bool enableIOSViewClipToPaddingBox() = 0; virtual bool enableImagePrefetchingAndroid() = 0; + virtual bool enableImageRequestDowngradingForNonVisibleImages() = 0; virtual bool enableImmediateUpdateModeForContentOffsetChanges() = 0; virtual bool enableImperativeFocus() = 0; virtual bool enableInteropViewManagerClassLookUpOptimizationIOS() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index 1b115f658d54..304a3e678218 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<<1b1d76d354f735488f8de5c1d0b83cc6>> + * @generated SignedSource<<8621fa2c2c8a2d88b54a3c3767510de3>> */ /** @@ -179,6 +179,11 @@ bool NativeReactNativeFeatureFlags::enableImagePrefetchingAndroid( return ReactNativeFeatureFlags::enableImagePrefetchingAndroid(); } +bool NativeReactNativeFeatureFlags::enableImageRequestDowngradingForNonVisibleImages( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::enableImageRequestDowngradingForNonVisibleImages(); +} + bool NativeReactNativeFeatureFlags::enableImmediateUpdateModeForContentOffsetChanges( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::enableImmediateUpdateModeForContentOffsetChanges(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index d010871c92bb..8b90f5bcddb1 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<<95947de110a15ea5c43af6213194058a>> + * @generated SignedSource<<734b2f736b310fdc9ca51eda8509a6fb>> */ /** @@ -90,6 +90,8 @@ class NativeReactNativeFeatureFlags bool enableImagePrefetchingAndroid(jsi::Runtime& runtime); + bool enableImageRequestDowngradingForNonVisibleImages(jsi::Runtime& runtime); + bool enableImmediateUpdateModeForContentOffsetChanges(jsi::Runtime& runtime); bool enableImperativeFocus(jsi::Runtime& runtime); diff --git a/packages/react-native/ReactCommon/react/renderer/components/image/ImageShadowNode.cpp b/packages/react-native/ReactCommon/react/renderer/components/image/ImageShadowNode.cpp index caea0e5fe5fe..1b05a1a4cedd 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/image/ImageShadowNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/image/ImageShadowNode.cpp @@ -13,12 +13,49 @@ #include #include #include +#include #include namespace facebook::react { const char ImageComponentName[] = "Image"; +namespace { + +bool isImageVisible( + const LayoutContext& layoutContext, + const LayoutMetrics& layoutMetrics) { + if (layoutContext.viewportSize.width <= 0 || + layoutContext.viewportSize.height <= 0 || + layoutMetrics.frame.size.width <= 0 || + layoutMetrics.frame.size.height <= 0) { + return true; + } + + auto imageFrame = layoutContext.experimental_layoutFrame; + if (imageFrame.size.width <= 0 || imageFrame.size.height <= 0) { + imageFrame = Rect{ + .origin = layoutContext.experimental_layoutOrigin, + .size = layoutMetrics.frame.size}; + } + auto viewportFrame = Rect{ + .origin = layoutContext.viewportOffset, + .size = layoutContext.viewportSize}; + auto visibleFrame = Rect::intersect(imageFrame, viewportFrame); + + return visibleFrame.size.width > 0 && visibleFrame.size.height > 0; +} + +ImageRequestPriority getImageRequestPriority( + const LayoutContext& layoutContext, + const LayoutMetrics& layoutMetrics) { + return isImageVisible(layoutContext, layoutMetrics) + ? ImageRequestPriority::Immediate + : ImageRequestPriority::Prefetch; +} + +} // namespace + void ImageShadowNode::setImageManager( const std::shared_ptr& imageManager) { ensureUnsealed(); @@ -37,12 +74,16 @@ void ImageShadowNode::setImageManager( if (sources.size() <= 1 || (layoutMetric.frame.size.width > 0 && layoutMetric.frame.size.height > 0)) { - updateStateIfNeeded(); + auto priority = ReactNativeFeatureFlags:: + enableImageRequestDowngradingForNonVisibleImages() + ? getStateData().getImageRequestParams().priority + : ImageRequestPriority::Immediate; + updateStateIfNeeded(priority); } } } -void ImageShadowNode::updateStateIfNeeded() { +void ImageShadowNode::updateStateIfNeeded(ImageRequestPriority priority) { ensureUnsealed(); const auto& savedState = getStateData(); @@ -71,6 +112,9 @@ void ImageShadowNode::updateStateIfNeeded() { layoutMetrics_.frame.size.width * layoutMetrics_.pointScaleFactor, .height = layoutMetrics_.frame.size.height * layoutMetrics_.pointScaleFactor} +#else + , + priority #endif ); @@ -155,7 +199,12 @@ ImageSource ImageShadowNode::getImageSource() const { #pragma mark - LayoutableShadowNode void ImageShadowNode::layout(LayoutContext layoutContext) { - updateStateIfNeeded(); + auto imageRequestPriority = + ReactNativeFeatureFlags:: + enableImageRequestDowngradingForNonVisibleImages() + ? getImageRequestPriority(layoutContext, getLayoutMetrics()) + : ImageRequestPriority::Immediate; + updateStateIfNeeded(imageRequestPriority); ConcreteViewShadowNode::layout(layoutContext); } diff --git a/packages/react-native/ReactCommon/react/renderer/components/image/ImageShadowNode.h b/packages/react-native/ReactCommon/react/renderer/components/image/ImageShadowNode.h index 3ecd4672d12d..d5e0f817913d 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/image/ImageShadowNode.h +++ b/packages/react-native/ReactCommon/react/renderer/components/image/ImageShadowNode.h @@ -57,7 +57,7 @@ class ImageShadowNode final std::shared_ptr imageManager_; - void updateStateIfNeeded(); + void updateStateIfNeeded(ImageRequestPriority priority); }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp index c630cb420188..2fe7f96c02ab 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp @@ -671,6 +671,13 @@ void YogaLayoutableShadowNode::layoutTree( yogaNode_.setHasNewLayout(false); } + if (ReactNativeFeatureFlags:: + enableImageRequestDowngradingForNonVisibleImages()) { + layoutContext.experimental_layoutOrigin = layoutContext.viewportOffset; + layoutContext.experimental_layoutFrame = Rect{ + .origin = layoutContext.viewportOffset, + .size = getLayoutMetrics().frame.size}; + } layout(layoutContext); } @@ -729,7 +736,21 @@ void YogaLayoutableShadowNode::layout(LayoutContext layoutContext) { childNode.setLayoutMetrics(newLayoutMetrics); if (newLayoutMetrics.displayType != DisplayType::None) { - childNode.layout(layoutContext); + auto childLayoutContext = layoutContext; + if (ReactNativeFeatureFlags:: + enableImageRequestDowngradingForNonVisibleImages()) { + auto childFrame = Rect{ + .origin = layoutContext.experimental_layoutOrigin + + newLayoutMetrics.frame.origin + getContentOriginOffset(false), + .size = newLayoutMetrics.frame.size}; + if (!childNode.getTraits().check( + ShadowNodeTraits::Trait::RootNodeKind)) { + childFrame = childFrame * childNode.getTransform(); + } + childLayoutContext.experimental_layoutOrigin = childFrame.origin; + childLayoutContext.experimental_layoutFrame = childFrame; + } + childNode.layout(childLayoutContext); } } } diff --git a/packages/react-native/ReactCommon/react/renderer/core/LayoutContext.h b/packages/react-native/ReactCommon/react/renderer/core/LayoutContext.h index f54f90159d40..1fce65c8d972 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/LayoutContext.h +++ b/packages/react-native/ReactCommon/react/renderer/core/LayoutContext.h @@ -10,6 +10,7 @@ #include #include +#include namespace facebook::react { @@ -64,6 +65,18 @@ struct LayoutContext { * Viewport size is size of the React Native's root view. */ Size viewportSize{}; + + /* + * Experimental: Origin of the shadow node currently being laid out, in viewport + * coordinates. This is populated while walking the layout tree. + */ + Point experimental_layoutOrigin{}; + + /* + * Experimental: Frame of the shadow node currently being laid out, in viewport + * coordinates after layout transforms are applied. + */ + Rect experimental_layoutFrame{}; }; inline bool operator==(const LayoutContext &lhs, const LayoutContext &rhs) diff --git a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/ImageRequestParams.h b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/ImageRequestParams.h index e960e01b5b5c..e1008696d1b2 100644 --- a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/ImageRequestParams.h +++ b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/android/react/renderer/imagemanager/ImageRequestParams.h @@ -61,6 +61,8 @@ class ImageRequestParams { ImageSource loadingIndicatorSource{}; std::string analyticTag{}; Size size{}; + // Consumed by Apple image managers for now; Android keeps Immediate. + ImageRequestPriority priority{ImageRequestPriority::Immediate}; bool operator==(const ImageRequestParams &rhs) const = default; }; diff --git a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/cxx/react/renderer/imagemanager/ImageRequestParams.h b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/cxx/react/renderer/imagemanager/ImageRequestParams.h index a30f0103dda9..01cf22f44f47 100644 --- a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/cxx/react/renderer/imagemanager/ImageRequestParams.h +++ b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/cxx/react/renderer/imagemanager/ImageRequestParams.h @@ -8,15 +8,21 @@ #pragma once #include +#include namespace facebook::react { class ImageRequestParams { public: ImageRequestParams() = default; - explicit ImageRequestParams(Float blurRadius) : blurRadius(blurRadius) {} + explicit ImageRequestParams(Float blurRadius, ImageRequestPriority priority = ImageRequestPriority::Immediate) + : blurRadius(blurRadius), priority(priority) + { + } Float blurRadius{}; + // Consumed by Apple image managers for now; other platforms keep Immediate. + ImageRequestPriority priority{ImageRequestPriority::Immediate}; bool operator==(const ImageRequestParams &rhs) const = default; }; diff --git a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/ImageManager.mm b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/ImageManager.mm index 7e15cdf0a4f2..3b65868977d6 100644 --- a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/ImageManager.mm +++ b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/ImageManager.mm @@ -37,11 +37,11 @@ ImageRequest ImageManager::requestImage( const ImageSource &imageSource, SurfaceId surfaceId, - const ImageRequestParams & /*imageRequestParams*/, + const ImageRequestParams &imageRequestParams, Tag /*tag*/) const { RCTImageManager *imageManager = (__bridge RCTImageManager *)self_; - return [imageManager requestImage:imageSource surfaceId:surfaceId]; + return [imageManager requestImage:imageSource surfaceId:surfaceId priority:imageRequestParams.priority]; } } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/ImageRequestParams.h b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/ImageRequestParams.h index 389f950ed317..e9afe1108018 100644 --- a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/ImageRequestParams.h +++ b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/ImageRequestParams.h @@ -8,15 +8,20 @@ #pragma once #include +#include namespace facebook::react { class ImageRequestParams { public: ImageRequestParams() {} - ImageRequestParams(Float blurRadius) : blurRadius(blurRadius) {} + ImageRequestParams(Float blurRadius, ImageRequestPriority priority = ImageRequestPriority::Immediate) + : blurRadius(blurRadius), priority(priority) + { + } Float blurRadius{}; + ImageRequestPriority priority{ImageRequestPriority::Immediate}; bool operator==(const ImageRequestParams &rhs) const = default; }; diff --git a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTImageManager.h b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTImageManager.h index da6223a542d2..02e8a2f48e0a 100644 --- a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTImageManager.h +++ b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTImageManager.h @@ -21,7 +21,8 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithImageLoader:(id)imageLoader; - (facebook::react::ImageRequest)requestImage:(facebook::react::ImageSource)imageSource - surfaceId:(facebook::react::SurfaceId)surfaceId; + surfaceId:(facebook::react::SurfaceId)surfaceId + priority:(facebook::react::ImageRequestPriority)priority; @end diff --git a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTImageManager.mm b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTImageManager.mm index 601dfe45ac38..40691149f9b0 100644 --- a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTImageManager.mm +++ b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTImageManager.mm @@ -36,7 +36,9 @@ - (instancetype)initWithImageLoader:(id)i return self; } -- (ImageRequest)requestImage:(ImageSource)imageSource surfaceId:(SurfaceId)surfaceId +- (ImageRequest)requestImage:(ImageSource)imageSource + surfaceId:(SurfaceId)surfaceId + priority:(ImageRequestPriority)priority { TraceSection s("RCTImageManager::requestImage"); @@ -96,7 +98,7 @@ - (ImageRequest)requestImage:(ImageSource)imageSource surfaceId:(SurfaceId)surfa scale:imageSource.scale clipped:NO resizeMode:RCTResizeModeStretch - priority:RCTImageLoaderPriorityImmediate + priority:RCTImageLoaderPriorityFromImageRequestPriority(priority) attribution:{ .surfaceId = surfaceId, } diff --git a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTImageManagerProtocol.h b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTImageManagerProtocol.h index 30bfbf391e35..00ff2227cf28 100644 --- a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTImageManagerProtocol.h +++ b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTImageManagerProtocol.h @@ -12,5 +12,6 @@ @protocol RCTImageManagerProtocol - (facebook::react::ImageRequest)requestImage:(facebook::react::ImageSource)imageSource - surfaceId:(facebook::react::SurfaceId)surfaceId; + surfaceId:(facebook::react::SurfaceId)surfaceId + priority:(facebook::react::ImageRequestPriority)priority; @end diff --git a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTImagePrimitivesConversions.h b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTImagePrimitivesConversions.h index a13e6fb6d408..1eae15ae4c7a 100644 --- a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTImagePrimitivesConversions.h +++ b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTImagePrimitivesConversions.h @@ -31,6 +31,19 @@ inline static UIViewContentMode RCTContentModeFromImageResizeMode(facebook::reac } } +inline static RCTImageLoaderPriority RCTImageLoaderPriorityFromImageRequestPriority( + facebook::react::ImageRequestPriority imageRequestPriority) +{ + switch (imageRequestPriority) { + case facebook::react::ImageRequestPriority::Immediate: + return RCTImageLoaderPriorityImmediate; + case facebook::react::ImageRequestPriority::Prefetch: + return RCTImageLoaderPriorityPrefetch; + default: + return RCTImageLoaderPriorityImmediate; + } +} + inline std::string toString(const facebook::react::ImageResizeMode &value) { switch (value) { diff --git a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTSyncImageManager.h b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTSyncImageManager.h index 395f65b9c6b1..689956e429ab 100644 --- a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTSyncImageManager.h +++ b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTSyncImageManager.h @@ -21,7 +21,8 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithImageLoader:(id)imageLoader; - (facebook::react::ImageRequest)requestImage:(facebook::react::ImageSource)imageSource - surfaceId:(facebook::react::SurfaceId)surfaceId; + surfaceId:(facebook::react::SurfaceId)surfaceId + priority:(facebook::react::ImageRequestPriority)priority; @end diff --git a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTSyncImageManager.mm b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTSyncImageManager.mm index f983c64acfa3..f9fb45fef965 100644 --- a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTSyncImageManager.mm +++ b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/react/renderer/imagemanager/RCTSyncImageManager.mm @@ -34,7 +34,9 @@ - (instancetype)initWithImageLoader:(id)i return self; } -- (ImageRequest)requestImage:(ImageSource)imageSource surfaceId:(SurfaceId)surfaceId +- (ImageRequest)requestImage:(ImageSource)imageSource + surfaceId:(SurfaceId)surfaceId + priority:(ImageRequestPriority)priority { auto telemetry = std::make_shared(surfaceId); auto sharedCancelationFunction = SharedFunction<>(); @@ -82,7 +84,7 @@ - (ImageRequest)requestImage:(ImageSource)imageSource surfaceId:(SurfaceId)surfa scale:imageSource.scale clipped:YES resizeMode:RCTResizeModeStretch - priority:RCTImageLoaderPriorityImmediate + priority:RCTImageLoaderPriorityFromImageRequestPriority(priority) attribution:{ .surfaceId = surfaceId, } diff --git a/packages/react-native/ReactCommon/react/renderer/imagemanager/primitives.h b/packages/react-native/ReactCommon/react/renderer/imagemanager/primitives.h index 66407cdd6fb1..2230b11aaef4 100644 --- a/packages/react-native/ReactCommon/react/renderer/imagemanager/primitives.h +++ b/packages/react-native/ReactCommon/react/renderer/imagemanager/primitives.h @@ -169,6 +169,11 @@ enum class ImageResizeMode : int8_t { None = 5, }; +enum class ImageRequestPriority : int8_t { + Immediate = 0, + Prefetch = 1, +}; + class ImageErrorInfo { public: std::string error{}; diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index aff2b57424b8..79279f96b4cf 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -337,6 +337,17 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'none', }, + enableImageRequestDowngradingForNonVisibleImages: { + defaultValue: false, + metadata: { + dateAdded: '2026-05-21', + description: + 'When enabled, ImageShadowNode downgrades image requests to prefetch priority when layout determines that the image does not intersect the viewport.', + expectedReleaseValue: true, + purpose: 'experimentation', + }, + ossReleaseStage: 'none', + }, enableImmediateUpdateModeForContentOffsetChanges: { defaultValue: false, metadata: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index dcf70518a3e3..bc79cad64cb9 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<> + * @generated SignedSource<<943b7be8ca8b83a74af1da421ee15759>> * @flow strict * @noformat */ @@ -73,6 +73,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{ enableIOSTextBaselineOffsetPerLine: Getter, enableIOSViewClipToPaddingBox: Getter, enableImagePrefetchingAndroid: Getter, + enableImageRequestDowngradingForNonVisibleImages: Getter, enableImmediateUpdateModeForContentOffsetChanges: Getter, enableImperativeFocus: Getter, enableInteropViewManagerClassLookUpOptimizationIOS: Getter, @@ -302,6 +303,10 @@ export const enableIOSViewClipToPaddingBox: Getter = createNativeFlagGe * When enabled, Android will build and initiate image prefetch requests on ImageShadowNode::layout */ export const enableImagePrefetchingAndroid: Getter = createNativeFlagGetter('enableImagePrefetchingAndroid', false); +/** + * When enabled, ImageShadowNode downgrades image requests to prefetch priority when layout determines that the image does not intersect the viewport. + */ +export const enableImageRequestDowngradingForNonVisibleImages: Getter = createNativeFlagGetter('enableImageRequestDowngradingForNonVisibleImages', false); /** * Dispatches state updates for content offset changes synchronously on the main thread. */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index 6473c1495dd0..f30b32b26421 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<<5a13b31ebbcc04ed6e2aa6e60dc4c1f4>> * @flow strict * @noformat */ @@ -52,6 +52,7 @@ export interface Spec extends TurboModule { +enableIOSTextBaselineOffsetPerLine?: () => boolean; +enableIOSViewClipToPaddingBox?: () => boolean; +enableImagePrefetchingAndroid?: () => boolean; + +enableImageRequestDowngradingForNonVisibleImages?: () => boolean; +enableImmediateUpdateModeForContentOffsetChanges?: () => boolean; +enableImperativeFocus?: () => boolean; +enableInteropViewManagerClassLookUpOptimizationIOS?: () => boolean; diff --git a/packages/react-native/src/private/testing/fantom/specs/NativeFantom.js b/packages/react-native/src/private/testing/fantom/specs/NativeFantom.js index a47d97b9aacf..f5ea5136cff0 100644 --- a/packages/react-native/src/private/testing/fantom/specs/NativeFantom.js +++ b/packages/react-native/src/private/testing/fantom/specs/NativeFantom.js @@ -129,6 +129,9 @@ interface Spec extends TurboModule { setImageResponse(uri: string, imageResponse: ImageResponse): void; clearImage(uri: string): void; clearAllImages(): void; + getImageRequestCount(uri: string): number; + getImageRequestPriority(uri: string): string; + clearImageRequests(): void; } export default TurboModuleRegistry.getEnforcing( diff --git a/private/react-native-fantom/tester/src/FantomImageManager.h b/private/react-native-fantom/tester/src/FantomImageManager.h new file mode 100644 index 000000000000..46c8d2c02ba2 --- /dev/null +++ b/private/react-native-fantom/tester/src/FantomImageManager.h @@ -0,0 +1,78 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace facebook::react { + +struct FantomImageRequest { + std::string uri; + ImageRequestPriority priority; +}; + +inline std::string toString(ImageRequestPriority priority) +{ + switch (priority) { + case ImageRequestPriority::Immediate: + return "immediate"; + case ImageRequestPriority::Prefetch: + return "prefetch"; + } +} + +class FantomImageManager final : public ImageManager { + public: + FantomImageManager() : ImageManager(nullptr) {} + + ImageRequest requestImage( + const ImageSource &imageSource, + SurfaceId surfaceId, + const ImageRequestParams &imageRequestParams, + Tag /*tag*/) const override + { + requests_.push_back({imageSource.uri, imageRequestParams.priority}); + return {imageSource, std::make_shared(surfaceId), {}}; + } + + size_t getRequestCount(const std::string &uri) const + { + auto count = size_t{}; + for (const auto &request : requests_) { + if (request.uri == uri) { + ++count; + } + } + return count; + } + + std::string getLatestRequestPriority(const std::string &uri) const + { + for (auto it = requests_.rbegin(); it != requests_.rend(); ++it) { + if (it->uri == uri) { + return toString(it->priority); + } + } + return ""; + } + + void clearRequests() + { + requests_.clear(); + } + + private: + mutable std::vector requests_; +}; + +} // namespace facebook::react diff --git a/private/react-native-fantom/tester/src/NativeFantom.cpp b/private/react-native-fantom/tester/src/NativeFantom.cpp index 5b18de491450..ff1bcd3725d6 100644 --- a/private/react-native-fantom/tester/src/NativeFantom.cpp +++ b/private/react-native-fantom/tester/src/NativeFantom.cpp @@ -317,4 +317,22 @@ void NativeFantom::clearAllImages(jsi::Runtime& /*rt*/) { appDelegate_.mountingManager_->imageLoader_->clearAllImages(); } +double NativeFantom::getImageRequestCount( + jsi::Runtime& /*rt*/, + const std::string& uri) { + return static_cast( + appDelegate_.mountingManager_->imageManager_->getRequestCount(uri)); +} + +std::string NativeFantom::getImageRequestPriority( + jsi::Runtime& /*rt*/, + const std::string& uri) { + return appDelegate_.mountingManager_->imageManager_->getLatestRequestPriority( + uri); +} + +void NativeFantom::clearImageRequests(jsi::Runtime& /*rt*/) { + appDelegate_.mountingManager_->imageManager_->clearRequests(); +} + } // namespace facebook::react diff --git a/private/react-native-fantom/tester/src/NativeFantom.h b/private/react-native-fantom/tester/src/NativeFantom.h index 2c0a19a20fb7..1a69d456dcd7 100644 --- a/private/react-native-fantom/tester/src/NativeFantom.h +++ b/private/react-native-fantom/tester/src/NativeFantom.h @@ -140,6 +140,9 @@ class NativeFantom : public NativeFantomCxxSpec { const NativeFantomSetImageResponseImageResponse &imageResponse); void clearImage(jsi::Runtime &rt, const std::string &uri); void clearAllImages(jsi::Runtime &rt); + double getImageRequestCount(jsi::Runtime &rt, const std::string &uri); + std::string getImageRequestPriority(jsi::Runtime &rt, const std::string &uri); + void clearImageRequests(jsi::Runtime &rt); private: TesterAppDelegate &appDelegate_; diff --git a/private/react-native-fantom/tester/src/TesterAppDelegate.cpp b/private/react-native-fantom/tester/src/TesterAppDelegate.cpp index 1fad3777e2f1..1ebdaee96470 100644 --- a/private/react-native-fantom/tester/src/TesterAppDelegate.cpp +++ b/private/react-native-fantom/tester/src/TesterAppDelegate.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -86,6 +87,7 @@ TesterAppDelegate::TesterAppDelegate( DevToolsHttpClientFactoryKey, getHttpClientFactory()); contextContainer->insert( DevToolsWebSocketClientFactoryKey, getWebSocketClientFactory()); + contextContainer->insert(ImageManagerKey, mountingManager_->imageManager_); runLoopObserverManager_ = std::make_shared(); @@ -191,6 +193,7 @@ void TesterAppDelegate::startSurface( LayoutContext layoutContext{ .pointScaleFactor = pointScaleFactor, .viewportOffset = {.x = offsetX, .y = offsetY}, + .viewportSize = extentsDp, }; reactHost_->startSurface( @@ -226,6 +229,7 @@ void TesterAppDelegate::updateSurfaceConstraints( LayoutContext layoutContext{ .pointScaleFactor = pointScaleFactor, + .viewportSize = extentsDp, }; reactHost_->setSurfaceConstraints( diff --git a/private/react-native-fantom/tester/src/TesterMountingManager.cpp b/private/react-native-fantom/tester/src/TesterMountingManager.cpp index 4526eace7d21..4fd1765bd33e 100644 --- a/private/react-native-fantom/tester/src/TesterMountingManager.cpp +++ b/private/react-native-fantom/tester/src/TesterMountingManager.cpp @@ -21,6 +21,7 @@ TesterMountingManager::TesterMountingManager( std::function&& onAfterMount) : onAfterMount_(onAfterMount), renderer_(std::make_unique()) { imageLoader_ = std::make_shared(); + imageManager_ = std::make_shared(); } void TesterMountingManager::executeMount( diff --git a/private/react-native-fantom/tester/src/TesterMountingManager.h b/private/react-native-fantom/tester/src/TesterMountingManager.h index cdfdf96d584d..c14a8a7e9334 100644 --- a/private/react-native-fantom/tester/src/TesterMountingManager.h +++ b/private/react-native-fantom/tester/src/TesterMountingManager.h @@ -8,6 +8,7 @@ #pragma once #include "FantomImageLoader.h" +#include "FantomImageManager.h" #include #include @@ -47,6 +48,7 @@ class TesterMountingManager : public IMountingManager { } std::shared_ptr imageLoader_; + std::shared_ptr imageManager_; std::shared_ptr getImageLoader() noexcept override { diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index 9a10db992942..2b4fbb3a4986 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -2597,6 +2597,7 @@ class facebook::react::ImageRequestParams { public facebook::react::Float blurRadius; public facebook::react::Float fadeDuration; public facebook::react::Float resizeMultiplier; + public facebook::react::ImageRequestPriority priority; public facebook::react::ImageResizeMode resizeMode; public facebook::react::ImageSource defaultSource; public facebook::react::ImageSource loadingIndicatorSource; @@ -6220,6 +6221,11 @@ enum facebook::react::HyphenationFrequency { Normal, } +enum facebook::react::ImageRequestPriority : int8_t { + Immediate, + Prefetch, +} + enum facebook::react::ImageResizeMode : int8_t { Center, Contain, diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index 9d8211cbf16b..06d8694b4b82 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -2579,6 +2579,7 @@ class facebook::react::ImageRequestParams { public facebook::react::Float blurRadius; public facebook::react::Float fadeDuration; public facebook::react::Float resizeMultiplier; + public facebook::react::ImageRequestPriority priority; public facebook::react::ImageResizeMode resizeMode; public facebook::react::ImageSource defaultSource; public facebook::react::ImageSource loadingIndicatorSource; @@ -6046,6 +6047,11 @@ enum facebook::react::HyphenationFrequency { Normal, } +enum facebook::react::ImageRequestPriority : int8_t { + Immediate, + Prefetch, +} + enum facebook::react::ImageResizeMode : int8_t { Center, Contain, diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index 93f507c31385..1f7567f39773 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -2594,6 +2594,7 @@ class facebook::react::ImageRequestParams { public facebook::react::Float blurRadius; public facebook::react::Float fadeDuration; public facebook::react::Float resizeMultiplier; + public facebook::react::ImageRequestPriority priority; public facebook::react::ImageResizeMode resizeMode; public facebook::react::ImageSource defaultSource; public facebook::react::ImageSource loadingIndicatorSource; @@ -6211,6 +6212,11 @@ enum facebook::react::HyphenationFrequency { Normal, } +enum facebook::react::ImageRequestPriority : int8_t { + Immediate, + Prefetch, +} + enum facebook::react::ImageResizeMode : int8_t { Center, Contain, diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index a2b9bb40e125..d4f7c124a3cb 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -1273,7 +1273,7 @@ interface RCTImageLoader : public NSObject { - public virtual facebook::react::ImageRequest requestImage:surfaceId:(facebook::react::ImageSource imageSource, facebook::react::SurfaceId surfaceId); + public virtual facebook::react::ImageRequest requestImage:surfaceId:priority:(facebook::react::ImageSource imageSource, facebook::react::SurfaceId surfaceId, facebook::react::ImageRequestPriority priority); public virtual instancetype initWithImageLoader:(id imageLoader); } @@ -2187,7 +2187,7 @@ interface RCTSwitchManager : public RCTViewManager { } interface RCTSyncImageManager : public NSObject { - public virtual facebook::react::ImageRequest requestImage:surfaceId:(facebook::react::ImageSource imageSource, facebook::react::SurfaceId surfaceId); + public virtual facebook::react::ImageRequest requestImage:surfaceId:priority:(facebook::react::ImageSource imageSource, facebook::react::SurfaceId surfaceId, facebook::react::ImageRequestPriority priority); public virtual instancetype initWithImageLoader:(id imageLoader); } @@ -3235,7 +3235,7 @@ protocol RCTImageLoaderWithAttributionProtocol : public RCTImageLoaderProtocol, } protocol RCTImageManagerProtocol : public NSObject { - public virtual facebook::react::ImageRequest requestImage:surfaceId:(facebook::react::ImageSource imageSource, facebook::react::SurfaceId surfaceId); + public virtual facebook::react::ImageRequest requestImage:surfaceId:priority:(facebook::react::ImageSource imageSource, facebook::react::SurfaceId surfaceId, facebook::react::ImageRequestPriority priority); } protocol RCTImageRedirectProtocol { @@ -5382,9 +5382,10 @@ class facebook::react::ImageRequest { class facebook::react::ImageRequestParams { public ImageRequestParams(); - public ImageRequestParams(facebook::react::Float blurRadius); + public ImageRequestParams(facebook::react::Float blurRadius, facebook::react::ImageRequestPriority priority = facebook::react::ImageRequestPriority::Immediate); public bool operator==(const facebook::react::ImageRequestParams& rhs) const = default; public facebook::react::Float blurRadius; + public facebook::react::ImageRequestPriority priority; } class facebook::react::ImageResponse { @@ -8765,6 +8766,11 @@ enum facebook::react::HyphenationFrequency { Normal, } +enum facebook::react::ImageRequestPriority : int8_t { + Immediate, + Prefetch, +} + enum facebook::react::ImageResizeMode : int8_t { Center, Contain, diff --git a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api index 66dd85e4cb8c..cd89d347ccfa 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api @@ -1198,7 +1198,7 @@ interface RCTImageLoader : public NSObject { - public virtual facebook::react::ImageRequest requestImage:surfaceId:(facebook::react::ImageSource imageSource, facebook::react::SurfaceId surfaceId); + public virtual facebook::react::ImageRequest requestImage:surfaceId:priority:(facebook::react::ImageSource imageSource, facebook::react::SurfaceId surfaceId, facebook::react::ImageRequestPriority priority); public virtual instancetype initWithImageLoader:(id imageLoader); } @@ -1936,7 +1936,7 @@ interface RCTSwitchComponentView : public RCTViewComponentView { } interface RCTSyncImageManager : public NSObject { - public virtual facebook::react::ImageRequest requestImage:surfaceId:(facebook::react::ImageSource imageSource, facebook::react::SurfaceId surfaceId); + public virtual facebook::react::ImageRequest requestImage:surfaceId:priority:(facebook::react::ImageSource imageSource, facebook::react::SurfaceId surfaceId, facebook::react::ImageRequestPriority priority); public virtual instancetype initWithImageLoader:(id imageLoader); } @@ -2878,7 +2878,7 @@ protocol RCTImageLoaderWithAttributionProtocol : public RCTImageLoaderProtocol, } protocol RCTImageManagerProtocol : public NSObject { - public virtual facebook::react::ImageRequest requestImage:surfaceId:(facebook::react::ImageSource imageSource, facebook::react::SurfaceId surfaceId); + public virtual facebook::react::ImageRequest requestImage:surfaceId:priority:(facebook::react::ImageSource imageSource, facebook::react::SurfaceId surfaceId, facebook::react::ImageRequestPriority priority); } protocol RCTImageRedirectProtocol { @@ -4989,9 +4989,10 @@ class facebook::react::ImageRequest { class facebook::react::ImageRequestParams { public ImageRequestParams(); - public ImageRequestParams(facebook::react::Float blurRadius); + public ImageRequestParams(facebook::react::Float blurRadius, facebook::react::ImageRequestPriority priority = facebook::react::ImageRequestPriority::Immediate); public bool operator==(const facebook::react::ImageRequestParams& rhs) const = default; public facebook::react::Float blurRadius; + public facebook::react::ImageRequestPriority priority; } class facebook::react::ImageResponse { @@ -8240,6 +8241,11 @@ enum facebook::react::HyphenationFrequency { Normal, } +enum facebook::react::ImageRequestPriority : int8_t { + Immediate, + Prefetch, +} + enum facebook::react::ImageResizeMode : int8_t { Center, Contain, diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index 84098faa3185..8219222a7c4d 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -1273,7 +1273,7 @@ interface RCTImageLoader : public NSObject { - public virtual facebook::react::ImageRequest requestImage:surfaceId:(facebook::react::ImageSource imageSource, facebook::react::SurfaceId surfaceId); + public virtual facebook::react::ImageRequest requestImage:surfaceId:priority:(facebook::react::ImageSource imageSource, facebook::react::SurfaceId surfaceId, facebook::react::ImageRequestPriority priority); public virtual instancetype initWithImageLoader:(id imageLoader); } @@ -2187,7 +2187,7 @@ interface RCTSwitchManager : public RCTViewManager { } interface RCTSyncImageManager : public NSObject { - public virtual facebook::react::ImageRequest requestImage:surfaceId:(facebook::react::ImageSource imageSource, facebook::react::SurfaceId surfaceId); + public virtual facebook::react::ImageRequest requestImage:surfaceId:priority:(facebook::react::ImageSource imageSource, facebook::react::SurfaceId surfaceId, facebook::react::ImageRequestPriority priority); public virtual instancetype initWithImageLoader:(id imageLoader); } @@ -3235,7 +3235,7 @@ protocol RCTImageLoaderWithAttributionProtocol : public RCTImageLoaderProtocol, } protocol RCTImageManagerProtocol : public NSObject { - public virtual facebook::react::ImageRequest requestImage:surfaceId:(facebook::react::ImageSource imageSource, facebook::react::SurfaceId surfaceId); + public virtual facebook::react::ImageRequest requestImage:surfaceId:priority:(facebook::react::ImageSource imageSource, facebook::react::SurfaceId surfaceId, facebook::react::ImageRequestPriority priority); } protocol RCTImageRedirectProtocol { @@ -5379,9 +5379,10 @@ class facebook::react::ImageRequest { class facebook::react::ImageRequestParams { public ImageRequestParams(); - public ImageRequestParams(facebook::react::Float blurRadius); + public ImageRequestParams(facebook::react::Float blurRadius, facebook::react::ImageRequestPriority priority = facebook::react::ImageRequestPriority::Immediate); public bool operator==(const facebook::react::ImageRequestParams& rhs) const = default; public facebook::react::Float blurRadius; + public facebook::react::ImageRequestPriority priority; } class facebook::react::ImageResponse { @@ -8756,6 +8757,11 @@ enum facebook::react::HyphenationFrequency { Normal, } +enum facebook::react::ImageRequestPriority : int8_t { + Immediate, + Prefetch, +} + enum facebook::react::ImageResizeMode : int8_t { Center, Contain, diff --git a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api index 3a7473351c4b..5f68b22fb882 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -4583,6 +4583,11 @@ enum facebook::react::HyphenationFrequency { Normal, } +enum facebook::react::ImageRequestPriority : int8_t { + Immediate, + Prefetch, +} + enum facebook::react::ImageResizeMode : int8_t { Center, Contain, diff --git a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api index 5a5fc19d0af6..81705abe51bd 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api @@ -4449,6 +4449,11 @@ enum facebook::react::HyphenationFrequency { Normal, } +enum facebook::react::ImageRequestPriority : int8_t { + Immediate, + Prefetch, +} + enum facebook::react::ImageResizeMode : int8_t { Center, Contain, diff --git a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api index f64fbd2790a4..29810fb16f09 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -4574,6 +4574,11 @@ enum facebook::react::HyphenationFrequency { Normal, } +enum facebook::react::ImageRequestPriority : int8_t { + Immediate, + Prefetch, +} + enum facebook::react::ImageResizeMode : int8_t { Center, Contain,