diff --git a/src/browser/webapi/CData.zig b/src/browser/webapi/CData.zig index 0d2bc53e6..b75c4fae1 100644 --- a/src/browser/webapi/CData.zig +++ b/src/browser/webapi/CData.zig @@ -135,6 +135,10 @@ pub fn getLength(self: *const CData) usize { return self._data.len; } +pub fn isEqualNode(self: *const CData, other: *const CData) bool { + return std.mem.eql(u8, self.getData(), other.getData()); +} + pub fn appendData(self: *CData, data: []const u8, page: *Page) !void { const new_data = try std.mem.concat(page.arena, u8, &.{ self._data, data }); try self.setData(new_data, page); @@ -256,6 +260,7 @@ pub const JsApi = struct { pub const data = bridge.accessor(CData.getData, CData.setData, .{}); pub const length = bridge.accessor(CData.getLength, null, .{}); + pub const isEqualNode = bridge.function(CData.isEqualNode, .{}); pub const appendData = bridge.function(CData.appendData, .{}); pub const deleteData = bridge.function(CData.deleteData, .{ .dom_exception = true }); pub const insertData = bridge.function(CData.insertData, .{ .dom_exception = true }); diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig index 32a2b18a5..03ec1e410 100644 --- a/src/browser/webapi/Element.zig +++ b/src/browser/webapi/Element.zig @@ -117,6 +117,57 @@ pub fn className(self: *const Element) []const u8 { }; } +/// TODO: localName and prefix comparison. +pub fn isEqualNode(self: *Element, other: *Element) bool { + const self_tag = self.getTagNameDump(); + const other_tag = other.getTagNameDump(); + // Compare namespaces and tags. + const dirty = self._namespace != other._namespace or !std.mem.eql(u8, self_tag, other_tag); + if (dirty) { + return false; + } + + // Compare attributes. + { + var self_iter = self.attributeIterator(); + var other_iter = other.attributeIterator(); + var self_count: usize = 0; + var other_count: usize = 0; + while (self_iter.next()) |a1| : (self_count += 1) { + const a2 = other_iter.next() orelse return false; + other_count += 1; + if (a1._name.eql(a2._name) and a1._value.eql(a2._value)) { + continue; + } + + return false; + } + + // Make sure both have equal number of attribs. + if (self_count != other_count) { + return false; + } + } + + // Compare children. + var self_iter = self.asNode().childrenIterator(); + var other_iter = other.asNode().childrenIterator(); + var self_count: usize = 0; + var other_count: usize = 0; + while (self_iter.next()) |self_node| : (self_count += 1) { + const other_node = other_iter.next() orelse return false; + other_count += 1; + if (self_node.isEqualNode(other_node)) { + continue; + } + + return false; + } + + // Make sure both have equal number of children. + return self_count == other_count; +} + pub fn getTagNameLower(self: *const Element) []const u8 { switch (self._type) { .html => |he| switch (he._type) { @@ -1034,6 +1085,7 @@ pub const JsApi = struct { pub const removeAttributeNode = bridge.function(Element.removeAttributeNode, .{ .dom_exception = true }); pub const shadowRoot = bridge.accessor(Element.getShadowRoot, null, .{}); pub const attachShadow = bridge.function(_attachShadow, .{ .dom_exception = true }); + pub const isEqualNode = bridge.function(Element.isEqualNode, .{}); const ShadowRootInit = struct { mode: []const u8, diff --git a/src/browser/webapi/Node.zig b/src/browser/webapi/Node.zig index ef6de4a7d..a9c4d9eef 100644 --- a/src/browser/webapi/Node.zig +++ b/src/browser/webapi/Node.zig @@ -236,6 +236,27 @@ pub fn nodeType(self: *const Node) u8 { }; } +pub fn isEqualNode(self: *Node, other: *Node) bool { + // Make sure types match. + if (self.nodeType() != other.nodeType()) { + return false; + } + + // TODO: Compare `localName` and prefix. + return switch (self._type) { + .element => self.as(Element).isEqualNode(other.as(Element)), + .attribute => self.as(Element.Attribute).isEqualNode(other.as(Element.Attribute)), + .cdata => self.as(CData).isEqualNode(other.as(CData)), + else => { + log.warn(.browser, "not implemented", .{ + .type = self._type, + .feature = "Node.isEqualNode", + }); + return false; + }, + }; +} + pub fn isInShadowTree(self: *Node) bool { var node = self._parent; while (node) |n| { @@ -760,6 +781,7 @@ pub const JsApi = struct { pub const cloneNode = bridge.function(Node.cloneNode, .{ .dom_exception = true }); pub const compareDocumentPosition = bridge.function(Node.compareDocumentPosition, .{}); pub const getRootNode = bridge.function(Node.getRootNode, .{}); + pub const isEqualNode = bridge.function(Node.isEqualNode, .{}); pub const toString = bridge.function(_toString, .{}); fn _toString(self: *const Node) []const u8 { diff --git a/src/browser/webapi/element/Attribute.zig b/src/browser/webapi/element/Attribute.zig index b5d45a612..0930af73f 100644 --- a/src/browser/webapi/element/Attribute.zig +++ b/src/browser/webapi/element/Attribute.zig @@ -78,6 +78,10 @@ pub fn getOwnerElement(self: *const Attribute) ?*Element { return self._element; } +pub fn isEqualNode(self: *const Attribute, other: *const Attribute) bool { + return std.mem.eql(u8, self.getName(), other.getName()) and std.mem.eql(u8, self.getValue(), other.getValue()); +} + pub const JsApi = struct { pub const bridge = js.Bridge(Attribute); @@ -96,6 +100,8 @@ pub const JsApi = struct { pub const value = bridge.accessor(Attribute.getValue, null, .{}); pub const namespaceURI = bridge.accessor(Attribute.getNamespaceURI, null, .{}); pub const ownerElement = bridge.accessor(Attribute.getOwnerElement, null, .{}); + + pub const isEqualNode = bridge.function(Attribute.isEqualNode, .{}); }; // This is what an Element references. It isn't exposed to JavaScript. In