diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index c4e15e4854..67083e1233 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -2687,6 +2687,8 @@ fn run_edit(doc: &mut pdfcraft_cos::Document, edit: &Edit, cx: &mut EditCtx) -> let ran = xfa::on_event(doc, &tpl, som, activity, &mut run); cx.xfa_datasets = run.datasets; cx.xfa_ran_away |= run.ran_away; + // The next edit of a batch reads the highlight this one's scripts set. + cx.highlight = run.highlight; ran.map_err(EditError::Invalid)?; } Edit::SetFieldImage { name, image } => { @@ -3484,6 +3486,8 @@ fn xfa_after_data_change(doc: &mut pdfcraft_cos::Document, cx: &mut EditCtx, cal .unwrap_or_else(|m| Err(format!("laying the form out again failed unexpectedly ({m})"))); cx.xfa_datasets = run.datasets; cx.xfa_ran_away |= run.ran_away; + // The next edit of a batch reads the highlight this one's scripts set. + cx.highlight = run.highlight; if let Err(e) = r { cx.xfa_out.errors.push(e); } @@ -4071,6 +4075,8 @@ impl Session { .unwrap_or_else(|m| Err(format!("the form's scripts failed unexpectedly ({m})"))); cx.xfa_datasets = run.datasets; cx.xfa_ran_away |= run.ran_away; + // The next edit of a batch reads the highlight this one's scripts set. + cx.highlight = run.highlight; if let Err(e) = ran { cx.xfa_out.errors.push(e); } diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 16278390cc..8b739e3056 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -8102,6 +8102,35 @@ fn a_forms_layout_ready_scripts_run_once_it_is_laid_out() { assert!(s.take_js_output(id).requests.is_empty()); } +/// In a batch of XFA events, a later event's scripts read the field highlight an earlier one's set: +/// `on`'s click turns it on, and `look`'s click, in the same batch, records what it reads. +#[test] +fn a_later_xfa_event_in_a_batch_reads_the_highlight_an_earlier_one_set() { + let script = |js: &str| format!(r#""#); + let xdp = format!( + r##" + + + +"##, + on = script("app.runtimeHighlight = true;"), + look = script("xfa.form.form.seen.rawValue = String(app.runtimeHighlight);"), + ); + let mut s = Session::new(); + let id = s.open("batch.pdf", None, Arc::new(pdfcraft_xfa::fixtures::shell(&xdp)), None).expect("opens"); + let click = |som: &str| Edit::XfaEvent { som: som.into(), activity: "click".into() }; + s.apply(id, Edit::Batch { edits: vec![click("form[0].on[0]"), click("form[0].look[0]")], label: "Click".into() }).expect("clicks"); + let seen = s.get(id).unwrap().form.iter().find(|f| f.name == "seen").map(|f| f.value.clone()).unwrap_or_default(); + assert_eq!(seen, vec!["true".to_string()]); +} + /// Field scripts of one edit read the field highlight an earlier one set: `on`'s calculation turns /// it on, and `seen`'s, which runs after it, records what it reads. #[test] diff --git a/crates/js/src/tests.rs b/crates/js/src/tests.rs index d29899125c..9ceb8dbcf2 100644 --- a/crates/js/src/tests.rs +++ b/crates/js/src/tests.rs @@ -1579,6 +1579,16 @@ mod runtime_highlight { assert_eq!(o.effects, vec![XfaEffect::Highlight(true)]); let o = run_xfa(script, &ev, &XfaDoc { highlight: true, ..Default::default() }, &root, Limits::default()); assert_eq!((o.result.as_deref(), o.effects.len()), (Some("true,true"), 0), "already on: nothing to ask for"); + // Past the effect cap the request is dropped, and the script still reads the setting the + // viewer has. + let full = format!( + "for (var i = 0; i < {}; i++) {{ xfa.host.beep(); }} app.runtimeHighlight = true; String(app.runtimeHighlight)", + crate::xfa::MAX_EFFECTS + ); + let o = run_xfa(&full, &ev, &XfaDoc::default(), &root, Limits::default()); + assert_eq!(o.error, None, "{o:?}"); + assert_eq!(o.result.as_deref(), Some("false")); + assert!(!o.effects.contains(&XfaEffect::Highlight(true))); } } diff --git a/crates/js/src/xfa.rs b/crates/js/src/xfa.rs index 3ad410c0d6..766fbf566c 100644 --- a/crates/js/src/xfa.rs +++ b/crates/js/src/xfa.rs @@ -1159,9 +1159,12 @@ fn runtime_highlight(_: &JsValue, _: &[JsValue], ctx: &mut Context) -> JsResult< fn set_runtime_highlight(_: &JsValue, args: &[JsValue], ctx: &mut Context) -> JsResult { let on = arg(args, 0).to_boolean(); let mut h = host(ctx)?.borrow_mut(); - if h.doc.highlight != on { + let e = XfaEffect::Highlight(on); + // Changed only when the request is kept: past the effect cap the script goes on reading the + // setting the viewer has. + if h.doc.highlight != on && h.reserve(&e, payload(&e)) { h.doc.highlight = on; - h.push(XfaEffect::Highlight(on)); + h.record(e); } Ok(JsValue::undefined()) }