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 6 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
65 changes: 49 additions & 16 deletions Libraries/Image/Image.android.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ const BaseImage = (props: ImagePropsType, forwardedRef) => {
const loadingIndicatorSource = resolveAssetSource(
props.loadingIndicatorSource,
);
const {crossOrigin, referrerPolicy, src, srcSet} = props;
dhruvtailor7 marked this conversation as resolved.
Show resolved Hide resolved
dhruvtailor7 marked this conversation as resolved.
Show resolved Hide resolved

if (source) {
const uri = source.uri;
Expand All @@ -139,12 +140,6 @@ const BaseImage = (props: ImagePropsType, forwardedRef) => {
}
}

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 @@ -161,15 +156,53 @@ const BaseImage = (props: ImagePropsType, forwardedRef) => {
source = null;
}

let style;
let sources;
if (source?.uri != null) {
const {width, height} = source;
style = flattenStyle([{width, height}, styles.base, props.style]);
sources = [{uri: source.uri}];
let style;

if (srcSet != null) {
const sourceList = [];
const srcList = srcSet.split(', ');
srcList.forEach(imageSrc => {
const [uri, xScale] = imageSrc.split(' ');
if (xScale) {
const scale = parseInt(xScale.split('x')[0], 10);
if (scale) {
const headers: {[string]: string} = {};
if (crossOrigin === 'use-credentials') {
headers['Access-Control-Allow-Credentials'] = 'true';
}
if (referrerPolicy != null) {
headers['Referrer-Policy'] = referrerPolicy;
}
sourceList.push({headers, scale, uri});
}
}
});
if (sourceList.length === 0) {
console.warn('The provided value for srcSet is not valid.');
}
style = flattenStyle([styles.base, props.style]) || {};
console.log(sourceList);
sources = sourceList;
} else if (src != null) {
dhruvtailor7 marked this conversation as resolved.
Show resolved Hide resolved
style = flattenStyle([styles.base, props.style]) || {};
const headers: {[string]: string} = {};
if (crossOrigin === 'use-credentials') {
headers['Access-Control-Allow-Credentials'] = 'true';
}
if (referrerPolicy != null) {
headers['Referrer-Policy'] = referrerPolicy;
}
dhruvtailor7 marked this conversation as resolved.
Show resolved Hide resolved
sources = [{uri: src, headers}];
} else {
style = flattenStyle([styles.base, props.style]);
sources = source;
if (source?.uri != null) {
const {width, height} = source;
style = flattenStyle([{width, height}, styles.base, props.style]);
sources = [{uri: source.uri}];
} else {
style = flattenStyle([styles.base, props.style]);
sources = source;
}
}

const {onLoadStart, onLoad, onLoadEnd, onError} = props;
Expand All @@ -180,7 +213,7 @@ const BaseImage = (props: ImagePropsType, forwardedRef) => {
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?.headers || sources[0]?.headers,
defaultSrc: defaultSource ? defaultSource.uri : null,
loadingIndicatorSrc: loadingIndicatorSource
? loadingIndicatorSource.uri
Expand All @@ -202,13 +235,13 @@ const BaseImage = (props: ImagePropsType, forwardedRef) => {
<TextAncestor.Consumer>
{hasTextAncestor => {
if (hasTextAncestor) {
let src = Array.isArray(sources) ? sources : [sources];
let nativeSrc = Array.isArray(sources) ? sources : [sources];
return (
<TextInlineImageNativeComponent
style={style}
resizeMode={props.resizeMode}
headers={nativeProps.headers}
src={src}
src={nativeSrc}
ref={forwardedRef}
/>
);
Expand Down
62 changes: 48 additions & 14 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 type {ImageSource} from './ImageSource';

function getSize(
uri: string,
Expand Down Expand Up @@ -111,18 +112,57 @@ const BaseImage = (props: ImagePropsType, forwardedRef) => {
height: undefined,
};

let sources;
let sources: ImageSource;
let style: ImageStyleProp;
if (Array.isArray(source)) {

const {crossOrigin, referrerPolicy, src, srcSet} = props;

if (srcSet != null) {
const sourceList = [];
const srcList = srcSet.split(', ');
srcList.forEach(imageSrc => {
const [uri, xScale] = imageSrc.split(' ');
if (xScale) {
const scale = parseInt(xScale.split('x')[0], 10);
if (scale) {
const headers: {[string]: string} = {};
if (crossOrigin === 'use-credentials') {
headers['Access-Control-Allow-Credentials'] = 'true';
}
if (referrerPolicy != null) {
headers['Referrer-Policy'] = referrerPolicy;
}
sourceList.push({headers, scale, uri});
}
}
});
if (sourceList.length === 0) {
console.warn('The provided value for srcSet is not valid.');
}
style = flattenStyle([styles.base, props.style]) || {};
sources = source;
sources = sourceList;
} else if (src != null) {
style = flattenStyle([styles.base, props.style]) || {};
const headers: {[string]: string} = {};
if (crossOrigin === 'use-credentials') {
headers['Access-Control-Allow-Credentials'] = 'true';
}
if (referrerPolicy != null) {
headers['Referrer-Policy'] = referrerPolicy;
}
sources = [{uri: src, headers}];
} else {
const {width, height, uri} = source;
style = flattenStyle([{width, height}, styles.base, props.style]) || {};
sources = [source];
if (Array.isArray(source)) {
style = flattenStyle([styles.base, props.style]) || {};
sources = source;
} else {
const {width, height, uri} = source;
style = flattenStyle([{width, height}, styles.base, props.style]) || {};
sources = [source];

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

Expand All @@ -131,12 +171,6 @@ const BaseImage = (props: ImagePropsType, forwardedRef) => {
// $FlowFixMe[prop-missing]
const 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.',
Expand Down
37 changes: 36 additions & 1 deletion Libraries/Image/ImageProps.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,14 @@ export type ImageProps = {|
*/
capInsets?: ?EdgeInsetsProp,

/**
* Adds the CORS related header to the request.
* Similar to crossorigin from HTML.
*
* See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-crossorigin
*/
crossOrigin?: ?('anonymous' | 'use-credentials'),

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

/**
* A string indicating which referrer to use when fetching the resource.
* Similar to referrerpolicy from HTML.
*
* See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-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 @@ -170,7 +195,17 @@ export type ImageProps = {|
*/
testID?: ?string,

src?: empty,
/**
* A string representing the resource identifier for the image.
*/
src?: ?string,

/**
* Similar to srcset from HTML.
*
* See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-srcset
*/
srcSet?: ?string,
children?: empty,
|};

Expand Down
2 changes: 1 addition & 1 deletion Libraries/Image/ImageViewNativeComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ type Props = $ReadOnly<{

// Android native props
shouldNotifyLoadEvents?: boolean,
src?: ?ResolvedAssetSource | $ReadOnlyArray<{uri: string, ...}>,
src?: ?ResolvedAssetSource | $ReadOnlyArray<{uri: string, ...}> | ?string,
headers?: ?{[string]: string},
defaultSrc?: ?string,
loadingIndicatorSrc?: ?string,
Expand Down
10 changes: 9 additions & 1 deletion packages/rn-tester/js/examples/Image/ImageExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -635,13 +635,21 @@ exports.description =

exports.examples = [
{
title: 'Plain Network Image',
title: 'Plain Network Image with `source` prop.',
description: ('If the `source` prop `uri` property is prefixed with ' +
'"http", then it will be downloaded from the network.': string),
render: function (): React.Node {
return <Image source={fullImage} style={styles.base} />;
},
},
{
title: 'Plain Network Image with `src` prop.',
description: ('If the `src` prop is defined with ' +
'"http", then it will be downloaded from the network.': string),
render: function (): React.Node {
return <Image src={fullImage.uri} style={styles.base} />;
},
},
{
title: 'Plain Blob Image',
description: ('If the `source` prop `uri` property is an object URL, ' +
Expand Down