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 <AK/BinarySearch.h>
#include <LibWeb/CSS/StyleValues/EasingStyleValue.h>
#include <LibWeb/CSS/StyleValues/IntegerStyleValue.h>
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
@ -289,6 +290,16 @@ EasingFunction EasingFunction::from_style_value(StyleValue const& style_value)
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()) {
return style_value.as_easing().function().visit(
[&](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) };
},
[](EasingStyleValue::CubicBezier const& cubic_bezier) -> EasingFunction {
auto resolved_x1 = cubic_bezier.x1.resolved({}).value_or(0.0);
auto resolved_y1 = cubic_bezier.y1.resolved({}).value_or(0.0);
auto resolved_x2 = cubic_bezier.x2.resolved({}).value_or(0.0);
auto resolved_y2 = cubic_bezier.y2.resolved({}).value_or(0.0);
[&](EasingStyleValue::CubicBezier const& cubic_bezier) -> EasingFunction {
auto resolved_x1 = resolve_number(cubic_bezier.x1);
auto resolved_y1 = resolve_number(cubic_bezier.y1);
auto resolved_x2 = resolve_number(cubic_bezier.x2);
auto resolved_y2 = resolve_number(cubic_bezier.y2);
return CubicBezierEasingFunction { resolved_x1, resolved_y1, resolved_x2, resolved_y2, cubic_bezier.to_string(SerializationMode::Normal) };
},
[](EasingStyleValue::Steps const& steps) -> EasingFunction {
auto resolved_interval_count = steps.number_of_intervals.resolved({}).value_or(1);
return StepsEasingFunction { resolved_interval_count, steps.position, steps.to_string(SerializationMode::ResolvedValue) };
[&](EasingStyleValue::Steps const& steps) -> EasingFunction {
return StepsEasingFunction { resolve_integer(steps.number_of_intervals), 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) {
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);
@ -2964,18 +2964,18 @@ RefPtr<StyleValue const> Parser::parse_easing_value(TokenStream<ComponentValue>&
auto y1 = parse_argument(1);
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;
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;
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;
EasingStyleValue::CubicBezier bezier {
x1.release_value(),
y1.release_value(),
x2.release_value(),
y2.release_value(),
x1.release_nonnull(),
y1.release_nonnull(),
x2.release_nonnull(),
y2.release_nonnull(),
};
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 intervals_token = TokenStream<ComponentValue>::of_single_token(intervals_argument);
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();
if (!intervals.has_value())
if (!intervals)
return nullptr;
// Perform extra validation
// 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.
// 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 (intervals->value() <= 1)
if (intervals->as_integer().integer() <= 1)
return nullptr;
} else if (intervals->value() <= 0) {
} else if (intervals->as_integer().integer() <= 0) {
return nullptr;
}
}
transaction.commit();
return EasingStyleValue::create(EasingStyleValue::Steps { *intervals, position });
return EasingStyleValue::create(EasingStyleValue::Steps { intervals.release_nonnull(), position });
}
return nullptr;

View file

@ -27,37 +27,37 @@ EasingStyleValue::Linear EasingStyleValue::Linear::identity()
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;
}
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;
}
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;
}
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;
}
EasingStyleValue::Steps EasingStyleValue::Steps::step_start()
{
static Steps steps { 1, StepPosition::Start };
static Steps steps { IntegerStyleValue::create(1), StepPosition::Start };
return steps;
}
EasingStyleValue::Steps EasingStyleValue::Steps::step_end()
{
static Steps steps { 1, StepPosition::End };
static Steps steps { IntegerStyleValue::create(1), StepPosition::End };
return steps;
}
@ -116,7 +116,7 @@ String EasingStyleValue::CubicBezier::to_string(SerializationMode mode) const
} else if (*this == CubicBezier::ease_in_out()) {
builder.append("ease-in-out"sv);
} 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());
}
@ -138,9 +138,9 @@ String EasingStyleValue::Steps::to_string(SerializationMode mode) const
return CSS::to_string(this->position);
}();
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 {
builder.appendff("steps({})", number_of_intervals.to_string(mode));
builder.appendff("steps({})", number_of_intervals->to_string(mode));
}
}
return MUST(builder.to_string());
@ -172,10 +172,18 @@ ValueComparingNonnullRefPtr<StyleValue const> EasingStyleValue::absolutized(Comp
return Linear { absolutized_stops };
},
[&](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 {
return steps;
return Steps {
steps.number_of_intervals->absolutized(computation_context),
steps.position
};
});
return EasingStyleValue::create(absolutized_function);

View file

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

View file

@ -2,8 +2,7 @@ Harness status: OK
Found 21 tests
20 Pass
1 Fail
21 Pass
Pass Property animation-timing-function value 'linear'
Pass Property animation-timing-function value 'ease'
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(1), jump-none)'
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
18 Pass
4 Fail
20 Pass
2 Fail
Pass Property transition-timing-function value 'linear'
Pass Property transition-timing-function value 'ease'
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)'
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(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)), jump-none)'
Pass Property transition-timing-function value 'steps(calc(2 * sign(1em - 1000px)), start)'
Pass Property transition-timing-function value 'linear, ease, linear'