diff --git a/Tests/LibWeb/Text/expected/WebAnimations/misc/easing-parsing.txt b/Tests/LibWeb/Text/expected/WebAnimations/misc/easing-parsing.txt
new file mode 100644
index 0000000000..631b0a0751
--- /dev/null
+++ b/Tests/LibWeb/Text/expected/WebAnimations/misc/easing-parsing.txt
@@ -0,0 +1 @@
+ PASS
diff --git a/Tests/LibWeb/Text/input/WebAnimations/misc/easing-parsing.html b/Tests/LibWeb/Text/input/WebAnimations/misc/easing-parsing.html
new file mode 100644
index 0000000000..d86440e8c2
--- /dev/null
+++ b/Tests/LibWeb/Text/input/WebAnimations/misc/easing-parsing.html
@@ -0,0 +1,74 @@
+
+
+
+
diff --git a/Userland/Libraries/LibWeb/Animations/AnimationEffect.h b/Userland/Libraries/LibWeb/Animations/AnimationEffect.h
index 033442d028..b070ea3624 100644
--- a/Userland/Libraries/LibWeb/Animations/AnimationEffect.h
+++ b/Userland/Libraries/LibWeb/Animations/AnimationEffect.h
@@ -184,7 +184,7 @@ protected:
JS::GCPtr m_associated_animation {};
// https://www.w3.org/TR/web-animations-1/#time-transformations
- TimingFunction m_timing_function { linear_timing_function };
+ TimingFunction m_timing_function { LinearTimingFunction {} };
// Used for calculating transitions in StyleComputer
Phase m_previous_phase { Phase::Idle };
diff --git a/Userland/Libraries/LibWeb/Animations/TimingFunction.cpp b/Userland/Libraries/LibWeb/Animations/TimingFunction.cpp
index cb5b9ed0fa..6ce08b3515 100644
--- a/Userland/Libraries/LibWeb/Animations/TimingFunction.cpp
+++ b/Userland/Libraries/LibWeb/Animations/TimingFunction.cpp
@@ -169,76 +169,39 @@ double StepsTimingFunction::operator()(double input_progress, bool before_flag)
TimingFunction TimingFunction::from_easing_style_value(CSS::EasingStyleValue const& easing_value)
{
- switch (easing_value.easing_function()) {
- case CSS::EasingFunction::Linear:
- return Animations::linear_timing_function;
- case CSS::EasingFunction::Ease:
- return Animations::ease_timing_function;
- case CSS::EasingFunction::EaseIn:
- return Animations::ease_in_timing_function;
- case CSS::EasingFunction::EaseOut:
- return Animations::ease_out_timing_function;
- case CSS::EasingFunction::EaseInOut:
- return Animations::ease_in_out_timing_function;
- case CSS::EasingFunction::CubicBezier: {
- auto values = easing_value.values();
- return {
- Animations::CubicBezierTimingFunction {
- values[0]->as_number().number(),
- values[1]->as_number().number(),
- values[2]->as_number().number(),
- values[3]->as_number().number(),
- },
- };
- }
- case CSS::EasingFunction::Steps: {
- auto values = easing_value.values();
- auto jump_at_start = false;
- auto jump_at_end = true;
+ return easing_value.function().visit(
+ [](CSS::EasingStyleValue::Linear const& linear) {
+ if (!linear.stops.is_empty()) {
+ dbgln("FIXME: Handle linear easing functions with stops");
+ }
+ return TimingFunction { LinearTimingFunction {} };
+ },
+ [](CSS::EasingStyleValue::CubicBezier const& bezier) {
+ return TimingFunction { CubicBezierTimingFunction { bezier.x1, bezier.y1, bezier.x2, bezier.y2 } };
+ },
+ [](CSS::EasingStyleValue::Steps const& steps) {
+ auto jump_at_start = false;
+ auto jump_at_end = false;
- if (values.size() > 1) {
- auto identifier = values[1]->to_identifier();
- switch (identifier) {
- case CSS::ValueID::JumpStart:
- case CSS::ValueID::Start:
+ switch (steps.position) {
+ case CSS::EasingStyleValue::Steps::Position::Start:
+ case CSS::EasingStyleValue::Steps::Position::JumpStart:
jump_at_start = true;
- jump_at_end = false;
break;
- case CSS::ValueID::JumpEnd:
- case CSS::ValueID::End:
- jump_at_start = false;
+ case CSS::EasingStyleValue::Steps::Position::End:
+ case CSS::EasingStyleValue::Steps::Position::JumpEnd:
jump_at_end = true;
break;
- case CSS::ValueID::JumpNone:
- jump_at_start = false;
- jump_at_end = false;
+ case CSS::EasingStyleValue::Steps::Position::JumpBoth:
+ jump_at_start = true;
+ jump_at_end = true;
break;
- default:
+ case CSS::EasingStyleValue::Steps::Position::JumpNone:
break;
}
- }
- return Animations::TimingFunction { Animations::StepsTimingFunction {
- .number_of_steps = static_cast(max(values[0]->as_integer().integer(), !(jump_at_end && jump_at_start) ? 1 : 0)),
- .jump_at_start = jump_at_start,
- .jump_at_end = jump_at_end,
- } };
- }
- case CSS::EasingFunction::StepEnd:
- return Animations::TimingFunction { Animations::StepsTimingFunction {
- .number_of_steps = 1,
- .jump_at_start = false,
- .jump_at_end = true,
- } };
- case CSS::EasingFunction::StepStart:
- return Animations::TimingFunction { Animations::StepsTimingFunction {
- .number_of_steps = 1,
- .jump_at_start = true,
- .jump_at_end = false,
- } };
- default:
- return Animations::ease_timing_function;
- }
+ return TimingFunction { StepsTimingFunction { steps.number_of_intervals, jump_at_start, jump_at_end } };
+ });
}
double TimingFunction::operator()(double input_progress, bool before_flag) const
diff --git a/Userland/Libraries/LibWeb/Animations/TimingFunction.h b/Userland/Libraries/LibWeb/Animations/TimingFunction.h
index f2bd14d2a2..69e956f1ef 100644
--- a/Userland/Libraries/LibWeb/Animations/TimingFunction.h
+++ b/Userland/Libraries/LibWeb/Animations/TimingFunction.h
@@ -56,11 +56,4 @@ struct TimingFunction {
double operator()(double input_progress, bool before_flag) const;
};
-static TimingFunction linear_timing_function { LinearTimingFunction {} };
-// NOTE: Magic values from
-static TimingFunction ease_timing_function { CubicBezierTimingFunction { 0.25, 0.1, 0.25, 1.0 } };
-static TimingFunction ease_in_timing_function { CubicBezierTimingFunction { 0.42, 0.0, 1.0, 1.0 } };
-static TimingFunction ease_out_timing_function { CubicBezierTimingFunction { 0.0, 0.0, 0.58, 1.0 } };
-static TimingFunction ease_in_out_timing_function { CubicBezierTimingFunction { 0.42, 0.0, 0.58, 1.0 } };
-
}
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
index 0497856526..947851aecb 100644
--- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
+++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
@@ -5065,102 +5065,169 @@ RefPtr Parser::parse_easing_value(TokenStream& token
auto const& part = tokens.next_token();
- StringView name;
- Optional const&> arguments;
if (part.is(Token::Type::Ident)) {
- name = part.token().ident();
- } else if (part.is_function()) {
- name = part.function().name();
- arguments = part.function().values();
- } else {
- return nullptr;
- }
+ auto name = part.token().ident();
+ auto maybe_simple_easing = [&] -> RefPtr {
+ if (name == "linear"sv)
+ return EasingStyleValue::create(EasingStyleValue::Linear {});
+ if (name == "ease"sv)
+ return EasingStyleValue::create(EasingStyleValue::CubicBezier::ease());
+ if (name == "ease-in"sv)
+ return EasingStyleValue::create(EasingStyleValue::CubicBezier::ease_in());
+ if (name == "ease-out"sv)
+ return EasingStyleValue::create(EasingStyleValue::CubicBezier::ease_out());
+ if (name == "ease-in-out"sv)
+ return EasingStyleValue::create(EasingStyleValue::CubicBezier::ease_in_out());
+ if (name == "step-start"sv)
+ return EasingStyleValue::create(EasingStyleValue::Steps::step_start());
+ if (name == "step-end"sv)
+ return EasingStyleValue::create(EasingStyleValue::Steps::step_end());
+ return {};
+ }();
- auto maybe_function = easing_function_from_string(name);
- if (!maybe_function.has_value())
- return nullptr;
-
- auto function = maybe_function.release_value();
- auto function_metadata = easing_function_metadata(function);
-
- if (function_metadata.parameters.is_empty() && arguments.has_value()) {
- dbgln_if(CSS_PARSER_DEBUG, "Too many arguments to {}. max: 0", name);
- return nullptr;
- }
-
- StyleValueVector values;
- size_t argument_index = 0;
- if (arguments.has_value()) {
- auto argument_tokens = TokenStream { *arguments };
- auto arguments_values = parse_a_comma_separated_list_of_component_values(argument_tokens);
- if (arguments_values.size() > function_metadata.parameters.size()) {
- dbgln_if(CSS_PARSER_DEBUG, "Too many arguments to {}. max: {}", name, function_metadata.parameters.size());
+ if (!maybe_simple_easing)
return nullptr;
- }
- for (auto& argument_values : arguments_values) {
- // Prune any whitespace before and after the actual argument values.
- argument_values.remove_all_matching([](auto& value) { return value.is(Token::Type::Whitespace); });
- if (argument_values.size() != 1) {
- dbgln_if(CSS_PARSER_DEBUG, "Too many values in argument to {}. max: 1", name);
+ transaction.commit();
+ return maybe_simple_easing;
+ }
+
+ if (!part.is_function())
+ return nullptr;
+
+ TokenStream argument_tokens { part.function().values() };
+ auto comma_separated_arguments = parse_a_comma_separated_list_of_component_values(argument_tokens);
+
+ // Remove whitespace
+ for (auto& argument : comma_separated_arguments)
+ argument.remove_all_matching([](auto& value) { return value.is(Token::Type::Whitespace); });
+
+ auto name = part.function().name();
+ if (name == "linear"sv) {
+ Vector stops;
+ for (auto const& argument : comma_separated_arguments) {
+ if (argument.is_empty() || argument.size() > 2)
+ return nullptr;
+
+ Optional offset;
+ Optional position;
+
+ for (auto const& part : argument) {
+ if (part.is(Token::Type::Number)) {
+ if (offset.has_value())
+ return nullptr;
+ offset = part.token().number_value();
+ } else if (part.is(Token::Type::Percentage)) {
+ if (position.has_value())
+ return nullptr;
+ position = part.token().percentage();
+ } else {
+ return nullptr;
+ };
+ }
+
+ if (!offset.has_value())
+ return nullptr;
+
+ stops.append({ offset.value(), move(position) });
+ }
+
+ if (stops.is_empty())
+ return nullptr;
+
+ transaction.commit();
+ return EasingStyleValue::create(EasingStyleValue::Linear { move(stops) });
+ }
+
+ if (name == "cubic-bezier") {
+ if (comma_separated_arguments.size() != 4)
+ return nullptr;
+
+ for (auto const& argument : comma_separated_arguments) {
+ if (argument.size() != 1)
+ return nullptr;
+ if (!argument[0].is(Token::Type::Number))
+ return nullptr;
+ }
+
+ EasingStyleValue::CubicBezier bezier {
+ comma_separated_arguments[0][0].token().number_value(),
+ comma_separated_arguments[1][0].token().number_value(),
+ comma_separated_arguments[2][0].token().number_value(),
+ comma_separated_arguments[3][0].token().number_value(),
+ };
+
+ if (bezier.x1 < 0.0 || bezier.x1 > 1.0 || bezier.x2 < 0.0 || bezier.x2 > 1.0)
+ return nullptr;
+
+ transaction.commit();
+ return EasingStyleValue::create(bezier);
+ }
+
+ if (name == "steps") {
+ if (comma_separated_arguments.is_empty() || comma_separated_arguments.size() > 2)
+ return nullptr;
+
+ for (auto const& argument : comma_separated_arguments) {
+ if (argument.size() != 1)
+ return nullptr;
+ }
+
+ EasingStyleValue::Steps steps;
+
+ auto intervals_argument = comma_separated_arguments[0][0];
+ if (!intervals_argument.is(Token::Type::Number))
+ 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] };
+ auto ident = parse_identifier_value(identifier_stream);
+ if (!ident)
+ return nullptr;
+ switch (ident->to_identifier()) {
+ case ValueID::JumpStart:
+ steps.position = EasingStyleValue::Steps::Position::JumpStart;
+ break;
+ case ValueID::JumpEnd:
+ steps.position = EasingStyleValue::Steps::Position::JumpEnd;
+ break;
+ case ValueID::JumpBoth:
+ steps.position = EasingStyleValue::Steps::Position::JumpBoth;
+ break;
+ case ValueID::JumpNone:
+ steps.position = EasingStyleValue::Steps::Position::JumpNone;
+ break;
+ case ValueID::Start:
+ steps.position = EasingStyleValue::Steps::Position::Start;
+ break;
+ case ValueID::End:
+ steps.position = EasingStyleValue::Steps::Position::End;
+ break;
+ default:
return nullptr;
}
-
- auto& value = argument_values[0];
- auto value_as_stream = TokenStream { argument_values };
- switch (function_metadata.parameters[argument_index].type) {
- case EasingFunctionParameterType::Number: {
- if (value.is(Token::Type::Number))
- values.append(NumberStyleValue::create(value.token().number().value()));
- else
- return nullptr;
- break;
- }
- case EasingFunctionParameterType::NumberZeroToOne: {
- if (value.is(Token::Type::Number) && value.token().number_value() >= 0 && value.token().number_value() <= 1)
- values.append(NumberStyleValue::create(value.token().number().value()));
- else
- return nullptr;
- break;
- }
- case EasingFunctionParameterType::Integer: {
- if (value.is(Token::Type::Number) && value.token().number().is_integer())
- values.append(IntegerStyleValue::create(value.token().number().integer_value()));
- else
- return nullptr;
- break;
- }
- case EasingFunctionParameterType::StepPosition: {
- if (!value.is(Token::Type::Ident))
- return nullptr;
- auto ident = parse_identifier_value(value_as_stream);
- if (!ident)
- return nullptr;
- switch (ident->to_identifier()) {
- case ValueID::JumpStart:
- case ValueID::JumpEnd:
- case ValueID::JumpNone:
- case ValueID::Start:
- case ValueID::End:
- values.append(*ident);
- break;
- default:
- return nullptr;
- }
- }
- }
-
- ++argument_index;
}
+
+ // Perform extra validation
+ // https://drafts.csswg.org/css-easing/#funcdef-step-easing-function-steps
+ // The first parameter specifies the number of intervals in the function. It must be a positive integer greater than 0
+ // unless the second parameter is jump-none in which case it must be a positive integer greater than 1.
+ if (steps.position == EasingStyleValue::Steps::Position::JumpNone) {
+ if (intervals < 1)
+ return nullptr;
+ } else if (intervals < 0) {
+ return nullptr;
+ }
+
+ steps.number_of_intervals = intervals;
+ transaction.commit();
+ return EasingStyleValue::create(steps);
}
- if (argument_index < function_metadata.parameters.size() && !function_metadata.parameters[argument_index].is_optional) {
- dbgln_if(CSS_PARSER_DEBUG, "Required parameter at position {} is missing", argument_index);
- return nullptr;
- }
-
- transaction.commit();
- return EasingStyleValue::create(function, move(values));
+ return nullptr;
}
// https://www.w3.org/TR/css-transforms-1/#transform-property
@@ -5483,7 +5550,7 @@ RefPtr Parser::parse_transition_value(TokenStream& t
transition.property_name = CustomIdentStyleValue::create("all"_fly_string);
if (!transition.easing)
- transition.easing = EasingStyleValue::create(EasingFunction::Ease, {});
+ transition.easing = EasingStyleValue::create(EasingStyleValue::CubicBezier::ease());
transitions.append(move(transition));
diff --git a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp
index 9401647155..de92b0041a 100644
--- a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp
+++ b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp
@@ -1557,7 +1557,8 @@ static void apply_animation_properties(DOM::Document& document, StyleProperties&
play_state = *play_state_value;
}
- Animations::TimingFunction timing_function = Animations::ease_timing_function;
+ static Animations::TimingFunction ease_timing_function = Animations::TimingFunction::from_easing_style_value(*CSS::EasingStyleValue::create(CSS::EasingStyleValue::CubicBezier::ease()));
+ Animations::TimingFunction timing_function = ease_timing_function;
if (auto timing_property = style.maybe_null_property(PropertyID::AnimationTimingFunction); timing_property && timing_property->is_easing())
timing_function = Animations::TimingFunction::from_easing_style_value(timing_property->as_easing());
diff --git a/Userland/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.cpp b/Userland/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.cpp
index 331e059545..3ddf1bfe35 100644
--- a/Userland/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.cpp
+++ b/Userland/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.cpp
@@ -13,33 +13,107 @@
namespace Web::CSS {
+// 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 { 0.25, 0.1, 0.25, 1.0 };
+ return bezier;
+}
+
+EasingStyleValue::CubicBezier EasingStyleValue::CubicBezier::ease_in()
+{
+ static CubicBezier bezier { 0.42, 0.0, 1.0, 1.0 };
+ return bezier;
+}
+
+EasingStyleValue::CubicBezier EasingStyleValue::CubicBezier::ease_out()
+{
+ static CubicBezier bezier { 0.0, 0.0, 0.58, 1.0 };
+ return bezier;
+}
+
+EasingStyleValue::CubicBezier EasingStyleValue::CubicBezier::ease_in_out()
+{
+ static CubicBezier bezier { 0.42, 0.0, 0.58, 1.0 };
+ return bezier;
+}
+
+EasingStyleValue::Steps EasingStyleValue::Steps::step_start()
+{
+ static Steps steps { 1, Steps::Position::Start };
+ return steps;
+}
+
+EasingStyleValue::Steps EasingStyleValue::Steps::step_end()
+{
+ static Steps steps { 1, Steps::Position::End };
+ return steps;
+}
+
String EasingStyleValue::to_string() const
{
- if (m_properties.easing_function == EasingFunction::StepStart)
- return "steps(1, start)"_string;
- if (m_properties.easing_function == EasingFunction::StepEnd)
- return "steps(1, end)"_string;
-
StringBuilder builder;
- builder.append(CSS::to_string(m_properties.easing_function));
+ m_function.visit(
+ [&](Linear const& linear) {
+ builder.append("linear"sv);
+ if (!linear.stops.is_empty()) {
+ builder.append('(');
- if (m_properties.values.is_empty())
- return MUST(builder.to_string());
-
- builder.append('(');
- for (size_t i = 0; i < m_properties.values.size(); ++i) {
- builder.append(m_properties.values[i]->to_string());
- if (i != m_properties.values.size() - 1)
- builder.append(", "sv);
- }
- builder.append(')');
+ bool first = true;
+ for (auto const& stop : linear.stops) {
+ if (!first)
+ builder.append(", "sv);
+ first = false;
+ builder.appendff("{}"sv, stop.offset);
+ if (stop.position.has_value())
+ builder.appendff(" {}"sv, stop.position.value());
+ }
+ builder.append(')');
+ }
+ },
+ [&](CubicBezier const& bezier) {
+ if (bezier == CubicBezier::ease()) {
+ builder.append("ease"sv);
+ } else if (bezier == CubicBezier::ease_in()) {
+ builder.append("ease-in"sv);
+ } else if (bezier == CubicBezier::ease_out()) {
+ builder.append("ease-out"sv);
+ } else if (bezier == CubicBezier::ease_in_out()) {
+ builder.append("ease-in-out"sv);
+ } else {
+ builder.appendff("cubic-bezier({}, {}, {}, {})", bezier.x1, bezier.y1, bezier.x2, bezier.y2);
+ }
+ },
+ [&](Steps const& steps) {
+ if (steps == Steps::step_start()) {
+ builder.append("step-start"sv);
+ } else if (steps == Steps::step_end()) {
+ builder.append("step-end"sv);
+ } else {
+ auto position = [&] -> Optional {
+ switch (steps.position) {
+ case Steps::Position::JumpStart:
+ return "jump-start"sv;
+ case Steps::Position::JumpNone:
+ return "jump-none"sv;
+ case Steps::Position::JumpBoth:
+ return "jump-both"sv;
+ case Steps::Position::Start:
+ return "start"sv;
+ default:
+ return {};
+ }
+ }();
+ if (position.has_value()) {
+ builder.appendff("steps({}, {})", steps.number_of_intervals, position.value());
+ } else {
+ builder.appendff("steps({})", steps.number_of_intervals);
+ }
+ }
+ });
return MUST(builder.to_string());
}
-bool EasingStyleValue::Properties::operator==(Properties const& other) const
-{
- return easing_function == other.easing_function && values == other.values;
-}
-
}
diff --git a/Userland/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.h b/Userland/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.h
index fae12ac36b..81ec6442c4 100644
--- a/Userland/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.h
+++ b/Userland/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.h
@@ -10,38 +10,80 @@
#pragma once
-#include
#include
namespace Web::CSS {
class EasingStyleValue final : public StyleValueWithDefaultOperators {
public:
- static ValueComparingNonnullRefPtr create(CSS::EasingFunction easing_function, StyleValueVector&& values)
+ struct Linear {
+ struct Stop {
+ double offset;
+ Optional position;
+
+ bool operator==(Stop const&) const = default;
+ };
+
+ Vector stops;
+
+ bool operator==(Linear const&) const = default;
+ };
+
+ struct CubicBezier {
+ static CubicBezier ease();
+ static CubicBezier ease_in();
+ static CubicBezier ease_out();
+ static CubicBezier ease_in_out();
+
+ double x1;
+ double y1;
+ double x2;
+ double y2;
+
+ bool operator==(CubicBezier const&) const = default;
+ };
+
+ struct Steps {
+ enum class Position {
+ JumpStart,
+ JumpEnd,
+ JumpNone,
+ JumpBoth,
+ Start,
+ End,
+ };
+
+ static Steps step_start();
+ static Steps step_end();
+
+ unsigned int number_of_intervals;
+ Position position { Position::End };
+
+ bool operator==(Steps const&) const = default;
+ };
+
+ using Function = Variant;
+
+ static ValueComparingNonnullRefPtr create(Function const& function)
{
- return adopt_ref(*new (nothrow) EasingStyleValue(easing_function, move(values)));
+ return adopt_ref(*new (nothrow) EasingStyleValue(function));
}
virtual ~EasingStyleValue() override = default;
- CSS::EasingFunction easing_function() const { return m_properties.easing_function; }
- StyleValueVector values() const { return m_properties.values; }
+ Function const& function() const { return m_function; }
virtual String to_string() const override;
- bool properties_equal(EasingStyleValue const& other) const { return m_properties == other.m_properties; }
+ bool properties_equal(EasingStyleValue const& other) const { return m_function == other.m_function; }
private:
- EasingStyleValue(CSS::EasingFunction easing_function, StyleValueVector&& values)
+ EasingStyleValue(Function function)
: StyleValueWithDefaultOperators(Type::Easing)
- , m_properties { .easing_function = easing_function, .values = move(values) }
+ , m_function(function)
{
}
- struct Properties {
- CSS::EasingFunction easing_function;
- StyleValueVector values;
- bool operator==(Properties const& other) const;
- } m_properties;
+ Function m_function;
};
}