From ca428aadae9d6a33f2f206b7e239577b60b35908 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sat, 7 Mar 2026 12:37:30 +0100 Subject: [PATCH 1/2] Use `Math.sumPrecise` in the scripting implementation This adds a *very basic* non-MOZCENTRAL polyfill for now, which we should be able to remove once the next QuickJS version is released; note the pending changelog at https://github.com/bellard/quickjs/blob/f1139494d18a2053630c5ed3384a42bb70db3c53/Changelog#L8 --- src/scripting_api/aform.js | 4 ++-- src/scripting_api/app_utils.js | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/scripting_api/aform.js b/src/scripting_api/aform.js index 17f3f1aea..81639bdd2 100644 --- a/src/scripting_api/aform.js +++ b/src/scripting_api/aform.js @@ -368,8 +368,8 @@ class AForm { AFSimple_Calculate(cFunction, cFields) { const actions = { - AVG: args => args.reduce((acc, value) => acc + value, 0) / args.length, - SUM: args => args.reduce((acc, value) => acc + value, 0), + AVG: args => Math.sumPrecise(args) / args.length, + SUM: args => Math.sumPrecise(args), PRD: args => args.reduce((acc, value) => acc * value, 1), MIN: args => Math.min(...args), MAX: args => Math.max(...args), diff --git a/src/scripting_api/app_utils.js b/src/scripting_api/app_utils.js index b46f8ea26..a35bf9de5 100644 --- a/src/scripting_api/app_utils.js +++ b/src/scripting_api/app_utils.js @@ -26,6 +26,18 @@ function serializeError(error) { return { command: "error", value }; } +if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) { + // TODO: Remove this once `Math.sumPrecise` is supported in QuickJS. + // + // Note that this isn't a "proper" polyfill, but since we're only using it to + // replace `Array.prototype.reduce()` invocations it should be fine. + if (typeof Math.sumPrecise !== "function") { + Math.sumPrecise = function (numbers) { + return numbers.reduce((a, b) => a + b, 0); + }; + } +} + export { FORMS_VERSION, serializeError, From 49e8240c199829e3eb71c39812aab0ee33b50ae2 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sat, 7 Mar 2026 12:41:23 +0100 Subject: [PATCH 2/2] Use `Map.prototype.getOrInsertComputed` in the scripting implementation This adds a basic non-MOZCENTRAL polyfill for now, which we should be able to remove once the next QuickJS version is released; note the pending changelog at https://github.com/bellard/quickjs/blob/f1139494d18a2053630c5ed3384a42bb70db3c53/Changelog#L7 --- src/scripting_api/app_utils.js | 19 ++++++++++++++++ src/scripting_api/doc.js | 41 +++++++++++++--------------------- 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/src/scripting_api/app_utils.js b/src/scripting_api/app_utils.js index a35bf9de5..e4378ebf7 100644 --- a/src/scripting_api/app_utils.js +++ b/src/scripting_api/app_utils.js @@ -26,6 +26,11 @@ function serializeError(error) { return { command: "error", value }; } +// Helpers for simple `Map.prototype.getOrInsertComputed()` invocations, +// to avoid duplicate function creation. +const makeArr = () => []; +const makeMap = () => new Map(); + if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) { // TODO: Remove this once `Math.sumPrecise` is supported in QuickJS. // @@ -36,10 +41,24 @@ if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) { return numbers.reduce((a, b) => a + b, 0); }; } + + // TODO: Remove this once `Map.prototype.getOrInsertComputed` is supported in + // QuickJS. + if (typeof Map.prototype.getOrInsertComputed !== "function") { + // eslint-disable-next-line no-extend-native + Map.prototype.getOrInsertComputed = function (key, callbackFn) { + if (!this.has(key)) { + this.set(key, callbackFn(key)); + } + return this.get(key); + }; + } } export { FORMS_VERSION, + makeArr, + makeMap, serializeError, USERACTIVATION_CALLBACKID, USERACTIVATION_MAXTIME_VALIDITY, diff --git a/src/scripting_api/doc.js b/src/scripting_api/doc.js index 9e20f6a9e..5b9c66d2b 100644 --- a/src/scripting_api/doc.js +++ b/src/scripting_api/doc.js @@ -13,10 +13,10 @@ * limitations under the License. */ +import { makeArr, makeMap, serializeError } from "./app_utils.js"; import { createActionsMap } from "./common.js"; import { PDFObject } from "./pdf_object.js"; import { PrintParams } from "./print_params.js"; -import { serializeError } from "./app_utils.js"; import { ZoomType } from "./constants.js"; const DOC_EXTERNAL = false; @@ -32,6 +32,10 @@ class InfoProxyHandler { } class Doc extends PDFObject { + #pageActions = null; + + #otherPageActions = null; + constructor(data) { super(data); @@ -96,11 +100,9 @@ class Doc extends PDFObject { this._zoom = data.zoom || 100; this._actions = createActionsMap(data.actions); this._globalEval = data.globalEval; - this._pageActions = null; this._userActivation = false; this._disablePrinting = false; this._disableSaving = false; - this._otherPageActions = null; } _initActions() { @@ -170,14 +172,14 @@ class Doc extends PDFObject { _dispatchPageEvent(name, actions, pageNumber) { if (name === "PageOpen") { - this._pageActions ||= new Map(); - if (!this._pageActions.has(pageNumber)) { - this._pageActions.set(pageNumber, createActionsMap(actions)); + this.#pageActions ??= new Map(); + if (!this.#pageActions.has(pageNumber)) { + this.#pageActions.set(pageNumber, createActionsMap(actions)); } this._pageNum = pageNumber - 1; } - for (const acts of [this._pageActions, this._otherPageActions]) { + for (const acts of [this.#pageActions, this.#otherPageActions]) { actions = acts?.get(pageNumber)?.get(name); if (actions) { for (const action of actions) { @@ -212,27 +214,16 @@ class Doc extends PDFObject { const po = field.obj._actions.get("PageOpen"); const pc = field.obj._actions.get("PageClose"); if (po || pc) { - this._otherPageActions ||= new Map(); - let actions = this._otherPageActions.get(field.obj._page + 1); - if (!actions) { - actions = new Map(); - this._otherPageActions.set(field.obj._page + 1, actions); - } + this.#otherPageActions ??= new Map(); + const actions = this.#otherPageActions.getOrInsertComputed( + field.obj._page + 1, + makeMap + ); if (po) { - let poActions = actions.get("PageOpen"); - if (!poActions) { - poActions = []; - actions.set("PageOpen", poActions); - } - poActions.push(...po); + actions.getOrInsertComputed("PageOpen", makeArr).push(...po); } if (pc) { - let pcActions = actions.get("PageClose"); - if (!pcActions) { - pcActions = []; - actions.set("PageClose", pcActions); - } - pcActions.push(...pc); + actions.getOrInsertComputed("PageClose", makeArr).push(...pc); } } }