Skip to content

Commit

Permalink
LibWeb: Match attribute selectors case insensitively in XML documents
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
tcl3 committed Aug 18, 2024
1 parent 0852e4b commit f12f796
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 62 deletions.
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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>
50 changes: 0 additions & 50 deletions Userland/Libraries/LibWeb/CSS/Parser/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<Token>);

Expand Down
9 changes: 1 addition & 8 deletions Userland/Libraries/LibWeb/CSS/Parser/SelectorParsing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
};

Expand Down
35 changes: 31 additions & 4 deletions Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit f12f796

Please sign in to comment.