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)); + } } } diff --git a/crates/obscura-dom/src/serialize.rs b/crates/obscura-dom/src/serialize.rs index 7a489a5..608b5b4 100644 --- a/crates/obscura-dom/src/serialize.rs +++ b/crates/obscura-dom/src/serialize.rs @@ -72,7 +72,17 @@ impl DomTree { } NodeData::Comment { contents } => { buf.push_str("" (that ends the comment), but script can via + // document.createComment("a-->b"). Emitting it verbatim would close + // the comment early and let the trailing text escape into markup. + // Neutralize the terminator so the serialized output round-trips as + // a single comment. + if contents.contains("-->") { + buf.push_str(&contents.replace("-->", "-->")); + } else { + buf.push_str(contents); + } buf.push_str("-->"); } NodeData::ProcessingInstruction { target, data } => {