From 755a576013efcc419d4e9e015aee064734cea1d0 Mon Sep 17 00:00:00 2001 From: Callum Law Date: Sat, 11 Oct 2025 15:09:11 +1300 Subject: [PATCH] LibWeb: Support relative lengths within easing function `calc()`s --- Libraries/LibWeb/CSS/EasingFunction.cpp | 27 +++++++++++------ Libraries/LibWeb/CSS/Parser/ValueParsing.cpp | 28 ++++++++--------- .../CSS/StyleValues/EasingStyleValue.cpp | 30 ++++++++++++------- .../LibWeb/CSS/StyleValues/EasingStyleValue.h | 12 ++++---- .../timing-functions-syntax-computed.txt | 5 ++-- .../transition-timing-function-computed.txt | 8 ++--- 6 files changed, 63 insertions(+), 47 deletions(-) diff --git a/Libraries/LibWeb/CSS/EasingFunction.cpp b/Libraries/LibWeb/CSS/EasingFunction.cpp index 4f322adf0b8..02c80a6c854 100644 --- a/Libraries/LibWeb/CSS/EasingFunction.cpp +++ b/Libraries/LibWeb/CSS/EasingFunction.cpp @@ -7,6 +7,7 @@ #include "EasingFunction.h" #include #include +#include #include #include @@ -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) }; }); } diff --git a/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp b/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp index 2e034782910..52ed75bba46 100644 --- a/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp +++ b/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp @@ -2954,7 +2954,7 @@ RefPtr Parser::parse_easing_value(TokenStream& auto parse_argument = [this, &comma_separated_arguments](auto index) { TokenStream 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 Parser::parse_easing_value(TokenStream& 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 Parser::parse_easing_value(TokenStream& auto const& intervals_argument = comma_separated_arguments[0][0]; auto intervals_token = TokenStream::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 is jump-none, the must be at least 2, or the function is invalid. // Otherwise, the 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; diff --git a/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.cpp index f8eecff8a0d..e81895b8601 100644 --- a/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.cpp @@ -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 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); diff --git a/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.h index 90d4bdb8959..c58e3df8097 100644 --- a/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.h @@ -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 x1; + ValueComparingNonnullRefPtr y1; + ValueComparingNonnullRefPtr x2; + ValueComparingNonnullRefPtr 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 number_of_intervals; + StepPosition position; bool operator==(Steps const&) const = default; diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-easing/timing-functions-syntax-computed.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-easing/timing-functions-syntax-computed.txt index fc78cd610dd..fe5119e6197 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-easing/timing-functions-syntax-computed.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-easing/timing-functions-syntax-computed.txt @@ -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)' \ No newline at end of file +Pass Property animation-timing-function value 'steps(calc(2 + sign(100em - 1px)), end)' \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-transitions/parsing/transition-timing-function-computed.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-transitions/parsing/transition-timing-function-computed.txt index d586e89e465..96545952135 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-transitions/parsing/transition-timing-function-computed.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-transitions/parsing/transition-timing-function-computed.txt @@ -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' \ No newline at end of file