Skip to content

Commit 66f27eb

Browse files
Titozzzmeta-codesync[bot]
authored andcommitted
Fix radial-gradient position being dropped after an explicit size (#57873)
Summary: `processBackgroundImage` silently drops the `at <position>` clause of a radial gradient whenever it follows an explicit size, and then re-parses the position values as a new size that overrides the declared one. In the explicit-size branch of `parseRadialGradientCSSString`, the parser shifts the next token to look for a second size value. When that token is not a length/percentage it is discarded instead of being put back — so for `radial-gradient(circle 100px at 25% 75%, red, blue)` the `at` token is swallowed, the loop then treats `25%` and `75%` as a new `<size>`, and the gradient parses as: ```js // before {shape: 'circle', size: {x: '25%', y: '75%'}, position: {top: '50%', left: '50%'}} // after (matches web) {shape: 'circle', size: {x: 100, y: 100}, position: {left: '25%', top: '75%'}} ``` The bug was invisible in tests because the only existing test for this syntax uses `at center`, which is indistinguishable from the default position. The fix is to unshift the peeked token back so the main loop processes it (this also fixes `<size> <shape>` orderings like `100px ellipse`, where the shape keyword was previously swallowed too). The structured C++ parser in `react/renderer/css/CSSBackgroundImage.h` is not affected — this is specific to the JS tokenizer. ## Changelog: [GENERAL] [FIXED] - Fix radial-gradient `at <position>` being ignored (and corrupting the size) when it follows an explicit size Pull Request resolved: #57873 Test Plan: Added two Fantom tests in `processBackgroundImage-itest.js` covering `circle 100px at 25% 75%` (single explicit size + position) and `50px 100px at left bottom` (two sizes + keyword position). Verified the parse output for a matrix of radial gradient strings against Chrome's accepted/computed values (all match after the fix, including the untouched `circle 100px at center` case covered by the existing test): | input | before | after | | --- | --- | --- | | `circle 100px at 25% 75%` | size `{25%, 75%}`, position center | size `{100, 100}`, position `{left 25%, top 75%}` | | `50px 100px at left bottom` | unaffected | unaffected | | `circle 100px at center` | ✓ (masked the bug) | ✓ | 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed By: fabriziocucci Differential Revision: D115426012 Pulled By: cipolleschi fbshipit-source-id: 9964c4196f1101655fe02fdbe219bc8f599f3e74
1 parent 727e87e commit 66f27eb

2 files changed

Lines changed: 21 additions & 0 deletions

File tree

packages/react-native/Libraries/StyleSheet/__tests__/processBackgroundImage-itest.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -999,6 +999,22 @@ describe('processBackgroundImage', () => {
999999
expect(result[0].type).toEqual('radial-gradient');
10001000
});
10011001

1002+
it('should handle radial gradient with explicit size and position', () => {
1003+
const input = 'radial-gradient(circle 100px at 25% 75%, red, blue)';
1004+
const result = processBackgroundImage(input);
1005+
expect(result[0].shape).toEqual('circle');
1006+
expect(result[0].size).toEqual({x: 100, y: 100});
1007+
expect(result[0].position).toEqual({left: '25%', top: '75%'});
1008+
});
1009+
1010+
it('should handle radial gradient with two explicit sizes and position', () => {
1011+
const input = 'radial-gradient(50px 100px at left bottom, red, blue)';
1012+
const result = processBackgroundImage(input);
1013+
expect(result[0].shape).toEqual('ellipse');
1014+
expect(result[0].size).toEqual({x: 50, y: 100});
1015+
expect(result[0].position).toEqual({left: '0%', top: '100%'});
1016+
});
1017+
10021018
// 1. position syntax: [ left | center | right | top | bottom | <length-percentage> ]
10031019
it('should handle radial gradient position length syntax', () => {
10041020
const input = 'radial-gradient(circle at 20px, red, blue)';

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,11 @@ function parseRadialGradientCSSString(
348348
size = {x: sizeX, y: sizeY};
349349
} else {
350350
hasExplicitSingleSize = true;
351+
// The token after the size is not a second size value (e.g. 'at' or a
352+
// shape keyword). Put it back so the loop can process it, otherwise
353+
// the position would be silently dropped and its values re-parsed as
354+
// a new size.
355+
firstPartTokens.unshift(token);
351356
}
352357
} else if (tokenTrimmed === 'at') {
353358
let top: string | number;

0 commit comments

Comments
 (0)