mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-08-04 05:17:24 +02:00
[api-minor] Convert getFieldObjects to return data in a Map
Compared to regular Objects there's a number of advantages to using Maps: - They support proper iteration. - They have a simple way to check for the existence of data. - They have a simple/efficient way to check the number of elements. If this functionality was added today, I cannot imagine that we'd choose an Object for this data. In the Firefox PDF Viewer sending Maps to the scripting-implementation should be fine, since it uses the browser `Cu.cloneInto` functionality; see https://searchfox.org/firefox-main/source/toolkit/components/pdfjs/content/PdfSandbox.sys.mjs However with QuickJS, used by the GENERIC viewer, all data needs to be stringified and Maps are converted into regular Objects (see also PR 21664). Hence the `objects` property, in the scripting-implementation, is converted back into a Map using the (renamed) `createMap` helper function.
This commit is contained in:
parent
7590ad5312
commit
82624a5e50
@ -1961,7 +1961,7 @@ class PDFDocument {
|
||||
const { acroForm } = annotationGlobals;
|
||||
|
||||
const visitedRefs = new RefSet();
|
||||
const allFields = Object.create(null);
|
||||
const allFields = new Map();
|
||||
const fieldPromises = new Map();
|
||||
const orphanFields = new RefSetCache();
|
||||
for (const fieldRef of acroForm.get("Fields")) {
|
||||
@ -1982,7 +1982,7 @@ class PDFDocument {
|
||||
Promise.all(promises).then(fields => {
|
||||
fields = fields.filter(field => !!field);
|
||||
if (fields.length > 0) {
|
||||
allFields[name] = fields;
|
||||
allFields.set(name, fields);
|
||||
}
|
||||
})
|
||||
);
|
||||
@ -1990,7 +1990,7 @@ class PDFDocument {
|
||||
await Promise.all(allPromises);
|
||||
|
||||
return {
|
||||
allFields: Object.keys(allFields).length ? allFields : null,
|
||||
allFields: allFields.size ? allFields : null,
|
||||
orphanFields,
|
||||
};
|
||||
});
|
||||
@ -2268,9 +2268,9 @@ class PDFDocument {
|
||||
return true;
|
||||
}
|
||||
if (fieldObjects?.allFields) {
|
||||
return Object.values(fieldObjects.allFields).some(fieldObject =>
|
||||
fieldObject.some(object => object.actions !== null)
|
||||
);
|
||||
return fieldObjects.allFields
|
||||
.values()
|
||||
.some(fieldObj => fieldObj.some(obj => obj.actions !== null));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -75,7 +75,7 @@ const TIMEZONE_OFFSET = new Date().getTimezoneOffset() * 60 * 1000;
|
||||
* @property {Object} svgFactory
|
||||
* @property {boolean} [enableScripting]
|
||||
* @property {boolean} [hasJSActions]
|
||||
* @property {Object} [fieldObjects]
|
||||
* @property {Map} [fieldObjects]
|
||||
*/
|
||||
|
||||
class AnnotationElementFactory {
|
||||
@ -803,7 +803,7 @@ class AnnotationElement {
|
||||
const fields = [];
|
||||
|
||||
if (this._fieldObjects) {
|
||||
const fieldObj = this._fieldObjects[name] || [];
|
||||
const fieldObj = this._fieldObjects.get(name) || [];
|
||||
|
||||
for (const { page, id, exportValues } of fieldObj) {
|
||||
if (page === -1) {
|
||||
@ -1217,12 +1217,12 @@ class LinkAnnotationElement extends AnnotationElement {
|
||||
if (resetFormFields.length !== 0 || resetFormRefs.length !== 0) {
|
||||
const fieldIds = new Set(resetFormRefs);
|
||||
for (const fieldName of resetFormFields) {
|
||||
const fields = this._fieldObjects[fieldName] || [];
|
||||
const fields = this._fieldObjects.get(fieldName) || [];
|
||||
for (const { id } of fields) {
|
||||
fieldIds.add(id);
|
||||
}
|
||||
}
|
||||
for (const fields of Object.values(this._fieldObjects)) {
|
||||
for (const fields of this._fieldObjects.values()) {
|
||||
for (const field of fields) {
|
||||
if (fieldIds.has(field.id) === include) {
|
||||
allFields.push(field);
|
||||
@ -1230,7 +1230,7 @@ class LinkAnnotationElement extends AnnotationElement {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const fields of Object.values(this._fieldObjects)) {
|
||||
for (const fields of this._fieldObjects.values()) {
|
||||
allFields.push(...fields);
|
||||
}
|
||||
}
|
||||
@ -3953,7 +3953,7 @@ class MediaAnnotationElement extends AnnotationElement {
|
||||
* @property {boolean} [enableScripting] - Enable embedded script execution.
|
||||
* @property {boolean} [hasJSActions] - Some fields have JS actions.
|
||||
* The default value is `false`.
|
||||
* @property {Object<string, Array<Object>> | null} [fieldObjects]
|
||||
* @property {Map<string, Array<Object>> | null} [fieldObjects]
|
||||
* @property {Map<string, HTMLCanvasElement>} [annotationCanvasMap]
|
||||
* @property {TextAccessibilityManager} [accessibilityManager]
|
||||
* @property {AnnotationEditorUIManager} [annotationEditorUIManager]
|
||||
|
||||
@ -1069,9 +1069,9 @@ class PDFDocumentProxy {
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<Object<string, Array<Object>> | null>} A promise that is
|
||||
* resolved with an {Object} containing /AcroForm field data for the JS
|
||||
* sandbox, or `null` when no field data is present in the PDF file.
|
||||
* @returns {Promise<Map<string, Array<Object>> | null>} A promise that is
|
||||
* resolved with a {Map} containing /AcroForm field data for the JS sandbox,
|
||||
* or `null` when no field data is present in the PDF file.
|
||||
*/
|
||||
getFieldObjects() {
|
||||
return this._transport.getFieldObjects();
|
||||
|
||||
@ -21,10 +21,8 @@ const FieldType = {
|
||||
time: 4,
|
||||
};
|
||||
|
||||
function createActionsMap(actions) {
|
||||
return actions instanceof Map
|
||||
? actions
|
||||
: new Map(actions ? Object.entries(actions) : null);
|
||||
function createMap(val) {
|
||||
return val instanceof Map ? val : new Map(val ? Object.entries(val) : null);
|
||||
}
|
||||
|
||||
function getFieldType(actions) {
|
||||
@ -49,4 +47,4 @@ function getFieldType(actions) {
|
||||
return FieldType.none;
|
||||
}
|
||||
|
||||
export { createActionsMap, FieldType, getFieldType };
|
||||
export { createMap, FieldType, getFieldType };
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
*/
|
||||
|
||||
import { makeArr, makeMap, serializeError } from "./app_utils.js";
|
||||
import { createActionsMap } from "./common.js";
|
||||
import { createMap } from "./common.js";
|
||||
import { PDFObject } from "./pdf_object.js";
|
||||
import { PrintParams } from "./print_params.js";
|
||||
import { ZoomType } from "./constants.js";
|
||||
@ -98,7 +98,7 @@ class Doc extends PDFObject {
|
||||
|
||||
this._zoomType = ZoomType.none;
|
||||
this._zoom = data.zoom || 100;
|
||||
this._actions = createActionsMap(data.actions);
|
||||
this._actions = createMap(data.actions);
|
||||
this._globalEval = data.globalEval;
|
||||
this._userActivation = false;
|
||||
this._disablePrinting = false;
|
||||
@ -174,7 +174,7 @@ class Doc extends PDFObject {
|
||||
if (name === "PageOpen") {
|
||||
this.#pageActions ??= new Map();
|
||||
this.#pageActions.getOrInsertComputed(pageNumber, () =>
|
||||
createActionsMap(actions)
|
||||
createMap(actions)
|
||||
);
|
||||
this._pageNum = pageNumber - 1;
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createActionsMap, FieldType, getFieldType } from "./common.js";
|
||||
import { createMap, FieldType, getFieldType } from "./common.js";
|
||||
import { makeArr, serializeError } from "./app_utils.js";
|
||||
import { Color } from "./color.js";
|
||||
import { PDFObject } from "./pdf_object.js";
|
||||
@ -65,7 +65,7 @@ class Field extends PDFObject {
|
||||
this.userName = data.userName;
|
||||
|
||||
// Private
|
||||
this._actions = createActionsMap(data.actions);
|
||||
this._actions = createMap(data.actions);
|
||||
this._browseForFileToSubmit = data.browseForFileToSubmit || null;
|
||||
this._buttonCaption = null;
|
||||
this._buttonIcon = null;
|
||||
@ -578,7 +578,7 @@ class RadioButtonField extends Field {
|
||||
for (const radioData of otherButtons) {
|
||||
this.exportValues.push(radioData.exportValues);
|
||||
this._radioIds.push(radioData.id);
|
||||
this._radioActions.push(createActionsMap(radioData.actions));
|
||||
this._radioActions.push(createMap(radioData.actions));
|
||||
if (this._value === radioData.exportValues) {
|
||||
this._id = radioData.id;
|
||||
}
|
||||
|
||||
@ -32,6 +32,7 @@ import { AForm } from "./aform.js";
|
||||
import { App } from "./app.js";
|
||||
import { Color } from "./color.js";
|
||||
import { Console } from "./console.js";
|
||||
import { createMap } from "./common.js";
|
||||
import { Doc } from "./doc.js";
|
||||
import { ProxyHandler } from "./proxy.js";
|
||||
import { serializeError } from "./app_utils.js";
|
||||
@ -70,11 +71,8 @@ function initSandbox(params) {
|
||||
const util = new Util({ externalCall });
|
||||
const appObjects = app._objects;
|
||||
|
||||
if (data.objects) {
|
||||
for (const [name, objs] of createMap(data.objects)) {
|
||||
const annotations = [];
|
||||
|
||||
for (const [name, objs] of Object.entries(data.objects)) {
|
||||
annotations.length = 0;
|
||||
let container = null;
|
||||
|
||||
for (const obj of objs) {
|
||||
@ -126,7 +124,6 @@ function initSandbox(params) {
|
||||
appObjects[container.id] = _object;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const color = new Color();
|
||||
|
||||
|
||||
@ -2028,8 +2028,11 @@ describe("api", function () {
|
||||
const pdfDoc = await loadingTask.promise;
|
||||
const fieldObjects = await pdfDoc.getFieldObjects();
|
||||
|
||||
expect(fieldObjects).toEqual({
|
||||
Text1: [
|
||||
expect(fieldObjects).toEqual(
|
||||
new Map([
|
||||
[
|
||||
"Text1",
|
||||
[
|
||||
{
|
||||
id: "25R",
|
||||
value: "",
|
||||
@ -2052,7 +2055,10 @@ describe("api", function () {
|
||||
type: "text",
|
||||
},
|
||||
],
|
||||
Button1: [
|
||||
],
|
||||
[
|
||||
"Button1",
|
||||
[
|
||||
{
|
||||
id: "26R",
|
||||
value: "Off",
|
||||
@ -2077,7 +2083,9 @@ describe("api", function () {
|
||||
type: "button",
|
||||
},
|
||||
],
|
||||
});
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
await loadingTask.destroy();
|
||||
});
|
||||
@ -2087,8 +2095,8 @@ describe("api", function () {
|
||||
const pdfDoc = await loadingTask.promise;
|
||||
const fieldObjects = await pdfDoc.getFieldObjects();
|
||||
|
||||
for (const name in fieldObjects) {
|
||||
const pageIndexes = fieldObjects[name].map(o => o.page);
|
||||
for (const [name, objs] of fieldObjects) {
|
||||
const pageIndexes = objs.map(o => o.page);
|
||||
let expected;
|
||||
|
||||
switch (name) {
|
||||
@ -7758,9 +7766,12 @@ small scripts as well as for`);
|
||||
|
||||
loadingTask = getDocument({ data: extracted });
|
||||
pdfDoc = await loadingTask.promise;
|
||||
expect(Object.keys(await pdfDoc.getFieldObjects())).toEqual(["group"]);
|
||||
|
||||
const fieldObjects = await pdfDoc.getFieldObjects();
|
||||
expect([...fieldObjects.keys()]).toEqual(["group"]);
|
||||
const annotations = await (await pdfDoc.getPage(1)).getAnnotations();
|
||||
expect(annotations[0].fieldName).toEqual("group");
|
||||
|
||||
await loadingTask.destroy();
|
||||
});
|
||||
|
||||
@ -7788,7 +7799,10 @@ small scripts as well as for`);
|
||||
|
||||
loadingTask = getDocument({ data });
|
||||
pdfDoc = await loadingTask.promise;
|
||||
expect(Object.keys(await pdfDoc.getFieldObjects())).toEqual(["field"]);
|
||||
|
||||
const fieldObjects = await pdfDoc.getFieldObjects();
|
||||
expect([...fieldObjects.keys()]).toEqual(["field"]);
|
||||
|
||||
await loadingTask.destroy();
|
||||
});
|
||||
|
||||
@ -7846,9 +7860,10 @@ small scripts as well as for`);
|
||||
|
||||
loadingTask = getDocument({ data });
|
||||
pdfDoc = await loadingTask.promise;
|
||||
expect(Object.keys(await pdfDoc.getFieldObjects())).toEqual([
|
||||
"signature",
|
||||
]);
|
||||
|
||||
const fieldObjects = await pdfDoc.getFieldObjects();
|
||||
expect([...fieldObjects.keys()]).toEqual(["signature"]);
|
||||
|
||||
await loadingTask.destroy();
|
||||
});
|
||||
|
||||
@ -7894,7 +7909,7 @@ small scripts as well as for`);
|
||||
// reflects the T entries of the fields in the AcroForm dictionary.
|
||||
const fieldObjects = await pdfDoc.getFieldObjects();
|
||||
expect(fieldObjects).not.toBeNull();
|
||||
expect(Object.keys(fieldObjects).sort()).toEqual(origFieldNames);
|
||||
expect([...fieldObjects.keys()].sort()).toEqual(origFieldNames);
|
||||
|
||||
await loadingTask.destroy();
|
||||
});
|
||||
@ -7952,7 +7967,7 @@ small scripts as well as for`);
|
||||
const allOrigFieldNames = [
|
||||
...new Set([...origPage1FieldNames, ...origPage2FieldNames]),
|
||||
].sort();
|
||||
expect(Object.keys(fieldObjects).sort()).toEqual(allOrigFieldNames);
|
||||
expect([...fieldObjects.keys()].sort()).toEqual(allOrigFieldNames);
|
||||
|
||||
await loadingTask.destroy();
|
||||
});
|
||||
@ -7964,9 +7979,8 @@ small scripts as well as for`);
|
||||
let pdfDoc = await loadingTask.promise;
|
||||
|
||||
expect(await pdfDoc.getCalculationOrderIds()).toEqual(["6R"]);
|
||||
expect(Object.keys((await pdfDoc.getFieldObjects()) || {})).toEqual([
|
||||
"group",
|
||||
]);
|
||||
const fieldObjects1 = await pdfDoc.getFieldObjects();
|
||||
expect([...fieldObjects1.keys()]).toEqual(["group"]);
|
||||
|
||||
const data = await pdfDoc.extractPages([{ document: null }]);
|
||||
await loadingTask.destroy();
|
||||
@ -7978,9 +7992,8 @@ small scripts as well as for`);
|
||||
expect(Array.isArray(calculationOrder)).toBeTrue();
|
||||
expect(calculationOrder.length).toEqual(1);
|
||||
expect(calculationOrder[0]).not.toEqual("6R");
|
||||
expect(Object.keys((await pdfDoc.getFieldObjects()) || {})).toEqual([
|
||||
"group",
|
||||
]);
|
||||
const fieldObjects2 = await pdfDoc.getFieldObjects();
|
||||
expect([...fieldObjects2.keys()]).toEqual(["group"]);
|
||||
|
||||
await loadingTask.destroy();
|
||||
});
|
||||
@ -8023,9 +8036,9 @@ small scripts as well as for`);
|
||||
loadingTask = getDocument({ data });
|
||||
pdfDoc = await loadingTask.promise;
|
||||
expect(pdfDoc.numPages).toEqual(2);
|
||||
expect(
|
||||
Object.keys((await pdfDoc.getFieldObjects()) ?? {}).sort()
|
||||
).toEqual(["first", "second"]);
|
||||
const fieldObjects = await pdfDoc.getFieldObjects();
|
||||
expect([...fieldObjects.keys()].sort()).toEqual(["first", "second"]);
|
||||
|
||||
for (const pageNumber of [1, 2]) {
|
||||
const fontName = await getAppearanceFontName(pdfDoc, pageNumber);
|
||||
expect(fontName).not.toBeNull();
|
||||
@ -8064,9 +8077,9 @@ small scripts as well as for`);
|
||||
loadingTask = getDocument({ data });
|
||||
pdfDoc = await loadingTask.promise;
|
||||
expect(pdfDoc.numPages).toEqual(2);
|
||||
expect(
|
||||
Object.keys((await pdfDoc.getFieldObjects()) ?? {}).sort()
|
||||
).toEqual(["broken", "main"]);
|
||||
const fieldObjects = await pdfDoc.getFieldObjects();
|
||||
expect([...fieldObjects.keys()].sort()).toEqual(["broken", "main"]);
|
||||
|
||||
const fontName = await getAppearanceFontName(pdfDoc, 2);
|
||||
expect(fontName).not.toBeNull();
|
||||
expect(fontName).not.toEqual("g_font_error");
|
||||
@ -8109,9 +8122,9 @@ small scripts as well as for`);
|
||||
loadingTask = getDocument({ data });
|
||||
pdfDoc = await loadingTask.promise;
|
||||
expect(pdfDoc.numPages).toEqual(2);
|
||||
expect(
|
||||
Object.keys((await pdfDoc.getFieldObjects()) ?? {}).sort()
|
||||
).toEqual(["check", "main"]);
|
||||
const fieldObjects = await pdfDoc.getFieldObjects();
|
||||
expect([...fieldObjects.keys()].sort()).toEqual(["check", "main"]);
|
||||
|
||||
const fontName = await getAppearanceFontName(pdfDoc, 2);
|
||||
expect(fontName).not.toBeNull();
|
||||
expect(fontName).not.toEqual("g_font_error");
|
||||
|
||||
@ -628,39 +628,43 @@ describe("document", function () {
|
||||
const kid2BisRef = Ref.get(266, 0);
|
||||
const parentRef = Ref.get(358, 0);
|
||||
|
||||
const allFields = Object.create(null);
|
||||
const allFieldsObj = Object.create(null);
|
||||
for (const name of ["parent", "kid1", "kid2", "kid11"]) {
|
||||
const buttonWidgetDict = new Dict();
|
||||
buttonWidgetDict.set("Type", Name.get("Annot"));
|
||||
buttonWidgetDict.set("Subtype", Name.get("Widget"));
|
||||
buttonWidgetDict.set("FT", Name.get("Btn"));
|
||||
buttonWidgetDict.set("T", name);
|
||||
allFields[name] = buttonWidgetDict;
|
||||
allFieldsObj[name] = buttonWidgetDict;
|
||||
}
|
||||
|
||||
allFields.kid1.set("Kids", [kid11Ref]);
|
||||
allFields.parent.set("Kids", [kid1Ref, kid2Ref, kid2BisRef]);
|
||||
allFieldsObj.kid1.set("Kids", [kid11Ref]);
|
||||
allFieldsObj.parent.set("Kids", [kid1Ref, kid2Ref, kid2BisRef]);
|
||||
|
||||
const xref = new XRefMock([
|
||||
{ ref: parentRef, data: allFields.parent },
|
||||
{ ref: kid1Ref, data: allFields.kid1 },
|
||||
{ ref: kid11Ref, data: allFields.kid11 },
|
||||
{ ref: kid2Ref, data: allFields.kid2 },
|
||||
{ ref: kid2BisRef, data: allFields.kid2 },
|
||||
{ ref: parentRef, data: allFieldsObj.parent },
|
||||
{ ref: kid1Ref, data: allFieldsObj.kid1 },
|
||||
{ ref: kid11Ref, data: allFieldsObj.kid11 },
|
||||
{ ref: kid2Ref, data: allFieldsObj.kid2 },
|
||||
{ ref: kid2BisRef, data: allFieldsObj.kid2 },
|
||||
]);
|
||||
|
||||
acroForm.set("Fields", [parentRef]);
|
||||
pdfDocument = getDocument(acroForm, xref);
|
||||
fields = (await pdfDocument.fieldObjects).allFields;
|
||||
|
||||
for (const [name, objs] of Object.entries(fields)) {
|
||||
fields[name] = objs.map(obj => obj.id);
|
||||
}
|
||||
const { allFields, orphanFields } = await pdfDocument.fieldObjects;
|
||||
|
||||
expect(fields["parent.kid1"]).toEqual(["314R"]);
|
||||
expect(fields["parent.kid1.kid11"]).toEqual(["159R"]);
|
||||
expect(fields["parent.kid2"]).toEqual(["265R", "266R"]);
|
||||
expect(fields.parent).toEqual(["358R"]);
|
||||
const objIds = Array.from(allFields.entries(), ([name, objs]) => [
|
||||
name,
|
||||
objs.map(obj => obj.id),
|
||||
]);
|
||||
expect(objIds).toEqual([
|
||||
["parent", ["358R"]],
|
||||
["parent.kid1", ["314R"]],
|
||||
["parent.kid1.kid11", ["159R"]],
|
||||
["parent.kid2", ["265R", "266R"]],
|
||||
]);
|
||||
expect(orphanFields.size).toEqual(3);
|
||||
});
|
||||
|
||||
it("should get field objects with a circular `Parent` chain", async function () {
|
||||
@ -693,9 +697,14 @@ describe("document", function () {
|
||||
acroForm.set("Fields", [widgetRef]);
|
||||
const pdfDocument = getDocument(acroForm, xref);
|
||||
|
||||
const { allFields } = await pdfDocument.fieldObjects;
|
||||
expect(Object.keys(allFields)).toEqual([""]);
|
||||
expect(allFields[""].map(obj => obj.id)).toEqual(["1R"]);
|
||||
const { allFields, orphanFields } = await pdfDocument.fieldObjects;
|
||||
|
||||
const objIds = Array.from(allFields.entries(), ([name, objs]) => [
|
||||
name,
|
||||
objs.map(obj => obj.id),
|
||||
]);
|
||||
expect(objIds).toEqual([["", ["1R"]]]);
|
||||
expect(orphanFields.size).toEqual(0);
|
||||
});
|
||||
|
||||
it("should check if fields have any actions", async function () {
|
||||
|
||||
@ -111,7 +111,7 @@ class PDFScriptingManager {
|
||||
// targeting an unknown id can be ignored.
|
||||
if (objects) {
|
||||
this.#objectIds = new Set();
|
||||
for (const fields of Object.values(objects)) {
|
||||
for (const fields of objects.values()) {
|
||||
for (const { id } of fields) {
|
||||
this.#objectIds.add(id);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user