From f12f7963db380948106ca6c45663387baa6bae17 Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Sun, 18 Aug 2024 21:17:08 +0100 Subject: [PATCH] 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. --- .../attribute-selector-case-sensitivity.txt | 4 ++ .../attribute-selector-case-sensitivity.html | 18 +++++++ Userland/Libraries/LibWeb/CSS/Parser/Parser.h | 50 ------------------- .../LibWeb/CSS/Parser/SelectorParsing.cpp | 9 +--- .../Libraries/LibWeb/CSS/SelectorEngine.cpp | 35 +++++++++++-- 5 files changed, 54 insertions(+), 62 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/css/attribute-selector-case-sensitivity.txt create mode 100644 Tests/LibWeb/Text/input/css/attribute-selector-case-sensitivity.html diff --git a/Tests/LibWeb/Text/expected/css/attribute-selector-case-sensitivity.txt b/Tests/LibWeb/Text/expected/css/attribute-selector-case-sensitivity.txt new file mode 100644 index 000000000000..1bfb3c1d2e5f --- /dev/null +++ b/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 diff --git a/Tests/LibWeb/Text/input/css/attribute-selector-case-sensitivity.html b/Tests/LibWeb/Text/input/css/attribute-selector-case-sensitivity.html new file mode 100644 index 000000000000..535d2ffabcd6 --- /dev/null +++ b/Tests/LibWeb/Text/input/css/attribute-selector-case-sensitivity.html @@ -0,0 +1,18 @@ + + + diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h index 029dff9c8cf5..43492dccf039 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h @@ -77,56 +77,6 @@ class Parser { [[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); diff --git a/Userland/Libraries/LibWeb/CSS/Parser/SelectorParsing.cpp b/Userland/Libraries/LibWeb/CSS/Parser/SelectorParsing.cpp index 15693ff8c79e..523a737b3475 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/SelectorParsing.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/SelectorParsing.cpp @@ -260,15 +260,8 @@ Parser::ParseErrorOr 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, } }; diff --git a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp index efd95c882b74..8710ae3b6a3a 100644 --- a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp +++ b/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: