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", "stableVersion": "6.2.108",
"baseVersion": "eddd70a2ca1054ad2e0792972c3f2774b89f0cd2", "baseVersion": "ce4ff55faaa83b39b0137dc458af6eea6f96235f",
"versionPrefix": "6.2." "versionPrefix": "6.3."
} }

View File

@ -1961,7 +1961,7 @@ class PDFDocument {
const { acroForm } = annotationGlobals; const { acroForm } = annotationGlobals;
const visitedRefs = new RefSet(); const visitedRefs = new RefSet();
const allFields = Object.create(null); const allFields = new Map();
const fieldPromises = new Map(); const fieldPromises = new Map();
const orphanFields = new RefSetCache(); const orphanFields = new RefSetCache();
for (const fieldRef of acroForm.get("Fields")) { for (const fieldRef of acroForm.get("Fields")) {
@ -1982,7 +1982,7 @@ class PDFDocument {
Promise.all(promises).then(fields => { Promise.all(promises).then(fields => {
fields = fields.filter(field => !!field); fields = fields.filter(field => !!field);
if (fields.length > 0) { if (fields.length > 0) {
allFields[name] = fields; allFields.set(name, fields);
} }
}) })
); );
@ -1990,7 +1990,7 @@ class PDFDocument {
await Promise.all(allPromises); await Promise.all(allPromises);
return { return {
allFields: Object.keys(allFields).length ? allFields : null, allFields: allFields.size ? allFields : null,
orphanFields, orphanFields,
}; };
}); });
@ -2268,9 +2268,9 @@ class PDFDocument {
return true; return true;
} }
if (fieldObjects?.allFields) { if (fieldObjects?.allFields) {
return Object.values(fieldObjects.allFields).some(fieldObject => return fieldObjects.allFields
fieldObject.some(object => object.actions !== null) .values()
); .some(fieldObj => fieldObj.some(obj => obj.actions !== null));
} }
return false; return false;
} }

View File

@ -75,7 +75,7 @@ const TIMEZONE_OFFSET = new Date().getTimezoneOffset() * 60 * 1000;
* @property {Object} svgFactory * @property {Object} svgFactory
* @property {boolean} [enableScripting] * @property {boolean} [enableScripting]
* @property {boolean} [hasJSActions] * @property {boolean} [hasJSActions]
* @property {Object} [fieldObjects] * @property {Map} [fieldObjects]
*/ */
class AnnotationElementFactory { class AnnotationElementFactory {
@ -803,7 +803,7 @@ class AnnotationElement {
const fields = []; const fields = [];
if (this._fieldObjects) { if (this._fieldObjects) {
const fieldObj = this._fieldObjects[name] || []; const fieldObj = this._fieldObjects.get(name) || [];
for (const { page, id, exportValues } of fieldObj) { for (const { page, id, exportValues } of fieldObj) {
if (page === -1) { if (page === -1) {
@ -1217,12 +1217,12 @@ class LinkAnnotationElement extends AnnotationElement {
if (resetFormFields.length !== 0 || resetFormRefs.length !== 0) { if (resetFormFields.length !== 0 || resetFormRefs.length !== 0) {
const fieldIds = new Set(resetFormRefs); const fieldIds = new Set(resetFormRefs);
for (const fieldName of resetFormFields) { for (const fieldName of resetFormFields) {
const fields = this._fieldObjects[fieldName] || []; const fields = this._fieldObjects.get(fieldName) || [];
for (const { id } of fields) { for (const { id } of fields) {
fieldIds.add(id); fieldIds.add(id);
} }
} }
for (const fields of Object.values(this._fieldObjects)) { for (const fields of this._fieldObjects.values()) {
for (const field of fields) { for (const field of fields) {
if (fieldIds.has(field.id) === include) { if (fieldIds.has(field.id) === include) {
allFields.push(field); allFields.push(field);
@ -1230,7 +1230,7 @@ class LinkAnnotationElement extends AnnotationElement {
} }
} }
} else { } else {
for (const fields of Object.values(this._fieldObjects)) { for (const fields of this._fieldObjects.values()) {
allFields.push(...fields); allFields.push(...fields);
} }
} }
@ -3953,7 +3953,7 @@ class MediaAnnotationElement extends AnnotationElement {
* @property {boolean} [enableScripting] - Enable embedded script execution. * @property {boolean} [enableScripting] - Enable embedded script execution.
* @property {boolean} [hasJSActions] - Some fields have JS actions. * @property {boolean} [hasJSActions] - Some fields have JS actions.
* The default value is `false`. * 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 {Map<string, HTMLCanvasElement>} [annotationCanvasMap]
* @property {TextAccessibilityManager} [accessibilityManager] * @property {TextAccessibilityManager} [accessibilityManager]
* @property {AnnotationEditorUIManager} [annotationEditorUIManager] * @property {AnnotationEditorUIManager} [annotationEditorUIManager]

View File

@ -1069,9 +1069,9 @@ class PDFDocumentProxy {
} }
/** /**
* @returns {Promise<Object<string, Array<Object>> | null>} A promise that is * @returns {Promise<Map<string, Array<Object>> | null>} A promise that is
* resolved with an {Object} containing /AcroForm field data for the JS * resolved with a {Map} containing /AcroForm field data for the JS sandbox,
* sandbox, or `null` when no field data is present in the PDF file. * or `null` when no field data is present in the PDF file.
*/ */
getFieldObjects() { getFieldObjects() {
return this._transport.getFieldObjects(); return this._transport.getFieldObjects();

View File

@ -21,10 +21,8 @@ const FieldType = {
time: 4, time: 4,
}; };
function createActionsMap(actions) { function createMap(val) {
return actions instanceof Map return val instanceof Map ? val : new Map(val ? Object.entries(val) : null);
? actions
: new Map(actions ? Object.entries(actions) : null);
} }
function getFieldType(actions) { function getFieldType(actions) {
@ -49,4 +47,4 @@ function getFieldType(actions) {
return FieldType.none; 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 { makeArr, makeMap, serializeError } from "./app_utils.js";
import { createActionsMap } from "./common.js"; import { createMap } from "./common.js";
import { PDFObject } from "./pdf_object.js"; import { PDFObject } from "./pdf_object.js";
import { PrintParams } from "./print_params.js"; import { PrintParams } from "./print_params.js";
import { ZoomType } from "./constants.js"; import { ZoomType } from "./constants.js";
@ -98,7 +98,7 @@ class Doc extends PDFObject {
this._zoomType = ZoomType.none; this._zoomType = ZoomType.none;
this._zoom = data.zoom || 100; this._zoom = data.zoom || 100;
this._actions = createActionsMap(data.actions); this._actions = createMap(data.actions);
this._globalEval = data.globalEval; this._globalEval = data.globalEval;
this._userActivation = false; this._userActivation = false;
this._disablePrinting = false; this._disablePrinting = false;
@ -174,7 +174,7 @@ class Doc extends PDFObject {
if (name === "PageOpen") { if (name === "PageOpen") {
this.#pageActions ??= new Map(); this.#pageActions ??= new Map();
this.#pageActions.getOrInsertComputed(pageNumber, () => this.#pageActions.getOrInsertComputed(pageNumber, () =>
createActionsMap(actions) createMap(actions)
); );
this._pageNum = pageNumber - 1; this._pageNum = pageNumber - 1;
} }

View File

@ -13,7 +13,7 @@
* limitations under the License. * 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 { makeArr, serializeError } from "./app_utils.js";
import { Color } from "./color.js"; import { Color } from "./color.js";
import { PDFObject } from "./pdf_object.js"; import { PDFObject } from "./pdf_object.js";
@ -65,7 +65,7 @@ class Field extends PDFObject {
this.userName = data.userName; this.userName = data.userName;
// Private // Private
this._actions = createActionsMap(data.actions); this._actions = createMap(data.actions);
this._browseForFileToSubmit = data.browseForFileToSubmit || null; this._browseForFileToSubmit = data.browseForFileToSubmit || null;
this._buttonCaption = null; this._buttonCaption = null;
this._buttonIcon = null; this._buttonIcon = null;
@ -578,7 +578,7 @@ class RadioButtonField extends Field {
for (const radioData of otherButtons) { for (const radioData of otherButtons) {
this.exportValues.push(radioData.exportValues); this.exportValues.push(radioData.exportValues);
this._radioIds.push(radioData.id); this._radioIds.push(radioData.id);
this._radioActions.push(createActionsMap(radioData.actions)); this._radioActions.push(createMap(radioData.actions));
if (this._value === radioData.exportValues) { if (this._value === radioData.exportValues) {
this._id = radioData.id; this._id = radioData.id;
} }

View File

@ -32,6 +32,7 @@ import { AForm } from "./aform.js";
import { App } from "./app.js"; import { App } from "./app.js";
import { Color } from "./color.js"; import { Color } from "./color.js";
import { Console } from "./console.js"; import { Console } from "./console.js";
import { createMap } from "./common.js";
import { Doc } from "./doc.js"; import { Doc } from "./doc.js";
import { ProxyHandler } from "./proxy.js"; import { ProxyHandler } from "./proxy.js";
import { serializeError } from "./app_utils.js"; import { serializeError } from "./app_utils.js";
@ -70,61 +71,57 @@ function initSandbox(params) {
const util = new Util({ externalCall }); const util = new Util({ externalCall });
const appObjects = app._objects; const appObjects = app._objects;
if (data.objects) { for (const [name, objs] of createMap(data.objects)) {
const annotations = []; const annotations = [];
let container = null;
for (const [name, objs] of Object.entries(data.objects)) { for (const obj of objs) {
annotations.length = 0; if (obj.type !== "") {
let container = null; annotations.push(obj);
} else {
container = obj;
}
}
for (const obj of objs) { let obj = container;
if (obj.type !== "") { if (annotations.length > 0) {
annotations.push(obj); obj = annotations[0];
} else { obj.send = send;
container = obj; }
obj.globalEval = globalEval;
obj.doc = _document;
obj.fieldPath = name;
obj.appObjects = appObjects;
obj.util = util;
const otherFields = annotations.slice(1);
let field;
switch (obj.type) {
case "radiobutton": {
field = new RadioButtonField(otherFields, obj);
break;
}
case "checkbox": {
field = new CheckboxField(otherFields, obj);
break;
}
default:
if (otherFields.length > 0) {
obj.siblings = otherFields.map(x => x.id);
} }
} field = new Field(obj);
}
let obj = container; const wrapped = new Proxy(field, proxyHandler);
if (annotations.length > 0) { const _object = { obj: field, wrapped };
obj = annotations[0]; doc._addField(name, _object);
obj.send = send; for (const object of objs) {
} appObjects[object.id] = _object;
}
obj.globalEval = globalEval; if (container) {
obj.doc = _document; appObjects[container.id] = _object;
obj.fieldPath = name;
obj.appObjects = appObjects;
obj.util = util;
const otherFields = annotations.slice(1);
let field;
switch (obj.type) {
case "radiobutton": {
field = new RadioButtonField(otherFields, obj);
break;
}
case "checkbox": {
field = new CheckboxField(otherFields, obj);
break;
}
default:
if (otherFields.length > 0) {
obj.siblings = otherFields.map(x => x.id);
}
field = new Field(obj);
}
const wrapped = new Proxy(field, proxyHandler);
const _object = { obj: field, wrapped };
doc._addField(name, _object);
for (const object of objs) {
appObjects[object.id] = _object;
}
if (container) {
appObjects[container.id] = _object;
}
} }
} }

View File

@ -2028,56 +2028,64 @@ describe("api", function () {
const pdfDoc = await loadingTask.promise; const pdfDoc = await loadingTask.promise;
const fieldObjects = await pdfDoc.getFieldObjects(); const fieldObjects = await pdfDoc.getFieldObjects();
expect(fieldObjects).toEqual({ expect(fieldObjects).toEqual(
Text1: [ new Map([
{ [
id: "25R", "Text1",
value: "", [
defaultValue: "", {
multiline: false, id: "25R",
password: false, value: "",
charLimit: 0, defaultValue: "",
comb: false, multiline: false,
editable: true, password: false,
hidden: false, charLimit: 0,
name: "Text1", comb: false,
rect: [24.1789, 719.66, 432.22, 741.66], editable: true,
actions: null, hidden: false,
page: 0, name: "Text1",
strokeColor: null, rect: [24.1789, 719.66, 432.22, 741.66],
fillColor: null, actions: null,
rotation: 0, page: 0,
datetimeFormat: undefined, strokeColor: null,
hasDatetimeHTML: false, fillColor: null,
type: "text", rotation: 0,
}, datetimeFormat: undefined,
], hasDatetimeHTML: false,
Button1: [ type: "text",
{ },
id: "26R", ],
value: "Off", ],
defaultValue: null, [
exportValues: undefined, "Button1",
editable: true, [
name: "Button1", {
rect: [455.436, 719.678, 527.436, 739.678], id: "26R",
hidden: false, value: "Off",
actions: new Map([ defaultValue: null,
[ exportValues: undefined,
"Action", editable: true,
[ name: "Button1",
`this.getField("Text1").value = this.info.authors.join("::");`, rect: [455.436, 719.678, 527.436, 739.678],
], hidden: false,
], actions: new Map([
]), [
page: 0, "Action",
strokeColor: null, [
fillColor: new Uint8ClampedArray([192, 192, 192]), `this.getField("Text1").value = this.info.authors.join("::");`,
rotation: 0, ],
type: "button", ],
}, ]),
], page: 0,
}); strokeColor: null,
fillColor: new Uint8ClampedArray([192, 192, 192]),
rotation: 0,
type: "button",
},
],
],
])
);
await loadingTask.destroy(); await loadingTask.destroy();
}); });
@ -2087,8 +2095,8 @@ describe("api", function () {
const pdfDoc = await loadingTask.promise; const pdfDoc = await loadingTask.promise;
const fieldObjects = await pdfDoc.getFieldObjects(); const fieldObjects = await pdfDoc.getFieldObjects();
for (const name in fieldObjects) { for (const [name, objs] of fieldObjects) {
const pageIndexes = fieldObjects[name].map(o => o.page); const pageIndexes = objs.map(o => o.page);
let expected; let expected;
switch (name) { switch (name) {
@ -7758,9 +7766,12 @@ small scripts as well as for`);
loadingTask = getDocument({ data: extracted }); loadingTask = getDocument({ data: extracted });
pdfDoc = await loadingTask.promise; 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(); const annotations = await (await pdfDoc.getPage(1)).getAnnotations();
expect(annotations[0].fieldName).toEqual("group"); expect(annotations[0].fieldName).toEqual("group");
await loadingTask.destroy(); await loadingTask.destroy();
}); });
@ -7788,7 +7799,10 @@ small scripts as well as for`);
loadingTask = getDocument({ data }); loadingTask = getDocument({ data });
pdfDoc = await loadingTask.promise; 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(); await loadingTask.destroy();
}); });
@ -7846,9 +7860,10 @@ small scripts as well as for`);
loadingTask = getDocument({ data }); loadingTask = getDocument({ data });
pdfDoc = await loadingTask.promise; 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(); await loadingTask.destroy();
}); });
@ -7894,7 +7909,7 @@ small scripts as well as for`);
// reflects the T entries of the fields in the AcroForm dictionary. // reflects the T entries of the fields in the AcroForm dictionary.
const fieldObjects = await pdfDoc.getFieldObjects(); const fieldObjects = await pdfDoc.getFieldObjects();
expect(fieldObjects).not.toBeNull(); expect(fieldObjects).not.toBeNull();
expect(Object.keys(fieldObjects).sort()).toEqual(origFieldNames); expect([...fieldObjects.keys()].sort()).toEqual(origFieldNames);
await loadingTask.destroy(); await loadingTask.destroy();
}); });
@ -7952,7 +7967,7 @@ small scripts as well as for`);
const allOrigFieldNames = [ const allOrigFieldNames = [
...new Set([...origPage1FieldNames, ...origPage2FieldNames]), ...new Set([...origPage1FieldNames, ...origPage2FieldNames]),
].sort(); ].sort();
expect(Object.keys(fieldObjects).sort()).toEqual(allOrigFieldNames); expect([...fieldObjects.keys()].sort()).toEqual(allOrigFieldNames);
await loadingTask.destroy(); await loadingTask.destroy();
}); });
@ -7964,9 +7979,8 @@ small scripts as well as for`);
let pdfDoc = await loadingTask.promise; let pdfDoc = await loadingTask.promise;
expect(await pdfDoc.getCalculationOrderIds()).toEqual(["6R"]); expect(await pdfDoc.getCalculationOrderIds()).toEqual(["6R"]);
expect(Object.keys((await pdfDoc.getFieldObjects()) || {})).toEqual([ const fieldObjects1 = await pdfDoc.getFieldObjects();
"group", expect([...fieldObjects1.keys()]).toEqual(["group"]);
]);
const data = await pdfDoc.extractPages([{ document: null }]); const data = await pdfDoc.extractPages([{ document: null }]);
await loadingTask.destroy(); await loadingTask.destroy();
@ -7978,9 +7992,8 @@ small scripts as well as for`);
expect(Array.isArray(calculationOrder)).toBeTrue(); expect(Array.isArray(calculationOrder)).toBeTrue();
expect(calculationOrder.length).toEqual(1); expect(calculationOrder.length).toEqual(1);
expect(calculationOrder[0]).not.toEqual("6R"); expect(calculationOrder[0]).not.toEqual("6R");
expect(Object.keys((await pdfDoc.getFieldObjects()) || {})).toEqual([ const fieldObjects2 = await pdfDoc.getFieldObjects();
"group", expect([...fieldObjects2.keys()]).toEqual(["group"]);
]);
await loadingTask.destroy(); await loadingTask.destroy();
}); });
@ -8023,9 +8036,9 @@ small scripts as well as for`);
loadingTask = getDocument({ data }); loadingTask = getDocument({ data });
pdfDoc = await loadingTask.promise; pdfDoc = await loadingTask.promise;
expect(pdfDoc.numPages).toEqual(2); expect(pdfDoc.numPages).toEqual(2);
expect( const fieldObjects = await pdfDoc.getFieldObjects();
Object.keys((await pdfDoc.getFieldObjects()) ?? {}).sort() expect([...fieldObjects.keys()].sort()).toEqual(["first", "second"]);
).toEqual(["first", "second"]);
for (const pageNumber of [1, 2]) { for (const pageNumber of [1, 2]) {
const fontName = await getAppearanceFontName(pdfDoc, pageNumber); const fontName = await getAppearanceFontName(pdfDoc, pageNumber);
expect(fontName).not.toBeNull(); expect(fontName).not.toBeNull();
@ -8064,9 +8077,9 @@ small scripts as well as for`);
loadingTask = getDocument({ data }); loadingTask = getDocument({ data });
pdfDoc = await loadingTask.promise; pdfDoc = await loadingTask.promise;
expect(pdfDoc.numPages).toEqual(2); expect(pdfDoc.numPages).toEqual(2);
expect( const fieldObjects = await pdfDoc.getFieldObjects();
Object.keys((await pdfDoc.getFieldObjects()) ?? {}).sort() expect([...fieldObjects.keys()].sort()).toEqual(["broken", "main"]);
).toEqual(["broken", "main"]);
const fontName = await getAppearanceFontName(pdfDoc, 2); const fontName = await getAppearanceFontName(pdfDoc, 2);
expect(fontName).not.toBeNull(); expect(fontName).not.toBeNull();
expect(fontName).not.toEqual("g_font_error"); expect(fontName).not.toEqual("g_font_error");
@ -8109,9 +8122,9 @@ small scripts as well as for`);
loadingTask = getDocument({ data }); loadingTask = getDocument({ data });
pdfDoc = await loadingTask.promise; pdfDoc = await loadingTask.promise;
expect(pdfDoc.numPages).toEqual(2); expect(pdfDoc.numPages).toEqual(2);
expect( const fieldObjects = await pdfDoc.getFieldObjects();
Object.keys((await pdfDoc.getFieldObjects()) ?? {}).sort() expect([...fieldObjects.keys()].sort()).toEqual(["check", "main"]);
).toEqual(["check", "main"]);
const fontName = await getAppearanceFontName(pdfDoc, 2); const fontName = await getAppearanceFontName(pdfDoc, 2);
expect(fontName).not.toBeNull(); expect(fontName).not.toBeNull();
expect(fontName).not.toEqual("g_font_error"); expect(fontName).not.toEqual("g_font_error");

View File

@ -628,39 +628,43 @@ describe("document", function () {
const kid2BisRef = Ref.get(266, 0); const kid2BisRef = Ref.get(266, 0);
const parentRef = Ref.get(358, 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"]) { for (const name of ["parent", "kid1", "kid2", "kid11"]) {
const buttonWidgetDict = new Dict(); const buttonWidgetDict = new Dict();
buttonWidgetDict.set("Type", Name.get("Annot")); buttonWidgetDict.set("Type", Name.get("Annot"));
buttonWidgetDict.set("Subtype", Name.get("Widget")); buttonWidgetDict.set("Subtype", Name.get("Widget"));
buttonWidgetDict.set("FT", Name.get("Btn")); buttonWidgetDict.set("FT", Name.get("Btn"));
buttonWidgetDict.set("T", name); buttonWidgetDict.set("T", name);
allFields[name] = buttonWidgetDict; allFieldsObj[name] = buttonWidgetDict;
} }
allFields.kid1.set("Kids", [kid11Ref]); allFieldsObj.kid1.set("Kids", [kid11Ref]);
allFields.parent.set("Kids", [kid1Ref, kid2Ref, kid2BisRef]); allFieldsObj.parent.set("Kids", [kid1Ref, kid2Ref, kid2BisRef]);
const xref = new XRefMock([ const xref = new XRefMock([
{ ref: parentRef, data: allFields.parent }, { ref: parentRef, data: allFieldsObj.parent },
{ ref: kid1Ref, data: allFields.kid1 }, { ref: kid1Ref, data: allFieldsObj.kid1 },
{ ref: kid11Ref, data: allFields.kid11 }, { ref: kid11Ref, data: allFieldsObj.kid11 },
{ ref: kid2Ref, data: allFields.kid2 }, { ref: kid2Ref, data: allFieldsObj.kid2 },
{ ref: kid2BisRef, data: allFields.kid2 }, { ref: kid2BisRef, data: allFieldsObj.kid2 },
]); ]);
acroForm.set("Fields", [parentRef]); acroForm.set("Fields", [parentRef]);
pdfDocument = getDocument(acroForm, xref); pdfDocument = getDocument(acroForm, xref);
fields = (await pdfDocument.fieldObjects).allFields;
for (const [name, objs] of Object.entries(fields)) { const { allFields, orphanFields } = await pdfDocument.fieldObjects;
fields[name] = objs.map(obj => obj.id);
}
expect(fields["parent.kid1"]).toEqual(["314R"]); const objIds = Array.from(allFields.entries(), ([name, objs]) => [
expect(fields["parent.kid1.kid11"]).toEqual(["159R"]); name,
expect(fields["parent.kid2"]).toEqual(["265R", "266R"]); objs.map(obj => obj.id),
expect(fields.parent).toEqual(["358R"]); ]);
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 () { it("should get field objects with a circular `Parent` chain", async function () {
@ -693,9 +697,14 @@ describe("document", function () {
acroForm.set("Fields", [widgetRef]); acroForm.set("Fields", [widgetRef]);
const pdfDocument = getDocument(acroForm, xref); const pdfDocument = getDocument(acroForm, xref);
const { allFields } = await pdfDocument.fieldObjects; const { allFields, orphanFields } = await pdfDocument.fieldObjects;
expect(Object.keys(allFields)).toEqual([""]);
expect(allFields[""].map(obj => obj.id)).toEqual(["1R"]); 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 () { 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. // targeting an unknown id can be ignored.
if (objects) { if (objects) {
this.#objectIds = new Set(); this.#objectIds = new Set();
for (const fields of Object.values(objects)) { for (const fields of objects.values()) {
for (const { id } of fields) { for (const { id } of fields) {
this.#objectIds.add(id); this.#objectIds.add(id);
} }