Skip to content

Commit fe046dc

Browse files
fix(style)(backgroundSize) - Passing unsupported backgroundSize on android causes crash (#55904)
Summary: Below snippet would lead to divide by zero crash on android. Currently, "`cover` | `contain`" keywords are not supported for background size as these only work on actual images (`url()` syntax) and not on gradients. These keywords were supposed to be removed in this [commit](34c041c) and added later in this [PR](#54994) ```jsx <View style={{ experimental_backgroundImage: "radial-gradient(circle, #f093fb, #f5576c)", experimental_backgroundSize: "cover", width: 100, height: 100 }} />; ``` ## Changelog: [ANDROID] [FIXED] - Unsupported background size value leading to crash <!-- Help reviewers and the release process by writing your own changelog entry. Pick one each for the category and type tags: For more details, see: https://reactnative.dev/contributing/changelogs-in-pull-requests Pull Request resolved: #55904 Test Plan: Tested the BackgroundImage example on RNTester. Reviewed By: christophpurrer Differential Revision: D95202400 Pulled By: NickGerleman fbshipit-source-id: 287f4710c4afc031b7f5e080f544e8dadfc007d2
1 parent 540120a commit fe046dc

7 files changed

Lines changed: 53 additions & 69 deletions

File tree

packages/react-native/Libraries/StyleSheet/StyleSheetTypes.d.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -423,13 +423,10 @@ type RadialGradientValue = {
423423

424424
export type BackgroundImageValue = LinearGradientValue | RadialGradientValue;
425425

426-
export type BackgroundSizeValue =
427-
| {
428-
x: string | number;
429-
y: string | number;
430-
}
431-
| 'cover'
432-
| 'contain';
426+
export type BackgroundSizeValue = {
427+
x: string | number;
428+
y: string | number;
429+
};
433430

434431
export type BackgroundRepeatKeyword =
435432
| 'repeat'

packages/react-native/Libraries/StyleSheet/StyleSheetTypes.js

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -779,13 +779,10 @@ type RadialGradientValue = {
779779

780780
export type BackgroundImageValue = LinearGradientValue | RadialGradientValue;
781781

782-
export type BackgroundSizeValue =
783-
| {
784-
x: string | number,
785-
y: string | number,
786-
}
787-
| 'cover'
788-
| 'contain';
782+
export type BackgroundSizeValue = {
783+
x: string | number,
784+
y: string | number,
785+
};
789786

790787
export type BackgroundRepeatKeyword =
791788
| 'repeat'

packages/react-native/Libraries/StyleSheet/__tests__/processBackgroundSize-test.js

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -34,30 +34,19 @@ describe('processBackgroundSize', () => {
3434
]);
3535
});
3636

37-
it('should handle array with cover', () => {
38-
expect(processBackgroundSize(['cover'])).toEqual(['cover']);
39-
});
40-
41-
it('should handle array with contain', () => {
42-
expect(processBackgroundSize(['contain'])).toEqual(['contain']);
43-
});
44-
45-
it('should handle array with mixed values', () => {
46-
expect(processBackgroundSize([{x: 100, y: 'auto'}, 'cover'])).toEqual([
37+
it('should handle array with multiple values', () => {
38+
expect(
39+
processBackgroundSize([
40+
{x: 100, y: 'auto'},
41+
{x: '50%', y: '50%'},
42+
]),
43+
).toEqual([
4744
{x: 100, y: 'auto'},
48-
'cover',
45+
{x: '50%', y: '50%'},
4946
]);
5047
});
5148

5249
// Test string input - single values
53-
it('should parse cover', () => {
54-
expect(processBackgroundSize('cover')).toEqual(['cover']);
55-
});
56-
57-
it('should parse contain', () => {
58-
expect(processBackgroundSize('contain')).toEqual(['contain']);
59-
});
60-
6150
it('should parse single length value', () => {
6251
expect(processBackgroundSize('100px')).toEqual([{x: 100, y: 'auto'}]);
6352
});
@@ -111,9 +100,9 @@ describe('processBackgroundSize', () => {
111100

112101
// Test multiple background sizes (comma-separated)
113102
it('should parse multiple background sizes', () => {
114-
expect(processBackgroundSize('100px 200px, cover')).toEqual([
103+
expect(processBackgroundSize('100px 200px, 50%')).toEqual([
115104
{x: 100, y: 200},
116-
'cover',
105+
{x: '50%', y: 'auto'},
117106
]);
118107
});
119108

@@ -125,16 +114,16 @@ describe('processBackgroundSize', () => {
125114
});
126115

127116
it('should parse multiple background sizes with newlines', () => {
128-
expect(processBackgroundSize('100px 200px,\nCover')).toEqual([
117+
expect(processBackgroundSize('100px 200px,\n50%')).toEqual([
129118
{x: 100, y: 200},
130-
'cover',
119+
{x: '50%', y: 'auto'},
131120
]);
132121
});
133122

134123
it('should parse multiple background sizes with extra whitespace', () => {
135-
expect(processBackgroundSize(' 100px 200px , cover ')).toEqual([
124+
expect(processBackgroundSize(' 100px 200px , 50% ')).toEqual([
136125
{x: 100, y: 200},
137-
'cover',
126+
{x: '50%', y: 'auto'},
138127
]);
139128
});
140129

@@ -172,11 +161,11 @@ describe('processBackgroundSize', () => {
172161
});
173162

174163
it('should handle mixed case keywords', () => {
175-
expect(processBackgroundSize('COVER')).toEqual(['cover']);
164+
expect(processBackgroundSize('AUTO')).toEqual([{x: 'auto', y: 'auto'}]);
176165
});
177166

178167
it('should handle mixed case keywords with spaces', () => {
179-
expect(processBackgroundSize(' Cover ')).toEqual(['cover']);
168+
expect(processBackgroundSize(' Auto ')).toEqual([{x: 'auto', y: 'auto'}]);
180169
});
181170

182171
it('should handle invalid mixed values', () => {
@@ -214,18 +203,26 @@ describe('processBackgroundSize', () => {
214203

215204
// Test complex scenarios
216205
it('should handle complex multiple background scenario', () => {
217-
expect(
218-
processBackgroundSize('100px 200px, 50% auto, cover, contain'),
219-
).toEqual([{x: 100, y: 200}, {x: '50%', y: 'auto'}, 'cover', 'contain']);
206+
expect(processBackgroundSize('100px 200px, 50% auto, auto')).toEqual([
207+
{x: 100, y: 200},
208+
{x: '50%', y: 'auto'},
209+
{x: 'auto', y: 'auto'},
210+
]);
220211
});
221212

222213
it('should handle all valid single value combinations', () => {
223-
expect(processBackgroundSize('100px, 50%, auto, cover, contain')).toEqual([
214+
expect(processBackgroundSize('100px, 50%, auto')).toEqual([
224215
{x: 100, y: 'auto'},
225216
{x: '50%', y: 'auto'},
226217
{x: 'auto', y: 'auto'},
227-
'cover',
228-
'contain',
229218
]);
230219
});
220+
221+
it('should return empty array for unsupported cover keyword', () => {
222+
expect(processBackgroundSize('cover')).toEqual([]);
223+
});
224+
225+
it('should return empty array for unsupported contain keyword', () => {
226+
expect(processBackgroundSize('contain')).toEqual([]);
227+
});
231228
});

packages/react-native/Libraries/StyleSheet/processBackgroundSize.js

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -57,19 +57,14 @@ function parseBackgroundSizeCSSString(
5757
return [];
5858
}
5959
} else if (parts.length === 1) {
60-
const part = parts[0].toLowerCase();
61-
if (part === 'cover' || part === 'contain') {
62-
result.push(part);
60+
const x = getValidLengthPercentageSizeOrNull(parts[0].toLowerCase());
61+
if (x != null) {
62+
result.push({
63+
x,
64+
y: 'auto',
65+
});
6366
} else {
64-
const x = getValidLengthPercentageSizeOrNull(parts[0].toLowerCase());
65-
if (x != null) {
66-
result.push({
67-
x,
68-
y: 'auto',
69-
});
70-
} else {
71-
return [];
72-
}
67+
return [];
7368
}
7469
}
7570
}

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/BackgroundImageDrawable.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,11 @@ internal class BackgroundImageDrawable(
134134
// So we draw in reverse (last drawn in canvas appears closest)
135135
for (index in layers.indices.reversed()) {
136136
val backgroundImageLayer = layers[index]
137-
val size = backgroundSize?.let { it.getOrNull(index % it.size) }
138-
val repeat = backgroundRepeat?.let { it.getOrNull(index % it.size) }
139-
val position = backgroundPosition?.let { it.getOrNull(index % it.size) }
137+
val size = backgroundSize?.takeIf { it.isNotEmpty() }?.let { it.getOrNull(index % it.size) }
138+
val repeat =
139+
backgroundRepeat?.takeIf { it.isNotEmpty() }?.let { it.getOrNull(index % it.size) }
140+
val position =
141+
backgroundPosition?.takeIf { it.isNotEmpty() }?.let { it.getOrNull(index % it.size) }
140142

141143
// 2. Calculate the size of a single tile.
142144
val (tileWidth, tileHeight) =

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ public open class ReactViewManager : ReactClippingViewManager<ReactViewGroup>()
167167
backgroundSizes.add(parsedBackgroundSize)
168168
}
169169
}
170-
BackgroundStyleApplicator.setBackgroundSize(view, backgroundSizes)
170+
BackgroundStyleApplicator.setBackgroundSize(view, backgroundSizes.ifEmpty { null })
171171
}
172172
} else {
173173
BackgroundStyleApplicator.setBackgroundSize(view, null)
@@ -186,7 +186,7 @@ public open class ReactViewManager : ReactClippingViewManager<ReactViewGroup>()
186186
backgroundPositions.add(parsedBackgroundPosition)
187187
}
188188
}
189-
BackgroundStyleApplicator.setBackgroundPosition(view, backgroundPositions)
189+
BackgroundStyleApplicator.setBackgroundPosition(view, backgroundPositions.ifEmpty { null })
190190
} else {
191191
BackgroundStyleApplicator.setBackgroundPosition(view, null)
192192
}
@@ -205,7 +205,7 @@ public open class ReactViewManager : ReactClippingViewManager<ReactViewGroup>()
205205
backgroundRepeats.add(parsedBackgroundRepeat)
206206
}
207207
}
208-
BackgroundStyleApplicator.setBackgroundRepeat(view, backgroundRepeats)
208+
BackgroundStyleApplicator.setBackgroundRepeat(view, backgroundRepeats.ifEmpty { null })
209209
} else {
210210
BackgroundStyleApplicator.setBackgroundRepeat(view, null)
211211
}

packages/rn-tester/js/examples/BackgroundImage/BackgroundImageExample.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,6 @@ exports.examples = [
361361
style={{
362362
experimental_backgroundImage:
363363
'linear-gradient(45deg, #667eea, #764ba2)',
364-
experimental_backgroundSize: 'cover',
365364
borderRadius: 20,
366365
}}
367366
testID="background-image-borders-1"
@@ -373,7 +372,6 @@ exports.examples = [
373372
style={{
374373
experimental_backgroundImage:
375374
'radial-gradient(circle, #f093fb, #f5576c)',
376-
experimental_backgroundSize: 'cover',
377375
borderWidth: 10,
378376
borderColor: 'red',
379377
}}
@@ -386,7 +384,6 @@ exports.examples = [
386384
style={{
387385
experimental_backgroundImage:
388386
'radial-gradient(circle, #f093fb, #f5576c)',
389-
experimental_backgroundSize: 'cover',
390387
borderTopLeftRadius: 10,
391388
borderTopRightRadius: 20,
392389
borderBottomLeftRadius: 30,
@@ -401,7 +398,6 @@ exports.examples = [
401398
style={{
402399
experimental_backgroundImage:
403400
'radial-gradient(circle, #f093fb, #f5576c)',
404-
experimental_backgroundSize: 'cover',
405401
borderTopWidth: 10,
406402
borderTopColor: 'red',
407403
borderBottomWidth: 20,

0 commit comments

Comments
 (0)