mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-08-01 20:07:22 +02:00
Merge pull request #21664 from Snuffleupagus/getJSActions-api
[api-minor] Convert `getJSActions` to return data in a Map
This commit is contained in:
commit
ce4ff55faa
@ -2887,10 +2887,7 @@ class TextWidgetAnnotation extends WidgetAnnotation {
|
||||
this.data.doNotScroll = this.hasFieldFlag(AnnotationFieldFlag.DONOTSCROLL);
|
||||
|
||||
// Check if we have a date or time.
|
||||
const {
|
||||
data: { actions },
|
||||
} = this;
|
||||
|
||||
const { actions } = this.data;
|
||||
if (!actions) {
|
||||
return;
|
||||
}
|
||||
@ -2898,32 +2895,35 @@ class TextWidgetAnnotation extends WidgetAnnotation {
|
||||
const AFDateTime =
|
||||
/^AF(Date|Time)_(?:Keystroke|Format)(?:Ex)?\(['"]?([^'"]+)['"]?\);$/;
|
||||
let canUseHTMLDateTime = false;
|
||||
|
||||
const aFormat = actions.get("Format"),
|
||||
aKeystroke = actions.get("Keystroke");
|
||||
if (
|
||||
(actions.Format?.length === 1 &&
|
||||
actions.Keystroke?.length === 1 &&
|
||||
AFDateTime.test(actions.Format[0]) &&
|
||||
AFDateTime.test(actions.Keystroke[0])) ||
|
||||
(actions.Format?.length === 0 &&
|
||||
actions.Keystroke?.length === 1 &&
|
||||
AFDateTime.test(actions.Keystroke[0])) ||
|
||||
(actions.Keystroke?.length === 0 &&
|
||||
actions.Format?.length === 1 &&
|
||||
AFDateTime.test(actions.Format[0]))
|
||||
(aFormat?.length === 1 &&
|
||||
aKeystroke?.length === 1 &&
|
||||
AFDateTime.test(aFormat[0]) &&
|
||||
AFDateTime.test(aKeystroke[0])) ||
|
||||
(aFormat?.length === 0 &&
|
||||
aKeystroke?.length === 1 &&
|
||||
AFDateTime.test(aKeystroke[0])) ||
|
||||
(aKeystroke?.length === 0 &&
|
||||
aFormat?.length === 1 &&
|
||||
AFDateTime.test(aFormat[0]))
|
||||
) {
|
||||
// If the Format and Keystroke actions are the same, we can just use
|
||||
// the Format action.
|
||||
canUseHTMLDateTime = true;
|
||||
}
|
||||
const actionsToVisit = [];
|
||||
if (actions.Format) {
|
||||
actionsToVisit.push(...actions.Format);
|
||||
if (aFormat) {
|
||||
actionsToVisit.push(...aFormat);
|
||||
}
|
||||
if (actions.Keystroke) {
|
||||
actionsToVisit.push(...actions.Keystroke);
|
||||
if (aKeystroke) {
|
||||
actionsToVisit.push(...aKeystroke);
|
||||
}
|
||||
if (canUseHTMLDateTime) {
|
||||
delete actions.Keystroke;
|
||||
actions.Format = actionsToVisit;
|
||||
actions.delete("Keystroke");
|
||||
actions.set("Format", actionsToVisit);
|
||||
}
|
||||
|
||||
for (const formatAction of actionsToVisit) {
|
||||
|
||||
@ -19,6 +19,7 @@ import {
|
||||
DocumentActionEventType,
|
||||
FormatError,
|
||||
info,
|
||||
makeArr,
|
||||
PermissionFlag,
|
||||
shadow,
|
||||
stringToUTF8String,
|
||||
@ -1287,10 +1288,10 @@ class Catalog {
|
||||
);
|
||||
|
||||
if (javaScript) {
|
||||
actions ??= Object.create(null);
|
||||
actions ??= new Map();
|
||||
|
||||
for (const [key, val] of javaScript) {
|
||||
(actions[key] ??= []).push(val);
|
||||
actions.getOrInsertComputed(key, makeArr).push(val);
|
||||
}
|
||||
}
|
||||
return shadow(this, "jsActions", actions);
|
||||
|
||||
@ -449,7 +449,7 @@ function _collectJS(entry, xref, list, parents) {
|
||||
}
|
||||
|
||||
function collectActions(xref, dict, eventType) {
|
||||
const actions = Object.create(null);
|
||||
const actions = new Map();
|
||||
const additionalActionsDicts = getInheritableProperty({
|
||||
dict,
|
||||
key: "AA",
|
||||
@ -475,7 +475,7 @@ function collectActions(xref, dict, eventType) {
|
||||
const list = [];
|
||||
_collectJS(rawActionDict, xref, list, parents);
|
||||
if (list.length > 0) {
|
||||
actions[action] = list;
|
||||
actions.set(action, list);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -487,10 +487,10 @@ function collectActions(xref, dict, eventType) {
|
||||
const list = [];
|
||||
_collectJS(actionDict, xref, list, parents);
|
||||
if (list.length > 0) {
|
||||
actions.Action = list;
|
||||
actions.set("Action", list);
|
||||
}
|
||||
}
|
||||
return Object.keys(actions).length ? actions : null;
|
||||
return actions.size ? actions : null;
|
||||
}
|
||||
|
||||
const XMLEntities = {
|
||||
|
||||
@ -1012,9 +1012,9 @@ class LinkAnnotationElement extends AnnotationElement {
|
||||
} else {
|
||||
if (
|
||||
data.actions &&
|
||||
(data.actions.Action ||
|
||||
data.actions["Mouse Up"] ||
|
||||
data.actions["Mouse Down"]) &&
|
||||
(data.actions.has("Action") ||
|
||||
data.actions.has("Mouse Up") ||
|
||||
data.actions.has("Mouse Down")) &&
|
||||
this.enableScripting &&
|
||||
this.hasJSActions
|
||||
) {
|
||||
@ -1158,14 +1158,14 @@ class LinkAnnotationElement extends AnnotationElement {
|
||||
* @param {Object} data
|
||||
* @memberof LinkAnnotationElement
|
||||
*/
|
||||
_bindJSAction(link, data) {
|
||||
_bindJSAction(link, { actions, id, overlaidText }) {
|
||||
link.href = this.linkService.getAnchorUrl("");
|
||||
const map = new Map([
|
||||
["Action", "onclick"],
|
||||
["Mouse Up", "onmouseup"],
|
||||
["Mouse Down", "onmousedown"],
|
||||
]);
|
||||
for (const name of Object.keys(data.actions)) {
|
||||
for (const name of actions.keys()) {
|
||||
const jsName = map.get(name);
|
||||
if (!jsName) {
|
||||
continue;
|
||||
@ -1173,16 +1173,13 @@ class LinkAnnotationElement extends AnnotationElement {
|
||||
link[jsName] = () => {
|
||||
this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
|
||||
source: this,
|
||||
detail: {
|
||||
id: data.id,
|
||||
name,
|
||||
},
|
||||
detail: { id, name },
|
||||
});
|
||||
return false;
|
||||
};
|
||||
}
|
||||
if (data.overlaidText) {
|
||||
link.title = data.overlaidText;
|
||||
if (overlaidText) {
|
||||
link.title = overlaidText;
|
||||
}
|
||||
link.onclick ||= () => false;
|
||||
|
||||
@ -1379,8 +1376,10 @@ class WidgetAnnotationElement extends AnnotationElement {
|
||||
}
|
||||
|
||||
_setEventListeners(element, elementData, names, getter) {
|
||||
const { actions } = this.data;
|
||||
|
||||
for (const [baseName, eventName] of names) {
|
||||
if (eventName === "Action" || this.data.actions?.[eventName]) {
|
||||
if (eventName === "Action" || actions?.has(eventName)) {
|
||||
if (eventName === "Focus" || eventName === "Blur") {
|
||||
elementData ||= { focused: false };
|
||||
}
|
||||
@ -1391,10 +1390,10 @@ class WidgetAnnotationElement extends AnnotationElement {
|
||||
eventName,
|
||||
getter
|
||||
);
|
||||
if (eventName === "Focus" && !this.data.actions?.Blur) {
|
||||
if (eventName === "Focus" && !actions?.has("Blur")) {
|
||||
// Ensure that elementData will have the correct value.
|
||||
this._setEventListener(element, elementData, "blur", "Blur", null);
|
||||
} else if (eventName === "Blur" && !this.data.actions?.Focus) {
|
||||
} else if (eventName === "Blur" && !actions?.has("Focus")) {
|
||||
this._setEventListener(element, elementData, "focus", "Focus", null);
|
||||
}
|
||||
}
|
||||
@ -1630,7 +1629,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
|
||||
}
|
||||
elementData.lastCommittedValue = target.value;
|
||||
elementData.commitKey = 1;
|
||||
if (!this.data.actions?.Focus) {
|
||||
if (!this.data.actions?.has("Focus")) {
|
||||
elementData.focused = true;
|
||||
}
|
||||
});
|
||||
@ -1752,7 +1751,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
|
||||
if (!elementData.focused || !event.relatedTarget) {
|
||||
return;
|
||||
}
|
||||
if (!this.data.actions?.Blur) {
|
||||
if (!this.data.actions?.has("Blur")) {
|
||||
elementData.focused = false;
|
||||
}
|
||||
const { target } = event;
|
||||
@ -1800,7 +1799,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
|
||||
_blurListener(event);
|
||||
});
|
||||
|
||||
if (this.data.actions?.Keystroke) {
|
||||
if (this.data.actions?.has("Keystroke")) {
|
||||
element.addEventListener("beforeinput", event => {
|
||||
elementData.lastCommittedValue = null;
|
||||
const { data, target } = event;
|
||||
|
||||
@ -867,8 +867,8 @@ class PDFDocumentProxy {
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<Object | null>} A promise that is resolved with
|
||||
* an {Object} with the JavaScript actions:
|
||||
* @returns {Promise<Map | null>} A promise that is resolved with a {Map} with
|
||||
* the JavaScript actions:
|
||||
* - from the name tree.
|
||||
* - from A or AA entries in the catalog dictionary.
|
||||
* , or `null` if no JavaScript exists.
|
||||
@ -1429,8 +1429,8 @@ class PDFPageProxy {
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<Object>} A promise that is resolved with an
|
||||
* {Object} with JS actions.
|
||||
* @returns {Promise<Map | null>} A promise that is resolved with a {Map} with
|
||||
* the JavaScript actions, or `null` if no JavaScript exists.
|
||||
*/
|
||||
getJSActions() {
|
||||
return this._transport.getPageJSActions(this._pageIndex);
|
||||
|
||||
@ -19,7 +19,9 @@ class SandboxSupport extends SandboxSupportBase {
|
||||
exportValueToSandbox(val) {
|
||||
// The communication with the Quickjs sandbox is based on strings
|
||||
// So we use JSON.stringfy to serialize
|
||||
return JSON.stringify(val);
|
||||
return JSON.stringify(val, (k, v) =>
|
||||
v instanceof Map ? Object.fromEntries(v) : v
|
||||
);
|
||||
}
|
||||
|
||||
importValueFromSandbox(val) {
|
||||
@ -65,7 +67,7 @@ class Sandbox {
|
||||
let success = false;
|
||||
let buf = 0;
|
||||
try {
|
||||
const sandboxData = JSON.stringify(data);
|
||||
const sandboxData = this.support.exportValueToSandbox(data);
|
||||
// "pdfjsScripting.initSandbox..." MUST be the last line to be evaluated
|
||||
// since the returned value is used for the communication.
|
||||
code.push(`pdfjsScripting.initSandbox({ data: ${sandboxData} })`);
|
||||
|
||||
@ -22,7 +22,9 @@ const FieldType = {
|
||||
};
|
||||
|
||||
function createActionsMap(actions) {
|
||||
return new Map(actions ? Object.entries(actions) : null);
|
||||
return actions instanceof Map
|
||||
? actions
|
||||
: new Map(actions ? Object.entries(actions) : null);
|
||||
}
|
||||
|
||||
function getFieldType(actions) {
|
||||
@ -30,10 +32,8 @@ function getFieldType(actions) {
|
||||
if (!format) {
|
||||
return FieldType.none;
|
||||
}
|
||||
format = format[0].trim();
|
||||
|
||||
format = format[0];
|
||||
|
||||
format = format.trim();
|
||||
if (format.startsWith("AFNumber_")) {
|
||||
return FieldType.number;
|
||||
}
|
||||
|
||||
@ -173,9 +173,9 @@ class Doc extends PDFObject {
|
||||
_dispatchPageEvent(name, actions, pageNumber) {
|
||||
if (name === "PageOpen") {
|
||||
this.#pageActions ??= new Map();
|
||||
if (!this.#pageActions.has(pageNumber)) {
|
||||
this.#pageActions.set(pageNumber, createActionsMap(actions));
|
||||
}
|
||||
this.#pageActions.getOrInsertComputed(pageNumber, () =>
|
||||
createActionsMap(actions)
|
||||
);
|
||||
this._pageNum = pageNumber - 1;
|
||||
}
|
||||
|
||||
|
||||
@ -2460,17 +2460,14 @@ describe("annotation", function () {
|
||||
annotationGlobalsMock,
|
||||
idFactoryMock
|
||||
);
|
||||
const fieldObject = await annotation.getFieldObject();
|
||||
const actions = fieldObject.actions;
|
||||
expect(actions["Mouse Enter"]).toEqual(["hello()"]);
|
||||
expect(actions["Mouse Exit"]).toEqual([
|
||||
"world()",
|
||||
"olleh()",
|
||||
"foo()",
|
||||
"dlrow()",
|
||||
"oof()",
|
||||
]);
|
||||
expect(actions["Mouse Down"]).toEqual(["bar()"]);
|
||||
const { actions } = await annotation.getFieldObject();
|
||||
expect(actions).toEqual(
|
||||
new Map([
|
||||
["Mouse Enter", ["hello()"]],
|
||||
["Mouse Exit", ["world()", "olleh()", "foo()", "dlrow()", "oof()"]],
|
||||
["Mouse Down", ["bar()"]],
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it("should save Japanese text", async function () {
|
||||
@ -3728,7 +3725,7 @@ describe("annotation", function () {
|
||||
);
|
||||
expect(data.annotationType).toEqual(AnnotationType.WIDGET);
|
||||
expect(data.pushButton).toBeTrue();
|
||||
expect(data.actions.Action).toEqual(["do_something();"]);
|
||||
expect(data.actions.get("Action")).toEqual(["do_something();"]);
|
||||
});
|
||||
|
||||
it("should handle push buttons that act as a tooltip only", async function () {
|
||||
|
||||
@ -986,9 +986,9 @@ describe("api", function () {
|
||||
expect(pdfDocument.numPages).toEqual(1);
|
||||
|
||||
const jsActions = await pdfDocument.getJSActions();
|
||||
expect(jsActions).toEqual({
|
||||
OpenAction: ["func=function(){app.alert(1)};func();"],
|
||||
});
|
||||
expect(jsActions).toEqual(
|
||||
new Map([["OpenAction", ["func=function(){app.alert(1)};func();"]]])
|
||||
);
|
||||
|
||||
const page = await pdfDocument.getPage(1);
|
||||
expect(page).toBeInstanceOf(PDFPageProxy);
|
||||
@ -1943,12 +1943,17 @@ describe("api", function () {
|
||||
// PDF document with "JavaScript" action in the OpenAction dictionary.
|
||||
const loadingTask = getDocument(buildGetDocumentParams("issue6106.pdf"));
|
||||
const pdfDoc = await loadingTask.promise;
|
||||
const { OpenAction } = await pdfDoc.getJSActions();
|
||||
const jsActions = await pdfDoc.getJSActions();
|
||||
|
||||
expect(OpenAction).toEqual([
|
||||
"this.print({bUI:true,bSilent:false,bShrinkToFit:true});",
|
||||
]);
|
||||
expect(OpenAction[0]).toMatch(AutoPrintRegExp);
|
||||
expect(jsActions).toEqual(
|
||||
new Map([
|
||||
[
|
||||
"OpenAction",
|
||||
["this.print({bUI:true,bSilent:false,bShrinkToFit:true});"],
|
||||
],
|
||||
])
|
||||
);
|
||||
expect(jsActions.get("OpenAction")[0]).toMatch(AutoPrintRegExp);
|
||||
|
||||
await loadingTask.destroy();
|
||||
});
|
||||
@ -1988,21 +1993,27 @@ describe("api", function () {
|
||||
const page3 = await pdfDoc.getPage(3);
|
||||
const page3Actions = await page3.getJSActions();
|
||||
|
||||
expect(docActions).toEqual({
|
||||
DidPrint: [`this.getField("Text2").value = "DidPrint";`],
|
||||
DidSave: [`this.getField("Text2").value = "DidSave";`],
|
||||
WillClose: [`this.getField("Text1").value = "WillClose";`],
|
||||
WillPrint: [`this.getField("Text1").value = "WillPrint";`],
|
||||
WillSave: [`this.getField("Text1").value = "WillSave";`],
|
||||
});
|
||||
expect(page1Actions).toEqual({
|
||||
PageOpen: [`this.getField("Text1").value = "PageOpen 1";`],
|
||||
PageClose: [`this.getField("Text2").value = "PageClose 1";`],
|
||||
});
|
||||
expect(page3Actions).toEqual({
|
||||
PageOpen: [`this.getField("Text5").value = "PageOpen 3";`],
|
||||
PageClose: [`this.getField("Text6").value = "PageClose 3";`],
|
||||
});
|
||||
expect(docActions).toEqual(
|
||||
new Map([
|
||||
["DidPrint", [`this.getField("Text2").value = "DidPrint";`]],
|
||||
["DidSave", [`this.getField("Text2").value = "DidSave";`]],
|
||||
["WillClose", [`this.getField("Text1").value = "WillClose";`]],
|
||||
["WillPrint", [`this.getField("Text1").value = "WillPrint";`]],
|
||||
["WillSave", [`this.getField("Text1").value = "WillSave";`]],
|
||||
])
|
||||
);
|
||||
expect(page1Actions).toEqual(
|
||||
new Map([
|
||||
["PageOpen", [`this.getField("Text1").value = "PageOpen 1";`]],
|
||||
["PageClose", [`this.getField("Text2").value = "PageClose 1";`]],
|
||||
])
|
||||
);
|
||||
expect(page3Actions).toEqual(
|
||||
new Map([
|
||||
["PageOpen", [`this.getField("Text5").value = "PageOpen 3";`]],
|
||||
["PageClose", [`this.getField("Text6").value = "PageClose 3";`]],
|
||||
])
|
||||
);
|
||||
|
||||
await loadingTask.destroy();
|
||||
});
|
||||
@ -2051,11 +2062,14 @@ describe("api", function () {
|
||||
name: "Button1",
|
||||
rect: [455.436, 719.678, 527.436, 739.678],
|
||||
hidden: false,
|
||||
actions: {
|
||||
Action: [
|
||||
`this.getField("Text1").value = this.info.authors.join("::");`,
|
||||
actions: new Map([
|
||||
[
|
||||
"Action",
|
||||
[
|
||||
`this.getField("Text1").value = this.info.authors.join("::");`,
|
||||
],
|
||||
],
|
||||
},
|
||||
]),
|
||||
page: 0,
|
||||
strokeColor: null,
|
||||
fillColor: new Uint8ClampedArray([192, 192, 192]),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user