From d49b1bf2eecd52ccd80a7eb4cfc25ad6d3531d7c Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Mon, 30 Aug 2021 18:20:19 +0200 Subject: [PATCH] Use the native `structuredClone` implementation when it's available With a recent addition to the HTML specification, the internal structured clone algorithm used in browsers is (or will be, once it's implemented) *directly* accessible to JavaScript; please see https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/structuredClone Hence we'll *eventually* not need to maintain our own structured clone functionality in the `LoopbackPort`-class in the API, however for the time being we'll feature detect `structuredClone` and fallback to the existing PDF.js implementation. Given that https://bugzilla.mozilla.org/show_bug.cgi?id=1722576 has landed in Firefox 94, we should no longer need the manually implemented `cloneValue`-functionality in MOZCENTRAL builds. Note also that in the Firefox built-in PDF Viewer it's not possible for users to *easily* disable workers, which should further reduce the risk of these changes. --- src/display/api.js | 147 ++++++++++++++++++++++++--------------------- 1 file changed, 79 insertions(+), 68 deletions(-) diff --git a/src/display/api.js b/src/display/api.js index ebfd329f8..f0e4ae38c 100644 --- a/src/display/api.js +++ b/src/display/api.js @@ -1899,83 +1899,94 @@ class PDFPageProxy { class LoopbackPort { constructor() { this._listeners = []; - this._deferred = Promise.resolve(undefined); + this._deferred = Promise.resolve(); } postMessage(obj, transfers) { - function cloneValue(value) { + function cloneValue(object) { + if ( + (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) || + globalThis.structuredClone + ) { + return globalThis.structuredClone(object, transfers); + } + // Trying to perform a structured clone close to the spec, including // transfers. - if ( - typeof value === "function" || - typeof value === "symbol" || - value instanceof URL - ) { - throw new Error( - `LoopbackPort.postMessage - cannot clone: ${value?.toString()}` - ); + function fallbackCloneValue(value) { + if ( + typeof value === "function" || + typeof value === "symbol" || + value instanceof URL + ) { + throw new Error( + `LoopbackPort.postMessage - cannot clone: ${value?.toString()}` + ); + } + + if (typeof value !== "object" || value === null) { + return value; + } + if (cloned.has(value)) { + // already cloned the object + return cloned.get(value); + } + let buffer, result; + if ((buffer = value.buffer) && isArrayBuffer(buffer)) { + // We found object with ArrayBuffer (typed array). + if (transfers?.includes(buffer)) { + result = new value.constructor( + buffer, + value.byteOffset, + value.byteLength + ); + } else { + result = new value.constructor(value); + } + cloned.set(value, result); + return result; + } + if (value instanceof Map) { + result = new Map(); + cloned.set(value, result); // Adding to cache now for cyclic references. + for (const [key, val] of value) { + result.set(key, fallbackCloneValue(val)); + } + return result; + } + if (value instanceof Set) { + result = new Set(); + cloned.set(value, result); // Adding to cache now for cyclic references. + for (const val of value) { + result.add(fallbackCloneValue(val)); + } + return result; + } + result = Array.isArray(value) ? [] : Object.create(null); + cloned.set(value, result); // Adding to cache now for cyclic references. + // Cloning all value and object properties, however ignoring properties + // defined via getter. + for (const i in value) { + let desc, + p = value; + while (!(desc = Object.getOwnPropertyDescriptor(p, i))) { + p = Object.getPrototypeOf(p); + } + if (typeof desc.value === "undefined") { + continue; + } + if (typeof desc.value === "function" && !value.hasOwnProperty?.(i)) { + continue; + } + result[i] = fallbackCloneValue(desc.value); + } + return result; } - if (typeof value !== "object" || value === null) { - return value; - } - if (cloned.has(value)) { - // already cloned the object - return cloned.get(value); - } - let buffer, result; - if ((buffer = value.buffer) && isArrayBuffer(buffer)) { - // We found object with ArrayBuffer (typed array). - if (transfers?.includes(buffer)) { - result = new value.constructor( - buffer, - value.byteOffset, - value.byteLength - ); - } else { - result = new value.constructor(value); - } - cloned.set(value, result); - return result; - } - if (value instanceof Map) { - result = new Map(); - cloned.set(value, result); // Adding to cache now for cyclic references. - for (const [key, val] of value) { - result.set(key, cloneValue(val)); - } - return result; - } - if (value instanceof Set) { - result = new Set(); - cloned.set(value, result); // Adding to cache now for cyclic references. - for (const val of value) { - result.add(cloneValue(val)); - } - return result; - } - result = Array.isArray(value) ? [] : Object.create(null); - cloned.set(value, result); // Adding to cache now for cyclic references. - // Cloning all value and object properties, however ignoring properties - // defined via getter. - for (const i in value) { - let desc, - p = value; - while (!(desc = Object.getOwnPropertyDescriptor(p, i))) { - p = Object.getPrototypeOf(p); - } - if (typeof desc.value === "undefined") { - continue; - } - if (typeof desc.value === "function" && !value.hasOwnProperty?.(i)) { - continue; - } - result[i] = cloneValue(desc.value); - } - return result; + const cloned = new WeakMap(); + return fallbackCloneValue(object); } - const cloned = new WeakMap(); const event = { data: cloneValue(obj) }; this._deferred.then(() => {