From 3a19a20d50924ea93f295ace8e40478c18c3f7a2 Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Mon, 13 Jul 2026 11:39:10 +0200 Subject: [PATCH] Avoid duplicate shared object clones --- src/core/editor/pdf_editor.js | 6 ++++++ test/unit/api_spec.js | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/core/editor/pdf_editor.js b/src/core/editor/pdf_editor.js index 56480666d..d8e964254 100644 --- a/src/core/editor/pdf_editor.js +++ b/src/core/editor/pdf_editor.js @@ -271,6 +271,12 @@ class PDFEditor { } const oldRef = obj; obj = await xref.fetchAsync(oldRef); + const mappedRef = oldRefMapping.get(oldRef); + if (mappedRef) { + // Another concurrent traversal may have allocated the clone while the + // source object was being fetched. + return mappedRef; + } if (typeof obj === "number") { // Simple value; no need to create a new reference. return obj; diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js index e1ab44de8..a2bacec90 100644 --- a/test/unit/api_spec.js +++ b/test/unit/api_spec.js @@ -6286,6 +6286,29 @@ small scripts as well as for`); await loadingTask.destroy(); }); + it("clones shared indirect objects reached concurrently only once", async function () { + const pdfData = assemblePdf([ + "1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n", + "2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n", + "3 0 obj\n<< /Type /Page /Parent 2 0 R " + + "/MediaBox [0 0 100 100] /Resources << /ExtGState " + + "<< /GS1 4 0 R /GS2 4 0 R >> >> >>\nendobj\n", + "4 0 obj\n<< /Type /ExtGState /CA 0.5 >>\nendobj\n", + ]); + + const loadingTask = getDocument({ data: pdfData }); + const pdfDoc = await loadingTask.promise; + const data = await pdfDoc.extractPages([{ document: null }]); + expect(countMarker(data, "/Type /ExtGState")).toEqual(1); + await loadingTask.destroy(); + + const newLoadingTask = getDocument({ data }); + const newPdfDoc = await newLoadingTask.promise; + expect(newPdfDoc.numPages).toEqual(1); + await newPdfDoc.getPage(1); + await newLoadingTask.destroy(); + }); + it("should merge two PDFs with page included ranges", async function () { const loadingTask = getDocument( buildGetDocumentParams("tracemonkey.pdf")