mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-02-04 14:33:47 +00:00
Merge pull request #1342 from lightpanda-io/better_namespace_support
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Improve support for non-HTML namespace
This commit is contained in:
1221
src/browser/Page.zig
1221
src/browser/Page.zig
File diff suppressed because it is too large
Load Diff
@@ -104,7 +104,7 @@ pub fn parseXML(self: *Parser, xml: []const u8) void {
|
||||
xml.len,
|
||||
&self.container,
|
||||
self,
|
||||
createElementCallback,
|
||||
createXMLElementCallback,
|
||||
getDataCallback,
|
||||
appendCallback,
|
||||
parseErrorCallback,
|
||||
@@ -225,17 +225,26 @@ fn _popCallback(self: *Parser, node: *Node) !void {
|
||||
}
|
||||
|
||||
fn createElementCallback(ctx: *anyopaque, data: *anyopaque, qname: h5e.QualName, attributes: h5e.AttributeIterator) callconv(.c) ?*anyopaque {
|
||||
return _createElementCallbackWithDefaultnamespace(ctx, data, qname, attributes, .unknown);
|
||||
}
|
||||
|
||||
fn createXMLElementCallback(ctx: *anyopaque, data: *anyopaque, qname: h5e.QualName, attributes: h5e.AttributeIterator) callconv(.c) ?*anyopaque {
|
||||
return _createElementCallbackWithDefaultnamespace(ctx, data, qname, attributes, .xml);
|
||||
}
|
||||
|
||||
fn _createElementCallbackWithDefaultnamespace(ctx: *anyopaque, data: *anyopaque, qname: h5e.QualName, attributes: h5e.AttributeIterator, default_namespace: Element.Namespace) ?*anyopaque {
|
||||
const self: *Parser = @ptrCast(@alignCast(ctx));
|
||||
return self._createElementCallback(data, qname, attributes) catch |err| {
|
||||
return self._createElementCallback(data, qname, attributes, default_namespace) catch |err| {
|
||||
self.err = .{ .err = err, .source = .create_element };
|
||||
return null;
|
||||
};
|
||||
}
|
||||
fn _createElementCallback(self: *Parser, data: *anyopaque, qname: h5e.QualName, attributes: h5e.AttributeIterator) !*anyopaque {
|
||||
fn _createElementCallback(self: *Parser, data: *anyopaque, qname: h5e.QualName, attributes: h5e.AttributeIterator, default_namespace: Element.Namespace) !*anyopaque {
|
||||
const page = self.page;
|
||||
const name = qname.local.slice();
|
||||
const namespace = qname.ns.slice();
|
||||
const node = try page.createElement(namespace, name, attributes);
|
||||
const namespace_string = qname.ns.slice();
|
||||
const namespace = if (namespace_string.len == 0) default_namespace else Element.Namespace.parse(namespace_string);
|
||||
const node = try page.createElementNS(namespace, name, attributes);
|
||||
|
||||
const pn = try self.arena.create(ParsedNode);
|
||||
pn.* = .{
|
||||
|
||||
@@ -19,12 +19,13 @@
|
||||
testing.expectEqual('http://www.w3.org/XML/1998/namespace', xmlElement.namespaceURI);
|
||||
|
||||
const nullNsElement = document.createElementNS(null, 'span');
|
||||
testing.expectEqual('SPAN', nullNsElement.tagName);
|
||||
testing.expectEqual('http://www.w3.org/1999/xhtml', nullNsElement.namespaceURI);
|
||||
testing.expectEqual('span', nullNsElement.tagName);
|
||||
testing.expectEqual(null, nullNsElement.namespaceURI);
|
||||
|
||||
const unknownNsElement = document.createElementNS('http://example.com/unknown', 'custom');
|
||||
testing.expectEqual('CUSTOM', unknownNsElement.tagName);
|
||||
testing.expectEqual('http://www.w3.org/1999/xhtml', unknownNsElement.namespaceURI);
|
||||
testing.expectEqual('custom', unknownNsElement.tagName);
|
||||
// Should be http://example.com/unknown
|
||||
testing.expectEqual('http://lightpanda.io/unsupported/namespace', unknownNsElement.namespaceURI);
|
||||
|
||||
const regularDiv = document.createElement('div');
|
||||
testing.expectEqual('DIV', regularDiv.tagName);
|
||||
@@ -36,5 +37,5 @@
|
||||
testing.expectEqual('te:ST', custom.tagName);
|
||||
testing.expectEqual('te', custom.prefix);
|
||||
testing.expectEqual('ST', custom.localName);
|
||||
testing.expectEqual('http://www.w3.org/1999/xhtml', custom.namespaceURI); // Should be test
|
||||
testing.expectEqual('http://lightpanda.io/unsupported/namespace', custom.namespaceURI); // Should be test
|
||||
</script>
|
||||
|
||||
@@ -168,7 +168,7 @@
|
||||
const root = doc.documentElement;
|
||||
testing.expectEqual(true, root !== null);
|
||||
// TODO: XML documents should preserve case, but we currently uppercase
|
||||
testing.expectEqual('ROOT', root.tagName);
|
||||
testing.expectEqual('root', root.tagName);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -206,10 +206,9 @@
|
||||
const doc = impl.createDocument('http://example.com', 'prefix:localName', null);
|
||||
|
||||
const root = doc.documentElement;
|
||||
// TODO: XML documents should preserve case, but we currently uppercase
|
||||
testing.expectEqual('prefix:LOCALNAME', root.tagName);
|
||||
// TODO: Custom namespaces are being overridden to XHTML namespace
|
||||
testing.expectEqual('http://www.w3.org/1999/xhtml', root.namespaceURI);
|
||||
testing.expectEqual('prefix:localName', root.tagName);
|
||||
// TODO: Custom namespaces are being replaced with an empty value
|
||||
testing.expectEqual('http://lightpanda.io/unsupported/namespace', root.namespaceURI);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -224,8 +223,7 @@
|
||||
doc.documentElement.appendChild(child);
|
||||
|
||||
testing.expectEqual(1, doc.documentElement.childNodes.length);
|
||||
// TODO: XML documents should preserve case, but we currently uppercase
|
||||
testing.expectEqual('CHILD', doc.documentElement.firstChild.tagName);
|
||||
testing.expectEqual('child', doc.documentElement.firstChild.tagName);
|
||||
testing.expectEqual('Test', doc.documentElement.firstChild.textContent);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -364,14 +364,14 @@
|
||||
];
|
||||
|
||||
for (const mime of mimes) {
|
||||
const doc = parser.parseFromString(sampleXML, "text/xml");
|
||||
const doc = parser.parseFromString(sampleXML, mime);
|
||||
const { firstChild: { childNodes, children: collection, tagName }, children } = doc;
|
||||
// doc.
|
||||
testing.expectEqual(true, doc instanceof XMLDocument);
|
||||
testing.expectEqual(1, children.length);
|
||||
// firstChild.
|
||||
// TODO: Modern browsers expect this in lowercase.
|
||||
testing.expectEqual("CATALOG", tagName);
|
||||
testing.expectEqual("catalog", tagName);
|
||||
testing.expectEqual(25, childNodes.length);
|
||||
testing.expectEqual(12, collection.length);
|
||||
// Check children of first child.
|
||||
@@ -379,12 +379,12 @@
|
||||
const {children: elements, id} = collection.item(i);
|
||||
testing.expectEqual("bk" + (100 + i + 1), id);
|
||||
// TODO: Modern browsers expect these in lowercase.
|
||||
testing.expectEqual("AUTHOR", elements.item(0).tagName);
|
||||
testing.expectEqual("TITLE", elements.item(1).tagName);
|
||||
testing.expectEqual("GENRE", elements.item(2).tagName);
|
||||
testing.expectEqual("PRICE", elements.item(3).tagName);
|
||||
testing.expectEqual("PUBLISH_DATE", elements.item(4).tagName);
|
||||
testing.expectEqual("DESCRIPTION", elements.item(5).tagName);
|
||||
testing.expectEqual("author", elements.item(0).tagName);
|
||||
testing.expectEqual("title", elements.item(1).tagName);
|
||||
testing.expectEqual("genre", elements.item(2).tagName);
|
||||
testing.expectEqual("price", elements.item(3).tagName);
|
||||
testing.expectEqual("publish_date", elements.item(4).tagName);
|
||||
testing.expectEqual("description", elements.item(5).tagName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,26 +57,26 @@ pub fn createHTMLDocument(_: *const DOMImplementation, title: ?[]const u8, page:
|
||||
_ = try document.asNode().appendChild(doctype.asNode(), page);
|
||||
}
|
||||
|
||||
const html_node = try page.createElement(null, "html", null);
|
||||
const html_node = try page.createElementNS(.html, "html", null);
|
||||
_ = try document.asNode().appendChild(html_node, page);
|
||||
|
||||
const head_node = try page.createElement(null, "head", null);
|
||||
const head_node = try page.createElementNS(.html, "head", null);
|
||||
_ = try html_node.appendChild(head_node, page);
|
||||
|
||||
if (title) |t| {
|
||||
const title_node = try page.createElement(null, "title", null);
|
||||
const title_node = try page.createElementNS(.html, "title", null);
|
||||
_ = try head_node.appendChild(title_node, page);
|
||||
const text_node = try page.createTextNode(t);
|
||||
_ = try title_node.appendChild(text_node, page);
|
||||
}
|
||||
|
||||
const body_node = try page.createElement(null, "body", null);
|
||||
const body_node = try page.createElementNS(.html, "body", null);
|
||||
_ = try html_node.appendChild(body_node, page);
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
pub fn createDocument(_: *const DOMImplementation, namespace: ?[]const u8, qualified_name: ?[]const u8, doctype: ?*DocumentType, page: *Page) !*Document {
|
||||
pub fn createDocument(_: *const DOMImplementation, namespace_: ?[]const u8, qualified_name: ?[]const u8, doctype: ?*DocumentType, page: *Page) !*Document {
|
||||
// Create XML Document
|
||||
const document = (try page._factory.document(Node.Document.XMLDocument{ ._proto = undefined })).asDocument();
|
||||
|
||||
@@ -88,7 +88,8 @@ pub fn createDocument(_: *const DOMImplementation, namespace: ?[]const u8, quali
|
||||
// Create and append root element if qualified_name provided
|
||||
if (qualified_name) |qname| {
|
||||
if (qname.len > 0) {
|
||||
const root = try page.createElement(namespace, qname, null);
|
||||
const namespace = if (namespace_) |ns| Node.Element.Namespace.parse(ns) else .xml;
|
||||
const root = try page.createElementNS(namespace, qname, null);
|
||||
_ = try document.asNode().appendChild(root, page);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +124,14 @@ const CreateElementOptions = struct {
|
||||
};
|
||||
|
||||
pub fn createElement(self: *Document, name: []const u8, options_: ?CreateElementOptions, page: *Page) !*Element {
|
||||
const node = try page.createElement(null, name, null);
|
||||
const namespace: Element.Namespace = blk: {
|
||||
if (self._type == .xml) {
|
||||
@branchHint(.unlikely);
|
||||
break :blk .xml;
|
||||
}
|
||||
break :blk .html;
|
||||
};
|
||||
const node = try page.createElementNS(namespace, name, null);
|
||||
const element = node.as(Element);
|
||||
|
||||
// Track owner document if it's not the main document
|
||||
@@ -142,7 +149,7 @@ pub fn createElement(self: *Document, name: []const u8, options_: ?CreateElement
|
||||
}
|
||||
|
||||
pub fn createElementNS(self: *Document, namespace: ?[]const u8, name: []const u8, page: *Page) !*Element {
|
||||
const node = try page.createElement(namespace, name, null);
|
||||
const node = try page.createElementNS(Element.Namespace.parse(namespace), name, null);
|
||||
|
||||
// Track owner document if it's not the main document
|
||||
if (self != page.document) {
|
||||
|
||||
@@ -53,15 +53,41 @@ pub const Namespace = enum(u8) {
|
||||
svg,
|
||||
mathml,
|
||||
xml,
|
||||
// We should keep the original value, but don't. If this becomes important
|
||||
// consider storing it in a page lookup, like `_element_class_lists`, rather
|
||||
// that adding a slice directly here (directly in every element).
|
||||
unknown,
|
||||
null,
|
||||
|
||||
pub fn toUri(self: Namespace) []const u8 {
|
||||
pub fn toUri(self: Namespace) ?[]const u8 {
|
||||
return switch (self) {
|
||||
.html => "http://www.w3.org/1999/xhtml",
|
||||
.svg => "http://www.w3.org/2000/svg",
|
||||
.mathml => "http://www.w3.org/1998/Math/MathML",
|
||||
.xml => "http://www.w3.org/XML/1998/namespace",
|
||||
.unknown => "http://lightpanda.io/unsupported/namespace",
|
||||
.null => null,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn parse(namespace_: ?[]const u8) Namespace {
|
||||
const namespace = namespace_ orelse return .null;
|
||||
if (namespace.len == "http://www.w3.org/1999/xhtml".len) {
|
||||
// Common case, avoid the string comparion. Recklessly
|
||||
@branchHint(.likely);
|
||||
return .html;
|
||||
}
|
||||
if (std.mem.eql(u8, namespace, "http://www.w3.org/XML/1998/namespace")) {
|
||||
return .xml;
|
||||
}
|
||||
if (std.mem.eql(u8, namespace, "http://www.w3.org/2000/svg")) {
|
||||
return .svg;
|
||||
}
|
||||
if (std.mem.eql(u8, namespace, "http://www.w3.org/1998/Math/MathML")) {
|
||||
return .mathml;
|
||||
}
|
||||
return .unknown;
|
||||
}
|
||||
};
|
||||
|
||||
_type: Type,
|
||||
@@ -211,60 +237,54 @@ pub fn getTagNameLower(self: *const Element) []const u8 {
|
||||
}
|
||||
|
||||
pub fn getTagNameSpec(self: *const Element, buf: []u8) []const u8 {
|
||||
switch (self._type) {
|
||||
return switch (self._type) {
|
||||
.html => |he| switch (he._type) {
|
||||
.custom => |e| {
|
||||
@branchHint(.unlikely);
|
||||
return upperTagName(&e._tag_name, buf);
|
||||
.anchor => "A",
|
||||
.body => "BODY",
|
||||
.br => "BR",
|
||||
.button => "BUTTON",
|
||||
.canvas => "CANVAS",
|
||||
.custom => |e| upperTagName(&e._tag_name, buf),
|
||||
.data => "DATA",
|
||||
.dialog => "DIALOG",
|
||||
.div => "DIV",
|
||||
.embed => "EMBED",
|
||||
.form => "FORM",
|
||||
.generic => |e| upperTagName(&e._tag_name, buf),
|
||||
.heading => |e| upperTagName(&e._tag_name, buf),
|
||||
.head => "HEAD",
|
||||
.html => "HTML",
|
||||
.hr => "HR",
|
||||
.iframe => "IFRAME",
|
||||
.img => "IMG",
|
||||
.input => "INPUT",
|
||||
.li => "LI",
|
||||
.link => "LINK",
|
||||
.meta => "META",
|
||||
.media => |m| switch (m._type) {
|
||||
.audio => "AUDIO",
|
||||
.video => "VIDEO",
|
||||
.generic => "MEDIA",
|
||||
},
|
||||
else => return switch (he._type) {
|
||||
.anchor => "A",
|
||||
.body => "BODY",
|
||||
.br => "BR",
|
||||
.button => "BUTTON",
|
||||
.canvas => "CANVAS",
|
||||
.custom => |e| upperTagName(&e._tag_name, buf),
|
||||
.data => "DATA",
|
||||
.dialog => "DIALOG",
|
||||
.div => "DIV",
|
||||
.embed => "EMBED",
|
||||
.form => "FORM",
|
||||
.generic => |e| upperTagName(&e._tag_name, buf),
|
||||
.heading => |e| upperTagName(&e._tag_name, buf),
|
||||
.head => "HEAD",
|
||||
.html => "HTML",
|
||||
.hr => "HR",
|
||||
.iframe => "IFRAME",
|
||||
.img => "IMG",
|
||||
.input => "INPUT",
|
||||
.li => "LI",
|
||||
.link => "LINK",
|
||||
.meta => "META",
|
||||
.media => |m| switch (m._type) {
|
||||
.audio => "AUDIO",
|
||||
.video => "VIDEO",
|
||||
.generic => "MEDIA",
|
||||
},
|
||||
.ol => "OL",
|
||||
.option => "OPTION",
|
||||
.p => "P",
|
||||
.script => "SCRIPT",
|
||||
.select => "SELECT",
|
||||
.slot => "SLOT",
|
||||
.span => "SPAN",
|
||||
.style => "STYLE",
|
||||
.template => "TEMPLATE",
|
||||
.textarea => "TEXTAREA",
|
||||
.title => "TITLE",
|
||||
.ul => "UL",
|
||||
.unknown => |e| switch (self._namespace) {
|
||||
.html => upperTagName(&e._tag_name, buf),
|
||||
.svg, .xml, .mathml => return e._tag_name.str(),
|
||||
},
|
||||
.ol => "OL",
|
||||
.option => "OPTION",
|
||||
.p => "P",
|
||||
.script => "SCRIPT",
|
||||
.select => "SELECT",
|
||||
.slot => "SLOT",
|
||||
.span => "SPAN",
|
||||
.style => "STYLE",
|
||||
.template => "TEMPLATE",
|
||||
.textarea => "TEXTAREA",
|
||||
.title => "TITLE",
|
||||
.ul => "UL",
|
||||
.unknown => |e| switch (self._namespace) {
|
||||
.html => upperTagName(&e._tag_name, buf),
|
||||
.svg, .xml, .mathml, .unknown, .null => e._tag_name.str(),
|
||||
},
|
||||
},
|
||||
.svg => |svg| return svg._tag_name.str(),
|
||||
}
|
||||
.svg => |svg| svg._tag_name.str(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getTagNameDump(self: *const Element) []const u8 {
|
||||
@@ -274,7 +294,7 @@ pub fn getTagNameDump(self: *const Element) []const u8 {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getNamespaceURI(self: *const Element) []const u8 {
|
||||
pub fn getNamespaceURI(self: *const Element) ?[]const u8 {
|
||||
return self._namespace.toUri();
|
||||
}
|
||||
|
||||
@@ -996,9 +1016,7 @@ pub fn getElementsByClassName(self: *Element, class_name: []const u8, page: *Pag
|
||||
|
||||
pub fn cloneElement(self: *Element, deep: bool, page: *Page) !*Node {
|
||||
const tag_name = self.getTagNameDump();
|
||||
const namespace_uri = self.getNamespaceURI();
|
||||
|
||||
const node = try page.createElement(namespace_uri, tag_name, self._attributes);
|
||||
const node = try page.createElementNS(self._namespace, tag_name, self._attributes);
|
||||
|
||||
// Allow element-specific types to copy their runtime state
|
||||
_ = Element.Build.call(node.as(Element), "cloned", .{ self, node.as(Element), page }) catch |err| {
|
||||
|
||||
@@ -132,7 +132,7 @@ pub fn setTitle(self: *HTMLDocument, title: []const u8, page: *Page) !void {
|
||||
}
|
||||
|
||||
// No title element found, create one
|
||||
const title_node = try page.createElement(null, "title", null);
|
||||
const title_node = try page.createElementNS(.html, "title", null);
|
||||
const title_element = title_node.as(Element);
|
||||
|
||||
// Only add text if non-empty
|
||||
|
||||
@@ -443,9 +443,9 @@ pub fn createContextualFragment(self: *const Range, html: []const u8, page: *Pag
|
||||
// Create a temporary element of the same type as the context for parsing
|
||||
// This preserves the parsing context without modifying the original node
|
||||
const temp_node = if (context_node.is(Node.Element)) |el|
|
||||
try page.createElement(el._namespace.toUri(), el.getTagNameLower(), null)
|
||||
try page.createElementNS(el._namespace, el.getTagNameLower(), null)
|
||||
else
|
||||
try page.createElement(null, "div", null);
|
||||
try page.createElementNS(.html, "div", null);
|
||||
|
||||
try page.parseHtmlAsChildren(temp_node, html);
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ const Image = @This();
|
||||
_proto: *HtmlElement,
|
||||
|
||||
pub fn constructor(w_: ?u32, h_: ?u32, page: *Page) !*Image {
|
||||
const node = try page.createElement(null, "img", null);
|
||||
const node = try page.createElementNS(.html, "img", null);
|
||||
const el = node.as(Element);
|
||||
|
||||
if (w_) |w| blk: {
|
||||
|
||||
Reference in New Issue
Block a user