LibWeb/CSS: Fix reference bug when canonicalizing linear easing stops

+2 WPT tests
This commit is contained in:
Veeti Paananen 2025-06-18 01:08:35 +03:00 committed by Jelle Raaijmakers
commit 21b531598d
Notes: github-actions[bot] 2025-06-18 06:54:17 +00:00
4 changed files with 176 additions and 2 deletions

View file

@ -81,7 +81,7 @@ EasingStyleValue::Linear::Linear(Vector<EasingStyleValue::Linear::Stop> stops)
// the input progress value of any preceding control point,
// set its input progress value to the largest input progress value of any preceding control point.
double largest_input = 0;
for (auto stop : stops) {
for (auto& stop : stops) {
if (stop.input.has_value()) {
if (stop.input.value() < largest_input) {
stop.input = largest_input;
@ -97,7 +97,7 @@ EasingStyleValue::Linear::Linear(Vector<EasingStyleValue::Linear::Stop> stops)
// between the preceding and following control points with input progress values.
Optional<size_t> run_start_idx;
for (size_t idx = 0; idx < stops.size(); idx++) {
auto stop = stops[idx];
auto& stop = stops[idx];
if (stop.input.has_value() && run_start_idx.has_value()) {
// Note: this stop is immediately after a run
// set inputs of [start, idx-1] stops to be evenly spaced between start-1 and idx

View file

@ -0,0 +1,10 @@
Harness status: OK
Found 5 tests
5 Pass
Pass linear function easing with output greater than 1
Pass linear function easing with output less than 1
Pass linear function easing, steps equivalent
Pass linear function easing, input value being unspecified in the first entry implies zero
Pass linear function easing, input value being unspecified in the last entry implies max input value

View file

@ -0,0 +1,99 @@
<!DOCTYPE html>
<meta charset=utf-8>
<meta name="assert" content="This test checks the output of linear timing functions" />
<title>Tests for the output of linear timing functions</title>
<link rel="help" href="https://drafts.csswg.org/css-easing-2/#the-linear-easing-function">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="testcommon.js"></script>
<body>
<div id="log"></div>
<script>
'use strict';
function assert_style_left_at(animation, time, expected_y) {
animation.currentTime = time;
assert_approx_equals(pxToNum(getComputedStyle(animation.effect.target).left),
expected_y * 100,
0.01,
'The left of the animation should be approximately ' +
expected_y * 100 + ' at ' + time + 'ms');
}
function assert_animations_equal_at(actual_animation, expected_animation, time) {
actual_animation.currentTime = time;
var actual_left = pxToNum(getComputedStyle(actual_animation.effect.target).left);
expected_animation.currentTime = time;
var expected_left = pxToNum(getComputedStyle(expected_animation.effect.target).left);
assert_approx_equals(actual_left,
expected_left,
0.01,
'The left of the animation should be approximately ' +
expected_left + ' at ' + time + 'ms');
}
function create_animated_div(t, easing_function) {
var target = createDiv(t);
target.style.position = 'absolute';
return target.animate(
[ { left: '0px' },
{ left: '100px' } ],
{ duration: 1000,
fill: 'forwards',
easing: easing_function });
}
test(function(t) {
var anim = create_animated_div(t, 'linear(0, 1.5, 1)');
assert_style_left_at(anim, 0, 0.0);
assert_style_left_at(anim, 250, 0.75);
assert_style_left_at(anim, 500, 1.5);
assert_style_left_at(anim, 750, 1.25);
assert_style_left_at(anim, 1000, 1.00);
}, 'linear function easing with output greater than 1');
test(function(t) {
var anim = create_animated_div(t, 'linear(1, -0.5, 0)');
assert_style_left_at(anim, 0, 1.0);
assert_style_left_at(anim, 250, 0.25);
assert_style_left_at(anim, 500, -0.5);
assert_style_left_at(anim, 750, -0.25);
assert_style_left_at(anim, 1000, 0.00);
}, 'linear function easing with output less than 1');
test(function(t) {
var anim = create_animated_div(t, 'linear(0.2 0% 20%, 0.4 20% 40%, 0.6 40% 60%, 0.8 60% 80%, 1.0 80% 100%)');
var equiv = create_animated_div(t, 'steps(5, jump-start)');
assert_animations_equal_at(anim, equiv, 0);
assert_animations_equal_at(anim, equiv, 200);
assert_animations_equal_at(anim, equiv, 400);
assert_animations_equal_at(anim, equiv, 600);
assert_animations_equal_at(anim, equiv, 800);
assert_animations_equal_at(anim, equiv, 1000);
}, 'linear function easing, steps equivalent');
test(function(t) {
var anim = create_animated_div(t, 'linear(0, 0.1 -10%, 1)');
var equiv = create_animated_div(t, 'linear(0, 0.1 0%, 1)');
assert_animations_equal_at(anim, equiv, 0);
assert_animations_equal_at(anim, equiv, 100);
assert_animations_equal_at(anim, equiv, 550);
assert_animations_equal_at(anim, equiv, 1000);
}, 'linear function easing, input value being unspecified in the first entry implies zero');
test(function(t) {
var anim = create_animated_div(t, 'linear(0, 0.9 110%, 1)');
var equiv = create_animated_div(t, 'linear(0, 0.9 110%, 1 110%)');
assert_animations_equal_at(anim, equiv, 0);
assert_animations_equal_at(anim, equiv, 450);
assert_animations_equal_at(anim, equiv, 900);
assert_animations_equal_at(anim, equiv, 950);
assert_animations_equal_at(anim, equiv, 1000);
}, 'linear function easing, input value being unspecified in the last entry implies max input value');
</script>
</body>

View file

@ -0,0 +1,65 @@
'use strict';
// Creates a <div> element, appends it to the document body and
// removes the created element during test cleanup.
function createDiv(test, doc) {
return createElement(test, 'div', doc);
}
// Creates an element with the given |tagName|, appends it to the document body
// and removes the created element during test cleanup.
// If |tagName| is null or undefined, creates a <div> element.
function createElement(test, tagName, doc) {
if (!doc) {
doc = document;
}
var element = doc.createElement(tagName || 'div');
doc.body.appendChild(element);
test.add_cleanup(function() {
element.remove();
});
return element;
}
// Convert px unit value to a Number
function pxToNum(str) {
return Number(String(str).match(/^(-?[\d.]+)px$/)[1]);
}
// Cubic bezier with control points (0, 0), (x1, y1), (x2, y2), and (1, 1).
function cubicBezier(x1, y1, x2, y2) {
function xForT(t) {
var omt = 1-t;
return 3 * omt * omt * t * x1 + 3 * omt * t * t * x2 + t * t * t;
}
function yForT(t) {
var omt = 1-t;
return 3 * omt * omt * t * y1 + 3 * omt * t * t * y2 + t * t * t;
}
function tForX(x) {
// Binary subdivision.
var mint = 0, maxt = 1;
for (var i = 0; i < 30; ++i) {
var guesst = (mint + maxt) / 2;
var guessx = xForT(guesst);
if (x < guessx) {
maxt = guesst;
} else {
mint = guesst;
}
}
return (mint + maxt) / 2;
}
return function bezierClosure(x) {
if (x == 0) {
return 0;
}
if (x == 1) {
return 1;
}
return yForT(tForX(x));
}
}