Fix radial-gradient position being dropped after an explicit size - #57873
Closed
Titozzz wants to merge 1 commit into
Closed
Fix radial-gradient position being dropped after an explicit size#57873Titozzz wants to merge 1 commit into
Titozzz wants to merge 1 commit into
Conversation
In processBackgroundImage, the explicit-size branch shifts the next token
to look for a second size value and discards it when it is not a length or
percentage. When that token is 'at', the whole position clause is lost:
the position defaults back to center and the position values are then
re-parsed as a new size, silently overriding the declared one.
radial-gradient(circle 100px at 25% 75%, red, blue) previously parsed as
size {x: '25%', y: '75%'} with position {top: '50%', left: '50%'};
it now parses as size {x: 100, y: 100} with position {left: '25%', top: '75%'}.
The existing test for this syntax only used 'at center', which is
indistinguishable from the default position, so the bug was invisible.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Titozzz
marked this pull request as ready for review
August 10, 2026 12:29
Titozzz
pushed a commit
to Titozzz/react-native-reanimated
that referenced
this pull request
Aug 10, 2026
Ports the two upstream react-native processBackgroundImage changes (react/react-native#57873 and react/react-native#57874) into the backgroundImage processor to keep both parsers in sync: - #57873 (position dropped after an explicit size) was already fixed here; align the code comment with upstream and use a px-sized circle in the regression test - #57874: reject a percentage radius for circle radial gradients (explicit 'circle 50%' and the inferred circle from a single '50%' size). Per the CSS spec a circle radius must be a <length>; percentages remain valid for ellipse sizes Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011p2PjxNunbHriH5853qssK
cipolleschi
approved these changes
Aug 10, 2026
|
@cipolleschi has imported this pull request. If you are a Meta employee, you can view this in D115426012. |
meta-codesync Bot
pushed a commit
that referenced
this pull request
Aug 10, 2026
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
|
@cipolleschi merged this pull request in 66f27eb. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary:
processBackgroundImagesilently drops theat <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 forradial-gradient(circle 100px at 25% 75%, red, blue)theattoken is swallowed, the loop then treats25%and75%as a new<size>, and the gradient parses as: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 like100px ellipse, where the shape keyword was previously swallowed too). The structured C++ parser inreact/renderer/css/CSSBackgroundImage.his 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 sizeTest Plan:
Added two Fantom tests in
processBackgroundImage-itest.jscoveringcircle 100px at 25% 75%(single explicit size + position) and50px 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 centercase covered by the existing test):circle 100px at 25% 75%{25%, 75%}, position center{100, 100}, position{left 25%, top 75%}50px 100px at left bottomcircle 100px at center🤖 Generated with Claude Code