Skip to content

Commit

Permalink
AcroForm: Add support for ResetForm action
Browse files Browse the repository at this point in the history
  - it aims to fix mozilla#12721.
  - Thanks to PR mozilla#14023, we've now the fieldObjects in the annotation layer so we can easily map fields names on their id if needed.
  - Reset values in the storage, in the JS sandbox and in the visible html elements.
  • Loading branch information
calixteman committed Sep 27, 2021
1 parent 3b3c487 commit 3bcef10
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 15 deletions.
20 changes: 15 additions & 5 deletions src/core/catalog.js
Original file line number Diff line number Diff line change
Expand Up @@ -1329,6 +1329,20 @@ class Catalog {
const actionName = actionType.name;

switch (actionName) {
case "ResetForm":
const flags = action.get("Flags");
const include = ((isNum(flags) ? flags : 0) & 1) === 0;
const fields = [];
const refs = [];
for (const obj of action.get("Fields") || []) {
if (isRef(obj)) {
refs.push(obj.toString());
} else if (isString(obj)) {
fields.push(stringToPDFString(obj));
}
}
resultObj.resetForm = { fields, refs, include };
break;
case "URI":
url = action.get("URI");
if (isName(url)) {
Expand Down Expand Up @@ -1408,11 +1422,7 @@ class Catalog {
}
/* falls through */
default:
if (
actionName === "JavaScript" ||
actionName === "ResetForm" ||
actionName === "SubmitForm"
) {
if (actionName === "JavaScript" || actionName === "SubmitForm") {
// Don't bother the user with a warning for actions that require
// scripting support, since those will be handled separately.
break;
Expand Down
149 changes: 139 additions & 10 deletions src/display/annotation_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,7 @@ class LinkAnnotationElement extends AnnotationElement {
parameters.data.dest ||
parameters.data.action ||
parameters.data.isTooltipOnly ||
parameters.data.resetForm ||
(parameters.data.actions &&
(parameters.data.actions.Action ||
parameters.data.actions["Mouse Up"] ||
Expand All @@ -453,17 +454,25 @@ class LinkAnnotationElement extends AnnotationElement {
this._bindNamedAction(link, data.action);
} else if (data.dest) {
this._bindLink(link, data.dest);
} else if (
data.actions &&
(data.actions.Action ||
data.actions["Mouse Up"] ||
data.actions["Mouse Down"]) &&
this.enableScripting &&
this.hasJSActions
) {
this._bindJSAction(link, data);
} else {
this._bindLink(link, "");
let hasClickAction = false;
if (
data.actions &&
(data.actions.Action ||
data.actions["Mouse Up"] ||
data.actions["Mouse Down"]) &&
this.enableScripting &&
this.hasJSActions
) {
hasClickAction = true;
this._bindJSAction(link, data);
}

if (data.resetForm) {
this._bindResetFormAction(link, data.resetForm);
} else if (!hasClickAction) {
this._bindLink(link, "");
}
}

if (this.quadrilaterals) {
Expand Down Expand Up @@ -556,6 +565,100 @@ class LinkAnnotationElement extends AnnotationElement {
}
link.className = "internalLink";
}

_bindResetFormAction(link, resetForm) {
link.className = "internalLink";
const otherClickAction = link.onclick;

link.onclick = () => {
if (otherClickAction) {
otherClickAction();
}

const {
fields: resetFormFields,
refs: resetFormRefs,
include,
} = resetForm;
if (resetFormFields.length !== 0 && !this._fieldsObjects) {
warn(
`_bindResetFormAction - "resetForm" action not supported, ` +
"ensure that the `fieldObjects` parameter is provided."
);
return false;
}

const allFields = [];
if (resetFormFields.length !== 0 || resetFormRefs.length !== 0) {
const fieldIds = new Set(resetFormRefs);
for (const fieldName of resetFormFields) {
const fields = this._fieldObjects[fieldName] || [];
for (const { id } of fields) {
fieldIds.add(id);
}
}
for (const fields of Object.values(this._fieldObjects)) {
for (const field of fields) {
const hasId = fieldIds.has(field.id);
if ((include && hasId) || (!include && !hasId)) {
allFields.push(field);
}
}
}
} else {
for (const fields of Object.values(this._fieldObjects)) {
allFields.push(...fields);
}
}

const storage = this.annotationStorage;
const allIds = [];
for (const field of allFields) {
const { id } = field;
allIds.push(id);
switch (field.type) {
case "text": {
const value = field.defaultValue || "";
storage.setValue(id, { value, valueAsString: value });
break;
}
case "checkbox":
case "radiobutton": {
const value = field.defaultValue === field.exportValues;
storage.setValue(id, { value });
break;
}
case "combobox":
case "listbox": {
const value = field.defaultValue || "";
storage.setValue(id, { value });
break;
}
default:
continue;
}
const domElement = document.getElementById(id);
if (!domElement || !GetElementsByNameSet.has(domElement)) {
continue;
}
domElement.dispatchEvent(new Event("resetform"));
}

if (this.enableScripting) {
// Update the values in the sandbox.
this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
source: this,
detail: {
id: "app",
ids: allIds,
name: "ResetForm",
},
});
}

return false;
};
}
}

class TextAnnotationElement extends AnnotationElement {
Expand Down Expand Up @@ -795,6 +898,12 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
);
});

element.addEventListener("resetform", event => {
const defaultValue = this.data.defaultFieldValue || "";
element.value = elementData.userValue = defaultValue;
delete elementData.formattedValue;
});

let blurListener = event => {
if (elementData.formattedValue) {
event.target.value = elementData.formattedValue;
Expand Down Expand Up @@ -1047,6 +1156,11 @@ class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement {
storage.setValue(id, { value: checked });
});

element.addEventListener("resetform", event => {
const defaultValue = data.defaultFieldValue || "Off";
event.target.checked = defaultValue === data.exportValue;
});

if (this.enableScripting && this.hasJSActions) {
element.addEventListener("updatefromsandbox", jsEvent => {
const actions = {
Expand Down Expand Up @@ -1117,6 +1231,14 @@ class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement {
storage.setValue(id, { value: checked });
});

element.addEventListener("resetform", event => {
const defaultValue = data.defaultFieldValue;
event.target.checked =
defaultValue !== null &&
defaultValue !== undefined &&
defaultValue === data.buttonValue;
});

if (this.enableScripting && this.hasJSActions) {
const pdfButtonValue = data.buttonValue;
element.addEventListener("updatefromsandbox", jsEvent => {
Expand Down Expand Up @@ -1217,6 +1339,13 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
}
}

selectElement.addEventListener("resetform", event => {
const defaultValue = this.data.defaultFieldValue;
Array.prototype.forEach.call(selectElement.options, option => {
option.selected = option.value === defaultValue;
});
});

// Insert the options into the choice field.
for (const option of this.data.options) {
const optionElement = document.createElement("option");
Expand Down
7 changes: 7 additions & 0 deletions src/scripting_api/event.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ class EventDispatcher {
baseEvent.actions,
baseEvent.pageNumber
);
} else if (id === "app" && baseEvent.name === "ResetForm") {
for (const fieldId of baseEvent.ids) {
const obj = this._objects[fieldId];
if (obj) {
obj.obj._reset();
}
}
}
return;
}
Expand Down
4 changes: 4 additions & 0 deletions src/scripting_api/field.js
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,10 @@ class Field extends PDFObject {
return false;
}

_reset() {
this.value = this.valueAsString = this.defaultValue;
}

_runActions(event) {
const eventName = event.name;
if (!this._actions.has(eventName)) {
Expand Down

0 comments on commit 3bcef10

Please sign in to comment.