LibWeb: Allow calc() values in cubic-bezier() easing functions

This commit is contained in:
Tim Ledbetter 2025-06-17 22:51:54 +01:00 committed by Jelle Raaijmakers
parent c5a3eaaf45
commit fa1e02e5d7
Notes: github-actions[bot] 2025-06-18 06:58:15 +00:00
5 changed files with 58 additions and 31 deletions

View file

@ -2621,19 +2621,30 @@ RefPtr<CSSStyleValue const> Parser::parse_easing_value(TokenStream<ComponentValu
for (auto const& argument : comma_separated_arguments) {
if (argument.size() != 1)
return nullptr;
if (!argument[0].is(Token::Type::Number))
return nullptr;
}
EasingStyleValue::CubicBezier bezier {
comma_separated_arguments[0][0].token().number_value(),
comma_separated_arguments[1][0].token().number_value(),
comma_separated_arguments[2][0].token().number_value(),
comma_separated_arguments[3][0].token().number_value(),
auto parse_argument = [this, &comma_separated_arguments](auto index) {
TokenStream<ComponentValue> argument_tokens { comma_separated_arguments[index] };
return parse_number(argument_tokens);
};
if (bezier.x1 < 0.0 || bezier.x1 > 1.0 || bezier.x2 < 0.0 || bezier.x2 > 1.0)
auto x1 = parse_argument(0);
auto y1 = parse_argument(1);
auto x2 = parse_argument(2);
auto y2 = parse_argument(3);
if (!x1.has_value() || !y1.has_value() || !x2.has_value() || !y2.has_value())
return nullptr;
if (!x1->is_calculated() && (x1->value() < 0.0 || x1->value() > 1.0))
return nullptr;
if (!x2->is_calculated() && (x2->value() < 0.0 || x2->value() > 1.0))
return nullptr;
EasingStyleValue::CubicBezier bezier {
x1.release_value(),
y1.release_value(),
x2.release_value(),
y2.release_value(),
};
transaction.commit();
return EasingStyleValue::create(bezier);

View file

@ -230,6 +230,11 @@ double EasingStyleValue::CubicBezier::evaluate_at(double input_progress, bool) c
};
// https://www.w3.org/TR/css-easing-1/#cubic-bezier-algo
auto resolved_x1 = clamp(x1.resolved({}).value_or(0.0), 0.0, 1.0);
auto resolved_y1 = y1.resolved({}).value_or(0.0);
auto resolved_x2 = clamp(x2.resolved({}).value_or(0.0), 0.0, 1.0);
auto resolved_y2 = y2.resolved({}).value_or(0.0);
// For input progress values outside the range [0, 1], the curve is extended infinitely using tangent of the curve
// at the closest endpoint as follows:
@ -237,13 +242,13 @@ double EasingStyleValue::CubicBezier::evaluate_at(double input_progress, bool) c
if (input_progress < 0.0) {
// 1. If the x value of P1 is greater than zero, use a straight line that passes through P1 and P0 as the
// tangent.
if (x1 > 0.0)
return y1 / x1 * input_progress;
if (resolved_x1 > 0.0)
return resolved_y1 / resolved_x1 * input_progress;
// 2. Otherwise, if the x value of P2 is greater than zero, use a straight line that passes through P2 and P0 as
// the tangent.
if (x2 > 0.0)
return y2 / x2 * input_progress;
if (resolved_x2 > 0.0)
return resolved_y2 / resolved_x2 * input_progress;
// 3. Otherwise, let the output progress value be zero for all input progress values in the range [-∞, 0).
return 0.0;
@ -252,13 +257,13 @@ double EasingStyleValue::CubicBezier::evaluate_at(double input_progress, bool) c
// - For input progress values greater than one,
if (input_progress > 1.0) {
// 1. If the x value of P2 is less than one, use a straight line that passes through P2 and P3 as the tangent.
if (x2 < 1.0)
return (1.0 - y2) / (1.0 - x2) * (input_progress - 1.0) + 1.0;
if (resolved_x2 < 1.0)
return (1.0 - resolved_y2) / (1.0 - resolved_x2) * (input_progress - 1.0) + 1.0;
// 2. Otherwise, if the x value of P1 is less than one, use a straight line that passes through P1 and P3 as the
// tangent.
if (x1 < 1.0)
return (1.0 - y1) / (1.0 - x1) * (input_progress - 1.0) + 1.0;
if (resolved_x1 < 1.0)
return (1.0 - resolved_y1) / (1.0 - resolved_x1) * (input_progress - 1.0) + 1.0;
// 3. Otherwise, let the output progress value be one for all input progress values in the range (1, ∞].
return 1.0;
@ -270,8 +275,8 @@ double EasingStyleValue::CubicBezier::evaluate_at(double input_progress, bool) c
auto x = input_progress;
auto solve = [&](auto t) {
auto x = cubic_bezier_at(x1, x2, t);
auto y = cubic_bezier_at(y1, y2, t);
auto x = cubic_bezier_at(resolved_x1, resolved_x2, t);
auto y = cubic_bezier_at(resolved_y1, resolved_y2, t);
return CubicBezier::CachedSample { x, y, t };
};
@ -317,7 +322,7 @@ double EasingStyleValue::CubicBezier::evaluate_at(double input_progress, bool) c
}
// https://drafts.csswg.org/css-easing/#bezier-serialization
String EasingStyleValue::CubicBezier::to_string(SerializationMode) const
String EasingStyleValue::CubicBezier::to_string(SerializationMode mode) const
{
StringBuilder builder;
if (*this == CubicBezier::ease()) {
@ -329,7 +334,18 @@ String EasingStyleValue::CubicBezier::to_string(SerializationMode) const
} else if (*this == CubicBezier::ease_in_out()) {
builder.append("ease-in-out"sv);
} else {
builder.appendff("cubic-bezier({}, {}, {}, {})", x1, y1, x2, y2);
auto x1_value = x1;
auto y1_value = y1;
auto x2_value = x2;
auto y2_value = y2;
if (mode == SerializationMode::ResolvedValue) {
x1_value = clamp(x1_value.resolved({}).value_or(0.0), 0.0, 1.0);
x2_value = clamp(x2_value.resolved({}).value_or(0.0), 0.0, 1.0);
y1_value = y1_value.resolved({}).value_or(0.0);
y2_value = y2_value.resolved({}).value_or(0.0);
}
builder.appendff("cubic-bezier({}, {}, {}, {})",
x1_value.to_string(), y1_value.to_string(), x2_value.to_string(), y2_value.to_string());
}
return MUST(builder.to_string());
}

View file

@ -47,10 +47,10 @@ public:
static CubicBezier ease_out();
static CubicBezier ease_in_out();
double x1;
double y1;
double x2;
double y2;
NumberOrCalculated x1 { 0 };
NumberOrCalculated y1 { 0 };
NumberOrCalculated x2 { 0 };
NumberOrCalculated y2 { 0 };
struct CachedSample {
double x;
@ -79,7 +79,7 @@ public:
static Steps step_start();
static Steps step_end();
IntegerOrCalculated number_of_intervals { 0 };
IntegerOrCalculated number_of_intervals { 1 };
Position position { Position::End };
bool operator==(Steps const&) const = default;

View file

@ -2,8 +2,8 @@ Harness status: OK
Found 21 tests
18 Pass
3 Fail
19 Pass
2 Fail
Pass Property animation-timing-function value 'linear'
Pass Property animation-timing-function value 'ease'
Pass Property animation-timing-function value 'ease-in'
@ -12,7 +12,7 @@ Pass Property animation-timing-function value 'ease-in-out'
Pass Property animation-timing-function value 'cubic-bezier(0.1, 0.2, 0.8, 0.9)'
Pass Property animation-timing-function value 'cubic-bezier(0, -2, 1, 3)'
Pass Property animation-timing-function value 'cubic-bezier(0, 0.7, 1, 1.3)'
Fail Property animation-timing-function value 'cubic-bezier(calc(-2), calc(0.7 / 2), calc(1.5), calc(0))'
Pass Property animation-timing-function value 'cubic-bezier(calc(-2), calc(0.7 / 2), calc(1.5), calc(0))'
Pass Property animation-timing-function value 'steps(4, start)'
Pass Property animation-timing-function value 'steps(2, end)'
Pass Property animation-timing-function value 'steps( 2, end )'

View file

@ -2,8 +2,8 @@ Harness status: OK
Found 22 tests
19 Pass
3 Fail
20 Pass
2 Fail
Pass e.style['animation-timing-function'] = "linear" should set the property value
Pass e.style['animation-timing-function'] = "ease" should set the property value
Pass e.style['animation-timing-function'] = "ease-in" should set the property value
@ -12,7 +12,7 @@ Pass e.style['animation-timing-function'] = "ease-in-out" should set the propert
Pass e.style['animation-timing-function'] = "cubic-bezier(0.1, 0.2, 0.8, 0.9)" should set the property value
Pass e.style['animation-timing-function'] = "cubic-bezier(0, -2, 1, 3)" should set the property value
Pass e.style['animation-timing-function'] = "cubic-bezier(0, 0.7, 1, 1.3)" should set the property value
Fail e.style['animation-timing-function'] = "cubic-bezier(calc(-2), calc(0.7 / 2), calc(1.5), calc(0))" should set the property value
Pass e.style['animation-timing-function'] = "cubic-bezier(calc(-2), calc(0.7 / 2), calc(1.5), calc(0))" should set the property value
Fail e.style['animation-timing-function'] = "cubic-bezier(0, sibling-index(), 1, sign(2em - 20px))" should set the property value
Pass e.style['animation-timing-function'] = "steps(4, start)" should set the property value
Pass e.style['animation-timing-function'] = "steps(2, end)" should set the property value