Repository navigation
M12: XFA scripts' event.target finds the document's fields, their signature state, and resets them - #4762
Draft
mtlcl410 wants to merge 23 commits into
Draft
M12: XFA scripts' event.target finds the document's fields, their signature state, and resets them#4762mtlcl410 wants to merge 23 commits into
mtlcl410 wants to merge 23 commits into
Conversation
# Conflicts: # crates/engine/src/tests.rs # crates/js/src/tests.rs # crates/ui-egui/tests/forms.rs
… back to the tab it opened; nLaunch 1 and 2 only; button click scripts only
# Conflicts: # parity/acrobat-features.toml
# Conflicts: # crates/engine/src/tests.rs
# Conflicts: # crates/js/src/lib.rs # crates/js/src/tests.rs # parity/acrobat-features.toml
# Conflicts: # crates/automation/src/a11y.rs # crates/automation/src/tools.rs # crates/engine/src/js.rs # crates/engine/src/tests.rs # crates/engine/src/xfa.rs # crates/js/src/lib.rs # crates/js/src/tests.rs # crates/js/src/xfa.rs # crates/ui-egui/src/js_ui.rs # crates/ui-egui/src/lib.rs # crates/ui-egui/tests/it/forms.rs # parity/acrobat-features.toml
# Conflicts: # crates/ui-egui/tests/it/forms.rs
# Conflicts: # crates/ui-egui/src/lib.rs
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
|
# Conflicts: # crates/automation/src/a11y.rs # crates/js/src/lib.rs # crates/js/src/xfa.rs # crates/ui-egui/src/dialogs.rs # crates/ui-egui/src/lib.rs
# Conflicts: # crates/automation/src/tools.rs # crates/js/src/globals.rs # crates/js/src/xfa.rs # crates/js/src/xfa/globals.rs # crates/js/src/xfa/model.rs # crates/ui-egui/src/workflows/js_ui.rs # parity/acrobat-features.toml
…nature state, and resets them In an XFA form's JavaScript, the bare event's target is the document (storytold#3575). It now has getField and resetForm, and the field objects getField returns have signatureInfo().status and signatureValidate(). - getField(name) finds a laid-out field by the SOM expression of its XFA field with every index written, how form tools name an XFA form's PDF fields (XFA 3.3 ch. 2, "Field Names", pp. 72-74). It returns the form object model's own field object; a value set through it sets the XFA field. A subform's SOM expression gives an object naming it; other names give null; no name is an error. A signature field isn't found while the form initializes. - signatureInfo().status and signatureValidate() give a signature field's state as PdfCraft read the document's signatures (ISO 32000-2 12.7.5.5): 0 unsigned, 1 unknown, 2 invalid, 4 valid. Nothing is checked again and nothing goes to the network. AcroForm scripts get them too (-1 for a field that isn't a signature field); in XFA scripts only signature fields have them. - resetForm(names) resets the named fields (one name or an array; full SOM expressions or dotted names), or all of them, through the same reset as xfa.host.resetData (ISO 32000-2 12.7.6.3), and the script reads the defaults back at once. Related: storytold#4743 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CBVyQEwtmEw3mS1MoAiwrK
mtlcl410
force-pushed
the
fix/xfa-event-target-document
branch
from
October 31, 2026 06:26
5b01d59 to
40e0e54
Compare
Contributor
Author
|
Rebased onto current main. Main has since split |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
Fixes #4761
Related #4743
In a dynamic XFA form's JavaScript, the bare
event's target is the document (#3575). Forms ask that document for a field, and for a signature field's state. Onmainthere is no such method, so these scripts stop withTypeError: not a callable functionand the rest of the script doesn't run. In a scan of 82 public XFA forms:event.target.getField(<full SOM name>)getField(...).signatureInfo().statusgetField(...).signatureValidate()event.target.resetForm(<name or array>)After this PR:
event.target.getField(name)finds a laid-out field by the SOM expression of its XFA field with every index written (form1[0].sbfrmFooter[0].SignatureField1[0]). That is how form tools name the PDF fields of an XFA form (XFA 3.3, ch. 2, "Field Names", pp. 72-74), and it is the only spelling that finds the field:form.form1.foot.sig,form1.foot.sig,xfa[0].form[0]..., a missing last index) and a field's short name givenull, as do an unknown name and"";nameis that expression (itstypeis undefined);getField()with no argument throws;nullin an initialize script); later events (a click, preSave) find it.getFieldreturns is the form object model's own field object (field_objectincrates/js/src/lib.rs), the same one AcroForm scripts get, over the document's fields named by their SOM expressions. A value set through it sets the XFA field the wayrawValuedoes, so the script reads it back there. Other properties set through it (readonly,display, colours) are noted in the console and left alone.signatureInfo().statusandsignatureValidate()give a signature field's state as a number: 0 unsigned, 1 unknown, 2 invalid, 4 valid. Signed or not is ISO 32000-2 §12.7.5.5 (a signature dictionary as the field's value); validity is what PdfCraft found when it read the document's signatures (pdfcraft_sign::pdf::list). Nothing is checked again and nothing goes to the network. In XFA scripts only signature fields have these methods. AcroForm scripts get them on every field, and a field that isn't a signature field gives -1.event.target.resetForm(names)resets the named fields (one name or an array; missing and empty entries are skipped; full SOM expressions or dotted names such asform1.body.t), or every field when no names are given. A list that names nothing resets nothing. It uses the same reset asxfa.host.resetData(ISO 32000-2 §12.7.6.3), and the script reads the defaults back right after the call.The API (
Doc.getField,Doc.resetForm,Field.signatureInfo,Field.signatureValidate, theSignatureInfo.statuscodes) is defined by the public JavaScript for Acrobat API Reference (https://opensource.adobe.com/dc-acrobat-sdk-docs/library/jsapiref/), sections "Doc methods", "Field methods" and "SignatureInfo properties". XFA 3.3 itself defines onlyxfa.event(ch. 10, "Properties", pp. 398-399), whose target is the form node; that is unchanged.Where the reference doesn't settle a case (which spellings
getFieldtakes, a subform's name, the initialize-timenullfor signature fields, methods on non-signature fields, whenresetFormtakes effect, which names it takes), the behaviour follows most readers that lay out dynamic XFA forms, observed on 13 synthetic forms.The form in #4743. Its form initialize calls
event.target.getField("form1[0].sbfrmFooter[0].SignatureField1[0]"), tests the result againstnull, and reads.signatureInfo().status. With this PR the initialize script runs to the end. Its other scripts also need the script objects in its<variables>(planned in the #3432 series). Built together with those locally, the next call that fails isevent.target.removeDataObject(...), when its validation button attaches its error list to the document. That is a separate feature, not part of this PR.Code
crates/js/src/xfa.rs:getFieldandresetFormon M6: form buttons open the files attached to their document (exportDataObject) #3575's document object;field_written(a value set through the field object goes to the XFA field);som_names, the name matchingresetDataalready used, moved here from the engine so both resets match names the same way.crates/js/src/lib.rs:SignatureState,FieldState::signature, andsignatureInfo/signatureValidateon the field object. In XFA scripts the field object writes through to the form.crates/js/src/xfa_session.rs:Script::fields, the document's fields as they are for each script.crates/engine/src/xfa.rs,js.rs,lib.rs: the runner builds the fields once per layout, named by SOM (fields_by_som), with each signature field's state, and keeps their values up to date as scripts change them. On opening, the signatures are read withpdfcraft_sign::pdf::list; in an edit, they come from the document's list.On a real form
The form in #4743 is the Romanian Ministry of Finance's electronic payment order (OPME, form 1129, public: https://mfinante.gov.ro/documents/2552173/2552377/31.OrdinPlataElectronic_2026_06_18_A2.0.46.pdf/86e41cfc-210e-023a-ab73-dd758954b420?t=1781789905297&download=true). Its root subform's initialize script reads the state of its two signature boxes:
It then makes the second signature box read-only or open depending on that state. Opened with
pdfcraft-cli, then NrOp and IbanPlatitor filled in and the validation button pressed:mainTypeError: not a callable function(thegetFieldcall), so the signature-state checks after it never rungetFieldgivesnullfor the unsigned box during initialize, so the script takes its own== nullbranch, as the form expectsNrOp (exit): ReferenceError: csDataTool is not defined, from the form's script objects in<variables>, whichmaindoesn't support yet (part of the #3432 series, not this PR)So this PR removes the first failure in that form; #4743 stays open until the script objects land too.
Before and after
Synthetic form 01,
getFieldby the full SOM name:typeof event.target.getFieldisfunction, the text field is found and its value read; the signature box givesnullbecause this runs in an initialize script. Main on the left, this PR on the right.Synthetic form 04, names the form doesn't have:
null(so== nulland=== nullare true),nullfor"", and an error for no argument. Main on the left, this PR on the right.Synthetic form 05, a subform's SOM name: an object whose
nameisform1[0].foot[0]and whosetypeis undefined. Main on the left, this PR on the right.Synthetic form 12 after pressing Run (sets
tandutochanged, thenevent.target.resetForm()), saved and rendered: both fields are back todefault. Main on the left, this PR on the right.Synthetic form 13,
event.targetin an initialize script: it is no longer the node (=== thisis false), andgetFieldandresetFormare functions next to #3575'sexportDataObject. The other members are still undefined. Main on the left, this PR on the right.The signature methods need a signed box, so they aren't pictured. On the synthetic forms, a click script gets 0 from both methods for the unsigned box; the engine test below gets 2 for a stand-in signature that fails its check. The other forms (02 other spellings, 03 short names, 06/07 signature methods, 08 signature methods on a text field, 09 a value set through
getField, 10/11resetFormwith one name and with an array that has a hole) are in the table below.getField("form.form1.foot.sig"),"form1.foot.sig","xfa[0].form[0]...sig[0]","form1[0].foot[0].sig"nulleachgetField("sig"),getField("t")null,nullsignatureInfo().statusof the unsigned box0signatureValidate()of the unsigned box0signatureInfo()/signatureValidate()on a text fieldgetField(t).value = "after", thent.rawValueafterresetForm("form1[0].body[0].t[0]"), thent.rawValuetstayschangeddefault,tisdefaultf = new Array(1); f[1] = "form1.body.t"; resetForm(f)treset,uunchangedHow you verified it
On
mainat 13bfbd0 with #3575 merged in, branch head 5b01d59:cargo fmt --check-D warningson the crates I changed (pdfcraft-js,pdfcraft-xfa,pdfcraft-engine,--all-targets)pdfcraft-js92 passed (5 new),pdfcraft-xfa128 passed,pdfcraft-engine375 + 16 passed (3 new), 0 failedcargo check --workspace --all-targetscargo xtask layers,cargo xtask assetsandcargo xtask parityTests
crates/js/src/tests.rsevent_target_get_field_finds_a_field_by_its_full_som_expression: the full SOM name finds the field (name, value, type). Four other spellings, the short name, an unknown name and""give null. A subform's name gives an object naming it. No argument is an error. Without the document's fields nothing is found and nothing fails.signature_info_and_validate_report_the_signature_state: 0/1/2/4 for the four states. A text field has neither method. An initialize script doesn't find a signature field but does find a text field.a_value_set_through_get_field_sets_the_xfa_field: the value becomes aSetValueeffect on the SOM,rawValuereads it back, andreadonlyis noted, not applied.event_target_reset_form_resets_the_named_fields_or_all: one name, an array with a hole and a dotted name, no argument (all),[]and[''](nothing); the script reads the defaults back at once.signature_fields_report_their_signature_state: AcroForm scripts get 4 for a valid signature and -1 for a text field.crates/engine/src/tests.rs(synthetic dynamic forms opened throughSession)xfa_scripts_reach_the_document_fields_through_the_event_target: on opening, the signature box isnulland an unknown name isnull, and a value set throughgetFieldreaches the field. A click then reads the box asform1[0].foot[0].sig[0]|0|0, and another click'sresetFormputstback todefaultand reads it back at once.xfa_scripts_see_a_signed_box_through_the_event_target:pdfcraft_xfa::fixtures::signed_shell_with(the existing signed fixture, with a button added) reads2for the signed box (its stand-in signature has no byte range) and0for the other.form_scripts_see_a_signature_fields_state: the console's AcroForm context reads the same2from the document's checked signatures.Each test was seen failing with its part of the change reverted (one part at a time, only in the non-test code, files restored after each run):
getField/resetFormnot on the document objectsignatureInfo/signatureValidatenot on the field objectresetFormnot seen at once by the scriptnullgetField()without a name not an errorNever-crash
unwrap/expect/panic!/unreachable!/todo!outside testsclip).resetFormreads arrays through the existing boundedstringsand keeps at most as many names as a snapshot has nodes. Unapplied-property notes are capped at 20.getFieldis a lookup over the fields the engine handed in.unsafeProvenance
Security
Parity and reach
parity/acrobat-features.toml,form.js-host: the notes describegetField,resetFormand the signature methods with what is still missing (status 3 isn't told apart from 1;signatureInfohas onlystatus; the other document members on the XFA event target). The new tests are listed as evidence.doc_open,form_fillandjs_run; no new tool is needed.crates/xfa/README.md, the scripting paragraph.Anything a reviewer should look at first
getField(t).value = "after"didn't stick: the field kept its value, and evengetField(t).valueread the old one back. Here the write sticks, as it does from any other event. Dropping a script's write without saying so would be a silent loss. No form in the scan sets values throughgetField, and only the initialize case was observed.nullfor signature fields follows the observation. The form in error when filling out PDF forms #4743 handles it (if (TheSignature1 == null) ... TheStatus1 = 0). A form that doesn't check will throw there, as it does in that reader.Status::Unknowncovers both an untrusted signer and an unchecked signature, so both give 1.pdfcraft_sign::pdf::list) before the scripts run; for an unsigned form that is cheap. The document's own list is built later from a separate cache.numPagesto the same document object initializer, so the two PRs touch adjacent lines.🤖 Generated with Claude Code
https://claude.ai/code/session_01CBVyQEwtmEw3mS1MoAiwrK