mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-12-16 08:18:59 +00:00
preserve casing (tags/attributes) for SVG/XML/MathML namespace
This commit is contained in:
@@ -355,7 +355,7 @@ pub fn documentIsLoaded(self: *Page) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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(
|
try self._event_manager.dispatch(
|
||||||
self.document.asEventTarget(),
|
self.document.asEventTarget(),
|
||||||
event,
|
event,
|
||||||
@@ -1292,7 +1292,9 @@ fn populateElementAttributes(self: *Page, element: *Element, list: anytype) !voi
|
|||||||
var existing = list orelse return;
|
var existing = list orelse return;
|
||||||
|
|
||||||
var attributes = try self.arena.create(Element.Attribute.List);
|
var attributes = try self.arena.create(Element.Attribute.List);
|
||||||
attributes.* = .{};
|
attributes.* = .{
|
||||||
|
.normalize = existing.normalize,
|
||||||
|
};
|
||||||
|
|
||||||
var it = existing.iterator();
|
var it = existing.iterator();
|
||||||
while (it.next()) |attr| {
|
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) {
|
if (@TypeOf(list) == @TypeOf(null) or list.count() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var attributes = try self.arena.create(Element.Attribute.List);
|
var attributes = try element.createAttributeList(self);
|
||||||
attributes.* = .{};
|
|
||||||
while (list.next()) |attr| {
|
while (list.next()) |attr| {
|
||||||
try attributes.putNew(attr.name.local.slice(), attr.value.slice(), self);
|
try attributes.putNew(attr.name.local.slice(), attr.value.slice(), self);
|
||||||
}
|
}
|
||||||
element._attributes = attributes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn createTextNode(self: *Page, text: []const u8) !*Node {
|
pub fn createTextNode(self: *Page, text: []const u8) !*Node {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
|
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
|
||||||
//
|
//
|
||||||
// Francis Bouvier <francis@lightpanda.io>
|
// Francis Bouvier <francis@lightpanda.io>
|
||||||
// Pierre Tachoire <pierre@lightpanda.io>
|
// Pierre Tachoire <pierre@lightpanda.io>
|
||||||
@@ -284,11 +284,11 @@ fn writeEscapedByte(input: []const u8, index: usize, writer: *std.Io.Writer) ![]
|
|||||||
// non breaking space
|
// non breaking space
|
||||||
if (input.len > index + 1 and input[index + 1] == 160) {
|
if (input.len > index + 1 and input[index + 1] == 160) {
|
||||||
try writer.writeAll(" ");
|
try writer.writeAll(" ");
|
||||||
return input [index + 2 ..];
|
return input[index + 2 ..];
|
||||||
}
|
}
|
||||||
try writer.writeByte(194);
|
try writer.writeByte(194);
|
||||||
},
|
},
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
}
|
}
|
||||||
return input[index + 1..];
|
return input[index + 1 ..];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,13 +242,7 @@ fn _addAttrsIfMissingCallback(self: *Parser, node: *Node, attributes: h5e.Attrib
|
|||||||
const element = node.as(Element);
|
const element = node.as(Element);
|
||||||
const page = self.page;
|
const page = self.page;
|
||||||
|
|
||||||
const attr_list = element._attributes orelse blk: {
|
const attr_list = try element.getOrCreateAttributeList(page);
|
||||||
const a = try page.arena.create(@import("../webapi/element/Attribute.zig").List);
|
|
||||||
a.* = .{};
|
|
||||||
element._attributes = a;
|
|
||||||
break :blk a;
|
|
||||||
};
|
|
||||||
|
|
||||||
while (attributes.next()) |attr| {
|
while (attributes.next()) |attr| {
|
||||||
const name = attr.name.local.slice();
|
const name = attr.name.local.slice();
|
||||||
const value = attr.value.slice();
|
const value = attr.value.slice();
|
||||||
|
|||||||
@@ -52,3 +52,16 @@
|
|||||||
testing.expectEqual(0, ec.length);
|
testing.expectEqual(0, ec.length);
|
||||||
testing.expectEqual(undefined, ec[0]);
|
testing.expectEqual(undefined, ec[0]);
|
||||||
</script>
|
</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 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 Privacy</p>', p.outerHTML);
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -6,23 +6,61 @@
|
|||||||
<SVG id=svg3></SVG>
|
<SVG id=svg3></SVG>
|
||||||
|
|
||||||
<script id=svg>
|
<script id=svg>
|
||||||
let svg1 = $('#svg1');
|
{
|
||||||
testing.expectEqual('svg', svg1.tagName);
|
let svg1 = $('#svg1');
|
||||||
testing.expectEqual('http://www.w3.org/2000/svg', svg1.namespaceURI);
|
testing.expectEqual('svg', svg1.tagName);
|
||||||
|
testing.expectEqual('http://www.w3.org/2000/svg', svg1.namespaceURI);
|
||||||
|
|
||||||
let svg2 = $('#svg2');
|
let svg2 = $('#svg2');
|
||||||
testing.expectEqual('svg', svg2.tagName);
|
testing.expectEqual('svg', svg2.tagName);
|
||||||
testing.expectEqual('http://www.w3.org/2000/svg', svg2.namespaceURI);
|
testing.expectEqual('http://www.w3.org/2000/svg', svg2.namespaceURI);
|
||||||
|
|
||||||
let svg3 = $('#svg3');
|
let svg3 = $('#svg3');
|
||||||
testing.expectEqual('svg', svg3.tagName);
|
testing.expectEqual('svg', svg3.tagName);
|
||||||
testing.expectEqual('http://www.w3.org/2000/svg', svg3.namespaceURI);
|
testing.expectEqual('http://www.w3.org/2000/svg', svg3.namespaceURI);
|
||||||
|
|
||||||
const svg4 = document.createElementNS('http://www.w3.org/2000/svg', 'SvG');
|
const svg4 = document.createElementNS('http://www.w3.org/2000/svg', 'SvG');
|
||||||
testing.expectEqual('SvG', svg4.tagName);
|
testing.expectEqual('SvG', svg4.tagName);
|
||||||
testing.expectEqual('http://www.w3.org/2000/svg', svg4.namespaceURI);
|
testing.expectEqual('http://www.w3.org/2000/svg', svg4.namespaceURI);
|
||||||
|
|
||||||
const svg5 = document.createElement('SvG');
|
const svg5 = document.createElement('SvG');
|
||||||
testing.expectEqual('SVG', svg5.tagName);
|
testing.expectEqual('SVG', svg5.tagName);
|
||||||
testing.expectEqual('http://www.w3.org/1999/xhtml', svg5.namespaceURI);
|
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>
|
</script>
|
||||||
|
|||||||
@@ -28,3 +28,22 @@
|
|||||||
testing.expectEqual('a<p></p>b', container.innerHTML);
|
testing.expectEqual('a<p></p>b', container.innerHTML);
|
||||||
testing.expectEqual(3, container.childNodes.length);
|
testing.expectEqual(3, container.childNodes.length);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<span id=token class="token" style="color:#ce9178">"puppeteer "</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>
|
||||||
|
|||||||
@@ -343,7 +343,7 @@ pub fn getOrCreateAttributeList(self: *Element, page: *Page) !*Attribute.List {
|
|||||||
pub fn createAttributeList(self: *Element, page: *Page) !*Attribute.List {
|
pub fn createAttributeList(self: *Element, page: *Page) !*Attribute.List {
|
||||||
std.debug.assert(self._attributes == null);
|
std.debug.assert(self._attributes == null);
|
||||||
const a = try page.arena.create(Attribute.List);
|
const a = try page.arena.create(Attribute.List);
|
||||||
a.* = .{.normalize = self._namespace == .html};
|
a.* = .{ .normalize = self._namespace == .html };
|
||||||
self._attributes = a;
|
self._attributes = a;
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ pub const JsApi = struct {
|
|||||||
// in our Entry? Because that would require an extra 8 bytes for every single
|
// 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.
|
// attribute in the DOM, and, again, we expect that to almost always be null.
|
||||||
pub const List = struct {
|
pub const List = struct {
|
||||||
|
normalize: bool,
|
||||||
_list: std.DoublyLinkedList = .{},
|
_list: std.DoublyLinkedList = .{},
|
||||||
|
|
||||||
pub fn isEmpty(self: *const List) bool {
|
pub fn isEmpty(self: *const List) bool {
|
||||||
@@ -273,7 +274,9 @@ pub const List = struct {
|
|||||||
entry: ?*Entry,
|
entry: ?*Entry,
|
||||||
};
|
};
|
||||||
fn getEntryAndNormalizedName(self: *const List, name: []const u8, page: *Page) !NormalizeAndEntry {
|
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 .{
|
return .{
|
||||||
.normalized = normalized,
|
.normalized = normalized,
|
||||||
.entry = self.getEntryWithNormalizedName(normalized),
|
.entry = self.getEntryWithNormalizedName(normalized),
|
||||||
|
|||||||
Reference in New Issue
Block a user