mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-06-13 05:31:03 +02:00
Fix form fields with their own canvas updated on non-rendered pages
When a read-only field (which has its own canvas) is updated by the sandbox while its page isn't rendered, showElementAndHideCanvas isn't called, so once the page is finally rendered the field still shows its outdated canvas instead of the new value. Replace the imperative canvas/element toggling with a `sandboxModified` class, set from the annotation storage both at render time and on sandbox updates, and let the CSS show the element and hide the canvas.
This commit is contained in:
parent
52a44a68be
commit
ecd43bc8e1
@ -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 ?? "";
|
||||
|
||||
@ -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);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
1
test/pdfs/.gitignore
vendored
1
test/pdfs/.gitignore
vendored
@ -931,3 +931,4 @@
|
||||
!issue21346.pdf
|
||||
!cidfont_cmap_overflow.pdf
|
||||
!jbig2_file_header.pdf
|
||||
!text_field_own_canvas_calc.pdf
|
||||
|
||||
64
test/pdfs/text_field_own_canvas_calc.pdf
Normal file
64
test/pdfs/text_field_own_canvas_calc.pdf
Normal 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
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user