浏览代码

LibWeb: Add namespaces to Attribute selectors

For now, we parse these, but don't actually consider the namespace when
matching them. `DOM::Element` does not (yet) store attribute namespaces
so we can't check what they are.
Sam Atkins 1 年之前
父节点
当前提交
debf38ee9d

+ 4 - 5
Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp

@@ -370,10 +370,9 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_attribute_simple_se
         return ParseError::SyntaxError;
         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;
         return ParseError::SyntaxError;
     }
     }
 
 
@@ -386,7 +385,7 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_attribute_simple_se
             // they are converted to lowercase, so we do that here too. If we want to be
             // 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
             // correct with XML later, we'll need to keep the original case and then do
             // a case-insensitive compare later.
             // 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,
             .case_type = Selector::SimpleSelector::Attribute::CaseType::DefaultMatch,
         }
         }
     };
     };

+ 7 - 2
Userland/Libraries/LibWeb/CSS/Selector.cpp

@@ -155,10 +155,15 @@ ErrorOr<String> Selector::SimpleSelector::serialize() const
         // 1. Append "[" (U+005B) to s.
         // 1. Append "[" (U+005B) to s.
         TRY(s.try_append('['));
         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.
         // 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),
         // 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.
         //    followed by the serialization of the attribute value as a string, to s.

+ 1 - 1
Userland/Libraries/LibWeb/CSS/Selector.h

@@ -177,7 +177,7 @@ public:
                 CaseInsensitiveMatch,
                 CaseInsensitiveMatch,
             };
             };
             MatchType match_type;
             MatchType match_type;
-            FlyString name {};
+            QualifiedName qualified_name;
             String value {};
             String value {};
             CaseType case_type;
             CaseType case_type;
         };
         };

+ 14 - 10
Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp

@@ -120,11 +120,15 @@ static inline bool matches_indeterminate_pseudo_class(DOM::Element const& elemen
     return false;
     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<CSS::CSSStyleSheet const&> 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) {
     if (attribute.match_type == CSS::Selector::SimpleSelector::Attribute::MatchType::HasAttribute) {
         // Early way out in case of an attribute existence selector.
         // 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);
     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) {
     switch (attribute.match_type) {
     case CSS::Selector::SimpleSelector::Attribute::MatchType::ExactValueMatch:
     case CSS::Selector::SimpleSelector::Attribute::MatchType::ExactValueMatch:
         return case_insensitive_match
         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: {
     case CSS::Selector::SimpleSelector::Attribute::MatchType::ContainsWord: {
         if (attribute.value.is_empty()) {
         if (attribute.value.is_empty()) {
             // This selector is always false is match value is empty.
             // This selector is always false is match value is empty.
             return false;
             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();
         auto const size = view.size();
         for (size_t i = 0; i < size; ++i) {
         for (size_t i = 0; i < size; ++i) {
             auto const value = view.at(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:
     case CSS::Selector::SimpleSelector::Attribute::MatchType::ContainsString:
         return !attribute.value.is_empty()
         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: {
     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 (element_attr_value.is_empty()) {
             // If the attribute value on element is empty, the selector is true
             // If the attribute value on element is empty, the selector is true
             // if the match value is also empty and false otherwise.
             // 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:
     case CSS::Selector::SimpleSelector::Attribute::MatchType::StartsWithString:
         return !attribute.value.is_empty()
         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:
     case CSS::Selector::SimpleSelector::Attribute::MatchType::EndsWithString:
         return !attribute.value.is_empty()
         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:
     default:
         break;
         break;
     }
     }
@@ -476,7 +480,7 @@ static inline bool matches(CSS::Selector::SimpleSelector const& component, Optio
     case CSS::Selector::SimpleSelector::Type::Class:
     case CSS::Selector::SimpleSelector::Type::Class:
         return element.has_class(component.name());
         return element.has_class(component.name());
     case CSS::Selector::SimpleSelector::Type::Attribute:
     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:
     case CSS::Selector::SimpleSelector::Type::PseudoClass:
         return matches_pseudo_class(component.pseudo_class(), style_sheet_for_rule, element, scope);
         return matches_pseudo_class(component.pseudo_class(), style_sheet_for_rule, element, scope);
     case CSS::Selector::SimpleSelector::Type::PseudoElement:
     case CSS::Selector::SimpleSelector::Type::PseudoElement:

+ 25 - 18
Userland/Libraries/LibWeb/Dump.cpp

@@ -386,6 +386,26 @@ void dump_selector(CSS::Selector const& selector)
     dbgln("{}", builder.string_view());
     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)
 void dump_selector(StringBuilder& builder, CSS::Selector const& selector)
 {
 {
     builder.append("  CSS::Selector:\n"sv);
     builder.append("  CSS::Selector:\n"sv);
@@ -446,27 +466,12 @@ void dump_selector(StringBuilder& builder, CSS::Selector const& selector)
             }
             }
 
 
             builder.appendff("{}:", type_description);
             builder.appendff("{}:", type_description);
+
             // FIXME: This is goofy
             // FIXME: This is goofy
             if (simple_selector.value.has<CSS::Selector::SimpleSelector::Name>()) {
             if (simple_selector.value.has<CSS::Selector::SimpleSelector::Name>()) {
                 builder.append(simple_selector.name());
                 builder.append(simple_selector.name());
             } else if (simple_selector.value.has<CSS::Selector::SimpleSelector::QualifiedName>()) {
             } else if (simple_selector.value.has<CSS::Selector::SimpleSelector::QualifiedName>()) {
-                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) {
             if (simple_selector.type == CSS::Selector::SimpleSelector::Type::PseudoClass) {
@@ -681,7 +686,9 @@ void dump_selector(StringBuilder& builder, CSS::Selector const& selector)
                     break;
                     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)
             if (i != relative_selector.simple_selectors.size() - 1)