From b2096442ead280b6f4aef9639445a3a88560520a Mon Sep 17 00:00:00 2001 From: sjorsdonkers <72333389+sjorsdonkers@users.noreply.github.com> Date: Tue, 8 Jul 2025 15:06:54 +0200 Subject: [PATCH 1/3] NodeIterator --- src/browser/dom/document.zig | 5 + src/browser/dom/dom.zig | 2 + src/browser/dom/node_filter.zig | 1 + src/browser/dom/node_iterator.zig | 213 ++++++++++++++++++++++++++++++ src/browser/dom/tree_walker.zig | 10 +- src/browser/html/window.zig | 2 +- 6 files changed, 228 insertions(+), 5 deletions(-) create mode 100644 src/browser/dom/node_iterator.zig diff --git a/src/browser/dom/document.zig b/src/browser/dom/document.zig index 911c3015d..ac732b63d 100644 --- a/src/browser/dom/document.zig +++ b/src/browser/dom/document.zig @@ -33,6 +33,7 @@ const Element = @import("element.zig").Element; const ElementUnion = @import("element.zig").Union; const TreeWalker = @import("tree_walker.zig").TreeWalker; const CSSStyleSheet = @import("../cssom/css_stylesheet.zig").CSSStyleSheet; +const NodeIterator = @import("node_iterator.zig").NodeIterator; const Range = @import("range.zig").Range; const Env = @import("../env.zig").Env; @@ -265,6 +266,10 @@ pub const Document = struct { return try TreeWalker.init(root, what_to_show, filter); } + pub fn _createNodeIterator(_: *parser.Document, root: *parser.Node, what_to_show: ?u32, filter: ?TreeWalker.TreeWalkerOpts) !NodeIterator { + return try NodeIterator.init(root, what_to_show, filter); + } + pub fn getActiveElement(self: *parser.Document, page: *Page) !?*parser.Element { if (page.getNodeState(@alignCast(@ptrCast(self)))) |state| { if (state.active_element) |ae| { diff --git a/src/browser/dom/dom.zig b/src/browser/dom/dom.zig index 13e8a8017..8cc6d55e5 100644 --- a/src/browser/dom/dom.zig +++ b/src/browser/dom/dom.zig @@ -28,6 +28,7 @@ const MutationObserver = @import("mutation_observer.zig"); const IntersectionObserver = @import("intersection_observer.zig"); const DOMParser = @import("dom_parser.zig").DOMParser; const TreeWalker = @import("tree_walker.zig").TreeWalker; +const NodeIterator = @import("node_iterator.zig").NodeIterator; const NodeFilter = @import("node_filter.zig").NodeFilter; const PerformanceObserver = @import("performance_observer.zig").PerformanceObserver; @@ -46,6 +47,7 @@ pub const Interfaces = .{ IntersectionObserver.Interfaces, DOMParser, TreeWalker, + NodeIterator, NodeFilter, @import("performance.zig").Interfaces, PerformanceObserver, diff --git a/src/browser/dom/node_filter.zig b/src/browser/dom/node_filter.zig index c7cc896d2..8ba80e10f 100644 --- a/src/browser/dom/node_filter.zig +++ b/src/browser/dom/node_filter.zig @@ -22,6 +22,7 @@ pub const NodeFilter = struct { pub const _FILTER_ACCEPT: u16 = 1; pub const _FILTER_REJECT: u16 = 2; pub const _FILTER_SKIP: u16 = 3; + pub const _SHOW_ALL: u32 = std.math.maxInt(u32); pub const _SHOW_ELEMENT: u32 = 0b1; pub const _SHOW_ATTRIBUTE: u32 = 0b10; diff --git a/src/browser/dom/node_iterator.zig b/src/browser/dom/node_iterator.zig new file mode 100644 index 000000000..336368ff7 --- /dev/null +++ b/src/browser/dom/node_iterator.zig @@ -0,0 +1,213 @@ +// Copyright (C) 2023-2025 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +const std = @import("std"); +const parser = @import("../netsurf.zig"); +const Env = @import("../env.zig").Env; +const TreeWalker = @import("tree_walker.zig").TreeWalker; + +// https://developer.mozilla.org/en-US/docs/Web/API/NodeIterator +pub const NodeIterator = struct { + walker: TreeWalker, + pointer_before_current: bool = true, + + pub fn init(node: *parser.Node, what_to_show: ?u32, filter: ?TreeWalker.TreeWalkerOpts) !NodeIterator { + return .{ .walker = try TreeWalker.init(node, what_to_show, filter) }; + } + + pub fn get_filter(self: *const NodeIterator) ?Env.Function { + return self.walker.filter; + } + + pub fn get_pointerBeforeReferenceNode(self: *const NodeIterator) bool { + return self.pointer_before_current; + } + + pub fn get_referenceNode(self: *const NodeIterator) *parser.Node { + return self.walker.current_node; + } + + pub fn get_root(self: *const NodeIterator) *parser.Node { + return self.walker.root; + } + + pub fn get_whatToShow(self: *const NodeIterator) u32 { + return self.walker.what_to_show; + } + + pub fn _nextNode(self: *NodeIterator) !?*parser.Node { + if (self.pointer_before_current) { // Unlike TreeWalker, NodeIterator starts at the first node + self.pointer_before_current = false; + if (.accept == try self.walker.verify(self.walker.current_node)) { + return self.walker.current_node; + } + } + + if (try self.firstChild(self.walker.current_node)) |child| { + self.walker.current_node = child; + return child; + } + + var current = self.walker.current_node; + while (current != self.walker.root) { + if (try self.walker.nextSibling(current)) |sibling| { + self.walker.current_node = sibling; + return sibling; + } + + current = (try parser.nodeParentNode(current)) orelse break; + } + + return null; + } + + pub fn _previousNode(self: *NodeIterator) !?*parser.Node { + if (!self.pointer_before_current) { + self.pointer_before_current = true; + if (.accept == try self.walker.verify(self.walker.current_node)) { + return self.walker.current_node; // Still need to verify as last may be first as well + } + } + if (self.walker.current_node == self.walker.root) return null; + + var current = self.walker.current_node; + while (try parser.nodePreviousSibling(current)) |previous| { + current = previous; + + switch (try self.walker.verify(current)) { + .accept => { + // Get last child if it has one. + if (try self.lastChild(current)) |child| { + self.walker.current_node = child; + return child; + } + + // Otherwise, this node is our previous one. + self.walker.current_node = current; + return current; + }, + .reject, .skip => { + // Get last child if it has one. + if (try self.lastChild(current)) |child| { + self.walker.current_node = child; + return child; + } + }, + } + } + + if (current != self.walker.root) { + if (try self.walker.parentNode(current)) |parent| { + self.walker.current_node = parent; + return parent; + } + } + + return null; + } + + fn firstChild(self: *const NodeIterator, node: *parser.Node) !?*parser.Node { + const children = try parser.nodeGetChildNodes(node); + const child_count = try parser.nodeListLength(children); + + for (0..child_count) |i| { + const index: u32 = @intCast(i); + const child = (try parser.nodeListItem(children, index)) orelse return null; + + switch (try self.walker.verify(child)) { + .accept => return child, // NOTE: Skip and reject are equivalent for NodeIterator, this is different from TreeWalker + .reject, .skip => if (try self.firstChild(child)) |gchild| return gchild, + } + } + + return null; + } + + fn lastChild(self: *const NodeIterator, node: *parser.Node) !?*parser.Node { + const children = try parser.nodeGetChildNodes(node); + const child_count = try parser.nodeListLength(children); + + var index: u32 = child_count; + while (index > 0) { + index -= 1; + const child = (try parser.nodeListItem(children, index)) orelse return null; + + switch (try self.walker.verify(child)) { + .accept => return child, // NOTE: Skip and reject are equivalent for NodeIterator, this is different from TreeWalker + .reject, .skip => if (try self.lastChild(child)) |gchild| return gchild, + } + } + + return null; + } +}; + +const testing = @import("../../testing.zig"); +test "Browser.DOM.NodeFilter" { + var runner = try testing.jsRunner(testing.tracking_allocator, .{}); + defer runner.deinit(); + + try runner.testCases(&.{ + .{ + \\ const nodeIterator = document.createNodeIterator( + \\ document.body, + \\ NodeFilter.SHOW_ELEMENT, + \\ { + \\ acceptNode(node) { + \\ return NodeFilter.FILTER_ACCEPT; + \\ }, + \\ }, + \\ ); + \\ nodeIterator.nextNode().nodeName; + , + "BODY", + }, + .{ "nodeIterator.nextNode().nodeName", "DIV" }, + .{ "nodeIterator.nextNode().nodeName", "A" }, + .{ "nodeIterator.previousNode().nodeName", "A" }, // pointer_before_current flips + .{ "nodeIterator.nextNode().nodeName", "A" }, // pointer_before_current flips + .{ "nodeIterator.previousNode().nodeName", "A" }, // pointer_before_current flips + .{ "nodeIterator.previousNode().nodeName", "DIV" }, + .{ "nodeIterator.previousNode().nodeName", "BODY" }, + .{ "nodeIterator.previousNode()", "null" }, // Not HEAD since body is root + .{ "nodeIterator.previousNode()", "null" }, // Keeps returning null + .{ "nodeIterator.nextNode().nodeName", "BODY" }, + + .{ "nodeIterator.nextNode().nodeName", null }, + .{ "nodeIterator.nextNode().nodeName", null }, + .{ "nodeIterator.nextNode().nodeName", null }, + .{ "nodeIterator.nextNode().nodeName", "SPAN" }, + .{ "nodeIterator.nextNode().nodeName", "P" }, + .{ "nodeIterator.nextNode()", "null" }, // Just the last one + .{ "nodeIterator.nextNode()", "null" }, // Keeps returning null + .{ "nodeIterator.previousNode().nodeName", "P" }, + }, .{}); + + try runner.testCases(&.{ + .{ + \\ const notationIterator = document.createNodeIterator( + \\ document.body, + \\ NodeFilter.SHOW_NOTATION, + \\ ); + \\ notationIterator.nextNode(); + , + "null", + }, + .{ "notationIterator.previousNode()", "null" }, + }, .{}); +} diff --git a/src/browser/dom/tree_walker.zig b/src/browser/dom/tree_walker.zig index a79567b32..55160c3dc 100644 --- a/src/browser/dom/tree_walker.zig +++ b/src/browser/dom/tree_walker.zig @@ -55,7 +55,7 @@ pub const TreeWalker = struct { const VerifyResult = enum { accept, skip, reject }; - fn verify(self: *const TreeWalker, node: *parser.Node) !VerifyResult { + pub fn verify(self: *const TreeWalker, node: *parser.Node) !VerifyResult { const node_type = try parser.nodeType(node); const what_to_show = self.what_to_show; @@ -77,7 +77,7 @@ pub const TreeWalker = struct { // Verify that we aren't filtering it out. if (self.filter) |f| { - const filter = try f.call(u32, .{node}); + const filter = try f.call(u16, .{node}); return switch (filter) { NodeFilter._FILTER_ACCEPT => .accept, NodeFilter._FILTER_REJECT => .reject, @@ -144,7 +144,7 @@ pub const TreeWalker = struct { return null; } - fn nextSibling(self: *const TreeWalker, node: *parser.Node) !?*parser.Node { + pub fn nextSibling(self: *const TreeWalker, node: *parser.Node) !?*parser.Node { var current = node; while (true) { @@ -174,7 +174,7 @@ pub const TreeWalker = struct { return null; } - fn parentNode(self: *const TreeWalker, node: *parser.Node) !?*parser.Node { + pub fn parentNode(self: *const TreeWalker, node: *parser.Node) !?*parser.Node { if (self.root == node) return null; var current = node; @@ -245,6 +245,8 @@ pub const TreeWalker = struct { } pub fn _previousNode(self: *TreeWalker) !?*parser.Node { + if (self.current_node == self.root) return null; + var current = self.current_node; while (try parser.nodePreviousSibling(current)) |previous| { current = previous; diff --git a/src/browser/html/window.zig b/src/browser/html/window.zig index c1be58c64..6ce63027e 100644 --- a/src/browser/html/window.zig +++ b/src/browser/html/window.zig @@ -360,7 +360,7 @@ const TimerCallback = struct { } call catch { - log.debug(.user_script, "callback error", .{ + log.warn(.user_script, "callback error", .{ .err = result.exception, .stack = result.stack, .source = "window timeout", From 85a9ade1d9071160ac9f4be45c91a7c8d8568530 Mon Sep 17 00:00:00 2001 From: sjorsdonkers <72333389+sjorsdonkers@users.noreply.github.com> Date: Wed, 9 Jul 2025 11:29:05 +0200 Subject: [PATCH 2/3] Separate NodeIterator impl, fix _filter --- src/browser/dom/document.zig | 2 +- src/browser/dom/node_filter.zig | 35 ++++++++ src/browser/dom/node_iterator.zig | 135 +++++++++++++++++++++++------- src/browser/dom/tree_walker.zig | 62 ++++---------- 4 files changed, 156 insertions(+), 78 deletions(-) diff --git a/src/browser/dom/document.zig b/src/browser/dom/document.zig index ac732b63d..795724188 100644 --- a/src/browser/dom/document.zig +++ b/src/browser/dom/document.zig @@ -266,7 +266,7 @@ pub const Document = struct { return try TreeWalker.init(root, what_to_show, filter); } - pub fn _createNodeIterator(_: *parser.Document, root: *parser.Node, what_to_show: ?u32, filter: ?TreeWalker.TreeWalkerOpts) !NodeIterator { + pub fn _createNodeIterator(_: *parser.Document, root: *parser.Node, what_to_show: ?u32, filter: ?NodeIterator.NodeIteratorOpts) !NodeIterator { return try NodeIterator.init(root, what_to_show, filter); } diff --git a/src/browser/dom/node_filter.zig b/src/browser/dom/node_filter.zig index 8ba80e10f..54ce6b67f 100644 --- a/src/browser/dom/node_filter.zig +++ b/src/browser/dom/node_filter.zig @@ -17,6 +17,8 @@ // along with this program. If not, see . const std = @import("std"); +const parser = @import("../netsurf.zig"); +const Env = @import("../env.zig").Env; pub const NodeFilter = struct { pub const _FILTER_ACCEPT: u16 = 1; @@ -38,6 +40,39 @@ pub const NodeFilter = struct { pub const _SHOW_NOTATION: u32 = 0b100000000000; }; +const VerifyResult = enum { accept, skip, reject }; + +pub fn verify(what_to_show: u32, filter: ?Env.Function, node: *parser.Node) !VerifyResult { + const node_type = try parser.nodeType(node); + + // Verify that we can show this node type. + if (!switch (node_type) { + .attribute => what_to_show & NodeFilter._SHOW_ATTRIBUTE != 0, + .cdata_section => what_to_show & NodeFilter._SHOW_CDATA_SECTION != 0, + .comment => what_to_show & NodeFilter._SHOW_COMMENT != 0, + .document => what_to_show & NodeFilter._SHOW_DOCUMENT != 0, + .document_fragment => what_to_show & NodeFilter._SHOW_DOCUMENT_FRAGMENT != 0, + .document_type => what_to_show & NodeFilter._SHOW_DOCUMENT_TYPE != 0, + .element => what_to_show & NodeFilter._SHOW_ELEMENT != 0, + .entity => what_to_show & NodeFilter._SHOW_ENTITY != 0, + .entity_reference => what_to_show & NodeFilter._SHOW_ENTITY_REFERENCE != 0, + .notation => what_to_show & NodeFilter._SHOW_NOTATION != 0, + .processing_instruction => what_to_show & NodeFilter._SHOW_PROCESSING_INSTRUCTION != 0, + .text => what_to_show & NodeFilter._SHOW_TEXT != 0, + }) return .reject; + + // Verify that we aren't filtering it out. + if (filter) |f| { + const acceptance = try f.call(u16, .{node}); + return switch (acceptance) { + NodeFilter._FILTER_ACCEPT => .accept, + NodeFilter._FILTER_REJECT => .reject, + NodeFilter._FILTER_SKIP => .skip, + else => .reject, + }; + } else return .accept; +} + const testing = @import("../../testing.zig"); test "Browser.DOM.NodeFilter" { var runner = try testing.jsRunner(testing.tracking_allocator, .{}); diff --git a/src/browser/dom/node_iterator.zig b/src/browser/dom/node_iterator.zig index 336368ff7..d79812e0a 100644 --- a/src/browser/dom/node_iterator.zig +++ b/src/browser/dom/node_iterator.zig @@ -19,19 +19,47 @@ const std = @import("std"); const parser = @import("../netsurf.zig"); const Env = @import("../env.zig").Env; -const TreeWalker = @import("tree_walker.zig").TreeWalker; +const NodeFilter = @import("node_filter.zig"); // https://developer.mozilla.org/en-US/docs/Web/API/NodeIterator +// While this is similar to TreeWalker it has it's own implementation as there are several suttle differences +// For example: +// - nextNode returns the reference node, whereas TreeWalker returns the next node +// - Skip and reject are equivalent for NodeIterator, for TreeWalker they are different pub const NodeIterator = struct { - walker: TreeWalker, + root: *parser.Node, + reference_node: *parser.Node, + what_to_show: u32, + filter: ?NodeIteratorOpts, + filter_func: ?Env.Function, + pointer_before_current: bool = true, - pub fn init(node: *parser.Node, what_to_show: ?u32, filter: ?TreeWalker.TreeWalkerOpts) !NodeIterator { - return .{ .walker = try TreeWalker.init(node, what_to_show, filter) }; + pub const NodeIteratorOpts = union(enum) { + function: Env.Function, + object: struct { acceptNode: Env.Function }, + }; + + pub fn init(node: *parser.Node, what_to_show: ?u32, filter: ?NodeIteratorOpts) !NodeIterator { + var filter_func: ?Env.Function = null; + if (filter) |f| { + filter_func = switch (f) { + .function => |func| func, + .object => |o| o.acceptNode, + }; + } + + return .{ + .root = node, + .reference_node = node, + .what_to_show = what_to_show orelse NodeFilter.NodeFilter._SHOW_ALL, + .filter = filter, + .filter_func = filter_func, + }; } - pub fn get_filter(self: *const NodeIterator) ?Env.Function { - return self.walker.filter; + pub fn get_filter(self: *const NodeIterator) ?NodeIteratorOpts { + return self.filter; } pub fn get_pointerBeforeReferenceNode(self: *const NodeIterator) bool { @@ -39,34 +67,34 @@ pub const NodeIterator = struct { } pub fn get_referenceNode(self: *const NodeIterator) *parser.Node { - return self.walker.current_node; + return self.reference_node; } pub fn get_root(self: *const NodeIterator) *parser.Node { - return self.walker.root; + return self.root; } pub fn get_whatToShow(self: *const NodeIterator) u32 { - return self.walker.what_to_show; + return self.what_to_show; } pub fn _nextNode(self: *NodeIterator) !?*parser.Node { if (self.pointer_before_current) { // Unlike TreeWalker, NodeIterator starts at the first node self.pointer_before_current = false; - if (.accept == try self.walker.verify(self.walker.current_node)) { - return self.walker.current_node; + if (.accept == try NodeFilter.verify(self.what_to_show, self.filter_func, self.reference_node)) { + return self.reference_node; } } - if (try self.firstChild(self.walker.current_node)) |child| { - self.walker.current_node = child; + if (try self.firstChild(self.reference_node)) |child| { + self.reference_node = child; return child; } - var current = self.walker.current_node; - while (current != self.walker.root) { - if (try self.walker.nextSibling(current)) |sibling| { - self.walker.current_node = sibling; + var current = self.reference_node; + while (current != self.root) { + if (try self.nextSibling(current)) |sibling| { + self.reference_node = sibling; return sibling; } @@ -79,41 +107,41 @@ pub const NodeIterator = struct { pub fn _previousNode(self: *NodeIterator) !?*parser.Node { if (!self.pointer_before_current) { self.pointer_before_current = true; - if (.accept == try self.walker.verify(self.walker.current_node)) { - return self.walker.current_node; // Still need to verify as last may be first as well + if (.accept == try NodeFilter.verify(self.what_to_show, self.filter_func, self.reference_node)) { + return self.reference_node; // Still need to verify as last may be first as well } } - if (self.walker.current_node == self.walker.root) return null; + if (self.reference_node == self.root) return null; - var current = self.walker.current_node; + var current = self.reference_node; while (try parser.nodePreviousSibling(current)) |previous| { current = previous; - switch (try self.walker.verify(current)) { + switch (try NodeFilter.verify(self.what_to_show, self.filter_func, current)) { .accept => { // Get last child if it has one. if (try self.lastChild(current)) |child| { - self.walker.current_node = child; + self.reference_node = child; return child; } // Otherwise, this node is our previous one. - self.walker.current_node = current; + self.reference_node = current; return current; }, .reject, .skip => { // Get last child if it has one. if (try self.lastChild(current)) |child| { - self.walker.current_node = child; + self.reference_node = child; return child; } }, } } - if (current != self.walker.root) { - if (try self.walker.parentNode(current)) |parent| { - self.walker.current_node = parent; + if (current != self.root) { + if (try self.parentNode(current)) |parent| { + self.reference_node = parent; return parent; } } @@ -129,7 +157,7 @@ pub const NodeIterator = struct { const index: u32 = @intCast(i); const child = (try parser.nodeListItem(children, index)) orelse return null; - switch (try self.walker.verify(child)) { + switch (try NodeFilter.verify(self.what_to_show, self.filter_func, child)) { .accept => return child, // NOTE: Skip and reject are equivalent for NodeIterator, this is different from TreeWalker .reject, .skip => if (try self.firstChild(child)) |gchild| return gchild, } @@ -147,7 +175,7 @@ pub const NodeIterator = struct { index -= 1; const child = (try parser.nodeListItem(children, index)) orelse return null; - switch (try self.walker.verify(child)) { + switch (try NodeFilter.verify(self.what_to_show, self.filter_func, child)) { .accept => return child, // NOTE: Skip and reject are equivalent for NodeIterator, this is different from TreeWalker .reject, .skip => if (try self.lastChild(child)) |gchild| return gchild, } @@ -155,6 +183,38 @@ pub const NodeIterator = struct { return null; } + + // This implementation is actually the same as :TreeWalker + fn parentNode(self: *const NodeIterator, node: *parser.Node) !?*parser.Node { + if (self.root == node) return null; + + var current = node; + while (true) { + if (current == self.root) return null; + current = (try parser.nodeParentNode(current)) orelse return null; + + switch (try NodeFilter.verify(self.what_to_show, self.filter_func, current)) { + .accept => return current, + .reject, .skip => continue, + } + } + } + + // This implementation is actually the same as :TreeWalker + fn nextSibling(self: *const NodeIterator, node: *parser.Node) !?*parser.Node { + var current = node; + + while (true) { + current = (try parser.nodeNextSibling(current)) orelse return null; + + switch (try NodeFilter.verify(self.what_to_show, self.filter_func, current)) { + .accept => return current, + .skip, .reject => continue, + } + } + + return null; + } }; const testing = @import("../../testing.zig"); @@ -210,4 +270,19 @@ test "Browser.DOM.NodeFilter" { }, .{ "notationIterator.previousNode()", "null" }, }, .{}); + + try runner.testCases(&.{ + .{ "nodeIterator.filter.acceptNode(document.body)", "1" }, + .{ "notationIterator.filter", "null" }, + .{ + \\ const rejectIterator = document.createNodeIterator( + \\ document.body, + \\ NodeFilter.SHOW_ALL, + \\ (e => { return NodeFilter.FILTER_REJECT}), + \\ ); + \\ rejectIterator.filter(document.body); + , + "2", + }, + }, .{}); } diff --git a/src/browser/dom/tree_walker.zig b/src/browser/dom/tree_walker.zig index 55160c3dc..22952f970 100644 --- a/src/browser/dom/tree_walker.zig +++ b/src/browser/dom/tree_walker.zig @@ -19,7 +19,7 @@ const std = @import("std"); const parser = @import("../netsurf.zig"); -const NodeFilter = @import("node_filter.zig").NodeFilter; +const NodeFilter = @import("node_filter.zig"); const Env = @import("../env.zig").Env; const Page = @import("../page.zig").Page; @@ -28,7 +28,8 @@ pub const TreeWalker = struct { root: *parser.Node, current_node: *parser.Node, what_to_show: u32, - filter: ?Env.Function, + filter: ?TreeWalkerOpts, + filter_func: ?Env.Function, pub const TreeWalkerOpts = union(enum) { function: Env.Function, @@ -48,45 +49,12 @@ pub const TreeWalker = struct { return .{ .root = node, .current_node = node, - .what_to_show = what_to_show orelse NodeFilter._SHOW_ALL, - .filter = filter_func, + .what_to_show = what_to_show orelse NodeFilter.NodeFilter._SHOW_ALL, + .filter = filter, + .filter_func = filter_func, }; } - const VerifyResult = enum { accept, skip, reject }; - - pub fn verify(self: *const TreeWalker, node: *parser.Node) !VerifyResult { - const node_type = try parser.nodeType(node); - const what_to_show = self.what_to_show; - - // Verify that we can show this node type. - if (!switch (node_type) { - .attribute => what_to_show & NodeFilter._SHOW_ATTRIBUTE != 0, - .cdata_section => what_to_show & NodeFilter._SHOW_CDATA_SECTION != 0, - .comment => what_to_show & NodeFilter._SHOW_COMMENT != 0, - .document => what_to_show & NodeFilter._SHOW_DOCUMENT != 0, - .document_fragment => what_to_show & NodeFilter._SHOW_DOCUMENT_FRAGMENT != 0, - .document_type => what_to_show & NodeFilter._SHOW_DOCUMENT_TYPE != 0, - .element => what_to_show & NodeFilter._SHOW_ELEMENT != 0, - .entity => what_to_show & NodeFilter._SHOW_ENTITY != 0, - .entity_reference => what_to_show & NodeFilter._SHOW_ENTITY_REFERENCE != 0, - .notation => what_to_show & NodeFilter._SHOW_NOTATION != 0, - .processing_instruction => what_to_show & NodeFilter._SHOW_PROCESSING_INSTRUCTION != 0, - .text => what_to_show & NodeFilter._SHOW_TEXT != 0, - }) return .reject; - - // Verify that we aren't filtering it out. - if (self.filter) |f| { - const filter = try f.call(u16, .{node}); - return switch (filter) { - NodeFilter._FILTER_ACCEPT => .accept, - NodeFilter._FILTER_REJECT => .reject, - NodeFilter._FILTER_SKIP => .skip, - else => .reject, - }; - } else return .accept; - } - pub fn get_root(self: *TreeWalker) *parser.Node { return self.root; } @@ -99,7 +67,7 @@ pub const TreeWalker = struct { return self.what_to_show; } - pub fn get_filter(self: *TreeWalker) ?Env.Function { + pub fn get_filter(self: *TreeWalker) ?TreeWalkerOpts { return self.filter; } @@ -115,7 +83,7 @@ pub const TreeWalker = struct { const index: u32 = @intCast(i); const child = (try parser.nodeListItem(children, index)) orelse return null; - switch (try self.verify(child)) { + switch (try NodeFilter.verify(self.what_to_show, self.filter_func, child)) { .accept => return child, .reject => continue, .skip => if (try self.firstChild(child)) |gchild| return gchild, @@ -134,7 +102,7 @@ pub const TreeWalker = struct { index -= 1; const child = (try parser.nodeListItem(children, index)) orelse return null; - switch (try self.verify(child)) { + switch (try NodeFilter.verify(self.what_to_show, self.filter_func, child)) { .accept => return child, .reject => continue, .skip => if (try self.lastChild(child)) |gchild| return gchild, @@ -144,13 +112,13 @@ pub const TreeWalker = struct { return null; } - pub fn nextSibling(self: *const TreeWalker, node: *parser.Node) !?*parser.Node { + fn nextSibling(self: *const TreeWalker, node: *parser.Node) !?*parser.Node { var current = node; while (true) { current = (try parser.nodeNextSibling(current)) orelse return null; - switch (try self.verify(current)) { + switch (try NodeFilter.verify(self.what_to_show, self.filter_func, current)) { .accept => return current, .skip, .reject => continue, } @@ -165,7 +133,7 @@ pub const TreeWalker = struct { while (true) { current = (try parser.nodePreviousSibling(current)) orelse return null; - switch (try self.verify(current)) { + switch (try NodeFilter.verify(self.what_to_show, self.filter_func, current)) { .accept => return current, .skip, .reject => continue, } @@ -174,7 +142,7 @@ pub const TreeWalker = struct { return null; } - pub fn parentNode(self: *const TreeWalker, node: *parser.Node) !?*parser.Node { + fn parentNode(self: *const TreeWalker, node: *parser.Node) !?*parser.Node { if (self.root == node) return null; var current = node; @@ -182,7 +150,7 @@ pub const TreeWalker = struct { if (current == self.root) return null; current = (try parser.nodeParentNode(current)) orelse return null; - switch (try self.verify(current)) { + switch (try NodeFilter.verify(self.what_to_show, self.filter_func, current)) { .accept => return current, .reject, .skip => continue, } @@ -251,7 +219,7 @@ pub const TreeWalker = struct { while (try parser.nodePreviousSibling(current)) |previous| { current = previous; - switch (try self.verify(current)) { + switch (try NodeFilter.verify(self.what_to_show, self.filter_func, current)) { .accept => { // Get last child if it has one. if (try self.lastChild(current)) |child| { From 2842fcadde84f03b42e5a8b7cecadce023609196 Mon Sep 17 00:00:00 2001 From: sjorsdonkers <72333389+sjorsdonkers@users.noreply.github.com> Date: Thu, 10 Jul 2025 10:26:44 +0200 Subject: [PATCH 3/3] fix callback crash with Node.Union --- src/browser/dom/node_filter.zig | 3 +- src/browser/dom/node_iterator.zig | 32 +++++++++++---------- src/browser/dom/tree_walker.zig | 46 ++++++++++++++++--------------- 3 files changed, 43 insertions(+), 38 deletions(-) diff --git a/src/browser/dom/node_filter.zig b/src/browser/dom/node_filter.zig index 54ce6b67f..88cb4e57a 100644 --- a/src/browser/dom/node_filter.zig +++ b/src/browser/dom/node_filter.zig @@ -19,6 +19,7 @@ const std = @import("std"); const parser = @import("../netsurf.zig"); const Env = @import("../env.zig").Env; +const Node = @import("node.zig").Node; pub const NodeFilter = struct { pub const _FILTER_ACCEPT: u16 = 1; @@ -63,7 +64,7 @@ pub fn verify(what_to_show: u32, filter: ?Env.Function, node: *parser.Node) !Ver // Verify that we aren't filtering it out. if (filter) |f| { - const acceptance = try f.call(u16, .{node}); + const acceptance = try f.call(u16, .{try Node.toInterface(node)}); return switch (acceptance) { NodeFilter._FILTER_ACCEPT => .accept, NodeFilter._FILTER_REJECT => .reject, diff --git a/src/browser/dom/node_iterator.zig b/src/browser/dom/node_iterator.zig index d79812e0a..039227afb 100644 --- a/src/browser/dom/node_iterator.zig +++ b/src/browser/dom/node_iterator.zig @@ -20,9 +20,11 @@ const std = @import("std"); const parser = @import("../netsurf.zig"); const Env = @import("../env.zig").Env; const NodeFilter = @import("node_filter.zig"); +const Node = @import("node.zig").Node; +const NodeUnion = @import("node.zig").Union; // https://developer.mozilla.org/en-US/docs/Web/API/NodeIterator -// While this is similar to TreeWalker it has it's own implementation as there are several suttle differences +// While this is similar to TreeWalker it has its own implementation as there are several subtle differences // For example: // - nextNode returns the reference node, whereas TreeWalker returns the next node // - Skip and reject are equivalent for NodeIterator, for TreeWalker they are different @@ -66,36 +68,36 @@ pub const NodeIterator = struct { return self.pointer_before_current; } - pub fn get_referenceNode(self: *const NodeIterator) *parser.Node { - return self.reference_node; + pub fn get_referenceNode(self: *const NodeIterator) !NodeUnion { + return try Node.toInterface(self.reference_node); } - pub fn get_root(self: *const NodeIterator) *parser.Node { - return self.root; + pub fn get_root(self: *const NodeIterator) !NodeUnion { + return try Node.toInterface(self.root); } pub fn get_whatToShow(self: *const NodeIterator) u32 { return self.what_to_show; } - pub fn _nextNode(self: *NodeIterator) !?*parser.Node { + pub fn _nextNode(self: *NodeIterator) !?NodeUnion { if (self.pointer_before_current) { // Unlike TreeWalker, NodeIterator starts at the first node self.pointer_before_current = false; if (.accept == try NodeFilter.verify(self.what_to_show, self.filter_func, self.reference_node)) { - return self.reference_node; + return try Node.toInterface(self.reference_node); } } if (try self.firstChild(self.reference_node)) |child| { self.reference_node = child; - return child; + return try Node.toInterface(child); } var current = self.reference_node; while (current != self.root) { if (try self.nextSibling(current)) |sibling| { self.reference_node = sibling; - return sibling; + return try Node.toInterface(sibling); } current = (try parser.nodeParentNode(current)) orelse break; @@ -104,11 +106,11 @@ pub const NodeIterator = struct { return null; } - pub fn _previousNode(self: *NodeIterator) !?*parser.Node { + pub fn _previousNode(self: *NodeIterator) !?NodeUnion { if (!self.pointer_before_current) { self.pointer_before_current = true; if (.accept == try NodeFilter.verify(self.what_to_show, self.filter_func, self.reference_node)) { - return self.reference_node; // Still need to verify as last may be first as well + return try Node.toInterface(self.reference_node); // Still need to verify as last may be first as well } } if (self.reference_node == self.root) return null; @@ -122,18 +124,18 @@ pub const NodeIterator = struct { // Get last child if it has one. if (try self.lastChild(current)) |child| { self.reference_node = child; - return child; + return try Node.toInterface(child); } // Otherwise, this node is our previous one. self.reference_node = current; - return current; + return try Node.toInterface(current); }, .reject, .skip => { // Get last child if it has one. if (try self.lastChild(current)) |child| { self.reference_node = child; - return child; + return try Node.toInterface(child); } }, } @@ -142,7 +144,7 @@ pub const NodeIterator = struct { if (current != self.root) { if (try self.parentNode(current)) |parent| { self.reference_node = parent; - return parent; + return try Node.toInterface(parent); } } diff --git a/src/browser/dom/tree_walker.zig b/src/browser/dom/tree_walker.zig index 22952f970..4822e615b 100644 --- a/src/browser/dom/tree_walker.zig +++ b/src/browser/dom/tree_walker.zig @@ -22,6 +22,8 @@ const parser = @import("../netsurf.zig"); const NodeFilter = @import("node_filter.zig"); const Env = @import("../env.zig").Env; const Page = @import("../page.zig").Page; +const Node = @import("node.zig").Node; +const NodeUnion = @import("node.zig").Union; // https://developer.mozilla.org/en-US/docs/Web/API/TreeWalker pub const TreeWalker = struct { @@ -55,12 +57,12 @@ pub const TreeWalker = struct { }; } - pub fn get_root(self: *TreeWalker) *parser.Node { - return self.root; + pub fn get_root(self: *TreeWalker) !NodeUnion { + return try Node.toInterface(self.root); } - pub fn get_currentNode(self: *TreeWalker) *parser.Node { - return self.current_node; + pub fn get_currentNode(self: *TreeWalker) !NodeUnion { + return try Node.toInterface(self.current_node); } pub fn get_whatToShow(self: *TreeWalker) u32 { @@ -157,35 +159,35 @@ pub const TreeWalker = struct { } } - pub fn _firstChild(self: *TreeWalker) !?*parser.Node { + pub fn _firstChild(self: *TreeWalker) !?NodeUnion { if (try self.firstChild(self.current_node)) |child| { self.current_node = child; - return child; + return try Node.toInterface(child); } return null; } - pub fn _lastChild(self: *TreeWalker) !?*parser.Node { + pub fn _lastChild(self: *TreeWalker) !?NodeUnion { if (try self.lastChild(self.current_node)) |child| { self.current_node = child; - return child; + return try Node.toInterface(child); } return null; } - pub fn _nextNode(self: *TreeWalker) !?*parser.Node { + pub fn _nextNode(self: *TreeWalker) !?NodeUnion { if (try self.firstChild(self.current_node)) |child| { self.current_node = child; - return child; + return try Node.toInterface(child); } var current = self.current_node; while (current != self.root) { if (try self.nextSibling(current)) |sibling| { self.current_node = sibling; - return sibling; + return try Node.toInterface(sibling); } current = (try parser.nodeParentNode(current)) orelse break; @@ -194,25 +196,25 @@ pub const TreeWalker = struct { return null; } - pub fn _nextSibling(self: *TreeWalker) !?*parser.Node { + pub fn _nextSibling(self: *TreeWalker) !?NodeUnion { if (try self.nextSibling(self.current_node)) |sibling| { self.current_node = sibling; - return sibling; + return try Node.toInterface(sibling); } return null; } - pub fn _parentNode(self: *TreeWalker) !?*parser.Node { + pub fn _parentNode(self: *TreeWalker) !?NodeUnion { if (try self.parentNode(self.current_node)) |parent| { self.current_node = parent; - return parent; + return try Node.toInterface(parent); } return null; } - pub fn _previousNode(self: *TreeWalker) !?*parser.Node { + pub fn _previousNode(self: *TreeWalker) !?NodeUnion { if (self.current_node == self.root) return null; var current = self.current_node; @@ -224,19 +226,19 @@ pub const TreeWalker = struct { // Get last child if it has one. if (try self.lastChild(current)) |child| { self.current_node = child; - return child; + return try Node.toInterface(child); } // Otherwise, this node is our previous one. self.current_node = current; - return current; + return try Node.toInterface(current); }, .reject => continue, .skip => { // Get last child if it has one. if (try self.lastChild(current)) |child| { self.current_node = child; - return child; + return try Node.toInterface(child); } }, } @@ -245,17 +247,17 @@ pub const TreeWalker = struct { if (current != self.root) { if (try self.parentNode(current)) |parent| { self.current_node = parent; - return parent; + return try Node.toInterface(parent); } } return null; } - pub fn _previousSibling(self: *TreeWalker) !?*parser.Node { + pub fn _previousSibling(self: *TreeWalker) !?NodeUnion { if (try self.previousSibling(self.current_node)) |sibling| { self.current_node = sibling; - return sibling; + return try Node.toInterface(sibling); } return null;