diff --git a/crates/forms/src/scripting.rs b/crates/forms/src/scripting.rs index 25c66e42bb..6e56aa19a9 100644 --- a/crates/forms/src/scripting.rs +++ b/crates/forms/src/scripting.rs @@ -182,6 +182,7 @@ pub fn document_scripts_named(doc: &Document) -> Vec<(String, String)> { let mut stack = vec![tree]; // By object number, as `get` reads a reference: `5 0 R` and `5 1 R` are the same object. let mut seen: HashSet = HashSet::new(); + let mut queued: HashSet = HashSet::new(); let mut visited = 0; while let Some(raw) = stack.pop() { if let Object::Ref(r) = raw @@ -214,7 +215,10 @@ pub fn document_scripts_named(doc: &Document) -> Vec<(String, String)> { // Children already seen are dropped before the room is taken: a long run of repeats // would otherwise fill the stack and crowd a distinct node, the script among them, out. let room = 10_000usize.saturating_sub(visited + stack.len()); - stack.extend(kids.into_iter().filter(|k| !matches!(k, Object::Ref(r) if seen.contains(&r.num))).take(room)); + // A child scheduled but not yet read counts as seen too (`queued`), so repeats of an + // unread node take one slot, not thousands. + let fresh = |k: &Object| !matches!(k, Object::Ref(r) if seen.contains(&r.num) || !queued.insert(r.num)); + stack.extend(kids.into_iter().filter(fresh).take(room)); } if let Some(pairs) = d.get(b"Names").map(|n| doc.resolve(n)).and_then(|n| n.as_array().cloned()) { for pair in pairs.chunks(2) { diff --git a/crates/forms/src/tests.rs b/crates/forms/src/tests.rs index 8e338016a2..86892e7212 100644 --- a/crates/forms/src/tests.rs +++ b/crates/forms/src/tests.rs @@ -5551,10 +5551,34 @@ fn repeated_name_tree_kids_do_not_crowd_out_a_script() { leaf_d.set(b"Names".to_vec(), pairs); let leaf = doc.add(Object::Dict(leaf_d)); let node = doc.add(Object::Dict(Dict::new())); - let kids: Vec = std::iter::repeat(Object::Ref(node)).take(9_999).chain([Object::Ref(leaf)]).collect(); + let kids: Vec = std::iter::repeat_n(Object::Ref(node), 9_999).chain([Object::Ref(leaf)]).collect(); doc.update_dict(node, |d| d.set(b"Kids".to_vec(), Object::Array(kids))).unwrap(); let mut names = names; names.set(b"JavaScript".to_vec(), Object::Ref(node)); doc.update_dict(root, |c| c.set(b"Names".to_vec(), Object::Dict(names))).unwrap(); assert_eq!(crate::scripting::document_scripts_named(&doc), vec![("Deep".to_string(), "app.alert(1);".to_string())]); } + +/// The same for repeats of a node not read yet: they take one slot, not one each. +#[test] +fn repeated_unread_name_tree_kids_do_not_crowd_out_a_script() { + use pdfcraft_cos::{Dict, Object}; + let mut doc = one_page(); + crate::scripting::set_document_script(&mut doc, "Deep", Some("app.alert(1);")).unwrap(); + let root = doc.root().unwrap(); + let cat = doc.get(root).as_dict().cloned().unwrap(); + let names = cat.get(b"Names").map(|n| doc.resolve(n)).and_then(|n| n.as_dict().cloned()).unwrap(); + let tree = names.get(b"JavaScript").map(|t| doc.resolve(t)).and_then(|t| t.as_dict().cloned()).unwrap(); + let mut leaf_d = Dict::new(); + leaf_d.set(b"Names".to_vec(), tree.get(b"Names").cloned().unwrap()); + let leaf = doc.add(Object::Dict(leaf_d)); + let empty = doc.add(Object::Dict(Dict::new())); + let mut top = Dict::new(); + let kids: Vec = std::iter::repeat_n(Object::Ref(empty), 9_999).chain([Object::Ref(leaf)]).collect(); + top.set(b"Kids".to_vec(), Object::Array(kids)); + let top = doc.add(Object::Dict(top)); + let mut names = names; + names.set(b"JavaScript".to_vec(), Object::Ref(top)); + doc.update_dict(root, |c| c.set(b"Names".to_vec(), Object::Dict(names))).unwrap(); + assert_eq!(crate::scripting::document_scripts_named(&doc), vec![("Deep".to_string(), "app.alert(1);".to_string())]); +}