diff --git a/src/core/document.js b/src/core/document.js index a7b1342ef999a..00467497feb34 100644 --- a/src/core/document.js +++ b/src/core/document.js @@ -1711,12 +1711,19 @@ class PDFDocument { : clearGlobalCaches(); } - async #collectFieldObjects(name, fieldRef, promises, annotationGlobals) { + async #collectFieldObjects( + name, + fieldRef, + promises, + annotationGlobals, + visitedRefs + ) { const { xref } = this; - if (!(fieldRef instanceof Ref)) { + if (!(fieldRef instanceof Ref) || visitedRefs.has(fieldRef)) { return; } + visitedRefs.put(fieldRef); const field = await xref.fetchAsync(fieldRef); if (!(field instanceof Dict)) { return; @@ -1724,6 +1731,25 @@ class PDFDocument { if (field.has("T")) { const partName = stringToPDFString(await field.getAsync("T")); name = name === "" ? partName : `${name}.${partName}`; + } else { + let obj = field; + while (true) { + obj = obj.getRaw("Parent"); + if (obj instanceof Ref) { + if (visitedRefs.has(obj)) { + break; + } + obj = await xref.fetchAsync(obj); + } + if (!(obj instanceof Dict)) { + break; + } + if (obj.has("T")) { + const partName = stringToPDFString(await obj.getAsync("T")); + name = name === "" ? partName : `${name}.${partName}`; + break; + } + } } if (!promises.has(name)) { @@ -1751,7 +1777,13 @@ class PDFDocument { const kids = await field.getAsync("Kids"); if (Array.isArray(kids)) { for (const kid of kids) { - await this.#collectFieldObjects(name, kid, promises, annotationGlobals); + await this.#collectFieldObjects( + name, + kid, + promises, + annotationGlobals, + visitedRefs + ); } } } @@ -1769,6 +1801,7 @@ class PDFDocument { return null; } + const visitedRefs = new RefSet(); const allFields = Object.create(null); const fieldPromises = new Map(); for (const fieldRef of await acroForm.getAsync("Fields")) { @@ -1776,7 +1809,8 @@ class PDFDocument { "", fieldRef, fieldPromises, - annotationGlobals + annotationGlobals, + visitedRefs ); } diff --git a/test/integration/scripting_spec.mjs b/test/integration/scripting_spec.mjs index 52b60b1dbd237..1154c0869c9aa 100644 --- a/test/integration/scripting_spec.mjs +++ b/test/integration/scripting_spec.mjs @@ -16,6 +16,7 @@ import { clearInput, closePages, + getAnnotationStorage, getComputedStyleSelector, getFirstSerialized, getQuerySelector, @@ -24,6 +25,7 @@ import { kbSelectAll, loadAndWait, scrollIntoView, + waitForEntryInStorage, } from "./test_utils.mjs"; describe("Interaction", () => { @@ -2225,4 +2227,58 @@ describe("Interaction", () => { ); }); }); + + describe("Radio button without T value", () => { + let pages; + let otherPages; + + beforeAll(async () => { + otherPages = await Promise.all( + global.integrationSessions.map(async session => + session.browser.newPage() + ) + ); + pages = await loadAndWait("bug1860602.pdf", getSelector("22R")); + }); + + afterAll(async () => { + await closePages(pages); + await Promise.all(otherPages.map(page => page.close())); + }); + + it("must check that only one radio is selected", async () => { + await Promise.all( + pages.map(async ([browserName, page], i) => { + await page.waitForFunction( + "window.PDFViewerApplication.scriptingReady === true" + ); + await scrollIntoView(page, getSelector("22R")); + + await page.click(getSelector("25R")); + await waitForEntryInStorage(page, "25R", { value: true }); + + let storage = await getAnnotationStorage(page); + expect(storage) + .withContext(`In ${browserName}`) + .toEqual({ + "22R": { value: false }, + "25R": { value: true }, + "28R": { value: false }, + }); + + await page.click(getSelector("22R")); + await waitForEntryInStorage(page, "22R", { value: true }); + + storage = await getAnnotationStorage(page); + expect(storage) + .withContext(`In ${browserName}`) + .toEqual({ + "22R": { value: true }, + "25R": { value: false }, + "28R": { value: false }, + }); + }) + ); + }); + }); }); diff --git a/test/integration/test_utils.mjs b/test/integration/test_utils.mjs index ea8c06d961018..05e43f4a12418 100644 --- a/test/integration/test_utils.mjs +++ b/test/integration/test_utils.mjs @@ -176,6 +176,28 @@ async function getFirstSerialized(page, filter = undefined) { return (await getSerialized(page, filter))[0]; } +function getAnnotationStorage(page) { + return page.evaluate(() => + Object.fromEntries( + window.PDFViewerApplication.pdfDocument.annotationStorage.serializable.map?.entries() || + [] + ) + ); +} + +function waitForEntryInStorage(page, key, value) { + return page.waitForFunction( + (k, v) => { + const { map } = + window.PDFViewerApplication.pdfDocument.annotationStorage.serializable; + return map && JSON.stringify(map.get(k)) === v; + }, + {}, + key, + JSON.stringify(value) + ); +} + function getEditors(page, kind) { return page.evaluate(aKind => { const elements = document.querySelectorAll(`.${aKind}Editor`); @@ -398,6 +420,7 @@ export { clearInput, closePages, dragAndDropAnnotation, + getAnnotationStorage, getComputedStyleSelector, getEditorDimensions, getEditors, @@ -427,6 +450,7 @@ export { scrollIntoView, serializeBitmapDimensions, waitForAnnotationEditorLayer, + waitForEntryInStorage, waitForEvent, waitForSelectedEditor, waitForSerialized,