From e830d1da4ed120de78413ff2417034d8f1742940 Mon Sep 17 00:00:00 2001
From: Rohit <71192000+rohitsux@users.noreply.github.com>
Date: Wed, 3 Jun 2026 21:03:27 +0530
Subject: [PATCH] feat(webapi): implement Node.lookupPrefix
Implement the Node.lookupPrefix(namespace) method per the WHATWG DOM
"locate a namespace prefix" algorithm: match the element's own
namespace and prefix, then scan its xmlns: attribute declarations for
one whose value is the namespace, then recurse to the parent element.
This is the inverse of the existing lookupNamespaceURI and completes
the namespace-introspection trio alongside isDefaultNamespace.
---
src/browser/tests/node/lookup_prefix.html | 46 +++++++++++++++++++++++
src/browser/webapi/Element.zig | 28 ++++++++++++++
src/browser/webapi/Node.zig | 23 ++++++++++++
3 files changed, 97 insertions(+)
create mode 100644 src/browser/tests/node/lookup_prefix.html
diff --git a/src/browser/tests/node/lookup_prefix.html b/src/browser/tests/node/lookup_prefix.html
new file mode 100644
index 00000000..38af8cf2
--- /dev/null
+++ b/src/browser/tests/node/lookup_prefix.html
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig
index d898dbd7..c8cdbfa2 100644
--- a/src/browser/webapi/Element.zig
+++ b/src/browser/webapi/Element.zig
@@ -422,6 +422,34 @@ pub fn lookupNamespaceURIForElement(self: *Element, prefix: ?[]const u8, frame:
return parent.lookupNamespaceURIForElement(prefix, frame);
}
+// Locate a namespace prefix: the inverse of lookupNamespaceURIForElement.
+// Given a namespace URI, find the prefix that declares it.
+pub fn lookupPrefixForElement(self: *Element, namespace: []const u8, frame: *Frame) ?[]const u8 {
+ // Step 1: element's own namespace/prefix
+ if (self.getNamespaceUri(frame)) |ns_uri| {
+ if (self._prefix()) |el_prefix| {
+ if (std.mem.eql(u8, ns_uri, namespace)) {
+ return el_prefix;
+ }
+ }
+ }
+
+ // Step 2: search xmlns: attribute declarations for one whose value is the namespace
+ if (self._attributes) |attrs| {
+ var iter = attrs.iterator();
+ while (iter.next()) |entry| {
+ const name = entry._name.str();
+ if (std.mem.startsWith(u8, name, "xmlns:") and std.mem.eql(u8, entry._value.str(), namespace)) {
+ return name["xmlns:".len..];
+ }
+ }
+ }
+
+ // Step 3: recurse to parent element
+ const parent = self.asNode().parentElement() orelse return null;
+ return parent.lookupPrefixForElement(namespace, frame);
+}
+
fn _prefix(self: *const Element) ?[]const u8 {
const name = self.getTagNameLower();
if (std.mem.indexOfPos(u8, name, 0, ":")) |pos| {
diff --git a/src/browser/webapi/Node.zig b/src/browser/webapi/Node.zig
index c57913aa..c4e0fe0a 100644
--- a/src/browser/webapi/Node.zig
+++ b/src/browser/webapi/Node.zig
@@ -379,6 +379,28 @@ pub fn lookupNamespaceURI(self: *Node, prefix_arg: ?[]const u8, frame: *Frame) ?
}
}
+pub fn lookupPrefix(self: *Node, namespace_arg: ?[]const u8, frame: *Frame) ?[]const u8 {
+ const namespace = namespace_arg orelse return null;
+ if (namespace.len == 0) return null;
+
+ switch (self._type) {
+ .element => |el| return el.lookupPrefixForElement(namespace, frame),
+ .document => |doc| {
+ const de = doc.getDocumentElement() orelse return null;
+ return de.lookupPrefixForElement(namespace, frame);
+ },
+ .document_type, .document_fragment => return null,
+ .attribute => |attr| {
+ const owner = attr.getOwnerElement() orelse return null;
+ return owner.lookupPrefixForElement(namespace, frame);
+ },
+ .cdata => {
+ const parent = self.parentElement() orelse return null;
+ return parent.lookupPrefixForElement(namespace, frame);
+ },
+ }
+}
+
pub fn isDefaultNamespace(self: *Node, namespace_arg: ?[]const u8, frame: *Frame) bool {
const namespace: ?[]const u8 = if (namespace_arg) |ns| (if (ns.len == 0) null else ns) else null;
const default_ns = self.lookupNamespaceURI(null, frame);
@@ -1197,6 +1219,7 @@ pub const JsApi = struct {
pub const getRootNode = bridge.function(Node.getRootNode, .{});
pub const isEqualNode = bridge.function(Node.isEqualNode, .{});
pub const lookupNamespaceURI = bridge.function(Node.lookupNamespaceURI, .{});
+ pub const lookupPrefix = bridge.function(Node.lookupPrefix, .{});
pub const isDefaultNamespace = bridge.function(Node.isDefaultNamespace, .{});
fn _baseURI(_: *Node, frame: *const Frame) []const u8 {