Skip to content

Commit

Permalink
feat: added crossOrigin, referrerPolicy, srcSet, width, `heig…
Browse files Browse the repository at this point in the history
…ht` and `src` props to the Image Component. (facebook#34481)

Summary:
This PR is for adding the support for `crossOrigin`, `referrerPolicy`, `width`, `height` and `srcSet` props to Image Component and mapping the `src` prop to `source.uri` of Image Component for the issue facebook#34424. An example is also added in the RNTester ImageExample.

## Changelog

[General] [Changed] - Map the `src` prop to `source.uri` prop in Image Component.
[General] [Added] - added `crossOrigin`, `referrerPolicy`, `width`, `height` and `srcSet` props to Image Component.

Pull Request resolved: facebook#34481

Test Plan:
1. Navigate to Image Component Example in the RNTester app.
2. Contains an example of the Image component using the `src` and `srcSet` prop.
3. For headers, inspect the Image request using Flipper.

<img src="https://user-images.githubusercontent.com/32268377/186153246-d7b72ce3-e082-46d9-87d1-aefacd3af34f.png" height="500" />

Reviewed By: christophpurrer

Differential Revision: D38982041

Pulled By: cipolleschi

fbshipit-source-id: dd6594e39b8f3b36cfcdafa35695254034f1fb7f
  • Loading branch information
dhruvtailor7 authored and OlimpiaZurek committed May 22, 2023
1 parent 1c2444f commit 8e8971d
Show file tree
Hide file tree
Showing 6 changed files with 323 additions and 30 deletions.
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

0 comments on commit 8e8971d

Please sign in to comment.