From 125f6925890bf7c01558ca4560f9443bedcd1d07 Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Mon, 13 Jul 2026 18:57:09 +0200 Subject: [PATCH] Commit the current editing session before saving (bug 2047516) When saving or downloading a document, an annotation that was still being edited (e.g. an in-progress ink drawing) wasn't committed first, so its content was missing from the saved file. --- src/display/editor/tools.js | 4 +- test/integration/ink_editor_spec.mjs | 59 ++++++++++++++++++++++++++++ web/app.js | 1 + 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/display/editor/tools.js b/src/display/editor/tools.js index 491b65a83..d33829c2c 100644 --- a/src/display/editor/tools.js +++ b/src/display/editor/tools.js @@ -1039,7 +1039,7 @@ class AnnotationEditorUIManager { }, { capture: true, signal } ); - window.addEventListener("beforeunload", this.#beforeUnload.bind(this), { + window.addEventListener("beforeunload", this.endCurrentEditing.bind(this), { capture: true, signal, }); @@ -1462,7 +1462,7 @@ class AnnotationEditorUIManager { this.highlightSelection(methodOfCreation, /* comment */ true); } - #beforeUnload(e) { + endCurrentEditing() { this.commitOrRemove(); this.currentLayer?.endDrawingSession(/* isAborted = */ false); } diff --git a/test/integration/ink_editor_spec.mjs b/test/integration/ink_editor_spec.mjs index 0dcbfd8cd..c33af9504 100644 --- a/test/integration/ink_editor_spec.mjs +++ b/test/integration/ink_editor_spec.mjs @@ -18,6 +18,7 @@ import { clearEditors, closePages, countStorageEntries, + createPromise, dragAndDrop, getAnnotationSelector, getEditors, @@ -26,6 +27,7 @@ import { getSerialized, isCanvasMonochrome, kbRedo, + kbSave, kbUndo, loadAndWait, moveEditor, @@ -41,6 +43,7 @@ import { waitForStorageEntries, waitForTimeout, } from "./test_utils.mjs"; +import { AnnotationEditorType } from "../../src/shared/util.js"; const selectAll = selectEditors.bind(null, "ink"); @@ -1348,3 +1351,59 @@ describe("Ink must committed when leaving the tab", () => { ); }); }); + +describe("Ink must be committed when the document is saved", () => { + let pages; + + beforeEach(async () => { + pages = await loadAndWait("empty.pdf", ".annotationEditorLayer"); + }); + + afterEach(async () => { + await closePages(pages); + }); + + it("must check that an in-progress drawing is committed and saved", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await switchToInk(page); + + const rect = await getRect(page, ".annotationEditorLayer"); + + const x = rect.x + 20; + const y = rect.y + 20; + await drawLine(page, x, y, x + 50, y + 50); + + // The drawing session is still open, hence nothing has been committed + // to the annotation storage yet. + const count = await countStorageEntries(page); + expect(count).withContext(`In ${browserName}`).toEqual(0); + + // `saveDocument` is only called when the annotation storage isn't + // empty. If the in-progress drawing weren't committed first, then + // `downloadOrSave` would take the "download" path and `saveDocument` + // would never run. + const saveDocumentCalled = await createPromise(page, resolve => { + window.PDFViewerApplication.pdfDocument.saveDocument = () => + resolve(); + }); + + // Save the document with the keyboard shortcut (Ctrl/Cmd+S). + await kbSave(page); + + // The in-progress drawing must have been committed to the storage... + await waitForStorageEntries(page, 1); + + // ...as an ink annotation... + const serialized = await getSerialized(page); + expect(serialized.length).withContext(`In ${browserName}`).toEqual(1); + expect(serialized[0].annotationType) + .withContext(`In ${browserName}`) + .toEqual(AnnotationEditorType.INK); + + // ...and the document must have been saved (and not merely downloaded). + await awaitPromise(saveDocumentCalled); + }) + ); + }); +}); diff --git a/web/app.js b/web/app.js index 104bd2cca..fdf2e2094 100644 --- a/web/app.js +++ b/web/app.js @@ -1388,6 +1388,7 @@ const PDFViewerApplication = { } return; } + this.pdfViewer._layerProperties.annotationEditorUIManager?.endCurrentEditing(); // In the Firefox case, this method MUST always trigger a download. // When the user is closing a modified and unsaved document, we display a