From ec51b40a4f9ac4971ad69a012255eedb51c397fa Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Fri, 15 Oct 2021 11:30:01 +0100 Subject: [PATCH] LibWeb: Move CSS selector-serialization code to Selector.{h,cpp} Also, renamed `builder` to `s` to match spec comments, and fixed a find-and-replace typo where some pseudo-class names were "last-of-pseudo_class" instead of "last-of-type". --- .../Libraries/LibWeb/CSS/CSSStyleRule.cpp | 255 ------------------ Userland/Libraries/LibWeb/CSS/Selector.cpp | 253 +++++++++++++++++ Userland/Libraries/LibWeb/CSS/Selector.h | 8 + 3 files changed, 261 insertions(+), 255 deletions(-) diff --git a/Userland/Libraries/LibWeb/CSS/CSSStyleRule.cpp b/Userland/Libraries/LibWeb/CSS/CSSStyleRule.cpp index 936c0b46a2..2f4203e41c 100644 --- a/Userland/Libraries/LibWeb/CSS/CSSStyleRule.cpp +++ b/Userland/Libraries/LibWeb/CSS/CSSStyleRule.cpp @@ -25,261 +25,6 @@ CSSStyleDeclaration* CSSStyleRule::style() return m_declaration; } -static StringView to_string(Selector::SimpleSelector::PseudoElement pseudo_element) -{ - switch (pseudo_element) { - case Selector::SimpleSelector::PseudoElement::Before: - return "before"sv; - case Selector::SimpleSelector::PseudoElement::After: - return "after"sv; - case Selector::SimpleSelector::PseudoElement::FirstLine: - return "first-line"sv; - case Selector::SimpleSelector::PseudoElement::FirstLetter: - return "first-letter"sv; - case Selector::SimpleSelector::PseudoElement::None: - break; - } - VERIFY_NOT_REACHED(); -} - -static StringView to_string(Selector::SimpleSelector::PseudoClass::Type pseudo_class) -{ - switch (pseudo_class) { - case Selector::SimpleSelector::PseudoClass::Type::Link: - return "link"sv; - case Selector::SimpleSelector::PseudoClass::Type::Visited: - return "visited"sv; - case Selector::SimpleSelector::PseudoClass::Type::Hover: - return "hover"sv; - case Selector::SimpleSelector::PseudoClass::Type::Focus: - return "focus"sv; - case Selector::SimpleSelector::PseudoClass::Type::FirstChild: - return "first-child"sv; - case Selector::SimpleSelector::PseudoClass::Type::LastChild: - return "last-child"sv; - case Selector::SimpleSelector::PseudoClass::Type::OnlyChild: - return "only-child"sv; - case Selector::SimpleSelector::PseudoClass::Type::Empty: - return "empty"sv; - case Selector::SimpleSelector::PseudoClass::Type::Root: - return "root"sv; - case Selector::SimpleSelector::PseudoClass::Type::FirstOfType: - return "first-of-pseudo_class"sv; - case Selector::SimpleSelector::PseudoClass::Type::LastOfType: - return "last-of-pseudo_class"sv; - case Selector::SimpleSelector::PseudoClass::Type::Disabled: - return "disabled"sv; - case Selector::SimpleSelector::PseudoClass::Type::Enabled: - return "enabled"sv; - case Selector::SimpleSelector::PseudoClass::Type::Checked: - return "checked"sv; - case Selector::SimpleSelector::PseudoClass::Type::Active: - return "active"sv; - case Selector::SimpleSelector::PseudoClass::Type::NthChild: - return "nth-child"sv; - case Selector::SimpleSelector::PseudoClass::Type::NthLastChild: - return "nth-last-child"sv; - case Selector::SimpleSelector::PseudoClass::Type::Not: - return "not"sv; - case Selector::SimpleSelector::PseudoClass::Type::None: - break; - } - VERIFY_NOT_REACHED(); -} - -static String serialize_a_group_of_selectors(NonnullRefPtrVector const&); - -// https://www.w3.org/TR/cssom/#serialize-a-simple-selector -static String serialize_a_simple_selector(Selector::SimpleSelector const& simple_selector) -{ - StringBuilder builder; - switch (simple_selector.type) { - case Selector::SimpleSelector::Type::TagName: - case Selector::SimpleSelector::Type::Universal: - // FIXME: 1. If the namespace prefix maps to a namespace that is not the default namespace and is not the null namespace (not in a namespace) append the serialization of the namespace prefix as an identifier, followed by a "|" (U+007C) to s. - // FIXME: 2. If the namespace prefix maps to a namespace that is the null namespace (not in a namespace) append "|" (U+007C) to s. - // 3. If this is a type selector append the serialization of the element name as an identifier to s. - if (simple_selector.type == Selector::SimpleSelector::Type::TagName) { - // FIXME: Use the "serialize an identifier" algorithm. - builder.append(simple_selector.value); - } - // 4. If this is a universal selector append "*" (U+002A) to s. - if (simple_selector.type == Selector::SimpleSelector::Type::Universal) - builder.append('*'); - break; - case Selector::SimpleSelector::Type::Attribute: - // 1. Append "[" (U+005B) to s. - builder.append('['); - - // FIXME: 2. If the namespace prefix maps to a namespace that is not the null namespace (not in a namespace) append the serialization of the namespace prefix as an identifier, followed by a "|" (U+007C) to s. - - // 3. Append the serialization of the attribute name as an identifier to s. - // FIXME: Use the "serialize an identifier" algorithm. - builder.append(simple_selector.attribute.name); - - // 4. If there is an attribute value specified, append "=", "~=", "|=", "^=", "$=", or "*=" as appropriate (depending on the type of attribute selector), - // followed by the serialization of the attribute value as a string, to s. - if (!simple_selector.attribute.value.is_null()) { - switch (simple_selector.attribute.match_type) { - case Selector::SimpleSelector::Attribute::MatchType::ExactValueMatch: - builder.append("="); - break; - case Selector::SimpleSelector::Attribute::MatchType::ContainsWord: - builder.append("~="); - break; - case Selector::SimpleSelector::Attribute::MatchType::ContainsString: - builder.append("*="); - break; - case Selector::SimpleSelector::Attribute::MatchType::StartsWithSegment: - builder.append("|="); - break; - case Selector::SimpleSelector::Attribute::MatchType::StartsWithString: - builder.append("^="); - break; - case Selector::SimpleSelector::Attribute::MatchType::EndsWithString: - builder.append("$="); - break; - default: - break; - } - } - // FIXME: 5. If the attribute selector has the case-sensitivity flag present, append " i" (U+0020 U+0069) to s. - - // 6. Append "]" (U+005D) to s. - builder.append(']'); - break; - - case Selector::SimpleSelector::Type::Class: - // Append a "." (U+002E), followed by the serialization of the class name as an identifier to s. - builder.append('.'); - // FIXME: Use the "serialize an identifier" algorithm. - builder.append(simple_selector.value); - break; - - case Selector::SimpleSelector::Type::Id: - // Append a "#" (U+0023), followed by the serialization of the ID as an identifier to s. - builder.append('#'); - // FIXME: Use the "serialize an identifier" algorithm. - builder.append(simple_selector.value); - break; - - case Selector::SimpleSelector::Type::PseudoClass: - switch (simple_selector.pseudo_class.type) { - case Selector::SimpleSelector::PseudoClass::Type::Link: - case Selector::SimpleSelector::PseudoClass::Type::Visited: - case Selector::SimpleSelector::PseudoClass::Type::Hover: - case Selector::SimpleSelector::PseudoClass::Type::Focus: - case Selector::SimpleSelector::PseudoClass::Type::FirstChild: - case Selector::SimpleSelector::PseudoClass::Type::LastChild: - case Selector::SimpleSelector::PseudoClass::Type::OnlyChild: - case Selector::SimpleSelector::PseudoClass::Type::Empty: - case Selector::SimpleSelector::PseudoClass::Type::Root: - case Selector::SimpleSelector::PseudoClass::Type::FirstOfType: - case Selector::SimpleSelector::PseudoClass::Type::LastOfType: - case Selector::SimpleSelector::PseudoClass::Type::Disabled: - case Selector::SimpleSelector::PseudoClass::Type::Enabled: - case Selector::SimpleSelector::PseudoClass::Type::Checked: - case Selector::SimpleSelector::PseudoClass::Type::Active: - // If the pseudo-class does not accept arguments append ":" (U+003A), followed by the name of the pseudo-class, to s. - builder.append(':'); - builder.append(to_string(simple_selector.pseudo_class.type)); - break; - case Selector::SimpleSelector::PseudoClass::Type::NthChild: - case Selector::SimpleSelector::PseudoClass::Type::NthLastChild: - case Selector::SimpleSelector::PseudoClass::Type::Not: - // Otherwise, append ":" (U+003A), followed by the name of the pseudo-class, followed by "(" (U+0028), - // followed by the value of the pseudo-class argument(s) determined as per below, followed by ")" (U+0029), to s. - builder.append(':'); - builder.append(to_string(simple_selector.pseudo_class.type)); - builder.append('('); - if (simple_selector.pseudo_class.type == Selector::SimpleSelector::PseudoClass::Type::NthChild - || simple_selector.pseudo_class.type == Selector::SimpleSelector::PseudoClass::Type::NthLastChild) { - // FIXME: The result of serializing the value using the rules to serialize an value. - TODO(); - } else if (simple_selector.pseudo_class.type == Selector::SimpleSelector::PseudoClass::Type::Not) { - // The result of serializing the value using the rules for serializing a group of selectors. - builder.append(serialize_a_group_of_selectors(simple_selector.pseudo_class.not_selector)); - } - builder.append(')'); - break; - default: - VERIFY_NOT_REACHED(); - } - break; - default: - dbgln("FIXME: Unsupported simple selector serialization for type {}", to_underlying(simple_selector.type)); - break; - } - return builder.to_string(); -} - -// https://www.w3.org/TR/cssom/#serialize-a-selector -static String serialize_a_selector(Selector const& selector) -{ - StringBuilder builder; - - // To serialize a selector let s be the empty string, run the steps below for each part of the chain of the selector, and finally return s: - for (size_t i = 0; i < selector.compound_selectors().size(); ++i) { - auto const& compound_selector = selector.compound_selectors()[i]; - // 1. If there is only one simple selector in the compound selectors which is a universal selector, append the result of serializing the universal selector to s. - if (compound_selector.simple_selectors.size() == 1 - && compound_selector.simple_selectors.first().type == Selector::SimpleSelector::Type::Universal) { - builder.append(serialize_a_simple_selector(selector.compound_selectors().first().simple_selectors.first())); - } - // 2. Otherwise, for each simple selector in the compound selectors... - // FIXME: ...that is not a universal selector of which the namespace prefix maps to a namespace that is not the default namespace... - // ...serialize the simple selector and append the result to s. - else { - for (auto& simple_selector : compound_selector.simple_selectors) { - builder.append(serialize_a_simple_selector(simple_selector)); - } - } - - // 3. If this is not the last part of the chain of the selector append a single SPACE (U+0020), - // followed by the combinator ">", "+", "~", ">>", "||", as appropriate, followed by another - // single SPACE (U+0020) if the combinator was not whitespace, to s. - if (i != selector.compound_selectors().size() - 1) { - builder.append(' '); - switch (compound_selector.combinator) { - case Selector::Combinator::ImmediateChild: - builder.append('>'); - break; - case Selector::Combinator::NextSibling: - builder.append('+'); - break; - case Selector::Combinator::SubsequentSibling: - builder.append('~'); - break; - case Selector::Combinator::Column: - builder.append("||"); - break; - default: - break; - } - } else { - // 4. If this is the last part of the chain of the selector and there is a pseudo-element, append "::" followed by the name of the pseudo-element, to s. - // FIXME: This doesn't feel entirely correct. Our model of pseudo-elements seems off. - if (!compound_selector.simple_selectors.is_empty() - && compound_selector.simple_selectors.first().type == Selector::SimpleSelector::Type::PseudoElement) { - builder.append("::"); - builder.append(to_string(compound_selector.simple_selectors.first().pseudo_element)); - } - } - } - - return builder.to_string(); -} - -// https://www.w3.org/TR/cssom/#serialize-a-group-of-selectors -static String serialize_a_group_of_selectors(NonnullRefPtrVector const& selectors) -{ - // To serialize a group of selectors serialize each selector in the group of selectors and then serialize a comma-separated list of these serializations. - StringBuilder builder; - for (auto& selector : selectors) - builder.append(serialize_a_selector(selector)); - return builder.to_string(); -} - // https://www.w3.org/TR/cssom/#serialize-a-css-rule String CSSStyleRule::serialized() const { diff --git a/Userland/Libraries/LibWeb/CSS/Selector.cpp b/Userland/Libraries/LibWeb/CSS/Selector.cpp index e20647dd4a..d6bbb927dc 100644 --- a/Userland/Libraries/LibWeb/CSS/Selector.cpp +++ b/Userland/Libraries/LibWeb/CSS/Selector.cpp @@ -44,4 +44,257 @@ u32 Selector::specificity() const return ids * 0x10000 + classes * 0x100 + tag_names; } +// https://www.w3.org/TR/cssom/#serialize-a-simple-selector +String Selector::SimpleSelector::serialize() const +{ + StringBuilder s; + switch (type) { + case Selector::SimpleSelector::Type::TagName: + case Selector::SimpleSelector::Type::Universal: + // FIXME: 1. If the namespace prefix maps to a namespace that is not the default namespace and is not the null namespace (not in a namespace) append the serialization of the namespace prefix as an identifier, followed by a "|" (U+007C) to s. + // FIXME: 2. If the namespace prefix maps to a namespace that is the null namespace (not in a namespace) append "|" (U+007C) to s. + // 3. If this is a type selector append the serialization of the element name as an identifier to s. + if (type == Selector::SimpleSelector::Type::TagName) { + // FIXME: Use the "serialize an identifier" algorithm. + s.append(value); + } + // 4. If this is a universal selector append "*" (U+002A) to s. + if (type == Selector::SimpleSelector::Type::Universal) + s.append('*'); + break; + case Selector::SimpleSelector::Type::Attribute: + // 1. Append "[" (U+005B) to s. + s.append('['); + + // FIXME: 2. If the namespace prefix maps to a namespace that is not the null namespace (not in a namespace) append the serialization of the namespace prefix as an identifier, followed by a "|" (U+007C) to s. + + // 3. Append the serialization of the attribute name as an identifier to s. + // FIXME: Use the "serialize an identifier" algorithm. + s.append(attribute.name); + + // 4. If there is an attribute value specified, append "=", "~=", "|=", "^=", "$=", or "*=" as appropriate (depending on the type of attribute selector), + // followed by the serialization of the attribute value as a string, to s. + if (!attribute.value.is_null()) { + switch (attribute.match_type) { + case Selector::SimpleSelector::Attribute::MatchType::ExactValueMatch: + s.append("="); + break; + case Selector::SimpleSelector::Attribute::MatchType::ContainsWord: + s.append("~="); + break; + case Selector::SimpleSelector::Attribute::MatchType::ContainsString: + s.append("*="); + break; + case Selector::SimpleSelector::Attribute::MatchType::StartsWithSegment: + s.append("|="); + break; + case Selector::SimpleSelector::Attribute::MatchType::StartsWithString: + s.append("^="); + break; + case Selector::SimpleSelector::Attribute::MatchType::EndsWithString: + s.append("$="); + break; + default: + break; + } + } + // FIXME: 5. If the attribute selector has the case-sensitivity flag present, append " i" (U+0020 U+0069) to s. + + // 6. Append "]" (U+005D) to s. + s.append(']'); + break; + + case Selector::SimpleSelector::Type::Class: + // Append a "." (U+002E), followed by the serialization of the class name as an identifier to s. + s.append('.'); + // FIXME: Use the "serialize an identifier" algorithm. + s.append(value); + break; + + case Selector::SimpleSelector::Type::Id: + // Append a "#" (U+0023), followed by the serialization of the ID as an identifier to s. + s.append('#'); + // FIXME: Use the "serialize an identifier" algorithm. + s.append(value); + break; + + case Selector::SimpleSelector::Type::PseudoClass: + switch (pseudo_class.type) { + case Selector::SimpleSelector::PseudoClass::Type::Link: + case Selector::SimpleSelector::PseudoClass::Type::Visited: + case Selector::SimpleSelector::PseudoClass::Type::Hover: + case Selector::SimpleSelector::PseudoClass::Type::Focus: + case Selector::SimpleSelector::PseudoClass::Type::FirstChild: + case Selector::SimpleSelector::PseudoClass::Type::LastChild: + case Selector::SimpleSelector::PseudoClass::Type::OnlyChild: + case Selector::SimpleSelector::PseudoClass::Type::Empty: + case Selector::SimpleSelector::PseudoClass::Type::Root: + case Selector::SimpleSelector::PseudoClass::Type::FirstOfType: + case Selector::SimpleSelector::PseudoClass::Type::LastOfType: + case Selector::SimpleSelector::PseudoClass::Type::Disabled: + case Selector::SimpleSelector::PseudoClass::Type::Enabled: + case Selector::SimpleSelector::PseudoClass::Type::Checked: + case Selector::SimpleSelector::PseudoClass::Type::Active: + // If the pseudo-class does not accept arguments append ":" (U+003A), followed by the name of the pseudo-class, to s. + s.append(':'); + s.append(pseudo_class_name(pseudo_class.type)); + break; + case Selector::SimpleSelector::PseudoClass::Type::NthChild: + case Selector::SimpleSelector::PseudoClass::Type::NthLastChild: + case Selector::SimpleSelector::PseudoClass::Type::Not: + // Otherwise, append ":" (U+003A), followed by the name of the pseudo-class, followed by "(" (U+0028), + // followed by the value of the pseudo-class argument(s) determined as per below, followed by ")" (U+0029), to s. + s.append(':'); + s.append(pseudo_class_name(pseudo_class.type)); + s.append('('); + if (pseudo_class.type == Selector::SimpleSelector::PseudoClass::Type::NthChild + || pseudo_class.type == Selector::SimpleSelector::PseudoClass::Type::NthLastChild) { + // FIXME: The result of serializing the value using the rules to serialize an value. + TODO(); + } else if (pseudo_class.type == Selector::SimpleSelector::PseudoClass::Type::Not) { + // The result of serializing the value using the rules for serializing a group of selectors. + s.append(serialize_a_group_of_selectors(pseudo_class.not_selector)); + } + s.append(')'); + break; + default: + VERIFY_NOT_REACHED(); + } + break; + default: + dbgln("FIXME: Unsupported simple selector serialization for type {}", to_underlying(type)); + break; + } + return s.to_string(); +} + +// https://www.w3.org/TR/cssom/#serialize-a-selector +String Selector::serialize() const +{ + StringBuilder s; + + // To serialize a selector let s be the empty string, run the steps below for each part of the chain of the selector, and finally return s: + for (size_t i = 0; i < compound_selectors().size(); ++i) { + auto const& compound_selector = compound_selectors()[i]; + // 1. If there is only one simple selector in the compound selectors which is a universal selector, append the result of serializing the universal selector to s. + if (compound_selector.simple_selectors.size() == 1 + && compound_selector.simple_selectors.first().type == Selector::SimpleSelector::Type::Universal) { + s.append(compound_selectors().first().simple_selectors.first().serialize()); + } + // 2. Otherwise, for each simple selector in the compound selectors... + // FIXME: ...that is not a universal selector of which the namespace prefix maps to a namespace that is not the default namespace... + // ...serialize the simple selector and append the result to s. + else { + for (auto& simple_selector : compound_selector.simple_selectors) { + s.append(simple_selector.serialize()); + } + } + + // 3. If this is not the last part of the chain of the selector append a single SPACE (U+0020), + // followed by the combinator ">", "+", "~", ">>", "||", as appropriate, followed by another + // single SPACE (U+0020) if the combinator was not whitespace, to s. + if (i != compound_selectors().size() - 1) { + s.append(' '); + switch (compound_selector.combinator) { + case Selector::Combinator::ImmediateChild: + s.append('>'); + break; + case Selector::Combinator::NextSibling: + s.append('+'); + break; + case Selector::Combinator::SubsequentSibling: + s.append('~'); + break; + case Selector::Combinator::Column: + s.append("||"); + break; + default: + break; + } + } else { + // 4. If this is the last part of the chain of the selector and there is a pseudo-element, append "::" followed by the name of the pseudo-element, to s. + // FIXME: This doesn't feel entirely correct. Our model of pseudo-elements seems off. + if (!compound_selector.simple_selectors.is_empty() + && compound_selector.simple_selectors.first().type == Selector::SimpleSelector::Type::PseudoElement) { + s.append("::"); + s.append(pseudo_element_name(compound_selector.simple_selectors.first().pseudo_element)); + } + } + } + + return s.to_string(); +} + +// https://www.w3.org/TR/cssom/#serialize-a-group-of-selectors +String serialize_a_group_of_selectors(NonnullRefPtrVector const& selectors) +{ + // To serialize a group of selectors serialize each selector in the group of selectors and then serialize a comma-separated list of these serializations. + StringBuilder builder; + for (auto& selector : selectors) + builder.append(selector.serialize()); + return builder.to_string(); +} + +constexpr StringView pseudo_element_name(Selector::SimpleSelector::PseudoElement pseudo_element) +{ + switch (pseudo_element) { + case Selector::SimpleSelector::PseudoElement::Before: + return "before"sv; + case Selector::SimpleSelector::PseudoElement::After: + return "after"sv; + case Selector::SimpleSelector::PseudoElement::FirstLine: + return "first-line"sv; + case Selector::SimpleSelector::PseudoElement::FirstLetter: + return "first-letter"sv; + case Selector::SimpleSelector::PseudoElement::None: + break; + } + VERIFY_NOT_REACHED(); +} + +constexpr StringView pseudo_class_name(Selector::SimpleSelector::PseudoClass::Type pseudo_class) +{ + switch (pseudo_class) { + case Selector::SimpleSelector::PseudoClass::Type::Link: + return "link"sv; + case Selector::SimpleSelector::PseudoClass::Type::Visited: + return "visited"sv; + case Selector::SimpleSelector::PseudoClass::Type::Hover: + return "hover"sv; + case Selector::SimpleSelector::PseudoClass::Type::Focus: + return "focus"sv; + case Selector::SimpleSelector::PseudoClass::Type::FirstChild: + return "first-child"sv; + case Selector::SimpleSelector::PseudoClass::Type::LastChild: + return "last-child"sv; + case Selector::SimpleSelector::PseudoClass::Type::OnlyChild: + return "only-child"sv; + case Selector::SimpleSelector::PseudoClass::Type::Empty: + return "empty"sv; + case Selector::SimpleSelector::PseudoClass::Type::Root: + return "root"sv; + case Selector::SimpleSelector::PseudoClass::Type::FirstOfType: + return "first-of-type"sv; + case Selector::SimpleSelector::PseudoClass::Type::LastOfType: + return "last-of-type"sv; + case Selector::SimpleSelector::PseudoClass::Type::Disabled: + return "disabled"sv; + case Selector::SimpleSelector::PseudoClass::Type::Enabled: + return "enabled"sv; + case Selector::SimpleSelector::PseudoClass::Type::Checked: + return "checked"sv; + case Selector::SimpleSelector::PseudoClass::Type::Active: + return "active"sv; + case Selector::SimpleSelector::PseudoClass::Type::NthChild: + return "nth-child"sv; + case Selector::SimpleSelector::PseudoClass::Type::NthLastChild: + return "nth-last-child"sv; + case Selector::SimpleSelector::PseudoClass::Type::Not: + return "not"sv; + case Selector::SimpleSelector::PseudoClass::Type::None: + break; + } + VERIFY_NOT_REACHED(); +} + } diff --git a/Userland/Libraries/LibWeb/CSS/Selector.h b/Userland/Libraries/LibWeb/CSS/Selector.h index d5941bd125..b144ca42de 100644 --- a/Userland/Libraries/LibWeb/CSS/Selector.h +++ b/Userland/Libraries/LibWeb/CSS/Selector.h @@ -102,6 +102,8 @@ public: String value {}; }; Attribute attribute {}; + + String serialize() const; }; enum class Combinator { @@ -130,6 +132,7 @@ public: Vector const& compound_selectors() const { return m_compound_selectors; } u32 specificity() const; + String serialize() const; private: explicit Selector(Vector&&); @@ -137,4 +140,9 @@ private: Vector m_compound_selectors; }; +constexpr StringView pseudo_element_name(Selector::SimpleSelector::PseudoElement); +constexpr StringView pseudo_class_name(Selector::SimpleSelector::PseudoClass::Type); + +String serialize_a_group_of_selectors(NonnullRefPtrVector const& selectors); + }