[JS] Fix several issues found in pdf in #13269
- 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.
This commit is contained in:
parent
3f187c2c6d
commit
3f29892d63
@ -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";
|
||||
|
||||
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -28,8 +28,6 @@ class App extends PDFObject {
|
||||
constructor(data) {
|
||||
super(data);
|
||||
|
||||
this.calculate = true;
|
||||
|
||||
this._constants = null;
|
||||
this._focusRect = true;
|
||||
this._fs = null;
|
||||
@ -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
|
||||
@ -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({
|
||||
@ -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() {
|
||||
@ -543,10 +564,21 @@ 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) {
|
||||
setInterval(cExpr, nMilliseconds = 0) {
|
||||
if (typeof cExpr === "object") {
|
||||
nMilliseconds = cExpr.nMilliseconds || 0;
|
||||
cExpr = cExpr.cExpr;
|
||||
}
|
||||
|
||||
if (typeof cExpr !== "string") {
|
||||
throw new TypeError("First argument of app.setInterval must be a string");
|
||||
}
|
||||
@ -560,7 +592,12 @@ class App extends PDFObject {
|
||||
return this._registerTimeout(callbackId, true);
|
||||
}
|
||||
|
||||
setTimeOut(cExpr, nMilliseconds) {
|
||||
setTimeOut(cExpr, nMilliseconds = 0) {
|
||||
if (typeof cExpr === "object") {
|
||||
nMilliseconds = cExpr.nMilliseconds || 0;
|
||||
cExpr = cExpr.cExpr;
|
||||
}
|
||||
|
||||
if (typeof cExpr !== "string") {
|
||||
throw new TypeError("First argument of app.setTimeOut must be a string");
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
@ -852,7 +855,7 @@ class Doc extends PDFObject {
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return null;
|
||||
}
|
||||
|
||||
_getChildren(fieldName) {
|
||||
@ -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");
|
||||
}
|
||||
@ -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)
|
||||
@ -1084,15 +1102,22 @@ 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) {
|
||||
const field = this.getField(fieldName);
|
||||
if (field) {
|
||||
field.value = field.defaultValue;
|
||||
field.valueAsString = field.value;
|
||||
mustCalculate = true;
|
||||
if (!fieldName) {
|
||||
continue;
|
||||
}
|
||||
const field = this.getField(fieldName);
|
||||
if (!field) {
|
||||
continue;
|
||||
}
|
||||
field.value = field.defaultValue;
|
||||
field.valueAsString = field.value;
|
||||
mustCalculate = true;
|
||||
}
|
||||
} else {
|
||||
mustCalculate = this._fields.size !== 0;
|
||||
|
@ -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);
|
||||
@ -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) {
|
||||
@ -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");
|
||||
|
@ -81,12 +81,14 @@ class Field extends PDFObject {
|
||||
this._strokeColor = data.strokeColor || ["G", 0];
|
||||
this._textColor = data.textColor || ["G", 0];
|
||||
this._value = data.value || "";
|
||||
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;
|
||||
|
||||
this.valueAsString = data.valueAsString || this._value;
|
||||
}
|
||||
|
||||
get currentValueIndices() {
|
||||
@ -246,6 +248,9 @@ class Field extends PDFObject {
|
||||
}
|
||||
|
||||
get valueAsString() {
|
||||
if (this._valueAsString === undefined) {
|
||||
this._valueAsString = this._value ? this._value.toString() : "";
|
||||
}
|
||||
return this._valueAsString;
|
||||
}
|
||||
|
||||
@ -286,6 +291,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) {
|
||||
@ -512,7 +520,7 @@ class RadioButtonField extends Field {
|
||||
}
|
||||
|
||||
set value(value) {
|
||||
if (value === null) {
|
||||
if (value === null || value === undefined) {
|
||||
this._value = "";
|
||||
}
|
||||
const i = this.exportValues.indexOf(value);
|
||||
@ -574,7 +582,7 @@ class CheckboxField extends RadioButtonField {
|
||||
}
|
||||
|
||||
set value(value) {
|
||||
if (value === "Off") {
|
||||
if (!value || value === "Off") {
|
||||
this._value = "Off";
|
||||
} else {
|
||||
super.value = value;
|
||||
|
@ -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);
|
||||
|
@ -38,6 +38,14 @@ class ProxyHandler {
|
||||
}
|
||||
|
||||
set(obj, prop, value) {
|
||||
if (obj._kidIds) {
|
||||
// If the field is a container for other fields then
|
||||
// dispatch the kids.
|
||||
obj._kidIds.forEach(id => {
|
||||
obj._appObjects[id].wrapped[prop] = value;
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof prop === "string" && !prop.startsWith("_") && prop in obj) {
|
||||
const old = obj[prop];
|
||||
obj[prop] = value;
|
||||
@ -46,7 +54,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;
|
||||
|
@ -787,7 +787,7 @@ describe("Interaction", () => {
|
||||
`
|
||||
['Text1', 'Text2', 'Text4',
|
||||
'List Box7', 'Group6'].map(x => this.getField(x).page).join(',')
|
||||
`
|
||||
`
|
||||
);
|
||||
|
||||
// Click on execute button to eval the above code.
|
||||
@ -802,4 +802,43 @@ describe("Interaction", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("in issue13269.pdf", () => {
|
||||
let pages;
|
||||
|
||||
beforeAll(async () => {
|
||||
pages = await loadAndWait("issue13269.pdf", "#\\32 7R");
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must update fields with the same name from JS", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await page.waitForFunction(
|
||||
"window.PDFViewerApplication.scriptingReady === true"
|
||||
);
|
||||
|
||||
await page.type("#\\32 7R", "hello");
|
||||
await page.keyboard.press("Enter");
|
||||
|
||||
await Promise.all(
|
||||
[4, 5, 6].map(async n =>
|
||||
page.waitForFunction(
|
||||
`document.querySelector("#\\\\32 ${n}R").value !== ""`
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const expected = "hello world";
|
||||
for (const n of [4, 5, 6]) {
|
||||
const text = await page.$eval(`#\\32 ${n}R`, el => el.value);
|
||||
expect(text).withContext(`In ${browserName}`).toEqual(expected);
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
1
test/pdfs/.gitignore
vendored
1
test/pdfs/.gitignore
vendored
@ -8,6 +8,7 @@
|
||||
!franz_2.pdf
|
||||
!fraction-highlight.pdf
|
||||
!german-umlaut-r.pdf
|
||||
!issue13269.pdf
|
||||
!xref_command_missing.pdf
|
||||
!issue1155r.pdf
|
||||
!issue2017r.pdf
|
||||
|
BIN
test/pdfs/issue13269.pdf
Normal file
BIN
test/pdfs/issue13269.pdf
Normal file
Binary file not shown.
@ -271,7 +271,7 @@ class PDFScriptingManager {
|
||||
this._pdfViewer.isInPresentationMode ||
|
||||
this._pdfViewer.isChangingPresentationMode;
|
||||
|
||||
const { id, command, value } = detail;
|
||||
const { id, siblings, command, value } = detail;
|
||||
if (!id) {
|
||||
switch (command) {
|
||||
case "clear":
|
||||
@ -309,13 +309,17 @@ class PDFScriptingManager {
|
||||
}
|
||||
}
|
||||
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
element.dispatchEvent(new CustomEvent("updatefromsandbox", { detail }));
|
||||
} else {
|
||||
delete detail.id;
|
||||
// The element hasn't been rendered yet, use the AnnotationStorage.
|
||||
this._pdfDocument?.annotationStorage.setValue(id, detail);
|
||||
delete detail.id;
|
||||
|
||||
const ids = siblings ? [id, ...siblings] : [id];
|
||||
for (const elementId of ids) {
|
||||
const element = document.getElementById(elementId);
|
||||
if (element) {
|
||||
element.dispatchEvent(new CustomEvent("updatefromsandbox", { detail }));
|
||||
} else {
|
||||
// The element hasn't been rendered yet, use the AnnotationStorage.
|
||||
this._pdfDocument?.annotationStorage.setValue(elementId, detail);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user