mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 12:44:43 +00:00
implement Node.lookupNamespaceURI() and Node.isDefaultNamespace()
Implements the DOM spec algorithms for namespace lookup on all node types. Stores custom namespace URIs in a page lookup for elements created via createElementNS with unknown namespaces. Fixes setAttributeNS to preserve qualified names for xmlns namespace declarations. Flips dom/nodes/Node-lookupNamespaceURI.html: 0/75 → 75/75.
This commit is contained in:
@@ -111,6 +111,7 @@ _element_shadow_roots: Element.ShadowRootLookup = .empty,
|
|||||||
_node_owner_documents: Node.OwnerDocumentLookup = .empty,
|
_node_owner_documents: Node.OwnerDocumentLookup = .empty,
|
||||||
_element_assigned_slots: Element.AssignedSlotLookup = .empty,
|
_element_assigned_slots: Element.AssignedSlotLookup = .empty,
|
||||||
_element_scroll_positions: Element.ScrollPositionLookup = .empty,
|
_element_scroll_positions: Element.ScrollPositionLookup = .empty,
|
||||||
|
_element_namespace_uris: Element.NamespaceUriLookup = .empty,
|
||||||
|
|
||||||
/// Lazily-created inline event listeners (or listeners provided as attributes).
|
/// Lazily-created inline event listeners (or listeners provided as attributes).
|
||||||
/// Avoids bloating all elements with extra function fields for rare usage.
|
/// Avoids bloating all elements with extra function fields for rare usage.
|
||||||
|
|||||||
@@ -160,6 +160,14 @@ pub fn createElementNS(self: *Document, namespace: ?[]const u8, name: []const u8
|
|||||||
const normalized_name = if (ns == .html) std.ascii.lowerString(&page.buf, name) else name;
|
const normalized_name = if (ns == .html) std.ascii.lowerString(&page.buf, name) else name;
|
||||||
const node = try page.createElementNS(ns, normalized_name, null);
|
const node = try page.createElementNS(ns, normalized_name, null);
|
||||||
|
|
||||||
|
// Store original URI for unknown namespaces so lookupNamespaceURI can return it
|
||||||
|
if (ns == .unknown) {
|
||||||
|
if (namespace) |uri| {
|
||||||
|
const duped = try page.dupeString(uri);
|
||||||
|
try page._element_namespace_uris.put(page.arena, node.as(Element), duped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Track owner document if it's not the main document
|
// Track owner document if it's not the main document
|
||||||
if (self != page.document) {
|
if (self != page.document) {
|
||||||
try page.setNodeOwnerDocument(node, self);
|
try page.setNodeOwnerDocument(node, self);
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ pub const ClassListLookup = std.AutoHashMapUnmanaged(*Element, *collections.DOMT
|
|||||||
pub const RelListLookup = std.AutoHashMapUnmanaged(*Element, *collections.DOMTokenList);
|
pub const RelListLookup = std.AutoHashMapUnmanaged(*Element, *collections.DOMTokenList);
|
||||||
pub const ShadowRootLookup = std.AutoHashMapUnmanaged(*Element, *ShadowRoot);
|
pub const ShadowRootLookup = std.AutoHashMapUnmanaged(*Element, *ShadowRoot);
|
||||||
pub const AssignedSlotLookup = std.AutoHashMapUnmanaged(*Element, *Html.Slot);
|
pub const AssignedSlotLookup = std.AutoHashMapUnmanaged(*Element, *Html.Slot);
|
||||||
|
pub const NamespaceUriLookup = std.AutoHashMapUnmanaged(*Element, []const u8);
|
||||||
|
|
||||||
pub const ScrollPosition = struct {
|
pub const ScrollPosition = struct {
|
||||||
x: u32 = 0,
|
x: u32 = 0,
|
||||||
@@ -364,6 +365,64 @@ pub fn getNamespaceURI(self: *const Element) ?[]const u8 {
|
|||||||
return self._namespace.toUri();
|
return self._namespace.toUri();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn getNamespaceUri(self: *Element, page: *Page) ?[]const u8 {
|
||||||
|
if (self._namespace != .unknown) return self._namespace.toUri();
|
||||||
|
return page._element_namespace_uris.get(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lookupNamespaceURIForElement(self: *Element, prefix: ?[]const u8, page: *Page) ?[]const u8 {
|
||||||
|
// Hardcoded reserved prefixes
|
||||||
|
if (prefix) |p| {
|
||||||
|
if (std.mem.eql(u8, p, "xml")) return "http://www.w3.org/XML/1998/namespace";
|
||||||
|
if (std.mem.eql(u8, p, "xmlns")) return "http://www.w3.org/2000/xmlns/";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: check element's own namespace/prefix
|
||||||
|
if (self.getNamespaceUri(page)) |ns_uri| {
|
||||||
|
const el_prefix = self._prefix();
|
||||||
|
const match = if (prefix == null and el_prefix == null)
|
||||||
|
true
|
||||||
|
else if (prefix != null and el_prefix != null)
|
||||||
|
std.mem.eql(u8, prefix.?, el_prefix.?)
|
||||||
|
else
|
||||||
|
false;
|
||||||
|
if (match) return ns_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: search xmlns attributes
|
||||||
|
if (self._attributes) |attrs| {
|
||||||
|
var iter = attrs.iterator();
|
||||||
|
while (iter.next()) |entry| {
|
||||||
|
if (prefix == null) {
|
||||||
|
if (entry._name.eql(comptime .wrap("xmlns"))) {
|
||||||
|
const val = entry._value.str();
|
||||||
|
return if (val.len == 0) null else val;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const name = entry._name.str();
|
||||||
|
if (std.mem.startsWith(u8, name, "xmlns:")) {
|
||||||
|
if (std.mem.eql(u8, name["xmlns:".len..], prefix.?)) {
|
||||||
|
const val = entry._value.str();
|
||||||
|
return if (val.len == 0) null else val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: recurse to parent element
|
||||||
|
const parent = self.asNode().parentElement() orelse return null;
|
||||||
|
return parent.lookupNamespaceURIForElement(prefix, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _prefix(self: *const Element) ?[]const u8 {
|
||||||
|
const name = self.getTagNameLower();
|
||||||
|
if (std.mem.indexOfPos(u8, name, 0, ":")) |pos| {
|
||||||
|
return name[0..pos];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn getLocalName(self: *Element) []const u8 {
|
pub fn getLocalName(self: *Element) []const u8 {
|
||||||
const name = self.getTagNameLower();
|
const name = self.getTagNameLower();
|
||||||
if (std.mem.indexOfPos(u8, name, 0, ":")) |pos| {
|
if (std.mem.indexOfPos(u8, name, 0, ":")) |pos| {
|
||||||
@@ -534,17 +593,26 @@ pub fn setAttributeNS(
|
|||||||
value: String,
|
value: String,
|
||||||
page: *Page,
|
page: *Page,
|
||||||
) !void {
|
) !void {
|
||||||
if (maybe_namespace) |namespace| {
|
const attr_name = if (maybe_namespace) |namespace| blk: {
|
||||||
|
// For xmlns namespace, store the full qualified name (e.g. "xmlns:bar")
|
||||||
|
// so lookupNamespaceURI can find namespace declarations.
|
||||||
|
if (std.mem.eql(u8, namespace, "http://www.w3.org/2000/xmlns/")) {
|
||||||
|
break :blk qualified_name;
|
||||||
|
}
|
||||||
if (!std.mem.eql(u8, namespace, "http://www.w3.org/1999/xhtml")) {
|
if (!std.mem.eql(u8, namespace, "http://www.w3.org/1999/xhtml")) {
|
||||||
log.warn(.not_implemented, "Element.setAttributeNS", .{ .namespace = namespace });
|
log.warn(.not_implemented, "Element.setAttributeNS", .{ .namespace = namespace });
|
||||||
}
|
}
|
||||||
}
|
break :blk if (std.mem.indexOfScalarPos(u8, qualified_name, 0, ':')) |idx|
|
||||||
|
qualified_name[idx + 1 ..]
|
||||||
const local_name = if (std.mem.indexOfScalarPos(u8, qualified_name, 0, ':')) |idx|
|
else
|
||||||
qualified_name[idx + 1 ..]
|
qualified_name;
|
||||||
else
|
} else blk: {
|
||||||
qualified_name;
|
break :blk if (std.mem.indexOfScalarPos(u8, qualified_name, 0, ':')) |idx|
|
||||||
return self.setAttribute(.wrap(local_name), value, page);
|
qualified_name[idx + 1 ..]
|
||||||
|
else
|
||||||
|
qualified_name;
|
||||||
|
};
|
||||||
|
return self.setAttribute(.wrap(attr_name), value, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setAttributeSafe(self: *Element, name: String, value: String, page: *Page) !void {
|
pub fn setAttributeSafe(self: *Element, name: String, value: String, page: *Page) !void {
|
||||||
@@ -1560,15 +1628,7 @@ pub const JsApi = struct {
|
|||||||
return buf.written();
|
return buf.written();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const prefix = bridge.accessor(_prefix, null, .{});
|
pub const prefix = bridge.accessor(Element._prefix, null, .{});
|
||||||
fn _prefix(self: *Element) ?[]const u8 {
|
|
||||||
const name = self.getTagNameLower();
|
|
||||||
if (std.mem.indexOfPos(u8, name, 0, ":")) |pos| {
|
|
||||||
return name[0..pos];
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const setAttribute = bridge.function(_setAttribute, .{ .dom_exception = true });
|
pub const setAttribute = bridge.function(_setAttribute, .{ .dom_exception = true });
|
||||||
fn _setAttribute(self: *Element, name: String, value: js.Value, page: *Page) !void {
|
fn _setAttribute(self: *Element, name: String, value: js.Value, page: *Page) !void {
|
||||||
|
|||||||
@@ -338,6 +338,35 @@ pub fn getNodeType(self: *const Node) u8 {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn lookupNamespaceURI(self: *Node, prefix_arg: ?[]const u8, page: *Page) ?[]const u8 {
|
||||||
|
const prefix: ?[]const u8 = if (prefix_arg) |p| (if (p.len == 0) null else p) else null;
|
||||||
|
|
||||||
|
switch (self._type) {
|
||||||
|
.element => |el| return el.lookupNamespaceURIForElement(prefix, page),
|
||||||
|
.document => |doc| {
|
||||||
|
const de = doc.getDocumentElement() orelse return null;
|
||||||
|
return de.lookupNamespaceURIForElement(prefix, page);
|
||||||
|
},
|
||||||
|
.document_type, .document_fragment => return null,
|
||||||
|
.attribute => |attr| {
|
||||||
|
const owner = attr.getOwnerElement() orelse return null;
|
||||||
|
return owner.lookupNamespaceURIForElement(prefix, page);
|
||||||
|
},
|
||||||
|
.cdata => {
|
||||||
|
const parent = self.parentElement() orelse return null;
|
||||||
|
return parent.lookupNamespaceURIForElement(prefix, page);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isDefaultNamespace(self: *Node, namespace_arg: ?[]const u8, page: *Page) bool {
|
||||||
|
const namespace: ?[]const u8 = if (namespace_arg) |ns| (if (ns.len == 0) null else ns) else null;
|
||||||
|
const default_ns = self.lookupNamespaceURI(null, page);
|
||||||
|
if (default_ns == null and namespace == null) return true;
|
||||||
|
if (default_ns != null and namespace != null) return std.mem.eql(u8, default_ns.?, namespace.?);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn isEqualNode(self: *Node, other: *Node) bool {
|
pub fn isEqualNode(self: *Node, other: *Node) bool {
|
||||||
if (self == other) {
|
if (self == other) {
|
||||||
return true;
|
return true;
|
||||||
@@ -1016,6 +1045,8 @@ pub const JsApi = struct {
|
|||||||
pub const compareDocumentPosition = bridge.function(Node.compareDocumentPosition, .{});
|
pub const compareDocumentPosition = bridge.function(Node.compareDocumentPosition, .{});
|
||||||
pub const getRootNode = bridge.function(Node.getRootNode, .{});
|
pub const getRootNode = bridge.function(Node.getRootNode, .{});
|
||||||
pub const isEqualNode = bridge.function(Node.isEqualNode, .{});
|
pub const isEqualNode = bridge.function(Node.isEqualNode, .{});
|
||||||
|
pub const lookupNamespaceURI = bridge.function(Node.lookupNamespaceURI, .{});
|
||||||
|
pub const isDefaultNamespace = bridge.function(Node.isDefaultNamespace, .{});
|
||||||
|
|
||||||
fn _baseURI(_: *Node, page: *const Page) []const u8 {
|
fn _baseURI(_: *Node, page: *const Page) []const u8 {
|
||||||
return page.base();
|
return page.base();
|
||||||
|
|||||||
Reference in New Issue
Block a user