mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-09 01:29:17 +00:00
LibWeb/CSS: Fix reference bug when canonicalizing linear easing stops
+2 WPT tests
This commit is contained in:
parent
5e1e0d4e18
commit
21b531598d
Notes:
github-actions[bot]
2025-06-18 06:54:17 +00:00
Author: https://github.com/veeti
Commit: 21b531598d
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5124
Reviewed-by: https://github.com/gmta ✅
4 changed files with 176 additions and 2 deletions
|
@ -81,7 +81,7 @@ EasingStyleValue::Linear::Linear(Vector<EasingStyleValue::Linear::Stop> stops)
|
||||||
// the input progress value of any preceding control point,
|
// 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.
|
// set its input progress value to the largest input progress value of any preceding control point.
|
||||||
double largest_input = 0;
|
double largest_input = 0;
|
||||||
for (auto stop : stops) {
|
for (auto& stop : stops) {
|
||||||
if (stop.input.has_value()) {
|
if (stop.input.has_value()) {
|
||||||
if (stop.input.value() < largest_input) {
|
if (stop.input.value() < largest_input) {
|
||||||
stop.input = 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.
|
// between the preceding and following control points with input progress values.
|
||||||
Optional<size_t> run_start_idx;
|
Optional<size_t> run_start_idx;
|
||||||
for (size_t idx = 0; idx < stops.size(); 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()) {
|
if (stop.input.has_value() && run_start_idx.has_value()) {
|
||||||
// Note: this stop is immediately after a run
|
// Note: this stop is immediately after a run
|
||||||
// set inputs of [start, idx-1] stops to be evenly spaced between start-1 and idx
|
// set inputs of [start, idx-1] stops to be evenly spaced between start-1 and idx
|
||||||
|
|
|
@ -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
|
|
@ -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>
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue