Sfoglia il codice sorgente

Meta/CodeGenerators+LibWeb: Implement parsing CSS easing functions

This only implements the parser bits, no functionality is implemented,
and no properties are parsed because of this.
Ali Mohammad Pur 2 anni fa
parent
commit
dd073b2711

+ 9 - 0
Meta/CMake/libweb_generators.cmake

@@ -1,5 +1,14 @@
 function (generate_css_implementation)
 function (generate_css_implementation)
     set(LIBWEB_INPUT_FOLDER "${CMAKE_CURRENT_SOURCE_DIR}")
     set(LIBWEB_INPUT_FOLDER "${CMAKE_CURRENT_SOURCE_DIR}")
+    invoke_generator(
+            "EasingFunctions.cpp"
+            Lagom::GenerateCSSEasingFunctions
+            "${LIBWEB_INPUT_FOLDER}/CSS/EasingFunctions.json"
+            "CSS/EasingFunctions.h"
+            "CSS/EasingFunctions.cpp"
+            arguments -j "${LIBWEB_INPUT_FOLDER}/CSS/EasingFunctions.json"
+    )
+
     invoke_generator(
     invoke_generator(
         "Enums.cpp"
         "Enums.cpp"
         Lagom::GenerateCSSEnums
         Lagom::GenerateCSSEnums

+ 6 - 5
Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt

@@ -1,10 +1,11 @@
 set(SOURCES "") # avoid pulling SOURCES from parent scope
 set(SOURCES "") # avoid pulling SOURCES from parent scope
 
 
-lagom_tool(GenerateCSSEnums                SOURCES GenerateCSSEnums.cpp LIBS LibMain)
-lagom_tool(GenerateCSSMediaFeatureID       SOURCES GenerateCSSMediaFeatureID.cpp LIBS LibMain)
-lagom_tool(GenerateCSSPropertyID           SOURCES GenerateCSSPropertyID.cpp LIBS LibMain)
-lagom_tool(GenerateCSSTransformFunctions   SOURCES GenerateCSSTransformFunctions.cpp LIBS LibMain)
-lagom_tool(GenerateCSSValueID              SOURCES GenerateCSSValueID.cpp LIBS LibMain)
+lagom_tool(GenerateCSSEasingFunctions       SOURCES GenerateCSSEasingFunctions.cpp LIBS LibMain)
+lagom_tool(GenerateCSSEnums                 SOURCES GenerateCSSEnums.cpp LIBS LibMain)
+lagom_tool(GenerateCSSMediaFeatureID        SOURCES GenerateCSSMediaFeatureID.cpp LIBS LibMain)
+lagom_tool(GenerateCSSPropertyID            SOURCES GenerateCSSPropertyID.cpp LIBS LibMain)
+lagom_tool(GenerateCSSTransformFunctions    SOURCES GenerateCSSTransformFunctions.cpp LIBS LibMain)
+lagom_tool(GenerateCSSValueID               SOURCES GenerateCSSValueID.cpp LIBS LibMain)
 lagom_tool(GenerateWindowOrWorkerInterfaces SOURCES GenerateWindowOrWorkerInterfaces.cpp LIBS LibMain LibIDL)
 lagom_tool(GenerateWindowOrWorkerInterfaces SOURCES GenerateWindowOrWorkerInterfaces.cpp LIBS LibMain LibIDL)
 lagom_tool(GenerateAriaRoles                SOURCES GenerateAriaRoles.cpp LIBS LibMain)
 lagom_tool(GenerateAriaRoles                SOURCES GenerateAriaRoles.cpp LIBS LibMain)
 
 

+ 219 - 0
Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSEasingFunctions.cpp

@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2022-2023, Sam Atkins <atkinssj@serenityos.org>
+ * Copyright (c) 2023, Ali Mohammad Pur <mpfard@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "GeneratorUtil.h"
+#include <AK/GenericLexer.h>
+#include <AK/SourceGenerator.h>
+#include <LibCore/ArgsParser.h>
+#include <LibMain/Main.h>
+
+ErrorOr<void> generate_header_file(JsonObject&, Core::File&);
+ErrorOr<void> generate_implementation_file(JsonObject&, Core::File&);
+
+ErrorOr<int> serenity_main(Main::Arguments arguments)
+{
+    StringView generated_header_path;
+    StringView generated_implementation_path;
+    StringView functions_json_path;
+
+    Core::ArgsParser args_parser;
+    args_parser.add_option(generated_header_path, "Path to the EasingFunctions header file to generate", "generated-header-path", 'h', "generated-header-path");
+    args_parser.add_option(generated_implementation_path, "Path to the EasingFunctions implementation file to generate", "generated-implementation-path", 'c', "generated-implementation-path");
+    args_parser.add_option(functions_json_path, "Path to the JSON file to read from", "json-path", 'j', "json-path");
+    args_parser.parse(arguments);
+
+    auto json = TRY(read_entire_file_as_json(functions_json_path));
+    VERIFY(json.is_object());
+    auto easing_data = json.as_object();
+
+    auto generated_header_file = TRY(Core::File::open(generated_header_path, Core::File::OpenMode::Write));
+    auto generated_implementation_file = TRY(Core::File::open(generated_implementation_path, Core::File::OpenMode::Write));
+
+    TRY(generate_header_file(easing_data, *generated_header_file));
+    TRY(generate_implementation_file(easing_data, *generated_implementation_file));
+
+    return 0;
+}
+
+ErrorOr<void> generate_header_file(JsonObject& easing_data, Core::File& file)
+{
+    StringBuilder builder;
+    SourceGenerator generator { builder };
+
+    TRY(generator.try_append(R"~~~(
+#pragma once
+
+#include <AK/Optional.h>
+#include <AK/StringView.h>
+#include <AK/Vector.h>
+
+namespace Web::CSS {
+
+)~~~"));
+
+    TRY(generator.try_appendln("enum class EasingFunction {"));
+    TRY(easing_data.try_for_each_member([&](auto& name, auto&) -> ErrorOr<void> {
+        auto member_generator = TRY(generator.fork());
+        TRY(member_generator.set("name:titlecase", TRY(title_casify(name))));
+        TRY(member_generator.try_appendln("    @name:titlecase@,"));
+        return {};
+    }));
+    TRY(generator.try_appendln("};"));
+
+    TRY(generator.try_appendln("Optional<EasingFunction> easing_function_from_string(StringView);"));
+    TRY(generator.try_appendln("StringView to_string(EasingFunction);"));
+
+    TRY(generator.try_append(R"~~~(
+enum class EasingFunctionParameterType {
+    Integer,
+    Number,
+    NumberZeroToOne,
+    StepPosition,
+};
+
+struct EasingFunctionParameter {
+    EasingFunctionParameterType type;
+    bool is_optional { false };
+};
+
+struct EasingFunctionMetadata {
+    Vector<EasingFunctionParameter> parameters;
+};
+EasingFunctionMetadata easing_function_metadata(EasingFunction);
+)~~~"));
+
+    TRY(generator.try_appendln("\n}"));
+
+    TRY(file.write_until_depleted(generator.as_string_view().bytes()));
+    return {};
+}
+
+ErrorOr<void> generate_implementation_file(JsonObject& easing_data, Core::File& file)
+{
+    StringBuilder builder;
+    SourceGenerator generator { builder };
+
+    TRY(generator.try_append(R"~~~(
+#include <LibWeb/CSS/EasingFunctions.h>
+#include <AK/Assertions.h>
+
+namespace Web::CSS {
+)~~~"));
+
+    TRY(generator.try_append(R"~~~(
+Optional<EasingFunction> easing_function_from_string(StringView name)
+{
+)~~~"));
+    TRY(easing_data.try_for_each_member([&](auto& name, auto&) -> ErrorOr<void> {
+        auto member_generator = TRY(generator.fork());
+        TRY(member_generator.set("name", TRY(String::from_deprecated_string(name))));
+        TRY(member_generator.set("name:titlecase", TRY(title_casify(name))));
+        TRY(member_generator.try_append(R"~~~(
+    if (name.equals_ignoring_ascii_case("@name@"sv))
+        return EasingFunction::@name:titlecase@;
+)~~~"));
+        return {};
+    }));
+    TRY(generator.try_append(R"~~~(
+    return {};
+}
+)~~~"));
+
+    TRY(generator.try_append(R"~~~(
+StringView to_string(EasingFunction easing_function)
+{
+    switch (easing_function) {
+)~~~"));
+    TRY(easing_data.try_for_each_member([&](auto& name, auto&) -> ErrorOr<void> {
+        auto member_generator = TRY(generator.fork());
+        TRY(member_generator.set("name", TRY(String::from_deprecated_string(name))));
+        TRY(member_generator.set("name:titlecase", TRY(title_casify(name))));
+        TRY(member_generator.try_append(R"~~~(
+    case EasingFunction::@name:titlecase@:
+        return "@name@"sv;
+)~~~"));
+        return {};
+    }));
+    TRY(generator.try_append(R"~~~(
+    default:
+        VERIFY_NOT_REACHED();
+    }
+}
+)~~~"));
+
+    TRY(generator.try_append(R"~~~(
+EasingFunctionMetadata easing_function_metadata(EasingFunction easing_function)
+{
+    switch (easing_function) {
+)~~~"));
+    TRY(easing_data.try_for_each_member([&](auto& name, auto& value) -> ErrorOr<void> {
+        VERIFY(value.is_object());
+
+        auto member_generator = TRY(generator.fork());
+        TRY(member_generator.set("name:titlecase", TRY(title_casify(name))));
+        TRY(member_generator.try_append(R"~~~(
+    case EasingFunction::@name:titlecase@:
+        return EasingFunctionMetadata {
+            .parameters = {)~~~"));
+
+        if (auto parameters = value.as_object().get_array("parameters"sv); parameters.has_value()) {
+            bool first = true;
+            // parameters: [ "<foo>", "<foo [0, 1]>" ]
+            TRY(parameters.value().try_for_each([&](JsonValue const& value) -> ErrorOr<void> {
+                GenericLexer lexer { value.as_string() };
+                VERIFY(lexer.consume_specific('<'));
+                auto parameter_type_name = lexer.consume_until([](char ch) { return ch == ' ' || ch == '>'; });
+                auto has_bounds = false;
+                auto is_optional = false;
+                if (lexer.consume_specific(" [")) {
+                    has_bounds = true;
+                    auto contents = lexer.consume_until(']');
+                    VERIFY(contents == "0, 1"sv);
+                    VERIFY(lexer.consume_specific(']'));
+                }
+                VERIFY(lexer.consume_specific('>'));
+                if (lexer.consume_specific('?'))
+                    is_optional = true;
+
+                StringView parameter_type = ""sv;
+                if (parameter_type_name == "number"sv)
+                    parameter_type = has_bounds ? "NumberZeroToOne"sv : "Number"sv;
+                else if (parameter_type_name == "integer"sv)
+                    parameter_type = "Integer"sv;
+                else if (parameter_type_name == "step-position"sv)
+                    parameter_type = "StepPosition"sv;
+                else
+                    VERIFY_NOT_REACHED();
+
+                TRY(member_generator.try_append(first ? " "sv : ", "sv));
+                first = false;
+
+                TRY(member_generator.try_append(TRY(String::formatted(
+                    "{{ EasingFunctionParameterType::{}, {} }}",
+                    parameter_type,
+                    is_optional ? "true"sv : "false"sv))));
+                return {};
+            }));
+        }
+
+        TRY(member_generator.try_append(R"~~~( }
+    };
+)~~~"));
+        return {};
+    }));
+    TRY(generator.try_append(R"~~~(
+    default:
+        VERIFY_NOT_REACHED();
+    }
+}
+)~~~"));
+
+    TRY(generator.try_appendln("\n}"));
+
+    TRY(file.write_until_depleted(generator.as_string_view().bytes()));
+    return {};
+}

+ 2 - 0
Userland/Libraries/LibWeb/CMakeLists.txt

@@ -89,6 +89,7 @@ set(SOURCES
     CSS/StyleValues/ConicGradientStyleValue.cpp
     CSS/StyleValues/ConicGradientStyleValue.cpp
     CSS/StyleValues/ContentStyleValue.cpp
     CSS/StyleValues/ContentStyleValue.cpp
     CSS/StyleValues/DisplayStyleValue.cpp
     CSS/StyleValues/DisplayStyleValue.cpp
+    CSS/StyleValues/EasingStyleValue.cpp
     CSS/StyleValues/EdgeStyleValue.cpp
     CSS/StyleValues/EdgeStyleValue.cpp
     CSS/StyleValues/FilterValueListStyleValue.cpp
     CSS/StyleValues/FilterValueListStyleValue.cpp
     CSS/StyleValues/FlexFlowStyleValue.cpp
     CSS/StyleValues/FlexFlowStyleValue.cpp
@@ -612,6 +613,7 @@ generate_css_implementation()
 set(GENERATED_SOURCES
 set(GENERATED_SOURCES
     ARIA/AriaRoles.cpp
     ARIA/AriaRoles.cpp
     CSS/DefaultStyleSheetSource.cpp
     CSS/DefaultStyleSheetSource.cpp
+    CSS/EasingFunctions.cpp
     CSS/Enums.cpp
     CSS/Enums.cpp
     CSS/MediaFeatureID.cpp
     CSS/MediaFeatureID.cpp
     CSS/PropertyID.cpp
     CSS/PropertyID.cpp

+ 23 - 0
Userland/Libraries/LibWeb/CSS/EasingFunctions.json

@@ -0,0 +1,23 @@
+{
+  "linear": {},
+  "ease": {},
+  "ease-in": {},
+  "ease-out": {},
+  "ease-in-out": {},
+  "cubic-bezier": {
+    "parameters": [
+      "<number [0, 1]>",
+      "<number>",
+      "<number [0, 1]>",
+      "<number>"
+    ]
+  },
+  "step-start": {},
+  "step-end": {},
+  "steps": {
+    "parameters": [
+      "<integer>",
+      "<step-position>?"
+    ]
+  }
+}

+ 4 - 0
Userland/Libraries/LibWeb/CSS/Identifiers.json

@@ -166,6 +166,10 @@
   "invert",
   "invert",
   "inverted",
   "inverted",
   "italic",
   "italic",
+  "jump-both",
+  "jump-end",
+  "jump-none",
+  "jump-start",
   "justify",
   "justify",
   "landscape",
   "landscape",
   "large",
   "large",

+ 103 - 0
Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp

@@ -45,6 +45,7 @@
 #include <LibWeb/CSS/StyleValues/ContentStyleValue.h>
 #include <LibWeb/CSS/StyleValues/ContentStyleValue.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/FlexFlowStyleValue.h>
 #include <LibWeb/CSS/StyleValues/FlexFlowStyleValue.h>
@@ -7039,6 +7040,108 @@ ErrorOr<RefPtr<StyleValue>> Parser::parse_text_decoration_line_value(TokenStream
     return StyleValueList::create(move(style_values), StyleValueList::Separator::Space);
     return StyleValueList::create(move(style_values), StyleValueList::Separator::Space);
 }
 }
 
 
+ErrorOr<RefPtr<StyleValue>> Parser::parse_easing_value(TokenStream<ComponentValue>& tokens)
+{
+    auto transaction = tokens.begin_transaction();
+
+    tokens.skip_whitespace();
+
+    auto const& part = tokens.next_token();
+
+    StringView name;
+    Optional<Vector<ComponentValue> 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 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());
+            return nullptr;
+        }
+        for (auto& argument_values : arguments_values) {
+            if (argument_values.size() != 1) {
+                dbgln_if(CSS_PARSER_DEBUG, "Too many values in argument to {}. max: 1", name);
+                return nullptr;
+            }
+
+            auto& value = argument_values[0];
+            switch (function_metadata.parameters[argument_index].type) {
+            case EasingFunctionParameterType::Number: {
+                if (value.is(Token::Type::Number))
+                    values.append(TRY(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(TRY(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(TRY(IntegerStyleValue::create(value.token().number().integer_value())));
+                else
+                    return nullptr;
+                break;
+            }
+            case EasingFunctionParameterType::StepPosition: {
+                if (!value.is(Token::Type::Ident))
+                    return nullptr;
+                auto ident = TRY(parse_identifier_value(value));
+                if (!ident)
+                    return nullptr;
+                switch (ident->to_identifier()) {
+                case ValueID::JumpStart:
+                case ValueID::JumpEnd:
+                case ValueID::JumpNone:
+                case ValueID::Start:
+                case ValueID::End:
+                    TRY(values.try_append(*ident));
+                    break;
+                default:
+                    return nullptr;
+                }
+            }
+            }
+
+            ++argument_index;
+        }
+    }
+
+    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));
+}
+
 ErrorOr<RefPtr<StyleValue>> Parser::parse_transform_value(Vector<ComponentValue> const& component_values)
 ErrorOr<RefPtr<StyleValue>> Parser::parse_transform_value(Vector<ComponentValue> const& component_values)
 {
 {
     StyleValueVector transformations;
     StyleValueVector transformations;

+ 1 - 0
Userland/Libraries/LibWeb/CSS/Parser/Parser.h

@@ -352,6 +352,7 @@ private:
     ErrorOr<RefPtr<StyleValue>> parse_single_shadow_value(TokenStream<ComponentValue>&, AllowInsetKeyword);
     ErrorOr<RefPtr<StyleValue>> parse_single_shadow_value(TokenStream<ComponentValue>&, AllowInsetKeyword);
     ErrorOr<RefPtr<StyleValue>> parse_text_decoration_value(Vector<ComponentValue> const&);
     ErrorOr<RefPtr<StyleValue>> parse_text_decoration_value(Vector<ComponentValue> const&);
     ErrorOr<RefPtr<StyleValue>> parse_text_decoration_line_value(TokenStream<ComponentValue>&);
     ErrorOr<RefPtr<StyleValue>> parse_text_decoration_line_value(TokenStream<ComponentValue>&);
+    ErrorOr<RefPtr<StyleValue>> parse_easing_value(TokenStream<ComponentValue>&);
     ErrorOr<RefPtr<StyleValue>> parse_transform_value(Vector<ComponentValue> const&);
     ErrorOr<RefPtr<StyleValue>> parse_transform_value(Vector<ComponentValue> const&);
     ErrorOr<RefPtr<StyleValue>> parse_transform_origin_value(Vector<ComponentValue> const&);
     ErrorOr<RefPtr<StyleValue>> parse_transform_origin_value(Vector<ComponentValue> const&);
     ErrorOr<RefPtr<StyleValue>> parse_grid_track_size_list(Vector<ComponentValue> const&, bool allow_separate_line_name_blocks = false);
     ErrorOr<RefPtr<StyleValue>> parse_grid_track_size_list(Vector<ComponentValue> const&, bool allow_separate_line_name_blocks = false);

+ 7 - 0
Userland/Libraries/LibWeb/CSS/StyleValue.cpp

@@ -25,6 +25,7 @@
 #include <LibWeb/CSS/StyleValues/ContentStyleValue.h>
 #include <LibWeb/CSS/StyleValues/ContentStyleValue.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/FlexFlowStyleValue.h>
 #include <LibWeb/CSS/StyleValues/FlexFlowStyleValue.h>
@@ -113,6 +114,12 @@ BorderRadiusStyleValue const& StyleValue::as_border_radius() const
     return static_cast<BorderRadiusStyleValue const&>(*this);
     return static_cast<BorderRadiusStyleValue const&>(*this);
 }
 }
 
 
+EasingStyleValue const& StyleValue::as_easing() const
+{
+    VERIFY(is_easing());
+    return static_cast<EasingStyleValue const&>(*this);
+}
+
 BorderRadiusShorthandStyleValue const& StyleValue::as_border_radius_shorthand() const
 BorderRadiusShorthandStyleValue const& StyleValue::as_border_radius_shorthand() const
 {
 {
     VERIFY(is_border_radius_shorthand());
     VERIFY(is_border_radius_shorthand());

+ 4 - 0
Userland/Libraries/LibWeb/CSS/StyleValue.h

@@ -101,6 +101,7 @@ public:
         Content,
         Content,
         CustomIdent,
         CustomIdent,
         Display,
         Display,
+        Easing,
         Edge,
         Edge,
         FilterValueList,
         FilterValueList,
         Flex,
         Flex,
@@ -158,6 +159,7 @@ public:
     bool is_content() const { return type() == Type::Content; }
     bool is_content() const { return type() == Type::Content; }
     bool is_custom_ident() const { return type() == Type::CustomIdent; }
     bool is_custom_ident() const { return type() == Type::CustomIdent; }
     bool is_display() const { return type() == Type::Display; }
     bool is_display() const { return type() == Type::Display; }
+    bool is_easing() const { return type() == Type::Easing; }
     bool is_edge() const { return type() == Type::Edge; }
     bool is_edge() const { return type() == Type::Edge; }
     bool is_filter_value_list() const { return type() == Type::FilterValueList; }
     bool is_filter_value_list() const { return type() == Type::FilterValueList; }
     bool is_flex() const { return type() == Type::Flex; }
     bool is_flex() const { return type() == Type::Flex; }
@@ -214,6 +216,7 @@ public:
     ContentStyleValue const& as_content() const;
     ContentStyleValue const& as_content() const;
     CustomIdentStyleValue const& as_custom_ident() const;
     CustomIdentStyleValue const& as_custom_ident() const;
     DisplayStyleValue const& as_display() const;
     DisplayStyleValue const& as_display() const;
+    EasingStyleValue const& as_easing() const;
     EdgeStyleValue const& as_edge() const;
     EdgeStyleValue const& as_edge() const;
     FilterValueListStyleValue const& as_filter_value_list() const;
     FilterValueListStyleValue const& as_filter_value_list() const;
     FlexFlowStyleValue const& as_flex_flow() const;
     FlexFlowStyleValue const& as_flex_flow() const;
@@ -267,6 +270,7 @@ public:
     ContentStyleValue& as_content() { return const_cast<ContentStyleValue&>(const_cast<StyleValue const&>(*this).as_content()); }
     ContentStyleValue& as_content() { return const_cast<ContentStyleValue&>(const_cast<StyleValue const&>(*this).as_content()); }
     CustomIdentStyleValue& as_custom_ident() { return const_cast<CustomIdentStyleValue&>(const_cast<StyleValue const&>(*this).as_custom_ident()); }
     CustomIdentStyleValue& as_custom_ident() { return const_cast<CustomIdentStyleValue&>(const_cast<StyleValue const&>(*this).as_custom_ident()); }
     DisplayStyleValue& as_display() { return const_cast<DisplayStyleValue&>(const_cast<StyleValue const&>(*this).as_display()); }
     DisplayStyleValue& as_display() { return const_cast<DisplayStyleValue&>(const_cast<StyleValue const&>(*this).as_display()); }
+    EasingStyleValue& as_easing() { return const_cast<EasingStyleValue&>(const_cast<StyleValue const&>(*this).as_easing()); }
     EdgeStyleValue& as_edge() { return const_cast<EdgeStyleValue&>(const_cast<StyleValue const&>(*this).as_edge()); }
     EdgeStyleValue& as_edge() { return const_cast<EdgeStyleValue&>(const_cast<StyleValue const&>(*this).as_edge()); }
     FilterValueListStyleValue& as_filter_value_list() { return const_cast<FilterValueListStyleValue&>(const_cast<StyleValue const&>(*this).as_filter_value_list()); }
     FilterValueListStyleValue& as_filter_value_list() { return const_cast<FilterValueListStyleValue&>(const_cast<StyleValue const&>(*this).as_filter_value_list()); }
     FlexFlowStyleValue& as_flex_flow() { return const_cast<FlexFlowStyleValue&>(const_cast<StyleValue const&>(*this).as_flex_flow()); }
     FlexFlowStyleValue& as_flex_flow() { return const_cast<FlexFlowStyleValue&>(const_cast<StyleValue const&>(*this).as_flex_flow()); }

+ 45 - 0
Userland/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.cpp

@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
+ * Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org>
+ * Copyright (c) 2022-2023, MacDue <macdue@dueutil.tech>
+ * Copyright (c) 2023, Ali Mohammad Pur <mpfard@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "EasingStyleValue.h"
+#include <AK/StringBuilder.h>
+
+namespace Web::CSS {
+
+ErrorOr<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;
+    TRY(builder.try_append(CSS::to_string(m_properties.easing_function)));
+
+    if (m_properties.values.is_empty())
+        return builder.to_string();
+
+    TRY(builder.try_append('('));
+    for (size_t i = 0; i < m_properties.values.size(); ++i) {
+        TRY(builder.try_append(TRY(m_properties.values[i]->to_string())));
+        if (i != m_properties.values.size() - 1)
+            TRY(builder.try_append(", "sv));
+    }
+    TRY(builder.try_append(')'));
+
+    return builder.to_string();
+}
+
+bool EasingStyleValue::Properties::operator==(Properties const& other) const
+{
+    return easing_function == other.easing_function && values == other.values;
+}
+
+}

+ 47 - 0
Userland/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.h

@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
+ * Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org>
+ * Copyright (c) 2022-2023, MacDue <macdue@dueutil.tech>
+ * Copyright (c) 2023, Ali Mohammad Pur <mpfard@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibWeb/CSS/EasingFunctions.h>
+#include <LibWeb/CSS/StyleValue.h>
+
+namespace Web::CSS {
+
+class EasingStyleValue final : public StyleValueWithDefaultOperators<EasingStyleValue> {
+public:
+    static ErrorOr<ValueComparingNonnullRefPtr<EasingStyleValue>> create(CSS::EasingFunction easing_function, StyleValueVector&& values)
+    {
+        return adopt_nonnull_ref_or_enomem(new (nothrow) EasingStyleValue(easing_function, move(values)));
+    }
+    virtual ~EasingStyleValue() override = default;
+
+    CSS::EasingFunction easing_function() const { return m_properties.easing_function; }
+    StyleValueVector values() const { return m_properties.values; }
+
+    virtual ErrorOr<String> to_string() const override;
+
+    bool properties_equal(EasingStyleValue const& other) const { return m_properties == other.m_properties; }
+
+private:
+    EasingStyleValue(CSS::EasingFunction easing_function, StyleValueVector&& values)
+        : StyleValueWithDefaultOperators(Type::Easing)
+        , m_properties { .easing_function = easing_function, .values = move(values) }
+    {
+    }
+
+    struct Properties {
+        CSS::EasingFunction easing_function;
+        StyleValueVector values;
+        bool operator==(Properties const& other) const;
+    } m_properties;
+};
+
+}

+ 1 - 0
Userland/Libraries/LibWeb/Forward.h

@@ -98,6 +98,7 @@ class ContentStyleValue;
 class CustomIdentStyleValue;
 class CustomIdentStyleValue;
 class Display;
 class Display;
 class DisplayStyleValue;
 class DisplayStyleValue;
+class EasingStyleValue;
 class EdgeStyleValue;
 class EdgeStyleValue;
 class ElementInlineCSSStyleDeclaration;
 class ElementInlineCSSStyleDeclaration;
 class ExplicitGridTrack;
 class ExplicitGridTrack;