Merge pull request #14869 from calixteman/14862

[JS] Fix few bugs present in the pdf for issue #14862
This commit is contained in:
Jonas Jenwald 2022-05-03 18:31:31 +02:00 committed by GitHub
commit 8135d7ccf6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 567 additions and 214 deletions

View File

@ -1518,7 +1518,8 @@ class WidgetAnnotation extends Annotation {
const storageEntry = annotationStorage
? annotationStorage.get(this.data.id)
: undefined;
let value = storageEntry && storageEntry.value;
let value =
storageEntry && (storageEntry.formattedValue || storageEntry.value);
if (value === undefined) {
if (!this._hasValueFromXFA || this.appearance) {
// The annotation hasn't been rendered so use the appearance.
@ -1981,7 +1982,7 @@ class TextWidgetAnnotation extends WidgetAnnotation {
return {
id: this.data.id,
value: this.data.fieldValue,
defaultValue: this.data.defaultFieldValue,
defaultValue: this.data.defaultFieldValue || "",
multiline: this.data.multiLine,
password: this.hasFieldFlag(AnnotationFieldFlag.PASSWORD),
charLimit: this.data.maxLen,

View File

@ -297,6 +297,110 @@ class AnnotationElement {
return container;
}
get _commonActions() {
const setColor = (jsName, styleName, event) => {
const color = event.detail[jsName];
event.target.style[styleName] = ColorConverters[`${color[0]}_HTML`](
color.slice(1)
);
};
return shadow(this, "_commonActions", {
display: event => {
const hidden = event.detail.display % 2 === 1;
event.target.style.visibility = hidden ? "hidden" : "visible";
this.annotationStorage.setValue(this.data.id, {
hidden,
print: event.detail.display === 0 || event.detail.display === 3,
});
},
print: event => {
this.annotationStorage.setValue(this.data.id, {
print: event.detail.print,
});
},
hidden: event => {
event.target.style.visibility = event.detail.hidden
? "hidden"
: "visible";
this.annotationStorage.setValue(this.data.id, {
hidden: event.detail.hidden,
});
},
focus: event => {
setTimeout(() => event.target.focus({ preventScroll: false }), 0);
},
userName: event => {
// tooltip
event.target.title = event.detail.userName;
},
readonly: event => {
if (event.detail.readonly) {
event.target.setAttribute("readonly", "");
} else {
event.target.removeAttribute("readonly");
}
},
required: event => {
if (event.detail.required) {
event.target.setAttribute("required", "");
} else {
event.target.removeAttribute("required");
}
},
bgColor: event => {
setColor("bgColor", "backgroundColor", event);
},
fillColor: event => {
setColor("fillColor", "backgroundColor", event);
},
fgColor: event => {
setColor("fgColor", "color", event);
},
textColor: event => {
setColor("textColor", "color", event);
},
borderColor: event => {
setColor("borderColor", "borderColor", event);
},
strokeColor: event => {
setColor("strokeColor", "borderColor", event);
},
});
}
_dispatchEventFromSandbox(actions, jsEvent) {
const commonActions = this._commonActions;
for (const name of Object.keys(jsEvent.detail)) {
const action = actions[name] || commonActions[name];
if (action) {
action(jsEvent);
}
}
}
_setDefaultPropertiesFromJS(element) {
if (!this.enableScripting) {
return;
}
// Some properties may have been updated thanks to JS.
const storedData = this.annotationStorage.getRawValue(this.data.id);
if (!storedData) {
return;
}
const commonActions = this._commonActions;
for (const [actionName, detail] of Object.entries(storedData)) {
const action = commonActions[actionName];
if (action) {
action({ detail, target: element });
// The action has been consumed: no need to keep it.
delete storedData[actionName];
}
}
}
/**
* Create quadrilaterals from the annotation's quadpoints.
*
@ -657,7 +761,7 @@ class LinkAnnotationElement extends AnnotationElement {
switch (field.type) {
case "text": {
const value = field.defaultValue || "";
storage.setValue(id, { value, valueAsString: value });
storage.setValue(id, { value });
break;
}
case "checkbox":
@ -794,85 +898,6 @@ class WidgetAnnotationElement extends AnnotationElement {
? "transparent"
: Util.makeHexColor(color[0], color[1], color[2]);
}
_dispatchEventFromSandbox(actions, jsEvent) {
const setColor = (jsName, styleName, event) => {
const color = event.detail[jsName];
event.target.style[styleName] = ColorConverters[`${color[0]}_HTML`](
color.slice(1)
);
};
const commonActions = {
display: event => {
const hidden = event.detail.display % 2 === 1;
event.target.style.visibility = hidden ? "hidden" : "visible";
this.annotationStorage.setValue(this.data.id, {
hidden,
print: event.detail.display === 0 || event.detail.display === 3,
});
},
print: event => {
this.annotationStorage.setValue(this.data.id, {
print: event.detail.print,
});
},
hidden: event => {
event.target.style.visibility = event.detail.hidden
? "hidden"
: "visible";
this.annotationStorage.setValue(this.data.id, {
hidden: event.detail.hidden,
});
},
focus: event => {
setTimeout(() => event.target.focus({ preventScroll: false }), 0);
},
userName: event => {
// tooltip
event.target.title = event.detail.userName;
},
readonly: event => {
if (event.detail.readonly) {
event.target.setAttribute("readonly", "");
} else {
event.target.removeAttribute("readonly");
}
},
required: event => {
if (event.detail.required) {
event.target.setAttribute("required", "");
} else {
event.target.removeAttribute("required");
}
},
bgColor: event => {
setColor("bgColor", "backgroundColor", event);
},
fillColor: event => {
setColor("fillColor", "backgroundColor", event);
},
fgColor: event => {
setColor("fgColor", "color", event);
},
textColor: event => {
setColor("textColor", "color", event);
},
borderColor: event => {
setColor("borderColor", "borderColor", event);
},
strokeColor: event => {
setColor("strokeColor", "borderColor", event);
},
};
for (const name of Object.keys(jsEvent.detail)) {
const action = actions[name] || commonActions[name];
if (action) {
action(jsEvent);
}
}
}
}
class TextWidgetAnnotationElement extends WidgetAnnotationElement {
@ -909,12 +934,12 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
// from parsing the elements correctly for the reference tests.
const storedData = storage.getValue(id, {
value: this.data.fieldValue,
valueAsString: this.data.fieldValue,
});
const textContent = storedData.valueAsString || storedData.value || "";
const textContent = storedData.formattedValue || storedData.value || "";
const elementData = {
userValue: null,
formattedValue: null,
valueOnFocus: "",
};
if (this.data.multiLine) {
@ -944,14 +969,15 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
});
element.addEventListener("resetform", event => {
const defaultValue = this.data.defaultFieldValue || "";
const defaultValue = this.data.defaultFieldValue ?? "";
element.value = elementData.userValue = defaultValue;
delete elementData.formattedValue;
elementData.formattedValue = null;
});
let blurListener = event => {
if (elementData.formattedValue) {
event.target.value = elementData.formattedValue;
const { formattedValue } = elementData;
if (formattedValue !== null && formattedValue !== undefined) {
event.target.value = formattedValue;
}
// Reset the cursor position to the start of the field (issue 12359).
event.target.scrollLeft = 0;
@ -962,32 +988,33 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
if (elementData.userValue) {
event.target.value = elementData.userValue;
}
elementData.valueOnFocus = event.target.value;
});
element.addEventListener("updatefromsandbox", jsEvent => {
const actions = {
value(event) {
elementData.userValue = event.detail.value || "";
elementData.userValue = event.detail.value ?? "";
storage.setValue(id, { value: elementData.userValue.toString() });
if (!elementData.formattedValue) {
event.target.value = elementData.userValue;
}
event.target.value = elementData.userValue;
},
valueAsString(event) {
elementData.formattedValue = event.detail.valueAsString || "";
if (event.target !== document.activeElement) {
formattedValue(event) {
const { formattedValue } = event.detail;
elementData.formattedValue = formattedValue;
if (
formattedValue !== null &&
formattedValue !== undefined &&
event.target !== document.activeElement
) {
// Input hasn't the focus so display formatted string
event.target.value = elementData.formattedValue;
event.target.value = formattedValue;
}
storage.setValue(id, {
formattedValue: elementData.formattedValue,
formattedValue,
});
},
selRange(event) {
const [selStart, selEnd] = event.detail.selRange;
if (selStart >= 0 && selEnd < event.target.value.length) {
event.target.setSelectionRange(selStart, selEnd);
}
event.target.setSelectionRange(...event.detail.selRange);
},
};
this._dispatchEventFromSandbox(actions, jsEvent);
@ -1009,14 +1036,18 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
if (commitKey === -1) {
return;
}
const { value } = event.target;
if (elementData.valueOnFocus === value) {
return;
}
// Save the entered value
elementData.userValue = event.target.value;
elementData.userValue = value;
this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
source: this,
detail: {
id,
name: "Keystroke",
value: event.target.value,
value,
willCommit: true,
commitKey,
selStart: event.target.selectionStart,
@ -1027,15 +1058,16 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
const _blurListener = blurListener;
blurListener = null;
element.addEventListener("blur", event => {
elementData.userValue = event.target.value;
if (this._mouseState.isDown) {
const { value } = event.target;
elementData.userValue = value;
if (this._mouseState.isDown && elementData.valueOnFocus !== value) {
// Focus out using the mouse: data are committed
this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
source: this,
detail: {
id,
name: "Keystroke",
value: event.target.value,
value,
willCommit: true,
commitKey: 1,
selStart: event.target.selectionStart,
@ -1048,19 +1080,56 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
if (this.data.actions?.Keystroke) {
element.addEventListener("beforeinput", event => {
elementData.formattedValue = "";
const { data, target } = event;
const { value, selectionStart, selectionEnd } = target;
let selStart = selectionStart,
selEnd = selectionEnd;
switch (event.inputType) {
// https://rawgit.com/w3c/input-events/v1/index.html#interface-InputEvent-Attributes
case "deleteWordBackward": {
const match = value
.substring(0, selectionStart)
.match(/\w*[^\w]*$/);
if (match) {
selStart -= match[0].length;
}
break;
}
case "deleteWordForward": {
const match = value
.substring(selectionStart)
.match(/^[^\w]*\w*/);
if (match) {
selEnd += match[0].length;
}
break;
}
case "deleteContentBackward":
if (selectionStart === selectionEnd) {
selStart -= 1;
}
break;
case "deleteContentForward":
if (selectionStart === selectionEnd) {
selEnd += 1;
}
break;
}
// We handle the event ourselves.
event.preventDefault();
this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
source: this,
detail: {
id,
name: "Keystroke",
value,
change: data,
change: data || "",
willCommit: false,
selStart: selectionStart,
selEnd: selectionEnd,
selStart,
selEnd,
},
});
});
@ -1104,6 +1173,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
this._setTextStyle(element);
this._setBackgroundColor(element);
this._setDefaultPropertiesFromJS(element);
this.container.appendChild(element);
return this.container;
@ -1213,6 +1283,7 @@ class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement {
}
this._setBackgroundColor(element);
this._setDefaultPropertiesFromJS(element);
this.container.appendChild(element);
return this.container;
@ -1300,6 +1371,7 @@ class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement {
}
this._setBackgroundColor(element);
this._setDefaultPropertiesFromJS(element);
this.container.appendChild(element);
return this.container;
@ -1322,6 +1394,8 @@ class PushButtonWidgetAnnotationElement extends LinkAnnotationElement {
container.title = this.data.alternativeText;
}
this._setDefaultPropertiesFromJS(container);
return container;
}
}
@ -1534,6 +1608,7 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
}
this._setBackgroundColor(selectElement);
this._setDefaultPropertiesFromJS(selectElement);
this.container.appendChild(selectElement);
return this.container;

View File

@ -50,6 +50,18 @@ class AnnotationStorage {
return Object.assign(defaultValue, value);
}
/**
* Get the value for a given key.
*
* @public
* @memberof AnnotationStorage
* @param {string} key
* @returns {Object}
*/
getRawValue(key) {
return this._storage.get(key);
}
/**
* Set the value for a given key
*

View File

@ -434,7 +434,7 @@ class App extends PDFObject {
oDoc = null,
oCheckbox = null
) {
if (typeof cMsg === "object") {
if (cMsg && typeof cMsg === "object") {
nType = cMsg.nType;
cMsg = cMsg.cMsg;
}
@ -580,7 +580,7 @@ class App extends PDFObject {
}
response(cQuestion, cTitle = "", cDefault = "", bPassword = "", cLabel = "") {
if (typeof cQuestion === "object") {
if (cQuestion && typeof cQuestion === "object") {
cDefault = cQuestion.cDefault;
cQuestion = cQuestion.cQuestion;
}
@ -590,7 +590,7 @@ class App extends PDFObject {
}
setInterval(cExpr, nMilliseconds = 0) {
if (typeof cExpr === "object") {
if (cExpr && typeof cExpr === "object") {
nMilliseconds = cExpr.nMilliseconds || 0;
cExpr = cExpr.cExpr;
}
@ -609,7 +609,7 @@ class App extends PDFObject {
}
setTimeOut(cExpr, nMilliseconds = 0) {
if (typeof cExpr === "object") {
if (cExpr && typeof cExpr === "object") {
nMilliseconds = cExpr.nMilliseconds || 0;
cExpr = cExpr.cExpr;
}

View File

@ -820,8 +820,8 @@ class Doc extends PDFObject {
/* Not implemented */
}
getField(cName) {
if (typeof cName === "object") {
_getField(cName) {
if (cName && typeof cName === "object") {
cName = cName.cName;
}
if (typeof cName !== "string") {
@ -859,6 +859,14 @@ class Doc extends PDFObject {
return null;
}
getField(cName) {
const field = this._getField(cName);
if (!field) {
return null;
}
return field.wrapped;
}
_getChildren(fieldName) {
// Children of foo.bar are foo.bar.oof, foo.bar.rab
// but not foo.bar.oof.FOO.
@ -889,7 +897,7 @@ class Doc extends PDFObject {
}
getNthFieldName(nIndex) {
if (typeof nIndex === "object") {
if (nIndex && typeof nIndex === "object") {
nIndex = nIndex.nIndex;
}
if (typeof nIndex !== "number") {
@ -1027,7 +1035,7 @@ class Doc extends PDFObject {
bAnnotations = true,
printParams = null
) {
if (typeof bUI === "object") {
if (bUI && typeof bUI === "object") {
nStart = bUI.nStart;
nEnd = bUI.nEnd;
bSilent = bUI.bSilent;
@ -1103,30 +1111,52 @@ class Doc extends PDFObject {
}
resetForm(aFields = null) {
if (aFields && !Array.isArray(aFields) && typeof aFields === "object") {
// Handle the case resetForm({ aFields: ... })
if (aFields && typeof aFields === "object") {
aFields = aFields.aFields;
}
if (aFields && !Array.isArray(aFields)) {
aFields = [aFields];
}
let mustCalculate = false;
let fieldsToReset;
if (aFields) {
fieldsToReset = [];
for (const fieldName of aFields) {
if (!fieldName) {
continue;
}
const field = this.getField(fieldName);
if (typeof fieldName !== "string") {
// In Acrobat if a fieldName is not a string all the fields are reset.
fieldsToReset = null;
break;
}
const field = this._getField(fieldName);
if (!field) {
continue;
}
field.value = field.defaultValue;
field.valueAsString = field.value;
fieldsToReset.push(field);
mustCalculate = true;
}
} else {
mustCalculate = this._fields.size !== 0;
for (const field of this._fields.values()) {
field.value = field.defaultValue;
field.valueAsString = field.value;
}
}
if (!fieldsToReset) {
fieldsToReset = this._fields.values();
mustCalculate = this._fields.size !== 0;
}
for (const field of fieldsToReset) {
field.obj.value = field.obj.defaultValue;
this._send({
id: field.obj._id,
value: field.obj.defaultValue,
formattedValue: null,
selRange: [0, 0],
});
}
if (mustCalculate) {
this.calculateNow();
}

View File

@ -45,6 +45,7 @@ class EventDispatcher {
this._objects = objects;
this._document.obj._eventDispatcher = this;
this._isCalculating = false;
}
mergeChange(event) {
@ -129,61 +130,84 @@ class EventDispatcher {
return;
case "Action":
this.runActions(source, source, event, name);
if (this._document.obj.calculate) {
this.runCalculate(source, event);
}
this.runCalculate(source, event);
return;
}
this.runActions(source, source, event, name);
if (name === "Keystroke") {
if (event.rc) {
if (event.willCommit) {
this.runValidation(source, event);
} else if (
event.change !== savedChange.change ||
if (name !== "Keystroke") {
return;
}
if (event.rc) {
if (event.willCommit) {
this.runValidation(source, event);
} else {
const value = (source.obj.value = this.mergeChange(event));
let selStart, selEnd;
if (
event.selStart !== savedChange.selStart ||
event.selEnd !== savedChange.selEnd
) {
source.wrapped.value = this.mergeChange(event);
// Selection has been changed by the script so apply the changes.
selStart = event.selStart;
selEnd = event.selEnd;
} else {
selEnd = selStart = savedChange.selStart + event.change.length;
}
} else if (!event.willCommit) {
source.obj._send({
id: source.obj._id,
value: savedChange.value,
selRange: [savedChange.selStart, savedChange.selEnd],
});
} else {
// Entry is not valid (rc == false) and it's a commit
// so just clear the field.
source.obj._send({
id: source.obj._id,
value: "",
selRange: [0, 0],
value,
selRange: [selStart, selEnd],
});
}
} else if (!event.willCommit) {
source.obj._send({
id: source.obj._id,
value: savedChange.value,
selRange: [savedChange.selStart, savedChange.selEnd],
});
} else {
// Entry is not valid (rc == false) and it's a commit
// so just clear the field.
source.obj._send({
id: source.obj._id,
value: "",
formattedValue: null,
selRange: [0, 0],
});
}
}
runValidation(source, event) {
const hasRan = this.runActions(source, source, event, "Validate");
const didValidateRun = this.runActions(source, source, event, "Validate");
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;
source.obj.value = event.value;
this.runCalculate(source, event);
const savedValue = (event.value = source.obj.value);
let formattedValue = null;
if (this.runActions(source, source, event, "Format")) {
formattedValue = event.value;
}
if (this._document.obj.calculate) {
this.runCalculate(source, event);
}
event.value = source.obj.value;
this.runActions(source, source, event, "Format");
source.wrapped.valueAsString = event.value;
source.obj._send({
id: source.obj._id,
value: savedValue,
formattedValue,
});
event.value = savedValue;
} else if (didValidateRun) {
// The value is not valid.
source.obj._send({
id: source.obj._id,
value: "",
formattedValue: null,
selRange: [0, 0],
});
}
}
@ -198,17 +222,42 @@ class EventDispatcher {
}
calculateNow() {
if (!this._calculationOrder) {
// This function can be called by a JS script (doc.calculateNow()).
// If !this._calculationOrder then there is nothing to calculate.
// _isCalculating is here to prevent infinite recursion with calculateNow.
// If !this._document.obj.calculate then the script doesn't want to have
// a calculate.
if (
!this._calculationOrder ||
this._isCalculating ||
!this._document.obj.calculate
) {
return;
}
this._isCalculating = true;
const first = this._calculationOrder[0];
const source = this._objects[first];
globalThis.event = new Event({});
this.runCalculate(source, globalThis.event);
try {
this.runCalculate(source, globalThis.event);
} catch (error) {
this._isCalculating = false;
throw error;
}
this._isCalculating = false;
}
runCalculate(source, event) {
if (!this._calculationOrder) {
// _document.obj.calculate is equivalent to doc.calculate and can be
// changed by a script to allow a future calculate or not.
// This function is either called by calculateNow or when an action
// is triggered (in this case we cannot be currently calculating).
// So there are no need to check for _isCalculating because it has
// been already done in calculateNow.
if (!this._calculationOrder || !this._document.obj.calculate) {
return;
}
@ -218,31 +267,43 @@ class EventDispatcher {
}
if (!this._document.obj.calculate) {
// An action may have changed calculate value.
continue;
// An action could have changed calculate value.
break;
}
event.value = null;
const target = this._objects[targetId];
let savedValue = target.obj.value;
this.runActions(source, target, event, "Calculate");
if (!event.rc) {
continue;
}
if (event.value !== null) {
target.wrapped.value = event.value;
// A new value has been calculated so set it.
target.obj.value = event.value;
}
event.value = target.obj.value;
this.runActions(target, target, event, "Validate");
if (!event.rc) {
if (target.obj.value !== savedValue) {
target.wrapped.value = savedValue;
}
continue;
}
event.value = target.obj.value;
this.runActions(target, target, event, "Format");
if (event.value !== null) {
target.wrapped.valueAsString = event.value;
savedValue = event.value = target.obj.value;
let formattedValue = null;
if (this.runActions(target, target, event, "Format")) {
formattedValue = event.value;
}
target.obj._send({
id: target.obj._id,
value: savedValue,
formattedValue,
});
}
}
}

View File

@ -87,8 +87,6 @@ class Field extends PDFObject {
this._globalEval = data.globalEval;
this._appObjects = data.appObjects;
this.valueAsString = data.valueAsString || this._value;
}
get currentValueIndices() {
@ -252,14 +250,11 @@ class Field extends PDFObject {
}
get valueAsString() {
if (this._valueAsString === undefined) {
this._valueAsString = this._value ? this._value.toString() : "";
}
return this._valueAsString;
return (this._value ?? "").toString();
}
set valueAsString(val) {
this._valueAsString = val ? val.toString() : "";
set valueAsString(_) {
// Do nothing.
}
browseForFileToSubmit() {
@ -376,7 +371,9 @@ class Field extends PDFObject {
}
if (this._children === null) {
this._children = this._document.obj._getChildren(this._fieldPath);
this._children = this._document.obj
._getChildren(this._fieldPath)
.map(child => child.wrapped);
}
return this._children;
}
@ -481,7 +478,7 @@ class Field extends PDFObject {
}
_reset() {
this.value = this.valueAsString = this.defaultValue;
this.value = this.defaultValue;
}
_runActions(event) {

View File

@ -120,8 +120,8 @@ function initSandbox(params) {
}
const wrapped = new Proxy(field, proxyHandler);
doc._addField(name, wrapped);
const _object = { obj: field, wrapped };
doc._addField(name, _object);
for (const object of objs) {
appObjects[object.id] = _object;
}

View File

@ -237,7 +237,7 @@ describe("Interaction", () => {
await page.click("[data-annotation-id='402R']");
await Promise.all(
["16", "22", "19", "05", "27"].map(id =>
["16", "22", "19", "05"].map(id =>
page.waitForFunction(
`document.querySelector("#\\\\34 ${id}R").value === ""`
)
@ -256,11 +256,14 @@ describe("Interaction", () => {
text = await page.$eval("#\\34 05R", el => el.value);
expect(text).toEqual("");
const sum = await page.$eval("#\\34 27R", el => el.value);
expect(sum).toEqual("");
checked = await page.$eval("#\\34 49R", el => el.checked);
expect(checked).toEqual(false);
const visibility = await page.$eval(
"#\\34 27R",
el => getComputedStyle(el).visibility
);
expect(visibility).toEqual("hidden");
})
);
});
@ -992,7 +995,7 @@ describe("Interaction", () => {
await clearInput(page, "#\\33 0R");
await page.focus("#\\32 9R");
await page.type("#\\32 9R", "12A");
await page.type("#\\32 9R", "12A", { delay: 100 });
await page.waitForFunction(
`document.querySelector("#\\\\32 9R").value !== "12A"`
);
@ -1001,7 +1004,7 @@ describe("Interaction", () => {
expect(text).withContext(`In ${browserName}`).toEqual("12");
await page.focus("#\\32 9R");
await page.type("#\\32 9R", "34");
await page.type("#\\32 9R", "34", { delay: 100 });
await page.click("[data-annotation-id='30R']");
await page.waitForFunction(
@ -1012,7 +1015,7 @@ describe("Interaction", () => {
expect(text).withContext(`In ${browserName}`).toEqual("");
await page.focus("#\\32 9R");
await page.type("#\\32 9R", "12345");
await page.type("#\\32 9R", "12345", { delay: 100 });
await page.click("[data-annotation-id='30R']");
text = await page.$eval(`#\\32 9R`, el => el.value);
@ -1049,7 +1052,7 @@ describe("Interaction", () => {
await clearInput(page, "#\\33 0R");
await page.focus("#\\33 0R");
await page.type("#\\33 0R", "(123) 456A");
await page.type("#\\33 0R", "(123) 456A", { delay: 100 });
await page.waitForFunction(
`document.querySelector("#\\\\33 0R").value !== "(123) 456A"`
);
@ -1058,7 +1061,7 @@ describe("Interaction", () => {
expect(text).withContext(`In ${browserName}`).toEqual("(123) 456");
await page.focus("#\\33 0R");
await page.type("#\\33 0R", "-789");
await page.type("#\\33 0R", "-789", { delay: 100 });
await page.click("[data-annotation-id='29R']");
await page.waitForFunction(
@ -1069,7 +1072,7 @@ describe("Interaction", () => {
expect(text).withContext(`In ${browserName}`).toEqual("");
await page.focus("#\\33 0R");
await page.type("#\\33 0R", "(123) 456-7890");
await page.type("#\\33 0R", "(123) 456-7890", { delay: 100 });
await page.click("[data-annotation-id='29R']");
text = await page.$eval(`#\\33 0R`, el => el.value);
@ -1108,7 +1111,7 @@ describe("Interaction", () => {
await clearInput(page, "#\\33 0R");
await page.focus("#\\33 0R");
await page.type("#\\33 0R", "123A");
await page.type("#\\33 0R", "123A", { delay: 100 });
await page.waitForFunction(
`document.querySelector("#\\\\33 0R").value !== "123A"`
);
@ -1117,7 +1120,7 @@ describe("Interaction", () => {
expect(text).withContext(`In ${browserName}`).toEqual("123");
await page.focus("#\\33 0R");
await page.type("#\\33 0R", "-456");
await page.type("#\\33 0R", "-456", { delay: 100 });
await page.click("[data-annotation-id='29R']");
await page.waitForFunction(
@ -1128,7 +1131,7 @@ describe("Interaction", () => {
expect(text).withContext(`In ${browserName}`).toEqual("");
await page.focus("#\\33 0R");
await page.type("#\\33 0R", "123-4567");
await page.type("#\\33 0R", "123-4567", { delay: 100 });
await page.click("[data-annotation-id='29R']");
text = await page.$eval(`#\\33 0R`, el => el.value);
@ -1137,4 +1140,166 @@ describe("Interaction", () => {
);
});
});
describe("in issue14862.pdf", () => {
let pages;
beforeAll(async () => {
pages = await loadAndWait("issue14862.pdf", "#\\32 7R");
pages.map(async ([, page]) => {
page.on("dialog", async dialog => {
await dialog.dismiss();
});
});
});
afterAll(async () => {
await closePages(pages);
});
it("must convert input in uppercase", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.waitForFunction(
"window.PDFViewerApplication.scriptingReady === true"
);
await page.type("#\\32 7R", "Hello", { delay: 100 });
await page.waitForFunction(
`document.querySelector("#\\\\32 7R").value !== "Hello"`
);
let text = await page.$eval("#\\32 7R", el => el.value);
expect(text).withContext(`In ${browserName}`).toEqual("HELLO");
await page.type("#\\32 7R", " world", { delay: 100 });
await page.waitForFunction(
`document.querySelector("#\\\\32 7R").value !== "HELLO world"`
);
text = await page.$eval("#\\32 7R", el => el.value);
expect(text).withContext(`In ${browserName}`).toEqual("HELLO WORLD");
await page.keyboard.press("Backspace");
await page.keyboard.press("Backspace");
await page.waitForFunction(
`document.querySelector("#\\\\32 7R").value !== "HELLO WORLD"`
);
text = await page.$eval("#\\32 7R", el => el.value);
expect(text).withContext(`In ${browserName}`).toEqual("HELLO WOR");
await page.type("#\\32 7R", "12.dL", { delay: 100 });
await page.waitForFunction(
`document.querySelector("#\\\\32 7R").value !== "HELLO WOR"`
);
text = await page.$eval("#\\32 7R", el => el.value);
expect(text).withContext(`In ${browserName}`).toEqual("HELLO WORDL");
await page.type("#\\32 7R", " ", { delay: 100 });
await page.keyboard.down("Control");
await page.keyboard.press("Backspace");
await page.keyboard.up("Control");
await page.waitForFunction(
`document.querySelector("#\\\\32 7R").value !== "HELLO WORDL "`
);
text = await page.$eval("#\\32 7R", el => el.value);
expect(text).withContext(`In ${browserName}`).toEqual("HELLO ");
await page.$eval("#\\32 7R", el => {
// Select LL
el.selectionStart = 2;
el.selectionEnd = 4;
});
await page.keyboard.press("a");
text = await page.$eval("#\\32 7R", el => el.value);
expect(text).withContext(`In ${browserName}`).toEqual("HEAO ");
})
);
});
it("must check that an infinite loop is not triggered", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.waitForFunction(
"window.PDFViewerApplication.scriptingReady === true"
);
await page.type("#\\32 8R", "Hello", { delay: 100 });
await page.waitForFunction(
`document.querySelector("#\\\\32 8R").value !== "123"`
);
let text = await page.$eval("#\\32 8R", el => el.value);
expect(text).withContext(`In ${browserName}`).toEqual("Hello123");
// The action will trigger a calculateNow which itself
// will trigger a resetForm (inducing a calculateNow) and a
// calculateNow.
await page.click("[data-annotation-id='31R']");
await page.waitForFunction(
`document.querySelector("#\\\\32 8R").value !== "Hello123"`
);
// Without preventing against infinite loop the field is empty.
text = await page.$eval("#\\32 8R", el => el.value);
expect(text).withContext(`In ${browserName}`).toEqual("123");
})
);
});
});
describe("in issue14705.pdf", () => {
let pages;
beforeAll(async () => {
pages = await loadAndWait("issue14705.pdf", "#\\32 9R");
pages.map(async ([, page]) => {
page.on("dialog", async dialog => {
await dialog.dismiss();
});
});
});
afterAll(async () => {
await closePages(pages);
});
it("must check that field value is correctly updated", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.waitForFunction(
"window.PDFViewerApplication.scriptingReady === true"
);
await page.type("#\\32 9R", "Hello World", { delay: 100 });
await page.click("#\\32 7R");
await page.waitForFunction(
`document.querySelector("#\\\\32 9R").value !== "Hello World"`
);
let text = await page.$eval("#\\32 9R", el => el.value);
expect(text).withContext(`In ${browserName}`).toEqual("checked");
await page.click("#\\32 7R");
await page.waitForFunction(
`document.querySelector("#\\\\32 9R").value !== "checked"`
);
text = await page.$eval("#\\32 9R", el => el.value);
expect(text).withContext(`In ${browserName}`).toEqual("unchecked");
})
);
});
});
});

View File

@ -520,3 +520,5 @@
!issue14502.pdf
!issue13211.pdf
!issue14627.pdf
!issue14862.pdf
!issue14705.pdf

BIN
test/pdfs/issue14705.pdf Normal file

Binary file not shown.

BIN
test/pdfs/issue14862.pdf Executable file

Binary file not shown.

View File

@ -1320,7 +1320,7 @@ describe("api", function () {
{
id: "25R",
value: "",
defaultValue: null,
defaultValue: "",
multiline: false,
password: false,
charLimit: null,

View File

@ -89,7 +89,7 @@ describe("Scripting", function () {
return s;
}
const number = 123;
const expected = (((number - 1) * number) / 2).toString();
const expected = ((number - 1) * number) / 2;
const refId = getId();
const data = {
@ -120,7 +120,8 @@ describe("Scripting", function () {
expect(send_queue.has(refId)).toEqual(true);
expect(send_queue.get(refId)).toEqual({
id: refId,
valueAsString: expected,
value: expected,
formattedValue: null,
});
});
});
@ -406,6 +407,7 @@ describe("Scripting", function () {
expect(send_queue.get(refId)).toEqual({
id: refId,
value: "hella",
selRange: [5, 5],
});
});
@ -478,7 +480,7 @@ describe("Scripting", function () {
expect(send_queue.get(refId1)).toEqual({
id: refId1,
value: "world",
valueAsString: "world",
formattedValue: null,
});
});
});
@ -799,7 +801,7 @@ describe("Scripting", function () {
expect(send_queue.get(refId)).toEqual({
id: refId,
value: "123456.789",
valueAsString: "123456.789",
formattedValue: null,
});
});
@ -978,7 +980,7 @@ describe("Scripting", function () {
expect(send_queue.get(refId)).toEqual({
id: refId,
value: "321",
valueAsString: "321",
formattedValue: null,
});
});
@ -1076,7 +1078,7 @@ describe("Scripting", function () {
expect(send_queue.get(refIds[3])).toEqual({
id: refIds[3],
value: 1,
valueAsString: "1",
formattedValue: null,
});
await sandbox.dispatchEventInSandbox({
@ -1089,7 +1091,7 @@ describe("Scripting", function () {
expect(send_queue.get(refIds[3])).toEqual({
id: refIds[3],
value: 3,
valueAsString: "3",
formattedValue: null,
});
await sandbox.dispatchEventInSandbox({
@ -1102,7 +1104,7 @@ describe("Scripting", function () {
expect(send_queue.get(refIds[3])).toEqual({
id: refIds[3],
value: 6,
valueAsString: "6",
formattedValue: null,
});
});
});
@ -1137,7 +1139,8 @@ describe("Scripting", function () {
selStart: 0,
selEnd: 0,
});
expect(send_queue.has(refId)).toEqual(false);
expect(send_queue.has(refId)).toEqual(true);
send_queue.delete(refId);
await sandbox.dispatchEventInSandbox({
id: refId,
@ -1148,7 +1151,8 @@ describe("Scripting", function () {
selStart: 1,
selEnd: 1,
});
expect(send_queue.has(refId)).toEqual(false);
expect(send_queue.has(refId)).toEqual(true);
send_queue.delete(refId);
await sandbox.dispatchEventInSandbox({
id: refId,
@ -1159,7 +1163,8 @@ describe("Scripting", function () {
selStart: 2,
selEnd: 2,
});
expect(send_queue.has(refId)).toEqual(false);
expect(send_queue.has(refId)).toEqual(true);
send_queue.delete(refId);
await sandbox.dispatchEventInSandbox({
id: refId,
@ -1187,7 +1192,8 @@ describe("Scripting", function () {
selStart: 3,
selEnd: 3,
});
expect(send_queue.has(refId)).toEqual(false);
expect(send_queue.has(refId)).toEqual(true);
send_queue.delete(refId);
await sandbox.dispatchEventInSandbox({
id: refId,
@ -1200,7 +1206,8 @@ describe("Scripting", function () {
expect(send_queue.has(refId)).toEqual(true);
expect(send_queue.get(refId)).toEqual({
id: refId,
valueAsString: "3F?0",
value: "3F?0",
formattedValue: null,
});
});
});
@ -1242,7 +1249,8 @@ describe("Scripting", function () {
selStart: i,
selEnd: i,
});
expect(send_queue.has(refId)).toEqual(false);
expect(send_queue.has(refId)).toEqual(true);
send_queue.delete(refId);
value += change;
}
@ -1301,7 +1309,8 @@ describe("Scripting", function () {
selStart: i,
selEnd: i,
});
expect(send_queue.has(refId)).toEqual(false);
expect(send_queue.has(refId)).toEqual(true);
send_queue.delete(refId);
value += change;
}
@ -1360,7 +1369,8 @@ describe("Scripting", function () {
selStart: i,
selEnd: i,
});
expect(send_queue.has(refId)).toEqual(false);
expect(send_queue.has(refId)).toEqual(true);
send_queue.delete(refId);
value += change;
}