setupMessageHandler()

in src/display/api.js [2634:2934]


  setupMessageHandler() {
    const { messageHandler, loadingTask } = this;

    messageHandler.on("GetReader", (data, sink) => {
      assert(
        this._networkStream,
        "GetReader - no `IPDFStream` instance available."
      );
      this._fullReader = this._networkStream.getFullReader();
      this._fullReader.onProgress = evt => {
        this._lastProgress = {
          loaded: evt.loaded,
          total: evt.total,
        };
      };
      sink.onPull = () => {
        this._fullReader
          .read()
          .then(function ({ value, done }) {
            if (done) {
              sink.close();
              return;
            }
            assert(
              value instanceof ArrayBuffer,
              "GetReader - expected an ArrayBuffer."
            );
            // Enqueue data chunk into sink, and transfer it
            // to other side as `Transferable` object.
            sink.enqueue(new Uint8Array(value), 1, [value]);
          })
          .catch(reason => {
            sink.error(reason);
          });
      };

      sink.onCancel = reason => {
        this._fullReader.cancel(reason);

        sink.ready.catch(readyReason => {
          if (this.destroyed) {
            return; // Ignore any pending requests if the worker was terminated.
          }
          throw readyReason;
        });
      };
    });

    messageHandler.on("ReaderHeadersReady", async data => {
      await this._fullReader.headersReady;

      const { isStreamingSupported, isRangeSupported, contentLength } =
        this._fullReader;

      // If stream or range are disabled, it's our only way to report
      // loading progress.
      if (!isStreamingSupported || !isRangeSupported) {
        if (this._lastProgress) {
          loadingTask.onProgress?.(this._lastProgress);
        }
        this._fullReader.onProgress = evt => {
          loadingTask.onProgress?.({
            loaded: evt.loaded,
            total: evt.total,
          });
        };
      }

      return { isStreamingSupported, isRangeSupported, contentLength };
    });

    messageHandler.on("GetRangeReader", (data, sink) => {
      assert(
        this._networkStream,
        "GetRangeReader - no `IPDFStream` instance available."
      );
      const rangeReader = this._networkStream.getRangeReader(
        data.begin,
        data.end
      );

      // When streaming is enabled, it's possible that the data requested here
      // has already been fetched via the `_fullRequestReader` implementation.
      // However, given that the PDF data is loaded asynchronously on the
      // main-thread and then sent via `postMessage` to the worker-thread,
      // it may not have been available during parsing (hence the attempt to
      // use range requests here).
      //
      // To avoid wasting time and resources here, we'll thus *not* dispatch
      // range requests if the data was already loaded but has not been sent to
      // the worker-thread yet (which will happen via the `_fullRequestReader`).
      if (!rangeReader) {
        sink.close();
        return;
      }

      sink.onPull = () => {
        rangeReader
          .read()
          .then(function ({ value, done }) {
            if (done) {
              sink.close();
              return;
            }
            assert(
              value instanceof ArrayBuffer,
              "GetRangeReader - expected an ArrayBuffer."
            );
            sink.enqueue(new Uint8Array(value), 1, [value]);
          })
          .catch(reason => {
            sink.error(reason);
          });
      };

      sink.onCancel = reason => {
        rangeReader.cancel(reason);

        sink.ready.catch(readyReason => {
          if (this.destroyed) {
            return; // Ignore any pending requests if the worker was terminated.
          }
          throw readyReason;
        });
      };
    });

    messageHandler.on("GetDoc", ({ pdfInfo }) => {
      this._numPages = pdfInfo.numPages;
      this._htmlForXfa = pdfInfo.htmlForXfa;
      delete pdfInfo.htmlForXfa;
      loadingTask._capability.resolve(new PDFDocumentProxy(pdfInfo, this));
    });

    messageHandler.on("DocException", ex => {
      loadingTask._capability.reject(wrapReason(ex));
    });

    messageHandler.on("PasswordRequest", ex => {
      this.#passwordCapability = Promise.withResolvers();

      try {
        if (!loadingTask.onPassword) {
          throw wrapReason(ex);
        }

        const updatePassword = password => {
          if (password instanceof Error) {
            this.#passwordCapability.reject(password);
          } else {
            this.#passwordCapability.resolve({ password });
          }
        };
        loadingTask.onPassword(updatePassword, ex.code);
      } catch (err) {
        this.#passwordCapability.reject(err);
      }
      return this.#passwordCapability.promise;
    });

    messageHandler.on("DataLoaded", data => {
      // For consistency: Ensure that progress is always reported when the
      // entire PDF file has been loaded, regardless of how it was fetched.
      loadingTask.onProgress?.({
        loaded: data.length,
        total: data.length,
      });

      this.downloadInfoCapability.resolve(data);
    });

    messageHandler.on("StartRenderPage", data => {
      if (this.destroyed) {
        return; // Ignore any pending requests if the worker was terminated.
      }

      const page = this.#pageCache.get(data.pageIndex);
      page._startRenderPage(data.transparency, data.cacheKey);
    });

    messageHandler.on("commonobj", ([id, type, exportedData]) => {
      if (this.destroyed) {
        return null; // Ignore any pending requests if the worker was terminated.
      }

      if (this.commonObjs.has(id)) {
        return null;
      }

      switch (type) {
        case "Font":
          if ("error" in exportedData) {
            const exportedError = exportedData.error;
            warn(`Error during font loading: ${exportedError}`);
            this.commonObjs.resolve(id, exportedError);
            break;
          }

          const inspectFont =
            this._params.pdfBug && globalThis.FontInspector?.enabled
              ? (font, url) => globalThis.FontInspector.fontAdded(font, url)
              : null;
          const font = new FontFaceObject(exportedData, inspectFont);

          this.fontLoader
            .bind(font)
            .catch(() => messageHandler.sendWithPromise("FontFallback", { id }))
            .finally(() => {
              if (!font.fontExtraProperties && font.data) {
                // Immediately release the `font.data` property once the font
                // has been attached to the DOM, since it's no longer needed,
                // rather than waiting for a `PDFDocumentProxy.cleanup` call.
                // Since `font.data` could be very large, e.g. in some cases
                // multiple megabytes, this will help reduce memory usage.
                font.data = null;
              }
              this.commonObjs.resolve(id, font);
            });
          break;
        case "CopyLocalImage":
          const { imageRef } = exportedData;
          assert(imageRef, "The imageRef must be defined.");

          for (const pageProxy of this.#pageCache.values()) {
            for (const [, data] of pageProxy.objs) {
              if (data?.ref !== imageRef) {
                continue;
              }
              if (!data.dataLen) {
                return null;
              }
              this.commonObjs.resolve(id, structuredClone(data));
              return data.dataLen;
            }
          }
          break;
        case "FontPath":
        case "Image":
        case "Pattern":
          this.commonObjs.resolve(id, exportedData);
          break;
        default:
          throw new Error(`Got unknown common object type ${type}`);
      }

      return null;
    });

    messageHandler.on("obj", ([id, pageIndex, type, imageData]) => {
      if (this.destroyed) {
        // Ignore any pending requests if the worker was terminated.
        return;
      }

      const pageProxy = this.#pageCache.get(pageIndex);
      if (pageProxy.objs.has(id)) {
        return;
      }
      // Don't store data *after* cleanup has successfully run, see bug 1854145.
      if (pageProxy._intentStates.size === 0) {
        imageData?.bitmap?.close(); // Release any `ImageBitmap` data.
        return;
      }

      switch (type) {
        case "Image":
        case "Pattern":
          pageProxy.objs.resolve(id, imageData);
          break;
        default:
          throw new Error(`Got unknown object type ${type}`);
      }
    });

    messageHandler.on("DocProgress", data => {
      if (this.destroyed) {
        return; // Ignore any pending requests if the worker was terminated.
      }
      loadingTask.onProgress?.({
        loaded: data.loaded,
        total: data.total,
      });
    });

    messageHandler.on("FetchBinaryData", async data => {
      if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
        throw new Error("Not implemented: FetchBinaryData");
      }
      if (this.destroyed) {
        throw new Error("Worker was destroyed.");
      }
      const factory = this[data.type];

      if (!factory) {
        throw new Error(
          `${data.type} not initialized, see the \`useWorkerFetch\` parameter.`
        );
      }
      return factory.fetch(data);
    });
  }