Merge pull request #21407 from calixteman/fix_hidden_updated_field

Fix form fields with their own canvas updated on non-rendered pages
This commit is contained in:
Tim van der Meij 2026-06-09 20:03:20 +02:00 committed by GitHub
commit c541d24ac3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 183 additions and 17 deletions

View File

@ -1321,15 +1321,6 @@ class WidgetAnnotationElement extends AnnotationElement {
return this.container;
}
showElementAndHideCanvas(element) {
if (this.data.hasOwnCanvas) {
if (element.previousSibling?.nodeName === "CANVAS") {
element.previousSibling.hidden = true;
}
element.hidden = false;
}
}
_getKeyModifier(event) {
return FeatureTest.platform.isMac ? event.metaKey : event.ctrlKey;
}
@ -1547,7 +1538,14 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
}
}
if (this.data.hasOwnCanvas) {
element.hidden = true;
// The rendered appearance (a canvas) is shown instead of this element.
this.container.classList.add("hasOwnCanvas");
if (storage.has(id)) {
// Once the field is modified, the `sandboxModified` class hides the
// (now outdated) canvas and shows this element instead.
// The field can already have been modified.
this.container.classList.add("sandboxModified");
}
}
GetElementsByNameSet.add(element);
this.contentElement = element;
@ -1637,7 +1635,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
});
element.addEventListener("updatefromsandbox", jsEvent => {
this.showElementAndHideCanvas(jsEvent.target);
this.container.classList.add("sandboxModified");
const actions = {
value(event) {
elementData.userValue = event.detail.value ?? "";

View File

@ -1886,23 +1886,30 @@ describe("Interaction", () => {
const selector = getAnnotationSelector("9R");
const hasVisibleCanvas = await page.$eval(
`${selector} > canvas`,
elem => elem && !elem.hasAttribute("hidden")
elem => getComputedStyle(elem).display !== "none"
);
expect(hasVisibleCanvas)
.withContext(`In ${browserName}`)
.toEqual(true);
const hasHiddenInput = await page.$eval(`${selector} > input`, elem =>
elem?.hasAttribute("hidden")
const hasHiddenInput = await page.$eval(
`${selector} > input`,
elem => getComputedStyle(elem).display === "none"
);
expect(hasHiddenInput).withContext(`In ${browserName}`).toEqual(true);
await page.click(getSelector("12R"));
await page.waitForSelector(`${selector} > canvas[hidden]`);
await page.waitForFunction(
sel =>
getComputedStyle(document.querySelector(`${sel} > canvas`))
.display === "none",
{},
selector
);
const hasHiddenCanvas = await page.$eval(
`${selector} > canvas`,
elem => elem?.hasAttribute("hidden")
elem => getComputedStyle(elem).display === "none"
);
expect(hasHiddenCanvas)
.withContext(`In ${browserName}`)
@ -1910,7 +1917,7 @@ describe("Interaction", () => {
const hasVisibleInput = await page.$eval(
`${selector} > input`,
elem => elem && !elem.hasAttribute("hidden")
elem => getComputedStyle(elem).display !== "none"
);
expect(hasVisibleInput)
.withContext(`In ${browserName}`)
@ -2696,4 +2703,89 @@ describe("Interaction", () => {
);
});
});
describe("in text_field_own_canvas_calc.pdf", () => {
let pages;
beforeEach(async () => {
pages = await loadAndWait(
"text_field_own_canvas_calc.pdf",
getSelector("7R"),
"page-fit"
);
});
afterEach(async () => {
await closePages(pages);
});
it("must show the field instead of its canvas when it was calculated while its page wasn't rendered", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
// The read-only "Mirror" field (8R) is on page 3, which hasn't been
// rendered yet.
expect(await page.$(getSelector("8R")))
.withContext(`In ${browserName}`)
.toBeNull();
// Modifying the "Source" field (7R) on page 1 mirrors its value into
// the read-only field on page 3 through a Calculate action.
await page.type(getSelector("7R"), "Hello PDF.js");
await page.keyboard.press("Enter");
await waitForEntryInStorage(
page,
"8R",
{ value: "Hello PDF.js" },
(stored, expected) =>
!!stored &&
JSON.parse(stored).value === JSON.parse(expected).value
);
// The value has been mirrored into the storage while page 3, and
// hence its annotation layer, hasn't been rendered yet.
const page3AnnotationCount = await page.evaluate(() => {
const layer = document.querySelector(
'.page[data-page-number="3"] .annotationLayer'
);
return layer ? layer.childElementCount : 0;
});
expect(page3AnnotationCount)
.withContext(`In ${browserName}`)
.toEqual(0);
// Render page 3.
await scrollIntoView(page, '.page[data-page-number="3"]');
const inputPage3Selector = getSelector("8R");
await page.waitForSelector(
`.sandboxModified:has(${inputPage3Selector})`,
{ visible: true }
);
// The field must show its (calculated) value and the now-outdated
// canvas must be hidden.
const { value, isFieldVisible, isCanvasHidden } = await page.evaluate(
sel => {
const input = document.querySelector(sel);
const canvas = input
.closest("section")
.querySelector("canvas.annotationContent");
return {
value: input.value,
isFieldVisible: getComputedStyle(input).display !== "none",
isCanvasHidden:
!!canvas && getComputedStyle(canvas).display === "none",
};
},
inputPage3Selector
);
expect(value)
.withContext(`In ${browserName}`)
.toEqual("Hello PDF.js");
expect(isFieldVisible).withContext(`In ${browserName}`).toBe(true);
expect(isCanvasHidden).withContext(`In ${browserName}`).toBe(true);
})
);
});
});
});

View File

@ -931,3 +931,4 @@
!issue21346.pdf
!cidfont_cmap_overflow.pdf
!jbig2_file_header.pdf
!text_field_own_canvas_calc.pdf

View File

@ -0,0 +1,64 @@
%PDF-1.7
%ÿÿÿÿ
1 0 obj
<< /Type /Catalog /Pages 2 0 R /AcroForm 3 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [4 0 R 5 0 R 6 0 R] /Count 3 >>
endobj
3 0 obj
<< /Fields [7 0 R 8 0 R] /CO [8 0 R] /DR << /Font << /Helv 9 0 R >> >> /DA (/Helv 12 Tf 0 g) /NeedAppearances true >>
endobj
4 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << >> /Annots [7 0 R] >>
endobj
5 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << >> >>
endobj
6 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << >> /Annots [8 0 R] >>
endobj
7 0 obj
<< /Type /Annot /Subtype /Widget /FT /Tx /T (Source) /Rect [100 650 300 670] /P 4 0 R /F 4 /DA (/Helv 12 Tf 0 g) /AP << /N 10 0 R >> >>
endobj
8 0 obj
<< /Type /Annot /Subtype /Widget /FT /Tx /T (Mirror) /Ff 1 /Rect [100 650 300 670] /P 6 0 R /F 4 /DA (/Helv 12 Tf 0 g) /AA << /C 11 0 R >> /AP << /N 12 0 R >> >>
endobj
9 0 obj
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Name /Helv >>
endobj
10 0 obj
<< /Type /XObject /Subtype /Form /BBox [0 0 200 20] /Resources << /Font << /Helv 9 0 R >> >> /Length 50 >>
stream
/Tx BMC q BT /Helv 12 Tf 0 g 2 5 Td () Tj ET Q EMC
endstream
endobj
11 0 obj
<< /Type /Action /S /JavaScript /JS (event.value = this.getField("Source").value;) >>
endobj
12 0 obj
<< /Type /XObject /Subtype /Form /BBox [0 0 200 20] /Resources << /Font << /Helv 9 0 R >> >> /Length 49 >>
stream
/Tx BMC q 0.85 0.85 0.85 rg 0 0 200 20 re f Q EMC
endstream
endobj
xref
0 13
0000000000 65535 f
0000000015 00000 n
0000000080 00000 n
0000000149 00000 n
0000000282 00000 n
0000000386 00000 n
0000000474 00000 n
0000000578 00000 n
0000000729 00000 n
0000000906 00000 n
0000000988 00000 n
0000001179 00000 n
0000001281 00000 n
trailer
<< /Size 13 /Root 1 0 R >>
startxref
1471
%%EOF

View File

@ -138,6 +138,17 @@
}
}
/* A field rendered to its own canvas shows that canvas and keeps its editable
element hidden. Once the field is modified the canvas is outdated, so it's
hidden and the element is shown instead. */
.hasOwnCanvas:not(.sandboxModified) :is(input, textarea) {
display: none;
}
.hasOwnCanvas.sandboxModified canvas.annotationContent {
display: none;
}
.textLayer.selecting ~ & section {
pointer-events: none;
}