LibWeb: Maintain easing keywords as KeywordStyleValue until use-time

This excludes `step-end` and `step-start` which are expected to be
converted to the equivalent function at parse time.

We are expected to serialize these as the explicit keywords - previously
we would parse as `EasingStyleValue` and serialize equivalent functions
as the keywords. This caused issues as we would incorrectly serialize
even explicit functions as the keyword.

This also allows us to move the magic easing functions to
`EasingFunction` rather than `EasingStyleValue` which is a bit tidier
This commit is contained in:
Callum Law 2025-10-11 23:34:35 +13:00 committed by Sam Atkins
commit 03be70087d
Notes: github-actions[bot] 2025-10-20 10:29:02 +00:00
15 changed files with 210 additions and 125 deletions

View file

@ -13,7 +13,9 @@
#include <LibWeb/Bindings/Intrinsics.h> #include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/CSS/ComputedProperties.h> #include <LibWeb/CSS/ComputedProperties.h>
#include <LibWeb/CSS/Parser/Parser.h> #include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/StyleComputer.h>
#include <LibWeb/CSS/StyleInvalidation.h> #include <LibWeb/CSS/StyleInvalidation.h>
#include <LibWeb/CSS/StyleValues/KeywordStyleValue.h>
#include <LibWeb/DOM/Element.h> #include <LibWeb/DOM/Element.h>
#include <LibWeb/Layout/Node.h> #include <LibWeb/Layout/Node.h>
#include <LibWeb/WebIDL/ExceptionOr.h> #include <LibWeb/WebIDL/ExceptionOr.h>
@ -606,9 +608,11 @@ Optional<double> AnimationEffect::transformed_progress() const
Optional<CSS::EasingFunction> AnimationEffect::parse_easing_string(StringView value) Optional<CSS::EasingFunction> AnimationEffect::parse_easing_string(StringView value)
{ {
if (auto style_value = parse_css_value(CSS::Parser::ParsingParams(), value, CSS::PropertyID::AnimationTimingFunction)) { if (auto style_value = parse_css_value(CSS::Parser::ParsingParams(), value, CSS::PropertyID::AnimationTimingFunction)) {
if (style_value->is_easing()) if (style_value->is_unresolved() || style_value->is_value_list() || style_value->is_css_wide_keyword())
return {};
// FIXME: We should absolutize style_value to resolve relative lengths within calcs // FIXME: We should absolutize style_value to resolve relative lengths within calcs
return CSS::EasingFunction::from_style_value(*style_value); return CSS::EasingFunction::from_style_value(style_value.release_nonnull());
} }
return {}; return {};

View file

@ -13,7 +13,6 @@
#include <LibWeb/Bindings/PlatformObject.h> #include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/CSS/EasingFunction.h> #include <LibWeb/CSS/EasingFunction.h>
#include <LibWeb/CSS/Enums.h> #include <LibWeb/CSS/Enums.h>
#include <LibWeb/CSS/StyleValues/EasingStyleValue.h>
namespace Web::Animations { namespace Web::Animations {
@ -194,7 +193,7 @@ protected:
GC::Ptr<Animation> m_associated_animation {}; GC::Ptr<Animation> m_associated_animation {};
// https://www.w3.org/TR/web-animations-1/#time-transformations // https://www.w3.org/TR/web-animations-1/#time-transformations
CSS::EasingFunction m_timing_function { CSS::EasingFunction::from_style_value(CSS::EasingStyleValue::create(CSS::EasingStyleValue::Linear::identity())) }; CSS::EasingFunction m_timing_function { CSS::EasingFunction::linear() };
// Used for calculating transitions in StyleComputer // Used for calculating transitions in StyleComputer
Phase m_previous_phase { Phase::Idle }; Phase m_previous_phase { Phase::Idle };

View file

@ -8,6 +8,7 @@
#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/IntegerStyleValue.h>
#include <LibWeb/CSS/StyleValues/KeywordStyleValue.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>
@ -268,6 +269,41 @@ static Vector<LinearEasingFunction::ControlPoint> canonicalize_linear_easing_fun
return canonicalized_control_points; return canonicalized_control_points;
} }
// https://drafts.csswg.org/css-easing-2/#linear-easing-function
EasingFunction EasingFunction::linear()
{
// Equivalent to linear(0, 1)
return LinearEasingFunction { { { 0, 0 }, { 1, 1 } }, "linear"_string };
}
// https://drafts.csswg.org/css-easing-2/#valdef-cubic-bezier-easing-function-ease-in
EasingFunction EasingFunction::ease_in()
{
// Equivalent to cubic-bezier(0.42, 0, 1, 1).
return CubicBezierEasingFunction { 0.42, 0, 1, 1, "ease-in"_string };
}
// https://drafts.csswg.org/css-easing-2/#valdef-cubic-bezier-easing-function-ease-out
EasingFunction EasingFunction::ease_out()
{
// Equivalent to cubic-bezier(0, 0, 0.58, 1).
return CubicBezierEasingFunction { 0, 0, 0.58, 1, "ease-out"_string };
}
// https://drafts.csswg.org/css-easing-2/#valdef-cubic-bezier-easing-function-ease-in-out
EasingFunction EasingFunction::ease_in_out()
{
// Equivalent to cubic-bezier(0.42, 0, 0.58, 1).
return CubicBezierEasingFunction { 0.42, 0, 0.58, 1, "ease-in-out"_string };
}
// https://drafts.csswg.org/css-easing-2/#valdef-cubic-bezier-easing-function-ease
EasingFunction EasingFunction::ease()
{
// Equivalent to cubic-bezier(0.25, 0.1, 0.25, 1).
return CubicBezierEasingFunction { 0.25, 0.1, 0.25, 1, "ease"_string };
}
EasingFunction EasingFunction::from_style_value(StyleValue const& style_value) EasingFunction EasingFunction::from_style_value(StyleValue const& style_value)
{ {
auto const resolve_number = [](StyleValue const& style_value) { auto const resolve_number = [](StyleValue const& style_value) {
@ -335,6 +371,22 @@ EasingFunction EasingFunction::from_style_value(StyleValue const& style_value)
}); });
} }
switch (style_value.to_keyword()) {
case Keyword::Linear:
return EasingFunction::linear();
case Keyword::EaseIn:
return EasingFunction::ease_in();
case Keyword::EaseOut:
return EasingFunction::ease_out();
case Keyword::EaseInOut:
return EasingFunction::ease_in_out();
case Keyword::Ease:
return EasingFunction::ease();
default: {
VERIFY_NOT_REACHED();
}
}
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} }

View file

@ -51,6 +51,13 @@ struct StepsEasingFunction {
struct EasingFunction : public Variant<LinearEasingFunction, CubicBezierEasingFunction, StepsEasingFunction> { struct EasingFunction : public Variant<LinearEasingFunction, CubicBezierEasingFunction, StepsEasingFunction> {
using Variant::Variant; using Variant::Variant;
static EasingFunction linear();
static EasingFunction ease_in();
static EasingFunction ease_out();
static EasingFunction ease_in_out();
static EasingFunction ease();
static EasingFunction from_style_value(StyleValue const&); static EasingFunction from_style_value(StyleValue const&);
double evaluate_at(double input_progress, bool before_flag) const; double evaluate_at(double input_progress, bool before_flag) const;

View file

@ -281,6 +281,13 @@
"inline", "inline",
"run-in" "run-in"
], ],
"easing-keyword": [
"linear",
"ease",
"ease-in",
"ease-out",
"ease-in-out"
],
"empty-cells": [ "empty-cells": [
"show", "show",
"hide" "hide"

View file

@ -29,7 +29,6 @@
#include <LibWeb/CSS/StyleValues/CursorStyleValue.h> #include <LibWeb/CSS/StyleValues/CursorStyleValue.h>
#include <LibWeb/CSS/StyleValues/CustomIdentStyleValue.h> #include <LibWeb/CSS/StyleValues/CustomIdentStyleValue.h>
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h> #include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
#include <LibWeb/CSS/StyleValues/EasingStyleValue.h>
#include <LibWeb/CSS/StyleValues/EdgeStyleValue.h> #include <LibWeb/CSS/StyleValues/EdgeStyleValue.h>
#include <LibWeb/CSS/StyleValues/FilterValueListStyleValue.h> #include <LibWeb/CSS/StyleValues/FilterValueListStyleValue.h>
#include <LibWeb/CSS/StyleValues/FitContentStyleValue.h> #include <LibWeb/CSS/StyleValues/FitContentStyleValue.h>
@ -825,7 +824,7 @@ Parser::ParseErrorOr<NonnullRefPtr<StyleValue const>> Parser::parse_css_value(Pr
return parsed_value.release_nonnull(); return parsed_value.release_nonnull();
return ParseError::SyntaxError; return ParseError::SyntaxError;
case PropertyID::TransitionTimingFunction: case PropertyID::TransitionTimingFunction:
if (auto parsed_value = parse_comma_separated_value_list(tokens, [this](auto& tokens) { return parse_easing_value(tokens); })) if (auto parsed_value = parse_simple_comma_separated_value_list(property_id, tokens); parsed_value && !tokens.has_next_token())
return parsed_value.release_nonnull(); return parsed_value.release_nonnull();
return ParseError::SyntaxError; return ParseError::SyntaxError;
case PropertyID::Translate: case PropertyID::Translate:
@ -5117,7 +5116,7 @@ RefPtr<StyleValue const> Parser::parse_transition_value(TokenStream<ComponentVal
continue; continue;
} }
if (auto easing = parse_easing_value(tokens)) { if (auto easing = parse_css_value_for_property(PropertyID::TransitionTimingFunction, tokens)) {
if (transition.easing) { if (transition.easing) {
ErrorReporter::the().report(InvalidPropertyError { ErrorReporter::the().report(InvalidPropertyError {
.property_name = "transition"_fly_string, .property_name = "transition"_fly_string,
@ -5127,7 +5126,7 @@ RefPtr<StyleValue const> Parser::parse_transition_value(TokenStream<ComponentVal
return {}; return {};
} }
transition.easing = easing->as_easing(); transition.easing = easing;
continue; continue;
} }
@ -5183,7 +5182,7 @@ RefPtr<StyleValue const> Parser::parse_transition_value(TokenStream<ComponentVal
transition.property_name = KeywordStyleValue::create(Keyword::All); transition.property_name = KeywordStyleValue::create(Keyword::All);
if (!transition.easing) if (!transition.easing)
transition.easing = EasingStyleValue::create(EasingStyleValue::CubicBezier::ease()); transition.easing = KeywordStyleValue::create(Keyword::Ease);
transitions.append(move(transition)); transitions.append(move(transition));

View file

@ -2865,20 +2865,10 @@ RefPtr<StyleValue const> Parser::parse_easing_value(TokenStream<ComponentValue>&
if (part.is(Token::Type::Ident)) { if (part.is(Token::Type::Ident)) {
auto name = part.token().ident(); auto name = part.token().ident();
auto maybe_simple_easing = [&] -> RefPtr<EasingStyleValue const> { auto maybe_simple_easing = [&] -> RefPtr<EasingStyleValue const> {
if (name.equals_ignoring_ascii_case("linear"sv))
return EasingStyleValue::create(EasingStyleValue::Linear::identity());
if (name.equals_ignoring_ascii_case("ease"sv))
return EasingStyleValue::create(EasingStyleValue::CubicBezier::ease());
if (name.equals_ignoring_ascii_case("ease-in"sv))
return EasingStyleValue::create(EasingStyleValue::CubicBezier::ease_in());
if (name.equals_ignoring_ascii_case("ease-out"sv))
return EasingStyleValue::create(EasingStyleValue::CubicBezier::ease_out());
if (name.equals_ignoring_ascii_case("ease-in-out"sv))
return EasingStyleValue::create(EasingStyleValue::CubicBezier::ease_in_out());
if (name.equals_ignoring_ascii_case("step-start"sv)) if (name.equals_ignoring_ascii_case("step-start"sv))
return EasingStyleValue::create(EasingStyleValue::Steps::step_start()); return EasingStyleValue::create(EasingStyleValue::Steps { IntegerStyleValue::create(1), StepPosition::Start });
if (name.equals_ignoring_ascii_case("step-end"sv)) if (name.equals_ignoring_ascii_case("step-end"sv))
return EasingStyleValue::create(EasingStyleValue::Steps::step_end()); return EasingStyleValue::create(EasingStyleValue::Steps { IntegerStyleValue::create(1), StepPosition::End });
return {}; return {};
}(); }();

View file

@ -345,7 +345,8 @@
"__comment": "FIXME: animation properties should be coordinating-lists", "__comment": "FIXME: animation properties should be coordinating-lists",
"multiplicity": "single", "multiplicity": "single",
"valid-types": [ "valid-types": [
"easing-function" "easing-function",
"easing-keyword"
] ]
}, },
"appearance": { "appearance": {
@ -3900,7 +3901,8 @@
"initial": "ease", "initial": "ease",
"multiplicity": "coordinating-list", "multiplicity": "coordinating-list",
"valid-types": [ "valid-types": [
"easing-function" "easing-function",
"easing-keyword"
] ]
}, },
"translate": { "translate": {

View file

@ -718,7 +718,7 @@ void StyleComputer::for_each_property_expanding_shorthands(PropertyID property_i
set_longhand_property(CSS::PropertyID::TransitionProperty, KeywordStyleValue::create(Keyword::All)); set_longhand_property(CSS::PropertyID::TransitionProperty, KeywordStyleValue::create(Keyword::All));
set_longhand_property(CSS::PropertyID::TransitionDuration, TimeStyleValue::create(CSS::Time::make_seconds(0))); set_longhand_property(CSS::PropertyID::TransitionDuration, TimeStyleValue::create(CSS::Time::make_seconds(0)));
set_longhand_property(CSS::PropertyID::TransitionDelay, TimeStyleValue::create(CSS::Time::make_seconds(0))); set_longhand_property(CSS::PropertyID::TransitionDelay, TimeStyleValue::create(CSS::Time::make_seconds(0)));
set_longhand_property(CSS::PropertyID::TransitionTimingFunction, EasingStyleValue::create(EasingStyleValue::CubicBezier::ease())); set_longhand_property(CSS::PropertyID::TransitionTimingFunction, KeywordStyleValue::create(Keyword::Ease));
set_longhand_property(CSS::PropertyID::TransitionBehavior, KeywordStyleValue::create(Keyword::Normal)); set_longhand_property(CSS::PropertyID::TransitionBehavior, KeywordStyleValue::create(Keyword::Normal));
} else if (value.is_transition()) { } else if (value.is_transition()) {
auto const& transitions = value.as_transition().transitions(); auto const& transitions = value.as_transition().transitions();
@ -1277,9 +1277,9 @@ static void apply_animation_properties(DOM::Document& document, CascadedProperti
play_state = *play_state_value; play_state = *play_state_value;
} }
EasingFunction timing_function = EasingFunction::from_style_value(EasingStyleValue::create(EasingStyleValue::CubicBezier::ease())); EasingFunction timing_function = EasingFunction::ease();
if (auto timing_property = cascaded_properties.property(PropertyID::AnimationTimingFunction); timing_property && timing_property->is_easing()) if (auto timing_property = cascaded_properties.property(PropertyID::AnimationTimingFunction); timing_property && (timing_property->is_easing() || (timing_property->is_keyword() && !timing_property->is_css_wide_keyword())))
timing_function = EasingFunction::from_style_value(timing_property->as_easing()); timing_function = EasingFunction::from_style_value(timing_property.release_nonnull());
Bindings::CompositeOperation composite_operation { Bindings::CompositeOperation::Replace }; Bindings::CompositeOperation composite_operation { Bindings::CompositeOperation::Replace };
if (auto composite_property = cascaded_properties.property(PropertyID::AnimationComposition); composite_property) { if (auto composite_property = cascaded_properties.property(PropertyID::AnimationComposition); composite_property) {
@ -1421,7 +1421,7 @@ static void compute_transitioned_properties(ComputedProperties const& style, DOM
[] { return TimeStyleValue::create(Time::make_seconds(0.0)); }); [] { return TimeStyleValue::create(Time::make_seconds(0.0)); });
auto timing_functions = normalize_transition_length_list( auto timing_functions = normalize_transition_length_list(
PropertyID::TransitionTimingFunction, PropertyID::TransitionTimingFunction,
[] { return EasingStyleValue::create(EasingStyleValue::CubicBezier::ease()); }); [] { return KeywordStyleValue::create(Keyword::Ease); });
auto transition_behaviors = normalize_transition_length_list( auto transition_behaviors = normalize_transition_length_list(
PropertyID::TransitionBehavior, PropertyID::TransitionBehavior,
[] { return KeywordStyleValue::create(Keyword::None); }); [] { return KeywordStyleValue::create(Keyword::None); });

View file

@ -16,58 +16,9 @@
namespace Web::CSS { namespace Web::CSS {
// https://drafts.csswg.org/css-easing-1/#valdef-easing-function-linear
EasingStyleValue::Linear EasingStyleValue::Linear::identity()
{
static Linear linear { { { NumberStyleValue::create(0), {} }, { NumberStyleValue::create(1), {} } } };
return linear;
}
// NOTE: Magic cubic bezier values from https://www.w3.org/TR/css-easing-1/#valdef-cubic-bezier-easing-function-ease
EasingStyleValue::CubicBezier EasingStyleValue::CubicBezier::ease()
{
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 { 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 { 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 { 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 { IntegerStyleValue::create(1), StepPosition::Start };
return steps;
}
EasingStyleValue::Steps EasingStyleValue::Steps::step_end()
{
static Steps steps { IntegerStyleValue::create(1), StepPosition::End };
return steps;
}
// https://drafts.csswg.org/css-easing/#linear-easing-function-serializing // https://drafts.csswg.org/css-easing/#linear-easing-function-serializing
String EasingStyleValue::Linear::to_string(SerializationMode mode) const String EasingStyleValue::Linear::to_string(SerializationMode mode) const
{ {
// The linear keyword is serialized as itself.
if (*this == identity())
return "linear"_string;
// To serialize a linear() function: // To serialize a linear() function:
// 1. Let s be the string "linear(". // 1. Let s be the string "linear(".
StringBuilder builder; StringBuilder builder;
@ -106,44 +57,23 @@ String EasingStyleValue::Linear::to_string(SerializationMode mode) const
// https://drafts.csswg.org/css-easing/#bezier-serialization // https://drafts.csswg.org/css-easing/#bezier-serialization
String EasingStyleValue::CubicBezier::to_string(SerializationMode mode) const String EasingStyleValue::CubicBezier::to_string(SerializationMode mode) const
{ {
StringBuilder builder; return MUST(String::formatted("cubic-bezier({}, {}, {}, {})", x1->to_string(mode), y1->to_string(mode), x2->to_string(mode), y2->to_string(mode)));
if (*this == CubicBezier::ease()) {
builder.append("ease"sv);
} else if (*this == CubicBezier::ease_in()) {
builder.append("ease-in"sv);
} else if (*this == CubicBezier::ease_out()) {
builder.append("ease-out"sv);
} 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));
}
return MUST(builder.to_string());
} }
// https://drafts.csswg.org/css-easing/#steps-serialization // https://drafts.csswg.org/css-easing/#steps-serialization
String EasingStyleValue::Steps::to_string(SerializationMode mode) 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.
// Instead, they serialize as "steps(1, start)" and "steps(1)", respectively.
if (*this == Steps::step_start()) {
builder.append("steps(1, start)"sv);
} else if (*this == Steps::step_end()) {
builder.append("steps(1)"sv);
} else {
auto position = [&] -> Optional<StringView> { auto position = [&] -> Optional<StringView> {
if (first_is_one_of(this->position, StepPosition::JumpEnd, StepPosition::End)) if (first_is_one_of(this->position, StepPosition::JumpEnd, StepPosition::End))
return {}; return {};
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()); return MUST(String::formatted("steps({}, {})", number_of_intervals->to_string(mode), position.value()));
} else {
builder.appendff("steps({})", number_of_intervals->to_string(mode));
} }
}
return MUST(builder.to_string()); return MUST(String::formatted("steps({})", number_of_intervals->to_string(mode)));
} }
String EasingStyleValue::Function::to_string(SerializationMode mode) const String EasingStyleValue::Function::to_string(SerializationMode mode) const

View file

@ -19,8 +19,6 @@ namespace Web::CSS {
class EasingStyleValue final : public StyleValueWithDefaultOperators<EasingStyleValue> { class EasingStyleValue final : public StyleValueWithDefaultOperators<EasingStyleValue> {
public: public:
struct Linear { struct Linear {
static Linear identity();
struct Stop { struct Stop {
ValueComparingNonnullRefPtr<StyleValue const> output; ValueComparingNonnullRefPtr<StyleValue const> output;
ValueComparingRefPtr<StyleValue const> input; ValueComparingRefPtr<StyleValue const> input;
@ -36,11 +34,6 @@ public:
}; };
struct CubicBezier { struct CubicBezier {
static CubicBezier ease();
static CubicBezier ease_in();
static CubicBezier ease_out();
static CubicBezier ease_in_out();
ValueComparingNonnullRefPtr<StyleValue const> x1; ValueComparingNonnullRefPtr<StyleValue const> x1;
ValueComparingNonnullRefPtr<StyleValue const> y1; ValueComparingNonnullRefPtr<StyleValue const> y1;
ValueComparingNonnullRefPtr<StyleValue const> x2; ValueComparingNonnullRefPtr<StyleValue const> x2;
@ -63,9 +56,6 @@ public:
}; };
struct Steps { struct Steps {
static Steps step_start();
static Steps step_end();
ValueComparingNonnullRefPtr<StyleValue const> number_of_intervals; ValueComparingNonnullRefPtr<StyleValue const> number_of_intervals;
StepPosition position; StepPosition position;

View file

@ -8,7 +8,6 @@
#include <LibWeb/CSS/CalculatedOr.h> #include <LibWeb/CSS/CalculatedOr.h>
#include <LibWeb/CSS/StyleValues/CustomIdentStyleValue.h> #include <LibWeb/CSS/StyleValues/CustomIdentStyleValue.h>
#include <LibWeb/CSS/StyleValues/EasingStyleValue.h>
#include <LibWeb/CSS/StyleValues/StyleValue.h> #include <LibWeb/CSS/StyleValues/StyleValue.h>
#include <LibWeb/CSS/Time.h> #include <LibWeb/CSS/Time.h>
@ -20,7 +19,7 @@ public:
ValueComparingRefPtr<StyleValue const> property_name; ValueComparingRefPtr<StyleValue const> property_name;
TimeOrCalculated duration { CSS::Time::make_seconds(0.0) }; TimeOrCalculated duration { CSS::Time::make_seconds(0.0) };
TimeOrCalculated delay { CSS::Time::make_seconds(0.0) }; TimeOrCalculated delay { CSS::Time::make_seconds(0.0) };
ValueComparingRefPtr<EasingStyleValue const> easing; ValueComparingRefPtr<StyleValue const> easing;
TransitionBehavior transition_behavior { TransitionBehavior::Normal }; TransitionBehavior transition_behavior { TransitionBehavior::Normal };
bool operator==(Transition const&) const = default; bool operator==(Transition const&) const = default;

View file

@ -0,0 +1,28 @@
Specified: #linear { animation-timing-function: linear; transition-timing-function: linear(0, 1); }
Keyword computed: linear
Function computed: linear(0, 1)
Specified: #ease-in { animation-timing-function: ease-in; transition-timing-function: cubic-bezier(0.42, 0, 1, 1); }
Keyword computed: ease-in
Function computed: cubic-bezier(0.42, 0, 1, 1)
Specified: #ease-out { animation-timing-function: ease-out; transition-timing-function: cubic-bezier(0, 0, 0.58, 1); }
Keyword computed: ease-out
Function computed: cubic-bezier(0, 0, 0.58, 1)
Specified: #ease-in-out { animation-timing-function: ease-in-out; transition-timing-function: cubic-bezier(0.42, 0, 0.58, 1); }
Keyword computed: ease-in-out
Function computed: cubic-bezier(0.42, 0, 0.58, 1)
Specified: #ease { animation-timing-function: ease; transition-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1); }
Keyword computed: ease
Function computed: cubic-bezier(0.25, 0.1, 0.25, 1)
Specified: #step-start { animation-timing-function: steps(1, start); transition-timing-function: steps(1, start); }
Keyword computed: steps(1, start)
Function computed: steps(1, start)
Specified: #step-end { animation-timing-function: steps(1); transition-timing-function: steps(1); }
Keyword computed: steps(1)
Function computed: steps(1)

View file

@ -2,11 +2,11 @@ Harness status: OK
Found 35 tests Found 35 tests
19 Pass 20 Pass
16 Fail 15 Fail
Pass e.style['animation-timing-function'] = "linear(0 0%, 1 100%)" should set the property value Pass e.style['animation-timing-function'] = "linear(0 0%, 1 100%)" should set the property value
Pass e.style['animation-timing-function'] = "linear( 0 0%, 1 100% )" should set the property value Pass e.style['animation-timing-function'] = "linear( 0 0%, 1 100% )" should set the property value
Fail e.style['animation-timing-function'] = "linear(0, 1)" should set the property value Pass e.style['animation-timing-function'] = "linear(0, 1)" should set the property value
Pass e.style['animation-timing-function'] = "linear(-10, -5, 0, 5, 10)" should set the property value Pass e.style['animation-timing-function'] = "linear(-10, -5, 0, 5, 10)" should set the property value
Pass e.style['animation-timing-function'] = "linear(-10 -10%, -5 -5%, 0, 5, 10)" should set the property value Pass e.style['animation-timing-function'] = "linear(-10 -10%, -5 -5%, 0, 5, 10)" should set the property value
Pass e.style['animation-timing-function'] = "linear(0 calc(0%), 0 calc(100%))" should set the property value Pass e.style['animation-timing-function'] = "linear(0 calc(0%), 0 calc(100%))" should set the property value

View file

@ -0,0 +1,78 @@
<!DOCTYPE html>
<html>
<body>
<style>
#linear {
animation-timing-function: linear;
transition-timing-function: linear(0, 1);
}
#ease-in {
animation-timing-function: ease-in;
transition-timing-function: cubic-bezier(0.42, 0, 1, 1);
}
#ease-out {
animation-timing-function: ease-out;
transition-timing-function: cubic-bezier(0, 0, 0.58, 1);
}
#ease-in-out {
animation-timing-function: ease-in-out;
transition-timing-function: cubic-bezier(0.42, 0, 0.58, 1);
}
#ease {
animation-timing-function: ease;
transition-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1);
}
#step-start {
animation-timing-function: step-start;
transition-timing-function: steps(1, start);
}
#step-end {
animation-timing-function: step-end;
transition-timing-function: steps(1, end);
}
</style>
<div id="linear"></div>
<div id="ease-in"></div>
<div id="ease-out"></div>
<div id="ease-in-out"></div>
<div id="ease"></div>
<div id="step-start"></div>
<div id="step-end"></div>
<script src="../include.js"></script>
<script>
test(() => {
const keywords = [
"linear",
"ease-in",
"ease-out",
"ease-in-out",
"ease",
"step-start",
"step-end",
];
for (let i = 0; i < keywords.length; i++) {
println(`Specified: ${document.styleSheets[0].cssRules[i].cssText}`);
println(
`Keyword computed: ${
getComputedStyle(document.getElementById(keywords[i]))
.animationTimingFunction
}`
);
println(
`Function computed: ${
getComputedStyle(document.getElementById(keywords[i]))
.transitionTimingFunction
}\n`
);
}
});
</script>
</body>
</html>