Merge pull request #15822 from calixteman/15818

[JS] Run the named actions before running the format when the file is open (issue #15818)
This commit is contained in:
calixteman 2022-12-13 21:56:50 +01:00 committed by GitHub
commit e182597cb1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 182 additions and 159 deletions

View File

@ -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();

View File

@ -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") {

View File

@ -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.
});
}
}

View File

@ -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");
})
);
});
});
});

View File

@ -562,3 +562,4 @@
!issue15789.pdf
!fields_order.pdf
!issue15815.pdf
!issue15818.pdf

BIN
test/pdfs/issue15818.pdf Executable file

Binary file not shown.

View File

@ -41,7 +41,6 @@ import { PresentationModeState } from "./ui_utils.js";
* @property {Promise<boolean>} [hasJSActionsPromise]
* @property {Promise<Object<string, Array<Object>> | null>}
* [fieldObjectsPromise]
* @property {Object} [mouseState]
* @property {Map<string, HTMLCanvasElement>} [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,
});

View File

@ -57,7 +57,6 @@ class DefaultAnnotationLayerFactory {
* @property {IL10n} l10n
* @property {boolean} [enableScripting]
* @property {Promise<boolean>} [hasJSActionsPromise]
* @property {Object} [mouseState]
* @property {Promise<Object<string, Array<Object>> | null>}
* [fieldObjectsPromise]
* @property {Map<string, HTMLCanvasElement>} [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,
});

View File

@ -199,7 +199,6 @@ class IPDFAnnotationLayerFactory {
* @property {IL10n} l10n
* @property {boolean} [enableScripting]
* @property {Promise<boolean>} [hasJSActionsPromise]
* @property {Object} [mouseState]
* @property {Promise<Object<string, Array<Object>> | null>}
* [fieldObjectsPromise]
* @property {Map<string, HTMLCanvasElement>} [annotationCanvasMap] - Map some
@ -220,7 +219,6 @@ class IPDFAnnotationLayerFactory {
l10n = undefined,
enableScripting = false,
hasJSActionsPromise = null,
mouseState = null,
fieldObjectsPromise = null,
annotationCanvasMap = null,
accessibilityManager = null,

View File

@ -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();

View File

@ -1703,7 +1703,6 @@ class PDFViewer {
* @property {IL10n} l10n
* @property {boolean} [enableScripting]
* @property {Promise<boolean>} [hasJSActionsPromise]
* @property {Object} [mouseState]
* @property {Promise<Object<string, Array<Object>> | null>}
* [fieldObjectsPromise]
* @property {Map<string, HTMLCanvasElement>} [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,