From 6323afab4670b19a380fb205d6c65eef53975f57 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Tue, 17 Feb 2026 09:42:27 +0100 Subject: [PATCH] Convert the `PDFObjects` class to use a `Map` internally This patch also adds unconditional `Map.prototype.getOrInsertComputed()` usage, which should be fine since it's [supported in the latest browsers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/getOrInsertComputed#browser_compatibility) and it'll be polyfilled (via core-js) in the `legacy` builds. --- src/display/pdf_objects.js | 28 ++++++++++++---------------- web/base_download_manager.js | 7 +++---- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/display/pdf_objects.js b/src/display/pdf_objects.js index 7e801c1c91575..c2318df701d4d 100644 --- a/src/display/pdf_objects.js +++ b/src/display/pdf_objects.js @@ -21,7 +21,7 @@ const INITIAL_DATA = Symbol("INITIAL_DATA"); * a worker. This class implements some basic methods to manage these objects. */ class PDFObjects { - #objs = Object.create(null); + #objs = new Map(); /** * Ensures there is an object defined for `objId`. @@ -30,10 +30,10 @@ class PDFObjects { * @returns {Object} */ #ensureObj(objId) { - return (this.#objs[objId] ||= { + return this.#objs.getOrInsertComputed(objId, () => ({ ...Promise.withResolvers(), data: INITIAL_DATA, - }); + })); } /** @@ -58,7 +58,7 @@ class PDFObjects { } // If there isn't a callback, the user expects to get the resolved data // directly. - const obj = this.#objs[objId]; + const obj = this.#objs.get(objId); // If there isn't an object yet or the object isn't resolved, then the // data isn't ready yet! if (!obj || obj.data === INITIAL_DATA) { @@ -72,7 +72,7 @@ class PDFObjects { * @returns {boolean} */ has(objId) { - const obj = this.#objs[objId]; + const obj = this.#objs.get(objId); return !!obj && obj.data !== INITIAL_DATA; } @@ -81,12 +81,12 @@ class PDFObjects { * @returns {boolean} */ delete(objId) { - const obj = this.#objs[objId]; + const obj = this.#objs.get(objId); if (!obj || obj.data === INITIAL_DATA) { // Only allow removing the object *after* it's been resolved. return false; } - delete this.#objs[objId]; + this.#objs.delete(objId); return true; } @@ -103,21 +103,17 @@ class PDFObjects { } clear() { - for (const objId in this.#objs) { - const { data } = this.#objs[objId]; + for (const { data } of this.#objs.values()) { data?.bitmap?.close(); // Release any `ImageBitmap` data. } - this.#objs = Object.create(null); + this.#objs.clear(); } *[Symbol.iterator]() { - for (const objId in this.#objs) { - const { data } = this.#objs[objId]; - - if (data === INITIAL_DATA) { - continue; + for (const [objId, { data }] of this.#objs) { + if (data !== INITIAL_DATA) { + yield [objId, data]; } - yield [objId, data]; } } } diff --git a/web/base_download_manager.js b/web/base_download_manager.js index 7caa616af7945..2c4c40eac1c50 100644 --- a/web/base_download_manager.js +++ b/web/base_download_manager.js @@ -64,11 +64,10 @@ class BaseDownloadManager { const contentType = isPdfData ? "application/pdf" : ""; if (isPdfData) { - let blobUrl; + const blobUrl = this.#openBlobUrls.getOrInsertComputed(data, () => + URL.createObjectURL(new Blob([data], { type: contentType })) + ); try { - blobUrl = this.#openBlobUrls.getOrInsertComputed(data, () => - URL.createObjectURL(new Blob([data], { type: contentType })) - ); const viewerUrl = this._getOpenDataUrl(blobUrl, filename, dest); window.open(viewerUrl);