浏览代码

LibWeb: Match attribute selectors case insensitively in XML documents

The values of attribute selectors are now compared case insensitively
by default if the attribute's document is not a HTML document, or the
element is not in the HTML namespace.
Tim Ledbetter 11 月之前
父节点
当前提交
00f03f3e90

+ 4 - 0
Tests/LibWeb/Text/expected/css/attribute-selector-case-sensitivity.txt

@@ -0,0 +1,4 @@
+The accept attribute is matched case insensitively in HTML documents true
+The accept attribute is matched case insensitively in XML documents false
+The accesskey attribute is matched case insensitively in HTML documents false
+The accesskey attribute is matched case insensitively in XML documents false

+ 18 - 0
Tests/LibWeb/Text/input/css/attribute-selector-case-sensitivity.html

@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<script src="../include.js"></script>
+<script>
+    test(() => {
+        const elementName = "a";
+        const attributeNames = ["accept", "accesskey"];
+        for (const attributeName of attributeNames) {
+            const htmlElement = document.createElement(elementName);
+            htmlElement.setAttribute(attributeName, "TeSt");
+            println(`The ${attributeName} attribute is matched case insensitively in HTML documents ${htmlElement.matches(`[${attributeName}^=test]`)}`);
+
+            const xmlDocument = new Document();
+            const xmlElement = xmlDocument.createElementNS("http://www.w3.org/1999/xhtml", elementName);
+            xmlElement.setAttribute(attributeName, "TeSt");
+            println(`The ${attributeName} attribute is matched case insensitively in XML documents ${xmlElement.matches(`[${attributeName}^=test]`)}`);
+        }
+    });
+</script>

+ 0 - 50
Userland/Libraries/LibWeb/CSS/Parser/Parser.h

@@ -77,56 +77,6 @@ public:
 
     [[nodiscard]] LengthOrCalculated parse_as_sizes_attribute();
 
-    // https://html.spec.whatwg.org/multipage/semantics-other.html#case-sensitivity-of-selectors
-    static constexpr Array case_insensitive_html_attributes = {
-        "accept"sv,
-        "accept-charset"sv,
-        "align"sv,
-        "alink"sv,
-        "axis"sv,
-        "bgcolor"sv,
-        "charset"sv,
-        "checked"sv,
-        "clear"sv,
-        "codetype"sv,
-        "color"sv,
-        "compact"sv,
-        "declare"sv,
-        "defer"sv,
-        "dir"sv,
-        "direction"sv,
-        "disabled"sv,
-        "enctype"sv,
-        "face"sv,
-        "frame"sv,
-        "hreflang"sv,
-        "http-equiv"sv,
-        "lang"sv,
-        "language"sv,
-        "link"sv,
-        "media"sv,
-        "method"sv,
-        "multiple"sv,
-        "nohref"sv,
-        "noresize"sv,
-        "noshade"sv,
-        "nowrap"sv,
-        "readonly"sv,
-        "rel"sv,
-        "rev"sv,
-        "rules"sv,
-        "scope"sv,
-        "scrolling"sv,
-        "selected"sv,
-        "shape"sv,
-        "target"sv,
-        "text"sv,
-        "type"sv,
-        "valign"sv,
-        "valuetype"sv,
-        "vlink"sv,
-    };
-
 private:
     Parser(ParsingContext const&, Vector<Token>);
 

+ 1 - 8
Userland/Libraries/LibWeb/CSS/Parser/SelectorParsing.cpp

@@ -260,15 +260,8 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_attribute_simple_se
         .type = Selector::SimpleSelector::Type::Attribute,
         .value = Selector::SimpleSelector::Attribute {
             .match_type = Selector::SimpleSelector::Attribute::MatchType::HasAttribute,
-            // FIXME: Case-sensitivity is defined by the document language.
-            // HTML is insensitive with attribute names, and our code generally assumes
-            // 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.
             .qualified_name = qualified_name,
-            .case_type = case_insensitive_html_attributes.contains_slow(qualified_name.name.lowercase_name)
-                ? Selector::SimpleSelector::Attribute::CaseType::CaseInsensitiveMatch
-                : Selector::SimpleSelector::Attribute::CaseType::DefaultMatch,
+            .case_type = Selector::SimpleSelector::Attribute::CaseType::DefaultMatch,
         }
     };
 

+ 31 - 4
Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp

@@ -213,10 +213,37 @@ static inline bool matches_attribute(CSS::Selector::SimpleSelector::Attribute co
     if (!attr)
         return false;
 
-    auto const case_insensitive_match = (attribute.case_type == CSS::Selector::SimpleSelector::Attribute::CaseType::CaseInsensitiveMatch);
-    auto const case_sensitivity = case_insensitive_match
-        ? CaseSensitivity::CaseInsensitive
-        : CaseSensitivity::CaseSensitive;
+    auto case_sensitivity = [&](CSS::Selector::SimpleSelector::Attribute::CaseType case_type) {
+        switch (case_type) {
+        case CSS::Selector::SimpleSelector::Attribute::CaseType::CaseInsensitiveMatch:
+            return CaseSensitivity::CaseInsensitive;
+        case CSS::Selector::SimpleSelector::Attribute::CaseType::CaseSensitiveMatch:
+            return CaseSensitivity::CaseSensitive;
+        case CSS::Selector::SimpleSelector::Attribute::CaseType::DefaultMatch:
+            // See: https://html.spec.whatwg.org/multipage/semantics-other.html#case-sensitivity-of-selectors
+            if (element.document().is_html_document()
+                && element.namespace_uri() == Namespace::HTML
+                && attribute_name.is_one_of(
+                    HTML::AttributeNames::accept, HTML::AttributeNames::accept_charset, HTML::AttributeNames::align,
+                    HTML::AttributeNames::alink, HTML::AttributeNames::axis, HTML::AttributeNames::bgcolor, HTML::AttributeNames::charset,
+                    HTML::AttributeNames::checked, HTML::AttributeNames::clear, HTML::AttributeNames::codetype, HTML::AttributeNames::color,
+                    HTML::AttributeNames::compact, HTML::AttributeNames::declare, HTML::AttributeNames::defer, HTML::AttributeNames::dir,
+                    HTML::AttributeNames::direction, HTML::AttributeNames::disabled, HTML::AttributeNames::enctype, HTML::AttributeNames::face,
+                    HTML::AttributeNames::frame, HTML::AttributeNames::hreflang, HTML::AttributeNames::http_equiv, HTML::AttributeNames::lang,
+                    HTML::AttributeNames::language, HTML::AttributeNames::link, HTML::AttributeNames::media, HTML::AttributeNames::method,
+                    HTML::AttributeNames::multiple, HTML::AttributeNames::nohref, HTML::AttributeNames::noresize, HTML::AttributeNames::noshade,
+                    HTML::AttributeNames::nowrap, HTML::AttributeNames::readonly, HTML::AttributeNames::rel, HTML::AttributeNames::rev,
+                    HTML::AttributeNames::rules, HTML::AttributeNames::scope, HTML::AttributeNames::scrolling, HTML::AttributeNames::selected,
+                    HTML::AttributeNames::shape, HTML::AttributeNames::target, HTML::AttributeNames::text, HTML::AttributeNames::type,
+                    HTML::AttributeNames::valign, HTML::AttributeNames::valuetype, HTML::AttributeNames::vlink)) {
+                return CaseSensitivity::CaseInsensitive;
+            }
+
+            return CaseSensitivity::CaseSensitive;
+        }
+        VERIFY_NOT_REACHED();
+    }(attribute.case_type);
+    auto case_insensitive_match = case_sensitivity == CaseSensitivity::CaseInsensitive;
 
     switch (attribute.match_type) {
     case CSS::Selector::SimpleSelector::Attribute::MatchType::ExactValueMatch: