LibWeb/CSS: Fix linear-gradient single color-stop usage

The Web::CSS::Parser's GradientParsing ignores color-stops if
it is only a single one. This change allows to have color-stops
with double positions against a single color.

Further, also allows for `linear-gradient(black)` and similar
other gradient functions
This commit is contained in:
Mehran Kamal 2025-01-30 15:15:33 +05:00 committed by Sam Atkins
parent 4fa372564d
commit cfe6702767
Notes: github-actions[bot] 2025-02-03 17:25:19 +00:00
12 changed files with 459 additions and 7 deletions

View file

@ -75,9 +75,6 @@ Optional<Vector<TElement>> Parser::parse_color_stop_list(TokenStream<ComponentVa
if (parse_color_stop_list_element(first_element) != ElementType::ColorStop)
return {};
if (!tokens.has_next_token())
return {};
Vector<TElement> color_stops { first_element };
while (tokens.has_next_token()) {
TElement list_element {};

View file

@ -19,7 +19,7 @@ class ConicGradientStyleValue final : public AbstractImageStyleValue {
public:
static ValueComparingNonnullRefPtr<ConicGradientStyleValue> create(Angle from_angle, ValueComparingNonnullRefPtr<PositionStyleValue> position, Vector<AngularColorStopListElement> color_stop_list, GradientRepeating repeating)
{
VERIFY(color_stop_list.size() >= 2);
VERIFY(!color_stop_list.is_empty());
return adopt_ref(*new (nothrow) ConicGradientStyleValue(from_angle, move(position), move(color_stop_list), repeating));
}

View file

@ -40,7 +40,7 @@ public:
static ValueComparingNonnullRefPtr<LinearGradientStyleValue> create(GradientDirection direction, Vector<LinearColorStopListElement> color_stop_list, GradientType type, GradientRepeating repeating)
{
VERIFY(color_stop_list.size() >= 2);
VERIFY(!color_stop_list.is_empty());
return adopt_ref(*new (nothrow) LinearGradientStyleValue(direction, move(color_stop_list), type, repeating));
}

View file

@ -45,7 +45,7 @@ public:
static ValueComparingNonnullRefPtr<RadialGradientStyleValue> create(EndingShape ending_shape, Size size, ValueComparingNonnullRefPtr<PositionStyleValue> position, Vector<LinearColorStopListElement> color_stop_list, GradientRepeating repeating)
{
VERIFY(color_stop_list.size() >= 2);
VERIFY(!color_stop_list.is_empty());
return adopt_ref(*new (nothrow) RadialGradientStyleValue(ending_shape, size, move(position), move(color_stop_list), repeating));
}

View file

@ -17,7 +17,7 @@ namespace Web::Painting {
static ColorStopData resolve_color_stop_positions(Layout::NodeWithStyleAndBoxModelMetrics const& node, auto const& color_stop_list, auto resolve_position_to_float, bool repeating)
{
VERIFY(color_stop_list.size() >= 2);
VERIFY(!color_stop_list.is_empty());
ColorStopList resolved_color_stops;
auto color_stop_length = [&](auto& stop) {

View file

@ -0,0 +1,39 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Gradient with a single stop</title>
<link rel="author" title="Jonathan Kew" href="mailto:jkew@mozilla.com">
<link rel="help" href="https://drafts.csswg.org/css-images-3/#color-stop-syntax">
<link rel="help" href="https://drafts.csswg.org/css-images-3/#coloring-gradient-line">
<meta name="assert" content="Tests that gradient with a single color stop renders the expected solid color">
<meta name="fuzzy" content="maxDifference=0-1; totalPixels=0-4500">
<link rel="match" href="../../../../../expected/wpt-import//css/reference/ref-filled-green-100px-square-only.html">
<style>
body {
background: #fff;
}
div {
width: 100px;
height: 100px;
position: absolute;
}
#fail {
background: red;
}
#test {
background-image: linear-gradient(green);
}
</style>
</head>
<body>
<p>Test passes if there is a filled green square.</p>
<div id="fail"></div>
<div id="test"></div>
</body>
</html>

View file

@ -0,0 +1,39 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Gradient with a single stop</title>
<link rel="author" title="Jonathan Kew" href="mailto:jkew@mozilla.com">
<link rel="help" href="https://drafts.csswg.org/css-images-3/#color-stop-syntax">
<link rel="help" href="https://drafts.csswg.org/css-images-3/#coloring-gradient-line">
<meta name="assert" content="Tests that gradient with a single color stop renders the expected solid color">
<meta name="fuzzy" content="maxDifference=0-1; totalPixels=0-4500">
<link rel="match" href="../../../../../expected/wpt-import//css/reference/ref-filled-green-100px-square-only.html">
<style>
body {
background: #fff;
}
div {
width: 100px;
height: 100px;
position: absolute;
}
#fail {
background: red;
}
#test {
background-image: linear-gradient(to right, green 90%);
}
</style>
</head>
<body>
<p>Test passes if there is a filled green square.</p>
<div id="fail"></div>
<div id="test"></div>
</body>
</html>

View file

@ -0,0 +1,39 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Gradient with a single stop</title>
<link rel="author" title="Jonathan Kew" href="mailto:jkew@mozilla.com">
<link rel="help" href="https://drafts.csswg.org/css-images-3/#color-stop-syntax">
<link rel="help" href="https://drafts.csswg.org/css-images-3/#coloring-gradient-line">
<meta name="assert" content="Tests that gradient with a single color stop renders the expected solid color">
<meta name="fuzzy" content="maxDifference=0-1; totalPixels=0-4500">
<link rel="match" href="../../../../../expected/wpt-import//css/reference/ref-filled-green-100px-square-only.html">
<style>
body {
background: #fff;
}
div {
width: 100px;
height: 100px;
position: absolute;
}
#fail {
background: red;
}
#test {
background-image: radial-gradient(green);
}
</style>
</head>
<body>
<p>Test passes if there is a filled green square.</p>
<div id="fail"></div>
<div id="test"></div>
</body>
</html>

View file

@ -0,0 +1,39 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Gradient with a single stop</title>
<link rel="author" title="Jonathan Kew" href="mailto:jkew@mozilla.com">
<link rel="help" href="https://drafts.csswg.org/css-images-4/#color-stop-syntax">
<link rel="help" href="https://drafts.csswg.org/css-images-4/#coloring-gradient-line">
<meta name="assert" content="Tests that gradient with a single color stop renders the expected solid color">
<meta name="fuzzy" content="maxDifference=0-1; totalPixels=0-10000">
<link rel="match" href="../../../../../expected/wpt-import//css/reference/ref-filled-green-100px-square-only.html">
<style>
body {
background: #fff;
}
div {
width: 100px;
height: 100px;
position: absolute;
}
#fail {
background: red;
}
#test {
background-image: conic-gradient(green);
}
</style>
</head>
<body>
<p>Test passes if there is a filled green square.</p>
<div id="fail"></div>
<div id="test"></div>
</body>
</html>

View file

@ -0,0 +1,39 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Gradient with a single stop</title>
<link rel="author" title="Jonathan Kew" href="mailto:jkew@mozilla.com">
<link rel="help" href="https://drafts.csswg.org/css-images-4/#color-stop-syntax">
<link rel="help" href="https://drafts.csswg.org/css-images-4/#coloring-gradient-line">
<meta name="assert" content="Tests that gradient with a single color stop renders the expected solid color">
<meta name="fuzzy" content="maxDifference=0-1; totalPixels=0-10000">
<link rel="match" href="../../../../../expected/wpt-import//css/reference/ref-filled-green-100px-square-only.html">
<style>
body {
background: #fff;
}
div {
width: 100px;
height: 100px;
position: absolute;
}
#fail {
background: red;
}
#test {
background-image: conic-gradient(from 0deg at center, green);
}
</style>
</head>
<body>
<p>Test passes if there is a filled green square.</p>
<div id="fail"></div>
<div id="test"></div>
</body>
</html>

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,92 @@
<!DOCTYPE html>
<html>
<head>
<title>Color-stops parsing</title>
<link rel="author" title="Florin Malita" href="mailto:fmalita@chromium.org">
<link rel="help" href="http://www.w3.org/TR/css-images-4/#color-stop-syntax">
<meta name="assert" content="General color stop parsing (applicable to all gradients) follows CSS Images 4 rules.">
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
</head>
<body>
<script>
var tests = [
// invalid stops
{ stops: "" , parse: false },
{ stops: "black, 25%" , parse: false },
{ stops: "black, invalid" , parse: false },
{ stops: "black, , white" , parse: false },
{ stops: "black, white, 75%" , parse: false },
{ stops: "black, 25% 50%, white" , parse: false },
{ stops: "black, 25%, 50%, white" , parse: false },
{ stops: "black 10% 25% 50%, white", parse: false },
{ stops: ",black, white" , parse: false },
{ stops: "0%, black, white" , parse: false },
// basic stops
{ stops: "black" , parse: true },
{ stops: "black 0%" , parse: true },
{ stops: "black, white" , parse: true },
{ stops: "black 0, white" , parse: true },
{ stops: "black 0%, white" , parse: true },
{ stops: "black 0%, white 100%" , parse: true },
{ stops: "black, green, white" , parse: true },
{ stops: "black 0%, green 50%, white 100%" , parse: true },
{ stops: "black 50%, green 10%, white 100%", parse: true },
// interpolation hints
{ stops: "black, 25%, white" , parse: true },
{ stops: "black 0%, 25%, white 100%" , parse: true },
{ stops: "black 0%, 15%, green 50%, 60%, white 100%", parse: true },
// dual-positioning
{ stops: "black 0% 50%, white" , parse: true },
{ stops: "black 0% 50%, white 50% 100%" , parse: true },
{ stops: "black 0% 50%, green 25% 75%, white 50% 100%", parse: true },
// kitchen sink
{ stops: "black 0% calc(100% / 5), 25%, green 30% 60%, calc(100% * 3 / 4), white calc(100% - 20%) 100%", parse: true },
// lots of stops
{
stops: (() => {
let longGradient = "";
for (let x = 0; x < 500; x++) {
longGradient += `white ${x/500}%, ${(2 * x + 1) / 1000}%, `;
}
longGradient += "black";
return longGradient;
})(),
parse: true
}
];
function check_gradient(gradient, stops, shouldParse) {
var div = document.createElement('div');
div.setAttribute("style", "background-image: " + gradient + "(" + stops + ")");
var inline_style = div.style.getPropertyValue("background-image");
assert_equals(inline_style.startsWith(gradient), shouldParse);
document.body.appendChild(div);
var computed_style = getComputedStyle(div).getPropertyValue("background-image");
assert_equals(computed_style.startsWith(gradient), shouldParse);
div.remove();
}
[ "linear-gradient",
"repeating-linear-gradient",
"radial-gradient",
"repeating-radial-gradient",
"conic-gradient",
"repeating-conic-gradient"
].forEach(function(gradient) {
tests.forEach(function(tst) {
test(function() {
check_gradient(gradient, tst.stops, tst.parse);
}, gradient + "(" + tst.stops + ") " + (tst.parse ? "[ parsable ]" : "[ unparsable ]"));
});
});
</script>
</body>
</html>