mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
M6: scripts see xfa_version, and xfa_installed in XFA forms (#4142)
This commit is contained in:
@@ -129,8 +129,16 @@ pub struct JsRunner {
|
||||
}
|
||||
|
||||
impl JsRunner {
|
||||
/// A runner for `cos`: what scripts read about the document (pages, info, whether it is
|
||||
/// an XFA form) and its document-level scripts, read once.
|
||||
pub fn new(cos: &pdfcraft_cos::Document, file_name: &str) -> JsRunner {
|
||||
let doc = pdfcraft_js::DocInfo { file_name: file_name.to_string(), num_pages: pdfcraft_model::pages(cos).len(), page: 0, info: info(cos) };
|
||||
let doc = pdfcraft_js::DocInfo {
|
||||
file_name: file_name.to_string(),
|
||||
num_pages: pdfcraft_model::pages(cos).len(),
|
||||
page: 0,
|
||||
info: info(cos),
|
||||
xfa: pdfcraft_xfa::xfa_shape(cos).is_some(),
|
||||
};
|
||||
JsRunner {
|
||||
doc,
|
||||
doc_scripts: pdfcraft_forms::document_scripts(cos),
|
||||
|
||||
@@ -4076,6 +4076,20 @@ fn form_javascript_validates_calculates_and_formats() {
|
||||
assert!(s.run_javascript(id, "1", None).is_err());
|
||||
}
|
||||
|
||||
/// XFA forms check `xfa_installed` and `xfa_version` in a document script that runs before
|
||||
/// each of their scripts, and alert when either is missing.
|
||||
#[test]
|
||||
fn scripts_in_an_xfa_form_see_the_xfa_plugin_installed() {
|
||||
let check = "typeof xfa_installed + ' ' + typeof xfa_version + ' ' + xfa_version";
|
||||
let mut s = Session::new().with_clock(|| 1_700_000_000);
|
||||
let xfa = s.open("form.pdf", None, Arc::new(pdfcraft_xfa::fixtures::shell(&pdfcraft_xfa::fixtures::template(3))), None).unwrap();
|
||||
let o = s.run_javascript(xfa, check, None).unwrap();
|
||||
assert_eq!((o.error, o.result.as_deref()), (None, Some("boolean number 3.5")));
|
||||
let plain = s.open("order.pdf", None, Arc::new(scripted_form()), None).unwrap();
|
||||
let o = s.run_javascript(plain, check, None).unwrap();
|
||||
assert_eq!((o.error, o.result.as_deref()), (None, Some("undefined number 3.5")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edit_history_does_not_replay_field_script_side_effects() {
|
||||
let mut s = Session::new().with_clock(|| 1_700_000_000);
|
||||
|
||||
+15
-2
@@ -144,6 +144,8 @@ pub struct DocInfo {
|
||||
pub page: usize,
|
||||
/// Document information (`this.info.title` etc.): lower-case key, value.
|
||||
pub info: Vec<(String, String)>,
|
||||
/// The document has an XFA form, so scripts see `xfa_installed`.
|
||||
pub xfa: bool,
|
||||
}
|
||||
|
||||
/// Something a script asked the viewer to do.
|
||||
@@ -971,6 +973,11 @@ fn color_object(ctx: &mut Context) -> JsObject {
|
||||
o.build()
|
||||
}
|
||||
|
||||
/// The XFA version scripts see as `xfa_version`.
|
||||
const XFA_VERSION: f64 = 3.5;
|
||||
|
||||
/// Define the globals every script sees: the document's functions and properties (`getField`,
|
||||
/// `numPages`, `info`, the XFA version), and `app`, `util`, `console`, `display` and `color`.
|
||||
fn install(ctx: &mut Context) -> JsResult<()> {
|
||||
let globals: [(&str, usize, NativeFunction); 9] = [
|
||||
("getField", 1, NativeFunction::from_fn_ptr(get_field)),
|
||||
@@ -991,9 +998,9 @@ fn install(ctx: &mut Context) -> JsResult<()> {
|
||||
let nf = function(ctx, NativeFunction::from_fn_ptr(num_fields));
|
||||
let pg = function(ctx, NativeFunction::from_fn_ptr(page_num_get));
|
||||
let ps = function(ctx, NativeFunction::from_fn_ptr(page_num_set));
|
||||
let (file, pages, info) = {
|
||||
let (file, pages, info, xfa) = {
|
||||
let h = host(ctx)?.borrow();
|
||||
(h.doc.file_name.clone(), h.doc.num_pages, h.doc.info.clone())
|
||||
(h.doc.file_name.clone(), h.doc.num_pages, h.doc.info.clone(), h.doc.xfa)
|
||||
};
|
||||
let mut io = ObjectInitializer::new(ctx);
|
||||
for (k, v) in &info {
|
||||
@@ -1006,6 +1013,12 @@ fn install(ctx: &mut Context) -> JsResult<()> {
|
||||
ctx.register_global_property(js_string!("numPages"), JsValue::from(pages as f64), rw)?;
|
||||
ctx.register_global_property(js_string!("documentFileName"), s(&file), rw)?;
|
||||
ctx.register_global_property(js_string!("info"), info, rw)?;
|
||||
// XFA forms check both before they run: every document sees the XFA version, and only an
|
||||
// XFA form sees `xfa_installed`.
|
||||
ctx.register_global_property(js_string!("xfa_version"), JsValue::from(XFA_VERSION), rw)?;
|
||||
if xfa {
|
||||
ctx.register_global_property(js_string!("xfa_installed"), JsValue::from(true), rw)?;
|
||||
}
|
||||
|
||||
let app = ObjectInitializer::new(ctx)
|
||||
.function(NativeFunction::from_fn_ptr(alert), js_string!("alert"), 1)
|
||||
|
||||
+27
-1
@@ -16,7 +16,7 @@ fn fields() -> Vec<FieldState> {
|
||||
}
|
||||
|
||||
fn doc() -> DocInfo {
|
||||
DocInfo { file_name: "order.pdf".into(), num_pages: 3, page: 0, info: vec![("title".into(), "Order".into())] }
|
||||
DocInfo { file_name: "order.pdf".into(), num_pages: 3, page: 0, info: vec![("title".into(), "Order".into())], xfa: false }
|
||||
}
|
||||
|
||||
fn go(script: &str, event: &Event) -> Outcome {
|
||||
@@ -1454,3 +1454,29 @@ fn shared_field_snapshots_isolate_writes_errors_and_script_globals() {
|
||||
assert_eq!(read.value, "undefined:4:false");
|
||||
assert!(read.changed.is_empty());
|
||||
}
|
||||
|
||||
/// The check XFA forms run on opening: without both globals, or with a version below 2.8, it
|
||||
/// alerts that the viewer can't show the form.
|
||||
const XFA_CHECK: &str = "if (typeof(xfa_installed) == 'undefined' || typeof(xfa_version) == 'undefined' || xfa_version < 2.8) app.alert('old viewer'); \
|
||||
event.value = typeof xfa_installed + ' ' + typeof xfa_version + ' ' + xfa_version;";
|
||||
|
||||
/// An XFA form sees `xfa_installed` (true) and `xfa_version` (3.5), so the check passes.
|
||||
#[test]
|
||||
fn an_xfa_form_sees_the_xfa_plugin_installed() {
|
||||
let xfa = DocInfo { xfa: true, ..doc() };
|
||||
let o = run(XFA_CHECK, &Event::doc("Open"), &xfa, &fields(), &[], Limits::default());
|
||||
assert_eq!(o.error, None);
|
||||
assert_eq!(o.value, "boolean number 3.5");
|
||||
assert!(o.alerts.is_empty(), "{:?}", o.alerts);
|
||||
let o = run("event.value = String(xfa_installed);", &Event::doc("Open"), &xfa, &fields(), &[], Limits::default());
|
||||
assert_eq!(o.value, "true");
|
||||
}
|
||||
|
||||
/// A form without XFA sees `xfa_version` but no `xfa_installed`, so the check alerts.
|
||||
#[test]
|
||||
fn a_plain_form_sees_the_xfa_version_but_not_installed() {
|
||||
let o = go(XFA_CHECK, &Event::doc("Open"));
|
||||
assert_eq!(o.error, None);
|
||||
assert_eq!(o.value, "undefined number 3.5");
|
||||
assert_eq!(o.alerts, ["old viewer"]);
|
||||
}
|
||||
|
||||
@@ -5165,8 +5165,8 @@ milestone = "M6"
|
||||
status = "partial"
|
||||
commands = ["tools.js_console"]
|
||||
tools = ["js_run"]
|
||||
evidence = ["crates/js/src/tests.rs::calculate_scripts_read_fields_and_set_the_value", "crates/engine/src/tests.rs::form_javascript_validates_calculates_and_formats", "crates/automation/tests/it/automation.rs::javascript_through_tools"]
|
||||
notes = "boa (pure Rust) with the forms object model: event, getField/Field (value, readonly, display, colours, items, check boxes), app, util.printf/printd/printx, console, display, color; no annotations, layers or dialogs (app.response returns null) yet."
|
||||
evidence = ["crates/js/src/tests.rs::calculate_scripts_read_fields_and_set_the_value", "crates/engine/src/tests.rs::form_javascript_validates_calculates_and_formats", "crates/automation/tests/it/automation.rs::javascript_through_tools", "crates/js/src/tests.rs::an_xfa_form_sees_the_xfa_plugin_installed", "crates/js/src/tests.rs::a_plain_form_sees_the_xfa_version_but_not_installed", "crates/engine/src/tests.rs::scripts_in_an_xfa_form_see_the_xfa_plugin_installed"]
|
||||
notes = "boa (pure Rust) with the forms object model: event, getField/Field (value, readonly, display, colours, items, check boxes), app, util.printf/printd/printx, console, display, color; xfa_version (3.5) in every document and xfa_installed in XFA forms, so their version check passes; no annotations, layers or dialogs (app.response returns null) yet, and no app.findComponent."
|
||||
|
||||
[[feature]]
|
||||
id = "form.js-console"
|
||||
|
||||
Reference in New Issue
Block a user