From e7300c5df77d9921bb6c4add5bcba02088b47e1a Mon Sep 17 00:00:00 2001 From: SGavrl <277843151+SGavrl@users.noreply.github.com> Date: Wed, 1 Jul 2026 15:58:49 +0000 Subject: [PATCH] fix(cdp): make DOMSnapshot walk iterative to bound recursion depth The DOMSnapshot walk recursed one frame per nesting level. The MAX_NODES cap bounds the node count but not the depth, so a deeply nested linear chain (script can build thousands of nested elements) could overflow the stack before the count guard triggered. Rewrite it with an explicit stack so depth lives on the heap. Pre-order traversal and the MAX_NODES cap are preserved. Fixes #341. --- crates/obscura-cdp/src/domains/domsnapshot.rs | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/crates/obscura-cdp/src/domains/domsnapshot.rs b/crates/obscura-cdp/src/domains/domsnapshot.rs index 9ba6091..a02409e 100644 --- a/crates/obscura-cdp/src/domains/domsnapshot.rs +++ b/crates/obscura-cdp/src/domains/domsnapshot.rs @@ -84,21 +84,32 @@ impl Interner { } /// Pre-order DFS from the document, recording each node and its parent's index. +/// Iterative with an explicit stack: the MAX_NODES cap bounds the node count, +/// but recursion depth is a separate axis. A deeply nested linear chain (script +/// can build thousands of nested elements) would recurse that many frames deep +/// and could overflow the stack before the count guard triggers. An explicit +/// stack keeps the depth on the heap (issue #341). fn walk( dom: &DomTree, - id: NodeId, - parent: i64, + root: NodeId, + root_parent: i64, order: &mut Vec, parent_idx: &mut Vec, ) { - if order.len() >= MAX_NODES { - return; - } - let my = order.len() as i64; - order.push(id); - parent_idx.push(parent); - for child in dom.children(id) { - walk(dom, child, my, order, parent_idx); + // (node, parent_index). Children are pushed in reverse so they pop in + // document order, preserving the original pre-order traversal. + let mut stack: Vec<(NodeId, i64)> = vec![(root, root_parent)]; + while let Some((id, parent)) = stack.pop() { + if order.len() >= MAX_NODES { + break; + } + let my = order.len() as i64; + order.push(id); + parent_idx.push(parent); + let children: Vec = dom.children(id); + for child in children.into_iter().rev() { + stack.push((child, my)); + } } }