From 2e07715c9d7a765a217d47003b07d28dab0ef713 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Mon, 23 Feb 2026 18:43:26 +0100 Subject: [PATCH] Reduce function creation when using `Map.prototype.getOrInsertComputed()` With the exception of the first invocation the callback function is unused, which means that a lot of pointless functions may be created. To avoid this we introduce helper functions for simple cases, such as creating `Map`s and `Objects`s. --- src/core/xfa/fonts.js | 6 ++---- src/core/xfa/som.js | 4 ++-- src/display/annotation_storage.js | 4 ++-- src/display/api.js | 8 +++++--- src/display/canvas.js | 6 ++---- src/display/pdf_objects.js | 10 ++++++---- src/pdf.js | 6 ++++++ src/shared/util.js | 7 +++++++ test/unit/pdf_spec.js | 4 ++++ web/pdfjs.js | 4 ++++ 10 files changed, 40 insertions(+), 19 deletions(-) diff --git a/src/core/xfa/fonts.js b/src/core/xfa/fonts.js index 4e9ce6007..17572b97b 100644 --- a/src/core/xfa/fonts.js +++ b/src/core/xfa/fonts.js @@ -13,9 +13,9 @@ * limitations under the License. */ +import { makeObj, warn } from "../../shared/util.js"; import { $globalData } from "./symbol_utils.js"; import { stripQuotes } from "./utils.js"; -import { warn } from "../../shared/util.js"; class FontFinder { constructor(pdfFonts) { @@ -48,9 +48,7 @@ class FontFinder { addPdfFont(pdfFont) { const cssFontInfo = pdfFont.cssFontInfo; const name = cssFontInfo.fontFamily; - const font = this.fonts.getOrInsertComputed(name, () => - Object.create(null) - ); + const font = this.fonts.getOrInsertComputed(name, makeObj); this.defaultFont ??= font; let property = ""; diff --git a/src/core/xfa/som.js b/src/core/xfa/som.js index 9d189e99a..219e87efd 100644 --- a/src/core/xfa/som.js +++ b/src/core/xfa/som.js @@ -19,7 +19,7 @@ import { $getChildrenByName, $getParent, } from "./symbol_utils.js"; -import { warn } from "../../shared/util.js"; +import { makeMap, warn } from "../../shared/util.js"; const namePattern = /^[^.[]+/; const indexPattern = /^[^\]]+/; @@ -193,7 +193,7 @@ function searchNode( let children, cached; if (useCache) { - cached = somCache.getOrInsertComputed(node, () => new Map()); + cached = somCache.getOrInsertComputed(node, makeMap); children = cached.get(cacheName); } diff --git a/src/display/annotation_storage.js b/src/display/annotation_storage.js index 2403fecf7..94063073a 100644 --- a/src/display/annotation_storage.js +++ b/src/display/annotation_storage.js @@ -13,7 +13,7 @@ * limitations under the License. */ -import { shadow, unreachable } from "../shared/util.js"; +import { makeMap, shadow, unreachable } from "../shared/util.js"; import { AnnotationEditor } from "./editor/editor.js"; import { MurmurHash3_64 } from "../shared/murmurhash3.js"; @@ -260,7 +260,7 @@ class AnnotationStorage { if (key === "type") { continue; } - const counters = map.getOrInsertComputed(key, () => new Map()); + const counters = map.getOrInsertComputed(key, makeMap); counters.set(val, (counters.get(val) ?? 0) + 1); } } diff --git a/src/display/api.js b/src/display/api.js index 2c0955556..19cbbaeed 100644 --- a/src/display/api.js +++ b/src/display/api.js @@ -25,6 +25,7 @@ import { getVerbosityLevel, info, isNodeJS, + makeObj, MathClamp, RenderingIntentFlag, setVerbosityLevel, @@ -1502,8 +1503,9 @@ class PDFPageProxy { optionalContentConfigPromise ||= this._transport.getOptionalContentConfig(renderingIntent); - const intentState = this._intentStates.getOrInsertComputed(cacheKey, () => - Object.create(null) + const intentState = this._intentStates.getOrInsertComputed( + cacheKey, + makeObj ); // Ensure that a pending `streamReader` cancel timeout is always aborted. if (intentState.streamReaderCancelTimeout) { @@ -1675,7 +1677,7 @@ class PDFPageProxy { ); const intentState = this._intentStates.getOrInsertComputed( intentArgs.cacheKey, - () => Object.create(null) + makeObj ); let opListTask; diff --git a/src/display/canvas.js b/src/display/canvas.js index 94d5c496b..25bcb4598 100644 --- a/src/display/canvas.js +++ b/src/display/canvas.js @@ -22,6 +22,7 @@ import { FONT_IDENTITY_MATRIX, ImageKind, info, + makeMap, OPS, shadow, TextRenderingMode, @@ -989,10 +990,7 @@ class CanvasGraphics { : [currentTransform.slice(0, 4), fillColor] ); - cache = this._cachedBitmapsMap.getOrInsertComputed( - mainKey, - () => new Map() - ); + cache = this._cachedBitmapsMap.getOrInsertComputed(mainKey, makeMap); const cachedImage = cache.get(cacheKey); if (cachedImage && !isPatternFill) { const offsetX = Math.round( diff --git a/src/display/pdf_objects.js b/src/display/pdf_objects.js index c2318df70..eccbe6a72 100644 --- a/src/display/pdf_objects.js +++ b/src/display/pdf_objects.js @@ -15,6 +15,11 @@ const INITIAL_DATA = Symbol("INITIAL_DATA"); +const dataObj = () => ({ + ...Promise.withResolvers(), + data: INITIAL_DATA, +}); + /** * A PDF document and page is built of many objects. E.g. there are objects for * fonts, images, rendering code, etc. These objects may get processed inside of @@ -30,10 +35,7 @@ class PDFObjects { * @returns {Object} */ #ensureObj(objId) { - return this.#objs.getOrInsertComputed(objId, () => ({ - ...Promise.withResolvers(), - data: INITIAL_DATA, - })); + return this.#objs.getOrInsertComputed(objId, dataObj); } /** diff --git a/src/pdf.js b/src/pdf.js index 707add654..8551c08ee 100644 --- a/src/pdf.js +++ b/src/pdf.js @@ -33,6 +33,8 @@ import { getUuid, ImageKind, InvalidPDFException, + makeMap, + makeObj, MathClamp, normalizeUnicode, OPS, @@ -123,6 +125,8 @@ globalThis.pdfjsLib = { isDataScheme, isPdfFile, isValidExplicitDest, + makeMap, + makeObj, MathClamp, noContextMenu, normalizeUnicode, @@ -182,6 +186,8 @@ export { isDataScheme, isPdfFile, isValidExplicitDest, + makeMap, + makeObj, MathClamp, noContextMenu, normalizeUnicode, diff --git a/src/shared/util.js b/src/shared/util.js index 7bada9fb8..f0aa24d29 100644 --- a/src/shared/util.js +++ b/src/shared/util.js @@ -1234,6 +1234,11 @@ function _isValidExplicitDest(validRef, validName, dest) { return true; } +// Helpers for simple `Map.prototype.getOrInsertComputed()` invocations, +// to avoid duplicate function creation. +const makeMap = () => new Map(); +const makeObj = () => Object.create(null); + // TODO: Replace all occurrences of this function with `Math.clamp` once // https://github.com/tc39/proposal-math-clamp/ is generally available. function MathClamp(v, min, max) { @@ -1331,6 +1336,8 @@ export { isNodeJS, LINE_DESCENT_FACTOR, LINE_FACTOR, + makeMap, + makeObj, MathClamp, MeshFigureType, normalizeUnicode, diff --git a/test/unit/pdf_spec.js b/test/unit/pdf_spec.js index 35e92b9ae..a0ddca083 100644 --- a/test/unit/pdf_spec.js +++ b/test/unit/pdf_spec.js @@ -24,6 +24,8 @@ import { getUuid, ImageKind, InvalidPDFException, + makeMap, + makeObj, MathClamp, normalizeUnicode, OPS, @@ -107,6 +109,8 @@ const expectedAPI = Object.freeze({ isDataScheme, isPdfFile, isValidExplicitDest, + makeMap, + makeObj, MathClamp, noContextMenu, normalizeUnicode, diff --git a/web/pdfjs.js b/web/pdfjs.js index e7a9fa3bc..a7a54f5d5 100644 --- a/web/pdfjs.js +++ b/web/pdfjs.js @@ -44,6 +44,8 @@ const { isDataScheme, isPdfFile, isValidExplicitDest, + makeMap, + makeObj, MathClamp, noContextMenu, normalizeUnicode, @@ -103,6 +105,8 @@ export { isDataScheme, isPdfFile, isValidExplicitDest, + makeMap, + makeObj, MathClamp, noContextMenu, normalizeUnicode,