fix(js): keep TreeWalker traversal in document order (#432)

This commit is contained in:
SGavrl committed 2026-07-19 19:40:27 +00:00
1 parent 29e20ae970
commit c12915a1a6
4 files changed
+210 -12

No files matched your search

+47
View File
@@ -553,6 +553,35 @@ impl DomTree {
result
}
/// Returns the node after `current` in document order, without leaving the
/// subtree rooted at `root`.
///
/// Keeping the ancestor climb inside the DOM avoids one JS/native crossing
/// per ancestor when a TreeWalker reaches a deep leaf.
pub fn next_in_subtree(&self, root: NodeId, current: NodeId) -> Option<NodeId> {
let inner = self.inner.borrow();
let current_node = inner.nodes.get(current.index())?.as_ref()?;
if let Some(child) = current_node.first_child {
return Some(child);
}
let mut node_id = current;
for _ in 0..=inner.nodes.len() {
if node_id == root {
return None;
}
let node = inner.nodes.get(node_id.index())?.as_ref()?;
if let Some(sibling) = node.next_sibling {
return Some(sibling);
}
node_id = node.parent?;
}
// Parent cycles are prevented by the mutation APIs. Keep a hard bound
// here as defense in depth for a malformed tree.
None
}
pub fn ancestors(&self, node_id: NodeId) -> Vec<NodeId> {
let inner = self.inner.borrow();
let mut result = Vec::new();
@@ -980,6 +1009,24 @@ mod tests {
assert_eq!(tree.len(), 1);
}
#[test]
fn test_next_in_subtree_follows_document_order_and_stays_within_root() {
let tree = DomTree::new();
let root = tree.new_node(NodeData::Text { contents: "root".into() });
let first = tree.new_node(NodeData::Text { contents: "first".into() });
let nested = tree.new_node(NodeData::Text { contents: "nested".into() });
let second = tree.new_node(NodeData::Text { contents: "second".into() });
tree.append_child(tree.document(), root);
tree.append_child(root, first);
tree.append_child(first, nested);
tree.append_child(root, second);
assert_eq!(tree.next_in_subtree(root, root), Some(first));
assert_eq!(tree.next_in_subtree(root, first), Some(nested));
assert_eq!(tree.next_in_subtree(root, nested), Some(second));
assert_eq!(tree.next_in_subtree(root, second), None);
}
// Builds a chain of `depth` nested <div> elements under the document and
// returns (outermost_div, innermost_div). Depth this large overflows a
// recursive tree walk and aborts the process, so it guards the iterative