Move the transfers computation into the AnnotationStorage class

Rather than having to *manually* determine the potential `transfers` at various spots in the API, we can let the `AnnotationStorage.serializable` getter include this.
To further simplify things, we can also let the `serializable` getter compute and include the `hash`-string as well.
This commit is contained in:
Jonas Jenwald 2023-06-29 08:43:02 +02:00
parent 88c7c8b5bf
commit 39113baa33
5 changed files with 66 additions and 75 deletions

View File

@ -17,6 +17,12 @@ import { objectFromMap, unreachable } from "../shared/util.js";
import { AnnotationEditor } from "./editor/editor.js"; import { AnnotationEditor } from "./editor/editor.js";
import { MurmurHash3_64 } from "../shared/murmurhash3.js"; import { MurmurHash3_64 } from "../shared/murmurhash3.js";
const SerializableEmpty = Object.freeze({
map: null,
hash: "",
transfers: undefined,
});
/** /**
* Key/value storage for annotation data in forms. * Key/value storage for annotation data in forms.
*/ */
@ -171,34 +177,27 @@ class AnnotationStorage {
*/ */
get serializable() { get serializable() {
if (this.#storage.size === 0) { if (this.#storage.size === 0) {
return null; return SerializableEmpty;
} }
const clone = new Map(); const map = new Map(),
hash = new MurmurHash3_64(),
transfers = [];
for (const [key, val] of this.#storage) { for (const [key, val] of this.#storage) {
const serialized = const serialized =
val instanceof AnnotationEditor ? val.serialize() : val; val instanceof AnnotationEditor ? val.serialize() : val;
if (serialized) { if (serialized) {
clone.set(key, serialized); map.set(key, serialized);
hash.update(`${key}:${JSON.stringify(serialized)}`);
if (serialized.bitmap) {
transfers.push(serialized.bitmap);
}
} }
} }
return clone; return map.size > 0
} ? { map, hash: hash.hexdigest(), transfers }
: SerializableEmpty;
/**
* PLEASE NOTE: Only intended for usage within the API itself.
* @ignore
*/
static getHash(map) {
if (!map) {
return "";
}
const hash = new MurmurHash3_64();
for (const [key, val] of map) {
hash.update(`${key}:${JSON.stringify(val)}`);
}
return hash.hexdigest();
} }
} }
@ -208,12 +207,21 @@ class AnnotationStorage {
* contents. (Necessary since printing is triggered synchronously in browsers.) * contents. (Necessary since printing is triggered synchronously in browsers.)
*/ */
class PrintAnnotationStorage extends AnnotationStorage { class PrintAnnotationStorage extends AnnotationStorage {
#serializable = null; #serializable;
constructor(parent) { constructor(parent) {
super(); super();
const { map, hash, transfers } = parent.serializable;
// Create a *copy* of the data, since Objects are passed by reference in JS. // Create a *copy* of the data, since Objects are passed by reference in JS.
this.#serializable = structuredClone(parent.serializable); const clone = structuredClone(
map,
(typeof PDFJSDev === "undefined" ||
PDFJSDev.test("SKIP_BABEL || TESTING")) &&
transfers
? { transfer: transfers }
: null
);
this.#serializable = { map: clone, hash, transfers };
} }
/** /**
@ -233,4 +241,4 @@ class PrintAnnotationStorage extends AnnotationStorage {
} }
} }
export { AnnotationStorage, PrintAnnotationStorage }; export { AnnotationStorage, PrintAnnotationStorage, SerializableEmpty };

View File

@ -41,6 +41,7 @@ import {
import { import {
AnnotationStorage, AnnotationStorage,
PrintAnnotationStorage, PrintAnnotationStorage,
SerializableEmpty,
} from "./annotation_storage.js"; } from "./annotation_storage.js";
import { import {
deprecated, deprecated,
@ -1811,22 +1812,18 @@ class PDFPageProxy {
/** /**
* @private * @private
*/ */
_pumpOperatorList({ renderingIntent, cacheKey, annotationStorageMap }) { _pumpOperatorList({
renderingIntent,
cacheKey,
annotationStorageSerializable,
}) {
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
assert( assert(
Number.isInteger(renderingIntent) && renderingIntent > 0, Number.isInteger(renderingIntent) && renderingIntent > 0,
'_pumpOperatorList: Expected valid "renderingIntent" argument.' '_pumpOperatorList: Expected valid "renderingIntent" argument.'
); );
} }
const { map, transfers } = annotationStorageSerializable;
const transfers = [];
if (annotationStorageMap) {
for (const annotation of annotationStorageMap.values()) {
if (annotation.bitmap) {
transfers.push(annotation.bitmap);
}
}
}
const readableStream = this._transport.messageHandler.sendWithStream( const readableStream = this._transport.messageHandler.sendWithStream(
"GetOperatorList", "GetOperatorList",
@ -1834,7 +1831,7 @@ class PDFPageProxy {
pageIndex: this._pageIndex, pageIndex: this._pageIndex,
intent: renderingIntent, intent: renderingIntent,
cacheKey, cacheKey,
annotationStorage: annotationStorageMap, annotationStorage: map,
}, },
transfers transfers
); );
@ -2459,7 +2456,7 @@ class WorkerTransport {
isOpList = false isOpList = false
) { ) {
let renderingIntent = RenderingIntentFlag.DISPLAY; // Default value. let renderingIntent = RenderingIntentFlag.DISPLAY; // Default value.
let annotationMap = null; let annotationStorageSerializable = SerializableEmpty;
switch (intent) { switch (intent) {
case "any": case "any":
@ -2492,7 +2489,7 @@ class WorkerTransport {
? printAnnotationStorage ? printAnnotationStorage
: this.annotationStorage; : this.annotationStorage;
annotationMap = annotationStorage.serializable; annotationStorageSerializable = annotationStorage.serializable;
break; break;
default: default:
warn(`getRenderingIntent - invalid annotationMode: ${annotationMode}`); warn(`getRenderingIntent - invalid annotationMode: ${annotationMode}`);
@ -2504,10 +2501,8 @@ class WorkerTransport {
return { return {
renderingIntent, renderingIntent,
cacheKey: `${renderingIntent}_${AnnotationStorage.getHash( cacheKey: `${renderingIntent}_${annotationStorageSerializable.hash}`,
annotationMap annotationStorageSerializable,
)}`,
annotationStorageMap: annotationMap,
}; };
} }
@ -2915,22 +2910,15 @@ class WorkerTransport {
"please use the getData-method instead." "please use the getData-method instead."
); );
} }
const annotationStorage = this.annotationStorage.serializable; const { map, transfers } = this.annotationStorage.serializable;
const transfers = [];
if (annotationStorage) {
for (const annotation of annotationStorage.values()) {
if (annotation.bitmap) {
transfers.push(annotation.bitmap);
}
}
}
return this.messageHandler return this.messageHandler
.sendWithPromise( .sendWithPromise(
"SaveDocument", "SaveDocument",
{ {
isPureXfa: !!this._htmlForXfa, isPureXfa: !!this._htmlForXfa,
numPages: this._numPages, numPages: this._numPages,
annotationStorage, annotationStorage: map,
filename: this._fullReader?.filename ?? null, filename: this._fullReader?.filename ?? null,
}, },
transfers transfers

View File

@ -688,13 +688,12 @@ describe("FreeText Editor", () => {
} }
const serialize = proprName => const serialize = proprName =>
page.evaluate( page.evaluate(name => {
name => const { map } =
[ window.PDFViewerApplication.pdfDocument.annotationStorage
...window.PDFViewerApplication.pdfDocument.annotationStorage.serializable.values(), .serializable;
].map(x => x[name]), return map ? Array.from(map.values(), x => x[name]) : [];
proprName }, proprName);
);
expect(await serialize("value")) expect(await serialize("value"))
.withContext(`In ${browserName}`) .withContext(`In ${browserName}`)
@ -805,13 +804,12 @@ describe("FreeText Editor", () => {
} }
const serialize = proprName => const serialize = proprName =>
page.evaluate( page.evaluate(name => {
name => const { map } =
[ window.PDFViewerApplication.pdfDocument.annotationStorage
...window.PDFViewerApplication.pdfDocument.annotationStorage.serializable.values(), .serializable;
].map(x => x[name]), return map ? Array.from(map.values(), x => x[name]) : [];
proprName }, proprName);
);
const rects = (await serialize("rect")).map(rect => const rects = (await serialize("rect")).map(rect =>
rect.slice(0, 2).map(x => Math.floor(x)) rect.slice(0, 2).map(x => Math.floor(x))

View File

@ -136,9 +136,11 @@ const mockClipboard = async pages => {
exports.mockClipboard = mockClipboard; exports.mockClipboard = mockClipboard;
const getSerialized = page => const getSerialized = page =>
page.evaluate(() => [ page.evaluate(() => {
...window.PDFViewerApplication.pdfDocument.annotationStorage.serializable.values(), const { map } =
]); window.PDFViewerApplication.pdfDocument.annotationStorage.serializable;
return map ? [...map.values()] : [];
});
exports.getSerialized = getSerialized; exports.getSerialized = getSerialized;
function getEditors(page, kind) { function getEditors(page, kind) {

View File

@ -50,7 +50,6 @@ import {
RenderingCancelledException, RenderingCancelledException,
StatTimer, StatTimer,
} from "../../src/display/display_utils.js"; } from "../../src/display/display_utils.js";
import { AnnotationStorage } from "../../src/display/annotation_storage.js";
import { AutoPrintRegExp } from "../../web/ui_utils.js"; import { AutoPrintRegExp } from "../../web/ui_utils.js";
import { GlobalImageCache } from "../../src/core/image_utils.js"; import { GlobalImageCache } from "../../src/core/image_utils.js";
import { GlobalWorkerOptions } from "../../src/display/worker_options.js"; import { GlobalWorkerOptions } from "../../src/display/worker_options.js";
@ -3525,12 +3524,8 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`)
// Update the contents of the form-field again. // Update the contents of the form-field again.
annotationStorage.setValue("22R", { value: "Printing again..." }); annotationStorage.setValue("22R", { value: "Printing again..." });
const annotationHash = AnnotationStorage.getHash( const { hash: annotationHash } = annotationStorage.serializable;
annotationStorage.serializable const { hash: printAnnotationHash } = printAnnotationStorage.serializable;
);
const printAnnotationHash = AnnotationStorage.getHash(
printAnnotationStorage.serializable
);
// Sanity check to ensure that the print-storage didn't change, // Sanity check to ensure that the print-storage didn't change,
// after the form-field was updated. // after the form-field was updated.
expect(printAnnotationHash).not.toEqual(annotationHash); expect(printAnnotationHash).not.toEqual(annotationHash);