Merge pull request #21671 from Snuffleupagus/getFieldObjects-Map

[api-minor] Convert `getFieldObjects` to return data in a Map
This commit is contained in:
Tim van der Meij 2026-08-02 22:37:22 +02:00 committed by GitHub
commit ae976b924b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 190 additions and 173 deletions

View File

@ -1,5 +1,5 @@
{
"stableVersion": "6.2.108",
"baseVersion": "eddd70a2ca1054ad2e0792972c3f2774b89f0cd2",
"versionPrefix": "6.2."
"baseVersion": "ce4ff55faaa83b39b0137dc458af6eea6f96235f",
"versionPrefix": "6.3."
}

View File

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

View File

@ -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]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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