Skip to content

Commit 269c880

Browse files
authored
Merge pull request #1246 from lightpanda-io/nikneym/is-equal-node
Support `isEqualNode`
2 parents 38fb5b1 + fe89aad commit 269c880

File tree

5 files changed

+152
-0
lines changed

5 files changed

+152
-0
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<!DOCTYPE html>
2+
<script src="../testing.js"></script>
3+
4+
<div class="song" rick="roll">
5+
<span>we're no strangers to love</span>
6+
you know the rules
7+
8+
<b>and so do I</b>
9+
</div>
10+
11+
<div class="song" rick="roll">
12+
<span>we're no strangers to love</span>
13+
you know the rules
14+
15+
<b>and so do I</b>
16+
</div>
17+
18+
<script id=isEqualNode>
19+
// Compare nodes of parsed elements.
20+
{
21+
const elements = document.getElementsByClassName("song");
22+
testing.expectEqual(true, elements.item(0).isEqualNode(elements.item(1)));
23+
}
24+
25+
{
26+
const e1 = document.createElement("div");
27+
e1.innerHTML = "<h1>We come from the land of the ice and snow</h1>";
28+
const e2 = document.createElement("div");
29+
e2.innerHTML = "<h1>We come from the land of the ice and snow</h1>";
30+
testing.expectEqual(true, e1.isEqualNode(e2));
31+
}
32+
33+
{
34+
const e1 = document.createElement("div");
35+
e1.innerHTML = "<h1>From the midnight sun where the hot springs flow</h1>";
36+
const e2 = document.createElement("div");
37+
e2.innerHTML = "<h1>from the midnight sun where the hot springs flow</h1>";
38+
testing.expectEqual(false, e1.isEqualNode(e2));
39+
}
40+
</script>

src/browser/webapi/CData.zig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ pub fn getLength(self: *const CData) usize {
146146
return self._data.len;
147147
}
148148

149+
pub fn isEqualNode(self: *const CData, other: *const CData) bool {
150+
return std.mem.eql(u8, self.getData(), other.getData());
151+
}
152+
149153
pub fn appendData(self: *CData, data: []const u8, page: *Page) !void {
150154
const new_data = try std.mem.concat(page.arena, u8, &.{ self._data, data });
151155
try self.setData(new_data, page);

src/browser/webapi/Element.zig

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,49 @@ pub fn className(self: *const Element) []const u8 {
117117
};
118118
}
119119

120+
pub fn attributesEql(self: *const Element, other: *Element) bool {
121+
if (self._attributes) |attr_list| {
122+
const other_list = other._attributes orelse return false;
123+
return attr_list.eql(other_list);
124+
}
125+
// Make sure no attrs in both sides.
126+
return other._attributes == null;
127+
}
128+
129+
/// TODO: localName and prefix comparison.
130+
pub fn isEqualNode(self: *Element, other: *Element) bool {
131+
const self_tag = self.getTagNameDump();
132+
const other_tag = other.getTagNameDump();
133+
// Compare namespaces and tags.
134+
const dirty = self._namespace != other._namespace or !std.mem.eql(u8, self_tag, other_tag);
135+
if (dirty) {
136+
return false;
137+
}
138+
139+
// Compare attributes.
140+
if (!self.attributesEql(other)) {
141+
return false;
142+
}
143+
144+
// Compare children.
145+
var self_iter = self.asNode().childrenIterator();
146+
var other_iter = other.asNode().childrenIterator();
147+
var self_count: usize = 0;
148+
var other_count: usize = 0;
149+
while (self_iter.next()) |self_node| : (self_count += 1) {
150+
const other_node = other_iter.next() orelse return false;
151+
other_count += 1;
152+
if (self_node.isEqualNode(other_node)) {
153+
continue;
154+
}
155+
156+
return false;
157+
}
158+
159+
// Make sure both have equal number of children.
160+
return self_count == other_count;
161+
}
162+
120163
pub fn getTagNameLower(self: *const Element) []const u8 {
121164
switch (self._type) {
122165
.html => |he| switch (he._type) {

src/browser/webapi/Node.zig

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,27 @@ pub fn getNodeType(self: *const Node) u8 {
286286
};
287287
}
288288

289+
pub fn isEqualNode(self: *Node, other: *Node) bool {
290+
// Make sure types match.
291+
if (self.getNodeType() != other.getNodeType()) {
292+
return false;
293+
}
294+
295+
// TODO: Compare `localName` and prefix.
296+
return switch (self._type) {
297+
.element => self.as(Element).isEqualNode(other.as(Element)),
298+
.attribute => self.as(Element.Attribute).isEqualNode(other.as(Element.Attribute)),
299+
.cdata => self.as(CData).isEqualNode(other.as(CData)),
300+
else => {
301+
log.warn(.browser, "not implemented", .{
302+
.type = self._type,
303+
.feature = "Node.isEqualNode",
304+
});
305+
return false;
306+
},
307+
};
308+
}
309+
289310
pub fn isInShadowTree(self: *Node) bool {
290311
var node = self._parent;
291312
while (node) |n| {
@@ -822,6 +843,7 @@ pub const JsApi = struct {
822843
pub const cloneNode = bridge.function(Node.cloneNode, .{ .dom_exception = true });
823844
pub const compareDocumentPosition = bridge.function(Node.compareDocumentPosition, .{});
824845
pub const getRootNode = bridge.function(Node.getRootNode, .{});
846+
pub const isEqualNode = bridge.function(Node.isEqualNode, .{});
825847

826848
pub const toString = bridge.function(_toString, .{});
827849
fn _toString(self: *const Node) []const u8 {

src/browser/webapi/element/Attribute.zig

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ pub fn getOwnerElement(self: *const Attribute) ?*Element {
7878
return self._element;
7979
}
8080

81+
pub fn isEqualNode(self: *const Attribute, other: *const Attribute) bool {
82+
return std.mem.eql(u8, self.getName(), other.getName()) and std.mem.eql(u8, self.getValue(), other.getValue());
83+
}
84+
8185
pub const JsApi = struct {
8286
pub const bridge = js.Bridge(Attribute);
8387

@@ -119,16 +123,45 @@ pub const JsApi = struct {
119123
// attribute in the DOM, and, again, we expect that to almost always be null.
120124
pub const List = struct {
121125
normalize: bool,
126+
/// Length of items in `_list`. Not usize to increase memory usage.
127+
/// Honestly, this is more than enough.
128+
_len: u32 = 0,
122129
_list: std.DoublyLinkedList = .{},
123130

124131
pub fn isEmpty(self: *const List) bool {
125132
return self._list.first == null;
126133
}
134+
127135
pub fn get(self: *const List, name: []const u8, page: *Page) !?[]const u8 {
128136
const entry = (try self.getEntry(name, page)) orelse return null;
129137
return entry._value.str();
130138
}
131139

140+
pub inline fn length(self: *const List) usize {
141+
return self._len;
142+
}
143+
144+
/// Compares 2 attribute lists for equality.
145+
pub fn eql(self: *List, other: *List) bool {
146+
if (self.length() != other.length()) {
147+
return false;
148+
}
149+
150+
var iter = self.iterator();
151+
search: while (iter.next()) |attr| {
152+
// Iterate over all `other` attributes.
153+
var other_iter = other.iterator();
154+
while (other_iter.next()) |other_attr| {
155+
if (attr.eql(other_attr)) {
156+
continue :search; // Found match.
157+
}
158+
}
159+
// Iterated over all `other` and not match.
160+
return false;
161+
}
162+
return true;
163+
}
164+
132165
// meant for internal usage, where the name is known to be properly cased
133166
pub fn getSafe(self: *const List, name: []const u8) ?[]const u8 {
134167
const entry = self.getEntryWithNormalizedName(name) orelse return null;
@@ -180,6 +213,7 @@ pub const List = struct {
180213
._value = try String.init(page.arena, value, .{}),
181214
});
182215
self._list.append(&entry._node);
216+
self._len += 1;
183217
}
184218

185219
if (is_id) {
@@ -203,6 +237,7 @@ pub const List = struct {
203237
._value = try String.init(page.arena, value, .{}),
204238
});
205239
self._list.append(&entry._node);
240+
self._len += 1;
206241
}
207242

208243
// not efficient, won't be called often (if ever!)
@@ -235,6 +270,7 @@ pub const List = struct {
235270
._value = try String.init(page.arena, value, .{}),
236271
});
237272
self._list.append(&entry._node);
273+
self._len += 1;
238274
}
239275

240276
pub fn delete(self: *List, name: []const u8, element: *Element, page: *Page) !void {
@@ -252,6 +288,7 @@ pub const List = struct {
252288
page.attributeRemove(element, result.normalized, old_value);
253289
_ = page._attribute_lookup.remove(@intFromPtr(entry));
254290
self._list.remove(&entry._node);
291+
self._len -= 1;
255292
page._factory.destroy(entry);
256293
}
257294

@@ -311,6 +348,12 @@ pub const List = struct {
311348
return @alignCast(@fieldParentPtr("_node", n));
312349
}
313350

351+
/// Returns true if 2 entries are equal.
352+
/// This doesn't compare `_node` fields.
353+
pub fn eql(self: *const Entry, other: *const Entry) bool {
354+
return self._name.eql(other._name) and self._value.eql(other._value);
355+
}
356+
314357
pub fn format(self: *const Entry, writer: *std.Io.Writer) !void {
315358
return formatAttribute(self._name.str(), self._value.str(), writer);
316359
}

0 commit comments

Comments
 (0)