From 1cde0bb8b8f0317d3823e11877d220ef83d585b7 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Fri, 5 Dec 2025 17:53:40 +0800 Subject: [PATCH] preserve casing (tags/attributes) for SVG/XML/MathML namespace --- src/browser/Page.zig | 10 ++-- src/browser/dump.zig | 6 +-- src/browser/parser/Parser.zig | 8 +-- src/browser/tests/element/element.html | 13 +++++ src/browser/tests/element/svg/svg.html | 68 ++++++++++++++++++------ src/browser/tests/node/normalize.html | 19 +++++++ src/browser/webapi/Element.zig | 2 +- src/browser/webapi/element/Attribute.zig | 5 +- 8 files changed, 99 insertions(+), 32 deletions(-) diff --git a/src/browser/Page.zig b/src/browser/Page.zig index 6c94e453..0845b72b 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -355,7 +355,7 @@ pub fn documentIsLoaded(self: *Page) void { } pub fn _documentIsLoaded(self: *Page) !void { - const event = try Event.init("DOMContentLoaded", .{}, self); + const event = try Event.init("DOMContentLoaded", .{ .bubbles = true }, self); try self._event_manager.dispatch( self.document.asEventTarget(), event, @@ -1292,7 +1292,9 @@ fn populateElementAttributes(self: *Page, element: *Element, list: anytype) !voi var existing = list orelse return; var attributes = try self.arena.create(Element.Attribute.List); - attributes.* = .{}; + attributes.* = .{ + .normalize = existing.normalize, + }; var it = existing.iterator(); while (it.next()) |attr| { @@ -1306,12 +1308,10 @@ fn populateElementAttributes(self: *Page, element: *Element, list: anytype) !voi if (@TypeOf(list) == @TypeOf(null) or list.count() == 0) { return; } - var attributes = try self.arena.create(Element.Attribute.List); - attributes.* = .{}; + var attributes = try element.createAttributeList(self); while (list.next()) |attr| { try attributes.putNew(attr.name.local.slice(), attr.value.slice(), self); } - element._attributes = attributes; } pub fn createTextNode(self: *Page, text: []const u8) !*Node { diff --git a/src/browser/dump.zig b/src/browser/dump.zig index 91128732..62807397 100644 --- a/src/browser/dump.zig +++ b/src/browser/dump.zig @@ -1,4 +1,4 @@ - // Copyright (C) 2023-2025 Lightpanda (Selecy SAS) +// Copyright (C) 2023-2025 Lightpanda (Selecy SAS) // // Francis Bouvier // Pierre Tachoire @@ -284,11 +284,11 @@ fn writeEscapedByte(input: []const u8, index: usize, writer: *std.Io.Writer) ![] // non breaking space if (input.len > index + 1 and input[index + 1] == 160) { try writer.writeAll(" "); - return input [index + 2 ..]; + return input[index + 2 ..]; } try writer.writeByte(194); }, else => unreachable, } - return input[index + 1..]; + return input[index + 1 ..]; } diff --git a/src/browser/parser/Parser.zig b/src/browser/parser/Parser.zig index e2c154dd..65e25ecd 100644 --- a/src/browser/parser/Parser.zig +++ b/src/browser/parser/Parser.zig @@ -242,13 +242,7 @@ fn _addAttrsIfMissingCallback(self: *Parser, node: *Node, attributes: h5e.Attrib const element = node.as(Element); const page = self.page; - const attr_list = element._attributes orelse blk: { - const a = try page.arena.create(@import("../webapi/element/Attribute.zig").List); - a.* = .{}; - element._attributes = a; - break :blk a; - }; - + const attr_list = try element.getOrCreateAttributeList(page); while (attributes.next()) |attr| { const name = attr.name.local.slice(); const value = attr.value.slice(); diff --git a/src/browser/tests/element/element.html b/src/browser/tests/element/element.html index 4461622f..f779c9cd 100644 --- a/src/browser/tests/element/element.html +++ b/src/browser/tests/element/element.html @@ -52,3 +52,16 @@ testing.expectEqual(0, ec.length); testing.expectEqual(undefined, ec[0]); + + diff --git a/src/browser/tests/element/svg/svg.html b/src/browser/tests/element/svg/svg.html index 77f29c31..b981089c 100644 --- a/src/browser/tests/element/svg/svg.html +++ b/src/browser/tests/element/svg/svg.html @@ -6,23 +6,61 @@ + + + + OVER 9000!! + + + + + OVER 9000!!! + + + diff --git a/src/browser/tests/node/normalize.html b/src/browser/tests/node/normalize.html index 45c2a0bb..ead599a6 100644 --- a/src/browser/tests/node/normalize.html +++ b/src/browser/tests/node/normalize.html @@ -28,3 +28,22 @@ testing.expectEqual('a

b', container.innerHTML); testing.expectEqual(3, container.childNodes.length); + +"puppeteer " +

Leto + + + Atreides

+ diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig index eadcff3d..758bb1f2 100644 --- a/src/browser/webapi/Element.zig +++ b/src/browser/webapi/Element.zig @@ -343,7 +343,7 @@ pub fn getOrCreateAttributeList(self: *Element, page: *Page) !*Attribute.List { pub fn createAttributeList(self: *Element, page: *Page) !*Attribute.List { std.debug.assert(self._attributes == null); const a = try page.arena.create(Attribute.List); - a.* = .{.normalize = self._namespace == .html}; + a.* = .{ .normalize = self._namespace == .html }; self._attributes = a; return a; } diff --git a/src/browser/webapi/element/Attribute.zig b/src/browser/webapi/element/Attribute.zig index b4b9a4ee..1919a24e 100644 --- a/src/browser/webapi/element/Attribute.zig +++ b/src/browser/webapi/element/Attribute.zig @@ -118,6 +118,7 @@ pub const JsApi = struct { // in our Entry? Because that would require an extra 8 bytes for every single // attribute in the DOM, and, again, we expect that to almost always be null. pub const List = struct { + normalize: bool, _list: std.DoublyLinkedList = .{}, pub fn isEmpty(self: *const List) bool { @@ -273,7 +274,9 @@ pub const List = struct { entry: ?*Entry, }; fn getEntryAndNormalizedName(self: *const List, name: []const u8, page: *Page) !NormalizeAndEntry { - const normalized = try normalizeNameForLookup(name, page); + const normalized = + if (self.normalize) try normalizeNameForLookup(name, page) else name; + return .{ .normalized = normalized, .entry = self.getEntryWithNormalizedName(normalized),