Skip to content

Commit 5821fca

Browse files
Titozzzmeta-codesync[bot]
authored andcommitted
Reject percentage radius for circle radial gradients (#57874)
Summary: > [!NOTE] > Stacked on #57873 (its commit is included here). Without that fix, `circle <length> at <position>` strings would mis-parse into percentage sizes and be wrongly rejected by this validation. Per [css-images-3 `<radial-size>`](https://www.w3.org/TR/css-images-3/#valdef-radial-size-length-0), a circle's explicit radius must be a `<length>` — percentages are only valid for ellipses. Browsers reject the whole declaration for values like `radial-gradient(circle 50%, red, blue)` (computed `background-image: none`), while React Native accepted them and rendered an arbitrary interpretation (`max` of the value resolved against width and against height). The same style string therefore silently diverged between native and web, contradicting the "Same as web" validation policy this parser already follows for other invalid values. `processBackgroundImage` now returns no gradient when a circle — explicit (`circle 50%`) or inferred from a single size (`radial-gradient(50%, ...)`, which browsers also reject) — has a percentage size. Ellipses with percentage sizes (`50% 20%`) are unaffected. Note: the structured C++ parser in `react/renderer/css/CSSBackgroundImage.h` has the same leniency (`CSSRadialGradientExplicitSize` accepts `<length-percentage>` for both axes regardless of shape) and could get the same validation as a follow-up. ## Changelog: [GENERAL] [FIXED] - Reject percentage radii for circle radial gradients, matching web behavior Pull Request resolved: #57874 Test Plan: Added three Fantom tests in `processBackgroundImage-itest.js`: `circle 50%` rejected, inferred-circle `50%` rejected, ellipse `50% 20%` still accepted. Verified the accept/reject matrix against Chrome (`getComputedStyle(...).backgroundImage === 'none'` for rejected values): | input | Chrome | RN before | RN after | | --- | --- | --- | --- | | `radial-gradient(circle 50%, red, blue)` | rejected | accepted | rejected | | `radial-gradient(50%, red, blue)` | rejected | accepted | rejected | | `radial-gradient(50% 20%, red, blue)` | accepted | accepted | accepted | | `radial-gradient(circle 100px, red, blue)` | accepted | accepted | accepted | 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed By: fabriziocucci Differential Revision: D115426055 Pulled By: cipolleschi fbshipit-source-id: 5ba6e2f1d6e36bd5e64d7ef4f917918514f87d81
1 parent 66f27eb commit 5821fca

2 files changed

Lines changed: 26 additions & 0 deletions

File tree

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -986,6 +986,23 @@ describe('processBackgroundImage', () => {
986986
expect(result[0].shape).toEqual('ellipse');
987987
});
988988

989+
it('should reject a circle with a percentage radius', () => {
990+
const input = 'radial-gradient(circle 50%, red, blue)';
991+
expect(processBackgroundImage(input)).toEqual([]);
992+
});
993+
994+
it('should reject an inferred circle with a percentage radius', () => {
995+
const input = 'radial-gradient(50%, red, blue)';
996+
expect(processBackgroundImage(input)).toEqual([]);
997+
});
998+
999+
it('should allow percentage sizes for ellipses', () => {
1000+
const input = 'radial-gradient(50% 20%, red, blue)';
1001+
const result = processBackgroundImage(input);
1002+
expect(result[0].shape).toEqual('ellipse');
1003+
expect(result[0].size).toEqual({x: '50%', y: '20%'});
1004+
});
1005+
9891006
it('should handle radial gradient with explicit shape with size', () => {
9901007
const input = 'radial-gradient(circle 100px at center, red, blue 80%)';
9911008
const result = processBackgroundImage(input);

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,15 @@ function parseRadialGradientCSSString(
567567
// If a single size is explicitly set and the shape is an ellipse, return null and do not apply any gradient. Same as web.
568568
return null;
569569
}
570+
571+
if (
572+
shape === 'circle' &&
573+
typeof size === 'object' &&
574+
(typeof size.x === 'string' || typeof size.y === 'string')
575+
) {
576+
// A circle radius must be a <length>. Percentages are only valid for ellipses, so return null and do not apply any gradient. Same as web.
577+
return null;
578+
}
570579
}
571580

572581
const colorStops = parseColorStopsCSSString(remainingParts);

0 commit comments

Comments
 (0)