Closed Bug 2057205 Opened 2 months ago Closed 1 month ago

A few radial gradients randomly duplicate in firefox.

Categories

(Core :: CSS Parsing and Computation, defect)

Firefox 153
defect

Tracking

()

RESOLVED FIXED
155 Branch
Tracking Status
firefox-esr140 --- unaffected
firefox-esr153 --- fixed
firefox153 --- wontfix
firefox154 --- fixed
firefox155 --- fixed

People

(Reporter: goxid30099, Assigned: nical)

References

(Regression)

Details

(Keywords: nightly-community, regression)

Attachments

(5 files, 1 obsolete file)

User Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:153.0) Gecko/20100101 Firefox/153.0
Firefox for Android

Steps to reproduce:

Hello.

I am a frontend dev and I was trying to make a CSS gradient art some time ago when I came across this bug where one of my many CSS gradients started to produce a / a few slightly shifted "copies" of the original one.

This is a code sample that I refactored from my original project, which captures the bug clearly:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div class="container"></div>


    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }


        body {
            height: 100svh;
        }


        .container {
            width: min(500px, 100vw);
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            aspect-ratio: 1/1;
            background-repeat: no-repeat;
            background-image: radial-gradient(ellipse 100% 94.33% at 50% 0%, transparent 40%, red 0 42%, transparent 0);
            background-size: 30.5% 30.5%;
            background-position: 54.1% 100.75%;
        }


    </style>
</body>
</html>

Steps to reproduce

Locally

  1. Copy this code and save it inside an index.html file.
  2. Launch it using Firefox.
  3. Open Browser DevTools by pressing F12 / Ctrl + Shift + I (on Windows) or going into Application settings > More Tools > Web Developer Tools.
  4. Make sure your DevTools is Docked to the right by going to ••• > Dock to Right.
  5. Resize Devtool tool to make window width between 0 to 500px.

Online

  1. Go to https://jsfiddle.net/8dq1feu5/ on Firefox.
  2. Try to resize the preview window to be between 0 to 500px.

Actual results:

The following html code produces artifacts at some smaller window widths (<500px, see code).

This happens on both my 1920x1080p Windows 11 laptop and my 1080x2460p andriod phone.

Expected results:

There should be a single gradient and no artifacts.

Attachment #9615147 - Attachment filename: Screenshot 2026-05-03 231625.png → screenshot.png

The Bugbug bot thinks this bug should belong to the 'Core::Widget: Win32' component, and is moving the bug to that component. Please correct in case you think the bot is wrong.

Component: Untriaged → Widget: Win32
Product: Firefox → Core
Component: Widget: Win32 → Layout: Images, Video, and HTML Frames

Thanks for reporting this!

I can reproduce the issue. This looks like an issue with the repeat handling, possibly because invariants about the aspect ratio or size are failing.

Status: UNCONFIRMED → NEW
Component: Layout: Images, Video, and HTML Frames → CSS Parsing and Computation
Ever confirmed: true

Set release status flags based on info from the regressing bug 2016125

:nical, since you are the author of the regressor, bug 2016125, could you take a look? Also, could you set the severity field?

For more information, please visit BugBot documentation.

optimize_radial_gradient shrinks a gradient's primitive rect to the bounding box
of the interesting part of the ellipse and pushes the removed margins into
tile_spacing. simplify_repeated_primitive then collapses that back into a single
tile, but the tile size is stored as a ratio of the primitive rect while
tile_spacing stays absolute, and the ratio is computed against the unsnapped rect
and resolved against the snapped one at prepare time. The round-trip is therefore
not exact and could yield a stretch size a fraction of a pixel smaller than the
primitive rect, making prepare_repeatable_quad believe the primitive repeats.

Two fixes:

  • compute_stretch_ratio now reports exactly 1.0 on an axis where the tile
    fuzzily covers the whole primitive rect, so a non-repeating primitive can't
    be resurrected as a repeating one with a sub-pixel stride.

  • The CPU repetition loop now clips each tile to the primitive rect as well as
    to the local clip rect. The last tile of a row or column always overhangs the
    primitive rect and used to rely on the local clip rect bounding it, which is
    not an invariant optimize_radial_gradient preserves. Without this the spurious
    repetition rendered a complete, unclipped duplicate of the gradient.

Assignee: nobody → nical.bugzilla
Status: NEW → ASSIGNED
Flags: needinfo?(nical.bugzilla)
Status: ASSIGNED → RESOLVED
Closed: 1 month ago
Resolution: --- → FIXED
Target Milestone: --- → 155 Branch

The patch landed in nightly and beta is affected, along with ESR.
:nical, is this bug important enough to require an uplift?

For more information, please visit BugBot documentation.

Flags: needinfo?(nical.bugzilla)
Flags: needinfo?(nical.bugzilla)

Manually rebased on top of the beta branch to resolve conflicts.

optimize_radial_gradient shrinks a gradient's primitive rect to the bounding box
of the interesting part of the ellipse and pushes the removed margins into
tile_spacing. simplify_repeated_primitive then collapses that back into a single
tile, but the tile size is stored as a ratio of the primitive rect while
tile_spacing stays absolute, and the ratio is computed against the unsnapped rect
and resolved against the snapped one at prepare time. The round-trip is therefore
not exact and could yield a stretch size a fraction of a pixel smaller than the
primitive rect, making prepare_repeatable_quad believe the primitive repeats.

Two fixes:

  • compute_stretch_ratio now reports exactly 1.0 on an axis where the tile
    fuzzily covers the whole primitive rect, so a non-repeating primitive can't
    be resurrected as a repeating one with a sub-pixel stride.

  • The CPU repetition loop now clips each tile to the primitive rect as well as
    to the local clip rect. The last tile of a row or column always overhangs the
    primitive rect and used to rely on the local clip rect bounding it, which is
    not an invariant optimize_radial_gradient preserves. Without this the spurious
    repetition rendered a complete, unclipped duplicate of the gradient.

Attachment #9624007 - Flags: approval-mozilla-beta?

firefox-beta Uplift Approval Request

  • User impact if declined/Reason for urgency: Visual glitches
  • Code covered by automated testing?: yes
  • Fix verified in Nightly?: yes
  • Needs manual QE testing?: no
  • Steps to reproduce for manual QE testing:
  • Risk associated with taking this patch: low
  • Explanation of risk level: Simple fixes
  • String changes made/needed?: No.
  • Is Android affected?: yes

Manually rebased on top of the beta branch to resolve conflicts.

optimize_radial_gradient shrinks a gradient's primitive rect to the bounding box
of the interesting part of the ellipse and pushes the removed margins into
tile_spacing. simplify_repeated_primitive then collapses that back into a single
tile, but the tile size is stored as a ratio of the primitive rect while
tile_spacing stays absolute, and the ratio is computed against the unsnapped rect
and resolved against the snapped one at prepare time. The round-trip is therefore
not exact and could yield a stretch size a fraction of a pixel smaller than the
primitive rect, making prepare_repeatable_quad believe the primitive repeats.

Two fixes:

  • compute_stretch_ratio now reports exactly 1.0 on an axis where the tile
    fuzzily covers the whole primitive rect, so a non-repeating primitive can't
    be resurrected as a repeating one with a sub-pixel stride.

  • The CPU repetition loop now clips each tile to the primitive rect as well as
    to the local clip rect. The last tile of a row or column always overhangs the
    primitive rect and used to rely on the local clip rect bounding it, which is
    not an invariant optimize_radial_gradient preserves. Without this the spurious
    repetition rendered a complete, unclipped duplicate of the gradient.

Original Revision: https://phabricator.services.mozilla.com/D314908

Attachment #9624012 - Flags: approval-mozilla-beta?

Manually rebased on top of the beta branch to resolve conflicts.

optimize_radial_gradient shrinks a gradient's primitive rect to the bounding box
of the interesting part of the ellipse and pushes the removed margins into
tile_spacing. simplify_repeated_primitive then collapses that back into a single
tile, but the tile size is stored as a ratio of the primitive rect while
tile_spacing stays absolute, and the ratio is computed against the unsnapped rect
and resolved against the snapped one at prepare time. The round-trip is therefore
not exact and could yield a stretch size a fraction of a pixel smaller than the
primitive rect, making prepare_repeatable_quad believe the primitive repeats.

Two fixes:

  • compute_stretch_ratio now reports exactly 1.0 on an axis where the tile
    fuzzily covers the whole primitive rect, so a non-repeating primitive can't
    be resurrected as a repeating one with a sub-pixel stride.

  • The CPU repetition loop now clips each tile to the primitive rect as well as
    to the local clip rect. The last tile of a row or column always overhangs the
    primitive rect and used to rely on the local clip rect bounding it, which is
    not an invariant optimize_radial_gradient preserves. Without this the spurious
    repetition rendered a complete, unclipped duplicate of the gradient.

Original Revision: https://phabricator.services.mozilla.com/D314908

Attachment #9624013 - Flags: approval-mozilla-esr153?

firefox-beta Uplift Approval Request

  • User impact if declined/Reason for urgency: Visual glitches.
  • Code covered by automated testing?: yes
  • Fix verified in Nightly?: yes
  • Needs manual QE testing?: no
  • Steps to reproduce for manual QE testing:
  • Risk associated with taking this patch: low
  • Explanation of risk level: Simple fix.
  • String changes made/needed?: No.
  • Is Android affected?: yes

firefox-esr153 Uplift Approval Request

  • User impact if declined/Reason for urgency: Visual glitches.
  • Code covered by automated testing?: yes
  • Fix verified in Nightly?: yes
  • Needs manual QE testing?: no
  • Steps to reproduce for manual QE testing:
  • Risk associated with taking this patch: low
  • Explanation of risk level: Simple fix.
  • String changes made/needed?: No.
  • Is Android affected?: yes
Attachment #9624007 - Attachment is obsolete: true
Attachment #9624007 - Flags: approval-mozilla-beta?
Attachment #9624012 - Flags: approval-mozilla-beta? → approval-mozilla-beta+
Flags: in-testsuite+
Attachment #9624013 - Flags: approval-mozilla-esr153? → approval-mozilla-esr153+
Attachment #9624013 - Attachment description: Bug 2057205 - (Beta version) Don't repeat a non-repeating gradient because of rounding. → Bug 2057205 - Don't repeat a non-repeating gradient because of rounding. a=RyanVM
Attachment #9624441 - Flags: approval-mozilla-esr153?
Attachment #9624441 - Flags: approval-mozilla-esr153? → approval-mozilla-esr153+
QA Whiteboard: [qa-triage-done-c155/b154]
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: