Skip to content

Commit

Permalink
[JS] Fix several issues found in pdf in #13269
Browse files Browse the repository at this point in the history
  - app.alert and few other function can use an object as parameter ({cMsg: ...});
  - support app.alert with a question and a yes/no answer;
  - update field siblings when one is changed in an action;
  - stop calculation if calculate is set to false in the middle of calculations;
  - get a boolean for checkboxes when they've been set through annotationStorage instead of a string.
  • Loading branch information
calixteman committed May 4, 2021
1 parent 3f187c2 commit e18c41c
Show file tree
Hide file tree
Showing 12 changed files with 201 additions and 41 deletions.
14 changes: 12 additions & 2 deletions src/display/annotation_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -922,12 +922,17 @@ class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement {
const storage = this.annotationStorage;
const data = this.data;
const id = data.id;
const value = storage.getValue(id, {
let value = storage.getValue(id, {
value:
data.fieldValue &&
((data.exportValue && data.exportValue === data.fieldValue) ||
(!data.exportValue && data.fieldValue !== "Off")),
}).value;
if (typeof value === "string") {
// The value has been changed through js and set in annotationStorage.
value = value !== "Off";
storage.setValue(id, { value });
}

this.container.className = "buttonWidgetAnnotation checkBox";

Expand Down Expand Up @@ -1012,9 +1017,14 @@ class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement {
const storage = this.annotationStorage;
const data = this.data;
const id = data.id;
const value = storage.getValue(id, {
let value = storage.getValue(id, {
value: data.fieldValue === data.buttonValue,
}).value;
if (typeof value === "string") {
// The value has been changed through js and set in annotationStorage.
value = value !== data.buttonValue;
storage.setValue(id, { value });
}

const element = document.createElement("input");
element.disabled = data.readOnly;
Expand Down
6 changes: 6 additions & 0 deletions src/pdf.sandbox.external.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ class SandboxSupportBase {
}
this.win.alert(cMsg);
},
confirm: cMsg => {
if (typeof cMsg !== "string") {
return false;
}
return this.win.confirm(cMsg);
},
prompt: (cQuestion, cDefault) => {
if (typeof cQuestion !== "string" || typeof cDefault !== "string") {
return null;
Expand Down
40 changes: 38 additions & 2 deletions src/scripting_api/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ class App extends PDFObject {
constructor(data) {
super(data);

this.calculate = true;

this._constants = null;
this._focusRect = true;
this._fs = null;
Expand Down Expand Up @@ -68,6 +66,7 @@ class App extends PDFObject {
this._timeoutCallbackId = 0;
this._globalEval = data.globalEval;
this._externalCall = data.externalCall;
this._document = data._document;
}

// This function is called thanks to the proxy
Expand Down Expand Up @@ -191,6 +190,14 @@ class App extends PDFObject {
throw new Error("app.activeDocs is read-only");
}

get calculate() {
return this._document.obj.calculate;
}

set calculate(calculate) {
this._document.obj.calculate = calculate;
}

get constants() {
if (!this._constants) {
this._constants = Object.freeze({
Expand Down Expand Up @@ -427,7 +434,21 @@ class App extends PDFObject {
oDoc = null,
oCheckbox = null
) {
if (typeof cMsg === "object") {
nType = cMsg.nType;
cMsg = cMsg.cMsg;
}
cMsg = (cMsg || "").toString();
nType =
typeof nType !== "number" || isNaN(nType) || nType < 0 || nType > 3
? 0
: nType;
if (nType >= 2) {
return this._externalCall("confirm", [cMsg]) ? 4 : 3;
}

this._externalCall("alert", [cMsg]);
return 1;
}

beep() {
Expand Down Expand Up @@ -543,10 +564,20 @@ class App extends PDFObject {
}

response(cQuestion, cTitle = "", cDefault = "", bPassword = "", cLabel = "") {
if (typeof cQuestion === "object") {
cDefault = cQuestion.cDefault;
cQuestion = cQuestion.cQuestion;
}
cQuestion = (cQuestion || "").toString();
cDefault = (cDefault || "").toString();
return this._externalCall("prompt", [cQuestion, cDefault || ""]);
}

setInterval(cExpr, nMilliseconds) {
if (typeof cExpr === "object") {
cExpr.nMilliseconds = cExpr;
cExpr.cExpr = cExpr;
}
if (typeof cExpr !== "string") {
throw new TypeError("First argument of app.setInterval must be a string");
}
Expand All @@ -561,6 +592,11 @@ class App extends PDFObject {
}

setTimeOut(cExpr, nMilliseconds) {
if (typeof cExpr === "object") {
cExpr.nMilliseconds = cExpr;
cExpr.cExpr = cExpr;
}

if (typeof cExpr !== "string") {
throw new TypeError("First argument of app.setTimeOut must be a string");
}
Expand Down
21 changes: 21 additions & 0 deletions src/scripting_api/doc.js
Original file line number Diff line number Diff line change
Expand Up @@ -820,6 +820,9 @@ class Doc extends PDFObject {
}

getField(cName) {
if (typeof cName === "object") {
cName = cName.cName;
}
if (typeof cName !== "string") {
throw new TypeError("Invalid field name: must be a string");
}
Expand Down Expand Up @@ -885,6 +888,9 @@ class Doc extends PDFObject {
}

getNthFieldName(nIndex) {
if (typeof nIndex === "object") {
nIndex = nIndex.nIndex;
}
if (typeof nIndex !== "number") {
throw new TypeError("Invalid field index: must be a number");
}
Expand Down Expand Up @@ -1020,6 +1026,18 @@ class Doc extends PDFObject {
bAnnotations = true,
printParams = null
) {
if (typeof bUI === "object") {
nStart = bUI.nStart;
nEnd = bUI.nEnd;
bSilent = bUI.bSilent;
bShrinkToFit = bUI.bShrinkToFit;
bPrintAsImage = bUI.bPrintAsImage;
bReverse = bUI.bReverse;
bAnnotations = bUI.bAnnotations;
printParams = bUI.printParams;
bUI = bUI.bUI;
}

// TODO: for now just use nStart and nEnd
// so need to see how to deal with the other params
// (if possible)
Expand Down Expand Up @@ -1084,6 +1102,9 @@ class Doc extends PDFObject {
}

resetForm(aFields = null) {
if (aFields && !Array.isArray(aFields) && typeof aFields === "object") {
aFields = aFields.aFields;
}
let mustCalculate = false;
if (aFields) {
for (const fieldName of aFields) {
Expand Down
51 changes: 34 additions & 17 deletions src/scripting_api/event.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,23 +96,33 @@ class EventDispatcher {
}
}

if (name === "Keystroke") {
savedChange = {
value: event.value,
change: event.change,
selStart: event.selStart,
selEnd: event.selEnd,
};
} else if (name === "Blur" || name === "Focus") {
Object.defineProperty(event, "value", {
configurable: false,
writable: false,
enumerable: true,
value: event.value,
});
} else if (name === "Validate") {
this.runValidation(source, event);
return;
switch (name) {
case "Keystroke":
savedChange = {
value: event.value,
change: event.change,
selStart: event.selStart,
selEnd: event.selEnd,
};
break;
case "Blur":
case "Focus":
Object.defineProperty(event, "value", {
configurable: false,
writable: false,
enumerable: true,
value: event.value,
});
break;
case "Validate":
this.runValidation(source, event);
return;
case "Action":
this.runActions(source, source, event, name);
if (this._document.obj.calculate) {
this.runCalculate(source, event);
}
return;
}

this.runActions(source, source, event, name);
Expand Down Expand Up @@ -143,8 +153,10 @@ class EventDispatcher {
if (event.rc) {
if (hasRan) {
source.wrapped.value = event.value;
source.wrapped.valueAsString = event.value;
} else {
source.obj.value = event.value;
source.obj.valueAsString = event.value;
}

if (this._document.obj.calculate) {
Expand Down Expand Up @@ -187,6 +199,11 @@ class EventDispatcher {
continue;
}

if (!this._document.obj.calculate) {
// An action may have changed calculate value.
continue;
}

event.value = null;
const target = this._objects[targetId];
this.runActions(source, target, event, "Calculate");
Expand Down
11 changes: 9 additions & 2 deletions src/scripting_api/field.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ class Field extends PDFObject {
this._valueAsString = data.valueAsString;
this._kidIds = data.kidIds || null;
this._fieldType = getFieldType(this._actions);
this._siblings = data.siblings || null;

this._globalEval = data.globalEval;
this._appObjects = data.appObjects;
Expand Down Expand Up @@ -246,6 +247,9 @@ class Field extends PDFObject {
}

get valueAsString() {
if (this._valueAsString === undefined) {
this._valueAsString = this._value ? this._value.toString() : "";
}
return this._valueAsString;
}

Expand Down Expand Up @@ -286,6 +290,9 @@ class Field extends PDFObject {
}
this._buttonCaption[nFace] = cCaption;
// TODO: send to the annotation layer
// Right now the button is drawn on the canvas using its appearance so
// update the caption means redraw...
// We should probably have an html button for this annotation.
}

buttonSetIcon(oIcon, nFace = 0) {
Expand Down Expand Up @@ -512,7 +519,7 @@ class RadioButtonField extends Field {
}

set value(value) {
if (value === null) {
if (value === null || value === undefined) {
this._value = "";
}
const i = this.exportValues.indexOf(value);
Expand Down Expand Up @@ -574,7 +581,7 @@ class CheckboxField extends RadioButtonField {
}

set value(value) {
if (value === "Off") {
if (!value || value === "Off") {
this._value = "Off";
} else {
super.value = value;
Expand Down
30 changes: 22 additions & 8 deletions src/scripting_api/initialization.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,29 @@ function initSandbox(params) {
obj.doc = _document;
obj.fieldPath = name;
obj.appObjects = appObjects;

let field;
if (obj.type === "radiobutton") {
const otherButtons = annotations.slice(1);
field = new RadioButtonField(otherButtons, obj);
} else if (obj.type === "checkbox") {
const otherButtons = annotations.slice(1);
field = new CheckboxField(otherButtons, obj);
} else {
field = new Field(obj);
switch (obj.type) {
case "radiobutton": {
const otherButtons = annotations.slice(1);
field = new RadioButtonField(otherButtons, obj);
break;
}
case "checkbox": {
const otherButtons = annotations.slice(1);
field = new CheckboxField(otherButtons, obj);
break;
}
case "text":
if (annotations.length <= 1) {
field = new Field(obj);
break;
}
obj.siblings = annotations.map(x => x.id).slice(1);
field = new Field(obj);
break;
default:
field = new Field(obj);
}

const wrapped = new Proxy(field, proxyHandler);
Expand Down
7 changes: 6 additions & 1 deletion src/scripting_api/proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,12 @@ class ProxyHandler {
data[prop] = obj[prop];

// send the updated value to the other side
obj._send(data);
if (!obj._siblings) {
obj._send(data);
} else {
data.siblings = obj._siblings;
obj._send(data);
}
}
} else {
obj._expandos[prop] = value;
Expand Down

0 comments on commit e18c41c

Please sign in to comment.