diff --git a/packages/react-native/Libraries/Image/Image.android.js b/packages/react-native/Libraries/Image/Image.android.js index f5e3f8a64ef9..b30c90dc0d4b 100644 --- a/packages/react-native/Libraries/Image/Image.android.js +++ b/packages/react-native/Libraries/Image/Image.android.js @@ -12,8 +12,10 @@ import type {HostInstance} from '../../src/private/types/HostInstance'; import type {ImageStyleProp} from '../StyleSheet/StyleSheet'; import type {RootTag} from '../Types/RootTagTypes'; import type {ImageProps} from './ImageProps'; +import type {ImageSourceHeaders} from './ImageSourceUtils'; import type {AbstractImageAndroid, ImageAndroid} from './ImageTypes.flow'; +import * as ReactNativeFeatureFlags from '../../src/private/featureflags/ReactNativeFeatureFlags'; import flattenStyle from '../StyleSheet/flattenStyle'; import StyleSheet from '../StyleSheet/StyleSheet'; import ImageAnalyticsTagContext from './ImageAnalyticsTagContext'; @@ -194,37 +196,55 @@ let BaseImage: AbstractImageAndroid = ({ ); } - const nativeProps = restProps as { - ...React.PropsOf, - }; - + let style_: ImageStyleProp; + let sources_; + let headers_: ?ImageSourceHeaders; if (Array.isArray(source_)) { const { headers: sourceHeaders, width: sourceWidth, height: sourceHeight, } = source_[0]; - if (sourceHeaders != null) { - nativeProps.headers = sourceHeaders; - } + headers_ = sourceHeaders; // Default to the first source's width and height if only one is provided - nativeProps.style = [ - source_.length === 1 && {width: sourceWidth, height: sourceHeight}, - styles.base, - style, - ]; - nativeProps.source = source_; + if (ReactNativeFeatureFlags.fixImageSrcDimensionPropagation()) { + style_ = [ + source_.length === 1 && {width: sourceWidth, height: sourceHeight}, + styles.base, + style, + ]; + } else { + style_ = [styles.base, style]; + } + sources_ = source_; } else { const {uri, width: sourceWidth, height: sourceHeight} = source_; if (uri === '') { console.warn('source.uri should not be an empty string'); } - nativeProps.style = [ + style_ = [ {width: sourceWidth ?? width, height: sourceHeight ?? height}, styles.base, style, ]; - nativeProps.source = [source_]; + sources_ = [source_]; + } + + const nativeProps = restProps as { + ...React.PropsOf, + }; + + // Both iOS and C++ sides expect to have "source" prop, whereas on Android it's "src" + // (for historical reasons). So in the latter case we populate both "src" and "source", + // in order to have a better alignment between platforms in the future. + // TODO: `src` should be eventually removed from the API on Android. + nativeProps.src = sources_; + nativeProps.source = sources_; + + nativeProps.style = style_; + + if (headers_ != null) { + nativeProps.headers = headers_; } if (onLoadStart != null) { diff --git a/packages/react-native/Libraries/Image/__tests__/Image-itest.js b/packages/react-native/Libraries/Image/__tests__/Image-itest.js index a0f38399d419..5cc8fd7cc1f6 100644 --- a/packages/react-native/Libraries/Image/__tests__/Image-itest.js +++ b/packages/react-native/Libraries/Image/__tests__/Image-itest.js @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. * * @flow strict-local + * @fantom_flags fixImageSrcDimensionPropagation:* * @format */ @@ -12,6 +13,7 @@ import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; import type {AccessibilityProps, HostInstance} from 'react-native'; +import * as ReactNativeFeatureFlags from '../../../src/private/featureflags/ReactNativeFeatureFlags'; import * as Fantom from '@react-native/fantom'; import * as React from 'react'; import {createRef} from 'react'; @@ -475,14 +477,23 @@ describe('', () => { .getRenderedOutput({props: ['source', 'width', 'height']}) .toJSX(), ).toEqual( - , + ReactNativeFeatureFlags.fixImageSrcDimensionPropagation() ? ( + + ) : ( + + ), ); }); }); diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index c83379238b1a..33456602489f 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -1076,6 +1076,16 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'none', }, + fixImageSrcDimensionPropagation: { + defaultValue: true, + metadata: { + description: + 'Fix image dimensions not being passed through when src is used', + expectedReleaseValue: true, + purpose: 'release', + }, + ossReleaseStage: 'none', + }, fixVirtualizeListCollapseWindowSize: { defaultValue: false, metadata: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index 85d88b3b9803..39893163009f 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<<8c2aa180526563dbaaeb91f1428e0c77>> * @flow strict * @noformat */ @@ -34,6 +34,7 @@ export type ReactNativeFeatureFlagsJsOnly = $ReadOnly<{ deferFlatListFocusChangeRenderUpdate: Getter, disableMaintainVisibleContentPosition: Getter, externalElementInspectionEnabled: Getter, + fixImageSrcDimensionPropagation: Getter, fixVirtualizeListCollapseWindowSize: Getter, isLayoutAnimationEnabled: Getter, shouldUseAnimatedObjectForTransform: Getter, @@ -169,6 +170,11 @@ export const disableMaintainVisibleContentPosition: Getter = createJava */ export const externalElementInspectionEnabled: Getter = createJavaScriptFlagGetter('externalElementInspectionEnabled', true); +/** + * Fix image dimensions not being passed through when src is used + */ +export const fixImageSrcDimensionPropagation: Getter = createJavaScriptFlagGetter('fixImageSrcDimensionPropagation', true); + /** * Fixing an edge case where the current window size is not properly calculated with fast scrolling. Window size collapsed to 1 element even if windowSize more than the current amount of elements */