From 2d5fc74cbf553de8b50a2d271b93d54230a7efee Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Mon, 9 Nov 2026 19:29:02 -0800 Subject: [PATCH] Scripts: a document a script opens counts toward the run's growth limit A document that document.open or file.new adds during a run counts whole toward the growth allowance: every object in its layers, symbols and patterns, opacity masks included, and its document data. --- crates/engine/src/script/mod.rs | 25 ++++++++++++++++----- crates/engine/src/tests/script_run.rs | 31 +++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/crates/engine/src/script/mod.rs b/crates/engine/src/script/mod.rs index 4b5380319..ee351d298 100644 --- a/crates/engine/src/script/mod.rs +++ b/crates/engine/src/script/mod.rs @@ -520,6 +520,14 @@ fn raise_ids(s: &mut Session) { s.script_high_water = marks; } +/// Every object in `d`: the art of its layers, its symbols and its patterns, opacity masks included. +fn all_objects(d: &crate::Document) -> u64 { + let layers = d.layers.iter().map(|n| n.count_with_masks()); + let symbols = d.symbols.iter().map(|s| s.art.count_with_masks()); + let patterns = d.patterns.iter().flat_map(|p| &p.art).map(|n| n.count_with_masks()); + layers.chain(symbols).chain(patterns).map(|n| n as u64).sum() +} + fn note_ids(s: &mut Session) { for st in &s.docs { let mark = s.script_high_water.entry(st.uid).or_insert(0); @@ -551,11 +559,18 @@ fn step(s: &mut Session, host: &Host, id: &str, params: &Value) -> Result<(Value note_ids(s); let (mut objects, mut bytes) = (0, 0); for st in &s.docs { - if let Some((_, d, ids)) = before.iter().find(|(uid, ..)| *uid == st.uid) - && !Arc::ptr_eq(d, &st.doc) - { - objects += st.doc.peek_next_id().saturating_sub(*ids); - bytes += d.retained_bytes_each(&st.doc).1 as u64; + match before.iter().find(|(uid, ..)| *uid == st.uid) { + Some((_, d, ids)) if !Arc::ptr_eq(d, &st.doc) => { + objects += st.doc.peek_next_id().saturating_sub(*ids); + bytes += d.retained_bytes_each(&st.doc).1 as u64; + } + Some(_) => {} + // A document the command opened or made counts whole. + None if mutating => { + objects += all_objects(&st.doc); + bytes += crate::Document::new(1.0, 1.0).retained_bytes_each(&st.doc).1 as u64; + } + None => {} } } Ok((v, objects, bytes, save)) diff --git a/crates/engine/src/tests/script_run.rs b/crates/engine/src/tests/script_run.rs index 7912c7b1d..566e36145 100644 --- a/crates/engine/src/tests/script_run.rs +++ b/crates/engine/src/tests/script_run.rs @@ -601,6 +601,37 @@ fn a_run_with_nobody_to_ask_ends_at_its_growth_allowance() { assert_eq!(state(&s), before); } +#[test] +fn a_document_a_run_opens_counts_toward_its_growth_allowance() { + TEST_GROWTH.with(|g| g.set(Some(20))); + let rects: String = (0..30).map(|i| format!(r#""#)).collect(); + let svg = format!(r#"{rects}"#); + let data = vectorcraft_format::base64_encode(svg.as_bytes()); + let mut s = two_docs(); + let before = state(&s); + let source = format!("try {{ vc.run('document.open', {{name: 'a.svg', dataBase64: '{data}'}}); }} catch (e) {{}}"); + let e = run(&mut s, &source).unwrap_err().to_string(); + assert!(e.contains("objects and data"), "{e}"); + assert_eq!(state(&s), before); + + // A document whose art is all in a symbol counts it too. + let mut maker = Session::new(); + maker.execute("file.new", &json!({})).unwrap(); + for x in 0..30 { + maker.execute("shape.rectangle", &rect(x)).unwrap(); + } + maker.execute("select.all", &json!({})).unwrap(); + let instance = maker.execute("symbol.new", &json!({"name": "Many"})).unwrap()["id"].as_u64().unwrap(); + maker.execute("edit.clear", &json!({"ids": [instance]})).unwrap(); + assert_eq!(objects(&maker, 0), 0); + let data = maker.execute("document.serialize", &json!({})).unwrap()["dataBase64"].as_str().unwrap().to_string(); + let source = format!("vc.run('document.open', {{name: 'b.vectorcraft', dataBase64: '{data}'}});"); + let e = run(&mut s, &source).unwrap_err().to_string(); + TEST_GROWTH.with(|g| g.set(None)); + assert!(e.contains("objects and data"), "{e}"); + assert_eq!(state(&s), before); +} + #[test] fn a_person_can_let_a_growing_run_go_on_or_stop_it() { TEST_GROWTH.with(|g| g.set(Some(20)));