Skip to content

Commit 47a05bc

Browse files
dhruvtailor7facebook-github-bot
dhruvtailor7
authored andcommitted
feat: added crossOrigin, referrerPolicy, srcSet, width, height and src props to the Image Component. (#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 #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: #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
1 parent 32d12e8 commit 47a05bc

File tree

6 files changed

+323
-30
lines changed

6 files changed

+323
-30
lines changed

Libraries/Image/Image.android.js

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import TextInlineImageNativeComponent from './TextInlineImageNativeComponent';
2323

2424
import type {ImageProps as ImagePropsType} from './ImageProps';
2525
import type {RootTag} from '../Types/RootTagTypes';
26+
import {getImageSourcesFromImageProps} from './ImageSourceUtils';
2627

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

135-
if (source) {
136-
const uri = source.uri;
137-
if (uri === '') {
138-
console.warn('source.uri should not be an empty string');
139-
}
140-
}
141-
142-
if (props.src) {
143-
console.warn(
144-
'The <Image> component requires a `source` property rather than `src`.',
145-
);
146-
}
147-
148136
if (props.children) {
149137
throw new Error(
150138
'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.',
@@ -163,24 +151,28 @@ const BaseImage = (props: ImagePropsType, forwardedRef) => {
163151

164152
let style;
165153
let sources;
166-
if (source?.uri != null) {
167-
const {width, height} = source;
154+
if (!Array.isArray(source) && source?.uri != null) {
155+
const {width = props.width, height = props.height, uri} = source;
168156
style = flattenStyle([{width, height}, styles.base, props.style]);
169-
sources = [{uri: source.uri}];
157+
sources = [{uri: uri, width: width, height: height}];
158+
if (uri === '') {
159+
console.warn('source.uri should not be an empty string');
160+
}
170161
} else {
171162
style = flattenStyle([styles.base, props.style]);
172163
sources = source;
173164
}
174165

166+
const {height, width, ...restProps} = props;
175167
const {onLoadStart, onLoad, onLoadEnd, onError} = props;
176168
const nativeProps = {
177-
...props,
169+
...restProps,
178170
style,
179171
shouldNotifyLoadEvents: !!(onLoadStart || onLoad || onLoadEnd || onError),
180172
src: sources,
181173
/* $FlowFixMe(>=0.78.0 site=react_native_android_fb) This issue was found
182174
* when making Flow check .android.js files. */
183-
headers: source?.headers,
175+
headers: (source?.[0]?.headers || source?.headers: ?{[string]: string}),
184176
defaultSrc: defaultSource ? defaultSource.uri : null,
185177
loadingIndicatorSrc: loadingIndicatorSource
186178
? loadingIndicatorSource.uri

Libraries/Image/Image.ios.js

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import NativeImageLoaderIOS from './NativeImageLoaderIOS';
2323

2424
import ImageViewNativeComponent from './ImageViewNativeComponent';
2525
import type {RootTag} from 'react-native/Libraries/Types/RootTagTypes';
26+
import {getImageSourcesFromImageProps} from './ImageSourceUtils';
2627

2728
function getSize(
2829
uri: string,
@@ -105,7 +106,7 @@ export type ImageComponentStatics = $ReadOnly<{|
105106
/* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's
106107
* LTI update could not be added via codemod */
107108
const BaseImage = (props: ImagePropsType, forwardedRef) => {
108-
const source = resolveAssetSource(props.source) || {
109+
const source = getImageSourcesFromImageProps(props) || {
109110
uri: undefined,
110111
width: undefined,
111112
height: undefined,
@@ -117,7 +118,7 @@ const BaseImage = (props: ImagePropsType, forwardedRef) => {
117118
style = flattenStyle([styles.base, props.style]) || {};
118119
sources = source;
119120
} else {
120-
const {width, height, uri} = source;
121+
const {width = props.width, height = props.height, uri} = source;
121122
style = flattenStyle([{width, height}, styles.base, props.style]) || {};
122123
sources = [source];
123124

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

134-
if (props.src != null) {
135-
console.warn(
136-
'The <Image> component requires a `source` property rather than `src`.',
137-
);
138-
}
139-
140135
if (props.children != null) {
141136
throw new Error(
142137
'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.',
143138
);
144139
}
145140

141+
const {src, width, height, ...restProps} = props;
142+
146143
return (
147144
<ImageAnalyticsTagContext.Consumer>
148145
{analyticTag => {
149146
return (
150147
<ImageViewNativeComponent
151-
{...props}
148+
{...restProps}
152149
ref={forwardedRef}
153150
style={style}
154151
resizeMode={resizeMode}

Libraries/Image/ImageProps.js

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import type {
2121
import type {ViewProps} from '../Components/View/ViewPropTypes';
2222
import type {Node, Ref} from 'react';
2323
import typeof Image from './Image';
24+
import type {DimensionValue} from '../StyleSheet/StyleSheetTypes';
2425

2526
export type ImageLoadEvent = SyntheticEvent<
2627
$ReadOnly<{|
@@ -98,6 +99,28 @@ export type ImageProps = {|
9899
*/
99100
capInsets?: ?EdgeInsetsProp,
100101

102+
/**
103+
* Adds the CORS related header to the request.
104+
* Similar to crossorigin from HTML.
105+
*
106+
* See https://reactnative.dev/docs/image#crossorigin
107+
*/
108+
crossOrigin?: ?('anonymous' | 'use-credentials'),
109+
110+
/**
111+
* Height of the image component.
112+
*
113+
* See https://reactnative.dev/docs/image#height
114+
*/
115+
height?: number,
116+
117+
/**
118+
* Width of the image component.
119+
*
120+
* See https://reactnative.dev/docs/image#width
121+
*/
122+
width?: number,
123+
101124
/**
102125
* Invoked on load error with `{nativeEvent: {error}}`.
103126
*
@@ -158,6 +181,23 @@ export type ImageProps = {|
158181
*/
159182
style?: ?ImageStyleProp,
160183

184+
/**
185+
* A string indicating which referrer to use when fetching the resource.
186+
* Similar to referrerpolicy from HTML.
187+
*
188+
* See https://reactnative.dev/docs/image#referrerpolicy
189+
*/
190+
referrerPolicy?: ?(
191+
| 'no-referrer'
192+
| 'no-referrer-when-downgrade'
193+
| 'origin'
194+
| 'origin-when-cross-origin'
195+
| 'same-origin'
196+
| 'strict-origin'
197+
| 'strict-origin-when-cross-origin'
198+
| 'unsafe-url'
199+
),
200+
161201
/**
162202
* Determines how to resize the image when the frame doesn't match the raw
163203
* image dimensions.
@@ -181,7 +221,20 @@ export type ImageProps = {|
181221
*/
182222
tintColor?: ColorValue,
183223

184-
src?: empty,
224+
/**
225+
* A string representing the resource identifier for the image. Similar to
226+
* src from HTML.
227+
*
228+
* See https://reactnative.dev/docs/image#src
229+
*/
230+
src?: ?string,
231+
232+
/**
233+
* Similar to srcset from HTML.
234+
*
235+
* See https://reactnative.dev/docs/image#srcset
236+
*/
237+
srcSet?: ?string,
185238
children?: empty,
186239
|};
187240

Libraries/Image/ImageSourceUtils.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
'use strict';
12+
13+
import type {ResolvedAssetSource} from './AssetSourceResolver';
14+
import type {ImageProps} from './ImageProps';
15+
16+
import resolveAssetSource from './resolveAssetSource';
17+
18+
/**
19+
* A function which returns the appropriate value for image source
20+
* by resolving the `source`, `src` and `srcSet` props.
21+
*/
22+
export function getImageSourcesFromImageProps(
23+
imageProps: ImageProps,
24+
): ?ResolvedAssetSource | $ReadOnlyArray<{uri: string, ...}> {
25+
let source = resolveAssetSource(imageProps.source);
26+
27+
let sources;
28+
29+
const {crossOrigin, referrerPolicy, src, srcSet, width, height} = imageProps;
30+
31+
const headers: {[string]: string} = {};
32+
if (crossOrigin === 'use-credentials') {
33+
headers['Access-Control-Allow-Credentials'] = 'true';
34+
}
35+
if (referrerPolicy != null) {
36+
headers['Referrer-Policy'] = referrerPolicy;
37+
}
38+
if (srcSet != null) {
39+
const sourceList = [];
40+
const srcSetList = srcSet.split(', ');
41+
// `src` prop should be used with default scale if `srcSet` does not have 1x scale.
42+
let shouldUseSrcForDefaultScale = true;
43+
srcSetList.forEach(imageSrc => {
44+
const [uri, xScale = '1x'] = imageSrc.split(' ');
45+
if (!xScale.endsWith('x')) {
46+
console.warn(
47+
'The provided format for scale is not supported yet. Please use scales like 1x, 2x, etc.',
48+
);
49+
} else {
50+
const scale = parseInt(xScale.split('x')[0], 10);
51+
if (!isNaN(scale)) {
52+
// 1x scale is provided in `srcSet` prop so ignore the `src` prop if provided.
53+
shouldUseSrcForDefaultScale =
54+
scale === 1 ? false : shouldUseSrcForDefaultScale;
55+
sourceList.push({headers: headers, scale, uri, width, height});
56+
}
57+
}
58+
});
59+
60+
if (shouldUseSrcForDefaultScale && src != null) {
61+
sourceList.push({
62+
headers: headers,
63+
scale: 1,
64+
uri: src,
65+
width,
66+
height,
67+
});
68+
}
69+
if (sourceList.length === 0) {
70+
console.warn('The provided value for srcSet is not valid.');
71+
}
72+
73+
sources = sourceList;
74+
} else if (src != null) {
75+
sources = [{uri: src, headers: headers, width, height}];
76+
} else {
77+
sources = source;
78+
}
79+
return sources;
80+
}

0 commit comments

Comments
 (0)