From c06c5f7cbd5546f704b26570201791e9388d8ed5 Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Thu, 18 Aug 2022 19:27:53 +0200 Subject: [PATCH] [Annotations] charLimit === 0 means unlimited (bug 1782564) Changing the charLimit in JS had no impact, so this patch aims to fix that and add an integration test for it. --- src/core/annotation.js | 4 +- src/display/annotation_layer.js | 49 ++++++++++++++++++++--- src/scripting_api/field.js | 13 +++++- test/integration/scripting_spec.js | 61 +++++++++++++++++++++++++++++ test/pdfs/.gitignore | 1 + test/pdfs/bug1782564.pdf | Bin 0 -> 7743 bytes test/unit/annotation_spec.js | 4 +- test/unit/api_spec.js | 2 +- 8 files changed, 122 insertions(+), 12 deletions(-) create mode 100755 test/pdfs/bug1782564.pdf diff --git a/src/core/annotation.js b/src/core/annotation.js index fff00fa8d..6aa8b4d20 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -2255,7 +2255,7 @@ class TextWidgetAnnotation extends WidgetAnnotation { // Determine the maximum length of text in the field. let maximumLength = getInheritableProperty({ dict, key: "MaxLen" }); if (!Number.isInteger(maximumLength) || maximumLength < 0) { - maximumLength = null; + maximumLength = 0; } this.data.maxLen = maximumLength; @@ -2266,7 +2266,7 @@ class TextWidgetAnnotation extends WidgetAnnotation { !this.hasFieldFlag(AnnotationFieldFlag.MULTILINE) && !this.hasFieldFlag(AnnotationFieldFlag.PASSWORD) && !this.hasFieldFlag(AnnotationFieldFlag.FILESELECT) && - this.data.maxLen !== null; + this.data.maxLen !== 0; this.data.doNotScroll = this.hasFieldFlag(AnnotationFieldFlag.DONOTSCROLL); } diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index 1e272ac89..052210367 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -1000,7 +1000,14 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement { const storedData = storage.getValue(id, { value: this.data.fieldValue, }); - const textContent = storedData.formattedValue || storedData.value || ""; + let textContent = storedData.formattedValue || storedData.value || ""; + const maxLen = storage.getValue(id, { + charLimit: this.data.maxLen, + }).charLimit; + if (maxLen && textContent.length > maxLen) { + textContent = textContent.slice(0, maxLen); + } + const elementData = { userValue: textContent, formattedValue: null, @@ -1030,6 +1037,10 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement { this._setRequired(element, this.data.required); + if (maxLen) { + element.maxLength = maxLen; + } + element.addEventListener("input", event => { storage.setValue(id, { value: event.target.value }); this.setPropertyOnSiblings( @@ -1088,6 +1099,36 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement { selRange(event) { event.target.setSelectionRange(...event.detail.selRange); }, + charLimit: event => { + const { charLimit } = event.detail; + const { target } = event; + if (charLimit === 0) { + target.removeAttribute("maxLength"); + return; + } + + target.setAttribute("maxLength", charLimit); + let value = elementData.userValue; + if (!value || value.length <= charLimit) { + return; + } + value = value.slice(0, charLimit); + target.value = elementData.userValue = value; + storage.setValue(id, { value }); + + this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { + source: this, + detail: { + id, + name: "Keystroke", + value, + willCommit: true, + commitKey: 1, + selStart: target.selectionStart, + selEnd: target.selectionEnd, + }, + }); + }, }; this._dispatchEventFromSandbox(actions, jsEvent); }); @@ -1225,13 +1266,9 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement { element.addEventListener("blur", blurListener); } - if (this.data.maxLen !== null) { - element.maxLength = this.data.maxLen; - } - if (this.data.comb) { const fieldWidth = this.data.rect[2] - this.data.rect[0]; - const combWidth = fieldWidth / this.data.maxLen; + const combWidth = fieldWidth / maxLen; element.classList.add("comb"); element.style.letterSpacing = `calc(${combWidth}px * var(--scale-factor) - 1ch)`; diff --git a/src/scripting_api/field.js b/src/scripting_api/field.js index cf7b99044..49dc39450 100644 --- a/src/scripting_api/field.js +++ b/src/scripting_api/field.js @@ -29,7 +29,6 @@ class Field extends PDFObject { this.buttonScaleHow = data.buttonScaleHow; this.ButtonScaleWhen = data.buttonScaleWhen; this.calcOrderIndex = data.calcOrderIndex; - this.charLimit = data.charLimit; this.comb = data.comb; this.commitOnSelChange = data.commitOnSelChange; this.currentValueIndices = data.currentValueIndices; @@ -69,6 +68,7 @@ class Field extends PDFObject { this._browseForFileToSubmit = data.browseForFileToSubmit || null; this._buttonCaption = null; this._buttonIcon = null; + this._charLimit = data.charLimit; this._children = null; this._currentValueIndices = data.currentValueIndices || 0; this._document = data.doc; @@ -151,6 +151,17 @@ class Field extends PDFObject { this.fillColor = color; } + get charLimit() { + return this._charLimit; + } + + set charLimit(limit) { + if (typeof limit !== "number") { + throw new Error("Invalid argument value"); + } + this._charLimit = Math.max(0, Math.floor(limit)); + } + get numItems() { if (!this._isChoice) { throw new Error("Not a choice widget"); diff --git a/test/integration/scripting_spec.js b/test/integration/scripting_spec.js index e5dd59c39..dffef9f76 100644 --- a/test/integration/scripting_spec.js +++ b/test/integration/scripting_spec.js @@ -1492,4 +1492,65 @@ describe("Interaction", () => { ); }); }); + + describe("in bug1782564.pdf", () => { + let pages; + + beforeAll(async () => { + pages = await loadAndWait("bug1782564.pdf", getSelector("7R")); + }); + + afterAll(async () => { + await closePages(pages); + }); + + it("must check that charLimit is correctly set", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await page.waitForFunction( + "window.PDFViewerApplication.scriptingReady === true" + ); + + await clearInput(page, getSelector("7R")); + // By default the charLimit is 0 which means that the input + // length is unlimited. + await page.type(getSelector("7R"), "abcdefghijklmnopq", { + delay: 10, + }); + + let value = await page.$eval(getSelector("7R"), el => el.value); + expect(value) + .withContext(`In ${browserName}`) + .toEqual("abcdefghijklmnopq"); + + // charLimit is set to 1 + await page.click(getSelector("9R")); + + await page.waitForFunction( + `document.querySelector('${getSelector( + "7R" + )}').value !== "abcdefgh"` + ); + + value = await page.$eval(getSelector("7R"), el => el.value); + expect(value).withContext(`In ${browserName}`).toEqual("a"); + + await clearInput(page, getSelector("7R")); + await page.type(getSelector("7R"), "xyz", { delay: 10 }); + + value = await page.$eval(getSelector("7R"), el => el.value); + expect(value).withContext(`In ${browserName}`).toEqual("x"); + + // charLimit is set to 2 + await page.click(getSelector("9R")); + + await clearInput(page, getSelector("7R")); + await page.type(getSelector("7R"), "xyz", { delay: 10 }); + + value = await page.$eval(getSelector("7R"), el => el.value); + expect(value).withContext(`In ${browserName}`).toEqual("xy"); + }) + ); + }); + }); }); diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 6df734f40..b3f167b00 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -535,3 +535,4 @@ !issue15092.pdf !bug1782186.pdf !tracemonkey_a11y.pdf +!bug1782564.pdf diff --git a/test/pdfs/bug1782564.pdf b/test/pdfs/bug1782564.pdf new file mode 100755 index 0000000000000000000000000000000000000000..a64589a3b4df3bc891e2becf5fb841d263373fd9 GIT binary patch literal 7743 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 literal 0 HcmV?d00001 diff --git a/test/unit/annotation_spec.js b/test/unit/annotation_spec.js index 2530d315e..dd9510626 100644 --- a/test/unit/annotation_spec.js +++ b/test/unit/annotation_spec.js @@ -1454,7 +1454,7 @@ describe("annotation", function () { ); expect(data.annotationType).toEqual(AnnotationType.WIDGET); expect(data.textAlignment).toEqual(null); - expect(data.maxLen).toEqual(null); + expect(data.maxLen).toEqual(0); expect(data.readOnly).toEqual(false); expect(data.hidden).toEqual(false); expect(data.multiLine).toEqual(false); @@ -1478,7 +1478,7 @@ describe("annotation", function () { ); expect(data.annotationType).toEqual(AnnotationType.WIDGET); expect(data.textAlignment).toEqual(null); - expect(data.maxLen).toEqual(null); + expect(data.maxLen).toEqual(0); expect(data.readOnly).toEqual(false); expect(data.hidden).toEqual(false); expect(data.multiLine).toEqual(false); diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js index 0caf0c2e1..bdcfb05d1 100644 --- a/test/unit/api_spec.js +++ b/test/unit/api_spec.js @@ -1390,7 +1390,7 @@ describe("api", function () { defaultValue: "", multiline: false, password: false, - charLimit: null, + charLimit: 0, comb: false, editable: true, hidden: false,