diff --git a/Libraries/LibWeb/Animations/AnimationEffect.cpp b/Libraries/LibWeb/Animations/AnimationEffect.cpp index 4912446489e..f0ff3dbe934 100644 --- a/Libraries/LibWeb/Animations/AnimationEffect.cpp +++ b/Libraries/LibWeb/Animations/AnimationEffect.cpp @@ -75,7 +75,7 @@ EffectTiming AnimationEffect::get_timing() const .iterations = m_iteration_count, .duration = m_iteration_duration, .direction = m_playback_direction, - .easing = m_timing_function.to_string(), + .easing = m_timing_function.to_string(CSS::SerializationMode::Normal), }; } @@ -110,7 +110,7 @@ ComputedEffectTiming AnimationEffect::get_computed_timing() const .iterations = m_iteration_count, .duration = duration, .direction = m_playback_direction, - .easing = m_timing_function.to_string(), + .easing = m_timing_function.to_string(CSS::SerializationMode::Normal), }, end_time(), diff --git a/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp b/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp index 1780eb3dd9e..01f744502c5 100644 --- a/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp +++ b/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp @@ -2651,11 +2651,10 @@ RefPtr Parser::parse_easing_value(TokenStream::of_single_token(intervals_argument); + auto intervals = parse_integer(intervals_token); + if (!intervals.has_value()) return nullptr; - if (!intervals_argument.token().number().is_integer()) - return nullptr; - auto intervals = intervals_argument.token().to_integer(); if (comma_separated_arguments.size() == 2) { TokenStream identifier_stream { comma_separated_arguments[1] }; @@ -2690,14 +2689,16 @@ RefPtr Parser::parse_easing_value(TokenStream 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 (steps.position == EasingStyleValue::Steps::Position::JumpNone) { - if (intervals <= 1) + if (!intervals->is_calculated()) { + if (steps.position == EasingStyleValue::Steps::Position::JumpNone) { + if (intervals->value() <= 1) + return nullptr; + } else if (intervals->value() <= 0) { return nullptr; - } else if (intervals <= 0) { - return nullptr; + } } - steps.number_of_intervals = intervals; + steps.number_of_intervals = *intervals; transaction.commit(); return EasingStyleValue::create(steps); } diff --git a/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.cpp index 324a17c7cba..7e5392b3982 100644 --- a/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.cpp @@ -11,6 +11,7 @@ #include "EasingStyleValue.h" #include #include +#include namespace Web::CSS { @@ -174,7 +175,7 @@ double EasingStyleValue::Linear::evaluate_at(double input_progress, bool before_ } // https://drafts.csswg.org/css-easing/#linear-easing-function-serializing -String EasingStyleValue::Linear::to_string() const +String EasingStyleValue::Linear::to_string(SerializationMode) const { // The linear keyword is serialized as itself. if (*this == identity()) @@ -316,7 +317,7 @@ double EasingStyleValue::CubicBezier::evaluate_at(double input_progress, bool) c } // https://drafts.csswg.org/css-easing/#bezier-serialization -String EasingStyleValue::CubicBezier::to_string() const +String EasingStyleValue::CubicBezier::to_string(SerializationMode) const { StringBuilder builder; if (*this == CubicBezier::ease()) { @@ -337,7 +338,10 @@ double EasingStyleValue::Steps::evaluate_at(double input_progress, bool before_f { // https://www.w3.org/TR/css-easing-1/#step-easing-algo // 1. Calculate the current step as floor(input progress value × steps). - auto current_step = floor(input_progress * number_of_intervals); + auto resolved_number_of_intervals = number_of_intervals.resolved({}).value_or(1); + resolved_number_of_intervals = max(resolved_number_of_intervals, position == Steps::Position::JumpNone ? 2 : 1); + + auto current_step = floor(input_progress * resolved_number_of_intervals); // 2. If the step position property is one of: // - jump-start, @@ -350,7 +354,7 @@ double EasingStyleValue::Steps::evaluate_at(double input_progress, bool before_f // - the before flag is set, and // - input progress value × steps mod 1 equals zero (that is, if input progress value × steps is integral), then // decrement current step by one. - auto step_progress = input_progress * number_of_intervals; + auto step_progress = input_progress * resolved_number_of_intervals; if (before_flag && trunc(step_progress) == step_progress) current_step -= 1; @@ -363,7 +367,7 @@ double EasingStyleValue::Steps::evaluate_at(double input_progress, bool before_f // jump-start or jump-end -> steps // jump-none -> steps - 1 // jump-both -> steps + 1 - auto jumps = number_of_intervals; + auto jumps = resolved_number_of_intervals; if (position == Steps::Position::JumpNone) { jumps--; } else if (position == Steps::Position::JumpBoth) { @@ -379,7 +383,7 @@ double EasingStyleValue::Steps::evaluate_at(double input_progress, bool before_f } // https://drafts.csswg.org/css-easing/#steps-serialization -String EasingStyleValue::Steps::to_string() const +String EasingStyleValue::Steps::to_string(SerializationMode mode) const { StringBuilder builder; // Unlike the other easing function keywords, step-start and step-end do not serialize as themselves. @@ -403,10 +407,15 @@ String EasingStyleValue::Steps::to_string() const return {}; } }(); + auto intervals = number_of_intervals; + if (mode == SerializationMode::ResolvedValue) { + auto resolved_value = number_of_intervals.resolved({}).value_or(1); + intervals = max(resolved_value, this->position == Steps::Position::JumpNone ? 2 : 1); + } if (position.has_value()) { - builder.appendff("steps({}, {})", number_of_intervals, position.value()); + builder.appendff("steps({}, {})", intervals.to_string(), position.value()); } else { - builder.appendff("steps({})", number_of_intervals); + builder.appendff("steps({})", intervals.to_string()); } } return MUST(builder.to_string()); @@ -420,11 +429,11 @@ double EasingStyleValue::Function::evaluate_at(double input_progress, bool befor }); } -String EasingStyleValue::Function::to_string() const +String EasingStyleValue::Function::to_string(SerializationMode mode) const { return visit( [&](auto const& curve) { - return curve.to_string(); + return curve.to_string(mode); }); } diff --git a/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.h index ef373afb187..bfe360d3384 100644 --- a/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.h @@ -11,6 +11,7 @@ #pragma once #include +#include namespace Web::CSS { @@ -35,7 +36,7 @@ public: bool operator==(Linear const&) const = default; double evaluate_at(double input_progress, bool before_flag) const; - String to_string() const; + String to_string(SerializationMode) const; Linear(Vector stops); }; @@ -62,7 +63,7 @@ public: bool operator==(CubicBezier const&) const; double evaluate_at(double input_progress, bool before_flag) const; - String to_string() const; + String to_string(SerializationMode) const; }; struct Steps { @@ -78,20 +79,20 @@ public: static Steps step_start(); static Steps step_end(); - unsigned int number_of_intervals; + IntegerOrCalculated number_of_intervals { 0 }; Position position { Position::End }; bool operator==(Steps const&) const = default; double evaluate_at(double input_progress, bool before_flag) const; - String to_string() const; + String to_string(SerializationMode) const; }; struct Function : public Variant { using Variant::Variant; double evaluate_at(double input_progress, bool before_flag) const; - String to_string() const; + String to_string(SerializationMode) const; }; static ValueComparingNonnullRefPtr create(Function const& function) @@ -102,7 +103,7 @@ public: Function const& function() const { return m_function; } - virtual String to_string(SerializationMode) const override { return m_function.to_string(); } + virtual String to_string(SerializationMode mode) const override { return m_function.to_string(mode); } bool properties_equal(EasingStyleValue const& other) const { return m_function == other.m_function; } 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 new file mode 100644 index 00000000000..8078d8b2590 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-easing/timing-functions-syntax-computed.txt @@ -0,0 +1,27 @@ +Harness status: OK + +Found 21 tests + +18 Pass +3 Fail +Pass Property animation-timing-function value 'linear' +Pass Property animation-timing-function value 'ease' +Pass Property animation-timing-function value 'ease-in' +Pass Property animation-timing-function value 'ease-out' +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 'steps(4, start)' +Pass Property animation-timing-function value 'steps(2, end)' +Pass Property animation-timing-function value 'steps( 2, end )' +Pass Property animation-timing-function value 'steps(2, jump-start)' +Pass Property animation-timing-function value 'steps(2, jump-end)' +Pass Property animation-timing-function value 'steps(2, jump-both)' +Pass Property animation-timing-function value 'steps(2, jump-none)' +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)' +Fail 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 diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-easing/timing-functions-syntax-valid.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-easing/timing-functions-syntax-valid.txt new file mode 100644 index 00000000000..e5a2fdef199 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-easing/timing-functions-syntax-valid.txt @@ -0,0 +1,28 @@ +Harness status: OK + +Found 22 tests + +19 Pass +3 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 +Pass e.style['animation-timing-function'] = "ease-out" should set the property value +Pass e.style['animation-timing-function'] = "ease-in-out" should set the property value +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 +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 +Pass e.style['animation-timing-function'] = "steps( 2, end )" should set the property value +Pass e.style['animation-timing-function'] = "steps(2, jump-start)" should set the property value +Pass e.style['animation-timing-function'] = "steps(2, jump-end)" should set the property value +Pass e.style['animation-timing-function'] = "steps(2, jump-both)" should set the property value +Pass e.style['animation-timing-function'] = "steps(2, jump-none)" should set the property value +Pass e.style['animation-timing-function'] = "steps(calc(-10), start)" should set the property value +Pass e.style['animation-timing-function'] = "steps(calc(5 / 2), start)" should set the property value +Pass e.style['animation-timing-function'] = "steps(calc(1), jump-none)" should set the property value +Fail e.style['animation-timing-function'] = "linear, ease, linear" should set the property value +Pass e.style['animation-timing-function'] = "steps(calc(2 + sign(100em - 1px)))" should set the property value \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/css/css-easing/timing-functions-syntax-computed.html b/Tests/LibWeb/Text/input/wpt-import/css/css-easing/timing-functions-syntax-computed.html new file mode 100644 index 00000000000..3d432de7234 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/css/css-easing/timing-functions-syntax-computed.html @@ -0,0 +1,42 @@ + + + + +CSS Easing: getComputedStyle().animationTimingFunction + + + + + + + +
+ + + diff --git a/Tests/LibWeb/Text/input/wpt-import/css/css-easing/timing-functions-syntax-valid.html b/Tests/LibWeb/Text/input/wpt-import/css/css-easing/timing-functions-syntax-valid.html new file mode 100644 index 00000000000..589717b9e76 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/css/css-easing/timing-functions-syntax-valid.html @@ -0,0 +1,42 @@ + + + + +CSS Easing: parsing animation-timing-function with valid values + + + + + + + + + +