From feec28583d2cd391bd6ef0a951c89c0149e669ca Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sat, 13 Jun 2026 18:33:43 +0200 Subject: [PATCH 1/2] Add an integration-test for merging a password-protected PDF Looking at the coverage data the password-handling part of the merge functionality wasn't being tested; see https://app.codecov.io/gh/mozilla/pdf.js/commit/e75a7cfd62a9dc14c5bbcd2808894d4761268d14/blob/src/core/worker.js#L652 --- test/integration/reorganize_pages_spec.mjs | 61 ++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/test/integration/reorganize_pages_spec.mjs b/test/integration/reorganize_pages_spec.mjs index e15a36bbf..850117125 100644 --- a/test/integration/reorganize_pages_spec.mjs +++ b/test/integration/reorganize_pages_spec.mjs @@ -3399,6 +3399,67 @@ describe("Reorganize Pages View", () => { ); }); + it("should merge a password-protected PDF after the current page", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await waitForThumbnailVisible(page, 1); + + // Navigate to page 2 so the merged PDF is inserted after it. + await page.evaluate(() => { + window.PDFViewerApplication.page = 2; + }); + await page.waitForFunction( + () => window.PDFViewerApplication.page === 2 + ); + await waitAndClick(page, getThumbnailSelector(2)); + + const handleMerged = await createPromise(page, resolve => { + window.PDFViewerApplication.eventBus.on( + "thumbnailsloaded", + resolve, + { once: true } + ); + }); + + const picker = await page.$("#viewsManagerAddFilePicker"); + await picker.uploadFile( + path.join(__dirname, "../pdfs/issue6010_1.pdf") + ); + + // Test with an incorrect password first, + // to ensure that re-prompting works correctly. + for (const password of ["Incorrect password", "abc"]) { + await page.waitForSelector("#passwordDialog", { visible: true }); + await page.type("#password", password); + await page.keyboard.press("Enter"); + } + + await awaitPromise(handleMerged); + + // Original 3 pages + 1 merged page = 4 pages total. + await page.waitForFunction( + () => parseInt(document.getElementById("pageNumber").max, 10) === 4 + ); + + // Focus must move to the first newly inserted page (page 3, since + // we merged after page 2). + await page.waitForFunction( + () => window.PDFViewerApplication.page === 3 + ); + + // Pages 1–2 come from the original document, then the page of + // the merged PDF, then page 3 of the original shifted to the end. + await waitForHavingContents(page, [1, 2, "Issue 6010", 3]); + + await waitForTextToBe( + page, + "#viewsManagerStatusActionLabel", + `${FSI}1${PDI} selected` + ); + }) + ); + }); + it("should merge a corrupt PDF (with invalid pages /Count) after the current page", async () => { await Promise.all( pages.map(async ([browserName, page]) => { From 2dc73ad2a76e17de15c298ec970487aae3a65249 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sat, 13 Jun 2026 22:54:33 +0200 Subject: [PATCH 2/2] Collect coverage data from all workers when closing integration-tests The "Merge PDF" integration-tests will (indirectly) invoke `PDFViewerApplication.open` as part of loading the new PDF document, which will end up creating a new `PDFWorker` instance. Currently worker coverage is only collected at the end of each integration-test, which means that in these cases we miss the coverage data from any "previous" workers. --- test/integration/test_utils.mjs | 19 ++----------------- web/app.js | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/test/integration/test_utils.mjs b/test/integration/test_utils.mjs index 67796e420..8aab655d6 100644 --- a/test/integration/test_utils.mjs +++ b/test/integration/test_utils.mjs @@ -151,19 +151,6 @@ function closePages(pages) { async function closeSinglePage(page) { const coverage = await page.evaluate(async () => { - // Collect coverage data from the worker before the document is closed. - let workerCoverage = null; - const handler = - window.PDFViewerApplication.pdfDocument?._transport?.messageHandler; - if (handler) { - try { - workerCoverage = await handler.sendWithPromise( - "GetWorkerCoverage", - null - ); - } catch {} - } - // Close the viewer gracefully, and clear local storage to avoid state // leaking from one test to another. await window.PDFViewerApplication.testingClose(); @@ -175,16 +162,14 @@ async function closeSinglePage(page) { // logic kicks in (see https://github.com/puppeteer/puppeteer/issues/2427). return { page: window.__coverage__ ? JSON.stringify(window.__coverage__) : null, - worker: workerCoverage ? JSON.stringify(workerCoverage) : null, + workers: window.__worker_coverage__?.map(c => JSON.stringify(c)) ?? null, }; }); if (coverage.page) { mergeCoverageIntoGlobal(JSON.parse(coverage.page)); } - if (coverage.worker) { - mergeCoverageIntoGlobal(JSON.parse(coverage.worker)); - } + coverage.workers?.map(c => mergeCoverageIntoGlobal(JSON.parse(c))); await page.close({ runBeforeUnload: false }); } diff --git a/web/app.js b/web/app.js index 52afe2be8..e3d9fb9fe 100644 --- a/web/app.js +++ b/web/app.js @@ -1165,6 +1165,23 @@ const PDFViewerApplication = { // Ignoring errors, to ensure that document closing won't break. } } + + if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("COVERAGE")) { + // Collect coverage data from the worker before the document is closed. + // + // Note that `PDFViewerApplication.open` may be invoked multiple times + // during an integration-test (see e.g. the "Merge PDF" tests). + const handler = this.pdfDocument?._transport?.messageHandler; + if (handler) { + try { + const workerCoverage = await handler.sendWithPromise( + "GetWorkerCoverage", + null + ); + (window.__worker_coverage__ ??= []).push(workerCoverage); + } catch {} + } + } const promises = []; promises.push(this.pdfLoadingTask.destroy());