diff --git a/Meta/CMake/libweb_generators.cmake b/Meta/CMake/libweb_generators.cmake index 4d28dea77d..2c3ac62cbd 100644 --- a/Meta/CMake/libweb_generators.cmake +++ b/Meta/CMake/libweb_generators.cmake @@ -45,6 +45,15 @@ function (generate_css_implementation) arguments -j "${LIBWEB_INPUT_FOLDER}/CSS/Properties.json" ) + invoke_generator( + "PseudoClass.cpp" + Lagom::GenerateCSSPseudoClass + "${LIBWEB_INPUT_FOLDER}/CSS/PseudoClasses.json" + "CSS/PseudoClass.h" + "CSS/PseudoClass.cpp" + arguments -j "${LIBWEB_INPUT_FOLDER}/CSS/PseudoClasses.json" + ) + invoke_generator( "TransformFunctions.cpp" Lagom::GenerateCSSTransformFunctions diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt b/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt index 36bf2da12b..7c50a50591 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt @@ -5,6 +5,7 @@ lagom_tool(GenerateCSSEnums SOURCES GenerateCSSEnums.cpp LIBS Li lagom_tool(GenerateCSSMathFunctions SOURCES GenerateCSSMathFunctions.cpp LIBS LibMain) lagom_tool(GenerateCSSMediaFeatureID SOURCES GenerateCSSMediaFeatureID.cpp LIBS LibMain) lagom_tool(GenerateCSSPropertyID SOURCES GenerateCSSPropertyID.cpp LIBS LibMain) +lagom_tool(GenerateCSSPseudoClass SOURCES GenerateCSSPseudoClass.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) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSPseudoClass.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSPseudoClass.cpp new file mode 100644 index 0000000000..88808dfac3 --- /dev/null +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSPseudoClass.cpp @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2022-2023, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "GeneratorUtil.h" +#include +#include +#include + +ErrorOr generate_header_file(JsonObject& pseudo_classes_data, Core::File& file); +ErrorOr generate_implementation_file(JsonObject& pseudo_classes_data, Core::File& file); + +ErrorOr serenity_main(Main::Arguments arguments) +{ + StringView generated_header_path; + StringView generated_implementation_path; + StringView identifiers_json_path; + + Core::ArgsParser args_parser; + args_parser.add_option(generated_header_path, "Path to the PseudoClasses header file to generate", "generated-header-path", 'h', "generated-header-path"); + args_parser.add_option(generated_implementation_path, "Path to the PseudoClasses implementation file to generate", "generated-implementation-path", 'c', "generated-implementation-path"); + args_parser.add_option(identifiers_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(identifiers_json_path)); + VERIFY(json.is_object()); + auto 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(data, *generated_header_file)); + TRY(generate_implementation_file(data, *generated_implementation_file)); + + return 0; +} + +ErrorOr generate_header_file(JsonObject& pseudo_classes_data, Core::File& file) +{ + StringBuilder builder; + SourceGenerator generator { builder }; + + TRY(generator.try_append(R"~~~( +#pragma once + +#include +#include + +namespace Web::CSS { + +enum class PseudoClass { +)~~~")); + + TRY(pseudo_classes_data.try_for_each_member([&](auto& name, auto&) -> ErrorOr { + 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_append(R"~~~( +}; + +Optional pseudo_class_from_string(StringView); +StringView pseudo_class_name(PseudoClass); + +struct PseudoClassMetadata { + enum class ParameterType { + None, + ANPlusB, + ANPlusBOf, + ForgivingSelectorList, + LanguageRanges, + SelectorList, + } parameter_type; + bool is_valid_as_function; + bool is_valid_as_identifier; +}; +PseudoClassMetadata pseudo_class_metadata(PseudoClass); + +} +)~~~")); + + TRY(file.write_until_depleted(generator.as_string_view().bytes())); + return {}; +} + +ErrorOr generate_implementation_file(JsonObject& pseudo_classes_data, Core::File& file) +{ + StringBuilder builder; + SourceGenerator generator { builder }; + + TRY(generator.try_append(R"~~~( +#include + +namespace Web::CSS { + +Optional pseudo_class_from_string(StringView string) +{ +)~~~")); + + TRY(pseudo_classes_data.try_for_each_member([&](auto& name, auto&) -> ErrorOr { + 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 (string.equals_ignoring_ascii_case("@name@"sv)) + return PseudoClass::@name:titlecase@; +)~~~")); + return {}; + })); + + TRY(generator.try_append(R"~~~( + + return {}; +} + +StringView pseudo_class_name(PseudoClass pseudo_class) +{ + switch (pseudo_class) { +)~~~")); + + TRY(pseudo_classes_data.try_for_each_member([&](auto& name, auto&) -> ErrorOr { + 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 PseudoClass::@name:titlecase@: + return "@name@"sv; +)~~~")); + return {}; + })); + + TRY(generator.try_append(R"~~~( + } + VERIFY_NOT_REACHED(); +} + +PseudoClassMetadata pseudo_class_metadata(PseudoClass pseudo_class) +{ + switch (pseudo_class) { +)~~~")); + + TRY(pseudo_classes_data.try_for_each_member([&](auto& name, JsonValue const& value) -> ErrorOr { + auto member_generator = TRY(generator.fork()); + auto& pseudo_class = value.as_object(); + auto argument_string = pseudo_class.get_deprecated_string("argument"sv).value(); + + bool is_valid_as_identifier = argument_string.is_empty(); + bool is_valid_as_function = !argument_string.is_empty(); + + if (argument_string.ends_with('?')) { + is_valid_as_identifier = true; + argument_string = argument_string.substring(0, argument_string.length() - 1); + } + + String parameter_type = "None"_string; + if (is_valid_as_function) { + if (argument_string == ""sv) { + parameter_type = "ANPlusB"_string; + } else if (argument_string == ""sv) { + parameter_type = "ANPlusBOf"_string; + } else if (argument_string == ""sv) { + parameter_type = "ForgivingSelectorList"_string; + } else if (argument_string == ""sv) { + parameter_type = "LanguageRanges"_string; + } else if (argument_string == ""sv) { + parameter_type = "SelectorList"_string; + } else { + warnln("Unrecognized pseudo-class argument type: `{}`", argument_string); + VERIFY_NOT_REACHED(); + } + } + + TRY(member_generator.set("name:titlecase", TRY(title_casify(name)))); + TRY(member_generator.set("parameter_type", parameter_type)); + TRY(member_generator.set("is_valid_as_function", is_valid_as_function ? "true"_string : "false"_string)); + TRY(member_generator.set("is_valid_as_identifier", is_valid_as_identifier ? "true"_string : "false"_string)); + + TRY(member_generator.try_append(R"~~~( + case PseudoClass::@name:titlecase@: + return { + .parameter_type = PseudoClassMetadata::ParameterType::@parameter_type@, + .is_valid_as_function = @is_valid_as_function@, + .is_valid_as_identifier = @is_valid_as_identifier@, + }; +)~~~")); + return {}; + })); + + TRY(generator.try_append(R"~~~( + } + VERIFY_NOT_REACHED(); +} + +} +)~~~")); + + TRY(file.write_until_depleted(generator.as_string_view().bytes())); + return {}; +} diff --git a/Meta/gn/secondary/Userland/Libraries/LibWeb/BUILD.gn b/Meta/gn/secondary/Userland/Libraries/LibWeb/BUILD.gn index a1335d70d7..35ac4b22f5 100644 --- a/Meta/gn/secondary/Userland/Libraries/LibWeb/BUILD.gn +++ b/Meta/gn/secondary/Userland/Libraries/LibWeb/BUILD.gn @@ -164,6 +164,23 @@ compiled_action("generate_css_property_id") { ] } +compiled_action("generate_css_pseudo_class") { + tool = "//Meta/Lagom/Tools/CodeGenerators/LibWeb:GenerateCSSPseudoClass" + inputs = [ "CSS/PseudoClasses.json" ] + outputs = [ + "$target_gen_dir/CSS/PseudoClass.h", + "$target_gen_dir/CSS/PseudoClass.cpp", + ] + args = [ + "-h", + rebase_path(outputs[0], root_build_dir), + "-c", + rebase_path(outputs[1], root_build_dir), + "-j", + rebase_path(inputs[0], root_build_dir), + ] +} + compiled_action("generate_css_transform_functions") { tool = "//Meta/Lagom/Tools/CodeGenerators/LibWeb:GenerateCSSTransformFunctions" @@ -228,6 +245,7 @@ source_set("all_generated") { ":generate_css_math_functions", ":generate_css_media_feature_id", ":generate_css_property_id", + ":generate_css_pseudo_class", ":generate_css_transform_functions", ":generate_css_value_id", ":generate_default_stylesheet_source", diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index f4d0066e4f..70ad8fba51 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -630,6 +630,7 @@ set(GENERATED_SOURCES CSS/MathFunctions.cpp CSS/MediaFeatureID.cpp CSS/PropertyID.cpp + CSS/PseudoClass.cpp CSS/QuirksModeStyleSheetSource.cpp CSS/TransformFunctions.cpp CSS/ValueID.cpp diff --git a/Userland/Libraries/LibWeb/CSS/PseudoClasses.json b/Userland/Libraries/LibWeb/CSS/PseudoClasses.json new file mode 100644 index 0000000000..942fcd5746 --- /dev/null +++ b/Userland/Libraries/LibWeb/CSS/PseudoClasses.json @@ -0,0 +1,116 @@ +{ + "active": { + "argument": "" + }, + "buffering": { + "argument": "" + }, + "checked": { + "argument": "" + }, + "defined": { + "argument": "" + }, + "disabled": { + "argument": "" + }, + "empty": { + "argument": "" + }, + "enabled": { + "argument": "" + }, + "first-child": { + "argument": "" + }, + "first-of-type": { + "argument": "" + }, + "focus": { + "argument": "" + }, + "focus-visible": { + "argument": "" + }, + "focus-within": { + "argument": "" + }, + "host": { + "argument": "?" + }, + "hover": { + "argument": "" + }, + "indeterminate": { + "argument": "" + }, + "is": { + "argument": "" + }, + "lang": { + "argument": "" + }, + "last-child": { + "argument": "" + }, + "last-of-type": { + "argument": "" + }, + "link": { + "argument": "" + }, + "muted": { + "argument": "" + }, + "not": { + "argument": "" + }, + "nth-child": { + "argument": "" + }, + "nth-last-child": { + "argument": "" + }, + "nth-last-of-type": { + "argument": "" + }, + "nth-of-type": { + "argument": "" + }, + "only-child": { + "argument": "" + }, + "only-of-type": { + "argument": "" + }, + "paused": { + "argument": "" + }, + "playing": { + "argument": "" + }, + "root": { + "argument": "" + }, + "scope": { + "argument": "" + }, + "seeking": { + "argument": "" + }, + "stalled": { + "argument": "" + }, + "target": { + "argument": "" + }, + "visited": { + "argument": "" + }, + "volume-locked": { + "argument": "" + }, + "where": { + "argument": "" + } +}