diff --git a/Libraries/LibWeb/Animations/PseudoElementParsing.cpp b/Libraries/LibWeb/Animations/PseudoElementParsing.cpp index 3286d7bbf5a..dc7a43c567a 100644 --- a/Libraries/LibWeb/Animations/PseudoElementParsing.cpp +++ b/Libraries/LibWeb/Animations/PseudoElementParsing.cpp @@ -28,7 +28,7 @@ WebIDL::ExceptionOr> pseudo_eleme // 3. If value is one of the legacy Selectors Level 2 single-colon selectors (':before', ':after', ':first-letter', or ':first-line'), // then return the equivalent two-colon selector (e.g. '::before'). if (value.has_value() && value->is_one_of(":before", ":after", ":first-letter", ":first-line")) { - return CSS::Selector::PseudoElementSelector::from_string(MUST(value->substring_from_byte_offset(1))); + return CSS::pseudo_element_from_string(MUST(value->substring_from_byte_offset(1))); } // 4. Otherwise, return value. diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index d452403eaf0..09009f99d40 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -956,6 +956,7 @@ set(GENERATED_SOURCES CSS/MediaFeatureID.cpp CSS/PropertyID.cpp CSS/PseudoClass.cpp + CSS/PseudoElement.cpp CSS/QuirksModeStyleSheetSource.cpp CSS/TransformFunctions.cpp MathML/MathMLStyleSheetSource.cpp diff --git a/Libraries/LibWeb/CSS/Parser/SelectorParsing.cpp b/Libraries/LibWeb/CSS/Parser/SelectorParsing.cpp index 4d060b4989e..7db637b1286 100644 --- a/Libraries/LibWeb/CSS/Parser/SelectorParsing.cpp +++ b/Libraries/LibWeb/CSS/Parser/SelectorParsing.cpp @@ -418,15 +418,15 @@ Parser::ParseErrorOr Parser::parse_pseudo_simple_selec auto pseudo_name = name_token.token().ident(); - if (auto pseudo_element = Selector::PseudoElementSelector::from_string(pseudo_name); pseudo_element.has_value()) { + if (auto pseudo_element = pseudo_element_from_string(pseudo_name); pseudo_element.has_value()) { // :has() is fussy about pseudo-elements inside it - if (m_pseudo_class_context.contains_slow(PseudoClass::Has) && !is_has_allowed_pseudo_element(pseudo_element->type())) { + if (m_pseudo_class_context.contains_slow(PseudoClass::Has) && !is_has_allowed_pseudo_element(*pseudo_element)) { return ParseError::SyntaxError; } return Selector::SimpleSelector { .type = Selector::SimpleSelector::Type::PseudoElement, - .value = pseudo_element.release_value() + .value = Selector::PseudoElementSelector { pseudo_element.release_value() } }; } @@ -481,20 +481,20 @@ Parser::ParseErrorOr Parser::parse_pseudo_simple_selec // Single-colon syntax allowed for ::after, ::before, ::first-letter and ::first-line for compatibility. // https://www.w3.org/TR/selectors/#pseudo-element-syntax - if (auto pseudo_element = Selector::PseudoElementSelector::from_string(pseudo_name); pseudo_element.has_value()) { - switch (pseudo_element.value().type()) { + if (auto pseudo_element = pseudo_element_from_string(pseudo_name); pseudo_element.has_value()) { + switch (pseudo_element.value()) { case PseudoElement::After: case PseudoElement::Before: case PseudoElement::FirstLetter: case PseudoElement::FirstLine: // :has() is fussy about pseudo-elements inside it - if (m_pseudo_class_context.contains_slow(PseudoClass::Has) && !is_has_allowed_pseudo_element(pseudo_element->type())) { + if (m_pseudo_class_context.contains_slow(PseudoClass::Has) && !is_has_allowed_pseudo_element(pseudo_element.value())) { return ParseError::SyntaxError; } return Selector::SimpleSelector { .type = Selector::SimpleSelector::Type::PseudoElement, - .value = pseudo_element.value() + .value = Selector::PseudoElementSelector { pseudo_element.value() } }; default: break; diff --git a/Libraries/LibWeb/CSS/PseudoElements.json b/Libraries/LibWeb/CSS/PseudoElements.json new file mode 100644 index 00000000000..a2ee9eca0c9 --- /dev/null +++ b/Libraries/LibWeb/CSS/PseudoElements.json @@ -0,0 +1,43 @@ +{ + "after": { + "spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-after", + "is-generated": true + }, + "backdrop": { + "spec": "https://drafts.csswg.org/css-position-4/#selectordef-backdrop" + }, + "before": { + "spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-before", + "is-generated": true + }, + "details-content": { + "spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-details-content" + }, + "file-selector-button": { + "spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-file-selector-button" + }, + "fill": { + "spec": "https://drafts.csswg.org/css-forms-1/#selectordef-fill" + }, + "first-letter": { + "spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-first-letter" + }, + "first-line": { + "spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-first-line" + }, + "marker": { + "spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-marker" + }, + "placeholder": { + "spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-placeholder" + }, + "selection": { + "spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-selection" + }, + "thumb": { + "spec": "https://drafts.csswg.org/css-forms-1/#selectordef-thumb" + }, + "track": { + "spec": "https://drafts.csswg.org/css-forms-1/#selectordef-track" + } +} diff --git a/Libraries/LibWeb/CSS/Selector.cpp b/Libraries/LibWeb/CSS/Selector.cpp index c2c2e10dc9c..563af96677c 100644 --- a/Libraries/LibWeb/CSS/Selector.cpp +++ b/Libraries/LibWeb/CSS/Selector.cpp @@ -535,74 +535,6 @@ String serialize_a_group_of_selectors(SelectorList const& selectors) return MUST(String::join(", "sv, selectors)); } -StringView Selector::PseudoElementSelector::name(PseudoElement pseudo_element) -{ - switch (pseudo_element) { - case PseudoElement::Before: - return "before"sv; - case PseudoElement::After: - return "after"sv; - case PseudoElement::FirstLine: - return "first-line"sv; - case PseudoElement::FirstLetter: - return "first-letter"sv; - case PseudoElement::Marker: - return "marker"sv; - case PseudoElement::Track: - return "track"sv; - case PseudoElement::Fill: - return "fill"sv; - case PseudoElement::Thumb: - return "thumb"sv; - case PseudoElement::Placeholder: - return "placeholder"sv; - case PseudoElement::Selection: - return "selection"sv; - case PseudoElement::Backdrop: - return "backdrop"sv; - case PseudoElement::FileSelectorButton: - return "file-selector-button"sv; - case PseudoElement::DetailsContent: - return "details-content"sv; - case PseudoElement::KnownPseudoElementCount: - case PseudoElement::UnknownWebKit: - VERIFY_NOT_REACHED(); - } - VERIFY_NOT_REACHED(); -} - -Optional Selector::PseudoElementSelector::from_string(FlyString const& name) -{ - if (name.equals_ignoring_ascii_case("after"sv)) { - return Selector::PseudoElementSelector { PseudoElement::After }; - } else if (name.equals_ignoring_ascii_case("before"sv)) { - return Selector::PseudoElementSelector { PseudoElement::Before }; - } else if (name.equals_ignoring_ascii_case("first-letter"sv)) { - return Selector::PseudoElementSelector { PseudoElement::FirstLetter }; - } else if (name.equals_ignoring_ascii_case("first-line"sv)) { - return Selector::PseudoElementSelector { PseudoElement::FirstLine }; - } else if (name.equals_ignoring_ascii_case("marker"sv)) { - return Selector::PseudoElementSelector { PseudoElement::Marker }; - } else if (name.equals_ignoring_ascii_case("track"sv)) { - return Selector::PseudoElementSelector { PseudoElement::Track }; - } else if (name.equals_ignoring_ascii_case("fill"sv)) { - return Selector::PseudoElementSelector { PseudoElement::Fill }; - } else if (name.equals_ignoring_ascii_case("thumb"sv)) { - return Selector::PseudoElementSelector { PseudoElement::Thumb }; - } else if (name.equals_ignoring_ascii_case("placeholder"sv)) { - return Selector::PseudoElementSelector { PseudoElement::Placeholder }; - } else if (name.equals_ignoring_ascii_case("selection"sv)) { - return Selector::PseudoElementSelector { PseudoElement::Selection }; - } else if (name.equals_ignoring_ascii_case("backdrop"sv)) { - return Selector::PseudoElementSelector { PseudoElement::Backdrop }; - } else if (name.equals_ignoring_ascii_case("file-selector-button"sv)) { - return Selector::PseudoElementSelector { PseudoElement::FileSelectorButton }; - } else if (name.equals_ignoring_ascii_case("details-content"sv)) { - return Selector::PseudoElementSelector { PseudoElement::DetailsContent }; - } - return {}; -} - NonnullRefPtr Selector::relative_to(SimpleSelector const& parent) const { // To make us relative to the parent, prepend it to the list of compound selectors, diff --git a/Libraries/LibWeb/CSS/Selector.h b/Libraries/LibWeb/CSS/Selector.h index e7ab692aaac..0effa56ec85 100644 --- a/Libraries/LibWeb/CSS/Selector.h +++ b/Libraries/LibWeb/CSS/Selector.h @@ -14,34 +14,12 @@ #include #include #include +#include namespace Web::CSS { using SelectorList = Vector>; -enum class PseudoElement : u8 { - Before, - After, - FirstLine, - FirstLetter, - Marker, - Track, - Fill, - Thumb, - Placeholder, - Selection, - Backdrop, - FileSelectorButton, - DetailsContent, - - // Keep this last. - KnownPseudoElementCount, - - // https://www.w3.org/TR/selectors-4/#compat - // NOTE: This is not last as the 'unknown -webkit- pseudo-elements' are not stored as part of any Element. - UnknownWebKit, -}; - // This is a in the spec. https://www.w3.org/TR/selectors-4/#complex class Selector : public RefCounted { public: @@ -61,21 +39,17 @@ public: bool operator==(PseudoElementSelector const&) const = default; - static Optional from_string(FlyString const&); - [[nodiscard]] static bool is_known_pseudo_element_type(PseudoElement type) { return to_underlying(type) < to_underlying(PseudoElement::KnownPseudoElementCount); } - static StringView name(PseudoElement pseudo_element); - StringView name() const { if (!m_name.is_empty()) return m_name; - return name(m_type); + return pseudo_element_name(m_type); } PseudoElement type() const { return m_type; } diff --git a/Libraries/LibWeb/DOM/Element.cpp b/Libraries/LibWeb/DOM/Element.cpp index bf9bd43fb94..6782f0c8176 100644 --- a/Libraries/LibWeb/DOM/Element.cpp +++ b/Libraries/LibWeb/DOM/Element.cpp @@ -1401,7 +1401,7 @@ void Element::serialize_pseudo_elements_as_json(JsonArraySerializer(i)))))); + MUST(object.add("name"sv, MUST(String::formatted("::{}", CSS::pseudo_element_name(static_cast(i)))))); MUST(object.add("type"sv, "pseudo-element")); MUST(object.add("parent-id"sv, unique_id().value())); MUST(object.add("pseudo-element"sv, i)); diff --git a/Libraries/LibWeb/Dump.cpp b/Libraries/LibWeb/Dump.cpp index ac5b1443cba..bd01266c18e 100644 --- a/Libraries/LibWeb/Dump.cpp +++ b/Libraries/LibWeb/Dump.cpp @@ -99,7 +99,7 @@ void dump_tree(StringBuilder& builder, DOM::Node const& node) if (element.use_pseudo_element().has_value()) { for (int i = 0; i < indent; ++i) builder.append(" "sv); - builder.appendff(" (pseudo-element: {})\n", CSS::Selector::PseudoElementSelector::name(element.use_pseudo_element().value())); + builder.appendff(" (pseudo-element: {})\n", CSS::pseudo_element_name(element.use_pseudo_element().value())); } } else if (is(node)) { builder.appendff("\"{}\"\n", as(node).data()); diff --git a/Meta/CMake/libweb_generators.cmake b/Meta/CMake/libweb_generators.cmake index 6a2b18cbec6..6cb0a3766f3 100644 --- a/Meta/CMake/libweb_generators.cmake +++ b/Meta/CMake/libweb_generators.cmake @@ -46,6 +46,15 @@ function (generate_css_implementation) arguments -j "${LIBWEB_INPUT_FOLDER}/CSS/PseudoClasses.json" ) + invoke_generator( + "PseudoElement.cpp" + Lagom::GenerateCSSPseudoElement + "${LIBWEB_INPUT_FOLDER}/CSS/PseudoElements.json" + "CSS/PseudoElement.h" + "CSS/PseudoElement.cpp" + arguments -j "${LIBWEB_INPUT_FOLDER}/CSS/PseudoElements.json" + ) + invoke_generator( "TransformFunctions.cpp" Lagom::GenerateCSSTransformFunctions @@ -115,6 +124,7 @@ function (generate_css_implementation) "CSS/MediaFeatureID.h" "CSS/PropertyID.h" "CSS/PseudoClass.h" + "CSS/PseudoElement.h" "CSS/TransformFunctions.h" ) list(TRANSFORM CSS_GENERATED_HEADERS PREPEND "${CMAKE_CURRENT_BINARY_DIR}/") diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt b/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt index 7984c8c976c..c154e5e3229 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt @@ -6,6 +6,7 @@ lagom_tool(GenerateCSSMathFunctions SOURCES GenerateCSSMathFunctions.cpp 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(GenerateCSSPseudoElement SOURCES GenerateCSSPseudoElement.cpp LIBS LibMain) lagom_tool(GenerateCSSStyleProperties SOURCES GenerateCSSStyleProperties.cpp LIBS LibMain) lagom_tool(GenerateCSSTransformFunctions SOURCES GenerateCSSTransformFunctions.cpp LIBS LibMain) lagom_tool(GenerateWindowOrWorkerInterfaces SOURCES GenerateWindowOrWorkerInterfaces.cpp LIBS LibMain LibIDL) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSPseudoElement.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSPseudoElement.cpp new file mode 100644 index 00000000000..ce630d9b147 --- /dev/null +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSPseudoElement.cpp @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2022-2025, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "GeneratorUtil.h" +#include +#include +#include + +ErrorOr generate_header_file(JsonObject& pseudo_elements_data, Core::File& file); +ErrorOr generate_implementation_file(JsonObject& pseudo_elements_data, Core::File& file); + +ErrorOr serenity_main(Main::Arguments arguments) +{ + StringView generated_header_path; + StringView generated_implementation_path; + StringView json_path; + + Core::ArgsParser args_parser; + args_parser.add_option(generated_header_path, "Path to the PseudoElements header file to generate", "generated-header-path", 'h', "generated-header-path"); + args_parser.add_option(generated_implementation_path, "Path to the PseudoElements implementation file to generate", "generated-implementation-path", 'c', "generated-implementation-path"); + args_parser.add_option(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(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_elements_data, Core::File& file) +{ + StringBuilder builder; + SourceGenerator generator { builder }; + + auto pseudo_element_count = 0u; + pseudo_elements_data.for_each_member([&pseudo_element_count](auto const&, auto const&) { ++pseudo_element_count; }); + generator.set("pseudo_element_underlying_type", underlying_type_for_enum(pseudo_element_count)); + + generator.append(R"~~~( +#pragma once + +#include +#include + +namespace Web::CSS { + +enum class PseudoElement : @pseudo_element_underlying_type@ { +)~~~"); + + pseudo_elements_data.for_each_member([&](auto& name, auto&) { + auto member_generator = generator.fork(); + member_generator.set("name:titlecase", title_casify(name)); + + member_generator.appendln(" @name:titlecase@,"); + }); + generator.append(R"~~~( + KnownPseudoElementCount, + + UnknownWebKit, +}; + +Optional pseudo_element_from_string(StringView); +StringView pseudo_element_name(PseudoElement); + +} +)~~~"); + + TRY(file.write_until_depleted(generator.as_string_view().bytes())); + return {}; +} + +ErrorOr generate_implementation_file(JsonObject& pseudo_elements_data, Core::File& file) +{ + StringBuilder builder; + SourceGenerator generator { builder }; + + generator.append(R"~~~( +#include + +namespace Web::CSS { + +Optional pseudo_element_from_string(StringView string) +{ +)~~~"); + + pseudo_elements_data.for_each_member([&](auto& name, auto&) { + auto member_generator = generator.fork(); + member_generator.set("name", name); + member_generator.set("name:titlecase", title_casify(name)); + + member_generator.append(R"~~~( + if (string.equals_ignoring_ascii_case("@name@"sv)) + return PseudoElement::@name:titlecase@; +)~~~"); + }); + + generator.append(R"~~~( + + return {}; +} + +StringView pseudo_element_name(PseudoElement pseudo_element) +{ + switch (pseudo_element) { +)~~~"); + + pseudo_elements_data.for_each_member([&](auto& name, auto&) { + auto member_generator = generator.fork(); + member_generator.set("name", name); + member_generator.set("name:titlecase", title_casify(name)); + + member_generator.append(R"~~~( + case PseudoElement::@name:titlecase@: + return "@name@"sv; +)~~~"); + }); + + generator.append(R"~~~( + case PseudoElement::KnownPseudoElementCount: + case PseudoElement::UnknownWebKit: + VERIFY_NOT_REACHED(); + } + VERIFY_NOT_REACHED(); +} + +} +)~~~"); + + TRY(file.write_until_depleted(generator.as_string_view().bytes())); + return {}; +} diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GeneratorUtil.h b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GeneratorUtil.h index 8a521828260..8cb9fe0d174 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GeneratorUtil.h +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GeneratorUtil.h @@ -103,3 +103,14 @@ inline String css_property_to_idl_attribute(StringView property_name, bool lower // 5. Return output. return MUST(output.to_string()); } + +inline StringView underlying_type_for_enum(size_t member_count) +{ + if (member_count <= NumericLimits::max()) + return "u8"sv; + if (member_count <= NumericLimits::max()) + return "u16"sv; + if (member_count <= NumericLimits::max()) + return "u32"sv; + return "u64"sv; +} diff --git a/Meta/gn/secondary/Meta/Lagom/Tools/CodeGenerators/LibWeb/BUILD.gn b/Meta/gn/secondary/Meta/Lagom/Tools/CodeGenerators/LibWeb/BUILD.gn index aafe289b14b..3db745be8e5 100644 --- a/Meta/gn/secondary/Meta/Lagom/Tools/CodeGenerators/LibWeb/BUILD.gn +++ b/Meta/gn/secondary/Meta/Lagom/Tools/CodeGenerators/LibWeb/BUILD.gn @@ -52,6 +52,14 @@ lagom_tool("GenerateCSSPseudoClass") { ] } +lagom_tool("GenerateCSSPseudoElement") { + sources = [ "GenerateCSSPseudoElement.cpp" ] + deps = [ + ":headers", + "//Userland/Libraries/LibMain", + ] +} + lagom_tool("GenerateCSSTransformFunctions") { sources = [ "GenerateCSSTransformFunctions.cpp" ] deps = [ diff --git a/Meta/gn/secondary/Userland/Libraries/LibWeb/BUILD.gn b/Meta/gn/secondary/Userland/Libraries/LibWeb/BUILD.gn index f6072d55309..0d6428c6b1a 100644 --- a/Meta/gn/secondary/Userland/Libraries/LibWeb/BUILD.gn +++ b/Meta/gn/secondary/Userland/Libraries/LibWeb/BUILD.gn @@ -185,6 +185,23 @@ compiled_action("generate_css_pseudo_class") { ] } +compiled_action("generate_css_pseudo_element") { + tool = "//Meta/Lagom/Tools/CodeGenerators/LibWeb:GenerateCSSPseudoElement" + inputs = [ "CSS/PseudoElement.json" ] + outputs = [ + "$target_gen_dir/CSS/PseudoElement.h", + "$target_gen_dir/CSS/PseudoElement.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"