Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added crossOrigin, referrerPolicy, srcSet, width, height and src props to the Image Component. #34481

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 11 additions & 19 deletions Libraries/Image/Image.android.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import TextInlineImageNativeComponent from './TextInlineImageNativeComponent';

import type {ImageProps as ImagePropsType} from './ImageProps';
import type {RootTag} from '../Types/RootTagTypes';
import {getImageSourcesFromImageProps} from './ImageSourceUtils';

let _requestId = 1;
function generateRequestId() {
Expand Down Expand Up @@ -126,25 +127,12 @@ export type ImageComponentStatics = $ReadOnly<{|
/* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's
* LTI update could not be added via codemod */
const BaseImage = (props: ImagePropsType, forwardedRef) => {
let source = resolveAssetSource(props.source);
let source = getImageSourcesFromImageProps(props);
const defaultSource = resolveAssetSource(props.defaultSource);
const loadingIndicatorSource = resolveAssetSource(
props.loadingIndicatorSource,
);

if (source) {
const uri = source.uri;
if (uri === '') {
console.warn('source.uri should not be an empty string');
}
}

if (props.src) {
console.warn(
'The <Image> component requires a `source` property rather than `src`.',
);
}

if (props.children) {
throw new Error(
'The <Image> component cannot contain children. If you want to render content on top of the image, consider using the <ImageBackground> component or absolute positioning.',
Expand All @@ -163,24 +151,28 @@ const BaseImage = (props: ImagePropsType, forwardedRef) => {

let style;
let sources;
if (source?.uri != null) {
const {width, height} = source;
if (!Array.isArray(source) && source?.uri != null) {
const {width = props.width, height = props.height, uri} = source;
style = flattenStyle([{width, height}, styles.base, props.style]);
sources = [{uri: source.uri}];
sources = [{uri: uri, width: width, height: height}];
if (uri === '') {
console.warn('source.uri should not be an empty string');
}
} else {
style = flattenStyle([styles.base, props.style]);
sources = source;
}

const {height, width, ...restProps} = props;
const {onLoadStart, onLoad, onLoadEnd, onError} = props;
const nativeProps = {
...props,
...restProps,
style,
shouldNotifyLoadEvents: !!(onLoadStart || onLoad || onLoadEnd || onError),
src: sources,
/* $FlowFixMe(>=0.78.0 site=react_native_android_fb) This issue was found
* when making Flow check .android.js files. */
headers: source?.headers,
headers: (source?.[0]?.headers || source?.headers: ?{[string]: string}),
defaultSrc: defaultSource ? defaultSource.uri : null,
loadingIndicatorSrc: loadingIndicatorSource
? loadingIndicatorSource.uri
Expand Down
15 changes: 6 additions & 9 deletions Libraries/Image/Image.ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import NativeImageLoaderIOS from './NativeImageLoaderIOS';

import ImageViewNativeComponent from './ImageViewNativeComponent';
import type {RootTag} from 'react-native/Libraries/Types/RootTagTypes';
import {getImageSourcesFromImageProps} from './ImageSourceUtils';

function getSize(
uri: string,
Expand Down Expand Up @@ -105,7 +106,7 @@ export type ImageComponentStatics = $ReadOnly<{|
/* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's
* LTI update could not be added via codemod */
const BaseImage = (props: ImagePropsType, forwardedRef) => {
const source = resolveAssetSource(props.source) || {
const source = getImageSourcesFromImageProps(props) || {
uri: undefined,
width: undefined,
height: undefined,
Expand All @@ -117,7 +118,7 @@ const BaseImage = (props: ImagePropsType, forwardedRef) => {
style = flattenStyle([styles.base, props.style]) || {};
sources = source;
} else {
const {width, height, uri} = source;
const {width = props.width, height = props.height, uri} = source;
style = flattenStyle([{width, height}, styles.base, props.style]) || {};
sources = [source];

Expand All @@ -131,24 +132,20 @@ const BaseImage = (props: ImagePropsType, forwardedRef) => {
// $FlowFixMe[prop-missing]
const tintColor = props.tintColor || style.tintColor;

if (props.src != null) {
console.warn(
'The <Image> component requires a `source` property rather than `src`.',
);
}

if (props.children != null) {
throw new Error(
'The <Image> component cannot contain children. If you want to render content on top of the image, consider using the <ImageBackground> component or absolute positioning.',
);
}

const {src, width, height, ...restProps} = props;

return (
<ImageAnalyticsTagContext.Consumer>
{analyticTag => {
return (
<ImageViewNativeComponent
{...props}
{...restProps}
ref={forwardedRef}
style={style}
resizeMode={resizeMode}
Expand Down
55 changes: 54 additions & 1 deletion Libraries/Image/ImageProps.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import type {
import type {ViewProps} from '../Components/View/ViewPropTypes';
import type {Node, Ref} from 'react';
import typeof Image from './Image';
import type {DimensionValue} from '../StyleSheet/StyleSheetTypes';

export type ImageLoadEvent = SyntheticEvent<
$ReadOnly<{|
Expand Down Expand Up @@ -98,6 +99,28 @@ export type ImageProps = {|
*/
capInsets?: ?EdgeInsetsProp,

/**
* Adds the CORS related header to the request.
* Similar to crossorigin from HTML.
*
* See https://reactnative.dev/docs/image#crossorigin
*/
crossOrigin?: ?('anonymous' | 'use-credentials'),

/**
* Height of the image component.
*
* See https://reactnative.dev/docs/image#height
*/
height?: number,

/**
* Width of the image component.
*
* See https://reactnative.dev/docs/image#width
*/
width?: number,

/**
* Invoked on load error with `{nativeEvent: {error}}`.
*
Expand Down Expand Up @@ -158,6 +181,23 @@ export type ImageProps = {|
*/
style?: ?ImageStyleProp,

/**
* A string indicating which referrer to use when fetching the resource.
* Similar to referrerpolicy from HTML.
*
* See https://reactnative.dev/docs/image#referrerpolicy
*/
referrerPolicy?: ?(
| 'no-referrer'
| 'no-referrer-when-downgrade'
| 'origin'
| 'origin-when-cross-origin'
| 'same-origin'
| 'strict-origin'
| 'strict-origin-when-cross-origin'
| 'unsafe-url'
),

/**
* Determines how to resize the image when the frame doesn't match the raw
* image dimensions.
Expand All @@ -181,7 +221,20 @@ export type ImageProps = {|
*/
tintColor?: ColorValue,

src?: empty,
/**
* A string representing the resource identifier for the image. Similar to
* src from HTML.
*
* See https://reactnative.dev/docs/image#src
*/
src?: ?string,

/**
* Similar to srcset from HTML.
*
* See https://reactnative.dev/docs/image#srcset
*/
srcSet?: ?string,
children?: empty,
|};

Expand Down
80 changes: 80 additions & 0 deletions Libraries/Image/ImageSourceUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* 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.
*
* @flow strict-local
* @format
*/

'use strict';

import type {ResolvedAssetSource} from './AssetSourceResolver';
import type {ImageProps} from './ImageProps';

import resolveAssetSource from './resolveAssetSource';

/**
* A function which returns the appropriate value for image source
* by resolving the `source`, `src` and `srcSet` props.
*/
export function getImageSourcesFromImageProps(
imageProps: ImageProps,
): ?ResolvedAssetSource | $ReadOnlyArray<{uri: string, ...}> {
let source = resolveAssetSource(imageProps.source);

let sources;

const {crossOrigin, referrerPolicy, src, srcSet, width, height} = imageProps;

const headers: {[string]: string} = {};
if (crossOrigin === 'use-credentials') {
headers['Access-Control-Allow-Credentials'] = 'true';
}
if (referrerPolicy != null) {
headers['Referrer-Policy'] = referrerPolicy;
}
if (srcSet != null) {
const sourceList = [];
const srcSetList = srcSet.split(', ');
// `src` prop should be used with default scale if `srcSet` does not have 1x scale.
let shouldUseSrcForDefaultScale = true;
srcSetList.forEach(imageSrc => {
const [uri, xScale = '1x'] = imageSrc.split(' ');
if (!xScale.endsWith('x')) {
console.warn(
'The provided format for scale is not supported yet. Please use scales like 1x, 2x, etc.',
);
} else {
const scale = parseInt(xScale.split('x')[0], 10);
if (!isNaN(scale)) {
// 1x scale is provided in `srcSet` prop so ignore the `src` prop if provided.
shouldUseSrcForDefaultScale =
scale === 1 ? false : shouldUseSrcForDefaultScale;
sourceList.push({headers: headers, scale, uri, width, height});
}
}
});

if (shouldUseSrcForDefaultScale && src != null) {
sourceList.push({
headers: headers,
scale: 1,
uri: src,
width,
height,
});
}
if (sourceList.length === 0) {
console.warn('The provided value for srcSet is not valid.');
}

sources = sourceList;
} else if (src != null) {
sources = [{uri: src, headers: headers, width, height}];
} else {
sources = source;
}
return sources;
}
Loading