preserve casing (tags/attributes) for SVG/XML/MathML namespace

This commit is contained in:
Karl Seguin
2025-12-05 17:53:40 +08:00
parent 61a1a2564e
commit 1cde0bb8b8
8 changed files with 99 additions and 32 deletions

View File

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

View File

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

View File

@@ -52,3 +52,16 @@
testing.expectEqual(0, ec.length);
testing.expectEqual(undefined, ec[0]);
</script>
<script id=nonBreakingSpace>
// Test non-breaking space encoding (critical for React hydration)
const div = document.createElement('div');
div.innerHTML = 'hello\xa0world';
testing.expectEqual('hello\xa0world', div.textContent);
testing.expectEqual('hello&nbsp;world', div.innerHTML);
// Test that outerHTML also encodes non-breaking spaces correctly
const p = document.createElement('p');
p.textContent = 'XAnge\xa0Privacy';
testing.expectEqual('<p>XAnge&nbsp;Privacy</p>', p.outerHTML);
</script>

View File

@@ -6,6 +6,7 @@
<SVG id=svg3></SVG>
<script id=svg>
{
let svg1 = $('#svg1');
testing.expectEqual('svg', svg1.tagName);
testing.expectEqual('http://www.w3.org/2000/svg', svg1.namespaceURI);
@@ -25,4 +26,41 @@
const svg5 = document.createElement('SvG');
testing.expectEqual('SVG', svg5.tagName);
testing.expectEqual('http://www.w3.org/1999/xhtml', svg5.namespaceURI);
}
</script>
<svg id=lower width="200" height="100" style="border:1px solid #ccc" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100">
<rect></rect>
<text x="100" y="95" font-size="14" text-anchor="middle">OVER 9000!!</text>
</svg>
<SVG ID=UPPER WIDTH="200" HEIGHT="100" STYLE="BORDER:1PX SOLID #CCC" XMLNS="http://www.w3.org/2000/svg" VIEWBOX="0 0 200 100">
<RECT></RECT>
<TEXT X="100" Y="95" FONT-SIZE="14" TEXT-ANCHOR="MIDDLE">OVER 9000!!!</TEXT>
</SVG>
<script id=casing>
testing.expectEqual(false, 'AString' instanceof SVGElement);
const lower = $('#lower');
testing.expectEqual('http://www.w3.org/2000/svg', lower.getAttribute('xmlns'));
testing.expectEqual('http://www.w3.org/2000/svg', lower.getAttributeNode('xmlns').value);
testing.expectEqual('http://www.w3.org/2000/svg', lower.attributes.getNamedItem('xmlns').value);
testing.expectEqual('0 0 200 100', lower.getAttribute('viewBox'));
testing.expectEqual('viewBox', lower.getAttributeNode('viewBox').name);
testing.expectEqual(true, lower.outerHTML.includes('viewBox'));
testing.expectEqual('svg', lower.tagName);
testing.expectEqual('rect', lower.querySelector('rect').tagName);
testing.expectEqual('text', lower.querySelector('text').tagName);
const upper = $('#UPPER');
testing.expectEqual('http://www.w3.org/2000/svg', upper.getAttribute('xmlns'));
testing.expectEqual('http://www.w3.org/2000/svg', upper.getAttributeNode('xmlns').value);
testing.expectEqual('http://www.w3.org/2000/svg', upper.attributes.getNamedItem('xmlns').value);
testing.expectEqual('0 0 200 100', upper.getAttribute('viewBox'));
testing.expectEqual('viewBox', upper.getAttributeNode('viewBox').name);
testing.expectEqual(true, upper.outerHTML.includes('viewBox'));
testing.expectEqual('svg', upper.tagName);
testing.expectEqual('rect', upper.querySelector('rect').tagName);
testing.expectEqual('text', upper.querySelector('text').tagName);
</script>

View File

@@ -28,3 +28,22 @@
testing.expectEqual('a<p></p>b', container.innerHTML);
testing.expectEqual(3, container.childNodes.length);
</script>
<span id=token class="token" style="color:#ce9178">&quot;puppeteer &quot;</span>
<h3 id=name>Leto
<!-- -->
<!-- -->
Atreides</h3>
<script id=adjascent_test_nodes>
const token = $('#token');
testing.expectEqual('"puppeteer "', token.firstChild.nodeValue);
const name = $('#name');
testing.expectEqual([
"Leto\n ",
" ",
"\n ",
" ",
"\n Atreides"
], Array.from(name.childNodes).map((n) => n.nodeValue));
</script>

View File

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