diff --git a/out.pdf b/out.pdf new file mode 100644 index 000000000..53bfb6596 Binary files /dev/null and b/out.pdf differ diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index 0562ff5c1..ea9ab09c3 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -780,7 +780,7 @@ class WidgetAnnotationElement extends AnnotationElement { detail: { id: this.data.id, name: eventName, - value: event.target.checked, + value: valueGetter(event), }, }); }); @@ -923,8 +923,6 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement { const elementData = { userValue: null, formattedValue: null, - beforeInputSelectionRange: null, - beforeInputValue: null, }; if (this.data.multiLine) { @@ -965,7 +963,6 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement { } // Reset the cursor position to the start of the field (issue 12359). event.target.scrollLeft = 0; - elementData.beforeInputSelectionRange = null; }; if (this.enableScripting && this.hasJSActions) { @@ -1007,7 +1004,6 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement { // Even if the field hasn't any actions // leaving it can still trigger some actions with Calculate element.addEventListener("keydown", event => { - elementData.beforeInputValue = event.target.value; // if the key is one of Escape, Enter or Tab // then the data are committed let commitKey = -1; @@ -1039,9 +1035,9 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement { const _blurListener = blurListener; blurListener = null; element.addEventListener("blur", event => { + elementData.userValue = event.target.value; if (this._mouseState.isDown) { // Focus out using the mouse: data are committed - elementData.userValue = event.target.value; this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { source: this, detail: { @@ -1057,42 +1053,22 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement { } _blurListener(event); }); - element.addEventListener("mousedown", event => { - elementData.beforeInputValue = event.target.value; - elementData.beforeInputSelectionRange = null; - }); - element.addEventListener("keyup", event => { - // keyup is triggered after input - if (event.target.selectionStart === event.target.selectionEnd) { - elementData.beforeInputSelectionRange = null; - } - }); - element.addEventListener("select", event => { - elementData.beforeInputSelectionRange = [ - event.target.selectionStart, - event.target.selectionEnd, - ]; - }); if (this.data.actions?.Keystroke) { - // We should use beforeinput but this - // event isn't available in Firefox - element.addEventListener("input", event => { - let selStart = -1; - let selEnd = -1; - if (elementData.beforeInputSelectionRange) { - [selStart, selEnd] = elementData.beforeInputSelectionRange; - } + element.addEventListener("beforeinput", event => { + elementData.formattedValue = ""; + const { data, target } = event; + const { value, selectionStart, selectionEnd } = target; this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { source: this, detail: { id, name: "Keystroke", - value: elementData.beforeInputValue, - change: event.data, + value, + change: data, willCommit: false, - selStart, - selEnd, + selStart: selectionStart, + selEnd: selectionEnd, }, }); }); diff --git a/src/pdf.sandbox.js b/src/pdf.sandbox.js index 1a1140c39..8aad3fd7f 100644 --- a/src/pdf.sandbox.js +++ b/src/pdf.sandbox.js @@ -103,7 +103,7 @@ class Sandbox { } dispatchEvent(event) { - this.support.callSandboxFunction("dispatchEvent", event); + this.support?.callSandboxFunction("dispatchEvent", event); } dumpMemoryUse() { diff --git a/src/scripting_api/aform.js b/src/scripting_api/aform.js index 664e3807d..1994e28ad 100644 --- a/src/scripting_api/aform.js +++ b/src/scripting_api/aform.js @@ -466,6 +466,10 @@ class AForm { const event = globalThis.event; const value = this.AFMergeChange(event); + if (!value) { + return; + } + const checkers = new Map([ ["9", char => char >= "0" && char <= "9"], [ @@ -498,10 +502,6 @@ class AForm { return true; } - if (!value) { - return; - } - const err = `${GlobalConstants.IDS_INVALID_VALUE} = "${cMask}"`; if (value.length > cMask.length) { @@ -538,10 +538,6 @@ class AForm { AFSpecial_Keystroke(psf) { const event = globalThis.event; - if (!event.value) { - return; - } - psf = this.AFMakeNumber(psf); let formatStr; diff --git a/src/scripting_api/event.js b/src/scripting_api/event.js index 16ddfb3db..f4fc873ab 100644 --- a/src/scripting_api/event.js +++ b/src/scripting_api/event.js @@ -151,6 +151,14 @@ class EventDispatcher { 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], + }); } } } diff --git a/test/integration/scripting_spec.js b/test/integration/scripting_spec.js index 139a3ed4f..e7cdb9b62 100644 --- a/test/integration/scripting_spec.js +++ b/test/integration/scripting_spec.js @@ -102,6 +102,11 @@ describe("Interaction", () => { pages.map(async ([browserName, page]) => { await page.type("#\\34 16R", "3.14159", { delay: 200 }); await page.click("#\\34 19R"); + + await page.waitForFunction( + `document.querySelector("#\\\\34 16R").value !== "3.14159"` + ); + const text = await page.$eval("#\\34 16R", el => el.value); expect(text).withContext(`In ${browserName}`).toEqual("3,14"); @@ -116,10 +121,20 @@ describe("Interaction", () => { pages.map(async ([browserName, page]) => { await page.type("#\\34 48R", "61803", { delay: 200 }); await page.click("#\\34 19R"); + + await page.waitForFunction( + `document.querySelector("#\\\\34 48R").value !== "61803"` + ); + let text = await page.$eval("#\\34 48R", el => el.value); expect(text).withContext(`In ${browserName}`).toEqual("61.803,00"); await page.click("#\\34 48R"); + + await page.waitForFunction( + `document.querySelector("#\\\\34 48R").value !== "61.803,00"` + ); + text = await page.$eval("#\\34 48R", el => el.value); expect(text).withContext(`In ${browserName}`).toEqual("61803"); @@ -128,6 +143,11 @@ describe("Interaction", () => { await page.type("#\\34 48R", "1.61803", { delay: 200 }); await page.click("#\\34 19R"); + + await page.waitForFunction( + `document.querySelector("#\\\\34 48R").value !== "1.61803"` + ); + text = await page.$eval("#\\34 48R", el => el.value); expect(text).withContext(`In ${browserName}`).toEqual("1,62"); }) @@ -137,11 +157,22 @@ describe("Interaction", () => { it("must format the field with 2 digits and leave field with a TAB", async () => { await Promise.all( pages.map(async ([browserName, page]) => { + const prevSum = await page.$eval("#\\34 27R", el => el.value); + await page.type("#\\34 22R", "2.7182818", { delay: 200 }); await page.keyboard.press("Tab"); + + await page.waitForFunction( + `document.querySelector("#\\\\34 22R").value !== "2.7182818"` + ); + const text = await page.$eval("#\\34 22R", el => el.value); expect(text).withContext(`In ${browserName}`).toEqual("2,72"); + await page.waitForFunction( + `document.querySelector("#\\\\34 27R").value !== "${prevSum}"` + ); + const sum = await page.$eval("#\\34 27R", el => el.value); expect(sum).withContext(`In ${browserName}`).toEqual("5,86"); }) @@ -156,9 +187,14 @@ describe("Interaction", () => { await page.type("#\\34 36R", "0.69314", { delay: 200 }); await page.keyboard.press("Escape"); + const text = await page.$eval("#\\34 36R", el => el.value); expect(text).withContext(`In ${browserName}`).toEqual("0.69314"); + await page.waitForFunction( + `document.querySelector("#\\\\34 71R").value !== "${sum}"` + ); + sum = await page.$eval("#\\34 71R", el => el.value); expect(sum).withContext(`In ${browserName}`).toEqual("3,55"); }) @@ -168,11 +204,17 @@ describe("Interaction", () => { it("must format the field with 2 digits on key ENTER", async () => { await Promise.all( pages.map(async ([browserName, page]) => { + const prevSum = await page.$eval("#\\34 27R", el => el.value); + await page.type("#\\34 19R", "0.577215", { delay: 200 }); await page.keyboard.press("Enter"); const text = await page.$eval("#\\34 19R", el => el.value); expect(text).toEqual("0.577215"); + await page.waitForFunction( + `document.querySelector("#\\\\34 27R").value !== "${prevSum}"` + ); + const sum = await page.$eval("#\\34 27R", el => el.value); expect(sum).toEqual("6,44"); }) @@ -194,6 +236,14 @@ describe("Interaction", () => { // click on reset button await page.click("[data-annotation-id='402R']"); + await Promise.all( + ["16", "22", "19", "05", "27"].map(id => + page.waitForFunction( + `document.querySelector("#\\\\34 ${id}R").value === ""` + ) + ) + ); + let text = await page.$eval("#\\34 16R", el => el.value); expect(text).toEqual(""); @@ -451,6 +501,10 @@ describe("Interaction", () => { "window.PDFViewerApplication.scriptingReady === true" ); + await page.waitForFunction( + `document.querySelector("#\\\\34 7R").value !== ""` + ); + let text = await page.$eval("#\\34 7R", el => el.value); expect(text).withContext(`In ${browserName}`).toEqual("PageOpen 1"); @@ -642,6 +696,7 @@ describe("Interaction", () => { } for (const num of [6, 4, 2, 1]) { + await clearInput(page, "#\\33 3R"); await page.click(`option[value=Export${num}]`); await page.waitForFunction( `document.querySelector("#\\\\33 3R").value !== ""` @@ -747,7 +802,7 @@ describe("Interaction", () => { await page.keyboard.press("Tab"); await page.waitForFunction( - `getComputedStyle(document.querySelector("#\\\\31 71R")).value !== "${prev}"` + `document.querySelector("#\\\\31 71R").value !== "${prev}"` ); sum += val; @@ -909,4 +964,177 @@ describe("Interaction", () => { ); }); }); + + describe("in issue14307.pdf (1)", () => { + let pages; + + beforeAll(async () => { + pages = await loadAndWait("issue14307.pdf", "#\\33 0R"); + pages.map(async ([, page]) => { + page.on("dialog", async dialog => { + await dialog.dismiss(); + }); + }); + }); + + afterAll(async () => { + await closePages(pages); + }); + + it("must check input for US zip format", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await page.waitForFunction( + "window.PDFViewerApplication.scriptingReady === true" + ); + + await clearInput(page, "#\\32 9R"); + await clearInput(page, "#\\33 0R"); + + await page.focus("#\\32 9R"); + await page.type("#\\32 9R", "12A"); + await page.waitForFunction( + `document.querySelector("#\\\\32 9R").value !== "12A"` + ); + + let text = await page.$eval(`#\\32 9R`, el => el.value); + expect(text).withContext(`In ${browserName}`).toEqual("12"); + + await page.focus("#\\32 9R"); + await page.type("#\\32 9R", "34"); + await page.click("[data-annotation-id='30R']"); + + await page.waitForFunction( + `document.querySelector("#\\\\32 9R").value !== "1234"` + ); + + text = await page.$eval(`#\\32 9R`, el => el.value); + expect(text).withContext(`In ${browserName}`).toEqual(""); + + await page.focus("#\\32 9R"); + await page.type("#\\32 9R", "12345"); + await page.click("[data-annotation-id='30R']"); + + text = await page.$eval(`#\\32 9R`, el => el.value); + expect(text).withContext(`In ${browserName}`).toEqual("12345"); + }) + ); + }); + }); + + describe("in issue14307.pdf (2)", () => { + let pages; + + beforeAll(async () => { + pages = await loadAndWait("issue14307.pdf", "#\\33 0R"); + pages.map(async ([, page]) => { + page.on("dialog", async dialog => { + await dialog.dismiss(); + }); + }); + }); + + afterAll(async () => { + await closePages(pages); + }); + + it("must check input for US phone number (long) format", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await page.waitForFunction( + "window.PDFViewerApplication.scriptingReady === true" + ); + + await clearInput(page, "#\\32 9R"); + await clearInput(page, "#\\33 0R"); + + await page.focus("#\\33 0R"); + await page.type("#\\33 0R", "(123) 456A"); + await page.waitForFunction( + `document.querySelector("#\\\\33 0R").value !== "(123) 456A"` + ); + + let text = await page.$eval(`#\\33 0R`, el => el.value); + expect(text).withContext(`In ${browserName}`).toEqual("(123) 456"); + + await page.focus("#\\33 0R"); + await page.type("#\\33 0R", "-789"); + await page.click("[data-annotation-id='29R']"); + + await page.waitForFunction( + `document.querySelector("#\\\\33 0R").value !== "(123) 456-789"` + ); + + text = await page.$eval(`#\\33 0R`, el => el.value); + expect(text).withContext(`In ${browserName}`).toEqual(""); + + await page.focus("#\\33 0R"); + await page.type("#\\33 0R", "(123) 456-7890"); + await page.click("[data-annotation-id='29R']"); + + text = await page.$eval(`#\\33 0R`, el => el.value); + expect(text) + .withContext(`In ${browserName}`) + .toEqual("(123) 456-7890"); + }) + ); + }); + }); + + describe("in issue14307.pdf (3)", () => { + let pages; + + beforeAll(async () => { + pages = await loadAndWait("issue14307.pdf", "#\\33 0R"); + pages.map(async ([, page]) => { + page.on("dialog", async dialog => { + await dialog.dismiss(); + }); + }); + }); + + afterAll(async () => { + await closePages(pages); + }); + + it("must check input for US phone number (short) format", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await page.waitForFunction( + "window.PDFViewerApplication.scriptingReady === true" + ); + + await clearInput(page, "#\\32 9R"); + await clearInput(page, "#\\33 0R"); + + await page.focus("#\\33 0R"); + await page.type("#\\33 0R", "123A"); + await page.waitForFunction( + `document.querySelector("#\\\\33 0R").value !== "123A"` + ); + + let text = await page.$eval(`#\\33 0R`, el => el.value); + expect(text).withContext(`In ${browserName}`).toEqual("123"); + + await page.focus("#\\33 0R"); + await page.type("#\\33 0R", "-456"); + await page.click("[data-annotation-id='29R']"); + + await page.waitForFunction( + `document.querySelector("#\\\\33 0R").value !== "123-456"` + ); + + text = await page.$eval(`#\\33 0R`, el => el.value); + expect(text).withContext(`In ${browserName}`).toEqual(""); + + await page.focus("#\\33 0R"); + await page.type("#\\33 0R", "123-4567"); + await page.click("[data-annotation-id='29R']"); + + text = await page.$eval(`#\\33 0R`, el => el.value); + expect(text).withContext(`In ${browserName}`).toEqual("123-4567"); + }) + ); + }); + }); }); diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index afb7f86bd..7d6292215 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -504,3 +504,4 @@ !PDFBOX-3148-2-fuzzed.pdf !poppler-90-0-fuzzed.pdf !issue14415.pdf +!issue14307.pdf diff --git a/test/pdfs/issue14307.pdf b/test/pdfs/issue14307.pdf new file mode 100644 index 000000000..4143b36f1 Binary files /dev/null and b/test/pdfs/issue14307.pdf differ diff --git a/web/generic_scripting.js b/web/generic_scripting.js index 62a413525..992604b44 100644 --- a/web/generic_scripting.js +++ b/web/generic_scripting.js @@ -56,7 +56,7 @@ class GenericScripting { async dispatchEventInSandbox(event) { const sandbox = await this._ready; - sandbox.dispatchEvent(event); + setTimeout(() => sandbox.dispatchEvent(event), 0); } async destroySandbox() {