mirror of
				https://github.com/lightpanda-io/browser.git
				synced 2025-10-29 15:13:28 +00:00 
			
		
		
		
	NodeIterator
This commit is contained in:
		| @@ -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| { | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
							
								
								
									
										213
									
								
								src/browser/dom/node_iterator.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										213
									
								
								src/browser/dom/node_iterator.zig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,213 @@ | ||||
| // Copyright (C) 2023-2025  Lightpanda (Selecy SAS) | ||||
| // | ||||
| // Francis Bouvier <francis@lightpanda.io> | ||||
| // Pierre Tachoire <pierre@lightpanda.io> | ||||
| // | ||||
| // 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 <https://www.gnu.org/licenses/>. | ||||
|  | ||||
| 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" }, | ||||
|     }, .{}); | ||||
| } | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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", | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 sjorsdonkers
					sjorsdonkers