diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index bfe9df5fe4..82288e3e57 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -370,10 +370,9 @@ Parser::ParseErrorOr Parser::parse_attribute_simple_se return ParseError::SyntaxError; } - // FIXME: Handle namespace prefix for attribute name. - auto const& attribute_part = attribute_tokens.next_token(); - if (!attribute_part.is(Token::Type::Ident)) { - dbgln_if(CSS_PARSER_DEBUG, "Expected ident for attribute name, got: '{}'", attribute_part.to_debug_string()); + auto maybe_qualified_name = parse_selector_qualified_name(attribute_tokens, AllowWildcardName::No); + if (!maybe_qualified_name.has_value()) { + dbgln_if(CSS_PARSER_DEBUG, "Expected qualified-name for attribute name, got: '{}'", attribute_tokens.peek_token().to_debug_string()); return ParseError::SyntaxError; } @@ -386,7 +385,7 @@ Parser::ParseErrorOr Parser::parse_attribute_simple_se // they are converted to lowercase, so we do that here too. If we want to be // correct with XML later, we'll need to keep the original case and then do // a case-insensitive compare later. - .name = FlyString::from_deprecated_fly_string(attribute_part.token().ident().to_lowercase_string()).release_value_but_fixme_should_propagate_errors(), + .qualified_name = maybe_qualified_name.release_value(), .case_type = Selector::SimpleSelector::Attribute::CaseType::DefaultMatch, } }; diff --git a/Userland/Libraries/LibWeb/CSS/Selector.cpp b/Userland/Libraries/LibWeb/CSS/Selector.cpp index 1daa3fcc5a..2c471bc44c 100644 --- a/Userland/Libraries/LibWeb/CSS/Selector.cpp +++ b/Userland/Libraries/LibWeb/CSS/Selector.cpp @@ -155,10 +155,15 @@ ErrorOr Selector::SimpleSelector::serialize() const // 1. Append "[" (U+005B) to s. TRY(s.try_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. + // 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. + if (attribute.qualified_name.namespace_type == QualifiedName::NamespaceType::Named) { + TRY(serialize_an_identifier(s, attribute.qualified_name.namespace_)); + TRY(s.try_append('|')); + } // 3. Append the serialization of the attribute name as an identifier to s. - TRY(serialize_an_identifier(s, attribute.name)); + TRY(serialize_an_identifier(s, attribute.qualified_name.name.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. diff --git a/Userland/Libraries/LibWeb/CSS/Selector.h b/Userland/Libraries/LibWeb/CSS/Selector.h index 7460c0ec99..f760d01e24 100644 --- a/Userland/Libraries/LibWeb/CSS/Selector.h +++ b/Userland/Libraries/LibWeb/CSS/Selector.h @@ -177,7 +177,7 @@ public: CaseInsensitiveMatch, }; MatchType match_type; - FlyString name {}; + QualifiedName qualified_name; String value {}; CaseType case_type; }; diff --git a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp index 2e84bb70b3..f270d02252 100644 --- a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp +++ b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp @@ -120,11 +120,15 @@ static inline bool matches_indeterminate_pseudo_class(DOM::Element const& elemen return false; } -static inline bool matches_attribute(CSS::Selector::SimpleSelector::Attribute const& attribute, DOM::Element const& element) +static inline bool matches_attribute(CSS::Selector::SimpleSelector::Attribute const& attribute, [[maybe_unused]] Optional style_sheet_for_rule, DOM::Element const& element) { + // FIXME: Check the attribute's namespace, once we support that in DOM::Element! + + auto attribute_name = attribute.qualified_name.name.name.to_deprecated_fly_string(); + if (attribute.match_type == CSS::Selector::SimpleSelector::Attribute::MatchType::HasAttribute) { // Early way out in case of an attribute existence selector. - return element.has_attribute(attribute.name.to_string().to_deprecated_string()); + return element.has_attribute(attribute_name); } auto const case_insensitive_match = (attribute.case_type == CSS::Selector::SimpleSelector::Attribute::CaseType::CaseInsensitiveMatch); @@ -135,14 +139,14 @@ static inline bool matches_attribute(CSS::Selector::SimpleSelector::Attribute co switch (attribute.match_type) { case CSS::Selector::SimpleSelector::Attribute::MatchType::ExactValueMatch: return case_insensitive_match - ? Infra::is_ascii_case_insensitive_match(element.attribute(attribute.name.to_string().to_deprecated_string()), attribute.value) - : element.attribute(attribute.name.to_string().to_deprecated_string()) == attribute.value.to_deprecated_string(); + ? Infra::is_ascii_case_insensitive_match(element.attribute(attribute_name), attribute.value) + : element.attribute(attribute_name) == attribute.value.to_deprecated_string(); case CSS::Selector::SimpleSelector::Attribute::MatchType::ContainsWord: { if (attribute.value.is_empty()) { // This selector is always false is match value is empty. return false; } - auto const view = element.attribute(attribute.name.to_string().to_deprecated_string()).split_view(' '); + auto const view = element.attribute(attribute_name).split_view(' '); auto const size = view.size(); for (size_t i = 0; i < size; ++i) { auto const value = view.at(i); @@ -156,9 +160,9 @@ static inline bool matches_attribute(CSS::Selector::SimpleSelector::Attribute co } case CSS::Selector::SimpleSelector::Attribute::MatchType::ContainsString: return !attribute.value.is_empty() - && element.attribute(attribute.name.to_string().to_deprecated_string()).contains(attribute.value, case_sensitivity); + && element.attribute(attribute_name).contains(attribute.value, case_sensitivity); case CSS::Selector::SimpleSelector::Attribute::MatchType::StartsWithSegment: { - auto const element_attr_value = element.attribute(attribute.name.to_string().to_deprecated_string()); + auto const element_attr_value = element.attribute(attribute_name); if (element_attr_value.is_empty()) { // If the attribute value on element is empty, the selector is true // if the match value is also empty and false otherwise. @@ -174,10 +178,10 @@ static inline bool matches_attribute(CSS::Selector::SimpleSelector::Attribute co } case CSS::Selector::SimpleSelector::Attribute::MatchType::StartsWithString: return !attribute.value.is_empty() - && element.attribute(attribute.name.to_string().to_deprecated_string()).starts_with(attribute.value, case_sensitivity); + && element.attribute(attribute_name).starts_with(attribute.value, case_sensitivity); case CSS::Selector::SimpleSelector::Attribute::MatchType::EndsWithString: return !attribute.value.is_empty() - && element.attribute(attribute.name.to_string().to_deprecated_string()).ends_with(attribute.value, case_sensitivity); + && element.attribute(attribute_name).ends_with(attribute.value, case_sensitivity); default: break; } @@ -476,7 +480,7 @@ static inline bool matches(CSS::Selector::SimpleSelector const& component, Optio case CSS::Selector::SimpleSelector::Type::Class: return element.has_class(component.name()); case CSS::Selector::SimpleSelector::Type::Attribute: - return matches_attribute(component.attribute(), element); + return matches_attribute(component.attribute(), style_sheet_for_rule, element); case CSS::Selector::SimpleSelector::Type::PseudoClass: return matches_pseudo_class(component.pseudo_class(), style_sheet_for_rule, element, scope); case CSS::Selector::SimpleSelector::Type::PseudoElement: diff --git a/Userland/Libraries/LibWeb/Dump.cpp b/Userland/Libraries/LibWeb/Dump.cpp index 6486c017d9..2d6ae7cdb5 100644 --- a/Userland/Libraries/LibWeb/Dump.cpp +++ b/Userland/Libraries/LibWeb/Dump.cpp @@ -386,6 +386,26 @@ void dump_selector(CSS::Selector const& selector) dbgln("{}", builder.string_view()); } +static void dump_qualified_name(StringBuilder& builder, CSS::Selector::SimpleSelector::QualifiedName const& qualified_name) +{ + StringView namespace_type; + switch (qualified_name.namespace_type) { + case CSS::Selector::SimpleSelector::QualifiedName::NamespaceType::Default: + namespace_type = "Default"sv; + break; + case CSS::Selector::SimpleSelector::QualifiedName::NamespaceType::None: + namespace_type = "None"sv; + break; + case CSS::Selector::SimpleSelector::QualifiedName::NamespaceType::Any: + namespace_type = "Any"sv; + break; + case CSS::Selector::SimpleSelector::QualifiedName::NamespaceType::Named: + namespace_type = "Named"sv; + break; + } + builder.appendff("NamespaceType={}, Namespace='{}', Name='{}'", namespace_type, qualified_name.namespace_, qualified_name.name.name); +} + void dump_selector(StringBuilder& builder, CSS::Selector const& selector) { builder.append(" CSS::Selector:\n"sv); @@ -446,27 +466,12 @@ void dump_selector(StringBuilder& builder, CSS::Selector const& selector) } builder.appendff("{}:", type_description); + // FIXME: This is goofy if (simple_selector.value.has()) { builder.append(simple_selector.name()); } else if (simple_selector.value.has()) { - auto qualified_name = simple_selector.qualified_name(); - StringView namespace_type; - switch (qualified_name.namespace_type) { - case CSS::Selector::SimpleSelector::QualifiedName::NamespaceType::Default: - namespace_type = "Default"sv; - break; - case CSS::Selector::SimpleSelector::QualifiedName::NamespaceType::None: - namespace_type = "None"sv; - break; - case CSS::Selector::SimpleSelector::QualifiedName::NamespaceType::Any: - namespace_type = "Any"sv; - break; - case CSS::Selector::SimpleSelector::QualifiedName::NamespaceType::Named: - namespace_type = "Named"sv; - break; - } - builder.appendff(" [NamespaceType={}, Namespace='{}', Name='{}']", namespace_type, qualified_name.namespace_, qualified_name.name.name); + dump_qualified_name(builder, simple_selector.qualified_name()); } if (simple_selector.type == CSS::Selector::SimpleSelector::Type::PseudoClass) { @@ -681,7 +686,9 @@ void dump_selector(StringBuilder& builder, CSS::Selector const& selector) break; } - builder.appendff(" [{}, name='{}', value='{}']", attribute_match_type_description, attribute.name, attribute.value); + builder.appendff(" [{}, ", attribute_match_type_description); + dump_qualified_name(builder, attribute.qualified_name); + builder.appendff(", value='{}']", attribute.value); } if (i != relative_selector.simple_selectors.size() - 1)