diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index dce12bf6ffa2e..9b6b7b4ed2d42 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -67,7 +67,6 @@ function getRectDims(rect) { * @property {boolean} [enableScripting] * @property {boolean} [hasJSActions] * @property {Object} [fieldObjects] - * @property {Object} [mouseState] */ class AnnotationElementFactory { @@ -177,7 +176,6 @@ class AnnotationElement { this.enableScripting = parameters.enableScripting; this.hasJSActions = parameters.hasJSActions; this._fieldObjects = parameters.fieldObjects; - this._mouseState = parameters.mouseState; if (isRenderable) { this.container = this._createContainer(ignoreBorder); @@ -1053,6 +1051,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement { userValue: textContent, formattedValue: null, lastCommittedValue: null, + commitKey: 1, }; if (this.data.multiLine) { @@ -1114,6 +1113,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement { target.value = elementData.userValue; } elementData.lastCommittedValue = target.value; + elementData.commitKey = 1; }); element.addEventListener("updatefromsandbox", jsEvent => { @@ -1178,8 +1178,9 @@ 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 => { - // if the key is one of Escape, Enter or Tab - // then the data are committed + elementData.commitKey = 1; + // If the key is one of Escape, Enter then the data are committed. + // If we've a Tab then data will be committed on blur. let commitKey = -1; if (event.key === "Escape") { commitKey = 0; @@ -1189,7 +1190,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement { // (see issue #15627). commitKey = 2; } else if (event.key === "Tab") { - commitKey = 3; + elementData.commitKey = 3; } if (commitKey === -1) { return; @@ -1217,13 +1218,12 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement { const _blurListener = blurListener; blurListener = null; element.addEventListener("blur", event => { + if (!event.relatedTarget) { + return; + } const { value } = event.target; elementData.userValue = value; - if ( - this._mouseState.isDown && - elementData.lastCommittedValue !== value - ) { - // Focus out using the mouse: data are committed + if (elementData.lastCommittedValue !== value) { this.linkService.eventBus?.dispatch("dispatcheventinsandbox", { source: this, detail: { @@ -1231,7 +1231,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement { name: "Keystroke", value, willCommit: true, - commitKey: 1, + commitKey: elementData.commitKey, selStart: event.target.selectionStart, selEnd: event.target.selectionEnd, }, @@ -2620,7 +2620,6 @@ class AnnotationLayer { enableScripting: parameters.enableScripting, hasJSActions: parameters.hasJSActions, fieldObjects: parameters.fieldObjects, - mouseState: parameters.mouseState || { isDown: false }, }); if (element.isRenderable) { const rendered = element.render(); diff --git a/src/scripting_api/doc.js b/src/scripting_api/doc.js index a2b0702ee9828..6d90971470002 100644 --- a/src/scripting_api/doc.js +++ b/src/scripting_api/doc.js @@ -101,27 +101,33 @@ class Doc extends PDFObject { this._disableSaving = false; } + _initActions() { + const dontRun = new Set([ + "WillClose", + "WillSave", + "DidSave", + "WillPrint", + "DidPrint", + "OpenAction", + ]); + // When a pdf has just been opened it doesn't really make sense + // to save it: it's up to the user to decide if they want to do that. + // A pdf can contain an action /FooBar which will trigger a save + // even if there are no WillSave/DidSave (which are themselves triggered + // after a save). + this._disableSaving = true; + for (const actionName of this._actions.keys()) { + if (!dontRun.has(actionName)) { + this._runActions(actionName); + } + } + this._runActions("OpenAction"); + this._disableSaving = false; + } + _dispatchDocEvent(name) { if (name === "Open") { - const dontRun = new Set([ - "WillClose", - "WillSave", - "DidSave", - "WillPrint", - "DidPrint", - "OpenAction", - ]); - // When a pdf has just been opened it doesn't really make sense - // to save it: it's up to the user to decide if they want to do that. - // A pdf can contain an action /FooBar which will trigger a save - // even if there are no WillSave/DidSave (which are themselves triggered - // after a save). this._disableSaving = true; - for (const actionName of this._actions.keys()) { - if (!dontRun.has(actionName)) { - this._runActions(actionName); - } - } this._runActions("OpenAction"); this._disableSaving = false; } else if (name === "WillPrint") { diff --git a/src/scripting_api/event.js b/src/scripting_api/event.js index d85f28fe12c9d..e1713c9ece8f3 100644 --- a/src/scripting_api/event.js +++ b/src/scripting_api/event.js @@ -91,6 +91,10 @@ class EventDispatcher { if (id === "doc") { const eventName = event.name; if (eventName === "Open") { + // Initialize named actions before calling formatAll to avoid any + // errors in the case where a formatter is using one of those named + // actions (see #15818). + this._document.obj._initActions(); // Before running the Open event, we format all the fields // (see bug 1766987). this.formatAll(); @@ -264,6 +268,7 @@ class EventDispatcher { value: "", formattedValue: null, selRange: [0, 0], + focus: true, // Stay in the field. }); } } diff --git a/test/integration/scripting_spec.js b/test/integration/scripting_spec.js index f759584e646a4..72666a501bbc1 100644 --- a/test/integration/scripting_spec.js +++ b/test/integration/scripting_spec.js @@ -1002,43 +1002,43 @@ describe("Interaction", () => { }); it("must check input for US zip format", async () => { - await Promise.all( - pages.map(async ([browserName, page]) => { - await page.waitForFunction( - "window.PDFViewerApplication.scriptingReady === true" - ); + // Run the tests sequentially to avoid any focus issues between the two + // browsers when an alert is displayed. + for (const [browserName, page] of pages) { + await page.waitForFunction( + "window.PDFViewerApplication.scriptingReady === true" + ); - await clearInput(page, getSelector("29R")); - await clearInput(page, getSelector("30R")); + await clearInput(page, getSelector("29R")); + await clearInput(page, getSelector("30R")); - await page.focus(getSelector("29R")); - await page.type(getSelector("29R"), "12A", { delay: 100 }); - await page.waitForFunction( - `${getQuerySelector("29R")}.value !== "12A"` - ); + await page.focus(getSelector("29R")); + await page.type(getSelector("29R"), "12A", { delay: 100 }); + await page.waitForFunction( + `${getQuerySelector("29R")}.value !== "12A"` + ); - let text = await page.$eval(getSelector(`29R`), el => el.value); - expect(text).withContext(`In ${browserName}`).toEqual("12"); + let text = await page.$eval(getSelector(`29R`), el => el.value); + expect(text).withContext(`In ${browserName}`).toEqual("12"); - await page.focus(getSelector("29R")); - await page.type(getSelector("29R"), "34", { delay: 100 }); - await page.click("[data-annotation-id='30R']"); + await page.focus(getSelector("29R")); + await page.type(getSelector("29R"), "34", { delay: 100 }); + await page.click("[data-annotation-id='30R']"); - await page.waitForFunction( - `${getQuerySelector("29R")}.value !== "1234"` - ); + await page.waitForFunction( + `${getQuerySelector("29R")}.value !== "1234"` + ); - text = await page.$eval(getSelector(`29R`), el => el.value); - expect(text).withContext(`In ${browserName}`).toEqual(""); + text = await page.$eval(getSelector(`29R`), el => el.value); + expect(text).withContext(`In ${browserName}`).toEqual(""); - await page.focus(getSelector("29R")); - await page.type(getSelector("29R"), "12345", { delay: 100 }); - await page.click("[data-annotation-id='30R']"); + await page.focus(getSelector("29R")); + await page.type(getSelector("29R"), "12345", { delay: 100 }); + await page.click("[data-annotation-id='30R']"); - text = await page.$eval(getSelector(`29R`), el => el.value); - expect(text).withContext(`In ${browserName}`).toEqual("12345"); - }) - ); + text = await page.$eval(getSelector(`29R`), el => el.value); + expect(text).withContext(`In ${browserName}`).toEqual("12345"); + } }); }); @@ -1059,45 +1059,43 @@ describe("Interaction", () => { }); 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" - ); + // Run the tests sequentially to avoid any focus issues between the two + // browsers when an alert is displayed. + for (const [browserName, page] of pages) { + await page.waitForFunction( + "window.PDFViewerApplication.scriptingReady === true" + ); - await clearInput(page, getSelector("29R")); - await clearInput(page, getSelector("30R")); + await clearInput(page, getSelector("29R")); + await clearInput(page, getSelector("30R")); - await page.focus(getSelector("30R")); - await page.type(getSelector("30R"), "(123) 456A", { delay: 100 }); - await page.waitForFunction( - `${getQuerySelector("30R")}.value !== "(123) 456A"` - ); + await page.focus(getSelector("30R")); + await page.type(getSelector("30R"), "(123) 456A", { delay: 100 }); + await page.waitForFunction( + `${getQuerySelector("30R")}.value !== "(123) 456A"` + ); - let text = await page.$eval(getSelector(`30R`), el => el.value); - expect(text).withContext(`In ${browserName}`).toEqual("(123) 456"); + let text = await page.$eval(getSelector(`30R`), el => el.value); + expect(text).withContext(`In ${browserName}`).toEqual("(123) 456"); - await page.focus(getSelector("30R")); - await page.type(getSelector("30R"), "-789", { delay: 100 }); - await page.click("[data-annotation-id='29R']"); + await page.focus(getSelector("30R")); + await page.type(getSelector("30R"), "-789", { delay: 100 }); + await page.click("[data-annotation-id='29R']"); - await page.waitForFunction( - `${getQuerySelector("30R")}.value !== "(123) 456-789"` - ); + await page.waitForFunction( + `${getQuerySelector("30R")}.value !== "(123) 456-789"` + ); - text = await page.$eval(getSelector(`30R`), el => el.value); - expect(text).withContext(`In ${browserName}`).toEqual(""); + text = await page.$eval(getSelector(`30R`), el => el.value); + expect(text).withContext(`In ${browserName}`).toEqual(""); - await page.focus(getSelector("30R")); - await page.type(getSelector("30R"), "(123) 456-7890", { delay: 100 }); - await page.click("[data-annotation-id='29R']"); + await page.focus(getSelector("30R")); + await page.type(getSelector("30R"), "(123) 456-7890", { delay: 100 }); + await page.click("[data-annotation-id='29R']"); - text = await page.$eval(getSelector("30R"), el => el.value); - expect(text) - .withContext(`In ${browserName}`) - .toEqual("(123) 456-7890"); - }) - ); + text = await page.$eval(getSelector("30R"), el => el.value); + expect(text).withContext(`In ${browserName}`).toEqual("(123) 456-7890"); + } }); }); @@ -1118,43 +1116,43 @@ describe("Interaction", () => { }); 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" - ); + // Run the tests sequentially to avoid any focus issues between the two + // browsers when an alert is displayed. + for (const [browserName, page] of pages) { + await page.waitForFunction( + "window.PDFViewerApplication.scriptingReady === true" + ); - await clearInput(page, getSelector("29R")); - await clearInput(page, getSelector("30R")); + await clearInput(page, getSelector("29R")); + await clearInput(page, getSelector("30R")); - await page.focus(getSelector("30R")); - await page.type(getSelector("30R"), "123A", { delay: 100 }); - await page.waitForFunction( - `${getQuerySelector("30R")}.value !== "123A"` - ); + await page.focus(getSelector("30R")); + await page.type(getSelector("30R"), "123A", { delay: 100 }); + await page.waitForFunction( + `${getQuerySelector("30R")}.value !== "123A"` + ); - let text = await page.$eval(getSelector(`30R`), el => el.value); - expect(text).withContext(`In ${browserName}`).toEqual("123"); + let text = await page.$eval(getSelector(`30R`), el => el.value); + expect(text).withContext(`In ${browserName}`).toEqual("123"); - await page.focus(getSelector("30R")); - await page.type(getSelector("30R"), "-456", { delay: 100 }); - await page.click("[data-annotation-id='29R']"); + await page.focus(getSelector("30R")); + await page.type(getSelector("30R"), "-456", { delay: 100 }); + await page.click("[data-annotation-id='29R']"); - await page.waitForFunction( - `${getQuerySelector("30R")}.value !== "123-456"` - ); + await page.waitForFunction( + `${getQuerySelector("30R")}.value !== "123-456"` + ); - text = await page.$eval(getSelector("30R"), el => el.value); - expect(text).withContext(`In ${browserName}`).toEqual(""); + text = await page.$eval(getSelector("30R"), el => el.value); + expect(text).withContext(`In ${browserName}`).toEqual(""); - await page.focus(getSelector("30R")); - await page.type(getSelector("30R"), "123-4567", { delay: 100 }); - await page.click("[data-annotation-id='29R']"); + await page.focus(getSelector("30R")); + await page.type(getSelector("30R"), "123-4567", { delay: 100 }); + await page.click("[data-annotation-id='29R']"); - text = await page.$eval(getSelector("30R"), el => el.value); - expect(text).withContext(`In ${browserName}`).toEqual("123-4567"); - }) - ); + text = await page.$eval(getSelector("30R"), el => el.value); + expect(text).withContext(`In ${browserName}`).toEqual("123-4567"); + } }); }); @@ -1249,6 +1247,11 @@ describe("Interaction", () => { "window.PDFViewerApplication.scriptingReady === true" ); + await page.click(getSelector("28R")); + await page.$eval(getSelector("28R"), el => + el.setSelectionRange(0, 0) + ); + await page.type(getSelector("28R"), "Hello", { delay: 100 }); await page.waitForFunction( `${getQuerySelector("28R")}.value !== "123"` @@ -1701,4 +1704,53 @@ describe("Interaction", () => { ); }); }); + + describe("in issue15818.pdf", () => { + let pages; + + beforeAll(async () => { + pages = await loadAndWait("issue15818.pdf", getSelector("27R")); + }); + + afterAll(async () => { + await closePages(pages); + }); + + it("must check the field value set when the document is open", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await page.waitForFunction( + "window.PDFViewerApplication.scriptingReady === true" + ); + + await page.waitForFunction(`${getQuerySelector("27R")}.value !== ""`); + + const text = await page.$eval(getSelector("27R"), el => el.value); + expect(text).withContext(`In ${browserName}`).toEqual("hello world"); + }) + ); + }); + + it("must check the format action is called when setFocus is used", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await page.waitForFunction( + "window.PDFViewerApplication.scriptingReady === true" + ); + + await page.type(getSelector("30R"), "abc"); + await page.waitForFunction( + `${getQuerySelector("30R")}.value !== "abc"` + ); + await page.waitForTimeout(10); + + const focusedId = await page.evaluate(_ => + window.document.activeElement.getAttribute("data-element-id") + ); + + expect(focusedId).withContext(`In ${browserName}`).toEqual("31R"); + }) + ); + }); + }); }); diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 6fbb661d2916a..68c603873e731 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -562,3 +562,4 @@ !issue15789.pdf !fields_order.pdf !issue15815.pdf +!issue15818.pdf diff --git a/test/pdfs/issue15818.pdf b/test/pdfs/issue15818.pdf new file mode 100755 index 0000000000000..9c3f7485986ea Binary files /dev/null and b/test/pdfs/issue15818.pdf differ diff --git a/web/annotation_layer_builder.js b/web/annotation_layer_builder.js index 62eac71c9c4d6..5306ee47f29f2 100644 --- a/web/annotation_layer_builder.js +++ b/web/annotation_layer_builder.js @@ -41,7 +41,6 @@ import { PresentationModeState } from "./ui_utils.js"; * @property {Promise} [hasJSActionsPromise] * @property {Promise> | null>} * [fieldObjectsPromise] - * @property {Object} [mouseState] * @property {Map} [annotationCanvasMap] * @property {TextAccessibilityManager} accessibilityManager */ @@ -66,7 +65,6 @@ class AnnotationLayerBuilder { enableScripting = false, hasJSActionsPromise = null, fieldObjectsPromise = null, - mouseState = null, annotationCanvasMap = null, accessibilityManager = null, }) { @@ -81,7 +79,6 @@ class AnnotationLayerBuilder { this.enableScripting = enableScripting; this._hasJSActionsPromise = hasJSActionsPromise || Promise.resolve(false); this._fieldObjectsPromise = fieldObjectsPromise || Promise.resolve(null); - this._mouseState = mouseState; this._annotationCanvasMap = annotationCanvasMap; this._accessibilityManager = accessibilityManager; @@ -144,7 +141,6 @@ class AnnotationLayerBuilder { enableScripting: this.enableScripting, hasJSActions, fieldObjects, - mouseState: this._mouseState, annotationCanvasMap: this._annotationCanvasMap, accessibilityManager: this._accessibilityManager, }); diff --git a/web/default_factory.js b/web/default_factory.js index 3d93b5474bb99..bba1cd1158987 100644 --- a/web/default_factory.js +++ b/web/default_factory.js @@ -57,7 +57,6 @@ class DefaultAnnotationLayerFactory { * @property {IL10n} l10n * @property {boolean} [enableScripting] * @property {Promise} [hasJSActionsPromise] - * @property {Object} [mouseState] * @property {Promise> | null>} * [fieldObjectsPromise] * @property {Map} [annotationCanvasMap] - Map some @@ -78,7 +77,6 @@ class DefaultAnnotationLayerFactory { l10n = NullL10n, enableScripting = false, hasJSActionsPromise = null, - mouseState = null, fieldObjectsPromise = null, annotationCanvasMap = null, accessibilityManager = null, @@ -94,7 +92,6 @@ class DefaultAnnotationLayerFactory { enableScripting, hasJSActionsPromise, fieldObjectsPromise, - mouseState, annotationCanvasMap, accessibilityManager, }); diff --git a/web/interfaces.js b/web/interfaces.js index b96c7fcdcb750..fce1898d96cb6 100644 --- a/web/interfaces.js +++ b/web/interfaces.js @@ -199,7 +199,6 @@ class IPDFAnnotationLayerFactory { * @property {IL10n} l10n * @property {boolean} [enableScripting] * @property {Promise} [hasJSActionsPromise] - * @property {Object} [mouseState] * @property {Promise> | null>} * [fieldObjectsPromise] * @property {Map} [annotationCanvasMap] - Map some @@ -220,7 +219,6 @@ class IPDFAnnotationLayerFactory { l10n = undefined, enableScripting = false, hasJSActionsPromise = null, - mouseState = null, fieldObjectsPromise = null, annotationCanvasMap = null, accessibilityManager = null, diff --git a/web/pdf_scripting_manager.js b/web/pdf_scripting_manager.js index 68c3a50e790bb..a0197b614a172 100644 --- a/web/pdf_scripting_manager.js +++ b/web/pdf_scripting_manager.js @@ -46,7 +46,6 @@ class PDFScriptingManager { this._destroyCapability = null; this._scripting = null; - this._mouseState = Object.create(null); this._ready = false; this._eventBus = eventBus; @@ -143,19 +142,9 @@ class PDFScriptingManager { this._closeCapability?.resolve(); }); - this._domEvents.set("mousedown", event => { - this._mouseState.isDown = true; - }); - this._domEvents.set("mouseup", event => { - this._mouseState.isDown = false; - }); - for (const [name, listener] of this._internalEvents) { this._eventBus._on(name, listener); } - for (const [name, listener] of this._domEvents) { - window.addEventListener(name, listener, true); - } try { const docProperties = await this._getDocProperties(); @@ -229,10 +218,6 @@ class PDFScriptingManager { }); } - get mouseState() { - return this._mouseState; - } - get destroyPromise() { return this._destroyCapability?.promise || null; } @@ -248,13 +233,6 @@ class PDFScriptingManager { return shadow(this, "_internalEvents", new Map()); } - /** - * @private - */ - get _domEvents() { - return shadow(this, "_domEvents", new Map()); - } - /** * @private */ @@ -508,16 +486,10 @@ class PDFScriptingManager { } this._internalEvents.clear(); - for (const [name, listener] of this._domEvents) { - window.removeEventListener(name, listener, true); - } - this._domEvents.clear(); - this._pageOpenPending.clear(); this._visitedPages.clear(); this._scripting = null; - delete this._mouseState.isDown; this._ready = false; this._destroyCapability?.resolve(); diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js index ec3ffbeb2a8dd..1ee158c0189ef 100644 --- a/web/pdf_viewer.js +++ b/web/pdf_viewer.js @@ -1703,7 +1703,6 @@ class PDFViewer { * @property {IL10n} l10n * @property {boolean} [enableScripting] * @property {Promise} [hasJSActionsPromise] - * @property {Object} [mouseState] * @property {Promise> | null>} * [fieldObjectsPromise] * @property {Map} [annotationCanvasMap] - Map some @@ -1724,7 +1723,6 @@ class PDFViewer { l10n = NullL10n, enableScripting = this.enableScripting, hasJSActionsPromise = this.pdfDocument?.hasJSActions(), - mouseState = this._scriptingManager?.mouseState, fieldObjectsPromise = this.pdfDocument?.getFieldObjects(), annotationCanvasMap = null, accessibilityManager = null, @@ -1740,7 +1738,6 @@ class PDFViewer { l10n, enableScripting, hasJSActionsPromise, - mouseState, fieldObjectsPromise, annotationCanvasMap, accessibilityManager,