mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 23:23: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 ElementUnion = @import("element.zig").Union;
|
||||||
const TreeWalker = @import("tree_walker.zig").TreeWalker;
|
const TreeWalker = @import("tree_walker.zig").TreeWalker;
|
||||||
const CSSStyleSheet = @import("../cssom/css_stylesheet.zig").CSSStyleSheet;
|
const CSSStyleSheet = @import("../cssom/css_stylesheet.zig").CSSStyleSheet;
|
||||||
|
const NodeIterator = @import("node_iterator.zig").NodeIterator;
|
||||||
const Range = @import("range.zig").Range;
|
const Range = @import("range.zig").Range;
|
||||||
|
|
||||||
const Env = @import("../env.zig").Env;
|
const Env = @import("../env.zig").Env;
|
||||||
@@ -265,6 +266,10 @@ pub const Document = struct {
|
|||||||
return try TreeWalker.init(root, what_to_show, filter);
|
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 {
|
pub fn getActiveElement(self: *parser.Document, page: *Page) !?*parser.Element {
|
||||||
if (page.getNodeState(@alignCast(@ptrCast(self)))) |state| {
|
if (page.getNodeState(@alignCast(@ptrCast(self)))) |state| {
|
||||||
if (state.active_element) |ae| {
|
if (state.active_element) |ae| {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ const MutationObserver = @import("mutation_observer.zig");
|
|||||||
const IntersectionObserver = @import("intersection_observer.zig");
|
const IntersectionObserver = @import("intersection_observer.zig");
|
||||||
const DOMParser = @import("dom_parser.zig").DOMParser;
|
const DOMParser = @import("dom_parser.zig").DOMParser;
|
||||||
const TreeWalker = @import("tree_walker.zig").TreeWalker;
|
const TreeWalker = @import("tree_walker.zig").TreeWalker;
|
||||||
|
const NodeIterator = @import("node_iterator.zig").NodeIterator;
|
||||||
const NodeFilter = @import("node_filter.zig").NodeFilter;
|
const NodeFilter = @import("node_filter.zig").NodeFilter;
|
||||||
const PerformanceObserver = @import("performance_observer.zig").PerformanceObserver;
|
const PerformanceObserver = @import("performance_observer.zig").PerformanceObserver;
|
||||||
|
|
||||||
@@ -46,6 +47,7 @@ pub const Interfaces = .{
|
|||||||
IntersectionObserver.Interfaces,
|
IntersectionObserver.Interfaces,
|
||||||
DOMParser,
|
DOMParser,
|
||||||
TreeWalker,
|
TreeWalker,
|
||||||
|
NodeIterator,
|
||||||
NodeFilter,
|
NodeFilter,
|
||||||
@import("performance.zig").Interfaces,
|
@import("performance.zig").Interfaces,
|
||||||
PerformanceObserver,
|
PerformanceObserver,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ pub const NodeFilter = struct {
|
|||||||
pub const _FILTER_ACCEPT: u16 = 1;
|
pub const _FILTER_ACCEPT: u16 = 1;
|
||||||
pub const _FILTER_REJECT: u16 = 2;
|
pub const _FILTER_REJECT: u16 = 2;
|
||||||
pub const _FILTER_SKIP: u16 = 3;
|
pub const _FILTER_SKIP: u16 = 3;
|
||||||
|
|
||||||
pub const _SHOW_ALL: u32 = std.math.maxInt(u32);
|
pub const _SHOW_ALL: u32 = std.math.maxInt(u32);
|
||||||
pub const _SHOW_ELEMENT: u32 = 0b1;
|
pub const _SHOW_ELEMENT: u32 = 0b1;
|
||||||
pub const _SHOW_ATTRIBUTE: u32 = 0b10;
|
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 };
|
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 node_type = try parser.nodeType(node);
|
||||||
const what_to_show = self.what_to_show;
|
const what_to_show = self.what_to_show;
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ pub const TreeWalker = struct {
|
|||||||
|
|
||||||
// Verify that we aren't filtering it out.
|
// Verify that we aren't filtering it out.
|
||||||
if (self.filter) |f| {
|
if (self.filter) |f| {
|
||||||
const filter = try f.call(u32, .{node});
|
const filter = try f.call(u16, .{node});
|
||||||
return switch (filter) {
|
return switch (filter) {
|
||||||
NodeFilter._FILTER_ACCEPT => .accept,
|
NodeFilter._FILTER_ACCEPT => .accept,
|
||||||
NodeFilter._FILTER_REJECT => .reject,
|
NodeFilter._FILTER_REJECT => .reject,
|
||||||
@@ -144,7 +144,7 @@ pub const TreeWalker = struct {
|
|||||||
return null;
|
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;
|
var current = node;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -174,7 +174,7 @@ pub const TreeWalker = struct {
|
|||||||
return null;
|
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;
|
if (self.root == node) return null;
|
||||||
|
|
||||||
var current = node;
|
var current = node;
|
||||||
@@ -245,6 +245,8 @@ pub const TreeWalker = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn _previousNode(self: *TreeWalker) !?*parser.Node {
|
pub fn _previousNode(self: *TreeWalker) !?*parser.Node {
|
||||||
|
if (self.current_node == self.root) return null;
|
||||||
|
|
||||||
var current = self.current_node;
|
var current = self.current_node;
|
||||||
while (try parser.nodePreviousSibling(current)) |previous| {
|
while (try parser.nodePreviousSibling(current)) |previous| {
|
||||||
current = previous;
|
current = previous;
|
||||||
|
|||||||
@@ -360,7 +360,7 @@ const TimerCallback = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
call catch {
|
call catch {
|
||||||
log.debug(.user_script, "callback error", .{
|
log.warn(.user_script, "callback error", .{
|
||||||
.err = result.exception,
|
.err = result.exception,
|
||||||
.stack = result.stack,
|
.stack = result.stack,
|
||||||
.source = "window timeout",
|
.source = "window timeout",
|
||||||
|
|||||||
Reference in New Issue
Block a user