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.
This commit is contained in:
Tim Ledbetter 2024-08-18 21:17:08 +01:00 committed by Andreas Kling
parent c422518792
commit 00f03f3e90
Notes: github-actions[bot] 2024-08-19 07:04:16 +00:00
5 changed files with 54 additions and 62 deletions

View file

@ -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

View file

@ -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>

View file

@ -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>);

View file

@ -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,
}
};

View file

@ -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: