diff --git a/src/browser/webapi/selector/List.zig b/src/browser/webapi/selector/List.zig index 81ef82c3f..2261e0c83 100644 --- a/src/browser/webapi/selector/List.zig +++ b/src/browser/webapi/selector/List.zig @@ -445,7 +445,11 @@ fn matchesPart(el: *Node.Element, part: Part, scope: *Node, frame: *Frame) bool } fn matchesAttribute(el: *Node.Element, attr: Selector.Attribute) bool { - const value = el.getAttributeSafe(attr.name) orelse { + // Attribute names match ASCII case-insensitively on HTML elements (both + // sides lowercased) and case-sensitively on foreign elements, whose + // attributes are stored as written. + const name = if (el._namespace == .html) attr.name else attr.original_name; + const value = el.getAttributeSafe(name) orelse { return false; }; diff --git a/src/browser/webapi/selector/Parser.zig b/src/browser/webapi/selector/Parser.zig index 29adb4b3b..b66ea8c71 100644 --- a/src/browser/webapi/selector/Parser.zig +++ b/src/browser/webapi/selector/Parser.zig @@ -962,6 +962,8 @@ fn attribute(self: *Parser, arena: Allocator) !Selector.Attribute { } const attr_name = try self.attributeName(arena); + // As written, for the case-sensitive match on foreign elements. + const original_name = try arena.dupe(u8, attr_name); // Normalize the name to lowercase for fast matching (consistent with Attribute.normalizeNameForLookup) const name = try Attribute.normalizeNameForLookupAlloc(arena, .wrap(attr_name)); @@ -974,7 +976,7 @@ fn attribute(self: *Parser, arena: Allocator) !Selector.Attribute { if (self.peek() == ']') { self.input = self.input[1..]; } - return .{ .name = name, .matcher = .presence, .case_insensitive = case_insensitive }; + return .{ .name = name, .original_name = .wrap(original_name), .matcher = .presence, .case_insensitive = case_insensitive }; } const matcher_type = try self.attributeMatcher(); @@ -1012,7 +1014,7 @@ fn attribute(self: *Parser, arena: Allocator) !Selector.Attribute { .presence => unreachable, }; - return .{ .name = name, .matcher = matcher, .case_insensitive = case_insensitive }; + return .{ .name = name, .original_name = .wrap(original_name), .matcher = matcher, .case_insensitive = case_insensitive }; } fn attributeName(self: *Parser, arena: Allocator) ![]const u8 { diff --git a/src/browser/webapi/selector/Selector.zig b/src/browser/webapi/selector/Selector.zig index 71658419b..c6762372c 100644 --- a/src/browser/webapi/selector/Selector.zig +++ b/src/browser/webapi/selector/Selector.zig @@ -229,7 +229,11 @@ pub const Part = union(enum) { }; pub const Attribute = struct { + // Lowercased for the HTML-element case-insensitive match. name: String, + // As written in the selector: attribute names match case-sensitively on + // foreign (SVG/MathML) elements, whose attributes are stored unnormalized. + original_name: String, matcher: AttributeMatcher, case_insensitive: bool, };