▼class WorkerMessageHandler[1002 lines]
71class WorkerMessageHandler {
72 static {
73 // Worker thread (and not Node.js)?
74 if (
75 typeof window === "undefined" &&
76 !isNodeJS &&
77 typeof self !== "undefined" &&
78 /* isMessagePort = */
79 typeof self.postMessage === "function" &&
80 "onmessage" in self
81 ) {
82 this.initializeFromPort(self);
83 }
84 }
85
86 static setup(handler, port) {
87 let testMessageProcessed = false;
88 handler.on("test", data => {
89 if (testMessageProcessed) {
90 return; // we already processed 'test' message once
91 }
92 testMessageProcessed = true;
93
94 // Ensure that `TypedArray`s can be sent to the worker.
95 handler.send("test", data instanceof Uint8Array);
96 });
97
98 handler.on("configure", data => {
99 setVerbosityLevel(data.verbosity);
100 });
101
102 handler.on("GetDocRequest", data => this.createDocumentHandler(data, port));
103
104 if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
105 handler.on("GetWorkerCoverage", function () {
106 return globalThis.__coverage__ ?? {};
107 });
108 }
109 }
110
111 static createDocumentHandler(docParams, port) {
112 // This context is actually holds references on pdfManager and handler,
113 // until the latter is destroyed.
114 let pdfManager;
115 let terminated = false;
116 let cancelXHRs = null;
117 const WorkerTasks = new Set();
118 const verbosity = getVerbosityLevel();
119
120 const { docId, apiVersion } = docParams;
121 const workerVersion =
122 typeof PDFJSDev !== "undefined" && !PDFJSDev.test("TESTING")
123 ? PDFJSDev.eval("BUNDLE_VERSION")
124 : null;
125 if (apiVersion !== workerVersion) {
126 throw new Error(
127 `The API version "${apiVersion}" does not match ` +
128 `the Worker version "${workerVersion}".`
129 );
130 }
131
132 if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
133 // Fail early, and predictably, rather than having (some) fonts fail to
134 // load/render with slightly cryptic error messages in environments where
135 // the `{Object, Array}.prototype` has been *incorrectly* extended.
136 //
137 // PLEASE NOTE: We do *not* want to slow down font parsing by adding
138 // `Object.hasOwn` checks all over the code-base.
139 const buildMsg = (type, prop) =>
140 `The \`${type}.prototype\` contains unexpected enumerable property ` +
141 `"${prop}", thus breaking e.g. \`for...in\` iteration of ${type}s.`;
142
143 for (const prop in {}) {
144 throw new Error(buildMsg("Object", prop));
145 }
146 for (const prop in []) {
147 throw new Error(buildMsg("Array", prop));
148 }
149 }
150 const workerHandlerName = docId + "_worker";
151 let handler = new MessageHandler(workerHandlerName, docId, port);
152
153 function ensureNotTerminated() {
154 if (terminated) {
155 throw new Error("Worker was terminated");
156 }
157 }
158
159 function startWorkerTask(task) {
160 WorkerTasks.add(task);
161 }
162
163 function finishWorkerTask(task) {
164 task.finish();
165 WorkerTasks.delete(task);
166 }
167
168 async function loadDocument(recoveryMode) {
169 await pdfManager.initDocument(recoveryMode);
170
171 const isPureXfa = await pdfManager.ensureDoc("isPureXfa");
172 if (isPureXfa) {
173 const task = new WorkerTask("loadXfaResources");
174 startWorkerTask(task);
175
176 await pdfManager.ensureDoc("loadXfaResources", [handler, task]);
177 finishWorkerTask(task);
178 }
179
180 const [numPages, fingerprints] = await Promise.all([
181 pdfManager.ensureDoc("numPages"),
182 pdfManager.ensureDoc("fingerprints"),
183 ]);
184
185 // Get htmlForXfa after numPages to avoid to create HTML twice.
186 const htmlForXfa = isPureXfa
187 ? await pdfManager.ensureDoc("htmlForXfa")
188 : null;
189
190 return { numPages, fingerprints, htmlForXfa };
191 }
192
193 async function getPdfManager({
194 data,
195 password,
196 disableAutoFetch,
197 rangeChunkSize,
198 docBaseUrl,
199 enableXfa,
200 evaluatorOptions,
201 }) {
202 const pdfManagerArgs = {
203 source: null,
204 disableAutoFetch,
205 docBaseUrl,
206 docId,
207 enableXfa,
208 evaluatorOptions,
209 handler,
210 length: 0,
211 password,
212 rangeChunkSize,
213 };
214
215 if (data) {
216 pdfManagerArgs.source = data;
217
218 return new LocalPdfManager(pdfManagerArgs);
219 }
220 const pdfStream = new PDFWorkerStream({ msgHandler: handler }),
221 fullReader = pdfStream.getFullReader();
222
223 const { promise, resolve, reject } = Promise.withResolvers();
224 let newPdfManager,
225 cachedChunks = [];
226 cancelXHRs = reason => pdfStream.cancelAllRequests(reason);
227
228 fullReader.headersReady
229 .then(() => {
230 if (!fullReader.isRangeSupported) {
231 return;
232 }
233 pdfManagerArgs.source = pdfStream;
234 pdfManagerArgs.length = fullReader.contentLength;
235 // We don't need auto-fetch when streaming is enabled.
236 pdfManagerArgs.disableAutoFetch ||= fullReader.isStreamingSupported;
237
238 newPdfManager = new NetworkPdfManager(pdfManagerArgs);
239 // There may be a chance that `newPdfManager` is not initialized for
240 // the first few iterations of the `readData` code. Be sure to send
241 // all cached chunks, if any, to chunked_stream via pdf_manager.
242 for (const chunk of cachedChunks) {
243 newPdfManager.sendProgressiveData(chunk);
244 }
245 cachedChunks = null;
246
247 resolve(newPdfManager);
248 cancelXHRs = null;
249 })
250 .catch(reason => {
251 reject(reason);
252 cancelXHRs = null;
253 });
254
255 async function readData() {
256 let loaded = 0;
257
258 while (true) {
259 const { value, done } = await fullReader.read();
260 ensureNotTerminated();
261
262 if (done) {
263 break;
264 }
265 if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
266 assert(
267 value instanceof ArrayBuffer,
268 "readData (getPdfManager) - expected an ArrayBuffer."
269 );
270 }
271 loaded += value.byteLength;
272
273 if (!fullReader.isStreamingSupported) {
274 handler.send("DocProgress", {
275 loaded,
276 total: fullReader.contentLength,
277 });
278 }
279
280 if (newPdfManager) {
281 newPdfManager.sendProgressiveData(value);
282 } else {
283 cachedChunks.push(value);
284 }
285 }
286
287 if (!newPdfManager) {
288 pdfManagerArgs.source = arrayBuffersToBytes(cachedChunks);
289 cachedChunks = null;
290
291 newPdfManager = new LocalPdfManager(pdfManagerArgs);
292 resolve(newPdfManager);
293 }
294 cancelXHRs = null;
295 }
296 readData().catch(reason => {
297 reject(reason);
298 cancelXHRs = null;
299 });
300
301 return promise;
302 }
303
304 async function getPassword(ex) {
305 if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
306 assert(
307 ex instanceof PasswordException,
308 "getPassword - must be a `PasswordException`."
309 );
310 }
311 const task = new WorkerTask(`PasswordException: response ${ex.code}`);
312 startWorkerTask(task);
313
314 try {
315 const res = await handler.sendWithPromise("PasswordRequest", ex);
316 return res.password;
317 } finally {
318 // Ensure that any `catch` handler runs *before* removing the task.
319 Promise.resolve().then(() => {
320 finishWorkerTask(task);
321 });
322 }
323 }
324
325 function setupDoc(data) {
326 function onSuccess(doc) {
327 ensureNotTerminated();
328 handler.send("GetDoc", { pdfInfo: doc });
329 }
330
331 function onFailure(ex) {
332 if (terminated) {
333 return;
334 }
335
336 if (ex instanceof PasswordException) {
337 getPassword(ex)
338 .then(password => {
339 pdfManager.updatePassword(password);
340 pdfManagerReady();
341 })
342 .catch(() => {
343 handler.send("DocException", ex);
344 });
345 } else {
346 // Ensure that we always fallback to `UnknownErrorException`.
347 handler.send("DocException", wrapReason(ex));
348 }
349 }
350
351 function pdfManagerReady() {
352 ensureNotTerminated();
353
354 loadDocument(false).then(onSuccess, function (reason) {
355 ensureNotTerminated();
356
357 // Try again with recoveryMode == true
358 if (!(reason instanceof XRefParseException)) {
359 onFailure(reason);
360 return;
361 }
362 pdfManager.requestLoadedStream().then(function () {
363 ensureNotTerminated();
364
365 loadDocument(true).then(onSuccess, onFailure);
366 }, onFailure);
367 });
368 }
369
370 ensureNotTerminated();
371
372 getPdfManager(data)
373 .then(function (newPdfManager) {
374 if (terminated) {
375 // We were in a process of setting up the manager, but it got
376 // terminated in the middle.
377 newPdfManager.terminate(
378 new AbortException("Worker was terminated.")
379 );
380 throw new Error("Worker was terminated");
381 }
382 pdfManager = newPdfManager;
383
384 pdfManager.requestLoadedStream(/* noFetch = */ true).then(
385 stream => {
386 handler.send("DataLoaded", { length: stream.bytes.byteLength });
387 },
388 () => {
389 // Avoid errors if document loading was terminated.
390 }
391 );
392 })
393 .then(pdfManagerReady, onFailure);
394 }
395
396 handler.on("GetPage", async function ({ pageIndex }) {
397 const page = await pdfManager.getPage(pageIndex);
398
399 const [rotate, ref, userUnit, view] = await Promise.all([
400 pdfManager.ensure(page, "rotate"),
401 pdfManager.ensure(page, "ref"),
402 pdfManager.ensure(page, "userUnit"),
403 pdfManager.ensure(page, "view"),
404 ]);
405 return {
406 rotate,
407 ref,
408 refStr: ref?.toString() ?? null,
409 userUnit,
410 view,
411 };
412 });
413
414 handler.on("GetPageIndex", function ({ num, gen }) {
415 return pdfManager.ensureCatalog("getPageIndex", [Ref.get(num, gen)]);
416 });
417
418 handler.on("GetDestinations", function () {
419 return pdfManager.ensureCatalog("destinations");
420 });
421
422 handler.on("GetDestination", function ({ id }) {
423 return pdfManager.ensureCatalog("getDestination", [id]);
424 });
425
426 handler.on("GetPageLabels", function () {
427 return pdfManager.ensureCatalog("pageLabels");
428 });
429
430 handler.on("GetPageLayout", function () {
431 return pdfManager.ensureCatalog("pageLayout");
432 });
433
434 handler.on("GetPageMode", function () {
435 return pdfManager.ensureCatalog("pageMode");
436 });
437
438 handler.on("GetViewerPreferences", function () {
439 return pdfManager.ensureCatalog("viewerPreferences");
440 });
441
442 handler.on("GetOpenAction", function () {
443 return pdfManager.ensureCatalog("openAction");
444 });
445
446 handler.on("GetAttachments", function () {
447 return pdfManager.ensureCatalog("attachments");
448 });
449
450 handler.on("GetAttachmentContent", async function (id) {
451 let passwordEx;
452
453 // Loop to prompt again after an incorrect password.
454 while (true) {
455 const password = passwordEx ? await getPassword(passwordEx) : null;
456
457 try {
458 if (password) {
459 pdfManager.updatePassword(password);
460 }
461 return await pdfManager.ensureCatalog("attachmentContent", [id]);
462 } catch (ex) {
463 if (ex instanceof PasswordException) {
464 passwordEx = ex;
465 continue;
466 }
467 throw ex;
468 }
469 }
470 });
471
472 handler.on("GetDocJSActions", function () {
473 return pdfManager.ensureCatalog("jsActions");
474 });
475
476 handler.on("GetPageJSActions", async function ({ pageIndex }) {
477 const page = await pdfManager.getPage(pageIndex);
478 return pdfManager.ensure(page, "jsActions");
479 });
480
481 handler.on(
482 "GetAnnotationsByType",
483 async function ({ types, pageIndexesToSkip }) {
484 const [numPages, annotationGlobals] = await Promise.all([
485 pdfManager.ensureDoc("numPages"),
486 pdfManager.ensureDoc("annotationGlobals"),
487 ]);
488
489 if (!annotationGlobals) {
490 return null;
491 }
492 const pagePromises = [];
493 const annotationPromises = [];
494 let task = null;
495 try {
496 for (let i = 0, ii = numPages; i < ii; i++) {
497 if (pageIndexesToSkip?.has(i)) {
498 continue;
499 }
500 if (!task) {
501 task = new WorkerTask("GetAnnotationsByType");
502 startWorkerTask(task);
503 }
504 pagePromises.push(
505 pdfManager
506 .getPage(i)
507 .then(page =>
508 page.collectAnnotationsByType(
509 handler,
510 task,
511 types,
512 annotationPromises,
513 annotationGlobals
514 )
515 )
516 );
517 }
518 await Promise.all(pagePromises);
519 const annotations = await Promise.all(annotationPromises);
520 return annotations.filter(Boolean);
521 } finally {
522 if (task) {
523 finishWorkerTask(task);
524 }
525 }
526 }
527 );
528
529 handler.on("GetOutline", function () {
530 return pdfManager.ensureCatalog("documentOutline");
531 });
532
533 handler.on("GetOptionalContentConfig", function () {
534 return pdfManager.ensureCatalog("optionalContentConfig");
535 });
536
537 handler.on("GetPermissions", function () {
538 return pdfManager.ensureCatalog("permissions");
539 });
540
541 handler.on("GetMetadata", function () {
542 return Promise.all([
543 pdfManager.ensureDoc("documentInfo"),
544 pdfManager.ensureCatalog("metadata"),
545 pdfManager.ensureCatalog("hasStructTree"),
546 ]);
547 });
548
549 handler.on("GetMarkInfo", function () {
550 return pdfManager.ensureCatalog("markInfo");
551 });
552
553 handler.on("GetData", async function () {
554 const stream = await pdfManager.requestLoadedStream();
555 return stream.bytes;
556 });
557
558 handler.on("GetAnnotations", async function ({ pageIndex, intent }) {
559 const page = await pdfManager.getPage(pageIndex);
560
561 const task = new WorkerTask(`GetAnnotations: page ${pageIndex}`);
562 startWorkerTask(task);
563
564 try {
565 return await page.getAnnotationsData(handler, task, intent);
566 } finally {
567 finishWorkerTask(task);
568 }
569 });
570
571 handler.on("GetFieldObjects", async function () {
572 const fieldObjects = await pdfManager.ensureDoc("fieldObjects");
573 return fieldObjects?.allFields || null;
574 });
575
576 handler.on("GetSignatures", function () {
577 return pdfManager.ensureDoc("signatures");
578 });
579
580 handler.on("GetSignatureData", function (id) {
581 return pdfManager.ensureDoc("getSignatureData", [id]);
582 });
583
584 handler.on("HasJSActions", function () {
585 return pdfManager.ensureDoc("hasJSActions");
586 });
587
588 handler.on("GetCalculationOrderIds", function () {
589 return pdfManager.ensureDoc("calculationOrderIds");
590 });
591
592 handler.on(
593 "ExtractPages",
594 async function ({ pageInfos, annotationStorage }) {
595 if (!pageInfos) {
596 warn("extractPages: nothing to extract.");
597 return null;
598 }
599 if (!Array.isArray(pageInfos)) {
600 pageInfos = [pageInfos];
601 }
602 let newDocumentId = 0;
603 for (const pageInfo of pageInfos) {
604 if (pageInfo.image) {
605 continue;
606 }
607 if (pageInfo.document === null) {
608 pageInfo.document = pdfManager.pdfDocument;
609 } else if (ArrayBuffer.isView(pageInfo.document)) {
610 const manager = new LocalPdfManager({
611 source: pageInfo.document,
612 docId: `${docId}_extractPages_${newDocumentId++}`,
613 handler,
614 password: pageInfo.password ?? null,
615 evaluatorOptions: Object.assign({}, pdfManager.evaluatorOptions),
616 });
617 let recoveryMode = false;
618 let isValid = true;
619 while (true) {
620 try {
621 await manager.requestLoadedStream();
622 await manager.initDocument(recoveryMode);
623 break;
624 } catch (e) {
625 if (e instanceof XRefParseException) {
626 if (recoveryMode === false) {
627 recoveryMode = true;
628 continue;
629 } else {
630 isValid = false;
631 warn("extractPages: XRefParseException.");
632 }
633 } else if (e instanceof PasswordException) {
634 try {
635 const password = await getPassword(e);
636 manager.updatePassword(password);
637 } catch {
638 isValid = false;
639 warn("extractPages: invalid password.");
640 }
641 } else {
642 isValid = false;
643 warn("extractPages: invalid document.");
644 }
645 if (!isValid) {
646 break;
647 }
648 }
649 }
650 if (!isValid) {
651 pageInfo.document = null;
652 }
653 const isPureXfa = await manager.ensureDoc("isPureXfa");
654 if (isPureXfa) {
655 pageInfo.document = null;
656 warn("extractPages does not support pure XFA documents.");
657 } else {
658 pageInfo.document = manager.pdfDocument;
659 }
660 } else {
661 warn("extractPages: invalid document.");
662 }
663 }
664 let task;
665 try {
666 const pdfEditor = new PDFEditor();
667 task = new WorkerTask(`ExtractPages: ${pageInfos.length} page(s)`);
668 startWorkerTask(task);
669
670 return await pdfEditor.extractPages(
671 pageInfos,
672 annotationStorage,
673 pdfManager.pdfDocument,
674 handler,
675 task
676 );
677 } catch (reason) {
678 warn(`extractPages: "${reason}".`);
679 return null;
680 } finally {
681 if (task) {
682 finishWorkerTask(task);
683 }
684 }
685 }
686 );
687
688 handler.on(
689 "SaveDocument",
690 async function ({ isPureXfa, numPages, annotationStorage, filename }) {
691 const globalPromises = [
692 pdfManager.requestLoadedStream(),
693 pdfManager.ensureCatalog("acroForm"),
694 pdfManager.ensureCatalog("acroFormRef"),
695 pdfManager.ensureDoc("startXRef"),
696 pdfManager.ensureDoc("xref"),
697 pdfManager.ensureCatalog("structTreeRoot"),
698 ];
699 const changes = new RefMap();
700 const promises = [];
701
702 const newAnnotationsByPage = !isPureXfa
703 ? getNewAnnotationsMap(annotationStorage)
704 : null;
705 const [
706 stream,
707 acroForm,
708 acroFormRef,
709 startXRef,
710 xref,
711 _structTreeRoot,
712 ] = await Promise.all(globalPromises);
713 const catalogRef = xref.trailer.getRaw("Root") || null;
714 let structTreeRoot;
715
716 if (newAnnotationsByPage) {
717 if (!_structTreeRoot) {
718 if (
719 await StructTreeRoot.canCreateStructureTree({
720 catalogRef,
721 pdfManager,
722 newAnnotationsByPage,
723 })
724 ) {
725 structTreeRoot = null;
726 }
727 } else if (
728 await _structTreeRoot.canUpdateStructTree({
729 pdfManager,
730 newAnnotationsByPage,
731 })
732 ) {
733 structTreeRoot = _structTreeRoot;
734 }
735
736 const imagePromises = AnnotationFactory.generateImages(
737 annotationStorage.values(),
738 xref,
739 pdfManager.evaluatorOptions.isOffscreenCanvasSupported
740 );
741 const newAnnotationPromises =
742 structTreeRoot === undefined ? promises : [];
743 for (const [pageIndex, annotations] of newAnnotationsByPage) {
744 newAnnotationPromises.push(
745 pdfManager.getPage(pageIndex).then(page => {
746 const task = new WorkerTask(`Save (editor): page ${pageIndex}`);
747 startWorkerTask(task);
748
749 return page
750 .saveNewAnnotations(
751 handler,
752 task,
753 annotations,
754 imagePromises,
755 changes
756 )
757 .finally(() => {
758 finishWorkerTask(task);
759 });
760 })
761 );
762 }
763 if (structTreeRoot === null) {
764 // No structTreeRoot exists, so we need to create one.
765 promises.push(
766 Promise.all(newAnnotationPromises).then(async () => {
767 await StructTreeRoot.createStructureTree({
768 newAnnotationsByPage,
769 xref,
770 catalogRef,
771 pdfManager,
772 changes,
773 });
774 })
775 );
776 } else if (structTreeRoot) {
777 promises.push(
778 Promise.all(newAnnotationPromises).then(async () => {
779 await structTreeRoot.updateStructureTree({
780 newAnnotationsByPage,
781 pdfManager,
782 changes,
783 });
784 })
785 );
786 }
787 }
788
789 if (isPureXfa) {
790 promises.push(
791 pdfManager.ensureDoc("serializeXfaData", [annotationStorage])
792 );
793 } else {
794 for (let pageIndex = 0; pageIndex < numPages; pageIndex++) {
795 promises.push(
796 pdfManager.getPage(pageIndex).then(function (page) {
797 const task = new WorkerTask(`Save: page ${pageIndex}`);
798 startWorkerTask(task);
799
800 return page
801 .save(handler, task, annotationStorage, changes)
802 .finally(() => {
803 finishWorkerTask(task);
804 });
805 })
806 );
807 }
808 }
809 const refs = await Promise.all(promises);
810
811 let xfaData = null;
812 if (isPureXfa) {
813 xfaData = refs[0];
814 if (!xfaData) {
815 return stream.bytes;
816 }
817 } else if (changes.size === 0) {
818 // No new refs so just return the initial bytes
819 return stream.bytes;
820 }
821
822 const needAppearances =
823 acroFormRef &&
824 acroForm instanceof Dict &&
825 changes.values().some(ref => ref.needAppearances);
826
827 const xfa = (acroForm instanceof Dict && acroForm.get("XFA")) || null;
828 let xfaDatasetsRef = null;
829 let hasXfaDatasetsEntry = false;
830 if (Array.isArray(xfa)) {
831 for (let i = 0, ii = xfa.length; i < ii; i += 2) {
832 if (xfa[i] === "datasets") {
833 xfaDatasetsRef = xfa[i + 1];
834 hasXfaDatasetsEntry = true;
835 }
836 }
837 if (xfaDatasetsRef === null) {
838 xfaDatasetsRef = xref.getNewTemporaryRef();
839 }
840 } else if (xfa) {
841 // TODO: Support XFA streams.
842 warn("Unsupported XFA type.");
843 }
844
845 let newXrefInfo = Object.create(null);
846 if (xref.trailer) {
847 // Get string info from Info in order to compute fileId.
848 const infoMap = new Map();
849 const xrefInfo = xref.trailer.get("Info") || null;
850 if (xrefInfo instanceof Dict) {
851 for (const [key, value] of xrefInfo) {
852 if (typeof value === "string") {
853 infoMap.set(key, stringToPDFString(value));
854 }
855 }
856 }
857
858 newXrefInfo = {
859 rootRef: catalogRef,
860 encryptRef: xref.trailer.getRaw("Encrypt") || null,
861 newRef: xref.getNewTemporaryRef(),
862 infoRef: xref.trailer.getRaw("Info") || null,
863 infoMap,
864 fileIds: xref.trailer.get("ID") || null,
865 startXRef,
866 filename,
867 };
868 }
869
870 return incrementalUpdate({
871 originalData: stream.bytes,
872 xrefInfo: newXrefInfo,
873 changes,
874 xref,
875 hasXfa: !!xfa,
876 xfaDatasetsRef,
877 hasXfaDatasetsEntry,
878 needAppearances,
879 acroFormRef,
880 acroForm,
881 xfaData,
882 // Use the same kind of XRef as the previous one.
883 useXrefStream: isDict(xref.topDict, "XRef"),
884 }).finally(() => {
885 xref.resetNewTemporaryRef();
886 });
887 }
888 );
889
890 handler.on(
891 "GetOperatorList",
892 function (
893 { pageId, pageIndex, intent, cacheKey, annotationStorage, modifiedIds },
894 sink
895 ) {
896 pdfManager.getPage(pageId).then(function (page) {
897 const task = new WorkerTask(`GetOperatorList: page ${pageIndex}`);
898 startWorkerTask(task);
899
900 // NOTE: Keep this condition in sync with the `info` helper function.
901 const start = verbosity >= VerbosityLevel.INFOS ? Date.now() : 0;
902
903 // Pre compile the pdf page and fetch the fonts/images.
904 page
905 .getOperatorList({
906 handler,
907 sink,
908 task,
909 intent,
910 cacheKey,
911 annotationStorage,
912 modifiedIds,
913 pageIndex,
914 })
915 .then(
916 opListInfo => {
917 if (start) {
918 info(
919 `${task.name}; time=${Date.now() - start}ms, len=${opListInfo.length}`
920 );
921 }
922 sink.close();
923 },
924 reason => {
925 if (task.terminated) {
926 return; // ignoring errors from the terminated thread
927 }
928 sink.error(reason);
929 }
930 )
931 .finally(() => {
932 finishWorkerTask(task);
933 });
934 });
935 }
936 );
937
938 handler.on(
939 "GetTextContent",
940 function (
941 { pageId, pageIndex, includeMarkedContent, disableNormalization },
942 sink
943 ) {
944 pdfManager.getPage(pageId).then(function (page) {
945 const task = new WorkerTask("GetTextContent: page " + pageIndex);
946 startWorkerTask(task);
947
948 // NOTE: Keep this condition in sync with the `info` helper function.
949 const start = verbosity >= VerbosityLevel.INFOS ? Date.now() : 0;
950
951 page
952 .extractTextContent({
953 handler,
954 task,
955 sink,
956 includeMarkedContent,
957 disableNormalization,
958 })
959 .then(
960 () => {
961 if (start) {
962 info(`${task.name}; time=${Date.now() - start}ms`);
963 }
964 sink.close();
965 },
966 reason => {
967 if (task.terminated) {
968 return; // ignoring errors from the terminated thread
969 }
970 sink.error(reason);
971 }
972 )
973 .finally(() => {
974 finishWorkerTask(task);
975 });
976 });
977 }
978 );
979
980 handler.on("GetStructTree", async function ({ pageIndex }) {
981 const page = await pdfManager.getPage(pageIndex);
982 return pdfManager.ensure(page, "getStructTree");
983 });
984
985 handler.on("FontFallback", function ({ id }) {
986 return pdfManager.fontFallback(id, handler);
987 });
988
989 if (
990 typeof PDFJSDev === "undefined" ||
991 PDFJSDev.test("TESTING || INTERNAL_VIEWER")
992 ) {
993 handler.on("GetRawData", async function ({ ref, page }) {
994 let value = null;
995 if (page >= 1) {
996 value = (
997 await pdfManager.ensureCatalog("getPageDict", [page - 1])
998 )[1];
999 } else if (ref) {
1000 value =
1001 typeof ref === "string"
1002 ? Ref.fromString(ref)
1003 : Ref.get(ref.num, ref.gen);
1004 }
1005 return pdfManager.ensureDoc("toJSObject", [value]);
1006 });
1007 }
1008
1009 handler.on("Cleanup", function () {
1010 return pdfManager.cleanup(/* manuallyTriggered = */ true);
1011 });
1012
1013 handler.on("Terminate", async function () {
1014 terminated = true;
1015
1016 const waitOn = [];
1017 if (pdfManager) {
1018 pdfManager.terminate(new AbortException("Worker was terminated."));
1019
1020 const cleanupPromise = pdfManager.cleanup();
1021 waitOn.push(cleanupPromise);
1022
1023 pdfManager = null;
1024 } else {
1025 clearGlobalCaches();
1026 }
1027 cancelXHRs?.(new AbortException("Worker was terminated."));
1028
1029 for (const task of WorkerTasks) {
1030 waitOn.push(task.finished);
1031 task.terminate();
1032 }
1033
1034 await Promise.all(waitOn);
1035 // Notice that even if we destroying handler, resolved response promise
1036 // must be sent back.
1037 handler.destroy();
1038 handler = null;
1039 });
1040
1041 handler.on("Ready", function () {
1042 setupDoc(docParams);
1043 docParams = null; // we don't need docParams anymore -- saving memory.
1044 });
1045
1046 if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
1047 handler.on("GetXFADatasets", function () {
1048 return pdfManager.ensureDoc("xfaDatasets");
1049 });
1050 handler.on("GetStartXRefPos", function () {
1051 return pdfManager.ensureDoc("startXRef");
1052 });
1053 handler.on("GetAnnotArray", async function ({ pageIndex }) {
1054 const page = await pdfManager.getPage(pageIndex);
1055 return page.annotations.map(a => a.toString());
1056 });
1057 handler.on("GetWorkerCoverage", function () {
1058 return globalThis.__coverage__ ?? {};
1059 });
1060 }
1061
1062 return workerHandlerName;
1063 }
1064
1065 static initializeFromPort(port) {
1066 const handler = new MessageHandler("worker", "main", port);
1067 this.setup(handler, port);
1068 handler.send("ready", null);
1069 }
1070}
1071
1072export { WorkerMessageHandler, WorkerTask };