address review: queue each unread script-tree child once

This commit is contained in:
Dylan M. Taylor committed 2026-11-10 22:43:56 +00:00
1 parent e2dc3f38bf
commit 55c005af5a
2 files changed
+30 -2

No files matched your search

+5 -1
View File
@@ -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<u32> = HashSet::new();
let mut queued: HashSet<u32> = 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) {
+25 -1
View File
@@ -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<Object> = std::iter::repeat(Object::Ref(node)).take(9_999).chain([Object::Ref(leaf)]).collect();
let kids: Vec<Object> = 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<Object> = 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())]);
}