LibWeb: Support relative lengths within easing function calc()s

This commit is contained in:
Callum Law 2025-10-11 15:09:11 +13:00 committed by Sam Atkins
parent ad41f053b8
commit 755a576013
Notes: github-actions[bot] 2025-10-20 10:29:08 +00:00
6 changed files with 63 additions and 47 deletions

View file

@ -7,6 +7,7 @@
#include "EasingFunction.h" #include "EasingFunction.h"
#include <AK/BinarySearch.h> #include <AK/BinarySearch.h>
#include <LibWeb/CSS/StyleValues/EasingStyleValue.h> #include <LibWeb/CSS/StyleValues/EasingStyleValue.h>
#include <LibWeb/CSS/StyleValues/IntegerStyleValue.h>
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h> #include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h> #include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
@ -289,6 +290,16 @@ EasingFunction EasingFunction::from_style_value(StyleValue const& style_value)
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
}; };
auto const resolve_integer = [](StyleValue const& style_value) {
if (style_value.is_integer())
return style_value.as_integer().integer();
if (style_value.is_calculated())
return style_value.as_calculated().resolve_integer({}).value();
VERIFY_NOT_REACHED();
};
if (style_value.is_easing()) { if (style_value.is_easing()) {
return style_value.as_easing().function().visit( return style_value.as_easing().function().visit(
[&](EasingStyleValue::Linear const& linear) -> EasingFunction { [&](EasingStyleValue::Linear const& linear) -> EasingFunction {
@ -311,18 +322,16 @@ EasingFunction EasingFunction::from_style_value(StyleValue const& style_value)
return LinearEasingFunction { resolved_control_points, linear.to_string(SerializationMode::ResolvedValue) }; return LinearEasingFunction { resolved_control_points, linear.to_string(SerializationMode::ResolvedValue) };
}, },
[](EasingStyleValue::CubicBezier const& cubic_bezier) -> EasingFunction { [&](EasingStyleValue::CubicBezier const& cubic_bezier) -> EasingFunction {
auto resolved_x1 = cubic_bezier.x1.resolved({}).value_or(0.0); auto resolved_x1 = resolve_number(cubic_bezier.x1);
auto resolved_y1 = cubic_bezier.y1.resolved({}).value_or(0.0); auto resolved_y1 = resolve_number(cubic_bezier.y1);
auto resolved_x2 = cubic_bezier.x2.resolved({}).value_or(0.0); auto resolved_x2 = resolve_number(cubic_bezier.x2);
auto resolved_y2 = cubic_bezier.y2.resolved({}).value_or(0.0); auto resolved_y2 = resolve_number(cubic_bezier.y2);
return CubicBezierEasingFunction { resolved_x1, resolved_y1, resolved_x2, resolved_y2, cubic_bezier.to_string(SerializationMode::Normal) }; return CubicBezierEasingFunction { resolved_x1, resolved_y1, resolved_x2, resolved_y2, cubic_bezier.to_string(SerializationMode::Normal) };
}, },
[](EasingStyleValue::Steps const& steps) -> EasingFunction { [&](EasingStyleValue::Steps const& steps) -> EasingFunction {
auto resolved_interval_count = steps.number_of_intervals.resolved({}).value_or(1); return StepsEasingFunction { resolve_integer(steps.number_of_intervals), steps.position, steps.to_string(SerializationMode::ResolvedValue) };
return StepsEasingFunction { resolved_interval_count, steps.position, steps.to_string(SerializationMode::ResolvedValue) };
}); });
} }

View file

@ -2954,7 +2954,7 @@ RefPtr<StyleValue const> Parser::parse_easing_value(TokenStream<ComponentValue>&
auto parse_argument = [this, &comma_separated_arguments](auto index) { auto parse_argument = [this, &comma_separated_arguments](auto index) {
TokenStream<ComponentValue> argument_tokens { comma_separated_arguments[index] }; TokenStream<ComponentValue> argument_tokens { comma_separated_arguments[index] };
return parse_number(argument_tokens); return parse_number_value(argument_tokens);
}; };
m_value_context.append(SpecialContext::CubicBezierFunctionXCoordinate); m_value_context.append(SpecialContext::CubicBezierFunctionXCoordinate);
@ -2964,18 +2964,18 @@ RefPtr<StyleValue const> Parser::parse_easing_value(TokenStream<ComponentValue>&
auto y1 = parse_argument(1); auto y1 = parse_argument(1);
auto y2 = parse_argument(3); auto y2 = parse_argument(3);
if (!x1.has_value() || !y1.has_value() || !x2.has_value() || !y2.has_value()) if (!x1 || !y1 || !x2 || !y2)
return nullptr; return nullptr;
if (!x1->is_calculated() && (x1->value() < 0.0 || x1->value() > 1.0)) if (x1->is_number() && (x1->as_number().number() < 0.0 || x1->as_number().number() > 1.0))
return nullptr; return nullptr;
if (!x2->is_calculated() && (x2->value() < 0.0 || x2->value() > 1.0)) if (x2->is_number() && (x2->as_number().number() < 0.0 || x2->as_number().number() > 1.0))
return nullptr; return nullptr;
EasingStyleValue::CubicBezier bezier { EasingStyleValue::CubicBezier bezier {
x1.release_value(), x1.release_nonnull(),
y1.release_value(), y1.release_nonnull(),
x2.release_value(), x2.release_nonnull(),
y2.release_value(), y2.release_nonnull(),
}; };
transaction.commit(); transaction.commit();
@ -3018,26 +3018,26 @@ RefPtr<StyleValue const> Parser::parse_easing_value(TokenStream<ComponentValue>&
auto const& intervals_argument = comma_separated_arguments[0][0]; auto const& intervals_argument = comma_separated_arguments[0][0];
auto intervals_token = TokenStream<ComponentValue>::of_single_token(intervals_argument); auto intervals_token = TokenStream<ComponentValue>::of_single_token(intervals_argument);
m_value_context.append(position == StepPosition::JumpNone ? SpecialContext::StepsIntervalsJumpNone : SpecialContext::StepsIntervalsNormal); m_value_context.append(position == StepPosition::JumpNone ? SpecialContext::StepsIntervalsJumpNone : SpecialContext::StepsIntervalsNormal);
auto intervals = parse_integer(intervals_token); auto intervals = parse_integer_value(intervals_token);
m_value_context.take_last(); m_value_context.take_last();
if (!intervals.has_value()) if (!intervals)
return nullptr; return nullptr;
// Perform extra validation // Perform extra validation
// https://drafts.csswg.org/css-easing/#step-easing-functions // https://drafts.csswg.org/css-easing/#step-easing-functions
// If the <step-position> is jump-none, the <integer> must be at least 2, or the function is invalid. // If the <step-position> is jump-none, the <integer> must be at least 2, or the function is invalid.
// Otherwise, the <integer> must be at least 1, or the function is invalid. // Otherwise, the <integer> must be at least 1, or the function is invalid.
if (!intervals->is_calculated()) { if (intervals->is_integer()) {
if (position == StepPosition::JumpNone) { if (position == StepPosition::JumpNone) {
if (intervals->value() <= 1) if (intervals->as_integer().integer() <= 1)
return nullptr; return nullptr;
} else if (intervals->value() <= 0) { } else if (intervals->as_integer().integer() <= 0) {
return nullptr; return nullptr;
} }
} }
transaction.commit(); transaction.commit();
return EasingStyleValue::create(EasingStyleValue::Steps { *intervals, position }); return EasingStyleValue::create(EasingStyleValue::Steps { intervals.release_nonnull(), position });
} }
return nullptr; return nullptr;

View file

@ -27,37 +27,37 @@ EasingStyleValue::Linear EasingStyleValue::Linear::identity()
EasingStyleValue::CubicBezier EasingStyleValue::CubicBezier::ease() EasingStyleValue::CubicBezier EasingStyleValue::CubicBezier::ease()
{ {
static CubicBezier bezier { 0.25, 0.1, 0.25, 1.0 }; static CubicBezier bezier { NumberStyleValue::create(0.25), NumberStyleValue::create(0.1), NumberStyleValue::create(0.25), NumberStyleValue::create(1.0) };
return bezier; return bezier;
} }
EasingStyleValue::CubicBezier EasingStyleValue::CubicBezier::ease_in() EasingStyleValue::CubicBezier EasingStyleValue::CubicBezier::ease_in()
{ {
static CubicBezier bezier { 0.42, 0.0, 1.0, 1.0 }; static CubicBezier bezier { NumberStyleValue::create(0.42), NumberStyleValue::create(0.0), NumberStyleValue::create(1.0), NumberStyleValue::create(1.0) };
return bezier; return bezier;
} }
EasingStyleValue::CubicBezier EasingStyleValue::CubicBezier::ease_out() EasingStyleValue::CubicBezier EasingStyleValue::CubicBezier::ease_out()
{ {
static CubicBezier bezier { 0.0, 0.0, 0.58, 1.0 }; static CubicBezier bezier { NumberStyleValue::create(0.0), NumberStyleValue::create(0.0), NumberStyleValue::create(0.58), NumberStyleValue::create(1.0) };
return bezier; return bezier;
} }
EasingStyleValue::CubicBezier EasingStyleValue::CubicBezier::ease_in_out() EasingStyleValue::CubicBezier EasingStyleValue::CubicBezier::ease_in_out()
{ {
static CubicBezier bezier { 0.42, 0.0, 0.58, 1.0 }; static CubicBezier bezier { NumberStyleValue::create(0.42), NumberStyleValue::create(0.0), NumberStyleValue::create(0.58), NumberStyleValue::create(1.0) };
return bezier; return bezier;
} }
EasingStyleValue::Steps EasingStyleValue::Steps::step_start() EasingStyleValue::Steps EasingStyleValue::Steps::step_start()
{ {
static Steps steps { 1, StepPosition::Start }; static Steps steps { IntegerStyleValue::create(1), StepPosition::Start };
return steps; return steps;
} }
EasingStyleValue::Steps EasingStyleValue::Steps::step_end() EasingStyleValue::Steps EasingStyleValue::Steps::step_end()
{ {
static Steps steps { 1, StepPosition::End }; static Steps steps { IntegerStyleValue::create(1), StepPosition::End };
return steps; return steps;
} }
@ -116,7 +116,7 @@ String EasingStyleValue::CubicBezier::to_string(SerializationMode mode) const
} else if (*this == CubicBezier::ease_in_out()) { } else if (*this == CubicBezier::ease_in_out()) {
builder.append("ease-in-out"sv); builder.append("ease-in-out"sv);
} else { } else {
builder.appendff("cubic-bezier({}, {}, {}, {})", x1.to_string(mode), y1.to_string(mode), x2.to_string(mode), y2.to_string(mode)); builder.appendff("cubic-bezier({}, {}, {}, {})", x1->to_string(mode), y1->to_string(mode), x2->to_string(mode), y2->to_string(mode));
} }
return MUST(builder.to_string()); return MUST(builder.to_string());
} }
@ -138,9 +138,9 @@ String EasingStyleValue::Steps::to_string(SerializationMode mode) const
return CSS::to_string(this->position); return CSS::to_string(this->position);
}(); }();
if (position.has_value()) { if (position.has_value()) {
builder.appendff("steps({}, {})", number_of_intervals.to_string(mode), position.value()); builder.appendff("steps({}, {})", number_of_intervals->to_string(mode), position.value());
} else { } else {
builder.appendff("steps({})", number_of_intervals.to_string(mode)); builder.appendff("steps({})", number_of_intervals->to_string(mode));
} }
} }
return MUST(builder.to_string()); return MUST(builder.to_string());
@ -172,10 +172,18 @@ ValueComparingNonnullRefPtr<StyleValue const> EasingStyleValue::absolutized(Comp
return Linear { absolutized_stops }; return Linear { absolutized_stops };
}, },
[&](CubicBezier const& cubic_bezier) -> Function { [&](CubicBezier const& cubic_bezier) -> Function {
return cubic_bezier; return CubicBezier {
cubic_bezier.x1->absolutized(computation_context),
cubic_bezier.y1->absolutized(computation_context),
cubic_bezier.x2->absolutized(computation_context),
cubic_bezier.y2->absolutized(computation_context)
};
}, },
[&](Steps const& steps) -> Function { [&](Steps const& steps) -> Function {
return steps; return Steps {
steps.number_of_intervals->absolutized(computation_context),
steps.position
};
}); });
return EasingStyleValue::create(absolutized_function); return EasingStyleValue::create(absolutized_function);

View file

@ -41,10 +41,10 @@ public:
static CubicBezier ease_out(); static CubicBezier ease_out();
static CubicBezier ease_in_out(); static CubicBezier ease_in_out();
NumberOrCalculated x1 { 0 }; ValueComparingNonnullRefPtr<StyleValue const> x1;
NumberOrCalculated y1 { 0 }; ValueComparingNonnullRefPtr<StyleValue const> y1;
NumberOrCalculated x2 { 0 }; ValueComparingNonnullRefPtr<StyleValue const> x2;
NumberOrCalculated y2 { 0 }; ValueComparingNonnullRefPtr<StyleValue const> y2;
struct CachedSample { struct CachedSample {
double x; double x;
@ -66,8 +66,8 @@ public:
static Steps step_start(); static Steps step_start();
static Steps step_end(); static Steps step_end();
IntegerOrCalculated number_of_intervals { 1 }; ValueComparingNonnullRefPtr<StyleValue const> number_of_intervals;
StepPosition position { StepPosition::End }; StepPosition position;
bool operator==(Steps const&) const = default; bool operator==(Steps const&) const = default;

View file

@ -2,8 +2,7 @@ Harness status: OK
Found 21 tests Found 21 tests
20 Pass 21 Pass
1 Fail
Pass Property animation-timing-function value 'linear' Pass Property animation-timing-function value 'linear'
Pass Property animation-timing-function value 'ease' Pass Property animation-timing-function value 'ease'
Pass Property animation-timing-function value 'ease-in' Pass Property animation-timing-function value 'ease-in'
@ -24,4 +23,4 @@ Pass Property animation-timing-function value 'steps(calc(-10), start)'
Pass Property animation-timing-function value 'steps(calc(5 / 2), start)' Pass Property animation-timing-function value 'steps(calc(5 / 2), start)'
Pass Property animation-timing-function value 'steps(calc(1), jump-none)' Pass Property animation-timing-function value 'steps(calc(1), jump-none)'
Pass Property animation-timing-function value 'linear, ease, linear' Pass Property animation-timing-function value 'linear, ease, linear'
Fail Property animation-timing-function value 'steps(calc(2 + sign(100em - 1px)), end)' Pass Property animation-timing-function value 'steps(calc(2 + sign(100em - 1px)), end)'

View file

@ -2,8 +2,8 @@ Harness status: OK
Found 22 tests Found 22 tests
18 Pass 20 Pass
4 Fail 2 Fail
Pass Property transition-timing-function value 'linear' Pass Property transition-timing-function value 'linear'
Pass Property transition-timing-function value 'ease' Pass Property transition-timing-function value 'ease'
Pass Property transition-timing-function value 'ease-in' Pass Property transition-timing-function value 'ease-in'
@ -23,6 +23,6 @@ Pass Property transition-timing-function value 'steps(2, jump-both)'
Pass Property transition-timing-function value 'steps(2, jump-none)' Pass Property transition-timing-function value 'steps(2, jump-none)'
Fail Property transition-timing-function value 'steps(calc(2 * sibling-index()), jump-none)' Fail Property transition-timing-function value 'steps(calc(2 * sibling-index()), jump-none)'
Fail Property transition-timing-function value 'steps(sibling-index(), jump-none)' Fail Property transition-timing-function value 'steps(sibling-index(), jump-none)'
Fail Property transition-timing-function value 'steps(calc(2 * sign(1em - 1000px)), jump-none)' Pass Property transition-timing-function value 'steps(calc(2 * sign(1em - 1000px)), jump-none)'
Fail Property transition-timing-function value 'steps(calc(2 * sign(1em - 1000px)), start)' Pass Property transition-timing-function value 'steps(calc(2 * sign(1em - 1000px)), start)'
Pass Property transition-timing-function value 'linear, ease, linear' Pass Property transition-timing-function value 'linear, ease, linear'